knit-graphs 0.0.10__py3-none-any.whl → 0.0.12__py3-none-any.whl
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.
- docs/source/conf.py +71 -71
- docs/source/index.rst +0 -4
- knit_graphs/Course.py +64 -41
- knit_graphs/Knit_Graph.py +70 -162
- knit_graphs/Knit_Graph_Visualizer.py +386 -185
- knit_graphs/Loop.py +124 -117
- knit_graphs/Pull_Direction.py +5 -2
- knit_graphs/Yarn.py +257 -219
- knit_graphs/artin_wale_braids/Crossing_Direction.py +2 -0
- knit_graphs/artin_wale_braids/Loop_Braid_Graph.py +47 -56
- knit_graphs/artin_wale_braids/Wale.py +67 -79
- knit_graphs/artin_wale_braids/Wale_Braid.py +8 -2
- knit_graphs/artin_wale_braids/Wale_Braid_Word.py +45 -31
- knit_graphs/artin_wale_braids/Wale_Group.py +53 -43
- knit_graphs/basic_knit_graph_generators.py +96 -140
- knit_graphs/directed_loop_graph.py +454 -0
- knit_graphs/knit_graph_builder.py +187 -0
- knit_graphs/knit_graph_errors/__init__.py +0 -0
- knit_graphs/knit_graph_errors/knit_graph_error.py +30 -0
- knit_graphs/py.typed +0 -0
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/METADATA +3 -2
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/RECORD +24 -19
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/LICENSE +0 -0
- {knit_graphs-0.0.10.dist-info → knit_graphs-0.0.12.dist-info}/WHEEL +0 -0
knit_graphs/Yarn.py
CHANGED
|
@@ -3,18 +3,22 @@
|
|
|
3
3
|
This module contains the Yarn class and Yarn_Properties dataclass which together represent the physical yarn used in knitting patterns.
|
|
4
4
|
The Yarn class manages the sequence of loops along a yarn and their floating relationships.
|
|
5
5
|
"""
|
|
6
|
+
|
|
6
7
|
from __future__ import annotations
|
|
7
8
|
|
|
9
|
+
from collections.abc import Iterator
|
|
8
10
|
from dataclasses import dataclass
|
|
9
|
-
from typing import TYPE_CHECKING,
|
|
10
|
-
|
|
11
|
-
from networkx import DiGraph, dfs_edges, dfs_preorder_nodes
|
|
11
|
+
from typing import TYPE_CHECKING, Self, TypeVar, cast, overload
|
|
12
12
|
|
|
13
|
+
from knit_graphs.directed_loop_graph import Directed_Loop_Graph, Float_Edge
|
|
14
|
+
from knit_graphs.knit_graph_errors.knit_graph_error import Use_Cut_Yarn_ValueError
|
|
13
15
|
from knit_graphs.Loop import Loop
|
|
14
16
|
|
|
15
17
|
if TYPE_CHECKING:
|
|
16
18
|
from knit_graphs.Knit_Graph import Knit_Graph
|
|
17
19
|
|
|
20
|
+
LoopT = TypeVar("LoopT", bound=Loop)
|
|
21
|
+
|
|
18
22
|
|
|
19
23
|
@dataclass(frozen=True)
|
|
20
24
|
class Yarn_Properties:
|
|
@@ -22,9 +26,8 @@ class Yarn_Properties:
|
|
|
22
26
|
|
|
23
27
|
This frozen dataclass contains all the physical and visual properties that characterize a yarn, including its structure, weight, and appearance.
|
|
24
28
|
"""
|
|
29
|
+
|
|
25
30
|
name: str = "yarn" # name (str): The name or identifier for this yarn type.
|
|
26
|
-
plies: int = 2 # plies (int): The number of individual strands twisted together to form the yarn.
|
|
27
|
-
weight: float = 28 # weight (float): The weight category or thickness of the yarn.
|
|
28
31
|
color: str = "green" # color (str): The color of the yarn for visualization purposes.
|
|
29
32
|
|
|
30
33
|
def __str__(self) -> str:
|
|
@@ -33,7 +36,7 @@ class Yarn_Properties:
|
|
|
33
36
|
Returns:
|
|
34
37
|
str: String representation in format "name(plies-weight,color)".
|
|
35
38
|
"""
|
|
36
|
-
return f"{self.name}({self.
|
|
39
|
+
return f"{self.name}({self.color})"
|
|
37
40
|
|
|
38
41
|
def __repr__(self) -> str:
|
|
39
42
|
"""Get the name of the yarn for debugging purposes.
|
|
@@ -52,7 +55,7 @@ class Yarn_Properties:
|
|
|
52
55
|
"""
|
|
53
56
|
return Yarn_Properties()
|
|
54
57
|
|
|
55
|
-
def __eq__(self, other:
|
|
58
|
+
def __eq__(self, other: object) -> bool:
|
|
56
59
|
"""Check equality with another Yarn_Properties instance.
|
|
57
60
|
|
|
58
61
|
Args:
|
|
@@ -61,7 +64,7 @@ class Yarn_Properties:
|
|
|
61
64
|
Returns:
|
|
62
65
|
bool: True if all properties (name, plies, weight, color) are equal, False otherwise.
|
|
63
66
|
"""
|
|
64
|
-
return
|
|
67
|
+
return isinstance(other, Yarn_Properties) and self.name == other.name and self.color == other.color
|
|
65
68
|
|
|
66
69
|
def __hash__(self) -> int:
|
|
67
70
|
"""Get hash value for use in sets and dictionaries.
|
|
@@ -69,48 +72,57 @@ class Yarn_Properties:
|
|
|
69
72
|
Returns:
|
|
70
73
|
int: Hash value based on all yarn properties.
|
|
71
74
|
"""
|
|
72
|
-
return hash((self.name, self.
|
|
75
|
+
return hash((self.name, self.color))
|
|
73
76
|
|
|
74
77
|
|
|
75
|
-
class Yarn:
|
|
78
|
+
class Yarn(Directed_Loop_Graph[LoopT, Float_Edge[LoopT]]):
|
|
76
79
|
"""A class to represent a yarn structure as a sequence of connected loops.
|
|
77
80
|
|
|
78
81
|
The Yarn class manages a directed graph of loops representing the physical yarn path through a knitted structure.
|
|
79
82
|
It maintains the sequential order of loops and their floating relationships, providing methods for navigation and manipulation of the yarn structure.
|
|
80
83
|
|
|
81
84
|
Attributes:
|
|
82
|
-
loop_graph (DiGraph): The directed graph loops connected by yarn-wise float edges.
|
|
83
85
|
properties (Yarn_Properties): The physical and visual properties of this yarn.
|
|
84
86
|
"""
|
|
85
|
-
FRONT_LOOPS: str = "Front_Loops"
|
|
86
|
-
_BACK_LOOPS: str = "Back_Loops"
|
|
87
87
|
|
|
88
|
-
def __init__(
|
|
88
|
+
def __init__(
|
|
89
|
+
self,
|
|
90
|
+
yarn_properties: Yarn_Properties | None = None,
|
|
91
|
+
knit_graph: Knit_Graph[LoopT] | None = None,
|
|
92
|
+
instance: int = 0,
|
|
93
|
+
loop_class: type[LoopT] | None = None,
|
|
94
|
+
):
|
|
89
95
|
"""Initialize a yarn with the specified properties and optional knit graph association.
|
|
90
96
|
|
|
91
97
|
Args:
|
|
92
98
|
yarn_properties (None | Yarn_Properties, optional): The properties defining this yarn. If None, uses default properties. Defaults to standard properties.
|
|
93
99
|
knit_graph (None | Knit_Graph, optional): The knit graph that will own this yarn. Can be None for standalone yarns. Defaults to None.
|
|
100
|
+
instance (int, optional): The instance of this yarn. As new yarns are formed by cuts, the instance will increase. Defaults to 0 (first instance of this yarn).
|
|
101
|
+
loop_class (type[LoopT], optional): The type of loop to create by default in this class. Defaults to Loop class.
|
|
94
102
|
"""
|
|
95
|
-
|
|
103
|
+
super().__init__()
|
|
104
|
+
self._instance: int = instance
|
|
105
|
+
self._is_cut: bool = False
|
|
106
|
+
self._loop_class: type[LoopT] = loop_class if loop_class is not None else cast(type[LoopT], Loop)
|
|
96
107
|
if yarn_properties is None:
|
|
97
108
|
yarn_properties = Yarn_Properties.default_yarn()
|
|
98
109
|
self.properties: Yarn_Properties = yarn_properties
|
|
99
|
-
self._first_loop:
|
|
100
|
-
self._last_loop:
|
|
101
|
-
self._knit_graph:
|
|
110
|
+
self._first_loop: LoopT | None = None
|
|
111
|
+
self._last_loop: LoopT | None = None
|
|
112
|
+
self._knit_graph: Knit_Graph[LoopT] = knit_graph if knit_graph is not None else Knit_Graph[LoopT]()
|
|
113
|
+
if self not in self.knit_graph.yarns:
|
|
114
|
+
self.knit_graph.add_yarn(self)
|
|
102
115
|
|
|
103
116
|
@property
|
|
104
|
-
def knit_graph(self) ->
|
|
105
|
-
"""
|
|
106
|
-
|
|
117
|
+
def knit_graph(self) -> Knit_Graph[LoopT]:
|
|
118
|
+
"""
|
|
107
119
|
Returns:
|
|
108
|
-
|
|
120
|
+
Knit_Graph | Knit_Graph: The knit graph that owns this yarn, or None if not associated with a graph.
|
|
109
121
|
"""
|
|
110
122
|
return self._knit_graph
|
|
111
123
|
|
|
112
124
|
@knit_graph.setter
|
|
113
|
-
def knit_graph(self, knit_graph: Knit_Graph) -> None:
|
|
125
|
+
def knit_graph(self, knit_graph: Knit_Graph[LoopT]) -> None:
|
|
114
126
|
"""Set the knit graph that owns this yarn.
|
|
115
127
|
|
|
116
128
|
Args:
|
|
@@ -118,88 +130,8 @@ class Yarn:
|
|
|
118
130
|
"""
|
|
119
131
|
self._knit_graph = knit_graph
|
|
120
132
|
|
|
121
|
-
def has_float(self, u: Loop, v: Loop) -> bool:
|
|
122
|
-
"""Check if there is a float edge between two loops on this yarn.
|
|
123
|
-
|
|
124
|
-
Args:
|
|
125
|
-
u (Loop): The first loop to check for float connection.
|
|
126
|
-
v (Loop): The second loop to check for float connection.
|
|
127
|
-
|
|
128
|
-
Returns:
|
|
129
|
-
bool: True if there is a float edge between the loops, False otherwise.
|
|
130
|
-
"""
|
|
131
|
-
return bool(self.loop_graph.has_edge(u, v))
|
|
132
|
-
|
|
133
|
-
def add_loop_in_front_of_float(self, front_loop: Loop, u: Loop, v: Loop) -> None:
|
|
134
|
-
"""Record that a loop falls in front of the float between two other loops.
|
|
135
|
-
|
|
136
|
-
Args:
|
|
137
|
-
front_loop (Loop): The loop that is positioned in front of the float.
|
|
138
|
-
u (Loop): The first loop in the float pair.
|
|
139
|
-
v (Loop): The second loop in the float pair.
|
|
140
|
-
"""
|
|
141
|
-
if not self.has_float(u, v):
|
|
142
|
-
if self.has_float(v, u):
|
|
143
|
-
self.add_loop_in_front_of_float(front_loop, v, u)
|
|
144
|
-
else:
|
|
145
|
-
return
|
|
146
|
-
self.loop_graph.edges[u, v][self.FRONT_LOOPS].add(front_loop)
|
|
147
|
-
front_loop.add_loop_in_front_of_float(u, v)
|
|
148
|
-
|
|
149
|
-
def add_loop_behind_float(self, back_loop: Loop, u: Loop, v: Loop) -> None:
|
|
150
|
-
"""Record that a loop falls behind the float between two other loops.
|
|
151
|
-
|
|
152
|
-
Args:
|
|
153
|
-
back_loop (Loop): The loop that is positioned behind the float.
|
|
154
|
-
u (Loop): The first loop in the float pair.
|
|
155
|
-
v (Loop): The second loop in the float pair.
|
|
156
|
-
"""
|
|
157
|
-
if not self.has_float(u, v):
|
|
158
|
-
if self.has_float(v, u):
|
|
159
|
-
self.add_loop_behind_float(back_loop, v, u)
|
|
160
|
-
else:
|
|
161
|
-
return
|
|
162
|
-
self.loop_graph.edges[u, v][self._BACK_LOOPS].add(back_loop)
|
|
163
|
-
back_loop.add_loop_behind_float(u, v)
|
|
164
|
-
|
|
165
|
-
def get_loops_in_front_of_float(self, u: Loop, v: Loop) -> set[Loop]:
|
|
166
|
-
"""Get all loops positioned in front of the float between two loops.
|
|
167
|
-
|
|
168
|
-
Args:
|
|
169
|
-
u (Loop): The first loop in the float pair.
|
|
170
|
-
v (Loop): The second loop in the float pair.
|
|
171
|
-
|
|
172
|
-
Returns:
|
|
173
|
-
set[Loop]: Set of loops positioned in front of the float between u and v, or empty set if no float exists.
|
|
174
|
-
"""
|
|
175
|
-
if not self.has_float(u, v):
|
|
176
|
-
if self.has_float(v, u):
|
|
177
|
-
return self.get_loops_in_front_of_float(v, u)
|
|
178
|
-
else:
|
|
179
|
-
return set()
|
|
180
|
-
else:
|
|
181
|
-
return cast(set[Loop], self.loop_graph.edges[u, v][self.FRONT_LOOPS])
|
|
182
|
-
|
|
183
|
-
def get_loops_behind_float(self, u: Loop, v: Loop) -> set[Loop]:
|
|
184
|
-
"""Get all loops positioned behind the float between two loops.
|
|
185
|
-
|
|
186
|
-
Args:
|
|
187
|
-
u (Loop): The first loop in the float pair.
|
|
188
|
-
v (Loop): The second loop in the float pair.
|
|
189
|
-
|
|
190
|
-
Returns:
|
|
191
|
-
set[Loop]: Set of loops positioned behind the float between u and v, or empty set if no float exists.
|
|
192
|
-
"""
|
|
193
|
-
if not self.has_float(u, v):
|
|
194
|
-
if self.has_float(v, u):
|
|
195
|
-
return self.get_loops_behind_float(v, u)
|
|
196
|
-
else:
|
|
197
|
-
return set()
|
|
198
|
-
else:
|
|
199
|
-
return cast(set[Loop], self.loop_graph.edges[u, v][self._BACK_LOOPS])
|
|
200
|
-
|
|
201
133
|
@property
|
|
202
|
-
def last_loop(self) ->
|
|
134
|
+
def last_loop(self) -> LoopT | None:
|
|
203
135
|
"""Get the most recently added loop at the end of the yarn.
|
|
204
136
|
|
|
205
137
|
Returns:
|
|
@@ -208,7 +140,7 @@ class Yarn:
|
|
|
208
140
|
return self._last_loop
|
|
209
141
|
|
|
210
142
|
@property
|
|
211
|
-
def first_loop(self) ->
|
|
143
|
+
def first_loop(self) -> LoopT | None:
|
|
212
144
|
"""Get the first loop at the beginning of the yarn.
|
|
213
145
|
|
|
214
146
|
Returns:
|
|
@@ -225,6 +157,14 @@ class Yarn:
|
|
|
225
157
|
"""
|
|
226
158
|
return self.last_loop is not None
|
|
227
159
|
|
|
160
|
+
@property
|
|
161
|
+
def is_cut(self) -> bool:
|
|
162
|
+
"""
|
|
163
|
+
Returns:
|
|
164
|
+
bool: True if yarn has been cut and will no longer form loops, False otherwise.
|
|
165
|
+
"""
|
|
166
|
+
return self._is_cut
|
|
167
|
+
|
|
228
168
|
@property
|
|
229
169
|
def yarn_id(self) -> str:
|
|
230
170
|
"""Get the string identifier for this yarn.
|
|
@@ -234,100 +174,194 @@ class Yarn:
|
|
|
234
174
|
"""
|
|
235
175
|
return str(self.properties)
|
|
236
176
|
|
|
237
|
-
|
|
238
|
-
|
|
177
|
+
@property
|
|
178
|
+
def float_iter(self) -> Iterator[tuple[LoopT, LoopT]]:
|
|
179
|
+
"""
|
|
180
|
+
Returns:
|
|
181
|
+
Iterator[tuple[Loop, Loop]]: An iterator over tuples of connected loops representing the yarn path.
|
|
182
|
+
"""
|
|
183
|
+
if self.first_loop is None:
|
|
184
|
+
return iter([])
|
|
185
|
+
return self.dfs_edges(self.first_loop)
|
|
186
|
+
|
|
187
|
+
def loops_in_front_of_floats(self) -> Iterator[tuple[LoopT, LoopT, set[LoopT]]]:
|
|
188
|
+
"""Get all float segments with loops positioned in front of them.
|
|
239
189
|
|
|
240
190
|
Returns:
|
|
241
|
-
|
|
191
|
+
list[tuple[Loop, Loop, set[Loop]]]: List of tuples containing the two loops defining each float and the set of loops positioned in front of that float.
|
|
192
|
+
Only includes floats that have loops in front of them.
|
|
242
193
|
"""
|
|
243
|
-
return self.
|
|
194
|
+
return ((u, v, d.front_loops) for u, v, d in self.edge_iter if len(d.front_loops) > 0)
|
|
244
195
|
|
|
245
|
-
def
|
|
246
|
-
"""Get
|
|
196
|
+
def loops_behind_floats(self) -> Iterator[tuple[LoopT, LoopT, set[LoopT]]]:
|
|
197
|
+
"""Get all float segments with loops positioned behind them.
|
|
247
198
|
|
|
248
199
|
Returns:
|
|
249
|
-
|
|
200
|
+
list[tuple[Loop, Loop, set[Loop]]]: List of tuples containing the two loops defining each float and the set of loops positioned behind that float.
|
|
201
|
+
Only includes floats that have loops behind them.
|
|
250
202
|
"""
|
|
251
|
-
return
|
|
203
|
+
return ((u, v, d.back_loops) for u, v, d in self.edge_iter if len(d.back_loops) > 0)
|
|
252
204
|
|
|
253
|
-
def
|
|
254
|
-
"""
|
|
205
|
+
def next_loop(self, loop: LoopT) -> LoopT | None:
|
|
206
|
+
"""
|
|
207
|
+
Args:
|
|
208
|
+
loop (LoopT): The loop to find the next loop from.
|
|
255
209
|
|
|
256
210
|
Returns:
|
|
257
|
-
|
|
211
|
+
LoopT | None: The next loop on yarn after the specified loop, or None if it's the last loop.
|
|
212
|
+
|
|
213
|
+
Raises:
|
|
214
|
+
KeyError: If the specified loop is not on this yarn.
|
|
215
|
+
"""
|
|
216
|
+
if loop not in self:
|
|
217
|
+
raise KeyError(f"Loop {loop} is not on Yarn")
|
|
218
|
+
successors = self.successors(loop)
|
|
219
|
+
return successors.pop() if len(successors) > 0 else None
|
|
220
|
+
|
|
221
|
+
def prior_loop(self, loop: LoopT) -> LoopT | None:
|
|
222
|
+
"""
|
|
223
|
+
Args:
|
|
224
|
+
loop (Loop): The loop to find the prior loop from.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
Loop | None: The prior loop on yarn before the specified loop, or None if it's the first loop.
|
|
228
|
+
|
|
229
|
+
Raises:
|
|
230
|
+
KeyError: If the specified loop is not on this yarn.
|
|
258
231
|
"""
|
|
259
|
-
|
|
232
|
+
if loop not in self:
|
|
233
|
+
raise KeyError(f"Loop {loop} is not on Yarn")
|
|
234
|
+
predecessors = self.predecessors(loop)
|
|
235
|
+
if len(predecessors) > 0:
|
|
236
|
+
return predecessors.pop()
|
|
237
|
+
else:
|
|
238
|
+
return None
|
|
260
239
|
|
|
261
|
-
def
|
|
262
|
-
"""
|
|
240
|
+
def has_float(self, u: LoopT, v: LoopT) -> bool:
|
|
241
|
+
"""Check if there is a float edge between two loops on this yarn.
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
u (Loop): The first loop to check for float connection.
|
|
245
|
+
v (Loop): The second loop to check for float connection.
|
|
263
246
|
|
|
264
247
|
Returns:
|
|
265
|
-
|
|
248
|
+
bool: True if there is a float edge between the loops, False otherwise.
|
|
266
249
|
"""
|
|
267
|
-
return
|
|
250
|
+
return bool(self.has_edge(u, v))
|
|
268
251
|
|
|
269
|
-
def
|
|
252
|
+
def get_loops_in_front_of_float(self, u: LoopT, v: LoopT) -> set[LoopT]:
|
|
253
|
+
"""Get all loops positioned in front of the float between two loops.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
u (Loop): The first loop in the float pair.
|
|
257
|
+
v (Loop): The second loop in the float pair.
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
set[Loop]: Set of loops positioned in front of the float between u and v, or empty set if no float exists.
|
|
261
|
+
"""
|
|
262
|
+
if not self.has_float(u, v):
|
|
263
|
+
if self.has_float(v, u):
|
|
264
|
+
return self.get_loops_in_front_of_float(v, u)
|
|
265
|
+
else:
|
|
266
|
+
return set()
|
|
267
|
+
else:
|
|
268
|
+
return self.get_edge(u, v).front_loops
|
|
269
|
+
|
|
270
|
+
def get_loops_behind_float(self, u: LoopT, v: LoopT) -> set[LoopT]:
|
|
271
|
+
"""Get all loops positioned behind the float between two loops.
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
u (Loop): The first loop in the float pair.
|
|
275
|
+
v (Loop): The second loop in the float pair.
|
|
276
|
+
|
|
277
|
+
Returns:
|
|
278
|
+
set[Loop]: Set of loops positioned behind the float between u and v, or empty set if no float exists.
|
|
279
|
+
"""
|
|
280
|
+
if not self.has_float(u, v):
|
|
281
|
+
if self.has_float(v, u):
|
|
282
|
+
return self.get_loops_behind_float(v, u)
|
|
283
|
+
else:
|
|
284
|
+
return set()
|
|
285
|
+
else:
|
|
286
|
+
return self.get_edge(u, v).back_loops
|
|
287
|
+
|
|
288
|
+
def make_loop_on_end(self) -> LoopT:
|
|
270
289
|
"""Create and add a new loop at the end of the yarn.
|
|
271
290
|
|
|
272
291
|
Returns:
|
|
273
292
|
Loop: The newly created loop that was added to the end of this yarn.
|
|
274
293
|
"""
|
|
275
294
|
loop_id = self._next_loop_id()
|
|
276
|
-
|
|
277
|
-
return self.add_loop_to_end(loop)
|
|
295
|
+
return self.add_loop_to_end(self._new_loop(loop_id))
|
|
278
296
|
|
|
279
297
|
def _next_loop_id(self) -> int:
|
|
280
|
-
"""
|
|
281
|
-
|
|
298
|
+
"""
|
|
282
299
|
Returns:
|
|
283
300
|
int: The ID of the next loop to be added to this yarn based on the knit graph or, if no knit graph is associated with this yarn, based on the last loop on this yarn.
|
|
284
301
|
"""
|
|
285
|
-
if self.knit_graph is
|
|
286
|
-
if self.knit_graph.last_loop is None:
|
|
287
|
-
return 0
|
|
288
|
-
else:
|
|
289
|
-
return self.knit_graph.last_loop.loop_id + 1
|
|
290
|
-
elif self.last_loop is None:
|
|
302
|
+
if self.knit_graph.last_loop is None:
|
|
291
303
|
return 0
|
|
292
304
|
else:
|
|
293
|
-
return self.last_loop.loop_id + 1
|
|
305
|
+
return self.knit_graph.last_loop.loop_id + 1
|
|
306
|
+
|
|
307
|
+
def _new_loop(self, loop_id: int) -> LoopT:
|
|
308
|
+
"""
|
|
309
|
+
Args:
|
|
310
|
+
loop_id (int): The loop id to create a new loop.
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
LoopT: A loop on this yarn with the given id.
|
|
314
|
+
|
|
315
|
+
Raises:
|
|
316
|
+
Use_Cut_Yarn_ValueError: If the yarn has been cut and can no longer form loops.
|
|
317
|
+
"""
|
|
318
|
+
if self.is_cut:
|
|
319
|
+
raise Use_Cut_Yarn_ValueError(self)
|
|
320
|
+
elif self.first_loop is not None:
|
|
321
|
+
return self.first_loop.__class__(loop_id, self, self.knit_graph) # type: ignore[arg-type]
|
|
322
|
+
else:
|
|
323
|
+
return self._loop_class(loop_id, self, self.knit_graph) # type: ignore[arg-type]
|
|
294
324
|
|
|
295
|
-
def remove_loop(self, loop:
|
|
325
|
+
def remove_loop(self, loop: LoopT | int) -> None:
|
|
296
326
|
"""
|
|
297
327
|
Remove the given loop from the yarn.
|
|
298
328
|
Reconnects any neighboring loops to form a new float with the positioned in-front-of or behind the original floats positioned accordingly.
|
|
299
329
|
Resets the first_loop and last_loop properties if the removed loop was the tail of the yarn.
|
|
300
330
|
Args:
|
|
301
|
-
loop (
|
|
331
|
+
loop (LoopT): The loop to remove from the yarn.
|
|
302
332
|
|
|
303
333
|
Raises:
|
|
304
334
|
KeyError: The given loop does not exist in the yarn.
|
|
305
335
|
"""
|
|
306
336
|
if loop not in self:
|
|
307
|
-
raise KeyError(f
|
|
337
|
+
raise KeyError(f"Loop {loop} does not exist on yarn {self}.")
|
|
338
|
+
elif isinstance(loop, int):
|
|
339
|
+
loop = self[loop]
|
|
308
340
|
prior_loop = self.prior_loop(loop)
|
|
309
341
|
next_loop = self.next_loop(loop)
|
|
310
|
-
if
|
|
342
|
+
if prior_loop is not None and next_loop is not None: # Loop is between two floats to be merged.
|
|
311
343
|
front_of_float_loops = self.get_loops_in_front_of_float(prior_loop, loop)
|
|
312
344
|
front_of_float_loops.update(self.get_loops_in_front_of_float(loop, next_loop))
|
|
313
345
|
back_of_float_loops = self.get_loops_behind_float(prior_loop, loop)
|
|
314
346
|
back_of_float_loops.update(self.get_loops_behind_float(loop, next_loop))
|
|
315
|
-
|
|
316
|
-
self.
|
|
347
|
+
super().remove_loop(loop)
|
|
348
|
+
self.add_edge(
|
|
349
|
+
prior_loop,
|
|
350
|
+
next_loop,
|
|
351
|
+
Float_Edge[LoopT](front_loops=front_of_float_loops, back_loops=back_of_float_loops),
|
|
352
|
+
)
|
|
317
353
|
for front_loop in front_of_float_loops:
|
|
318
354
|
front_loop.add_loop_in_front_of_float(prior_loop, next_loop)
|
|
319
355
|
for back_loop in back_of_float_loops:
|
|
320
356
|
back_loop.add_loop_behind_float(prior_loop, next_loop)
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
def add_loop_to_end(self, loop: Loop) -> Loop:
|
|
357
|
+
else:
|
|
358
|
+
super().remove_loop(loop)
|
|
359
|
+
if next_loop is None: # This was the last loop, make the prior loop the last loop.
|
|
360
|
+
self._last_loop = prior_loop
|
|
361
|
+
if prior_loop is None: # This was the first loop, make the next loop the first loop.
|
|
362
|
+
self._first_loop = next_loop
|
|
363
|
+
|
|
364
|
+
def add_loop_to_end(self, loop: LoopT) -> LoopT:
|
|
331
365
|
"""Add an existing loop to the end of this yarn and associated knit graph.
|
|
332
366
|
|
|
333
367
|
Args:
|
|
@@ -341,124 +375,128 @@ class Yarn:
|
|
|
341
375
|
self.knit_graph.add_loop(loop)
|
|
342
376
|
return loop
|
|
343
377
|
|
|
344
|
-
def insert_loop(self, loop:
|
|
378
|
+
def insert_loop(self, loop: LoopT, prior_loop: LoopT | None = None) -> None:
|
|
345
379
|
"""Insert a loop into the yarn sequence after the specified prior loop.
|
|
346
380
|
|
|
347
381
|
Args:
|
|
348
382
|
loop (Loop): The loop to be added to this yarn.
|
|
349
383
|
prior_loop (Loop | None): The loop that should come before this loop on the yarn. If None, defaults to the last loop (adding to end of yarn).
|
|
350
384
|
"""
|
|
351
|
-
|
|
352
|
-
if
|
|
385
|
+
super().add_loop(loop)
|
|
386
|
+
if self.last_loop is None:
|
|
353
387
|
self._last_loop = loop
|
|
354
388
|
self._first_loop = loop
|
|
355
389
|
return
|
|
356
|
-
|
|
390
|
+
if prior_loop is None:
|
|
357
391
|
prior_loop = self.last_loop
|
|
358
|
-
|
|
392
|
+
super().add_edge(prior_loop, loop, Float_Edge[LoopT]())
|
|
359
393
|
if prior_loop == self.last_loop:
|
|
360
394
|
self._last_loop = loop
|
|
361
395
|
|
|
362
|
-
def
|
|
363
|
-
"""
|
|
396
|
+
def add_loop_in_front_of_float(self, front_loop: LoopT, start_of_float: LoopT) -> None:
|
|
397
|
+
"""Record that a loop falls in front of the float between two other loops.
|
|
364
398
|
|
|
365
399
|
Args:
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
Returns:
|
|
369
|
-
Loop | None: The next loop on yarn after the specified loop, or None if it's the last loop.
|
|
370
|
-
|
|
371
|
-
Raises:
|
|
372
|
-
KeyError: If the specified loop is not on this yarn.
|
|
400
|
+
front_loop (Loop): The loop that is positioned in front of the float.
|
|
401
|
+
start_of_float (Loop): The first loop in the float pair.
|
|
373
402
|
"""
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
return cast(Loop, successors[0])
|
|
379
|
-
else:
|
|
380
|
-
return None
|
|
381
|
-
|
|
382
|
-
def prior_loop(self, loop: Loop) -> Loop | None:
|
|
383
|
-
"""Get the loop that precedes the specified loop on this yarn.
|
|
403
|
+
end_of_float = self.next_loop(start_of_float)
|
|
404
|
+
if end_of_float is not None:
|
|
405
|
+
self.get_edge(start_of_float, end_of_float).add_loop_in_front_of_float(front_loop)
|
|
406
|
+
front_loop.add_loop_in_front_of_float(start_of_float, end_of_float)
|
|
384
407
|
|
|
408
|
+
def remove_loop_relative_to_floats(self, loop: LoopT) -> None:
|
|
409
|
+
"""
|
|
410
|
+
Removes the given loop from positions relative to floats along this yarn.
|
|
385
411
|
Args:
|
|
386
|
-
loop (
|
|
387
|
-
|
|
388
|
-
Returns:
|
|
389
|
-
Loop | None: The prior loop on yarn before the specified loop, or None if it's the first loop.
|
|
390
|
-
|
|
391
|
-
Raises:
|
|
392
|
-
KeyError: If the specified loop is not on this yarn.
|
|
412
|
+
loop (LoopT): The loop to remove from positions relative to floats on this yarn.
|
|
393
413
|
"""
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
predecessors = [*self.loop_graph.predecessors(loop)]
|
|
397
|
-
if len(predecessors) > 0:
|
|
398
|
-
return cast(Loop, predecessors[0])
|
|
399
|
-
else:
|
|
400
|
-
return None
|
|
414
|
+
for _u, _v, float_data in self.edge_iter:
|
|
415
|
+
float_data.remove_loop_relative_to_floats(loop)
|
|
401
416
|
|
|
402
|
-
def
|
|
403
|
-
"""
|
|
417
|
+
def add_loop_behind_float(self, back_loop: LoopT, start_of_float: LoopT) -> None:
|
|
418
|
+
"""Record that a loop falls in front of the float between two other loops.
|
|
404
419
|
|
|
405
420
|
Args:
|
|
406
|
-
|
|
421
|
+
back_loop (Loop): The loop that is positioned behind the float.
|
|
422
|
+
start_of_float (Loop): The first loop in the float pair.
|
|
423
|
+
"""
|
|
424
|
+
end_of_float = self.next_loop(start_of_float)
|
|
425
|
+
if end_of_float is not None:
|
|
426
|
+
self.get_edge(start_of_float, end_of_float).add_loop_behind_float(back_loop)
|
|
427
|
+
back_loop.add_loop_behind_float(start_of_float, end_of_float)
|
|
428
|
+
|
|
429
|
+
def cut_yarn(self) -> Self:
|
|
430
|
+
"""Cut yarn to make it no longer active and create a new yarn instance of the same type.
|
|
407
431
|
|
|
408
432
|
Returns:
|
|
409
|
-
|
|
433
|
+
Yarn[LoopT]: New yarn of the same type after cutting this yarn.
|
|
410
434
|
"""
|
|
411
|
-
|
|
435
|
+
self._is_cut = True
|
|
436
|
+
return self.__class__(self.properties, self.knit_graph, self._instance + 1, self._loop_class)
|
|
412
437
|
|
|
413
|
-
def
|
|
414
|
-
"""
|
|
438
|
+
def __str__(self) -> str:
|
|
439
|
+
"""Get the string representation of this yarn.
|
|
415
440
|
|
|
416
441
|
Returns:
|
|
417
|
-
|
|
442
|
+
str: The yarn identifier string.
|
|
418
443
|
"""
|
|
419
|
-
return
|
|
444
|
+
return self.yarn_id
|
|
420
445
|
|
|
421
|
-
def
|
|
422
|
-
"""
|
|
446
|
+
def __repr__(self) -> str:
|
|
447
|
+
"""Get the representation string of this yarn for debugging.
|
|
423
448
|
|
|
424
449
|
Returns:
|
|
425
|
-
|
|
450
|
+
str: The representation of the yarn properties.
|
|
426
451
|
"""
|
|
427
|
-
return
|
|
452
|
+
return repr(self.properties)
|
|
428
453
|
|
|
429
|
-
def
|
|
430
|
-
"""Get
|
|
454
|
+
def __hash__(self) -> int:
|
|
455
|
+
"""Get the hash value of this yarn for use in sets and dictionaries.
|
|
431
456
|
|
|
432
457
|
Returns:
|
|
433
|
-
|
|
434
|
-
Only includes floats that have loops in front of them.
|
|
458
|
+
int: Hash value based on the yarn properties.
|
|
435
459
|
"""
|
|
436
|
-
return
|
|
437
|
-
if len(self.get_loops_in_front_of_float(u, v)) > 0]
|
|
460
|
+
return hash((self._instance, self.properties))
|
|
438
461
|
|
|
439
|
-
def
|
|
440
|
-
"""
|
|
462
|
+
def __iter__(self) -> Iterator[LoopT]:
|
|
463
|
+
"""Iterate over loops on this yarn in sequence from first to last.
|
|
441
464
|
|
|
442
465
|
Returns:
|
|
443
|
-
|
|
444
|
-
Only includes floats that have loops behind them.
|
|
466
|
+
Iterator[Loop]: An iterator over the loops on this yarn in their natural sequence order.
|
|
445
467
|
"""
|
|
446
|
-
|
|
447
|
-
|
|
468
|
+
if self.first_loop is None:
|
|
469
|
+
return iter([])
|
|
470
|
+
return self.dfs_preorder_loops(self.first_loop)
|
|
471
|
+
|
|
472
|
+
@overload
|
|
473
|
+
def __getitem__(self, item: int) -> LoopT: ...
|
|
474
|
+
|
|
475
|
+
@overload
|
|
476
|
+
def __getitem__(self, item: tuple[LoopT | int, LoopT | int]) -> Float_Edge[LoopT]: ...
|
|
477
|
+
|
|
478
|
+
@overload
|
|
479
|
+
def __getitem__(self, item: slice) -> list[LoopT]: ...
|
|
448
480
|
|
|
449
|
-
def __getitem__(
|
|
481
|
+
def __getitem__(
|
|
482
|
+
self, item: int | tuple[LoopT | int, LoopT | int] | slice
|
|
483
|
+
) -> LoopT | Float_Edge[LoopT] | list[LoopT]:
|
|
450
484
|
"""Get a loop by its ID from this yarn.
|
|
451
485
|
|
|
452
486
|
Args:
|
|
453
|
-
item (int
|
|
487
|
+
item (int | tuple[LoopT | int, LoopT | int] | slice):
|
|
488
|
+
The loop ,loop ID, or float between two loops to retrieve from this yarn.
|
|
489
|
+
If given a slice, it will retrieve the elements between the specified indices in the standard ordering of loops along the yarn.
|
|
454
490
|
|
|
455
491
|
Returns:
|
|
456
|
-
|
|
492
|
+
LoopT: The loop on the yarn with the matching ID.
|
|
493
|
+
Float_Edge[LoopT]: The float data for the given pair of loops forming a float.
|
|
494
|
+
list[LoopT]: The loops in the slice of the yarn based on their ordering along the yarn.
|
|
457
495
|
|
|
458
496
|
Raises:
|
|
459
|
-
KeyError: If the
|
|
497
|
+
KeyError: If the item is not found on this yarn.
|
|
460
498
|
"""
|
|
461
|
-
if item
|
|
462
|
-
|
|
499
|
+
if isinstance(item, slice):
|
|
500
|
+
return list(self)[item]
|
|
463
501
|
else:
|
|
464
|
-
return
|
|
502
|
+
return super().__getitem__(item)
|