Skip to content

Commit 29ba8f4

Browse files
committed
Add Sierpiński arrowhead curve example
1 parent ae028d7 commit 29ba8f4

File tree

4 files changed

+93
-1
lines changed

4 files changed

+93
-1
lines changed

examples/README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ You can preview output files in Inkscape with Ink/Stitch installed.
1515
## Example list
1616

1717
* `python-pystitch-helloworld`: Basic example, a small square
18-
* `python-pystitch-addblock`: Simplified syntax, multiple squares with color changing
18+
* `python-pystitch-addblock`: Simplified syntax, multiple squares with color changing
19+
* `python-pystitch-sierpinski`: Build's Sierpiński arrowhead curve of selected depth
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
from pathlib import Path
2+
from pystitch import EmbPattern, CONTINGENCY_TIE_ON_THREE_SMALL, CONTINGENCY_TIE_OFF_THREE_SMALL, CONTINGENCY_LONG_STITCH_SEW_TO
3+
from typing import List, Tuple
4+
from utils import parse_args, UNITS_PER_MM
5+
import math
6+
import pystitch
7+
8+
args = parse_args(default_filename = f'out/{Path(__file__).parent.name}.jef')
9+
10+
# Create and encode pattern
11+
SIDE_WIDTH = args.side_width * UNITS_PER_MM
12+
CURVE_ORDER = args.curve_order
13+
print(f'Curve order: {CURVE_ORDER}')
14+
15+
16+
def build_arrowhead_curve(order: int, segment_length: float) -> List[Tuple[float, float]]:
17+
path = [(0.0, 0.0)]
18+
angle = 60 if order % 2 == 1 else 0
19+
20+
def expand(symbol: str, depth: int):
21+
if depth == 0:
22+
return symbol
23+
result = ''
24+
for char in symbol:
25+
if char == 'A':
26+
result += 'B-A-B'
27+
elif char == 'B':
28+
result += 'A+B+A'
29+
else:
30+
result += char
31+
return expand(result, depth - 1)
32+
33+
34+
instructions = expand('A', order)
35+
for cmd in instructions:
36+
if cmd in 'AB':
37+
x, y = path[-1]
38+
dx = segment_length * math.cos(math.radians(angle))
39+
dy = segment_length * math.sin(math.radians(angle))
40+
path.append((x + dx, y - dy))
41+
elif cmd == '+':
42+
angle += 60
43+
elif cmd == '-':
44+
angle -= 60
45+
return path
46+
47+
48+
segment_length = SIDE_WIDTH / pow(2, CURVE_ORDER)
49+
print(f'Stitch length: {segment_length / UNITS_PER_MM}')
50+
if (segment_length > 6 * UNITS_PER_MM) or (1 * UNITS_PER_MM > segment_length):
51+
print(f'Computed stitch length of {segment_length / UNITS_PER_MM} mm is out of recommended range 1-6 mm.')
52+
53+
pattern = EmbPattern()
54+
pattern.add_block(build_arrowhead_curve(CURVE_ORDER, segment_length), "red")
55+
pattern.move_center_to_origin()
56+
pystitch.write(pattern, args.output, settings = {
57+
'max_stitch': segment_length + 1, # Forces acceptable stitch length, the value might be outside of range supported by the machine,
58+
'long_stitch_contingency': CONTINGENCY_LONG_STITCH_SEW_TO,
59+
'tie_on': CONTINGENCY_TIE_ON_THREE_SMALL, # Tie-on stitches before the pattern
60+
'tie_off': CONTINGENCY_TIE_OFF_THREE_SMALL, # Tie-off stitches after the pattern
61+
})

examples/python-pystitch-sierpinski/out/.gitkeep

Whitespace-only changes.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
from argparse import ArgumentParser
2+
import os
3+
4+
UNITS_PER_MM = 10
5+
6+
def parse_args(default_filename: str = 'out/out.jef'):
7+
ALLOWED_EXTENSIONS = [
8+
'.dst', # Tajima Embroidery Format
9+
'.exp', # Melco Expanded Embroidery Format
10+
'.gcode', # gcode Format, Text File
11+
'.jef', # Janome Embroidery Format
12+
'.pec', # Brother Embroidery Format
13+
'.pes', # Brother Embroidery Format
14+
'.tbf', # Tajima Embroidery Format
15+
'.u01', # Barudan Embroidery Format
16+
'.vp3', # Pfaff Embroidery Format
17+
'.xxx' # Singer Embroidery Format
18+
]
19+
20+
parser = ArgumentParser(description="Generate an embroidery design.")
21+
parser.add_argument('-o', '--output', type=str, default=default_filename, help=f"Output file path. {default_filename} by default")
22+
parser.add_argument('-d', '--curve-order', type=int, default=5, help="Curve order. 5 by default")
23+
parser.add_argument('-s', '--side-width', type=float, default=60, help="Side width, in mm. 60 mm by default")
24+
args = parser.parse_args()
25+
26+
_, ext = os.path.splitext(args.output)
27+
if ext.lower() not in ALLOWED_EXTENSIONS:
28+
raise ValueError(f"Invalid file extension '{ext}'. Allowed extensions: {ALLOWED_EXTENSIONS}")
29+
30+
return args

0 commit comments

Comments
 (0)