yarnover 0.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- yarnover-0.1/PKG-INFO +24 -0
- yarnover-0.1/pyproject.toml +12 -0
- yarnover-0.1/readme.md +18 -0
- yarnover-0.1/setup.cfg +4 -0
- yarnover-0.1/src/yarnover/__init__.py +12 -0
- yarnover-0.1/src/yarnover/exceptions.py +7 -0
- yarnover-0.1/src/yarnover/needle.py +142 -0
- yarnover-0.1/src/yarnover/pattern.py +701 -0
- yarnover-0.1/src/yarnover/swatch.py +86 -0
- yarnover-0.1/src/yarnover/util.py +96 -0
- yarnover-0.1/src/yarnover.egg-info/PKG-INFO +24 -0
- yarnover-0.1/src/yarnover.egg-info/SOURCES.txt +12 -0
- yarnover-0.1/src/yarnover.egg-info/dependency_links.txt +1 -0
- yarnover-0.1/src/yarnover.egg-info/top_level.txt +1 -0
yarnover-0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yarnover
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: yarnover
|
|
5
|
+
Description-Content-Type: text/markdown
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Pattern - represents a knitting pattern
|
|
9
|
+
|
|
10
|
+
`Pattern.cast_on(n)`
|
|
11
|
+
|
|
12
|
+
alias `co`
|
|
13
|
+
|
|
14
|
+
Cast on `n` stitches
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
`Work.work_stitch(
|
|
18
|
+
- name: The name or code of the stitch
|
|
19
|
+
- parent_count = 1: The number of stitches from the left needle that will be worked
|
|
20
|
+
- slip_parent_stitches = True: Indicates whether to slip the parent stitches
|
|
21
|
+
- add_stitch_to_right_needle = True: Indicates whether to add the new stitch to the right needle
|
|
22
|
+
- tags = []:
|
|
23
|
+
|
|
24
|
+
`Work.slip_stitch()
|
yarnover-0.1/readme.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
Pattern - represents a knitting pattern
|
|
3
|
+
|
|
4
|
+
`Pattern.cast_on(n)`
|
|
5
|
+
|
|
6
|
+
alias `co`
|
|
7
|
+
|
|
8
|
+
Cast on `n` stitches
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
`Work.work_stitch(
|
|
12
|
+
- name: The name or code of the stitch
|
|
13
|
+
- parent_count = 1: The number of stitches from the left needle that will be worked
|
|
14
|
+
- slip_parent_stitches = True: Indicates whether to slip the parent stitches
|
|
15
|
+
- add_stitch_to_right_needle = True: Indicates whether to add the new stitch to the right needle
|
|
16
|
+
- tags = []:
|
|
17
|
+
|
|
18
|
+
`Work.slip_stitch()
|
yarnover-0.1/setup.cfg
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
from yarnover.pattern import Pattern
|
|
2
|
+
from yarnover.swatch import MetricSwatch
|
|
3
|
+
|
|
4
|
+
def co(count : int = 0) -> Pattern:
|
|
5
|
+
"""Star new :class:`Pattern`, optionally casting on ``count`` stitches
|
|
6
|
+
|
|
7
|
+
Equivant to ``Pattern().co(count)``
|
|
8
|
+
|
|
9
|
+
:param count: The number of stitches to cast on.
|
|
10
|
+
"""
|
|
11
|
+
return Pattern().co(count)
|
|
12
|
+
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from yarnover.exceptions import KnittingException
|
|
2
|
+
from typing import List, Optional, Set
|
|
3
|
+
|
|
4
|
+
from logging import getLogger
|
|
5
|
+
|
|
6
|
+
logger = getLogger(__name__)
|
|
7
|
+
|
|
8
|
+
Stitch = int
|
|
9
|
+
|
|
10
|
+
Marker = str
|
|
11
|
+
|
|
12
|
+
class Needle:
|
|
13
|
+
def __init__(self, initial_stitches : List[Stitch|Marker] = list()) -> None:
|
|
14
|
+
self.is_circular = False
|
|
15
|
+
self.items : List[Stitch|Marker] = list()
|
|
16
|
+
if initial_stitches:
|
|
17
|
+
self.items.extend(initial_stitches)
|
|
18
|
+
|
|
19
|
+
def __repr__(self):
|
|
20
|
+
return f"<Needle ({len(self.items)})>"
|
|
21
|
+
|
|
22
|
+
def push(self, stitch):
|
|
23
|
+
self.items.append(stitch)
|
|
24
|
+
|
|
25
|
+
def extend(self, items):
|
|
26
|
+
for item in items:
|
|
27
|
+
self.push(item)
|
|
28
|
+
|
|
29
|
+
def _pop_next_item(self):
|
|
30
|
+
if self.is_empty:
|
|
31
|
+
raise KnittingException("No items left to pop")
|
|
32
|
+
return self.items.pop()
|
|
33
|
+
|
|
34
|
+
def pop(self, count : int = 1) -> List[Stitch|Marker]:
|
|
35
|
+
items = list()
|
|
36
|
+
for _ in range(count):
|
|
37
|
+
item = self._pop_next_item()
|
|
38
|
+
items.append(item)
|
|
39
|
+
return items
|
|
40
|
+
|
|
41
|
+
def get_next_stitch(self) -> Stitch:
|
|
42
|
+
for i in range(len(self.items)):
|
|
43
|
+
item = self.peek(i)
|
|
44
|
+
if isinstance(item, Stitch):
|
|
45
|
+
return item
|
|
46
|
+
else:
|
|
47
|
+
raise KnittingException("No stitches left on needle")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def peek(self, position : int = 0):
|
|
51
|
+
return self.items[-(position+1)]
|
|
52
|
+
|
|
53
|
+
@property
|
|
54
|
+
def is_empty(self):
|
|
55
|
+
return len(self.items) == 0
|
|
56
|
+
|
|
57
|
+
def __iter__(self):
|
|
58
|
+
return reversed(self.items)
|
|
59
|
+
|
|
60
|
+
def distance_to_marker(self) -> int:
|
|
61
|
+
"""
|
|
62
|
+
Returns the number of stitches left before the next marker.
|
|
63
|
+
|
|
64
|
+
:param stitches_left: The number of stitches that must be left before the marker.
|
|
65
|
+
"""
|
|
66
|
+
for n, item in enumerate(self):
|
|
67
|
+
if isinstance(item, Marker):
|
|
68
|
+
return n
|
|
69
|
+
else:
|
|
70
|
+
raise KnittingException("No markers left found")
|
|
71
|
+
|
|
72
|
+
def distance_to_end(self):
|
|
73
|
+
for _, item in enumerate(self):
|
|
74
|
+
if isinstance(item, Marker):
|
|
75
|
+
raise KnittingException("Marker found before end")
|
|
76
|
+
return len(self.items)
|
|
77
|
+
|
|
78
|
+
def distance_to_marker_or_end(self):
|
|
79
|
+
distance = 0
|
|
80
|
+
for item in iter(self):
|
|
81
|
+
if isinstance(item, Marker):
|
|
82
|
+
return distance
|
|
83
|
+
else:
|
|
84
|
+
distance += 1
|
|
85
|
+
return distance
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@property
|
|
89
|
+
def stitch_count(self):
|
|
90
|
+
count = 0
|
|
91
|
+
for item in self.items:
|
|
92
|
+
if isinstance(item, Stitch):
|
|
93
|
+
count += 1
|
|
94
|
+
return count
|
|
95
|
+
|
|
96
|
+
def measure(self, from_marker, to_marker):
|
|
97
|
+
state = 0
|
|
98
|
+
count = 0
|
|
99
|
+
if from_marker is None:
|
|
100
|
+
state = 1
|
|
101
|
+
for item in self:
|
|
102
|
+
if state == 0 and isinstance(item, Marker):
|
|
103
|
+
assert item != to_marker
|
|
104
|
+
if item == from_marker:
|
|
105
|
+
state = 1
|
|
106
|
+
elif state == 1:
|
|
107
|
+
if isinstance(item, Stitch):
|
|
108
|
+
count += 1
|
|
109
|
+
elif isinstance(item, Marker):
|
|
110
|
+
assert item == to_marker
|
|
111
|
+
state = 2
|
|
112
|
+
if to_marker is not None:
|
|
113
|
+
assert state == 2
|
|
114
|
+
return count
|
|
115
|
+
|
|
116
|
+
def summarize(self):
|
|
117
|
+
summary = [0]
|
|
118
|
+
for item in self:
|
|
119
|
+
if isinstance(item, Stitch):
|
|
120
|
+
summary[-1] += 1
|
|
121
|
+
elif isinstance(item, Marker):
|
|
122
|
+
summary.append(0)
|
|
123
|
+
return summary
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class BackNeedle(Needle):
|
|
127
|
+
def __init__(self, needle : Needle) -> None:
|
|
128
|
+
self.is_circular = True
|
|
129
|
+
needle.is_circular = True
|
|
130
|
+
self.items : List[Stitch|Marker] = needle.items
|
|
131
|
+
|
|
132
|
+
def push(self, stitch):
|
|
133
|
+
self.items.insert(0, stitch)
|
|
134
|
+
|
|
135
|
+
def _pop_next_item(self):
|
|
136
|
+
return self.items.pop(0)
|
|
137
|
+
|
|
138
|
+
def peek(self, position : int = 0):
|
|
139
|
+
return self.items[position]
|
|
140
|
+
|
|
141
|
+
def __iter__(self):
|
|
142
|
+
return iter(self.items)
|
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
from contextlib import contextmanager
|
|
2
|
+
import math
|
|
3
|
+
from networkx import DiGraph, reverse_view
|
|
4
|
+
from typing import Callable, List, Optional, Self, Sequence, Set
|
|
5
|
+
|
|
6
|
+
from yarnover.exceptions import KnittingException, PatternException
|
|
7
|
+
from yarnover.needle import Needle, BackNeedle, Stitch, Marker
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
def format_marker(action, name):
|
|
14
|
+
if name:
|
|
15
|
+
return f"{action}<{name}>"
|
|
16
|
+
else:
|
|
17
|
+
return action
|
|
18
|
+
|
|
19
|
+
def format_stitches(code, count):
|
|
20
|
+
if count == 1:
|
|
21
|
+
return code
|
|
22
|
+
else:
|
|
23
|
+
return f"{code}{count}"
|
|
24
|
+
|
|
25
|
+
def format_do_this_to_that(code, stitches_left, count, log_count):
|
|
26
|
+
if stitches_left == 0:
|
|
27
|
+
if log_count:
|
|
28
|
+
return f"{code}({count})"
|
|
29
|
+
else:
|
|
30
|
+
return code
|
|
31
|
+
else:
|
|
32
|
+
if log_count:
|
|
33
|
+
return f"{code}-{stitches_left}({count})"
|
|
34
|
+
else:
|
|
35
|
+
return f"{code}-{stitches_left}"
|
|
36
|
+
|
|
37
|
+
def format_row_block_reference(from_row, to_row):
|
|
38
|
+
if from_row == to_row-1:
|
|
39
|
+
return f"row {from_row}"
|
|
40
|
+
else:
|
|
41
|
+
return f"rows {from_row}-{to_row-1}"
|
|
42
|
+
|
|
43
|
+
def format_increase_count(inc):
|
|
44
|
+
if inc < -1:
|
|
45
|
+
return f"{-inc} sts decreased"
|
|
46
|
+
if inc == -1:
|
|
47
|
+
return f"{-inc} st decreased"
|
|
48
|
+
if inc == 0:
|
|
49
|
+
return f"no sts increased"
|
|
50
|
+
if inc == 1:
|
|
51
|
+
return f"{inc} st increased"
|
|
52
|
+
if inc > 1:
|
|
53
|
+
return f"{inc} sts increased"
|
|
54
|
+
|
|
55
|
+
class Repeater:
|
|
56
|
+
"""Repeats a callable with summarized logging.
|
|
57
|
+
"""
|
|
58
|
+
def __init__(self, p : "Pattern", f : Callable[["Pattern"], "Pattern"]):
|
|
59
|
+
# TODO: Kan afhængighed til Pattern fjernes?
|
|
60
|
+
"""
|
|
61
|
+
:param p: The pattern whose logging is replaced.
|
|
62
|
+
:param f: The callable to be called. Executing the callable should affect the state of ``p``.
|
|
63
|
+
"""
|
|
64
|
+
self.p = p
|
|
65
|
+
self.f = f
|
|
66
|
+
|
|
67
|
+
def once(self):
|
|
68
|
+
return self.f(self.p)
|
|
69
|
+
|
|
70
|
+
def repeat(self, count : int):
|
|
71
|
+
log_length = len(self.p.row_log)
|
|
72
|
+
for _ in range(count):
|
|
73
|
+
self.f(self.p)
|
|
74
|
+
self.p.row_log = self.p.row_log[:log_length]
|
|
75
|
+
self.p.row_log.append(f"repeat {count} times")
|
|
76
|
+
return self.p
|
|
77
|
+
|
|
78
|
+
def to_end(self, stitches_left : int = 0, log_count : bool = True):
|
|
79
|
+
"""Repeat the callable to end, summarising output.
|
|
80
|
+
|
|
81
|
+
:param stitches_left: The number of stitches before the end to stop.
|
|
82
|
+
:param log_count: If True, the number for repetitions will be logged.
|
|
83
|
+
"""
|
|
84
|
+
log_length = len(self.p.row_log)
|
|
85
|
+
count = 0
|
|
86
|
+
while not self.p.at_end(stitches_left):
|
|
87
|
+
self.f(self.p)
|
|
88
|
+
count += 1
|
|
89
|
+
self.p.row_log = self.p.row_log[:log_length]
|
|
90
|
+
if count == 0:
|
|
91
|
+
return self.p
|
|
92
|
+
if stitches_left:
|
|
93
|
+
if log_count:
|
|
94
|
+
if count == 1:
|
|
95
|
+
self.p.row_log.append(f"repeat once to {stitches_left} before end")
|
|
96
|
+
if count > 1:
|
|
97
|
+
self.p.row_log.append(f"repeat to {stitches_left} before end ({count} times)")
|
|
98
|
+
else:
|
|
99
|
+
self.p.row_log.append(f"repeat to {stitches_left} before end")
|
|
100
|
+
else:
|
|
101
|
+
self.p.row_log.append(f"repeat to end")
|
|
102
|
+
return self.p
|
|
103
|
+
|
|
104
|
+
def to_marker(self, marker_name : str = "", stitches_left : int = 0, log_count : bool = True):
|
|
105
|
+
log_length = len(self.p.row_log)
|
|
106
|
+
count = 0
|
|
107
|
+
while not self.p.at_marker(marker_name, stitches_left):
|
|
108
|
+
self.f(self.p)
|
|
109
|
+
count += 1
|
|
110
|
+
self.p.row_log = self.p.row_log[:log_length]
|
|
111
|
+
if stitches_left:
|
|
112
|
+
if log_count:
|
|
113
|
+
self.p.row_log.append(f"repeat to {stitches_left} before marker ({count} times)")
|
|
114
|
+
else:
|
|
115
|
+
self.p.row_log.append(f"repeat to {stitches_left} before marker")
|
|
116
|
+
else:
|
|
117
|
+
if log_count:
|
|
118
|
+
self.p.row_log.append(f"repeat to marker ({count} times)")
|
|
119
|
+
else:
|
|
120
|
+
self.p.row_log.append(f"repeat to marker")
|
|
121
|
+
return self.p
|
|
122
|
+
|
|
123
|
+
class Pattern:
|
|
124
|
+
def __init__(self):
|
|
125
|
+
self.left = Needle()
|
|
126
|
+
self.right = Needle()
|
|
127
|
+
self.g : DiGraph = DiGraph()
|
|
128
|
+
self.g_rev = self.g.reverse(copy=False)
|
|
129
|
+
|
|
130
|
+
self.next_stitch_number : int = 0
|
|
131
|
+
self.last_stitch_worked : int | None = None
|
|
132
|
+
|
|
133
|
+
self.row_number = 0
|
|
134
|
+
self.stitch_number = 0
|
|
135
|
+
self.initial_stitch_count = 0
|
|
136
|
+
|
|
137
|
+
self.row_log = list()
|
|
138
|
+
|
|
139
|
+
self.quiet = False
|
|
140
|
+
|
|
141
|
+
def log_paragraph(self, text : str):
|
|
142
|
+
if self.row_log:
|
|
143
|
+
raise PatternException("Row in progress, can't log paragraph") # TODO: Skal vi det?
|
|
144
|
+
print(text)
|
|
145
|
+
|
|
146
|
+
def work_stitch(self,
|
|
147
|
+
code : str,
|
|
148
|
+
parent_count : int = 1,
|
|
149
|
+
slip_from_left_needle : bool = True,
|
|
150
|
+
add_to_right_needle : bool = True):
|
|
151
|
+
"""
|
|
152
|
+
Adds a stitches to the work.
|
|
153
|
+
|
|
154
|
+
:param code: The code of the new stitch.
|
|
155
|
+
:param parent_count: The number of stitches from the left needle to work.
|
|
156
|
+
:param slip_from_left_needle: Whether to slip the parent stitches from the left needle
|
|
157
|
+
:param add_to_right_needle: Whether to add the new stitch to the right needle
|
|
158
|
+
:meta private:
|
|
159
|
+
"""
|
|
160
|
+
stitch = self.next_stitch_number
|
|
161
|
+
|
|
162
|
+
column_number = self.get_column_number(stitch, parent_count)
|
|
163
|
+
|
|
164
|
+
if column_number is None:
|
|
165
|
+
print("!")
|
|
166
|
+
|
|
167
|
+
self.g.add_node(
|
|
168
|
+
stitch,
|
|
169
|
+
name=code,
|
|
170
|
+
row_number=self.row_number,
|
|
171
|
+
column_number=column_number,
|
|
172
|
+
stitch_number=self.stitch_number,
|
|
173
|
+
)
|
|
174
|
+
|
|
175
|
+
if self.last_stitch_worked is not None:
|
|
176
|
+
self.g.add_edge(self.last_stitch_worked, stitch, relation="successor")
|
|
177
|
+
|
|
178
|
+
self.last_stitch_worked = stitch
|
|
179
|
+
self.next_stitch_number += 1
|
|
180
|
+
self.stitch_number += 1 # on row
|
|
181
|
+
|
|
182
|
+
if parent_count > 0:
|
|
183
|
+
for i in range(parent_count):
|
|
184
|
+
parent_stitch = self.left.peek(i)
|
|
185
|
+
if not isinstance(parent_stitch, Stitch):
|
|
186
|
+
raise KnittingException(f"Trying to work non-stitch item: {parent_stitch}")
|
|
187
|
+
self.g.add_edge(parent_stitch, stitch, relation="child")
|
|
188
|
+
|
|
189
|
+
if slip_from_left_needle:
|
|
190
|
+
self.left.pop(parent_count)
|
|
191
|
+
if add_to_right_needle:
|
|
192
|
+
self.right.push(stitch)
|
|
193
|
+
|
|
194
|
+
def get_column_number(self, stitch, parent_count):
|
|
195
|
+
# If this is the first row, number sequentially
|
|
196
|
+
if self.row_number == 0:
|
|
197
|
+
return stitch
|
|
198
|
+
|
|
199
|
+
# If this is an unparented increase, bump and assign
|
|
200
|
+
elif parent_count == 0:
|
|
201
|
+
if self.left.is_empty:
|
|
202
|
+
if self.last_stitch_worked:
|
|
203
|
+
return self.g.nodes[self.last_stitch_worked]["column_number"]+1
|
|
204
|
+
else:
|
|
205
|
+
raise KnittingException("No next stitch, no previous stitch")
|
|
206
|
+
else:
|
|
207
|
+
return self.g.nodes[self.left.get_next_stitch()]["column_number"]
|
|
208
|
+
self.bump_stitches(column_number)
|
|
209
|
+
|
|
210
|
+
# If the first parent stitch has other children, bump and insert
|
|
211
|
+
elif parent_count > 0:
|
|
212
|
+
parent_stitch = self.left.peek()
|
|
213
|
+
if not isinstance(parent_stitch, Stitch):
|
|
214
|
+
raise KnittingException(f"Trying to knit non-stitch item: {parent_stitch}")
|
|
215
|
+
has_sibling = False
|
|
216
|
+
for related_stitch in self.g.successors(parent_stitch):
|
|
217
|
+
if self.g[parent_stitch][related_stitch]["relation"] == "child":
|
|
218
|
+
has_sibling = True
|
|
219
|
+
return self.g.nodes[parent_stitch]["column_number"]
|
|
220
|
+
self.bump_stitches(column_number)
|
|
221
|
+
break
|
|
222
|
+
if not has_sibling:
|
|
223
|
+
return self.g.nodes[parent_stitch]["column_number"]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
def bump_stitches(self, from_column : int):
|
|
227
|
+
nodes_to_bump = list()
|
|
228
|
+
for node, column_number in self.g.nodes(data="column_number"):
|
|
229
|
+
if column_number >= from_column: # type: ignore
|
|
230
|
+
nodes_to_bump.append(node)
|
|
231
|
+
for node in nodes_to_bump:
|
|
232
|
+
self.g.nodes[node]["column_number"] += 1
|
|
233
|
+
|
|
234
|
+
def repeat_stitch(self,
|
|
235
|
+
code : str,
|
|
236
|
+
parent_count : int = 1,
|
|
237
|
+
slip_from_left_needle : bool = True,
|
|
238
|
+
add_to_right_needle : bool = True,
|
|
239
|
+
count : int = 1):
|
|
240
|
+
"""
|
|
241
|
+
Convenience function to work the same stitch several times.
|
|
242
|
+
|
|
243
|
+
:param code: The code of the new stitch.
|
|
244
|
+
:param count: The number of stitches to work.
|
|
245
|
+
:meta private:
|
|
246
|
+
"""
|
|
247
|
+
logger.debug(f"{self.row_number}.{self.stitch_number}: {code}*{count}")
|
|
248
|
+
for _ in range(count):
|
|
249
|
+
self.work_stitch(
|
|
250
|
+
code,
|
|
251
|
+
parent_count,
|
|
252
|
+
slip_from_left_needle,
|
|
253
|
+
add_to_right_needle
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def stitch_to_end(self,
|
|
257
|
+
code : str,
|
|
258
|
+
parent_count : int = 1,
|
|
259
|
+
slip_from_left_needle : bool = True,
|
|
260
|
+
add_to_right_needle : bool = True,
|
|
261
|
+
stitches_left : int = 0,
|
|
262
|
+
marker_action : str = "fail") -> int:
|
|
263
|
+
"""
|
|
264
|
+
Repeat a stitch until all the stitches on the needle have been worked and all markers slipped or removed.
|
|
265
|
+
|
|
266
|
+
:param code: The code of the stitch.
|
|
267
|
+
:param stitches_left: The number of stitches that should be left unworked.
|
|
268
|
+
:param marker_action: What to do when a marker is encountered - "fail", "slip" or "remove".
|
|
269
|
+
:meta private:
|
|
270
|
+
"""
|
|
271
|
+
total_count = 0
|
|
272
|
+
log_length = len(self.row_log)
|
|
273
|
+
blocks = self.summarize()
|
|
274
|
+
while len(blocks) > 1:
|
|
275
|
+
block = blocks.pop(0)
|
|
276
|
+
total_count += block
|
|
277
|
+
self.repeat_stitch(
|
|
278
|
+
code,
|
|
279
|
+
parent_count,
|
|
280
|
+
slip_from_left_needle,
|
|
281
|
+
add_to_right_needle,
|
|
282
|
+
block)
|
|
283
|
+
if marker_action == "slip":
|
|
284
|
+
self.sm()
|
|
285
|
+
elif marker_action == "remove":
|
|
286
|
+
self.rm()
|
|
287
|
+
else:
|
|
288
|
+
raise KnittingException("Marker before end")
|
|
289
|
+
block = blocks.pop(0)
|
|
290
|
+
if block < stitches_left:
|
|
291
|
+
raise KnittingException(f"Less than {stitches_left} left")
|
|
292
|
+
self.repeat_stitch(
|
|
293
|
+
code,
|
|
294
|
+
parent_count,
|
|
295
|
+
slip_from_left_needle,
|
|
296
|
+
add_to_right_needle,
|
|
297
|
+
block)
|
|
298
|
+
total_count += block-stitches_left
|
|
299
|
+
self.row_log = self.row_log[:log_length]
|
|
300
|
+
return total_count
|
|
301
|
+
|
|
302
|
+
def stitch_to_marker(self,
|
|
303
|
+
code : str,
|
|
304
|
+
parent_count : int = 1,
|
|
305
|
+
slip_from_left_needle : bool = True,
|
|
306
|
+
add_to_right_needle : bool = True,
|
|
307
|
+
stitches_left : int = 0):
|
|
308
|
+
"""
|
|
309
|
+
Repeat a stitch until all the stitches until the next marker is reached
|
|
310
|
+
|
|
311
|
+
:param code: The code of the stitch.
|
|
312
|
+
:param stitches_left: The number of stitches that should be left unworked.
|
|
313
|
+
:meta private:
|
|
314
|
+
"""
|
|
315
|
+
log_length = len(self.row_log)
|
|
316
|
+
distance = self.left.distance_to_marker()
|
|
317
|
+
if distance < stitches_left:
|
|
318
|
+
raise KnittingException(f"Less than {stitches_left} stitches left to marker")
|
|
319
|
+
count = distance-stitches_left
|
|
320
|
+
self.repeat_stitch(
|
|
321
|
+
code,
|
|
322
|
+
parent_count,
|
|
323
|
+
slip_from_left_needle,
|
|
324
|
+
add_to_right_needle,
|
|
325
|
+
count)
|
|
326
|
+
self.row_log = self.row_log[:log_length]
|
|
327
|
+
return count
|
|
328
|
+
|
|
329
|
+
# Markers
|
|
330
|
+
def at_marker(self, marker_name : str = "", stitches_left : int = 0) -> bool:
|
|
331
|
+
"""
|
|
332
|
+
Return True iff the next item on the left needle is a marker.
|
|
333
|
+
|
|
334
|
+
:param stitches_left: The number of stitches ahead the marker must be.
|
|
335
|
+
:meta private:
|
|
336
|
+
"""
|
|
337
|
+
if self.left.is_empty:
|
|
338
|
+
return False
|
|
339
|
+
next_item = self.left.peek(stitches_left)
|
|
340
|
+
if isinstance(next_item, Marker):
|
|
341
|
+
if marker_name:
|
|
342
|
+
return next_item == marker_name
|
|
343
|
+
else:
|
|
344
|
+
return True
|
|
345
|
+
|
|
346
|
+
# Distances
|
|
347
|
+
def at_end(self, stitches_left : int = 0):
|
|
348
|
+
if self.left.is_circular:
|
|
349
|
+
raise KnittingException(f"Circular needle has no end")
|
|
350
|
+
distance = self.left.distance_to_end()
|
|
351
|
+
if distance < stitches_left:
|
|
352
|
+
raise KnittingException(f"Less than {stitches_left} stitches left")
|
|
353
|
+
return distance == stitches_left
|
|
354
|
+
|
|
355
|
+
def distance_to_marker(self, stitches_left : int = 0):
|
|
356
|
+
"""
|
|
357
|
+
Returns the number of stitches left before the next marker.
|
|
358
|
+
There must be at least one marker left on the needle.
|
|
359
|
+
If `stitches_left` is provided, this will return the number of stitches before
|
|
360
|
+
that number of stitches are left.
|
|
361
|
+
|
|
362
|
+
:meta private:
|
|
363
|
+
"""
|
|
364
|
+
distance = self.left.distance_to_marker()
|
|
365
|
+
if distance < stitches_left:
|
|
366
|
+
raise KnittingException(f"Less than {stitches_left} stitches left before marker")
|
|
367
|
+
return distance - stitches_left
|
|
368
|
+
|
|
369
|
+
# Stitches
|
|
370
|
+
def co(self, count : int = 1):
|
|
371
|
+
"""
|
|
372
|
+
Cast on one or more stitches.
|
|
373
|
+
"""
|
|
374
|
+
self.repeat_stitch(code="co", parent_count=0, count=count)
|
|
375
|
+
self.row_log.append(format_stitches("co", count))
|
|
376
|
+
return self
|
|
377
|
+
|
|
378
|
+
def s(self, count : int = 1):
|
|
379
|
+
"""
|
|
380
|
+
Slip one or more stitches from the left needle to the right.
|
|
381
|
+
|
|
382
|
+
:raise KnittingException: If there are no stitches to slip.
|
|
383
|
+
"""
|
|
384
|
+
for _ in range(count):
|
|
385
|
+
if self.left.is_empty:
|
|
386
|
+
raise KnittingException(f"No more stitches to work")
|
|
387
|
+
if not isinstance(self.left.peek(), Stitch):
|
|
388
|
+
raise KnittingException(f"Next item is not a stitch: {self.left.peek()}")
|
|
389
|
+
self.right.extend(self.left.pop())
|
|
390
|
+
self.row_log.append(format_stitches("s", count))
|
|
391
|
+
return self
|
|
392
|
+
|
|
393
|
+
def k(self, count : int = 1):
|
|
394
|
+
"""Knit one or more s0titches.
|
|
395
|
+
"""
|
|
396
|
+
self.repeat_stitch(code="k", count=count)
|
|
397
|
+
self.row_log.append(format_stitches("k", count))
|
|
398
|
+
return self
|
|
399
|
+
|
|
400
|
+
def ktbl(self, count : int = 1):
|
|
401
|
+
"""Knit one or more stitches through the back loop.
|
|
402
|
+
"""
|
|
403
|
+
self.repeat_stitch(code="ktbl", count=count)
|
|
404
|
+
self.row_log.append(format_stitches("ktbl", count))
|
|
405
|
+
return self
|
|
406
|
+
|
|
407
|
+
def p(self, count : int = 1):
|
|
408
|
+
"""Purl one or more stitches.
|
|
409
|
+
"""
|
|
410
|
+
self.repeat_stitch("p", count=count)
|
|
411
|
+
self.row_log.append(format_stitches("p", count))
|
|
412
|
+
return self
|
|
413
|
+
|
|
414
|
+
# Decreases
|
|
415
|
+
def k2tog(self):
|
|
416
|
+
"""Knit two together.
|
|
417
|
+
"""
|
|
418
|
+
self.work_stitch("k2tog", parent_count=2)
|
|
419
|
+
self.row_log.append(f"k2tog")
|
|
420
|
+
return self
|
|
421
|
+
|
|
422
|
+
def ssk(self):
|
|
423
|
+
"""Slip, slip, knit decrease"""
|
|
424
|
+
self.work_stitch("ssk", parent_count=2)
|
|
425
|
+
self.row_log.append(f"ssk")
|
|
426
|
+
return self
|
|
427
|
+
|
|
428
|
+
# Increases
|
|
429
|
+
def yo(self, count : int = 1):
|
|
430
|
+
"""Yarnover
|
|
431
|
+
"""
|
|
432
|
+
self.repeat_stitch(code="yo", parent_count=0, count=count)
|
|
433
|
+
self.row_log.append(format_stitches("yo", count))
|
|
434
|
+
return self
|
|
435
|
+
|
|
436
|
+
def m(self, count : int = 1):
|
|
437
|
+
"""
|
|
438
|
+
An unspecified and unparented increase.
|
|
439
|
+
"""
|
|
440
|
+
self.work_stitch("m", parent_count=0)
|
|
441
|
+
self.row_log.append(format_stitches("m", count))
|
|
442
|
+
return self
|
|
443
|
+
|
|
444
|
+
def m1l(self):
|
|
445
|
+
"""Make one left.
|
|
446
|
+
"""
|
|
447
|
+
self.work_stitch("m1l", parent_count=0)
|
|
448
|
+
self.row_log.append(f"m1l")
|
|
449
|
+
return self
|
|
450
|
+
|
|
451
|
+
def m1r(self):
|
|
452
|
+
"""Make one right."""
|
|
453
|
+
self.work_stitch("m1r", parent_count=0)
|
|
454
|
+
self.row_log.append(f"m1r")
|
|
455
|
+
return self
|
|
456
|
+
|
|
457
|
+
def kfb(self):
|
|
458
|
+
"""Knit front and back.
|
|
459
|
+
"""
|
|
460
|
+
self.work_stitch("kfb1", slip_from_left_needle=False)
|
|
461
|
+
self.work_stitch("kfb2")
|
|
462
|
+
self.row_log.append(f"kfb")
|
|
463
|
+
return self
|
|
464
|
+
|
|
465
|
+
# Bindoff
|
|
466
|
+
def bo(self, count : int = 1):
|
|
467
|
+
"""Bind off.
|
|
468
|
+
"""
|
|
469
|
+
self.repeat_stitch(code="bo", add_to_right_needle=False, count=count)
|
|
470
|
+
self.row_log.append(format_stitches("bo", count))
|
|
471
|
+
return self
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
def pte(self,
|
|
475
|
+
stitches_left : int = 0,
|
|
476
|
+
slip_markers : bool = False,
|
|
477
|
+
log_count : bool = True):
|
|
478
|
+
"""Purl to end
|
|
479
|
+
If knitting in the round, this purls all stitches currently on the needle.
|
|
480
|
+
|
|
481
|
+
:params stitches_left: The number of stitches to leave on the
|
|
482
|
+
right needle before the end.
|
|
483
|
+
:param slip_markers: If true, slip any markers encountered. If false,
|
|
484
|
+
markers will trigger an error.
|
|
485
|
+
:param log_count: Whether to log the total count knitted.
|
|
486
|
+
"""
|
|
487
|
+
count = self.stitch_to_end(code="p",
|
|
488
|
+
stitches_left=stitches_left,
|
|
489
|
+
marker_action="slip" if slip_markers else "none",
|
|
490
|
+
)
|
|
491
|
+
self.row_log.append(format_do_this_to_that("pte", stitches_left, count, log_count))
|
|
492
|
+
return self
|
|
493
|
+
|
|
494
|
+
def kte(self,
|
|
495
|
+
stitches_left : int = 0,
|
|
496
|
+
slip_markers : bool = False,
|
|
497
|
+
log_count : bool = True,
|
|
498
|
+
):
|
|
499
|
+
"""Knit to end.
|
|
500
|
+
|
|
501
|
+
If knitting in the round, this knits all stitches currently on the needle
|
|
502
|
+
|
|
503
|
+
:params stitches_left: The number of stitches to leave on the
|
|
504
|
+
right needle before the end.
|
|
505
|
+
:param slip_markers: If true, slip any markers encountered. If false,
|
|
506
|
+
markers will trigger an error.
|
|
507
|
+
:param log_count: Whether to log the total count knitted.
|
|
508
|
+
"""
|
|
509
|
+
count = self.stitch_to_end(code="k",
|
|
510
|
+
stitches_left=stitches_left,
|
|
511
|
+
marker_action="slip" if slip_markers else "none",
|
|
512
|
+
)
|
|
513
|
+
self.row_log.append(format_do_this_to_that("kte", stitches_left, count, log_count))
|
|
514
|
+
return self
|
|
515
|
+
|
|
516
|
+
def ktm(self, stitches_left : int = 0, log_count : bool = True):
|
|
517
|
+
"""
|
|
518
|
+
Knit to the next marker.
|
|
519
|
+
|
|
520
|
+
:params stitches_left: The number of stitches to leave on the
|
|
521
|
+
right needle before the marker.
|
|
522
|
+
:param log_count: Whether to log the total count knitted.
|
|
523
|
+
"""
|
|
524
|
+
count = self.stitch_to_marker("k", stitches_left=stitches_left)
|
|
525
|
+
self.row_log.append(format_do_this_to_that("ktm", stitches_left, count, log_count))
|
|
526
|
+
return self
|
|
527
|
+
|
|
528
|
+
def ptm(self, stitches_left : int = 0, log_count : bool = False):
|
|
529
|
+
"""
|
|
530
|
+
Purl to the next marker.
|
|
531
|
+
|
|
532
|
+
:params stitches_left: The number of stitches to leave on the
|
|
533
|
+
right needle before the marker.
|
|
534
|
+
"""
|
|
535
|
+
count = self.stitch_to_marker("p", stitches_left=stitches_left)
|
|
536
|
+
self.row_log.append(format_do_this_to_that("ptm", stitches_left, count, log_count))
|
|
537
|
+
return self
|
|
538
|
+
|
|
539
|
+
def bind_off_to_end(self, stitches_left : int = 0):
|
|
540
|
+
self.stitch_to_end(code="bo",
|
|
541
|
+
stitches_left=stitches_left,
|
|
542
|
+
marker_action="remove"
|
|
543
|
+
)
|
|
544
|
+
self.row_log.append("bind off to end")
|
|
545
|
+
return self
|
|
546
|
+
|
|
547
|
+
# Markers
|
|
548
|
+
def pm(self, name : Optional[str] = None):
|
|
549
|
+
"""Place a marker.
|
|
550
|
+
|
|
551
|
+
:param name: If specified, this will be the name of the marker.
|
|
552
|
+
"""
|
|
553
|
+
logger.debug(f"Placing marker {name}")
|
|
554
|
+
marker = Marker(name)
|
|
555
|
+
self.right.push(marker)
|
|
556
|
+
self.row_log.append(format_marker("pm", name))
|
|
557
|
+
return self
|
|
558
|
+
|
|
559
|
+
def sm(self, name : Optional[str] = None):
|
|
560
|
+
"Slip a marker from the left to the right needle"
|
|
561
|
+
(marker,) = self.left.pop()
|
|
562
|
+
if not isinstance(marker, Marker):
|
|
563
|
+
raise KnittingException(f"Next item is not a marker")
|
|
564
|
+
if name and marker != name:
|
|
565
|
+
raise KnittingException(f"Expecting marker named {name}, got {marker}")
|
|
566
|
+
logger.debug(f"Slipping marker {marker}")
|
|
567
|
+
self.right.push(marker)
|
|
568
|
+
self.row_log.append(format_marker("sm", name))
|
|
569
|
+
return self
|
|
570
|
+
|
|
571
|
+
def rm(self, name : Optional[str] = None):
|
|
572
|
+
"""Remove a marker. If ``name`` is specified, the marker must have that name.
|
|
573
|
+
|
|
574
|
+
:param name: The name of the marker. If this is left out, the maker will be removed, regardless of name.
|
|
575
|
+
:raise KnittingException: If the next item on the left needle is not a marker
|
|
576
|
+
or the name of the marker doesn't match.
|
|
577
|
+
"""
|
|
578
|
+
logger.debug(f"Removing marker {name}")
|
|
579
|
+
(marker,) = self.left.pop()
|
|
580
|
+
if not isinstance(marker, Marker):
|
|
581
|
+
raise KnittingException(f"Next item is not a marker")
|
|
582
|
+
if name and marker != name:
|
|
583
|
+
raise KnittingException(f"Expecting marker named {name}, got {marker}")
|
|
584
|
+
self.row_log.append(format_marker("rm", name))
|
|
585
|
+
return self
|
|
586
|
+
|
|
587
|
+
def turn(self, short_row=False):
|
|
588
|
+
"""
|
|
589
|
+
Swap the left and right needles.
|
|
590
|
+
|
|
591
|
+
Unless `short_row` is set to True, turning with stitches left on the left needle
|
|
592
|
+
will raise an exception.
|
|
593
|
+
"""
|
|
594
|
+
if short_row or self.left.is_empty:
|
|
595
|
+
self.right, self.left = self.left, self.right
|
|
596
|
+
if not short_row:
|
|
597
|
+
self.end_of_row()
|
|
598
|
+
logger.debug(f"Turn")
|
|
599
|
+
else:
|
|
600
|
+
raise KnittingException(f"Short row")
|
|
601
|
+
return self
|
|
602
|
+
|
|
603
|
+
def end_of_row(self, description : str = ""):
|
|
604
|
+
"""End the current row and output logging.
|
|
605
|
+
|
|
606
|
+
:param description: A descriptive text to replace the generated log of the row.
|
|
607
|
+
"""
|
|
608
|
+
increases = self.left.stitch_count - self.initial_stitch_count
|
|
609
|
+
description = description or ' '.join(self.row_log)
|
|
610
|
+
if not self.quiet:
|
|
611
|
+
if increases == 0 or self.left.stitch_count == 0:
|
|
612
|
+
print(f"- Row {self.row_number}: {description}")
|
|
613
|
+
else:
|
|
614
|
+
print(f"- Row {self.row_number}: {description} ({format_increase_count(increases)})")
|
|
615
|
+
self.row_log.clear()
|
|
616
|
+
logger.debug(f"End of row")
|
|
617
|
+
self.stitch_number = 0
|
|
618
|
+
self.row_number += 1
|
|
619
|
+
self.initial_stitch_count = self.left.stitch_count
|
|
620
|
+
return self
|
|
621
|
+
|
|
622
|
+
def join(self):
|
|
623
|
+
"""
|
|
624
|
+
Join the work.
|
|
625
|
+
|
|
626
|
+
This replaces the current left needle with a "reverse view" of the right needle (to simulate a circular needle),
|
|
627
|
+
and requires the current left needle to be empty.
|
|
628
|
+
"""
|
|
629
|
+
if self.left.items:
|
|
630
|
+
raise KnittingException(f"Unable to join, {len(self.left.items)} left on left needle")
|
|
631
|
+
self.left = BackNeedle(self.right)
|
|
632
|
+
return self
|
|
633
|
+
|
|
634
|
+
def move_stitches(self, count : int):
|
|
635
|
+
"Move stitches to another needle."
|
|
636
|
+
logger.debug(f"Moving {count} stitches from left needle")
|
|
637
|
+
self.row_log.append(f"remove {count} stitches")
|
|
638
|
+
return self.left.pop(count)
|
|
639
|
+
|
|
640
|
+
def do(self, f : Callable[[Self], Self], description : Optional[str] = None):
|
|
641
|
+
"""
|
|
642
|
+
Execute a callable once and return a :class:`Repeater` that allows you to repeat it.
|
|
643
|
+
|
|
644
|
+
:param f: The callable to repeat.
|
|
645
|
+
:return: A :class:`Repeater` object that
|
|
646
|
+
"""
|
|
647
|
+
log_checkpoint = len(self.row_log)
|
|
648
|
+
f(self)
|
|
649
|
+
self.row_log, block_log = self.row_log[:log_checkpoint], self.row_log[log_checkpoint:]
|
|
650
|
+
self.row_log.append(f"[{description or ' '.join(block_log)}]")
|
|
651
|
+
|
|
652
|
+
return Repeater(self, f)
|
|
653
|
+
|
|
654
|
+
def repeat(self, count : int):
|
|
655
|
+
"""Yields numbers from 0 to ``count``.
|
|
656
|
+
|
|
657
|
+
After the first iteration, output is suppressed. On completion,
|
|
658
|
+
a summary of the additional repetitions is added to the log.
|
|
659
|
+
|
|
660
|
+
:param count: The number of repetitions, including the first one.
|
|
661
|
+
"""
|
|
662
|
+
start_row = self.row_number
|
|
663
|
+
yield 0
|
|
664
|
+
end_row = self.row_number
|
|
665
|
+
with self.quietly:
|
|
666
|
+
for i in range(1, count):
|
|
667
|
+
yield i
|
|
668
|
+
|
|
669
|
+
print(f"- Rows {format_row_block_reference(end_row, self.row_number)}: Repeat {format_row_block_reference(start_row, end_row)} {count-1} more times")
|
|
670
|
+
|
|
671
|
+
def summarize(self):
|
|
672
|
+
"""Summarizes the stitches on the left needle.
|
|
673
|
+
|
|
674
|
+
This method returns a list of numbers that represent the sections of stitches between
|
|
675
|
+
the current location, the end, and any markers in between.
|
|
676
|
+
The numbers sum to the number of stitches on the left needle and the length of the list is
|
|
677
|
+
one more than the number of markers (i.e. one number if there are zero markers, two if
|
|
678
|
+
there is one marker).
|
|
679
|
+
|
|
680
|
+
.. attention::
|
|
681
|
+
|
|
682
|
+
If knitting in the round with a single BOR marker that has just been slipped, a list of *two*
|
|
683
|
+
items is still returned: The total number of stitches on the needle and zero, which is the number of stitches
|
|
684
|
+
from the BOR marker to the current location.
|
|
685
|
+
|
|
686
|
+
:return: A list of integers one longer than the number of markers (i.e. one number if there are zero markers)
|
|
687
|
+
:meta private:
|
|
688
|
+
"""
|
|
689
|
+
return self.left.summarize()
|
|
690
|
+
|
|
691
|
+
|
|
692
|
+
@property
|
|
693
|
+
@contextmanager
|
|
694
|
+
def quietly(self):
|
|
695
|
+
log_checkpoint = len(self.row_log)
|
|
696
|
+
yield None
|
|
697
|
+
self.row_log = self.row_log[:log_checkpoint]
|
|
698
|
+
|
|
699
|
+
def resume(self, stitches):
|
|
700
|
+
if self.left.is_empty:
|
|
701
|
+
self.left.items.extend(stitches)
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
|
|
2
|
+
from typing import Optional
|
|
3
|
+
from yarnover.exceptions import KnittingException
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
Distance = float
|
|
7
|
+
|
|
8
|
+
class Swatch:
|
|
9
|
+
"""Represents a swatch, used for converting measurements to rows and stitches.
|
|
10
|
+
"""
|
|
11
|
+
def __init__(self,
|
|
12
|
+
swatch_unit_count : int,
|
|
13
|
+
unit : str,
|
|
14
|
+
swatch_stitches,
|
|
15
|
+
swatch_rows):
|
|
16
|
+
self.swatch_unit_count = swatch_unit_count
|
|
17
|
+
self.unit = unit
|
|
18
|
+
self.stitches_per_unit = swatch_stitches / swatch_unit_count
|
|
19
|
+
self.rows_per_unit = swatch_rows / swatch_unit_count
|
|
20
|
+
|
|
21
|
+
def units_to_stitches(self,
|
|
22
|
+
distance : float,
|
|
23
|
+
divisible_by : int = 1) -> int:
|
|
24
|
+
"""Convert units of measurement to stitches.
|
|
25
|
+
|
|
26
|
+
:param distance: A distance, measured in the unit of the swatch
|
|
27
|
+
"""
|
|
28
|
+
if divisible_by != 1:
|
|
29
|
+
return divisible_by * self.units_to_stitches(distance / divisible_by)
|
|
30
|
+
return round(distance * self.stitches_per_unit)
|
|
31
|
+
|
|
32
|
+
def stitches_to_units(self, stitches : int) -> int:
|
|
33
|
+
"""Convert stitches to units of measurement.
|
|
34
|
+
|
|
35
|
+
:param stitches: The number of stitches
|
|
36
|
+
"""
|
|
37
|
+
return round(stitches / self.stitches_per_unit)
|
|
38
|
+
|
|
39
|
+
def units_to_rows(self,
|
|
40
|
+
distance : float,
|
|
41
|
+
divisible_by : int = 1) -> int:
|
|
42
|
+
"""Convert units of measurement to rows"""
|
|
43
|
+
if divisible_by != 1:
|
|
44
|
+
return divisible_by * self.units_to_rows(distance / divisible_by)
|
|
45
|
+
return round(distance * self.rows_per_unit)
|
|
46
|
+
|
|
47
|
+
def rows_to_units(self, rows : int) -> int:
|
|
48
|
+
"""Convert rows to units of measurement.
|
|
49
|
+
|
|
50
|
+
:param rows: The number of rows
|
|
51
|
+
"""
|
|
52
|
+
return round(rows / self.rows_per_unit)
|
|
53
|
+
|
|
54
|
+
class MetricSwatch(Swatch):
|
|
55
|
+
def __init__(self, stitches_per_10_cm : int, rows_per_10_cm : Optional[int] = None):
|
|
56
|
+
"""Initiate a metric swatch from row and stitch counts for a 10 cm swatch.
|
|
57
|
+
"""
|
|
58
|
+
super().__init__(
|
|
59
|
+
10,
|
|
60
|
+
"cm",
|
|
61
|
+
stitches_per_10_cm,
|
|
62
|
+
rows_per_10_cm
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
def cm_to_stitches(self, cm : float, divisible_by : int = 1) -> int:
|
|
66
|
+
"""Convert measurements in cm to stitches
|
|
67
|
+
|
|
68
|
+
:param cm: The number of cm to convert
|
|
69
|
+
:param divisible_by: A number that the return value must be divisible by.
|
|
70
|
+
:return: A number of stitches
|
|
71
|
+
"""
|
|
72
|
+
return self.units_to_stitches(cm, divisible_by)
|
|
73
|
+
|
|
74
|
+
def cm_to_rows(self, cm : float, divisible_by : int = 1) -> int:
|
|
75
|
+
"""Convert measurements in cm to rows
|
|
76
|
+
|
|
77
|
+
:param cm: The number of cm to convert
|
|
78
|
+
:param divisible_by: A number that the return value must be divisible by.
|
|
79
|
+
:return: A number of rows
|
|
80
|
+
"""
|
|
81
|
+
return self.units_to_rows(cm, divisible_by)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
if __name__=="__main__":
|
|
85
|
+
print(MetricSwatch(29, 31).cm_to_stitches(10, 4))
|
|
86
|
+
print(MetricSwatch(29, 31).cm_to_rows(10, 4))
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
from yarnover.exceptions import PatternException
|
|
3
|
+
|
|
4
|
+
def not_negative(i : int):
|
|
5
|
+
if i < 0:
|
|
6
|
+
raise PatternException(f"Expecting a non-negative number, got {i}")
|
|
7
|
+
return i
|
|
8
|
+
|
|
9
|
+
def positive(i : int):
|
|
10
|
+
if i <= 0:
|
|
11
|
+
raise PatternException(f"Expecting a positive number, got {i}")
|
|
12
|
+
return i
|
|
13
|
+
|
|
14
|
+
def half(i):
|
|
15
|
+
result, remainder = divmod(i, 2)
|
|
16
|
+
if remainder:
|
|
17
|
+
raise PatternException(f"Expecting an even number, got {i}")
|
|
18
|
+
else:
|
|
19
|
+
return result
|
|
20
|
+
|
|
21
|
+
def decrease_pattern(from_stitches : int,
|
|
22
|
+
to_stitches : int,
|
|
23
|
+
over_rows,
|
|
24
|
+
odd_row_placement : str = "even"):
|
|
25
|
+
decreases = from_stitches - to_stitches
|
|
26
|
+
if decreases < 0:
|
|
27
|
+
raise PatternException()
|
|
28
|
+
decrease_pairs = half(decreases)
|
|
29
|
+
return split_number(over_rows, decrease_pairs, odd_row_placement)
|
|
30
|
+
|
|
31
|
+
def decrease(from_stitches : int,
|
|
32
|
+
to_stitches : int,
|
|
33
|
+
row_count : int,
|
|
34
|
+
f : Callable[[float], float]):
|
|
35
|
+
decreases = from_stitches - to_stitches
|
|
36
|
+
for i in range(row_count):
|
|
37
|
+
stitches = int(f(float(i) / decreases))
|
|
38
|
+
print(stitches)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def centered_split(total : int,
|
|
44
|
+
group_count : int,
|
|
45
|
+
group_size : int = 1,
|
|
46
|
+
):
|
|
47
|
+
total_to_divide = total - group_count*group_size
|
|
48
|
+
space_count = group_count + 1
|
|
49
|
+
space_size, remainder = divmod(total_to_divide, space_count)
|
|
50
|
+
a = remainder // 2
|
|
51
|
+
b = remainder - a
|
|
52
|
+
return a, space_count-1, space_size, space_size+b
|
|
53
|
+
|
|
54
|
+
"""
|
|
55
|
+
split n into a groups of (m+1) then b groups of m
|
|
56
|
+
a+b = g
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
def uncentered_split(total : int,
|
|
60
|
+
group_count : int,
|
|
61
|
+
group_size : int = 1,
|
|
62
|
+
):
|
|
63
|
+
total_to_divide = total - group_count*group_size
|
|
64
|
+
space_count = group_count
|
|
65
|
+
space_size, remainder = divmod(total_to_divide, space_count)
|
|
66
|
+
large_spaces = remainder
|
|
67
|
+
small_spaces = space_count - remainder
|
|
68
|
+
return large_spaces, space_size+1, small_spaces, space_size
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def split_number(number : int,
|
|
72
|
+
group_count : int,
|
|
73
|
+
remainder_placement : str = "even"):
|
|
74
|
+
if number < 0:
|
|
75
|
+
raise PatternException()
|
|
76
|
+
group_size, remainder = divmod(number, group_count)
|
|
77
|
+
groups = [group_size] * group_count
|
|
78
|
+
if remainder_placement == "top":
|
|
79
|
+
groups[0] += remainder
|
|
80
|
+
elif remainder_placement == "bottom":
|
|
81
|
+
groups[-1] += remainder
|
|
82
|
+
elif remainder_placement == "even":
|
|
83
|
+
for block_number in range(remainder):
|
|
84
|
+
groups[block_number] += 1
|
|
85
|
+
return groups
|
|
86
|
+
|
|
87
|
+
if __name__=="__main__":
|
|
88
|
+
w1 = 90
|
|
89
|
+
w2 = 46
|
|
90
|
+
rows = 116
|
|
91
|
+
decrease_pairs = half(w1-w2)
|
|
92
|
+
head, repeats, width, tail = centered_split(rows, decrease_pairs)
|
|
93
|
+
# print(f"{head} rows, then {repeats} times [{width} rows + decrease] + {tail}")
|
|
94
|
+
# print(decrease_pattern(w1, w2, rows))
|
|
95
|
+
# print(uncentered_split(rows, decrease_pairs))
|
|
96
|
+
decrease(100, 50, lambda x: 1.0-x)
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: yarnover
|
|
3
|
+
Version: 0.1
|
|
4
|
+
Summary: yarnover
|
|
5
|
+
Description-Content-Type: text/markdown
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Pattern - represents a knitting pattern
|
|
9
|
+
|
|
10
|
+
`Pattern.cast_on(n)`
|
|
11
|
+
|
|
12
|
+
alias `co`
|
|
13
|
+
|
|
14
|
+
Cast on `n` stitches
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
`Work.work_stitch(
|
|
18
|
+
- name: The name or code of the stitch
|
|
19
|
+
- parent_count = 1: The number of stitches from the left needle that will be worked
|
|
20
|
+
- slip_parent_stitches = True: Indicates whether to slip the parent stitches
|
|
21
|
+
- add_stitch_to_right_needle = True: Indicates whether to add the new stitch to the right needle
|
|
22
|
+
- tags = []:
|
|
23
|
+
|
|
24
|
+
`Work.slip_stitch()
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
readme.md
|
|
3
|
+
src/yarnover/__init__.py
|
|
4
|
+
src/yarnover/exceptions.py
|
|
5
|
+
src/yarnover/needle.py
|
|
6
|
+
src/yarnover/pattern.py
|
|
7
|
+
src/yarnover/swatch.py
|
|
8
|
+
src/yarnover/util.py
|
|
9
|
+
src/yarnover.egg-info/PKG-INFO
|
|
10
|
+
src/yarnover.egg-info/SOURCES.txt
|
|
11
|
+
src/yarnover.egg-info/dependency_links.txt
|
|
12
|
+
src/yarnover.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
yarnover
|