StitchCraft
Machine embroidery for VectorCraft, in Rust. StitchCraft turns vector art into machine embroidery files — PES for Brother machines first, then DST and more — with running and bean stitch, satin columns, tatami, contour and meander fills, and more.
How it is built:
- It never crashes. Every input is untrusted, every loop is bounded, every problem is a coded, explained diagnostic.
- It is deterministic. The same design gives the same file on every computer, so bugs reproduce and previews match the sew-out.
- It is proven. Behaviour is specified as requirements, checked by a conformance suite on every change, and sewn on a real machine at every milestone.
- Its docs cannot drift. Reference pages and pictures are generated from the code and regenerated on every pull request.
Where to go
- Embroidering? Start with the user guide and embroidery basics.
- Building StitchCraft? Read the technical design, then the roadmap.
- Contributing? See contributing.
Status: StitchCraft sews the strokes of an SVG design and writes PES and DST files. A stroke is sewn in running stitch or manual stitch, with bean stitch where it is set. Milestone M4 is under way: the engine sews a satin column’s underlays and top stitches, and
stitch plansews the satins of an SVG from M8, when it reads Ink/Stitch’s settings. Fills come in M5. The roadmap has every step.
User guide
StitchCraft writes machine embroidery files from SVG files and VectorCraft documents. It also reads, draws and converts the files of other embroidery software. The guide follows the Diátaxis structure, with a section for each stitch type.
| Section | Use it to | Pages |
|---|---|---|
| Install | Build the stitch command | |
| Tutorials | Learn by doing, start to finish | Your first sew-out on a Brother, Turn an SVG into a PES file, A patch in VectorCraft |
| How-to guides | Solve one problem | Check a file, convert it, fit the hoop, report a bug |
| Stitch types | See what each stitch does and what its parameters change | Running stitch, lock stitches |
| Reference | Look up commands, parameters, messages and machines | |
| Explanation | Understand why embroidery behaves as it does | Embroidery basics |
StitchCraft makes the pictures and the command output in this guide when the docs are built. The reference pages are generated from its code, and CI fails when any of them is out of date.
Install StitchCraft
StitchCraft has no packaged releases yet. Build the stitch command from its source on Linux, macOS or
Windows. It takes a few minutes.
Build it
Install Rust with rustup, then get the source and build it:
git clone https://github.com/KaboomFox/stitchcraft
cd stitchcraft
cargo install --locked --path apps/stitchcraft-cli
The repository names the Rust version it is built with, and rustup fetches that version the first time.
cargo puts stitch in its bin folder, which rustup adds to your PATH.
Check it
stitch --version
It prints stitch and its version number.
To update StitchCraft, run git pull in the stitchcraft folder and the cargo install line again.
The VectorCraft plug-ins
The plug-ins say how each object of a VectorCraft drawing
sews, and show its stitches. There are 3, one for each kind of stitch, and 2 more that write PES and DST
files from VectorCraft’s File › Export, from VectorCraft 0.14. 3 filters change the drawing: one keeps a
design’s settings and machine with the document and one draws a test sheet, from VectorCraft 0.16, and
one makes lines into satin columns, from VectorCraft 0.17. All are built from the same source. In the
stitchcraft folder:
rustup target add wasm32-unknown-unknown
cargo build -p stitchcraft-vc-plugin --release --target wasm32-unknown-unknown --features fill
The build writes target/wasm32-unknown-unknown/release/stitchcraft_vc_plugin.wasm. Copy it into a
folder for plug-ins as stitchcraft-fill.wasm. Build again with --features stroke, --features satin,
--features pes, --features dst, --features settings, --features testsheet and
--features stroke-to-satin, and copy the file each time, as stitchcraft-stroke.wasm, stitchcraft-satin.wasm,
stitchcraft-pes.wasm, stitchcraft-dst.wasm, stitchcraft-settings.wasm, stitchcraft-testsheet.wasm
and stitchcraft-stroke-to-satin.wasm. VectorCraft 0.13 and earlier refuse the exporters.
In VectorCraft’s desktop app, choose that folder in Edit › Preferences › Plug-ins & Scratch Disks ›
Additional Plug-ins Folder, which is under Performance & Storage up to VectorCraft 0.14. VectorCraft
installs the plug-ins in it whenever it starts, all but Stroke to Satin. It asks for more work than a
filter usually may do, which VectorCraft lets you allow when you install it on its own: choose Object ›
Plug-ins › Install Plug-in… and pick stitchcraft-stroke-to-satin.wasm. VectorCraft asks you first, and
once you allow it, keeps it and installs it again when it starts. The effects are under Effect › Plug-ins, the formats in File ›
Export › Export As, and the filters under Object › Plug-ins. In VectorCraft on the web, open each
file with File › Open instead, once each time VectorCraft is opened. Embroider a VectorCraft
design shows how to use them.
Next
Sew your first test sheet, or turn an SVG into a PES file. The command line reference lists every command.
Your first sew-out on a Brother
You will sew StitchCraft’s orientation and scale sheet, TS-01, on a Brother machine such as the PE800, and check that its size and direction are exactly right. It takes about twenty minutes.
You need
- StitchCraft, installed.
- A Brother embroidery machine that reads PES files from a USB stick, and a hoop that sews at least 120 × 120 mm, such as the PE800’s 5 × 7 in hoop.
- Medium-weight woven cotton, medium tear-away stabilizer, a 75/11 embroidery needle, 40 wt polyester thread (black shows the lines best), white bobbin thread.
- A ruler or calipers with millimetres.
1. Write the file
$ stitch testsheet TS-01 -o TS-01.pes
TS-01 · Orientation and scale
file TS-01.pes (PES v1, 1587 bytes)
sha256 854cde9f533e3b6567afb570a0eb3d4547e7813f5649da97d27be104f8cd18c6
profile brother-pe800-5x7 (Brother PE800 with its 5 × 7 in hoop)
size 120.0 × 120.0 mm
stitches 274 stitches, 7 jumps, 6 trims, 0 colour changes, 0 stops
threads 1. Black (#000000), shown as Brother PEC 20 "Black"
after sewing, check:
- The F reads normally: not mirrored, not upside down, not turned.
- Each arm of the cross measures 100.0 ± 0.5 mm end to end, horizontally and vertically.
- The corner squares measure 10.0 mm on every side.
- Ticks are 10 mm apart; the long ticks mark the ends and the centre.
That is real output: this page is regenerated from StitchCraft itself, so the checksum is the one your
file will have. The checksum is how a sew-out report names exactly the bytes that were sewn. Every test
sheet is in the test sheets reference; stitch testsheet --list lists
them too.
2. Look at it first
$ stitch preview TS-01.pes -o TS-01.png
TS-01.png · 992 × 992 pixels
from TS-01.pes · PES (#PES0001) · "TS-01"
style realistic, 8 pixels per millimetre
The picture is drawn from the file itself, at the exact positions the machine will use, so this is what
you should see in the hoop. Add --style simple to see every stitch, needle hole, trim and jump instead:
the way to check a design before you sew it.
3. Load it on the machine
Copy TS-01.pes to a USB stick, plug it into the machine and select the design. The machine should show
it at 120 × 120 mm, with an upright “F” in the top-left quarter. If it does not list the file at all,
stop here and report that — it is the most important thing this sheet tests.
4. Hoop and sew
Hoop the fabric and stabilizer drum-tight, centre the design, attach the hoop, and sew. The machine sews the cross first, then the “F”, then the four corner squares, trimming between parts if it can.
5. Check
Measure the sew-out against the TS-01 checks.
stitch testsheet printed the same list when it wrote the file.
6. Tell us
File a Sew-out report on GitHub with photos of the front and back (a ruler in view), your measurements, and anything odd (machine testing).
Turn an SVG into a PES file
In this tutorial you save a small design as SVG and plan it for a Brother machine. Then you look at what it will sew, and sew it. The design is a red star inside a blue ring, 44 mm across.
You need
- StitchCraft, installed.
- A text editor, or a drawing program that saves SVG, such as Inkscape.
- To sew it: the machine and materials of your first sew-out.
1. Save the design
Save this text as badge.svg:
<svg xmlns="http://www.w3.org/2000/svg" width="50mm" height="50mm" viewBox="0 0 50 50">
<!-- A red star inside a blue ring, 44 mm across. The star sews first. -->
<polygon id="star" fill="none" stroke="#c8102e" stroke-width="0.4"
points="25,10 28.53,20.15 39.27,20.36 30.71,26.85 33.82,37.14
25,31 16.18,37.14 19.29,26.85 10.73,20.36 21.47,20.15"/>
<circle id="ring" cx="25" cy="25" r="22" fill="none" stroke="#1f4e9c" stroke-width="0.4"/>
</svg>
StitchCraft sews each shape’s stroke as a line of running stitch, in the stroke’s colour. It reads the
design’s size from width, height and viewBox, and here one unit is 1 mm. Shapes are sewn in the
order the file lists them, and the star comes first.
2. Plan it
$ stitch plan badge.svg -o badge.pes --preview badge.png
badge.svg
file badge.pes (PES v1, 1498 bytes)
sha256 5fea2f6734fadacdc1f3f1a7268bd964d3ab6c4bf1ba3b9f8fd6d10a4aae0d8c
profile brother-pe800-5x7 (Brother PE800 with its 5 × 7 in hoop)
size 44.0 × 44.0 mm
stitches 124 stitches, 2 jumps, 1 trim, 1 colour change, 0 stops
threads 1. unnamed (#c8102e), shown as Brother PEC 5 "Red"
2. unnamed (#1f4e9c), shown as Brother PEC 2 "Blue"
preview badge.png (384 × 384 pixels)
stitch plan writes the machine file and, with --preview, a picture of it. It plans for the
brother-pe800-5x7 profile, the PE800 with its 5 × 7 in hoop, unless --profile names another. Each colour is a thread, and the
PES file shows it as the nearest Brother palette colour that no other thread of the design has. The
machine stops between the red and the blue
for you to change the thread.
3. Look at it
The preview shows the embroidery as it will sew:
The simple style, from stitch preview badge.pes -o badge-simple.png --style simple, shows each needle
point:
The stitches are spread evenly along each straight edge, and every corner of the star has a needle point. The grey dashes are the frame’s move from the star to the ring, after the colour change.
4. Sew it
Load badge.pes and sew it as in your first sew-out. Thread
red first, and blue when the machine stops.
When StitchCraft warns
StitchCraft says what it changes or leaves out of a design. This one from StitchCraft’s tests has a fill of 2 rectangles and 2 lines that nearly touch:
$ stitch plan strokes.svg -o strokes.pes --preview strokes.png --report strokes.json
strokes.svg
file strokes.pes (PES v1, 2840 bytes)
sha256 d0ce3f4cb7843412b2ab0d4aeab07385fdf20b16a7e77cac3cefcf0b4b9193a3
profile brother-pe800-5x7 (Brother PE800 with its 5 × 7 in hoop)
size 60.0 × 45.0 mm
stitches 556 stitches, 6 jumps, 5 trims, 3 colour changes, 0 stops
threads 1. unnamed (#c00000), shown as Brother PEC 5 "Red"
2. unnamed (#0040c0), shown as Brother PEC 21 "Ultramarine"
3. unnamed (#c00000), shown as Brother PEC 5 "Red"
4. unnamed (#00a000), shown as Brother PEC 37 "Leaf Green"
preview strokes.png (512 × 392 pixels)
report strokes.json
warning SC-W0307 (svg:patch:fill): This fill's area falls into 2 parts that are sewn one after another, with a jump between each.
hint: To choose the order the parts are sewn in, break the fill apart into one element for each part.
info SC-I0504: A needle point less than the shortest stitch (0.3 mm) from a needle point next to it was left out.
SC-W0307names the fill, whose 2 rectangles are sewn one after the other, with a jump between them.SC-I0504says a needle point too close to the one before was left out, where the lines nearly touch.
stitch explain with a code tells you more, and diagnostic codes lists
them all. --report writes the file’s counts, size and SHA-256, the threads and the messages as JSON, for
scripts.
Next
Stitch types shows what each stitch type does and the parameters that change it.
A patch in VectorCraft
In this tutorial you draw a round patch in VectorCraft and choose how each part of it sews, with the StitchCraft plug-ins showing the stitches as you go. Then you write the machine file and sew it. The patch is 30 mm across. It has a blue disc of fill, with a white star of running stitch on it, inside a gold satin border.
You need
- VectorCraft, in a version the compatibility page lists, with the StitchCraft plug-ins installed.
- StitchCraft’s command line, installed.
- To sew it: the machine and materials of your first sew-out.
Sizes here are in points, VectorCraft’s own unit: 72 points make an inch, and about 2.8 a millimetre. A
field reads a size typed with its unit, such as 80 pt, whichever unit it shows.
1. Draw the disc and fill it
Choose File › New, and make the document 108 pt wide and 108 pt high. Choose the Ellipse tool and click near the artboard’s top left corner. In the Ellipse dialog, make the circle 80 pt wide and 80 pt high. Give it a blue fill and no stroke. A fill sews in the colour of its object’s fill.
With the circle selected, choose Effect › Plug-ins › StitchCraft Fill:
The dialog holds Ink/Stitch’s settings for a fill in sections, each with its label and unit. Rest the pointer on a label for its help, and the button after a field opens its page. Set Angle to 45, and choose OK. If the dialog is taller than your screen, see below.
The whole StitchCraft Fill dialog
The circle now shows the rows the fill sews, at 45 degrees. VectorCraft lets a plug-in do only so much
work for a preview, and a larger fill, such as a disc more than about 30 mm across, shows its outline
instead of its rows. stitch export --preview shows every stitch, whatever the size.
2. Draw the star
Choose the Star tool and click in the middle of the disc. Make Radius 1 26 pt, Radius 2 11 pt and Points 5. Give the star no fill and a white stroke. A line sews in the colour of its object’s stroke. Choose Effect › Plug-ins › StitchCraft Stroke, and OK. Its settings sew the line in running stitch, 2.5 mm a stitch.
The whole StitchCraft Stroke dialog
3. Draw the border
A satin column is drawn as its 2 edges, its rails, joined into one compound path (satin columns in VectorCraft). Lock the disc and the star first, and Select All then takes only the rails: choose Select › All, then Object › Lock › Selection.
With the Ellipse tool, draw 2 more circles, one 84 pt across and one 70 pt across. Choose Select › All, then Object › Align › Horizontal Align Center and Object › Align › Vertical Align Center. Join the circles with Object › Compound Path › Make. Give the compound path no fill and a gold stroke, and choose Effect › Plug-ins › StitchCraft Satin, then OK.
The whole StitchCraft Satin dialog
4. Centre it
Choose Object › Unlock All and Select › All. Align everything again with Object › Align › Horizontal Align Center, then Vertical Align Center. The patch is done, and VectorCraft shows the stitches the plug-ins preview:
The objects sew in the order they were drawn. The disc sews first and the star next, and the border sews last, over the edges of both.
5. Write the machine file
Save the document as round-patch.vectorcraft, and run stitch export on it:
$ stitch export round-patch.vectorcraft -o round-patch.pes --preview round-patch.png
round-patch.vectorcraft
file round-patch.pes (PES v1, 4751 bytes)
sha256 c06c3defb172d4e8e33096eaa48fbc7a898c25dfe4f83cc8b555c192fe792363
profile brother-pe800-5x7 (Brother PE800 with its 5 × 7 in hoop)
size 29.6 × 29.6 mm
stitches 1633 stitches, 3 jumps, 2 trims, 2 colour changes, 0 stops
threads 1. unnamed (#1f4e9c), shown as Brother PEC 2 "Blue"
2. unnamed (#ffffff), shown as Brother PEC 29 "White"
3. unnamed (#d9a400), shown as Brother PEC 11 "Deep Gold"
preview round-patch.png (269 × 269 pixels)
round-patch.png shows what the machine will sew:
6. Sew it
Load round-patch.pes and sew it as in your first sew-out.
Thread blue first, white when the machine stops, and gold when it stops again.
Dialogs taller than the screen
From VectorCraft 0.14 the rows of a dialog taller than the window scroll, and a section’s heading folds its rows away. The satin’s dialog is taller than most screens.
VectorCraft 0.12 and 0.13 don’t scroll a plug-in’s dialog, and list every setting under its Ink/Stitch name. The fill’s and the satin’s dialogs are taller than most screens there, and the stroke’s is taller than the window VectorCraft opens with. Their first rows, or the OK button, can be out of sight. Make VectorCraft’s window as tall as the screen allows, and click a field you can see. Tab and Shift+Tab move to the others, and Enter applies the dialog.
Next
Embroider a VectorCraft design shows how to leave objects
out and mark where a shape starts and ends. It also explains what stitch export says.
How-to guides
Each guide solves one problem:
- Check a machine file before sewing with
stitch inspectandstitch preview - Convert between machine formats with
stitch convert - Fit a design to the hoop when it is larger than the hoop or the comfort zone
- Embroider a VectorCraft design with the plug-ins and
stitch export - Make satin columns and keep presets in VectorCraft with VectorCraft’s own commands and Graphic Styles
- Report a bug with a bundle that reproduces it (
stitch bug-report)
Planned, each with the milestone that brings its feature:
| Guide | Milestone |
|---|---|
| Make lettering-style satin borders | M4 |
| Stop a fill from puckering (density, underlay, compensation) | M5 |
| Bring an Ink/Stitch design into VectorCraft | M8 |
Check a machine file before sewing
StitchCraft reads PES, PEC and DST files from any software. It can describe a file and draw what the
machine will sew, before you spend thread on it. The examples use test sheet TS-02, written with
stitch testsheet TS-02 -o TS-02.pes.
Describe the file
$ stitch inspect TS-02.pes --profile brother-pe800-5x7
TS-02.pes · PES (#PES0001) · "TS-02"
sha256 7c9e1a79353ef17172d1125172635b7f37bce80a1328c936c5eafa436bfff90b
size 120.0 × 70.0 mm
stitches 93 stitches, 17 jumps, 16 trims, 2 colour changes, 1 stop
extent x -60.0 to 60.0 mm, y -35.0 to 35.0 mm from where the machine starts
lengths stitches from 2.50 to 2.50 mm
threads 1. Red (#ed171f), shown as Brother PEC 5 "Red"
2. Blue (#0a55a3), shown as Brother PEC 2 "Blue"
3. Emerald Green (#00673e), shown as Brother PEC 54 "Emerald Green"
4. Emerald Green (#00673e), shown as Brother PEC 54 "Emerald Green" — a stop: keep the same thread
profile brother-pe800-5x7 (Brother PE800 with its 5 × 7 in hoop)
0 stitches longer than 12 mm, 0 shorter than 0.3 mm
- size is the width and height of the stitching.
- stitches counts the needle points, and the jumps, trims, colour changes and stops between them.
- extent gives the lowest and highest x and y of the stitching, from where the machine starts.
- lengths gives the shortest and the longest stitch, to a hundredth of a millimetre, and how many stitches do not move at all, when a file has any.
- threads lists the colour blocks in sewing order. Each colour is stored as a Brother palette colour in a PES file, which the line names. After a stop you sew on with the same thread.
- profile appears with
--profile, with the number of stitches longer than the machine’s longest stitch and shorter than its shortest.stitch profileslists the machines. A file does not say which stitches lock the thread, and StitchCraft’s lock stitches are as short as 0.2 mm on purpose. The line also counts the short stitches 0.2 mm or longer, which may be locks.
Look at it
$ stitch preview TS-02.pes -o TS-02.png --style simple
TS-02.png · 992 × 592 pixels
from TS-02.pes · PES (#PES0001) · "TS-02"
style simple, 8 pixels per millimetre
The simple style draws each stitch as a thin line with a dot at each needle point. A red cross marks a trim and a blue square a stop. Grey dashes show where the frame travels after a cut, and dashes in the thread’s colour show a jump thread left on the fabric.
Without --style simple, the picture shows the finished embroidery, with loose jump threads where they
will be. The rendering design has the whole legend.
What to look for
- A design larger than the hoop, or than the machine’s comfort zone: see fit the hoop.
- Jump threads where you expected trims. Each one has to be cut by hand after sewing.
- Thread colours. The machine shows each block in its palette colour, and it sews whichever thread you load.
Convert between machine formats
stitch convert reads a PES, PEC or DST file and writes it as PES or DST. The output file’s extension
picks the format, or --format names it. Each format records some things the other cannot, and the
command says what changed.
PES to DST
DST is the format most commercial machines read. Here test sheet TS-02 is converted and the new file read back:
$ stitch convert TS-02.pes -o TS-02.dst
TS-02.pes · PES (#PES0001) → DST
file TS-02.dst (DST, 1085 bytes)
sha256 88d453b2512bb535f9dd2205cbb0be753a5eb9b1700ae9abf2e88062fabff927
size 120.0 × 70.0 mm
stitches 93 stitches, 46 jumps, 14 trims, 3 colour changes, 0 stops
threads 1. Red (#ed171f)
2. Blue (#0a55a3)
3. Emerald Green (#00673e)
4. Emerald Green (#00673e) — a stop: keep the same thread
$ stitch inspect TS-02.dst
TS-02.dst · DST · "TS-02"
sha256 88d453b2512bb535f9dd2205cbb0be753a5eb9b1700ae9abf2e88062fabff927
size 120.0 × 70.0 mm
stitches 93 stitches, 46 jumps, 14 trims, 3 colour changes, 0 stops
extent x -60.0 to 60.0 mm, y -35.0 to 35.0 mm from where the machine starts
lengths stitches from 2.50 to 2.50 mm
threads 1. thread 1 (#000000)
2. thread 2 (#000000)
3. thread 3 (#000000)
4. thread 4 (#000000)
(DST stores no colours: each block is a pause for the next thread)
- Colours. A DST file records where the colour changes are, without the colours. The machine pauses at each colour change, and you load the next thread yourself. Note the thread list before you convert.
- Stops. DST has one pause command, for colour changes and stops alike. The stop in TS-02 reads back as a third colour change.
- Trims. DST has no trim command either. StitchCraft writes a trim as 3 short jumps that end where they started, and a machine set to cut at 3 jumps in a row cuts there.
- Long jumps. A DST record moves the frame at most 12.1 mm, and a longer jump takes several records.
A jump longer than 24.2 mm takes 3 or more, and a machine set to 3 cuts the thread there too.
SC-I0605counts these extra cuts. After a tie-off they save you cutting the jump thread by hand. Without one, the end of the stitching can pull out. - Jumps in a PES file. StitchCraft flags every jump in a PES file as a trim, as Ink/Stitch does, so
TS-02 reads back with a trim before each of them, and its DST file cuts there. Only a PES file with
plain jumps, as some other programs write, gets the extra cuts that
SC-I0605counts.
DST to PES
A PES file needs a colour for each thread, and a DST file has none to give. Each thread becomes a black
placeholder, and SC-W0604 says so. The PES file gives each a Brother colour of its own, the nearest to
black still free, from Black to Dark Gray, Dark Brown and on. The machine shows a new colour at each
thread change, and pauses there as the DST file did. The colours it shows mean nothing. Load the threads
the design’s author gives.
The machine formats design describes how each format is written and read.
Fit a design to the hoop
Each machine profile is a machine with one of its hoops, and its hoop size is
the largest area the machine sews in that hoop. The reference Brother PE800 has a profile for each of its
3 hoops. The 5 × 7 in hoop sews 130 × 180 mm, the 4 × 4 in hoop 100 × 100 mm and the small hoop
20 × 60 mm. Commands plan for the 5 × 7 in hoop unless --profile names another.
Larger than the hoop
Here a border is 210 mm long, and stitch plan ends with an error instead of writing the file:
$ stitch plan wide.svg -o wide.pes
error SC-E0701: The design is 210.0 × 0.0 mm, but the Brother PE800 with its 5 × 7 in hoop sews at most 130 × 180 mm.
hint: Scale the design down, or split it into parts sewn in separate hoopings.
more: stitch explain SC-E0701
nothing was written
(exit status 1)
The exit status is 1. Scale the design down, or split it into parts that are sewn in separate hoopings. When turning the design by 90° makes it fit, the error offers that instead.
A smaller hoop
Name the hoop’s profile, and StitchCraft checks the design against that hoop. Test sheet TS-10B fills the 5 × 7 in hoop, so the 4 × 4 in hoop is too small for it:
$ stitch testsheet TS-10B --profile brother-pe800-4x4 -o TS-10B.pes
error SC-E0701: The design is 130.0 × 180.0 mm, but the Brother PE800 with its 4 × 4 in hoop sews at most 100 × 100 mm.
hint: Scale the design down, or split it into parts sewn in separate hoopings.
more: stitch explain SC-E0701
nothing was written
(exit status 1)
stitch profiles lists every profile with its hoop.
Larger than the comfort zone
A profile may also have a comfort zone: the part of the hoop where the fabric is taut enough for the
stitches to land where they should. A design larger than it is still written, with a warning.
stitch explain says more about any code:
$ stitch explain SC-W0702
SC-W0702: Design is larger than the comfort zone
The design fits the hoop but is larger than the profile's comfort zone: the area where this
machine and hoop sew most accurately. Near the hoop's edge fabric is held less firmly, so large
designs pucker and shift more.
The file was written. Rotate the design if the message says that brings it inside the comfort
zone, use a firmer stabilizer, or scale the design down.
The built-in profiles have no comfort zone yet. A sew-out that shows a hoop’s edges sewing badly would give its profile one.
Embroider a VectorCraft design
Draw the design in VectorCraft, and say how each object sews with the StitchCraft plug-ins, which show
its stitches as you work. Then write the machine file with VectorCraft’s File › Export › Export As,
from VectorCraft 0.14, or with stitch export from the saved document. Both write the same file.
Choose each object’s stitches
With the plug-ins installed, select an object and choose Effect › Plug-ins › StitchCraft Fill, StitchCraft Stroke or StitchCraft Satin. The dialog holds Ink/Stitch’s settings for that kind of stitch, and the object shows its stitches in place of its art. The thread is the colour of the object’s fill for a fill, and of its stroke for a stroke or a satin column.
From VectorCraft 0.14 each setting has a label and its unit, in the sections of the parameter reference. Rest the pointer on a label for its help, or press the button after the field for its page in the reference. A setting shows only when it applies: a satin’s staggers show for staggered splits. VectorCraft before 0.14 lists every setting, each under its Ink/Stitch name.
An object without a StitchCraft effect sews as Ink/Stitch sews a shape of an SVG file. Its fills are
filled, and its strokes are sewn as running stitch. With stitch export --auto, StitchCraft chooses its
stitches by width instead, as for a picture traced into shapes.
- A line up to 1 mm wide runs, or up to the narrowest satin stroke the document keeps (below). From 0.5 mm wide it is a triple run, each stitch sewn 3 times.
- A line up to 7 mm wide is a satin column as wide as drawn, its round or square caps included.
- A wider line, or a fill, is a tatami fill. A part of a fill no wider than the narrowest satin stroke anywhere runs along its middle, as so narrow a line does.
Objects are sewn in the order they are drawn, the bottom one first. Satin columns and presets in VectorCraft shows how to draw a column with 2 rails, and how to keep settings in graphic styles.
Leave out what isn’t to be sewn
Name an object or a layer stitch:ignore, by double-clicking its name in the Layers panel, and it is left
out with everything in it. Placement lines, notes and pictures to trace stay in the drawing this way. A
note can follow the name after a space, as in stitch:ignore placement line. Upper and lower case are
read alike. A hidden object is left out too.
VectorCraft’s preview can’t see names. An object named stitch:ignore that a StitchCraft effect sews, its
own or its group’s, still shows its stitches there, and stitch export leaves it out.
Write the machine file
With the exporters installed, choose File › Export › Export As, and PES embroidery (StitchCraft) or DST embroidery (StitchCraft) as the format. Once the file is picked, a dialog shows 2 options:
- Digitize automatically, which chooses the stitches of the objects without a StitchCraft effect by
their width, as
stitch export --autodoes. - Machine, the machine and hoop the design is planned for:
document, the one the document keeps (below), orbrother-pe800-5x7when it keeps none, unless another is chosen.stitch profileslists them.
The design takes the file’s name, which machines show. What stitch export would say about the design
shows with the export’s warnings, one line for each message. When one of them is an error, nothing is
written, and VectorCraft says the first. VectorCraft lets an export run for a while, and stops one that
needs more: stitch export writes it.
Or save the document, and run stitch export on it. This patch has a fill, a satin border, a curve of
running stitch and 2 squares in a group that a graphic style’s fill effect sews. Its placement line is
named stitch:ignore placement line:
$ stitch export patch.vectorcraft -o patch.pes --preview patch.png
patch.vectorcraft
file patch.pes (PES v1, 6656 bytes)
sha256 6236ce2cb1bc9db739830a38a457cff67689195f86e1200e79b846240d943457
profile brother-pe800-5x7 (Brother PE800 with its 5 × 7 in hoop)
size 50.0 × 54.0 mm
stitches 2466 stitches, 5 jumps, 4 trims, 3 colour changes, 0 stops
threads 1. unnamed (#bf0000), shown as Brother PEC 5 "Red"
2. unnamed (#0033b3), shown as Brother PEC 21 "Ultramarine"
3. unnamed (#008033), shown as Brother PEC 56 "Moss Green"
4. unnamed (#e6b31a), shown as Brother PEC 11 "Deep Gold"
preview patch.png (432 × 464 pixels)
info SC-I0811 (vc:10): The object "stitch:ignore placement line" is named stitch:ignore, and is left out with everything in it.
hint: Rename it to stitch it.
warning SC-W0807 (vc:4): The object "stitch:start" marks where the shape grouped with it starts, and is not stitched. StitchCraft doesn't yet start a shape at a marker: it chooses where.
warning SC-W0807 (vc:7): A group "Dots" has fills or strokes of its own, which VectorCraft paints what it holds with. They are not stitched as shapes of their own, and give their thread and stroke to the StitchCraft effects they paint.
hint: Make objects of them with Object › Expand Appearance.
stitch export writes the file for the machine --profile names, or else the one the document keeps, or
else brother-pe800-5x7, and --preview adds a picture. It is stitch plan by another name, which
reads an SVG file the same way and takes the same options.
StitchCraft reads documents of up to 64 MiB. Pictures embedded to trace from can make one larger: place them with Link checked in VectorCraft’s Place dialog, and VectorCraft saves a small preview of each in the document instead.
Keep the design’s settings and machine with it
From VectorCraft 0.16, a document can keep the machine it is for, and the 3 settings Ink/Stitch keeps with a design for all of its objects. With the plug-ins installed, choose Object › Plug-ins › StitchCraft Design Settings, set them, and press OK:
- Machine, the machine and hoop the design is planned for,
brother-pe800-5x7by default. - Shortest jump, Shortest stitch and Narrowest satin stroke, in millimetres. Rest the pointer on a label for what each does.
The document saves them. The exporters and stitch export plan the design for the machine it keeps,
unless the export’s Machine option or --profile names another. They use its settings as they use
an SVG file’s design settings. Whatever is selected, the filter leaves every object as it is.
VectorCraft opens the dialog at the defaults each time, not at what the document keeps, so set all 4 when
you run it again. A live effect isn’t given what the document keeps. The stitches shown as you work are
planned for brother-pe800-5x7 with the default settings, and a longer shortest stitch shows only in the
machine file and in stitch export --preview. A setting that StitchCraft can’t read keeps its default,
and stitch export says so (SC-W0812).
Draw a test sheet
The test sheets drawn as designs, TS-02B to TS-09, can start from VectorCraft 0.16 and later. With the plug-ins installed, choose Object › Plug-ins › StitchCraft Test Sheet, pick the sheet, and press OK. Rest the pointer on Sheet for what each one tests. The sheet is drawn in the middle of the artboard, each shape with its StitchCraft effect and settings, and shows its stitches. Write it as any design, with File › Export › Export As. Selected objects stay as they are, and the sheet goes above them, in their group or layer.
The sheet sews as stitch testsheet writes it, moved. A fill’s rows lie where the document’s origin puts
them, so they may fall a little elsewhere across a shape, and look the same. The threads keep their
colours but not their names in the Brother palette, and the machine file is the same. StitchCraft Stroke,
Satin and Fill must be installed: VectorCraft gives a new object only the effects of installed plug-ins.
TS-01, TS-02 and TS-10A to TS-10C are drawn stitch by stitch, and come from stitch testsheet alone.
Read what it says
Each line under the summary names an object by its id in the document, such as vc:10, and says what
was done with it. In the patch, the placement line is left out, as its name asks (SC-I0811). The start
marker isn’t applied yet (below). The group’s own gold fill, which the graphic style put on it, isn’t
sewn as a shape of its own, and gives the squares their thread. Other messages name what isn’t sewn as it
is drawn, with the VectorCraft command that makes it sewable, such as Type › Create Outlines for text
(SC-W0807). The diagnostic codes page explains each code, as
stitch explain SC-W0807 does.
Start and end markers
A small path named stitch:start or stitch:end, grouped with a fill or a satin column, marks where the
fill or the column is to start or end, as Ink/Stitch’s starting and ending positions do. A marker is
never sewn, and a group or a layer is never a marker. StitchCraft doesn’t start or end a shape at one
yet, and chooses where, as the patch’s start marker shows.
When it sews wrong
stitch bug-report design.vectorcraft --says "what is wrong" writes a bundle that includes the document.
Report a bug says what to do with it.
Satin columns and presets in VectorCraft
The StitchCraft plug-ins add their effects to VectorCraft’s Effect › Plug-ins menu, and VectorCraft’s own commands do the rest of what a design needs.
Make a satin column VectorCraft can show
A satin column drawn as one path sews as wide as its stroke. From VectorCraft 0.14 StitchCraft Satin shows
its stitches as wide as the object’s stroke. Before 0.14 VectorCraft gives an effect the path but not its
stroke, and StitchCraft Satin leaves such a path as it is and says why (SC-I0810). It does so too for an
object without a stroke of its own, such as a path a group’s stroke paints. The file still sews it. To see
its stitches in VectorCraft then, draw the column with 2 rails:
- Draw the column’s 2 edges as 2 paths.
- Select both and choose Object › Compound Path › Make (Cmd+8).
- With the compound path selected, choose Effect › Plug-ins › StitchCraft Satin.
Apply the effect after joining the rails. VectorCraft 0.10.0 and later keep the appearance of the path at
the back, and earlier versions that of the path in front, which may not be the one with the effect. From
0.10.0, VectorCraft may also reverse the front path, and always does for 2 straight rails. The column
turns it back (reverse_rails, automatic). A reversed ring also starts somewhere else, so of 2 rings the
second starts at its node nearest the first’s start. The stitches are the same whatever node the rails
were drawn from and whichever way they run.
Add a rung
Rungs set the angle of the stitches along the column. With the Pen tool, add a line across both rails where the stitches should run. Select it with the column, and choose Object › Compound Path › Make. From VectorCraft 0.10.0 the column, behind the line, keeps its effect. Before 0.10.0 the compound path takes the line’s appearance: apply StitchCraft Satin to it again.
Make a line into a satin column
From VectorCraft 0.17, Object › Plug-ins › StitchCraft Stroke to Satin makes each selected line into a satin column drawn with its 2 rails and rungs, as Ink/Stitch’s Stroke to Satin does. The line is the column’s centre line, and its stroke gives the column’s width: the topmost stroke that paints, as StitchCraft Satin follows. The rails and rungs are those StitchCraft makes of a satin column drawn as one path when it sews it. So the column sews as the line did with the satin effect, and its rails and rungs can then be edited.
- Give each line a stroke as wide as the column.
- Select the lines, and choose Object › Plug-ins › StitchCraft Stroke to Satin.
Each line becomes a compound path of its rails and rungs, without a fill, its stroke the line’s colour,
one pixel wide. It has StitchCraft Satin with the line’s satin settings, or the defaults, in the place of
the line’s StitchCraft effects, and keeps the line’s other effects. A path of several lines makes a
column of each. A satin column already drawn with rails is left as it is. When a line can’t be made into a
column, nothing changes, and VectorCraft says why (SC-E0814), for a path without a stroke, or a line
whose column would sew less than the line: one too short or tight for its width, or one that crosses
itself.
Making the rails of a curved line takes more work than a filter usually may do. So Stroke to Satin asks
for more when it is installed, and VectorCraft asks you first, then runs it in the background with
Cancel. StitchCraft Satin must be installed too. VectorCraft 0.15 and 0.16 run it with the time any
filter gets, and stop it on most curved lines. Before 0.15 it asks for a newer VectorCraft (SC-E0813).
Keep settings as a preset
A graphic style keeps an appearance, StitchCraft’s effects included:
- Select an object whose StitchCraft effect is set as you like it.
- Choose Window › Graphic Styles › New Graphic Style.
- Select other objects and click the style in the Graphic Styles panel.
Each object gets a copy of the effect, with the style’s parameters, and sews with them. A style applied
to a group or a layer puts its effects on the group, and every path inside it sews with them. The style’s
fills and strokes go on the group too, and VectorCraft paints them over the group’s contents. Each path
inside sews in the colour on top, the style’s fill for a fill effect and the style’s stroke for a satin
column, which follows its width. Where the style has no paint of that kind, or lets the path show through
it, the path’s own paint of that kind gives the colour, and with none on either, the topmost paint of the
other kind does. A path without paint under a style without any is invisible and isn’t sewn. To show the
paths’ own colours over the style’s, drag the Contents row above the style’s fills and strokes in the
Appearance panel. Keep StitchCraft’s effects at one level, though. A path that has its own StitchCraft
effect inside such a group, or sits inside 2 such groups, is drawn as one effect run on the stitches of
the other. It sews as the nearest effect says, and the command line warns about it (SC-W0807).
Alt-clicking a style adds its effects to the object’s own, and an object with 2 fill effects sews twice.
Give each StitchCraft effect an object of its own. An object with 2 sews each on its shape, but
VectorCraft draws the second on the stitches of the first, so the preview isn’t what is sewn, and the
command line warns about it (SC-W0807). For a fill with an outline, copy the shape, choose Edit ›
Paste in Front, and give the shape behind a fill effect and the copy in front a stroke effect.
Turn a preview into paths
Object › Expand Appearance turns what an effect shows into ordinary objects, with strokes outlined. Those objects are the shapes the preview draws, and they sew differently from the effect. Expand a copy for a picture of the stitches, and keep the original to sew.
Draw a test sheet
Embroider a VectorCraft design shows how to draw a
test sheet into a document from VectorCraft 0.16, and stitch testsheet
writes every sheet.
Report a bug
When StitchCraft gets a design wrong, a maintainer needs one file to see what you saw: a bug-report bundle. Write one when:
- the stitches differ from the design
- a message is unclear
- StitchCraft crashes
Write the bundle
Run stitch bug-report with the design, an SVG file or a VectorCraft document, and the profile and
format you sew with, and --auto if you planned it with --auto:
stitch bug-report design.svg --profile brother-pe800-4x4 --says "the zigzag's corners are rounded"
It plans the design exactly as stitch plan does and writes design.bug-report.json next to it (-o
names another file). The bundle holds:
- your design file, whole, or a VectorCraft document’s JSON without the pictures after it, and the design name the machine file’s label holds;
- the profile, the format (
--format pes|dst, the profile’s own by default) and the version of StitchCraft, and whether the shapes without settings were digitized automatically (--auto); - a digest of the plan’s stitches, and the machine file’s size and digest;
- the messages, or the crash’s message;
- what you wrote with
--says.
It holds your design. Share it only where you are happy for the design to be seen.
Report it
Open an issue at https://github.com/KaboomFox/stitchcraft/issues. Say what you expected and what you
got, and attach the bundle. GitHub takes .json files up to 25 MB. Zip a larger one first.
When StitchCraft finds the bug itself
StitchCraft checks each plan before writing a machine file. If a check fails (SC-E0009) or StitchCraft
crashes, stitch plan (or stitch export) writes the bundle for you in place of the machine file. The
bundle goes next to the file you named, as design.bug-report.json for design.pes. The command says
where it is and ends with exit status 4. Attach that bundle to an issue.
For maintainers: replay a bundle
stitch bug-report --replay design.bug-report.json
This plans the bundled design again with the bundled profile and format, and compares everything the bundle recorded:
design.bug-report.json: written by StitchCraft 0.1.0 on windows x86_64, replayed by 0.1.0 on linux x86_64
design same: design.svg (sha256 …)
plan same: 118 stitches, 4 jumps, 0 trims, 2 colour changes, 0 stops (sha256 …)
machine file same: 1725 bytes (sha256 …)
diagnostics same: 2
reproduced
StitchCraft gives the same plan, bit for bit, for the same design, profile, format and version on every
platform, so with the version that wrote the bundle, the replay reproduces it. With another version, what
differs is listed under then: and now:: that is how to tell whether a bug is fixed. The exit status is
1 when the bundle does not reproduce. The design is in the bundle’s design.svg field, ready to save and
open, or in design.svg_hex as hexadecimal when the file is not UTF-8 text, as a Latin-1 SVG is not. Of a
VectorCraft document, the bundle keeps the part StitchCraft reads, in design.vectorcraft: its JSON,
without the images and other data VectorCraft keeps after it. Compressed documents are kept whole, in
design.vectorcraft_hex. A bundle also names the commit StitchCraft was built from, when it was built from
a git checkout, which stitch --version shows too: until the first release, every build says the same
version.
Anyone can attach a bundle to an issue. The replay writes each character from it that a terminal would act
on as an escape, such as \u{1b} for the one that starts a terminal’s control sequences.
Stitch types
StitchCraft turns each element of a design into stitches of one type. In an SVG file an element is the stroke or the fill of a shape, and each sews in its own thread colour.
| Stitch type | Sews | Available |
|---|---|---|
| Running stitch | strokes: outlines, details, lettering at small sizes | now |
| Bean stitch | strokes, each stitch sewn 3 times or more | now |
| Manual stitch | strokes whose nodes are the needle points | now |
| Zigzag stroke | strokes as wide as their line: light borders, couched looks | now |
| Satin column | wide strokes and borders | now |
| E-stitch | satin columns as appliqué edges: a spine along one rail, with spikes to the other | now |
| S-stitch | satin columns sewn one way at a time: a stitch across at each step, joined along the edges | now |
| Zigzag satin | satin columns sewn as a zigzag, every stitch slanting from one edge to the other | now |
| Tatami fill | areas, in rows of running stitches | now |
Every element also gets lock stitches where its stitching starts and ends, and a trim or a
stop after it when it asks for one. An element of a type that is not sewn yet is left out, and SC-W0011
names it. The roadmap says what each milestone adds.
Setting parameters
A parameter belongs to one element. Ink/Stitch stores parameters in the SVG file as attributes, such as
inkstitch:running_stitch_length_mm="1.5", and StitchCraft’s parameters have the same names and
defaults.
stitch plan does not read these attributes yet, and every element sews with the defaults. SC-W0802
says so when a file has them. The SVG reader reads them from milestone M8, and the VectorCraft plug-in
sets them from M6. Ink/Stitch’s trim and stop commands are read today.
The pictures on these pages set parameters on the engine directly, to show what each one does.
Running stitch
A running stitch is a line of single stitches along a path. StitchCraft sews each stroke of an SVG design
with it, unless the stroke’s stroke_method names another stitch. Use it for outlines, fine details and
lettering at small sizes.
The pictures on this page are drawn by StitchCraft’s own engine and renderer, from the designs in
docs/fixtures/. Each dot is a needle point, and orange rings mark the lock stitches at each end. Each
parameter is set per element, and setting parameters says what
stitch plan reads today.
Stitch length
running_stitch_length_mm sets how long each stitch is, 2.5 mm by default. Between 2 corners the stitches
are spread evenly, and none is longer than the stitch length.
1.5 mm
2.5 mm
4 mm
Short stitches follow a curve closely and make more needle holes. Long stitches are quicker to sew and
show more of each stitch. With a list of lengths, such as 2.5 1, the stitches take the lengths one after another along the
path.
Corners and curves
Where the path turns by more than 30°, StitchCraft puts a needle point on the corner, and the line keeps
its shape. Along a curve, a stitch strays from the curve by at most running_stitch_tolerance_mm, 0.2 mm
by default. With a long stitch length the tolerance shows. Here a half disc is sewn in stitches up to 6 mm
long. Both corners of its straight side get a needle point in every panel. The tolerance sets how closely
the stitches follow the curve.
| 0.1 mm | 0.5 mm | 2 mm |
|---|---|---|
At 2 mm the curve is sewn in 3 straight stitches, and the half disc comes out as a trapezoid. At 0.1 mm StitchCraft sews the curve in more stitches, still all of one length, until each keeps within 0.1 mm of it, and the curve comes out round.
Bean stitch and repeats
bean_stitch_repeats sews each stitch more than once, for a bolder line. At 1 each stitch is sewn 3
times, there, back and there again, and at 2 it is sewn 5 times. With a list such as 1 0, the stitches
alternate between bold and plain.
repeats sews the whole path more than once. At 2 the needle goes there and back, and at 3 there, back
and there again. With an even number the stitching ends where it started.
Random stitch length
Rows of running stitch close together line their needle points up across the rows, and the eye sees
stripes. enable_random_stitch_length varies each stitch’s length by up to
random_stitch_length_jitter_percent, 10 % by default, and the stripes go.
even
random
The stitching still ends on each corner. random_seed sets where the random lengths start. The same
element and seed give the same stitches every time, on every computer, and elements alike given the same
seed vary alike. Left empty, each element varies its own way.
Manual stitch
With stroke_method set to manual_stitch, each node of the path is a needle point and nothing else is.
A curve gives only its end node. Use it to place stitches by hand, or to sew a stitch file that was
traced as a path.
| running stitch | manual stitch |
|---|---|
max_stitch_length_mm splits a hand-placed stitch that is longer into equal parts. Bean stitch applies
to manual stitch, and repeats do not.
The shortest stitch
A stitch shorter than the shortest stitch the machine sews well hammers one hole. StitchCraft never sews
one. The shortest stitch is the machine profile’s, 0.3 mm on the reference Brother, or the element’s own
min_stitch_length_mm when that is longer. A part of the path too short for a stitch is left out, and
SC-W0401 names it. A stitch length below twice the shortest stitch is raised to that, and SC-W0402
says so.
Parameters
The reference lists each parameter with its range and default, on a page for each group:
- running stitch
- repeats and bean stitch
- the stroke method
- the settings every stitch type shares, with manual stitch’s longest stitch
Zigzag stroke
A zigzag stroke crosses a path from side to side and back, as wide as the stroke’s line. Set an element’s
stroke_method to zigzag_stitch for it. Use it for a light border, a couched look, or a stem too thin
for a satin column.
In VectorCraft, set the StitchCraft Stroke effect’s stroke_method to zigzag_stitch, and stitch export
sews it. VectorCraft’s live effect shows the object as it is drawn, as wide as the zigzag, since VectorCraft
does not tell a plug-in a stroke’s width. stitch plan reads Ink/Stitch’s settings from SVG files from
milestone M8 (setting parameters).
The pictures are drawn as the running stitch’s are, from docs/fixtures/zigzag.svg, a wave
drawn 3 mm wide.
Spacing
zigzag_spacing_mm is how far it is along the path from one stitch to the next that crosses the same
way, a zig and a zag together, 0.4 mm by default. The needle points are spread evenly along the path, in
a whole number of zigs and zags, so the zigzag starts and ends on the same side.
0.4 mm
1 mm
2 mm
Ink/Stitch takes a list of spacings and sews the first, and so does StitchCraft.
Width and pull compensation
The zigzag is as wide as the stroke’s line. Thread pulls the fabric in along each stitch, and a zigzag
sews narrower than it is drawn. stroke_pull_compensation_mm takes each side further out by its value.
One value widens both sides. With 2, the first is for the left of the way the path goes and the second
for its right. A negative value makes the zigzag narrower.
Angle
zigzag_angle turns every stitch away from square to the path, from −89° to 89°. A positive angle turns
the stitches clockwise as the drawing shows them. On a curve the turned stitches point like the spokes
of a wheel.
0°
45°
−45°
Repeats and bean stitch
repeats sews the zigzag more than once, every other time backwards, and bean_stitch_repeats sews each
stitch more than once, as for a running stitch.
A closed path
A path that ends where it starts ends its zigzag on its first needle point, and the line closes without a gap.
The shortest stitch
Across a zigzag much narrower than the shortest stitch the machine sews well, its stitches are too short,
and StitchCraft leaves their needle points out when it finishes the plan (SC-I0504). A part of the path
that is one point is left out, and SC-W0401 names it.
Parameters
The reference lists each parameter with its range and default:
E-stitch
An E-stitch edges appliqué. A row of stitches runs along one edge of a band, its spine, and at each step
a spike reaches across the band and comes back, like a row of E’s laid end to end. It is the blanket
stitch, which holds an appliqué’s raw edge down with far less thread than a satin column. Ink/Stitch’s
documentation suggests it for appliqué on clothes for small children. Set an element’s satin_column on
and its satin_method to e_stitch for it.
Draw it as a satin column, with 2 rails along the band’s edges. The spine runs along the first rail, and the spikes go across to the second. Rungs across the rails, where they turn, keep the spikes square to the band.
In VectorCraft, join the 2 rails with Object › Compound Path › Make and apply the StitchCraft Satin
effect to them, with its satin_method set to e_stitch. The effect shows its stitches, and
stitch export sews them. An SVG file’s Ink/Stitch settings are read from milestone M8
(setting parameters).
The pictures are drawn from docs/fixtures/e-stitch.svg, a curved band 4 mm wide.
Spacing
zigzag_spacing_mm is how far apart the spikes are, 0.4 mm by default as for a satin column. An E-stitch
wants it wider: 2 or 3 mm leaves the fabric between the spikes showing.
1 mm
2 mm
3 mm
Which edge
The spine runs along the first rail, the first subpath the path draws. swap_satin_rails puts it on the
other rail, and the spikes then point the other way. Put the spine on the appliqué’s outer edge, over the
raw edge of its fabric.
Rails swapped
Long spikes and long steps
Spikes longer than max_stitch_length_mm are split as a satin column’s stitches are, and each spike
comes back down through the needle holes of its own splits. A step of the spine longer than the longest
stitch is split only if the spacing as set is longer than the longest stitch, as in Ink/Stitch, so a step
that random_zigzag_spacing_percent lengthens past it is sewn in one stitch. A stitch longer than 12 mm
may snag, and SC-W0209 names it.
Leaning stitches
Without enough rungs, the points of the 2 rails pair up by length alone, and where one rail runs on ahead
of the other the spikes lean along the band. Where they lean more than 45° from square to it, SC-W0208
names the place to add a rung. Every satin method gives both warnings.
Parameters
An E-stitch reads every setting of a satin column, from its underlays and compensation to its random variation and where it starts and ends. The reference lists each one with its range and default:
S-stitch
An S-stitch is a satin column sewn one way at a time. At each step one stitch crosses the band, the next
crosses back, and between them the needle runs a short way along an edge. Its thread draws S’s laid end
to end. Split by a longest stitch, its stitches lie across the band in rows that follow it, the look
of a fill that bends with the shape. Turn an element’s satin_column on and choose s_stitch as its
satin_method.
Draw it as a satin column, with 2 rails along the band’s edges and rungs across them where they turn. The
first stitch goes from the second rail to the first, and swap_satin_rails starts it from the other.
In VectorCraft, make the 2 rails one path with Object › Compound Path › Make, give it the StitchCraft
Satin effect, and set the effect’s satin_method to s_stitch: VectorCraft draws the stitches, and
stitch export sews them. StitchCraft reads the same settings from an SVG file’s Ink/Stitch attributes
from milestone M8 (setting parameters).
The pictures are drawn from docs/fixtures/s-stitch.svg, a curved band 6 mm wide.
Spacing
zigzag_spacing_mm is how far apart the stitches across are, 0.4 mm by default. Each goes the other way
from the one before. A satin column at the same spacing crosses the band twice as often, as its needle
comes back across between its stitches. A finer spacing covers the band more closely.
1 mm
0.4 mm
Split stitches
With max_stitch_length_mm set, a stitch across longer than it is split, as a satin column’s stitches
are. By default the splits of neighbouring stitches line up, in rows along the band. The staggered split
method moves each stitch’s splits along from the last one’s. The rows then slant, and the needle holes of
neighbouring stitches no longer line up.
Default
Staggered
A step along an edge is never split, however long, as in Ink/Stitch. Where the spacing is wider than the longest stitch, the steps are longer than it.
Leaning and long stitches
Where the stitches lean more than 45° from square to the band, without enough rungs, SC-W0208 names the
place to add one, and SC-W0209 names a top stitch longer than 12 mm, which may snag. Every satin method
gives both.
Parameters
Every satin column setting applies to an S-stitch too, its underlays, compensation and random variation included. The reference gives each one’s range and default:
Zigzag satin
A zigzag satin is a satin column sewn as a zigzag. Every stitch slants across the band, from a point on one
edge to a point on the other half the spacing along, and the next slants back. Turn an element’s
satin_column on and choose zigzag as its satin_method. It is drawn, and set up in VectorCraft, as an
S-stitch is, with zigzag in place of s_stitch. Its first stitch goes from the first rail
to the second, and swap_satin_rails starts it from the other.
The pictures use the S-stitch’s band, docs/fixtures/s-stitch.svg.
Spacing
zigzag_spacing_mm is how far apart the zigzag’s points are along each edge, 0.4 mm by default, as for a
satin column. The 2 edges take turns, half the spacing apart, and the needle crosses the band as often as
a satin column’s does. A finer spacing covers the band more closely.
1 mm
0.4 mm
Beside a satin column
A satin column’s stitches across go square to the band, and each stitch back slants to the next one across. Every stitch of a zigzag satin slants, half as far along the band as a satin column’s stitch back.
Zigzag satin
Satin column
Split stitches
With max_stitch_length_mm set, a stitch longer than it is split, as a satin column’s stitches are, and
split_method says where the splits go. By default each stitch splits into its own fewest equal parts,
where a satin column’s stitch back splits into as many as the stitch across before it, as in Ink/Stitch.
Underlays
A zigzag satin sews a satin column’s underlays. Its contour underlay stops where the zigzag’s points stop
on each edge, so that it stays under the stitches, as Ink/Stitch’s does. After an odd centre walk
Ink/Stitch’s stops elsewhere, as the contour_underlay
reference says.
Leaning and long stitches
A zigzag satin gives the 2 warnings of every satin method, which the
S-stitch page describes. SC-W0208 measures how the rails pair
up, where a satin column’s stitches across would go. The slant every zigzag stitch has by design, the
more the wider the spacing and the narrower the band, is left out of it.
Parameters
Every satin column setting applies to a zigzag satin too, its underlays, compensation and random variation included. The reference gives each one’s range and default:
Lock stitches, trims and stops
A lock, or tie, is a few small stitches where stitching starts (the tie-in) or ends (the tie-off). It
stops the thread from pulling out when the machine cuts it or jumps to the next part. StitchCraft adds
the locks an element’s settings call for, and a trim or a stop after an element that sets trim_after or
stop_after.
Where locks go
The needle sews straight on from one part of a design to the next when nothing separates them: the same
thread, and a gap of at most 3 mm (the collapse length) or the element’s min_jump_stitch_length_mm.
There it sews no locks. Anywhere else the stitching ends with a tie-off, the needle jumps, and the next
part starts with a tie-in.
tieschooses which ends get a lock. By default both ends do. It can also choose the start or the end alone, or neither.force_lock_stitchesadds the tie-off at the end of each part, even where the next part is close enough to sew on.- Manual stitch gets locks only when
force_lock_stitchesis set, because its needle points are placed by hand.
Lock shapes
lock_start and lock_end choose the shape at each end. The default, the half stitch, goes forth and
back twice over half the first stitch, and the stitching covers it. back_forth does the same over steps
of lock_start_scale_mm and lock_end_scale_mm, 0.7 mm by default.
The drawn shapes are larger. lock_start_scale_percent and lock_end_scale_percent size
them, 100 % by default. Each picture shows an 8 mm line with the shape at both ends:
| arrow | bowtie | cross | star |
|---|---|---|---|
| simple | triangle | zigzag | half_stitch |
|---|---|---|---|
The shapes are StitchCraft’s own designs, sized to hide under the stitching they secure. A file from Ink/Stitch that names a shape gets StitchCraft’s shape of that name.
A custom lock (custom) is your own steps, written in lock_custom_start and lock_custom_end as
numbers in sizes of the scale in millimetres. Positive steps go into the stitching. 1 -1 1 -1 goes forth
and back twice, as back_forth does. The steps follow the stitching round its corners.
Every lock stitch is at least 0.2 mm long, to keep the needle out of the hole it has just left.
StitchCraft lengthens a step or enlarges a drawn lock that would be smaller, and SC-W0502 says so.
Trims and stops
trim_aftercuts the thread after the element, with a tie-off before the cut.stop_afterpauses the machine after the element, while you place appliqué fabric or check the work. When the design has a stop position, the frame moves there before each stop.
A PES file marks a trim on the jump after it. Some Brother machines are reported to ignore that mark, and test sheet TS-02 checks what the reference machine does. DST has no trim command. StitchCraft writes a trim there as 3 short jumps that end where they started, and a DST machine set to cut at 3 jumps in a row cuts there. The machine formats design says how each format records a trim.
Parameters
Common parameters lists each of these settings with its range and default.
Embroidery basics
Machine embroidery is drawing with thread under tension on fabric that moves. Almost every setting in StitchCraft exists because of one of the effects below. Terms in bold are in the glossary.
Stitches are straight lines between holes
The machine moves the hoop and drops the needle: each stitch is a straight segment between two needle penetrations. A curve is a chain of short stitches; the shorter they are, the smoother the curve but the more holes the fabric takes. Too short (under about 0.3–0.5 mm) and the needle hits the same hole, which shreds thread and fabric; too long (beyond about 7 mm on exposed satin, 12 mm anywhere) and stitches snag and sag.
Three stitch families
- Running stitch — a dotted line of single stitches, for outlines, details and travel underneath. Bean stitch sews each stitch three (or more) times for a bold line.
- Satin — stitches swing side to side between two edges, making the glossy band seen in lettering and borders. Good from about 1.5 mm to 8–10 mm wide.
- Fill — rows of running stitches covering an area. Tatami offsets the needle points from row to row so no lines show.
The fabric fights back: pull and push
Thread under tension pulls the fabric together across the stitch direction: a satin column ends up narrower than drawn, a fill shorter across its rows. The same stitches push fabric outward along the stitch direction, so columns grow at their ends. Pull compensation widens what is sewn so the result matches what was drawn. How much depends on fabric, stabilizer, density and thread — which is why machine test sheet TS-07 exists.
Underlay: the foundation
Underlay is a light layer sewn first: it tacks fabric to stabilizer, lifts the top stitches so they look full, and stops fabric texture poking through. Satins use centre-walk, contour or zigzag underlay; fills use a sparse fill at a cross angle.
Density
Row spacing (fills) and zigzag spacing (satins) set density. Denser covers better but stiffens the fabric, adds stitches and time, and increases pull. Around 0.4 mm suits most 40 wt thread on woven fabric; small text and knits want different values.
Stabilizer and hooping
Stabilizer backs the fabric so it does not distort. Tear-away suits stable wovens; cut-away suits knits and dense designs. Hoop the fabric drum-tight without stretching it. Most “the design is wrong” problems on the machine are stabilizer or hooping problems; the test-sheet protocol keeps these constant so we test the design, not the setup.
Jumps, trims and locks
Moving from one place to another without sewing is a jump; cutting the thread is a trim. Before a trim or colour change the thread is secured with a lock stitch (tie-off), and after it with a tie-in, so nothing unravels. Short moves are cheaper sewn as travel stitches hidden under later stitching.
Hoops and their fields
A hoop keeps the fabric drum-tight, and the machine sews only inside the hoop’s field, which is smaller than its frame. The reference Brother PE800’s 5 × 7 in hoop sews at most 130 × 180 mm, and its 4 × 4 in hoop 100 × 100 mm. The fabric is most stable near the centre, and large dense designs pull more. A design sews best in the smallest hoop it fits. StitchCraft stops with an error at a design larger than the hoop it plans for, and warns beyond a profile’s comfort zone where the profile has one.
Reference
Most reference pages are generated from the code they describe, so they cannot drift from it: cargo xtask docs rewrites them, and CI fails when one is stale.
| Page | Source | Status |
|---|---|---|
| Command line | generated from the stitch command definition | available |
| Machine profiles | generated from profile data | available |
| File formats | generated from the format registry | available |
| Test sheets | generated from the test-sheet registry | available |
| Diagnostic codes | generated from the diagnostics registry | available |
| Glossary | hand-written | available |
| VectorCraft compatibility | generated from the compatibility gate’s record, compat/supported.toml | available |
| Parameters (one page per stitch type) | generated from the parameter registry | available |
| Ink/Stitch compatibility contract | generated from conformance/inkstitch-params.toml and the parameter registry | available |
Command line
stitch is StitchCraft’s command-line tool. Each section shows the command’s own --help.
stitch
stitch: the StitchCraft command-line tool
Usage: stitch <COMMAND>
Commands:
plan Plan a design, an SVG file or a VectorCraft document, for a machine and write the machine file, with a picture and a report if asked [alias: export]
testsheet Write a machine-checkpoint test sheet (docs/src/plan/machine-testing.md)
inspect Read a machine file (PES, PEC or DST) and describe it: size, stitches, threads, stitch lengths
preview Draw a machine file (PES, PEC or DST) as a picture of what it will sew (PNG)
convert Write a machine file (PES, PEC or DST) in another format
profiles List the built-in machine profiles
explain Explain a diagnostic code, such as SC-W0702
bug-report Write a bug-report bundle that reproduces what StitchCraft does with a design, or replay one
help Print this message or the help of the given subcommand(s)
Options:
-h, --help
Print help
-V, --version
Print version
Exit status: 0 done (warnings allowed) · 1 the design or file has errors (nothing written) · 2 usage
error · 3 a file could not be read or written · 4 a bug in StitchCraft (a bug-report bundle is written
where it can be: see `stitch bug-report`).
stitch plan
Plan a design, an SVG file or a VectorCraft document, for a machine and write the machine file, with a picture and a report if asked
Usage: stitch plan [OPTIONS] --output <OUTPUT> <DESIGN>
Arguments:
<DESIGN>
The design: an SVG file, or a VectorCraft document (.vectorcraft)
Options:
-o, --output <OUTPUT>
The machine file to write
-p, --profile <PROFILE>
The machine profile (see `stitch profiles`); by default the one a VectorCraft document keeps, which StitchCraft's Design Settings filter sets, or else the reference machine, which `stitch profiles` lists first
--format <FORMAT>
The file format; by default the output file's extension decides
Possible values:
- pes: Brother PES, version 1
- dst: Tajima DST
--preview <PREVIEW>
Also draw a picture of what the file will sew (PNG)
--report <REPORT>
Also write a report of the plan and what was said about it (JSON), even when nothing else is written
--auto
Digitize automatically the shapes that have no settings: StitchCraft chooses the stitches of each by its width (docs/src/design/image-to-embroidery.md). A shape with settings is left to them
--sort-colours
Sew the shapes with settings in the order StitchCraft chooses too, as --auto sews the shapes it digitizes: shapes that overlap keep their paint order, and each thread then sews as long as it can, the nearest shape next (docs/src/design/image-to-embroidery.md)
-h, --help
Print help (see a summary with '-h')
stitch testsheet
Write a machine-checkpoint test sheet (docs/src/plan/machine-testing.md)
Usage: stitch testsheet [OPTIONS] [SHEET]
Arguments:
[SHEET]
The sheet, such as TS-01 (see --list)
Options:
--list
List the test sheets and exit
-p, --profile <PROFILE>
The machine profile to check the sheet against (see `stitch profiles`); by default the one for the hoop the sheet is sewn in
-o, --output <OUTPUT>
The file to write
--format <FORMAT>
The file format; by default the output file's extension decides
Possible values:
- pes: Brother PES, version 1
- dst: Tajima DST
-h, --help
Print help (see a summary with '-h')
stitch inspect
Read a machine file (PES, PEC or DST) and describe it: size, stitches, threads, stitch lengths
Usage: stitch inspect [OPTIONS] <FILE>
Arguments:
<FILE>
The machine file
Options:
-p, --profile <PROFILE>
Also check the design against a machine profile (see `stitch profiles`)
-h, --help
Print help
stitch preview
Draw a machine file (PES, PEC or DST) as a picture of what it will sew (PNG)
Usage: stitch preview [OPTIONS] --output <OUTPUT> <FILE>
Arguments:
<FILE>
The machine file
Options:
-o, --output <OUTPUT>
The picture to write (PNG)
--style <STYLE>
How the picture looks
Possible values:
- realistic: The finished embroidery on fabric, with the jump threads the machine leaves for you to cut
- simple: Every stitch, needle hole, move, trim and stop, for checking a design
[default: realistic]
--scale <SCALE>
Pixels per millimetre
[default: 8]
-h, --help
Print help (see a summary with '-h')
stitch convert
Write a machine file (PES, PEC or DST) in another format
Usage: stitch convert [OPTIONS] --output <OUTPUT> <FILE>
Arguments:
<FILE>
The machine file to read
Options:
-o, --output <OUTPUT>
The file to write
--format <FORMAT>
The file format; by default the output file's extension decides
Possible values:
- pes: Brother PES, version 1
- dst: Tajima DST
-h, --help
Print help (see a summary with '-h')
stitch profiles
List the built-in machine profiles
Usage: stitch profiles
Options:
-h, --help
Print help
stitch explain
Explain a diagnostic code, such as SC-W0702
Usage: stitch explain <CODE>
Arguments:
<CODE>
The code
Options:
-h, --help
Print help
stitch bug-report
Write a bug-report bundle that reproduces what StitchCraft does with a design, or replay one.
The bundle is one file to attach to an issue, and it holds the design. With --replay, plan a bundle's design again and compare; the exit status is 1 when it does not reproduce.
Usage: stitch bug-report [OPTIONS] [DESIGN]
Arguments:
[DESIGN]
The design StitchCraft gets wrong: an SVG file, or a VectorCraft document (.vectorcraft)
Options:
--replay <REPLAY>
Replay this bundle instead: plan its design again, with the profile and format it names, and say whether everything comes out the same
-o, --output <OUTPUT>
The bundle to write; by default next to the design, as DESIGN.bug-report.json
-p, --profile <PROFILE>
The machine profile (see `stitch profiles`); by default the one a VectorCraft document keeps, which StitchCraft's Design Settings filter sets, or else the reference machine, which `stitch profiles` lists first
--format <FORMAT>
The file format; by default the profile's
Possible values:
- pes: Brother PES, version 1
- dst: Tajima DST
--says <SAYS>
What goes wrong, in your words; it goes into the bundle
--auto
Digitize automatically the shapes that have no settings: StitchCraft chooses the stitches of each by its width (docs/src/design/image-to-embroidery.md). A shape with settings is left to them
--sort-colours
Sew the shapes with settings in the order StitchCraft chooses too, as --auto sews the shapes it digitizes: shapes that overlap keep their paint order, and each thread then sews as long as it can, the nearest shape next (docs/src/design/image-to-embroidery.md)
-h, --help
Print help (see a summary with '-h')
stitch help
Print this message or the help of the given subcommand(s)
Usage: stitch help [COMMAND]...
Arguments:
[COMMAND]...
Print help for the subcommand(s)
Machine profiles
A profile holds the facts about one machine and hoop that decide what StitchCraft may ask of it. Its values are machine facts: they change only with a sew-out report that justifies them, and each profile says where its values come from. stitch profiles prints the same list.
brother-pe800-5x7
Brother PE800 with its 5 × 7 in hoop
| Fact | Value |
|---|---|
| Hoop (sewing field) | 130 × 180 mm |
| File format | PES v1 |
| Stitch length | 0.3 mm to 12 mm (lock stitches from 0.2 mm) |
| Trims | the format’s trim command |
| Thread palette | Brother PEC |
Evidence. Machine: the owner of the reference machine (2026-10-10). Field: Brother’s 5 × 7 in hoop for the PE800, 130 × 180 mm. Stitch limits and trim support: common Brother home-machine values, to be confirmed by machine checkpoints MC-1 and MC-2 (test sheets TS-01 to TS-04 and TS-10).
brother-pe800-4x4
Brother PE800 with its 4 × 4 in hoop
| Fact | Value |
|---|---|
| Hoop (sewing field) | 100 × 100 mm |
| File format | PES v1 |
| Stitch length | 0.3 mm to 12 mm (lock stitches from 0.2 mm) |
| Trims | the format’s trim command |
| Thread palette | Brother PEC |
Evidence. Hoop: the owner’s (2026-10-10). Field: Brother’s 4 × 4 in hoop, 100 × 100 mm. The rest as for brother-pe800-5x7.
brother-pe800-small
Brother PE800 with its small hoop
| Fact | Value |
|---|---|
| Hoop (sewing field) | 20 × 60 mm |
| File format | PES v1 |
| Stitch length | 0.3 mm to 12 mm (lock stitches from 0.2 mm) |
| Trims | the format’s trim command |
| Thread palette | Brother PEC |
Evidence. Hoop: the owner’s, sold as 1 × 2.5 in (2026-10-10). Field: Brother’s small hoop, 2 × 6 cm, upright as the 5 × 7 in’s until test sheet TS-10C confirms it. The rest as for brother-pe800-5x7.
File formats
The machine-file formats StitchCraft writes. How each is encoded, and why, is in the machine formats design.
| Format | Extension | Colour changes and stops | Colours stored as |
|---|---|---|---|
| PES v1 | .pes | at most 255 | indices into the Brother PEC palette (nearest colour, CIEDE2000) |
| DST | .dst | at most 999 | nothing: the machine pauses and the operator loads the next thread |
Test sheets
Designs drawn in code for the machine checkpoints, each for one hoop of the reference machine. Write one with stitch testsheet <id> -o <id>.pes. The command checks it against the profile of that hoop, or the one --profile names, and prints what to check after sewing it, as listed here. The pictures are previews: they show what the machine will sew, including jump threads it leaves for you to cut. In VectorCraft, the Test Sheet filter draws a sheet drawn as a design into a document (how).
TS-01 — Orientation and scale
120.0 × 120.0 mm · 274 stitches, 7 jumps, 7 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Threads: Black.
After sewing, check:
- The F reads normally: not mirrored, not upside down, not turned.
- Each arm of the cross measures 100.0 ± 0.5 mm end to end, horizontally and vertically.
- The corner squares measure 10.0 mm on every side.
- Ticks are 10 mm apart; the long ticks mark the ends and the centre.
TS-02 — Colour changes, a stop, jumps and trims
120.0 × 70.0 mm · 93 stitches, 17 jumps, 8 trims, 2 colour changes, 1 stop
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Threads: Red, Blue, Emerald Green, Emerald Green (stop).
After sewing, check:
- The machine stops for red → blue and blue → green, and once more in the middle of the green line (the stop).
- Left half (red, a trim before every jump): for each row (gaps of 2, 5, 15, 30 mm, top to bottom), was the thread between the two dashes cut?
- Right half (blue, jumps without trims, which a PES file flags as trims too): did every dash sew? For each row, and between rows, was the thread cut?
- Any loose loops, knots or bird’s nests on the back, and where.
TS-02B — TS-02 drawn as a design: trims elements ask for
120.0 × 70.0 mm · 230 stitches, 17 jumps, 8 trims, 2 colour changes, 1 stop
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Drawn as a design, which VectorCraft’s Test Sheet filter draws too.
Threads: Red, Blue, Emerald Green, Emerald Green (stop).
After sewing, check:
- The machine stops for red → blue and blue → green, and once more in the middle of the green line (the stop), and cuts the thread at each.
- Left half (red; every dash but the last asks for a trim after it): the machine cuts no jump, so its rows (gaps of 2, 5, 15, 30 mm, top to bottom) are joined by threads. Did every dash stay sewn while the machine went on to the next?
- Right half (blue; no trims): the 2 mm gap is sewn across, and threads join the other dashes and the rows. Did every dash stay sewn?
- Each dash starts and ends with a small lock. Snip the threads between the dashes, then pull each tail gently: does the dash hold?
- Any loose loops, knots or bird’s nests on the back, and where.
TS-03 — Running stitch: lengths, bean stitch, curves, the shortest stitch
60.0 × 78.0 mm · 588 stitches, 15 jumps, 15 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Drawn as a design, which VectorCraft’s Test Sheet filter draws too.
Threads: Blue.
After sewing, check:
- Running stitch lines (top five; 1.5, 2.0, 2.5, 3.0 and 4.0 mm): the stitches of each line are even; ten stitches, counted inside the small lock at each end, measure 15, 20, 25, 30 and 40 mm.
- Bean stitch lines (next two; each stitch sewn three and five times): solid and raised, with no gaps.
- Circles (6 mm across; tolerance 0.1, 0.2 and 0.5 mm, left to right): round, with fewer and straighter stitches to the right.
- Short stitches placed by hand (bottom five; 0.3, 0.4, 0.5, 0.7 and 1.0 mm, each with a small back-and-forth lock at both ends): which lines sew cleanly, with no thread breaks, knots or bunching on the back? The shortest clean one is the machine’s shortest stitch; if the 0.3 mm line is clean, it is 0.3 mm or less: the sheet has no shorter line.
TS-04 — Lock stitches: do they hold, and do they show?
110.0 × 64.5 mm · 623 stitches, 27 jumps, 27 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Drawn as a design, which VectorCraft’s Test Sheet filter draws too.
Threads: Red.
After sewing, check:
- Rows, top to bottom: half stitch, arrow, back and forth, bowtie, cross, star, simple, triangle, zigzag. Each line has its lock at both ends and was trimmed after, but the machine cuts no jump: snip the threads between the lines, then pull each tail gently. Does the lock hold, or does the line come undone?
- Columns, left to right, are small, medium and large: the half stitch on first stitches of 1, 1.5 and 2.5 mm, back and forth at 0.5, 0.7 and 1.0 mm, the others at 70, 100 and 150 %.
- Rows 1, 3, 5, 7 and 9 are sewn left to right and the others right to left: a line’s start is the end it was sewn from.
- Which locks show from the front, and how much (1 hidden to 5 obvious)?
- Any thread breaks or knots at the locks, and where.
TS-05 — Satin width ladder: spacing, width and pull
91.0 × 76.0 mm · 3440 stitches, 30 jumps, 30 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Drawn as a design, which VectorCraft’s Test Sheet filter draws too.
Threads: Blue.
After sewing, check:
- Rows, top to bottom, are at zigzag spacings of 0.3, 0.4 and 0.5 mm, and the columns of each row are 1 to 10 mm wide, left to right.
- Coverage: in which columns does the fabric show between the stitches? Rate each row 1 (bare) to 5 (solid).
- Width: measure the 2, 6 and 10 mm columns of each row across, halfway down. How much narrower than drawn are they?
- Edges: straight and crisp, or wavy? From which width on do the long stitches lie loose or catch?
- Any puckering round the columns, thread breaks, or loops on the back, and where.
TS-06 — Satin underlays side by side
62.0 × 30.0 mm · 966 stitches, 5 jumps, 5 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Drawn as a design, which VectorCraft’s Test Sheet filter draws too.
Threads: Red.
After sewing, check:
- Columns, left to right: no underlay, a centre walk, a contour, a zigzag, and a contour with a zigzag, each 6 mm wide.
- Edges: rate each column 1 (ragged) to 5 (crisp).
- Loft: rate each column 1 (flat) to 5 (full and raised).
- Does any underlay show past the top stitches, at the edges or at the ends?
- Measure each column across, halfway down, and note any puckering round it.
TS-07 — Fill and outline registration at 3 pull compensations
126.2 × 126.0 mm · 13527 stitches, 6 jumps, 6 trims, 1 colour change, 0 stops
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Drawn as a design, which VectorCraft’s Test Sheet filter draws too.
Threads: Red, Black.
After sewing, check:
- Circles: top left with no pull compensation, top right with 0.2 mm, bottom with 0.4 mm. Each red fill is 60 mm across, with a black running stitch along its edge.
- At 12 points round each circle, as the hours of a clock: does fabric show between the fill and the outline, or does the fill reach past it? Measure the largest gap and the largest overlap of each circle.
- Measure each fill across, left to right and top to bottom. How much less than 60 mm is it, and how much of that does the compensation make up?
- Any puckering round the circles, and where.
TS-08 — Fill density and angle
100.0 × 170.0 mm · 16248 stitches, 15 jumps, 15 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Drawn as a design, which VectorCraft’s Test Sheet filter draws too.
Threads: Blue.
After sewing, check:
- Rows, top to bottom, are at row spacings of 0.25, 0.3, 0.4, 0.5 and 0.6 mm, and the columns at angles of 0°, 45° and 90°, left to right. Each square is 30 mm wide.
- Coverage: in which squares does the fabric show between the rows? Rate each row of squares 1 (bare) to 5 (solid).
- At 0.25 mm the rows lie nearer each other than the machine’s shortest stitch of 0.3 mm. In the 0° and 90° squares, whose edges meet the rows square on, each row’s first needle point is left out: does a sliver of fabric show at the rows’ starts, along those edges? The 45° square’s rows end 0.35 mm apart along its edges and keep their starts.
- Stiffness: rate each row of squares 1 (soft) to 5 (stiff as card). Is there puckering round any square, or furrows where needle points line up?
- Measure the 0° and the 90° square at 0.4 mm across, both ways. How much less than 30 mm are they?
TS-09 — Fill underlay, travel and gap-filling rows round holes
66.0 × 114.0 mm · 7780 stitches, 6 jumps, 3 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Drawn as a design, which VectorCraft’s Test Sheet filter draws too.
Threads: Emerald Green.
After sewing, check:
- Top to bottom: without underlay, with Ink/Stitch’s default underlay, and with the underlay and 2 gap-filling rows. Each is a 50 × 30 mm rectangle with a square hole and a round hole, and a bar 6 mm to its right that belongs to the same fill.
- Travel: does any running stitch between rows show on top of the fill? Where?
- The bar: was the thread to it cut, or does a jump thread run to it? Does the fill hold at both ends of that jump?
- Holes: are their edges clean, with no row across them and no fabric showing round them?
- Where the rows round a hole meet again, does a gap open along a row? Does the bottom copy, with gap-filling rows, close it?
- How the 3 copies differ in stiffness, and any puckering.
TS-10A — Hoop size: the 4 × 4 in hoop, a 100 × 100 mm frame
100.0 × 100.0 mm · 195 stitches, 4 jumps, 4 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its 4 × 4 in hoop, profile brother-pe800-4x4.
Threads: Blue.
After sewing, check:
- With the 4 × 4 in hoop on the machine: does the machine take the file and sew it, without asking for a larger hoop?
- The frame measures 100.0 × 100.0 mm (± 0.5 mm), the F at the top left.
TS-10B — Hoop size: the 5 × 7 in hoop, a 130 × 180 mm frame
130.0 × 180.0 mm · 283 stitches, 4 jumps, 4 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its 5 × 7 in hoop, profile brother-pe800-5x7.
Threads: Blue.
After sewing, check:
- With the 5 × 7 in hoop on the machine: does the machine take the file and sew it?
- The frame measures 130.0 × 180.0 mm (± 0.5 mm), tall side top to bottom, the F at the top left.
TS-10C — Hoop size: the small hoop, a 20 × 60 mm frame
20.0 × 60.0 mm · 92 stitches, 4 jumps, 4 trims, 0 colour changes, 0 stops
Hoop: the Brother PE800 with its small hoop, profile brother-pe800-small.
Threads: Blue.
After sewing, check:
- With the small hoop on the machine: does the machine take the file and sew it, without asking for a larger hoop?
- If it asks for a larger hoop, turn the design a quarter turn on the machine’s screen. Does it take it then?
- The frame measures 20.0 × 60.0 mm (± 0.5 mm), tall side top to bottom, the F at the top left.
Diagnostic codes
Every problem StitchCraft reports has a code. E codes are errors (nothing is written), W codes are warnings (the file is written), I codes are information. stitch explain <code> prints the same explanation in the terminal. Codes are never reused.
| Code | Severity | Title |
|---|---|---|
SC-E0004 | Error | Budget exhausted |
SC-E0005 | Error | Preview too large |
SC-E0009 | Error | Internal check failed |
SC-E0010 | Error | Nothing to stitch |
SC-W0011 | Warning | Stitch type not sewn yet; element skipped |
SC-E0012 | Error | Bug-report bundle could not be replayed |
SC-E0101 | Error | Parameter has the wrong type or an unknown choice |
SC-W0102 | Warning | Parameter clamped to its allowed range |
SC-W0105 | Warning | Unknown parameter preserved but ignored |
SC-E0201 | Error | Satin column has no rails |
SC-W0202 | Warning | Satin rails taken as the two longest subpaths |
SC-W0203 | Warning | Satin rung does not cross both rails |
SC-W0205 | Warning | Satin subpath is one point; left out |
SC-W0206 | Warning | Satin contour underlay too short for its insets; it keeps its length |
SC-W0207 | Warning | Satin rung crosses a rail more than once; left out |
SC-W0208 | Warning | Satin stitches lean more than 45° from square to the column; add a rung here |
SC-W0209 | Warning | Satin stitch longer than 12 mm; it may snag |
SC-W0210 | Warning | Satin rails without rungs have different numbers of nodes |
SC-W0211 | Warning | Satin push compensation too long for a rail; that rail keeps its length |
SC-W0212 | Warning | Satin column drawn as one path too narrow; sewn as a stroke |
SC-W0213 | Warning | Parts of a satin column drawn as one path left out |
SC-E0214 | Error | Satin column drawn as one path could not be made into rails |
SC-W0215 | Warning | Satin column too small for the shortest stitch; left out |
SC-E0216 | Error | Satin column has no section to sew |
SC-W0303 | Warning | Parts of a fill too small to sew left out |
SC-W0304 | Warning | Fill smaller than 1.4 mm² |
SC-W0305 | Warning | Fill part too thin for rows; sewn round its outline |
SC-I0306 | Info | Nonzero fill rule fills a part inside that the even-odd rule leaves empty |
SC-W0307 | Warning | Fill in parts that are apart; each sewn on its own |
SC-I0308 | Info | Fill underlay too thin for rows; sewn round the outline |
SC-W0309 | Warning | Fill’s expand shrinks a part away; sewn at its own size |
SC-W0310 | Warning | Fill’s underlay inset shrinks a part away; underlay over all of it |
SC-W0312 | Warning | Fill’s pull compensation below 0; read as 0 |
SC-I0313 | Info | Fill drawn twice along its edge; Ink/Stitch fills the part left empty |
SC-W0314 | Warning | Legacy fill sewn as a tatami fill without underlay |
SC-W0401 | Warning | Path too small for the shortest stitch; skipped |
SC-W0402 | Warning | Stitch length below twice the shortest stitch; raised |
SC-W0403 | Warning | Hand-placed stitch shorter than the shortest stitch; point left out |
SC-W0404 | Warning | Hand-placed stitch longer than max_stitch_length_mm; kept for the shortest stitch |
SC-W0405 | Warning | Zigzag stitch shorter than the shortest stitch; the zigzag sews narrower |
SC-W0502 | Warning | Lock stitch shorter than 0.2 mm; lengthened |
SC-W0503 | Warning | Custom lock cannot be sewn as written |
SC-I0504 | Info | Stitches shorter than the shortest stitch merged |
SC-W0505 | Warning | Trim or stop after an element that sews nothing; left out |
SC-E0601 | Error | Too many colour changes for the file format |
SC-E0602 | Error | Design too large for the file format |
SC-E0603 | Error | Machine file could not be read |
SC-W0604 | Warning | Thread colours unknown |
SC-I0605 | Info | Long jumps cut in DST |
SC-W0606 | Warning | Threads share a Brother colour |
SC-E0701 | Error | Design does not fit the hoop |
SC-W0702 | Warning | Design is larger than the comfort zone |
SC-I0703 | Info | Stitches longer than the machine’s longest stitch; split |
SC-E0801 | Error | SVG could not be read |
SC-W0802 | Warning | SVG feature ignored |
SC-I0805 | Info | Object left out, as the file asks |
SC-W0804 | Warning | Element geometry invalid or out of range |
SC-W0803 | Warning | VectorCraft file from a newer format version; read best-effort |
SC-E0806 | Error | VectorCraft file could not be read |
SC-W0807 | Warning | VectorCraft object or feature not stitched |
SC-W0808 | Warning | VectorCraft geometry unusable |
SC-I0809 | Info | VectorCraft layer or guide left out, as it does not print |
SC-I0810 | Info | VectorCraft live effect leaves the object as it is |
SC-I0811 | Info | VectorCraft object left out, as its name asks |
SC-W0812 | Warning | VectorCraft design setting ignored |
SC-E0813 | Error | VectorCraft too old for the filter |
SC-E0814 | Error | VectorCraft line not made into a satin column |
SC-I0901 | Info | Shapes digitized automatically |
SC-I0902 | Info | Line a satin column cannot follow; filled |
SC-W0903 | Warning | Line too wide to fill; left out |
SC-I0904 | Info | Areas reach under the shapes sewn after them |
SC-I0905 | Info | Speck merged into a neighbour |
SC-W0906 | Warning | Background left out |
SC-I0907 | Info | Elements sewn in an order of their own |
SC-E0004
Error — Budget exhausted
A budget limits how much work StitchCraft may spend on one element, and how many stitches one design may have, so that no input can make it run for hours or run out of memory — on the command line or inside VectorCraft’s live preview.
When planning, an element that needs more work than its budget was skipped and the rest of the design was planned: simplify the element (fewer nodes, wider spacing, a smaller area) or split it into several elements. A design that needs more stitches than its limit was not planned, and the message names the element where it ran out: widen the spacing of its largest elements, lower their repeats or bean stitch, or split the design into several files. When reading a file or drawing a preview, nothing was produced: simplify the drawing, or split it into several files.
SC-E0005
Error — Preview too large
Preview images have a size limit, so that drawing one never runs out of memory. At the scale asked for, this design’s preview would be larger, so no image was written.
Use a smaller scale: the message says the largest that fits. A design that runs more than 10 metres from the hoop centre cannot be previewed at any scale.
SC-E0009
Error — Internal check failed
StitchCraft checks every stitch plan against its own rules before writing a machine file. One of
those checks failed, which means StitchCraft has a bug: the file was not written, so nothing
wrong reaches your machine. stitch plan writes a bug-report bundle instead, next to the file it
was asked for. An export in VectorCraft writes nothing, and stitch bug-report of the saved
document writes the bundle.
Please attach the bundle to an issue; the message names the rule that failed. The bundle holds your design, so that the bug can be reproduced.
SC-E0010
Error — Nothing to stitch
The design has no stitches: it is empty, or every element was skipped (see the other messages). StitchCraft never writes an empty machine file, because some machines refuse or mishandle them.
Add an element with an embroidery stitch type, or fix the errors reported for the elements.
SC-W0011
Warning — Stitch type not sewn yet; element skipped
The element’s stitch type is one this version of StitchCraft does not sew yet, so the element was
skipped; the rest of the design is planned. Running and manual stitches are sewn from milestone M3,
satin columns from M4, tatami fills from M5, and zigzag strokes and the other satin methods from M7.
The other stitch types follow (docs/src/plan/roadmap.md), but for Ink/Stitch’s legacy fill, which
is sewn as a tatami fill (SC-W0314).
The message names the stitch type. Choose one StitchCraft sews, or sew the element with another tool for now.
SC-E0012
Error — Bug-report bundle could not be replayed
stitch bug-report --replay was given a file that is not a bug-report bundle it can replay: not
JSON, not a bundle, a bundle format newer than this StitchCraft, or one that names a profile or a
format this StitchCraft does not have. Nothing was replayed.
The message says what is wrong. A bundle written by a newer StitchCraft needs that version or a later one; a bundle changed by hand, or by an email program, needs the original file.
SC-E0101
Error — Parameter has the wrong type or an unknown choice
A parameter’s value could not be understood: a number where a word was expected, a choice that is not one of the parameter’s options, or a list with the wrong number of values. The element was not stitched: guessing could sew something you did not ask for.
The message names the parameter, the value and what the parameter accepts. The parameter reference lists every parameter with its accepted values.
SC-W0102
Warning — Parameter clamped to its allowed range
A parameter’s value is outside the range StitchCraft accepts, so the nearest allowed value was used, and the element was stitched with it. Values outside the range are impossible (a negative length) or beyond what machines sew reliably.
The message names the parameter, your value and the range. Change the value to remove the warning.
SC-W0105
Warning — Unknown parameter preserved but ignored
The design carries a parameter StitchCraft does not know: one from a newer version of StitchCraft or Ink/Stitch, or one for a stitch type StitchCraft does not support yet. It was kept, so saving the design does not lose it, but it does not change the stitches.
The Ink/Stitch compatibility page says when each Ink/Stitch parameter is supported.
SC-E0201
Error — Satin column has no rails
A satin column’s path has no subpath longer than a point, so it has no rails to sew between. The element was not stitched.
Draw the column as two rails, its edges, with rungs across both.
SC-W0202
Warning — Satin rails taken as the two longest subpaths
Where the subpaths of a satin column meet does not say which two are its rails, so the two
longest were taken as the rails. The rule is Ink/Stitch’s: with one rung, the rails are the two
subpaths that meet only one other, and with more rungs, the two that meet more than two others.
With exactly two rungs across two rails, each of the four meets two others, as the strokes of a
# do, and the rule cannot tell rails from rungs.
The message names the two subpaths taken, numbered from 1 in the order the path draws them. If they are not the rails, use three rungs or more, each crossing both rails once.
SC-W0203
Warning — Satin rung does not cross both rails
A rung of a satin column misses one of the rails, or both. It still says which points go together: where it misses a rail, the point of that rail nearest the rung is used, as in Ink/Stitch.
The message names the rung, numbered from 1 in the order the path draws it. Extend it across both rails, so that where it joins them is what you drew.
SC-W0205
Warning — Satin subpath is one point; left out
A subpath of a satin column is one point, a stray node, so it is neither a rail nor a rung. It was left out.
The message names the subpath, numbered from 1 in the order the path draws it. Delete the node, or if it was meant as a rung, draw the rung across both rails.
SC-W0206
Warning — Satin contour underlay too short for its insets; it keeps its length
A satin column’s contour underlay stops short of the column’s start and end by its inset, but a side of it is so short that this would leave less than half a CSS pixel (0.13 mm), so that side runs to the column’s ends, as in Ink/Stitch.
The message gives the insets at each end. Lower contour_underlay_inset_mm, or turn the contour
underlay off for so short a column.
SC-W0207
Warning — Satin rung crosses a rail more than once; left out
A rung of a satin column crosses a rail more than once, so it does not say which point of one rail goes with which point of the other, and it was left out. The column is sewn without it.
The message names the subpath, numbered from 1 in the order the path draws it. Redraw the rung as a straight line across both rails.
SC-W0208
Warning — Satin stitches lean more than 45° from square to the column; add a rung here
The stitches of a satin column lean more than 45° from square to the column, to a tenth of a degree: they go further along it than across it. The column runs halfway between its 2 rails. Without rungs, or with too few, a column’s stitches follow its rails’ lengths alone, and where one rail runs on ahead of the other they lean. Every satin method says so, at the middle of each stretch of stitches that lean. Ink/Stitch does not.
The message gives how many stitches lean and the most any of them leans. Add a rung across the column at the place named, square to it, and the stitches follow it.
SC-W0209
Warning — Satin stitch longer than 12 mm; it may snag
A top stitch of a satin column is longer than 12 mm, as the column sews it. A stitch that long floats loose over the fabric, where it snags and sags. Every satin method says so once, at the middle of its longest top stitch, and its underlays and travel do not count. Ink/Stitch does not say so.
The message gives the stitch’s length. Set max_stitch_length_mm to split long stitches, make the
column narrower, or sew the shape as a fill. Finalize splits a stitch longer than the machine’s
longest stitch anyway (SC-I0703).
SC-W0210
Warning — Satin rails without rungs have different numbers of nodes
A satin column’s path draws no rungs, so its rails’ nodes pair up instead, as in Ink/Stitch: the 2nd node of one rail with the 2nd of the other, and on in order. Its rails have different numbers of nodes, so the rail with more has nodes that pair with none, and between them the stitches follow the rails’ lengths alone.
The message gives both counts. Add rungs across the column, or give both rails the same number of nodes.
SC-W0211
Warning — Satin push compensation too long for a rail; that rail keeps its length
A satin column’s push compensation, taken off a rail at the column’s start and end, would leave less than half a CSS pixel (0.13 mm) of it, so that rail keeps its length, as in Ink/Stitch. A negative value at the other end still lengthens it.
The message gives the push compensation at each end. Lower push_compensation_mm, or lengthen the
column.
SC-W0212
Warning — Satin column drawn as one path too narrow; sewn as a stroke
A satin column is drawn as one path, its centre line, and its stroke is no wider than the design’s
min_satin_stroke_width_mm (1 mm unless the design sets it). A column that narrow is sewn as a
stroke, by the element’s stroke settings, as in Ink/Stitch: a running stitch unless they say
otherwise.
The message gives the stroke’s width and the limit. Widen the stroke to sew a satin column, or draw the column with 2 rails.
SC-W0213
Warning — Parts of a satin column drawn as one path left out
A satin column drawn as one path, its centre line, is made into rails by offsetting the line by half the stroke’s width to each side. Where the line crosses itself or comes back near itself, the offsets split into several curves, and the line is cut in half, and the halves again, at most 20 times. Parts whose offsets still split are left out, and so are parts that turn so tightly that an offset vanishes. Ink/Stitch leaves out the same parts without a word. The rest of the column is sewn.
The message says how many parts were left out. Draw the column with 2 rails where its line crosses itself or turns more tightly than the column is wide, or make the stroke narrower.
SC-E0214
Error — Satin column drawn as one path could not be made into rails
A satin column drawn as one path, its centre line, could not be made into rails. Its offsets split however it is cut, an offset vanishes because the stroke is wider than the line’s turns allow, or the line is shorter than a CSS pixel. The column is not sewn.
Draw the column with 2 rails, or make the stroke narrower.
SC-W0215
Warning — Satin column too small for the shortest stitch; left out
A satin column is so small that every needle point of it lies within the shortest stitch of its
first: a speck of a column, a few tenths of a millimetre across. Any stitch it could sew would be
shorter than the machine sews well, would hammer one spot of the fabric and could break the thread,
so it is left out, as a stroke that small is (SC-W0401). Ink/Stitch sews such a column. A zigzag
satin 0.1 mm long or less is left out too, its underlays with it: its zigzag has one point and no
stitch across. Ink/Stitch sews its underlays alone.
The message gives the shortest stitch, or says the column is too short for a stitch across. Draw the column larger, or delete it if it is a stray.
SC-E0216
Error — Satin column has no section to sew
A satin column’s rungs, or its push compensation, leave each of its sections with no length on one of its rails, so no stitch goes across it and the column is not sewn. A rung from the first rail’s start to the second rail’s end does this, and so does push compensation that takes off the parts of a short column’s rails where its rungs meet them. Ink/Stitch says such a column cannot be stitched.
Move the rungs so that each meets both rails away from their ends, or lower push_compensation_mm.
SC-W0303
Warning — Parts of a fill too small to sew left out
A fill is sewn in rows of stitches across each part of its area. A part of 0.21 mm² or less (3 square CSS pixels), a speck about half a millimetre across, is too small for a row, so it is left out, as Ink/Stitch leaves it out. Where every part is that small, the fill sews nothing. So does a fill that bounds no area at all: subpaths that are lines or points, or that its fill rule leaves empty, such as 2 copies of one square under the even-odd rule.
The message counts the parts left out and is placed at the first. Draw them larger, or sew them as a running stitch.
SC-W0304
Warning — Fill smaller than 1.4 mm²
This fill covers less than 1.4 mm² (20 square CSS pixels). Rows of stitches packed into so small an area pile up, and a running stitch round it or a satin column across it usually sews it better. Ink/Stitch gives the same advice. The fill is kept as drawn.
SC-W0305
Warning — Fill part too thin for rows; sewn round its outline
A fill is sewn in rows of stitches the row spacing apart, and a part of it so thin that no row crosses it gets none. Such a part is sewn as a running stitch round its outline instead, as Ink/Stitch sews it.
The message counts the parts. Make them wider, set a smaller row_spacing_mm, or sew them as a
stroke or a satin column.
SC-I0306
Info — Nonzero fill rule fills a part inside that the even-odd rule leaves empty
A fill’s fill rule says which parts of it are filled where its subpaths lie inside each other or
overlap. Under the rule nonzero, SVG’s default, a part that the subpaths wind round twice the same
way is filled; the even-odd rule leaves it empty. StitchCraft fills it, as the drawing shows it.
Ink/Stitch takes every subpath but the largest as a hole, whatever the fill rule. Where such a part
lies inside the fill, away from its edge, the same file leaves it empty in Ink/Stitch.
To leave the part empty in both, set the fill rule to even-odd, or draw the inner subpath the other way round.
SC-W0307
Warning — Fill in parts that are apart; each sewn on its own
A fill whose area falls into separate parts, such as one drawn as several shapes side by side, or an outline that crosses itself, is sewn one part after another: each part’s rows on their own, with a jump to the next, which the plan may trim. Ink/Stitch warns about such fills too.
To choose the order the parts are sewn in, break the fill apart into one element for each part.
SC-I0308
Info — Fill underlay too thin for rows; sewn round the outline
A fill’s underlay is sewn in rows fill_underlay_row_spacing_mm apart, 3 times the row spacing
unless set. On a part so thin across those rows that none crosses it, that layer of underlay is
sewn as a running stitch round the part’s outline instead, as Ink/Stitch sews it, and the top rows
are sewn over it as usual.
The message counts the layers, and each part has one for each underlay angle. To sew the underlay
in rows, set a smaller fill_underlay_row_spacing_mm or another fill_underlay_angle. To sew no
underlay, turn fill_underlay off.
SC-W0309
Warning — Fill’s expand shrinks a part away; sewn at its own size
A fill’s top rows are sewn on each part grown by expand_mm, or shrunk where it is negative. A part
too thin for that much shrinking, which wears it away entirely, is sewn at its own size instead, as
Ink/Stitch sews it, and Ink/Stitch warns of it too.
The message counts the parts. Make expand_mm nearer 0.
SC-W0310
Warning — Fill’s underlay inset shrinks a part away; underlay over all of it
A fill’s underlay is sewn on each part shrunk by fill_underlay_inset_mm. A part too thin for that
much shrinking, which wears it away entirely, has its underlay sewn over all of it instead, as
Ink/Stitch sews it, and Ink/Stitch warns of it too.
The message counts the parts. Set a smaller fill_underlay_inset_mm, or turn fill_underlay off.
SC-W0312
Warning — Fill’s pull compensation below 0; read as 0
A fill’s rows reach past its edge by its pull compensation, which makes up for the thread pulling
the fabric in. Ink/Stitch reads a fill’s pull_compensation_mm or pull_compensation_percent below
0 as 0, so that the rows reach the edge and no less, and so does StitchCraft. A satin column reads
the same settings below 0 as narrower.
The message names the settings. Set them to 0 or more; to keep the rows inside the edge, use a
negative expand_mm.
SC-I0313
Info — Fill drawn twice along its edge; Ink/Stitch fills the part left empty
Two subpaths of a fill run along each other at its edge, around a part that its fill rule leaves empty. A notch drawn as a hole whose sides lie on the outline is one, and a shape drawn twice is another. StitchCraft leaves the part empty, as the drawing shows it. Ink/Stitch repairs such a shape by merging each stretch drawn twice into one, which fills the part, and the same file then sews it filled in Ink/Stitch.
To sew it alike in both, draw the outline round the empty part instead of a subpath along it.
SC-W0314
Warning — Legacy fill sewn as a tatami fill without underlay
Ink/Stitch’s legacy fill is its first fill method, which it keeps for its older files: it gives the
method to a fill whose auto_fill is off. StitchCraft does not sew it, and sews such a fill as a
tatami fill with the same settings and no underlay, as legacy fill sews none. Its rows and their
needle points are a tatami fill’s, as in Ink/Stitch. Ink/Stitch sews a legacy fill’s rows in
stretches that join, each on its own and back and forth; its flip starts the rows from the other
side, and its reverse sews the fill backwards. A tatami fill routes its rows and joins them with
travel stitches, and StitchCraft keeps flip and reverse without reading them (SC-W0105).
To sew the fill alike in both, set its fill method to tatami fill and turn fill_underlay off.
SC-W0401
Warning — Path too small for the shortest stitch; skipped
A part of a stroke is too small for the shortest stitch the machine sews well, so it was left out: one stitch that short would hammer one spot of the fabric and could break the thread. The part is a single point (a stray node), shorter than the shortest stitch, or longer but curled up so that all of it lies within the shortest stitch of its ends: a tiny closed loop, say.
The message gives the part’s length and the shortest stitch. Enlarge the part, join it to its neighbour, or delete it if it is a stray.
SC-W0402
Warning — Stitch length below twice the shortest stitch; raised
A running stitch’s length, a stroke’s or that of one of a satin column’s underlays, is shorter than twice the shortest stitch, so it was lengthened to twice the shortest stitch. Stitches are spread evenly between corners; at half the length or more, every stitch then stays at or above the shortest stitch, whatever the distance between corners.
The message names the parameter and gives both lengths. Use a longer stitch length, or a shorter minimum if your machine sews shorter stitches well.
SC-W0403
Warning — Hand-placed stitch shorter than the shortest stitch; point left out
Hand-placed stitches (manual stitch) shorter than the shortest stitch the machine sews well: the needle point that made each one too short was left out, so the stitch before it runs on to the next point. A part’s last point is always kept; the one before it goes instead.
The message gives how many and the shortest of them. Move the nodes apart, or delete the extra ones.
SC-W0404
Warning — Hand-placed stitch longer than max_stitch_length_mm; kept for the shortest stitch
Hand-placed stitches (manual stitch) sewn longer than max_stitch_length_mm. A stitch longer than
that is split into equal parts no longer than it, but never into parts shorter than the shortest
stitch the machine sews well, which here took fewer parts: a shorter stitch would hammer one spot and
could break the thread. Ink/Stitch splits such a stitch into the fewest parts no longer than the
setting, however short they are.
The message gives how many stitches are longer and the longest of them. Set max_stitch_length_mm
to at least twice the shortest stitch: every split then leaves parts long enough.
SC-W0405
Warning — Zigzag stitch shorter than the shortest stitch; the zigzag sews narrower
A zigzag stroke’s stitches shorter than the shortest stitch the machine sews well, across a zigzag
too narrow for its spacing. Finishing leaves out a needle point of each such stitch, as it leaves out
any point that near the one before it (SC-I0504), so the zigzag sews narrower there. Where every
stitch is that short, every other point goes, and the zigzag sews as a line along one side.
Ink/Stitch’s shortest stitch is 0.1 mm by default, and it sews such a zigzag as it is.
The message gives how many stitches are that short and the shortest. On a straight path, a stitch
across a zigzag w wide at a spacing s is √(w² + (s/2)²) long. Make the zigzag wider, the stroke
or its pull compensation, or space it out, until that is at least the shortest stitch.
SC-W0502
Warning — Lock stitch shorter than 0.2 mm; lengthened
A lock stitch would have been shorter than 0.2 mm, the shortest stitch a lock may have. The needle would go back into the hole it just left, which can cut the thread and does not lock it. Each such step of a lock made of steps (back and forth, or a custom lock written as numbers) was lengthened to 0.2 mm, and a drawn lock was enlarged until its shortest stitch is 0.2 mm long. A lock of steps follows the stitching. Where a sharp turn would fold it onto itself, it was sewn straight along the first (or last) stitch.
The message says which lock, and by how much. Set a larger lock size (lock_start_scale_mm,
lock_end_scale_mm) or scale (lock_start_scale_percent, lock_end_scale_percent), or write
longer custom steps.
SC-W0503
Warning — Custom lock cannot be sewn as written
The lock is set to custom, but its shape cannot be sewn as written. A custom lock is numbers
separated by spaces, the steps the needle takes along the stitching in sizes of
lock_*_scale_mm, or an SVG path that draws it. StitchCraft sews the numbers. It does not sew a
drawn custom lock yet, and sews the half stitch in its place. Parts that are not numbers, and steps
longer than 10 m, are left out. If no step is left, the half stitch is sewn instead.
The message says what was wrong. Write the lock as numbers, such as 1 -1 1 -1, or choose
another lock shape.
SC-I0504
Info — Stitches shorter than the shortest stitch merged
Needle points less than the shortest stitch from a needle point next to them were left out, so the
stitch before each runs on to the next and none is shorter than the machine sews well. This happens
where one element’s stitching runs straight on into the next and they nearly touch: the stitch
between them is whatever is left of the gap. A point that stays, such as a lock point or the last
point before a jump, trim or stop, leaves out the points before it that it is too near, and a stitch
into or out of a lock point may be as short as 0.2 mm. Where small elements are sewn over each other,
a point that stays can go too, when it comes within the shortest stitch of another that stays.
Nothing visible changes, but across a zigzag too narrow for the shortest stitch, which SC-W0405
names.
The message gives how many points were left out and the shortest stitches that applied to them. Nothing needs doing. If the count is large, check for elements drawn on top of each other.
SC-W0505
Warning — Trim or stop after an element that sews nothing; left out
The element is set to trim or stop after it (trim_after, stop_after, or Ink/Stitch’s trim and
stop commands), but it sews no stitch, so there is no place for the trim or the stop: it is left
out, as Ink/Stitch leaves it out. Another message says why the element sews nothing.
Make the element sew, or set the trim or stop on the element before it.
SC-E0601
Error — Too many colour changes for the file format
The file format can record only a limited number of colour changes and stops (PES: 255). This design has more, so the file was not written.
Merge elements that use the same thread so they sew in one block, or remove stops.
SC-E0602
Error — Design too large for the file format
The design’s coordinates or stitch data do not fit the fields of this file format, so the file was not written. Within a real hoop this does not happen; it points at a design that is far larger than any hoop, or at stray objects far from the rest of the design.
Check the design’s size and remove stray objects.
SC-E0603
Error — Machine file could not be read
The machine file could not be read: it is not in the format its name or first bytes suggest, it ends early, or a record in it makes no sense. StitchCraft reads every file as if it could be damaged or hostile, so a bad file is reported, never half-read in silence.
The message says where reading stopped. If the machine sews the file, it may be a format variant StitchCraft does not know yet: please report it with the file.
SC-W0604
Warning — Thread colours unknown
The file stores no thread colours (DST files never do: they only say where the machine pauses for the next thread), so every thread has a black placeholder colour in previews. A PES file converted from this one names each thread by the Brother colour nearest black that is still free (Black, Dark Gray, Dark Brown and so on), which says nothing of the threads the design needs.
The stitches are not affected. Load the threads the design needs, in the order the design’s author gives; StitchCraft cannot know them.
SC-I0605
Info — Long jumps cut in DST
DST has no trim command: machines cut the thread when they meet three or more jump records in a row (a machine setting; three is the common one), and a jump longer than 24.2 mm takes three or more records. So the thread will be cut before such a jump, although the plan does not trim there. With a tie-off before the jump the cut is usually welcome: there is no jump thread to clip by hand.
Nothing to do. Where an element’s ties are off, turn them on so its stitching holds when it is cut, or sew a PES file, where only trims cut. A machine set to another number of jump records cuts before other jumps: see its DST setting.
SC-W0606
Warning — Threads share a Brother colour
A PES file names each thread by a colour of the Brother palette, which has 61 thread colours. StitchCraft gives each different thread a colour of its own, the nearest one still free, so the machine asks for each thread change. A design with more different threads than that has some share a colour, and the machine shows that colour for each of them: at a change between 2 of them it shows the thread already in the machine, as for a stop.
Load the threads in the order the report lists them, rather than by the colours the machine shows. Or use fewer different threads: merge those of nearly the same colour.
SC-E0701
Error — Design does not fit the hoop
The design is wider or taller than the machine’s hoop, so the machine cannot sew it in one hooping (most machines refuse the file). A design that fits the hoop can still reach past its edge where it is placed: its origin, which goes to the hoop’s centre, can lie far from its middle, and its stop position, where the frame goes before each stop, can lie outside the hoop.
If the message says the design fits when turned 90 degrees, rotate it. Otherwise scale it down, or split it into parts that are sewn in separate hoopings. Where it reaches past the edge from where it is placed, move its origin nearer its middle, or its stop position nearer the design.
SC-W0702
Warning — Design is larger than the comfort zone
The design fits the hoop but is larger than the profile’s comfort zone: the area where this machine and hoop sew most accurately. Near the hoop’s edge fabric is held less firmly, so large designs pucker and shift more.
The file was written. Rotate the design if the message says that brings it inside the comfort zone, use a firmer stabilizer, or scale the design down.
SC-I0703
Info — Stitches longer than the machine’s longest stitch; split
Stitches longer than the machine’s longest stitch were split into equal parts, each no longer than it: a long stitch placed by hand, say, or a custom lock’s long step. A long loose stitch snags and sags; the machine may refuse it.
To choose where the needle goes down instead, add nodes (manual stitch) or set
max_stitch_length_mm.
SC-E0801
Error — SVG could not be read
The SVG file could not be read: it is not well-formed XML, its root is not an <svg> element,
its size or viewBox does not read or makes no sense, or it is larger than StitchCraft reads, its
style sheets included. Nothing was stitched.
The message says what is wrong and, for XML errors, where. Open the file in a vector editor and save it again as plain SVG.
SC-W0802
Warning — SVG feature ignored
Part of the SVG uses a feature that StitchCraft does not stitch, so that part is left out or
simplified: text that is not converted to paths, raster images, clones (<use>), nested <svg>
elements, style sheet rules with selectors StitchCraft does not read, or a gradient or pattern used
as a colour. Values that do not read are named too. A colour that does not read is ignored, as a
viewer ignores it, and one of Ink/Stitch’s design settings keeps its default. A line that --auto
sews across its width, as a satin column or a fill, is as wide as its transforms stretch it on
average, so a transform that stretches it more one way than the other, and draws it wider one way,
is named too.
The message names the element and what was done. Convert text and clones to paths in the editor (in Inkscape: Path › Object to Path, Edit › Clone › Unlink Clone), and give shapes plain colours.
SC-I0805
Info — Object left out, as the file asks
An object or a layer is left out because the file asks for it: an Ink/Stitch “ignore object” or
“ignore layer” command, or the object’s Ink/Stitch setting ignore_object. Designs keep
templates, placement lines and notes this way, in the drawing but out of the sew-out, and
Ink/Stitch leaves them out too.
The message names what was left out and why. To stitch it, delete the command’s symbol, or turn the setting off in Ink/Stitch’s parameters.
SC-W0804
Warning — Element geometry invalid or out of range
An element’s geometry cannot be used: its path data or its list of points has an error (the path is stitched up to the error, as SVG viewers draw it), its transform is not valid (it is skipped with everything inside it), it draws nothing, or it lies more than 10 metres from the document’s origin, beyond what machine files can record (it is skipped). A stroke width, join or miter limit that does not read is named as well, and ignored as a viewer ignores it, and so is an empty width, height or viewBox of the drawing, or an aspect ratio that does not read. A stroke too wide to measure is skipped.
The message names the element. Check its path data, points and transform, or remove stray objects far from the design.
SC-W0803
Warning — VectorCraft file from a newer format version; read best-effort
The .vectorcraft file comes from a newer VectorCraft: its format version is newer than the newest
StitchCraft reads. StitchCraft reads it as that version, usually all of a design’s paths and paints,
where older versions of VectorCraft refuse the file.
The message gives the file’s format version and the newest StitchCraft reads. Compare the stitches with the drawing, or save a copy in that format from the newer VectorCraft: File › Save a Copy, then the Version in VectorCraft Options.
SC-E0806
Error — VectorCraft file could not be read
The .vectorcraft file could not be read: it is not a VectorCraft document (its JSON does not
read, it names another format, or it holds no document), the data VectorCraft keeps after its JSON
is damaged, or it unpacks to more JSON than StitchCraft reads. Nothing was stitched.
A StitchCraft live effect or filter gives this message when what VectorCraft passes it does not read as VectorCraft’s plug-in ABI v1 says, which a VectorCraft with another plug-in ABI would do. The effect leaves the object as it is, and the filter changes nothing.
The message says what is wrong and, for JSON errors, where. Open the file in VectorCraft and save it again. For a plug-in, install the StitchCraft plug-ins made for that version of VectorCraft.
SC-W0807
Warning — VectorCraft object or feature not stitched
Part of the VectorCraft document is not stitched: text, images, symbols, objects VectorCraft
computes as it draws them (blends, envelopes, meshes, repeats, placed documents and Intertwine
objects), a kind of object StitchCraft does not read, clipping and opacity masks, VectorCraft’s own
effects, a brush’s art, a StitchCraft effect it cannot use, or a group’s or a layer’s own fills and
strokes. A gradient is sewn in its first colour, and a pattern, or a colour that does not read, in
black. An object that StitchCraft effects reach from more than one level, its own and a group’s
around it, or 2 groups’, sews as the nearest say: VectorCraft runs each on the stitches the one
before drew, which no machine sews. A start or end marker, an object named stitch:start or
stitch:end, is never stitched, and StitchCraft does not yet start or end a shape where one says.
A name that begins with stitch: and names no marker StitchCraft reads is said too, as is a group or
a layer named as a marker, which only a shape can be: the name is ignored. A satin column, a zigzag
or a fill made of a stroke is as wide as the stroke all along and centred on its path, so a stroke
aligned inside or outside a closed path is said, as is a width profile, and round or square caps
where the stitches end at the path’s end.
The message names the object and what was done. In VectorCraft, Type › Create Outlines makes paths of text, and Object › Expand of blends, envelopes, repeats and placed documents. Break Link to Symbol makes paths of a symbol, and Object › Expand Appearance of what effects and brushes draw.
SC-W0808
Warning — VectorCraft geometry unusable
An object’s geometry cannot be used, and the object, or the stroke, is left out: a coordinate or a stroke’s width is not a number, the object draws nothing, or it lies more than 10 metres from the document’s origin, beyond what machine files can record. A StitchCraft stroke or satin effect takes a width that is not a number as 0. A StitchCraft live effect leaves such an object as it is, with this message as the plug-in’s last error.
The message names the object. Check its anchors, or remove stray objects far from the design.
SC-I0809
Info — VectorCraft layer or guide left out, as it does not print
A layer or a guide is left out because VectorCraft does not print it: a template layer, a layer whose Print option is off, or a guide. VectorCraft shows them, dimmed or as guides, and keeps them out of what it prints and exports; StitchCraft keeps them out of the sew-out.
The message names the layer or the guide. To stitch a layer, turn its Template option off, or its Print option on, in VectorCraft’s Layer Options.
SC-I0810
Info — VectorCraft live effect leaves the object as it is
A StitchCraft live effect draws the object it is applied to as its stitches, or as much of them as
its preview level shows. It leaves the object as it is when it cannot show them, and VectorCraft
shows this message as the plug-in’s last error. The object sews no stitch, or its preview level needs
more work than VectorCraft lets a live effect do. Or it is as wide as its stroke, a satin column
drawn as one path or a zigzag stroke, and VectorCraft gives the effect no stroke: before 0.14, for an
object without one, or where the stroke is a group’s. Or the effect sits on one of the object’s fills
or strokes, which stitch export doesn’t use. When another message says why an object sews no
stitch, the effect shows that message instead.
The message says which. A satin column drawn as one path, and a zigzag stroke, sew as wide as the
stroke that paints them when exported with stitch export. To see their stitches in VectorCraft, give
the object a stroke as wide as they are, or draw a satin column’s 2 rails and join them with Object ›
Compound Path › Make. Apply StitchCraft’s effects to the object itself, not to one of its fills or
strokes. For a preview that needs more work, the hint names a cheaper level to set the effect’s
preview_level to.
SC-I0811
Info — VectorCraft object left out, as its name asks
An object or a layer is left out because its name asks for it: the name is stitch:ignore, alone
or followed by white space and a note, in any mix of upper and lower case. Designs keep placement
lines, templates and notes this way, in the drawing but out of the sew-out, as Ink/Stitch’s ignore
commands keep them. Everything in a group or a layer so named is left out with it.
The message names what was left out. To stitch it, rename it in VectorCraft’s Layers panel.
SC-W0812
Warning — VectorCraft design setting ignored
A design setting that StitchCraft’s Design Settings filter keeps with a VectorCraft document does not
read: a length that is not a number of 0 or more, or a machine this StitchCraft doesn’t have, as a
document from a newer one may name. The setting keeps its default, and the design is planned for
the machine --profile or the export names, or else for the reference machine, the Brother PE800’s
5 × 7 in hoop.
The message names the setting and what it holds. To set it again, choose Object › Plug-ins › StitchCraft Design Settings in VectorCraft, which keeps every setting anew.
SC-E0813
Error — VectorCraft too old for the filter
A StitchCraft filter needs what the VectorCraft that ran it doesn’t give a filter, so it declined to run, and nothing changed. StitchCraft Design Settings keeps a document’s design settings and machine as data VectorCraft keeps with a document for a filter, from 0.16. Stroke to Satin and Test Sheet give the objects they make StitchCraft’s effects, as effect records VectorCraft keeps from 0.15. Test Sheet tells such a VectorCraft by what it gives every filter from 0.16, since a 0.15 with nothing selected looks as 0.14 does.
The message names the version the filter needs. Update VectorCraft to it or later. Until then, the
export’s Machine option and stitch export --profile name the machine, the design settings keep
their defaults, a satin column is drawn with its 2 rails and joined with Make Compound Path, and
stitch testsheet makes the test sheets.
SC-E0814
Error — VectorCraft line not made into a satin column
StitchCraft’s Stroke to Satin filter makes each selected line into a satin column drawn with rails
and rungs, which sews as the line does with the satin effect. It changed nothing, because nothing was
selected, or one of the lines can’t be made so: its path has no stroke to take the column’s width
from, or can’t be used, its rails can’t be made or would leave parts of it out, as SC-E0214 and
SC-W0213 say of a satin column drawn as one path, or the column would be read back with other rails
or rungs, as one whose rails cross each other is.
The message names the object. Select the lines, give the path a stroke as wide as the column, draw that column with its 2 rails, or leave the object out of the selection.
SC-I0901
Info — Shapes digitized automatically
--auto digitized the design’s shapes that have no settings: StitchCraft chose each one’s
stitches by its width, measured to the micrometre. An area is a tatami fill, and a part of it
narrower than the design’s narrowest satin stroke (min_satin_stroke_width_mm, 1 mm unless the
design sets it) everywhere is a running stitch along its centre line. A line no wider than that is a
running stitch, a line up to 7 mm wide a satin column as wide as the line, and a wider line a
tatami fill of the area it paints. A running stitch is tripled from 0.5 mm wide. A line whose every
part a satin column cannot follow is filled too (SC-I0902), and counted as a fill. An area whose
every part runs is counted as a running stitch, and any other as a fill.
The message counts the shapes by the stitches they sew as. To sew a shape another way, give it settings: in VectorCraft, apply a StitchCraft effect to it. A shape with settings is not digitized automatically.
SC-I0902
Info — Line a satin column cannot follow; filled
--auto sews a line up to 7 mm wide as a satin column along its path, but a satin column cannot
follow this part of the line: it turns more tightly than the line is wide, so the column’s edges
would fold over themselves, or it is shorter than a CSS pixel, or it is a dot that the line’s round
or square cap draws. The area it paints is filled instead, as a line wider than 7 mm is.
The message says which. To sew it as a satin column, draw the line with gentler turns or narrower, or draw the column with 2 rails and give it settings.
SC-W0903
Warning — Line too wide to fill; left out
--auto fills the area a line paints when the line is wider than 7 mm, or when a satin column cannot
follow it, and the area this line paints would reach more than 10 metres from the design’s origin,
beyond what machine files can record. It is left out. A stroke that wide is usually a mistake in
the file.
The message gives the line’s width. Check the line’s stroke width.
SC-I0904
Info — Areas reach under the shapes sewn after them
--auto makes each automatic area reach 0.4 mm under the shapes sewn after it, from where it touches
them. The thread pulls stitches in from the edges of their shape, and without the overlap fabric shows
between shapes that only meet. Where an area sewn after it covers part of it, as in a trace that stacks
its colours, the covered part is cut away, 0.4 mm inside the covering area’s edge, and sewn once. A
satin column or a line sewn after an area goes over it, and the area is sewn whole under it. The
running stitch along a narrow part of an area covers that part: the areas sewn before it reach
under it, and when the areas sewn after it cover the part whole it sews nothing.
The message counts the areas that reach under others and those cut away. An area that the areas sewn after it cover whole sews nothing, and the message names it, as it does a narrow part’s line. It also names an area whose neighbours need more work to find than the budget allows it, which neither reaches under them nor is cut away, and a satin column whose area needs more, which the areas before it don’t reach under, and which keeps its place in paint order beside the shapes near it. To sew a shape as it is drawn, give it settings: in VectorCraft, apply a StitchCraft effect to it.
SC-I0905
Info — Speck merged into a neighbour
--auto takes an automatic area smaller than 1 mm², or a part of one that small, for a speck the trace
left. It is too small to sew, and would sew a knot. It is merged into the automatic area it shares the
longest edge with, which may be the rest of its own area, and no hole shows where it was. A speck that
shares no edge with an automatic area is left out.
The message gives the speck’s area and says where it went: into a neighbour, back to the rest of its area, or nowhere, as when finding its neighbours needs more work than the budget allows it. To sew it as it is, give it settings: in VectorCraft, apply a StitchCraft effect to it. A larger Noise in VectorCraft’s Image Trace leaves such specks out of the trace.
SC-W0906
Warning — Background left out
--auto takes the lowest shape of the design for the picture’s background when it is an automatic
area, other shapes are drawn on it, and it covers the whole design, its holes aside, to within 0.5 mm
of each side. It is left out. A background is usually the fabric’s own colour, and sewing it doubles
the stitches and stiffens the design. A background that needs more work to find than the budget
allows is sewn, and the message says so.
To sew it, give it settings: in VectorCraft, apply a StitchCraft effect to it. Ignore White in VectorCraft’s Image Trace leaves a white background out of the trace.
SC-I0907
Info — Elements sewn in an order of their own
--auto sews the automatic elements in an order of its own, and --sort-colours every element.
Shapes that overlap as drawn keep their paint order, the lower one first, and a satin column or a
line that borders a fill sews after it, over its edge. Within those rules each thread sews as long
as an element of it is free to sew, and the next element is the nearest. Elements with settings keep
their order among themselves unless --sort-colours is given, and nothing moves across a stop.
The message says how many thread changes the order has, and how many paint order has. It also names
an element whose neighbours need more work to find than the budget allows, which keeps its place in
paint order as an element with a stop after it does, and a line whose paint can’t be found, which
keeps its place beside the shapes near it. SC-I0904 names a satin column whose paint can’t be found.
And it names the elements left in paint order when choosing the order needs more work than the budget
allows. To keep shapes in their paint order among themselves, give them settings and leave
--sort-colours off: in VectorCraft, apply a StitchCraft effect to each.
Parameters
Every setting a design element can carry, by the key a design stores it under. A parameter that Ink/Stitch also has keeps Ink/Stitch’s key, meaning and default, so files move between the two tools unchanged (compatibility contract). Where StitchCraft reads or sews one otherwise, its entry says how, and below is how reading any value differs.
StitchCraft never falls back to a default in silence. A value it cannot use is an error that says what is
accepted (SC-E0101); a value outside its range is clamped, with a warning
(SC-W0102); a key it does not know is kept unchanged, with a warning
(SC-W0105).
Writing values
Designs store every value as text, the same way for every host:
- Lengths are millimetres. They may carry a unit right after the number:
2.5,2.5mm,0.1inand7ptare all accepted, and2.5 mmis not. - Angles are degrees (
45,45°or45deg), turned into the range from −180 (exclusive) to 180. - Percentages may end in
%. - On or off is
trueorfalse, in any case.yes,y,tand1are on too, andno,n,f,0andoffoff.onis an error, since Ink/Stitch reads it as off. - Lists (several lengths, numbers or angles) are separated by spaces:
2.5 1.2. A comma separates nothing and is no decimal point, so2.5,1.2and3,5are errors that say so. An angle in a list may end with one comma (0, 90), as older files write a fill’s underlay angles. - Seeds are a number or any text. Elements given the same seed vary alike; empty means “derive it from the element”, so every element differs but every run is the same.
- Empty text, or spaces alone, is not set: the parameter takes its default, as in Ink/Stitch. Spaces, tabs and line ends round a value are left out, as Ink/Stitch strips them.
An element reads only the settings its stitch type uses, and only those its other settings show: a running stitch reads no zigzag spacing, and no length variation while random stitch length is off. A setting it does not read is kept with the element, unchecked, and changes nothing.
Programs that store values as JSON (and the JSON Schema that describes them) use
numbers, true/false, strings and arrays instead, and null for an empty value.
Where reading differs from Ink/Stitch
- DEV-PRM-001. A value StitchCraft cannot read refuses its element (SC-E0101), and the rest of the design is sewn. Ink/Stitch sews such a value as the parameter’s default, or a toggle as off, without a word: it sews 3,5 for running_stitch_length_mm as 2.5 mm stitches, and a toggle of on as off. A toggle reads yes, y, true, t and 1 as on here, as Ink/Stitch does, and only their opposites and off as off. StitchCraft reads a tab, or several spaces, between a list’s values as one space, where Ink/Stitch sews a list with a tab between values as its default, and a fill’s underlay angles with 2 spaces between them too. It also reads a unit right after a number, mm, in or pt after a length, a degree sign or deg after an angle and a percent sign after a percentage, where Ink/Stitch sews the default.
- DEV-PRM-002. Every number a parameter takes keeps to a range of StitchCraft’s, given in the parameter’s reference entry. A value outside it is clamped into it, with SC-W0102. Ink/Stitch sets a floor for most of its numbers and few ceilings, so many of these ranges are narrower than its own: repeats has no ceiling there and stops at 100 here.
- DEV-PRM-003. An optional length below 0 is read as not set, as 0 is, and SC-W0102 says so. Ink/Stitch reads it as a length and sews what that gives: with a min_jump_stitch_length_mm below 0, it ties off, jumps and ties in between every 2 groups of stitches.
- DEV-PRM-004. A list takes at most 16 values and a pair 2, and no value may be longer than 4,096 bytes: more refuses the element (SC-E0101), whose message quotes the start of a long value. Ink/Stitch reads a list of any length, takes the first 2 values of a longer pair, and reads text of any length.
Groups
| Group | Parameters | Applies to |
|---|---|---|
| Common parameters | 16 | every stitch type |
| Stroke parameters | 1 | Running stitch, Ripple stitch, Zigzag stitch, Manual stitch |
| Running parameters | 4 | Running stitch, Ripple stitch |
| Repeat parameters | 2 | Running stitch, Ripple stitch, Zigzag stitch, Manual stitch |
| Zigzag stroke parameters | 3 | Zigzag stitch |
| Satin parameters | 34 | Satin column, E-stitch, S-stitch, Zigzag satin |
| Pull parameters | 2 | Satin column, E-stitch, S-stitch, Zigzag satin, Tatami fill |
| Fill parameters | 2 | Tatami fill, Circular fill, Contour fill, Cross stitch, Guided fill, Linear gradient fill, Meander fill, Tartan fill, Legacy fill |
| Tatami parameters | 15 | Tatami fill |
Every parameter
| Key | Label | Group |
|---|---|---|
angle | Angle | Tatami parameters |
bean_stitch_repeats | Bean stitch | Repeat parameters |
center_walk_underlay | Centre walk underlay | Satin parameters |
center_walk_underlay_position | Centre walk position | Satin parameters |
center_walk_underlay_repeats | Centre walk repeats | Satin parameters |
center_walk_underlay_stitch_length_mm | Centre walk stitch length | Satin parameters |
center_walk_underlay_stitch_tolerance_mm | Centre walk tolerance | Satin parameters |
contour_underlay | Contour underlay | Satin parameters |
contour_underlay_inset_mm | Contour inset | Satin parameters |
contour_underlay_inset_percent | Contour inset (% of width) | Satin parameters |
contour_underlay_stitch_length_mm | Contour stitch length | Satin parameters |
contour_underlay_stitch_tolerance_mm | Contour tolerance | Satin parameters |
enable_random_stitch_length | Random stitch length | Running parameters |
end_at_nearest_point | End at nearest point | Satin parameters |
end_row_spacing_mm | End row spacing | Tatami parameters |
expand_mm | Expand | Tatami parameters |
fill | Sew the fill | Fill parameters |
fill_method | Fill method | Fill parameters |
fill_underlay | Underlay | Tatami parameters |
fill_underlay_angle | Underlay angle | Tatami parameters |
fill_underlay_inset_mm | Underlay inset | Tatami parameters |
fill_underlay_max_stitch_length_mm | Underlay longest stitch | Tatami parameters |
fill_underlay_row_spacing_mm | Underlay row spacing | Tatami parameters |
fill_underlay_skip_last | Skip last underlay stitch | Tatami parameters |
force_lock_stitches | Always lock | Common parameters |
gap_fill_rows | Gap-filling rows | Tatami parameters |
lock_custom_end | Custom end lock | Common parameters |
lock_custom_start | Custom start lock | Common parameters |
lock_end | End lock | Common parameters |
lock_end_scale_mm | End lock size | Common parameters |
lock_end_scale_percent | End lock scale | Common parameters |
lock_start | Start lock | Common parameters |
lock_start_scale_mm | Start lock size | Common parameters |
lock_start_scale_percent | Start lock scale | Common parameters |
max_stitch_length_mm | Longest stitch | Common parameters |
min_jump_stitch_length_mm | Shortest jump | Common parameters |
min_random_split_length_mm | Shortest split stitch | Satin parameters |
min_stitch_length_mm | Shortest stitch | Common parameters |
pull_compensation_mm | Pull compensation | Pull parameters |
pull_compensation_percent | Pull compensation (%) | Pull parameters |
push_compensation_mm | Push compensation | Satin parameters |
random_seed | Random seed | Common parameters |
random_split_jitter_percent | Split jitter | Satin parameters |
random_split_phase | Random split phase | Satin parameters |
random_stitch_length_jitter_percent | Length variation | Running parameters |
random_width_decrease_percent | Random width decrease | Satin parameters |
random_width_increase_percent | Random width increase | Satin parameters |
random_zigzag_spacing_percent | Random zigzag spacing | Satin parameters |
repeats | Repeats | Repeat parameters |
reverse_rails | Reverse rails | Satin parameters |
row_spacing_mm | Row spacing | Tatami parameters |
running_stitch_length_mm | Stitch length | Running parameters |
running_stitch_position | Running stitch position | Satin parameters |
running_stitch_tolerance_mm | Curve tolerance | Running parameters |
satin_column | Satin column | Satin parameters |
satin_method | Method | Satin parameters |
short_stitch_distance_mm | Short stitch distance | Satin parameters |
short_stitch_inset | Short stitch inset | Satin parameters |
skip_last | Skip last stitch | Tatami parameters |
split_method | Split method | Satin parameters |
split_staggers | Staggers | Satin parameters |
staggers | Staggers | Tatami parameters |
start_at_nearest_point | Start at nearest point | Satin parameters |
stop_after | Stop after | Common parameters |
stroke_method | Method | Stroke parameters |
stroke_pull_compensation_mm | Pull compensation | Zigzag stroke parameters |
swap_satin_rails | Swap rails | Satin parameters |
ties | Lock stitches | Common parameters |
trim_after | Trim after | Common parameters |
underlay_underpath | Underlay underpath | Tatami parameters |
underpath | Underpath | Tatami parameters |
zigzag_angle | Zigzag angle | Zigzag stroke parameters |
zigzag_spacing_mm | Zigzag spacing | Zigzag stroke parameters |
zigzag_spacing_mm | Zigzag spacing | Satin parameters |
zigzag_underlay | Zigzag underlay | Satin parameters |
zigzag_underlay_inset_mm | Zigzag underlay inset | Satin parameters |
zigzag_underlay_inset_percent | Zigzag underlay inset (% of width) | Satin parameters |
zigzag_underlay_max_stitch_length_mm | Zigzag underlay longest stitch | Satin parameters |
zigzag_underlay_spacing_mm | Zigzag underlay spacing | Satin parameters |
Common parameters
Settings every stitch type shares: lock stitches where the element’s stitching starts and ends, a trim or a stop after it, the shortest stitch and jump, the longest stitch, and where random variation starts. Plan assembly sews the locks, trims and stops, and finalize holds each element’s stitches to its shortest stitch.
Applies to every stitch type.
Lock stitches
ties
Lock stitches. Where the element gets lock stitches: a few tiny stitches that stop the thread from unravelling where its stitching starts and ends.
- Accepts: one of
0(At the start and the end),1(At the start),2(At the end),3(None) - Default:
0 - Ink/Stitch: same key, meaning and default
force_lock_stitches
Always lock. Lock the end of every group of stitches, and jump to the next, even where the needle would
otherwise go straight on to it. The end lock is sewn even where ties leaves it out, manual
stitch gets the locks it otherwise goes without, and a fill whose top rows expand_mm cuts into
pieces locks each piece on its own.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Ink/Stitch: same key, meaning and default
lock_start
Start lock. The shape of the lock stitches at the start. The half stitch goes back and forth over the first half of the first stitch, which hides it. The other shapes are larger and grip more.
- Accepts: one of
half_stitch(Half stitch),arrow(Arrow),back_forth(Back and forth),bowtie(Bowtie),cross(Cross),star(Star),simple(Simple),triangle(Triangle),zigzag(Zigzag),custom(Custom) - Default:
half_stitch - Ink/Stitch: same key; StitchCraft differs:
- DEV-LCK-001. Each Ink/Stitch lock id is accepted, with StitchCraft’s own shapes behind the ids and the same intent. The half stitch goes forth and back twice over half the first stitch, and back_forth twice over one step, where Ink/Stitch’s go out two steps and back. The drawn shapes are new. A custom lock drawn as an SVG path sews the half stitch, with SC-W0503, until M8.
lock_custom_start
Custom start lock. The steps a custom start lock takes along the stitching before it starts, as numbers separated
by spaces, each a number of lock_start_scale_mm. Positive steps go into the stitching. The
last step ends where the stitching starts, and 1 -1 1 -1 goes forth and back twice. Ink/Stitch
also takes an SVG path that draws the lock. StitchCraft does not sew those yet, and sews the
half stitch in its place (SC-W0503).
- Accepts: text of at most 4096 bytes
- Default: empty
- Shown when
lock_startiscustom - Ink/Stitch: same key; StitchCraft differs:
- DEV-LCK-002. Locks of steps follow the stitching round its corners, as Ink/Stitch’s custom steps do. They differ in 5 places. Past the end of stitching shorter than the lock, they go straight on from the last stitch, where Ink/Stitch puts the rest of the lock on the last needle point. Where following a sharp turn would sew a stitch shorter than 0.2 mm, the lock is sewn straight along the first (or last) stitch, with SC-W0502, where Ink/Stitch follows the turn. A step shorter than 0.2 mm is lengthened to 0.2 mm, with SC-W0502, where Ink/Stitch sews it as it is. A custom step of 0 is left out, with SC-W0503, where Ink/Stitch sews a stitch in place for it. And a custom step longer than 10 m, the data model’s reach, is left out with SC-W0503.
lock_start_scale_mm
Start lock size. How long each step of a start lock made of steps (back and forth, or custom) is.
- Accepts: a length from 0.1 to 10 mm
- Default:
0.7 - Shown when
lock_startisback_forthorcustom - Ink/Stitch: same key, meaning and default
lock_start_scale_percent
Start lock scale. How large a drawn start lock is, in percent of its own size: at 100 % it reaches about 1.4 mm along the stitching.
- Accepts: a percentage from 10 to 500
- Default:
100 - Shown when
lock_startisarrow,bowtie,cross,star,simple,triangle,zigzagorcustom - Ink/Stitch: same key, meaning and default
lock_end
End lock. The shape of the lock stitches at the end. The half stitch goes back and forth over the last half of the last stitch, which hides it. The other shapes are larger and grip more.
- Accepts: one of
half_stitch(Half stitch),arrow(Arrow),back_forth(Back and forth),bowtie(Bowtie),cross(Cross),star(Star),simple(Simple),triangle(Triangle),zigzag(Zigzag),custom(Custom) - Default:
half_stitch - Ink/Stitch: same key; StitchCraft differs:
- DEV-LCK-001. Each Ink/Stitch lock id is accepted, with StitchCraft’s own shapes behind the ids and the same intent. The half stitch goes forth and back twice over half the first stitch, and back_forth twice over one step, where Ink/Stitch’s go out two steps and back. The drawn shapes are new. A custom lock drawn as an SVG path sews the half stitch, with SC-W0503, until M8.
lock_custom_end
Custom end lock. The steps a custom end lock takes along the stitching after it ends, as numbers separated by
spaces, each a number of lock_end_scale_mm. Positive steps go back into the stitching. The
first step starts where the stitching ends, and 1 -1 1 -1 goes back and forth twice.
Ink/Stitch also takes an SVG path that draws the lock. StitchCraft does not sew those yet, and
sews the half stitch in its place (SC-W0503).
- Accepts: text of at most 4096 bytes
- Default: empty
- Shown when
lock_endiscustom - Ink/Stitch: same key; StitchCraft differs:
- DEV-LCK-002. Locks of steps follow the stitching round its corners, as Ink/Stitch’s custom steps do. They differ in 5 places. Past the end of stitching shorter than the lock, they go straight on from the last stitch, where Ink/Stitch puts the rest of the lock on the last needle point. Where following a sharp turn would sew a stitch shorter than 0.2 mm, the lock is sewn straight along the first (or last) stitch, with SC-W0502, where Ink/Stitch follows the turn. A step shorter than 0.2 mm is lengthened to 0.2 mm, with SC-W0502, where Ink/Stitch sews it as it is. A custom step of 0 is left out, with SC-W0503, where Ink/Stitch sews a stitch in place for it. And a custom step longer than 10 m, the data model’s reach, is left out with SC-W0503.
lock_end_scale_mm
End lock size. How long each step of an end lock made of steps (back and forth, or custom) is.
- Accepts: a length from 0.1 to 10 mm
- Default:
0.7 - Shown when
lock_endisback_forthorcustom - Ink/Stitch: same key, meaning and default
lock_end_scale_percent
End lock scale. How large a drawn end lock is, in percent of its own size: at 100 % it reaches about 1.4 mm along the stitching.
- Accepts: a percentage from 10 to 500
- Default:
100 - Shown when
lock_endisarrow,bowtie,cross,star,simple,triangle,zigzagorcustom - Ink/Stitch: same key, meaning and default
Trims and stops
trim_after
Trim after. Cut the thread after this element, even if the next one starts close by.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Ink/Stitch: same key, meaning and default
stop_after
Stop after. Pause the machine after this element: to add an appliqué fabric, or to check the work.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Ink/Stitch: same key, meaning and default
Shortest stitch and jump
min_stitch_length_mm
Shortest stitch. Stitches shorter than this are merged into their neighbours, so the needle does not hammer one hole. Empty: the larger of the document’s setting and the machine’s minimum.
- Accepts: a length from 0 to 10 mm, or empty (0 or less counts as empty)
- Default: empty
- Ink/Stitch: same key and default; StitchCraft differs:
- DEV-PRM-003. An optional length below 0 is read as not set, as 0 is, and SC-W0102 says so. Ink/Stitch reads it as a length and sews what that gives: with a min_jump_stitch_length_mm below 0, it ties off, jumps and ties in between every 2 groups of stitches.
- DEV-FIN-001. StitchCraft fits every stitch to the machine: it leaves out needle points less than their element’s shortest stitch from the one before, never less than the machine’s (0.3 mm on the Brother), keeping the ends of each run of stitches and its lock points unless a stitch to one would fall short, and splits stitches longer than the machine’s longest. Ink/Stitch drops stitches no longer than the element’s own shortest stitch, else 0.1 mm, and splits nothing. Along an edge square to them, a tatami fill’s rows nearer each other than the shortest stitch hand over in a stitch too short, so the next row’s first needle point is left out and a sliver at its start shows: a bar 5 mm wide at 0.25 mm on the Brother is 97 % covered. Along an edge at 45°, their ends lie 0.35 mm apart, and the handovers stay.
min_jump_stitch_length_mm
Shortest jump. A move to the next element no longer than this is sewn straight on, without lock stitches or a jump. Empty: the document’s setting.
- Accepts: a length from 0 to 20 mm, or empty (0 or less counts as empty)
- Default: empty
- Ink/Stitch: same key and default; StitchCraft differs:
- DEV-PRM-003. An optional length below 0 is read as not set, as 0 is, and SC-W0102 says so. Ink/Stitch reads it as a length and sews what that gives: with a min_jump_stitch_length_mm below 0, it ties off, jumps and ties in between every 2 groups of stitches.
Longest stitch
max_stitch_length_mm
Longest stitch. Manual stitch splits a stitch longer than this into equal parts, and a satin column splits it as its split method says. Empty, every stitch is sewn whole. A tatami fill sews its rows in stitches this long.
- Accepts: a length from 0.1 to 25 mm, or empty (0 or less counts as empty); for fills, a length from 0.1 to 25 mm, empty being the default
- Default: empty, and
4for fills - Applies to: Manual stitch, Satin column, E-stitch, S-stitch, Zigzag satin, Tatami fill
- Ink/Stitch: same key and default; StitchCraft differs:
- DEV-PRM-003. An optional length below 0 is read as not set, as 0 is, and SC-W0102 says so. Ink/Stitch reads it as a length and sews what that gives: with a min_jump_stitch_length_mm below 0, it ties off, jumps and ties in between every 2 groups of stitches.
- DEV-RUN-006. No hand-placed stitch is shorter than the shortest stitch. A node closer than that to the needle point before it is left out (SC-W0403), and a stitch longer than max_stitch_length_mm is split into no more parts than keep each at the shortest stitch or longer (SC-W0404). Ink/Stitch splits such a stitch into the fewest equal parts no longer than max_stitch_length_mm, however short they are, and its plan later leaves out stitches of at most 0.1 mm (its default), without a word.
Random variation
random_seed
Random seed. Where random variation starts: a running stitch’s random lengths, a satin column’s random widths and spacing, and a tatami fill’s random lengths. The same seed gives the same stitches, another seed others, and elements alike given one seed vary alike. Empty, each element gets its own.
- Accepts: a number or any text, or empty to derive it from the element
- Default: empty
- Applies to: Running stitch, Ripple stitch, Satin column, E-stitch, S-stitch, Zigzag satin, Tatami fill
- Ink/Stitch: same key and default; StitchCraft differs:
- DEV-RUN-005. A running stitch’s random lengths are drawn from StitchCraft’s generator (SplitMix64, seeded from random_seed, or from the element’s id when it has none), so the values differ from Ink/Stitch’s for the same seed. They are placed otherwise too. StitchCraft draws a stitch’s length and its span’s phase from 2 numbers, starts a phase only where a span starts, and scales each span’s stitches to end on its corner. Ink/Stitch draws one number for both a stitch’s length and its phase, starts a new phase after each stitch the tolerance shortened as well, and leaves what is left before a corner as the last stitch. Both key each span’s draws by its place in its subpath; StitchCraft’s keys hold the subpath’s place in the path too, where Ink/Stitch’s start again in each subpath, so subpaths drawn alike sew alike there.
- DEV-SAT-002. Random satin widths, spacing and splits are drawn from StitchCraft’s generator (SplitMix64, seeded from random_seed, or from the element’s id when it has none), so the values differ from Ink/Stitch’s for the same seed. The ranges are Ink/Stitch’s, and so is when a width or a spacing is drawn. Ink/Stitch draws each stitch’s splits from a generator of the stitch’s own, where StitchCraft draws them from the element’s, in the order the stitches are sewn.
- DEV-FILL-004. A tatami fill’s random stitch lengths are drawn from StitchCraft’s generator (SplitMix64, seeded from random_seed, or from the element’s id when it has none), in the order its row segments are sewn, so the values differ from Ink/Stitch’s for the same seed. The ranges are Ink/Stitch’s: the first point a share of the longest stitch from the segment’s start, and each next one a stitch on, longer or shorter by up to the jitter.
Stroke parameters
How a stroke is sewn.
Applies to Running stitch, Ripple stitch, Zigzag stitch, Manual stitch.
Stroke
stroke_method
Method. The stitch the path is sewn with. A running stitch goes along it and a zigzag across it, and manual
stitch puts a needle point on each of its nodes. Ripple stitch arrives in a later version, and
until then an element set to it is skipped (SC-W0011).
- Accepts: one of
running_stitch(Running stitch),ripple_stitch(Ripple stitch),zigzag_stitch(Zigzag stitch),manual_stitch(Manual stitch) - Default:
running_stitch - Ink/Stitch: same key, meaning and default
Running parameters
Running stitch: single stitches along the path, for outlines, details and travel.
Applies to Running stitch, Ripple stitch.
Running stitch
running_stitch_length_mm
Stitch length. How long each stitch is. Between corners the stitches are spread evenly, so each one is at most this long. Several lengths separated by spaces sew as a repeating pattern: “2.5 1” sews long, short, long, short. The stitches that join a satin column’s underlays are no longer than the first length.
- Accepts: 1 to 16 lengths from 0.1 to 25 mm, separated by spaces
- Default:
2.5 - Applies to: Running stitch, Ripple stitch, Satin column, E-stitch, S-stitch, Zigzag satin, Tatami fill
- Ink/Stitch: same key and default; StitchCraft differs:
- DEV-RUN-003. No running stitch is shorter than the shortest stitch, measured straight: corners keep their needle before other points do, a stitch a drop makes too long is split, and lengths below twice the shortest stitch are raised (SC-W0402). A satin column’s travel length is raised the same way, for the straight stitches that join its parts as for its line to the start and the end. Where a satin column’s parts meet nearer than the shortest stitch, the needle’s way between them gives way to the next part’s first point, and so does an underlay’s last point or the column’s start, which moves there; beside a top stitch’s end, which stays where it is, the next part’s points that near it give way instead. Ink/Stitch’s running stitch has no shortest stitch, and it joins a satin column’s parts as they are; its plan later drops stitches of at most 0.1 mm (its default), keeping the stitch before.
- DEV-SAT-005. A satin column’s travel, and a tatami fill’s, take the first of the lengths running_stitch_length_mm gives. Ink/Stitch reads a satin’s or a fill’s value as one number, and sews a value of several with 2.5 mm, the default.
running_stitch_tolerance_mm
Curve tolerance. How far a stitch may stray from a curve. A smaller tolerance follows curves more closely, with more and shorter stitches. A satin column’s way to its start and from its end keeps to it too.
- Accepts: a length from 0.01 to 5 mm
- Default:
0.2 - Applies to: Running stitch, Ripple stitch, Satin column, E-stitch, S-stitch, Zigzag satin, Tatami fill
- Ink/Stitch: same key; StitchCraft differs:
- DEV-SAT-006. A satin column’s and a tatami fill’s running_stitch_tolerance_mm defaults to 0.2 mm, the value Ink/Stitch sews when a file sets none. Ink/Stitch’s settings window shows 0.1 mm.
enable_random_stitch_length
Random stitch length. Vary the stitch lengths at random instead of spreading them evenly, or in a tatami fill, instead of placing the needle points on the stagger grid. Lines and rows sewn close together then do not line their needle holes up, which avoids moiré patterns.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Applies to: Running stitch, Ripple stitch, Tatami fill
- Ink/Stitch: same key, meaning and default
random_stitch_length_jitter_percent
Length variation. How much each stitch may be longer or shorter than the stitch length, in percent of it. A tatami
fill’s stitch length is its longest stitch. Where the random lengths start is the element’s
random_seed.
- Accepts: a percentage from 0 to 100
- Default:
10 - Shown when
enable_random_stitch_lengthistrue - Applies to: Running stitch, Ripple stitch, Tatami fill
- Ink/Stitch: same key, meaning and default
Repeat parameters
Repeats and bean stitch: sewing a line more than once, for a bolder line or one that comes back.
Applies to Running stitch, Ripple stitch, Zigzag stitch, Manual stitch.
Repeats
repeats
Repeats. How many times to sew along the path: 2 goes there and back, 3 there, back and there again. An even number ends where the path starts, which suits a line that must come back.
- Accepts: a whole number from 1 to 100
- Default:
1 - Applies to: Running stitch, Ripple stitch, Zigzag stitch
- Ink/Stitch: same key, meaning and default
bean_stitch_repeats
Bean stitch. How many times to go back and forth over each stitch: 1 sews every stitch three times (there, back and there again), 2 five times. Several numbers separated by spaces are taken in turn along the stitches: “1 0” makes every other stitch bold.
- Accepts: 1 to 16 whole numbers from 0 to 10, separated by spaces
- Default:
0 - Ink/Stitch: same key, meaning and default
Zigzag stroke parameters
Zigzag stroke: stitches across the path from side to side, as wide as the stroke.
Applies to Zigzag stitch.
Zigzag stroke
zigzag_spacing_mm
Zigzag spacing. How far it is along the path from one stitch to the next that crosses the same way, a zig and a zag together. Ink/Stitch takes several spacings separated by spaces, and sews the first.
- Accepts: 1 to 16 lengths from 0.01 to 100 mm, separated by spaces
- Default:
0.4 - Ink/Stitch: same key, meaning and default
stroke_pull_compensation_mm
Pull compensation. How much further out than half the stroke’s width the needle goes on each side, to make up for the thread pulling the fabric in. 2 values set the 2 sides: the left of the way the path goes, then the right. Negative values make the zigzag narrower.
- Accepts: a length from -10 to 10 mm, or 2 separated by a space
- Default:
0 - Ink/Stitch: same key, meaning and default
zigzag_angle
Zigzag angle. Turns every stitch away from square to the path, in degrees: positive turns clockwise as the drawing shows it. On a curve the turned stitches point like the spokes of a wheel.
- Accepts: an angle from -89 to 89 degrees
- Default:
0 - Ink/Stitch: same key, meaning and default
Satin parameters
Satin column: a band of stitches between two rails.
Applies to Satin column, E-stitch, S-stitch, Zigzag satin.
Satin column
satin_column
Satin column. Sew the path as a satin column. Two of its subpaths are the rails, the column’s edges, and the
others are rungs across both, which say which point of one rail goes with which of the other.
Without rungs, the rails’ nodes pair up instead. A path of 1 subpath is the column’s centre
line: the column is as wide as the stroke, and turns its corners as the stroke’s join does.
Off, the path is sewn as a stroke, by its stroke_method.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Applies to: Running stitch, Manual stitch, Zigzag stitch, Ripple stitch, Satin column, E-stitch, S-stitch, Zigzag satin
- Ink/Stitch: same key, meaning and default
satin_method
Method. How the column is sewn. A satin column sews stitches straight across it, from one rail to the other and back. An E-stitch sews a spike across the column at each pair and a spine along its first rail between them, an edge for appliqué. An S-stitch sews one stitch across at each pair, every other one the other way, with a step along a rail between them: split by a longest stitch, they cover the column in rows, as a fill does. A zigzag satin sews a zigzag between the rails, every stitch slanting half the spacing along.
- Accepts: one of
satin_column(Satin column),e_stitch(E-stitch),s_stitch(S-stitch),zigzag(Zigzag satin) - Default:
satin_column - Ink/Stitch: same key, meaning and default
zigzag_spacing_mm
Zigzag spacing. The distance from one pair of needle points across the column to the next. A satin column goes across and back in one spacing, an E-stitch sews a spike every spacing, and an S-stitch a stitch across, each the other way from the one before. In a zigzag satin the needle goes across and back in one spacing too, its points on the 2 rails half a spacing apart. It is measured across the column at its outside edge. On a curve the stitches lie closer together at the inside edge.
- Accepts: a length from 0.01 to 10 mm
- Default:
0.4 - Ink/Stitch: same key, meaning and default
Rails
reverse_rails
Reverse rails. Which rails are sewn against the way they are drawn, so that both run the same way. Automatic reverses the second rail when it runs against the first.
- Accepts: one of
automatic(Automatic),none(Neither),first(The first),second(The second),both(Both) - Default:
automatic - Ink/Stitch: same key, meaning and default
swap_satin_rails
Swap rails. Make the second rail the first. The column starts on its first rail, and each stitch across it goes from the first rail to the second.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Ink/Stitch: same key, meaning and default
Start and end
start_at_nearest_point
Start at nearest point. Start near where the elements before left the needle: on the line between the rails, or on the column’s edge when only that is close, then along the line under the column to its start. It saves a jump and a trim.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
true - Ink/Stitch: same key, meaning and default
end_at_nearest_point
End at nearest point. End near where the next element starts: the column is sewn up to the end point, along the line under it to its end, and back, and ends on its edge nearest the next element.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
true - Ink/Stitch: same key, meaning and default
running_stitch_position
Running stitch position. Where the line the needle follows to the start and to the end runs, in percent of the way from the first rail to the second: 50 is the middle.
- Accepts: a percentage from 0 to 100
- Default:
50 - Ink/Stitch: same key, meaning and default
Compensation
push_compensation_mm
Push compensation. How much shorter the column is made at its start and its end. Satin stitches push the fabric out along the column, so it comes out longer than drawn, and this makes up for it. Negative values lengthen the column. 2 values set the start, then the end.
- Accepts: a length from -10 to 10 mm, or 2 separated by a space
- Default:
0 - Ink/Stitch: same key, meaning and default
Random variation
random_width_decrease_percent
Random width decrease. How much narrower than the compensated column a stitch may come out on each side, chosen at random for each stitch, in percent of the column’s width there. A ragged edge looks like fur or grass. 2 values set the first rail’s side, then the second’s.
- Accepts: a percentage from 0 to 100, or 2 separated by a space
- Default:
0 - Ink/Stitch: same key, meaning and default
random_width_increase_percent
Random width increase. How much wider than the compensated column a stitch may come out on each side, chosen at random for each stitch, in percent of the column’s width there. 2 values set the first rail’s side, then the second’s.
- Accepts: a percentage from 0 to 100, or 2 separated by a space
- Default:
0 - Ink/Stitch: same key, meaning and default
random_zigzag_spacing_percent
Random zigzag spacing. How much the distance to each stitch may differ from the zigzag spacing, chosen at random, in percent of the spacing, longer or shorter.
- Accepts: a percentage from 0 to 100
- Default:
0 - Ink/Stitch: same key, meaning and default
Short stitches
short_stitch_inset
Short stitch inset. How far a crowded needle point is moved in along its stitch, in percent of the stitch’s width. On the inside of a tight curve the needle points of a rail crowd together, and the thread piles up there. Moving some of them in spreads them out. Points that crowd one after another take turns with several values separated by spaces.
- Accepts: 1 to 16 percentages from 0 to 50, separated by spaces
- Default:
15 - Ink/Stitch: same key, meaning and default
short_stitch_distance_mm
Short stitch distance. How close a needle point may come to the last one left in place on its rail before it is moved in. 0 moves none.
- Accepts: a length from 0 to 5 mm
- Default:
0.25 - Ink/Stitch: same key, meaning and default
Split stitches
split_method
Split method. How stitches longer than the longest stitch (max_stitch_length_mm) are split. Default splits
each into the fewest equal parts no longer than it. Simple splits at whole multiples of it from
the stitch’s start. Staggered moves those splits along from one stitch to the next, so the needle
holes of neighbouring stitches do not line up in a row.
- Accepts: one of
default(Default),simple(Simple),staggered(Staggered) - Default:
default - Ink/Stitch: same key, meaning and default
random_split_jitter_percent
Split jitter. How far each split may move at random, in percent of a part, either way. With a random split phase, how much each part’s length may vary instead.
- Accepts: a percentage from 0 to 100
- Default:
0 - Shown when
split_methodisdefault - Ink/Stitch: same key, meaning and default
random_split_phase
Random split phase. Start each stitch’s splits at a random distance from its start, and space them by the longest stitch, instead of dividing the stitch evenly. The needle holes of neighbouring stitches then fall apart, at the cost of a few more stitches.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Shown when
split_methodisdefault - Ink/Stitch: same key, meaning and default
min_random_split_length_mm
Shortest split stitch. With a random split phase, also split stitches longer than this but no longer than the longest stitch. 0 splits every stitch. Empty: the longest stitch.
- Accepts: a length from 0 to 25 mm, or empty
- Default: empty
- Shown when
split_methodisdefault - Ink/Stitch: same key, meaning and default
split_staggers
Staggers. How many stitches the staggered splits take to come back to where they started. A fraction draws diagonals that show less than whole numbers do.
- Accepts: a number from 0.01 to 100
- Default:
4 - Shown when
split_methodisstaggered - Ink/Stitch: same key, meaning and default
Centre walk underlay
center_walk_underlay
Centre walk underlay. Sew a running stitch along the middle of the column first, there and back. It holds the fabric still along the column, and suits narrow columns. The other underlays and the top stitches follow it.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Ink/Stitch: same key, meaning and default
center_walk_underlay_stitch_length_mm
Centre walk stitch length. How long each stitch of the centre walk is. Between corners its stitches are spread evenly, so each one is at most this long.
- Accepts: a length from 0.1 to 25 mm
- Default:
3 - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-RUN-003. No running stitch is shorter than the shortest stitch, measured straight: corners keep their needle before other points do, a stitch a drop makes too long is split, and lengths below twice the shortest stitch are raised (SC-W0402). A satin column’s travel length is raised the same way, for the straight stitches that join its parts as for its line to the start and the end. Where a satin column’s parts meet nearer than the shortest stitch, the needle’s way between them gives way to the next part’s first point, and so does an underlay’s last point or the column’s start, which moves there; beside a top stitch’s end, which stays where it is, the next part’s points that near it give way instead. Ink/Stitch’s running stitch has no shortest stitch, and it joins a satin column’s parts as they are; its plan later drops stitches of at most 0.1 mm (its default), keeping the stitch before.
center_walk_underlay_stitch_tolerance_mm
Centre walk tolerance. How far a centre walk stitch may stray from the middle of the column on a curve. A smaller tolerance follows curves more closely, with more and shorter stitches.
- Accepts: a length from 0.01 to 5 mm
- Default:
0.2 - Ink/Stitch: same key; StitchCraft differs:
- DEV-SAT-004. The centre walk underlay keeps within center_walk_underlay_stitch_tolerance_mm of its line, and follows points that far apart. Ink/Stitch declares that parameter but uses running_stitch_tolerance_mm in its place, the tolerance of the satin’s travel to its start and end. Both are 0.2 mm unless a file sets them.
center_walk_underlay_repeats
Centre walk repeats. How many times the centre walk is sewn: 2 goes there and back, 3 there, back and there again. An odd number ends at the column’s end, and the rest of the column is then sewn from its end.
- Accepts: a whole number from 1 to 100
- Default:
2 - Ink/Stitch: same key, meaning and default
center_walk_underlay_position
Centre walk position. Where the centre walk runs, in percent of the way from the first rail to the second: 50 is the middle.
- Accepts: a percentage from 0 to 100
- Default:
50 - Ink/Stitch: same key, meaning and default
Contour underlay
contour_underlay
Contour underlay. Sew a running stitch along each edge of the column, a little inside it, after any centre walk. It holds the edges still and keeps them crisp.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-SAT-011. After an odd centre walk, which turns a zigzag satin’s run round, StitchCraft stops the contour underlay’s sides where it stops them without one: at the zigzag’s last point on the first rail, and its first and last on the second, in the order the column places them. Ink/Stitch looks for those points in the order the top stitches are sewn, after it has turned them round, and finds the first points where it means the last. Its first side is not cut, and its second shrinks to a stub: on a column 10 mm long and 4 mm wide at a zigzag spacing of 2 mm, 0.2 mm long.
contour_underlay_stitch_length_mm
Contour stitch length. How long each stitch of the contour is. Between corners its stitches are spread evenly, so each one is at most this long.
- Accepts: a length from 0.1 to 25 mm
- Default:
3 - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-RUN-003. No running stitch is shorter than the shortest stitch, measured straight: corners keep their needle before other points do, a stitch a drop makes too long is split, and lengths below twice the shortest stitch are raised (SC-W0402). A satin column’s travel length is raised the same way, for the straight stitches that join its parts as for its line to the start and the end. Where a satin column’s parts meet nearer than the shortest stitch, the needle’s way between them gives way to the next part’s first point, and so does an underlay’s last point or the column’s start, which moves there; beside a top stitch’s end, which stays where it is, the next part’s points that near it give way instead. Ink/Stitch’s running stitch has no shortest stitch, and it joins a satin column’s parts as they are; its plan later drops stitches of at most 0.1 mm (its default), keeping the stitch before.
contour_underlay_stitch_tolerance_mm
Contour tolerance. How far a contour stitch may stray from its line on a curve. A smaller tolerance follows curves more closely, with more and shorter stitches.
- Accepts: a length from 0.01 to 5 mm
- Default:
0.2 - Ink/Stitch: same key, meaning and default
contour_underlay_inset_mm
Contour inset. How far inside each rail the contour runs, so that it does not show past the top stitches. It also stops short of the column’s start by the first rail’s value and of its end by the second’s. 2 values set the first rail’s side, then the second’s.
- Accepts: a length from -10 to 10 mm, or 2 separated by a space
- Default:
0.4 - Ink/Stitch: same key, meaning and default
contour_underlay_inset_percent
Contour inset (% of width). More inset, in percent of the column’s width at each point, added to the length above. 2 values set the first rail’s side, then the second’s.
- Accepts: a percentage from -100 to 100, or 2 separated by a space
- Default:
0 - Ink/Stitch: same key, meaning and default
Zigzag underlay
zigzag_underlay
Zigzag underlay. Sew a sparse zigzag across the column, to its end and back, before the top stitches. It lifts them off the fabric and makes a wide column look fuller.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Ink/Stitch: same key, meaning and default
zigzag_underlay_spacing_mm
Zigzag underlay spacing. How far apart the zigzag’s points on each rail are, each way.
- Accepts: a length from 0.01 to 10 mm
- Default:
3 - Ink/Stitch: same key, meaning and default
zigzag_underlay_inset_mm
Zigzag underlay inset. How far inside each rail the zigzag’s points are. Empty: half the contour’s inset, which puts them between the contour and the edge. 2 values set the first rail’s side, then the second’s.
- Accepts: a length from -10 to 10 mm, or 2 separated by a space, or empty
- Default: empty
- Ink/Stitch: same key, meaning and default
zigzag_underlay_inset_percent
Zigzag underlay inset (% of width). More inset, in percent of the column’s width at each point, added to the length above. Empty: half the contour’s. 2 values set the first rail’s side, then the second’s.
- Accepts: a percentage from -100 to 100, or 2 separated by a space, or empty
- Default: empty
- Ink/Stitch: same key, meaning and default
zigzag_underlay_max_stitch_length_mm
Zigzag underlay longest stitch. Split zigzag stitches longer than this into equal parts. Empty: none is split.
- Accepts: a length from 0.1 to 25 mm, or empty (0 or less counts as empty)
- Default: empty
- Ink/Stitch: same key and default; StitchCraft differs:
- DEV-PRM-003. An optional length below 0 is read as not set, as 0 is, and SC-W0102 says so. Ink/Stitch reads it as a length and sews what that gives: with a min_jump_stitch_length_mm below 0, it ties off, jumps and ties in between every 2 groups of stitches.
Pull parameters
Pull compensation: how far a satin column’s stitches and a tatami fill’s rows reach past the shape’s edge, to make up for the thread pulling the fabric in.
Applies to Satin column, E-stitch, S-stitch, Zigzag satin, Tatami fill.
Pull compensation
pull_compensation_mm
Pull compensation. How far each end of every stitch reaches past the shape’s edge: a satin column’s stitches past its rails, and a fill’s rows past its outline and its holes. The thread pulls the fabric in along the stitches as it sews, so a shape comes out smaller than drawn, and this makes up for it. 2 values set the 2 ends: a satin’s first rail’s side, then the second’s, or the end each fill row starts from at the fill’s angle, then the other (at 0°, the left end, then the right). Negative values make a satin narrower; a fill reads them as 0, as Ink/Stitch does.
- Accepts: a length from -10 to 10 mm, or 2 separated by a space
- Default:
0 - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-FILL-009. A fill’s pull compensation lengthens each top row at its ends, and the part keeps its holes and its pieces as drawn. Ink/Stitch rebuilds the part round the lengthened rows instead: each is widened by half the row spacing and 0.01 px with round ends, the widened rows are joined, and the outline is smoothed. Its rows then reach past the compensation by up to half a row spacing, less what the smoothing takes off. A hole of less than a row spacing squared, or one the widened rows close over, is filled. Where the widened rows fall into pieces, only the largest is kept, so a part whose rows a neck too thin for a row divides loses all but its largest side.
pull_compensation_percent
Pull compensation (%). More pull compensation, in percent of each stitch’s length, added to the length above: a satin’s stitches reach out by a share of the column’s width there, and a fill’s rows by a share of each row’s length, so wide parts and long rows reach further. 2 values set the 2 ends, as above. A fill reads negative values as 0, as Ink/Stitch does.
- Accepts: a percentage from -100 to 100, or 2 separated by a space
- Default:
0 - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-FILL-009. A fill’s pull compensation lengthens each top row at its ends, and the part keeps its holes and its pieces as drawn. Ink/Stitch rebuilds the part round the lengthened rows instead: each is widened by half the row spacing and 0.01 px with round ends, the widened rows are joined, and the outline is smoothed. Its rows then reach past the compensation by up to half a row spacing, less what the smoothing takes off. A hole of less than a row spacing squared, or one the widened rows close over, is filled. Where the widened rows fall into pieces, only the largest is kept, so a part whose rows a neck too thin for a row divides loses all but its largest side.
Fill parameters
How a fill is sewn.
Applies to Tatami fill, Circular fill, Contour fill, Cross stitch, Guided fill, Linear gradient fill, Meander fill, Tartan fill, Legacy fill.
Fill
fill
Sew the fill. Whether the fill is sewn. Off, it sews nothing, and the shape’s outline, if it has one, is sewn as its stroke settings say. The element before it still ends near it, as Ink/Stitch’s does.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
true - Ink/Stitch: same key, meaning and default
fill_method
Fill method. The stitch the area is filled with. A tatami fill sews rows of running stitches across it, and a
fill set to Ink/Stitch’s legacy fill is sewn as a tatami fill without underlay (SC-W0314). The
other methods arrive in later versions; until then an element set to one is skipped
(SC-W0011).
- Accepts: one of
tatami_fill(Tatami fill),circular_fill(Circular fill),contour_fill(Contour fill),cross_stitch(Cross stitch),guided_fill(Guided fill),linear_gradient_fill(Linear gradient fill),meander_fill(Meander fill),tartan_fill(Tartan fill),legacy_fill(Legacy fill) - Default:
tatami_fill - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-FILL-014. A fill whose fill_method is legacy_fill is sewn as a tatami fill without underlay (SC-W0314), its rows routed and joined by travel stitches as a tatami fill’s are, from near the needle to near the next element. Ink/Stitch’s legacy fill sews each stretch of rows that join on its own, back and forth, with no travel stitches between stretches; its flip starts the rows from the other side, and its reverse sews the whole fill backwards. StitchCraft keeps both settings without reading them (SC-W0105).
Tatami parameters
Tatami fill: rows of running stitches across the fill’s area, the needle points of neighbouring rows offset so that they never line up. Ink/Stitch’s default fill.
Applies to Tatami fill.
Rows
angle
Angle. The direction of the rows, in degrees counter-clockwise from horizontal: 0 sews horizontal rows and 90 upright ones.
- Accepts: an angle in degrees
- Default:
0 - Ink/Stitch: same key, meaning and default
row_spacing_mm
Row spacing. How far apart the rows are, measured square to them. Rows closer together cover the fabric more fully, and pull it in more.
- Accepts: a length from 0.1 to 100 mm
- Default:
0.25 - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-FILL-012. A point of a part’s outline within 1e-9 mm of a row lies on it, and a row within 1e-9 mm of the part’s far side lies on that side and is left out. Each row lies a whole number of row spacings past the part’s first row, multiplied out. Ink/Stitch adds the row spacing in CSS pixels from row to row, and cuts each row with the part exactly, once the part is put on a grid of 1e-10 pixels. So a row on a side runs along it, crosses the part a hair beside it or misses it as the numbers round, and a part can have a row a hair inside its far side.
end_row_spacing_mm
End row spacing. The spacing of the last rows, for a fill that fades: from the first row the spacing changes steadily towards it, reaching it the part’s height away, in each part of the fill. Empty, every row is the row spacing from the next.
- Accepts: a length from 0.1 to 100 mm, or empty (0 or less counts as empty)
- Default: empty
- Ink/Stitch: same key; StitchCraft differs:
- DEV-FILL-003. With end_row_spacing_mm, the step from row to row changes from the row spacing to the end spacing over the part’s height, measured from the first row, and stays at the end spacing beyond it. Ink/Stitch carries the change on past the height, which reaches the rows within a row spacing of the part’s far side when the first row lies before the part. With an end spacing smaller than the row spacing, over a part a few rows tall, the steps then shrink towards 0 and the rows pile up short of the far side without end.
- DEV-PRM-003. An optional length below 0 is read as not set, as 0 is, and SC-W0102 says so. Ink/Stitch reads it as a length and sews what that gives: with a min_jump_stitch_length_mm below 0, it ties off, jumps and ties in between every 2 groups of stitches.
expand_mm
Expand. How far past the drawn edge the top rows reach: each part is grown by this much, with round corners, before its rows are laid. The thread’s pull opens gaps between fills sewn side by side, and this closes them. Negative values pull the rows in from the edge.
- Accepts: a length from -10 to 10 mm
- Default:
0 - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-FILL-008. Where fill_underlay_inset_mm, or a negative expand_mm, cuts a part into pieces, the pieces are sewn in order of their point of least x, and of least y among pieces level with each other, each with its least x within a nanometre of the one before. GEOS gives them in the order it walks its graph, which Ink/Stitch sews them in. Of 2,000 random polygons grown or shrunk, 85 gave 2 or more pieces, and 81 of those came in the same order; the 4 others had a sliver among their pieces. For pieces level at their least x, GEOS’s order turns on where the ring’s points start: 2 squares one above the other, shrunk apart, give the lower first when the ring is listed from the bottom and the upper first when it is listed from the top, and 310 of 400 random such shapes, listed from random points, gave the lower first. Each piece’s top rows start at the point nearest the needle after the piece before, where Ink/Stitch starts every piece’s top rows nearest the needle as it was before the first.
gap_fill_rows
Gap-filling rows. Rows sewn again where the fill leaves a stretch of more than 3 rows for another: that many rows past the stretch’s last row, back and forth, before the needle travels on. The rows on the far side are sewn later, and the fabric between can be pulled apart by then; these close the gap. Rounded up to an even number, so the needle comes back to where the stretch ended.
- Accepts: a whole number from 0 to 100
- Default:
0 - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-FILL-010. Each gap-fill row is the next row of the grid past the stretch’s last row, the stretch of its segment beside the gap-fill row before it, or the last row for the first, so it stops at the part’s edge, and the rows stop where the part ends. Ink/Stitch repeats the last row, moved a spacing further each time and keeping its length, so a repeat can run outside the part; its spacing is the distance between the last 2 rows, where the grid’s rows follow the end row spacing. The hops between gap-fill rows and the way back are running stitches kept inside the part as travel is, where Ink/Stitch stitches them as rows, straight.
Stitches
staggers
Staggers. How many rows the needle points take to come back to where they started. Along each row they lie
the longest stitch (max_stitch_length_mm) apart, a staggers-th of a stitch along from the
row before’s, so that neighbouring rows never line them up. A fraction draws diagonals that show
less than whole numbers do. A negative number staggers the rows the other way, and 0 counts as 1,
which lines every row’s points up, as in Ink/Stitch.
- Accepts: a number from -100 to 100
- Default:
4 - Ink/Stitch: same key, meaning and default
skip_last
Skip last stitch. Leave out the needle point at the end of each row. It lies close to the start of the next row, and leaving it out lowers the stitch count and the density along the fill’s edge.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Ink/Stitch: same key, meaning and default
Travel
underpath
Underpath. Travel between rows runs inside the fill, under rows sewn after it, which hide it. Off, the needle travels along the fill’s outline, where the travel can show along the edge.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
true - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-FILL-011. With underpath off, or in a part the gratings of lines miss, the needle travels between 2 points of one ring the shorter way round it, by length, and on a tie the way that does not pass the ring’s start. Ink/Stitch’s travel along the outline counts nodes instead: its graph has a node every CSS pixel along the outline’s sides, and at their corners and the rows’ ends, and its edges carry no length, so the way with fewer nodes is taken. That is about the shorter way, but a way past many short sides, as round a curve drawn in short pieces, has more nodes for its length, and the way with fewer nodes can be the longer.
- DEV-FILL-013. In a part the gratings of lines miss, with underpath on, the needle travels along the part’s rings, as with underpath off. Ink/Stitch also travels along the outline there, but it smooths every way it travels with underpath on, as it smooths a way under the rows: the way cuts the outline’s corners inside the part, and is kept inside it where it leaves it.
Underlay
fill_underlay
Underlay. Sew underlay before the top rows: rows of stitches farther apart than the top rows, square to them unless set otherwise. It holds the fabric still while the fill is sewn and lifts the top rows, so less fabric shows between them.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
true - Ink/Stitch: same key, meaning and default
fill_underlay_angle
Underlay angle. The direction of the underlay’s rows, in degrees counter-clockwise from horizontal. Several angles separated by spaces sew a layer of underlay at each, one after another. Empty: square to the top rows, at the fill’s angle plus 90°.
- Accepts: 1 to 16 angles in degrees, separated by spaces, or empty
- Default: empty
- Ink/Stitch: same key, meaning and default
fill_underlay_row_spacing_mm
Underlay row spacing. How far apart the underlay’s rows are, measured square to them. Empty: 3 times the row spacing.
- Accepts: a length from 0.1 to 100 mm, or empty (0 or less counts as empty)
- Default: empty
- Ink/Stitch: same key and default; StitchCraft differs:
- DEV-PRM-003. An optional length below 0 is read as not set, as 0 is, and SC-W0102 says so. Ink/Stitch reads it as a length and sews what that gives: with a min_jump_stitch_length_mm below 0, it ties off, jumps and ties in between every 2 groups of stitches.
fill_underlay_max_stitch_length_mm
Underlay longest stitch. How far apart the needle points along the underlay’s rows are. Empty: the fill’s longest stitch
(max_stitch_length_mm).
- Accepts: a length from 0.1 to 25 mm, or empty (0 or less counts as empty)
- Default: empty
- Ink/Stitch: same key and default; StitchCraft differs:
- DEV-PRM-003. An optional length below 0 is read as not set, as 0 is, and SC-W0102 says so. Ink/Stitch reads it as a length and sews what that gives: with a min_jump_stitch_length_mm below 0, it ties off, jumps and ties in between every 2 groups of stitches.
fill_underlay_inset_mm
Underlay inset. How far inside the fill’s edge its underlay stops: each part is shrunk by this much, with round corners, before the underlay’s rows are laid, so that none of the underlay shows beyond the top rows. Negative values let it reach past the edge.
- Accepts: a length from -10 to 10 mm
- Default:
0 - Ink/Stitch: same key and default; StitchCraft differs:
- DEV-FILL-008. Where fill_underlay_inset_mm, or a negative expand_mm, cuts a part into pieces, the pieces are sewn in order of their point of least x, and of least y among pieces level with each other, each with its least x within a nanometre of the one before. GEOS gives them in the order it walks its graph, which Ink/Stitch sews them in. Of 2,000 random polygons grown or shrunk, 85 gave 2 or more pieces, and 81 of those came in the same order; the 4 others had a sliver among their pieces. For pieces level at their least x, GEOS’s order turns on where the ring’s points start: 2 squares one above the other, shrunk apart, give the lower first when the ring is listed from the bottom and the upper first when it is listed from the top, and 310 of 400 random such shapes, listed from random points, gave the lower first. Each piece’s top rows start at the point nearest the needle after the piece before, where Ink/Stitch starts every piece’s top rows nearest the needle as it was before the first.
fill_underlay_skip_last
Skip last underlay stitch. Leave out the needle point at the end of each underlay row, as skip_last does for the top rows.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
false - Ink/Stitch: same key, meaning and default
underlay_underpath
Underlay underpath. Travel between the underlay’s rows runs inside the fill, under the underlay rows sewn after it. Off, the needle travels along the fill’s outline.
- Accepts: true or false, in any case, or yes, y, t or 1 for true and no, n, f, 0 or off for false
- Default:
true - Ink/Stitch: same key, meaning and default
Glossary
| Term | Meaning |
|---|---|
| Bean stitch | A running stitch where each stitch is sewn back and forth (3, 5, … passes) for a bold line |
| Bird’s nest | A tangle of thread under the fabric, usually from bobbin or tension problems |
| Collapse length | Moves shorter than this are sewn instead of jumped (default 3 mm) |
| Colour block | A run of stitches in one thread colour; blocks are separated by colour changes |
| Comfort zone | The part of the hoop a machine profile may recommend keeping designs inside, with a warning for larger ones. No built-in profile has one yet |
| Contour fill | A fill whose rows follow the shape’s outline inward |
| Density | How close rows or zigzags are (row spacing / zigzag spacing) |
| Hoop | The frame that holds fabric; its sewable field is a machine profile’s hard limit |
| Jump | A move between stitches without sewing; the thread spans the gap until trimmed |
| Lock stitch (tie-in, tie-off) | Tiny stitches that secure the thread at the start or end of a section |
| Needle penetration | One drop of the needle; consecutive penetrations make a stitch |
| PES / PEC | Brother’s design format; the PEC block inside a PES file holds the stitches |
| Profile (machine profile) | StitchCraft’s description of a machine: hoop, comfort zone, file format, stitch limits, trims, palette |
| Pull compensation | Widening shapes so they come out the drawn size after thread tension pulls the fabric |
| Push | Fabric moving outward along the stitch direction; columns grow at their ends |
| Rail | One of the two edges of a satin column |
| Row spacing | Distance between rows of a fill |
| Rung | A short line across a satin’s rails that fixes which points correspond |
| Running stitch | A line of single stitches |
| Satin column | Stitches swinging between two rails; glossy bands for borders and lettering |
| Stabilizer | Backing material that keeps fabric from distorting while stitching |
| Stagger | The offset of needle points between neighbouring fill rows; staggers is how many rows before the pattern repeats |
| Stitch plan | The full ordered list of stitches, jumps, trims and colour changes for a design |
| Tatami | The standard fill: parallel rows with staggered needle points |
| Travel | Running stitches that move the needle inside a shape, hidden by later stitching |
| Trim | A thread cut performed by the machine |
| Underlay | A light foundation layer sewn before the top stitches |
| Zigzag spacing | Peak-to-peak distance of a satin’s zigzag |
VectorCraft compatibility
The VectorCraft releases the StitchCraft plug-ins work with. Each passed both levels of the compatibility gate: the plug-in contract, in VectorCraft’s own plug-in host, and the end-to-end scenarios, in which VectorCraft’s command line applies the effects, saves, and StitchCraft sews what it saved. The gate checks VectorCraft’s latest release, its latest pre-release, its release branch and its main every day. A release that passes is added here by the pull request that adopts it.
| VectorCraft | Track | Passed the gate |
|---|---|---|
| 0.18.0 | stable | 2026-10-29 |
| 0.17.0 | stable | 2026-10-25 |
| 0.16.0 | stable | 2026-10-25 |
| 0.13.1 | stable | 2026-10-18 |
| 0.12.0 | stable | 2026-10-18 |
The plug-ins speak VectorCraft’s plug-in ABI v1. Pull requests are checked against VectorCraft 0.18.0, the release the gate pins in compat/vectorcraft.toml.
Design documents
These pages are the specification StitchCraft is built from. They describe behaviour and algorithms in our own words and never carry Ink/Stitch’s code (ADR-0012).
Reading order
- Technical design document — the whole system in 25 minutes.
- Architecture — crates, layers, where code goes.
- Data model, parameter registry, diagnostics.
- Engine pipeline, the stitch generators and image to embroidery.
- Machine formats and rendering previews.
- VectorCraft integration, the plug-in features RFC and the compatibility gate.
- Quality: conformance, determinism, guardrails, docs pipeline.
- File compatibility: SVG input and the Ink/Stitch compatibility contract.
- Decisions: ADRs.
Keeping these pages true
- A design page changes in the same PR as the behaviour it describes. The comment
<!-- implements: … -->below its title names the source files it describes.cargo xtask docs for PATHlists the pages for a file.cargo xtask docs --checkfails when a branch changes those files and leaves the page as it was, unless a commit message on the branch saysDesign-reviewed: <page>. - Pages cite requirement ids (
REQ-…) and diagnostic codes (SC-…); the docs check fails on unknown ids. - Facts about Ink/Stitch and VectorCraft name the commit they were checked against.
StitchCraft — Technical Design Document
| Status | Draft 0.1 — design approved for M0/M1, later sections refined as milestones land |
| Date | 2026-10-08 |
| Scope | A Rust machine-embroidery engine and its integration with VectorCraft |
| Reading time | ~25 minutes; every section links to the detailed design it summarizes |
StitchCraft turns vector art into machine embroidery. It is two things that ship together:
- An engine — host-agnostic Rust crates that turn shapes plus embroidery parameters into a stitch plan and encode it as machine files (PES for Brother first, then DST, EXP, JEF, VP3 …).
- A VectorCraft plug-in — the engine inside VectorCraft, ArtCraft’s open-source, clean-room Illustrator written in Rust, so embroidery is designed where the art is drawn.
It covers the common machine-embroidery stitch types (running, bean, satin, tatami fill, contour, meander, ripple, …) and reads the embroidery parameters Ink/Stitch stores in SVG files, so designs move between the tools. Its quality bar: it never crashes, it produces the same file on every platform, and every behaviour is pinned by a conformance suite and by sew-outs on a real machine.
1. Goals
| ID | Goal | How we know |
|---|---|---|
| G1 | Read and write every parameter in the Ink/Stitch compatibility contract, in phases | Compatibility contract coverage column reaches 100 % for phase P1–P3 rows |
| G2 | Never crash on any input | No-panic lints, fuzzing, bounded work budgets (guardrails, robustness) |
| G3 | Correctness proven, not assumed | Every requirement has a passing conformance case; machine checkpoints signed off (conformance, machine testing) |
| G4 | Determinism | Byte-identical files on Linux, macOS, Windows and wasm32 (determinism) |
| G5 | Host-agnostic engine | VectorCraft plug-in, CLI and (later) an in-tree VectorCraft crate share one engine (architecture) |
| G6 | Documentation that cannot drift from the code | Reference generated from code, images regenerated in CI on every PR (docs pipeline) |
| G7 | Code we are proud to show | Enforced layering, small modules, playbooks for contributors (guardrails) |
| G8 | Survive VectorCraft releases | Plug-in tested against VectorCraft stable, pre-releases, release and main (compatibility gate) |
Non-goals for 1.0
- An Inkscape extension.
- Photo stitch: photographs sewn in blended colours. Pictures of flat colour, such as logos, clip art and drawings, are digitized automatically from M13 (image to embroidery).
- Sending files to machines over USB/Wi-Fi; the output is a file you copy to the machine.
- A standalone GUI. The GUI is VectorCraft; the CLI renders previews to PNG.
2. Target machine and the first user
The first production target is a Brother PE800, a home embroidery machine whose largest hoop,
5 × 7 in, sews at most 130 × 180 mm, loading PES files (ADR 0013).
It becomes the built-in profile brother-pe800-5x7, the reference profile, with one profile for each of
its other hoops: brother-pe800-4x4 (100 × 100 mm) and brother-pe800-small (20 × 60 mm).
| Profile field | Value | Behaviour |
|---|---|---|
| Hoop (hard limit) | 130 × 180 mm | Error SC-E0701 if the plan does not fit |
| Comfort zone | none | A profile may have one: warning SC-W0702 if the design is larger |
| File format | PES v1 with PEC block | Changed only after a machine checkpoint proves another works |
| Max stitch length | 12.0 mm (split above) | Refined at MC-1 |
| Min stitch length | 0.3 mm (merged below) | Refined at MC-2 |
| Trims | A trim flag on every PEC jump after the first move (REQ-FMT-010) | MC-1: the PE800 cuts none of them |
| Thread palette | Brother PEC palette (nearest colour, CIEDE2000) | — |
Profiles are data (see data model); adding a machine never touches the engine.
3. Background
3.1 File compatibility with Ink/Stitch
Ink/Stitch, a GPL-3.0 extension for Inkscape, stores its embroidery settings in SVG files as
inkstitch:<name> attributes. StitchCraft reads and writes the same attribute names, values and
defaults, so a design moves between the tools unchanged. Those names, types and defaults are facts
listed in the compatibility contract; where StitchCraft’s behaviour
differs on purpose, the deviations ledger (conformance/deviations.toml) says how and why. StitchCraft
is an independent project and contains no Ink/Stitch code, documentation text or data files (ADR-0001).
3.2 VectorCraft, the host
VectorCraft (MIT OR Apache-2.0, Rust 2024, ~290,000 lines) is a clean-room Illustrator. What matters here (details and file references in VectorCraft integration):
- Plug-in ABI v1: sandboxed WebAssembly modules run by
wasmiwith no imports. Two kinds: object filters (one undo step) and live effects (rewrite one object’s geometry whenever it is drawn). Parameters come from a manifest schema (number, int, bool, choice; at most 64). - Live-effect parameters are stored per object in the document as
{"id": "plugin.<id>", "params": {…}}— so embroidery settings can live on the art today, with no VectorCraft change. - Limits that shape our design: 50 M instructions per call (+4,000 per input byte), 5 s per live-effect run, input is geometry only (no paint), no file-format plug-ins, no panels.
- Everything is a command, reachable from the UI, a JSON control channel, an MCP server and a
headless CLI (
vectorcraft-cli run --in f --cmd … --export out.png), which our tests and screenshot pipeline drive.
3.3 Licence and clean room
Ink/Stitch is GPL-3.0; VectorCraft forbids GPL code in its tree. StitchCraft is MIT OR Apache-2.0 and never copies GPL code: the design docs in this folder describe behaviour (inputs, outputs, parameters, algorithms) in our own words, and implementers work from these docs. Ink/Stitch’s source may be read to understand its behaviour, never copied or transliterated. Parameter names are kept identical to Ink/Stitch’s SVG attributes because that is the interoperability contract. See ADR-0001 and ADR-0012.
4. Requirements
4.1 Functional scope by phase
| Capability | Phase | Milestone |
|---|---|---|
| Stitch plan, Brother profile, PES + DST writers, test sheets | P1 | M1 |
| PES/DST readers, preview renderer, fuzzing | P1 | M2 |
| Running stitch (repeats, bean, random length), manual stitch, lock stitches, trims, stops, colour changes, SVG input | P1 | M3 |
| Satin column (rails/rungs, underlays, pull/push compensation, short and split stitches) | P1 | M4 |
| Tatami fill (underlay, pull compensation, staggers, gap fill, underpath travel) | P1 | M5 |
VectorCraft plug-in (ABI v1), export from .vectorcraft, compatibility gate, and the plug-in features VectorCraft added from 0.14 (exports from File › Export, labelled dialogs, design settings and a machine for each document, and the tools filter) | P1 | M6 |
| Zigzag/E/S stitches, contour, meander, circular fill | P2 | M7 |
| Image to embroidery: stitch types chosen by width, overlaps, sewing order, VectorCraft’s Auto effect | P2 | M13, between M7.5 and M7.6 |
| Ink/Stitch SVG import (attributes, commands, clones) and differential testing | P2 | M8 |
| Lettering in embroidery fonts, and text in pictures set again as lettering | P3 | M14 |
| Guided, gradient, ripple, tartan, cross stitch; auto-run/auto-satin | P3 | M10 |
| EXP, JEF, VP3, XXX, U01 formats; thread catalogues | P3 | M11 |
| 1.0 hardening, translations, release automation | — | M12 |
| VectorCraft plug-in features still proposed upstream (RFC), for previews drawn as overlays, heavy live effects and the objects beside a live effect | after 1.0 | M9, last |
The full milestone and step list is in the roadmap.
4.2 Non-functional requirements
| ID | Requirement |
|---|---|
| NFR-ROB-1 | No panics in shipped code; no unsafe outside the one audited plug-in ABI module |
| NFR-ROB-2 | All work is bounded by explicit budgets (stitches, iterations, input sizes); exceeding one is a diagnostic, never a hang |
| NFR-DET-1 | Same input and version ⇒ byte-identical output on every supported platform, including wasm32 |
| NFR-PERF-1 | A 130 × 180 mm tatami fill at 0.4 mm spacing, the reference hoop’s whole field, plans in ≤ 50 ms natively. Baseline set at M5.9: 29 ms on a 2.1 GHz Xeon, 282 million instructions, guarded by the instruction count (cargo xtask perf) |
| NFR-PERF-2 | Live-effect previews fit VectorCraft’s v1 budget (≤ 50 M instructions, ≤ 5 s) for a 130 × 180 mm design |
| NFR-PORT-1 | Engine crates build for wasm32-unknown-unknown without WASI |
| NFR-DOC-1 | 100 % of parameters, diagnostics, CLI commands and formats have generated reference pages |
| NFR-TEST-1 | Every requirement in conformance/requirements.toml has ≥ 1 passing case before its milestone closes |
| NFR-SEC-1 | Every parser of untrusted bytes (SVG, .vectorcraft, PES, DST, …) is fuzzed and resource-capped |
5. Architecture
Hosts / adapters Engine Outputs
┌────────────────────┐ ┌───────────────────────────────────────┐ ┌──────────────────┐
│ SVG (Ink/Stitch) │─┐ │ normalize ─▶ validate ─▶ generate │ │ PES / DST / … │
│ .vectorcraft file │─┼──▶ │ per element (StitchGroup) │ ──▶ │ PNG preview │
│ VectorCraft plug-in│─┘ │ ─▶ assemble plan (order, travel, │ │ JSON report │
└────────────────────┘ │ ties, trims, colours) ─▶ finalize │ │ plug-in geometry │
Design │ (profile limits) ─▶ StitchPlan │ └──────────────────┘
└───────────────────────────────────────┘
+ Diagnostics at every stage
The workspace is split into layered crates; a crate may depend only on lower layers, enforced by
cargo xtask layers:
| Layer | Crate | Responsibility |
|---|---|---|
| L0 | stitchcraft-core | Units (mm), points, deterministic math and RNG, diagnostics model |
| L0 | stitchcraft-params | The parameter registry: the single source of truth for every parameter |
| L1 | stitchcraft-plan | Stitch plan model, machine profiles, thread palettes, plan invariants |
| L2 | stitchcraft-engine | Design model, stitch generators, plan assembly |
| L2 | stitchcraft-formats | Machine file readers and writers |
| L2 | stitchcraft-render | Deterministic CPU preview renderer (docs, CLI, conformance) |
| L3 | stitchcraft-svg | SVG and Ink/Stitch-attribute adapter |
| L3 | stitchcraft-vectorcraft | .vectorcraft document adapter and effect-record mapping |
| test | stitchcraft-testkit | Fixtures, property-test strategies, invariant checkers |
| app | stitchcraft-cli (stitch) | Command-line tool |
| app | stitchcraft-vc-plugin | VectorCraft WebAssembly plug-ins |
Details, dependency rules and the reasoning are in architecture.
6. Key decisions
| ADR | Decision |
|---|---|
| 0001 | MIT OR Apache-2.0, clean-room from Ink/Stitch; keep its parameter names for interoperability |
| 0002 | Host-agnostic engine; VectorCraft ABI v1 plug-in first, new plug-in features upstream, in-tree crate optional |
| 0003 | One parameter registry generates UI schemas, CLI, docs, SVG mapping and test strategies |
| 0004 | Determinism by construction: ordered collections, libm, seeded RNG, absolute quantization |
| 0005 | kurbo for curves, i_overlay for polygon booleans/offsets, petgraph + own Eulerian walk, rstar |
| 0006 | mdBook, Diátaxis structure, generated reference, CI-regenerated images |
| 0007 | PES v1 first for the Brother machine; DST second |
| 0008 | Conformance-first development: requirement IDs and cases before code |
| 0009 | Adopt VectorCraft’s proven conventions; improve the ones that drift |
| 0010 | Every user-facing problem is a coded diagnostic with an explanation page |
| 0011 | StitchCraft can move into VectorCraft’s repository as a folder |
| 0012 | Ink/Stitch’s source may be read, never copied |
| 0013 | A Brother PE800 is the reference machine, with a profile for each of its 3 hoops |
| 0014 | Elements are generated in order, each seeing the needle before it and the next element |
7. Data model (summary)
Full types in data model.
- Units: millimetres as
f64, y pointing down (same as SVG and VectorCraft). Adapters convert (pt × 25.4/72, SVG user units through the viewBox). Encoders quantize absolute positions to 0.1 mm once, so long designs never drift. - Design (engine input): ordered
Elements —id(stable host id),shape(Region,PathorSatin),thread,params(ParamSet),commands(start/end points, trim, stop, ignore) — plus design settings (collapse length, minimum stitch length, origin) and a machine profile. - StitchPlan (engine output): colour blocks of stitches; every stitch has a position, a kind
(
Normal,Jump,Trim,Stop,ColorChange,End) and provenance (element id and role: underlay, top, travel, lock) used by previews, reports and diagnostics. - Diagnostics:
SC-E…errors andSC-W…warnings with element, location, message and a fix hint; each code has a generated explanation page (diagnostics).
8. Engine pipeline (summary)
Detailed in engine pipeline.
- Normalize each element. Curves are flattened to tolerance, and each fill’s region is built by its fill rule. Degenerate pieces are dropped with a diagnostic.
- Validate parameters against the registry (type, range, applicability) and shapes against the stitch type (e.g. a satin needs two rails).
- Generate a
StitchGroupper element with its stitch type’s generator. Generators are pure functions of (shape, typed params, seed, budget) — no globals, no I/O. - Assemble the plan: keep document order, connect groups (travel inside a region, or tie-off → jump/trim → tie-in), insert colour changes and stops, apply commands.
- Finalize against the profile: split stitches above the maximum, merge below the minimum, check the hoop and colour limits.
- Check invariants (the same checker the conformance suite uses) before any file is written.
9. Stitch generators (summary)
| Generator | Phase | Design |
|---|---|---|
| Running, bean, manual, random-length | P1 | strokes |
| Satin column (+ E, S, zigzag variants) | P1/P2 | satin |
| Tatami fill | P1 | fills |
| Contour, meander, circular | P2 | fills |
| Guided, linear gradient, tartan, cross stitch | P3 | fills |
| Ripple, zigzag stroke | P2/P3 | strokes |
| Lock stitches, travel, ordering | P1 | engine pipeline |
Generators follow Ink/Stitch’s stitch placement, worked out from its behaviour and from published
algorithms such as an Eulerian path through a fill’s rows, or offset curves for a contour fill’s rings.
Each generator’s page cites them. Where one differs, conformance/deviations.toml says how and
why.
10. Machine formats
PES (v1 header + PEC block) and DST first, then EXP, JEF, VP3, XXX, U01. Writers quantize once, encode commands per the format’s rules and validate against limits (displacement per record, colour count). Readers are bounded and fuzzed. pyembroidery (MIT) is used in CI as an independent oracle: it must read our files back to the same stitches. Details: formats.
11. Robustness
- Shipped code has no
unwrap,expect,panic!,unreachable!,todo!,unimplemented!, and no unchecked indexing on data; enforced by workspace lints (adopted from VectorCraft). - Every generator receives a work budget (stitch count, iterations); running out returns a diagnostic and the partial-free result “nothing for this element”, never a hang. Budgets are counted in work units, not wall time, so they are deterministic.
- Degradation is explicit: when a fill is too small for rows, the outline fallback is emitted with
SC-W0305; when travel cannot stay inside a region, the plan uses tie-off + trim + tie-in and says so — never a silent straight line across the design. - Parsers cap sizes and counts before allocating; files are untrusted.
12. Determinism
Ordered collections only (BTreeMap, Vec), transcendental functions from libm, a documented
seeded PRNG (SplitMix64) with seeds derived from element id and the random_seed parameter,
absolute quantization at encode time, and golden files compared byte for byte on every operating system
CI tests on.
Details: determinism.
13. VectorCraft integration (summary)
Three phases (full design in VectorCraft integration):
- ABI v1, no upstream change (M6). Plug-ins
dev.stitchcraft.running,.satinand.fillare live effects: applying one stores the embroidery parameters on the object and draws a lightweight stitch preview. VectorCraft’s own commands make satin columns of 2 paths, add rungs and bake previews, and its Graphic Styles keep embroidery settings as presets. Machine files come fromstitch export design.vectorcraft. (The namespace is one constant, changed in one place once the project has a domain.) - Plug-in features, proposed upstream. An RFC for the ArtCraft team proposed exporter plug-ins, rich parameter schemas (labels, units, groups, conditions), a display-only overlay channel for real stitch previews, budget classes and document-level plug-in data. Every item is generic, not embroidery-specific. VectorCraft took most of them into ABI v1, in 0.14.0 to 0.17.0, and M6.8 to M6.12 use them. M9, after 1.0, proposes the rest: overlays, heavy live effects and the objects beside a live effect’s object.
- In-tree crate, optional. Because the engine is MIT/Apache and host-agnostic, VectorCraft (or a fork) can embed it natively with panels and a simulator.
14. Testing and conformance (summary)
Conformance-first (conformance): requirements have IDs; cases are data files; the runner reports a requirement × case matrix.
| Level | What | Where it runs |
|---|---|---|
| L0 | Plan invariants on every generated plan | every PR |
| L1 | Format conformance: golden bytes, round trips, pyembroidery oracle, fuzzing | PR + nightly |
| L2 | Generator properties: containment, coverage, spacing, determinism, monotonicity | every PR |
| L3 | Ink/Stitch differential metrics on a corpus (pinned Ink/Stitch as a black-box oracle) | nightly |
| L4 | Physical sew-outs on the Brother machine, recorded through an issue form | milestone gates |
Plus unit tests, property tests (proptest), snapshot tests (insta), CLI-in-docs tests
(trycmd), mutation testing (cargo-mutants) and coverage ratchets.
15. Documentation (summary)
mdBook site organised by Diátaxis (tutorials, how-to, reference, explanation) plus these design docs. Reference pages are generated from the code and CI fails if they are stale; stitch images are rendered by our renderer from conformance fixtures; VectorCraft UI screenshots are produced by scripted scenarios through VectorCraft’s control channel and compared on every PR. A label on the PR regenerates them. Details: docs pipeline.
16. Security and supply chain
cargo-deny (licences allow-list with GPL denied, advisories, duplicate versions), unsafe forbidden
outside the audited ABI module, fuzzed parsers, pinned toolchain, Dependabot for crates and Actions,
least-privilege workflow tokens.
17. Release and versioning
SemVer per crate; the plug-in’s version and its VectorCraft ABI version are reported in its
manifest. Changelogs from conventional commit scopes (release-plz, M12). Plug-in .wasm files and
CLI binaries are attached to GitHub Releases with checksums. A release requires: green gates,
compatibility gate green for the current VectorCraft stable, and the machine checkpoint for that
milestone signed off.
18. Risks
| ID | Risk | Mitigation |
|---|---|---|
| R1 | PES header fields are partly undocumented; the machine could reject a file | PES v1 minimal header; pyembroidery oracle; MC-1 in the first milestone |
| R2 | ABI v1 budgets too small for full previews | Budget-aware coarse previews, full fidelity in the CLI, and the RFC’s overlay proposal |
| R3 | ArtCraft declines the RFC’s open proposals | The plug-in works without them, and an in-tree integration in a fork is optional |
| R4 | Polygon offset/boolean robustness | Spike M0.7; input sanitizing; fuzzing; fallback to diagnostics, never panics |
| R5 | Copied GPL code slips in | Design docs first, in our own words (ADR-0012); cargo xtask cleanroom; review checklist |
| R6 | Cross-platform float differences | libm, no FMA-sensitive formulas in quantization, cross-platform hash CI |
| R7 | Scope: the compatibility contract is large | Phases; compatibility contract tracks coverage; post-1.0 items explicit |
| R8 | Brother trim behaviour varies by model | Profile flag; MC-1 tests both encodings |
| R9 | VectorCraft breaking changes | Compatibility gate on stable, pre-release, release and main |
| R10 | Headless UI screenshots need a GPU | Spike M0.8 found none needed. Mesa draws the window in software under Xvfb, and the screenshots match above the status bar (docs pipeline) |
19. Open questions
- Exact Brother model (affects PES version and trim support) — answered at MC-1 at the latest.
- Final project name and plug-in id namespace (working name StitchCraft; plug-in ids derive from one constant).
- ArtCraft’s appetite for the RFC’s open proposals and for embroidery in-tree. Ask on their Discord before M9. VectorCraft adopted the rest from 0.14.
- Where the repository lives on GitHub (needed for the docs site URL and badges).
Architecture
This page is the map of the code: which crate owns what, which way dependencies point, and where a new piece of code belongs. If you are about to add a file and are not sure where, the decision table at the end answers it.
Principles
- The engine knows nothing about hosts. It never sees SVG, VectorCraft, files or the network.
Hosts adapt their documents into a
Designand consume aStitchPlan. - Pure core, effects at the edges. Generators are deterministic functions of their inputs.
File I/O, clocks, environment variables and terminals live only in apps and
xtask. - One source of truth per concept. Parameters live in the registry, diagnostics in the diagnostics registry, machine profiles in profile data. Everything else (UI schemas, CLI flags, docs pages, test strategies) is generated from them.
- Layering is enforced, not suggested.
cargo xtask layersfails the build when a crate reaches up or sideways. - Small modules with one job. Files over 800 lines warn and over 1,500 fail
(
cargo xtask filesize); a stitch type is a module, not a branch in a big match.
Crates and layers
L3 stitchcraft-svg stitchcraft-vectorcraft (adapters)
│ │
L2 stitchcraft-engine ◀───────┘ stitchcraft-formats stitchcraft-render
│ │ │
L1 stitchcraft-plan ◀──────────────────┴─────────────────────┘
│
L0 stitchcraft-core ◀── stitchcraft-params
apps: stitchcraft-cli (bin `stitch`) stitchcraft-vc-plugin (wasm32 cdylib)
test: stitchcraft-testkit (dev-dependency only)
tools: stitchcraft-xtask (`xtask/`, run as `cargo xtask`)
| Crate | Layer | Owns | Must not |
|---|---|---|---|
stitchcraft-core | L0 | Mm, Point, Vec2, tolerances, deterministic math (libm wrappers), SplitMix64 RNG, Budget, Diagnostic/Code/Severity | depend on any workspace crate |
stitchcraft-params | L0 | params!, ParamSpec, ParamSet, value parsing and validation, the registry audit (the list of declarations is stitchcraft_engine::registry) | know any stitch algorithm |
stitchcraft-plan | L1 | Stitch, StitchKind, ColorBlock, StitchPlan, Thread, palettes, MachineProfile, plan invariants | generate stitches; read/write files |
stitchcraft-engine | L2 | Design/Element/Shape, automatic digitizing, normalization, generators, plan assembly, finalizing for the machine | depend on formats, render or any adapter |
stitchcraft-formats | L2 | readers/writers (PES/PEC, DST, …), quantization, format limits | depend on engine (it encodes plans) |
stitchcraft-render | L2 | CPU preview images of plans (thread look, simple look, overlays) | depend on engine |
stitchcraft-svg | L3 | SVG → Design (geometry, styles, inkstitch:* attributes, commands), plan → SVG | contain stitch logic |
stitchcraft-vectorcraft | L3 | .vectorcraft → Design (commands in names included), effect records ↔ ParamSet, plug-in JSON objects | contain stitch logic |
stitchcraft-testkit | test | fixtures, proptest strategies (from the registry), invariant assertions | be a normal dependency |
stitchcraft-cli | app | the stitch command; all filesystem access | contain engine logic |
stitchcraft-vc-plugin | app | VectorCraft ABI v1 shim + plug-in manifests | contain engine logic; unsafe outside abi.rs |
stitchcraft-xtask | tools | gates (ci, layers, docs, cleanroom, …), generators, compatibility runs; works from its own folder, on StitchCraft’s packages only (ADR-0011) | ship to users |
Intra-layer edges are allowed only where listed (stitchcraft-params → stitchcraft-core). The table
lives in xtask/src/layers.rs and is append-only, like VectorCraft’s.
Why this split
planbelowengineandformatslets the formats crate encode and decode plans without pulling in geometry and generators; a file converter (stitch convert a.dst b.pes) links only L0–L2 formats code.paramsat L0 because both the engine (typed views) and the adapters (attribute mapping) need it, and because docs/UI generation must not depend on algorithms.- Adapters at L3 so the engine can be tested without any host, and a new host (another editor, a web service) is one crate, not a refactor.
- Two apps because the CLI needs the filesystem and threads while the plug-in must stay inside a sandbox with no imports.
External dependencies
Dependencies are added deliberately and recorded in the crate’s README section “Dependencies”.
cargo-deny enforces the licence allow-list (MIT, Apache-2.0, BSD-2/3, ISC, Zlib, Unicode) and denies
GPL/AGPL/LGPL.
| Need | Crate | Licence | Where |
|---|---|---|---|
| Deterministic transcendental math | libm 0.2 | MIT | core |
| Errors | thiserror 2 | MIT/Apache | all libraries |
| XML / SVG syntax | roxmltree, svgtypes | MIT/Apache | svg |
.vectorcraft documents: JSON, its coordinates read exactly, unpacked from gzip by a Rust backend | serde, serde_json, flate2 1 | MIT/Apache | vectorcraft |
| Rasterizing previews, and reading VectorCraft’s pictures to compare them | tiny-skia 0.11 | BSD-3-Clause | render, xtask |
| JSON reports, and the plug-in’s JSON | serde_json | MIT/Apache | cli, vc-plugin |
| Content hashes of bug-report bundles | sha2 0.11 | MIT/Apache | cli |
| CLI parsing | clap 4 | MIT/Apache | cli, xtask |
| Tooling’s TOML and JSON | toml, toml_edit, serde_json | MIT/Apache | xtask |
| Tests | proptest, insta | MIT/Apache, Apache | dev only |
The engine’s geometry is its own, with no crate for curves, regions, offsets, nearest points or graphs:
spike M0.7 accepted that (ADR-0005). Versions above are those
Cargo.toml asks for (2026-10-17), and Cargo.lock is the truth.
Hosts: ports and adapters
The engine exposes one entry point and one input type:
pub fn plan(design: &Design, profile: &MachineProfile, budget: &Budget) -> PlanOutcome; // since M3.8
pub struct PlanOutcome {
pub plan: Option<StitchPlan>, // None when nothing could be sewn
pub diagnostics: Vec<Diagnostic>,
// per-element groups, for previews and reports, arrive with the VectorCraft plug-in (M6)
}
The machine profile is an input because the shortest stitch a generator may sew is the machine’s, and finalizing (M3.9) fits the plan to the machine’s longest stitch and hoop.
and a per-element entry point, which plan calls for each element and live previews call for one:
pub fn generate(element: &Element, settings: &DesignSettings, profile: &MachineProfile,
neighbours: &Neighbours, budget: &Budget) -> Generation; // since M3.8
pub struct Generation {
pub generated: Option<Generated>, // the element's stitch groups; None when it is skipped
pub diagnostics: Vec<Diagnostic>,
}
A live preview sees one object, so it has no neighbours, and it sews on the reference machine.
Adapters only translate:
| Adapter | From | To |
|---|---|---|
stitchcraft-svg | SVG document (+ inkstitch:* attributes, command symbols) | Design |
stitchcraft-vectorcraft | .vectorcraft JSON (+ commands in names), plug-in input objects, effect records | Design / Element |
stitchcraft-vc-plugin | ABI v1 call (JSON in linear memory) | generate → preview geometry JSON |
stitchcraft-cli | files and flags | plan → formats/render/report |
A future in-tree VectorCraft crate would be a fifth adapter calling the same two functions.
Errors and diagnostics
- Diagnostics describe problems with the user’s design (a satin with three rails, a fill smaller than its row spacing, a design larger than the hoop). They are values, collected, coded and shown; planning continues where it can.
- Errors (
Result<_, XxxError>) describe failures to do the work (a file is truncated, a budget is exhausted, an I/O failure). Each crate has onethiserrorenum; apps turn errors into diagnostics or exit codes. - Nothing below the apps prints, exits or panics.
- A bug in StitchCraft is neither. It shows as a failed plan check (
SC-E0009) or as a panic despite the lints. The command line catches it and ends with exit status 4. Forstitch planit also writes a bug-report bundle that reproduces the bug (determinism). A VectorCraft exporter declines with it, so VectorCraft doesn’t write the file. - The command line’s own promises. A command refuses outputs that name the same file as each other,
or as a file it reads (
REQ-CLI-007), and writes each file under a temporary name of its own, created new and synced before it is renamed into place (REQ-CLI-006). Built for WASI, ascargo xtask wasm --goldensruns it, a program has no process id, so there the names count from 0. What it prints of a file it wrote is the file read back (REQ-CLI-008,stitchcraft_formats::read_back, which the VectorCraft exporters call too). A closed standard output keeps a command’s exit status, any other error writing it ends with 3 (REQ-CLI-009), and help and errors are coloured only on a terminal (REQ-CLI-010).
Feature flags
Kept minimal: stitchcraft-engine/experimental exposes generators whose status is experimental
in the registry (they appear in docs with a badge); stitchcraft-formats/<format> flags exist only if
a format pulls a heavy dependency (none planned). No flag may change the output of a stable generator.
WebAssembly constraints
Everything in layers L0 to L3 must build for wasm32-unknown-unknown, which cargo xtask wasm checks. That target
has no threads, file system, clocks, environment, processes or randomness from the OS, and std’s calls
for them build there but fail or panic when run, so clippy.toml keeps library crates from calling
them. The plug-in is built
in release with the workspace’s profile (thin LTO, one codegen unit), and on wasm32-unknown-unknown
a panic aborts. That is one more reason the no-panic rule is absolute: the module stops, and
VectorCraft reports the plug-in as failed.
Where does my code go?
| You are adding… | Put it in | Also touch |
|---|---|---|
| A new stitch type | crates/stitchcraft-engine/src/generators/<name>/ | registry params, diagnostics, conformance cases, docs page — see the playbook |
| A parameter | the generator’s params! block | nothing else by hand: docs and schemas regenerate; a new …Params declaration also gets a line in stitchcraft_engine::registry::PARAMETERS — see the playbook |
| A machine format | crates/stitchcraft-formats/src/<format>/ | format playbook |
| A machine profile | crates/stitchcraft-plan/src/profiles/ (data) | machine-testing record |
| A diagnostic | the diagnostics registry in stitchcraft-core | explanation text + a case that triggers it |
| SVG/Ink/Stitch parsing | stitchcraft-svg | compatibility contract row |
| VectorCraft document reading | stitchcraft-vectorcraft | compatibility gate scenario |
| A CLI subcommand | apps/stitchcraft-cli/src/commands/ | trycmd example in the docs |
| A repo check | xtask/src/ | cargo xtask ci step list and the guardrails page |
Data model
The types every crate shares. Code sketches are illustrative (ignore); the crates’ rustdoc is the
API reference once the types exist. Invariants listed here are checked in code (constructors return
Result) and by conformance level L0.
Units and coordinates (stitchcraft-core)
/// A length in millimetres. Always finite; construction from untrusted input is checked.
pub struct Mm(f64);
/// A point in millimetres, y pointing down (SVG and VectorCraft convention).
pub struct Point { pub x: f64, pub y: f64 }
- Internal unit: millimetres. Embroidery parameters are specified in mm and machine files use 0.1 mm, so mm keeps the numbers people read and the numbers we compute the same.
- Host units are converted at the adapter: VectorCraft points ×25.4/72; SVG user units through
the root
viewBox/width/height, with CSS pixels at 96 per inch. - Quantization happens once, in the encoder, on absolute positions:
round_half_even(x * 10). Deltas are differences of quantized absolutes, so rounding never accumulates. - Axis conventions per format are the encoder’s job (DST is y-up). Test sheet TS-01 (an asymmetric “F” with a ruler) proves orientation and scale on the machine.
Engine input: Design (stitchcraft-engine)
Hosts translate their documents into a Design (stitchcraft_engine::design), so the engine never sees a
host document and every host gets the same stitches for the same design. Since M3.3:
pub struct Design { // checked by Design::new
elements: Vec<Element>,
pub settings: DesignSettings,
pub sort_colours: bool, // since M13.3: the elements with settings sew in the order StitchCraft chooses too
}
pub struct Element {
pub id: ElementId, // stable host id: "svg:path123:fill", "vc:42:fill"
pub name: Option<String>, // the name the user gave it (Inkscape's label)
pub shape: Shape,
pub thread: Thread,
pub params: ParamSet, // as the host stores them; validated against the registry when planned
pub automatic: bool, // since M13.1: a shape without settings, read with --auto, whose stitches StitchCraft chooses
}
pub struct Reading { // since M13.1: what every host's reader is asked
pub auto: bool, // --auto
pub sort_colours: bool, // since M13.3: --sort-colours, which the reader passes on (Design::read_as)
}
pub struct DesignSettings { // since M3.8; read from an Ink/Stitch file since M4.8, its commands from M8,
// and from a VectorCraft document since M6.11
pub collapse_len: Mm, // 3 mm: same-thread moves no longer than this are sewn on, not jumped
pub min_stitch_len: Option<Mm>, // the design's shortest stitch, for elements that set none
pub origin: Option<Point>, // where the hoop's centre goes; None: the centre of the stitches
pub stop_position: Option<Point>,// where the frame moves before each stop; None: it stays
pub min_satin_stroke_width: Mm, // 1 mm: a satin column drawn as one path no wider is sewn as a stroke (M4.8)
}
pub enum Shape {
Stroke { path: Path, width: Mm, join: Join, cap: Cap }, // an outline: running stitch and the other stroke methods
Fill { path: Path, rule: FillRule }, // an area: its boundary, and which parts are inside
}
pub enum Join { Miter { limit: f64 }, Round, Bevel } // a stroke's corners; Join::UNSET is a miter limited at 5
pub enum Cap { Butt, Round, Square } // since M13.1: how a stroke ends where an open subpath ends
pub struct Path { pub subpaths: Vec<Subpath> } // in millimetres, y down
pub struct Subpath { pub start: Point, pub segments: Vec<Segment>, pub closed: bool }
pub enum Segment { Line(Point), Quad(Point, Point), Cubic(Point, Point, Point) }
Elements are in the host’s paint order, bottom first. They sew in that order unless the design has
automatic elements or sort_colours is set: since M13.3 automatic digitizing then chooses the order
(image to embroidery). Curves keep their control points, and the
generators flatten them with the tolerance their parameters give.
A host reads 3 of the settings by Ink/Stitch’s names, collapse_len_mm, min_stitch_len_mm and
min_satin_stroke_width_mm: an SVG file’s metadata, and since M6.11 the data a VectorCraft document keeps
for StitchCraft’s Design Settings filter. SETTINGS lists them with the label and help that filter’s
dialog shows, and Setting::get and Setting::set read and write each in millimetres. A shortest stitch
of 0 is None, which leaves it to the machine.
A stroke’s width and join, since M4.8, are as the host draws them: in SVG, stroke-width scaled by the
transforms, and stroke-linejoin with stroke-miterlimit (SVG input).
Only a satin column drawn as one path uses them, as its width and its corners. Shape::stroke(path)
makes a stroke that says nothing of either: 1 CSS pixel wide, with Join::UNSET. Its cap, since M13.1, is
SVG’s stroke-linecap or VectorCraft’s cap, Butt when nothing sets it. Ink/Stitch sews no cap, and
neither does an element with settings. An automatic element’s stitches follow it
(image to embroidery).
An automatic element is a shape without settings that the user asked StitchCraft to digitize: the readers
mark it when Reading::auto is set, and the engine turns it into ordinary elements with the settings its
width calls for before anything is generated (stitchcraft_engine::auto).
Documents repeat ids, so adapters make theirs unique with stitchcraft_core::Labels, which gives a
label repeated in a document ~2, ~3 and so on, in every adapter alike.
Invariants, checked by Design::new: element ids are unique, and every point is finite and within
±10,000 mm, as far as machine-file coordinates go. No stroke is narrower than 0, a miter limit is a
finite number of at least 1, and the settings’ lengths are not negative. Adapters drop what they cannot
represent with a diagnostic of their own, so a failed check is a bug in the adapter (SC-E0009).
Design::moved gives the design moved by an offset, every point of its elements and its origin and stop
position, checked the same way. VectorCraft’s Test Sheet filter draws a test sheet in the middle of the
artboard with it (REQ-VC-015).
Commands attached to elements come with M8, for Ink/Stitch’s command symbols such as start and end
points. Trims and stops are the element parameters trim_after and stop_after, and the origin and
stop position are design settings (M3.8).
Region
A fill’s region is not part of the design: the normalizer builds it from the fill’s path and fill rule,
since M5.1 (stitchcraft_engine::normalize::region):
pub struct Region { pub parts: Vec<Polygon> } // in the order the drawing reaches them
pub struct Polygon { pub outline: Vec<Point>, pub holes: Vec<Vec<Point>> } // closed rings, in millimetres
Each ring starts at its point that comes first in the drawing, and repeats it at its end. Outlines turn
clockwise and holes counter-clockwise in y-up axes, and in those axes the region lies on the right of
every ring. Parts of 3 square CSS pixels (0.21 mm²) or less are left out, with SC-W0303. The
fills design builds the region step by step.
SatinShape
A satin column’s path is recognized, since M4.1, as one of 2 shapes (stitchcraft_engine::normalize::satin):
pub enum Shape {
Rails(Satin), // 2 rails as polylines, and what pairs their points: rungs, or the rails' nodes
CentreLine { line: Vec<Point>, closed: bool }, // one subpath, flattened; closed when the path ends with Z
}
A centre line is made into a Satin of its own, its rails offset from the line by the stroke’s width and
join (stitchcraft_engine::normalize::centre_line, since M4.9). How rails and rungs are recognised from
a host path, and made from a centre line, is part of the satin design.
Parameters
ParamSet maps registry keys to typed values; generators receive typed views
(TatamiParams { row_spacing: Mm, angle: Deg, … }) produced by the registry. See
parameter registry.
Engine output: StitchPlan (stitchcraft-plan)
pub struct StitchPlan {
pub blocks: Vec<ColorBlock>, // sewing order; a thread change between blocks, the end after the last
pub elements: Vec<ElementId>, // what `ElementRef`s point at
}
pub struct ColorBlock {
pub thread: Thread,
pub stitches: Vec<Stitch>,
}
pub struct Stitch {
pub at: Point, // where the needle is after this entry (mm, y down, hoop centre = 0,0)
pub kind: StitchKind,
pub origin: Provenance,
}
pub enum StitchKind { Normal, Jump, Trim, Stop }
pub struct Provenance {
pub element: Option<ElementRef>, // index into `elements`; None for plan-level entries
pub role: Role, // Top, Underlay, Travel, Lock, Command
}
- Colour changes and the end are structure, not entries. The machine changes thread between two blocks and ends after the last one, so a plan cannot misplace either; encoders write exactly one colour change between blocks and one end.
TrimandStophappen where the needle is; theiratrepeats the needle position so every entry has one (bounds, previews and reports never special-case commands), and the invariant checker verifies it. Encoders decide how a format expresses them. PEC puts a trim flag on every jump after the first move, and records a stop as a change to the same thread. DST spells a trim as a run of jumps. See formats.- The origin is the centre of the hoop, where the needle starts, above the fabric. The first
Normalstitch is the first time it goes down. PlanBuilderappends entries with the needle tracked;PlanStatscounts stitches, jumps, trims, stops and thread changes for reports.- A plan does not name a profile: it is checked against one,
invariants::check(&plan, &profile).
Plan invariants
Conformance level L0 checks these on every plan the suite produces. A violation is a bug in whatever
built the plan: the command line reports it as SC-E0009 and writes nothing. “While sewing” means the
previous movement was a Normal stitch with no trim since — the stitch lays thread between two holes,
and its length is what the machine and fabric feel; the first stitch after a jump, a trim or a thread
change starts a new run. A length or a position within LENGTH_SLACK (10⁻⁹ mm) of a limit counts as at
it, at the hoop’s edge as for a stitch’s length, so rounding cannot push a stitch placed exactly at a limit
past it. Generators place stitches by the same test, and so does the check that a design fits the hoop.
| Requirement | Rule | Since |
|---|---|---|
REQ-PLAN-001 | Every position is finite and inside the profile’s hoop, centred on the origin. | M1 |
REQ-PLAN-002 | While sewing, every stitch is between the profile’s min_stitch (lock stitches, into or out of a lock point: 0.2 mm) and max_stitch. | M1 |
REQ-PLAN-003 | Every block sews at least one stitch; trims and stops happen where the needle is. | M1 |
REQ-PLAN-005 | While sewing, no stitch lands where the needle already is. | M1 |
REQ-PLAN-006 | Thread changes plus stops fit the format (PES: 255; DST: 999). | M1 |
REQ-PLAN-007 | Every Normal and Jump entry names an element the plan lists. A trim or a stop need not name one. | M3 |
REQ-PLAN-004, a tie-off before a trim and a tie-in after it, is retired. Whether they belong there
depends on the elements’ lock settings, which a plan does not hold. Assembly’s cases check it with the
settings (REQ-ASM-003, REQ-LCK-001).
Threads and palettes
pub struct Thread {
pub color: Rgb, // what the user chose
pub name: Option<String>, // shown to the operator
}
Palettes are static tables (the Brother PEC palette first) of PaletteEntry { index, name, color, matchable }. Nearest-colour matching uses CIEDE2000 in CIELAB (D65), through stitchcraft_core::math
so it is deterministic, with ties going to the lower index; it is tested against the 34 reference pairs
of Sharma, Wu & Dalal (2005). Entries that are not threads — Brother’s applique steps 62–64 — are never
matched. A format that names threads by entries gives a design’s threads theirs together
(Palette::assign, REQ-THREAD-002). Different threads get different entries while they last, the nearest
pairs first, and a thread read from such a file gets its own (formats, PES). Each table names
its source in its module docs and in a row of NOTICE. The PEC palette is the one
MIT-licensed pyembroidery publishes, with entries 62 and 63 from its fork pystitch. Thread
catalogues (brand and number) arrive with M11.
Machine profiles
pub struct MachineProfile {
pub id: &'static str, // "brother-pe800-5x7"
pub name: &'static str, // "Brother PE800 with its 5 × 7 in hoop"
pub hoop: Size, // 130 × 180 mm
pub comfort: Option<Size>, // None — a warning beyond it, not an error
pub format: FormatId, // PesV1 | Dst
pub max_stitch: Mm, // 12.0
pub min_stitch: Mm, // 0.3
pub trims: TrimSupport, // Command | LongJumps(Mm) | None
pub palette: PaletteId, // BrotherPec
pub evidence: &'static str, // where the values come from
}
Profiles are data in stitchcraft-plan/src/profiles/, listed by stitch profiles and documented by
generated reference pages. A profile’s values are machine facts: changing one requires a
machine-testing record that justifies it (machine testing), and evidence
says where each value comes from. Format limits that are not machine facts (the most colour changes a
format records) live on FormatId, so the invariant checker and the encoders read the same number.
A profile is a machine with one hoop, so a machine with 3 hoops has 3 profiles. profiles::REFERENCE is
the reference machine’s, the Brother PE800 with its 5 × 7 in hoop
(ADR 0013). Code that means the machine the checkpoints
run on imports it rather than naming a hoop: the command line plans for it unless --profile names
another or a VectorCraft document keeps one
(integration). Each
test sheet names the profile of the hoop it is sewn in.
check_fit(bounds) turns the design’s size into SC-E0701 (larger than the hoop) or SC-W0702 (larger
than the comfort zone), with “rotate 90°” as a one-click fix when turning the design would fit
(REQ-PRF-002).
Budgets
pub struct Budget {
pub max_stitches: u32, // per design, default 2,000,000
pub max_work: u64, // abstract work units per element, and once more for assembly
}
Work units are counted per inner loop iteration (scanline crossings, graph edge visits, offset
steps) so a budget behaves identically on every machine. Each element has the work budget to itself,
and so does working out what it offers the element before it to end near. Exhaustion there yields
SC-E0004 for the element, and other elements still plan. Assembly and finalize share the budget once
more, and running out there plans nothing. SC-E0004 then says so for the whole design.
Work bounds time, not memory: a unit can make a point, and 500 million points are gigabytes. A few
settings multiply an element’s needle points. 10 mm of zigzag at the smallest spacing, sewn 100 times
with bean stitch 10, is 4.2 million. So the stitch limit bounds what generators hold as well
(REQ-GEN-004):
- A generator spends a stitch for each needle point as it makes it, and for each one a pass after the first or bean stitch sews again.
- What it lays out before sewing it must fit in the stitches left, though it spends none on it: a satin column’s pairs, say, or a fill’s row segments.
- Once done, it settles at the needle points it gives. Some it made were dropped on the way, such as points too close together, and some were copied, such as an E-stitch’s way back down its spike.
So a generator never holds much more than the stitches left, and it may stop a little short of the limit
where it made many more points than it keeps. Each element is given the stitches the elements before it
left. The first that needs more ends planning there, with SC-E0004 naming it: the design cannot be sewn
within its limit, and making the rest would only fill memory.
Parameter registry
A parameter is declared once for the stitch types that read it, next to the generator that uses it. Everything else that needs to know about parameters is generated from that declaration:
┌──▶ typed params struct used by the generator
├──▶ validation (types, ranges, applicability) with coded diagnostics
params! { … } ─────────┼──▶ VectorCraft plug-in manifests (schema per plug-in, ≤ 64 params)
(in the generator) ├──▶ CLI: `stitch params`, `--param key=value`
├──▶ SVG mapping (`inkstitch:<key>` attributes, read and write)
├──▶ reference docs pages + JSON Schema (generated, checked in CI)
└──▶ proptest strategies for conformance and fuzzing
Nothing about a declaration is written twice, so its label, help, range and default cannot drift apart between the code, the docs and the user interfaces. The diagram is the design’s aim. Generated outputs says which outputs exist today and which steps add the others.
Declaring parameters
Parameters are declared with a macro_rules! macro (no proc-macro crate, so contributors read plain
Rust and builds stay fast). The field name is the key; the doc comment is the help text, written for an
embroiderer. The real declaration of the settings every stitch type shares, in
crates/stitchcraft-engine/src/common.rs, starts like this:
params! {
/// Settings every stitch type shares: lock stitches where the element's stitching starts and
/// ends, a trim or a stop after it, and the shortest stitch and jump.
pub struct CommonParams for StitchType::ALL;
"Lock stitches" {
/// Where the element gets lock stitches: …
ties: Choice = "0", label "Lock stitches",
options ["0" => "At the start and the end", "1" => "At the start", "2" => "At the end", "3" => "None"];
/// The shape of the lock stitches at the start. …
lock_start: Choice = "half_stitch", label "Start lock", choices LOCKS,
origin Origin::InkStitchDeviates { deviation: "DEV-LCK-001" };
/// The shape of a custom start lock: …
lock_custom_start: Text = "", label "Custom start lock", when lock_start == "custom";
/// How long each step of a start lock made of steps (back and forth, or custom) is.
lock_start_scale_mm: Length = "0.7", label "Start lock size", range (0.1, 10.0),
when lock_start in SIZED_IN_MM;
…
}
}
Each parameter is key: Kind = "default", label "Label", followed by what its kind needs and what is
not the usual: range (min, max) for numbers and lists, options [...] or choices CONST for choices,
applies when it covers fewer stitch types than its struct, defaults [Family::Fill => "4"] when a
family of stitch types has a default of its own, when other == "value" (or when other in VALUES, a
list of them) when a user interface shows it, and an element reads it, only then, origin (default
Origin::InkStitch) and stability (default Stability::Stable).
A combination a kind does not take does not compile. Defaults are written as a design stores them, so
they are checked by the same code as a design’s values. The grammar is in the macros module’s
documentation.
The macro expands to:
- the typed struct,
pub struct CommonParams { pub ties: &'static str, pub lock_start_scale_mm: Mm, … }, each field’s type given by its kind (kindsmodule), so a field and its validation cannot disagree; CommonParams::SPECS(aconstslice ofParamSpec) andCommonParams::GROUP;CommonParams::from_set(&ParamSet) -> Result<Validated<CommonParams>, Vec<Diagnostic>>: every parameter read from its text or its default, with the clamping warnings beside the values;CommonParams::from_set_as(&ParamSet, StitchType): the same as an element sewn as that stitch type reads it, with its family’s own defaults and only the parameters it uses (The parameters an element reads). The engine reads an element’s settings with it.
The registry itself is stitchcraft_engine::registry::PARAMETERS, a plain const list of every
declaration’s GROUP in the engine, next to the generators (no life-before-main tricks; adding a
generator’s parameters is one line there). stitchcraft-params knows no stitch algorithm.
ParamSpec
pub struct ParamSpec {
pub key: &'static str, // registry key == Ink/Stitch attribute name when one exists
pub label: &'static str, // short UI label (English source string, translation key)
pub help: &'static str, // the doc comment; `help()` gives the Markdown
pub kind: Kind, // with its limits: range, options, maximum length
pub default: &'static str, // as a design stores it; "" for an empty optional value
pub family_defaults: &'static [(Family, &'static str)], // a family's own default: `default_for`, `in_family`
pub group: &'static str, // UI and docs section
pub applies_to: &'static [StitchType],
pub visible_when: Option<Condition>, // another key of the declaration and the values it shows and reads this for: lock_custom_start only for lock_start custom
pub stability: Stability, // Stable | Experimental | Deprecated { use_instead }
pub origin: Origin, // InkStitch | InkStitchDeviates { deviation } | StitchCraft
}
InkStitchDeviates names its entry in the deviations ledger (conformance/deviations.toml), which says
what differs and why; the reference pages show that entry, so a deviation is described once. A since
version and a registry schema version arrive with the first release, together with
migrations.
Kinds and validation
| Kind | Generators get | Validation | Ink/Stitch equivalent |
|---|---|---|---|
Length | Mm | finite, within range; a unit may follow the number with no space: mm, in, pt | float, unit mm |
OptionalLength | Option<Mm> | as Length, or empty: the setting then comes from the document or the machine, or there is none. 0 counts as empty, since Ink/Stitch reads 0 as not set, and a length below 0 does too, with SC-W0102 (DEV-PRM-003) | float, unit mm, no default |
OptionalLengthWithZero | Option<Mm> | as OptionalLength, but only empty text is empty, and 0 is a length, as Ink/Stitch reads a satin’s shortest stitch to split | float, unit mm, no default |
Angle | degrees, f64 | finite; normalized to (−180, 180] | float, unit deg |
BoundedAngle | degrees, f64 | finite, within range, never turned by whole turns. 270 is above a range that ends at 89, and reads as 89, as Ink/Stitch reads a zigzag stroke’s angle | float, unit deg |
Percent | f64 | finite, within range | float, unit % |
Number | f64 | finite, within range, without a unit | float, no unit |
Count | u32 | whole, within range | int |
Toggle | bool | true or false in any case, also Ink/Stitch’s yes, y, t and 1 for true and their opposites and off for false. on is SC-E0101, where Ink/Stitch reads it as false (DEV-PRM-001) | boolean, toggle |
Choice | &'static str (the id) | one of the declared ids; unknown ids are a diagnostic, not a default | combo, dropdown |
Seed | Option<u64> | any; empty means “derive from the element”; text that is not a number is hashed (FNV-1a) | random_seed |
LengthList | Vec<Mm> | 1 to 16 values, each in range (patterns like "2.5 1.0") | string/float lists |
LengthPair | [Mm; 2] | 1 value for both sides, or 2 for the first side and the second (a satin’s rails, or its start and end). Each is in range, with units as for Length | float, unit mm (each side) |
PercentPair | [f64; 2] | as LengthPair, in percent | float, unit % (each side) |
OptionalLengthPair, OptionalPercentPair | Option<[Mm; 2]>, Option<[f64; 2]> | as LengthPair and PercentPair, or empty: the setting then comes from another one, as a zigzag underlay’s inset comes from the contour’s. 0 is a value, as Ink/Stitch reads it | float, unit mm (each side) or % (each side), no default |
PercentList | Vec<f64> | 1 to 16 percentages, each in range (levels like "15 30") | float, unit %, read as a list |
CountList | Vec<u32> | 1–16 whole numbers, each in range | string/int lists |
OptionalAngleList | Option<Vec<f64>> | 1 to 16 angles, each read as an Angle. Empty, the setting comes from another one, as a fill’s underlay angles come from its angle | float, unit deg, read as a list, no default |
Text | String | stripped of the white space round it, as Ink/Stitch strips it, then at most 4,096 bytes. A grammar of its own, such as a custom lock’s, is checked where it is used | string |
Invalid values never fall back silently to defaults: the element gets SC-E0101 (not a value of the
kind; the element cannot be stitched) or SC-W0102 (clamped into range), with the key, the value and what
is accepted. Keys the registry does not know are kept, so they round-trip, and reported once as
SC-W0105. Parsing lives in one place (value module), so every host reads text the same way: an SVG
attribute, a command-line key=value, a VectorCraft effect record.
SC-W0102 names the value used, or for a list what the list is read as, since an element may sew only
some of a list’s values: a zigzag stroke sews its first spacing.
The ranges are StitchCraft’s. Ink/Stitch sets a floor for most of its numbers and few ceilings, so many
ranges here are narrower, and a value outside one is clamped where Ink/Stitch would sew it
(DEV-PRM-002). A list holds at most 16 values, a pair 2, and no value is longer than 4,096 bytes, which
bounds what reading one costs before any budget is charged. Longer text is SC-E0101, and its message
quotes the value’s first 64 characters (DEV-PRM-004).
Empty text, or spaces alone, is not set, whatever the kind, and the parameter takes its default
(REQ-PRM-002). Ink/Stitch strips every value and reads an empty one so, and its settings window stores a
field the user clears as empty text. A value is stripped of the white space round it before it is read,
whatever its kind, text included. A custom lock’s numbers with a tab or a line end round them are numbers
here, as there. Spaces separate the values of a list or a pair, as Ink/Stitch splits
them. A comma separates nothing and is no decimal point, so text with one is SC-E0101, with a hint to
use spaces and points. Ink/Stitch reads 3,5 or 45,90 as the default instead, without a word
(DEV-PRM-001). Ink/Stitch leaves out one comma at the end of each of a fill’s underlay angles, for
older files, and so does StitchCraft for an angle of any list.
The parameters an element reads (REQ-PRM-004)
An element reads only the parameters its stitch type uses. Any other parameter takes its default, and
so does one whose when is false for the element. Its text is kept and not checked. Ink/Stitch reads a
setting only where its stitch type uses it, and a path keeps a satin’s settings when its stroke goes
back to running stitch: that path must still sew, with nothing said of what it does not read. A when
is read from the other parameter’s value on the element, or its default, as the element reads that
parameter. So the audit requires the other parameter to be of the same declaration and never hidden
itself. It must also apply to every stitch type the hidden one applies to. A host that checks a whole
set, such as a settings panel, reads every parameter with from_set, declaration by declaration. It
reads only the declarations of the families it shows: the satin column’s declaration of
zigzag_spacing_mm cannot read a zigzag stroke’s 2 spacings (rule 7).
Completeness (REQ-PRM-001)
A declaration that compiles can still be wrong: an empty range, a default outside it, a repeated option,
a condition on a parameter that does not exist or that an element could not read it by. stitchcraft_params::audit lists every such problem, and
the engine’s req_prm_001_every_registered_parameter_is_complete test requires the list to be empty.
cargo xtask docs --check adds the cross-checks with outside data. A parameter of Ink/Stitch origin must
be in the compatibility contract with a compatible type and the default
Ink/Stitch sews, and one of StitchCraft’s own must not take an Ink/Stitch key. Deviations must name ledger
entries, and the lock and method identifiers must equal Ink/Stitch’s.
Naming rules
-
If Ink/Stitch has the parameter with the same meaning, use its attribute name verbatim (
row_spacing_mm). This keeps SVG files compatible with no mapping table. -
If our meaning differs, keep the name, mark
origin: InkStitchDeviates, and record the difference in the deviations ledger (conformance/deviations.toml). An entry that changes how other keys of Ink/Stitch’s read or sew lists them underparams, and each key’s reference entry shows it. -
New parameters follow the same style:
snake_case, a unit suffix for lengths (_mm), percentages (_percent) and angles where Ink/Stitch does (anglealone for row angles). -
Never reuse a removed key with a new meaning.
-
Defaults are part of the contract. For a parameter of Ink/Stitch origin the registry default is the value Ink/Stitch sews for an SVG that omits the attribute, and the file stitches the same way in both tools. That value is the default Ink/Stitch’s settings window shows, except for the few parameters its code reads with another value. The contract records those as
fallback, andconformance/inkstitch/check_params.pycompares every one of its getters with the contract. Better starting values for new objects come from presets and machine profiles, never from changing a default. -
A key belongs to its stitch types. Ink/Stitch gives some keys to several elements with their own defaults:
max_stitch_length_mmis empty on manual stitch and satin columns and 4 mm on fills. The contract is per key and stitch type, so the contract page matches a declaration with the rows for the stitch types it applies to, and a declaration for a stitch type Ink/Stitch does not give the key is reported. A key is declared once for a family (rule 7), and a family whose default differs gets its own withdefaults, as fills do formax_stitch_length_mm. A family with a default of its own needs a value (ParamSpec::in_family). There an empty value means its default. A length of 0 or less is raised to the least the key accepts instead of counting as empty, as Ink/Stitch reads a fill’s longest stitch. Elsewhere an optional value left empty is no value. The engine reads an element’s settings with its family’s defaults (from_set_as). The contract page compares each row with the default of the row’s family. The reference page lists each family’s default and what the family accepts, and the JSON Schema gives the defaults underx-stitchcraft.family_defaults. -
A family’s own kind and range. Some of Ink/Stitch’s keys differ by more than their default from one family to another. Its
zigzag_spacing_mmis a list for a zigzag stroke and one number for a satin column, and a meander fill reads it as a number that defaults to 0. Its ripple stitch reads astaggersof 0 as no stagger, but a tatami fill’s range starts at 0.01. Such a key is declared again for the family that reads it its own way, in a declaration for that family’s stitch types (REQ-PRM-005). The registry’s audit lets a key have one declaration for each family at most, and an element reads the key as its family’s declaration says.find_forfinds a family’s declaration, andfindthe first, which is the only one for every other key. The JSON Schema gives such a key any of its declarations’ schemas, each with the stitch types it applies to. The index lists the key once for each declaration, under its group, and the contract compares each declaration with the rows of Ink/Stitch’s for its stitch types. Every declaration of one of Ink/Stitch’s keys has an Ink/Stitch origin. The reference pages show a ledger entry that names the key with each declaration.An SVG node’s fill and its stroke read the same settings. A key of Ink/Stitch’s is never one of StitchCraft’s own, even for a family Ink/Stitch does not give it, because that family would read a value written for Ink/Stitch’s meaning. A node’s fill may also read a value that only its stroke’s declaration can read, such as a zigzag stroke’s 2 spacings read by a meander fill. Ink/Stitch sews that fill with its default, and StitchCraft would refuse it (
DEV-PRM-001). The meander fill’s step (M7.8) chooses between the two.
Generated outputs
| Output | Generated by | Checked by | Since |
|---|---|---|---|
The reference pages, the index and one page per declaration (docs/src/user/reference/params/*.md), with the deviations ledger’s entries | cargo xtask docs | cargo xtask docs --check, which fails on a stale page and on a page left from a declaration since removed (cargo xtask docs deletes those) | M3.1 |
docs/src/user/reference/params.schema.json: the parameters as JSON values (numbers, booleans, strings, arrays, null for empty), for editors, presets and JSON hosts | cargo xtask docs | same | M3.1 |
| The StitchCraft column of the Ink/Stitch compatibility contract | cargo xtask docs | same, plus the cross-checks above | M3.1 |
| VectorCraft manifests, one for each stitch family’s module, with each parameter’s label, unit, section, help, help page and the values that show it | at runtime from the registry, when VectorCraft asks a module for its manifest (stitchcraft-vc-plugin’s manifest and dialog) | plug-in tests validate them against VectorCraft’s manifest rules, cargo xtask docs --check finds each help page, the contract tests install them in VectorCraft’s host, and the window probe checks the dialogs’ rows | M6.2, and the dialogs M6.10 |
| SVG attribute read/write | stitchcraft-svg iterates the registry | round-trip property test REQ-PRM-003 | planned for M8 |
CLI parameter help (stitch params, --param key=value) | at runtime from the registry | trycmd examples | planned, in no step yet |
proptest strategies | stitchcraft-testkit iterates the registry | used by every generator property test | planned, in no step yet |
Splitting for VectorCraft ABI v1
VectorCraft took at most 64 parameters per plug-in, with no groups or conditional visibility, until 0.14.
The plug-in crate builds one plug-in per stitch family for that (stroke, satin, fill), each
with only the parameters that apply to it; the shared lock/trim parameters are included in each. A test
fails if any family’s manifest exceeds 64 parameters or 64 KiB, so adding a parameter that would break
the limit is caught in the PR, not by a user. A parameter has one value, of a kind every release takes.
A parameter that needs a text, a seed or several values keeps its default in VectorCraft
(manifests). VectorCraft 0.14 adopted richer schemas from the
RFC: labels, units, help, groups and conditions, texts
and lists, and 256 parameters, 64 of them outside groups. Releases before it ignore the labels, units, help,
groups and conditions, and refuse the rest. Each manifest gives a parameter the registry’s label, unit, help
and group, and the conditions of its declaration’s applies_to and visible_when (M6.10,
dialogs). A parameter’s help page is its entry on the reference page
of its declaration, which ParamGroup::slug names for the docs generator and the plug-in alike.
VectorCraft shows help as plain text, so stitchcraft_core::text::plain takes the marks out of the
Markdown.
Presets
A preset is a named partial ParamSet ("denim", "knit, light") stored as a TOML file in a
presets directory of the parameters crate. The same code as user input validates it, and a generated
page documents it. In VectorCraft, users can also save appearance (including embroidery live effects) as
Graphic Styles. Applying one copies its effect records into each object, and a design sews them
(integration, REQ-VC-006).
Versioning and migration
The registry has a schema version. A breaking change (renamed key, changed unit) ships a migration
function vN → vN+1 and a conformance case with an old-format input. Unknown keys from newer files are
preserved on round trips where the host allows it, and reported once as SC-W0105.
Diagnostics
A diagnostic is a problem with the user’s design or input, explained in the user’s terms, with a
stable code. Diagnostics are values: the engine collects them and keeps planning whatever it can.
Errors in the Rust sense (Result) are reserved for failures to do the work at all
(architecture).
pub struct Diagnostic {
pub code: Code, // SC-W0702
pub severity: Severity, // Error | Warning | Info
pub element: Option<ElementId>, // which object
pub at: Option<Point>, // where on the canvas (mm)
pub message: String, // one sentence, specific: "Design is 162 × 148 mm; …"
pub fix: Option<Fix>, // a hint, or a machine-applicable fix
}
Codes
SC- + E (error: this element, or the design, cannot be stitched), W (warning: stitched, but
probably not what you want) or I (info) + four digits grouped by area:
| Range | Area |
|---|---|
| 0000–0099 | Input and budgets |
| 0100–0199 | Parameters |
| 0200–0299 | Satin |
| 0300–0399 | Fills |
| 0400–0499 | Strokes (running, bean, manual, zigzag, ripple) |
| 0500–0599 | Plan assembly (ties, travel, trims, colours) |
| 0600–0699 | Formats and threads |
| 0700–0799 | Machine profile (hoop, limits) |
| 0800–0899 | Hosts (SVG, VectorCraft) |
| 0900 to 0999 | Automatic digitizing (image to embroidery) |
Codes are never reused. A retired code keeps its page with “retired in vX”.
The registry
All codes live in one table in stitchcraft-core: the registry! block in src/diag.rs, which
defines the Code enum. Each entry is one line — variant, id, severity, title — and its doc comment is
the long explanation (Markdown: what it means, why it matters for the sew-out, how to fix it), so the
text users read is also the code’s rustdoc. Tests reject an entry whose id is malformed or duplicated,
whose letter disagrees with its severity, or whose explanation is missing. From that table:
cargo xtask docsgenerates the diagnostics index (one page per code, likerustc’s error index);stitch explain SC-W0702prints the explanation in the terminal;- in VectorCraft, the plug-in shows each message after its code, for the user to look up in the index or
with
stitch explain; cargo xtask conformancefails when a code has nodiag_sc_<code>_<what>test that produces it from real input and checks the text a user reads, or when such a test names a code that is not registered (conformance); the registry’s own tests fail when a code has no explanation.
That last rule is what keeps error paths alive: a message that no test ever renders can ship with a typo in its formatting and nobody sees it until a user does.
Writing good messages
- Say what is wrong and by how much: “Rows are 0.25 mm apart but the region is only 0.18 mm tall.”
- Name the object and point at the place.
- Offer the fix in embroidery terms: “Use a running stitch for parts this thin, or widen the shape.”
- No blame, no internal names (
LineString,GEOS), no stack traces.
As text
A diagnostic reads the same wherever StitchCraft prints it. Its Display is the severity, the code and
the message: warning SC-W0702: Design is 190.0 × 150.0 mm; …. Diagnostic::headline adds the object
it is about, as in warning SC-W0307 (svg:patch:fill): The area falls into 2 parts., and the command
line prints that, with the fix on the line below. Diagnostic::one_line puts the fix after the
headline, for a host that shows each diagnostic as one line of text, as VectorCraft shows an export’s
warnings.
Fixes
Fix::Hint(String) is advice. Fix::Apply(Edit) is a change the host can apply with one click and
undo (for example “rotate the design 90°” for SC-W0702 when the rotated bounds fit, or “drop the
dangling rung” for SC-W0203). Applied fixes go through the host’s command system so they are undoable.
Initial codes
Codes referenced by the design docs; each is registered in the milestone that implements it. A
registered code must keep its row here, with the same severity and a title that starts with the
registry’s (cargo xtask docs --check), so a code allocated for one thing cannot be registered for
another.
| Code | Severity | Title | Milestone |
|---|---|---|---|
SC-E0004 | Error | Budget exhausted (work for one element, work joining the elements, or stitches for the design) | M1 |
SC-E0005 | Error | Preview too large (over 4,096 pixels on a side at the requested scale) | M2 |
SC-E0009 | Error | Internal check failed (a StitchCraft bug; a bug-report bundle is written) | M1 |
SC-E0010 | Error | Nothing to stitch (no embroiderable elements) | M1 |
SC-W0011 | Warning | Stitch type not sewn yet; element skipped | M3 |
SC-E0012 | Error | Bug-report bundle could not be replayed | M3 |
SC-E0101 | Error | Parameter has the wrong type or an unknown choice | M3 |
SC-W0102 | Warning | Parameter clamped to its allowed range | M3 |
SC-W0105 | Warning | Unknown parameter preserved but ignored | M3 |
SC-E0201 | Error | Satin column has no rails (no subpath longer than a point) | M4 |
SC-W0202 | Warning | Satin rails taken as the two longest subpaths (where the subpaths meet does not say) | M4 |
SC-W0203 | Warning | Satin rung does not cross both rails (dangling rung); the rail’s nearest point is used | M4 |
SC-W0205 | Warning | Satin subpath is one point; left out | M4 |
SC-W0206 | Warning | Satin contour underlay too short for its insets; it keeps its length | M4 |
SC-W0207 | Warning | Satin rung crosses a rail more than once; left out | M4 |
SC-W0208 | Warning | Satin stitches lean more than 45° from square to the column; add a rung here | M7 |
SC-W0209 | Warning | Satin stitch longer than 12 mm; it may snag | M7 |
SC-W0210 | Warning | Satin rails without rungs have different numbers of nodes (some pair with none) | M4 |
SC-W0211 | Warning | Satin push compensation too long for a rail; that rail keeps its length | M4 |
SC-W0212 | Warning | Satin column drawn as one path too narrow; sewn as a stroke | M4 |
SC-W0213 | Warning | Parts of a satin column drawn as one path left out (still crossing after 20 cuts in half, or turning too tightly for its width) | M4 |
SC-E0214 | Error | Satin column drawn as one path could not be made into rails (too short, or too tight for its width) | M4 |
SC-W0215 | Warning | Satin column too small for the shortest stitch; left out (all within it of its first needle point, or a zigzag satin with no stitch across, underlays and all; Ink/Stitch sews a speck) | M4 |
SC-E0216 | Error | Satin column has no section to sew (its rungs or push compensation take each section’s length off a rail) | M4 |
SC-W0303 | Warning | Parts of a fill too small to sew left out (3 square CSS pixels or less, as in Ink/Stitch); a fill of no area sews nothing | M5 |
SC-W0304 | Warning | Fill smaller than 1.4 mm² (20 square CSS pixels); a running stitch or a satin column may sew it better | M5 |
SC-W0305 | Warning | Fill part too thin for rows; sewn round its outline (as in Ink/Stitch) | M5 |
SC-I0306 | Info | Nonzero fill rule fills a part inside that the even-odd rule leaves empty (Ink/Stitch leaves it empty) | M5 |
SC-W0307 | Warning | Fill in parts that are apart; each sewn on its own, joined by jumps | M5 |
SC-I0308 | Info | Fill underlay too thin for rows; sewn round the outline (as in Ink/Stitch) | M5 |
SC-W0309 | Warning | Fill’s expand shrinks a part away; sewn at its own size (as in Ink/Stitch) | M5 |
SC-W0310 | Warning | Fill’s underlay inset shrinks a part away; underlay over all of it (as in Ink/Stitch) | M5 |
SC-W0311 | Warning | Contour fill leaves out rings it does not reach, where its rings part or lie too far apart | M7 |
SC-W0312 | Warning | Fill’s pull compensation below 0; read as 0 (as in Ink/Stitch) | M5 |
SC-I0313 | Info | Fill drawn twice along its edge; Ink/Stitch fills the part left empty (its repair merges the stretch) | M5 |
SC-W0314 | Warning | Legacy fill sewn as a tatami fill without underlay (Ink/Stitch’s legacy fill is not built) | M7 |
SC-W0401 | Warning | Path too small for the shortest stitch; skipped | M3 |
SC-W0402 | Warning | Stitch length below twice the shortest stitch; raised to it, so even spacing never sews a stitch shorter than the shortest | M3 |
SC-W0403 | Warning | Hand-placed stitch shorter than the shortest stitch; point left out | M3 |
SC-W0404 | Warning | Hand-placed stitch longer than max_stitch_length_mm; kept for the shortest stitch | M3 |
SC-W0405 | Warning | Zigzag stitch shorter than the shortest stitch; the zigzag sews narrower | M7 |
SC-W0502 | Warning | Lock stitch shorter than 0.2 mm; lengthened | M3 |
SC-W0503 | Warning | Custom lock cannot be sewn as written | M3 |
SC-I0504 | Info | Stitches shorter than the shortest stitch merged | M3 |
SC-W0505 | Warning | Trim or stop after an element that sews nothing; left out | M3 |
SC-E0601 | Error | Too many colour changes for the file format | M1 |
SC-E0602 | Error | Design too large for the file format (coordinates or data exceed its fields) | M1 |
SC-E0603 | Error | Machine file could not be read (wrong format, truncated, or a record that makes no sense) | M2 |
SC-W0604 | Warning | Thread colours unknown (the file stores none, as DST never does; threads are placeholders) | M2 |
SC-I0605 | Info | Long jumps cut in DST (machines cut the thread before three or more jump records in a row) | M3 |
SC-W0606 | Warning | Threads share a Brother colour (more different threads than a PES file’s 61 thread colours) | M3 |
SC-E0701 | Error | Design does not fit the hoop | M1 |
SC-W0702 | Warning | Design is larger than the comfort zone (the profile’s most accurate area) | M1 |
SC-I0703 | Info | Stitches longer than the machine’s longest stitch; split | M3 |
SC-E0801 | Error | SVG could not be read (with the parser’s position) | M3 |
SC-W0802 | Warning | SVG feature ignored (e.g. raster image, text not converted to paths) | M3 |
SC-W0803 | Warning | VectorCraft file from a newer format version; read best-effort (a version newer than the newest the adapter reads) | M6 |
SC-W0804 | Warning | Element geometry invalid or out of range (path data error, invalid transform, draws nothing, beyond 10 m, a stroke’s width, join or miter limit that does not read); the usable part is stitched | M3 |
SC-I0805 | Info | Object left out, as the file asks (Ink/Stitch’s ignore commands and ignore_object setting) | M3 |
SC-E0806 | Error | VectorCraft file could not be read (not a VectorCraft document, or larger than the adapter reads) | M6 |
SC-W0807 | Warning | VectorCraft object or feature not stitched (text, images, symbols, live objects, clipping, VectorCraft’s own effects, brushes, gradients, patterns, a group’s own paints), StitchCraft effects from more than one level, a start or end marker, or a stitch: name StitchCraft doesn’t read | M6 |
SC-W0808 | Warning | VectorCraft geometry unusable (a number that does not read or a point beyond 10 m; a shape that draws nothing) | M6 |
SC-I0809 | Info | VectorCraft layer or guide left out, as it does not print (template layers, layers whose printing is off, guides) | M6 |
SC-I0810 | Info | VectorCraft live effect leaves the object as it is (it sews no stitch, its preview level needs more work than VectorCraft lets a live effect do, it is a satin column drawn as one path or a zigzag stroke and VectorCraft gives the effect no stroke, or the effect is on one of the object’s fills or strokes) | M6 |
SC-I0811 | Info | VectorCraft object left out, as its name asks (an object or a layer named stitch:ignore) | M6 |
SC-W0812 | Warning | VectorCraft design setting ignored (a length that isn’t a number of 0 or more, or a machine StitchCraft doesn’t have, which the Design Settings filter keeps with the document) | M6 |
SC-E0813 | Error | VectorCraft too old for the filter (the Design Settings filter run by a VectorCraft before 0.16, which doesn’t keep a filter’s data, Stroke to Satin by one before 0.15, which doesn’t keep a filter’s effect records, and Test Sheet by one before 0.16, which doesn’t say it keeps them) | M6 |
SC-E0814 | Error | VectorCraft line not made into a satin column (Stroke to Satin refused a selection because it is empty or a path has no stroke, or because a line drawn with its rails and rungs would sew otherwise) | M6 |
SC-I0901 | Info | Shapes digitized automatically (--auto), counted by their stitches (running stitches, satin columns or tatami fills) | M13 |
SC-I0902 | Info | Line a satin column cannot follow; filled (a part of an automatic line that turns more tightly than it is wide or is shorter than a CSS pixel, or a dot its cap draws) | M13 |
SC-W0903 | Warning | Line too wide to fill; left out (an automatic line whose filled area would reach more than 10 m from the origin) | M13 |
SC-I0904 | Info | Areas reach under the shapes sewn after them (automatic areas grown 0.4 mm under the shapes sewn after them that they touch, or cut away under areas sewn after them) | M13 |
SC-I0905 | Info | Speck merged into a neighbour (an automatic area or a part of one smaller than 1 mm², merged into the area it shares the longest edge with, or left out) | M13 |
SC-W0906 | Warning | Background left out (the lowest shape, an automatic area that covers the whole design under the other shapes) | M13 |
SC-I0907 | Info | Elements sewn in an order of their own (automatic elements, and with --sort-colours every element, sewn in the order StitchCraft chooses, with the thread changes of each order; an element or an order that needs more work than the budget allows keeps paint order) | M13 |
SVG input
The SVG adapter, stitchcraft-svg, turns an SVG file into a Design
and a list of warnings. It reads what an SVG viewer draws, in millimetres, and reports what it leaves
out. The engine never sees SVG (architecture).
What becomes an element
- Shapes. Each
<path>,<rect>,<circle>,<ellipse>,<line>,<polyline>and<polygon>that paints becomes one element per paint. A fill becomes an area and a stroke becomes an outline. A<line>has no fill. - Where a shape starts. A running stitch starts where its path starts and runs the way it runs, and each basic shape starts and runs as Ink/Stitch reads it. A circle or an ellipse starts at its top and turns left on screen. SVG 2 starts it at its right and turns the other way, which only dashes or markers would show.
- Order. Elements follow document order, which is SVG’s paint order and the sewing order. A
shape’s fill is sewn before its stroke, as Ink/Stitch sews them, and the outline covers the fill’s edge.
When
paint-orderpaints the stroke first,SC-W0802says the fill is sewn first. - Groups.
<g>,<a>and<switch>pass their transform and style to their children. A<switch>draws only its first child whose conditions pass, as a viewer does. Ink/Stitch sews every child of a switch, and drops the transforms of the switch and of the elements above it (DEV-SVG-004). - Ids. An element’s id is
svg:<label>:fillorsvg:<label>:stroke. The label is the SVG element’sid, or<tag>@<n>for the n-th element of that kind. A repeated id gets~2,~3and so on (stitchcraft_core::Labels, as in the VectorCraft adapter). The adapter’s own diagnostics name the SVG element assvg:<label>. - Names. An
inkscape:labelbecomes the element’s name. - Automatic elements. Read with
--auto(Reading::auto), a shape with no attribute in Ink/Stitch’s namespace is an automatic element, whose stitches StitchCraft chooses by its width (image to embroidery,REQ-AUTO-001). The trim and stop of Ink/Stitch’s commands attached to it still apply. A shape with anyinkstitch:attribute is not automatic: it sews as its settings say once they are read (M8), and with the defaults until then. Read with--sort-colours(Reading::sort_colours), the design’s shapes with settings sew in the order StitchCraft chooses too (image to embroidery,REQ-AUTO-011).
Geometry and units
- The root’s
width,height,viewBoxandpreserveAspectRatiomap user units to millimetres. A user unit is a CSS pixel, 1/96 inch, unless they say otherwise. A root with only a width or only a height takes the other from its viewBox’s shape, as SVG 2 sizes an image, and a percentage counts as missing. Ink/Stitch takes both sides from the viewBox, in pixels, when either is missing (DEV-SVG-009). - A viewer ignores a root
width,heightorviewBoxthat is empty, and apreserveAspectRatiothat does not read. The adapter ignores them too, andSC-W0804names each. Awidth,heightorviewBoxthat does not read could put the design at any size, and the file is refused (SC-E0801). So is a size of 0 or less, and a viewBox with no area, in which a viewer draws nothing. - Transforms compose through every ancestor. The matrices use
stitchcraft_core::math, so the result is the same on every platform (determinism). - Path data is read as SVG viewers draw it, arcs included. An error in the data, or in the list of
points of a
<polyline>or<polygon>, ends the path at the error, andSC-W0804names it. inkex skips what is not a number in a list of points, and Ink/Stitch sews the rest (DEV-SVG-010). - A path has the subpaths a viewer draws. A move-to with nothing drawn after it is no subpath, and a
command that draws after a closepath starts a new subpath where the closed one started. inkex keeps
such a move-to as a subpath of one point and runs the closed subpath on, so Ink/Stitch can count other
subpaths, which tell a satin column’s rails from its rungs (
DEV-SVG-011). - An arc becomes cubic curves as inkex, which Ink/Stitch reads files with, cuts it: in equal pieces, one
more than the whole quarter turns it makes. Manual stitch puts a needle point on each piece’s end, as
Ink/Stitch’s does. inkex counts the quarter turns in floating point, and its rounding can cut an arc of
a whole number of quarter turns into one piece fewer (
DEV-SVG-002). - A
<circle>or an<ellipse>starts at its top and goes round anticlockwise on screen, as inkex draws it, so a stroke round it starts and runs as Ink/Stitch’s does. - A
<rect>with one radius rounds its corners with it, and one with a radius of 0 has square corners. An<ellipse>with one radius is a circle of that radius. That is how SVG 2 draws them. inkex rounds a rect whoserxorryis 0 with the other, and takes an ellipse’s missing radius as 0 (DEV-SVG-006). - Positions are exact to within a micrometre (
REQ-SVG-001). Any path data reads without failing the adapter, and fuzzing checks it (REQ-SVG-002). - A length may have spaces around it. One that does not read is 0, as in a viewer, and
SC-W0804names it. A percentage is of the viewport’s width, height or diagonal over √2, anemis 16 user units and anex8, as with a viewer’s default font. inkex reads them as 0 in a shape’s attributes (DEV-SVG-005).
Colour and visibility
fill,stroke,colorandcurrentColorgive the thread colour. A gradient gives its first colour and a pattern its fallback colour, each with a warning. A gradient of one colour, such as an Inkscape swatch, gives that colour without one. A gradient without stops paints nothing, as in a viewer, and so does a reference to an element the file does not have, unless a colour is written after it. Ink/Stitch sews such a fill in black (DEV-SVG-007).- Colours read as editors write them. Keywords such as
currentcolormay be in any case. An ICC colour after the sRGB one, which Inkscape’s colour-managed picker writes, is left for the sRGB colour. - Properties come from presentation attributes (
fill="red"), the rules of style sheets and thestyleattribute, in the order of CSS’s cascade, as in a viewer (REQ-SVG-006). A rule overrides a presentation attribute, and thestyleattribute overrides a rule. Of 2 rules, the more specific one applies (more ids, then more classes, then an element name), and of 2 as specific, the later one. An!importantdeclaration overrides all of these. A paint, colour, stroke width, join or miter limit that does not read gives way to the next declaration, as in CSS. When none of an element’s paints reads, the inherited paint stays, andSC-W0802names the value. inkex takes the declaration the cascade puts first whether it reads or not, and Ink/Stitch sews a fill whose colour does not read in black and leaves such a stroke out (DEV-SVG-008).display,visibility,opacity,fill-opacity,stroke-opacity,fill-ruleandpaint-orderare read as inkex reads them, from the declaration the cascade puts first. A value there that does not read is ignored, and the declarations after it are not used, where a viewer would use the next one. - Style sheets are the
<style>elements of the file, wherever they are, whose type is CSS. Illustrator and CorelDRAW write a drawing’s colours as classes there. The adapter reads a rule whose selector is an element name or*followed by classes and ids, such asrect,.cls-1,#logoorpath.st0.st1. It reads a list of such selectors, such as.a, .b, as well. OneSC-W0802names the other selectors, such asg > rectora:hover, and the at-rules, such as@mediaand@import. What they set is not used. Ink/Stitch reads styles through inkex, which applies more kinds of selector. display: nonehides an element and everything in it. A hidden or fully transparent paint is left out, as a viewer leaves it out.visibilityhides a paint, and so does anopacityof 0 on its element or above it. Afill-opacityorstroke-opacityof 0 makes its paint clear, as does a colour that istransparentor has an alpha of 0. Ink/Stitch leaves out only whatdisplayhides and a fill whosefill-opacityis written0(DEV-SVG-003). The insides of<defs>,<symbol>,<marker>,<pattern>,<clipPath>and<mask>are never drawn.
Stroke width and join
A stroke’s width and join give a satin column drawn as one path its width and its corners
(single-path satin). Other strokes are sewn along their paths,
however wide they are drawn. Both are read as Ink/Stitch reads them (REQ-SVG-004), except an automatic
element’s join, read as SVG draws it. Its cap matters only to an automatic element, whose stitches cover
the line as it is drawn (REQ-AUTO-003).
- Width. The width is
stroke-width, inherited, 1 user unit when nothing sets it. It is a length in the shape’s own user units, as in a viewer, and a percentage is of the viewport’s diagonal over √2. The transforms down to the shape scale it by the average of how far they stretch the x and y axes. That is exact when they scale both axes alike. Under an uneven scale or a skew, a viewer draws the stroke wider one way than the other, and the average is the middle way. An automatic line sewn across its width is then as wide as that average all along.SC-W0802names such a line (REQ-AUTO-005). The transforms scale a stroke withvector-effect: non-scaling-stroketoo, as Ink/Stitch scales it, though a viewer does not. - Join.
roundandbevelare read as they are named. Amiterjoin is limited bystroke-miterlimit, 4 when nothing sets it, as SVG says. Any other join, or none, is a miter limited at 5, as in Ink/Stitch. SVG 2’smiter-clipandarcscount as other joins. An automatic element’s corners follow the drawing instead (REQ-AUTO-004). There, a join that is not set is SVG’s initial miter, limited bystroke-miterlimit, and so aremiter-clipandarcs, which few viewers draw. - Cap.
stroke-linecap, inherited,buttwhen nothing sets it:butt,roundorsquare, in any case. Ink/Stitch sews no cap, and reads none. - Values that do not read. A width, join or limit that does not read is ignored, as a viewer
ignores it, and
SC-W0804names it. A cap that does not read is ignored too, without a word: it changes nothing for a shape with settings. A negative width and a limit below 1 do not read. A stroke too wide to measure, such as1e308in, is left out withSC-W0804. Its fill is still sewn.
Ink/Stitch reads a width in %, em or ex as 0, and a join written in capitals as no join at all. The
adapter reads them as a viewer draws them (DEV-SVG-001).
Ink/Stitch objects
An Ink/Stitch SVG has 3 kinds of object that Ink/Stitch reads and does not sew. The adapter sews none of
them (REQ-SVG-003).
- Commands. The
trimandstopcommands turn on the object’strim_afterandstop_after. Theignore_objectandignore_layercommands leave out an object or a layer, as theinkstitch:ignore_objectsetting does when it says yes in one of Ink/Stitch’s words, which a toggle reads too, andSC-I0805lists each object left out. Thestarting_pointandending_pointcommands, which place where Ink/Stitch starts and ends a fill or a satin column, are not applied yet (REQ-GEN-001), andSC-W0802names each one on an object a viewer shows. The command table gives what the adapter does with each of the 12 commands. - Connectors tie a command to its object.
- Helper paths have one of Ink/Stitch’s start markers. They steer the stitches of other objects.
SC-W0802 reports a connector that ties no command, a helper path, and a command that the adapter does
not apply yet. The module docs of crates/stitchcraft-svg/src/inkstitch.rs describe how the adapter
finds these objects, with the Ink/Stitch files they were read from.
Design settings
Ink/Stitch keeps 3 settings for the whole design in the file’s first <metadata> element:
collapse_len_mm, min_stitch_len_mm and min_satin_stroke_width_mm. Each is an element of
Ink/Stitch’s namespace whose text is the value in millimetres, and the first of each name counts. They
become the design’s collapse length, shortest stitch and narrowest satin stroke (REQ-SVG-005).
SC-W0802 names a value that is not a number of 0 or more, and the design keeps its default. The
defaults are in the data model. The settings’
names, with the label and help a dialog shows, are listed once in stitchcraft_engine::design::SETTINGS,
which a VectorCraft document’s settings are read with too
(integration).
Reports
SC-W0802reports a feature that is not stitched. Text, images, clones, nested<svg>elements and the style sheet rules that are not read are such features. So are clipping, masks, filters, markers and the Ink/Stitch settings that are not read yet. Clipping is planned for M8.SC-W0804reports geometry that cannot be used:- path data or a list of points with an error, stitched up to the error
- a transform that does not parse, left out with its contents
- an element that draws nothing
- a point more than 10 m from the origin
- a stroke width, join or miter limit that does not read, and a stroke too wide to measure
- an empty root
width,heightorviewBox, and a rootpreserveAspectRatiothat does not read
SC-E0801refuses a file that is not SVG, or that is larger than the adapter reads. It refuses a file whose size cannot be used too: a rootwidth,heightorviewBoxthat does not read, a size of 0 or less, or a viewBox with no area.
Text and limits
A file is UTF-8 text, or ISO-8859-1 when its XML declaration says so, which the adapter turns into UTF-8. A file in any other encoding is refused when it has a byte outside ASCII.
A file is at most 64 MiB with a million XML nodes, and its style sheets have at most a million selectors,
declarations and at-rules. Illustrator declares its namespaces as XML entities, and an entity whose value
is plain text is read. A file is refused when an entity’s value holds markup or other entities, or when
its references would expand the file past 64 MiB. Without these rules a small file could need a large
amount of memory. Only the document type declaration’s internal subset, <!DOCTYPE svg [ … ]>, declares
entities: a declaration in a comment or in character data declares nothing. A name declared twice counts
at its longer value. XML keeps the first declaration, and the limit must hold whichever the parser keeps.
Reading charges the budget for each XML node, path segment and 8 bytes of a transform, point list or
style sheet. Each element is charged for its classes, the style sheet rules tested against it and the
declarations it takes (budgets).
Engine pipeline
From a Design to a checked StitchPlan. Each stage is a module in stitchcraft-engine with its
own tests; stages communicate only through the types in the data model.
Design ──▶ 0 auto ──▶ 1 normalize ──▶ 2 validate ──▶ 3 generate (per element) ──▶ 4 assemble ──▶ 5 finalize ──▶ 6 check ──▶ StitchPlan
│ │ │ │ │ │ │
└────────────┴───────────────┴───── diagnostics accumulate at every stage ┴──────────────┴──────────────┘
0. Automatic digitizing
Since M13.1, before anything else, each element the host marked automatic, a shape without settings read
with --auto, becomes the ordinary elements its width calls for, with the settings that say so
(stitchcraft_engine::auto::digitized, image to embroidery).
- An area is sewn as it is, apart from its narrow parts: each becomes a running stitch along its centre line (M13.4), and the rest of the area the fill of its other parts.
- A line that runs is sewn as it is, tripled from 0.5 mm wide.
- A line sewn as a satin column becomes a column for each of its subpaths, or the fill of a subpath a
column cannot follow (
SC-I0902). - A wider line becomes the fill of the area it paints.
Every stage after it sees only ordinary elements. A digitized shape sews, offers its neighbours what it
offers and is said of as the same shape set by hand would be. SC-I0901 counts the shapes digitized. A
design without automatic elements passes through unchanged and uncopied. Each automatic element has the
budget’s work to itself, naming its parts included, and one that needs more is left out with SC-E0004
(REQ-GEN-002). Digitizing doesn’t add an error of its own (REQ-AUTO-006). What cannot be sewn as its
width says is sewn otherwise or left out with a warning.
The digitized elements are then sewn as neighbours, in an order of their own. What overlaps keeps its
paint order, and a line sews after the fill it borders. Then each thread sews as long as it can, the
nearest element next (image to embroidery, SC-I0907). Elements
with settings keep their order among themselves, unless --sort-colours is given. The switch brings a
design without automatic elements through this stage too.
1. Normalize
Per element, independent of the others:
- Flatten curves with the element’s tolerance (a tenth of
running_stitch_tolerance_mmfor strokes, and a tenth of a CSS pixel for fills and satin columns, as Ink/Stitch flattens them). Curves are halved (de Casteljau) until each piece’s control points lie within the tolerance of its chord (stitchcraft_engine::normalize::stroke), using only arithmetic and square roots so that every platform gets the same points.kurbo’s adaptive flattening is not used for this: in 0.13 it callspowf(CubicBez::to_quads) andhypot(QuadBez::estimate_subdiv) from the platform’s maths library, whose last bits differ between systems (determinism). Corners, joins between segments that turn by more than 30°, are marked so running stitches land exactly on them; a curve’s own bend is never a corner. - Regions: a fill’s subpaths are cut wherever they meet, and its fill rule says which faces between
them are filled. The edges between filled and empty faces become outlines and holes, each turned one
way, and parts too small to sew are left out with
SC-W0303(fills › Region,stitchcraft_engine::normalize::region). - Satins: classify subpaths into rails and rungs (satin), or make them from a column’s centre line (single-path satin).
- Strokes: each subpath becomes its own path piece, in document order.
- Commands become hints (start/end/target points) on the element.
Adapters have already applied transforms and converted units, so normalization never sees host types.
2. Validate
- Parameters are read by the registry as the element’s stitch type reads them. Blank text takes the default, and a setting the stitch type does not use, or that its other settings hide, is kept unread (parameters). The rest are checked for type and range, and the generator receives a typed struct or nothing.
- Shapes are checked against the stitch type. A satin needs a subpath longer than a point (
SC-E0201), a fill needs a non-empty region, and a running stitch needs a path longer than the minimum stitch length. - An element with an error diagnostic is skipped; the rest of the design still plans. A design whose
every element was skipped yields
SC-E0010.stitch planwrites the machine file only when no error is said, as Ink/Stitch writes its file only when no element fails (REQ-CLI-003).
3. Generate
Each element becomes stitch groups (stitchcraft_engine::generate): the needle points of each part of it
that a jump may separate from the next, in sewing order. A stroke gives one group per subpath, and a part
too small for a stitch gives none (SC-W0401). The element’s own settings stay with the element, and
assembly reads them there: its thread, locks, trim and stop.
The stitch type picks the generator. A stroke whose satin_column setting is on is a satin column,
whatever its stroke_method says, as in Ink/Stitch. Its rails and rungs are recognized, what
recognition finds is reported, and its satin_method picks the generator: the satin stitch, sewn from
M4.2, the E-stitch, from M7.3, the S-stitch, from M7.4, and the zigzag satin, from M7.5. A satin column also reads the running stitch’s length (running_stitch_length_mm): the stitches
that join its underlays are no longer than the first value. For any other stroke the generator is its
stroke_method. running_stitch (the default) and manual_stitch are sewn from M3, and zigzag_stitch
from M7.2, as wide as the stroke. A satin column drawn as one path no wider than the design’s
min_satin_stroke_width is sewn as a stroke, with SC-W0212, as in Ink/Stitch (REQ-SAT-015). Wider,
it is sewn between rails made from its line from M4.9 (REQ-SAT-016). A fill whose fill setting is
off sews nothing, and nothing is said of it (REQ-FILL-003). Another fill’s region is built first, so
what it leaves out and how it will be sewn are said (SC-W0303, SC-W0304, SC-I0306, SC-W0307)
whatever comes next, and its fill_method picks the generator: the tatami fill, sewn from M5.4, which
reads the running stitch’s length and tolerance for its travel between rows (the first of the lengths,
as a satin column does), under the rows from M5.5, and sews its underlay before its top rows from
M5.6. A fill set to Ink/Stitch’s legacy fill is sewn as a tatami fill without underlay, with
SC-W0314: StitchCraft does not build that method (REQ-FILL-005). The other stroke, satin and
fill methods are skipped with SC-W0011 until their milestones. An element whose parameters are wrong
(SC-E0101) is skipped too, and the rest of the design still plans.
Each element is generated with the shortest stitch for it: the larger of the machine’s (the profile’s
min_stitch) and the element’s min_stitch_length_mm, or the design’s shortest stitch when the element
sets none. A satin column is given its longest stitch as well, the element’s max_stitch_length_mm,
which its split stitches keep to.
A preview that cannot afford a fill’s stitches shows its top rows (stitchcraft_engine::generate::rows,
from M6.3). The fill’s settings are read as for sewing it, and what they say is said the same, but only
the region is built and the rows laid over it: no needle point is placed and no route is searched.
Rules every generator follows:
- Pure: output depends only on (normalized shape, typed params, neighbours, seed, budget).
- The needle before, the next element after. Elements are generated in sewing order
(ADR 0014,
REQ-GEN-003). Each is given the last needle point of the elements before it, whatever their thread, and what the next element offers to end near. That is its first point, or, when it starts at its own nearest point, its shape: a satin column’s rails or a tatami fill’s area. The offer comes from the next element’s shape and settings and the design’s, never its stitches, so nothing waits for a later element. A satin column and a tatami fill start and end by them (satin, fills), and explicit start and end commands will override them (REQ-GEN-001). Each ends near its next stitch, its point nearest the offer as Ink/Stitch’s geometry library finds it, a point of one shape inside the other’s area first (generators::next). - Seeded randomness: each element’s generator is seeded from its
random_seed, one of the settings every stitch type shares. When that is empty, a hash of its id seeds it. A seed replaces the id, as in Ink/Stitch. The PRNG is SplitMix64 fromstitchcraft-core(determinism). - Budgeted: every inner loop charges work units. Each element has the budget’s work to itself. One
that runs out (
SC-E0004) is skipped, and the others still plan. The stitch limit is for the whole design. Assembly and finalize share the budget’s work once more, and running out there plans nothing.SC-E0004then says so for the whole design. A generator also spends the design’s stitches on the needle points it makes (budgets,REQ-GEN-004). Each element gets what the elements before it left, and the first that needs more ends planning, withSC-E0004naming it. - Roles: every stitch is tagged by its role. Assembly tags the stitches of groups as top and the lock stitches as lock. The jumps between groups are travel. Previews draw the lock stitches apart. A generator hands assembly its needle points without roles, so an element’s underlay, and the running stitches between its rows, are tagged top as well, until generators tag their own.
Generators are listed in algorithms.
4. Plan assembly
Groups are joined into colour blocks in the order the elements sew (host paint order, bottom first, or the
order stage 0 chose), by stitchcraft_engine::assemble.
Ordering
- Assembly keeps the order the elements come in, so that embroidery stacking follows the art’s stacking.
That is paint order, unless stage 0 put the automatic elements, or with
--sort-coloursevery element, in an order chosen for few thread changes, in which a shape still sews after every shape it is drawn over, where they overlap by more than rounding leaves. - A thread change starts a new colour block (
REQ-ASM-001). Threads are compared by colour, as Ink/Stitch compares them: two names for one colour are one thread, and the block keeps the first.
Connecting consecutive groups
The needle sews straight on from one group to the next only when nothing separates them. With d the
distance from where the needle is to the next group’s first needle point:
| Between two groups | Connection |
|---|---|
Same thread, d no more than the earlier element’s min_jump_stitch_length_mm if it sets one, else the design’s collapse length (3 mm), and the earlier element does not force locks | Sewn on: the next group’s first stitch starts where the needle is; finalize splits it if it is longer than the machine’s longest stitch |
| Same thread, otherwise | Tie-off → Jump → tie-in (REQ-ASM-002) |
After an element’s last group, when it says trim_after | Tie-off → Trim → Jump → tie-in (REQ-ASM-003) |
After an element’s last group, when it says stop_after | Tie-off → Jump to the stop position, if the design has one → Stop → Jump → tie-in (REQ-ASM-003, REQ-ASM-005) |
| Thread change | Tie-off → a new colour block → Jump → tie-in |
| Before the design’s first group | Jump → tie-in |
| After the design’s last group | Tie-off |
An element that sews nothing, because it is skipped or too small for a stitch, has no place for its trim
or stop. Both are left out, as Ink/Stitch leaves them out, and SC-W0505 says so.
A jump lands where sewing resumes, on the tie-in’s first point or on the group’s first point when it has
no tie-in, and the needle goes down there before the first stitch. The jump’s own thread is the
machine’s business. The plan trims where an element’s trim_after is set, as Ink/Stitch’s does. A PES
file also flags every jump as a trim, as pystitch writes Ink/Stitch’s
(formats), and whether a machine cuts at a jump is its own setting. At
MC-1 the reference machine cut the thread only at thread changes and stops.
Ink/Stitch (read at d59c9ab) joins groups the same way: one stitch from one group to the next within
the collapse length, locks and a jump beyond it. It sews that stitch as it is; StitchCraft’s finalize
splits it if it is longer than the machine’s longest stitch, which only a min_jump_stitch_length_mm
beyond that length can cause.
Lock stitches (ties)
- A tie-in goes only at the start of a group that the needle jumps to, the design’s first included. A
tie-off goes only at the end of a group that a jump, trim, stop or thread change follows, or that ends
the design. Groups sewn on from one to the next are sewn without locks between them (
REQ-LCK-001). tiessays which of those an element’s groups get: both, the tie-in (before), the tie-off (after) or neither.force_lock_stitchesadds the tie-off whatevertiessays, never a tie-in. It also makes each group of the element end with a jump, and the tie-off is sewn there. That is Ink/Stitch’s rule (read atd59c9ab).- Manual stitch is sewn without locks unless
force_lock_stitchesis set, because its points are placed by hand. - A group with fewer than two needle points is sewn without locks: it has no stitch to lock.
- Lock stitches specifies the lock itself: its shape (
lock_start,lock_end), its size and its shortest stitch (0.2 mm).
Origin and stop position
The finished plan is in hoop coordinates: the design’s origin goes to the hoop’s centre, (0, 0), where the
needle starts. The origin is the design’s own (Ink/Stitch’s origin command, read from M8) or else the centre
of the box around every point where the needle goes down (REQ-ASM-005). Assembly builds the plan in the
design’s coordinates, and finalize moves it once it has left out the points the machine cannot sew, so the
box is the finished plan’s, as in Ink/Stitch. A design’s stop position
(Ink/Stitch’s stop position command) adds a jump to it before each Stop. At the stop position the frame
is clear of the needle for an appliqué or a check, and sewing resumes with a jump back.
Commands
| Command | Effect | From |
|---|---|---|
| Start / end point | Entry/exit hints for the generator (REQ-GEN-001). The VectorCraft adapter reads them from markers named stitch:start and stitch:end, which it names until then | M8 |
| Target point | Centre for circular fills and ripple targets | M7, M10 |
| Trim after, stop after | trim_after and stop_after on the element’s last group (see the connection table). The SVG adapter reads Ink/Stitch’s trim and stop commands as these settings. | M3.8 (engine), M3 (adapter) |
| Ignore object / ignore layer | The adapter drops the element and says so in the report (REQ-ASM-004). The VectorCraft adapter reads them from the name stitch:ignore | M3 (SVG), M6 (VectorCraft) |
| Origin | The design setting origin | M3.8 (engine), M8 (adapter) |
| Stop position | The design setting stop_position | M3.8 (engine), M8 (adapter) |
5. Finalize
Finalize fits the plan to the machine profile (stitchcraft_engine::finalize), and a plan that plan
returns can be written as it is. Generators sew within the machine’s limits where their settings let
them. What is left to fit comes from joining groups and from settings the machine cannot follow.
- The shortest stitch of each needle point is its element’s: the one its generator used, so finalize
never thins what the generator spaced. That is the element’s
min_stitch_length_mm, else the design’smin_stitch_len, and never shorter than the machine’s (profile.min_stitch). Where one element’s stitching runs straight on into the next, the stitch between them can be anything up to the collapse length, 0 included. Within each run of stitches, from where the needle lands to the next jump, trim or stop, a needle point less than its shortest stitch from the one before is left out. The stitch then runs on to the next point. The run’s first and last points and lock points stay, and the points before them are left out instead. Elements sewn over each other can bring the last point or a lock point within the shortest stitch of one that stays before it, once the points between are left out. It is then left out itself, the points before it stay, and a trim or stop after it happens where the needle now is. So is a point where the needle already is, which would sew in place. Only the run’s first point always stays. A stitch into or out of a lock point is a lock stitch, whose shortest is 0.2 mm (REQ-PLAN-002).SC-I0504says how many points were left out (REQ-FIN-001). The rule is one function, which a generator that joins pieces of its own also applies, as a tatami fill does to its rows and the travel between them, so that finalize finds nothing of its stitches to thin. A satin column joins its parts by rules of its own instead, since the end of a top stitch must not move (satin). - The longest stitch. A stitch longer than
profile.max_stitchis split into the fewest equal parts no longer than it (SC-I0703,REQ-FIN-001). Such a stitch comes from a stitch placed by hand, a custom lock’s long step, a satin column wider than the machine’s longest stitch, a tatami fill whosemax_stitch_length_mmis longer than it, or a move sewn on under amin_jump_stitch_length_mmthat long. Each part counts against the design’s stitch budget. - The hoop’s centre. The fitted plan is moved so the design’s origin is at the hoop’s centre
(origin,
REQ-ASM-005). - Colour limits: colour changes and stops above what the profile’s format records →
SC-E0601. - Hoop: a design larger than the hoop →
SC-E0701, with a rotate-to-fit fix when rotating 90° would fit. When a design’s origin is far from its middle, a design smaller than the hoop can cross the hoop’s edge. That isSC-E0701as well. So is a stop position past the edge, which the message names. The size is the stitches’ own. For a design that cannot be sewn,SC-E0701is the only message about its size. Otherwise, larger than the comfort zone →SC-W0702(REQ-FIN-002). - The end is the plan’s structure: the machine ends after the last block.
After an error at any step, plan returns no plan, and the error’s diagnostic says why.
Splitting jumps longer than a format can encode in one record is not done here: it is the encoder’s job, because the limit is a property of the file format, not of the machine.
Ink/Stitch (read at d59c9ab) removes short stitches once, over the whole plan: an entry no farther than
its shortest stitch (the element’s min_stitch_length_mm, else the design’s min_stitch_len_mm, 0.1 mm
unless the file sets it) from the last one kept
is dropped. It keeps lock stitches, the stitch right after a jump, and stop, trim and colour-change
entries. A jump itself can be dropped. Nothing is split. StitchCraft uses the same shortest stitch for
each element as Ink/Stitch, but never one below the machine’s (0.3 mm on the Brother). It keeps the ends
of each run and splits stitches the machine cannot sew: a deviation (DEV-FIN-001), because a file can
sew differently in the two tools.
6. Check
The plan invariant checker (stitchcraft-plan::invariants, the same code conformance level L0 runs)
validates the result. A violation is a bug in StitchCraft: plan returns no plan and reports
SC-E0009 internal check failed (REQ-FIN-003). The command line then does not write a machine file.
It writes a bug-report bundle that reproduces the failure (REQ-CLI-002). The conformance suite has a case for every invariant.
7. Incremental and parallel planning
- Cache: a group is keyed by (element shape hash, params hash, neighbours, seed, engine version). The VectorCraft live effects already cache per object; the CLI and future panels reuse the same key.
- Parallelism (native only): elements are generated in sewing order, each seeing its neighbours
(ADR 0014). Reading parameters and recognizing shapes
depend on the element alone. That work may run in parallel with
rayonbehind the CLI’sparallelfeature. Its results are joined in document order, and the output is then identical to a sequential run. The wasm plug-in stays single-threaded.
8. Outputs
| Output | Produced by |
|---|---|
| Machine file | stitchcraft-formats writer chosen by the profile or the output extension |
| Preview PNG | stitchcraft-render, from the quantized plan (REQ-RND-001) |
| Report JSON | The CLI’s, with the counts and size of the machine file read back as a machine reads it, its SHA-256, the threads and the diagnostics |
| Plug-in preview geometry | stitchcraft-vc-plugin, from one element’s groups, its top rows or its path, by the preview’s level (integration) |
Stitch generators
A generator turns one normalized element into stitches. This folder specifies each one: what it must produce, how, which parameters steer it, what can go wrong, and the properties the conformance suite checks.
| Page | Generators |
|---|---|
| Strokes | running, bean, manual, random-length, zigzag stroke, ripple |
| Satin | satin column, E-stitch, S-stitch, zigzag satin, underlays |
| Fills | tatami, contour, meander, circular, guided, linear gradient, tartan, cross stitch |
| Lock stitches | the tie-in and tie-off at the ends of an element’s stitching |
Travel and the joining of elements, and which ends get a lock, are part of plan assembly. Centre lines gives the middle of a narrow part of a shape, which automatic digitizing sews along.
The generator contract
pub trait Generator {
type Shape; // StrokePath, SatinShape or Region
type Params; // typed view generated by the parameter registry
fn generate(
&self,
shape: &Self::Shape,
params: &Self::Params,
hints: &Hints, // entry/exit/target points
rng: &mut SplitMix64,
budget: &mut Budget,
) -> Result<GroupStitches, GenerateError>; // GenerateError -> coded diagnostic
}
- Pure and deterministic; no globals; no allocation proportional to anything but output and input size.
- Never panics; every index is checked; every loop charges the budget.
- Returns stitches tagged with roles, an entry and an exit point.
- Unusual inputs produce a coded diagnostic, a documented fallback, or both — never silence.
Property checklist (every generator)
Every generator page lists its specific properties; these apply to all of them and are checked
automatically by stitchcraft-testkit for every generator registered:
- Determinism: the same inputs give identical stitches; shuffling unrelated elements does not change this element’s stitches.
- Finite and bounded: all coordinates finite and within the shape’s bounds expanded by the compensation parameters plus 0.05 mm.
- Length limits: top stitches within the generator’s maximum length; no zero-length stitches.
- Hints honoured: with start/end hints the first/last stitch lies within 0.1 mm of the nearest
point of the shape to the hint (
REQ-GEN-001). - Degenerate inputs: the shared degenerate corpus (zero-area, hair-thin, self-touching, collinear,
duplicate points, 1-point paths, huge coordinates, 10⁶-vertex paths) never fails and never exceeds the
budget without
SC-E0004. - Monotonicity where meaningful: denser spacing never yields fewer stitches; larger shapes never yield fewer stitches.
Sources
Each page rests on embroidery practice (the terms every digitizer uses), published algorithms and papers, Ink/Stitch’s documentation and, where the documentation is silent, Ink/Stitch’s behaviour as its source shows it. Pages describe all of it in our own words and never copy code; a link to the source a statement rests on is welcome when it helps the next reader check it again (ADR-0012).
Stroke generators
Strokes follow a path. Shape: StrokePath (an ordered list of polylines, corners marked, from the
normalizer). Phase P1 (M3) unless noted.
A stroke starts where it is drawn and ends where it is drawn, as in Ink/Stitch. Its generator is given its neighbours, as every generator is (ADR 0014), and a stroke sews the same whatever they are. A satin column or a tatami fill before a stroke ends near the stroke’s first point.
Running stitch
The workhorse: outlines, details, travel, underlay.
Parameters: running_stitch_length_mm (one length or a repeating pattern of lengths, LengthList),
running_stitch_tolerance_mm, repeats, bean_stitch_repeats, enable_random_stitch_length,
random_stitch_length_jitter_percent, random_seed.
Placement
A stitch is the straight line between two needle points, so placement measures along the path but checks straight. “The shortest stitch” is the one Finalize enforces (settings or profile, whichever is larger).
- Flatten. The normalizer turns the path into polylines within a tenth of the tolerance (the other nine tenths are left for the stitches) and marks its corners: joins between segments where the path turns by more than 30°. A curve’s own bend is never a corner, however tight.
- Split at corners. Every corner gets a needle penetration, so sharp shapes stay sharp, unless it is closer than the shortest stitch, along the path, to the previous penetration or to the end: then the spans on either side of it are joined.
- Even spacing per span. For a span of length
Lbetween corners and target lengths, placen = ceil(L / s − ε)stitches spacedL / napart along the path. Every stitch is at mostsand the last one is never a short leftover — the classic even-division rule digitizers use. With several lengths ("2.5 1.0") the lengths repeat along the path, carrying on from span to span; each span takes as many as it needs and scales them by one factor to end exactly on its corner. Lengths below twice the shortest stitch are raised to it (SC-W0402), so a span longer than the longest length scales its stitches by more than a half and keeps each at or above the shortest stitch; in a shorter span, a stitch scaled below it joins its shorter neighbour. Where those stitches stray from the path by more than the tolerance left after flattening, as on a curve too tight for them, the span is spread again in one stitch more, then one more, as long as that takes no more stitches than step 5 would make of it, until they keep to it. A small circle is so sewn in even stitches as long as the tolerance left after flattening allows, where splitting the stray ones would halve them. With random length, each try draws its lengths again from where the span’s draws began. - Measure straight. Where the path bends back on itself within less than the shortest stitch (a
cusp, a tight loop, a curl at an end), two points far apart along it can be close in a straight line.
Going along the needle points: one closer than the shortest stitch to the last one kept is dropped; a
corner drops the points kept since the previous corner instead, and is dropped itself if that is not
enough; the end drops whatever it takes, corners too. A drop lengthens the stitch over it, and one
longer than the longest length is split where the path is half its length from its start (at most
the longest length), as often as needed. When only the start is left and the end is too close to it
(a small closed loop), the stitch goes by way of the point of the path farthest from both ends; if
even that is closer than the shortest stitch, the part is not stitched (
SC-W0401). - Respect the curve. If the chord between two consecutive needle points strays from the path by
more than the tolerance left after flattening, split that stitch at the path’s point of greatest
deviation, among those at least the shortest stitch from both its ends, and repeat. A part longer than
the longest length is split along the path as in step 4. So the stitches stray from the path by at
most
running_stitch_tolerance_mm, except where the path’s details are smaller than the shortest stitch. There a point of the flattened path that is passed over lies nearer than the shortest stitch to a needle of its stitch, and the path strays by less than the shortest stitch plus a tenth of the tolerance. - Random length. With
enable_random_stitch_length, each stitch length is drawn uniformly froms × (1 ± jitter), the first of each span a random fraction of that (a random phase, below), then the span is rescaled to end on its corner. Each span draws from its own stream of the element’s generator, keyed by its subpath’s place in the path and its own place in the subpath, so a change to one span leaves the lengths of the others as they were. The generator is seeded with the element’srandom_seed(a setting every stitch type that varies at random shares), or with its id when that is empty. A seed replaces the id, as in Ink/Stitch, so elements alike given one seed sew alike, and the same element and seed always give the same stitches.
When the rules disagree, the shortest stitch wins (a shorter stitch hammers one spot and can break the
thread), then corners, then the tolerance. A part of the path that is a single point, shorter than the
shortest stitch, or lying all within it of its ends, is not stitched (SC-W0401). Only arithmetic and square roots are used, so
every platform places the same stitches.
Compared with Ink/Stitch: the same parameters, meanings and defaults; the placement differs in four
documented ways, DEV-RUN-001 to DEV-RUN-004 in the deviations ledger (conformance/deviations.toml),
and random length draws and places its lengths in other ways (DEV-RUN-005).
Satin underlays walk lines made point by point by the satin generator (satin), and steps 2 to 5 place their stitches, with each walk’s own length and tolerance. Such a line is a polyline already, and each join that turns by more than 30° is a corner. A line too small for a stitch of the shortest length is sewn as its 2 ends, as Ink/Stitch sews it, and finalizing merges what is too short.
A wide stroke’s area, which automatic digitizing fills (image to embroidery), is made from the path segment by segment, flattened within the shapes’ tolerance: its joins go where 2 segments meet, and it bends smoothly inside a curve. Its ends and corners are square to the chords there, and half the stroke’s width turns the angle such a chord makes with its curve into a shift of the corner. So a curve that ends the subpath, or meets the next segment at a corner, is halved further at that end until the shift is within the tolerance too. Where 2 segments meet smoothly nothing more is needed.
Repeats and bean stitch
repeats = ksews the path k times, alternating direction; odd k ends at the far end, even k returns to the start (useful for travel that must come back). Each pass starts where the last one ended, so a turnaround is not a stitch in place.bean_stitch_repeats = breplaces every stitch A→B with A→B→A→B… (2b+1 passes), producing the thick “triple stitch” line at b = 1. A list ("1 0") alternates bean and plain stitches.- Order, as in Ink/Stitch (read at
d59c9ab): bean stitch applies after repeats, to every stitch of every pass, with the list running on from pass to pass. Ink/Stitch repeats the turnaround point, so the turnaround takes one step of the list; StitchCraft counts that step too, without sewing it, so a file sews the same in both. With a two-value list, each stretch of the path then gets the same treatment on every pass. - A zigzag’s turnaround takes no step of the list, as in Ink/Stitch (zigzag stroke).
- Repeats apply to running, ripple and zigzag strokes; bean stitch to those and to manual stitch (the compatibility contract lists each parameter’s stitch types).
Random length
Ink/Stitch draws each stitch from s × (1 ± jitter) and starts each stretch between corners at a random
phase, so rows sewn side by side with the same settings do not line their holes up. One random number
gives a stitch both its length and its phase: the first stitch of a stretch is its drawn length times
that number. After a stitch that the tolerance made shorter, the next starts a new phase too. Ink/Stitch
does not rescale, so the last stitch before a corner is whatever is left. Each stretch draws from a
stream keyed by the seed and the stretch’s place in its subpath, and the places start again in each
subpath, so subpaths drawn alike sew alike.
StitchCraft draws a stitch’s length and its span’s phase from 2 numbers. A phase starts only where a span
starts, and each span is rescaled to end on its corner (placement, step 6: no short leftover). Its streams
are keyed by the subpath’s place as well, so subpaths drawn alike, such as lines side by side, start at
different phases, which is what the random phase is for (DEV-RUN-005).
Properties (conformance)
- Stitches are spread evenly between corners, in more of them where they stray from the points of the
flattened path by more than the tolerance left after flattening, as long as that takes no more than
splitting. Measured straight, every stitch is at most
s × (1 + jitter)+ 1 µm for the longest lengthsand at least the shortest stitch, and a part too small for that is reported (REQ-RUN-001). - Deviation from the source path ≤ tolerance, both ways, measured densely (
REQ-RUN-002). - Corners are penetration points unless that would make a stitch too short (
REQ-RUN-003). - Bean stitch sews each stitch
2b + 1times, so a run of n stitches with b throughout has exactlyn × (2b + 1); the list runs on across repeats, each turnaround taking a step (REQ-RUN-004). - Repeats alternate direction with no stitch in place; their parity decides the exit point
(
REQ-RUN-005). - With random length the lengths vary. The same element and seed give the same stitches, and another
seed gives others. A seed replaces the element’s id, and elements given one seed draw the same lengths.
Each span draws from its own stream, so a change to one span leaves the others’ lengths as they were
(
REQ-RUN-007).
Diagnostics
SC-W0401 part of the path too small for the shortest stitch (skipped); SC-W0402 stitch length below
twice the shortest stitch (raised), a stroke’s or a satin underlay’s, which the message names.
Manual stitch
Every node of the path is a needle penetration, in order — for hand-placed stitches and imported stitch
files. A curve gives only its end node: its control points are not stitched and it is not flattened. When
the path is closed it comes back to its start, and a node where the needle already is sews no stitch.
Segments longer than max_stitch_length_mm (if set, a setting manual stitch shares with satin columns)
are split into the fewest equal parts no longer than it, but never into parts shorter than the shortest
stitch, and SC-W0404 says how many stitches stay longer for that. Ink/Stitch splits a segment into the
fewest equal parts no longer than the setting, however short they are (DEV-RUN-006). A value of 0 means
“not set”, as in Ink/Stitch, and so does one below 0, so every stitch is then sewn as drawn. Bean stitch
applies, and repeats do not. Lock stitches are added only when force_lock_stitches is set, as in
Ink/Stitch (read at d59c9ab).
Ink/Stitch’s bean stitch takes a step of its list for every point it lists, and it lists some where the needle already is. A node drawn twice is listed twice, and so is the end of each stitch Ink/Stitch splits. Where a closed path came back to its start already, the start is listed once more. Its stitch plan leaves out stitches shorter than its shortest stitch only after the bean stitch, and a node left out for that keeps its step. StitchCraft gives each of these its step, sewing nothing, and a bean list then falls on the same stitches in both tools.
The shortest stitch holds here too: a node closer than it to the needle point before it is left out, so
the stitch before runs on to the next node, and SC-W0403 says how many. A part’s last node is always
kept, leaving out the one before it instead. Ink/Stitch drops such points later, over the whole plan,
without a word, and only those 0.1 mm or less from the point before at its default (DEV-RUN-006). A part
that is one point, or too small for one stitch, is not stitched (SC-W0401).
Properties: needle points are the nodes in order plus only the documented splits, and SC-W0404 names
a stitch kept longer than max_stitch_length_mm (REQ-RUN-006). Every stitch is at least as long as the
shortest stitch (REQ-RUN-008).
Zigzag stroke (P2, M7)
A zigzag along the path, as wide as its stroke. The needle crosses the path from side to side, as
Ink/Stitch sews its zig-zag stitch (stroke_method zigzag_stitch, read at d59c9ab). It suits a light
border, a couched look or a stem too thin for a satin column.
Parameters: zigzag_spacing_mm, the distance along the path from one stitch to the next that crosses
the same way, a zig and a zag together, 0.4 mm by default; stroke_pull_compensation_mm, how much further
out than half the stroke’s width each side’s needle points go, one value for both sides or 2; and
zigzag_angle, which turns every stitch away from square to the path, from −89° to 89°. repeats and
bean_stitch_repeats apply. The width is the stroke’s, read as a satin column drawn as one path reads it.
Strokes, satin columns and fills each read zigzag_spacing_mm their own way
(parameter registry, rule 7). A stroke reads a list, as Ink/Stitch does.
Only its first value counts.
Placement
Each part of the path, one per subpath, is sewn on its own, as a running stitch is.
- Spread. The part is flattened within a tenth of a CSS pixel, as a satin column’s or a fill’s
subpaths are, and
n + 1points are spread along it at equal distances, its ends included.nis its length divided by half the spacing, rounded half to even, at least 2, and one more if that is odd. So the zigzag has a whole number of zigs and zags, and it starts and ends on the same side. - Directions. Point 0’s direction is the line to point 1, and the last point’s the line from the one before it. Every other point’s direction is halfway between the line from the point before it and the line to the point after it, each taken with length 1, so a point at a corner leans evenly into both of its sides. The lines are those between the spread points, not the path’s own.
- Across. Each point moves square to its direction, turned by
zigzag_angle(positive turns clockwise as the drawing shows it). The points numbered 0, 2, 4 and on move to the right of the way the path goes, by half the width plus the second pull compensation. Points 1, 3, 5 and on move to the left, by half the width plus the first. A pull compensation below minus half the width moves its points over to the other side. Each point whose direction has no length stays where it is, as on a turn straight back. - Closed paths. A stroke closes when one of its subpaths moves and the others, if any, draw nothing:
single points, or segments of no length. That subpath’s ends must lie nearer each other than 0.05 CSS
pixels. Its last needle point then moves onto its first, and the zigzag ends where it started.
Ink/Stitch counts a subpath of no length as a second line, and keeps such a zigzag open
(
DEV-RUN-008). - Repeats and bean stitch. Repeats sew the zigzag again, every other pass backwards, and each pass
after the first starts with the needle point after the one the last pass ended at. Bean stitch then
sews each stitch
2b + 1times, with its list taken one after another along the stitches of all the passes, as for a running stitch (repeats and bean stitch). Ink/Stitch does not list the point a pass turns at twice in a zigzag, as it does in a running stitch, so that point takes no step of the bean list here.
The needle points come from arithmetic, square roots and the cosine and sine of the angle
(stitchcraft_core::math) alone, and every platform places them alike.
Compared with Ink/Stitch. Ink/Stitch spreads the points along a running stitch it sews first, at half
each spacing and within running_stitch_tolerance_mm of the path. StitchCraft spreads them along the
path, flattened as Ink/Stitch flattens it for that running stitch (DEV-RUN-007). At the default spacing
those running stitches are 0.2 mm long, chords of the path’s curves, so the two lines and the points
spread along them differ by hundredths of a millimetre. A part whose length lies close to a rounding
boundary of its count of zigs and zags may have one zig and zag more or fewer. With several spacings,
Ink/Stitch’s zigzag takes the first, and its running stitch takes half of each, one after another. A long
one cuts across a curve by up to running_stitch_tolerance_mm, and the zigzag follows it there, inside
the path. StitchCraft sews the first spacing along the path. A part of the path that is one point is not
stitched (SC-W0401), as for a running stitch (DEV-RUN-004).
Properties (conformance)
- The needle points of each part, before repeats, are
n + 1withneven, as step 1 counts them, and their places along the flattened path areL / napart (REQ-RUN-009). - Each needle point lies across its direction from its place on the path, turned by
zigzag_angle: even points at half the width plus the second pull compensation to the right, odd points at half the width plus the first to the left (REQ-RUN-010). - A closed stroke ends on its first needle point (
REQ-RUN-011). - Repeats and bean stitch sew the zigzag as step 5 says, with no step of the bean list for a turn
(
REQ-RUN-012).
Diagnostics
SC-W0401 for a part of the path that is one point. Finishing leaves out a needle point of each stitch
shorter than the shortest stitch (SC-I0504). At the default spacing, 0.4 mm, and a shortest stitch of
0.3 mm, that is every stitch of a straight zigzag narrower than 0.224 mm, and the zigzag sews as a line.
SC-W0405 names a zigzag with stitches that short, and the width it needs. Ink/Stitch’s shortest stitch
is 0.1 mm by default, and it sews such a zigzag as it is.
Ripple stitch (P3, M10)
A series of copies of the stroke, each progressively scaled, offset or interpolated towards a target
point (or between two rails for “satin-guided ripple”), joined into one continuous running stitch.
Parameters: line_count, min_line_dist_mm, staggers, skip_start, skip_end, flip_copies,
exponent, flip_exponent, reverse, grid_size_mm, grid_first, scale_axis, scale_start,
scale_end, rotate_ripples, join_style, satin_guide_pattern_position, reverse_rails,
swap_satin_rails, plus running stitch parameters for each line. Design to be completed at M10; the
properties will include “lines never cross the target point” and “each line is within tolerance of its
interpolated curve”.
Satin generators
A satin column is a band of closely spaced stitches that swing from one edge to the other. It is the signature look of lettering and borders, and the stitch type where pull compensation and underlay matter most. Phase P1 (M4); the E, S and zigzag variants are P2 (M7).
Shape
rail A ●────────●──────────●─────────●
\ | \ | \ |
rungs \ | \ | \ | (optional: say which points correspond)
rail B ●──\┴───────\──┴───────\─┴───●
- Rails are the two edges. They run in the same general direction.
- Rungs are short segments crossing both rails; they pin which point on rail A corresponds to which point on rail B, controlling the stitch angle through curves.
- Single-path satin: a centre line whose stroke gives the width (below). It makes quick borders.
Recognizing rails and rungs
An element is a satin column when its satin_column setting is on, whatever its stroke_method says.
Its path’s subpaths are flattened as Ink/Stitch flattens them: each curve is halved until its control
points lie within a tenth of a CSS pixel of the line between its ends. The same subpaths are then the
same polylines in both, which decide where the needle points go, where subpaths meet, and where a column
drawn as one path gets its rungs. A path read from SVG has the subpaths a viewer draws, and inkex, which
Ink/Stitch reads files with, does not always count them alike (DEV-SVG-011). The reader finds where
each pair of subpaths meets, crossing or touching. A point the two share counts once, and a rung that
ends exactly on a rail meets it.
StitchCraft tells rails from rungs as Ink/Stitch does, and a file sews the same in both.
- A subpath that is one point is left out, with
SC-W0205. - With 1 subpath left, the path is the column’s centre line (below). With 2, they
are the rails, and their nodes pair up in place of rungs (see Correspondence). With none, the element gets
SC-E0201and no stitches. - With 3 subpaths, the rails are the 2 that meet exactly 1 other subpath. With 4 or more, the rails are the 2 that meet more than 2 others. This step takes only subpaths longer than a tenth of a CSS pixel.
- When step 3 does not find exactly 2 rails, the 2 longest subpaths are taken as the rails, and
SC-W0202names them. Of equally long subpaths, the first drawn comes first. Rails apart from each other, with exactly 2 rungs between them, are always taken by length, because each of the 4 subpaths meets 2 others, as the strokes of a#do. The longer pair is usually the rails, and with a third rung step 3 finds them. - Rails may meet each other, as the two sides of a pointed column do at its tips. That meeting counts in step 3.
- Every other subpath is a rung. A rung joins the point where it crosses the first rail to the point
where it crosses the second. Where it misses a rail, the point of that rail nearest the rung is used,
with
SC-W0203. Of points as near as each other, it is the one shapely’s nearest points of the rung and the rail give (see Equally near points). A rung that crosses a rail more than once does not say which crossing it means. It is left out, withSC-W0207, and the satin is sewn without it.
Recognition runs before any stitch is computed. The generator only ever gets rails, and odd geometry is a diagnostic that names the subpath, never a crash. Subpaths are numbered from 1 in the order the path draws them, and the rails keep that order.
StitchCraft differs from Ink/Stitch in 2 places, DEV-SAT-001 in the deviations ledger
(conformance/deviations.toml). Ink/Stitch still uses a rung that crosses one rail twice and misses
the other, or that runs along a rail for a while, at one of its crossings. StitchCraft leaves it out.
Ink/Stitch also counts a line of zero length among the subpaths in step 3, where StitchCraft leaves it
out as a point.
Single-path satin
A satin column drawn as one subpath is its centre line. Its stroke gives the column its width and its corners: the stroke’s width as the transforms scale it, and its join (SVG input).
A column no wider than the design’s narrowest satin stroke is too narrow to stitch across
(REQ-SAT-015). That limit is DesignSettings::min_satin_stroke_width, Ink/Stitch’s
min_satin_stroke_width_mm, and it is 1 mm unless the design sets it. Such a column is sewn as the
element’s stroke settings sew a stroke, by default in running stitch. SC-W0212 gives its width and the
limit. As a stroke, it offers the element before it its first point.
The rule counts the path’s subpaths as Ink/Stitch counts them, before any is left out as a point. A path
of 2 subpaths, one of them a point, is never sewn as a stroke for its width. Recognition leaves the point
out and reads the other subpath as a centre line. In SVG, a move-to with nothing drawn after it is no
subpath, where inkex keeps it as a point, so Ink/Stitch can sew a path as a satin column where
StitchCraft sews it as a stroke (DEV-SVG-011).
A wider column is sewn between 2 rails made from its centre line. Ink/Stitch makes the rails and places the rungs this way. It measures in CSS pixels, and its limits below are converted to millimetres.
Rails
The rails are the centre line offset by half the stroke’s width to each side, with the stroke’s join at
their corners (REQ-SAT-016). In y-up axes the first lies to the right of the line as it runs and the
second to its left. The offsets are shapely’s offset_curve, which Ink/Stitch calls, point for point
(offset curves). Inside a bend the offsets meet, and outside it they turn as the join
says.
Rungs
Rungs cross the column where its line runs straight beside a sharp corner, and at its nodes
(REQ-SAT-017):
- Each corner of the line scores the square of its turn in degrees, in the hundredth of the line’s length where it lies. Each score is spread over 4 hundredths to either side, weighted 1, 2, 4, 8, 16, 8, 4, 2, 1. A rung goes where the spread scores stop falling or start rising: before and after each sharp corner, where the stitches can fan round it.
- A rung goes at each point of the flattened line as well, never at its very start or end. A straight line of 2 points gets one rung in its middle.
- Going along the line, a rung less than 1 mm from the last place tried is left out, whether step 4 kept that place’s rung or not.
- Each rung is perpendicular to the line, 1.2 times the column’s width long, and is kept only when it crosses each rail exactly once. It pairs the 2 points where it crosses them.
A column whose rungs are all left out is one section, from its rails’ starts to their ends (see Correspondence), as in Ink/Stitch. Ink/Stitch adds rungs across the nodes from the path as drawn, and a path of one line gives none.
Lines that cross themselves
Where the line crosses itself or comes back near itself, its offsets split into several curves. A line whose offsets split, or whose ends lie within a CSS pixel of each other, is cut in half by length, and each half is made on its own, the halves of a half again, at most 20 times deep. The halves’ rails are joined end to end, and a rung pairs the first points of the second half’s rails, where the halves meet.
A part whose offsets still split after 20 cuts is left out, and so is a part whose offset vanishes to
one side, where the line turns more tightly than the column is wide. SC-W0213 counts the stretches of the line left
out, and the rest of the column is sewn. A stretch shorter than a CSS pixel is left out at once, since
every half of it would be too. A column with no part left has no rails, and gets SC-E0214 and no
stitches.
Closed lines
A line whose subpath is closed with Z is a closed line, as Ink/Stitch reads a path whose last command
is Z. Subpaths left out as points have no say, and an open line followed by a point closed with Z is
open. After a closed line, a move-to, which draws nothing, leaves it closed, where Ink/Stitch reads it as
open (DEV-SAT-001).
Before its rails are made, a closed line is rolled to start in the middle of its first segment whose rung, a thousandth of a CSS pixel longer than the column is wide, crosses the edge of the stroke exactly twice: away from where the line crosses itself or folds tight. An end of the rung that lies on the edge counts as a crossing, as shapely counts it. A line that ends where it starts, closed or drawn back to its start, then starts and ends in the middle of its first segment. Its points are rounded to a ten-thousandth of a CSS pixel as Ink/Stitch rounds them. Points that then repeat are left out.
Which lines are the rails
The rails made from the line are the column’s rails, and every rung pairs the points where it crosses
them. Ink/Stitch hands the lines it makes back to its satin column as subpaths, which tells rails from
rungs again by the rules of Recognizing rails and rungs. When a column
is short beside its width, its rungs are longer than its rails. With exactly 2 rungs, or with rungs
that cross each other, where the lines meet does not tell the rails apart, and Ink/Stitch takes the 2
longest lines, rungs, as the rails. With 3 rungs or more that do not cross, each rail meets more than 2
lines and each rung only the 2 rails, so the rails are found by where they meet. A line that crosses
itself can make a rung meet more lines than a rail does. StitchCraft keeps the rails it made in both
cases, DEV-SAT-007 in the deviations ledger. Where 2 parts join, Ink/Stitch adds a rung across the gap
and uses the points where it crosses the rails. Those are the first points of the second part’s rails
wherever the parts meet on a straight stretch, and StitchCraft pairs those first points. Ink/Stitch also
keeps a first part whose offset vanishes to one side, with its other rail, where StitchCraft leaves it
out with the rest.
Making the rails costs work in proportion to the square of the line’s number of points. A line of 1,000 points takes up to about 35 million units of the default budget of 500 million. A plan makes a column’s rails twice: once to sew it, and once for the element before it to end near it.
Drawn with its rails and rungs
Ink/Stitch’s Stroke to Satin hands the user a column made from a centre line as a path of its rails and
rungs, and StitchCraft’s does in VectorCraft (REQ-VC-014, VectorCraft
integration). generators::satin::drawn
draws the rails as they are made. Each rung is a straight line that starts and ends past the points it
pairs, by a hundredth of the distance between them, so that it crosses each rail once. Read back by the
rules above, the path gives the same rails, and its rungs pair the points
the line’s rungs pair, to within a millionth of a millimetre. It sews as the line does.
A column of 2 rungs or none would be read back otherwise. With 2 rungs, where the lines meet doesn’t tell
the rails apart, and the 2 longest lines are taken, which on a column shorter than it is wide are its
rungs. With none, a path of 2 lines is 2 rails whose nodes pair up. So such a column is drawn with one
more rung, which pairs the rails’ first points: the section it ends has no length, and the column sews as
before. A line whose rails can’t be made (SC-E0214), or leave parts of it out (SC-W0213), isn’t made into
a column, since the column would sew less than the line. Rails that cross each other aren’t drawn
either, since their column reads back with other rails. A line no wider than the design’s narrowest satin stroke sews as a
stroke (above). Drawn with its rails, it is a satin column as narrow as it is drawn,
which is what the tool is asked for.
Orientation
swap_satin_railsmakes the second rail the first, before any reversal. The first rail sews first in each pair, and the column starts on it, but for an S-stitch, whose first pair goes from the second rail. Asymmetric values (one value per side) name it first, and the E-stitch spine runs on it.reverse_rails = automaticreverses rail B, the second, when that brings the rails’ points closer together, as in Ink/Stitch. Points at every tenth of each rail’s length, from its start to 90 %, are paired twice, with rail B forwards and with it backwards. The pairing whose distances add up to less wins.none,first,secondandbothforce it.- Of 2 rails that are rings, each ending where it starts (closed with
Z, or drawn back to its start), rail B first starts at its node nearest rail A’s first node, before any reversal (DEV-SAT-009). Ink/Stitch takes each ring’s own start, and warns that a closed rail has no definite start. VectorCraft’s Make Compound Path, from 0.10.0, turns the front of 2 rings round, which starts it at what was its last node. Rings sewn from their own starts then get stitches across the column. A node, not the nearest point, keeps rings that start together as drawn, with a rung across their starts where one is drawn. A column made from a centre line keeps its rails as made, which start together.
Correspondence
Every rung cuts each rail at the distance along it of the rung’s point on it, after any reversal. Each
rail is cut at its own distances, in order, and the n-th part of one rail goes with the n-th part of the
other: a section. A part of no length leaves its section out, as where two rungs meet a rail at one
point or one meets it at an end. A column left with no section sews nothing, and SC-E0216 says why.
Ink/Stitch refuses it too, unless rounding its rails in CSS pixels leaves a section a few millionths of
a pixel long, which it sews as a fan. Within a section, the point at a fraction of rail A’s part goes with the
point at the same fraction of rail B’s.
A column of 2 subpaths has no rungs, and gets rungs across its rails’ nodes instead, as Ink/Stitch adds
them. The 2nd node of one rail goes with the 2nd node of the other, and on in order, without each rail’s
2 ends. Rails with different numbers of nodes pair as many as the one with fewer has (SC-W0210).
Ink/Stitch’s own warning for this counts the rails’ points after flattening, not their nodes, and can
warn where the nodes pair as drawn. Rails of 2 nodes each get one rung, across the points 0.2 CSS pixels
along the straight line from each rail’s first node to its last, and sew as one section. Of 2 rings, the
second’s nodes start at the node its line starts at, and each ring’s start counts once at each end of
its nodes. VectorCraft closes a ring with a segment back to its start and Z, which lists its start
twice at the end of its nodes.
Each rail’s nodes are turned as reverse_rails turns the rail drawn in that place, first or second,
before any swap. Swapping the rails then changes only which one sews first, and the same nodes pair
either way. Where the rails have different numbers of nodes, the longer rail’s last nodes, in the order
the turning leaves them, go without a partner.
Each rung added is a tenth longer than the line between its points, half at each end, and cuts each rail where it comes nearest it, measured as at the start and end (see Equally near points). That is the node itself, or on a curved rail of 2 nodes, where the rung crosses the curve, or the curve’s point nearest the rung when the rung does not reach it.
Where the pairing leaves stitches leaning far along the column, SC-W0208 names the place to add a rung
(Leaning and long stitches).
Compensation
Thread under tension pulls the fabric in across a satin column, which then sews narrower than it is drawn. The stitches also push the fabric out at the column’s ends, and it sews longer. StitchCraft makes up for both as Ink/Stitch does.
- Push compensation (
push_compensation_mm) acts on the rails after any reversal, before they are cut into sections. It takes its length off each rail at the column’s start and end, or adds it where negative, straight on from the rail’s first or last segment. 2 values set the start, then the end. The points where a drawn rung cuts come from the rails as drawn, so a rung in a part taken off cuts the rail at its end and leaves its section out. A rung added across the nodes is measured against the compensated rails. A rail the shortening would leave shorter than half a CSS pixel keeps its length (SC-W0211), and a lengthening at its other end still applies. - Pull compensation acts on each pair as it is placed. Both ends move outward along the line through
the pair, each by
pull_compensation_mmpluspull_compensation_percentof the pair’s width, and 2 values set the first rail’s side, then the second’s. Negative values move the ends inward. Ends that would cross meet instead, where their moves divide the width. A pair whose ends are closer than a ten-thousandth of a CSS pixel has no direction to move them in, and stays as it is. A tatami fill lengthens its rows with the same move, as Ink/Stitch does, and the 2 settings are declared once for both (stitchcraft_engine::generators::pull, fills). - Random variation comes from the element’s generator, seeded with
random_seed(determinism). Each pair’s share of the width on each side is drawn betweenpull_compensation_percentlessrandom_width_decrease_percentand plusrandom_width_increase_percent. Each step’s spacing is drawn between the zigzag spacing less and plusrandom_zigzag_spacing_percentof it, and never below a hundredth of it. As in Ink/Stitch, a spacing is drawn at each section’s start and after each pair, and the widths with each pair. Ink/Stitch draws from another generator, and its random values differ (DEV-SAT-002).
Pairs are placed and measured from each other before pull compensation, which leaves each pair’s place along the column as it was.
Top stitches (method satin_column)
- Place pairs along the sections, as Ink/Stitch places them. A pair of needle points goes across
the column, one on each rail at the same fraction of its section. Each pair is meant to lie
zigzag_spacing_mmfrom the one before, measured across the column. The measure is taken at a right angle to the previous pair, at whichever of its ends is farther (the outside of a curve). Where the previous pair has no length, it is the distance from that pair’s point.- Within a section, a pair is placed the spacing’s share of the section’s longer part past the one before. A pair that lands more than 5 % off the spacing moves by its step scaled by how far off it is. It moves at most twice, and the first move stops at the section’s end.
- The first pair of a section after the first goes as far past the section’s start as the previous pair is short of the spacing.
- The column starts with a pair at its start and ends with one at its end, unless the last pair is within 0.1 mm of it.
- With
random_zigzag_spacing_percent, each step’s spacing is drawn at random (Compensation), and a pair moves to lie that spacing from the one before.
- Compensate width. Each pair is widened by pull compensation as it is placed, with its random share (Compensation).
- Short stitches on curves, as in Ink/Stitch, where they are on by default. On the inside of a
tight curve, a rail’s needle points crowd together and the thread piles up. On each rail, a point
closer than
short_stitch_distance_mmto the last point left in place there moves in along its stitch, by ashort_stitch_insetpercentage of the stitch’s width. Points that crowd one after another take turns with the percentages and start over at the first, so15 30insets them by 15 %, 30 %, 15 % and on. A point at least that far from the last one left in place stays, and the next points are measured from it. A distance of 0 insets none. The points are the compensated ones, and an inset moves its point toward the other end of the stitch as negative pull compensation does. Even splits (step 4) hold an inset to a third of the longest stitch. - Split long stitches, as Ink/Stitch splits them, where the element sets a longest stitch
(
max_stitch_length_mm, one of the settings every stitch type shares). A stitch’s ends are its pair’s compensated points, and it is sewn between their insets.split_methodsays where the splits go.defaultsplits a stitch into the fewest equal parts no longer than the longest stitch, and the stitch back after it into as many parts as the stitch across before.random_split_jitter_percentmoves each split by up to that share of a part, either way, at random.- With
random_split_phase, the default method starts the splits a random share of the longest stitch from the stitch’s inset start instead, and follows at the longest stitch, longer or shorter by up to the jitter. Stitches no longer thanmin_random_split_length_mm(by default the longest stitch) are not split. Of 2 or more splits, one within the element’s shortest stitch of an end is left out. simplesplits at whole multiples of the longest stitch from a stitch’s start, and from a stitch back’s end, where they lie between its insets. The splits line up in rows.staggereddoes the same from a start that moves along by asplit_staggers-th of the longest stitch from one stitch to the next, and comes back after that many stitches. Fractions are allowed.- With even splits (the default method without random phase), a short stitch’s inset (step 3) is at most a third of the longest stitch.
- Alternate. Each pair is sewn rail A first, then rail B, and the needle goes A, B, A, B and on.
The stitch from A to B goes straight across, and the one from B to the next A slants. One zigzag cycle
(A → B → A) spans
zigzag_spacing_mm. The exit end is chosen by assembly (see Start and end). Random splits are drawn after the pairs are placed, in the order the stitches are sewn.
A column too small to sew. A column whose needle points, underlays and travel included, all lie
within its shortest stitch of its first, a speck a few tenths of a millimetre across, has no stitch the
machine sews well. It sews nothing, and SC-W0215 says so, as a stroke that small is left out
(SC-W0401). Ink/Stitch sews it (DEV-SAT-008).
E-stitch (method e_stitch)
An E-stitch edges appliqué with a spine of stitches along the first rail and a spike across the column at
each pair, like a row of E’s laid end to end: the blanket stitch. Ink/Stitch’s documentation suggests a
wider zigzag spacing for it than for a satin. StitchCraft sews it as Ink/Stitch sews its “E” stitch, read
at d59c9ab.
- Pairs. The pairs are placed, compensated and inset as the top stitches’ are (steps 1 to 3 of
Top stitches), with their random variation drawn alike. Each pair
is a spike, and the spikes lie
zigzag_spacing_mmapart. - Spikes. At each pair the needle goes from the first rail’s point across to the second rail’s, and back down the same needle holes. The stitch across is split as a top stitch across is (step 4), as the stitch across that a satin column would sew at that pair. Its way back goes through the same splits in reverse, where a satin column’s stitch back to the next pair is split anew.
- The spine. From one spike to the next, the needle goes along the first rail in one stitch, from the spike’s inset point to the next spike’s. Where the zigzag spacing is longer than the longest stitch, that stitch is split, with no randomness. Its splits are those of a stitch from the spike’s inset point to the next pair’s compensated point on the first rail: by the default method the fewest equal parts no longer than the longest stitch, and by the simple and staggered methods whole multiples of the longest stitch from its start. As in Ink/Stitch, the zigzag spacing as set decides whether the spine is split, whatever length its stitches come out.
- Order. The spikes go from the column’s start to its end, or from its end after an odd centre walk,
and
swap_satin_railsputs the spine on the other rail. The underlays, the start and the end are a satin column’s: the top stitches’ run is cut and turned as a satin column’s is (Start and end).
S-stitch (method s_stitch)
An S-stitch sews one stitch across the column at each pair, every other one back the other way, and goes
on from each to the next along a rail, so that the needle draws S’s laid end to end. Split by a longest
stitch, its stitches cover the column in rows that follow it, as a fill’s rows do. StitchCraft sews it as
Ink/Stitch sews its “S” stitch, read at 6555892.
- Pairs. The pairs are placed, compensated and inset as the top stitches’ are (steps 1 to 3 of Top stitches), with their random variation drawn alike.
- Stitches across. Each pair is sewn once, from one inset point to the other. The first pair, and every other one after it, goes from the second rail to the first, and the others from the first to the second. Each stitch ends on the rail the next one starts from. A stitch splits as a stitch across from the first rail to the second would (step 4). It is sewn the way its pair goes, with its splits in reverse where it starts on the second rail. The default method splits it into the fewest equal parts, with no count taken from the stitch before. The simple and staggered methods number the stitches 0, 1, 2 and on, where a satin column’s stitches across take 1, 3, 5. The staggered method moves an S-stitch’s splits along at every stitch.
- Steps. From one pair to the next, the needle goes along the rail its stitch ended on in one stitch,
from that inset point to the next pair’s. The step is never split, whatever its length. Ink/Stitch
works out splits for it where the zigzag spacing is longer than the longest stitch, and sews none of
them (
lib/elements/satin_column/stitches.py,_do_s_stitch). - Order. The pairs go from the column’s start to its end, and an odd centre walk turns the whole run
round, to go from its end.
swap_satin_railsswaps the rail the column starts on. Its underlays, start and end are those of a satin column (Start and end).
Zigzag satin (method zigzag)
A zigzag satin sews a zigzag between the rails. The needle goes from a point on the first rail to a point
on the second half the zigzag spacing along, then back to the first rail half the spacing on again. Each
rail’s points lie the zigzag spacing apart, and the needle crosses the column twice for each spacing along
it, as a satin column’s needle does. Every stitch slants, where a satin column’s stitches across go square
to the column. StitchCraft sews it as Ink/Stitch sews its zigzag satin, read at 6555892
(lib/elements/satin_column/stitches.py, _do_zigzag).
- Pairs. The pairs are placed and compensated as the top stitches’ are (steps 1 and 2 of Top stitches), at half the zigzag spacing. Their random variation is drawn alike.
- Alternate points. One point of each pair is sewn. The first pair gives its first rail’s point, the
next pair its second rail’s point, and the pairs after them alternate the same way. Each 2 of those
points in a row are a pair of the zigzag, from the first rail to the second. A point left over at the
column’s end is left out. A column with no second pair, 0.1 mm long or less, has no stitch across. It
is left out with its underlays, as a column too small to sew is (
SC-W0215,DEV-SAT-008). - Short stitches inset those pairs where their points crowd (step 3). Each point moves along its own slanted stitch.
- Stitches. The needle goes across each pair, from its first inset point to its second, then back to the next pair’s first, as in a satin column. Long stitches split by the column’s split method (step 4). By the simple and staggered methods, the stitch back to the zigzag’s pair numbered i splits as a satin column’s stitch numbered 2i would, measured from its end on the first rail. The stitch across that pair splits as one numbered 2i + 1. By the default method each stitch splits into its own fewest equal parts. A satin column’s stitch back takes its count from the stitch across before it, and a zigzag satin’s does not, as in Ink/Stitch.
- Order, start and end follow a satin column’s. An odd centre walk turns the run round. The end cuts the top stitches where they come nearest the end’s cut (Start and end), which on a zigzag falls on one of its points or part way along a stitch. The outline that the start and the end are found on is Ink/Stitch’s. It runs through pairs placed and compensated at the full zigzag spacing, as a satin column’s top stitches are, and not through the zigzag’s own. An outline through those would end where they do, as much as 0.1 mm from where Ink/Stitch’s ends, and turn its corners elsewhere. The outline’s random variation is drawn from a copy of the generator as the zigzag’s pairs found it, so that the zigzag’s own draws stay as they were. With none, the outline is exactly Ink/Stitch’s.
- Underlays are a satin column’s, but the contour stops where the zigzag does, as in Ink/Stitch
(
lib/elements/satin_column/underlay.py,_do_contour_underlay). The zigzag’s points, as inset, mark where it stops on each rail: its last point on the first rail, and its first and last on the second. Each side is cut at its points nearest those on its rail, and then stops short of the column’s ends by the insets (Underlays). A cut that would leave less than half a CSS pixel of a side is not made, as for the insets. Such a cut comes from a zigzag of one stitch. After an odd centre walk the sides stop at the same places. Ink/Stitch looks for the points in the run as sewn, turned round, and so cuts its first side nowhere and its second to a stub (DEV-SAT-011).
Leaning and long stitches
Every satin method gives 2 warnings about how its stitches lie. Ink/Stitch gives neither.
- Leaning stitches (
SC-W0208). Without rungs, or with too few, the stitches follow the rails’ lengths alone, and where one rail runs on ahead of the other they lean along the column. The column looks for that in the pairs placed at the zigzag spacing with no compensation and nothing random, the pairs that cut it (Cuts in Start and end). At a pair, the column runs halfway between the ways its 2 rails run there, each the line from the point of the pair before on that rail to the point of the pair after, taken with length 1. The first and last pair use their own point in place of the missing neighbour’s, and a rail whose 2 points coincide gives way to the other. Where one rail steps out, a stitch square to the other rail so leans less than 45°. A pair leans too far when its stitch goes further along the column than across it, more than 45° from square to a tenth of a degree, so that rounding never tips a 45° stitch over. A pair shorter than the element’s shortest stitch does not lean, as where the rails meet, and neither does one whose rails both stand still or run opposite ways. Each stretch of pairs in a row that lean too far gets oneSC-W0208, at the middle of its middle pair, with the number of its stitches and the most any of them leans, to a tenth of a degree. A rung there, square to the column, sets them straight. - Long stitches (
SC-W0209). A stitch longer than 12 mm floats loose over the fabric, where it snags and sags. The longest of the column’s top stitches, sewn with their splits and insets, gets oneSC-W0209when it is longer than 12 mm. The warning is at the stitch’s middle and gives its length. The stitches are measured as the column sews them: where its end cuts a stitch in 2, each half is a stitch. The underlays and the needle’s travel are left out. Settingmax_stitch_length_mmsplits long stitches, and a shape that wide may sew better as a fill. Finalize splits a stitch longer than the machine’s longest stitch anyway (SC-I0703).
Underlays
Underlays are sewn before the top stitches, as Ink/Stitch sews them. The centre walk comes first, then the contour and the zigzag, each one the element turns on, and all of them go into one run with the top stitches.
Each underlay places pairs of points across the column as the top stitches are placed (step 1 of Top stitches), along the same sections, after any swap, reversal and push compensation. A pair moves in by its underlay’s insets as negative pull compensation moves it, and nothing varies at random, so turning an underlay on leaves the top stitches as they were.
| Underlay | Parameters | What it does |
|---|---|---|
| Centre walk | center_walk_underlay, _stitch_length_mm, _stitch_tolerance_mm, _repeats, _position | Pairs every _stitch_tolerance_mm, each moved in until its ends meet _position percent of the way from the first rail to the second (50 is the middle). A running stitch of _stitch_length_mm follows the line through them within the tolerance, there and back _repeats times |
| Contour | contour_underlay, _stitch_length_mm, _stitch_tolerance_mm, _inset_mm, _inset_percent | Pairs every _stitch_tolerance_mm, inset by _inset_mm plus _inset_percent of the width on each side, and a running stitch along each side. Each side then stops short of the column’s start by the first rail’s inset in millimetres and of its end by the second’s, as push compensation shortens a rail. A side too short for that keeps its length (SC-W0206). The first rail’s side goes towards the column’s end, and the second’s back |
| Zigzag | zigzag_underlay, _spacing_mm, _inset_mm, _inset_percent, _max_stitch_length_mm | Pairs every half _spacing_mm, inset by its own insets, which are half the contour’s when left empty. It zigzags to the column’s end through one end of each pair, the rails taking turns. It comes back through the other ends. Each way, a rail’s points lie the spacing apart. A stitch longer than _max_stitch_length_mm is split into equal parts |
A centre walk with an odd number of repeats ends at the column’s end. The contour’s first side then goes
back and its second side on, the zigzag starts from the end, and the top stitches run from the end to the
start. Straight stitches join each part to the next, all of one length and no longer than the column’s
travel length. That is the first of the running stitch’s lengths (running_stitch_length_mm), raised to
twice the shortest stitch where it is shorter, as a running stitch’s lengths are (DEV-RUN-003), and
SC-W0402 says so once for the column.
Where a part starts within the shortest stitch of the needle, the needle’s last point gives way to the
part’s first. An underlay’s last point, or the column’s start, moves to it, and the stitch into that
point ends there. Points that only route the needle are left out (Start and end). A top
stitch’s point gives way to no part. Where the way along the line leaves the needle at one, the next
part’s points within the shortest stitch of it are left out instead, and the needle goes on to the first
one farther. Otherwise finalize leaves out the part’s first point with SC-I0504, even where it is a top
stitch’s end. Parts meet that near in many columns. A zigzag underlay comes back 0.2 mm in from the rail
where an S-stitch starts, and after an odd centre walk it often ends that near where the top stitches
start. The end’s cut can fall where the zigzag underlay’s 2 ways cross, and the way along the line can
end beside where a centre walk is cut. In a column 0.5 mm wide, the centre walk is cut beside the top
stitches’ end. Ink/Stitch joins its parts as they are, and its plan keeps a stitch longer than its own
shortest stitch, 0.1 mm by default (DEV-RUN-003).
Under a zigzag satin the contour’s sides first stop where the zigzag does (Zigzag satin, step 6).
The walks are running stitches, placed as a stroke’s are (strokes), and they
differ from Ink/Stitch’s in the same ways (DEV-RUN-001 to DEV-RUN-003). Where the insets cross in a
narrow column, a pair’s ends meet as pull compensation’s do, and the contour’s sides then run along one
line. StitchCraft differs from Ink/Stitch in 2 more places. The centre walk keeps to its own tolerance,
where Ink/Stitch uses the satin’s running stitch tolerance (DEV-SAT-004). A running stitch length of
several values gives the travel its first, where Ink/Stitch falls back to 2.5 mm (DEV-SAT-005).
Start and end
A column starts near where the elements before it left the needle, and ends near where the next element starts, as in Ink/Stitch, which does both by default. The engine gives each element 2 neighbours (ADR 0014). The first is the last needle point of the elements before it, whatever their thread. The second is what the next element offers to end near. Strokes offer their first point. Satin columns that start at their own nearest point offer their rails, and others the start of their first rail. Either way the rails are as they are sewn, after any swap and reversal. Tatami fills offer their area, their parts.
The line. The way to the start and the way to the end follow a line between the rails,
running_stitch_position percent of the way from the first rail to the second (50 is the middle). It is
built as the centre walk’s is. Pairs are placed every running_stitch_tolerance_mm and moved in until
their ends meet the line. A running stitch of the column’s travel length
(Underlays) goes through them within the same tolerance.
Cuts. A place on the column is found by its cut, one of the pairs placed at the zigzag spacing with
no compensation and nothing random. The cut is the pair with the point nearest the place. Where the
needle enters the line for a place is the line’s point nearest that cut. A part of the column is cut in 2
at its own point nearest the cut. A side of it that lies along the cut, both its ends within 1e-5 CSS
pixels of the cut’s line, meets the cut as sides along one line do. So a stitch across that pull
compensation widens past the rails is cut at the cut’s first rail point, and one it narrows at its own
start (DEV-SAT-010). Where a needle point of the part lies within the element’s shortest stitch of that
point, as finalize measures it, the part is cut at the needle point instead. Each half then ends in a
stitch the machine sews, where finalize would leave a needle point out with a note (SC-I0504). Cuts
fall beside needle points most on a zigzag satin, whose cuts lie on its own points or within their pull
compensation, and at a stitch across that lies along the cut, where pull compensation moves the stitch’s
end just past the cut’s first rail point. Ink/Stitch cuts where the part comes nearest, and its plan drops
a stitch no longer than its shortest stitch. When the cut’s 2 points coincide, where the rails meet, a
part is not cut: it is all first, or all second when its start is within 0.1 CSS pixels of the place.
Starting at the nearest point (start_at_nearest_point). With a needle point before it, the column
starts at the point of the line nearest that needle point. When that point is farther from the needle
than the jump length, and the compensated outline is nearer than the jump length, it starts at the
outline’s point nearest the needle instead. The jump length is the element’s min_jump_stitch_length_mm,
or the design’s collapse length when the element sets none or 0. The compensated outline is the 2
polylines through the ends of the top stitches’ pairs, after pull compensation and random width, before
short stitches. A zigzag satin’s runs through pairs at the full spacing (Zigzag satin, step 5). From its
start, the needle enters the line where the start’s cut says and follows it to
where the first stitch’s cut says, and the column goes on from its first stitch as before.
Ending at the nearest point (end_at_nearest_point). With an element after it, the column ends at
the point of its compensated outline nearest the next stitch. The next stitch is the point of the
column’s rails nearest the next element’s first point, or nearest its rails when it offers them. Before
a fill whose area holds the start of the column’s first rail, or failing that of its second, the next
stitch is that start, and otherwise the point of the rails nearest the fill’s rings. The column’s rails
are measured as they are sewn. The column ignores an end less than 5 CSS pixels (1.32 mm)
from the line’s end, and ends as if it were the last element. Otherwise every part of the column is cut
in 2 at the end’s cut. The centre walk is cut before its repeats, and the zigzag’s ways before their long
stitches are split. The contour’s sides and the top stitches are cut too. The column sews every part’s
first half in order. Then the needle follows the line from the end’s cut to the line’s end, and the
column sews every part’s second half in order and stitches the end last.
The halves follow each part’s direction. The centre walk’s second half is turned to run from the column’s end, and each half is sewn its repeats. The contour’s first pass is its first side up to the cut and its second side from the cut back to the start, and its second pass the rest of the second side, then the rest of the first. The zigzag’s first pass goes to the cut and back, and its second goes from the end to the cut and back to the end. The top stitches’ second half is turned to run from the column’s end to the cut.
After an odd centre walk, which brings the needle to the column’s end, the column’s start and end trade places in the halves above. The first pass works from the column’s end towards the cut, and the second from its start. The centre walk’s halves are its part from the cut to the end and its part from the cut to the start, each sewn its repeats. The first pass begins at the cut. Between the passes the needle goes to the line at the cut, where the second pass begins.
A point inside an area. An area is 0 away from every point in it or on its rings, so before it measures between lines, Ink/Stitch’s geometry library looks for such a point. It tests the next element’s points first, then the element’s own, each against every part of the other’s area before the next point, and the first that lies in a part is the next stitch. Polylines are tested by their starts, in order, and an area by its parts’ outline starts, never a hole’s. So before a fill, the column’s next stitch is the start of its first rail that lies in the fill, or else of its second. These rules were checked against shapely 2.2.0 as a black box. A fill finds its own next stitch by them too (fills).
Equally near points. Shapes are often equally near at several points, as rails side by side are. The
column takes the point Ink/Stitch takes, found in the order its geometry library, shapely, finds them.
Shapes are measured from one to the other, polyline by polyline and side by side. Sides that cross or
touch meet exactly where they do, and distances are measured as that library measures them, so equal
distances compare as equal. A cut is measured from the part it cuts, and the line is entered measuring
from the cut. The engine’s tests check its answers against shapely’s for 1,600 random cases on coarse
grids, where ties are common (conformance/fixtures/geometry/shapely-nearest.txt, written by
conformance/oracle/nearest.py). Shapely computes a crossing point in extended precision, so where that
point also lies on another side, the 2 can differ in which side they find it on.
Straight stitches join each piece to the next as between underlays, no longer than the travel length. A point of the line within the shortest stitch of the needle, or of the next piece’s first point, only routes it, and is left out. An end within the shortest stitch of the column’s last needle point moves that point to the end. The column’s last stitch, often a top stitch, then goes to the end in one stitch. Ink/Stitch sews the end as a stitch of its own, which its stitch plan drops when it is no longer than its shortest stitch. An explicit start or end command, read from M8, would replace the needle point or the next stitch.
Variants (P2, M7)
satin_method | Look | Design notes |
|---|---|---|
e_stitch | A spine of running stitches along one rail with regular spikes to the other: the “E” or blanket stitch used for appliqué edges | Sewn from M7.3 (E-stitch) |
s_stitch | Stitches across the column, every other one back the other way, joined by steps along the rails | Sewn from M7.4 (S-stitch) |
zigzag | An open zigzag between the rails | Sewn from M7.5 (Zigzag satin) |
Properties (conformance)
| Requirement | Property |
|---|---|
REQ-SAT-001 | Pull compensation moves both ends of every top stitch outward along it, by pull_compensation_mm plus pull_compensation_percent of its width (± 0.01 mm). Ends moved past each other meet |
REQ-SAT-002 | Consecutive pairs are the zigzag spacing apart, measured across the previous pair at its farther end, exactly so between straight parallel rails and within 5 % on curves. The last pair is at most a spacing on |
REQ-SAT-003 | With a longest stitch, a longer top stitch is split as split_method says. By default it splits into the fewest equal parts, and the stitch back into as many as the stitch across before. Simple and staggered splits fall at whole multiples of the longest stitch, from a start that staggered moves along |
REQ-SAT-004 | Underlays come before the top stitches and leave them as they were: the centre walk, then the contour and the zigzag. Straight stitches of one length join them, no longer than the running stitch’s length raised to twice the shortest stitch. A part that starts within the shortest stitch of the needle takes the place of an underlay’s last point, and the needle’s way gives way to it. Beside a top stitch’s point the part’s points give way instead. After an odd centre walk the rest runs from the column’s end |
REQ-SAT-005 | Rails and rungs are told apart as Ink/Stitch tells them, in any drawing order. A path with no subpath longer than a point produces SC-E0201 and no stitches, and a rung that misses a rail joins the rail’s nearest point (SC-W0203), the one shapely gives of points as near. A column with no section to sew produces SC-E0216 and no stitches |
REQ-SAT-006 | A parameter with a value for each side changes only its own side: a rail’s stitch ends, or the column’s start or end |
REQ-SAT-007 | Random widths and spacing are reproducible from the element and its seed, and stay within their ranges |
REQ-SAT-008 | Push compensation shortens the rails at the column’s start and end before they are cut, or lengthens them where negative. A rail it would leave shorter than half a CSS pixel keeps its length (SC-W0211) |
REQ-SAT-009 | On each rail, a top stitch end closer than short_stitch_distance_mm to the last end left in place moves in along its stitch by the next short_stitch_inset percentage. Ends far enough from it stay |
REQ-SAT-010 | The centre walk follows the line at its position between the rails within its tolerance, in stitches no longer than its length, there and back its repeats |
REQ-SAT-011 | The contour runs along each rail at its insets, towards the end on the first rail’s side and back on the second’s. It stops short of the column’s start and end by the rails’ insets in millimetres |
REQ-SAT-012 | The zigzag goes through one end of each pair, the rails taking turns, and back through the others. Its insets are half the contour’s when left empty, and its long stitches split into equal parts |
REQ-SAT-013 | Starting at its nearest point, a column begins on the line between its rails nearest the needle, or on its outline when only that is within the jump length, and follows the line to its first stitch |
REQ-SAT-014 | Ending at its nearest point, a column ends on its outline nearest the next stitch. Every part is cut in 2 there, at its own needle point when one lies within the shortest stitch of the cut. The column sews the first halves, then the second halves from the line’s end, and stitches the end last |
REQ-SAT-015 | A column drawn as one subpath whose stroke is no wider than the design’s narrowest satin stroke sews as a stroke would, with SC-W0212. It offers its first point, as a stroke does |
REQ-SAT-018 | A column whose needle points all lie within its shortest stitch of its first sews nothing, with SC-W0215, and the rest of the design is planned. A zigzag satin with no stitch across sews nothing either, not even its underlays |
REQ-SAT-019 | Of 2 rings, the second starts at its node nearest the first’s first node, its nodes too. The stitches go straight across whatever node each ring was drawn from and however it is closed, and rings drawn alike keep their starts (DEV-SAT-009) |
REQ-SAT-020 | An E-stitch sews each of a satin column’s pairs as a spike: from the first rail’s inset point across to the second’s and back through the same splits. The stitch across splits as a top stitch across does, by default into the fewest equal parts |
REQ-SAT-021 | Between spikes an E-stitch goes along the first rail in one stitch. Where the zigzag spacing is longer than the longest stitch, that stitch splits with no randomness: into the fewest equal parts by default, at whole multiples of the longest stitch by the simple and staggered methods |
REQ-SAT-022 | Each stretch of pairs in a row whose stitches lean more than 45° from square to the column, to a tenth of a degree, gets one SC-W0208 at the middle of its middle pair, whatever the satin method. The column runs halfway between its 2 rails. Stitches square to a rail, and stitches at 45°, give none |
REQ-SAT-023 | A satin column whose longest top stitch, as sewn, is longer than 12 mm gets one SC-W0209 at that stitch’s middle, whatever the satin method. A longest stitch that splits them, or an end that cuts one in 2, leaves none |
REQ-SAT-024 | An S-stitch sews each of a satin column’s pairs once across, the first and every other one from the second rail, the rest from the first. A stitch splits as one from the first rail to the second would. The default method makes the fewest equal parts, and the simple and staggered methods number the stitch as its pair is, from 0 |
REQ-SAT-025 | An S-stitch steps along a rail from each pair’s inset point to the next pair’s in one stitch, which it never splits |
REQ-SAT-026 | A zigzag satin places a satin column’s pairs at half the zigzag spacing and sews one point of each, the rails alternating from the first rail. Each 2 points in a row are a pair across, and a point left over is left out. The pairs are inset along their own stitches and sewn across and back as a satin column’s. Its outline runs through pairs at the full spacing |
REQ-SAT-027 | A zigzag satin’s stitches split by the column’s split method. The simple and staggered methods number the stitch back to pair i 2i and the stitch across it 2i + 1. By default each splits into its own fewest equal parts, the stitch back taking no count from the stitch across before it |
REQ-SAT-028 | Under a zigzag satin, the contour’s first side stops at the zigzag’s last point on the first rail, and its second side at its first and last points on the second rail, before the insets. An odd centre walk moves none of these places |
Machine checkpoint MC-3 sews a width ladder (1–10 mm) and an underlay comparison to tune defaults (machine testing).
Diagnostics
SC-E0201, SC-W0202, SC-W0203, SC-W0205, SC-W0206 (a contour underlay too short for its insets
keeps its length), SC-W0207, SC-W0208 (stitches leaning more than 45° from square to the column),
SC-W0209 (a top stitch longer than 12 mm), SC-W0210, SC-W0211, SC-W0212, SC-W0213, SC-E0214,
SC-W0215 (a column too small to sew) and SC-E0216 (a column without a section to sew). The running
stitch’s SC-W0402 names a travel length raised to twice the shortest stitch.
Offset curves
A satin column drawn as one path gets its rails by offsetting its centre line by half the stroke’s width
to each side (single-path satin). Ink/Stitch makes them with shapely’s
LineString.offset_curve, which the GEOS library computes, and the same rails sew the same column. So
StitchCraft builds the curves that function returns, point for point (REQ-SAT-016), with the join
Ink/Stitch passes and 16 segments to a quarter circle, shapely’s default.
The curve and its side
An offset curve runs beside a line at a fixed distance, on the line’s left in y-up axes for a positive distance, which is its right on screen, and on the other side for a negative one. It runs the same way as the line. It is the part of the edge of the line’s stroke, the area within the distance of the line, that lies along that side. The answer is one curve, or several where the edge splits that side into pieces, or none where the stroke covers the whole side.
A line of fewer than 2 distinct points, or of no length, has no offset, and a line of 2 points is that segment moved sideways. At no distance the offset is the line as it is. Anything longer goes through 3 steps.
1. The raw curve
Each segment of the line is moved sideways by the distance, and consecutive moved segments are joined at the node between them:
- On the outside of a turn, the join decides. A mitre extends the 2 moved segments to the point where they meet, unless that point lies farther from the node than the mitre limit times the distance. Then it is cut square across the turn’s bisector at the limit, or bevelled when even the bevel lies beyond it. A round join is an arc about the node in equal steps of about a 64th of a full turn, and a bevel joins the moved ends straight, or takes them as one point when they are less than a thousandth of the distance apart.
- On the inside of a turn, the curve goes to the point where the moved segments cross, or, if they miss each other, in towards the node and back out along 2 short closing segments that the stroke covers.
- A turn back along the same line is a half circle with a round join, and a step across otherwise.
Before the segments are moved, the line is simplified on the side the curve lies on. A node where the line turns towards that side, nearer than a hundredth of the distance to the node kept before it, is dropped: there the moved segments would cross and leave a small loop. GEOS also measures the node against the straight line past it, and against the lines to the points between, but a node that near the one before always passes. The first and last segments always stay. Points of the curve less than a ten-thousandth of the distance from the one before are left out.
2. The edge of the stroke
The raw curves of both sides, the left forwards and the right backwards, joined by round caps around the line’s ends, make one ring around the line. Where the line turns or comes back near itself, the ring crosses itself. The stroke covers the points the ring winds round at least once, counted clockwise.
The ring is cut at each point where it crosses itself, and at each end of a segment that lies on another segment, within a billionth of the half width. Each piece is judged by the winding just beside its middle, on each side. A piece with the covered area on one side only is part of the edge, turned to keep that area on its right, and pieces that coincide are one edge. The winding is counted with an exact orientation test (determinism), so a point that lies on a segment is never on one side on one computer and on the other side on another.
The edges join into rings. Where a ring touches itself, it takes the edge that turns farthest to the right, and so stays as small as it can be, as the rings of GEOS’s polygons are. The rings that run clockwise are shells, and the others are holes. A ring of no area bounds nothing and is left out. The offset is looked for on the shell of the largest area and on the holes inside it.
3. The sections
The pieces of the edge that lie on the raw curve of the offset’s side, both ends within a ten-thousandth of the distance of one of its segments, are the offset. A piece placed along several segments takes the place of the last. Consecutive pieces along a ring join into sections, the first starting at the piece placed first when it was placed, and the sections are ordered by where along the raw curve each starts. One section is the curve, and more mean the offset splits.
Inside a tight bend the raw curve loops back on itself, inside the stroke: the edge skips the loop, and so does the offset. Where the line comes back near itself, the stroke covers part of the raw curve, which splits the offset there.
Buffers: a fill’s part grown or shrunk
The same module grows or shrinks a fill’s part, as shapely’s buffer does for Ink/Stitch’s inset
underlay and expand_mm (fills, REQ-FILL-TAT-012). Each ring’s raw curve is
built as in step 1, closed, with round corners of 16 segments to a quarter circle. It runs to the ring’s
left, outside an outline and inside a hole: a positive distance grows the part. In place of steps 2
and 3, the area the curves wind round clockwise at least once comes from the fill’s own region code, and
the loops where the curves fold over drop out of it. GEOS’s rules for a ring the buffer wears away, and
for a small ring whose curve turns inside out, are followed as the buffer module’s docs say, and
conformance/oracle/buffer.py checks the polygons against shapely’s.
Checking against shapely
conformance/oracle/offset.py asks shapely 2.2.0, with the GEOS 3.14.1 its wheels bundle, for the
offset curves of 1,050 random lines: gentle and wild walks, arcs, spirals, waves and zigzags, walks on a
grid of whole numbers, whose points often lie in line and whose segments run back along others, and
walks of tiny steps, which are simplified before they are offset. Each is offset at one of 6 distances
to either side, with one of the joins and 5 mitre limits. Shapely’s answers are the fixture
conformance/fixtures/geometry/shapely-offset.txt, and the engine’s unit tests check every curve point
within a billionth of a millimetre, a point repeated a hair apart counted once. Shapely runs as a black
box, through its public API.
5 grid walks differ, all lines that run back exactly along themselves or whose stroke’s edge touches itself exactly. There the pieces of the edge coincide, and whether GEOS takes 2 of them to meet turns on the rounding of its own arithmetic. The engine merges points nearer each other than a billionth of the half width, and judges pieces that coincide as one. Its curves differ there by a node on a straight stretch, or by a short stretch beside the doubled-back part of the line. The test names those 5 lines, so that a change to them is seen.
Shapely’s answers fix what the polygons are. A property test checks that any polygon gives valid ones:
any_polygon_with_holes_grows_and_shrinks_into_valid_parts grows and shrinks random stars with up to 3
star-shaped holes by up to 8 mm. Each must finish within the work budget. Every ring must be closed and
within reach of the polygon’s bounds, with outlines clockwise and holes counter-clockwise (y up). Growing
must add area, and shrinking take it away. Spike M0.7 ran it on 10,000 polygons (ADR-0005),
and its speed against GEOS’s is recorded there.
A closed line, one that ends where it starts, is buffered by GEOS as a ring, with no caps. The engine offsets only open lines. A satin column’s centre line with its ends together is cut in half first.
Building the edge costs work in proportion to the square of the ring’s length: every piece is checked against every other, and its winding against every segment of the ring. A line of 1,000 points takes up to about 17 million units of work for each offset.
Fill generators
A fill covers the area its path bounds, its region, with rows of stitches. Tatami is P1 (M5); contour, meander and circular are P2 (M7); guided, linear gradient, tartan and cross stitch are P3 (M10). Ink/Stitch’s legacy fill is not planned, and is sewn as a tatami fill (below).
A fill sews the drawing’s shape with Ink/Stitch’s stitches. Ink/Stitch’s rows, their spacing and stagger, the order it sews them in, its travel, underlay and compensation are what make its fills look good embroidered. StitchCraft keeps them, with their parameters’ names and defaults, and a file sews alike in both. Where the area Ink/Stitch fills departs from the drawing, StitchCraft follows the drawing, and a diagnostic says where the two differ. A part Ink/Stitch leaves out without a word is left out with a warning.
A fill whose fill setting is off sews nothing, and nothing is said of it, as in Ink/Stitch
(REQ-FILL-003). The path’s outline, if it has one, is sewn as its stroke settings say, and the element
before it still ends near its area, as Ink/Stitch’s does.
Region
A fill’s region is the area an SVG renderer paints for its path, in parts with holes
(stitchcraft_engine::normalize::region, REQ-FILL-001):
- Rings. Each subpath is flattened within a tenth of a CSS pixel, as Ink/Stitch flattens a fill’s
outline, and closed. A subpath of fewer than 3 distinct points bounds nothing and is left out
(
DEV-FILL-002). - Cuts. Wherever 2 segments cross, touch or run along each other, both are cut there, on a grid of 2⁻³⁵ mm, about 2.9 × 10⁻¹¹ mm. The rings’ points go on the grid, much as Ink/Stitch puts a fill’s shape on a grid of 10⁻¹⁰ CSS pixels, about 2.6 × 10⁻¹¹ mm. So the corners that shapes share, which relative path data and transforms leave a few units in the last place apart, become one point, and a gap or an overlap that narrow closes. A segment that passes through the cell of a corner, or of a point where 2 segments cross, is bent through that point. This is snap rounding, and the edges then meet only at their ends however rounding left the drawing. Before it, crossings worked out in floating point and snapped to points nearby could leave 2 edges crossing with no point between them, and whole faces filled or empty against the rule. On the grid, which side of a line a point lies on and which way a ring turns are decided exactly. A stretch drawn more than once becomes one edge that counts how many times it was drawn each way.
- Faces. The edges bound faces. Outside everything the winding number is 0, and crossing an edge from its right to its left adds its count. Every face’s winding number follows from its neighbours’ in whole numbers.
- Fill rule. A face is filled when its winding number is not 0 (
nonzero, SVG’s default) or is odd (evenodd). Ink/Stitch’s area differs in 2 ways, and StitchCraft follows the drawing in both (DEV-FILL-001). Ink/Stitch takes every subpath but the largest as a hole. Wherenonzerofills a face inside that even-odd leaves empty, StitchCraft fills it, andSC-I0306says Ink/Stitch leaves it empty. And Ink/Stitch repairs a shape that is not valid by merging the subpaths’ lines, a stretch drawn twice into one, and filling the faces they bound by how deeply they nest. A face beside the outside nests in nothing, and is always filled. Where the fill rule leaves such a face empty, a notch drawn as a hole along the outline or a subpath drawn twice,SC-I0313says Ink/Stitch fills it. - Rings of the parts. The edges between filled and empty faces are joined into rings with the filled side on their right. Outlines turn clockwise and holes counter-clockwise in y-up axes, the other way round on screen, where y points down. Where rings meet at a point, they are joined the way GEOS joins them, the geometry library behind Ink/Stitch’s shapes. Parts that touch at a point stay apart. A hole that touches its outline stays a hole. Each ring starts at its point that comes first in the drawing, and parts and holes keep the drawing’s order too.
- Parts too small to sew. A part of 3 square CSS pixels or less (0.21 mm²) is too small for a row
of stitches and is left out. Ink/Stitch leaves it out without a word. StitchCraft says so with
SC-W0303(REQ-FILL-002). A fill under 20 square CSS pixels (1.4 mm²) getsSC-W0304, as Ink/Stitch warns of it. A fill in parts getsSC-W0307, since each part is sewn on its own.
The work is charged to the element’s budget, one unit for each point, edge bent and grid point tested
against it, pair of edges whose spans along x overlap, half edge, ring compared with a face or an outline,
and point walked to find a ring’s bounds or its place among the faces and outlines (REQ-FILL-004). Each
ring’s bounds are found once. A ring that bounds no face is located only in the faces whose bounds cover
its own, and a hole apart from every outline only in the outlines whose bounds cover its own.
Property tests draw random rings on a grid, and random rings with float coordinates beside near-copies of
themselves, moved or turned by a hair or drawn back, and count how many times they wind round points off
the edges. Such a point lies in the region exactly when the fill rule fills it, unless it lies in a part
left out. Every ring found is simple and turns the right way, and the edges the rings are cut into meet
only at their ends. Drawings of shapes that share corners and edges, written with relative path data as
Inkscape writes them, fill their shapes’ area, in 1 part where the shapes share edges.
Regions are combined by the same cuts and faces (stitchcraft_engine::normalize::region::ops), as
automatic digitizing combines the shapes of a picture where they meet
(Neighbours). A region’s parts never overlap and its outlines turn
clockwise. Its rings then wind -1 times round each point inside it and 0 times round every other point. Of
the faces that the rings of 2 regions bound, the union keeps those some ring winds round and the
intersection those wound -2 times. The difference first reverses the second region’s rings, then keeps the
faces wound -1 times. A region grows or shrinks by each part’s offset curves, with round corners
(normalize::offset), and the parts grown are joined, since they may then overlap.
Tatami fill
Parallel rows of running stitches across the region, with the needle points of neighbouring rows offset so they never line up into visible furrows. Most fills in most designs are tatami. Each step below sews as Ink/Stitch sews, on StitchCraft’s region, unless it says where it differs. The roadmap’s M5 steps build them in this order.
Parameters: angle, row_spacing_mm, end_row_spacing_mm, max_stitch_length_mm, staggers,
skip_last, underpath, running_stitch_length_mm, running_stitch_tolerance_mm, gap_fill_rows,
pull_compensation_mm, pull_compensation_percent, expand_mm, random_seed,
enable_random_stitch_length, random_stitch_length_jitter_percent; underlay: fill_underlay,
fill_underlay_angle, fill_underlay_row_spacing_mm, fill_underlay_max_stitch_length_mm,
fill_underlay_inset_mm, fill_underlay_skip_last, underlay_underpath.
1. Rows
Rows are laid across each part of the region on its own (stitchcraft_engine::generators::tatami::rows,
since M5.2). They run at angle, counter-clockwise from horizontal on screen: at 0° left to right, at
90° up the screen. Measured across them from the design’s origin, the first row of a part lies the
largest whole number of row_spacing_mm from the origin that does not pass the part, and the rows follow
it row_spacing_mm apart up to the part’s far side, with none on it (REQ-FILL-TAT-002). Fills side by
side at the same angle and spacing then share their rows. Angles of whole right angles are exact, so rows
at 0° or 90° run exactly along the drawing’s axes. The nth row lies n spacings past the first, multiplied
out, since a spacing such as 0.1 mm rounds in binary, and added up row by row its rounding grew into a
row on the far side.
With end_row_spacing_mm, each step to the next row is the row spacing plus the difference to the end
spacing times the distance from the first row over the part’s height. The spacing then reaches the end
spacing a part’s height from the first row. Ink/Stitch carries the change on beyond that. Over a part
only a few rows tall, a shrinking spacing then turns so small that the rows pile up short of the far side
without end. StitchCraft keeps the end spacing from there on (DEV-FILL-003).
A row’s segments are the stretches of it in the part, its outline included, split at every point where
the row meets the outline (REQ-FILL-TAT-010). Those are the points where it crosses the outline or the
outline touches it from inside, and both ends of a stretch where it runs along the outline. A row along
an edge is then a segment of its own, and a row that only touches the part at a point has none. This is
how GEOS, the library behind Ink/Stitch’s shapes, cuts a line with a polygon. Its answers for 2,511 rows
across 406 random polygons of grid squares and half squares, 98 of them with holes, are recorded and
checked (conformance/fixtures/geometry/shapely-rows.txt).
Which stretches lie inside is decided by counting the edges that cross the row, each over its height
from its lower end up to but not including its upper end.
A side drawn on a row lies a hair off it by the time it is cut: the region puts its corners on a grid
2.9e-11 mm wide, and the row’s place rounds. So a point of the outline within 1e-9 mm of a row lies on
it, and a row within 1e-9 mm of a part’s far side lies on that side, and is left out. A part’s first and
last rows, and the segments of a row drawn along a side, then don’t depend on the rounding. Ink/Stitch’s
do (DEV-FILL-012).
The top rows alone, laid over each part grown by expand_mm as the fill grows it, are what a preview
shows of a fill too costly to sew within the preview’s budget (generate::rows, since M6.3). Laying
them costs a small part of sewing the fill, since no needle point is placed and no route is searched. A
28 mm square takes 626 units of the engine’s work for its rows, and 62,532 to sew.
2. Needle points along a row
A row’s needle points lie on a grid along it, as Ink/Stitch places them
(stitchcraft_engine::generators::tatami::stitches, since M5.3). The grid’s points are the longest
stitch apart, max_stitch_length_mm. A fill always has one: 4 mm when it is not set or left empty, and
0.1 mm, with SC-W0102, when it is 0 or less, as in Ink/Stitch. Measured along the rows from
the design’s origin, they lie at whole numbers of stitches plus the row’s offset (REQ-FILL-TAT-003). A
row’s number is how far across the rows it lies from the origin, over row_spacing_mm, rounded to the
nearest whole number, halves to the even one. Its offset is the fractional part of that number over
staggers, times the longest stitch. Each row’s points then lie a staggers-th of a stitch along from the row
before’s and come back after staggers rows, so neighbouring rows never line them up into furrows. A
fraction of a stagger draws diagonals that show less. A negative staggers staggers the rows the other
way, and 0 counts as 1, which lines every row’s points up, as in Ink/Stitch. Fills side by side at the
same angle share the grid, as they share their rows.
A segment is sewn from one end to the other, as routing decides. It takes its start, then every
grid point past the start and before the end, and then its end, unless skip_last is on or the last
point lies within 0.1 mm of it. A start on the grid is sewn once. Sewn the other way, a segment takes the
same grid points.
With enable_random_stitch_length, random lengths take the grid’s place. The point nearest the start
lies a random share of the longest stitch past it, and each next one a stitch on, longer or shorter by up to
random_stitch_length_jitter_percent of it, while they lie before the end. The draws come from the
element’s generator, seeded with random_seed, in the order the segments are sewn. Ink/Stitch seeds a
generator for each segment, and its random values differ (DEV-FILL-004).
3. Routing: every segment once
Each part’s segments are sewn once each, in the order Ink/Stitch routes them
(stitchcraft_engine::generators::tatami::route, since M5.4, REQ-FILL-TAT-001).
- Order. The segments are taken row by row, across the rows, and within a row by how far their start lies from the corner of the part’s bounding box where x and y are least. Each runs along its row.
- Graph. Each segment is an edge between its 2 ends, and the ends are nodes. A node belongs to the ring nearest it, or to the outline when a hole is as near. Its place is its distance along that ring from the ring’s start. Along each ring an edge joins each node to the next, and every other one of those stretches, from the first, gets a second edge. A node then meets its row and 3 stretches of ring, an even number of edges. Ink/Stitch’s graph knows an edge between 2 nodes by its kind, so a ring of 2 nodes doubles only one of its 2 stretches. This graph does the same.
- Start and end. The fill starts at the point of its rings nearest the needle and ends at the point
nearest its next stitch, which the next element’s shape gives (section 5). Each of the 2 points gets a
node, which splits the stretch of that ring whose chord passes nearest it. Then the node nearest the
needle is where the fill starts, and the one nearest the next stitch where it ends. Without a needle
the fill starts at its first segment’s start, and without a next element it ends where it starts. Only
rings that rows reach are looked at. Ink/Stitch joins an unreached ring with straight lines, which it
sews as rows (
DEV-FILL-006). - Even degrees. A row through a corner of the outline, or one along it, shares a node with the row
beside it, and some nodes then meet an odd number of edges. Ink/Stitch adds paths between such nodes
that may run along rows, and sews those rows a second time. StitchCraft pairs the odd nodes so that the
needle’s way between pairs is shortest in all and joins each pair with an edge. Up to 16 nodes every
pairing is weighed, and past that each node pairs with the nearest left. No row is sewn twice
(
DEV-FILL-006). A pairing edge stands for the way between its 2 nodes that Ink/Stitch’s paths double: one edge where a row or a stretch of ring joins the 2, and several otherwise. Travel along it is never a handover between neighbouring rows forskip_last, and it ends a stretch for gap-fill rows only where it stands for several. - Walk. Ink/Stitch walks a copy of the graph, which its graph library makes by adding the edges again, node by node in the order the nodes came, each node’s edges in its own order. So in the copy a node lists first its neighbours that came before it, in the order they came, and then the others, itself included, in its own order. The walk starts at the end node. At each node it takes the node’s row if one is left, and otherwise the node’s first edge left. A node lists its edges neighbour by neighbour, each neighbour’s in the order they came. A neighbour whose last edge is taken drops out of the list. When the walk reaches a node with no edge left it steps back, and the edges, in the order it steps back over them, are the route. The route comes back to the end node, and before it the needle runs from the fill’s start to that node. The running stitches between rows then mostly come just before the rows beside them, which cover them, and the rows go back and forth like a mown lawn.
- Between rows. Steps between rows in a row are one stretch, from where the last row ended to where
the next starts. The needle travels it under the rows (section 4), or with
underpathoff along the ring between 2 points of one ring, the shorter way round, and between 2 rings the shortest way along rings and rows (DEV-FILL-006). Ink/Stitch’s needle takes the way round with fewer nodes of its graph, a node every CSS pixel along the outline’s sides, which is about the shorter way but not always (DEV-FILL-011). It sews running stitches of the firstrunning_stitch_length_mm, at least twice the shortest stitch, withinrunning_stitch_tolerance_mm. Ink/Stitch reads a fill’s value as one number, and sews a value of several in its default, 2.5 mm (DEV-SAT-005). The first running stitch would sew the row’s end again, so it is left out. Withskip_lastthe row’s end is not sewn, and the first running stitch stays unless the next row starts beside it. - The shortest stitch. Rows the row spacing apart start and end nearer each other than the shortest
stitch a machine sews well, and a row’s first grid point can lie just past its start. A needle point
nearer the one before it than the shortest stitch is left out, as Ink/Stitch leaves out points nearer
than its own shortest stitch. A part’s last point stays, and the points before it that are too near it
go instead, unless that would bring it within the shortest stitch of the part’s first: then it goes
itself. This is finalize’s rule, applied in the generator. Where the rows lie nearer each other than the
machine’s shortest stitch and meet an edge square on, each row hands over to the next in a stitch too
short to sew, so the next row’s first needle point is left out (
DEV-FIN-001). The thread then runs from the row’s end to the next row’s second needle point, and a sliver at each row’s start shows. On the Brother, whose shortest stitch is 0.3 mm, a bar 5 mm wide at Ink/Stitch’s default of 0.25 mm is 97 % covered, and at 0.3 mm or more it is covered to its edges (REQ-FILL-TAT-004). Ink/Stitch’s shortest stitch, 0.1 mm by default, keeps the handover. Along an edge at a slant to the rows, their ends lie farther apart: the spacing over the sine of the angle between edge and rows, 0.35 mm at 45° for rows 0.25 mm apart, which the Brother sews.
The route follows Ink/Stitch’s from the same graph. StitchCraft’s rings start where the drawing starts
them, and Ink/Stitch’s where GEOS’s overlay starts them, so which stretches are doubled, and with them the
route, can differ (DEV-FILL-005).
4. Travel under the rows
With underpath, on by default, the needle runs between rows inside the part, under the rows sewn
after it, which cover it, as Ink/Stitch’s does (stitchcraft_engine::generators::tatami::underpath,
since M5.5, REQ-FILL-TAT-005).
- Lines. 3 gratings of lines cross the part, laid as rows are (section 1). 2 of them lie at 45° to
the rows, one either way, their lines 2 mm apart, and the third square to the rows, its lines √2 mm
apart. A part smaller than 10,000 square CSS pixels (700 mm²) has less room, and its lines lie half as
far apart. The square lines pass through the crossings of the others, so the 3 make one network. Its
nodes are the crossings, the lines’ ends on the rings and the route’s nodes, and along each ring an
edge joins each node to the next. A crossing within 0.005 px of a node already found is that node.
Where either 45° grating misses the part, the needle runs as with
underpathoff. Ink/Stitch smooths that way as it smooths a way under the rows, step 4, and StitchCraft keeps to the rings (DEV-FILL-013). - Costs. A piece of line costs its length in CSS pixels over its distance from the part’s rings, simplified within 0.5 mm, plus a tenth of a pixel. A stretch of ring costs 3 times the straight line between its ends. Travel then keeps to the middle of the part, away from its edges.
- Sewn rows close lines. Once a row is sewn, the pieces of line that cross it close, and no later travel follows them over it.
- The way. The needle takes the cheapest way. The way is cut into points 10 px apart within 4 px of it, the corners of the points after its start are cut 5 times, a quarter of the way along each side from each end, and its own ends go back at either end: it leaves its start straight and curves from there. Then it is kept inside the part. Where it leaves the part or runs along a ring, the ring takes its place, the shorter way round, from where it leaves to where it comes back: the hole it leaves into, or else the outline. It is sewn in running stitches as in section 3.
- A hole between the lines. Where no line joins the 2 points, as for a hole small enough to lie
between the lines, the needle runs along the rings and rows, as with
underpathoff. Ink/Stitch goes straight there, but it travels between 2 rings only from the fill’s start to its end, before any row is sewn. StitchCraft also travels between rings to pair nodes (DEV-FILL-006), after rows are sewn, which a straight way would cross. - A needle inside the part. The fill starts at the node of the network nearest the needle (since M5.7). For a needle on the rings or outside the part, that is the route’s start. For one inside, as after an inset underlay, it is a node inside, and the needle travels the cheapest way from there to the route’s start, or on to the end of the route’s first stretch of travel, as Ink/Stitch’s does.
Equally cheap ways, the simplified rings and the points a way is cut into follow StitchCraft’s own rules,
so the way can differ from Ink/Stitch’s in its details (DEV-FILL-007).
Travel asks the rings where a point lies on them, how far a line lies from them, and where a stitch meets
them or lies inside. Each question looks only at the ring sides near its point, line or stitch, filed in a
grid of square cells about as many as the sides, and gets the answer a look at every side would give
(REQ-FILL-TAT-013).
5. Parts
A region in parts sews them one at a time, as Ink/Stitch sews them (REQ-FILL-TAT-008, since M5.4).
The part nearest the needle goes first, a part the needle is in being 0 away. With no needle, the part
with the least x goes first. Ink/Stitch sorts the parts with a stable sort. GEOS’s repair gave the parts
of 400 of 400 random fills tied at their least x in order of their point of least x, and of least y
among the points level with it (shapely 2.2.0, GEOS 3.14.1). Parts that tie go in that order here too.
Each part ends at its point nearest the next part. The last ends at the point of the fill nearest its
next stitch. That is the next element’s first stitch when it lies in the fill. Of a satin column’s
rails, the start of the first that lies in the fill is the next stitch, and of a fill’s parts, the first
outline’s start that does. Failing those, for a fill after a fill, it is the first start of this fill’s
outlines that lies in the next, and otherwise the fill’s point nearest the next element. Ink/Stitch’s
geometry library finds it so (satin).
Each part is a group of its own, which assembly joins to the next as it joins any 2 groups:
straight on within the collapse length, and otherwise with a jump and the ties and trims the element’s
settings give. SC-W0307 says the fill is in parts.
6. Underlay
Before its top rows, each part sews its underlay, on by default (REQ-FILL-TAT-011, since M5.6). The
underlay is a layer of rows for each angle in fill_underlay_angle, in the order given, or with none set
one at angle + 90°, square to the top rows. A layer’s rows lie evenly fill_underlay_row_spacing_mm
apart (default: 3 times the row spacing), with no end spacing. Their needle points lie on the stagger
grid with the fill’s staggers, fill_underlay_max_stitch_length_mm apart (default: the fill’s longest
stitch), never at random lengths. fill_underlay_skip_last and underlay_underpath take the place of
skip_last and underpath.
Each layer is routed as the top rows are (sections 3 and 4). It starts at the point of the part’s rings
nearest the needle and ends there, as Ink/Stitch routes a fill with nothing to end near. The next layer,
and then the top rows, start where it ended, so a part is still one run. A layer with no row is sewn round
the part’s outline from its start, as the top rows are (section 8), and SC-I0308 says so. The needle
goes on to that start as Ink/Stitch’s stitch plan joins a layer to the next one: straight on within the
element’s jump length, and farther with a jump. The generator makes that join itself, because Ink/Stitch
never forces locks after a layer of underlay, where assembly would after any group of an element with
force_lock_stitches.
The underlay is sewn on the part shrunk by fill_underlay_inset_mm, and the top rows on the part grown by
expand_mm, or shrunk where it is negative (REQ-FILL-TAT-012, since M5.7). A part is grown or shrunk
as GEOS buffers a polygon, which Ink/Stitch calls, with round corners of 16 segments to a quarter circle
(stitchcraft_engine::normalize::offset). Each ring is offset, the offsets are cut where they cross, and
the faces they wind round clockwise at least once are the area. Before offsetting, GEOS leaves out a ring
that shrinking wears away. After, it leaves out a small ring’s offset that has turned inside out. The
engine follows both rules. The polygons agree with shapely’s within a nanometre for 330 random polygons,
some at the edges of both rules (conformance/oracle/buffer.py).
Shrinking can cut a part into pieces. Each piece is sewn on its own, in order of its point of least x, and
of least y among pieces level with each other: a run of pieces, each with its least x within a nanometre
of the one before. That is GEOS’s order in all but a few cases, where GEOS orders
pieces with a sliver among them, or pieces level at their least x, as it walks its graph (DEV-FILL-008).
Every layer of the underlay is sewn on one piece before the next, each starting nearest the needle. Each
piece’s top rows start nearest the needle too, after the piece before, and end nearest the next part or
element. Ink/Stitch starts every piece’s top rows nearest where the needle was before the first
(DEV-FILL-008). Ink/Stitch sews each piece’s top rows as a group of its own. With
force_lock_stitches, so does StitchCraft: assembly ties each off and jumps to the next, however near. Where shrinking wears a part away entirely, the part itself is sewn, as in Ink/Stitch,
and SC-W0310 (inset) or SC-W0309 (expand) says so, as Ink/Stitch warns. Ink/Stitch also warns of an
inset that wears a part away when the fill has no underlay to inset, and StitchCraft does not.
7. Pull compensation and gap-fill rows
The thread pulls a row in at its ends. Each top row’s segment is lengthened at both ends by
pull_compensation_mm plus pull_compensation_percent of its length (REQ-FILL-TAT-006, since M5.8).
The first value applies at the end the rows start from at the fill’s angle. A second value, if given,
applies at the other end. Where 2 segments of a row meet end to end, because the row only touches the
outline there from inside or runs along it (section 1), the row goes on in the part. Both segments end at
that point as they are, so the row is sewn once through it, as through Ink/Stitch’s rebuilt part, where the
row is one segment. Ink/Stitch moves a satin’s stitches with the same function, and the two settings are declared once
for both (stitchcraft_engine::generators::pull). A fill reads a value below 0 as 0, as Ink/Stitch does,
and SC-W0312 says so. The route is planned on the part as drawn. The needle’s ways keep their course,
but each starts where the row before it ends and ends where the next row starts, both lengthened.
Underlay is not compensated, as in Ink/Stitch.
Ink/Stitch rebuilds the part round the lengthened rows, each widened by half the row spacing with round
ends, and smooths the outline. Its rows then reach up to half a row spacing further, and it fills holes
smaller than a row spacing squared and holes the widened rows close over. Where the widened rows form
separate pieces, it keeps only the largest. StitchCraft keeps the part’s holes, gaps and pieces, which the designer drew
(DEV-FILL-009).
Where the route leaves a stretch of more than 3 rows for another, the fabric can open a gap along its
last row before the rows past it are sewn. There, gap_fill_rows, rounded up to an even number, sews
that many more rows past the last one, back and forth, and comes back to where it ended
(REQ-FILL-TAT-007, since M5.8). A stretch’s rows are counted as Ink/Stitch counts them: the rows
reached by travel. The route’s first row is one of them, because Ink/Stitch’s route always starts with
travel, of no length where it starts on that row. Ink/Stitch’s gap fill ends a stretch where the walk
goes from one row to the next by more than one edge once the loops between them are taken out, and so does
this one. An edge that comes back to a node where an earlier one of those edges ended takes out the edges
since, and itself, but a loop back to the row it left stays. So travel out and back before a handover to the
row beside ends a stretch, and so does a pairing edge that stands for a way of several. The extra rows go
on the way the stretch went, away from the last row sewn before the last one in another row. A last row
sewn on through a joint is 2 segments of one row, and the way comes from the row before them, as
Ink/Stitch takes it from the row the needle left for that row. Each extra row is the next row of the
grid, the stretch of its segment beside the extra row before it, or the last row for the first,
lengthened as the rows are. The extra rows are one strip, stop at the part’s edge, and stop where the part
ends. The hop from each to the next, and the way back, are kept inside the part as travel is. Ink/Stitch
repeats the last row a spacing further each time, and a repeat can run outside the part (DEV-FILL-010).
Underlay has no gap-fill rows.
8. No rows
A part thinner than the row spacing has no row. It is sewn as a running stitch round its outline, from
the outline’s start, as Ink/Stitch sews it, and SC-W0305 says so (since M5.4). A layer of underlay with
no row is sewn so too, and SC-I0308 says so (since M5.6). Where the fill rule leaves every face empty,
the fill sews nothing, and SC-W0303 says so.
Properties (conformance)
| Requirement | Property |
|---|---|
REQ-FILL-TAT-001 | Every row segment is covered exactly once by top stitches |
REQ-FILL-TAT-002 | Measured row spacing equals the parameter (± 2 %), or follows the start→end gradient |
REQ-FILL-TAT-003 | Needle points follow the stagger grid: a row’s points lie a staggers-th of the longest stitch along from the row before’s, and no 2 neighbouring rows line them up. A segment ends at its end unless skip_last is on or its last point lies within 0.1 mm of it |
REQ-FILL-TAT-004 | Coverage (stitchcraft_testkit::metrics): rasterized at 0.05 mm with 0.4 mm thread width, ≥ 98 % of the region is covered at spacings from the machine’s shortest stitch to 0.4 mm, since M5.9 |
REQ-FILL-TAT-005 | With underpath, travel takes the cheapest way along the lines under the rows not sewn yet, and its way lies inside the region (± 0.05 mm) |
REQ-FILL-TAT-006 | Each top row extends past its segment’s ends by the compensation; the part keeps its holes and pieces |
REQ-FILL-TAT-007 | Gap-fill rows follow a stretch of more than 3 rows, which ends where Ink/Stitch’s gap fill ends one; all rows, gap-fill rows included, and the stitches between them lie inside the part within its compensation |
REQ-FILL-TAT-008 | A region in parts sews each part on its own, nearest the needle first, each ending nearest the next part, the last nearest the next element |
REQ-FILL-TAT-009 | A 130 × 180 mm rectangle at 0.4 mm, the reference hoop’s whole field, plans within the NFR-PERF-1 budget, guarded by cargo xtask perf since M5.9 |
REQ-FILL-TAT-011 | Before its top rows a part sews a layer of underlay for every angle, which starts and ends at its point nearest the needle, where the top rows then start |
REQ-FILL-TAT-012 | The underlay covers the part shrunk by its inset and the top rows the part grown by expand_mm, as shapely buffers it, within a nanometre |
REQ-FILL-TAT-013 | Questions about the rings look only at their sides near each point, line and stitch, and answer as a look at every side would: a 70 mm square with 14 × 14 round holes and a 120 × 60 mm background with 60 detailed holes plan within a tenth of the default work budget |
Machine checkpoint MC-4 sews a density ladder, an angle set and a fill-with-outline registration test to tune pull compensation (machine testing).
Contour fill
Rings that follow the region’s outline inward, like growth rings (P2, M7.7), as Ink/Stitch sews its
contour fill (read at 6555892). The outline and the holes are offset inward by row_spacing_mm, again
and again, with the corners join_style gives them, round, mitred or bevelled, until nothing is left.
Each ring lies inside the one it was offset from, and the rings form a tree. Where the shape narrows, a
ring parts into several, and a hole’s ring joins the outline’s where they meet. clockwise sets which way
the rings run. contour_strategy sews the tree:
- Inner to outer (
0). The needle goes round the outline from its point nearest the fill’s start. At the first point within 1.5 row spacings of a ring inside it, or within 2.05 if no point is that near, it goes on to that ring and sews it, and the rings inside it, the same way, back to where it entered. It then goes on round the outer ring, a row spacing past where it left. A ring that no point of the ring outside it comes within 2.05 row spacings of is not sewn. Each innermost ring is sewn the other way round, or withavoid_self_crossingevery other level of rings is.smoothness_mmsmooths the path, which is then kept inside the region. - Single spiral (
1). One spiral from the outline in to the centre, each turn running from a ring to the next one inside it. Where a ring parts into several, the spiral goes on into the one whose centre lies nearest its own, and the others are not sewn. - Double spiral (
2). The same rings, every other one on a spiral in and the rest on a spiral back out.
The path is sewn as a running stitch of max_stitch_length_mm within running_stitch_tolerance_mm,
with bean stitch and repeats. Ink/Stitch leaves out the rings it does not reach without a word.
StitchCraft names them (SC-W0311). Ink/Stitch’s repeats double the stitching each time, so that k
repeats sew 2^(k−1) passes. Whether StitchCraft’s do too is settled with the fill.
Properties: rings lie the row spacing apart (± 5 %), and neighbouring rings never cross. Each strategy sews one unbroken path per part.
Meander fill
A path that wanders through a pattern (P2, M7.8), as Ink/Stitch sews its meander fill (read at
6555892). A tile’s lines are repeated over each part of the region, scaled by meander_scale_percent
and turned by meander_angle. The lines that lie wholly inside the part make a graph of their ends.
Lines with a free end, one that meets no other line, are taken away, again and again, until none is
left, and the graph’s pieces are joined where they lie nearest. The
path starts at the graph’s point nearest the fill’s start and ends at the one nearest the next element,
or else nearest the point of the outline across from the start. It begins as the shortest way between
the 2. Then, in an order the random seed sets, each of its steps is replaced by a detour of up to 7 steps
through points the path has not passed, and each 2 steps in a row by one of up to 10, until no detour is
found. The path never passes a point twice. It is smoothed by smoothness_mm and sewn as a running
stitch of running_stitch_length_mm, or, where zigzag_spacing_mm is above 0, as a zigzag
zigzag_width_mm wide. clip keeps it inside the part. Bean stitch and repeats follow, and the repeats
double the stitching each time, as the contour fill’s do. Its random numbers are StitchCraft’s own, as for
a tatami fill’s random lengths (DEV-FILL-004), and the path differs from Ink/Stitch’s for the same
seed.
Tiles are StitchCraft’s own, generated in code (Hilbert, Peano, Truchet-style arcs, waves, pebbles…),
never copied from other projects. meander_pattern takes our tile ids, and Ink/Stitch’s tile names map
to the closest of ours as a recorded deviation.
Circular fill
A double spiral round a centre (P2, M7.9), as Ink/Stitch sews its circular fill (read at 6555892): the
point of a target_point command, or else the centre of each part’s area. Circles round it lie
row_spacing_mm apart, or graded towards end_row_spacing_mm with their distance from the centre, out
to 2 spacings past the part’s farthest point, with one of 0.1 CSS pixel radius at the centre. Alternate
circles are joined into a spiral in, and the rest into one back out, as the contour fill’s double spiral
is, and the part cuts the spiral into pieces. One piece is sewn as a running stitch, joined along the outline
to where the fill starts and ends. Several are routed as tatami rows are, with the travel under them at
angle and skip_last. A part that lies within a row spacing of its centre is sewn as one circle of that
radius. Bean stitch leaves the travel out, and the repeats double the stitching, as the contour fill’s
do.
Guided fill (P3)
Rows that follow a guide line instead of a straight angle: the guide is copied (Copy) or offset
(Parallel Offset, Buffer) across the region at row_spacing_mm, each copy clipped to the region and
sewn with the tatami needle grid (staggers, stitch_position_method). Specified fully at M10.
Linear gradient fill (P3)
A tatami variant whose rows alternate between the colours of a linear gradient with a probability that follows the gradient position, producing a blend from interleaved threads; one colour block per stop. Specified at M10.
Tartan fill (P3)
A woven plaid from a stripe specification (sett): warp and weft stripes become fills at tartan_angle
with rows_per_thread and optional herringbone. Specified at M10.
Cross stitch (P3)
The region is covered by grid cells (pattern_size_mm, cross_offset_mm, cross_rotation,
canvas_grid_origin); a cell becomes a cross when the region covers at least fill_coverage percent of
it; crosses use cross_stitch_method (simple, half, upright, double, Smyrna, flipped variants) and are
routed row by row. Specified at M10.
Legacy fill (not planned)
Ink/Stitch’s first fill method, which it keeps for its older files: it gives legacy_fill to a fill whose
auto_fill is off when it opens one. Its rows lie on a tatami fill’s grating, with a tatami fill’s needle
points along them (staggers, skip_last), and it sews no underlay. It sews each stretch of rows that
join on its own, back and forth, with no travel stitches between stretches; flip starts its rows from
the other side, and reverse sews the whole fill backwards.
StitchCraft does not build it, since the method exists for Ink/Stitch’s older files. A fill set to it is
sewn as a tatami fill with its settings and no underlay, and SC-W0314 says so (REQ-FILL-005). It is a
tatami fill in every other way too. The element before it ends near its area, and its rows are routed and
joined by travel stitches as a tatami fill’s are (DEV-FILL-014). flip and reverse are not planned,
and a design that sets them gets SC-W0105. The VectorCraft plug-in’s fill dialog does not offer the
method.
References
- D. Hilbert, “Über die stetige Abbildung einer Linie auf ein Flächenstück,” Mathematische Annalen 38, 1891.
Lock stitches
A lock, or tie, is a few small stitches where an element’s stitching starts (the tie-in) or ends (the
tie-off). The lock stops the thread from pulling out when it is trimmed or the needle jumps on.
Plan assembly puts locks at the ends of groups, as ties,
force_lock_stitches, jumps and trims say. This page specifies the lock itself:
stitchcraft_engine::locks, phase P1 (M3.7).
Parameters: lock_start and lock_end (the shape), lock_custom_start, lock_custom_end,
lock_start_scale_mm, lock_end_scale_mm, lock_start_scale_percent, lock_end_scale_percent
(reference).
Placement
The anchor is the group’s first needle point for a tie-in and its last for a tie-off. A group with no needle point other than the anchor has no stitch to lock, and assembly sews it without one.
- Steps are distances along the stitching from the anchor, positive into it. A lock of steps follows the stitching round its corners. It lies on the stitches that come after it, and they cover it. A negative distance goes straight back along the first stitch, before the anchor. Past the end of stitching shorter than the lock, the lock goes straight on from the last stitch.
- A drawn lock lies in a frame at the anchor. Its x axis runs along the stitch from the anchor to the next needle point at another place, pointing into the stitching. Its y axis is a quarter turn from x.
A sharp turn can bring two needle points of a lock of steps closer together than the steps between them.
When following the stitching would sew a stitch shorter than 0.2 mm, the lock is sewn straight along the
first stitch, in the frame, and SC-W0502 says so.
Tie-in and tie-off
A lock is sewn in the same order at both ends. At a tie-off, into the stitching is back along it.
- A tie-in leads into the anchor. The needle sews the lock from its first point, and the group’s first needle point follows.
- A tie-off leaves from the anchor: the group’s last needle point, then the lock.
The built-in locks come back to where they started. A tie-in starts where the stitching starts, and a tie-off ends where the stitching ended, which is where the thread is trimmed.
Shapes
The ids are Ink/Stitch’s, and a file that names a bowtie gets one. The shapes behind them are
StitchCraft’s own designs (deviation DEV-LCK-001). One table in
crates/stitchcraft-engine/src/locks/shapes.rs defines them. The lists of locks that each size parameter
applies to come from that table.
| Lock | Kind | Size | What the needle sews |
|---|---|---|---|
half_stitch (default) | steps | the first stitch | forth and back over half the first (or last) stitch, twice: steps half as long as that stitch, from 0.2 mm to 1 mm |
back_forth | steps | lock_*_scale_mm | forth and back over one step, twice: the half stitch’s rhythm, at a size the user sets |
custom | steps | lock_*_scale_mm | the element’s own steps (custom locks) |
arrow | drawn | lock_*_scale_percent | a shaft along the stitching to the tip of an arrowhead 1.4 mm ahead, round the head and back down the shaft |
bowtie | drawn | lock_*_scale_percent | two triangles tip to tip across the stitching, 1.2 mm long, their long stitches crossing in the middle |
cross | drawn | lock_*_scale_percent | an X 0.8 mm across, 0.3 to 1.1 mm ahead |
star | drawn | lock_*_scale_percent | a five-pointed star 1.2 mm across, one of its points at the anchor |
simple | drawn | lock_*_scale_percent | a diamond of four 0.58 mm stitches round the stitching |
triangle | drawn | lock_*_scale_percent | a triangle opening ahead, its tip at the anchor, 1.2 mm long |
zigzag | drawn | lock_*_scale_percent | zigzag across the stitching for 1.2 mm, then straight back along it |
A settings window shows lock_*_scale_mm only for the locks made of steps it sizes (back_forth,
custom) and lock_*_scale_percent only for the drawn ones and custom, as Ink/Stitch’s does. The half
stitch is sized from the stitch it lies on and takes its size from neither parameter.
The drawn shapes follow the same rules. Each one secures the thread in the same way and hides under the stitching it secures:
- a loop from the anchor round and back to it
- at most 1.4 mm along the stitching and 0.6 mm across it at 100 %, mostly ahead of the anchor
- stitches 0.46 mm to 1.4 mm long at 100 %, well clear of the 0.2 mm a lock stitch needs and long enough to grip
Steps
Steps are distances along the stitching, in sewing order, positive into it. A tie-in’s needle positions are
the anchor less the steps still to come, and its last step ends at the anchor. A tie-off’s positions are
the anchor plus the steps sewn so far. Take steps of 2 -1 in sizes of 0.5 mm. A tie-in starts 0.5 mm
behind the start of the stitching, goes 1 mm forth and goes 0.5 mm back onto it. A tie-off goes 1 mm back
over the last stitches and 0.5 mm forth again. Steps that add up to 0, as the built-in locks’ steps do,
start a tie-in and end a tie-off at the anchor.
Custom locks
lock_custom_start and lock_custom_end hold numbers separated by spaces, or an SVG path that draws the
lock. The numbers are steps, in sizes of lock_*_scale_mm. The kind of text sets how a file sews, and
StitchCraft reads it as Ink/Stitch does (REQ-LCK-004). Stripped of the white space round it, as Ink/Stitch
strips every value, a text made only of digits, spaces, dots, commas and minus signs is numbers. Any other
text is a path. The steps are the pieces between spaces that read as numbers, and 1,5 is not one of them.
StitchCraft reports what it cannot sew (SC-W0503), where Ink/Stitch passes over it in silence:
- Pieces that are not numbers are left out, with steps longer than 10 m (the data model’s reach) and steps of 0, which move nowhere and would sew in place.
- With no step left, as in an empty text, the half stitch is sewn instead.
- A lock drawn as an SVG path is not sewn yet, and the half stitch is sewn instead. The engine never reads SVG, and the SVG adapter will hand it the path (roadmap M8).
What comes of a lock’s settings alone, these and the enlargements below, is the same at every end the lock is sewn at, so it is said once for the element, with no place. A lock sewn straight where the stitching turns is said where it is sewn.
The shortest lock stitch
A lock stitch is at least 0.2 mm long (LOCK_MIN_STITCH), and the plan checker holds lock stitches to
that length (REQ-PLAN-002). The needle never goes back into the hole it just left. Joins count: a
tie-in’s last stitch into the anchor and a tie-off’s first stitch out of it.
- A step shorter than 0.2 mm is lengthened to it, keeping its direction.
- A drawn lock whose shortest stitch would be shorter is enlarged as a whole until that stitch is 0.2 mm long, keeping its shape.
- A lock of steps that a sharp turn would fold onto itself is sewn straight (placement).
SC-W0502 reports each of these, with the size. The half stitch never needs them.
Compared with Ink/Stitch
Read at d59c9ab (ADR-0012). The ids, how a custom lock’s text is read,
and which size parameter applies to which lock are the same. The shapes differ (DEV-LCK-001):
- Ink/Stitch’s half stitch goes towards the first needle point at least 0.5 mm away, in a straight line to it, out and back by halves of that distance capped at 1.5 mm. StitchCraft’s goes forth and back over half the first stitch, which then covers it.
- Ink/Stitch’s back-and-forth lock goes two steps out and two back. StitchCraft’s goes forth and back over one step, twice, like its half stitch.
- The drawn shapes are StitchCraft’s own designs.
- Ink/Stitch draws custom SVG paths. StitchCraft sews the half stitch with
SC-W0503until M8.
Both follow the stitching with custom steps. They differ in 5 places (DEV-LCK-002):
- Ink/Stitch puts the needle points past the end of stitching shorter than the lock on its last needle point. StitchCraft goes straight on from the last stitch, and no two needle points coincide.
- Ink/Stitch follows a turn however sharp. StitchCraft sews straight a lock that a turn would fold onto itself.
- Ink/Stitch sews a step shorter than 0.2 mm as it is. StitchCraft lengthens it to 0.2 mm.
- Ink/Stitch sews a step of 0 as a stitch in place. StitchCraft leaves it out.
- StitchCraft leaves out a step longer than 10 m, which Ink/Stitch would sew.
Properties
The conformance suite checks, through the engine’s public API (crates/stitchcraft-engine/tests/locks.rs):
- Each id sews a lock that joins the stitching where it starts or ends (
REQ-LCK-002). - The shortest lock stitch is 0.2 mm, joins included, for any group, lock and size. The same input
sews the same lock (
REQ-LCK-002, property test). - Sizes: the drawn locks scale with
lock_*_scale_percentalone, and the locks made of steps withlock_*_scale_mmalone. The half stitch scales with neither (REQ-LCK-002). - Custom steps sew as Ink/Stitch reads them, round the stitching’s corners (
REQ-LCK-004).
Plan assembly (M3.8) checks where locks go (ties, REQ-LCK-001). Machine checkpoint MC-2 checks with test
sheet TS-04 whether the default locks hold when the tail is pulled (REQ-LCK-003).
Centre lines
A narrow part of a shape sews along its middle. Automatic digitizing sews a part narrower than the
narrowest satin column as a running stitch along it (M13.4), and from M13.5 a wider one as satin columns
between its own outlines. Which parts are narrow, and how they sew, is on the
image to embroidery page. This page says how a part’s
centre line is found (REQ-AUTO-014).
The part is cut into triangles by a constrained Delaunay triangulation: every edge of its outline and holes
is an edge of a triangle, and no triangle’s circle holds a corner that the triangle can see. The edges that
2 triangles share cross the part, and their middles follow its middle (stitchcraft_engine::normalize’s
triangulation and strips).
Points along the edges
A shared edge crosses the part only where its triangles are about as long as the part is wide, so long edges, such as the sides of a straight line, are cut first. Each edge is cut into the fewest pieces, all as long as each other, that are no longer than the part is wide on average: twice its area over its perimeter, and never less than 0.1 mm, which bounds the points by the perimeter.
Every point goes on the grid that a fill’s region is built on, 2^-35 mm (fills). The region’s own points lie on it already. On the grid every difference of 2 coordinates is an exact float. The exact predicates below then take one product for each term of their sums.
The triangulation
- Points. A box well round the part, its corners on whole millimetres, is cut into 2 triangles, and the points go in one at a time, ring by ring. A point inside a triangle splits it into 3, and a point on an edge splits the 2 triangles beside the edge into 4. A point already in the mesh, such as a corner where a hole touches the outline, is that point. The edges round a new point are then flipped until each passes the Delaunay test: the corner across the edge lies outside the circle through the triangle’s corners (Lawson 1977). The triangle that a point lies in is found by walking towards it from the triangle where the last point was found, a step or 2 in ring order.
- Edges of the part. An edge of the part that no triangle has yet is made by flipping the edges it crosses, until none does (Sloan 1993). An edge whose 2 triangles make a quadrilateral that isn’t convex waits its turn again. A point on the edge between its ends cuts it into 2 edges. The part’s edges are never flipped after that.
- Inside. The triangles round the box’s corners lie outside the part, and crossing an edge of the part goes in or out. The triangles inside it are then found by walking out from the box.
- Delaunay again. The edges between triangles inside the part are flipped until each passes the test. Where 4 or more points lie on one circle with the other points outside it, as a square’s corners do, more than one cut of them into triangles passes the test, and a tie-break chooses one. Each point’s lift, its squared distance from the origin, counts as raised by a vanishing amount of its own, the least point’s by x and then y the most (a symbolic perturbation, after Edelsbrunner and Mücke 1990). No 4 points then lie on one circle, and there is one constrained Delaunay triangulation, which flipping reaches from any triangulation of a polygon. The triangles are then the same in whatever order the points went in.
Each question of where a point lies, on which side of a line or inside which circle, is decided exactly
(stitchcraft_core::exact, after Shewchuk 1997). Flipping then stops and every walk ends, and a part gives
the same triangles on every platform. Each step costs a unit of work. Were 2 edges of the part to cross,
the mesh would lose its shape, which is a bug. Automatic digitizing then fills the part and says so with
SC-E0009.
Strips, junctions and ends
Where 2 triangles inside the part share an edge, the edge is a chord. A triangle is part of a strip when it has 2 chords, a junction, where 3 strips meet, when it has 3, and an end when it has 1.
- Ends. The circle on a chord is the one whose diameter the chord is, its edge included. A round end lies within the circle on its chord, and so does a square one: such an end is a cap, which the line doesn’t enter. Each cap grows across the chords while the circle on each contains every corner of the cap. It grows through each strip triangle, and through a junction once caps reach 2 of its sides, across its third chord, the 2 caps growing on as one. The line ends at the middle of the last chord a cap grew across, or at the middle of a junction whose other sides are caps. Where an end’s chord doesn’t contain the end’s own corner, as at a sharp point, the end is a tip, and the line runs on to the corner. Igarashi, Matsuoka and Tanaka’s Teddy (1999) trims the ends of a shape’s spine the same way.
- Round ends. The points round a round end lie on a circle, or nearly, and any triangles between them pass the Delaunay test. Many of those triangles are junctions, and the caps of the end’s corners come to 2 sides of each. Were a cap to stop at a junction, the line would bend through the junctions to the edge. The caps grow through them instead, to where the end meets the rest of the part, and the line ends there, on the part’s middle.
- The line. The line joins the middles of the chords outside the caps. It crosses each strip triangle from chord to chord, and runs to each of a junction’s chords outside the caps from the junction’s middle, the average of its corners.
- Bumps. A traced outline has small bumps, and a sharp turn has a corner beyond where the middles of its strips meet. Each gives a junction and a short branch to an end. A branch from a junction to an end that is no longer than twice the junction triangle’s longest edge is left out, the shortest first, until no junction has one. A junction left with 2 branches is a bend of the line.
- Junctions. At each junction left, the 2 branches that run on most nearly straight are one line, and the third ends there. A branch’s direction runs from the junction’s middle to the first of its points that lies at least as far away as the junction triangle’s longest edge, or to its end if none does. The 2 whose directions are most nearly opposite run on.
- Rings. A part that is a ring of strips, such as a letter O, has a closed line.
- Each piece once. A line that would run on along a piece of the centre line that another line has sewn ends there instead, so no piece is sewn twice. At the waist of a figure 8, each junction may pair the waist with a different loop. The line round one loop then runs on through the waist and closes, and the line round the other loop ends where it comes back to the waist.
- Order. The lines sew one after another, the longest first. The next one is the line with an end nearest where the last one ended, sewn from that end. A closed line starts at its point nearest.
Dots have no line, since the caps of their ends meet.
Width along the line
A part’s width at a point of its centre line is the diameter of the largest circle centred there that fits inside the part: twice the distance from the point to the nearest edge of its outline and holes. Automatic digitizing averages it along the line, and a part from 0.5 mm wide is a triple run. The edges are put in the cells of a grid as fine as the pieces the part was cut into. A point’s nearest edge is looked for in the cells round it, ring by ring, until no edge farther out could be nearer. A narrow part’s edges lie a cell or so from its middle. Each point then costs a few units of work.
Sources
- C. L. Lawson, Software for C¹ surface interpolation, in Mathematical Software III (1977), for flipping edges until each passes the Delaunay test.
- S. W. Sloan, A fast algorithm for generating constrained Delaunay triangulations, Computers & Structures 47 (1993), for making a missing edge by flipping the edges it crosses.
- J. R. Shewchuk, Adaptive Precision Floating-Point Arithmetic and Fast Robust Geometric Predicates (1997), for the exact predicates.
- H. Edelsbrunner and E. P. Mücke, Simulation of Simplicity, ACM Transactions on Graphics 9 (1990), for breaking ties between points on one circle by perturbing them symbolically.
- T. Igarashi, S. Matsuoka and H. Tanaka’s Teddy, a sketching interface for 3D freeform design (SIGGRAPH 1999), for the circles that trim a spine’s ends.
Image to embroidery
Automatic digitizing makes a design that sews well from a picture of flat colours, such as a logo, clip art or a drawing, once it is traced into shapes. Settings on the shapes are optional. StitchCraft chooses the stitches of each shape by its width at the size it sews. Neighbouring shapes overlap where they meet. Specks and the background are left out, and the sewing is ordered for few colour changes and short jumps. Milestone M13 of the roadmap builds it, after M7.5 and before M7.6. Photographs sewn in blended colours need another technique, outside the roadmap.
The flow
A picture becomes a machine file in 2 steps:
- In VectorCraft, place the picture, scale it to the size it will sew, and trace it with Object › Image Trace › Make.
- Run
stitch export picture.vectorcraft --auto -o picture.pes --preview picture.png.
An Image Trace object is a group of the traced paths, with the source image hidden beside them
(imageTrace.make in VectorCraft’s crates/engine, main at 0842941). stitch export already reads
those paths, and Expand is optional. Without --auto it sews each of them as Ink/Stitch sews an SVG
shape: every area as a tatami fill and every line as a running stitch, in paint order. In a test document
of a traced square and a strip 2.1 mm wide beside it, both sewed as tatami fills. With --auto, each
shape sews as this page describes. Once MC-8 confirms the rules on the machine, M13.8 makes automatic
digitizing the default for VectorCraft documents, and --auto is then needed for SVG files only.
VectorCraft’s exporter plug-ins (P1, 0.14) move the second
step into VectorCraft, as File › Export › Export As › PES, which StitchCraft’s formats are exporters for since
M6.9. Their auto option is --auto.
An SVG file traced elsewhere, such as by Inkscape’s Trace Bitmap, sews the same way with
stitch plan picture.svg --auto. Without the switch, an SVG file’s shapes sew as Ink/Stitch sews them,
since the file may be one of Ink/Stitch’s.
What VectorCraft does and what StitchCraft does
The 2 programs split the work. VectorCraft’s Image Trace (its crates/trace) already does what a picture
needs before it is embroidered:
- Its colour modes reduce the picture to a few flat colours. Its Document Library palette traces with a swatch library’s colours exactly.
- Its Noise setting leaves out areas smaller than a number of pixels, and Ignore White leaves out white areas.
- Its Abutting method traces each colour’s areas as paths that share their edges with their neighbours.
- Create Strokes traces lines up to its Stroke width as stroked centre lines.
- The paths are smooth curves over the steps of the pixels.
StitchCraft links none of VectorCraft’s crates (integration), and a tracer of its own would repeat VectorCraft’s. StitchCraft does the embroidery. It chooses the stitches of each shape, the overlaps between shapes and the order they sew in.
stitch palette (M13.7) writes a machine profile’s thread palette as a GIMP palette file (.gpl).
VectorCraft loads that file as a swatch library (Window › Swatch Libraries › Other Library…), and Image
Trace’s Document Library palette then traces the picture in the threads’ colours.
Automatic elements
An automatic element is a shape whose stitches StitchCraft chooses. The readers make one of:
- each object of a VectorCraft document that has no StitchCraft effect, with
--auto; - each path inside an object or a group that has the StitchCraft Auto effect (M13.7), unless the path has a StitchCraft effect of its own;
- each SVG element without
inkstitch:settings, with--auto.
An element with settings is not digitized automatically, whether or not the switch is given: it sews as
its settings say, as far as its reader reads them. So a picture can be digitized automatically and one of
its shapes then set by hand. (The SVG reader reads no inkstitch: settings but ignore_object until M8,
and says so with SC-W0802, so such a shape sews with the defaults until then.) A shape’s fill gives its
area and its stroke gives its line, each with its thread, width, join and cap, as for any element. A trim
or a stop that an Ink/Stitch command asks for is not a setting of the stitches, and an automatic element
keeps it. An element sewn as several elements keeps it for the last of them.
The readers mark the element (Element::automatic), and they read the switch from Reading, which every
host takes. Before anything is generated, the engine turns each automatic element into ordinary elements
whose settings say what to sew (stitchcraft_engine::auto). The generators, the assembly and every host
then treat them as shapes set by hand. SC-I0901 counts the shapes digitized, by the stitches they sew
as. An automatic line sewn across its width follows the drawing, and the reader names what the drawing
draws across the line that its stitches don’t follow (REQ-AUTO-005). Of a VectorCraft document, that is
the line’s alignment inside or outside a closed path and its width profile (SC-W0807). Of an SVG file,
it is transforms that stretch the line more one way than the other (SC-W0802). Under such transforms a
viewer draws it wider one way, and its stitches are as wide as the transforms stretch it on average. Its caps are
followed (below), and so are its corners, joined as the drawing joins them. An SVG join that is not set
is SVG’s miter, limited by stroke-miterlimit, where Ink/Stitch reads a miter limited at 5.
Digitizing doesn’t add an error of its own (REQ-AUTO-006). A part that its width’s stitches cannot sew
is sewn otherwise, or left out with a warning, and a shape that paints nothing sews as it would without
the switch. One odd shape among hundreds never keeps a design from being written.
Stitch types by width
An automatic element is measured at the size it sews, in millimetres, to the micrometre (REQ-AUTO-002).
A width written in another unit that converts to a bound give or take a rounding error, such as 7 mm in
CSS pixels, is that bound. Its width at a place is the diameter of the largest circle that fits inside it
there. Its parts sew by their width:
| Width | Sewn as |
|---|---|
Up to the design’s min_satin_stroke_width_mm, 1 mm unless the design sets it | A running stitch along the centre line, a triple run (bean stitch) from 0.5 mm wide |
| Wider, up to the widest satin column, 7 mm | Satin columns, whose rails are the outline |
| Above 7 mm | A tatami fill |
A stroke is a line as wide as it is drawn (M13.1):
-
No wider than
min_satin_stroke_width_mmit sews as a running stitch along its path (REQ-AUTO-002), tripled from 0.5 mm wide (below). A satin column that narrow is too narrow to stitch across (REQ-SAT-015), so the bound is the same. -
Up to 7 mm it sews as a satin column as wide as its stroke, with its path as the column’s centre line, as a satin column drawn as one path does (
REQ-SAT-016,REQ-AUTO-003). A centre line is one subpath, so a stroke of several sews a column for each, in order. The second and later columns are named after the stroke with#2,#3and so on. A traced line’s ends are round or square, and its caps are part of the line drawn. An open end with a round or square cap reaches half the stroke’s width past the path’s end, straight on, as far as the cap does. A round cap’s end is then square, which covers its corners too. A column cannot follow a subpath that turns more tightly than the stroke is wide, such as a small ring traced from a dot. Nor can it follow a subpath shorter than a CSS pixel. Its rails would fold over themselves, or vanish. The rails the satin column would make tell whether it can follow a subpath. A subpath it cannot follow sews as the area it paints, filled as a wider line is (SC-I0902). So does a dot that a round or square cap draws. A dot with a butt cap draws nothing, and is left out (SC-W0401). -
Wider than that, the area it paints is filled (
REQ-AUTO-004). That area is each segment widened by half the stroke’s width to each side, with its corners joined as the stroke’s join joins them and its open ends capped with its cap. It is the stroke’s area within the shapes’ tolerance. A miter longer than its limit is cut square. Those pieces, joined into one area, are the fill’s outline. A part of it smaller than 1 mm² is a speck, merged into a neighbour or left out as specks are (Neighbours). An area that would reach more than 10 m from the origin is left out (SC-W0903). No design lies there.The pieces are not handed to the area code one by one, which would cost many times the work of their outline. Each open subpath is one ring instead. It follows the right side forward, the end cap, the left side back and the start cap. The outer side goes round each join, and the inner side goes in to the path’s point and out again. A closed subpath is 2 rings, one for each side. Such rings wind round each point as many times as the pieces cover it. The nonzero rule then fills exactly the pieces’ union. A line of more than 32 chords is cut into lengths of 32 chords. Each length is a ring made into its area alone, and the areas are then joined. A 10 mm line a metre long fills within the default budget that way.
Triple runs (M13.4, REQ-AUTO-015). A running stitch that automatic digitizing makes is a triple run
from 0.5 mm wide: each stitch is sewn 3 times, bean stitch 1. A running stitch sewn once is as fine as the
thread, and a line drawn half a millimetre wide or wider sews bolder tripled. A stroke is as wide as it is
drawn, and a narrow part (below) as wide as the circles that fit inside it along the middle. Widths are
compared to the micrometre, and MC-8 confirms or changes the bound.
Until M13.5 a satin column has a satin column’s defaults, and M13.5 gives columns their underlay by width, strokes’ columns as well as shapes’. A shape’s area is a tatami fill apart from its narrow parts (M13.4), and the rules below for its parts as wide as a satin column follow in M13.5 and M13.6.
Narrow parts (M13.4, REQ-AUTO-013). A part of a shape that is narrower than
min_satin_stroke_width_mm everywhere sews as a running stitch along its centre line, as a stroke that
narrow does. No circle that wide fits inside such a part: shrunk by half the width with the polygon buffer
of M5.7, nothing of it is left. The width is taken half a micrometre wider, so that a part as wide as the
bound, give or take a rounding error, is narrow, as a stroke is. Before that, a part smaller than 1 mm² is a
speck (Neighbours), and stays in the area. So does a part whose centre line has no length, a
dot.
- What sews. The rest of the shape is a tatami fill, and each narrow part’s line sews after it, in the
order of the parts. It is named after the shape with
#2,#3and so on, or with the shape’s own name when nothing of it is filled. A trim or a stop after the shape follows its last part. - Its width. A part’s width at a point of its centre line is the diameter of the largest circle centred there that fits inside it. Averaged along the line, it says whether the part is a triple run (centre lines). A strip as wide all along as a stroke is as wide as the stroke.
- To its neighbours a narrow part’s line paints the part. It sews after a fill it borders, and the areas sewn before it reach under it as under a satin column (Neighbours).
- As drawn. A shape’s narrow parts are found as it is drawn, before its neighbours. A part that only the areas sewn after it leave narrow is cut away round them and filled with the rest of its shape. A stacked trace’s dark outline under the colours inside it is often such a part. Whether to find narrow parts after the cut too is decided with M13.6, which splits shapes into parts.
SC-I0901counts a shape whose every part runs as a running stitch, and any other as a tatami fill. Finding a part’s width and centre line is part of the work of digitizing its shape. A part whose width or centre line can’t be found, a bug, stays in the fill, andSC-E0009names its shape.
Shapes in parts. A shape wider than 7 mm in places and narrower in others is split. Its broad parts are what an opening with a disc 7 mm across leaves: the shape shrunk by 3.5 mm and grown back by 3.5 mm, with the polygon buffer of M5.7. The rest of the shape is its narrow parts. A narrow part shorter along its centre line than twice its width is a corner that the opening rounded, and it goes back to the fill beside it.
Centre lines. A narrow part’s centre line, its width along it and the rungs across it come from a constrained Delaunay triangulation of its outline, with points added along its long edges so that no triangle is much longer than the part is wide:
- Each edge of a triangle that lies inside the part is a chord across it, as long as the part is wide there.
- A triangle with 2 inside edges is part of a strip, and its inside edges are the strip’s rungs.
- A triangle with 3 inside edges is a junction, where 3 strips meet, and one with 1 is an end.
The centre line joins the middles of the chords (M13.4, REQ-AUTO-014). A round end stops it about half
its width from the end, on its middle, and a sharp point draws it on to the point. The small bumps of a traced outline, and
the corner past a sharp turn, would branch it, and are left out. At each junction left, the 2 branches that
run on most nearly straight are one line and the third ends there, and a ring has a closed line. No piece
of the line is sewn twice. A part’s lines sew one after another, the longest first, each from its end
nearest where the last one ended. The centre lines page has the rules in
full.
A strip between 2 ends or junctions sews as a satin column (M13.5). Its rails are the 2 sides of the outline between them, and some of its chords are its rungs. A junction joins the 2 columns that run on most nearly straight, and the third ends under them. A ring, such as a letter O, is a closed column between its outer and inner outlines. The satin generator sews these columns as it sews columns drawn with rails and rungs.
Settings by width. A column’s underlay depends on its width. These are starting values, and MC-8 confirms or changes them:
| Column width | Underlay |
|---|---|
| Below 2 mm | Centre walk |
| 2 to 4 mm | Contour |
| Above 4 mm | Contour and zigzag |
Fills keep the tatami fill’s defaults, underlay included.
Neighbours
The shapes of a traced picture meet. VectorCraft’s Abutting method traces each area with a hole where the
next one lies, and its Overlapping method, like Inkscape’s Trace Bitmap with its Stack option, traces each
colour whole, over the colours under it. Sewn as drawn, either sews badly where shapes meet, and digitizing
mends it in 3 steps, in this order (stitchcraft_engine::auto::neighbours, M13.2).
- Background (
REQ-AUTO-009). The lowest shape is the picture’s background when it is an automatic area with other shapes drawn on it, and its outlines, its holes aside, cover the box around the design to within 0.5 mm of each side. The box is the one around the defining points of every shape’s path, the background’s own included. So a disc in the corner of a square, lowest but covering less of the box, is part of the picture. The background is left out withSC-W0906before anything is digitized. With a StitchCraft effect, it sews as that effect says. Finding it has the work budget to itself, and a background that needs more is sewn, whichSC-W0906says. - Specks (
REQ-AUTO-008). An automatic area, or a part of one, smaller than 1 mm² is too small to sew: it would sew a knot. It is merged into the automatic area it shares the longest edge with, which may be its own. A speck that fills a hole goes into the area around it, and no hole shows where it was. One that rounding left a hair from the rest of its own area rejoins that. On a tie, it goes into the area drawn first. The edge 2 shapes share is how much of the area’s rings lies within twice the shapes’ tolerance of the speck. A speck that doesn’t share an edge with an automatic area is left out, and so is one whose neighbours need more work to find than the budget allows it: put back as drawn, it could overlap a speck merged in beside it.SC-I0905names each speck, by its area rounded to a hundredth of a square millimetre and kept under 1 mm², and where it went. A line filled because it is too wide for a column (M13.1) is an area too, so a part of it that small is a speck. - Overlaps (
REQ-AUTO-007). Pull draws stitches in from the edges of their shape, so shapes that only meet show fabric between them. Each automatic area reaches 0.4 mm under each area, satin column, filled line or narrow part’s line sewn after it, from where it touches that shape, and no further than that shape. Shapes touch where their edges meet within twice the shapes’ tolerance, so a gap that rounding left between edges drawn as one closes too. Along an edge where a shape runs on near the area without touching it, the area doesn’t reach, and fabric shows between them as drawn. A satin column covers the area between its rails as they are sewn, turned to run the same way. Where an area sewn after it covers part of it, it is cut away under that area, 0.4 mm inside the area’s edge, and that part is sewn once: a stacked trace then sews the areas of an abutting one. A part that the areas sewn after it cover whole sews nothing, however near their edge it lies. A narrow part’s line covers its part, and isn’t cut away. It sews nothing when the areas sewn after it cover the part whole, and sews whole when they cover the part in places. A column or a line sewn after an area goes over it, and the area is sewn whole under it. Cutting a fill round small letters adds stitches and can leave gaps (Inkscape forum).SC-I0904counts the areas that reach under others and those cut away. It also names each area cut away whole, which sews nothing. The areas are named in the order they are sewn.
Only automatic areas change. A shape with settings is sewn as they say, and a satin column or a line
digitized from a stroke keeps its stitches: whether a column should reach under the fills beside it is
M13.5’s, which makes columns from shapes. A fill that bounds no area is left to its fill, which says so.
The areas are taken from the last sewn to the first, so that each meets the shapes after it as they are
sewn. Each area has the work budget to itself for its specks and for its overlaps, and costs the work of the
shapes near it and a unit for each other shape it looks past, not the work of the whole design: what a
shape sewn after it paints is found once, with the work budget to itself. An area whose overlaps need more
keeps the region it has, and a speck that needs more is left out. SC-I0904 and SC-I0905 say so. A satin
column whose area needs more is not reached under, which SC-I0904 says too. The regions are combined by
one function, which cuts the rings of both where they meet and keeps the faces by their winding numbers
(normalize::region::ops).
Measured. The test pictures (Testing) are sewn as drawn, each shape a tatami fill, and
digitized, and the edges where their shapes meet are measured with the thread pulled in by 0.4 mm, the
pull the overlap is there to make up for: the share of the edges that the top stitches’ thread covers
once each is pulled in where the thread turns (edge coverage, stitchcraft_testkit::metrics).
| Test picture | As drawn | Digitized | Stitches as drawn | Stitches digitized |
|---|---|---|---|---|
| Stripes, 3 abutting | 98.9 % | 100 % | 2,994 | 3,221 |
| Badge, abutting | 89.1 % | 98.7 % | 3,761 | 3,773 |
| Badge, stacked | 100 % | 97.2 % | 3,981 | 3,764 |
| Badges in a row, abutting | 94.2 % | 99.8 % | 4,947 | 4,918 |
| Badges in a row, stacked | 100 % | 99.9 % | 4,872 | 4,934 |
The abutting badge’s shapes, sewn as drawn, leave a tenth of their edges bare. Digitized, they overlap, and
98.7 % is covered. The stripes’ edges are covered as drawn too, but by the running stitches the fills travel
on along them, under their rows, not by the rows: digitized, the rows themselves overlap. The stacked badge as
drawn sews its lower colours whole under the upper ones, which covers its edges with 217 more stitches,
6 %, sewn twice. Digitized, it sews the abutting badge’s areas, within 9 stitches. The row of badges, each
the badge at three fifths of its size and sewn in the order below, covers its edges as the badge does, and
stacked it sews within 16 stitches of the abutting row. Without pull, every edge is covered as drawn and
digitized alike, to within a hundredth. tests/auto_pictures.rs keeps the numbers
from falling back, and MC-8 tells whether 0.4 mm is the pull on the machine.
Sewing order
Automatic elements sew in an order StitchCraft chooses (stitchcraft_engine::auto::neighbours::order,
M13.3). The order is chosen after specks are merged, so that a speck merged into an area is no element of
its own, and before areas reach under their neighbours, which follows it. VectorCraft’s Auto Grouping
draws each shape together with the shapes inside it. Sewn in paint order, a picture traced with it changes
thread at nearly every shape, and each change is a stop to rethread a home machine. The order comes from 4
rules.
- What overlaps keeps its paint order (
REQ-AUTO-010). Of 2 shapes whose paint overlaps, the lower sews first, or it would cover the upper one. An area paints its region, and a satin column the area between its rails as they are sewn. Lines paint the areas their strokes paint as drawn, and a narrow part’s line the part. Shapes that only meet, as abutting paths do, may sew in either order. Rounding leaves a sliver no wider than twice the shapes’ tolerance between edges drawn as one, and shapes overlap only where their overlap is wider. Where 2 lines 0.1 mm wide cross, they overlap. - A line after the fill it borders. A satin column or a line that touches a fill without overlapping it sews after the fill and covers its edge, and the fill reaches under a column (Neighbours). Rule 1 comes first. A line that rule 1 already sews before the fill, through the shapes it overlaps, keeps its place.
- Few thread changes. The next element has the thread that is sewing, as long as an element of that
thread is free to sew: every element that must sew before it has. Otherwise the thread changes to that
of the first free element in paint order. Threads are the same when their colours are, as plan assembly
counts thread changes. A PES file gives different colours different palette entries while they last
(
REQ-THREAD-002), so 2 colours are one thread to the machine only past the palette’s 64 entries. - The nearest next. Of the free elements of that thread, the next is the one whose box lies nearest
the box of the element sewn last. On a tie it is the one whose box’s centre lies nearest, and then the
one drawn first. Each element then starts and ends at its points nearest its neighbours
(
REQ-GEN-003).
Elements with settings keep their order among themselves, and automatic elements move round them within
the rules. The parts of a shape sewn as several elements, such as a line’s satin columns, keep their order
too, so that a trim after the shape still comes after its last part. --sort-colours lets the elements
with settings move as well (REQ-AUTO-011), whether or not --auto is given. It is for a design drawn in
an order that suits drawing it rather than sewing it. The switch is the command line’s. VectorCraft’s
exporters order the automatic elements when their auto option is on, and sew the elements with settings
as drawn.
An element with a stop after it keeps its place: the elements drawn before it sew before it, and those
drawn after it after it. A user sets a stop for a reason, such as placing appliqué fabric, and it comes
where it was drawn. SC-I0907 says when the order isn’t paint order, with the thread changes of each.
M10’s routing tools, which order satin columns and running stitches drawn by hand, start from this order.
Shapes are near when their boxes come within twice the shapes’ tolerance of each other. An automatic area’s box is round its region, and a narrow part’s line’s round the part. Any other shape’s box is round its path’s points, grown for a stroke by half its width times √(L² + 1) for a miter limit L. That holds the corners of a miter clipped at its limit, as a satin column’s rails clip it. Other strokes grow by half their width times √2, which holds a square cap’s corners. Rule 4 measures with the same boxes. The order finds a shape’s paint only when the shape is near an element that moves.
Work (REQ-AUTO-012). Each element has the work budget to itself to find how it lies beside the
shapes drawn after it. It costs a unit for each of them, and the work of those near it. One that needs
more keeps its place in paint order as an element with a stop after it does: nothing moves across it. A
shape whose paint can’t be found within the budget keeps its place beside the shapes near it. Choosing the
order has the budget once more, and costs a unit for each free element looked at each time an element is
placed. An order that needs more sews the elements not placed by then in paint order, after the others.
SC-I0907 says each. SC-I0904 says it of a satin column instead, whose area the areas sewn before it
then don’t reach under either. A fill’s generator needs the work of its region too, and says it of a fill
(SC-E0004).
Measured. The test pictures, sewn as drawn and digitized as Neighbours sews them.
| Test picture | Thread changes as drawn | Digitized | Jumps as drawn | Digitized |
|---|---|---|---|---|
| Stripes, 3 abutting | 2 | 2 | 3 | 3 |
| Badge, abutting | 2 | 2 | 3 | 3 |
| Badge, stacked | 2 | 2 | 3 | 3 |
| Badges in a row, abutting | 8 | 2 | 9 | 7 |
| Badges in a row, stacked | 8 | 2 | 9 | 7 |
The stripes and the badge on its own have one shape of each colour, which no order sews with fewer
changes, and digitizing keeps their order. The row of 3 badges, each 30 mm across, is drawn badge by badge,
as Auto Grouping draws it. It changes thread 8 times as drawn. Digitized, each thread sews its 3 shapes one
after another, and the row changes thread twice. Its red discs touch, and the needle sews on from one to
the next without a jump. The row jumps 7 times instead of 9. tests/auto_pictures.rs keeps the numbers
from falling back.
Settings
The rules’ values become StitchCraft’s own parameters, registered as any other
(parameter registry), when the Auto effect’s dialog lets a user set them (M13.7). The settings
are the widest satin column, the narrowest triple run, the overlap, the smallest area and a switch that
leaves the background out. Their defaults are the values on this page, 7 mm, 0.5 mm, 0.4 mm and 1 mm², and
MC-8 confirms or changes them. Until then nothing can set them, and the rules use those values
(stitchcraft_engine::auto::SATIN_MAX_WIDTH since M13.1, OVERLAP and SMALLEST_AREA since M13.2, and
TRIPLE_RUN_WIDTH since M13.4). The narrowest satin column is the design’s min_satin_stroke_width_mm.
With --auto the settings take their defaults.
Previews in VectorCraft
The Auto effect is a live effect like the plug-ins of each stitch family
(plug-ins). VectorCraft runs a group’s effects on each path inside
it (REQ-VC-006), so the effect on a traced group previews every path’s stitches, each sewn as its width
says. A live effect is given its path alone. Its preview shows each shape’s stitches without the
overlaps, the merged specks or the order, which stitch export adds. A preview that costs more than ABI
v1’s budget shows less, with SC-I0810, as the other effects’ previews do.
Tracing again replaces the group with a new one, without the effect. Apply the effect after the last
trace, or use --auto, which works without it.
With VectorCraft’s newer plug-in features
- Exporters (0.14, P1). File › Export › Export As › PES
plans the document inside VectorCraft, and its
autooption digitizes the objects without effects (M6.9). The flow is then trace and export, both in VectorCraft. - Effects in filter objects (0.15, P8). A filter could set on each path the effect of the family its width chose. The user then adjusts it in that effect’s dialog.
- Budget classes (P4, still proposed for live effects). The Auto effect’s previews of large shapes would get the heavy budget.
Text in pictures
Text traced from a picture is shapes, and the width rules sew its letters’ strokes as satin columns and running stitches. TS-13 sews block letters from 4 to 15 mm tall to find the smallest that reads well. Smaller letters need lettering in an embroidery font, made to sew at small sizes. M14.5 replaces the traced letters inside a marked area with lettering of the marker’s text, as tall as they were. Spike M14.6 looks for a way to find and read the text without a marker.
Pictures from the command line
stitch plan picture.png --auto would leave VectorCraft out of the flow. Spike M13.9 chooses how it
traces:
- VectorCraft’s command line, when it is installed, opens the picture and traces it
(
vectorcraft-cli run --in picture.png --cmd imageTrace.make …). This repeats no code. - StitchCraft reads PNG and JPEG files and traces them itself. This works without VectorCraft.
Testing
- Each rule gets its requirements and failing cases before its code (ADR-0008).
- The test pictures are drawn in code, as the test sheets are, and no logo or picture of anyone else’s is used.
- Each test picture’s automatic plan is compared with its plan in paint order: colour changes, jumps,
trims, stitches, and how much of each shape and of each shared edge the stitches cover
(
stitchcraft_testkit::metrics). - Level B of the compatibility gate gets a case in which VectorCraft’s command line traces a picture and StitchCraft sews the document it saved, on every track.
- TS-13 sews the test pictures at MC-8 (machine testing).
Codes 0900 to 0999 are automatic digitizing’s (diagnostics).
Machine formats
stitchcraft-formats encodes a StitchPlan into machine files and decodes machine files into plans.
PES (Brother) and DST (Tajima) come first because the first target machine is a Brother and DST is the
lingua franca every machine and digitizer reads.
Principles
- Quantize once. Absolute positions are rounded to 0.1 mm (round half to even) at the start of encoding; deltas are differences of quantized positions, so error never accumulates (determinism).
- Format limits live in the encoder. Each writer knows its maximum displacement per record and splits long moves into several jump records; the engine never needs to know.
- Commands are explicit. Each writer documents how it expresses
Jump,Trim,Stop,ColorChangeandEnd, and conformance checks every one of them per format (REQ-FMT-003). - Readers are hostile-input parsers. Every length, count and offset is checked before use; total
stitches are capped (default 2,000,000); allocation is proportional to verified sizes; the reader
returns the plan or a typed error, never a panic (fuzzed,
NFR-SEC-1). - Never write an empty file (
SC-E0010), and accept empty or zero-stitch files when reading (REQ-FMT-004). - Facts are cited. Format knowledge comes from public descriptions and MIT-licensed pyembroidery /
pystitch as a reference implementation; where we rely on them, the source is noted in the module and
in
NOTICE.
PES and PEC
PES is Brother’s design format. A PES file is a PES header (and, for newer versions, design-editor data) followed by a PEC block, which is what the machine actually sews from.
What we write (v1)
| Part | Content |
|---|---|
| Signature | #PES0001 |
| PEC offset | 32-bit little-endian offset of the PEC block: 22 |
| PES section | A hoop indicator (0 = 100 × 100 mm, 1 = 130 × 180 mm; we write 1 for designs larger than 100 × 100 mm) and no design-editor objects. Machines sew from the PEC block; the section is for PE-Design. |
| PEC header | LA: + label (16 characters, padded) + carriage return; thumbnail size; colour entries − 1; one Brother palette index per colour entry (each block, and each stop); padding to 512 bytes |
| PEC graphics header | Bytes to the thumbnails, the design’s width and height in 0.1 mm, and the start as seen from the design’s top-left corner. That field holds −204.8 to 204.7 mm on each axis, and a design whose corner lies farther from the start is refused (REQ-FMT-011, SC-E0602) |
| PEC stitches | Relative moves in 0.1 mm (see below) |
| Thumbnails | 48 × 38 one-bit pictures with a rounded frame, of the whole design and then of each colour entry, at one scale so that they line up. No thread is drawn before the needle first goes down. Stitches stay inside the frame, and the byte after the stitch data’s end mark is 00 (REQ-FMT-012) |
The byte-level layout is documented in the writer modules (crates/stitchcraft-formats/src/pes/), next
to the code that writes it, with the spec vectors that test it.
Stitch encoding. A move is encoded per axis: a short form (one byte, 7-bit two’s complement) for small
displacements, and a long form (two bytes, 12-bit two’s complement with flags) for larger ones up to ±2047
units (204.7 mm). Each axis is short or long on its own; we use the short form for −63…62. Long-form flags
mark the move as a jump (0x10) or a trim (0x20). A colour change is 0xFE 0xB0 followed by a
byte that alternates between 0x02 and 0x01, starting with 0x02; the end is 0xFF. PEC has no separate
stop command: a stop is a colour change to the same thread, which readers recognise as a stop.
Trims ride on the next move. PEC cannot cut in place, so a trim flags the next jump; a sewn move after a trim becomes a trim-flagged jump to its target and a zero-length stitch there, so the needle still goes down where the plan says. A trim right before the end needs no record.
Jumps are trim-flagged, as pystitch writes them (REQ-FMT-010). A machine treats a StitchCraft file
as it treats one from Ink/Stitch. Only the stitch data’s first record, the move from the origin,
keeps the plain jump flag. StitchCraft first flagged only the jumps that follow a Trim in the plan and
wrote the others as plain jumps, so the plan decided. Whether a trim-flagged jump is cut is the machine’s
choice. At machine checkpoint MC-1 the reference PE800 cut no jump of either flag and sewed test sheet
TS-02 the same with both, and it cut the thread only at thread changes and stops. Machines that trim
jumps cut those longer than their setting. A jump that goes nowhere has no record, since a record of it
would be trim-flagged.
The pieces of a sewn move longer than 204.7 mm keep the plain flag, because a cut there would cut the
stitch’s own thread. No profile allows a stitch that long.
Checked against a reference. For the canonical plans, whose threads are palette colours, every PEC
header byte equals pystitch’s output for the same stitches, the palette indices and the origin fields
among them. A pinned pyembroidery reads our files to the same needle-downs, cuts and pauses as
StitchCraft’s own reader (REQ-FMT-005, run as a pinned CI job from M2.4). Threads of other colours can get
other entries than pystitch gives them (DEV-THREAD-001).
Colours (REQ-THREAD-002). The PEC header stores palette indices, not RGB, and a machine knows
threads only by them. Threads written as one entry are one thread to it, shown in one colour, and a change
between them reads as a stop. So the threads of a design get their entries together. A thread read from a
PES file is an entry, by its colour and name, and is written back as it, Brother’s applique steps 62 to 64
included. The other threads are given the 61 thread colours, one each while they last, the nearest pairs
first by CIEDE2000. A thread gets its nearest entry unless a thread nearer to it took it. Equal threads
share one. With more different threads than that, the rest share their nearest entries, and SC-W0606
says how many. stitch plan, convert and inspect name each thread by the entry the file has.
Catalogue numbers from other brands cannot survive PES v1. A PES v6 writer (below) could store a thread
list with RGB and codes.
Risks found in the field, and how we test them
Embroiderers report these with files from other software:
- Trims ignored on some Brother machines. Test sheet TS-02 sews jumps with and without a trim before them. At MC-1 the PE800 cut none of them: on that machine the operator cuts the jump threads. The MC-1 sew-out report sets the profile’s trim support.
- Large designs hidden by an older Brother-family machine for PES v1 files beyond about 130 × 180 mm (root cause unknown). The PES v1 section carries a hoop indication, and pystitch sets it to the 130 × 180 mm class, which is our unconfirmed hypothesis for that report. The reference PE800’s largest field is that class, 130 × 180 mm. MC-1 sews TS-10 at the whole field of each of its hoops from a PES v1 file. If one of them does not load, a PES v6 writer with explicit hoop dimensions follows, and the test is repeated.
- Colour expectations: documented behaviour — what you see on the machine is the PEC palette colour; the thread chart in the report lists the source colour and the PEC match.
Later versions
A PES v6 writer (#PES0060) is planned behind the profile’s format field, only if a machine
checkpoint needs it (hoop dimensions, thread lists). Its header contains fields whose meaning is only
partly known publicly; every field we write is either understood or copied from the v1 values that
machines accept, and covered by a golden file.
DST (Tajima)
| Part | Content |
|---|---|
| Header | 512 bytes of ASCII fields — LA: label, ST: stitch count, CO: colour changes, +X, -X, +Y, -Y extents, AX, AY, MX, MY, PD — padded with spaces and terminated by 0x1A |
| Records | 3 bytes each; x and y displacements in balanced-ternary bit patterns up to ±121 units (12.1 mm) per record |
| Axis | y is up: positive y in the plan (down) is written negative |
The third byte always has bits 0 and 1 set; bit 7 marks a jump (0x83 with no other bits); 0xC3
is a colour change (and, by common convention, a stop); 0xF3 with zero displacement ends the design.
DST has no trim command. Machines count jump records in a row, and when there are as many as their
setting, they cut the thread before the jumps — if something was sewn since it was last cut or changed
(REQ-FMT-008). The setting is commonly three (it can be as high as five; Brother’s PR machines take 1 to
8, matched by PE-DESIGN’s “number of jumps for trim”), and three is how pyembroidery reads DST too. So:
- a trim is three small jumps that cancel out — (+2, −2), (−4, +4), (+2, −2) units, the sequence pystitch writes — so the frame goes nowhere while the machine counts three;
- a jump longer than 24.2 mm takes three or more records, so it is a trim too, whether the plan asks
for one or not. The writer says so:
SC-I0605counts the places where its machines will cut the thread and the plan does not trim. With a tie-off before the jump the cut is usually welcome — there is no jump thread to clip; without one (an element whosetiesare off), the stitching may unravel.
A machine set to another count cuts before other jumps, and does not cut at a three-jump trim if set higher. A machine profile that writes DST records its count, validated on that machine.
Moves longer than 12.1 mm are split evenly into several records by the encoder: all jumps for a jump, and
jumps then the final stitch for a sewn move (the thread lies the same way: the needle only goes down at the
end). Header extents are measured in DST axes (y up) over every position the frame visits; ST counts the
records before the end record.
Reading
stitch inspect and the round-trip tests read machine files back. Readers treat every file as possibly
damaged or hostile: every offset and length is checked before use, the record count is capped at the stitch
budget (2,000,000), positions must stay within ±10 m, and a problem is a typed error (SC-E0603) — never a
panic and never a silent guess. Readers are literal: each record becomes one plan entry, so inspect shows
what is in the file. Unusual but readable things (a thread index outside the palette, a thumbnail offset
that points nowhere) become warnings in the result. stitch inspect gives lengths to a hundredth of a
millimetre and counts a stitch that does not move on its own. A file does not mark its lock stitches, so of
the stitches shorter than a profile’s shortest it also counts those 0.2 mm or longer, as a lock stitch may
be. stitch plan, testsheet and convert read the file they wrote back the same way, and give its counts
and size (REQ-CLI-008).
| Reader | Reads | What it has to infer |
|---|---|---|
| PES / PEC | the PEC block of any PES version (#PES0001 … #PES0060), and bare #PEC0001 files; the block must start with LA: | A colour change to the same palette entry is a stop, as PEC writes stops. StitchCraft’s writer gives different threads different entries, so only threads beyond the palette’s 61 colours read back as a stop. Whether the stitch data starts with the origin field (below). |
| DST | the header’s label and every record | Three or more jumps in a row are a trim before them where something was sewn since the thread was last cut or changed, as DST machines read them (REQ-FMT-008); a trim’s own spelling at the start of the run — up to 8 jumps of at most 1 mm that end where they started — moves the frame nowhere and is not kept as jumps. Colours: none — each block gets a placeholder thread, and a stop reads as a colour change. |
The PEC origin field (REQ-FMT-009). Brother’s software, pystitch and StitchCraft write 4 bytes
before the first record, shaped like a long jump on both axes. They hold the offset from the corner of
the box that the header’s width and height give to the start of the design. pyembroidery 1.4.32 to
1.5.1 leave the field out, and their first record is often a long jump that looks the same. The reader
takes the 4 bytes as the field only if the design read after them fits that box, and otherwise reads them
as the first record, with a warning that Brother machines expect the field. Files from pyembroidery
1.5.1 itself test both cases (conformance/oracle/write_pes.py).
Every reader survives every truncation and single-byte change of the golden files (a deterministic test
that runs on every PR), and an hour of coverage-guided fuzzing every night (REQ-FMT-006;
fuzzing).
Round trips. A file cannot say everything a plan says (a trim is a flag in PEC and three jumps in DST;
long moves become several records), so a plan read back is not the same plan — it makes the machine do
the same thing. stitchcraft-testkit::equivalence defines that: where the needle goes down, where the
thread is cut, where the machine pauses. Every writer/reader pair keeps it on 256 random plans per PR (fixed
seed) and 10,000 more each night (fresh seed). DST is held to what its machines do with the plan
(equivalence::dst_events): they cut the thread before long jumps the plan does not trim. Fresh seeds
paid off before this was even merged: they found a DST design whose split jump pieces cancelled exactly,
which is why only jumps of at most 1 mm make up a trim’s spelling.
PES is held to what its file says (equivalence::pes_events): a trim flag on every jump.
Reading back. read_back reads a file just written, with its plan’s threads, as a machine reads it.
The command line describes and draws what it wrote from it (REQ-CLI-008). The command line and the
VectorCraft exporters also call it before they hand a file out. A file StitchCraft wrote that doesn’t read
back is a bug in it (SC-E0009), and then nothing is written.
Converting. stitch convert is a read followed by a write, so the converted file makes the machine do
the same thing as the original: the round-trip guarantee above — except that a DST file cuts the thread
before long jumps where a PES machine leaves a jump thread, which SC-I0605 reports. A PES file from
StitchCraft has a trim flag on every jump already, so only PES files with plain jumps get those cuts.
Records carry over
literally (a DST file’s 12 mm jump pieces stay separate jumps in a PES file), and what the new format
cannot say is reported instead of guessed. A DST file’s threads are placeholders, all black (SC-W0604).
A PES file made from one names each by the Brother colour nearest black that is still free.
Later formats
| Format | Machines | Milestone | Notes |
|---|---|---|---|
| EXP | Melco, Bernina | M11 | 2-byte records, 0x80 escape codes for commands |
| JEF | Janome | M11 | Janome thread table indices; per-palette mapping |
| VP3 | Husqvarna Viking, Pfaff | M11 | Nested length-prefixed sections; thread descriptions as strings |
| XXX | Singer | M11 | |
| U01 | Barudan | M11 | |
| CSV / JSON | Debugging and interchange | M2 | Human-readable plan dumps; the conformance suite’s golden format for stitch lists |
| HUS, VIP | Husqvarna (older) | — | Compressed; read-only candidates after 1.0 |
Conformance
| Requirement | What it checks |
|---|---|
REQ-FMT-001 | Golden bytes: canonical plans encode to committed files byte for byte |
REQ-FMT-002 | Round trip: decode(encode(plan)) makes the machine do what the plan does — needle-downs, cuts and pauses at the same 0.1 mm positions — for every writer/reader pair, on random plans |
REQ-FMT-003 | Every command kind survives every writer/reader pair (and is sewn in TS-02) |
REQ-FMT-004 | Empty and zero-stitch files read cleanly; empty plans are never written |
REQ-FMT-005 | Independent oracle: pyembroidery reads our files to the same stitches (CI job with a pinned version) |
REQ-FMT-006 | Fuzzed readers never panic, never allocate beyond caps, always terminate |
REQ-FMT-007 | Long moves are split within the format’s per-record limit |
REQ-FMT-008 | DST is read as its machines sew it (three or more jumps in a row after sewing cut the thread), and writing DST reports where they will cut and the plan does not (SC-I0605) |
REQ-FMT-011 | A PES design whose top-left corner lies more than 204.7 mm from the start is refused, not written with its origin field wrapped round |
REQ-FMT-012 | PEC thumbnails draw stitches only inside their frames, so the stitch data’s end mark is followed by 00 |
REQ-THREAD-002 | Different threads get different palette entries, the nearest pairs first, and a thread read from a file keeps its entry |
The pyembroidery oracle is a conformance case (conformance/cases/formats/pyembroidery-oracle.toml): a
pinned pyembroidery, installed by hash in CI, reads every golden machine file through
conformance/oracle/decode.py, and its reading must make the machine do what our reader’s does. Only its
output is compared; no pyembroidery code is vendored. pyembroidery places a PES design’s top-left corner at
its origin where StitchCraft keeps the machine origin, so both readings are compared from their first
needle-down.
Rendering previews
stitchcraft-render draws a stitch plan as a PNG image: for stitch preview, for the images in these
docs, and for conformance diffs. Two promises shape it:
- What is shown is what sews (
REQ-RND-001). Machine files store positions rounded to 0.1 mm, so a preview drawn from the unrounded plan shows stitches that “move” once the file is saved. StitchCraft rounds every position with the writers’ own function (Point::to_tenths) before drawing anything. - The same bytes everywhere (
REQ-RND-002). A preview is a pure function of the plan and the settings, so documentation images can be compared byte for byte on every pull request.
stitch plan --preview draws the machine file it wrote, read back as stitch preview reads it, in the
design’s colours. A DST machine’s cut before a long jump then shows as a cut (REQ-CLI-008).
Two steps: scene, then pixels
StitchPlan ──Scene::of──▶ Scene (machine units, plain data) ──Scene::render──▶ PNG
The scene is what the fabric will show, on the 0.1 mm grid. Tests compare scenes, which says what differs; only the golden files compare pixels.
| Scene item | Meaning |
|---|---|
| Hole | A place where the needle goes down, in sewing order, with the thread’s colour |
Arrival Sewn | A stitch laid from the previous hole (StitchPlan::sewn_stitches, the single definition of “sewing”) |
Arrival Loose | The frame moved without sewing and the thread was not cut: a jump thread that lies loose until cut by hand |
Arrival Cut | The thread was cut since the previous hole (a trim or a thread change), or this is the first hole |
Mark Trim, Stop | Where the needle is when the machine cuts or pauses |
Jumps are not in the scene. The thread runs straight from hole to hole however the frame travelled, so a
place where a jump paused changes nothing on the fabric, and machine files split long jumps into pieces
anyway. Marks describe what the machine does, not how a file spells it, in the same way as the round-trip
oracle (stitchcraft_testkit::equivalence): two trims in a row cut once, the order of commands at one
spot does not matter, and a trim after the last hole cuts nothing that stays. So marks are a sorted set
and trailing trims are left out. The test for REQ-RND-001 relies on this: for every test sheet and for
random plans, the scene of a plan equals the scene of its PES file read back. The only differences
allowed are the ones PES cannot record: palette colours instead of chosen colours, and no stitch roles.
Styles
| Realistic | Simple | |
|---|---|---|
| Purpose | The finished embroidery | Checking a design |
| Background | Unbleached cotton #f4efe6 | White |
| Stitch | Thread 0.4 mm wide (40 wt): dark edge, body, light crest, round ends | Thin line in the thread’s colour |
| Needle hole | (the stitch ends show it) | Dot |
| Loose jump thread | Thinner thread, on top of what is under it | Dashed, thread colour |
| Travel after a cut | Nothing | Dashed, grey |
| Lock stitch | (like any stitch) | Orange ring |
| Trim, stop | Nothing | Red cross, blue square |
Everything is drawn in sewing order, so later stitches lie on earlier ones as they do on the fabric. The realistic style draws loose jump threads because they are on the fabric when the hoop comes off the machine; a design that should have trims shows them, which is what test sheet TS-02 is about. (A machine that cuts long jumps by itself would remove some of them; previews do not model that yet.)
Size and scale
The scale is in pixels per millimetre (default 8, about 200 dpi; 0.1 to 50), so a design looks the same
at every scale, only sharper. The image is the design’s extent plus a 2 mm margin on each side. Images
are at most 4,096 pixels on a side (64 MiB of pixels); a larger request is refused with SC-E0005, whose
message gives the largest scale that fits. Drawing charges the caller’s work budget: one unit per element
plus one per pixel of its length, so SC-E0004 stops a pathological request instead of a long wait.
Determinism
| Risk | Answer |
|---|---|
| SIMD code paths giving different rounding | tiny-skia is built without its simd feature: the same scalar pipeline on every CPU |
| Platform trigonometry | Only lines and circles are drawn; tiny-skia flattens circles and round caps (conics) with arithmetic and square roots, never reaching its cubic root-finding or rotations; f32::sin & co. are disallowed in clippy.toml |
| Coordinates | Grid positions are integers below 2^24, converted to f32 exactly |
| PNG encoding | png with miniz_oxide: pure Rust, no timestamps, versions pinned by Cargo.lock |
The golden PNG files in conformance/golden/render/ are compared by cargo test on Linux, macOS and
Windows. Like every golden file, they change only on purpose: re-bless with STITCHCRAFT_BLESS=1 cargo test -p stitchcraft-render --test preview in a pull request labelled golden-change that says why in
CHANGELOG.md.
Later
- The hoop and the comfort zone drawn around the design, from a machine profile.
- Thread direction and sheen for fills and satins, and a density map, once those stitch types exist (M3–M5).
- Inside VectorCraft the plug-in returns stitch geometry and VectorCraft draws it on its own canvas; this renderer is for files, the command line and the docs.
VectorCraft integration
How StitchCraft lives inside VectorCraft: what VectorCraft offers today, how we use it without changing VectorCraft (phase 1), what we propose upstream (phase 2), and how an in-tree integration would look (phase 3).
VectorCraft snapshot reviewed: main at 89ea46c (2026-10-08), version 0.6.0, and its plug-in API
again at 8bf090b (2026-10-24), after 0.16.0, at v0.17.0 (2026-10-25) and at v0.18.0 (2026-10-28).
File references below are to the first unless a version is named. The
compatibility gate tests the plug-ins against each VectorCraft release and branch.
1. VectorCraft in brief
| Fact | Detail | Where |
|---|---|---|
| What | Clean-room reimplementation of Illustrator in pure Rust; desktop (macOS, Windows, Linux, FreeBSD) and web (wasm) | README.md |
| Licence | MIT OR Apache-2.0; no GPL code allowed in the tree | Cargo.toml, AGENTS.md |
| Toolchain | Rust 2024, rust-version = 1.95 | Cargo.toml |
| Layers | geom, color L0 → doc L1 → pathops, brush, trace, text, effects, plugins L2 → render, svg, pdf, format, cad, eps, metafile L3 → tools L4 → engine L5 → ui-egui, mcp L6 → apps | xtask/src/layers.rs |
| Commands | Declarative cmd! registry: id, label, menu path, shortcut, params doc, enabled, run; reachable from UI, JSON control channel and MCP | crates/engine/src/cmd/* |
| Headless CLI | vectorcraft-cli run [--in FILE] [--cmd ID [--params JSON]]... [--export FILE]..., convert, info, commands, mcp | apps/vectorcraft-cli/src/main.rs |
| Control channel | JSON lines; engine.execute, ui.menu.invoke, ui.dialog.set, ui.click, ui.screenshot {path?} (needs a presented frame), ui.render {path?, scale?} (headless artboard render) | crates/ui-egui/src/control.rs |
| Releases | Tags v0.1.0 … v0.18.0. Pushing the release branch drafts a GitHub Release, and SemVer pre-releases (-rc.1) are flagged as pre-releases | .github/workflows/release.yml |
2. Extension points that exist today
Plug-in ABI v1 (crates/plugins, docs/plugins.md)
- Sandbox: a core WebAssembly module run by
wasmi2.0, no imports at all (no WASI, clock, file system or randomness). Exports:memory,vc_abi_version() → 1,vc_manifest(),vc_alloc(size),vc_run(input, len, params, len). Input and output are JSON. - Kinds: object filters (read selected paths and compound paths — geometry, fills, strokes, opacity — and return updated or new objects; one undo step) and live effects (rewrite one object’s geometry every time it is drawn, cached by plug-in, parameters and input geometry). From 0.14, exporters write the whole document in a file format of their own, listed by File › Export › Export As.
- Parameters: manifest schema with
number,int,bool,choice; at most 64 parameters (MAX_PARAMS,crates/plugins/src/manifest.rs:11) and a 64 KiB manifest; no labels, units, groups or conditions until 0.14, and the dialog shows the parameters’ names. From 0.14:stringandnumberListtoo, up to 256 parameters with at most 64 outside groups, and each parameter’slabel,unit,group,help,helpUrlandvisibleWhen. - Limits (
crates/plugins/src/runtime.rs): module ≤ 32 MiB; memory ≤ 512 MiB; output ≤ 64 MiB JSON; fuel 50 M instructions + 4,000 per input byte; wall clock 60 s per filter run and 5 s per live-effect run (crates/plugins/src/effect.rs), with a fresh instance for each call. An export gets 10 G instructions and 300 s (0.14). From 0.17 a filter that declares"budget": "heavy"gets 1 G and 300 s, once the user allows it at install, and runs in the background. From 0.18 a run pays nothing for compiling the functions it calls, which wasmi does at each one’s first call. Before, a first run paid, and could stop with most of its budget left when the 20 M-instruction slice it was in ran out while a function compiled. From M13.2 on, the gate’s Level B found 0.17.0’s command line stopping the PES exporter so on the test sheet TS-04, an export that takes 34 M instructions in 0.18.0’s host. - Live-effect input is the object’s geometry:
type,path,fillRule,bounds. From 0.14 it holds the object’sfillsandstrokestoo, read only, each stroke with its width, and from 0.15 its join, cap and miter limit._context.itemsays whether the effect is on the object or on one of its paints. The objects beside it are still not given. - Filters are given each stroke’s join, cap and miter limit and each object’s effect records, and may
return effect records (0.15). From 0.16 a filter keeps up to 1 MiB of data with the document
(
documentData). - Persistence: applying a live effect stores
{"id": "plugin.<id>", "params": {…}}in the object’s appearance (Effect { id, params: serde_json::Value, visible },crates/doc/src/appearance.rs). Documents keep the record when the plug-in is missing and draw the object unchanged. - Not in v1: tool plug-ins, panel plug-ins, host callbacks (progress, cancel), importers, live effects that change paint. File-format plug-ins came with the exporters (0.14).
Other facts that shape the design
- No generic per-object metadata.
Nodehasattrs: Option<Box<ObjectAttributes>>with only centre display, image map, URL and note (crates/doc/src/node.rs). The effect record is therefore the right place for embroidery parameters. - Document-level foreign data lives in
Document.unknown(used for the perspective grid and brushes), preserved on round trips (crates/doc/src/lib.rs). From 0.16 the data filters keep with a document is there too, underpluginData. - SVG import keeps only the attributes it understands. The importer looks attributes up by name
(presentation attributes, references, names such as
inkscape:label) and has no place to keep unknown ones (crates/svg/src/import.rs), so an Ink/Stitch SVG opened in VectorCraft up to 0.16 loses itsinkstitch:*parameters. From 0.17 VectorCraft keeps attributes in other applications’ namespaces from SVG import to SVG export, and reads none of them. SVG export can embed the whole native document (vectorcraft:document) when editing data is preserved. - Export formats are a static table (
FORMATSincrates/engine/src/cmd/fileio/mod.rs). From 0.14 an exporter plug-in adds a format to it,plugin.<id>, which is how StitchCraft can add PES.
3. Phases
| Phase | Needs from VectorCraft | Delivers | Milestone |
|---|---|---|---|
| 1. ABI v1 plug-ins | nothing | per-object embroidery settings, previews, tools; machine files via stitch export file.vectorcraft | M6 |
| 2. Plug-in features (upstream RFC) | generic plug-in features, most of them in VectorCraft 0.14 to 0.16 | previews as wide as a stroke, export from File › Export, labelled dialogs, design settings and a machine per document, and filters that make satin columns and draw test sheets (M6.8 to M6.12). Overlay previews of every stitch, and heavy live effects, after 1.0 (M9) | M6, M9 |
| 3. In-tree crate (optional) | ArtCraft’s interest, or a fork | native speed, simulator panel, satin tool | post-1.0 |
4. Phase 1 design (ABI v1, no VectorCraft change)
Plug-ins
One .wasm per stitch family keeps each dialog relevant and under 64 parameters, which every VectorCraft
release takes:
| Plug-in id | Kind | Stitch types | Notes |
|---|---|---|---|
<ns>.stroke | live effect | running, bean, manual (P1), zigzag (M7.2), ripple (later) | stroke_method choice selects the variant |
<ns>.satin | live effect | satin (P1), E (M7.3), S (M7.4), zigzag (M7.5) | rails and rungs from the object’s compound path |
<ns>.fill | live effect | tatami (P1); contour, meander, circular (P2) | fill_method choice |
<ns>.auto | live effect | the stitches automatic digitizing chooses by width (M13.7) | on a group, previews each path inside (image to embroidery) |
<ns>.pes, <ns>.dst | exporter (VectorCraft 0.14) | every family | File › Export › Export As writes the same machine file as stitch export (M6.9, below) |
<ns>.settings | filter (VectorCraft 0.16) | none | keeps the design settings and the machine with the document (M6.11, below) |
<ns>.stroke-to-satin | heavy filter (VectorCraft 0.17) | satin | makes each selected line into a satin column drawn with its rails and rungs (M6.12, below) |
<ns>.testsheet | filter (VectorCraft 0.16) | every family | draws a test sheet into the document, each object with its effect (M6.12, below) |
The tools an object filter was to give are VectorCraft’s own commands (tools) and M6.12’s 2 filters (Stroke to Satin and Test Sheet).
<ns> is a reverse-DNS namespace defined in one constant (PLUGIN_NAMESPACE) in the VectorCraft adapter,
which reads the effect records too. The adapter makes the ids from it, a plug-in’s with plugin_id and its
live effect’s with effect_id, and effect_name reads one back. The plug-in crate and the compatibility
gate use the same functions. A live effect’s id ends with its stitch family’s
(stitchcraft_params::Family::id).
Manifests are generated from the parameter registry; a
test validates each against VectorCraft’s manifest rules (id charset, ≤ 64 parameters, names, ranges, and
from 0.14 the keys that say how the dialog shows a parameter), so a manifest VectorCraft would refuse fails
the tests before a release.
Modules. The plug-in crate builds one module for each family, chosen by a Cargo feature named after
the family: stroke, satin or fill, one for each machine format, by its extension: pes or dst, and
one for each filter: settings, stroke-to-satin and testsheet. Without one it builds M0.6’s hello
effect. The modules contain the same code, which the native tests run whichever it is, and the feature only
picks the manifest and the run that vc_manifest and vc_run serve. cargo xtask compat contract builds
the nine modules, notes their sizes and loads them into VectorCraft’s host.
Manifests. A family’s manifest is made from the registry when VectorCraft asks for it, so a parameter added to the registry shows in the dialog without another change. A key that several families declare, each its own way, is in each family’s manifest as that family declares it (parameter registry, rule 7). VectorCraft’s parameters are numbers, whole numbers, toggles and choices, and each always has a value. VectorCraft 0.14 adds texts and lists of numbers, which older releases refuse. A record stores every declared parameter, at its default when the user leaves it. So a parameter is declared when one such value means what the parameter means:
- a length, an angle, a percentage or a number as a number in its range, and a count as a whole number;
- a list or a pair whose default is one value as that value, which stands for the whole list or both sides, as in Ink/Stitch;
- an optional length that 0 leaves unset as a number from 0.
Texts, seeds, and settings left empty to take another’s value keep their defaults. In the registry today
they are the custom locks’ steps, random_seed, a fill underlay’s angles, a satin’s zigzag underlay
insets and its shortest random split. A choice leaves out the options that a text spells out, which are
the custom locks, and fill_method leaves out Ink/Stitch’s legacy fill, which StitchCraft sews as a
tatami fill (REQ-FILL-005). satin_column belongs to the effect, which sets it from its family. The dialog opens
with the group that declares the family’s method. The groups only the family uses, for its default
method, come next, then those it shares with the other families, then those of its other methods, such as
the zigzag stroke’s. The settings every element shares come last, as in Ink/Stitch’s dialogs.
How the dialog shows them (REQ-VC-012, M6.10). From 0.14 a parameter can say how the dialog shows
it, and the plug-in says all of it from the registry (apps/stitchcraft-vc-plugin/src/dialog.rs):
- Its row has the registry’s label, and the unit of its kind of value: mm, % or °.
- The registry’s groups are the dialog’s sections, as they are the headings of the reference pages. VectorCraft draws a section where its group first appears, with every row of the group in it. Each group’s rows come together in the manifest, and a release before 0.14 shows them in the same order.
- Its help is the registry’s help as plain text, cut at a sentence within VectorCraft’s 1,000 characters.
A choice’s help ends with what each option means, by its label in the registry: VectorCraft’s
drop-down list shows the ids that designs store, such as
tatami_fill. Its help page is its entry in the parameter reference on the docs site, the book ofmain(dialog::SITE_URL). The docs check finds the page and the heading each manifest links to. - Each row shows only while the method is one the parameter applies to. Where the registry shows a
setting only with some values of another, as its
whensays, the dialog does too: the length variation of a random stitch length shows while that is on, and a satin’s staggers for staggered splits. Rows that nothing in the dialog would show are left out. VectorCraft gives the plug-in the value of a hidden row as well, so what the dialog shows never changes what sews. - The plug-in’s own parameters, the preview level and the exporters’ 2 options, have a label, help and a page too.
A release before 0.14 ignores these keys, since its ParamSpec reads a parameter’s type, range and
default and lets other keys pass (crates/plugins/src/manifest.rs at 0.12.0). Its dialog shows the keys
and every parameter, as it did. The compatibility gate’s pictures of the dialogs are taken in the pinned
release’s window, and the window probe checks that each shows the rows the manifest shows at the defaults,
by label, each with the control of its kind.
What a live effect shows. A family’s effect sews its object with the engine’s generate, on the
reference machine and with no neighbours, as one object of a design. It returns the stitches as geometry,
the exact level below. When it can’t show stitches, it gives back why. VectorCraft then draws the object
as it is and shows the message as the plug-in’s last error. That happens when the object cannot be sewn or
sews no stitch, with the message that says why. A drawing that vanished instead could not be clicked to
change the setting that emptied it. ABI v1 has no way to pass VectorCraft the warnings about stitches the
effect shows, and stitch export reports them. How much of the stitches it shows, within what VectorCraft
lets it spend, is its preview level (below).
The stroke an effect follows (REQ-VC-010, M6.8). A satin column drawn as one path and a zigzag stroke
are as wide as their stroke. From 0.14 VectorCraft gives a live effect its object’s fills and strokes, and a
stroke or a satin effect on the object follows the topmost stroke that has a paint and a width. It reads
the stroke’s width, and from 0.15 its join, cap and miter limit, with the reader’s own code
(stroke_style in crates/stitchcraft-vectorcraft). stitch export follows the topmost stroke VectorCraft
draws, and the 2 can differ in 2 ways. VectorCraft gives a live effect every stroke of its object without
its opacity or whether it shows, and gives it none of the paints of the groups around it. Given no stroke,
before 0.14, for an object without one, or where only a group’s stroke paints it, the effect leaves a satin
column drawn as one path and a zigzag stroke as they are, with SC-I0810. An effect on one of the object’s
fills or strokes leaves the object as it is too, with SC-I0810: stitch export doesn’t use such an effect
(SC-W0807).
The workflow
- Draw the art in VectorCraft as usual.
- Select an object → Effect › Plug-ins › StitchCraft Fill (or Stroke / Satin) → set parameters in
the generated dialog. The parameters are now stored on the object (undoable, saved in the
.vectorcraftfile) and the object shows a stitch preview. - Save the document.
stitch export design.vectorcraft -o design.pes --preview design.pngwrites the machine file, for the machine--profilenames, or else the one the document keeps (below), or elsebrother-pe800-5x7, and a picture of what will sew. It says what the reader left out or changed, with the engine’s warnings.stitch exportis another name forstitch plan, which reads a VectorCraft document (.vectorcraft,.drawcraft, or a.vctemplatetemplate) as it reads an SVG file (REQ-VC-008), and--reportadds the JSON report. A bug-report bundle includes the part of the document the reader reads, which is its JSON without the images after it. Its replay reads that part the same way. From VectorCraft 0.14, File › Export › Export As writes the same file with StitchCraft’s exporters, without saving first (below).
Stitching order is paint order (bottom first). Hidden objects are skipped, as VectorCraft draws nothing for them, and locked objects are stitched. Thread colours come from the object’s fill for a fill, and from its stroke for a stroke or a satin column, as in Ink/Stitch, mapped to the profile’s palette.
Exporters: File › Export writes PES and DST
VectorCraft 0.14 added exporter plug-ins (the RFC’s P1).
File › Export › Export As lists each after the built-in formats by its name, and document.export writes
one for a path with the extension it lists. VectorCraft gives the exporter the document as a native
.vectorcraft file’s JSON, as document.save writes it but without the data of images of 8 KiB or more,
with the parameters of the dialog it shows when the file is picked, and the file’s extension and name. The
exporter returns the file in base64, with warnings for the user, or declines with a message, and then
nothing is written (VectorCraft’s docs/plugins.md, Exporters).
M6.9 makes StitchCraft’s formats exporters (REQ-VC-011, apps/stitchcraft-vc-plugin/src/export.rs).
There are two, PES embroidery (StitchCraft) and DST embroidery (StitchCraft), each a module of its
own, and a VectorCraft before 0.14 refuses to install them. An exporter writes the file stitch export
writes of the document:
- It reads the document with the same reader and plans it with the engine for the machine its
profileoption names, and the design takes the file’s name. The option’s default,document, is the machine the document keeps, orbrother-pe800-5x7when it keeps none, asstitch exportplans it without--profile(below). Reading the file back, asstitch exportdoes, stops it at a writer’s bug (SC-E0009). The budget isstitch export’s own too. Itsautooption digitizes the objects without StitchCraft effects, as--autodoes, and sews them in the order StitchCraft chooses. The exporters have no switch like--sort-colours, and the objects with effects keep their order among themselves (image to embroidery). Level B of the compatibility gate exports a document with VectorCraft’s command line and checks that the files arestitch export’s, byte for byte. - What
stitch exportsays, the export says as its warnings, one line each, naming the object and with the hint after it. VectorCraft shows 100 of at most 500 characters each, so of more the last says how many morestitch exportlists. - When anything said is an error, the exporter declines with the first, after how many there are, and
VectorCraft doesn’t write the file, as
stitch exportdoesn’t (REQ-CLI-003). VectorCraft shows the first 300 characters of a decline, afterplug-in failed:.
An export may run 10 billion instructions, and 4,000 more for each byte of the document, and 300 seconds.
The engine’s budget is stitch export’s, so a design that needs more is stopped by VectorCraft with its
own error, plug-in exceeded its instruction budget, and stitch export writes it. The contract test
req_vc_011_a_fill_of_the_whole_hoop_exports_within_vectorcraft_s_limits measures the largest designs
the reference machine sews (VectorCraft 0.16.0 to 0.18.0 alike, 2026-10-29):
| Design, each fill with its defaults | Instructions | Of those allowed |
|---|---|---|
| A fill of 128 × 178 mm, the 5 × 7 in hoop’s whole field | 1.6 billion | 16 % |
| 63 fills of 14 mm squares across the field | 4.3 billion | 43 % |
A fill’s work grows less than its area, and every fill takes some whatever its size: the squares cover half
the field and take more than twice as much. About 140 fills of that size are as many as an export can
plan, which few designs for a home machine’s hoop reach, and stitch export writes the others. The test
sheets drawn as designs export within 17 % of what an export may run, once the Test Sheet filter has drawn
them into a document (below). A live effect is given no warnings
channel, and an exporter no picture: stitch export --preview still draws what will sew.
Design settings and machine kept with the document
Ink/Stitch keeps 3 settings for the whole design with an SVG file, collapse_len_mm, min_stitch_len_mm
and min_satin_stroke_width_mm (SVG input), and a VectorCraft document has
no place of its own for them. VectorCraft 0.16 keeps up to 1 MiB of JSON with a document for each filter,
the RFC’s P5. The filter returns it as
documentData, and the native file saves it in document.unknown.pluginData under the plug-in’s id. An
exporter reads every plug-in’s in the document it is given, and a live effect isn’t given it.
M6.11 makes a filter keep them (REQ-VC-013): StitchCraft Design Settings, in Object › Plug-ins
(<ns>.settings, apps/stitchcraft-vc-plugin/src/settings.rs). Its dialog has the machine, a choice of
the built-in profiles, then each design setting as a length. The settings’ names, labels and help come
from stitchcraft_engine::design::SETTINGS, the table the SVG adapter reads Ink/Stitch’s settings with.
The machine is StitchCraft’s own, as Ink/Stitch’s hoop is the page. On OK the filter keeps all 4, the
settings under Ink/Stitch’s names and the machine’s id as profile:
{"profile": "brother-pe800-5x7", "collapse_len_mm": 3, "min_stitch_len_mm": 0, "min_satin_stroke_width_mm": 1}
The filter returns each object it is given by its id alone, as VectorCraft deletes an input object that a filter leaves out, and so its run leaves every object as it was.
The VectorCraft adapter reads the data (stitchcraft_vectorcraft::settings) as the SVG adapter reads
Ink/Stitch’s. The settings kept become the design’s settings, and the machine kept is
VectorCraft::machine, which stitchcraft_vectorcraft::machine reads before the rest of the document.
SC-W0812 names what doesn’t read, which keeps its default:
- a setting that isn’t a length of 0 or more, or that this StitchCraft doesn’t know;
- a machine it doesn’t have;
- data that isn’t an object, whose settings all keep their defaults.
In a damaged file whose pluginData isn’t an object, VectorCraft finds nothing kept, and so does the
adapter. stitch export and stitch bug-report plan for the machine --profile names, or else the one kept, or
else the reference machine (stitchcraft_cli::commands::plan::machine_for). The exporters’ profile
option starts at document, which follows the same order, and names a built-in profile only when the user
picks one.
VectorCraft opens a filter’s dialog at its manifest’s defaults, not at what the filter kept. The filter
keeps every setting each time it runs, and the user sets all of them again. A live effect isn’t given what
the document keeps, and the previews are planned with the default settings for the reference machine. A
longer shortest stitch kept with the document shows in the machine file and in stitch export --preview,
and not in the preview.
The contract test req_vc_013_the_settings_filter_keeps_the_machine_each_exporter_plans_for has
VectorCraft’s host run the filter as plugin.run runs it, then exports the document with what it kept. Level B’s settings
scenario does the same with VectorCraft’s command line: a shortest stitch of 1.5 mm changes the PES file,
the exports are stitch export’s, and with the small hoop kept the export of a 1 in square fails with
SC-E0701.
Tools: VectorCraft’s own commands
The plan had one object filter, <ns>.tools, to make a satin column from 2 paths, add a rung, bake a
preview to paths, make a line into a satin column and draw a test sheet. VectorCraft’s own commands do the
first three, and M6.4 checked
them in its source, 0.7.0 to 0.12.0. Make Compound Path changed at 0.10.0. Before, the compound path takes
the appearance of the frontmost path it is made of and turns none round. From 0.10.0, it takes the
backmost path’s, and reverses each path in front that winds the same way as the backmost one. A
straight path counts as winding clockwise, so of 2 straight rails the front one is always reversed. So
the StitchCraft effect is applied after the paths are joined, which works in every version:
| Task | In VectorCraft | Why it sews |
|---|---|---|
| Satin column of 2 paths | Object › Compound Path › Make (object.compoundPath.make), then the satin effect | The column turns a second rail that runs the other way back (reverse_rails, automatic), as Ink/Stitch does (REQ-VC-007). The reversal also starts a ring at what was its last node, and of 2 rings the second starts at its node nearest the first’s start (REQ-SAT-019) |
| Rung | Pen tool line across the rails, then Make Compound Path with the column | Which way the rung runs, and where among the paths it lies, doesn’t change the points it pairs. Before 0.10.0 the compound takes the rung’s appearance, and the effect is applied again |
| Baked preview | Object › Expand Appearance (effect.expandAppearance) | It runs the live effect and makes objects of what each fill and stroke paints, strokes outlined. Those are the preview’s shapes, which sew differently from the effect. Bake a copy to show, and sew the original |
| Presets | Graphic Styles (graphicStyle.new, graphicStyle.apply) | Applying a style copies its effects into each object, or into the group it is applied to, whose effects apply to each path inside (REQ-VC-006) |
The last two needed what ABI v1 gave a filter later, and M6.12 made them filters of their own.
Tools: Stroke to Satin and Test Sheet
M6.12’s 2 filters draw objects with StitchCraft’s effects, which a filter’s output may hold from
VectorCraft 0.15 (the RFC’s P8). The VectorCraft adapter reads what a filter is given and writes what it
gives back (stitchcraft_vectorcraft::filter). A paint, an effect record and an opacity given back are
written as the JSON text VectorCraft gave, since VectorCraft keeps a paint’s swatch, and a record as it
was, only when they come back unchanged, which it tells by their values. A path is written in points,
each coordinate the shortest decimal that reads back to it. VectorCraft admits a new effect record as
effect.apply would apply it: a StitchCraft effect’s only while its plug-in is installed, its parameters
filled in to their defaults.
Stroke to Satin (REQ-VC-014, apps/stitchcraft-vc-plugin/src/stroke_to_satin.rs), Ink/Stitch’s
Stroke to Satin, once Convert Line to Satin, makes each subpath of each selected path that has a stroke
into a satin column drawn with its rails and rungs. The line is the column’s centre line, as wide as the
stroke a satin effect on it would follow, with that stroke’s join. The engine makes the rails and rungs it
makes of such a column when it sews one (REQ-SAT-016, REQ-SAT-017), and draws them as straight lines,
each rung longer than the gap between the points it pairs by a hundredth of it at either end, so that it
crosses the rails (satin generators). Read back, the column
pairs the same points, so it sews as the line did. A column of 2 rungs or none gets a rung at its start
too, since Ink/Stitch’s rule would read the 2 longest lines as its rails (DEV-SAT-007).
Each column is a compound path without fills. Its stroke is the line’s paint one CSS pixel wide, which
shows the rails and draws the satin effect’s stitches thin. The first column takes the object’s place,
and the others go above it with its opacity. The satin effect takes the place of the object’s first
StitchCraft effect, with the line’s satin settings or the defaults. The object’s other StitchCraft effects
are left out, and its other effects kept. An object the satin effect already reads as rails is given back
as it is. The filter refuses with SC-E0814, and the document stays as it was, when the selection is
empty, a path has no stroke that paints or can’t be used, or a line can’t be made into a column that sews
as it does. Such a line’s rails can’t be made whole (SC-E0214, SC-W0213), or they cross each other.
Before 0.15, which drops the records a filter gives, it declines with SC-E0813.
M6.4 measured making the rails inside VectorCraft’s host at up to 162 million instructions, where a light
run gets about 52 million. The rails are the line offset as GEOS offsets it, which builds the boundary of
the whole stroke for each rail. So Stroke to Satin is a heavy filter (P4): VectorCraft 0.17 gives each run 1 G
instructions and 300 s, once the user allows it at install, and runs it in the background. The contract
test req_vc_014_stroke_to_satin_makes_columns_that_export_as_the_lines_did converts M6.4’s lines in
VectorCraft 0.17.0’s and 0.18.0’s hosts alike, 3 mm wide, as plugin.run runs a filter, and StitchCraft’s
PES exporter writes each column’s file as it wrote the line’s (2026-10-29):
| Line, 3 mm wide | Instructions | Of those allowed |
|---|---|---|
| A zigzag of 10 corners | 18 million | 2 % |
| An S curve 30 mm long | 50 million | 5 % |
| A circle 10 mm across | 73 million | 7 % |
| A circle 30 mm across | 91 million | 9 % |
| An S curve 60 mm long | 91 million | 9 % |
| A circle 50 mm across | 233 million | 23 % |
That is at most 360 instructions for each unit of the engine’s work, which the plug-in prices at 450, and
the filter stops with SC-E0004 before VectorCraft would. A VectorCraft before 0.17 runs it as a light
filter, which almost every curved line outruns, and stops it with its own error.
Test Sheet (REQ-VC-015, apps/stitchcraft-vc-plugin/src/testsheet.rs) draws one of the sheets drawn
as designs, TS-02B to TS-09, into the document: the sheet’s design moved by whole millimetres so that its
middle is nearest the middle of the artboard _context names (Design::moved). Each element is an object
named by its id, with its path and its thread as the paint its family sews with. Its settings are its
family’s effect record, each the value its dialog row takes, which reads back as the setting it was. The
objects selected are given back as they are, and the sheet goes above them. The document sews as the
sheet’s design moved. A fill’s rows lie where the design’s origin puts them, so moved they may fall
elsewhere across a shape, and the sheet looks the same. The threads lose their names in the Brother
palette, since VectorCraft unlinks the swatch of a paint a filter gives, and the PES file is the same.
VectorCraft 0.15 keeps the records, but with an empty selection it looks to a filter as 0.14 does, which
would drop them. Only the documentData every filter gets from 0.16 tells them apart, so before 0.16 the
filter declines with SC-E0813. The sheets drawn stitch by stitch are plans, which no object holds.
The contract test req_vc_015_each_test_sheet_draws_into_a_document_that_exports_within_vectorcraft_s_limits
draws each sheet into a new document in VectorCraft 0.17.0’s and 0.18.0’s hosts alike, and exports it with
the PES exporter (2026-10-29). Drawing takes 14 to 25 million instructions, at most half of a light filter’s run.
Exporting takes 23 million to 1.6 billion, at most 17 % of an export’s, the most for the fills of TS-07,
TS-08 and TS-09.
Live-effect previews within v1 budgets
A live effect gets 50 million instructions, plus 4,000 for each byte of its input, and 5 seconds. Spike
M0.6 measured what JSON costs in VectorCraft’s host (wasmi 2.0, in VectorCraft 0.12.0 and on main at
8bc6532, 2026-10-17). The hello plug-in reads its input with serde_json and writes the geometry
back:
| Measured | Value |
|---|---|
| Reading JSON | 512 instructions a byte |
Writing JSON from a serde_json value | 822 instructions a byte |
| One anchor of a path, as JSON | 40 bytes |
| Speed on GitHub’s Linux runners | about 1.9 billion instructions a second |
The instruction budget runs out long before the clock: 50 million instructions take about 26 ms. Reading
a path uses an eighth of the 4,000 instructions each of its bytes brings. M6.3 measured the plug-ins
themselves in the host, on the shapes the contract tests run, with the same numbers in VectorCraft 0.12.0
and on main:
| Measured | Value |
|---|---|
| Writing the plug-ins’ geometry, by hand, to a thousandth of a point | about 16 instructions a byte |
| One anchor, written by hand | 18 to 20 bytes along a stroke or a satin column, 26 to 27 in a fill’s ribbons |
| A unit of the engine’s work for a stroke | about 880 instructions |
| A unit of the engine’s work for a satin column | about 4,730 instructions |
| A unit of the engine’s work making a satin column’s rails from its centre line (M6.8, VectorCraft 0.16.0) | about 330 instructions |
| A unit of the engine’s work for a fill | 1,900 to 5,300 instructions, by the part of the work |
| The exact preview of a 7 mm square fill with its defaults | 27 million instructions, 85 % of the engine’s work spent on the travel under its rows |
| The rows preview of square fills from 3.5 mm to 226 mm | 1.6 to 11 million instructions |
| The outline preview of square fills from 3.5 mm to 903 mm | 1.7 to 5 million instructions |
So the engine’s work, and not the JSON, is what limits a preview. The plug-in turns the instructions it
has left after reading its input into a budget of the engine’s work units, and prices what it writes by
the anchor. It shows less when the engine runs out, or when what it would write costs more than its
share, instead of being stopped (apps/stitchcraft-vc-plugin/src/preview.rs):
| Level | Geometry returned | Auto shows it |
|---|---|---|
| Exact | The stitch polyline (strokes, satins) or one thin closed ribbon per stitch (fills) | For a stroke or a satin column, within nine tenths of the budget |
| Rows | One ribbon per row segment of a fill’s top rows: where its stitches lie and which way they run | For a fill, within eight tenths |
| Outline | The object’s path as lines for a stroke or a satin column, and as ribbons for a fill, which are hatched every 3 mm in its direction | When the others cost too much |
The preview_level parameter, auto by default, lets the user pick a level (VectorCraft reserves the
name preview). A level the user picks gets the whole budget, and when that is not enough, SC-I0810
names a cheaper one. The units of a stroke’s work and of a satin column’s cost the same on every shape
measured, so a budget of units bounds their exact previews. A fill’s units vary too much for that. A 7 mm
square’s exact preview already takes more than half of what VectorCraft allows, and a budget of units that
ran out early, in the dearer work, took 71 million instructions. So auto shows a fill’s rows, and its exact
preview is the user’s to pick, with all that VectorCraft allows, which stops one that needs more with its
own error. A satin column drawn as one path makes its rails from its centre line first, in many units of
about 330 instructions each, which the satin column’s price counts as dear as its stitches. One bend takes
33,100 units, so auto shows the outline of a centre line that bends. Its exact preview is the user’s to
pick too: 12.6 million instructions for one bend, 25 million for a quarter circle of 17 points and 46
million for a sine curve of 32 points, of the 51 to 55 million VectorCraft allows them. The
contract test req_vc_003_every_preview_level_finishes_within_vectorcraft_s_limits runs each level on
shapes of growing size on every track the compatibility gate tests, and fails
when VectorCraft stops a run that must finish. It prints what each run takes, which the budget’s numbers
are measured from. hello_reads_and_writes_json_within_the_recorded_budget still measures what JSON
costs. The real stitches are always available from the CLI preview, and the RFC’s overlay channel (P3, in
review upstream) would remove this limitation.
Paint caveat: a live effect changes only geometry. A fill preview’s ribbons are painted with the object’s own fill, and a stroke preview with its stroke. From 0.14 a stroke or a satin effect reads its stroke’s style, and still cannot change the paints. Users set the fill/stroke to the thread colour, which they want anyway for export.
Commands in v1
Ink/Stitch’s commands are symbols linked to an object, and ABI v1 has nothing to link. trim_after and
stop_after are ordinary parameters. StitchCraft reads the others from names, an object’s or a layer’s,
which VectorCraft’s Layers panel sets (REQ-VC-009). A name is read when it begins with stitch:,
after any white space, in any mix of upper and lower case. Its word ends at the first white space, so a
note can follow it:
| Name | Ink/Stitch’s command | What StitchCraft does |
|---|---|---|
stitch:ignore | ignore_object, ignore_layer | Leaves the object or the layer out with everything in it, and lists it with SC-I0811 |
stitch:start, stitch:end | starting_point, ending_point | Marks where the fill or the satin column grouped with it starts or ends, and never stitches the marker. Until the generators start and end at hints (REQ-GEN-001, M8), they choose where, and SC-W0807 names the marker. A group or a layer is never a marker: SC-W0807 names it, and what it holds is read as under any other name |
Any other word after stitch: | Ignores the name, and SC-W0807 names the object, in case the word is a misspelt marker |
Hiding an object leaves it out too, without a word, and a name on a hidden object says nothing. A live
effect is given its object’s geometry, with its fills and strokes from VectorCraft 0.14 (VectorCraft’s
docs/plugins.md, Input), never its name. So
VectorCraft’s preview still shows the stitches of an object these names leave out, a marker included,
when it has a StitchCraft effect or is in a group that has one. stitch export leaves them out. The RFC’s
sixth proposal, still open in that part, would let a live effect read the markers beside its object, so
its preview could start and end at them.
Reading .vectorcraft files (stitchcraft-vectorcraft)
stitchcraft_vectorcraft::read turns a .vectorcraft file into a design, and reports what it left out
or simplified, as the SVG adapter does for SVG files. It reads the format as VectorCraft documents it
(crates/format and the types crates/doc saves) and links none of VectorCraft’s crates. VectorCraft
versions 0.7.0 to 0.12.0 were read for this design.
The container. A file is JSON, compressed with gzip when its first two bytes are 1F 8B. VectorCraft
compresses from format 3 on, but the adapter reads any version either way. In format 4, images and other
large data can follow the JSON, behind the marker \n\0VCBLOBS\0. JSON text never holds a NUL byte. The
JSON ends at the first one, and nothing after the marker is read. The top level must say "format": "vectorcraft", or "drawcraft", VectorCraft’s earlier name, and hold a document. Formats 1 to 4 read
alike: points as {"x", "y"} or [x, y] in any of them. A newer format is read as far as it goes, with
SC-W0803, where VectorCraft’s own readers refuse it.
Limits. At most 64 MiB of JSON, unpacked, as for an SVG file, and the command line reads at most 64
MiB of any file (REQ-CLI-005), images included. A gzip stream is unpacked only up to the marker, and no
further than the limit. Nesting deeper than the JSON reader allows (128 levels, about 40 levels of
groups) refuses the file. VectorCraft itself keeps 32. The JSON is read into the few things the adapter
uses, and the rest is skipped as it is read: images, previews and text runs are never kept. Every object,
appearance item, effect and anchor charges the budget, and so does each element for its copy of the
object’s path.
Coordinates. VectorCraft keeps every path in document coordinates. Moving or scaling a group moves its paths’ anchors, so layers, groups and compound paths have no transform. Coordinates are points, 1/72 inch, with y down, and the adapter multiplies them by 25.4/72 into millimetres. The document’s origin stays the origin. Coordinates are read exactly, as VectorCraft reads them, from their text in the JSON. An anchor’s handles are absolute positions. A missing handle, or one within a billionth of a point of its anchor, leaves that end of the segment straight, and a segment without handles is a line.
What is stitched. The layers in order, bottom first, and in each its children in paint order:
- Paths and compound paths are the art. A compound path’s children are one shape, with the compound’s appearance and fill rule.
- Groups and layers pass their children on, and their StitchCraft effects with them (below). Clipping
is left for M8, as in the SVG adapter: a clipping group’s or layer’s art is stitched whole, with
SC-W0807. - Hidden objects, and everything in them, are left out without a word, as VectorCraft draws nothing for them. So are objects and paints of opacity 0, paints turned off and strokes of width 0.
- Objects and layers named
stitch:ignore, and everything in them, are left out withSC-I0811. Start and end markers are never stitched (commands). - Template layers, layers that don’t print, and guides are left out with
SC-I0809. VectorCraft shows them, dimmed or as guides, but prints and exports none of them. - A Shaper group’s sources are left out. The group shows the shapes it makes of them, and keeps them
as its first child, transparent. The Layers panel’s Make Clipping Mask (
layer.clippingMask.toggle) puts a Shaper group’s clipping object before them. - Text, images and symbols, and the objects VectorCraft computes as it draws them (blends, envelopes,
meshes, repeats, placed documents and VectorCraft’s Intertwine objects), are left out with
SC-W0807. The message names VectorCraft’s command that makes paths of them, such as Type › Create Outlines for text. So is a kind of object this version doesn’t read.
Paints and threads. An object without StitchCraft’s effects is stitched as Ink/Stitch stitches an SVG
shape. Its visible fills are filled, in the appearance’s order, and its visible strokes then have a running
stitch each. Since M13.1, read with --auto (stitchcraft_vectorcraft::read_with), the elements of an
object are automatic instead when neither it nor a group or layer around it has a StitchCraft effect
(image to embroidery, REQ-AUTO-001). StitchCraft then chooses their stitches by
their width. Read with --sort-colours, the objects with effects sew in the order StitchCraft chooses too
(REQ-AUTO-011). A stroke’s cap is read with its width and join, and an automatic line’s stitches follow its
round or square caps. Of a line sewn across its width, the reader names the alignment inside or outside a
closed path and the width profile, which its stitches don’t follow (SC-W0807, REQ-AUTO-005). The
thread’s colour is a solid paint’s colour, with CMYK and grey converted by
VectorCraft’s formula without colour management ((1 − c)(1 − k) and 1 − k) and Lab through sRGB. A
gradient gives its first stop’s colour, with SC-W0807 unless every stop has that colour, and a pattern
gives black, with SC-W0807. A stroke’s width, in points, and its join, with its miter limit (10 when
the file omits it, as VectorCraft draws it), shape a satin column drawn as one path. A stroke drawn with
a brush is stitched along its path, without the brush’s art, with SC-W0807.
What VectorCraft draws of a stroke that its stitches don’t follow is named with SC-W0807, and the
message says how to stitch it, usually with Object › Path › Outline Stroke:
- A stroke’s arrowheads are not stitched, and a dashed stroke is stitched along all of its path. A dash pattern that VectorCraft draws solid, with a length below 0 or that is not a number, or only 0s, is no dash.
- A stroke painted below a fill is sewn after the fill, over it.
- A satin column or a zigzag made of a stroke is as wide as the stroke all along and centred on the path. VectorCraft draws a closed path’s stroke inside or outside it when it is aligned so, and varies its width along a width profile. It also caps an open path’s ends with round or square caps. A running stitch along the path is the same whichever way the stroke is drawn.
A group’s or a layer’s own
fills and strokes, which paint every member, are not stitched as shapes of their own. They give
StitchCraft effects their thread and stroke (below), with SC-W0807.
Effects. StitchCraft’s live effects are kept in the object’s appearance as {"id": "plugin.dev.stitchcraft.<family>", "params": {…}, "visible": true}. The family is stroke, satin or
fill (stitchcraft_params::Family). An object with visible StitchCraft effects is stitched as they
say, in their order, instead of by its paints. A fill effect fills the object’s shape, a stroke
effect stitches along it and a satin effect makes a satin column of it. With this rule, an object sews
what its effects say and nothing else. Each effect is sewn on the shape as it is drawn. VectorCraft
draws an effect after the first on the stitches of the one before, though, so the preview of an object
with 2 is not what is sewn, and SC-W0807 says so, once for an object and once for a group. A fill with
an outline takes 2 objects: the shape with a fill effect, and a copy pasted in front of it (Edit › Paste
in Front) with a stroke effect. The record’s parameters are the element’s. A number is written as a decimal, a toggle as true
or false, a choice as its id and a list as its values with spaces between. What the record leaves out
takes its default, as in the plug-in’s dialog. The thread is the colour of the topmost fill VectorCraft
draws for a fill, and of the topmost stroke for the others, falling back on the other kind. An opaque
paint comes before one of opacity under 1, which lets what is under it show. A stroke or satin effect
follows the width and join of that stroke, or a width of 0 when none is drawn. VectorCraft writes a
stroke of paint none for an object without one, which it doesn’t draw. A satin column too narrow to
stitch across is sewn as a stroke, with SC-W0212. SC-W0807 names a zigzag 0 mm wide. VectorCraft’s own effects change what an object
draws, and are left out with SC-W0807 (Object › Expand Appearance makes paths of what they draw).
A group’s effects (REQ-VC-006). VectorCraft runs a group’s or a layer’s geometry effects on each
path and compound path inside it, however deep, with the group’s bounds as their frame (reshape_leaves
in crates/effects, 0.7.0 to 0.12.0), and paints what they return with the path’s own paints. The
group’s own paints paint the shapes inside it as its effects and those of the groups around reshaped them
(evaluate_container). They go over its contents. The Contents row of its Appearance panel
(contents_index) is last in the list by default, and the paints listed under it go under the contents.
StitchCraft’s live effects are geometry effects, and applying a graphic style to a group puts its effects
and paints on the group. So each path inside sews as the group’s StitchCraft effects say, and as the
preview shows it. A name can leave one out (commands). Its thread and stroke come from
the paints over it, the topmost first, as for an object’s own paints. The groups’ paints over their
contents come first, the outermost first, then the object’s own, then the groups’ paints under their
contents, the innermost first. The same order applies to an object’s own effects, which VectorCraft
paints with its own paints, under the groups’ paints over the object’s shape. A path that neither its own
paints nor a group’s paint is invisible, and is left out without a word.
A clip group’s first object is its clipping object. VectorCraft paints a path, a compound path or text
there with its own paints alone (clip_paint in crates/doc): its fills under the clipped art and its
strokes over it. A group’s effects run on such a path too. After Make Clipping Mask it has no paint, and
sews nothing. VectorCraft draws nothing of a group or an image there, which an imported file can have,
and neither is sewn. Where StitchCraft effects meet from more than one level, a path’s own and a group’s,
or 2 groups’, VectorCraft draws one effect run on the stitches another drew, which a machine cannot sew.
The path sews as the nearest effects say, and SC-W0807 names it.
Ids. An element is vc:<object id>:<what>: fill or stroke for a paint, with its rank among the
object’s fills or strokes from the second on (fill.2), and the family for an effect. VectorCraft keeps
an object’s id when it saves, so a report and a preview name the same object. A repeated id gets ~2,
~3, … as in the SVG adapter.
Reports. SC-E0806 refuses a file that isn’t a VectorCraft document, or is larger than the adapter
reads. SC-W0807 names what isn’t stitched. SC-W0808 names geometry that can’t be used, such as a
number that doesn’t read or a point more than 10 m from the origin, and a shape that draws nothing.
SC-I0809 lists the layers and guides left out because VectorCraft doesn’t print them, and SC-I0811 the
objects and layers left out by their names. The compatibility gate will have each VectorCraft version it
tests write files for the adapter to read back (Level B, M6.6).
Importing Ink/Stitch SVGs into VectorCraft
Because VectorCraft up to 0.16 drops inkstitch:* attributes on SVG import, phase 1 offers
stitch import-inkstitch design.svg -o design.vectorcraft: our SVG adapter reads the Ink/Stitch file
and writes a VectorCraft document with the same art and the parameters as live-effect records (M8).
From 0.17 VectorCraft keeps the attributes from SVG import to SVG export, but its live effects read none
of them.
5. Phase 2: plug-in features proposed upstream (summary)
Detailed in the RFC, which began as a proposal for an ABI v2. VectorCraft took most of it into ABI v1 instead, each as an addition older hosts ignore or refuse to install. Every item is generic, useful to any plug-in and not only embroidery:
| Proposal | In VectorCraft | StitchCraft’s use |
|---|---|---|
| 1. Exporter plug-ins, for File › Export › PES | 0.14.0 | M6.9 |
| 2. Rich parameter schemas: labels, units, groups, help, conditions, texts and lists, 256 parameters | 0.14.0 | M6.10 |
| 3. Display-only overlays from live effects, real stitch previews at low cost | in review | M9 |
| 4. Budget classes, with the user’s consent | heavy filters in 0.17.0, heavy live effects proposed | M6.12 (Stroke to Satin), M9 |
| 5. Document-level plug-in data, for the machine profile and palettes | 0.16.0, for filters and exporters | M6.11 |
| 6. Read-only context for live effects | the object’s paints in 0.14.0, the markers beside it proposed | M6.8, M9 |
| 7. SVG foreign-attribute preservation | 0.17.0 | Ink/Stitch files keep their settings through VectorCraft |
| 8. Effects and stroke style in filter objects | 0.15.0 | M6.12 (Stroke to Satin, Test Sheet) |
6. Phase 3: an in-tree crate (optional)
If ArtCraft wants embroidery natively — or in a fork — a vectorcraft-embroidery crate at VectorCraft’s
L2 (depending on geom, doc and our MIT/Apache engine crates) would provide commands in
crates/engine/src/cmd/embroidery.rs (apply settings, export PES/DST through the FORMATS table, test
sheets), panels in crates/ui-egui (Embroidery parameters, Stitch Simulator) and a satin rung tool in
crates/tools. The engine and adapters do not change; only a new adapter is written. This is why the
engine must stay host-agnostic.
7. Patterns we adopt from VectorCraft
VectorCraft’s conventions are mostly excellent and we follow them; where they drift we improve them. See ADR-0009 for the audit.
RFC: VectorCraft plug-in features
| Audience | ArtCraft / VectorCraft maintainers |
| Status | Mostly adopted (status): 7 proposals in VectorCraft 0.14.0 to 0.17.0. P3 is in review, and the rest of P4 and P6 is open |
| Compatibility | Additive. Every plug-in written for ABI v1 keeps working, and vc_abi_version still returns 1 |
| Author’s interest | StitchCraft (machine embroidery), but every proposal serves other plug-ins too |
This page began as the RFC for a plug-in ABI v2. VectorCraft took most of its proposals into ABI v1 instead, from pull requests by StitchCraft’s author. Each came as keys that hosts from before it ignore, or as a kind of plug-in that they refuse to install, and a plug-in that uses none of them installs anywhere. VectorCraft’s plug-in documentation is the contract. This page records what was adopted and when, and what StitchCraft still proposes. Its path keeps the old name, which links point at. The roadmap plans StitchCraft’s use of the adopted features in M6.8 to M6.12, and proposes the open ones in M9.
Status
Checked against VectorCraft’s docs/plugins.md and its history, in the 0.18.0 release (2026-10-28):
| Proposal | In VectorCraft | Since | Pull request |
|---|---|---|---|
| P1, exporter plug-ins | Adopted | 0.14.0 | #3171 |
| P2, rich parameter schemas | Adopted | 0.14.0 | #3172 |
| P3, overlays for live effects | In review | #3807 | |
| P4, budget classes | Heavy filters adopted, heavy live effects open | 0.17.0 | #3721 |
| P5, document-level plug-in data | Adopted for filters and exporters | 0.16.0 | #3720 |
| P6, context for live effects | Fills and strokes adopted, neighbours open | 0.14.0, a stroke’s style 0.15.0 | #3173 |
| P7, foreign SVG attributes | Adopted | 0.17.0 | #3758 |
| P8, effects and stroke style in filters | Adopted | 0.15.0 | #3174 |
The plug-in classes this RFC was for, and what VectorCraft serves of them now:
| Plug-in class | Now |
|---|---|
| Output generators for embroidery (PES/DST), plotters (HPGL), laser cutters (G-code) and cutting machines | Exporters write their own formats (P1) |
| Generators with many settings (halftones, hatching, embroidery, pattern fills) | Up to 256 parameters, labelled, in groups, with help and conditions (P2) |
| Previews of things that are not the object’s geometry (stitches, toolpaths, cut order) | Still only by replacing geometry, at JSON’s cost for every point (P3, in review) |
| Heavy but legitimate computation (routing, packing, simulation) | Heavy filters (P4), and an export’s own budget. A live effect still has a light run’s budget |
| Plug-ins with document-wide settings (machine profile, plotter pens) | Data kept with the document (P5) |
| Tools that set up objects for a live effect (a satin column from a line, a test sheet) | Filters read and return effect records and a stroke’s style (P8) |
Adopted
How VectorCraft adopted each proposal, where it differs from what was proposed.
P1: Exporter plug-ins
A plug-in of "kind": "exporter" lists 1 to 8 extensions. File › Export › Export As lists it after the
built-in formats as plugin.<id>, which document.export writes too. It is given the whole document as a
native .vectorcraft file’s JSON, where the RFC proposed the filters’ object JSON. The JSON lists every
layer, object, appearance and effect record, with the data plug-ins keep with the document. It returns
{"dataBase64": …, "warnings": […]}, and the host writes the file. An export gets 10 G instructions, plus
4,000 for each input byte, and 300 s, and runs on the export’s worker with Background Export on. Its
_context names the file’s extension and name, and a plug-in with parameters asks for them once the file
is picked.
P2: Rich parameter schemas
As proposed: label, unit, group, help, helpUrl (an https:// page) and visibleWhen, the types
string (with maxLength, at most 4,096) and numberList (with minItems and maxItems, at most 256).
A plug-in declares up to 256 parameters, at most 64 of them outside groups, where the RFC proposed a
manifest key to ask for more. visibleWhen names checkboxes and choices declared before the parameter. A
hidden parameter still goes to the plug-in. A plug-in’s parameters scroll in its dialog when they don’t fit
the window.
P4: Budget classes for filters
A filter that declares "budget": "heavy" gets 1 G instructions, plus 4,000 for each input byte, and 300 s
(HEAVY_FUEL, HEAVY_WALL_TIME_MS). It installs once the user allows it, and runs in the background with
progress and Cancel. A host from before heavy filters ignores the key, and the filter gets a light run’s
budget there. Only a filter may be heavy, as a live effect runs whenever the document is drawn. A heavy
filter’s budget grows with its input alone: the host learns how much output there is only once the run has
ended.
P5: Document-level plug-in data
A filter is given what it last kept with the document as _context.documentData, and keeps new data by
returning documentData, at most 1 MiB of JSON, in the same undo step as its objects. The native file
keeps it in document.unknown.pluginData under the plug-in’s id. An exporter reads every plug-in’s, in the
document it is given. A live effect isn’t given it: its geometry depends on its object alone.
P6: The paints of a live effect’s object
A live effect is given its object’s fills and strokes. Each stroke comes with its width, and from
0.15.0 with its join, cap and miterLimit, as a filter’s does (P8). _context.item says where the
effect is: on the object, or in the own effects of one of its fills or strokes. The paints are read only,
and the effect still changes only the geometry. The objects beside it are not given
(below).
P7: Preserve foreign SVG attributes
As proposed: attributes in other applications’ XML namespaces stay on their objects from SVG import to SVG
export, and copies keep them. Nothing in VectorCraft reads them. An Ink/Stitch SVG opened in VectorCraft
keeps its inkstitch:* parameters when it is saved as SVG again.
P8: Effects and stroke style in filter objects
As proposed: a filter is given each stroke’s join, cap and miterLimit, as a live effect is, and each
object’s own effect records. It may return effects, which replace the object’s own. Each is admitted as
effect.apply admits it: a known effect, or an installed live-effect plug-in with parameters its manifest
takes. A tool can make a line into a satin column with its effect, or draw a test sheet whose objects sew.
Open
P3: Display-only overlays for live effects
A live effect may return "overlay": {"format": "f32-polylines", "data": base64}, with its geometry or
instead of it. The overlay is a compact binary list of polylines, each with an RGBA colour and a width in
points. The host draws overlays on the canvas above the object, never exports or hit-tests them, and caches
them with the effect result. This makes previews cheap, 8 bytes for each point instead of the 40 or so JSON
takes, and keeps the object’s real geometry untouched. An embroidery preview could then show every stitch
of a large fill, where it shows a fill’s rows or its outline today. #3807 adds overlays, and is in
review.
P4: Budget classes for live effects
A live effect declares "budget": "heavy", allowed once at install as a heavy filter is. It gets more
instructions and time and runs off the UI thread, with the object drawn as it is until the result comes.
Fuel for each byte read back, as well as for each byte given, would make the budget follow what a
generator writes. An embroidery effect’s exact preview of a fill 7 mm across takes 27 million
instructions, and a larger fill’s stops at the light budget’s 50 million.
P6: The objects beside a live effect’s object
The live-effect input holds, optionally, the objects in the same group whose names start with a prefix the
manifest declares ("contextPrefixes": ["stitch:"]). Embroidery reads start and end markers from it, so a
preview starts and ends where stitch export does. Other plug-ins use the context for guides and anchors.
Security and determinism
The proposals leave the sandbox as it is: plug-ins still cannot touch files, the network, clocks or randomness. Exporters return bytes, which the host writes. Overlays are plain data, and heavy budgets are opt-in and still bounded. Results are still deterministic functions of their inputs, which a host may cache.
Implementation sketch and tests
| Proposal | Main files | Tests |
|---|---|---|
| P3 | crates/plugins/src/effect.rs, canvas drawing in crates/ui-egui/src/canvas.rs | a fuzz test of overlay decoding, a cache key test |
| P4, live effects | crates/plugins/src/{effect,runtime}.rs, the install flow | fuel accounting tests, a drawing that waits for a heavy result |
| P6, objects beside | crates/plugins/src/objects.rs | ABI tests |
We would contribute implementations and tests following VectorCraft’s AGENTS.md, one proposal per PR.
StitchCraft’s compatibility gate already runs a third-party contract test against every VectorCraft
release, release branch and main (compatibility gate).
VectorCraft compatibility gate
Goal: know within a day when a VectorCraft change would break the StitchCraft plug-in — ideally before VectorCraft ships it — and keep a public, generated table of which VectorCraft versions work.
Tracks
| Track | What we test against | How it is discovered | When | Effect of a failure |
|---|---|---|---|---|
stable | Latest published, non-pre-release VectorCraft release | SemVer tags from git ls-remote | Daily, and on every PR touching the plug-in, the adapter or the gate | Blocks our release; its issue is labelled upstream-stable |
prerelease | Latest published alpha/beta/RC (-alpha, -beta, -rc SemVer tags) | SemVer tags from git ls-remote, when newer than the latest release | Daily | Its issue is labelled upstream-prerelease; does not block |
release-branch | Head of VectorCraft’s release branch, which their pipeline builds into the next (still private, draft) release | git ls-remote … refs/heads/release | Daily | Its issue is labelled upstream-canary |
main | Head of VectorCraft’s main | git ls-remote … refs/heads/main | Daily | Its issue is labelled upstream-canary |
Each track has one issue at a time, commented on while the track fails and closed when it passes again.
Draft releases are not visible to the public API, so the release branch is the earliest public signal
of an upcoming release (VectorCraft’s release workflow runs on pushes to release,
.github/workflows/release.yml); pre-release tags catch alphas, betas and release candidates as soon as
they are published.
compat/vectorcraft.toml records the versions currently pinned per track (the branch for branch tracks,
and the SHA-256 of the stable release’s Linux archive), so that pull requests are checked against a fixed
release and every change of pin is a reviewable PR.
Two levels of test
Level A — ABI contract (fast, minutes)
compat/vc-contract is a small crate outside the workspace that depends on VectorCraft’s
vectorcraft-plugins crate at the track’s ref (a git dependency rewritten per track by
cargo xtask compat). It loads our freshly built .wasm files into VectorCraft’s real host code and
checks:
- Each module installs (
vectorcraft_plugins::registry::install_bytes), and its manifest is accepted (id, kind, parameter count and types). vc_abi_versionis a version the host supports.- Live effects run through
vectorcraft_plugins::effect::applyon fixture geometry, finish within the host’s limits, and return the expected preview geometry: paths of stitches for strokes and satin columns and closed ribbons for fills. When an effect can’t show stitches, its last error says why. Which level a shape shows, and the geometry of each, the plug-in’s native tests check. - Filters run and return valid objects. Exporters run through
vectorcraft_plugins::export::runon documents written for the test, return a file of their format, with the export’s warnings, plan for the machine their dialog names, and decline with the first error when there is one (REQ-VC-011). The Design Settings filter, run asplugin.runruns a filter, keeps its dialog’s values with the document and returns its objects as they were. The exporters plan that document for the machine it keeps, unless their dialog names another (REQ-VC-013). Stroke to Satin installs as a heavy filter, and makes each of M6.4’s lines into a column that the PES exporter writes as it wrote the line (REQ-VC-014). Test Sheet draws each sheet into a new document, which the PES exporter writes within an export’s budget (REQ-VC-015). The effect records they give are admitted asplugin.runadmits them, by the manifests of the installed live effects. - Error paths: invalid parameters are rejected by the host with the expected message.
- Budget: the hello plug-in’s cost for each byte of JSON it reads and writes stays within a quarter
above the costs recorded for previews.
Every preview level finishes on shapes of growing size, within VectorCraft’s limits (
REQ-VC-003), and an export of a fill of the reference hoop’s whole field finishes within an export’s (REQ-VC-011). Stroke to Satin finishes within a heavy filter’s, and Test Sheet within a light one’s. Each run prints the costs.
vectorcraft-plugins depends only on geom, color, doc and wasmi, so this builds quickly.
Level B — end to end (slower, tens of minutes)
Uses VectorCraft’s own command line, vectorcraft-cli run. For stable and prerelease it is the
binary in the release’s Linux archive (VectorCraft’s packaging/linux/package.sh puts it there). The
archive is checked against the SHA-256 the pins file records for the pinned release, or against the one
the release’s SHA256SUMS.txt lists for a newer release. For the branch tracks it is built from the
branch, with a cached target directory. cargo xtask compat track <track> --ref <tag|sha> runs both
levels and writes the track’s result for the report, and cargo xtask compat e2e runs Level B alone.
The scenarios are data files in compat/scenarios/*.toml, each one or more runs of vectorcraft-cli. A
run starts from a new document, or from one an earlier run saved, and draws its shapes with VectorCraft’s
commands. So every file VectorCraft opens is one it wrote, and StitchCraft reads only what the version
under test saved. Steps work on the selection, which drawing a shape sets: VectorCraft 0.7.0 can’t give
one command another’s result. VectorCraft runs a live effect once for an object and its parameters, and
remembers only a module’s last failure, which the next success clears. So each module a run installs is
asked for its last error after every PNG export, which runs the live effects, and it must have none. A
scenario draws each object it gives an effect before it gives another the same family’s, and
cargo xtask compat refuses an apply step no drawing shows. Exporting a PES or a DST file draws
nothing: an exporter is given the document’s JSON. An export that must fail names the code of the error it
says, with fails. VectorCraft stops a run at a step that fails, so that export ends its run, and no check
names its file. A run installs a module whose manifest sets a heavy budget, Stroke to Satin’s, with
allowHeavy, as the user allows it. The checks are StitchCraft’s, since VectorCraft’s pictures change from version to
version and what sews must not:
| Scenario | Steps | Checks |
|---|---|---|
install-and-list | install the 8 modules, then plugin.list | each family’s live effect, each format’s exporter and each filter is listed (REQ-VC-002, REQ-VC-011, REQ-VC-013, REQ-VC-014, REQ-VC-015) |
apply | a square filled red and a line, given the fill and stroke effects with parameters, then saved | as sent, with a fill in red and a stroke (REQ-VC-004, REQ-PRM-003) |
compound-satin | 2 rails and a rung, Make Compound Path, then the satin effect | as sent, and one satin column (REQ-VC-007) |
graphic-style | a group of 2 squares, and a gold square with a fill effect, drawn, whose new graphic style is then applied to the group | as sent, and 3 fills in gold at the effect’s 90° (REQ-VC-006) |
missing-plugin | a fill saved and drawn, then opened and saved again without the plug-ins | both sew alike, as sent: the records are kept (REQ-PRM-003) |
export | a square filled red and a line far below it, given the fill and stroke effects, saved, then exported to PES and DST by StitchCraft’s exporters | each file is the one stitch export writes of the saved document, and the export’s warnings are what it says, the DST’s cut on the long jump among them (REQ-VC-011) |
settings | a 1 in square given the fill effect and exported to PES, then the Design Settings filter run with the 4 × 4 in hoop and a shortest stitch of 1.5 mm, and the square saved and exported to PES and DST; then the filter run with the small hoop, and the square saved and exported to PES | each document keeps what the filter was run with, each file is the one stitch export writes, the PES file differs from the one before the filter, and the small hoop’s export fails with SC-E0701 (REQ-VC-013) |
stroke-to-satin | an S curve, a V and a circle with strokes 3 mm wide and the satin effect, saved and drawn, then made into columns by Stroke to Satin, saved, drawn and exported to PES | the columns sew as the lines did, 3 satin columns in the strokes’ colour, and the file is the one stitch export writes (REQ-VC-014) |
testsheets | each sheet drawn as a design, drawn by Test Sheet into a new document, then saved and exported to PES | each document sews as the sheet’s design moved by whole millimetres to the middle of its artboard, and each file is the one stitch export writes (REQ-VC-015) |
As sent: the saved file sews as it does with the StitchCraft records of the objects the effects were
applied to put back as the steps sent them. So VectorCraft stored what the dialog set, on the objects it
was applied to, in a form StitchCraft reads. An object effect.apply named that the file doesn’t hold
fails the check. A file is sewn as stitch plan sews it, for the machine it keeps or else the reference
machine, and 2 are compared
byte for byte, as is a machine file exported with the one stitch export writes of its document, by the
format and the name of the file. Elements are counted by kind, thread and settings. A paint’s fill is a fill too, and has no
settings. The live-effect budget of each preview level is Level A’s (REQ-VC-003).
Automation
schedule (daily) ──▶ discover tracks ──▶ for each track: build plug-ins ─▶ Level A ─▶ Level B ─▶ its result
│
┌───────────────────────────────────────────────────────────────────────────────────┘
▼
results ──▶ `cargo xtask compat report` ──▶ an issue per track: opened, commented on, or closed
│
└─ a new stable or pre-release passes ─▶ bot PR "compat: VectorCraft vX.Y.Z":
pins, compat/supported.toml and the generated page
cargo xtask compat reportdecides, and the workflow acts on what it prints. The rules are Rust with tests. Filing an issue is too (cargo xtask compat issue), which comments on an open issue with the same title rather than open another.- Each track’s issue is titled “VectorCraft compatibility:
- A new stable release or pre-release that passes both levels is adopted. The report writes its pins (for
a stable release, the archive’s SHA-256 the run checked, and the contract crate’s tag) and its row in
compat/supported.toml, andcargo xtask docsregenerates the page. The bot pushes the branchcompat/vectorcraft-<tag>and opens the pull request. It then dispatches ci, docs, goldens and compat on the branch itself, because GitHub runs workflows for a push made with the workflow’s own token only when they are dispatched. A new VectorCraft version is adopted only through a reviewed pull request with the full CI green. - GitHub Actions can open a pull request only if the repository’s settings allow it (Settings › Actions ›
General › Workflow permissions). Otherwise the run opens an issue, or comments on it, titled
VectorCraft compatibility: a new release waits for its pull request, which names the branch. The first run with nothing to adopt closes it. - The report job alone writes:
contents,pull-requests,issuesandactionswith the defaultGITHUB_TOKEN. It runs on schedule, or when the workflow is started by hand onmain, and never for a cancelled run, whose tracks may have stopped before writing their results.
Moving into VectorCraft
StitchCraft must stay movable into VectorCraft’s own repository (ADR-0011).
The move job rehearses it against the latest stable release and main (pull requests that touch
manifests or tooling: the pinned stable release):
- Check out VectorCraft and put this repository into it as the folder
stitchcraft/. - Nested:
cargo xtask compat join --nested .., then build a throwaway VectorCraft crate that depends onstitchcraft-engineby path. - Joined:
cargo xtask compat join .., then runcargo xtask cifrom the folder: StitchCraft’s gates, inside VectorCraft’s workspace, with its lockfile and lints.
A failure means a change here, or one in VectorCraft, has made the move harder. The scheduled run reports
it in an issue of its own, labelled upstream-canary.
VectorCraft’s window
The window job runs the app VectorCraft releases, from the Linux archive of the pinned stable release,
in a desktop without a screen. cargo xtask compat window takes screenshots of the window and drives it
through its accessibility tree with xa11y, as the docs’ vectorcraft-ui pictures will be made
(docs pipeline). The archive’s SHA-256 is pinned beside the
stable tag in compat/vectorcraft.toml, so the bot PR that moves the pin records the new archive’s sum
too, the one the release’s SHA256SUMS.txt lists and the run checked. The same archive holds
vectorcraft-cli, for Level B.
The supported-versions table
The user guide’s VectorCraft compatibility page is generated by
cargo xtask docs from compat/supported.toml, never hand-edited. The record lists the releases that
passed both levels, with the day each passed. Only the pull request that adopts a release writes to it. A
branch’s result changes daily and is reported in its issue, never in the record. A row of the page looks
like this:
| VectorCraft | Track | Passed the gate |
|---|---|---|
| 0.12.0 | stable | 2026-10-18 |
It also names the release the gate pins, which pull requests are checked against.
Going upstream
The strongest gate is in VectorCraft itself: we will offer VectorCraft a tiny third-party contract test
(our minimal plug-in built as a fixture, like their desaturate.wasm) so cargo xtask ci catches ABI
changes before release. VectorCraft runs its gates locally rather than on every PR in GitHub Actions
(observed in .github/workflows/ at 89ea46c), so a test in their suite is worth more than a test in
ours.
Conformance testing
When behaviour is specified only by its implementation, fixes for one input break another and nobody can say what “correct” means. StitchCraft specifies behaviour as requirements, proves each with cases, and reports the result as a matrix on every pull request. Code is written to make cases pass, not the other way round (ADR-0008).
Vocabulary
| Term | Meaning |
|---|---|
| Requirement | A numbered, testable statement: REQ-FILL-TAT-006: pull compensation preserves region topology |
| Case | Input + parameters + profile + expectations, as a data file, linked to ≥ 1 requirement |
| Check | A reusable assertion (an invariant, a metric bound, a golden comparison, an expected diagnostic) |
| Level | Where the case’s truth comes from (L0 invariants … L4 a real machine) |
| Bless | Accept a changed golden output, in a PR, with a reason |
| Deviation | A documented, intended difference from Ink/Stitch’s behaviour |
Requirements
conformance/requirements.toml:
[[req]]
id = "REQ-FILL-TAT-006"
area = "fill/tatami"
level = "L2"
statement = "Pull compensation preserves the number of holes and connected components of the region."
rationale = "Compensating by growing the whole shape closes gaps the designer left on purpose."
milestone = "M5"
status = "planned" # planned | active | retired
Rules, enforced in CI:
- ids are unique (
--check), and never reused: a retired requirement stays in the file asretired. The checks read the file as it is, so review keeps the second rule.--checkhelps, failing for a case that names an id the file lacks, anddocs --checkfails for a page that names one; - every case names existing requirements, and an
activerequirement has at least one case (--check) that passes (the run); - a milestone cannot be closed while one of its requirements is
planned(review); - docs that mention a
REQ-…id must refer to an existing one (part ofcargo xtask docs --check).
Cases
Cases come in two forms, and the report treats them alike.
Rust tests named after a requirement. A test function named req_<area>_<nnn>_<what> is a case for
REQ-<AREA>-<NNN>: req_plan_002_stitch_lengths proves REQ-PLAN-002, req_fill_tat_006_gap_preserved
would prove REQ-FILL-TAT-006. The name is the link, so there is no list to keep in sync; --check finds
them by scanning the source and rejects a name that points at no requirement. Use them for rules best
shown with small hand-built inputs — an invariant’s violating and passing examples, a palette’s spot
checks.
Rust tests named after a diagnostic code. A test function named diag_sc_<e|w|i><nnnn>_<what> is a
case for the code SC-<E|W|I><NNNN>: it produces the code from real input through the public API and
checks the full text a user reads, message and fix (diag_sc_e0701_a_design_larger_than_the_hoop_names_both_sizes).
Every registered code needs one: --check fails for a code without a diag_ case and for a diag_ name
that points at no registered code, and the run fails when a code’s cases do not pass. The report lists
the codes in a table of their own. This is what keeps error paths alive
(diagnostics).
Data cases, conformance/cases/<area>/<name>.toml: an input, a profile and expectations, run by the
suite in-process. Unknown fields are errors, so a typo cannot silently switch a check off. M1 has the
testsheet kind:
id = "ts-10b"
requirements = ["REQ-PLAN-001", "REQ-PLAN-002", "REQ-PLAN-003", "REQ-PLAN-005", "REQ-PLAN-006", "REQ-PRF-002", "REQ-FMT-001"]
kind = "testsheet"
sheet = "TS-10B"
profile = "brother-pe800-5x7"
[expect]
size_mm = [130.0, 180.0]
diagnostics = [] # exactly these codes, here none
golden = ["golden/testsheets/TS-10B.pes"] # byte for byte; the format comes from the extension
The plan invariants run on the sheet’s plan before anything else. The golden files are the very bytes sewn at the machine checkpoint, so a change to how a sheet sews cannot slip through unnoticed.
The oracle kind reads machine files with an independent reader — a pinned pyembroidery, through
conformance/oracle/decode.py — and with StitchCraft’s, and requires the machine to do the same thing
either way (REQ-FMT-005):
id = "pyembroidery-oracle"
requirements = ["REQ-FMT-005"]
kind = "oracle"
files = ["golden/formats/every-command.pes", "golden/testsheets/TS-01.dst"] # …
It needs Python with pyembroidery (STITCHCRAFT_PYTHON, see conformance/oracle/requirements.txt):
without it the case is skipped locally (⏭ in the report) and fails in CI, like every optional tool. A
requirement whose cases were all skipped is skipped too, and fails the run only in CI.
Geometry that Ink/Stitch leaves to shapely is checked against shapely’s own answers. Scripts in
conformance/oracle/ ask a pinned shapely, through its public API alone, about random shapes:
- the nearest points of rails (
nearest.py) - offset curves (
offset.py) - the measures that make a satin column’s rails from its centre line (
centre_line.py) - polygons shrunk and grown, for a fill’s inset underlay and its expanded rows (
buffer.py) - the pieces of rows that polygons cut, for a fill’s rows (
rows.py)
Their answers are fixtures under conformance/fixtures/geometry/, one question to a line of text, and the
engine’s unit tests read them with a shared reader (normalize::fixture) and answer every question the
same way. Regenerating a fixture needs shapely, and checking it does not.
The plan kind takes a design, an SVG file or a VectorCraft document, the whole way to machine files, as
stitch plan does for a profile. It checks the plan, and compares the files written from it with golden
files. The strokes case:
# The whole way from a design to a machine file: an SVG of strokes in two threads and back, two lines
# that nearly touch and a fill in 2 parts, each with its underlay, planned as `stitch plan` plans it. The
# golden files are what that writes.
id = "strokes"
requirements = ["REQ-ASM-001", "REQ-ASM-002", "REQ-LCK-001", "REQ-FIN-001", "REQ-FIN-003", "REQ-FILL-TAT-001", "REQ-FILL-TAT-008", "REQ-FILL-TAT-011"]
kind = "plan"
design = "fixtures/svg/strokes.svg"
profile = "brother-pe800-5x7"
[expect]
size_mm = [59.920508, 45.0]
diagnostics = ["SC-W0307", "SC-I0504"]
golden = ["golden/plans/strokes.pes", "golden/plans/strokes.dst"]
What a generator keeps of a shape, such as the holes of a fill, is checked by Rust tests named after their
requirement. Fixtures are small files under conformance/fixtures/, hand-written or generated, each with
a row in the fixtures index stating its origin and licence (--check). Large real-world corpora will
live in a repository of their own, as VectorCraft’s do. cargo xtask corpus (M8) will download them at a
commit and with a SHA-256 this repository pins.
The engine’s cases build their small designs with stitchcraft_testkit::designs. It also reads a plan’s
shape at a glance: J L4 S5 L4 is a jump, 4 lock stitches, 5 needle points of stitching and 4 more lock
stitches. Its strokes are as wide as an SVG stroke whose style says nothing, and widened gives one a
width, capped a cap, and automatic makes any element one whose stitches StitchCraft chooses. Its fills are closed rings filled by the nonzero rule (fill, through rings), so a hole is drawn
the other way round from its outline. The satin cases sew their columns with stitchcraft_testkit::satins, as an element is sewn on the
reference machine, with the stitch lengths the engine gives it there (satin_lengths), and read the needle
points back in pairs across the column. A column is sewn alone (sewn_satin) or between the neighbours a
case gives it (sewn_between), and satin_diagnostics keeps its warnings’ places for the cases that
check where a warning points. The cases of a satin method sew a straight column by that method
(sewn_ladder) and compare its needle points with the ones they expect, but for rounding (near).
The cases of automatic digitizing sew the test pictures, which stitchcraft_testkit::pictures draws in
code, each with the edges where its shapes meet. There are 3 stripes side by side, and a badge and a row
of badges, each traced abutting or stacked (image to embroidery).
Levels
L0 — Plan invariants (every PR)
The plan invariants run on every plan the suite produces, whatever the case is about. A case never needs to ask for them.
L1 — Formats (every PR; fuzzing nightly)
Golden bytes for canonical plans; round trips on random plans (proptest); every command per format;
empty and degenerate files; the pyembroidery oracle job (pinned version, in its own CI job) decodes
our files and must agree on every stitch; cargo-fuzz targets run nightly with a persisted corpus
(formats).
A file read back must do what its format tells the machine, which can mean more cuts than the plan has.
DST machines cut before long runs of jumps (REQ-FMT-008), and a PES file flags every jump as a trim
(REQ-FMT-010). stitchcraft_testkit::equivalence::format_events gives both, and drops a cut with
nothing sewn since the last one, which changes nothing on the fabric.
Fuzzing
fuzz/ has a target for each reader (read_pes, read_dst, read_svg and read_vectorcraft), and
one for a file’s whole journey (read_write_preview: read, write in every format, read the files back,
preview). Each target is one line. Its body is in stitchcraft-testkit::fuzz, where every pull request
runs it on the seed files, every shortening of them and random or damaged bytes, on stable Rust and all
three operating systems. The properties:
- machine files (
REQ-FMT-006). No reader panics or gives more than 2,000,000 entries or a position beyond ±10 m. Each format writes a plan read from anything or refuses it. PES refuses a design more than 204.7 mm from its start (REQ-FMT-011). Each file reads back to a plan the machine sews the same way, for DST the way DST machines sew it (REQ-FMT-008). Previews never panic. - SVG (
REQ-SVG-002): no panic; the reader refuses only with its own codes (SC-E0801,SC-E0004); what it reads is a valid design, with no point beyond ±10 m, and everything it says about the file is a warning. - VectorCraft (
REQ-VC-001), as for SVG, withSC-E0806for a file that isn’t a VectorCraft document. The same bytes compressed with gzip read the same, unless unpacking them spends the budget.
Every night nightly.yml fuzzes each target for 20 minutes with libFuzzer (-timeout=10,
-rss_limit_mb=2048). The corpus starts from the seeds, the golden machine files or the SVG and
VectorCraft fixtures (with dictionaries of their keywords, fuzz/svg.dict and fuzz/vectorcraft.dict).
It is kept, minimised, in the Actions cache, and each night continues where the last stopped. A crash
fails the job and keeps the input as an artifact. To work on one locally:
cargo +nightly fuzz run read_pes # until stopped; corpus in fuzz/corpus/read_pes
cargo +nightly fuzz run read_pes fuzz/artifacts/read_pes/crash-… # reproduce a crash
A crash becomes a regression test next to the code it found a bug in, before the fix.
L2 — Generator properties (every PR)
Property tests per generator (algorithms), run over:
- hand-written cases (the shapes people actually draw: letters, circles, leaves, rings, stars);
- the degenerate corpus (zero-area, hair-thin, self-touching, collinear, duplicate points, single points, near-coincident rings, huge coordinates, 10⁶ vertices);
- random shapes from
stitchcraft-testkitstrategies, with fixed seeds in PR CI (fast, reproducible) and fresh seeds nightly (with failing seeds saved as new cases).
Metrics are defined once in stitchcraft-testkit::metrics so every case measures the same way:
| Metric | Definition |
|---|---|
| Coverage | Rasterize the region and the stitch lines (thread width 0.4 mm) at 0.05 mm; covered fraction of region pixels |
| Edge coverage | Points every 0.05 mm along an edge where 2 shapes meet. The fraction within 0.2 mm of a top stitch, with each stitch pulled in where the thread turns |
| Containment | Maximum distance of any stitch point outside the region (0 if inside) |
| Row spacing | Median and spread of perpendicular distances between consecutive rows |
| Stitch length | Min, max and distribution of Normal stitch lengths by role |
| Furrows | Longest run of rows whose needle points align within 0.1 mm |
| Topology | Holes and components of the region vs. of the stitched area |
Coverage is defined since M5.9 and edge coverage since M13.2, and the other metrics arrive with the cases that need them. A pixel is the region’s when its centre lies inside the region’s rings, by the even-odd rule, and covered when its centre lies within 0.2 mm of a stitch the plan sews, whatever its role: the thread is the stitch widened to 0.4 mm and rounded at its ends. Each stitch is drawn a row of pixels at a time, along the stretch where the row crosses that shape. The work grows with the pixels the thread covers, not with the stitches times the pixels.
Edge coverage pulls each top stitch in where the thread turns, as thread pulls a row in at its ends. The
needle holes along a straight run stay where they are. Underlay and locks are left out, as they are
meant to be covered (stitchcraft_testkit::metrics).
L3 — Ink/Stitch differential (nightly)
A pinned Ink/Stitch release runs as a black box on a corpus of Ink/Stitch-authored SVGs (our own or
CC0), in a container, producing stitch files. We compare metrics, not stitches: stitch count
(±10 %), bounds (±0.5 mm), colour sequence (exact), trims and jumps (±1), coverage IoU (≥ 0.9). Running
Ink/Stitch as an oracle uses it, it does not copy it; implementers see metric reports, not Ink/Stitch
code. Differences we intend are recorded in the deviations ledger (conformance/deviations.toml),
each with a reason and a link to the requirement that motivates it (for example DEV-LCK-001: the
lock shapes are StitchCraft’s own). Starts in M8.
L4 — Physical (milestone gates)
Machine checkpoints sew generated test sheets on the target machine and record the results through the
Sew-out report issue form (machine testing). A checkpoint result is
linked from the requirements it validates (physical = ["MC-1"]), and a profile value can change only
with such a record.
The runner and its report
cargo xtask conformance runs every data case in-process and every Rust-test case through one cargo test, then writes target/conformance/:
report.md— the requirement × case matrix, failures first, requirements nobody tests yet folded away; in GitHub Actions it is appended to the job summary, so every pull request shows it;report.json— machine-readable results, for the docs (requirement pages will show their status);diffs/— for a machine-file golden that differs, previews (simple style) of the golden file and of the new output, so a reviewer sees what sews differently; CI keeps them as a workflow artifact when a run fails.
It fails when a case fails or an active requirement has no passing case. --filter <text> runs only the
cases whose id, test name or requirements contain the text (and skips the “every active requirement”
rule, since the run is partial). cargo xtask ci runs the whole suite; the docs workflow runs --check.
cargo xtask conformance --bless <case> rewrites a data case’s golden files. CI refuses a pull request
that changes files under conformance/golden/ unless it carries the golden-change label and a line in
CHANGELOG.md. For the canonical-plan goldens of the format tests, bless with
STITCHCRAFT_BLESS=1 cargo test -p stitchcraft-formats --test golden; for the preview goldens
(REQ-RND-002), with STITCHCRAFT_BLESS=1 cargo test -p stitchcraft-render --test preview. Later, stitch conformance run
will let users validate a machine profile with the same cases.
Gates
| When | What must pass |
|---|---|
| Every PR | L0, L1 (no fuzzing), L2 with fixed seeds, --check rules, the data cases’ golden files on Linux, macOS and Windows (cross-platform check), coverage floors, mutants in the changed lines |
| Nightly | L1 fuzzing, L2 fresh seeds, L3 differential |
| Weekly | Mutation testing of every shipped crate, against the recorded counts |
| Milestone close | All requirements of the milestone active and green; its machine checkpoint signed off |
| Release | Everything above, plus the compatibility gate green on VectorCraft stable |
Unit, snapshot and doc tests still exist
Conformance cases describe behaviour users see. Unit tests (in modules), snapshot tests (insta, for
intermediate structures such as cell decompositions) and CLI examples in the docs (trycmd) still cover
internals and documentation; mutation testing (cargo-mutants) and a coverage ratchet (cargo-llvm-cov)
measure how well all of them together bite (guardrails).
Both are ratchets. conformance/coverage.toml holds a line-coverage floor per crate, which a pull
request may not fall below and cargo xtask coverage --record only raises. conformance/mutation.toml
holds, per crate, how many mutants no test notices: the weekly run fails when a crate has more, and
cargo xtask mutants --record only lowers the counts. The weekly job summary lists every missed mutant,
which is the to-do list for better tests. Each mutant is judged by its own crate’s tests
(.cargo/mutants.toml), so each crate answers for its own code.
New code answers for itself at once: every pull request runs the mutants in the lines it changes
(cargo mutants --in-diff, minutes rather than the full run’s quarter of an hour per part), and
cargo xtask mutants --changed fails for any that no test notices. The exception is an equivalent
mutant, which changes nothing a test could observe; it is listed under [[equivalent]] in
conformance/mutation.toml with its file, its description, the source line it changes and why, so a
listed equivalent stops counting as one as soon as its line changes.
A mutant can also break the condition that ends a loop, as when a walk over a graph never takes away the
edges it has used. The loop then runs until the work budget ends or cargo-mutants’ timeout stops it,
which counts apart. A loop that keeps 40 bytes for each unit of work wants 20 GB by the end of the
default budget, more than a CI runner has, and the job ends without a result when the runner runs out of
memory. So in the mutants jobs each test binary runs with at most 4 GiB of address space, through
prlimit as Cargo’s runner: such a mutant aborts at the cap, which counts as noticed.
Mutation testing runs the tests hundreds of times for one pull request. A test that writes files puts
them in a folder of its own (stitchcraft_testkit::temp::TempDir), which goes with everything in it
when the test ends, whether it passed or failed. Until October 2026 the command line’s tests left their
folders in the system’s temporary folder: 14 folders and about 65 MB on each run of the workspace’s
tests.
Determinism
Same input, same version ⇒ byte-identical machine file and preview, on Linux, macOS, Windows and wasm32. This makes golden-file conformance possible, makes bug reports reproducible, makes caches safe, and means the docs images regenerated in CI match the ones on a contributor’s laptop.
Sources of non-determinism and how we remove them
| Source | Rule | Enforced by |
|---|---|---|
| Hash map iteration order | No HashMap/HashSet in engine, formats, render, params or adapters’ output paths; use BTreeMap, BTreeSet, IndexMap or sorted Vec | clippy::disallowed_types in clippy.toml |
Platform libm (sin, cos, atan2, exp, log, powf and their kin differ by last bits across OSes, and powi’s precision varies even between calls) | Call transcendental functions only through stitchcraft_core::math, which uses the pure-Rust libm crate, and square by multiplying | clippy::disallowed_methods on f64::sin & co., and on the f32 ones |
| Which side of a line a point lies on, or of a circle: rounded, the answer comes out wrong for some points nearly on the line or the circle | stitchcraft_core::exact::side gives it exactly for the offset curves, and exact::incircle for the centre lines’ triangulation (below) | unit tests against exact integer arithmetic |
| Randomness | Only stitchcraft_core::rng::SplitMix64, seeded per element from random_seed or a hash of the element id, with the OS’s randomness and the time kept out | clippy::disallowed_types on RandomState, SystemTime and Instant; deny.toml lets rand and getrandom in only through the property tests’ proptest |
| Floating-point contraction (FMA) | Rust does not contract a * b + c implicitly, and we never call mul_add | clippy::disallowed_methods on f64::mul_add and f32::mul_add, everywhere |
| Parallelism | Library crates run on the caller’s thread alone. The parallel planning that the engine pipeline plans for the command line joins its results in document order | clippy::disallowed_methods on std::thread::spawn and its kin |
| Sorting ties | Every sort uses a total order with an explicit tie-breaker (f64::total_cmp, then index) | review checklist; property tests shuffle inputs |
| Quantization drift | Quantize absolute positions once, at encode time, with round-half-even (Point::to_tenths, shared by the writers and the previews); deltas are differences of quantized values | format conformance (REQ-FMT-002, REQ-RND-001) |
| Rasterization (SIMD code paths, platform trigonometry in curve flattening) | tiny-skia without its simd feature; previews draw only lines and circles; grid positions convert to f32 exactly; pure-Rust PNG encoding without timestamps (rendering) | golden PNG files compared on all three operating systems (REQ-RND-002); f32 transcendental methods are disallowed like the f64 ones |
| Dependency upgrades | Cargo.lock committed; golden files re-blessed only in a PR that explains why | CI + review |
Exact orientation
Whether a point lies left of a line, right of it or on it is the sign of a cross product. Rounded, that
sign can come out wrong for points nearly in line. Geometry built on a wrong sign tears, with a corner
turned the wrong way or two crossing segments taken to miss. stitchcraft_core::exact::side decides the
sign in plain floating point when the product is farther from zero than its largest possible rounding
error, and otherwise computes it exactly as a sum of floats that do not overlap, with the error-free
sums and products of J. R. Shewchuk’s Adaptive Precision Floating-Point Arithmetic and Fast Robust
Geometric Predicates (1997). Where both of its products are 0, as for points level or upright with each
other, the cross product is exactly 0 without that sum, since far from underflow a product is 0 only where
a factor is. It uses only +, − and ×, and every platform gets the same answer, which is the true one.
stitchcraft_core::exact::incircle decides in the same way whether a point lies inside the circle through 3
others. Its answer is the sign of a 4 × 4 determinant. That is computed in plain floating point when it lies
farther from zero than Shewchuk’s bound on its error, and exactly otherwise. A Delaunay triangulation that
flips its edges by a rounded test can flip one edge back and forth forever, and one built on the exact test
stops.
The seeded PRNG
SplitMix64 (Steele, Lea & Flood, 2014) is tiny, fast, statistically adequate for jitter, and fully
specified by its 64-bit state update — so its output can be written into a conformance case and will
never change with a dependency upgrade. An element’s generator starts from its random_seed, mixed once
by SplitMix64 itself, or, when it has none, from the FNV-1a hash of its id. A seed replaces the id, as in
Ink/Stitch, so elements given one seed draw the same numbers. A part of an element can draw from its own stream
of the element’s generator (SplitMix64::stream). The part’s place keys the stream, so a change to one
part leaves the numbers of the others as they were. A running stitch keys one for each span between
corners. The derivations are documented and tested with fixed vectors.
Cross-platform check
ci.yml runs cargo test on Linux, macOS and Windows for every pull request. Among the tests,
xtask’s every_data_case_but_the_oracle_passes_here runs the conformance suite’s data cases. Each test
sheet and plan is made again and encoded, and its machine files must equal their golden files byte for
byte, so all three operating systems make the bytes the machine checkpoints sewed. The previews’
golden files (REQ-RND-002), the formats’ canonical plans (REQ-FMT-001) and the frozen reference
values of stitchcraft-core’s tests are compared on all three as well. The oracle case reads the files
with pyembroidery, which only the Linux gates job installs.
WebAssembly is checked too, since VectorCraft runs the engine as WebAssembly. cargo xtask wasm checks
that the library crates and the plug-in build for wasm32-unknown-unknown, and the lints above keep the
library crates off what that target lacks. The wasm-goldens gate (cargo xtask wasm --goldens) builds
stitch for wasm32-wasip1 and runs it under wasmtime on every test-sheet and plan case, each with the
command line the case stands for. Each machine file it writes must be its golden file byte for byte. WASI
gives stitch the files it reads and writes. Both are WebAssembly, and the engine’s maths is its own, so
its arithmetic is the plug-in’s.
Bug reports
Because the same design, profile, format and version give the same plan everywhere, those four are all a
bug report needs, with the switches the design was read with. --auto decides how its shapes without
settings sew, and --sort-colours whether its shapes with settings sew in the order StitchCraft chooses.
stitch bug-report writes them into one JSON file, with the design itself in place of a path to it, and
each switch only when it was on. A bundle of a run without them is as it always was. The design is an SVG
file whole, or the part of a VectorCraft document its reader reads, which
reads as the whole document does. The file also records what came of them:
- a SHA-256 of each entry of the plan, from each coordinate’s bits, its kind, role and element, and each block’s thread
- the machine file’s size and SHA-256, and the design name its label holds, which the replay uses as it is
- the first line of each diagnostic, and a panic’s message if there was one, with the phase it came in (planning, drawing the picture or writing the files)
A replay plans the design again, and draws the picture again for a panic that came while drawing it. It
leaves the files unwritten. stitch --version gives the version a build will be released as. Builds from a
git checkout add their commit, which bundles record too. A replay reads the bundle’s format first, and
refuses a format it does not know by its number, whatever else the file holds.
stitch bug-report --replay plans the design again through the same code as stitch plan and compares
each record (REQ-CLI-001). stitch plan writes a bundle by itself when it finds a bug in StitchCraft
(REQ-CLI-002). The user guide’s Report a bug shows both.
The plan’s digest is written out entry by entry rather than from the plan’s Debug text, so that a
compiler that prints numbers differently cannot change it.
What is not promised
- Output across StitchCraft versions: a new version may change stitches. Changes to golden files are reviewed and listed in the changelog.
- Wall-clock timings: budgets count work units precisely so that results never depend on speed.
Guardrails
Guardrails keep the code clean while many people (and agents) change it. Each one below is a check
that runs automatically — locally with cargo xtask ci, and on every pull request in GitHub Actions —
so the rules hold without anyone having to remember them. Most are adopted from VectorCraft; the
ADR-0009 audit says which, and what we changed.
The checks
| Guardrail | Enforces | Configured in | Runs |
|---|---|---|---|
| Format | One style, no bikeshedding | rustfmt.toml | cargo xtask ci · CI |
| Clippy, warnings denied | Idiomatic, bug-prone patterns flagged | workspace [lints], clippy.toml | cargo xtask ci · CI |
| No panics | No unwrap, expect, panic!, unreachable!, todo!, unimplemented!, dbg! outside tests; indexing_slicing denied in parsers | workspace [lints.clippy], crate attributes | cargo xtask ci · CI |
No unsafe | unsafe_code denied in the workspace and forbidden at every crate root, the compatibility probe’s included, except the plug-in’s: its ABI shim alone may use unsafe, with a // SAFETY: comment of its own above each block | workspace [lints.rust], cargo xtask unsafe-audit | cargo xtask ci · CI |
| Determinism | No HashMap/HashSet, RandomState, platform sin/cos/atan2/powi and their kin, mul_add, SystemTime or Instant; no files, threads, environment or processes in library crates; rand and getrandom only through proptest | clippy.toml disallowed-types / disallowed-methods, deny.toml [bans] | cargo xtask ci · CI |
| Layering | Crates depend only on lower layers (architecture); dependencies like clap or tiny-skia only where allowed | xtask/src/layers.rs (append-only table) | cargo xtask layers · CI |
| Module size | Rust files warn above 800 lines, fail above 1,500 (generated files exempt) | xtask/src/filesize.rs | cargo xtask filesize · CI |
| WebAssembly | L0 to L3 crates and the plug-in build for wasm32-unknown-unknown, and stitch, built for WASI and run by wasmtime, makes every golden file of the conformance cases byte for byte | xtask/src/wasm.rs, xtask/src/conformance/wasm.rs | cargo xtask wasm · cargo xtask wasm --goldens · CI |
| Movable into VectorCraft | Every package is stitchcraft-*; crates inherit only workspace keys VectorCraft defines; tooling works from its own folder on StitchCraft’s packages (ADR-0011) | xtask/src/layers.rs, xtask/src/util.rs, xtask/src/join.rs | move job in compat.yml · daily and on PRs touching manifests or tooling |
| No GPL code | No GPL/AGPL licence text, in any case, nor a module’s short GPL notice, and no pasted Python source anywhere in the repository: every text file, documents and files without an extension included (ADR-0012) | xtask/src/cleanroom.rs | cargo xtask cleanroom · CI |
| Docs | Generated pages fresh and none left over; links, anchors (as mdBook makes them) and included files valid; mentioned cargo xtask commands exist; REQ- and DEV- ids exist, and a code is registered once its milestone is done; no long sentence on two pages; every repository path named exists | xtask/src/docs.rs, xtask/src/docs_audit.rs, xtask/src/markdown.rs | cargo xtask docs --check · CI |
| Design pages follow the code | Every source file of a crate or app is on a page’s implements comment, and a branch that changes those files changes the page too or records a Design-reviewed: line | xtask/src/design_map.rs, .claude/settings.json (names the pages after each edit) | cargo xtask docs --check · CI |
| Docs images | Every image a page shows is one a shot in docs/shots.toml writes, with alt text, and regenerating to the committed file. VectorCraft’s own pictures, which its pinned release makes, regenerate to within a tolerance of theirs, and its plug-in dialogs show a label for each parameter | xtask/src/shots/, compat/vc-window/tests/shots.rs | cargo xtask shots --check · CI; cargo xtask shots --vectorcraft --check · shots-ui job |
| Conformance | Requirement/case consistency; every fixture has its row in the fixtures index; all cases pass; changed goldens need the golden-change label and a changelog line | xtask/src/conformance/, ci.yml, goldens.yml | cargo xtask conformance · CI |
| Dependencies | Licence allow-list (GPL family denied), advisories, duplicates, sources, for the workspace, the fuzz targets’ and the window probe’s. An advisory accepted is listed with its reason | deny.toml | cargo xtask ci · CI (cargo-deny) |
| Spelling | Typos in code, docs, commit-facing text | typos.toml | CI |
| Prose | New Markdown lines follow the writing style: the house rules and ai-tells, a style for phrasing that machine-written text overuses | .vale.ini, .vale/styles/, xtask/src/prose.rs (Vale) | cargo xtask prose · CI |
| MSRV | Builds on the declared rust-version | workspace Cargo.toml | CI |
| Cross-platform | Tests on Linux, macOS and Windows, among them the conformance suite’s data cases, whose machine files must equal the golden files on each, and the same machine files made as WebAssembly (determinism) | ci.yml | CI |
| API docs | Rustdoc builds without warnings: no broken, ambiguous or private intra-doc links | cargo xtask ci (RUSTDOCFLAGS=-D warnings) | cargo xtask ci · CI |
| Public API review | Changes to a library crate’s public API show in the PR diff, as a change to its public-api.txt | xtask/src/api.rs (cargo-public-api, pinned) | cargo xtask api --check · CI |
| Coverage ratchet | No crate’s line coverage drops below its floor, and floors only go up. A crate below its floor has its untested lines named on the pull request, those only another build of a function or a closure on the line runs included | conformance/coverage.toml, xtask/src/coverage.rs (cargo-llvm-cov) | cargo xtask coverage · CI job |
| Performance budget | Planning the reference hoop’s whole field (REQ-FILL-TAT-009) takes at most 2 % more instructions than recorded. Records only go down. A count below half the record fails too: the counted function was lost. Its time is reported against NFR-PERF-1’s 50 ms, with a warning above it | conformance/perf.toml, xtask/src/perf.rs (valgrind’s callgrind), the engine’s field example | cargo xtask perf · CI job |
| Mutation testing | Tests notice changed behaviour: every mutant in the lines a pull request changes is noticed or a listed equivalent; no crate gets more mutants no test notices than recorded, and records only go down. A weekly part whose unmutated tree failed its tests, or a recorded crate with no mutant tried, fails | .cargo/mutants.toml, conformance/mutation.toml, xtask/src/mutants.rs | mutants.yml · changed lines every PR, in 4 parts · everything weekly |
| Fuzzing | Readers never panic, respect their caps and terminate; anything read round-trips and previews | fuzz/, stitchcraft-testkit::fuzz (conformance) | bodies every PR · an hour nightly |
cargo xtask ci runs everything that does not need extra tools; tools that are not installed locally
are reported as skipped, while CI installs them and treats them as required. cargo xtask refuses an
option it does not know with exit status 2, before anything runs, so a misspelt --check cannot run a
command’s other mode. In GitHub Actions every
problem a check finds is also an annotation, on the right line of the pull request’s diff when it names
one, and a failed fuzz run puts the end of its output in one: the reason is visible without opening a
log.
The performance budget counts instructions, because they come out the same on any machine and in any run, where milliseconds do not. Its check fails for a change that slows planning, on whichever runner it runs. The time is still measured and reported, against the budget the requirement states in milliseconds.
Coverage counts each build of a function apart and takes the build that runs the most lines. The unit
tests and the integration tests use separate builds, a generic function has one per type it is used with,
and a closure is a function of its own. So a line can count as untested though the lcov report counts it
run. Only the other kind of test runs it, or only a closure on the line runs, as with a ? after a
closure argument whose error the tests never bring. The coverage job names these lines and the function
they count against. Run them from the kind of test that runs most of the function.
cargo xtask coverage runs the job locally. It needs cargo-llvm-cov and rustup’s llvm-tools. Without
the latter, LLVM_COV and LLVM_PROFDATA can name another LLVM’s llvm-cov and llvm-profdata that read
the toolchain’s profiles: LLVM 20’s read those of Rust 1.97.
Design rules (reviewed, partly linted)
- Everything user-visible is data first. A stitch type, parameter, diagnostic, profile or format is registered in its registry; UIs, docs and tests are generated from registries.
- Validate at the edge, trust inside. Untrusted inputs are parsed into validated types once
(
Mm,Region, typed params); internal functions take those types and do not re-check. - Total functions. Every function returns for every input it accepts; impossible states are made unrepresentable by types rather than asserted.
- Bounded work. Loops over data charge a
Budget; recursion has an explicit depth bound. - No silent fallbacks. A degraded result always comes with a coded diagnostic.
- One crate, one error enum (
thiserror), with messages a user can act on. - Docs on every public item (
missing_docsdenied in library crates); module docs explain why. - Tests next to code, conformance cases in
conformance/, integration tests intests/.
Contribution flow
- One roadmap step per PR, titled with its id:
M3.4: running stitch even spacing. Small PRs (target under ~400 lines of non-generated change) review faster and revert cleanly. - Conventional commit scopes (
feat(engine):,fix(formats):,docs:) feed the changelog. - The PR template asks for: the step id, requirements touched, cases added, docs updated, golden changes justified, screenshots refreshed if UI changed, whether a sew-out is needed.
- Required checks on
main(branch protection): thecianddocsworkflows. Merges are squash merges;mainis always releasable. - Playbooks make common changes mechanical: new stitch type, new parameter, new format, new diagnostic, and the review checklist.
- Agents (Claude Code and others) start at
AGENTS.md, which links here; the same gates apply.
Documentation pipeline
The bar: documentation that cannot silently drift from the code. Hand-made screenshots, parameter lists kept apart from the code and an unversioned site all go stale without anyone noticing. Ours are generated where they can be, tested where they cannot, and regenerated on every pull request.
Structure
The site is an mdBook in docs/, organised by the
Diátaxis framework so each page has one job:
| Section | Job | Examples |
|---|---|---|
| Tutorials | Learn by doing, start to finish | “Your first sew-out on a Brother”, “A patch in VectorCraft” |
| How-to guides | Solve one task | “Stop a fill from puckering”, “Export for a different machine” |
| Reference | Look things up (mostly generated) | parameters, diagnostics, CLI, machine profiles, formats, compatibility |
| Explanation | Understand why | embroidery basics, pull and push, underlay, how routing works |
| Design / Plan / Contributing | Build StitchCraft | this page, the roadmap, playbooks |
The print.html page mdBook builds is the printable manual, and mdBook’s built-in search covers
everything.
Sources of truth → generated pages
| Source (code or data) | Generated page(s) | Generator |
|---|---|---|
| Parameter registry | one page per stitch type with every parameter, its range, default, units and Ink/Stitch name; JSON Schema | cargo xtask docs |
| Diagnostics registry | diagnostics index, one page per code | cargo xtask docs |
clap CLI definition | CLI reference | cargo xtask docs |
| Machine profiles | machine profile reference | cargo xtask docs |
| Format modules (limits, commands) | format reference | cargo xtask docs |
conformance/inkstitch-params.toml + registry | compatibility contract with status | cargo xtask docs |
| Compatibility runs | supported VectorCraft versions | cargo xtask compat report (M6.6) |
Generated pages are committed (reviewers see user-facing changes in the diff) and marked with a header
comment; cargo xtask docs --check regenerates them in memory and fails if anything differs, or if a
file in a generated directory has lost its generator. The header is for people. The checks know
a generated page by the generator that writes it, so a page that only says it is generated is checked as
one written by hand. The conformance report is not a page of the book: cargo xtask conformance writes it
to target/conformance/, and CI shows it in the job summary.
Images: four kinds, each reproducible
Every image in the docs is declared in docs/shots.toml with an id, a kind and how to make it. Pages
reference images/generated/<id>.png, or a photo in images/photos/. An image at any other path fails the
docs check, whatever its file name.
| Kind | Made by | Compared by | Notes |
|---|---|---|---|
stitch | stitchcraft-render (rendering) from a test sheet, or from a design the engine plans, an SVG file or a VectorCraft document | exact bytes (rendering is deterministic) | Stitch-type illustrations, parameter comparisons, diagnostics examples |
vectorcraft-render | vectorcraft-cli of the pinned stable release, with the plug-in’s modules installed: the canvas after the shot’s steps, exported as PNG at scale pixels per point | perceptual tolerance (≤ 0.2 % of pixels differing by more than 8/255 in a channel, and ≤ 8 of them in any square of 16 pixels a side) | How the previews look in VectorCraft’s canvas, without its window |
vectorcraft-ui | VectorCraft’s window, of the same release, with the document vectorcraft-cli drew. The shot’s menu path and the family’s row are pressed through the accessibility tree, and the screenshot is cropped to the menus or the dialog | the same tolerance | Menus and dialogs, in a desktop without a screen (VectorCraft’s window in CI) |
photo | a person, of a real sew-out | presence + metadata record (machine, fabric, commit, checkpoint) | Never regenerated; listed with their sew-out report |
A shot declaration:
[[shot]]
id = "running-length"
kind = "stitch"
fixture = "docs/fixtures/wave.svg" # planned by the engine for the reference machine, as `stitch plan` plans it
style = "simple"
scale = 8.0 # pixels per millimetre
alt = "A wave in running stitch"
panels = [
{ caption = "1.5 mm", params = { running_stitch_length_mm = "1.5" } },
{ caption = "4 mm", params = { running_stitch_length_mm = "4" } },
]
Each panel sets its parameters on every element of the design and becomes one image, running-length-1.png
and so on. A test sheet is drawn as it is, so a shot of one has no panels. The plan may report only the codes the shot lists in allow, and a shot fails on any other:
a picture never shows a design that the engine changed without the page saying so. The alt text is
mandatory (accessibility), and the docs check fails on images without it.
A VectorCraft shot draws its document with VectorCraft’s own commands, as a Level B scenario does (compatibility gate), in points:
[[shot]]
id = "vectorcraft-fill-dialog"
kind = "vectorcraft-ui"
plugins = ["stroke", "satin", "fill"] # the modules VectorCraft installs
steps = [
{ cmd = "file.new", params = { width = 144, height = 144 } },
{ cmd = "shape.ellipse", params = { x = 12, y = 12, width = 120, height = 120 } },
]
menu = ["Effect", "Plug-ins"] # the menu bar's title, then rows
dialog = "fill" # the picture shows that family's dialog
alt = "The StitchCraft Fill dialog in VectorCraft…"
cargo xtask shots --vectorcraft makes these shots. vectorcraft-cli runs the steps and ends the run
with a drawing, which runs the live effects. Each module is asked for its last error after it, and a shot
with one fails. VectorCraft keeps a module’s last error only until its next success: a shot applies each
stitch family once at most, and one that applies a family twice is an error. A render shot’s picture is
that drawing. A UI shot opens the document in VectorCraft’s window and needs a menu, and with a
dialog the picture shows the dialog of that family’s effect.
A render shot can keep its document as a file of the repository, document, for the pages that sew it:
the tutorial’s patch is sewn from docs/fixtures/round-patch.vectorcraft, which VectorCraft draws from
the shot’s steps. The generator writes the file while the committed one sews otherwise than the drawing,
and --check fails then. Another release may save the same drawing otherwise: the file stays while it
sews the same.
A page shows a shot between the lines <!-- shot: running-length --> and <!-- /shot -->. cargo xtask docs writes the figure there. Without panels the figure is one image. With panels it is a table with a
column per panel, its caption on top and its image below it. A shot with layout = "rows" shows its
panels one under another instead, each caption in bold above an image as wide as the page: pictures side by
side shrink to share the page’s width, and a wide picture’s stitches then become too small to tell apart. Each image’s alternative text is the shot’s,
followed by the panel’s caption. The page
cannot show an image under any other text, and a stale figure fails --check like a stale page.
VectorCraft’s window in CI
Spike M0.8 ran VectorCraft’s window in CI, as the vectorcraft-ui kind needs, and drove it through its
accessibility tree. Both work, and M6.7 built that kind’s generator this way.
cargo xtask compat window runs the window probe, compat/vc-window, against the app VectorCraft
releases: bin/vectorcraft from the Linux x86_64 archive of the stable release that
compat/vectorcraft.toml pins, checked against the SHA-256 recorded there. The probe’s tests run in a
desktop of their own, with an X server (Xvfb) and a D-Bus session that nothing else uses:
- Mesa draws the window in software, through OpenGL (
WGPU_BACKEND=gl), with its OpenGL renderer, llvmpipe. Left to choose, the app would take Mesa’s Vulkan renderer wherever that one is installed, and the pixels would depend on the runner. - The accessibility bus is told that a screen reader is on, since AccessKit, VectorCraft’s accessibility
layer, builds the tree only then. The flag stays in the session’s memory (
GSETTINGS_BACKEND=memory). Through GSettings it would reach the person’s own settings, and GNOME would start a screen reader at their next login.
The compat workflow’s window job runs the command daily, and on pull requests that touch the plug-in,
compat/, the tooling or a manifest. It keeps the screenshots, the trees and the app’s logs.
Screenshots: the same pixels, but for the frame timings. The window opens at 1440 × 900 pixels, drawn by llvmpipe, and a screenshot takes about 0.65 s. Above the status bar, screenshots of one window match to the pixel. The screenshots of two starts of the app match there too, and so did two runs on GitHub’s runners. The status bar differs, since it shows the frame timings (“render 4.4 ms · ui 1.4 ms · 24 fps”), which change with every frame. VectorCraft’s own visual fixtures record where that text is, as text that changes. So the generator crops each picture to the menu or dialog it shows. A picture of the whole window leaves the status bar out. The tolerance in the table stays: the runner’s Mesa comes with Ubuntu’s updates, and a new one may move an anti-aliased edge.
xa11y: AT-SPI’s press and clicks both work. xa11y finds the app by its process. AT-SPI’s press, as a
screen reader uses it, opens View and chooses its row Outline. Clicks at the middle of the bounds the tree
gives View and its row Preview, as a person makes them, bring the drawing back. The control channel sees
each change. A row’s name is its label after the room for a check mark, and its shortcut
( Outline Ctrl+Y). With no window manager, the window opens at the screen’s corner, so the tree’s
bounds are the screenshot’s pixels.
The generator: VectorCraft driven to each screen. tests/shots.rs of the probe takes the UI shots
that cargo xtask shots --vectorcraft lists in a job file, in the same desktop. The tree is used as a
person would use the app. AT-SPI’s press opens the menu bar’s title and each row of the shot’s path,
then the row of the family’s effect, and the dialog is the largest element named after the plug-in. The
window opens the document it starts with. The control channel installs the modules, which a person does
with file dialogs. It also selects everything and sizes the window, and it reads the app’s state. A
menus’ picture is cropped to the title and the rows that opened, with 10 pixels around them for the
menus’ frames. A dialog’s picture is cropped to the dialog.
A dialog must show a row for each parameter of its module’s manifest that shows at the defaults, under
the label the manifest gives it (REQ-VC-012). Its control must be the one VectorCraft gives the
parameter’s type. A number gets a text field and a toggle a checkbox, and a choice gets a drop-down list.
At the window’s size when it opens, 1440 × 900, the fill’s and the satin’s dialogs are too tall for it:
the stroke’s is about 850 pixels tall, the fill’s about 1,500 and the satin’s about 2,050. From 0.14
VectorCraft then scrolls the rows, and the picture would show only some of them. The probe fails on a
dialog with a scroll bar. A dialog’s picture is taken in a window 2,400 pixels tall instead, on a screen
of 3000 × 2500.
Command-line examples
Pages show what a command prints by including it from docs/src/user/reference/generated/. The commands
are declared in docs/examples.toml, and cargo xtask docs runs each one with the stitch binary in an
empty directory. A declaration can copy files of the repository into that directory and run commands
that are not shown first. The output is the binary’s own, standard output then standard error, with the
exit status when it is not 0. When a command prints something new, --check fails until the output is
written again. Pages show what the command prints today.
What runs on every pull request
.github/workflows/docs.yml runs two jobs. The steps of the first, check:
cargo xtask docs --checkfails for a generated page that is stale or left over, and a page missing fromSUMMARY.md. A broken relative link,#anchoror included file fails it, with ids made as mdBook makes them, smart punctuation included. A doc that names acargo xtasksubcommand that does not exist fails it too: we found that drift in VectorCraft’s ownAGENTS.md. Ids must be in their files,REQ-…andDEV-…alike, and anSC-…code registered once its milestone is done. An image must be one a shot writes, with alt text.cargo xtask conformance --checkfails when requirements and cases disagree, or a fixture has no row in the fixtures index.cargo xtask shots --checkfails for a declared image that is missing or has no alt text. It also regenerates eachstitchimage and compares it with the committed file, which must come out byte for byte the same. It checks VectorCraft’s shots’ declarations without running VectorCraft.mdbook build docsfails when the book does not build.cargo test --docfails when a Rust snippet does not compile. Command-line examples are generated pages, whichdocs --checkcovers.typosfails on a misspelling (allow-list intypos.toml).
External links are checked nightly by lychee, not on pull requests.
The workflow’s second job, shots-ui, runs cargo xtask shots --vectorcraft --check on Ubuntu, with
the desktop the window needs (.github/actions/desktop). It makes VectorCraft’s pictures with the pinned
release and compares each with the committed file within the tolerance.
When a shot differs, --check writes the new image to target/shots/ and the job keeps it as the
stale-images artifact, next to the committed one in the PR’s diff. shots-ui keeps VectorCraft’s as
stale-vectorcraft-images, with the documents, the window’s screenshots and its records. An image under
docs/src/images/generated/ that no shot declares is an error too, so renamed shots leave nothing
behind.
Refreshing images
Changing a stitch algorithm should change its pictures. The PR author runs cargo xtask shots locally,
and cargo xtask shots --vectorcraft on Linux, with the packages .github/actions/desktop installs and
Rust’s wasm32-unknown-unknown target, which downloads the pinned release. Or a maintainer adds the label
docs:refresh-shots, and docs-refresh.yml regenerates every shot, VectorCraft’s first, and every
generated page. For a pull request from a branch of this repository, it pushes a commit to the branch and
removes the label. For a fork, the run keeps the files as an artifact, and its summary explains how to
apply them. When a picture can’t be made, the others are still committed, and the job fails after them.
GitHub doesn’t start workflows for a push made with a workflow’s token, and the refresh then dispatches
ci, docs and goldens for the new commit itself. The refreshed images go through review like any
other change.
Pages show a shot with the shot’s own alt text: the test-sheets reference is generated with one picture
per stitch shot of each sheet, so a new sheet gets its picture by declaring a shot.
Publishing
- Push to
main→ build and publish to/dev/on GitHub Pages. - Tag
vX.Y.Z→ publish to/vX.Y/and point/latest/at it. Older versions stay online. - Every page shows the version it documents and links to the same page in
/dev/.
Writing rules
- Every user-visible change updates the docs in the same PR (PR template checkbox; reviewers enforce; generated pages update themselves).
- Plain language; define embroidery terms on first use and link the glossary.
- Show, then tell: a picture or a runnable example before the paragraph.
- Never document a parameter by hand: improve its doc comment in the registry instead.
- Explanations cite sources (papers, standards, public references) where they make claims.
Translations (M12)
UI strings use Fluent message files generated from registry labels and help; the docs use
mdbook-i18n-helpers (gettext catalogues). Translations are community-maintained; a translated page shows
when its source changed after the translation, so stale translations are visible instead of misleading.
Ink/Stitch compatibility contract
The parameters an Ink/Stitch SVG can carry, as inkstitch:<name> attributes, its other attributes and
its commands, and when StitchCraft supports each one. This is the interoperability contract: StitchCraft’s
registry uses these names as its keys so files move between the tools unchanged
(ADR-0001).
- Checked against: Ink/Stitch
mainatd59c9ab(2026-09-17). The last line counts the declarations. - Facts only: names, types, units, defaults and applicability. Ink/Stitch’s descriptions are GPL text and are not copied; StitchCraft’s own help text lives in the parameter registry.
- Machine-readable source:
conformance/inkstitch-params.toml; this page is generated from it and from the parameter registry bycargo xtask docs, andcargo xtask docs --checkfails if they disagree: a parameter declared with an Ink/Stitch origin must be listed here with a compatible type, and its default must be the value Ink/Stitch sews. - Default is the value Ink/Stitch’s settings window shows. For a few parameters its code sews another
value when a file sets none, and the cell gives that value after the default, as in
0.1 (0.2 when unset).—means no default: the value is derived from another parameter or the shape. - StitchCraft says whether the parameter registry declares the
parameter. planned: not yet; a design that sets it keeps it unchanged and gets
SC-W0105. not planned: never, and a design that sets it gets the same. registered: StitchCraft reads and checks it under the same key, meaning and default; it changes the stitches once its stitch type or feature lands (see Phase and the parameter’s reference entry). deviates: registered, with a meaning that differs as its reference entry describes. - Phase is when the parameter’s stitch type lands (roadmap);
—when it is not planned.
All stitch types (ties, locks, trims, stops)
| Attribute | Type | Unit | Default | Applies to | Phase | StitchCraft |
|---|---|---|---|---|---|---|
min_stitch_length_mm | float | mm | — | all | P1 (M3) | registered |
min_jump_stitch_length_mm | float | mm | — | all | P1 (M3) | registered |
ties | dropdown | — | 0 | all | P1 (M3) | registered |
force_lock_stitches | boolean | — | false | all | P1 (M3) | registered |
lock_start | combo | — | half_stitch | all | P1 (M3) | deviates |
lock_custom_start | string | — | — | custom | P1 (M3) | deviates |
lock_start_scale_mm | float | mm | 0.7 | back_forth, custom | P1 (M3) | registered |
lock_start_scale_percent | float | % | 100 | arrow, bowtie, cross, star, simple, triangle, zigzag, custom | P1 (M3) | registered |
lock_end | combo | — | half_stitch | all | P1 (M3) | deviates |
lock_custom_end | string | — | — | custom | P1 (M3) | deviates |
lock_end_scale_mm | float | mm | 0.7 | back_forth, custom | P1 (M3) | registered |
lock_end_scale_percent | float | % | 100 | arrow, bowtie, cross, star, simple, triangle, zigzag, custom | P1 (M3) | registered |
trim_after | boolean | — | false | all | P1 (M3) | registered |
stop_after | boolean | — | false | all | P1 (M3) | registered |
Strokes: running, ripple, zigzag, manual
| Attribute | Type | Unit | Default | Applies to | Phase | StitchCraft |
|---|---|---|---|---|---|---|
satin_column | toggle | — | — | all | P1 (M3) | registered |
stroke_method | combo | — | 0 | all | P1 (M3) | registered |
repeats | int | — | 1 | running_stitch, ripple_stitch, zigzag_stitch | P1 (M3) | registered |
bean_stitch_repeats | str | — | 0 | running_stitch, ripple_stitch, manual_stitch, zigzag_stitch | P1 (M3) | registered |
manual_pattern_placement | boolean | — | false | ripple_stitch | P3 (M10) | planned |
running_stitch_length_mm | string | mm | 2.5 | running_stitch, ripple_stitch | P1 (M3) | registered |
running_stitch_tolerance_mm | float | mm | 0.2 | running_stitch, ripple_stitch | P1 (M3) | deviates |
enable_random_stitch_length | boolean | — | false | running_stitch, ripple_stitch | P1 (M3) | registered |
random_stitch_length_jitter_percent | float | ± % | 10 | running_stitch, ripple_stitch | P1 (M3) | registered |
max_stitch_length_mm | float | mm | — | manual_stitch | P1 (M3) | registered |
zigzag_spacing_mm | string | mm | 0.4 | zigzag_stitch | P2 (M7) | registered |
stroke_pull_compensation_mm | float | mm (each side) | 0 | zigzag_stitch | P2 (M7) | registered |
zigzag_angle | float | ° | 0 | zigzag_stitch | P2 (M7) | registered |
line_count | int | — | 10 | ripple_stitch | P3 (M10) | planned |
min_line_dist_mm | float | mm | — | ripple_stitch | P3 (M10) | planned |
satin_guide_pattern_position | combo | — | default | ripple_stitch | P3 (M10) | planned |
staggers | int | — | 0 (1 when unset) | ripple_stitch | P3 (M10) | planned |
skip_start | int | — | 0 | ripple_stitch | P3 (M10) | planned |
skip_end | int | — | 0 | ripple_stitch | P3 (M10) | planned |
flip_copies | boolean | — | true | ripple_stitch | P3 (M10) | planned |
exponent | float | — | 1 | ripple_stitch | P3 (M10) | planned |
flip_exponent | boolean | — | false | ripple_stitch | P3 (M10) | planned |
reverse | boolean | — | false | ripple_stitch | P3 (M10) | planned |
reverse_rails | combo | — | automatic | ripple_stitch | P3 (M10) | planned |
swap_satin_rails | boolean | — | false | ripple_stitch | P3 (M10) | planned |
grid_size_mm | float | mm | 0 | ripple_stitch | P3 (M10) | planned |
grid_first | boolean | — | false | ripple_stitch | P3 (M10) | planned |
scale_axis | dropdown | — | 0 | ripple_stitch | P3 (M10) | planned |
scale_start | float | % | 100 | ripple_stitch | P3 (M10) | planned |
scale_end | float | % | 0.0 | ripple_stitch | P3 (M10) | planned |
rotate_ripples | boolean | — | true | ripple_stitch | P3 (M10) | planned |
join_style | dropdown | — | 0 | ripple_stitch | P3 (M10) | planned |
random_seed | random_seed | — | — | running_stitch, ripple_stitch | P1 (M3) | registered |
Satin columns
| Attribute | Type | Unit | Default | Applies to | Phase | StitchCraft |
|---|---|---|---|---|---|---|
satin_column | toggle | — | — | all | P1 (M4) | registered |
satin_method | combo | — | 0 | all | P1 (M4) | registered |
random_width_decrease_percent | float | % (each side) | 0 | all | P1 (M4) | registered |
random_width_increase_percent | float | % (each side) | 0 | all | P1 (M4) | registered |
random_zigzag_spacing_percent | float | ± % | 0 | all | P1 (M4) | registered |
split_method | combo | — | 0 | all | P1 (M4) | registered |
max_stitch_length_mm | float | mm | — | all | P1 (M4) | registered |
random_split_jitter_percent | float | ± % | 0 | default | P1 (M4) | registered |
random_split_phase | boolean | — | false | default | P1 (M4) | registered |
min_random_split_length_mm | float | mm | — | default | P1 (M4) | registered |
split_staggers | float | — | 4 | staggered | P1 (M4) | registered |
short_stitch_inset | float | % | 15 | all | P1 (M4) | registered |
short_stitch_distance_mm | float | mm | 0.25 | all | P1 (M4) | registered |
zigzag_spacing_mm | float | mm/cycle | 0.4 | all | P1 (M4) | registered |
pull_compensation_percent | float | % (each side) | 0 | all | P1 (M4) | registered |
pull_compensation_mm | float | mm (each side) | 0 | all | P1 (M4) | registered |
push_compensation_mm | float | mm (each side) | 0 | all | P1 (M4) | registered |
reverse_rails | combo | — | automatic | all | P1 (M4) | registered |
swap_satin_rails | boolean | — | false | all | P1 (M4) | registered |
running_stitch_length_mm | float | mm | 2.5 | all | P1 (M4) | registered |
running_stitch_tolerance_mm | float | mm | 0.1 (0.2 when unset) | all | P1 (M4) | deviates |
running_stitch_position | float | % | 50 | all | P1 (M4) | registered |
start_at_nearest_point | boolean | — | true | all | P1 (M4) | registered |
end_at_nearest_point | boolean | — | true | all | P1 (M4) | registered |
contour_underlay | toggle | — | — | all | P1 (M4) | registered |
contour_underlay_stitch_length_mm | float | mm | 3 | all | P1 (M4) | registered |
contour_underlay_stitch_tolerance_mm | float | mm | 0.2 | all | P1 (M4) | registered |
contour_underlay_inset_mm | float | mm (each side) | 0.4 | all | P1 (M4) | registered |
contour_underlay_inset_percent | float | % (each side) | 0 | all | P1 (M4) | registered |
center_walk_underlay | toggle | — | — | all | P1 (M4) | registered |
center_walk_underlay_stitch_length_mm | float | mm | 3 | all | P1 (M4) | registered |
center_walk_underlay_stitch_tolerance_mm | float | mm | 0.2 | all | P1 (M4) | deviates |
center_walk_underlay_repeats | int | — | 2 | all | P1 (M4) | registered |
center_walk_underlay_position | float | % | 50 | all | P1 (M4) | registered |
zigzag_underlay | toggle | — | — | all | P1 (M4) | registered |
zigzag_underlay_spacing_mm | float | mm | 3 | all | P1 (M4) | registered |
zigzag_underlay_inset_mm | float | mm (each side) | — | all | P1 (M4) | registered |
zigzag_underlay_inset_percent | float | % (each side) | — | all | P1 (M4) | registered |
zigzag_underlay_max_stitch_length_mm | float | mm | — | all | P1 (M4) | registered |
random_seed | random_seed | — | — | all | P1 (M4) | registered |
Fills
| Attribute | Type | Unit | Default | Applies to | Phase | StitchCraft |
|---|---|---|---|---|---|---|
fill | toggle | — | true | all | P1 (M5) | registered |
fill_method | combo | — | 0 | all | P1 (M5) | registered |
guided_fill_strategy | dropdown | — | 0 | guided_fill | P3 (M10) | planned |
contour_strategy | dropdown | — | 0 | contour_fill | P2 (M7) | planned |
join_style | dropdown | — | 0 | contour_fill | P2 (M7) | planned |
avoid_self_crossing | boolean | — | false | contour_fill | P2 (M7) | planned |
clockwise | boolean | — | true | contour_fill | P2 (M7) | planned |
meander_pattern | combo | — | 0 | meander_fill | P2 (M7) | planned |
meander_angle | float | degrees | 0 | meander_fill | P2 (M7) | planned |
meander_scale_percent | float | % | 100 | meander_fill | P2 (M7) | planned |
clip | boolean | — | false | meander_fill | P2 (M7) | planned |
smoothness_mm | float | mm | 0 | contour_fill, guided_fill, meander_fill | P2 (M7) | planned |
expand_mm | float | mm | 0 | all | P1 (M5) | registered |
gap_fill_rows | int | rows | 0 | tatami_fill | P1 (M5) | registered |
angle | float | deg | 0 | tatami_fill, legacy_fill | P1 (M5) | registered |
guided_fill_angle | float | deg | — | guided_fill | P3 (M10) | planned |
tartan_angle | float | deg | -45 | tartan_fill | P3 (M10) | planned |
max_stitch_length_mm | float | mm | 4.0 | tatami_fill, contour_fill, guided_fill, linear_gradient_fill, tartan_fill, legacy_fill | P1 (M5) | registered |
row_spacing_mm | float | mm | 0.25 | tatami_fill, contour_fill, guided_fill, circular_fill, linear_gradient_fill, tartan_fill, legacy_fill | P1 (M5) | registered |
end_row_spacing_mm | float | mm | — | tatami_fill, circular_fill, legacy_fill | P1 (M5) | deviates |
stitch_position_method | combo | — | 0 | guided_fill | P3 (M10) | planned |
staggers | int | — | 4 | tatami_fill, guided_fill, linear_gradient_fill, tartan_fill, legacy_fill | P1 (M5) | registered |
skip_last | boolean | — | false | tatami_fill, guided_fill, linear_gradient_fill, legacy_fill | P1 (M5) | registered |
flip | boolean | — | false | legacy_fill | — | not planned |
reverse | boolean | — | false | legacy_fill | — | not planned |
stop_at_ending_point | boolean | — | false | linear_gradient_fill, tartan_fill | P3 (M10) | planned |
underpath | boolean | — | true | tatami_fill, guided_fill, circular_fill | P1 (M5) | registered |
running_stitch_length_mm | float | mm | 2.5 | tatami_fill, guided_fill, meander_fill, circular_fill, linear_gradient_fill, tartan_fill | P1 (M5) | registered |
running_stitch_tolerance_mm | float | mm | 0.1 (0.2 when unset) | tatami_fill, contour_fill, guided_fill, meander_fill, circular_fill, linear_gradient_fill, tartan_fill | P1 (M5) | deviates |
enable_random_stitch_length | boolean | — | false | tatami_fill, contour_fill, guided_fill, circular_fill, linear_gradient_fill | P1 (M5) | registered |
random_stitch_length_jitter_percent | float | ± % | 10 | tatami_fill, contour_fill, guided_fill, circular_fill, linear_gradient_fill | P1 (M5) | registered |
repeats | int | — | 1 | meander_fill, contour_fill, circular_fill | P2 (M7) | planned |
bean_stitch_repeats | str | — | 0 | meander_fill, circular_fill, contour_fill, guided_fill, tartan_fill | P2 (M7) | planned |
zigzag_spacing_mm | float | mm | 0 | meander_fill | P2 (M7) | planned |
zigzag_width_mm | float | mm | 3 | meander_fill | P2 (M7) | planned |
rows_per_thread | int | — | 2 | tartan_fill | P3 (M10) | planned |
herringbone_width_mm | int | mm | 0 | tartan_fill | P3 (M10) | planned |
pull_compensation_mm | float | mm (each side) | 0 | tatami_fill | P1 (M5) | registered |
pull_compensation_percent | float | % (each side) | 0 | tatami_fill | P1 (M5) | registered |
fill_underlay | toggle | — | true | all | P1 (M5) | registered |
fill_underlay_angle | float | deg | — | all | P1 (M5) | registered |
fill_underlay_row_spacing_mm | float | mm | — | all | P1 (M5) | registered |
fill_underlay_max_stitch_length_mm | float | mm | — | all | P1 (M5) | registered |
fill_underlay_inset_mm | float | mm | 0 | all | P1 (M5) | registered |
fill_underlay_skip_last | boolean | — | false | all | P1 (M5) | registered |
underlay_underpath | boolean | — | true | all | P1 (M5) | registered |
random_seed | random_seed | — | — | tatami_fill, contour_fill, guided_fill, circular_fill, meander_fill, linear_gradient_fill | P1 (M5) | registered |
cross_stitch_method | combo | — | 0 | cross_stitch | P3 (M10) | planned |
cross_thread_count | int | — | 4 | cross_stitch | P3 (M10) | planned |
pattern_size_mm | float | mm (x y) | 3 | cross_stitch | P3 (M10) | planned |
canvas_grid_origin | boolean | — | true | cross_stitch | P3 (M10) | planned |
cross_offset_mm | float | mm (x y) | 0 | cross_stitch | P3 (M10) | planned |
cross_rotation | float | deg | 0 | cross_stitch | P3 (M10) | planned |
fill_coverage | int | % | 50 | cross_stitch | P3 (M10) | planned |
max_cross_stitch_length_mm | float | mm | 11.0 | cross_stitch | P3 (M10) | planned |
Clones (<use>)
| Attribute | Type | Unit | Default | Applies to | Phase | StitchCraft |
|---|---|---|---|---|---|---|
clone | toggle | — | true | all | P2 (M8) | planned |
angle | float | deg | — | all | P2 (M8) | planned |
flip_angle | boolean | — | false | all | P2 (M8) | planned |
Method identifiers
The values the method parameters take (stroke_method, satin_method, fill_method), with phases. A
value of phase — is not planned as a method of its own, and the deviations ledger
(conformance/deviations.toml) says how an element set to it is sewn.
| Parameter | Value | Phase |
|---|---|---|
stroke_method | running_stitch | P1 (M3) |
stroke_method | ripple_stitch | P3 (M10) |
stroke_method | zigzag_stitch | P2 (M7) |
stroke_method | manual_stitch | P1 (M3) |
satin_method | satin_column | P1 (M4) |
satin_method | e_stitch | P2 (M7) |
satin_method | s_stitch | P2 (M7) |
satin_method | zigzag | P2 (M7) |
fill_method | tatami_fill | P1 (M5) |
fill_method | circular_fill | P2 (M7) |
fill_method | contour_fill | P2 (M7) |
fill_method | cross_stitch | P3 (M10) |
fill_method | guided_fill | P3 (M10) |
fill_method | linear_gradient_fill | P3 (M10) |
fill_method | meander_fill | P2 (M7) |
fill_method | tartan_fill | P3 (M10) |
fill_method | legacy_fill | — |
Lock stitch identifiers (lock_start, lock_end): half_stitch, arrow, back_forth, bowtie, cross, star, simple, triangle, zigzag, custom. StitchCraft accepts every identifier; the shapes are its own
designs with the same intent, a deviation recorded in the deviations ledger (conformance/deviations.toml).
Commands
Ink/Stitch’s command symbols (a <use> of the symbol inkstitch_<name>), where each applies, and what
StitchCraft’s SVG adapter does with it (stitchcraft_svg::inkstitch). Their connectors are never stitched.
| Command | Applies to | StitchCraft |
|---|---|---|
starting_point | the object a connector ties it to | planned: noted on the object as not applied (SC-W0802) |
ending_point | the object a connector ties it to | planned: noted on the object as not applied (SC-W0802) |
target_point | the object a connector ties it to | read with the stitch types it positions |
autoroute_start | the object a connector ties it to | nothing to sew: input to Ink/Stitch’s tools |
autoroute_end | the object a connector ties it to | nothing to sew: input to Ink/Stitch’s tools |
stop | the object a connector ties it to | applied: turns on stop_after |
trim | the object a connector ties it to | applied: turns on trim_after |
ignore_object | the object a connector ties it to | applied: the object is left out and listed (SC-I0805) |
satin_cut_point | the object a connector ties it to | nothing to sew: input to Ink/Stitch’s tools |
ignore_layer | every layer it is in | applied: every layer it is in is left out and listed (SC-I0805) |
origin | the document | planned: noted as not applied (SC-W0802) |
stop_position | the document | planned: noted as not applied (SC-W0802) |
Other attributes
The inkstitch:* attributes that declare no parameter: settings of an element, of Ink/Stitch’s
patterns, stitch plan or sew stack, and legacy names Ink/Stitch’s updater rewrites in older files.
| Attribute | Kind | Phase | StitchCraft |
|---|---|---|---|
ignore_object | element | P1 (M3) | read |
stroke_first | element | P1 (M8) | planned |
cutwork_needle | element | — | not planned |
pattern_interval | pattern | — | not planned |
pattern_offset | pattern | — | not planned |
invisible_layers | stitch plan | — | not planned |
layer_visibility | stitch plan | — | not planned |
sew_stack | sew stack | — | not planned |
sew_stack_only | sew stack | — | not planned |
polyline | legacy | P2 (M8) | planned |
auto_fill | legacy | P2 (M8) | planned |
e_stitch | legacy | P2 (M8) | planned |
manual_stitch | legacy | P2 (M8) | planned |
grid_size | legacy | P2 (M8) | planned |
145 parameter declarations, 92 registered in StitchCraft.
Architecture decision records
Short records of decisions that shape the code, with their context and the alternatives we rejected. New ADRs are numbered sequentially and never renumbered; a decision that changes gets a new ADR that supersedes the old one.
| ADR | Title | Status |
|---|---|---|
| 0001 | MIT OR Apache-2.0, clean-room from Ink/Stitch | Accepted (decision 2 replaced by 0012) |
| 0002 | Host-agnostic engine; VectorCraft ABI v1 plug-in first | Accepted |
| 0003 | One parameter registry generates everything | Accepted |
| 0004 | Determinism by construction | Accepted |
| 0005 | Geometry stack | Accepted (spike M0.7) |
| 0006 | mdBook, Diátaxis, generated reference, CI-regenerated images | Accepted |
| 0007 | PES v1 first, for the Brother 200 × 200 mm machine | Accepted (decisions 2 and 3 replaced by 0013) |
| 0008 | Conformance-first development | Accepted |
| 0009 | Adopt VectorCraft’s conventions; improve the ones that drift | Accepted |
| 0010 | Coded diagnostics with explanation pages | Accepted |
| 0011 | StitchCraft can move into VectorCraft’s repository | Accepted |
| 0012 | Read Ink/Stitch’s source, never copy it | Accepted |
| 0013 | The reference machine is a Brother PE800, with a profile for each of its hoops | Accepted |
| 0014 | Generators see their neighbours: the needle before, the next element after | Accepted |
cargo xtask docs --check fails if an ADR file is missing from this table or its status here differs
from the file’s status line.
ADR-0001: MIT OR Apache-2.0, clean-room from Ink/Stitch
Status: Accepted · 2026-10-08 · decision 2 replaced by ADR-0012
Context
Ink/Stitch, the behaviour reference, is GPL-3.0. VectorCraft, the host, is MIT OR Apache-2.0 and its
AGENTS.md forbids GPL/AGPL code in its tree. A port of Ink/Stitch’s Python to Rust would be a
derivative work and would have to be GPL-3.0: it could still run as a separately distributed plug-in,
but it could never be contributed to VectorCraft or embedded in it, and every ArtCraft-family project
around it is permissively licensed.
Decision
- StitchCraft is licensed MIT OR Apache-2.0, like VectorCraft.
- It is a clean-room implementation. The design documents in
docs/src/design/describe behaviour — inputs, outputs, parameters, published algorithms — in our own words. Implementers work from these documents, public embroidery knowledge, published papers and Ink/Stitch’s public user documentation, and never open, copy or transliterate Ink/Stitch source code. Replaced by ADR-0012: the source may be read, never copied. - Interoperability is preserved: parameter names, their meanings and defaults, method identifiers and command names match Ink/Stitch’s SVG attributes, because reading and writing the same files is the point (compatibility contract).
- Not copied, ever: Ink/Stitch code, docs text, parameter descriptions, lock-stitch shape data, meander tiles, fonts, palettes compiled by Ink/Stitch, images.
- Format facts may be taken from MIT-licensed pyembroidery/pystitch with attribution in
NOTICE; prefer public specifications where they exist. - Running Ink/Stitch as a black-box oracle for metric comparisons (conformance L3) is allowed: it uses the program, it does not copy it.
Consequences
- Upstreaming to VectorCraft and embedding the engine anywhere stays possible.
- We cannot take shortcuts by translating Ink/Stitch’s algorithms; we design them, which the quality goals require anyway.
- Our documents describe Ink/Stitch only by its public behaviour, documentation and file format, never by its code. (ADR-0012: they may describe behaviour its source shows, in our own words.)
cargo xtask cleanroomfails on GPL licence text and on Ink/Stitch source paths anywhere in the repository, documents included; the review checklist asks about provenance. (ADR-0012: source paths are allowed, so documents can link what they read.)
Alternatives considered
- GPL-3.0 port: fastest path to parity and the most faithful behaviour, but closes the door on VectorCraft and contradicts the ecosystem’s licensing. Rejected.
- Contribute Rust to Ink/Stitch: different host, different language, different goals. Out of scope.
ADR-0002: Host-agnostic engine; VectorCraft ABI v1 plug-in first
Status: Accepted · 2026-10-08
Context
VectorCraft offers sandboxed WebAssembly plug-ins (ABI v1): object filters and live effects, with parameters stored per object as effect records, but no exporters, panels or overlays, and tight budgets. Changing VectorCraft requires its maintainers’ agreement. A machine needs files from day one.
Decision
- The engine (
stitchcraft-core…stitchcraft-render) knows no host. Hosts adapt documents into aDesignand consume aStitchPlan. - Phase 1: ship VectorCraft plug-ins on ABI v1 (live effects per stitch family, one tools filter)
and produce machine files with
stitch export file.vectorcraft. No VectorCraft change needed. - Phase 2: propose generic ABI v2 features upstream (RFC).
- Phase 3 (optional): an in-tree VectorCraft crate as one more adapter.
Amended at M6.4: phase 1 drops the tools filter. VectorCraft’s own commands make a satin column of 2 paths, add rungs and bake previews, and making a line into a column needs more instructions than ABI v1 gives a filter (tools).
Amended 2026-10-25: VectorCraft took most of phase 2 into ABI v1 from 0.14.0 on, where an ABI v2 was proposed (status). Exporters, rich parameters, data kept with the document, heavy filters and a live effect’s paints came that way. M6.8 to M6.12 use them, and M6.12 brings the tools filter back as a heavy filter. Overlays, heavy live effects and the objects beside a live effect are still proposed, in M9. M6.9’s exporters end the two-step workflow below for VectorCraft 0.14 and later, whose File › Export writes the machine file.
Consequences
- Useful on the machine before any upstream discussion.
- Previews in v1 are budget-limited (coarse levels for large fills); the CLI shows full fidelity.
- Two-step workflow (design in VectorCraft, export with the CLI) until exporter plug-ins exist.
- The compatibility gate is required, because we depend on a host we do not control.
Alternatives considered
- Fork VectorCraft and integrate natively now: best UX, but a fork to maintain against a fast-moving upstream (~290k lines). Kept as phase 3.
- Standalone app: duplicates the editor. Rejected; the CLI covers headless needs.
ADR-0003: One parameter registry generates everything
Status: Accepted · 2026-10-08
Context
Parameters appear in at least seven places: generators, validation, VectorCraft manifests, the CLI, SVG attributes, documentation and property tests. When each place restates them by hand they drift apart: a parameter is added in code and never documented, or its documented default goes stale.
Decision
Declare each parameter once, next to its generator, with a params! macro (doc comment = help text).
Generate typed structs, validation, manifests, CLI help, SVG mapping, docs pages, JSON Schema and
proptest strategies from it. Registry keys are Ink/Stitch attribute names where the meaning matches,
and defaults equal Ink/Stitch’s. Details: parameter registry.
Consequences
- Adding a parameter is a one-place change; docs and UIs cannot forget it.
- A
macro_rules!macro keeps contributors in plain Rust; if it grows unwieldy (reassessed after M3), a derive macro can replace it without changing the registry’s data model. - VectorCraft v1’s 64-parameter limit is checked by a test at PR time.
Alternatives considered
- Serde structs + hand-written docs: the drift we are trying to avoid.
- External schema file (YAML) generating Rust: another language and a build step; doc comments next to code are where contributors look.
ADR-0004: Determinism by construction
Status: Accepted · 2026-10-08
Context
Golden-file conformance, reproducible bug reports, safe caching and CI-regenerated docs images all require identical output across platforms and runs.
Decision
Ordered collections only; transcendental math through libm; a specified PRNG (SplitMix64) with seeds
from element ids and random_seed; quantization of absolute positions once at encode time; total-order
sorts with tie-breakers; budgets in work units, not time; golden files compared byte for byte on Linux,
macOS and Windows. Enforced by clippy disallowed-types/disallowed-methods. Details:
determinism.
Consequences
- Slightly more verbose code (
BTreeMap,math::sin); negligible performance cost. - Byte-exact goldens are possible for formats and stitch renders.
Alternatives considered
- Tolerance-only comparisons everywhere: hides real regressions and makes images flaky. Rejected except for VectorCraft UI screenshots, which we do not control.
Amendment, 2026-10-14
The decision first named a CI job that would compare output hashes across Linux, macOS, Windows and wasm. CI compares each operating system’s outputs with the golden files instead, and builds the WebAssembly target without running it (cross-platform check).
ADR-0005: Geometry stack
Status: Accepted · 2026-10-17, after spike M0.7 · proposed 2026-10-08
Context
The engine needs curve flattening, polygon booleans and offsets with holes (fill rules, insets for underlay and contour fill), spatial queries and graph algorithms. They must be pure Rust and build for WebAssembly. Their licences must be permissive, and they must hold up on degenerate input.
Decision
The engine does its own geometry, in Rust on f64, and depends on no geometry crate. libm, through
stitchcraft_core::math, gives it the few transcendental functions it needs, the same on every platform.
| Need | Proposed | Chosen | Why |
|---|---|---|---|
| Flattening curves | kurbo 0.13 | The engine halves each curve (normalize::stroke), since M3.4 | kurbo’s flatten calls the platform’s powf and hypot, whose last bits differ from one platform to the next |
| A fill’s region | i_overlay 9 | The engine’s region code (normalize::region), since M5.1 | An integer-snapped overlay moves the drawing’s points onto its grid, and its loops cannot charge an element’s work budget. The engine keeps every point the drawing has and decides sides of lines exactly. It charges its work as it goes |
| Offsets: satin rails, insets, grown fills | i_overlay 9 | The engine’s offsetter (normalize::offset), since M4 and M5.7 | Ink/Stitch offsets with shapely, whose curves are GEOS’s. The engine builds the same curves, and checks them against shapely’s |
| The nearest point or line | rstar 0.13 | Scans of one element’s points at a time, charged to the work budget (normalize::near) | The hoop’s whole field plans in about 30 ms with them (REQ-FILL-TAT-009), so an index would buy nothing yet |
| Routing a fill’s rows | petgraph 0.8 with a walk of our own | The engine’s own graph (generators::tatami::graph), since M5.4 | Ink/Stitch’s route depends on the order in which its graph library lists a node’s edges, and the engine’s graph lists them in that order |
| Transcendental functions | libm 0.2 | libm 0.2, through stitchcraft_core::math | The same bits on every platform |
The geometry lives with the code that uses it, in normalize and in the generators, rather than behind one
geometry module. Each piece follows what Ink/Stitch does in its place, and its docs say what that is.
Spike M0.7
The criteria were written for the crates proposed. The spike measured the engine’s own geometry against them.
| Criterion | Result |
|---|---|
| 1. Booleans and offsets of 10,000 random polygons with holes, and of a degenerate corpus, give valid output without panicking or hanging | Met. The property test any_polygon_with_holes_grows_and_shrinks_into_valid_parts grew and shrank 10,000 random polygons with holes, and every part it got was valid. It ran with PROPTEST_CASES=10000, where CI runs 256 each time. The offsets agree with shapely’s for 1,050 random lines, and the grown and shrunk polygons for 330. The region code’s tests take random rings, and rings with copies of themselves a rounding error apart. Every loop charges the work budget, so a plan that runs too long ends with SC-E0004 |
| 2. The same output bytes on Linux, macOS, Windows and wasm32 | Met. The tests run on the 3 systems, and the wasm-goldens gate makes every golden file of the conformance cases with stitch built for WASI and run by wasmtime (determinism): 16 files, byte for byte |
| 3. Offsetting a 150 mm region with 1,000 vertices inward 300 times in under 200 ms natively | Not met: 218 to 448 ms, against GEOS’s 104 to 149 ms for the same parts and points (below). Contour fill is the first stitch type to need it. The offsetter meets this criterion in M7, before contour fill relies on it |
| 4. Building for wasm32 adds under 300 KiB to the plug-in | Met for the geometry: no geometry crate is linked, so none adds to the plug-in. The engine, linked into the plug-in by M6.2, adds 844 KiB. A stitch family’s module is 1,014 KiB, and hello’s, with the ABI shim and the JSON reader alone, is 171 KiB (a release build with thin LTO, at opt-level 3, in the compatibility gate’s contract job). The engine’s own geometry is part of the 844 KiB and isn’t measured apart. VectorCraft takes modules of up to 32 MiB |
Criterion 3, measured on a 2.1 GHz Xeon in an optimized build: a ring 150 mm across, of 1,000 points, round or with 7 waves of 2 mm, shrunk 300 times in steps of 0.25 mm, and of 0.4 mm. shapely 2.2.0, with the GEOS 3.14.1 its wheels bundle, ran on the same machine, and its parts and points came out the same as the engine’s.
| Ring | Step | Engine | GEOS |
|---|---|---|---|
| Round | 0.25 mm | 218 ms | 104 ms |
| Round | 0.4 mm | 135 ms | 61 ms |
| 7 waves | 0.25 mm | 448 ms | 149 ms |
| 7 waves | 0.4 mm | 261 ms | 115 ms |
Consequences
- No geometry crate is there to vet, update or replace. The conformance suite and the shapely oracles
(
conformance/oracle/) check the engine’s geometry, and a change to it is a change of the engine. - CI makes the golden files again as WebAssembly. A plan the plug-in makes is one the command line makes.
- The offsetter is 2 to 3 times slower than GEOS on large regions. Until M7, a fill offsets each part at most twice, for its inset underlay and its grown rows. Contour fill offsets hundreds of times.
ADR-0006: mdBook, Diátaxis, generated reference, CI-regenerated images
Status: Accepted · 2026-10-08
Context
The docs must stay correct on every PR, including screenshots. Documentation written once by hand drifts from the code it describes, and nobody notices until a reader is misled.
Decision
- mdBook (Rust-native, fast, search and print built in, no Ruby/Node toolchain).
- Diátaxis structure: tutorials, how-to, reference, explanation; plus design, plan, contributing.
- Generated reference from registries, committed and checked for staleness.
- Declared images (
docs/shots.toml) of four kinds — stitch renders, VectorCraft renders, VectorCraft UI screenshots, photos — regenerated and compared in CI, refreshed by a PR label. - Versioned publishing (
/dev/,/vX.Y/,/latest/).
Details: docs pipeline.
Consequences
- Docs changes are reviewable like code; drift fails the build.
- UI screenshots depend on spike M0.8 (headless VectorCraft window in CI).
Alternatives considered
- Jekyll on GitHub Pages: no checks, Ruby toolchain. Rejected.
- Docusaurus / MkDocs: capable, but bring Node or Python into a Rust-only repository.
ADR-0007: PES v1 first, for the Brother 200 × 200 mm machine
Status: Accepted · 2026-10-08 · decisions 2 and 3 replaced by ADR-0013
Context
The first physical test machine is a Brother with a 200 × 200 mm (8 × 8 in) hoop; designs are kept to about 150 mm (6 in). Brother machines load PES. Embroiderers report PES pitfalls with other software: trims ignored by some Brother models, and an older Brother-family machine that hides PES v1 designs larger than about 130 × 180 mm.
Decision
- The first writer is PES v1 + PEC; DST is second (universal fallback, independent check).
- Built-in profile
brother-200x200: hoop 200 × 200 mm (error beyond), comfort 150 × 150 mm (warning beyond), max stitch 12 mm, min stitch 0.3 mm, Brother PEC palette. - MC-1 (end of M1) tests orientation and scale, colour changes, both trim encodings, and designs 150 and 190 mm wide. A PES v6 writer with explicit hoop dimensions is added only if MC-1 shows v1 is insufficient.
Consequences
- The earliest milestone produces something the user can sew.
- Profile values are provisional until MC-1/MC-2 records confirm them.
ADR-0008: Conformance-first development
Status: Accepted · 2026-10-08
Context
When behaviour is specified only by its implementation, nobody can say what “correct” means: a fix for one input silently breaks another, and a regression looks like any other change. Stitch generators are especially prone to this, because their output is thousands of coordinates no reviewer reads.
Decision
Behaviour is specified as numbered requirements (conformance/requirements.toml) and proven by data-
driven cases (conformance/cases/) across five levels (invariants, formats, generator properties,
Ink/Stitch differential metrics, physical sew-outs). For each roadmap step, requirements and cases come
first, then the code that makes them pass. Gates per PR, nightly, milestone and release. Details:
conformance.
Consequences
- “Done” is objective: requirements active and green, machine checkpoint signed off.
- Golden changes are explicit (
--bless, label, changelog line). - Up-front cost per feature; repaid by fewer regressions and reproducible bugs.
ADR-0009: Adopt VectorCraft’s conventions; improve the ones that drift
Status: Accepted · 2026-10-08
Context
StitchCraft lives next to VectorCraft and may one day be embedded in it. Contributors who know one
codebase should feel at home in the other, and an upstream PR is easier when our code already follows
their rules. We audited VectorCraft (main at 89ea46c) for patterns worth following and checked
whether each one is actually enforced.
Audit
| # | VectorCraft pattern | Evidence | Verdict | StitchCraft |
|---|---|---|---|---|
| 1 | Never crash: unwrap, expect, panic!, todo!, unimplemented!, unreachable! denied workspace-wide; documented alternatives | Cargo.toml [workspace.lints.clippy]; docs/development.md “Robustness” | Adopt | Same lints, plus indexing_slicing denied in parsers and dbg_macro |
| 2 | Panic guard at every entry point | vectorcraft_engine::guard | Adapt | Libraries never need it; the CLI wraps main and writes a bug-report bundle; the wasm plug-in cannot catch panics, so the lints are absolute |
| 3 | Everything is a command in a declarative registry reachable from UI, control channel and MCP | crates/engine/src/cmd/*, cmd! macro | Adopt the idea, improve the data | Everything user-visible is registered (parameters, diagnostics, profiles, formats, CLI commands); parameters are typed schemas that generate docs, where VectorCraft documents command parameters as free-form strings |
| 4 | Layering enforced by cargo xtask layers with an append-only table and a unit-tested rule engine | xtask/src/layers.rs | Adopt | Same model in xtask/src/layers.rs |
| 5 | One local gate, cargo xtask ci, with a step summary | xtask/src/main.rs (cmd_ci) | Adopt and extend | Same command — and the same gates run on every pull request in GitHub Actions. VectorCraft’s workflows run the test suite on a pull request only when it changes Cargo manifests or FreeBSD packaging (freebsd.yml), relying on contributors running the gate locally; for external contributions we want required checks |
| 6 | AGENTS.md as the contract for contributors and agents | AGENTS.md | Adopt, keep DRY | CLAUDE.md is a pointer to AGENTS.md. VectorCraft keeps two copies that have drifted: CLAUDE.md (63 lines) lacks AGENTS.md’s (81 lines) contributor-credits section |
| 7 | Clean-room rules with an automated check | AGENTS.md says cargo xtask cleanroom runs in cargo xtask ci | Adopt and implement | At 89ea46c there is no cleanroom subcommand in xtask/src/main.rs and cmd_ci does not run one — documentation and code drifted. Our cargo xtask cleanroom exists, runs in CI, and cargo xtask docs --check fails when docs mention a subcommand that does not exist |
| 8 | Asset attribution enforced (ASSETS.md, checked by VectorCraft’s xtask assets step) | xtask/src/assets.rs | Adopt | Every committed image, font or fixture has a provenance row; generated images are attributed to their shot declaration |
| 9 | Untrusted input is fuzzed with property tests | engine/tests/import_fuzz.rs, format/tests/prop_format.rs | Adopt and extend | Property tests on every parser, plus coverage-guided cargo-fuzz nightly with a persisted corpus |
| 10 | cargo xtask wasm keeps crates building for the web | xtask/src/main.rs (cmd_wasm) | Adopt | Same, for L0–L3 and the plug-in |
| 11 | Shared corpora pinned by commit and SHA-256, never committed | AGENTS.md, craftrules standards/test-corpora.md | Adopt | cargo xtask corpus |
| 12 | Honest ROADMAP.md, updated in the same PR as the work | ROADMAP.md, AGENTS.md | Adopt | ROADMAP.md status table + plan; the PR template asks for the update |
| 13 | One task id per commit (M2.1: pen tool) | AGENTS.md | Adopt | One roadmap step per PR, id in the title |
| 14 | PR template checklist | .github/pull_request_template.md | Adopt and automate | Most checklist items are CI checks; the template keeps the human ones |
| 15 | Rich doc comments on fields and modules | e.g. crates/doc/src/node.rs | Adopt and enforce | missing_docs denied in library crates |
| 16 | Dependencies explained in Cargo.toml comments | e.g. the wasmi features comment | Adopt | Same, plus cargo-deny |
| 17 | Headless UI tests that assert on what a frame shows | crates/ui-egui/src/panels/transparency.rs tests | Adopt (phase 3 panels) | Same style for any panel we contribute |
| 18 | Very large modules | menus.rs 3,752 lines, canvas.rs 2,941 lines | Do not adopt | Files warn at 800 and fail at 1,500 lines |
| 19 | Hand-taken screenshots in docs | ASSETS.md lists them as screenshots of the app | Do not adopt | Declared, regenerated and compared in CI (docs pipeline) |
| 20 | Workspace dependency declared but unused (egui_kittest) | Cargo.toml:74; only named in xtask/src/layers.rs | Avoid | cargo-machete (or an xtask equivalent) flags unused declarations |
| 21 | unsafe_code = "deny", example plug-in outside the workspace uses unsafe for the ABI | Cargo.toml, crates/plugins/example | Adopt and tighten | forbid everywhere; the plug-in’s ABI shim is the single audited exception, checked by cargo xtask unsafe-audit |
Decision
Follow VectorCraft’s conventions by default (rows marked Adopt), with the improvements listed. When VectorCraft changes a convention we follow, we re-evaluate here. When we improve on one (rows 5, 6, 7, 19, 20), we offer the improvement upstream where it is generic.
Consequences
- VectorCraft contributors recognize the shape of the code, the gate and the rules.
- Upstreaming (RFC proposals, phase 3) meets their review standards from the start.
- We take on a little more CI (GitHub Actions on every PR), which external contributions need.
ADR-0010: Coded diagnostics with explanation pages
Status: Accepted · 2026-10-08
Context
Users need to understand why a design will not sew well, and maintainers need to know which message a user saw. A message without a code cannot be looked up, searched for in bug reports or tested on its own, and an error path that no test ever renders can ship a broken message.
Decision
Every user-facing problem is a Diagnostic with a stable code (SC-E0201), severity, element, location,
message and optional fix. A single registry holds codes and long explanations; docs pages and
stitch explain are generated from it; every code must have a conformance case that triggers it.
Details: diagnostics.
Consequences
- Error paths are executed in CI; messages cannot rot unnoticed.
- Issue reports can cite a code; search engines find the explanation page.
ADR-0011: StitchCraft can move into VectorCraft’s repository
Status: Accepted · 2026-10-08
Context
If VectorCraft’s maintainers want machine embroidery in VectorCraft itself, they should be able to take this repository into theirs without a rewrite. Much already lines up: the same licence (MIT OR Apache-2.0), edition (2024), minimum Rust version (1.95), formatting and lint style (ADR-0009).
We tried it on 2026-10-08 with VectorCraft main at a469568c: a copy of this repository as the folder
stitchcraft/, and a VectorCraft crate depending on stitchcraft-engine by path. It failed in three
ways:
- Cargo gave our crates VectorCraft’s workspace settings, because the path dependency sits inside
VectorCraft’s workspace directory. Our crates inherited
publish, which VectorCraft’s workspace does not define, so Cargo refused to load them. Once VectorCraft’s manifest excluded the folder (exclude = ["stitchcraft"]), the path dependency resolved against our own workspace and built. - Both repositories call their tooling package
xtask, so the two cannot share one workspace. - Our tooling ran
cargo … --workspaceand read every package incargo metadata. Inside a shared workspace it would build, lint and test VectorCraft’s crates too, and the layering check would call them unknown.
Decision
The repository stays movable as one folder, in either of two ways. Both are one command, and CI
rehearses both against VectorCraft’s latest release and main.
Nested. Copy the folder in, with history if wanted
(git subtree add --prefix=stitchcraft https://github.com/KaboomFox/stitchcraft main), then run
cargo xtask compat join --nested .. from it. That adds the folder to VectorCraft’s exclude list.
StitchCraft keeps its own workspace, lockfile, toolchain pin and gates (cargo xtask ci inside the
folder), and VectorCraft’s crates may depend on StitchCraft’s by path.
Joined. After the copy, run cargo xtask compat join .. instead (or after a nested join). It edits
VectorCraft’s root Cargo.toml in place, keeping its comments and layout:
- our crates, apps and tooling become members;
- our
[workspace.dependencies]are merged into VectorCraft’s: VectorCraft’s own entry is reused where it satisfies ours, and any features we need are added; - our root
Cargo.tomlandCargo.lockare removed, leaving one workspace and one lockfile.
It refuses, writing nothing, when the two cannot merge: a dependency in an incompatible version, a
setting our crates inherit that VectorCraft does not define, or a name clash. It prints the rows
VectorCraft’s layering table needs. The tooling keeps working from the folder: cargo xtask ci there
builds, lints and tests StitchCraft’s packages, with VectorCraft’s lockfile and lints.
These rules keep it that way:
- Every package is named
stitchcraft-*, the tooling included (stitchcraft-xtask, still run ascargo xtask). The layering table holds only prefixed names, and an unlisted package failscargo xtask layers. - Crate manifests inherit only what VectorCraft’s workspace defines:
version,edition,license,rust-version,repository, and lints. Each crate setspublish = falseitself. - Tooling is relative to its own folder. Files are found from the folder that holds
xtask/, never from the Git root or the current directory. Cargo commands name StitchCraft’s packages instead of--workspace(util::packages). Inside a larger workspace,cargo-denyis skipped, because it judges a whole workspace and VectorCraft’s answers to VectorCraft’s policy. - Configuration that should follow the code lives in the folder. Clippy and rustfmt read the
configuration file nearest each crate, so our determinism rules (
clippy.toml) keep applying to our crates, and VectorCraft’s to theirs. - Our lints are at least VectorCraft’s. Joined, our crates take VectorCraft’s workspace lints but
keep our
clippy.toml, so its thresholds must match VectorCraft’s: from VectorCraft’smainafter 0.18.0, both denycognitive_complexityabove 30,too_many_argumentsandwildcard_imports. Our workspace denies what VectorCraft’s does. A breach then fails our gate before the rehearsal finds it. - Generated files never depend on where the folder is: no absolute paths, no repository-relative
paths outside it.
cargo xtask docs --checkandshots --checkrun in the rehearsal.
What stays with VectorCraft’s maintainers
- Workflows. GitHub reads them only at the repository root. Ours call
cargo xtask …, so moving them is a matter ofworking-directory: stitchcraft. - Their own checks. In the joined form, their layering table needs rows for our crates (printed by
compat join), and their repository-wide checks will see our files. - The plug-in shim.
apps/stitchcraft-vc-plugintalks to VectorCraft through ABI v1. Inside VectorCraft, the engine can be called directly instead; that is their integration choice.
Consequences
- Taking StitchCraft into VectorCraft is a folder copy plus one command, and the
movejob incompat.ymlproves it daily, so a change that would break it fails here first. - Small constraints for us: no
publish.workspace, no--workspacein tooling, and a prefixed name for every package.
Alternatives considered
- Publish the crates to crates.io and let VectorCraft depend on them. Possible later. It doesn’t bring the docs, tests and gates along, and it isn’t what “moving into their codebase” means.
- Relative
pathdependencies instead of workspace dependencies. That would make crates movable without merging tables, but it departs from VectorCraft’s convention and repeats every version in every crate.compat joinmerges the tables instead.
ADR-0012: Read Ink/Stitch’s source, never copy it
Status: Accepted · 2026-10-08
Context
ADR-0001 made StitchCraft a strict clean room: nobody working on it
opened Ink/Stitch’s source. What that protects is the MIT OR Apache-2.0 licence: a port of GPL-3.0 code
would be a derivative work, and VectorCraft, where StitchCraft may move
(ADR-0011), takes no GPL code. But copyright covers code, not
behaviour or algorithms: reading a program does not make a work derivative, copying it does.
VectorCraft draws the line in the same place. Its AGENTS.md (at dcc0de7) says “Never copy GPL/AGPL
code (Inkscape, lib2geom…)”, and keeps its never-read rule for Adobe software, whose terms forbid
reverse engineering.
The strict rule has a cost. Ink/Stitch’s documentation is silent on many details that decide whether a file behaves the same in both tools (edge cases of parameters, how lists repeat, the font files), and guessing them makes interoperability bugs.
Decision
- Ink/Stitch’s source, like any GPL/AGPL code, may be read to understand its behaviour. Nothing from it is copied, pasted, transliterated or closely paraphrased into StitchCraft, and no GPL code, text or data file enters the repository.
- Behaviour is written into our design docs in our own words, and code is written from those docs, as before. A design statement may link the Ink/Stitch source it rests on when that helps the next reader check it again; no link is required.
- This replaces decision 2 of ADR-0001. Its other decisions stand: the licence, interoperability through Ink/Stitch’s names and defaults, the things never copied, format facts from pyembroidery with attribution, and Ink/Stitch as a black-box oracle.
Consequences
- Interoperability details can be checked against what Ink/Stitch actually does instead of inferred from its documentation.
- The risk moves from “never seen” to “never copied”: code written right after reading another implementation tends to mirror its structure even when no line is copied. Writing the design doc first, in our own words, and implementing from it is the guard; the review checklist asks that nothing was copied.
cargo xtask cleanroomstill fails on GPL licence text and on pasted Python source. It no longer fails on Ink/Stitch file paths, so a design doc can link the source it read.
Alternatives considered
- Keep the strict clean room: the cleanest provenance, but it leaves interoperability details to guesswork, and it is stricter than VectorCraft, the host whose licence sets the constraint. Rejected.
- Require a pinned citation for everything learned from the source, checked by the gate: traceable, but more process than VectorCraft asks for, and a link only helps where a statement may need checking again. Rejected: links are welcome, not required.
ADR-0013: The reference machine is a Brother PE800, with a profile for each of its hoops
Status: Accepted · 2026-10-10
Context
ADR-0007 took the first test machine to be a Brother with a
200 × 200 mm (8 × 8 in) hoop, and gave it the profile brother-200x200, with a 150 × 150 mm comfort
zone. The machine’s owner has since said what it is: a Brother PE800 (2026-10-10). Brother’s page for the
PE800 gives a 5 × 7 in maximum embroidery area, and hoop sellers give the PE800’s 5 × 7 in hoop (SA444) a
130 × 180 mm field, the size PES v1’s large-hoop indicator names.
The owner has 3 hoops: the 5 × 7 in that comes with the machine, a 4 × 4 in (100 × 100 mm) and a small hoop sold as 1 × 2.5 in. That is the size Brother’s US site gives its small SA442 hoop. Brother’s Canadian site gives the same hoop as 3/4 × 2 3/8 in, and Brother’s small frames are sold in Europe as 2 × 6 cm.
The old profile allowed designs the machine cannot sew. Test sheets TS-02 and TS-02B were 140 mm wide, and the TS-10 frames 150 and 190 mm. A profile is one machine with one hoop (data model), so a machine with 3 hoops needs 3.
Decision
- Built-in profiles
brother-pe800-5x7(130 × 180 mm),brother-pe800-4x4(100 × 100 mm) andbrother-pe800-small(20 × 60 mm) replacebrother-200x200. Everything but the hoop stays as it was. Those values were common Brother home-machine values rather than facts about an 8 × 8 in machine, and the checkpoints still confirm them. brother-pe800-5x7is the reference profile,profiles::REFERENCE. The command line plans for it unless--profilenames another, and code that means the machine the checkpoints run on imports it rather than naming a hoop.- No profile has a comfort zone. The 150 × 150 mm zone belonged to the 8 × 8 in hoop, and nothing is known yet about where the PE800’s hoops hold the fabric less well. A sew-out report that shows it gives that hoop’s profile one.
- The small hoop’s field is 20 × 60 mm, the metric figure that both inch sizes round. A design that fits it fits the hoop. It stands upright, 20 mm across and 60 mm from back to front as the 5 × 7 in’s field does, until test sheet TS-10C shows which way the machine holds it.
- Test sheets fit the reference hoop. TS-02 and TS-02B narrow to 120 mm, with their longest jump 30 mm
long where it was 40. The TS-10 sheets become frames filling each hoop’s field, 100 × 100 mm (A),
130 × 180 mm (B) and 20 × 60 mm (C). Each test sheet names the profile of the hoop it is sewn in, and
stitch testsheetchecks the sheet against it unless--profilenames another. - This replaces decisions 2 and 3 of ADR-0007. Its first decision, PES v1 first and DST second, stands.
Consequences
- MC-1’s TS-02 and TS-10 files change, and so does MC-2’s TS-02B. The kits are rebuilt with new golden files. The other sheets’ files are the same, byte for byte.
- PES v1’s hoop indicator names the PE800’s 2 large fields, 100 × 100 and 130 × 180 mm. The question ADR-0007 left to MC-1, whether a machine takes larger designs from PES v1, does not arise for the reference machine. TS-10 still checks that each hoop takes a design as large as its profile allows.
- By default, designs must fit 130 × 180 mm. A larger one gets
SC-E0701, which offers to turn it 90° when its sides fit the other way round. SC-W0702stays in the code and its tests, though no built-in profile raises it.
Alternatives considered
- Keep
brother-200x200alongside, for 8 × 8 in machines: a profile for a machine nobody sews on is a guess. Rejected until someone does. - One PE800 profile, for the 5 × 7 in hoop only: the owner sews in all 3 hoops, and a design meant for the 4 × 4 in should be checked against it. Rejected.
- The small hoop at its nominal 25.4 × 63.5 mm: designs up to that size may not fit its field. Rejected.
- A comfort zone a margin inside each hoop, such as 120 × 170 mm: no evidence for any number yet. Rejected until a sew-out report gives one.
ADR-0014: Generators see their neighbours: the needle before, the next element after
Status: Accepted · 2026-10-10
Context
The engine pipeline’s design generated each element on its own. The previous element’s geometry was to suggest where the next one starts, and no generator was to wait for another’s stitches, so elements could be generated in parallel and cached (engine pipeline).
Ink/Stitch, whose files StitchCraft sews alike, does otherwise. A satin column starts at the point of its
line nearest where the elements before it left the needle: their last stitch, which only generating them
gives. It ends at the point of its outline nearest where the next element starts, which the next
element’s shape and settings say. Both are on by default (start_at_nearest_point,
end_at_nearest_point), and fills do the same from M5. A start taken from the previous element’s
geometry differs from Ink/Stitch’s whenever that element ends somewhere its geometry does not say. A satin
column that ends at its nearest point is one such element. Fills and running stitches sewn there and back
are others.
Decision
- Elements are generated in sewing order. Each is given the last needle point of the elements before it that sew any, whatever their thread.
- Each is also given what the next element in the design offers to end near: its first point, or its shape when it starts at its own nearest point. The offer depends on the next element’s shape and settings only, never on its stitches, so no element waits for a later one.
- Generators are pure functions of their shape, parameters, these 2 neighbours, seed and budget.
- Where an element’s stitch type is known, it offers what Ink/Stitch’s would, even before StitchCraft sews that type. A tatami fill, sewn from M5, offers its area. The element before ends near its next stitch, found on its own shape as Ink/Stitch’s geometry library finds it, a point of one shape inside the other’s area first.
Consequences
- Generation is sequential. Each element still has its own budget, and a cache keyed on an element’s inputs includes its 2 neighbours.
- Moving, adding or removing an element can change the stitches of the elements beside it, as in Ink/Stitch.
- Start and end commands, read from M8, take the place of the neighbours they override.
Alternatives considered
- Start from the previous element’s geometry: keeps generation parallel, but sews differently from Ink/Stitch wherever an element does not end where its shape suggests. Rejected: files must sew alike.
- Generate twice, first in parallel without neighbours and then again with them. Every element whose start depends on the one before still waits for it. The second pass is sequential anyway. Rejected.
Roadmap: small steps to the final project
Each step is one pull request: small, reviewable, with its tests, conformance cases and docs. Each milestone ends with green gates and, where it produces something sewable, a machine checkpoint (MC) on the reference Brother PE800 with photos and measurements filed through the sew-out report form (machine testing).
Step ids are stable (M3.4); PR titles start with them. Status lives in ROADMAP.md at the repository
root and is updated in the same PR that completes a step.
Legend: ✅ done in the bootstrap commit · 🧪 spike (answers a question; may produce a decision instead of code) · 🧵 machine checkpoint · 👤 owner action
M0 — Foundations and guardrails
Goal: a repository where the rules are enforced before the first line of engine code.
| Step | Deliverable | Done when |
|---|---|---|
| M0.1 ✅ | Workspace, crates skeleton, licences, toolchain pin, lints, rustfmt/clippy/deny/typos configs | cargo build and cargo test pass |
| M0.2 ✅ | cargo xtask gates: ci, layers, docs --check, shots --check, cleanroom, unsafe-audit, filesize, wasm, conformance --check, compat discover/contract | cargo xtask ci passes locally |
| M0.3 ✅ | Workflows: ci.yml, docs.yml, compat.yml (discovery), nightly.yml; templates; Dependabot | First PR runs green on GitHub |
| M0.4 ✅ | mdBook site, design docs, ADRs, this roadmap, machine-testing protocol | mdbook build and the docs check pass |
| M0.5 👤 (partly ✅) | Create the GitHub repository, push, enable Pages (from the gh-pages branch) and branch protection with required checks ci and docs; replace OWNER in CODEOWNERS and Cargo.toml and add a contact to CODE_OF_CONDUCT.md; create the labels golden-change, docs:refresh-shots, upstream-canary, upstream-prerelease, sewout, sewout:pass, sewout:tune, sewout:bug, good first issue | A test PR cannot merge while a check is red |
| M0.6 ✅ 🧪 | VectorCraft “hello” plug-in: an identity live effect built in CI for wasm32-unknown-unknown, a Level A contract test against the pinned stable vectorcraft-plugins (v0.6.0, now v0.7.0), release and main, and the fuel it spends per byte of output | The contract test is green in compat.yml on every track (2026-10-08), and the budget is recorded in the integration doc: 512 instructions a byte read and 822 written (2026-10-17) |
| M0.7 ✅ 🧪 | Geometry spike (ADR-0005 criteria) | ADR-0005 accepted with the engine’s own geometry, and the golden files made as WebAssembly in CI (2026-10-17) |
| M0.8 ✅ 🧪 | Headless VectorCraft window in CI (Xvfb + Mesa) taking ui.screenshot twice with identical pixels; one xa11y test that drives the same window through its accessibility tree (AT-SPI on Linux) | Decision recorded in the docs pipeline page, for both (2026-10-17). Screenshots match above the status bar, whose frame timings change. xa11y chooses menu rows by AT-SPI’s press and by clicks at the tree’s places |
| M0.9 ✅ | Movable into VectorCraft (ADR-0011): packages prefixed, manifests inheriting only what VectorCraft defines, tooling scoped to its folder and packages, cargo xtask compat join [--nested], and a daily move job | move green against VectorCraft’s latest release and main |
| M0.10 ✅ | Clean room, tightened: no document describes Ink/Stitch’s code; Ink/Stitch is named only for file compatibility and in the independence notices; cargo xtask cleanroom scans documents too | cleanroom green over the whole repository |
M1 — Stitch plan, PES/DST writers, first sew-out
Goal: sew a machine file StitchCraft wrote. No SVG yet: test sheets are generated in code.
| Step | Deliverable | Done when |
|---|---|---|
| M1.1 ✅ | stitchcraft-core: Budget and the diagnostics model (Mm, Point, math and SplitMix64 with frozen reference values are already in the bootstrap) | unit tests; budget exhaustion is a diagnostic |
| M1.2 ✅ | stitchcraft-plan: Stitch, StitchKind, ColorBlock, StitchPlan; invariant checker (REQ-PLAN-001 to 003, 005 to 007) | each invariant has a violating and a passing case |
| M1.3 ✅ | Machine profiles + brother-200x200 (since replaced by the PE800’s, ADR 0013); hoop and comfort diagnostics SC-E0701/SC-W0702 with rotate hint | REQ-PRF-001, REQ-PRF-002 active |
| M1.4 ✅ | Quantization (absolute, round-half-even); PEC stitch encoder with spec vectors | REQ-FMT-002 partial; encoder unit tests |
| M1.5 ✅ | PES v1 writer: header, PEC header, palette indices, thumbnails | golden files (REQ-FMT-001) |
| M1.6 ✅ | Brother PEC palette table + CIEDE2000 nearest colour | REQ-THREAD-001 spot checks |
| M1.7 ✅ | DST writer (header, ternary records, jump splitting, commands) | golden files; REQ-FMT-007 |
| M1.8 ✅ | stitch testsheet <TS-xx> --profile … -o file (and --list), stitch profiles, stitch explain; the CLI moves to clap (stitch inspect needs the readers: M2.1) | trycmd examples in the docs |
| M1.9 ✅ | Conformance runner v1: requirements × cases matrix, L0 + L1 goldens, job summary | report visible on PRs |
| M1.10 ✅ | Docs: profile, format, test-sheet, diagnostics and command-line reference generated; tutorial “Your first sew-out” with real output | docs check green |
| MC-1 🧵 | Sew TS-01 (orientation, scale), TS-02 (colour changes, stops, jumps with and without trims), TS-10A/B/C (a frame filling each of the PE800’s 3 hoops) from PES v1; same TS-01 from DST if the machine reads DST | Sew-out report filed; profile values confirmed or changed; PES v6 decision made |
M2 — Readers, preview renderer, fuzzing
| Step | Deliverable | Done when |
|---|---|---|
| M2.1 ✅ | PES/PEC reader (bounded, typed errors); stitch inspect file (counts, bounds, colours, stitch lengths, profile check) | empty files REQ-FMT-004; every truncation and byte flip handled |
| M2.2 ✅ | DST reader (trims inferred from cancelling jump runs) | same |
| M2.3 ✅ | Command round trip for every writer/reader pair; machine-equivalence oracle; fresh-seed nightly | REQ-FMT-002, REQ-FMT-003 active |
| M2.4 ✅ | pyembroidery oracle (pinned by hash) as a conformance case in the gates job | REQ-FMT-005 active |
| M2.5 ✅ | cargo-fuzz targets for both readers; nightly job with persisted corpus | REQ-FMT-006; 1 h fuzzing clean |
| M2.6 ✅ | stitchcraft-render: realistic and simple styles from the quantized plan | REQ-RND-001; golden PNG files |
| M2.7 ✅ | stitch preview, stitch convert; the stitch shot generator behind cargo xtask shots; the docs-refresh.yml label workflow | first generated docs images, byte-compared on every PR |
| M2.8 ✅ | Coverage ratchet and mutation-testing baseline; public-API snapshots | thresholds recorded |
M3 — Running stitch family and plan assembly
| Step | Deliverable | Done when |
|---|---|---|
| M3.1 ✅ | Parameter registry (params!, ParamSet, validation, generated reference + JSON Schema) | REQ-PRM-001, REQ-PRM-002; docs --check covers params |
| M3.2 ✅ | Diagnostics registry, stitch explain, generated diagnostics index | every registered code has a page and a case |
| M3.3 ✅ | Design model; SVG adapter v1: paths, groups, transforms, viewBox units, colours, visibility (no inkstitch:* yet) | REQ-SVG-001, REQ-SVG-002 (fuzzed path data) |
| M3.4 ✅ | Running stitch: corners, even spacing, patterns, tolerance; stitch lengths measured straight | REQ-RUN-001..003 |
| M3.5 ✅ | Repeats, bean stitch, random length | REQ-RUN-004, REQ-RUN-005, REQ-RUN-007 |
| M3.6 ✅ | Manual stitch | REQ-RUN-006, REQ-RUN-008 |
| M3.7 ✅ | Lock stitches: every shape, its size and its place at either end of the stitching | REQ-LCK-002, REQ-LCK-004 |
| M3.8 ✅ | Plan assembly: order, collapse, tie-off/jump/trim/tie-in (where ties says), stops, colour changes, the origin and stop position; stitchcraft_engine::plan sends each element to its generator | REQ-ASM-001..003, REQ-ASM-005, REQ-LCK-001, REQ-GEN-002 |
| M3.9 ✅ | Finalize: split, merge, hoop, colour limits, the plan check; stitch plan in.svg -o out.pes --preview out.png --report out.json | REQ-FIN-001..003; the strokes plan case, SVG to machine file |
| M3.10 ✅ | stitch bug-report bundle; stitch plan writes one for a failed plan check or a panic | a bundle reproduces its plan byte for byte (REQ-CLI-001, REQ-CLI-002) |
| MC-2 🧵 | TS-03 (running/bean lengths, the shortest stitch), TS-04 (lock holding test), TS-02B (TS-02 with element-driven trims); kit: stitch testsheet for each | report filed; min-stitch and lock defaults confirmed (REQ-LCK-003) |
M4 — Satin column
| Step | Deliverable | Done when |
|---|---|---|
| M4.1 ✅ | Satin shape: rails and rungs told apart as Ink/Stitch tells them, diagnostics SC-E0201, SC-W0202, SC-W0203, SC-W0205, SC-W0207 | REQ-SAT-005 |
| M4.2 ✅ | Correspondence and stitch placement as in Ink/Stitch (rails turned, sections at the rungs or the nodes), SC-W0210. Satin columns are sewn | REQ-SAT-002 |
| M4.3 ✅ | Pull and push compensation (symmetric/asymmetric), random width and spacing, SC-W0211 | REQ-SAT-001, REQ-SAT-006 to REQ-SAT-008 |
| M4.4 ✅ | Short stitches on curves | REQ-SAT-009, a case set on tight curves |
| M4.5 ✅ | Split stitches (simple, staggered, random) | REQ-SAT-003 |
| M4.6 ✅ | Underlays: centre walk, contour, zigzag | REQ-SAT-004, REQ-SAT-010 to REQ-SAT-012 |
| M4.7 ✅ | Start/end nearest point; generators see their neighbours (ADR 0014) | REQ-GEN-003, REQ-SAT-013, REQ-SAT-014 |
| M4.8 ✅ | A stroke’s width and join, and Ink/Stitch’s design settings, read from SVG as Ink/Stitch reads them. A satin column drawn as one path and too narrow to stitch across is sewn as a stroke, SC-W0212 | REQ-SVG-004, REQ-SVG-005, REQ-SAT-015 |
| M4.9 ✅ | Single-path satin. Its centre line is offset by half the stroke’s width to each side, with the stroke’s join, into 2 rails, as shapely offsets it, with rungs beside its corners and at its nodes, as Ink/Stitch places them | REQ-SAT-016, REQ-SAT-017 |
| MC-3 🧵 | TS-05 (width ladder 1 to 10 mm × 3 densities), TS-06 (underlay comparison); kit: stitch testsheet for each, sewable from M4.6 | report filed; satin defaults tuned |
M5 — Tatami fill
| Step | Deliverable | Done when |
|---|---|---|
| M5.1 ✅ | A fill’s region as the drawing shows it under its fill rule, in parts with holes, its subpaths cut exactly where they meet. Parts too small to sew are left out with SC-W0303, as Ink/Stitch leaves them out. Small fills (SC-W0304), the fill rule (SC-I0306) and fills in parts (SC-W0307) are pointed out | REQ-FILL-001, REQ-FILL-002 |
| M5.2 ✅ | Rows as Ink/Stitch lays them, at the angle and a whole number of row spacings from the origin, graded towards the end spacing. They are cut where they meet the outline, as GEOS cuts them | REQ-FILL-TAT-002, REQ-FILL-TAT-010 |
| M5.3 ✅ | Needle points as Ink/Stitch places them, on the stagger grid from the origin, with skip_last and random stitch length. A fill’s longest stitch defaults to 4 mm, so a parameter can have a default for each family | REQ-FILL-TAT-003 |
| M5.4 ✅ | Routing as Ink/Stitch routes. Every segment sewn once, back and forth, from the needle to the next element, with running stitches along the outline between. Parts sewn one at a time, nearest the needle first, each ending nearest the next. A part too thin for any row sewn round its outline, SC-W0305. fill_method registered. Fills are sewn | REQ-FILL-TAT-001, REQ-FILL-TAT-008 |
| M5.5 ✅ | Travel under the rows (underpath), off the rows already sewn: the cheapest way along 3 gratings of lines, smoothed and kept inside the part. Where no line leads, along rings and rows (DEV-FILL-007) | REQ-FILL-TAT-005 |
| M5.6 ✅ | Underlay as Ink/Stitch sews it: before its top rows each part sews a layer for each angle, each from the point nearest the needle back to it. fill_underlay and its settings registered, with a list of angles as a kind of parameter. A layer too thin for a row is sewn round the outline, SC-I0308 | REQ-FILL-TAT-011 |
| M5.7 ✅ | The underlay on the part shrunk by fill_underlay_inset_mm, and the top rows on the part grown by expand_mm. A part is buffered as GEOS buffers a polygon, and checked against shapely. Pieces a shrinking cuts a part into sewn one after another (DEV-FILL-008), and a part a shrinking wears away said, SC-W0309, SC-W0310. A needle inside the part starts at the nearest node of the lines under the rows | REQ-FILL-TAT-012 |
| M5.8 ✅ | Pull compensation at the rows’ ends, holes and parts kept (DEV-FILL-009), and below 0 read as 0, SC-W0312; gap-fill rows from the grid, inside the region (DEV-FILL-010). The pull compensation settings are shared with satins | REQ-FILL-TAT-006, REQ-FILL-TAT-007 |
| M5.9 ✅ | Coverage metric (stitchcraft_testkit::metrics), at least 98 % from the machine’s shortest stitch to 0.4 mm; the hoop’s whole field plans, its instructions guarded by cargo xtask perf | REQ-FILL-TAT-004, REQ-FILL-TAT-009 |
| MC-4 🧵 | TS-07 (fill + outline registration), TS-08 (density ladder, angles), TS-09 (underlay, travel); kit: stitch testsheet for each, sewable from M5.9 | report filed; fill defaults and pull compensation tuned |
M6 — VectorCraft plug-in (ABI v1) and export from .vectorcraft
M6.1 to M6.7 made the plug-ins with ABI v1 as VectorCraft 0.7 to 0.13 had it. M6.8 to M6.12 use the plug-in features VectorCraft added from 0.14 (RFC), and come before M13.7, whose Auto effect uses them.
| Step | Deliverable | Done when |
|---|---|---|
| M6.1 ✅ | stitchcraft-vectorcraft: read .vectorcraft files of formats 1 to 4, plain or compressed: layers, groups, paths and compound paths, paints as threads, and StitchCraft’s effect records, which say how an object sews. VectorCraft keeps paths in document coordinates, with no transforms to apply. What isn’t stitched is named (SC-W0807, SC-W0808, SC-I0809). Caps: 64 MiB of JSON and the JSON reader’s depth | REQ-VC-001, REQ-VC-004, REQ-VC-005; fuzzed (read_vectorcraft) |
| M6.2 ✅ | The plug-in crate, with manifests generated per family and the ABI shim (abi.rs, whose unsafe is audited). One module per family, whose live effect shows the object’s stitches, or says why it shows none (SC-I0810) | REQ-VC-002 (manifests valid, ≤ 64 params), and the module’s size with the engine recorded (ADR-0005) |
| M6.3 ✅ | Live-effect previews with budget-aware levels: exact, a fill’s rows, and the outline, picked by preview_level or by what the budget affords, the engine’s work priced in instructions measured in VectorCraft’s host | REQ-VC-003 per level, within 5 s / fuel |
| M6.4 ✅ | Tools as VectorCraft’s own commands, checked in its source. Make Compound Path joins a satin column’s 2 paths and adds rungs. Expand Appearance bakes a preview, and Graphic Styles are presets. A group’s StitchCraft effects sew the paths inside it, as VectorCraft draws them. The tools filter is left for later: making a line into a satin column took 51 to 162 million instructions in VectorCraft’s host against about 52 million, and a filter cannot give the objects it draws effects | REQ-VC-006, REQ-VC-007 |
| M6.5 ✅ | stitch export design.vectorcraft, another name for stitch plan, which reads a VectorCraft document as it reads an SVG file. The commands are written in names. stitch:ignore leaves an object or a layer out, and stitch:start and stitch:end mark where a shape starts and ends, never stitched and named until M8 applies such hints | REQ-VC-008, REQ-VC-009; the end-to-end case vectorcraft-patch |
| M6.6 ✅ | The compatibility gate, complete. Level A and Level B run on all four tracks. In Level B, VectorCraft’s own command line makes documents with the plug-ins’ effects, and StitchCraft sews what it saved. An issue per track, a bot PR adopting each new release that passes, and the supported-versions page generated from the record that PR writes | first scheduled run green |
| M6.7 ✅ | UI screenshots pipeline on M0.8’s window probe (cargo xtask compat window), each picture cropped to its menu or dialog; tutorial “A patch in VectorCraft”; xa11y drives VectorCraft to each screen and checks the plug-in’s controls | shots-ui job green |
| M6.8 ✅ | Live effects read their object’s stroke, which VectorCraft gives them from 0.14 with its fills. A stroke or a satin effect follows the topmost stroke that paints, as stitch export does: its width, and from 0.15 its join, cap and miter limit. A satin column drawn as one path and a zigzag stroke show their stitches as wide as their stroke. Without a stroke the effect leaves such an object as it is (SC-I0810). It does so too when it is applied to one of the object’s fills or strokes, which stitch export doesn’t use. Making the rails of a centre line that bends costs many units of the engine’s work. The auto level shows its outline then, and the exact level its stitches. Before M13.7, whose Auto effect chooses a line’s stitches by its width | REQ-VC-010 |
| M6.9 ✅ | VectorCraft exporters for StitchCraft’s machine formats, PES and DST, in File › Export › Export As (VectorCraft 0.14’s exporter plug-ins). An exporter plans the document it is given for the machine profile as stitch export plans a .vectorcraft file. It returns the machine file, with what the plan says as the export’s warnings, and writes nothing when that holds an error. A parameter digitizes the shapes without settings automatically, as --auto does | REQ-VC-011; the Level B case export, in which VectorCraft’s command line exports a document to PES and DST, and the files and the warnings are stitch export’s. A fill of the reference hoop’s whole field exports within an export’s budget in VectorCraft’s host. The test sheets are M6.12’s, which draws them into a document |
| M6.10 ✅ | The plug-ins’ dialogs as the parameter registry describes the parameters, with VectorCraft 0.14’s richer schemas: each one’s label, unit, help and help page, the registry’s groups as sections, and each shown only for the methods it applies to (visibleWhen) | REQ-VC-012; the manifests valid against VectorCraft’s rules, each help page a heading of the book, and shots-ui’s pictures of the dialogs labelled and grouped |
| M6.11 ✅ | A document’s design settings kept with it, as data VectorCraft 0.16 keeps with a document for a filter. Ink/Stitch keeps 3 with a file, collapse_len_mm, min_stitch_len_mm and min_satin_stroke_width_mm, which a VectorCraft document couldn’t set. The machine profile is kept too, though Ink/Stitch has no such profile and its hoop is the page. The Design Settings filter sets them, and the exporters and stitch export plan with them, for the machine kept unless --profile or the export’s dialog names another. What doesn’t read keeps its default (SC-W0812), and a VectorCraft before 0.16, which would drop them, is told so (SC-E0813). A live effect isn’t given the document’s data, and its previews keep the defaults and the reference machine | REQ-VC-013; the Level B case settings, in which a shortest stitch kept changes the PES file, the exports are stitch export’s, and a hoop kept that can’t hold the design stops its export |
| M6.12 ✅ | The tools as filters, which set up objects with their effects (VectorCraft 0.15). Stroke to Satin, once Convert Line to Satin, makes each selected line into a satin column drawn with its rails and rungs, with the satin effect, which sews as the line did. It is a heavy filter, which VectorCraft adds in 0.17: its 18 to 233 million instructions fit a heavy filter’s 1 G. Test Sheet draws stitch testsheet’s sheets drawn as designs into the document, each object with its effect, from 0.16 | REQ-VC-014 and REQ-VC-015. VectorCraft 0.17.0 is pinned, and the Level B cases stroke-to-satin, in which the columns sew as the lines did, and testsheets, in which each sheet sews as its design moved and exports within an export’s budget |
| MC-5 🧵 | TS-12 (a design authored in VectorCraft) and TS-10 again through the full workflow | report filed |
M7 — More stitch types I
One step for each stitch type, with its requirements, cases and docs (playbook). M7.6 to M7.9 come after M13.
| Step | Deliverable | Done when |
|---|---|---|
| M7.1 ✅ | A family of stitch types can declare a key again with a kind and range of its own (parameter registry, rule 7). Ink/Stitch’s zigzag_spacing_mm is a list of lengths for a zigzag stroke and one length for a satin column, and a meander fill reads it as a length that is 0 by default. An element reads the key as its family declares it. The reference pages, the JSON Schema, the plug-ins’ dialogs and the contract check give each family its own | REQ-PRM-005 |
| M7.2 ✅ | Zigzag stroke, sewn as Ink/Stitch sews it. Its angle is a new kind of setting, an angle kept within its range rather than turned by whole turns | REQ-RUN-009 to REQ-RUN-012, and the type listed for MC-6 |
| M7.3 ✅ | E-stitch, sewn as Ink/Stitch sews it. Every satin method warns where stitches lean more than 45° from square to their column (SC-W0208) and where a top stitch is longer than 12 mm (SC-W0209) | REQ-SAT-020 to REQ-SAT-023, and the type listed for MC-6 |
| M7.4 ✅ | S-stitch, sewn as Ink/Stitch sews it | REQ-SAT-024 and REQ-SAT-025, and the type listed for MC-6 |
| M7.5 ✅ | Zigzag satin, sewn as Ink/Stitch sews it | REQ-SAT-026 to REQ-SAT-028, and the type listed for MC-6 |
| M7.6 | The offsetter meets the third criterion of ADR-0005 before contour fill relies on it: 300 insets of a region 150 mm across, of 1,000 points, in under 200 ms. At M0.7 they took 218 to 448 ms, against GEOS’s 104 to 149 ms | cargo xtask perf guards the criterion |
| M7.7 | Contour fill, with its three strategies, as Ink/Stitch sews them | its requirements active and the type listed for MC-6 |
| M7.8 | Meander fill with generated tiles. A node’s meander fill may meet a zigzag_spacing_mm only its zigzag stroke can read, which Ink/Stitch sews as the fill’s default. The step chooses whether StitchCraft does too (rule 7) | same |
| M7.9 | Circular fill | same |
| MC-6 🧵 | TS-11 (a sampler of the new types); kit: stitch testsheet | report filed; the new types’ defaults confirmed |
M13 — Image to embroidery
Goal: a picture traced in VectorCraft sews well from one command, with no settings on its shapes (design). M13 comes after M7.5 and before M7.6 (decided on 2026-10-19). The steps that make every traced shape sew better with the generators StitchCraft has come first, and the steps that make new kinds of shape into stitches follow.
| Step | Deliverable | Done when |
|---|---|---|
| M13.1 ✅ | Automatic elements. --auto on stitch plan and stitch export lets StitchCraft choose the stitches of the shapes that have no settings. A stroke no wider than min_satin_stroke_width_mm sews as a running stitch. Up to the widest satin column, 7 mm, it sews as a satin column as wide as the stroke, its round or square caps included, and a wider one fills the area it paints, as does a line a satin column cannot follow. Areas are tatami fills. SC-I0901 counts the shapes digitized | its requirements active, a traced line sews as wide as VectorCraft draws it, and --auto doesn’t add an error of its own |
| M13.2 ✅ | Neighbours. Each shape reaches under the shapes it touches that sew after it, specks merge into the shape around them, and the background is left out, each said by a diagnostic | the coverage of the edges that the test pictures’ shapes share, measured and recorded |
| M13.3 ✅ | Sewing order. Paint order where shapes overlap, columns and lines after the fills they border, then the fewest colour changes and the nearest element next. --sort-colours orders a design’s elements with settings by the same rules | the test pictures sew with fewer colour changes and jumps than in paint order |
| M13.4 ✅ | Centre lines from a constrained Delaunay triangulation. A shape narrower than min_satin_stroke_width_mm everywhere sews as a running stitch along its centre line, tripled from 0.5 mm, as an automatic stroke that narrow is | its requirements active |
| M13.5 | Columns from shapes. A shape no wider than the widest satin column sews as satin columns between its own outlines, joined where its strips meet and closed round its rings, with an underlay by width, which an automatic stroke’s column gets too | same |
| M13.6 | Shapes in parts. A shape’s broad parts are filled, and its narrow parts sew as columns and lines that overlap the fill | same; TS-13 drawn |
| M13.7 | VectorCraft. The Auto effect previews each path’s stitches, and its dialog sets the rules’ settings, registered then. stitch palette writes a profile’s threads for Image Trace’s Document Library palette. Tutorial “From a picture to a machine file”, and a Level B case in which VectorCraft traces a picture | shots-ui and the compatibility gate green with the new case |
| MC-8 🧵 | TS-13 (test pictures digitized automatically, beside the same shapes sewn in paint order); kit: stitch testsheet | report filed; the rules’ values confirmed or changed |
| M13.8 | Automatic digitizing by default for VectorCraft documents | the end-to-end cases expect it |
| M13.9 🧪 | stitch plan picture.png: VectorCraft’s command line traces the picture, or StitchCraft reads and traces it itself | decision recorded in the design |
M8 — Ink/Stitch interoperability
SVG adapter v2:
- every
inkstitch:*attribute in the compatibility contract - the command symbols not read yet, such as start and end points (
REQ-GEN-001). The origin and stop position commands go into the design settings. Since M3 the adapter applies trim, stop and the ignore commands, and it never stitches commands, connectors or helper paths (REQ-SVG-003). - clones (
<use>) - clip paths, as Ink/Stitch applies them: strokes and fills cut to the clip paths of the element and of
the groups around it, with the region code from M5.1. Until then the adapter sews the whole shape, and
SC-W0802says so. - custom locks drawn as SVG paths, which the adapter reads for the engine. Until then they sew the half
stitch, with
SC-W0503. stitch import-inkstitch file.svg -o file.vectorcraft- L3 differential conformance against a pinned Ink/Stitch with the deviations ledger
Ink/Stitch’s font
library is part of that corpus: hundreds of real Ink/Stitch files, mostly satin, downloaded at a pinned
commit by cargo xtask corpus and never committed, using only fonts whose licence allows it (OFL, CC0,
CC-BY, CC-BY-SA; 132 of the 142 fonts on 2026-10-08).
M14 — Lettering
Goal: names, monograms and words sewn in embroidery fonts, set as Ink/Stitch’s lettering sets them
(review). M14 comes after M8, whose SVG adapter reads the files that Ink/Stitch’s
fonts are made of. Fonts come from a repository of their own, as VectorCraft’s do. They are StitchCraft’s
own fonts, Ink/Stitch’s fonts whose licence allows it (OFL, CC0, CC-BY, CC-BY-SA, each with its licence
file and an ASSETS.md row, never NC, GPL or unclear terms), and the fonts a user has installed for
Ink/Stitch, read in their own format.
| Step | Deliverable | Done when |
|---|---|---|
| M14.1 🧪 | The font repository, the licences it takes, and a reader for a font in Ink/Stitch’s format, from Ink/Stitch’s public documentation of it | ADR-0001 amended for fonts; one font read, its glyphs sewn |
| M14.2 | stitch lettering sets text in a font. Its glyphs are placed by the font’s advances and kerning, with the spacing of letters, words and lines and the alignment the user sets. The scale stays within the heights the font sews at, and a warning names the font’s range outside them | its requirements active |
| M14.3 | Lettering along a path. The colours of a multicolour font sorted, a trim after each letter, word or line as chosen, and lines sewn back and forth | same |
| M14.4 | Lettering in VectorCraft: a text object sews in the embroidery font its StitchCraft setting names, read from the document’s text. The step chooses how the setting is given, as an effect or a name. A preview of the font in VectorCraft waits for a live effect to be given more than its object’s geometry and paints (RFC, P6) | a Level B case |
| MC-9 🧵 | TS-14 (3 fonts at the least and the greatest heights they sew at); kit: stitch testsheet | report filed |
| M14.5 | Text in pictures: the traced shapes inside an area marked stitch:text are replaced by lettering of the marker’s text, as tall as the traced letters (design) | its requirements active |
| M14.6 🧪 | Text in pictures found and read without a marker | decision recorded in the design |
M10 — More stitch types II
Guided fill, linear gradient fill, ripple stitch, tartan fill, cross stitch; auto-run and auto-satin routing tools; per-layer ordering (underlay now, top later). VectorCraft’s tools filter, with Convert Line to Satin, is M6.12.
M11 — Formats II
EXP, JEF, VP3, XXX, U01 writers and readers with goldens and oracle checks; thread catalogues (Madeira, Isacord, Robison-Anton, …) with sourced tables; PES v6 thread lists if not done in M1.
M12 — 1.0
Performance pass and budgets; docs completeness audit (every reference page has examples and images);
translations (UI Fluent catalogues, docs gettext); release automation (release-plz, signed binaries,
checksums); 24 h fuzz soak; MC-7 🧵 full regression set on the machine.
After 1.0
Appliqué workflow, design splitting for small hoops, print worksheets, and an in-tree VectorCraft crate if
wanted. stitch import design.pes -o design.vectorcraft opens a machine file in VectorCraft, its stitches
written as manual stitches by the same document writer as M8’s stitch import-inkstitch.
M9 (VectorCraft plug-in features, upstream)
M9 comes last, after 1.0 and the work after it, and nothing before it waits on ArtCraft. Its id stays M9, as
every step id does. VectorCraft adopted most of the RFC into ABI v1 in
0.14.0 to 0.17.0, and M6.8 to M6.12 use what it adopted. Overlay previews (P3) are in
review already, as storytold/vectorcraft#3807. M9
proposes the rest, one PR for each, with tests, following VectorCraft’s AGENTS.md: heavy live effects (P4)
and the objects beside a live effect’s object (P6). The plug-ins then show every stitch of a large fill, and
start and end their previews at the markers.
Machine testing protocol
Automated tests prove the files are what we intend. Only a machine proves they sew well. This page is the protocol for machine checkpoints (MC-1 … MC-9) on the reference machine: a Brother PE800, whose 5 × 7 in hoop sews at most 130 × 180 mm. Its 4 × 4 in and small hoops have profiles too, and the TS-10 sheets test them (ADR 0013).
Roles
- StitchCraft generates each test sheet deterministically:
stitch testsheet TS-xx -o TS-xx.peschecks it against the profile of the hoop it is for. It prints that profile, the file’s SHA-256, its size and counts, the threads in the order they are sewn (with the Brother palette name the machine shows for each) and what to check after sewing. Its size and counts are the file’s, read back as the machine reads it.stitch preview TS-xx.pes -o TS-xx.pngdraws a picture of what the file sews.stitch testsheet --listlists the sheets. The MC-1 sheets are drawn stitch by stitch, to test the machine and the file formats; from MC-2, sheets are drawn as designs and planned by the engine, so sewing them tests its stitches, locks and plan assembly too. - The tester sews it with the standard setup below, photographs it, measures it, and files a Sew-out report issue (the form asks for everything on this page).
- A maintainer turns the report into changes: profile values, parameter defaults, new conformance cases, or bugs. Every profile change links its report.
Standard setup (keep it constant)
Record anything that differs in the report.
| Item | Standard |
|---|---|
| Fabric | Medium-weight woven cotton (quilting cotton or twill), pressed |
| Stabilizer | One layer of medium tear-away; cut-away for knits (TS-09 only) |
| Needle | 75/11 embroidery needle, fresh at each checkpoint |
| Top thread | 40 wt polyester embroidery thread; colours as listed on the expected-result sheet (any brand) |
| Bobbin | 60 wt (90 wt) bobbin thread, white |
| Hoop | The 5 × 7 in hoop, unless the sheet names another; fabric drum-tight, design centred |
| Machine settings | Note the machine’s speed and its own trim/jump settings (some Brother models trim automatically on long jumps) |
| Starting | Hold the thread’s tail for the first few stitches: at MC-1 a tail left loose pulled the first stitches out |
| Jump threads | The PE800 cuts the thread at thread changes and stops, but at no jump (MC-1). At each thread change and stop, snip the jump threads sewn so far, and snip the rest before any pull test. The sheets never sew over a jump thread that is still there |
Photographing and measuring
- Front and back, flat, in daylight or a daylight lamp, camera straight above.
- A ruler in every photo, along the top and left edges, millimetre side visible.
- Close-ups of anything odd (loops, gaps, puckers, bird’s nests), with the ruler.
- Measure with a steel ruler or calipers; report in mm with one decimal.
- Rate each item 1–5 (5 = indistinguishable from expected) and add a sentence.
Test sheets
stitch testsheet writes each sheet. From M6.12, VectorCraft’s Test Sheet filter draws the sheets drawn as
designs, TS-02B to TS-09, into a document, where they sew as stitch testsheet writes them, moved to the
middle of the artboard (REQ-VC-015). A checkpoint can so sew a sheet through VectorCraft’s workflow.
| Sheet | What it checks | Measure / observe | First used |
|---|---|---|---|
| TS-01 Orientation & scale | An asymmetric “F”, a 100 mm cross with 10 mm ticks, 10 mm squares in the corners (120 × 120 mm) | F not mirrored or rotated; 100 mm line = 100.0 ± 0.5 mm in X and Y; squares square | MC-1 |
| TS-02 Commands | 3 colour blocks, a stop, and rows of 2 dashes separated by jumps of 2, 5, 15 and 30 mm. The left half (red) has a trim before every jump, the right half (blue) none; PES flags every jump as a trim (REQ-FMT-010), so in a PES file the halves differ only in the plan (120 × 70 mm) | The machine stops for colours and the stop; every dash sews; which jumps were cut on each half; any loose loops | MC-1, MC-2 |
| TS-02B Commands, from elements | TS-02 drawn as a design and planned by the engine. Each red dash but the last sets a trim after it (trim_after). The blue ones set none, and the 2 mm gap between them is sewn across, because it is within the 3 mm collapse length. The green line stops halfway (stop_after). The stitching has a lock wherever it starts or ends (120 × 70 mm) | The machine stops for colours and the stop; does any dash pull out while the machine goes on, uncut, to the next? Once the jump threads are snipped, does each dash hold? | MC-2 |
| TS-03 Running stitch | Lines at 1.5, 2.0, 2.5, 3.0 and 4.0 mm stitch length, bean 1× and 2×, circles at tolerances of 0.1, 0.2 and 0.5 mm, and 20 stitches placed by hand at 0.3, 0.4, 0.5, 0.7 and 1.0 mm (60 × 78 mm). The long lines alternate between left to right and right to left. The circles are sewn right to left, and each hand-placed line starts below where the last one ended | Even stitches, smooth curves, solid bean lines, and the shortest hand-placed stitch that sews cleanly: 0.3 mm or less if the 0.3 mm line is clean, since the sheet has no shorter line | MC-2 |
| TS-04 Lock stitches | Lines 30 mm long with every lock shape but custom at both ends, trimmed after: the half stitch on first stitches of 1, 1.5 and 2.5 mm, back-and-forth at 0.5, 0.7 and 1.0 mm, drawn shapes at 70, 100 and 150 % (110 × 65 mm). The rows alternate between left to right and right to left | Snip the jump threads first. Pull each tail gently: does the lock stay, or does the thread pull out? Is the lock visible from the front? | MC-2 |
| TS-05 Satin width ladder | 3 rows at zigzag spacings of 0.3, 0.4 and 0.5 mm, each of 10 columns 20 mm tall and 1 to 10 mm wide, with no underlay (91 × 76 mm). Each column is sewn as drawn and trimmed after; the rows alternate between left to right and right to left, and the columns between down and up, each from its corner nearest the one before | Coverage, fabric showing between stitches, edges straight, long stitches loose, how much narrower than drawn | MC-3 |
| TS-06 Satin underlays | The same column, 6 mm wide and 30 mm tall, with no underlay, a centre walk, a contour, a zigzag, and a contour with a zigzag (62 × 30 mm). Each column is sewn as drawn and trimmed after, alternately down and up from its corner nearest the one before, the centre walk first | Edge sharpness, loft, puckering, underlay showing | MC-3 |
| TS-07 Registration | 3 circles 60 mm across, filled in red with the default underlay at pull compensations of 0, 0.2 and 0.4 mm, then outlined in black with a running stitch along their edges (126.2 × 126 mm). Each fill and outline is trimmed after, and each outline starts where the jump to it arrives | Gap or overlap between fill and outline at 12 points around each circle, how much narrower than drawn each fill sews, puckering | MC-4 |
| TS-08 Fill density & angle | 15 squares 30 mm wide, with the default underlay: rows at spacings of 0.25, 0.3, 0.4, 0.5 and 0.6 mm, top to bottom, and columns at angles of 0°, 45° and 90° (100 × 170 mm). Each square is trimmed after | Coverage, stiffness, puckering, visible furrows. At 0.25 mm, below the Brother’s shortest stitch of 0.3 mm, a sliver at each row’s start in the 0° and 90° squares, where the rows meet the edges square on (DEV-FIN-001). The 45° square’s rows end 0.35 mm apart along its edges and keep their starts. If the machine sews 0.25 mm stitches cleanly, the report can lower the profile’s shortest stitch | MC-4 |
| TS-09 Fill underlay & travel | A rectangle 50 × 30 mm with a square hole and a round hole, and a bar 6 mm to its right, all one fill, sewn 3 times (66 × 114 mm). The top copy has no underlay. The others have the default underlay, and the bottom one 2 gap-filling rows too. Each copy is trimmed after | Travel hidden? Does the fill hold at both ends of the jump to the bar? Holes clean? A gap where the rows round a hole meet? | MC-4 |
| TS-10A/B/C Hoop size | Frames of 100 × 100 mm (A, the 4 × 4 in hoop), 130 × 180 mm (B, the 5 × 7 in) and 20 × 60 mm (C, the small hoop), each as large as its hoop’s field, with a centre cross and an “F” in the top-left corner | The machine takes each design in its hoop without asking for a larger one, and sews it to size. The small hoop’s field is the right way round | MC-1, MC-5 |
| TS-11 New stitch types | Samples of the milestone’s new types. So far zigzag strokes (M7.2), straight and curved, at spacings of 0.4, 1 and 2 mm and widths of 1, 2 and 4 mm, and one at 45°. E-stitches (M7.3) 3 and 5 mm wide at spacings of 2 and 3 mm, along a straight edge and round a circle, on appliqué fabric. S-stitches (M7.4) 6 and 10 mm wide at spacings of 0.4 and 0.6 mm, straight and curved, the 10 mm ones split at 3 mm by the default and the staggered methods. Zigzag satins (M7.5) 4 and 8 mm wide at spacings of 0.4 and 1 mm, straight and curved, beside satin columns of the same widths and spacings, and one 8 mm wide at 1 mm over a contour underlay | Per type, as listed on its expected-result sheet. A zigzag sews narrower than drawn, and the report says by how much, whether its sides are even round curves and which is the narrowest width that sews cleanly. The E-stitch’s spine should cover the appliqué’s raw edge, and its spikes hold it without puckering. The S-stitch should cover its band at 0.4 mm with its steps along the edges even, and the report says whether the default method’s splits leave a furrow along the band that the staggered ones do not. The zigzag satin should cover its band at 0.4 mm as its satin column does, and the report says whether it sews narrower than the satin column beside it. Its contour underlay should not show past the zigzag at either end | MC-6 |
| TS-12 Real design | A design up to 130 × 180 mm authored in VectorCraft with fill, satin, running stitch and 3 colours, its PES file written by VectorCraft’s File › Export › Export As (M6.9) | Overall quality, registration between colours, and time against the estimate. Satin columns start and end at their nearest points: is the way the needle takes under them hidden? Satin columns drawn as one path: are they as wide as their strokes, and do their corners sew clean? A graphic style applied to a group: do the paths inside sew as VectorCraft shows them, with the style’s settings and colour? A satin border of 2 circles joined by Make Compound Path: do its stitches go straight across all the way round? A satin column that Stroke to Satin made of a line (M6.12): does it sew as the line did with the satin effect? | MC-5, MC-7 |
| TS-13 Pictures digitized automatically (M13) | Test pictures drawn in code and digitized with --auto, each beside the same shapes sewn in paint order. A badge is a broad fill inside a ring. Block letters are 4, 6, 10 and 15 mm tall. A cartoon face has black outlines 1, 2 and 4 mm wide. Lines are 0.3, 0.6 and 0.9 mm wide, and specks lie round a disc | Fabric showing between colours, on both halves, and on the digitized half whether each area’s 0.4 mm overlap under the next covers the gap or shows as a ridge (M13.2). Whether a stacked trace’s digitized half, its covered parts cut away, sews as flat as the abutting one, and what became of the specks round the disc (M13.2). Where a shape runs on beside an area without touching it, whether the fabric that shows there as drawn reads as a gap (M13.2). The smallest letters that read. Whether each outline’s columns cover the edges of the fills beside them, and whether the triple runs show as wide as their lines. A column’s square end past the end of a line with round caps (M13.1), and how far it shows past the round end. Small traced rings and hairpins, filled because no satin column can follow them (M13.1), and how solid they sew beside their columns’ neighbours. Colour changes, jumps, trims and time against the half in paint order, on a row of badges drawn badge by badge too, as Auto Grouping draws them (M13.3). Whether any shape covers one drawn over it, which the order must never let happen, and whether each line sewn after the fill it borders covers the fill’s edge (M13.3). Lines traced as areas, 0.3, 0.6 and 0.9 mm wide, sewn along their middles (M13.4): whether each reaches as far as the line drawn, whether the 0.6 and 0.9 mm lines’ triple runs show as wide as they are drawn and the 0.3 mm line’s single run doesn’t look thin, and whether the fills beside a line, reaching under it, leave fabric showing along it. Whether a junction’s third branch, which ends at the line it meets, leaves a gap or a knot there (M13.4) | MC-8 |
| TS-14 Lettering (M14) | 3 embroidery fonts, a block, a script and a small one, each at the least and the greatest heights it sews at, and a name along an arc | Whether every glyph reads at its least height, satin coverage at its greatest, and whether the locks of small glyphs hold after the trims | MC-9 |
Checkpoint calendar
| Checkpoint | After | Sheets | Questions it must answer |
|---|---|---|---|
| MC-1 | M1 | TS-01, TS-02, TS-10A/B/C | Does the machine read our PES? Right size and orientation? Which trim encoding works? Does each hoop take a design as large as its profile allows, from PES v1? |
| MC-2 | M3 | TS-03, TS-04, TS-02B | Minimum stitch length and lock defaults? Do the locks hold where the machine cuts the thread, and where a jump thread is snipped? |
| MC-3 | M4 | TS-05, TS-06 | Satin spacing, width limits, underlay defaults, pull compensation |
| MC-4 | M5 | TS-07, TS-08, TS-09 | Fill spacing, pull compensation, gap-filling rows, underlay and travel defaults |
| MC-5 | M6 | TS-12, TS-10 | Is the VectorCraft workflow complete and correct? Does a test sheet drawn by the Test Sheet filter sew as stitch testsheet writes it? |
| MC-8 | M13.7 | TS-13 | Do pictures digitized automatically sew better than the same shapes in paint order? Are the widest satin column, the narrowest triple run, the overlap and the underlays by width right? |
| MC-6 | M7 | TS-11 | New stitch types acceptable? |
| MC-9 | M14 | TS-14 | Do the fonts sew legibly across the heights they claim? |
| MC-7 | M12 | all | Release regression |
Report template
The GitHub issue form Sew-out report asks for these fields; this is the same template for notes kept elsewhere:
### Sew-out report — TS-__ — MC-__
- StitchCraft version / commit:
- File SHA-256 (from `stitch testsheet`):
- Machine model and firmware: Hoop:
- Fabric / stabilizer / needle / thread / bobbin: (standard? if not, what)
- Machine speed and trim/jump settings:
| Check | Expected | Measured / observed | Rating 1–5 |
|---|---|---|---|
| | | | |
Problems seen (loops, gaps, puckers, thread breaks, bird's nests, skipped trims):
Photos attached: front ☐ back ☐ close-ups ☐ ruler visible ☐
From report to change
- A maintainer reads the report and labels it
sewout:pass,sewout:tuneorsewout:bug. - Tune: the profile or default changes in a PR that links the report; a conformance case pins the new value; the docs’ parameter pages show the evidence link.
- Bug: a failing conformance case reproducing it comes first, then the fix.
- Pass: the checkpoint row in
ROADMAP.mdis ticked with the report link.
How people use Ink/Stitch
This review lists what Ink/Stitch’s users make and ask for, with the roadmap step that covers each in StitchCraft. The most frequent come first, as far as the sources show. It was made on 2026-10-19 to look for major uses the roadmap lacked, after automatic digitizing of pictures turned out to be missing from it. It changed the roadmap in 4 places (changes).
Sources
- Ink/Stitch’s documentation and tutorials, at inkstitch.org: the workflow, the Params dialog, the tools, lettering, thread colours, troubleshooting, and import and export.
- Its release notes from v3.0.0 (2023) to v3.3.0 (July 2026), and its lettering web page, Ink/Stitch Create.
- Questions and answers in public, on Ink/Stitch’s GitHub issues and discussions, the Inkscape forum’s embroidery section and Reddit’s r/Machine_Embroidery, and the titles of tutorial videos and how-to articles.
No survey of Ink/Stitch’s users has published its results. The survey of December 2024 serves a doctoral thesis, and its answers are not public. So the order below is how often a use came up in these sources. It measures what people write about, which can differ from what they do most.
What people make
| Use | What the sources show | StitchCraft |
|---|---|---|
| Shapes drawn by hand, their stitches set one by one | Ink/Stitch’s basic use and workflow are to draw, set the stitches, and save a machine file. The Params dialog sets each element’s stitches, with a preview | M6’s effects in VectorCraft. Their dialogs show the parameters’ names alone until M6.10 labels and groups them, with the richer parameters VectorCraft 0.14 adopted |
| Pictures made into embroidery | The workflow’s way from a picture is Trace Bitmap, Simplify and Cleanup Document, with the stitches then set by hand. Tutorial videos trace a cartoon and text and logos. An article calls the result finicky (Machine Embroidery Geek). A traced logo showed yellow behind its black lettering and sewed too many stitches, and Ink/Stitch crashed when its letters were made satin (Inkscape forum). A logo 3 inches wide came out muddled, with its letters filled where satin was needed. Its owner then paid a digitizer 5 dollars (Reddit) | M13 (design) |
| Lettering in embroidery fonts | Lettering has a menu of its own, with lettering along a path and batch lettering for lists of names. The font library lists 93 fonts, each with the heights it sews at, and Ink/Stitch Create sets lettering on the web. The advice for small text is to use Ink/Stitch’s fonts (Reddit). Text typed in an installed font fails as a fill whose border crosses itself (Inkscape forum) | M14, before 1.0. Large text in any font, made into paths with Type › Create Outlines in VectorCraft, sews by M13’s rules |
| Patches and appliqué | Searches for tutorials find many patches, from a beginners’ guide to patch shapes with a merrow-like edge. Ink/Stitch has an appliqué tutorial and the E-stitch for appliqué | Fills, satin borders, the E-stitch and stops sew patches today. An appliqué tool with placement and tack-down lines stays after 1.0 |
| Fewer colour changes and a good order | The FAQ’s “Too many color changes” (help), and issues such as #1337, “Despite two colors I have 6 color changes”, and #378, “reorder by color”. The routing tutorial leaves the order to the user. Its tools restack elements and route satin columns and running stitches, and Jump to Stroke turns a jump into a running stitch | M13.3 orders automatic elements, and with --sort-colours any design. M10 has the routing tools |
| The colours of real threads | Thread palettes installed into Inkscape, and Apply Palette for the nearest thread. A guide from the City of San Diego picks Ink/Stitch’s Brother palette before anything is drawn | The nearest colour of the PEC palette (M1.6), stitch palette for tracing (M13.7), thread catalogues (M11) |
| Bought designs opened, resized or moved to a machine | Requests to open a PES file to enlarge it (#1716) or to shrink it (#2022). Ink/Stitch opens machine files as manual stitches, and its maintainers warn that scaling changes the density | stitch inspect, stitch preview and stitch convert (M2). Opening a machine file in VectorCraft is added after 1.0 |
| Designs larger than the hoop | Requests to split a design for a small hoop (#182, discussion 1341) | The hoop check (M1.3). Splitting stays after 1.0 |
| Checking a design before sewing | The simulator, realistic previews and a density map | VectorCraft’s previews (M6.3) and stitch preview |
| Worksheets for the machine’s operator | Print PDF, with the colour blocks, thread names and stitch counts | The report and the picture that stitch plan writes. Worksheets stay after 1.0 |
| Other stitch types | Ripple, contour, meander, circular, guided and gradient fills, tartan, cross stitch from pictures (v3.3.0), and tutorials on redwork, cutwork and free-standing lace | M7.6 to M7.9, and M10 |
What people struggle with
| Trouble | Sources | StitchCraft |
|---|---|---|
| Installing and starting Ink/Stitch, above all on macOS | Issues such as #4553, “v. 3.3.0 does not run on iMac/Sequoia”, and #600, “Ink/Stitch is greyed out in Inkscape”. A Params dialog and previews that never open (Inkscape forum) | The plug-ins are WebAssembly modules that VectorCraft loads, with no other program to install. Signed binaries come with 1.0 (M12) |
| Settings that are hard to choose | A reader found the Params settings hard to judge in advance (Machine Embroidery Geek). Another found Ink/Stitch “not beginner friendly” (Reddit) | M13 chooses the stitches, and VectorCraft previews them |
| Shapes that Ink/Stitch refuses | Fill: Shape is not valid for text and traced shapes whose borders cross (EduTechWiki) | A fill sews the region a viewer paints, crossings included (M5.1) |
| Long waits | An hour of “Rendering stitch-plan” on a complex drawing (discussion 1834) | Budgets bound every loop over input, and cargo xtask perf guards the time of the hoop’s whole field (NFR-PERF-1) |
| Small text that does not come out crisp | Lettering on a fill, in a logo 2 inches wide, on a PE800 (Inkscape forum) | M14’s fonts at the heights they sew at, and M13’s rules for fills under columns |
Changes to the roadmap
- M13, image to embroidery. It is new, and comes after M7.5 and before M7.6.
- M14, lettering. It moves from after 1.0 to after M8, whose SVG adapter reads the files of Ink/Stitch’s fonts. Its last steps set the text in a picture again as lettering.
- Colour sorting for any design.
--sort-coloursgives a design with settings the order of M13.3. It was after 1.0. - Machine files opened in VectorCraft. This is planned after 1.0.
stitch import design.pes -o design.vectorcraftwrites their stitches as manual stitches, with the same document writer as M8’sstitch import-inkstitch.
Contributing
Welcome. StitchCraft is built to be easy to change safely: the rules are checked by machines, common changes follow playbooks, and every behaviour has a requirement and a test.
Start here
- Read
AGENTS.mdat the repository root (the short contract for humans and agents) and the architecture map. - Pick a step from the roadmap or an issue labelled
good first issue. - Set up: install Rust (the toolchain in
rust-toolchain.tomlinstalls itself), thenrustup target add wasm32-unknown-unknown. Thewasm-goldensgate needswasm32-wasip1too, and wasmtime, andcargo xtask ciskips it without them. - Before every push:
cargo xtask ci.
Playbooks
| Change | Playbook |
|---|---|
| Add a stitch type | New stitch type |
| Add or change a parameter | New parameter |
| Add a machine format | New format |
| Add a diagnostic | New diagnostic |
| Review a PR | Review checklist |
| Write docs, messages or help text | Writing style |
Ground rules
- Never copy GPL code: Ink/Stitch’s source (GPL-3.0) may be read to understand its behaviour, but nothing from it is copied or transliterated. Write the behaviour into the design docs in your own words and code from them (ADR-0012).
- No panics, no
unsafe, deterministic output — the lints will tell you (guardrails). - One roadmap step per PR, its id in the title; requirements and cases first.
- Docs in the same PR as the behaviour change; never hand-write a parameter’s reference.
- Be kind. Reviews are about code, not people.
Playbook: add a stitch type
A stitch type touches several registries, but each touch is small. Follow the steps in order; the gates catch anything you miss.
- Design page first. Add or extend the section in
docs/src/design/algorithms/with: purpose, parameters (registry keys; Ink/Stitch names where they exist), the algorithm in your own words, properties, diagnostics, references. Never copy code (ADR-0012). - Requirements. Add
REQ-…entries toconformance/requirements.tomlwithstatus = "planned"and the milestone. - Cases. Add cases under
conformance/cases/<area>/: typical shapes, the degenerate corpus entries that matter, and one case per diagnostic. They fail for now — that is the point. - Module. Create
crates/stitchcraft-engine/src/generators/<name>/withmod.rs(theGeneratorimpl),params.rs(theparams!block, doc comments as help text) andtests.rs. Keep files under 800 lines; split by concern (sampling, routing, compensation). - Register the generator in the generator table and its params in the registry list (one line each).
- Make the cases pass. Run
cargo xtask conformance --filter <area>; switch the requirements toactive. - Docs.
cargo xtask docsregenerates the parameter pages; write the user-facing page indocs/src/user/(what it is for, when to use it, two or three declared shots); runcargo xtask shots. - Gate.
cargo xtask ci, then open the PR with the roadmap step id. - Sew-out. If the type is new to the machine, list it for the next machine checkpoint (TS-11).
Playbook: add or change a parameter
- Name it. If Ink/Stitch has the same parameter, use its attribute name and meaning, and the default it sews for a file that sets none (naming rules). Otherwise follow the same style.
- Declare it in the generator’s
params!block: kind, default (written as a design stores it), range or options, section, visibility condition, origin, and the doc comment — the help users will read, so write it for an embroiderer and mention the visible effect. A new declaration (a new…Paramsstruct) also gets one line institchcraft_engine::registry::PARAMETERSand its generated page a line indocs/src/SUMMARY.md(cargo xtask docs --checksays which). - Use it through the typed struct; never read raw
ParamSetvalues in a generator. - Test it: a conformance case where the parameter changes the result in the documented direction, plus a property test if it has a monotonic effect.
- Check limits: the plug-in test fails if the family’s VectorCraft manifest would exceed 64 parameters, which every VectorCraft release takes. If it does, discuss moving rarely used parameters to a preset, or a group: VectorCraft 0.14 takes 256 parameters, 64 of them outside groups.
- Regenerate docs:
cargo xtask docswrites the reference page, the JSON Schema and the compatibility contract’s StitchCraft column, and checks the declaration against the contract. An Ink/Stitch parameter needs a compatible type and the default Ink/Stitch sews, and a deliberate difference needs an entry inconformance/deviations.toml. Add a before/after shot if the effect is visual. - Changing an existing parameter’s meaning, unit or default is a breaking change: add a migration and a case with old input, and a changelog line.
Playbook: add a machine format
- Collect the facts from public specifications or MIT/Apache-licensed implementations, and record
the sources at the top of the module and, if data is derived, in
NOTICE. - Write the format page section in
docs/src/design/formats.md: structure, units, axis, command encoding, per-record limits, colours, gotchas. - Requirements and cases: golden files for canonical plans, round trip, commands, empty files,
limits (
REQ-FMT-*per format). - Module:
crates/stitchcraft-formats/src/<format>/withwrite.rs,read.rs,consts.rs,tests.rs. Readers: check every length and offset before use, cap counts, return typed errors;indexing_slicingis denied here. - Fuzz target: a body
read_<format>institchcraft-testkit::fuzz(its tests run it on the new golden files), a one-line target infuzz/fuzz_targets/read_<format>.rs, its name in thefuzzmatrix ofnightly.yml; run it for 10 minutes locally (cargo +nightly fuzz run read_<format>). The format’s golden files join the seed corpus by themselves. - Oracle: add the format to the pyembroidery oracle job if pyembroidery supports it.
- Profiles: if a machine uses it, add or update a profile — and schedule a machine test before calling it supported.
Playbook: add a diagnostic
- Pick the code from the right range (diagnostics); never reuse a retired one.
- Register it in
stitchcraft-core’s diagnostics table with severity, title and the long explanation: what it means, why it matters for the sew-out, how to fix it, an example. - Emit it where the problem is detected, with the element, a location when there is one, a specific message (“0.18 mm tall; rows are 0.25 mm apart”) and a fix hint or applicable fix.
- Trigger it in a test named
diag_sc_w0305_<what>, in the crate that emits it: produce the code from real input through the public API and assert the whole text a user reads (d.to_string()and the fix).cargo xtask conformance --checkfails until such a test exists. - Regenerate docs:
cargo xtask docsadds the explanation page; consider a shot showing the problem and the fix.
Review checklist
CI checks the mechanical rules. Reviewers check what machines cannot:
Behaviour
- The PR implements exactly its roadmap step; requirements it touches are listed and
active. - New behaviour has conformance cases, including degenerate inputs and each new diagnostic.
- Golden changes are explained and expected (label
golden-change+ changelog line). - No silent fallback: every degraded path emits a coded diagnostic.
Design
- Code sits in the right crate (where does my code go?).
- Types make invalid states unrepresentable; functions are total.
- Loops over data charge the budget; recursion is bounded.
- Modules have one job; names say what things are in embroidery terms.
Provenance
- Nothing was copied, pasted or transliterated from Ink/Stitch or other GPL/AGPL code; behaviour is in the design docs in our own words.
- New data tables (palettes, format constants) cite their source.
Docs and UX
- User-facing docs updated in this PR; help text in the registry reads well for an embroiderer.
- New prose follows the writing style, and
cargo xtask prosepasses. - Shots refreshed if pictures should change; alt text is meaningful.
- Messages are specific, actionable and free of internal jargon.
Machine
- If the change affects what sews, it is listed for the next machine checkpoint.
Writing style
These rules hold for StitchCraft’s prose: docs, messages, help text and CHANGELOG entries. The docs have 3 kinds of reader. Embroiderers and contributors read them, and AI agents load them as context. Plain sentences serve all three, and they cost an agent fewer words.
cargo xtask prose checks every Markdown line that a branch adds. It runs Vale
with two styles from .vale/styles:
- StitchCraft, the house rules. It checks the projects’ names and statements that give software a mind.
- ai-tells, phrasing and punctuation that machine-written text overuses, from vale-ai-tells (MIT). The rules are kept in the repository as their authors wrote them.
Lines that a branch does not touch are not checked. Older pages follow the rules when they are rewritten. Pages generated from the registries are not checked either: their tables, labels and deviation notes have a format of their own. Their prose is the registries’ help text, which follows these rules all the same.
Rules
- State the fact first. Start with what the software does: “The reader leaves connectors out.” Put the reason after it. Give a reason only when someone has confirmed it.
- Give one idea per sentence. A sentence that needs a semicolon or a dash is two sentences. Use a list for 3 or more items.
- Say what software does. It reads, writes, sews, skips and reports. It does not know, want, decide or carry.
- Use one name for one thing. Take the name from the glossary below, and do not swap in a synonym for variety.
- Give the number. Write “0.3 mm” or “3 jump records” in place of “very short” or “several”.
- Cut filler. Delete words such as
simply,just,note thatandin order to. - Write plain statements. Avoid contrast formulas such as “X, not Y”, a colon that sets up a reveal, slogans and dramatic fragments.
- Keep dashes out of prose. Use a comma, a full stop or brackets in place of an em dash. Write a range with “to”: “M3.1 to M3.5”, “0.1 to 25 mm”.
- Name a thing or a task in a heading. “Lock stitches” and “Convert a file” are good headings. A question or a “How it works” heading is not.
- Describe the current design. History goes in
CHANGELOG.mdand Git. A design page says how the code works today. - Keep each fact on one page. Other pages link to it.
cargo xtask docs --checkreports a sentence of 12 or more words that is on 2 pages. - Spell in British English, as the code does: colour, centre, millimetre.
A rule can have an exception. To accept a match, turn the rule off around the lines with a comment, and give the reason in the same comment:
<!-- A field name and its value, as the machine shows them. -->
<!-- vale ai-tells.ColonUsage = NO -->
Format: PES version 1
<!-- vale ai-tells.ColonUsage = YES -->
Glossary
| Use | For | Do not use |
|---|---|---|
| design | what is embroidered: its elements and settings | artwork, pattern (for the whole) |
| element | one object of the design, sewn as a unit | object, shape (for the element) |
| stitch plan, plan | the needle positions and commands, in sewing order | stitch list, stitch file |
| needle point | one place where the needle goes through the fabric | penetration, drop |
| stitch | the thread between two needle points | step |
| jump | a move of the frame without sewing | travel (for a jump) |
| trim | a cut of the thread | cut (in prose about plans) |
| stop | a pause of the machine | halt, pause command |
| colour change | a change to the next thread | thread change |
| colour block | the stitches sewn with one thread | colour section |
| lock stitches | the tie-in at the start of a run and the tie-off at its end | knots, tacks |
| machine file | a PES or DST file | embroidery file, export |
| sew-out | sewing a design on a machine to check it | test run |
| test sheet | a design made to test one thing on a machine | sampler |
| hoop | the frame the fabric is stretched in | frame (for the hoop) |
| Ink/Stitch, StitchCraft, VectorCraft, pyembroidery | the projects, spelled their way | Inkstitch, Stitchcraft |