Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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

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 plan sews 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.

SectionUse it toPages
InstallBuild the stitch command
TutorialsLearn by doing, start to finishYour first sew-out on a Brother, Turn an SVG into a PES file, A patch in VectorCraft
How-to guidesSolve one problemCheck a file, convert it, fit the hoop, report a bug
Stitch typesSee what each stitch does and what its parameters changeRunning stitch, lock stitches
ReferenceLook up commands, parameters, messages and machines
ExplanationUnderstand why embroidery behaves as it doesEmbroidery 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-W0307 names the fill, whose 2 rectangles are sewn one after the other, with a jump between them.
  • SC-I0504 says 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

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:

Planned, each with the milestone that brings its feature:

GuideMilestone
Make lettering-style satin bordersM4
Stop a fill from puckering (density, underlay, compensation)M5
Bring an Ink/Stitch design into VectorCraftM8

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 profiles lists 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-I0605 counts 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-I0605 counts.

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 --auto does.
  • Machine, the machine and hoop the design is planned for: document, the one the document keeps (below), or brother-pe800-5x7 when it keeps none, unless another is chosen. stitch profiles lists 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-5x7 by 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:

  1. Draw the column’s 2 edges as 2 paths.
  2. Select both and choose Object › Compound Path › Make (Cmd+8).
  3. 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.

  1. Give each line a stroke as wide as the column.
  2. 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:

  1. Select an object whose StitchCraft effect is set as you like it.
  2. Choose Window › Graphic Styles › New Graphic Style.
  3. 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 typeSewsAvailable
Running stitchstrokes: outlines, details, lettering at small sizesnow
Bean stitchstrokes, each stitch sewn 3 times or morenow
Manual stitchstrokes whose nodes are the needle pointsnow
Zigzag strokestrokes as wide as their line: light borders, couched looksnow
Satin columnwide strokes and bordersnow
E-stitchsatin columns as appliqué edges: a spine along one rail, with spikes to the othernow
S-stitchsatin columns sewn one way at a time: a stitch across at each step, joined along the edgesnow
Zigzag satinsatin columns sewn as a zigzag, every stitch slanting from one edge to the othernow
Tatami fillareas, in rows of running stitchesnow

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 mm0.5 mm2 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 stitchmanual 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:

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.

  • ties chooses which ends get a lock. By default both ends do. It can also choose the start or the end alone, or neither.
  • force_lock_stitches adds 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_stitches is 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:

arrowbowtiecrossstar
simpletrianglezigzaghalf_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_after cuts the thread after the element, with a tie-off before the cut.
  • stop_after pauses 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.

PageSourceStatus
Command linegenerated from the stitch command definitionavailable
Machine profilesgenerated from profile dataavailable
File formatsgenerated from the format registryavailable
Test sheetsgenerated from the test-sheet registryavailable
Diagnostic codesgenerated from the diagnostics registryavailable
Glossaryhand-writtenavailable
VectorCraft compatibilitygenerated from the compatibility gate’s record, compat/supported.tomlavailable
Parameters (one page per stitch type)generated from the parameter registryavailable
Ink/Stitch compatibility contractgenerated from conformance/inkstitch-params.toml and the parameter registryavailable

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

FactValue
Hoop (sewing field)130 × 180 mm
File formatPES v1
Stitch length0.3 mm to 12 mm (lock stitches from 0.2 mm)
Trimsthe format’s trim command
Thread paletteBrother 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

FactValue
Hoop (sewing field)100 × 100 mm
File formatPES v1
Stitch length0.3 mm to 12 mm (lock stitches from 0.2 mm)
Trimsthe format’s trim command
Thread paletteBrother 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

FactValue
Hoop (sewing field)20 × 60 mm
File formatPES v1
Stitch length0.3 mm to 12 mm (lock stitches from 0.2 mm)
Trimsthe format’s trim command
Thread paletteBrother 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.

FormatExtensionColour changes and stopsColours stored as
PES v1.pesat most 255indices into the Brother PEC palette (nearest colour, CIEDE2000)
DST.dstat most 999nothing: 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.

CodeSeverityTitle
SC-E0004ErrorBudget exhausted
SC-E0005ErrorPreview too large
SC-E0009ErrorInternal check failed
SC-E0010ErrorNothing to stitch
SC-W0011WarningStitch type not sewn yet; element skipped
SC-E0012ErrorBug-report bundle could not be replayed
SC-E0101ErrorParameter has the wrong type or an unknown choice
SC-W0102WarningParameter clamped to its allowed range
SC-W0105WarningUnknown parameter preserved but ignored
SC-E0201ErrorSatin column has no rails
SC-W0202WarningSatin rails taken as the two longest subpaths
SC-W0203WarningSatin rung does not cross both rails
SC-W0205WarningSatin subpath is one point; left out
SC-W0206WarningSatin contour underlay too short for its insets; it keeps its length
SC-W0207WarningSatin rung crosses a rail more than once; left out
SC-W0208WarningSatin stitches lean more than 45° from square to the column; add a rung here
SC-W0209WarningSatin stitch longer than 12 mm; it may snag
SC-W0210WarningSatin rails without rungs have different numbers of nodes
SC-W0211WarningSatin push compensation too long for a rail; that rail keeps its length
SC-W0212WarningSatin column drawn as one path too narrow; sewn as a stroke
SC-W0213WarningParts of a satin column drawn as one path left out
SC-E0214ErrorSatin column drawn as one path could not be made into rails
SC-W0215WarningSatin column too small for the shortest stitch; left out
SC-E0216ErrorSatin column has no section to sew
SC-W0303WarningParts of a fill too small to sew left out
SC-W0304WarningFill smaller than 1.4 mm²
SC-W0305WarningFill part too thin for rows; sewn round its outline
SC-I0306InfoNonzero fill rule fills a part inside that the even-odd rule leaves empty
SC-W0307WarningFill in parts that are apart; each sewn on its own
SC-I0308InfoFill underlay too thin for rows; sewn round the outline
SC-W0309WarningFill’s expand shrinks a part away; sewn at its own size
SC-W0310WarningFill’s underlay inset shrinks a part away; underlay over all of it
SC-W0312WarningFill’s pull compensation below 0; read as 0
SC-I0313InfoFill drawn twice along its edge; Ink/Stitch fills the part left empty
SC-W0314WarningLegacy fill sewn as a tatami fill without underlay
SC-W0401WarningPath too small for the shortest stitch; skipped
SC-W0402WarningStitch length below twice the shortest stitch; raised
SC-W0403WarningHand-placed stitch shorter than the shortest stitch; point left out
SC-W0404WarningHand-placed stitch longer than max_stitch_length_mm; kept for the shortest stitch
SC-W0405WarningZigzag stitch shorter than the shortest stitch; the zigzag sews narrower
SC-W0502WarningLock stitch shorter than 0.2 mm; lengthened
SC-W0503WarningCustom lock cannot be sewn as written
SC-I0504InfoStitches shorter than the shortest stitch merged
SC-W0505WarningTrim or stop after an element that sews nothing; left out
SC-E0601ErrorToo many colour changes for the file format
SC-E0602ErrorDesign too large for the file format
SC-E0603ErrorMachine file could not be read
SC-W0604WarningThread colours unknown
SC-I0605InfoLong jumps cut in DST
SC-W0606WarningThreads share a Brother colour
SC-E0701ErrorDesign does not fit the hoop
SC-W0702WarningDesign is larger than the comfort zone
SC-I0703InfoStitches longer than the machine’s longest stitch; split
SC-E0801ErrorSVG could not be read
SC-W0802WarningSVG feature ignored
SC-I0805InfoObject left out, as the file asks
SC-W0804WarningElement geometry invalid or out of range
SC-W0803WarningVectorCraft file from a newer format version; read best-effort
SC-E0806ErrorVectorCraft file could not be read
SC-W0807WarningVectorCraft object or feature not stitched
SC-W0808WarningVectorCraft geometry unusable
SC-I0809InfoVectorCraft layer or guide left out, as it does not print
SC-I0810InfoVectorCraft live effect leaves the object as it is
SC-I0811InfoVectorCraft object left out, as its name asks
SC-W0812WarningVectorCraft design setting ignored
SC-E0813ErrorVectorCraft too old for the filter
SC-E0814ErrorVectorCraft line not made into a satin column
SC-I0901InfoShapes digitized automatically
SC-I0902InfoLine a satin column cannot follow; filled
SC-W0903WarningLine too wide to fill; left out
SC-I0904InfoAreas reach under the shapes sewn after them
SC-I0905InfoSpeck merged into a neighbour
SC-W0906WarningBackground left out
SC-I0907InfoElements 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.1in and 7pt are all accepted, and 2.5 mm is not.
  • Angles are degrees (45, 45° or 45deg), turned into the range from −180 (exclusive) to 180.
  • Percentages may end in %.
  • On or off is true or false, in any case. yes, y, t and 1 are on too, and no, n, f, 0 and off off. on is 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, so 2.5,1.2 and 3,5 are 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

GroupParametersApplies to
Common parameters16every stitch type
Stroke parameters1Running stitch, Ripple stitch, Zigzag stitch, Manual stitch
Running parameters4Running stitch, Ripple stitch
Repeat parameters2Running stitch, Ripple stitch, Zigzag stitch, Manual stitch
Zigzag stroke parameters3Zigzag stitch
Satin parameters34Satin column, E-stitch, S-stitch, Zigzag satin
Pull parameters2Satin column, E-stitch, S-stitch, Zigzag satin, Tatami fill
Fill parameters2Tatami fill, Circular fill, Contour fill, Cross stitch, Guided fill, Linear gradient fill, Meander fill, Tartan fill, Legacy fill
Tatami parameters15Tatami fill

Every parameter

KeyLabelGroup
angleAngleTatami parameters
bean_stitch_repeatsBean stitchRepeat parameters
center_walk_underlayCentre walk underlaySatin parameters
center_walk_underlay_positionCentre walk positionSatin parameters
center_walk_underlay_repeatsCentre walk repeatsSatin parameters
center_walk_underlay_stitch_length_mmCentre walk stitch lengthSatin parameters
center_walk_underlay_stitch_tolerance_mmCentre walk toleranceSatin parameters
contour_underlayContour underlaySatin parameters
contour_underlay_inset_mmContour insetSatin parameters
contour_underlay_inset_percentContour inset (% of width)Satin parameters
contour_underlay_stitch_length_mmContour stitch lengthSatin parameters
contour_underlay_stitch_tolerance_mmContour toleranceSatin parameters
enable_random_stitch_lengthRandom stitch lengthRunning parameters
end_at_nearest_pointEnd at nearest pointSatin parameters
end_row_spacing_mmEnd row spacingTatami parameters
expand_mmExpandTatami parameters
fillSew the fillFill parameters
fill_methodFill methodFill parameters
fill_underlayUnderlayTatami parameters
fill_underlay_angleUnderlay angleTatami parameters
fill_underlay_inset_mmUnderlay insetTatami parameters
fill_underlay_max_stitch_length_mmUnderlay longest stitchTatami parameters
fill_underlay_row_spacing_mmUnderlay row spacingTatami parameters
fill_underlay_skip_lastSkip last underlay stitchTatami parameters
force_lock_stitchesAlways lockCommon parameters
gap_fill_rowsGap-filling rowsTatami parameters
lock_custom_endCustom end lockCommon parameters
lock_custom_startCustom start lockCommon parameters
lock_endEnd lockCommon parameters
lock_end_scale_mmEnd lock sizeCommon parameters
lock_end_scale_percentEnd lock scaleCommon parameters
lock_startStart lockCommon parameters
lock_start_scale_mmStart lock sizeCommon parameters
lock_start_scale_percentStart lock scaleCommon parameters
max_stitch_length_mmLongest stitchCommon parameters
min_jump_stitch_length_mmShortest jumpCommon parameters
min_random_split_length_mmShortest split stitchSatin parameters
min_stitch_length_mmShortest stitchCommon parameters
pull_compensation_mmPull compensationPull parameters
pull_compensation_percentPull compensation (%)Pull parameters
push_compensation_mmPush compensationSatin parameters
random_seedRandom seedCommon parameters
random_split_jitter_percentSplit jitterSatin parameters
random_split_phaseRandom split phaseSatin parameters
random_stitch_length_jitter_percentLength variationRunning parameters
random_width_decrease_percentRandom width decreaseSatin parameters
random_width_increase_percentRandom width increaseSatin parameters
random_zigzag_spacing_percentRandom zigzag spacingSatin parameters
repeatsRepeatsRepeat parameters
reverse_railsReverse railsSatin parameters
row_spacing_mmRow spacingTatami parameters
running_stitch_length_mmStitch lengthRunning parameters
running_stitch_positionRunning stitch positionSatin parameters
running_stitch_tolerance_mmCurve toleranceRunning parameters
satin_columnSatin columnSatin parameters
satin_methodMethodSatin parameters
short_stitch_distance_mmShort stitch distanceSatin parameters
short_stitch_insetShort stitch insetSatin parameters
skip_lastSkip last stitchTatami parameters
split_methodSplit methodSatin parameters
split_staggersStaggersSatin parameters
staggersStaggersTatami parameters
start_at_nearest_pointStart at nearest pointSatin parameters
stop_afterStop afterCommon parameters
stroke_methodMethodStroke parameters
stroke_pull_compensation_mmPull compensationZigzag stroke parameters
swap_satin_railsSwap railsSatin parameters
tiesLock stitchesCommon parameters
trim_afterTrim afterCommon parameters
underlay_underpathUnderlay underpathTatami parameters
underpathUnderpathTatami parameters
zigzag_angleZigzag angleZigzag stroke parameters
zigzag_spacing_mmZigzag spacingZigzag stroke parameters
zigzag_spacing_mmZigzag spacingSatin parameters
zigzag_underlayZigzag underlaySatin parameters
zigzag_underlay_inset_mmZigzag underlay insetSatin parameters
zigzag_underlay_inset_percentZigzag underlay inset (% of width)Satin parameters
zigzag_underlay_max_stitch_length_mmZigzag underlay longest stitchSatin parameters
zigzag_underlay_spacing_mmZigzag underlay spacingSatin 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_start is custom
  • 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_start is back_forth or custom
  • 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_start is arrow, bowtie, cross, star, simple, triangle, zigzag or custom
  • 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_end is custom
  • 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_end is back_forth or custom
  • 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_end is arrow, bowtie, cross, star, simple, triangle, zigzag or custom
  • 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 4 for 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_length is true
  • 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_method is default
  • 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_method is default
  • 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_method is default
  • 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_method is staggered
  • 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

TermMeaning
Bean stitchA running stitch where each stitch is sewn back and forth (3, 5, … passes) for a bold line
Bird’s nestA tangle of thread under the fabric, usually from bobbin or tension problems
Collapse lengthMoves shorter than this are sewn instead of jumped (default 3 mm)
Colour blockA run of stitches in one thread colour; blocks are separated by colour changes
Comfort zoneThe 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 fillA fill whose rows follow the shape’s outline inward
DensityHow close rows or zigzags are (row spacing / zigzag spacing)
HoopThe frame that holds fabric; its sewable field is a machine profile’s hard limit
JumpA 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 penetrationOne drop of the needle; consecutive penetrations make a stitch
PES / PECBrother’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 compensationWidening shapes so they come out the drawn size after thread tension pulls the fabric
PushFabric moving outward along the stitch direction; columns grow at their ends
RailOne of the two edges of a satin column
Row spacingDistance between rows of a fill
RungA short line across a satin’s rails that fixes which points correspond
Running stitchA line of single stitches
Satin columnStitches swinging between two rails; glossy bands for borders and lettering
StabilizerBacking material that keeps fabric from distorting while stitching
StaggerThe offset of needle points between neighbouring fill rows; staggers is how many rows before the pattern repeats
Stitch planThe full ordered list of stitches, jumps, trims and colour changes for a design
TatamiThe standard fill: parallel rows with staggered needle points
TravelRunning stitches that move the needle inside a shape, hidden by later stitching
TrimA thread cut performed by the machine
UnderlayA light foundation layer sewn before the top stitches
Zigzag spacingPeak-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.

VectorCraftTrackPassed the gate
0.18.0stable2026-10-29
0.17.0stable2026-10-25
0.16.0stable2026-10-25
0.13.1stable2026-10-18
0.12.0stable2026-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

  1. Technical design document — the whole system in 25 minutes.
  2. Architecture — crates, layers, where code goes.
  3. Data model, parameter registry, diagnostics.
  4. Engine pipeline, the stitch generators and image to embroidery.
  5. Machine formats and rendering previews.
  6. VectorCraft integration, the plug-in features RFC and the compatibility gate.
  7. Quality: conformance, determinism, guardrails, docs pipeline.
  8. File compatibility: SVG input and the Ink/Stitch compatibility contract.
  9. 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 PATH lists the pages for a file. cargo xtask docs --check fails when a branch changes those files and leaves the page as it was, unless a commit message on the branch says Design-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

StatusDraft 0.1 — design approved for M0/M1, later sections refined as milestones land
Date2026-10-08
ScopeA 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:

  1. 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 …).
  2. 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

IDGoalHow we know
G1Read and write every parameter in the Ink/Stitch compatibility contract, in phasesCompatibility contract coverage column reaches 100 % for phase P1–P3 rows
G2Never crash on any inputNo-panic lints, fuzzing, bounded work budgets (guardrails, robustness)
G3Correctness proven, not assumedEvery requirement has a passing conformance case; machine checkpoints signed off (conformance, machine testing)
G4DeterminismByte-identical files on Linux, macOS, Windows and wasm32 (determinism)
G5Host-agnostic engineVectorCraft plug-in, CLI and (later) an in-tree VectorCraft crate share one engine (architecture)
G6Documentation that cannot drift from the codeReference generated from code, images regenerated in CI on every PR (docs pipeline)
G7Code we are proud to showEnforced layering, small modules, playbooks for contributors (guardrails)
G8Survive VectorCraft releasesPlug-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 fieldValueBehaviour
Hoop (hard limit)130 × 180 mmError SC-E0701 if the plan does not fit
Comfort zonenoneA profile may have one: warning SC-W0702 if the design is larger
File formatPES v1 with PEC blockChanged only after a machine checkpoint proves another works
Max stitch length12.0 mm (split above)Refined at MC-1
Min stitch length0.3 mm (merged below)Refined at MC-2
TrimsA trim flag on every PEC jump after the first move (REQ-FMT-010)MC-1: the PE800 cuts none of them
Thread paletteBrother 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 wasmi with 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

CapabilityPhaseMilestone
Stitch plan, Brother profile, PES + DST writers, test sheetsP1M1
PES/DST readers, preview renderer, fuzzingP1M2
Running stitch (repeats, bean, random length), manual stitch, lock stitches, trims, stops, colour changes, SVG inputP1M3
Satin column (rails/rungs, underlays, pull/push compensation, short and split stitches)P1M4
Tatami fill (underlay, pull compensation, staggers, gap fill, underpath travel)P1M5
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)P1M6
Zigzag/E/S stitches, contour, meander, circular fillP2M7
Image to embroidery: stitch types chosen by width, overlaps, sewing order, VectorCraft’s Auto effectP2M13, between M7.5 and M7.6
Ink/Stitch SVG import (attributes, commands, clones) and differential testingP2M8
Lettering in embroidery fonts, and text in pictures set again as letteringP3M14
Guided, gradient, ripple, tartan, cross stitch; auto-run/auto-satinP3M10
EXP, JEF, VP3, XXX, U01 formats; thread cataloguesP3M11
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 effectafter 1.0M9, last

The full milestone and step list is in the roadmap.

4.2 Non-functional requirements

IDRequirement
NFR-ROB-1No panics in shipped code; no unsafe outside the one audited plug-in ABI module
NFR-ROB-2All work is bounded by explicit budgets (stitches, iterations, input sizes); exceeding one is a diagnostic, never a hang
NFR-DET-1Same input and version ⇒ byte-identical output on every supported platform, including wasm32
NFR-PERF-1A 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-2Live-effect previews fit VectorCraft’s v1 budget (≤ 50 M instructions, ≤ 5 s) for a 130 × 180 mm design
NFR-PORT-1Engine crates build for wasm32-unknown-unknown without WASI
NFR-DOC-1100 % of parameters, diagnostics, CLI commands and formats have generated reference pages
NFR-TEST-1Every requirement in conformance/requirements.toml has ≥ 1 passing case before its milestone closes
NFR-SEC-1Every 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:

LayerCrateResponsibility
L0stitchcraft-coreUnits (mm), points, deterministic math and RNG, diagnostics model
L0stitchcraft-paramsThe parameter registry: the single source of truth for every parameter
L1stitchcraft-planStitch plan model, machine profiles, thread palettes, plan invariants
L2stitchcraft-engineDesign model, stitch generators, plan assembly
L2stitchcraft-formatsMachine file readers and writers
L2stitchcraft-renderDeterministic CPU preview renderer (docs, CLI, conformance)
L3stitchcraft-svgSVG and Ink/Stitch-attribute adapter
L3stitchcraft-vectorcraft.vectorcraft document adapter and effect-record mapping
teststitchcraft-testkitFixtures, property-test strategies, invariant checkers
appstitchcraft-cli (stitch)Command-line tool
appstitchcraft-vc-pluginVectorCraft WebAssembly plug-ins

Details, dependency rules and the reasoning are in architecture.

6. Key decisions

ADRDecision
0001MIT OR Apache-2.0, clean-room from Ink/Stitch; keep its parameter names for interoperability
0002Host-agnostic engine; VectorCraft ABI v1 plug-in first, new plug-in features upstream, in-tree crate optional
0003One parameter registry generates UI schemas, CLI, docs, SVG mapping and test strategies
0004Determinism by construction: ordered collections, libm, seeded RNG, absolute quantization
0005kurbo for curves, i_overlay for polygon booleans/offsets, petgraph + own Eulerian walk, rstar
0006mdBook, Diátaxis structure, generated reference, CI-regenerated images
0007PES v1 first for the Brother machine; DST second
0008Conformance-first development: requirement IDs and cases before code
0009Adopt VectorCraft’s proven conventions; improve the ones that drift
0010Every user-facing problem is a coded diagnostic with an explanation page
0011StitchCraft can move into VectorCraft’s repository as a folder
0012Ink/Stitch’s source may be read, never copied
0013A Brother PE800 is the reference machine, with a profile for each of its 3 hoops
0014Elements 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, Path or Satin), 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 and SC-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.

  1. 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.
  2. Validate parameters against the registry (type, range, applicability) and shapes against the stitch type (e.g. a satin needs two rails).
  3. Generate a StitchGroup per element with its stitch type’s generator. Generators are pure functions of (shape, typed params, seed, budget) — no globals, no I/O.
  4. 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.
  5. Finalize against the profile: split stitches above the maximum, merge below the minimum, check the hoop and colour limits.
  6. Check invariants (the same checker the conformance suite uses) before any file is written.

9. Stitch generators (summary)

GeneratorPhaseDesign
Running, bean, manual, random-lengthP1strokes
Satin column (+ E, S, zigzag variants)P1/P2satin
Tatami fillP1fills
Contour, meander, circularP2fills
Guided, linear gradient, tartan, cross stitchP3fills
Ripple, zigzag strokeP2/P3strokes
Lock stitches, travel, orderingP1engine 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):

  1. ABI v1, no upstream change (M6). Plug-ins dev.stitchcraft.running, .satin and .fill are 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 from stitch export design.vectorcraft. (The namespace is one constant, changed in one place once the project has a domain.)
  2. 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.
  3. 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.

LevelWhatWhere it runs
L0Plan invariants on every generated planevery PR
L1Format conformance: golden bytes, round trips, pyembroidery oracle, fuzzingPR + nightly
L2Generator properties: containment, coverage, spacing, determinism, monotonicityevery PR
L3Ink/Stitch differential metrics on a corpus (pinned Ink/Stitch as a black-box oracle)nightly
L4Physical sew-outs on the Brother machine, recorded through an issue formmilestone 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

IDRiskMitigation
R1PES header fields are partly undocumented; the machine could reject a filePES v1 minimal header; pyembroidery oracle; MC-1 in the first milestone
R2ABI v1 budgets too small for full previewsBudget-aware coarse previews, full fidelity in the CLI, and the RFC’s overlay proposal
R3ArtCraft declines the RFC’s open proposalsThe plug-in works without them, and an in-tree integration in a fork is optional
R4Polygon offset/boolean robustnessSpike M0.7; input sanitizing; fuzzing; fallback to diagnostics, never panics
R5Copied GPL code slips inDesign docs first, in our own words (ADR-0012); cargo xtask cleanroom; review checklist
R6Cross-platform float differenceslibm, no FMA-sensitive formulas in quantization, cross-platform hash CI
R7Scope: the compatibility contract is largePhases; compatibility contract tracks coverage; post-1.0 items explicit
R8Brother trim behaviour varies by modelProfile flag; MC-1 tests both encodings
R9VectorCraft breaking changesCompatibility gate on stable, pre-release, release and main
R10Headless UI screenshots need a GPUSpike 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

  1. Exact Brother model (affects PES version and trim support) — answered at MC-1 at the latest.
  2. Final project name and plug-in id namespace (working name StitchCraft; plug-in ids derive from one constant).
  3. 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.
  4. 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

  1. The engine knows nothing about hosts. It never sees SVG, VectorCraft, files or the network. Hosts adapt their documents into a Design and consume a StitchPlan.
  2. 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.
  3. 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.
  4. Layering is enforced, not suggested. cargo xtask layers fails the build when a crate reaches up or sideways.
  5. 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`)
CrateLayerOwnsMust not
stitchcraft-coreL0Mm, Point, Vec2, tolerances, deterministic math (libm wrappers), SplitMix64 RNG, Budget, Diagnostic/Code/Severitydepend on any workspace crate
stitchcraft-paramsL0params!, ParamSpec, ParamSet, value parsing and validation, the registry audit (the list of declarations is stitchcraft_engine::registry)know any stitch algorithm
stitchcraft-planL1Stitch, StitchKind, ColorBlock, StitchPlan, Thread, palettes, MachineProfile, plan invariantsgenerate stitches; read/write files
stitchcraft-engineL2Design/Element/Shape, automatic digitizing, normalization, generators, plan assembly, finalizing for the machinedepend on formats, render or any adapter
stitchcraft-formatsL2readers/writers (PES/PEC, DST, …), quantization, format limitsdepend on engine (it encodes plans)
stitchcraft-renderL2CPU preview images of plans (thread look, simple look, overlays)depend on engine
stitchcraft-svgL3SVG → Design (geometry, styles, inkstitch:* attributes, commands), plan → SVGcontain stitch logic
stitchcraft-vectorcraftL3.vectorcraft → Design (commands in names included), effect records ↔ ParamSet, plug-in JSON objectscontain stitch logic
stitchcraft-testkittestfixtures, proptest strategies (from the registry), invariant assertionsbe a normal dependency
stitchcraft-cliappthe stitch command; all filesystem accesscontain engine logic
stitchcraft-vc-pluginappVectorCraft ABI v1 shim + plug-in manifestscontain engine logic; unsafe outside abi.rs
stitchcraft-xtasktoolsgates (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

  • plan below engine and formats lets 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.
  • params at 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.

NeedCrateLicenceWhere
Deterministic transcendental mathlibm 0.2MITcore
Errorsthiserror 2MIT/Apacheall libraries
XML / SVG syntaxroxmltree, svgtypesMIT/Apachesvg
.vectorcraft documents: JSON, its coordinates read exactly, unpacked from gzip by a Rust backendserde, serde_json, flate2 1MIT/Apachevectorcraft
Rasterizing previews, and reading VectorCraft’s pictures to compare themtiny-skia 0.11BSD-3-Clauserender, xtask
JSON reports, and the plug-in’s JSONserde_jsonMIT/Apachecli, vc-plugin
Content hashes of bug-report bundlessha2 0.11MIT/Apachecli
CLI parsingclap 4MIT/Apachecli, xtask
Tooling’s TOML and JSONtoml, toml_edit, serde_jsonMIT/Apachextask
Testsproptest, instaMIT/Apache, Apachedev 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:

AdapterFromTo
stitchcraft-svgSVG document (+ inkstitch:* attributes, command symbols)Design
stitchcraft-vectorcraft.vectorcraft JSON (+ commands in names), plug-in input objects, effect recordsDesign / Element
stitchcraft-vc-pluginABI v1 call (JSON in linear memory)generate → preview geometry JSON
stitchcraft-clifiles and flagsplan → 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 one thiserror enum; 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. For stitch plan it 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, as cargo xtask wasm --goldens runs 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 inAlso touch
A new stitch typecrates/stitchcraft-engine/src/generators/<name>/registry params, diagnostics, conformance cases, docs page — see the playbook
A parameterthe generator’s params! blocknothing 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 formatcrates/stitchcraft-formats/src/<format>/format playbook
A machine profilecrates/stitchcraft-plan/src/profiles/ (data)machine-testing record
A diagnosticthe diagnostics registry in stitchcraft-coreexplanation text + a case that triggers it
SVG/Ink/Stitch parsingstitchcraft-svgcompatibility contract row
VectorCraft document readingstitchcraft-vectorcraftcompatibility gate scenario
A CLI subcommandapps/stitchcraft-cli/src/commands/trycmd example in the docs
A repo checkxtask/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.
  • Trim and Stop happen where the needle is; their at repeats 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 Normal stitch is the first time it goes down.
  • PlanBuilder appends entries with the needle tracked; PlanStats counts 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.

RequirementRuleSince
REQ-PLAN-001Every position is finite and inside the profile’s hoop, centred on the origin.M1
REQ-PLAN-002While 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-003Every block sews at least one stitch; trims and stops happen where the needle is.M1
REQ-PLAN-005While sewing, no stitch lands where the needle already is.M1
REQ-PLAN-006Thread changes plus stops fit the format (PES: 255; DST: 999).M1
REQ-PLAN-007Every 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 (kinds module), so a field and its validation cannot disagree;
  • CommonParams::SPECS (a const slice of ParamSpec) and CommonParams::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

KindGenerators getValidationInk/Stitch equivalent
LengthMmfinite, within range; a unit may follow the number with no space: mm, in, ptfloat, unit mm
OptionalLengthOption<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
OptionalLengthWithZeroOption<Mm>as OptionalLength, but only empty text is empty, and 0 is a length, as Ink/Stitch reads a satin’s shortest stitch to splitfloat, unit mm, no default
Angledegrees, f64finite; normalized to (−180, 180]float, unit deg
BoundedAngledegrees, f64finite, 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 anglefloat, unit deg
Percentf64finite, within rangefloat, unit %
Numberf64finite, within range, without a unitfloat, no unit
Countu32whole, within rangeint
Togglebooltrue 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 defaultcombo, dropdown
SeedOption<u64>any; empty means “derive from the element”; text that is not a number is hashed (FNV-1a)random_seed
LengthListVec<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 Lengthfloat, unit mm (each side)
PercentPair[f64; 2]as LengthPair, in percentfloat, unit % (each side)
OptionalLengthPair, OptionalPercentPairOption<[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 itfloat, unit mm (each side) or % (each side), no default
PercentListVec<f64>1 to 16 percentages, each in range (levels like "15 30")float, unit %, read as a list
CountListVec<u32>1–16 whole numbers, each in rangestring/int lists
OptionalAngleListOption<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 anglefloat, unit deg, read as a list, no default
TextStringstripped 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 usedstring

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

  1. 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.

  2. 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 under params, and each key’s reference entry shows it.

  3. New parameters follow the same style: snake_case, a unit suffix for lengths (_mm), percentages (_percent) and angles where Ink/Stitch does (angle alone for row angles).

  4. Never reuse a removed key with a new meaning.

  5. 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, and conformance/inkstitch/check_params.py compares 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.

  6. A key belongs to its stitch types. Ink/Stitch gives some keys to several elements with their own defaults: max_stitch_length_mm is 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 with defaults, as fills do for max_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 under x-stitchcraft.family_defaults.

  7. 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_mm is 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 a staggers of 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_for finds a family’s declaration, and find the 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

OutputGenerated byChecked bySince
The reference pages, the index and one page per declaration (docs/src/user/reference/params/*.md), with the deviations ledger’s entriescargo xtask docscargo 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 hostscargo xtask docssameM3.1
The StitchCraft column of the Ink/Stitch compatibility contractcargo xtask docssame, plus the cross-checks aboveM3.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 itat 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’ rowsM6.2, and the dialogs M6.10
SVG attribute read/writestitchcraft-svg iterates the registryround-trip property test REQ-PRM-003planned for M8
CLI parameter help (stitch params, --param key=value)at runtime from the registrytrycmd examplesplanned, in no step yet
proptest strategiesstitchcraft-testkit iterates the registryused by every generator property testplanned, 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:

RangeArea
0000–0099Input and budgets
0100–0199Parameters
0200–0299Satin
0300–0399Fills
0400–0499Strokes (running, bean, manual, zigzag, ripple)
0500–0599Plan assembly (ties, travel, trims, colours)
0600–0699Formats and threads
0700–0799Machine profile (hoop, limits)
0800–0899Hosts (SVG, VectorCraft)
0900 to 0999Automatic 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 docs generates the diagnostics index (one page per code, like rustc’s error index);
  • stitch explain SC-W0702 prints 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 conformance fails when a code has no diag_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.

CodeSeverityTitleMilestone
SC-E0004ErrorBudget exhausted (work for one element, work joining the elements, or stitches for the design)M1
SC-E0005ErrorPreview too large (over 4,096 pixels on a side at the requested scale)M2
SC-E0009ErrorInternal check failed (a StitchCraft bug; a bug-report bundle is written)M1
SC-E0010ErrorNothing to stitch (no embroiderable elements)M1
SC-W0011WarningStitch type not sewn yet; element skippedM3
SC-E0012ErrorBug-report bundle could not be replayedM3
SC-E0101ErrorParameter has the wrong type or an unknown choiceM3
SC-W0102WarningParameter clamped to its allowed rangeM3
SC-W0105WarningUnknown parameter preserved but ignoredM3
SC-E0201ErrorSatin column has no rails (no subpath longer than a point)M4
SC-W0202WarningSatin rails taken as the two longest subpaths (where the subpaths meet does not say)M4
SC-W0203WarningSatin rung does not cross both rails (dangling rung); the rail’s nearest point is usedM4
SC-W0205WarningSatin subpath is one point; left outM4
SC-W0206WarningSatin contour underlay too short for its insets; it keeps its lengthM4
SC-W0207WarningSatin rung crosses a rail more than once; left outM4
SC-W0208WarningSatin stitches lean more than 45° from square to the column; add a rung hereM7
SC-W0209WarningSatin stitch longer than 12 mm; it may snagM7
SC-W0210WarningSatin rails without rungs have different numbers of nodes (some pair with none)M4
SC-W0211WarningSatin push compensation too long for a rail; that rail keeps its lengthM4
SC-W0212WarningSatin column drawn as one path too narrow; sewn as a strokeM4
SC-W0213WarningParts 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-E0214ErrorSatin column drawn as one path could not be made into rails (too short, or too tight for its width)M4
SC-W0215WarningSatin 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-E0216ErrorSatin column has no section to sew (its rungs or push compensation take each section’s length off a rail)M4
SC-W0303WarningParts 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 nothingM5
SC-W0304WarningFill smaller than 1.4 mm² (20 square CSS pixels); a running stitch or a satin column may sew it betterM5
SC-W0305WarningFill part too thin for rows; sewn round its outline (as in Ink/Stitch)M5
SC-I0306InfoNonzero fill rule fills a part inside that the even-odd rule leaves empty (Ink/Stitch leaves it empty)M5
SC-W0307WarningFill in parts that are apart; each sewn on its own, joined by jumpsM5
SC-I0308InfoFill underlay too thin for rows; sewn round the outline (as in Ink/Stitch)M5
SC-W0309WarningFill’s expand shrinks a part away; sewn at its own size (as in Ink/Stitch)M5
SC-W0310WarningFill’s underlay inset shrinks a part away; underlay over all of it (as in Ink/Stitch)M5
SC-W0311WarningContour fill leaves out rings it does not reach, where its rings part or lie too far apartM7
SC-W0312WarningFill’s pull compensation below 0; read as 0 (as in Ink/Stitch)M5
SC-I0313InfoFill drawn twice along its edge; Ink/Stitch fills the part left empty (its repair merges the stretch)M5
SC-W0314WarningLegacy fill sewn as a tatami fill without underlay (Ink/Stitch’s legacy fill is not built)M7
SC-W0401WarningPath too small for the shortest stitch; skippedM3
SC-W0402WarningStitch length below twice the shortest stitch; raised to it, so even spacing never sews a stitch shorter than the shortestM3
SC-W0403WarningHand-placed stitch shorter than the shortest stitch; point left outM3
SC-W0404WarningHand-placed stitch longer than max_stitch_length_mm; kept for the shortest stitchM3
SC-W0405WarningZigzag stitch shorter than the shortest stitch; the zigzag sews narrowerM7
SC-W0502WarningLock stitch shorter than 0.2 mm; lengthenedM3
SC-W0503WarningCustom lock cannot be sewn as writtenM3
SC-I0504InfoStitches shorter than the shortest stitch mergedM3
SC-W0505WarningTrim or stop after an element that sews nothing; left outM3
SC-E0601ErrorToo many colour changes for the file formatM1
SC-E0602ErrorDesign too large for the file format (coordinates or data exceed its fields)M1
SC-E0603ErrorMachine file could not be read (wrong format, truncated, or a record that makes no sense)M2
SC-W0604WarningThread colours unknown (the file stores none, as DST never does; threads are placeholders)M2
SC-I0605InfoLong jumps cut in DST (machines cut the thread before three or more jump records in a row)M3
SC-W0606WarningThreads share a Brother colour (more different threads than a PES file’s 61 thread colours)M3
SC-E0701ErrorDesign does not fit the hoopM1
SC-W0702WarningDesign is larger than the comfort zone (the profile’s most accurate area)M1
SC-I0703InfoStitches longer than the machine’s longest stitch; splitM3
SC-E0801ErrorSVG could not be read (with the parser’s position)M3
SC-W0802WarningSVG feature ignored (e.g. raster image, text not converted to paths)M3
SC-W0803WarningVectorCraft file from a newer format version; read best-effort (a version newer than the newest the adapter reads)M6
SC-W0804WarningElement 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 stitchedM3
SC-I0805InfoObject left out, as the file asks (Ink/Stitch’s ignore commands and ignore_object setting)M3
SC-E0806ErrorVectorCraft file could not be read (not a VectorCraft document, or larger than the adapter reads)M6
SC-W0807WarningVectorCraft 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 readM6
SC-W0808WarningVectorCraft geometry unusable (a number that does not read or a point beyond 10 m; a shape that draws nothing)M6
SC-I0809InfoVectorCraft layer or guide left out, as it does not print (template layers, layers whose printing is off, guides)M6
SC-I0810InfoVectorCraft 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-I0811InfoVectorCraft object left out, as its name asks (an object or a layer named stitch:ignore)M6
SC-W0812WarningVectorCraft 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-E0813ErrorVectorCraft 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-E0814ErrorVectorCraft 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-I0901InfoShapes digitized automatically (--auto), counted by their stitches (running stitches, satin columns or tatami fills)M13
SC-I0902InfoLine 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-W0903WarningLine too wide to fill; left out (an automatic line whose filled area would reach more than 10 m from the origin)M13
SC-I0904InfoAreas 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-I0905InfoSpeck 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-W0906WarningBackground left out (the lowest shape, an automatic area that covers the whole design under the other shapes)M13
SC-I0907InfoElements 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-order paints the stroke first, SC-W0802 says 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>:fill or svg:<label>:stroke. The label is the SVG element’s id, or <tag>@<n> for the n-th element of that kind. A repeated id gets ~2, ~3 and so on (stitchcraft_core::Labels, as in the VectorCraft adapter). The adapter’s own diagnostics name the SVG element as svg:<label>.
  • Names. An inkscape:label becomes 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 any inkstitch: 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, viewBox and preserveAspectRatio map 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, height or viewBox that is empty, and a preserveAspectRatio that does not read. The adapter ignores them too, and SC-W0804 names each. A width, height or viewBox that 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, and SC-W0804 names 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 whose rx or ry is 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-W0804 names it. A percentage is of the viewport’s width, height or diagonal over √2, an em is 16 user units and an ex 8, 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, color and currentColor give 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 currentcolor may 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 the style attribute, in the order of CSS’s cascade, as in a viewer (REQ-SVG-006). A rule overrides a presentation attribute, and the style attribute 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 !important declaration 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, and SC-W0802 names 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-rule and paint-order are 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 as rect, .cls-1, #logo or path.st0.st1. It reads a list of such selectors, such as .a, .b, as well. One SC-W0802 names the other selectors, such as g > rect or a:hover, and the at-rules, such as @media and @import. What they set is not used. Ink/Stitch reads styles through inkex, which applies more kinds of selector.
  • display: none hides an element and everything in it. A hidden or fully transparent paint is left out, as a viewer leaves it out. visibility hides a paint, and so does an opacity of 0 on its element or above it. A fill-opacity or stroke-opacity of 0 makes its paint clear, as does a colour that is transparent or has an alpha of 0. Ink/Stitch leaves out only what display hides and a fill whose fill-opacity is written 0 (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-W0802 names such a line (REQ-AUTO-005). The transforms scale a stroke with vector-effect: non-scaling-stroke too, as Ink/Stitch scales it, though a viewer does not.
  • Join. round and bevel are read as they are named. A miter join is limited by stroke-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’s miter-clip and arcs count 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 by stroke-miterlimit, and so are miter-clip and arcs, which few viewers draw.
  • Cap. stroke-linecap, inherited, butt when nothing sets it: butt, round or square, 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-W0804 names 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 as 1e308in, is left out with SC-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 trim and stop commands turn on the object’s trim_after and stop_after. The ignore_object and ignore_layer commands leave out an object or a layer, as the inkstitch:ignore_object setting does when it says yes in one of Ink/Stitch’s words, which a toggle reads too, and SC-I0805 lists each object left out. The starting_point and ending_point commands, which place where Ink/Stitch starts and ends a fill or a satin column, are not applied yet (REQ-GEN-001), and SC-W0802 names 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-W0802 reports 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-W0804 reports 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, height or viewBox, and a root preserveAspectRatio that does not read
  • SC-E0801 refuses 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 root width, height or viewBox that 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_mm for 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 calls powf (CubicBez::to_quads) and hypot (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 plan writes 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 from stitchcraft-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-E0004 then 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, with SC-E0004 naming 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-colours every 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 groupsConnection
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 locksSewn 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, otherwiseTie-off → Jump → tie-in (REQ-ASM-002)
After an element’s last group, when it says trim_afterTie-off → Trim → Jump → tie-in (REQ-ASM-003)
After an element’s last group, when it says stop_afterTie-off → Jump to the stop position, if the design has one → Stop → Jump → tie-in (REQ-ASM-003, REQ-ASM-005)
Thread changeTie-off → a new colour block → Jump → tie-in
Before the design’s first groupJump → tie-in
After the design’s last groupTie-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).
  • ties says which of those an element’s groups get: both, the tie-in (before), the tie-off (after) or neither. force_lock_stitches adds the tie-off whatever ties says, 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 at d59c9ab).
  • Manual stitch is sewn without locks unless force_lock_stitches is 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

CommandEffectFrom
Start / end pointEntry/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 thenM8
Target pointCentre for circular fills and ripple targetsM7, M10
Trim after, stop aftertrim_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 layerThe adapter drops the element and says so in the report (REQ-ASM-004). The VectorCraft adapter reads them from the name stitch:ignoreM3 (SVG), M6 (VectorCraft)
OriginThe design setting originM3.8 (engine), M8 (adapter)
Stop positionThe design setting stop_positionM3.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.

  1. 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’s min_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-I0504 says 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).
  2. The longest stitch. A stitch longer than profile.max_stitch is 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 whose max_stitch_length_mm is longer than it, or a move sewn on under a min_jump_stitch_length_mm that long. Each part counts against the design’s stitch budget.
  3. The hoop’s centre. The fitted plan is moved so the design’s origin is at the hoop’s centre (origin, REQ-ASM-005).
  4. Colour limits: colour changes and stops above what the profile’s format records → SC-E0601.
  5. 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 is SC-E0701 as 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-E0701 is the only message about its size. Otherwise, larger than the comfort zone → SC-W0702 (REQ-FIN-002).
  6. 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 rayon behind the CLI’s parallel feature. 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

OutputProduced by
Machine filestitchcraft-formats writer chosen by the profile or the output extension
Preview PNGstitchcraft-render, from the quantized plan (REQ-RND-001)
Report JSONThe 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 geometrystitchcraft-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.

PageGenerators
Strokesrunning, bean, manual, random-length, zigzag stroke, ripple
Satinsatin column, E-stitch, S-stitch, zigzag satin, underlays
Fillstatami, contour, meander, circular, guided, linear gradient, tartan, cross stitch
Lock stitchesthe 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:

  1. Determinism: the same inputs give identical stitches; shuffling unrelated elements does not change this element’s stitches.
  2. Finite and bounded: all coordinates finite and within the shape’s bounds expanded by the compensation parameters plus 0.05 mm.
  3. Length limits: top stitches within the generator’s maximum length; no zero-length stitches.
  4. 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).
  5. 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.
  6. 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).

  1. 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.
  2. 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.
  3. Even spacing per span. For a span of length L between corners and target length s, place n = ceil(L / s − ε) stitches spaced L / n apart along the path. Every stitch is at most s and 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.
  4. 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).
  5. 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.
  6. Random length. With enable_random_stitch_length, each stitch length is drawn uniformly from s × (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’s random_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 = k sews 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 = b replaces 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 length s and 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 + 1 times, so a run of n stitches with b throughout has exactly n × (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.

  1. 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 + 1 points are spread along it at equal distances, its ends included. n is 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.
  2. 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.
  3. 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.
  4. 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).
  5. 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 + 1 times, 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 + 1 with n even, as step 1 counts them, and their places along the flattened path are L / n apart (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.

  1. A subpath that is one point is left out, with SC-W0205.
  2. 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-E0201 and no stitches.
  3. 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.
  4. When step 3 does not find exactly 2 rails, the 2 longest subpaths are taken as the rails, and SC-W0202 names 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.
  5. Rails may meet each other, as the two sides of a pointed column do at its tips. That meeting counts in step 3.
  6. 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, with SC-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):

  1. 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.
  2. 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.
  3. 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.
  4. 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_rails makes 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 = automatic reverses 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, second and both force 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_mm plus pull_compensation_percent of 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 between pull_compensation_percent less random_width_decrease_percent and plus random_width_increase_percent. Each step’s spacing is drawn between the zigzag spacing less and plus random_zigzag_spacing_percent of 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)

  1. 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_mm from 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.
  2. Compensate width. Each pair is widened by pull compensation as it is placed, with its random share (Compensation).
  3. 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_mm to the last point left in place there moves in along its stitch, by a short_stitch_inset percentage of the stitch’s width. Points that crowd one after another take turns with the percentages and start over at the first, so 15 30 insets 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.
  4. 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_method says where the splits go.
    • default splits 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_percent moves 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 than min_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.
    • simple splits 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.
    • staggered does the same from a start that moves along by a split_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.
  5. 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.

  1. 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_mm apart.
  2. 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.
  3. 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.
  4. Order. The spikes go from the column’s start to its end, or from its end after an odd centre walk, and swap_satin_rails puts 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.

  1. 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.
  2. 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.
  3. 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).
  4. 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_rails swaps 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).

  1. 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.
  2. 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).
  3. Short stitches inset those pairs where their points crowd (step 3). Each point moves along its own slanted stitch.
  4. 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.
  5. 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.
  6. 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 one SC-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 one SC-W0209 when 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. Setting max_stitch_length_mm splits 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.

UnderlayParametersWhat it does
Centre walkcenter_walk_underlay, _stitch_length_mm, _stitch_tolerance_mm, _repeats, _positionPairs 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
Contourcontour_underlay, _stitch_length_mm, _stitch_tolerance_mm, _inset_mm, _inset_percentPairs 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
Zigzagzigzag_underlay, _spacing_mm, _inset_mm, _inset_percent, _max_stitch_length_mmPairs 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_methodLookDesign notes
e_stitchA spine of running stitches along one rail with regular spikes to the other: the “E” or blanket stitch used for appliqué edgesSewn from M7.3 (E-stitch)
s_stitchStitches across the column, every other one back the other way, joined by steps along the railsSewn from M7.4 (S-stitch)
zigzagAn open zigzag between the railsSewn from M7.5 (Zigzag satin)

Properties (conformance)

RequirementProperty
REQ-SAT-001Pull 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-002Consecutive 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-003With 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-004Underlays 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-005Rails 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-006A 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-007Random widths and spacing are reproducible from the element and its seed, and stay within their ranges
REQ-SAT-008Push 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-009On 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-010The 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-011The 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-012The 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-013Starting 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-014Ending 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-015A 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-018A 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-019Of 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-020An 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-021Between 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-022Each 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-023A 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-024An 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-025An 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-026A 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-027A 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-028Under 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):

  1. 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).
  2. 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.
  3. 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.
  4. 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. Where nonzero fills a face inside that even-odd leaves empty, StitchCraft fills it, and SC-I0306 says 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-I0313 says Ink/Stitch fills it.
  5. 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.
  6. 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²) gets SC-W0304, as Ink/Stitch warns of it. A fill in parts gets SC-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).

  1. 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.
  2. 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.
  3. 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).
  4. 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 for skip_last, and it ends a stretch for gap-fill rows only where it stands for several.
  5. 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.
  6. 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 underpath off 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 first running_stitch_length_mm, at least twice the shortest stitch, within running_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. With skip_last the row’s end is not sewn, and the first running stitch stays unless the next row starts beside it.
  7. 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).

  1. 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 underpath off. Ink/Stitch smooths that way as it smooths a way under the rows, step 4, and StitchCraft keeps to the rings (DEV-FILL-013).
  2. 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.
  3. 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.
  4. 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.
  5. 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 underpath off. 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.
  6. 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)

RequirementProperty
REQ-FILL-TAT-001Every row segment is covered exactly once by top stitches
REQ-FILL-TAT-002Measured row spacing equals the parameter (± 2 %), or follows the start→end gradient
REQ-FILL-TAT-003Needle 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-004Coverage (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-005With 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-006Each top row extends past its segment’s ends by the compensation; the part keeps its holes and pieces
REQ-FILL-TAT-007Gap-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-008A 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-009A 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-011Before 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-012The 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-013Questions 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 with avoid_self_crossing every other level of rings is. smoothness_mm smooths 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.

LockKindSizeWhat the needle sews
half_stitch (default)stepsthe first stitchforth 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_forthstepslock_*_scale_mmforth and back over one step, twice: the half stitch’s rhythm, at a size the user sets
customstepslock_*_scale_mmthe element’s own steps (custom locks)
arrowdrawnlock_*_scale_percenta shaft along the stitching to the tip of an arrowhead 1.4 mm ahead, round the head and back down the shaft
bowtiedrawnlock_*_scale_percenttwo triangles tip to tip across the stitching, 1.2 mm long, their long stitches crossing in the middle
crossdrawnlock_*_scale_percentan X 0.8 mm across, 0.3 to 1.1 mm ahead
stardrawnlock_*_scale_percenta five-pointed star 1.2 mm across, one of its points at the anchor
simpledrawnlock_*_scale_percenta diamond of four 0.58 mm stitches round the stitching
triangledrawnlock_*_scale_percenta triangle opening ahead, its tip at the anchor, 1.2 mm long
zigzagdrawnlock_*_scale_percentzigzag 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-W0503 until 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):

  1. Each id sews a lock that joins the stitching where it starts or ends (REQ-LCK-002).
  2. 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).
  3. Sizes: the drawn locks scale with lock_*_scale_percent alone, and the locks made of steps with lock_*_scale_mm alone. The half stitch scales with neither (REQ-LCK-002).
  4. 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

  1. 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.
  2. 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.
  3. 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.
  4. 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:

  1. In VectorCraft, place the picture, scale it to the size it will sew, and trace it with Object › Image Trace › Make.
  2. 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:

WidthSewn as
Up to the design’s min_satin_stroke_width_mm, 1 mm unless the design sets itA running stitch along the centre line, a triple run (bean stitch) from 0.5 mm wide
Wider, up to the widest satin column, 7 mmSatin columns, whose rails are the outline
Above 7 mmA tatami fill

A stroke is a line as wide as it is drawn (M13.1):

  • No wider than min_satin_stroke_width_mm it 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, #3 and 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, #3 and 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-I0901 counts 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, and SC-E0009 names 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 widthUnderlay
Below 2 mmCentre walk
2 to 4 mmContour
Above 4 mmContour 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).

  1. 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 with SC-W0906 before 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, which SC-W0906 says.
  2. 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-I0905 names 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.
  3. 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-I0904 counts 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 pictureAs drawnDigitizedStitches as drawnStitches digitized
Stripes, 3 abutting98.9 %100 %2,9943,221
Badge, abutting89.1 %98.7 %3,7613,773
Badge, stacked100 %97.2 %3,9813,764
Badges in a row, abutting94.2 %99.8 %4,9474,918
Badges in a row, stacked100 %99.9 %4,8724,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.

  1. 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.
  2. 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.
  3. 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.
  4. 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 pictureThread changes as drawnDigitizedJumps as drawnDigitized
Stripes, 3 abutting2233
Badge, abutting2233
Badge, stacked2233
Badges in a row, abutting8297
Badges in a row, stacked8297

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 auto option 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, ColorChange and End, 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)

PartContent
Signature#PES0001
PEC offset32-bit little-endian offset of the PEC block: 22
PES sectionA 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 headerLA: + 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 headerBytes 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 stitchesRelative moves in 0.1 mm (see below)
Thumbnails48 × 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:

  1. 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.
  2. 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.
  3. 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)

PartContent
Header512 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
Records3 bytes each; x and y displacements in balanced-ternary bit patterns up to ±121 units (12.1 mm) per record
Axisy 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-I0605 counts 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 whose ties are 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).

ReaderReadsWhat it has to infer
PES / PECthe 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).
DSTthe header’s label and every recordThree 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

FormatMachinesMilestoneNotes
EXPMelco, BerninaM112-byte records, 0x80 escape codes for commands
JEFJanomeM11Janome thread table indices; per-palette mapping
VP3Husqvarna Viking, PfaffM11Nested length-prefixed sections; thread descriptions as strings
XXXSingerM11
U01BarudanM11
CSV / JSONDebugging and interchangeM2Human-readable plan dumps; the conformance suite’s golden format for stitch lists
HUS, VIPHusqvarna (older)—Compressed; read-only candidates after 1.0

Conformance

RequirementWhat it checks
REQ-FMT-001Golden bytes: canonical plans encode to committed files byte for byte
REQ-FMT-002Round 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-003Every command kind survives every writer/reader pair (and is sewn in TS-02)
REQ-FMT-004Empty and zero-stitch files read cleanly; empty plans are never written
REQ-FMT-005Independent oracle: pyembroidery reads our files to the same stitches (CI job with a pinned version)
REQ-FMT-006Fuzzed readers never panic, never allocate beyond caps, always terminate
REQ-FMT-007Long moves are split within the format’s per-record limit
REQ-FMT-008DST 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-011A 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-012PEC thumbnails draw stitches only inside their frames, so the stitch data’s end mark is followed by 00
REQ-THREAD-002Different 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 itemMeaning
HoleA place where the needle goes down, in sewing order, with the thread’s colour
Arrival SewnA stitch laid from the previous hole (StitchPlan::sewn_stitches, the single definition of “sewing”)
Arrival LooseThe frame moved without sewing and the thread was not cut: a jump thread that lies loose until cut by hand
Arrival CutThe thread was cut since the previous hole (a trim or a thread change), or this is the first hole
Mark Trim, StopWhere 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

RealisticSimple
PurposeThe finished embroideryChecking a design
BackgroundUnbleached cotton #f4efe6White
StitchThread 0.4 mm wide (40 wt): dark edge, body, light crest, round endsThin line in the thread’s colour
Needle hole(the stitch ends show it)Dot
Loose jump threadThinner thread, on top of what is under itDashed, thread colour
Travel after a cutNothingDashed, grey
Lock stitch(like any stitch)Orange ring
Trim, stopNothingRed 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

RiskAnswer
SIMD code paths giving different roundingtiny-skia is built without its simd feature: the same scalar pipeline on every CPU
Platform trigonometryOnly 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
CoordinatesGrid positions are integers below 2^24, converted to f32 exactly
PNG encodingpng 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

FactDetailWhere
WhatClean-room reimplementation of Illustrator in pure Rust; desktop (macOS, Windows, Linux, FreeBSD) and web (wasm)README.md
LicenceMIT OR Apache-2.0; no GPL code allowed in the treeCargo.toml, AGENTS.md
ToolchainRust 2024, rust-version = 1.95Cargo.toml
Layersgeom, 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 → appsxtask/src/layers.rs
CommandsDeclarative cmd! registry: id, label, menu path, shortcut, params doc, enabled, run; reachable from UI, JSON control channel and MCPcrates/engine/src/cmd/*
Headless CLIvectorcraft-cli run [--in FILE] [--cmd ID [--params JSON]]... [--export FILE]..., convert, info, commands, mcpapps/vectorcraft-cli/src/main.rs
Control channelJSON 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
ReleasesTags 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 wasmi 2.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: string and numberList too, up to 256 parameters with at most 64 outside groups, and each parameter’s label, unit, group, help, helpUrl and visibleWhen.
  • 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’s fills and strokes too, read only, each stroke with its width, and from 0.15 its join, cap and miter limit. _context.item says 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. Node has attrs: 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, under pluginData.
  • 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 its inkstitch:* 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 (FORMATS in crates/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

PhaseNeeds from VectorCraftDeliversMilestone
1. ABI v1 plug-insnothingper-object embroidery settings, previews, tools; machine files via stitch export file.vectorcraftM6
2. Plug-in features (upstream RFC)generic plug-in features, most of them in VectorCraft 0.14 to 0.16previews 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 forknative speed, simulator panel, satin toolpost-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 idKindStitch typesNotes
<ns>.strokelive effectrunning, bean, manual (P1), zigzag (M7.2), ripple (later)stroke_method choice selects the variant
<ns>.satinlive effectsatin (P1), E (M7.3), S (M7.4), zigzag (M7.5)rails and rungs from the object’s compound path
<ns>.filllive effecttatami (P1); contour, meander, circular (P2)fill_method choice
<ns>.autolive effectthe stitches automatic digitizing chooses by width (M13.7)on a group, previews each path inside (image to embroidery)
<ns>.pes, <ns>.dstexporter (VectorCraft 0.14)every familyFile › Export › Export As writes the same machine file as stitch export (M6.9, below)
<ns>.settingsfilter (VectorCraft 0.16)nonekeeps the design settings and the machine with the document (M6.11, below)
<ns>.stroke-to-satinheavy filter (VectorCraft 0.17)satinmakes each selected line into a satin column drawn with its rails and rungs (M6.12, below)
<ns>.testsheetfilter (VectorCraft 0.16)every familydraws 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 of main (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 when says, 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

  1. Draw the art in VectorCraft as usual.
  2. 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 .vectorcraft file) and the object shows a stitch preview.
  3. Save the document.
  4. stitch export design.vectorcraft -o design.pes --preview design.png writes the machine file, for the machine --profile names, or else the one the document keeps (below), or else brother-pe800-5x7, and a picture of what will sew. It says what the reader left out or changed, with the engine’s warnings. stitch export is another name for stitch plan, which reads a VectorCraft document (.vectorcraft, .drawcraft, or a .vctemplate template) as it reads an SVG file (REQ-VC-008), and --report adds 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 profile option names, and the design takes the file’s name. The option’s default, document, is the machine the document keeps, or brother-pe800-5x7 when it keeps none, as stitch export plans it without --profile (below). Reading the file back, as stitch export does, stops it at a writer’s bug (SC-E0009). The budget is stitch export’s own too. Its auto option digitizes the objects without StitchCraft effects, as --auto does, 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 are stitch export’s, byte for byte.
  • What stitch export says, 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 more stitch export lists.
  • 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 export doesn’t (REQ-CLI-003). VectorCraft shows the first 300 characters of a decline, after plug-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 defaultsInstructionsOf those allowed
A fill of 128 × 178 mm, the 5 × 7 in hoop’s whole field1.6 billion16 %
63 fills of 14 mm squares across the field4.3 billion43 %

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:

TaskIn VectorCraftWhy it sews
Satin column of 2 pathsObject › Compound Path › Make (object.compoundPath.make), then the satin effectThe 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)
RungPen tool line across the rails, then Make Compound Path with the columnWhich 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 previewObject › 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
PresetsGraphic 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 wideInstructionsOf those allowed
A zigzag of 10 corners18 million2 %
An S curve 30 mm long50 million5 %
A circle 10 mm across73 million7 %
A circle 30 mm across91 million9 %
An S curve 60 mm long91 million9 %
A circle 50 mm across233 million23 %

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:

MeasuredValue
Reading JSON512 instructions a byte
Writing JSON from a serde_json value822 instructions a byte
One anchor of a path, as JSON40 bytes
Speed on GitHub’s Linux runnersabout 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:

MeasuredValue
Writing the plug-ins’ geometry, by hand, to a thousandth of a pointabout 16 instructions a byte
One anchor, written by hand18 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 strokeabout 880 instructions
A unit of the engine’s work for a satin columnabout 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 fill1,900 to 5,300 instructions, by the part of the work
The exact preview of a 7 mm square fill with its defaults27 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 mm1.6 to 11 million instructions
The outline preview of square fills from 3.5 mm to 903 mm1.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):

LevelGeometry returnedAuto shows it
ExactThe 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
RowsOne ribbon per row segment of a fill’s top rows: where its stitches lie and which way they runFor a fill, within eight tenths
OutlineThe 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 directionWhen 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:

NameInk/Stitch’s commandWhat StitchCraft does
stitch:ignoreignore_object, ignore_layerLeaves the object or the layer out with everything in it, and lists it with SC-I0811
stitch:start, stitch:endstarting_point, ending_pointMarks 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 with SC-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:

ProposalIn VectorCraftStitchCraft’s use
1. Exporter plug-ins, for File › Export › PES0.14.0M6.9
2. Rich parameter schemas: labels, units, groups, help, conditions, texts and lists, 256 parameters0.14.0M6.10
3. Display-only overlays from live effects, real stitch previews at low costin reviewM9
4. Budget classes, with the user’s consentheavy filters in 0.17.0, heavy live effects proposedM6.12 (Stroke to Satin), M9
5. Document-level plug-in data, for the machine profile and palettes0.16.0, for filters and exportersM6.11
6. Read-only context for live effectsthe object’s paints in 0.14.0, the markers beside it proposedM6.8, M9
7. SVG foreign-attribute preservation0.17.0Ink/Stitch files keep their settings through VectorCraft
8. Effects and stroke style in filter objects0.15.0M6.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

AudienceArtCraft / VectorCraft maintainers
StatusMostly 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
CompatibilityAdditive. Every plug-in written for ABI v1 keeps working, and vc_abi_version still returns 1
Author’s interestStitchCraft (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):

ProposalIn VectorCraftSincePull request
P1, exporter plug-insAdopted0.14.0#3171
P2, rich parameter schemasAdopted0.14.0#3172
P3, overlays for live effectsIn review#3807
P4, budget classesHeavy filters adopted, heavy live effects open0.17.0#3721
P5, document-level plug-in dataAdopted for filters and exporters0.16.0#3720
P6, context for live effectsFills and strokes adopted, neighbours open0.14.0, a stroke’s style 0.15.0#3173
P7, foreign SVG attributesAdopted0.17.0#3758
P8, effects and stroke style in filtersAdopted0.15.0#3174

The plug-in classes this RFC was for, and what VectorCraft serves of them now:

Plug-in classNow
Output generators for embroidery (PES/DST), plotters (HPGL), laser cutters (G-code) and cutting machinesExporters 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

ProposalMain filesTests
P3crates/plugins/src/effect.rs, canvas drawing in crates/ui-egui/src/canvas.rsa fuzz test of overlay decoding, a cache key test
P4, live effectscrates/plugins/src/{effect,runtime}.rs, the install flowfuel accounting tests, a drawing that waits for a heavy result
P6, objects besidecrates/plugins/src/objects.rsABI 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

TrackWhat we test againstHow it is discoveredWhenEffect of a failure
stableLatest published, non-pre-release VectorCraft releaseSemVer tags from git ls-remoteDaily, and on every PR touching the plug-in, the adapter or the gateBlocks our release; its issue is labelled upstream-stable
prereleaseLatest published alpha/beta/RC (-alpha, -beta, -rc SemVer tags)SemVer tags from git ls-remote, when newer than the latest releaseDailyIts issue is labelled upstream-prerelease; does not block
release-branchHead of VectorCraft’s release branch, which their pipeline builds into the next (still private, draft) releasegit ls-remote … refs/heads/releaseDailyIts issue is labelled upstream-canary
mainHead of VectorCraft’s maingit ls-remote … refs/heads/mainDailyIts 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:

  1. Each module installs (vectorcraft_plugins::registry::install_bytes), and its manifest is accepted (id, kind, parameter count and types).
  2. vc_abi_version is a version the host supports.
  3. Live effects run through vectorcraft_plugins::effect::apply on 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.
  4. Filters run and return valid objects. Exporters run through vectorcraft_plugins::export::run on 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 as plugin.run runs 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 as plugin.run admits them, by the manifests of the installed live effects.
  5. Error paths: invalid parameters are rejected by the host with the expected message.
  6. 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:

ScenarioStepsChecks
install-and-listinstall the 8 modules, then plugin.listeach 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)
applya square filled red and a line, given the fill and stroke effects with parameters, then savedas sent, with a fill in red and a stroke (REQ-VC-004, REQ-PRM-003)
compound-satin2 rails and a rung, Make Compound Path, then the satin effectas sent, and one satin column (REQ-VC-007)
graphic-stylea group of 2 squares, and a gold square with a fill effect, drawn, whose new graphic style is then applied to the groupas sent, and 3 fills in gold at the effect’s 90° (REQ-VC-006)
missing-plugina fill saved and drawn, then opened and saved again without the plug-insboth sew alike, as sent: the records are kept (REQ-PRM-003)
exporta 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 exporterseach 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)
settingsa 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 PESeach 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-satinan 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 PESthe 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)
testsheetseach sheet drawn as a design, drawn by Test Sheet into a new document, then saved and exported to PESeach 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 report decides, 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: fails”. It lists what failed at each level, with links to the run and to VectorCraft’s changes since the last release that passed. The issue is closed when the track passes again. A track fails when its job ends without writing a result. VectorCraft always has a release and a main branch, so a run that discovers no version of either counts that track as failing. When discovery fails, the other tracks’ issues are left as they are. The move rehearsal has an issue of its own, which a run without the rehearsal leaves alone.
  • 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, and cargo xtask docs regenerates the page. The bot pushes the branch compat/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, issues and actions with the default GITHUB_TOKEN. It runs on schedule, or when the workflow is started by hand on main, 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):

  1. Check out VectorCraft and put this repository into it as the folder stitchcraft/.
  2. Nested: cargo xtask compat join --nested .., then build a throwaway VectorCraft crate that depends on stitchcraft-engine by path.
  3. Joined: cargo xtask compat join .., then run cargo xtask ci from 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:

VectorCraftTrackPassed the gate
0.12.0stable2026-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

TermMeaning
RequirementA numbered, testable statement: REQ-FILL-TAT-006: pull compensation preserves region topology
CaseInput + parameters + profile + expectations, as a data file, linked to ≥ 1 requirement
CheckA reusable assertion (an invariant, a metric bound, a golden comparison, an expected diagnostic)
LevelWhere the case’s truth comes from (L0 invariants … L4 a real machine)
BlessAccept a changed golden output, in a PR, with a reason
DeviationA 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 as retired. The checks read the file as it is, so review keeps the second rule. --check helps, failing for a case that names an id the file lacks, and docs --check fails for a page that names one;
  • every case names existing requirements, and an active requirement 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 of cargo 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, with SC-E0806 for 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-testkit strategies, 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:

MetricDefinition
CoverageRasterize the region and the stitch lines (thread width 0.4 mm) at 0.05 mm; covered fraction of region pixels
Edge coveragePoints 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
ContainmentMaximum distance of any stitch point outside the region (0 if inside)
Row spacingMedian and spread of perpendicular distances between consecutive rows
Stitch lengthMin, max and distribution of Normal stitch lengths by role
FurrowsLongest run of rows whose needle points align within 0.1 mm
TopologyHoles 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

WhenWhat must pass
Every PRL0, 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
NightlyL1 fuzzing, L2 fresh seeds, L3 differential
WeeklyMutation testing of every shipped crate, against the recorded counts
Milestone closeAll requirements of the milestone active and green; its machine checkpoint signed off
ReleaseEverything 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

SourceRuleEnforced by
Hash map iteration orderNo HashMap/HashSet in engine, formats, render, params or adapters’ output paths; use BTreeMap, BTreeSet, IndexMap or sorted Vecclippy::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 multiplyingclippy::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 circlestitchcraft_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
RandomnessOnly 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 outclippy::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_addclippy::disallowed_methods on f64::mul_add and f32::mul_add, everywhere
ParallelismLibrary 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 orderclippy::disallowed_methods on std::thread::spawn and its kin
Sorting tiesEvery sort uses a total order with an explicit tie-breaker (f64::total_cmp, then index)review checklist; property tests shuffle inputs
Quantization driftQuantize 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 valuesformat 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 upgradesCargo.lock committed; golden files re-blessed only in a PR that explains whyCI + 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

GuardrailEnforcesConfigured inRuns
FormatOne style, no bikesheddingrustfmt.tomlcargo xtask ci · CI
Clippy, warnings deniedIdiomatic, bug-prone patterns flaggedworkspace [lints], clippy.tomlcargo xtask ci · CI
No panicsNo unwrap, expect, panic!, unreachable!, todo!, unimplemented!, dbg! outside tests; indexing_slicing denied in parsersworkspace [lints.clippy], crate attributescargo xtask ci · CI
No unsafeunsafe_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 blockworkspace [lints.rust], cargo xtask unsafe-auditcargo xtask ci · CI
DeterminismNo 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 proptestclippy.toml disallowed-types / disallowed-methods, deny.toml [bans]cargo xtask ci · CI
LayeringCrates depend only on lower layers (architecture); dependencies like clap or tiny-skia only where allowedxtask/src/layers.rs (append-only table)cargo xtask layers · CI
Module sizeRust files warn above 800 lines, fail above 1,500 (generated files exempt)xtask/src/filesize.rscargo xtask filesize · CI
WebAssemblyL0 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 bytextask/src/wasm.rs, xtask/src/conformance/wasm.rscargo xtask wasm · cargo xtask wasm --goldens · CI
Movable into VectorCraftEvery 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.rsmove job in compat.yml · daily and on PRs touching manifests or tooling
No GPL codeNo 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.rscargo xtask cleanroom · CI
DocsGenerated 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 existsxtask/src/docs.rs, xtask/src/docs_audit.rs, xtask/src/markdown.rscargo xtask docs --check · CI
Design pages follow the codeEvery 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: linextask/src/design_map.rs, .claude/settings.json (names the pages after each edit)cargo xtask docs --check · CI
Docs imagesEvery 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 parameterxtask/src/shots/, compat/vc-window/tests/shots.rscargo xtask shots --check · CI; cargo xtask shots --vectorcraft --check · shots-ui job
ConformanceRequirement/case consistency; every fixture has its row in the fixtures index; all cases pass; changed goldens need the golden-change label and a changelog linextask/src/conformance/, ci.yml, goldens.ymlcargo xtask conformance · CI
DependenciesLicence 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 reasondeny.tomlcargo xtask ci · CI (cargo-deny)
SpellingTypos in code, docs, commit-facing texttypos.tomlCI
ProseNew 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
MSRVBuilds on the declared rust-versionworkspace Cargo.tomlCI
Cross-platformTests 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.ymlCI
API docsRustdoc builds without warnings: no broken, ambiguous or private intra-doc linkscargo xtask ci (RUSTDOCFLAGS=-D warnings)cargo xtask ci · CI
Public API reviewChanges to a library crate’s public API show in the PR diff, as a change to its public-api.txtxtask/src/api.rs (cargo-public-api, pinned)cargo xtask api --check · CI
Coverage ratchetNo 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 includedconformance/coverage.toml, xtask/src/coverage.rs (cargo-llvm-cov)cargo xtask coverage · CI job
Performance budgetPlanning 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 itconformance/perf.toml, xtask/src/perf.rs (valgrind’s callgrind), the engine’s field examplecargo xtask perf · CI job
Mutation testingTests 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.rsmutants.yml · changed lines every PR, in 4 parts · everything weekly
FuzzingReaders never panic, respect their caps and terminate; anything read round-trips and previewsfuzz/, 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)

  1. 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.
  2. 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.
  3. Total functions. Every function returns for every input it accepts; impossible states are made unrepresentable by types rather than asserted.
  4. Bounded work. Loops over data charge a Budget; recursion has an explicit depth bound.
  5. No silent fallbacks. A degraded result always comes with a coded diagnostic.
  6. One crate, one error enum (thiserror), with messages a user can act on.
  7. Docs on every public item (missing_docs denied in library crates); module docs explain why.
  8. Tests next to code, conformance cases in conformance/, integration tests in tests/.

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): the ci and docs workflows. Merges are squash merges; main is 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:

SectionJobExamples
TutorialsLearn by doing, start to finish“Your first sew-out on a Brother”, “A patch in VectorCraft”
How-to guidesSolve one task“Stop a fill from puckering”, “Export for a different machine”
ReferenceLook things up (mostly generated)parameters, diagnostics, CLI, machine profiles, formats, compatibility
ExplanationUnderstand whyembroidery basics, pull and push, underlay, how routing works
Design / Plan / ContributingBuild StitchCraftthis 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 registryone page per stitch type with every parameter, its range, default, units and Ink/Stitch name; JSON Schemacargo xtask docs
Diagnostics registrydiagnostics index, one page per codecargo xtask docs
clap CLI definitionCLI referencecargo xtask docs
Machine profilesmachine profile referencecargo xtask docs
Format modules (limits, commands)format referencecargo xtask docs
conformance/inkstitch-params.toml + registrycompatibility contract with statuscargo xtask docs
Compatibility runssupported VectorCraft versionscargo 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.

KindMade byCompared byNotes
stitchstitchcraft-render (rendering) from a test sheet, or from a design the engine plans, an SVG file or a VectorCraft documentexact bytes (rendering is deterministic)Stitch-type illustrations, parameter comparisons, diagnostics examples
vectorcraft-rendervectorcraft-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 pointperceptual 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-uiVectorCraft’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 dialogthe same toleranceMenus and dialogs, in a desktop without a screen (VectorCraft’s window in CI)
photoa person, of a real sew-outpresence + 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 --check fails for a generated page that is stale or left over, and a page missing from SUMMARY.md. A broken relative link, #anchor or included file fails it, with ids made as mdBook makes them, smart punctuation included. A doc that names a cargo xtask subcommand that does not exist fails it too: we found that drift in VectorCraft’s own AGENTS.md. Ids must be in their files, REQ-… and DEV-… alike, and an SC-… code registered once its milestone is done. An image must be one a shot writes, with alt text.
  • cargo xtask conformance --check fails when requirements and cases disagree, or a fixture has no row in the fixtures index.
  • cargo xtask shots --check fails for a declared image that is missing or has no alt text. It also regenerates each stitch image 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 docs fails when the book does not build.
  • cargo test --doc fails when a Rust snippet does not compile. Command-line examples are generated pages, which docs --check covers.
  • typos fails on a misspelling (allow-list in typos.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 main at d59c9ab (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 by cargo xtask docs, and cargo xtask docs --check fails 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)

AttributeTypeUnitDefaultApplies toPhaseStitchCraft
min_stitch_length_mmfloatmm—allP1 (M3)registered
min_jump_stitch_length_mmfloatmm—allP1 (M3)registered
tiesdropdown—0allP1 (M3)registered
force_lock_stitchesboolean—falseallP1 (M3)registered
lock_startcombo—half_stitchallP1 (M3)deviates
lock_custom_startstring——customP1 (M3)deviates
lock_start_scale_mmfloatmm0.7back_forth, customP1 (M3)registered
lock_start_scale_percentfloat%100arrow, bowtie, cross, star, simple, triangle, zigzag, customP1 (M3)registered
lock_endcombo—half_stitchallP1 (M3)deviates
lock_custom_endstring——customP1 (M3)deviates
lock_end_scale_mmfloatmm0.7back_forth, customP1 (M3)registered
lock_end_scale_percentfloat%100arrow, bowtie, cross, star, simple, triangle, zigzag, customP1 (M3)registered
trim_afterboolean—falseallP1 (M3)registered
stop_afterboolean—falseallP1 (M3)registered

Strokes: running, ripple, zigzag, manual

AttributeTypeUnitDefaultApplies toPhaseStitchCraft
satin_columntoggle——allP1 (M3)registered
stroke_methodcombo—0allP1 (M3)registered
repeatsint—1running_stitch, ripple_stitch, zigzag_stitchP1 (M3)registered
bean_stitch_repeatsstr—0running_stitch, ripple_stitch, manual_stitch, zigzag_stitchP1 (M3)registered
manual_pattern_placementboolean—falseripple_stitchP3 (M10)planned
running_stitch_length_mmstringmm2.5running_stitch, ripple_stitchP1 (M3)registered
running_stitch_tolerance_mmfloatmm0.2running_stitch, ripple_stitchP1 (M3)deviates
enable_random_stitch_lengthboolean—falserunning_stitch, ripple_stitchP1 (M3)registered
random_stitch_length_jitter_percentfloat± %10running_stitch, ripple_stitchP1 (M3)registered
max_stitch_length_mmfloatmm—manual_stitchP1 (M3)registered
zigzag_spacing_mmstringmm0.4zigzag_stitchP2 (M7)registered
stroke_pull_compensation_mmfloatmm (each side)0zigzag_stitchP2 (M7)registered
zigzag_anglefloat°0zigzag_stitchP2 (M7)registered
line_countint—10ripple_stitchP3 (M10)planned
min_line_dist_mmfloatmm—ripple_stitchP3 (M10)planned
satin_guide_pattern_positioncombo—defaultripple_stitchP3 (M10)planned
staggersint—0 (1 when unset)ripple_stitchP3 (M10)planned
skip_startint—0ripple_stitchP3 (M10)planned
skip_endint—0ripple_stitchP3 (M10)planned
flip_copiesboolean—trueripple_stitchP3 (M10)planned
exponentfloat—1ripple_stitchP3 (M10)planned
flip_exponentboolean—falseripple_stitchP3 (M10)planned
reverseboolean—falseripple_stitchP3 (M10)planned
reverse_railscombo—automaticripple_stitchP3 (M10)planned
swap_satin_railsboolean—falseripple_stitchP3 (M10)planned
grid_size_mmfloatmm0ripple_stitchP3 (M10)planned
grid_firstboolean—falseripple_stitchP3 (M10)planned
scale_axisdropdown—0ripple_stitchP3 (M10)planned
scale_startfloat%100ripple_stitchP3 (M10)planned
scale_endfloat%0.0ripple_stitchP3 (M10)planned
rotate_ripplesboolean—trueripple_stitchP3 (M10)planned
join_styledropdown—0ripple_stitchP3 (M10)planned
random_seedrandom_seed——running_stitch, ripple_stitchP1 (M3)registered

Satin columns

AttributeTypeUnitDefaultApplies toPhaseStitchCraft
satin_columntoggle——allP1 (M4)registered
satin_methodcombo—0allP1 (M4)registered
random_width_decrease_percentfloat% (each side)0allP1 (M4)registered
random_width_increase_percentfloat% (each side)0allP1 (M4)registered
random_zigzag_spacing_percentfloat± %0allP1 (M4)registered
split_methodcombo—0allP1 (M4)registered
max_stitch_length_mmfloatmm—allP1 (M4)registered
random_split_jitter_percentfloat± %0defaultP1 (M4)registered
random_split_phaseboolean—falsedefaultP1 (M4)registered
min_random_split_length_mmfloatmm—defaultP1 (M4)registered
split_staggersfloat—4staggeredP1 (M4)registered
short_stitch_insetfloat%15allP1 (M4)registered
short_stitch_distance_mmfloatmm0.25allP1 (M4)registered
zigzag_spacing_mmfloatmm/cycle0.4allP1 (M4)registered
pull_compensation_percentfloat% (each side)0allP1 (M4)registered
pull_compensation_mmfloatmm (each side)0allP1 (M4)registered
push_compensation_mmfloatmm (each side)0allP1 (M4)registered
reverse_railscombo—automaticallP1 (M4)registered
swap_satin_railsboolean—falseallP1 (M4)registered
running_stitch_length_mmfloatmm2.5allP1 (M4)registered
running_stitch_tolerance_mmfloatmm0.1 (0.2 when unset)allP1 (M4)deviates
running_stitch_positionfloat%50allP1 (M4)registered
start_at_nearest_pointboolean—trueallP1 (M4)registered
end_at_nearest_pointboolean—trueallP1 (M4)registered
contour_underlaytoggle——allP1 (M4)registered
contour_underlay_stitch_length_mmfloatmm3allP1 (M4)registered
contour_underlay_stitch_tolerance_mmfloatmm0.2allP1 (M4)registered
contour_underlay_inset_mmfloatmm (each side)0.4allP1 (M4)registered
contour_underlay_inset_percentfloat% (each side)0allP1 (M4)registered
center_walk_underlaytoggle——allP1 (M4)registered
center_walk_underlay_stitch_length_mmfloatmm3allP1 (M4)registered
center_walk_underlay_stitch_tolerance_mmfloatmm0.2allP1 (M4)deviates
center_walk_underlay_repeatsint—2allP1 (M4)registered
center_walk_underlay_positionfloat%50allP1 (M4)registered
zigzag_underlaytoggle——allP1 (M4)registered
zigzag_underlay_spacing_mmfloatmm3allP1 (M4)registered
zigzag_underlay_inset_mmfloatmm (each side)—allP1 (M4)registered
zigzag_underlay_inset_percentfloat% (each side)—allP1 (M4)registered
zigzag_underlay_max_stitch_length_mmfloatmm—allP1 (M4)registered
random_seedrandom_seed——allP1 (M4)registered

Fills

AttributeTypeUnitDefaultApplies toPhaseStitchCraft
filltoggle—trueallP1 (M5)registered
fill_methodcombo—0allP1 (M5)registered
guided_fill_strategydropdown—0guided_fillP3 (M10)planned
contour_strategydropdown—0contour_fillP2 (M7)planned
join_styledropdown—0contour_fillP2 (M7)planned
avoid_self_crossingboolean—falsecontour_fillP2 (M7)planned
clockwiseboolean—truecontour_fillP2 (M7)planned
meander_patterncombo—0meander_fillP2 (M7)planned
meander_anglefloatdegrees0meander_fillP2 (M7)planned
meander_scale_percentfloat%100meander_fillP2 (M7)planned
clipboolean—falsemeander_fillP2 (M7)planned
smoothness_mmfloatmm0contour_fill, guided_fill, meander_fillP2 (M7)planned
expand_mmfloatmm0allP1 (M5)registered
gap_fill_rowsintrows0tatami_fillP1 (M5)registered
anglefloatdeg0tatami_fill, legacy_fillP1 (M5)registered
guided_fill_anglefloatdeg—guided_fillP3 (M10)planned
tartan_anglefloatdeg-45tartan_fillP3 (M10)planned
max_stitch_length_mmfloatmm4.0tatami_fill, contour_fill, guided_fill, linear_gradient_fill, tartan_fill, legacy_fillP1 (M5)registered
row_spacing_mmfloatmm0.25tatami_fill, contour_fill, guided_fill, circular_fill, linear_gradient_fill, tartan_fill, legacy_fillP1 (M5)registered
end_row_spacing_mmfloatmm—tatami_fill, circular_fill, legacy_fillP1 (M5)deviates
stitch_position_methodcombo—0guided_fillP3 (M10)planned
staggersint—4tatami_fill, guided_fill, linear_gradient_fill, tartan_fill, legacy_fillP1 (M5)registered
skip_lastboolean—falsetatami_fill, guided_fill, linear_gradient_fill, legacy_fillP1 (M5)registered
flipboolean—falselegacy_fill—not planned
reverseboolean—falselegacy_fill—not planned
stop_at_ending_pointboolean—falselinear_gradient_fill, tartan_fillP3 (M10)planned
underpathboolean—truetatami_fill, guided_fill, circular_fillP1 (M5)registered
running_stitch_length_mmfloatmm2.5tatami_fill, guided_fill, meander_fill, circular_fill, linear_gradient_fill, tartan_fillP1 (M5)registered
running_stitch_tolerance_mmfloatmm0.1 (0.2 when unset)tatami_fill, contour_fill, guided_fill, meander_fill, circular_fill, linear_gradient_fill, tartan_fillP1 (M5)deviates
enable_random_stitch_lengthboolean—falsetatami_fill, contour_fill, guided_fill, circular_fill, linear_gradient_fillP1 (M5)registered
random_stitch_length_jitter_percentfloat± %10tatami_fill, contour_fill, guided_fill, circular_fill, linear_gradient_fillP1 (M5)registered
repeatsint—1meander_fill, contour_fill, circular_fillP2 (M7)planned
bean_stitch_repeatsstr—0meander_fill, circular_fill, contour_fill, guided_fill, tartan_fillP2 (M7)planned
zigzag_spacing_mmfloatmm0meander_fillP2 (M7)planned
zigzag_width_mmfloatmm3meander_fillP2 (M7)planned
rows_per_threadint—2tartan_fillP3 (M10)planned
herringbone_width_mmintmm0tartan_fillP3 (M10)planned
pull_compensation_mmfloatmm (each side)0tatami_fillP1 (M5)registered
pull_compensation_percentfloat% (each side)0tatami_fillP1 (M5)registered
fill_underlaytoggle—trueallP1 (M5)registered
fill_underlay_anglefloatdeg—allP1 (M5)registered
fill_underlay_row_spacing_mmfloatmm—allP1 (M5)registered
fill_underlay_max_stitch_length_mmfloatmm—allP1 (M5)registered
fill_underlay_inset_mmfloatmm0allP1 (M5)registered
fill_underlay_skip_lastboolean—falseallP1 (M5)registered
underlay_underpathboolean—trueallP1 (M5)registered
random_seedrandom_seed——tatami_fill, contour_fill, guided_fill, circular_fill, meander_fill, linear_gradient_fillP1 (M5)registered
cross_stitch_methodcombo—0cross_stitchP3 (M10)planned
cross_thread_countint—4cross_stitchP3 (M10)planned
pattern_size_mmfloatmm (x y)3cross_stitchP3 (M10)planned
canvas_grid_originboolean—truecross_stitchP3 (M10)planned
cross_offset_mmfloatmm (x y)0cross_stitchP3 (M10)planned
cross_rotationfloatdeg0cross_stitchP3 (M10)planned
fill_coverageint%50cross_stitchP3 (M10)planned
max_cross_stitch_length_mmfloatmm11.0cross_stitchP3 (M10)planned

Clones (<use>)

AttributeTypeUnitDefaultApplies toPhaseStitchCraft
clonetoggle—trueallP2 (M8)planned
anglefloatdeg—allP2 (M8)planned
flip_angleboolean—falseallP2 (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.

ParameterValuePhase
stroke_methodrunning_stitchP1 (M3)
stroke_methodripple_stitchP3 (M10)
stroke_methodzigzag_stitchP2 (M7)
stroke_methodmanual_stitchP1 (M3)
satin_methodsatin_columnP1 (M4)
satin_methode_stitchP2 (M7)
satin_methods_stitchP2 (M7)
satin_methodzigzagP2 (M7)
fill_methodtatami_fillP1 (M5)
fill_methodcircular_fillP2 (M7)
fill_methodcontour_fillP2 (M7)
fill_methodcross_stitchP3 (M10)
fill_methodguided_fillP3 (M10)
fill_methodlinear_gradient_fillP3 (M10)
fill_methodmeander_fillP2 (M7)
fill_methodtartan_fillP3 (M10)
fill_methodlegacy_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.

CommandApplies toStitchCraft
starting_pointthe object a connector ties it toplanned: noted on the object as not applied (SC-W0802)
ending_pointthe object a connector ties it toplanned: noted on the object as not applied (SC-W0802)
target_pointthe object a connector ties it toread with the stitch types it positions
autoroute_startthe object a connector ties it tonothing to sew: input to Ink/Stitch’s tools
autoroute_endthe object a connector ties it tonothing to sew: input to Ink/Stitch’s tools
stopthe object a connector ties it toapplied: turns on stop_after
trimthe object a connector ties it toapplied: turns on trim_after
ignore_objectthe object a connector ties it toapplied: the object is left out and listed (SC-I0805)
satin_cut_pointthe object a connector ties it tonothing to sew: input to Ink/Stitch’s tools
ignore_layerevery layer it is inapplied: every layer it is in is left out and listed (SC-I0805)
originthe documentplanned: noted as not applied (SC-W0802)
stop_positionthe documentplanned: 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.

AttributeKindPhaseStitchCraft
ignore_objectelementP1 (M3)read
stroke_firstelementP1 (M8)planned
cutwork_needleelement—not planned
pattern_intervalpattern—not planned
pattern_offsetpattern—not planned
invisible_layersstitch plan—not planned
layer_visibilitystitch plan—not planned
sew_stacksew stack—not planned
sew_stack_onlysew stack—not planned
polylinelegacyP2 (M8)planned
auto_filllegacyP2 (M8)planned
e_stitchlegacyP2 (M8)planned
manual_stitchlegacyP2 (M8)planned
grid_sizelegacyP2 (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.

ADRTitleStatus
0001MIT OR Apache-2.0, clean-room from Ink/StitchAccepted (decision 2 replaced by 0012)
0002Host-agnostic engine; VectorCraft ABI v1 plug-in firstAccepted
0003One parameter registry generates everythingAccepted
0004Determinism by constructionAccepted
0005Geometry stackAccepted (spike M0.7)
0006mdBook, Diátaxis, generated reference, CI-regenerated imagesAccepted
0007PES v1 first, for the Brother 200 × 200 mm machineAccepted (decisions 2 and 3 replaced by 0013)
0008Conformance-first developmentAccepted
0009Adopt VectorCraft’s conventions; improve the ones that driftAccepted
0010Coded diagnostics with explanation pagesAccepted
0011StitchCraft can move into VectorCraft’s repositoryAccepted
0012Read Ink/Stitch’s source, never copy itAccepted
0013The reference machine is a Brother PE800, with a profile for each of its hoopsAccepted
0014Generators see their neighbours: the needle before, the next element afterAccepted

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

  1. StitchCraft is licensed MIT OR Apache-2.0, like VectorCraft.
  2. 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.
  3. 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).
  4. Not copied, ever: Ink/Stitch code, docs text, parameter descriptions, lock-stitch shape data, meander tiles, fonts, palettes compiled by Ink/Stitch, images.
  5. Format facts may be taken from MIT-licensed pyembroidery/pystitch with attribution in NOTICE; prefer public specifications where they exist.
  6. 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 cleanroom fails 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

  1. The engine (stitchcraft-core … stitchcraft-render) knows no host. Hosts adapt documents into a Design and consume a StitchPlan.
  2. 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.
  3. Phase 2: propose generic ABI v2 features upstream (RFC).
  4. 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.

NeedProposedChosenWhy
Flattening curveskurbo 0.13The engine halves each curve (normalize::stroke), since M3.4kurbo’s flatten calls the platform’s powf and hypot, whose last bits differ from one platform to the next
A fill’s regioni_overlay 9The engine’s region code (normalize::region), since M5.1An 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 fillsi_overlay 9The engine’s offsetter (normalize::offset), since M4 and M5.7Ink/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 linerstar 0.13Scans 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 rowspetgraph 0.8 with a walk of our ownThe engine’s own graph (generators::tatami::graph), since M5.4Ink/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 functionslibm 0.2libm 0.2, through stitchcraft_core::mathThe 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.

CriterionResult
1. Booleans and offsets of 10,000 random polygons with holes, and of a degenerate corpus, give valid output without panicking or hangingMet. 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 wasm32Met. 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 nativelyNot 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-inMet 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.

RingStepEngineGEOS
Round0.25 mm218 ms104 ms
Round0.4 mm135 ms61 ms
7 waves0.25 mm448 ms149 ms
7 waves0.4 mm261 ms115 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

  1. The first writer is PES v1 + PEC; DST is second (universal fallback, independent check).
  2. 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.
  3. 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 patternEvidenceVerdictStitchCraft
1Never crash: unwrap, expect, panic!, todo!, unimplemented!, unreachable! denied workspace-wide; documented alternativesCargo.toml [workspace.lints.clippy]; docs/development.md “Robustness”AdoptSame lints, plus indexing_slicing denied in parsers and dbg_macro
2Panic guard at every entry pointvectorcraft_engine::guardAdaptLibraries 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
3Everything is a command in a declarative registry reachable from UI, control channel and MCPcrates/engine/src/cmd/*, cmd! macroAdopt the idea, improve the dataEverything 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
4Layering enforced by cargo xtask layers with an append-only table and a unit-tested rule enginextask/src/layers.rsAdoptSame model in xtask/src/layers.rs
5One local gate, cargo xtask ci, with a step summaryxtask/src/main.rs (cmd_ci)Adopt and extendSame 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
6AGENTS.md as the contract for contributors and agentsAGENTS.mdAdopt, keep DRYCLAUDE.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
7Clean-room rules with an automated checkAGENTS.md says cargo xtask cleanroom runs in cargo xtask ciAdopt and implementAt 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
8Asset attribution enforced (ASSETS.md, checked by VectorCraft’s xtask assets step)xtask/src/assets.rsAdoptEvery committed image, font or fixture has a provenance row; generated images are attributed to their shot declaration
9Untrusted input is fuzzed with property testsengine/tests/import_fuzz.rs, format/tests/prop_format.rsAdopt and extendProperty tests on every parser, plus coverage-guided cargo-fuzz nightly with a persisted corpus
10cargo xtask wasm keeps crates building for the webxtask/src/main.rs (cmd_wasm)AdoptSame, for L0–L3 and the plug-in
11Shared corpora pinned by commit and SHA-256, never committedAGENTS.md, craftrules standards/test-corpora.mdAdoptcargo xtask corpus
12Honest ROADMAP.md, updated in the same PR as the workROADMAP.md, AGENTS.mdAdoptROADMAP.md status table + plan; the PR template asks for the update
13One task id per commit (M2.1: pen tool)AGENTS.mdAdoptOne roadmap step per PR, id in the title
14PR template checklist.github/pull_request_template.mdAdopt and automateMost checklist items are CI checks; the template keeps the human ones
15Rich doc comments on fields and modulese.g. crates/doc/src/node.rsAdopt and enforcemissing_docs denied in library crates
16Dependencies explained in Cargo.toml commentse.g. the wasmi features commentAdoptSame, plus cargo-deny
17Headless UI tests that assert on what a frame showscrates/ui-egui/src/panels/transparency.rs testsAdopt (phase 3 panels)Same style for any panel we contribute
18Very large modulesmenus.rs 3,752 lines, canvas.rs 2,941 linesDo not adoptFiles warn at 800 and fail at 1,500 lines
19Hand-taken screenshots in docsASSETS.md lists them as screenshots of the appDo not adoptDeclared, regenerated and compared in CI (docs pipeline)
20Workspace dependency declared but unused (egui_kittest)Cargo.toml:74; only named in xtask/src/layers.rsAvoidcargo-machete (or an xtask equivalent) flags unused declarations
21unsafe_code = "deny", example plug-in outside the workspace uses unsafe for the ABICargo.toml, crates/plugins/exampleAdopt and tightenforbid 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:

  1. 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.
  2. Both repositories call their tooling package xtask, so the two cannot share one workspace.
  3. Our tooling ran cargo … --workspace and read every package in cargo 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.toml and Cargo.lock are 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 as cargo xtask). The layering table holds only prefixed names, and an unlisted package fails cargo xtask layers.
  • Crate manifests inherit only what VectorCraft’s workspace defines: version, edition, license, rust-version, repository, and lints. Each crate sets publish = false itself.
  • 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-deny is 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’s main after 0.18.0, both deny cognitive_complexity above 30, too_many_arguments and wildcard_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 --check and shots --check run 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 of working-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-plugin talks 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 move job in compat.yml proves it daily, so a change that would break it fails here first.
  • Small constraints for us: no publish.workspace, no --workspace in 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 path dependencies 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 join merges 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

  1. 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.
  2. 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.
  3. 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 cleanroom still 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

  1. Built-in profiles brother-pe800-5x7 (130 × 180 mm), brother-pe800-4x4 (100 × 100 mm) and brother-pe800-small (20 × 60 mm) replace brother-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.
  2. brother-pe800-5x7 is the reference profile, profiles::REFERENCE. The command line plans for it unless --profile names another, and code that means the machine the checkpoints run on imports it rather than naming a hoop.
  3. 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.
  4. 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.
  5. 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 testsheet checks the sheet against it unless --profile names another.
  6. 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-W0702 stays in the code and its tests, though no built-in profile raises it.

Alternatives considered

  • Keep brother-200x200 alongside, 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

  1. Elements are generated in sewing order. Each is given the last needle point of the elements before it that sew any, whatever their thread.
  2. 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.
  3. Generators are pure functions of their shape, parameters, these 2 neighbours, seed and budget.
  4. 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.

StepDeliverableDone when
M0.1 ✅Workspace, crates skeleton, licences, toolchain pin, lints, rustfmt/clippy/deny/typos configscargo 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/contractcargo xtask ci passes locally
M0.3 ✅Workflows: ci.yml, docs.yml, compat.yml (discovery), nightly.yml; templates; DependabotFirst PR runs green on GitHub
M0.4 ✅mdBook site, design docs, ADRs, this roadmap, machine-testing protocolmdbook 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 issueA 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 outputThe 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 jobmove 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 toocleanroom 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.

StepDeliverableDone 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 hintREQ-PRF-001, REQ-PRF-002 active
M1.4 ✅Quantization (absolute, round-half-even); PEC stitch encoder with spec vectorsREQ-FMT-002 partial; encoder unit tests
M1.5 ✅PES v1 writer: header, PEC header, palette indices, thumbnailsgolden files (REQ-FMT-001)
M1.6 ✅Brother PEC palette table + CIEDE2000 nearest colourREQ-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 summaryreport visible on PRs
M1.10 ✅Docs: profile, format, test-sheet, diagnostics and command-line reference generated; tutorial “Your first sew-out” with real outputdocs 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 DSTSew-out report filed; profile values confirmed or changed; PES v6 decision made

M2 — Readers, preview renderer, fuzzing

StepDeliverableDone 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 nightlyREQ-FMT-002, REQ-FMT-003 active
M2.4 ✅pyembroidery oracle (pinned by hash) as a conformance case in the gates jobREQ-FMT-005 active
M2.5 ✅cargo-fuzz targets for both readers; nightly job with persisted corpusREQ-FMT-006; 1 h fuzzing clean
M2.6 ✅stitchcraft-render: realistic and simple styles from the quantized planREQ-RND-001; golden PNG files
M2.7 ✅stitch preview, stitch convert; the stitch shot generator behind cargo xtask shots; the docs-refresh.yml label workflowfirst generated docs images, byte-compared on every PR
M2.8 ✅Coverage ratchet and mutation-testing baseline; public-API snapshotsthresholds recorded

M3 — Running stitch family and plan assembly

StepDeliverableDone 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 indexevery 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 straightREQ-RUN-001..003
M3.5 ✅Repeats, bean stitch, random lengthREQ-RUN-004, REQ-RUN-005, REQ-RUN-007
M3.6 ✅Manual stitchREQ-RUN-006, REQ-RUN-008
M3.7 ✅Lock stitches: every shape, its size and its place at either end of the stitchingREQ-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 generatorREQ-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.jsonREQ-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 panica 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 eachreport filed; min-stitch and lock defaults confirmed (REQ-LCK-003)

M4 — Satin column

StepDeliverableDone 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-W0207REQ-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 sewnREQ-SAT-002
M4.3 ✅Pull and push compensation (symmetric/asymmetric), random width and spacing, SC-W0211REQ-SAT-001, REQ-SAT-006 to REQ-SAT-008
M4.4 ✅Short stitches on curvesREQ-SAT-009, a case set on tight curves
M4.5 ✅Split stitches (simple, staggered, random)REQ-SAT-003
M4.6 ✅Underlays: centre walk, contour, zigzagREQ-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-W0212REQ-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 themREQ-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.6report filed; satin defaults tuned

M5 — Tatami fill

StepDeliverableDone 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 outREQ-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 themREQ-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 familyREQ-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 sewnREQ-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-I0308REQ-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 rowsREQ-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 satinsREQ-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 perfREQ-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.9report 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.

StepDeliverableDone 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 depthREQ-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 hostREQ-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 effectsREQ-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 hintsREQ-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 writesfirst 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 controlsshots-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 widthREQ-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 doesREQ-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 machineREQ-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.16REQ-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 workflowreport 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.

StepDeliverableDone 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 ownREQ-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 turnsREQ-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 itREQ-SAT-024 and REQ-SAT-025, and the type listed for MC-6
M7.5 ✅Zigzag satin, sewn as Ink/Stitch sews itREQ-SAT-026 to REQ-SAT-028, and the type listed for MC-6
M7.6The 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 mscargo xtask perf guards the criterion
M7.7Contour fill, with its three strategies, as Ink/Stitch sews themits requirements active and the type listed for MC-6
M7.8Meander 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.9Circular fillsame
MC-6 🧵TS-11 (a sampler of the new types); kit: stitch testsheetreport 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.

StepDeliverableDone 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 digitizedits 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 diagnosticthe 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 rulesthe 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 isits requirements active
M13.5Columns 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 toosame
M13.6Shapes in parts. A shape’s broad parts are filled, and its narrow parts sew as columns and lines that overlap the fillsame; TS-13 drawn
M13.7VectorCraft. 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 pictureshots-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 testsheetreport filed; the rules’ values confirmed or changed
M13.8Automatic digitizing by default for VectorCraft documentsthe 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 itselfdecision 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-W0802 says 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.

StepDeliverableDone 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 itADR-0001 amended for fonts; one font read, its glyphs sewn
M14.2stitch 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 themits requirements active
M14.3Lettering 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 forthsame
M14.4Lettering 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 testsheetreport filed
M14.5Text 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 markerdecision 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.pes checks 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.png draws a picture of what the file sews. stitch testsheet --list lists 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.

ItemStandard
FabricMedium-weight woven cotton (quilting cotton or twill), pressed
StabilizerOne layer of medium tear-away; cut-away for knits (TS-09 only)
Needle75/11 embroidery needle, fresh at each checkpoint
Top thread40 wt polyester embroidery thread; colours as listed on the expected-result sheet (any brand)
Bobbin60 wt (90 wt) bobbin thread, white
HoopThe 5 × 7 in hoop, unless the sheet names another; fabric drum-tight, design centred
Machine settingsNote the machine’s speed and its own trim/jump settings (some Brother models trim automatically on long jumps)
StartingHold the thread’s tail for the first few stitches: at MC-1 a tail left loose pulled the first stitches out
Jump threadsThe 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.

SheetWhat it checksMeasure / observeFirst used
TS-01 Orientation & scaleAn 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 squareMC-1
TS-02 Commands3 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 loopsMC-1, MC-2
TS-02B Commands, from elementsTS-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 stitchLines 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 endedEven 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 lineMC-2
TS-04 Lock stitchesLines 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 leftSnip 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 ladder3 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 beforeCoverage, fabric showing between stitches, edges straight, long stitches loose, how much narrower than drawnMC-3
TS-06 Satin underlaysThe 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 firstEdge sharpness, loft, puckering, underlay showingMC-3
TS-07 Registration3 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 arrivesGap or overlap between fill and outline at 12 points around each circle, how much narrower than drawn each fill sews, puckeringMC-4
TS-08 Fill density & angle15 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 afterCoverage, 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 stitchMC-4
TS-09 Fill underlay & travelA 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 afterTravel 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 sizeFrames 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 cornerThe 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 roundMC-1, MC-5
TS-11 New stitch typesSamples 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 underlayPer 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 endMC-6
TS-12 Real designA 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 discFabric 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 arcWhether every glyph reads at its least height, satin coverage at its greatest, and whether the locks of small glyphs hold after the trimsMC-9

Checkpoint calendar

CheckpointAfterSheetsQuestions it must answer
MC-1M1TS-01, TS-02, TS-10A/B/CDoes 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-2M3TS-03, TS-04, TS-02BMinimum stitch length and lock defaults? Do the locks hold where the machine cuts the thread, and where a jump thread is snipped?
MC-3M4TS-05, TS-06Satin spacing, width limits, underlay defaults, pull compensation
MC-4M5TS-07, TS-08, TS-09Fill spacing, pull compensation, gap-filling rows, underlay and travel defaults
MC-5M6TS-12, TS-10Is the VectorCraft workflow complete and correct? Does a test sheet drawn by the Test Sheet filter sew as stitch testsheet writes it?
MC-8M13.7TS-13Do 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-6M7TS-11New stitch types acceptable?
MC-9M14TS-14Do the fonts sew legibly across the heights they claim?
MC-7M12allRelease 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

  1. A maintainer reads the report and labels it sewout:pass, sewout:tune or sewout:bug.
  2. 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.
  3. Bug: a failing conformance case reproducing it comes first, then the fix.
  4. Pass: the checkpoint row in ROADMAP.md is 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

UseWhat the sources showStitchCraft
Shapes drawn by hand, their stitches set one by oneInk/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 previewM6’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 embroideryThe 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 fontsLettering 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 orderThe 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 stitchM13.3 orders automatic elements, and with --sort-colours any design. M10 has the routing tools
The colours of real threadsThread 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 drawnThe 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 machineRequests 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 densitystitch inspect, stitch preview and stitch convert (M2). Opening a machine file in VectorCraft is added after 1.0
Designs larger than the hoopRequests 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 sewingThe simulator, realistic previews and a density mapVectorCraft’s previews (M6.3) and stitch preview
Worksheets for the machine’s operatorPrint PDF, with the colour blocks, thread names and stitch countsThe report and the picture that stitch plan writes. Worksheets stay after 1.0
Other stitch typesRipple, contour, meander, circular, guided and gradient fills, tartan, cross stitch from pictures (v3.3.0), and tutorials on redwork, cutwork and free-standing laceM7.6 to M7.9, and M10

What people struggle with

TroubleSourcesStitchCraft
Installing and starting Ink/Stitch, above all on macOSIssues 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 chooseA 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 refusesFill: 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 waitsAn 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 crispLettering 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-colours gives 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.vectorcraft writes their stitches as manual stitches, with the same document writer as M8’s stitch 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

  1. Read AGENTS.md at the repository root (the short contract for humans and agents) and the architecture map.
  2. Pick a step from the roadmap or an issue labelled good first issue.
  3. Set up: install Rust (the toolchain in rust-toolchain.toml installs itself), then rustup target add wasm32-unknown-unknown. The wasm-goldens gate needs wasm32-wasip1 too, and wasmtime, and cargo xtask ci skips it without them.
  4. Before every push: cargo xtask ci.

Playbooks

ChangePlaybook
Add a stitch typeNew stitch type
Add or change a parameterNew parameter
Add a machine formatNew format
Add a diagnosticNew diagnostic
Review a PRReview checklist
Write docs, messages or help textWriting 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.

  1. 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).
  2. Requirements. Add REQ-… entries to conformance/requirements.toml with status = "planned" and the milestone.
  3. 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.
  4. Module. Create crates/stitchcraft-engine/src/generators/<name>/ with mod.rs (the Generator impl), params.rs (the params! block, doc comments as help text) and tests.rs. Keep files under 800 lines; split by concern (sampling, routing, compensation).
  5. Register the generator in the generator table and its params in the registry list (one line each).
  6. Make the cases pass. Run cargo xtask conformance --filter <area>; switch the requirements to active.
  7. Docs. cargo xtask docs regenerates the parameter pages; write the user-facing page in docs/src/user/ (what it is for, when to use it, two or three declared shots); run cargo xtask shots.
  8. Gate. cargo xtask ci, then open the PR with the roadmap step id.
  9. 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

  1. 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.
  2. 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 …Params struct) also gets one line in stitchcraft_engine::registry::PARAMETERS and its generated page a line in docs/src/SUMMARY.md (cargo xtask docs --check says which).
  3. Use it through the typed struct; never read raw ParamSet values in a generator.
  4. 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.
  5. 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.
  6. Regenerate docs: cargo xtask docs writes 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 in conformance/deviations.toml. Add a before/after shot if the effect is visual.
  7. 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

  1. 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.
  2. Write the format page section in docs/src/design/formats.md: structure, units, axis, command encoding, per-record limits, colours, gotchas.
  3. Requirements and cases: golden files for canonical plans, round trip, commands, empty files, limits (REQ-FMT-* per format).
  4. Module: crates/stitchcraft-formats/src/<format>/ with write.rs, read.rs, consts.rs, tests.rs. Readers: check every length and offset before use, cap counts, return typed errors; indexing_slicing is denied here.
  5. Fuzz target: a body read_<format> in stitchcraft-testkit::fuzz (its tests run it on the new golden files), a one-line target in fuzz/fuzz_targets/read_<format>.rs, its name in the fuzz matrix of nightly.yml; run it for 10 minutes locally (cargo +nightly fuzz run read_<format>). The format’s golden files join the seed corpus by themselves.
  6. Oracle: add the format to the pyembroidery oracle job if pyembroidery supports it.
  7. Profiles: if a machine uses it, add or update a profile — and schedule a machine test before calling it supported.

Playbook: add a diagnostic

  1. Pick the code from the right range (diagnostics); never reuse a retired one.
  2. 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.
  3. 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.
  4. 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 --check fails until such a test exists.
  5. Regenerate docs: cargo xtask docs adds 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 prose passes.
  • 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 that and in 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.md and Git. A design page says how the code works today.
  • Keep each fact on one page. Other pages link to it. cargo xtask docs --check reports 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

UseForDo not use
designwhat is embroidered: its elements and settingsartwork, pattern (for the whole)
elementone object of the design, sewn as a unitobject, shape (for the element)
stitch plan, planthe needle positions and commands, in sewing orderstitch list, stitch file
needle pointone place where the needle goes through the fabricpenetration, drop
stitchthe thread between two needle pointsstep
jumpa move of the frame without sewingtravel (for a jump)
trima cut of the threadcut (in prose about plans)
stopa pause of the machinehalt, pause command
colour changea change to the next threadthread change
colour blockthe stitches sewn with one threadcolour section
lock stitchesthe tie-in at the start of a run and the tie-off at its endknots, tacks
machine filea PES or DST fileembroidery file, export
sew-outsewing a design on a machine to check ittest run
test sheeta design made to test one thing on a machinesampler
hoopthe frame the fabric is stretched inframe (for the hoop)
Ink/Stitch, StitchCraft, VectorCraft, pyembroiderythe projects, spelled their wayInkstitch, Stitchcraft