knit-graphs 0.0.8__tar.gz → 0.0.10__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.
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/PKG-INFO +4 -4
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/pyproject.toml +4 -67
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Course.py +24 -5
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Knit_Graph.py +98 -46
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Loop.py +134 -19
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Yarn.py +53 -13
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Loop_Braid_Graph.py +11 -1
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Wale.py +68 -53
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Wale_Group.py +80 -65
- knit_graphs-0.0.8/src/knit_graphs/_base_classes.py +0 -173
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/LICENSE +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/README.md +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/Makefile +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/make.bat +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Course.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Knit_Graph.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Knit_Graph_Visualizer.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Loop.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Pull_Direction.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.Yarn.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Crossing_Direction.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Loop_Braid_Graph.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Wale.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Wale_Braid.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Wale_Braid_Word.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.Wale_Group.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.artin_wale_braids.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.basic_knit_graph_generators.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/api/knit_graphs.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/conf.py +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/index.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/docs/source/installation.rst +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Knit_Graph_Visualizer.py +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/Pull_Direction.py +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/__init__.py +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Crossing_Direction.py +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Wale_Braid.py +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/Wale_Braid_Word.py +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/artin_wale_braids/__init__.py +0 -0
- {knit_graphs-0.0.8 → knit_graphs-0.0.10}/src/knit_graphs/basic_knit_graph_generators.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: knit-graphs
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.10
|
|
4
4
|
Summary: A graph representation of knitted structures where each loop is a node and edges represent yarn and stitch relationships.
|
|
5
|
-
Home-page: https://mhofmann-
|
|
5
|
+
Home-page: https://mhofmann-khoury.github.io/knit_graph/
|
|
6
6
|
License: MIT
|
|
7
7
|
Keywords: ACT Lab,Northeastern University,knit,machine knit,fabrication,textile
|
|
8
8
|
Author: Megan Hofmann
|
|
@@ -22,8 +22,8 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
22
22
|
Classifier: Topic :: Scientific/Engineering
|
|
23
23
|
Requires-Dist: networkx (>=3.5)
|
|
24
24
|
Requires-Dist: plotly (>=6.3.0,<7.0.0)
|
|
25
|
-
Project-URL: Documentation, https://mhofmann-
|
|
26
|
-
Project-URL: Repository, https://github.com/mhofmann-Khoury/
|
|
25
|
+
Project-URL: Documentation, https://mhofmann-khoury.github.io/knit_graph/
|
|
26
|
+
Project-URL: Repository, https://github.com/mhofmann-Khoury/knit_graph/
|
|
27
27
|
Description-Content-Type: text/markdown
|
|
28
28
|
|
|
29
29
|
# knit_graphs
|
|
@@ -12,15 +12,15 @@ build-backend = "poetry.core.masonry.api" # Use Poetry's build system
|
|
|
12
12
|
# All the information about your project that will appear on PyPI
|
|
13
13
|
[tool.poetry]
|
|
14
14
|
name = "knit-graphs"
|
|
15
|
-
version = "0.0.
|
|
15
|
+
version = "0.0.10"
|
|
16
16
|
description = "A graph representation of knitted structures where each loop is a node and edges represent yarn and stitch relationships."
|
|
17
17
|
authors = ["Megan Hofmann <m.hofmann@northeastern.edu>"]
|
|
18
18
|
maintainers = ["Megan Hofmann <m.hofmann@northeastern.edu>"]
|
|
19
19
|
license = "MIT" # License type (shows up on PyPI)
|
|
20
20
|
readme = "README.md" # File to use as long description on PyPI
|
|
21
|
-
homepage = "https://mhofmann-
|
|
22
|
-
repository = "https://github.com/mhofmann-Khoury/
|
|
23
|
-
documentation = "https://mhofmann-
|
|
21
|
+
homepage = "https://mhofmann-khoury.github.io/knit_graph/"
|
|
22
|
+
repository = "https://github.com/mhofmann-Khoury/knit_graph/"
|
|
23
|
+
documentation = "https://mhofmann-khoury.github.io/knit_graph/"
|
|
24
24
|
keywords = ["ACT Lab", "Northeastern University", "knit", "machine knit", "fabrication", "textile"]
|
|
25
25
|
classifiers = [ # PyPI classifiers for categorization
|
|
26
26
|
"Development Status :: 3 - Alpha", # Update as project matures (Alpha -> Beta -> Production/Stable)
|
|
@@ -52,8 +52,6 @@ include = [
|
|
|
52
52
|
"README.md", # Project description
|
|
53
53
|
"LICENSE", # License file
|
|
54
54
|
"docs/**/*", # All documentation files
|
|
55
|
-
# "src/**/data/**/*", # Example: include data files
|
|
56
|
-
# "src/**/templates/**/*", # Example: include template files
|
|
57
55
|
]
|
|
58
56
|
|
|
59
57
|
# Exclude files from the distribution package (keeps package size down)
|
|
@@ -80,29 +78,11 @@ exclude = [
|
|
|
80
78
|
python = ">=3.11,<3.14"
|
|
81
79
|
networkx = ">=3.5"
|
|
82
80
|
plotly = "^6.3.0"
|
|
83
|
-
# Examples:
|
|
84
|
-
# requests = "^2.31.0" # For HTTP requests
|
|
85
|
-
# pydantic = "^2.0.0" # For data validation
|
|
86
|
-
# click = "^8.1.0" # For CLI applications
|
|
87
|
-
# numpy = "^1.24.0" # For numerical computing
|
|
88
|
-
# pandas = "^2.0.0" # For data manipulation
|
|
89
|
-
|
|
90
|
-
# =============================================================================
|
|
91
|
-
# OPTIONAL DEPENDENCIES (EXTRAS)
|
|
92
|
-
# =============================================================================
|
|
93
|
-
# Optional dependency groups that users can install with pip install "package[extra]"
|
|
94
|
-
#[tool.poetry.extras]
|
|
95
|
-
# Examples:
|
|
96
|
-
# cli = ["click", "rich"] # For command-line interface features
|
|
97
|
-
# viz = ["matplotlib", "plotly"] # For visualization features
|
|
98
|
-
# dev = ["pytest", "mypy"] # For development tools (though prefer dev dependencies)
|
|
99
81
|
|
|
100
82
|
# =============================================================================
|
|
101
83
|
# DEVELOPMENT DEPENDENCIES
|
|
102
84
|
# =============================================================================
|
|
103
85
|
# These packages are only needed during development and testing
|
|
104
|
-
|
|
105
|
-
|
|
106
86
|
[tool.poetry.group.dev.dependencies]
|
|
107
87
|
importlib-resources = ">=6.5.2"
|
|
108
88
|
|
|
@@ -136,49 +116,12 @@ sphinx-autoapi = "^3.0.0" # Automatically generates API documentation
|
|
|
136
116
|
# -------------------------------------------------------------------------
|
|
137
117
|
# DEVELOPMENT TOOLS AND UTILITIES
|
|
138
118
|
# -------------------------------------------------------------------------
|
|
139
|
-
ipython = "^8.14.0" # Enhanced interactive Python shell for debugging
|
|
140
|
-
rich = "^13.5.0" # Beautiful terminal output and debugging
|
|
141
119
|
tox = "^4.11.0" # Test across multiple Python versions locally
|
|
142
120
|
|
|
143
121
|
# -------------------------------------------------------------------------
|
|
144
122
|
# RELEASE AND BUILD TOOLS
|
|
145
123
|
# -------------------------------------------------------------------------
|
|
146
124
|
build = "^0.10.0" # PEP 517 build tool (for creating distributions)
|
|
147
|
-
bump2version = "^1.0.0" # Automated version bumping tool
|
|
148
|
-
|
|
149
|
-
# -------------------------------------------------------------------------
|
|
150
|
-
# PLATFORM-SPECIFIC DEPENDENCIES
|
|
151
|
-
# -------------------------------------------------------------------------
|
|
152
|
-
# Only install on Windows systems (for colored terminal output)
|
|
153
|
-
colorama = {version = "^0.4.6", markers = "sys_platform == 'win32'"}
|
|
154
|
-
|
|
155
|
-
# =============================================================================
|
|
156
|
-
# DOCUMENTATION-SPECIFIC DEPENDENCIES
|
|
157
|
-
# =============================================================================
|
|
158
|
-
# Separate dependency group for building documentation (allows selective installation)
|
|
159
|
-
[tool.poetry.group.docs.dependencies]
|
|
160
|
-
sphinx = "^7.1.0" # Documentation generator (main tool)
|
|
161
|
-
sphinx-rtd-theme = "^1.3.0" # Professional-looking theme
|
|
162
|
-
sphinx-autodoc-typehints = "^1.24.0" # Automatically include type hints in docs
|
|
163
|
-
sphinx-autoapi = "^3.0.0" # Automatically generates API documentation
|
|
164
|
-
myst-parser = "^2.0.0" # Support for Markdown files in documentation
|
|
165
|
-
|
|
166
|
-
# =============================================================================
|
|
167
|
-
# COMMAND LINE SCRIPTS
|
|
168
|
-
# =============================================================================
|
|
169
|
-
# Define command-line entry points for your package
|
|
170
|
-
#[tool.poetry.scripts]
|
|
171
|
-
# Example:
|
|
172
|
-
# your-command = "your_project_name.cli:main" # Creates 'your-command' executable
|
|
173
|
-
|
|
174
|
-
# =============================================================================
|
|
175
|
-
# PROJECT URLS FOR PYPI
|
|
176
|
-
# =============================================================================
|
|
177
|
-
# Additional URLs that will be displayed on your PyPI project page
|
|
178
|
-
#[tool.poetry.urls]
|
|
179
|
-
#"Bug Tracker" = "https://github.com/mhofmann-Khoury/knit-graphs/issues"
|
|
180
|
-
#"Changelog" = "https://github.com/mhofmann-Khoury/knit-graphs/blob/main/CHANGELOG.md"
|
|
181
|
-
#"Discussions" = "https://github.com/your-username/your-project-name/discussions"
|
|
182
125
|
|
|
183
126
|
# =============================================================================
|
|
184
127
|
# ISORT IMPORT SORTER CONFIGURATION
|
|
@@ -244,12 +187,6 @@ check_untyped_defs = false # Don't type-check untyped test methods
|
|
|
244
187
|
warn_return_any = false # Don't warn about returning 'Any' in tests
|
|
245
188
|
warn_unused_ignores = false # Don't warn about unused type ignores in tests
|
|
246
189
|
|
|
247
|
-
# Add overrides for specific third-party libraries that may have type issues
|
|
248
|
-
# Example:
|
|
249
|
-
# [[tool.mypy.overrides]]
|
|
250
|
-
# module = ["problematic_library.*"]
|
|
251
|
-
# ignore_errors = true
|
|
252
|
-
|
|
253
190
|
# =============================================================================
|
|
254
191
|
# UNITTEST TESTING FRAMEWORK CONFIGURATION
|
|
255
192
|
# =============================================================================
|
|
@@ -4,11 +4,13 @@ This module contains the Course class which represents a horizontal row of loops
|
|
|
4
4
|
"""
|
|
5
5
|
from __future__ import annotations
|
|
6
6
|
|
|
7
|
-
from typing import Iterator, cast
|
|
7
|
+
from typing import TYPE_CHECKING, Iterator, cast
|
|
8
8
|
|
|
9
|
-
from knit_graphs._base_classes import _Base_Knit_Graph
|
|
10
9
|
from knit_graphs.Loop import Loop
|
|
11
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from knit_graphs.Knit_Graph import Knit_Graph
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
class Course:
|
|
14
16
|
"""Course object for organizing loops into knitting rows.
|
|
@@ -17,15 +19,32 @@ class Course:
|
|
|
17
19
|
It maintains an ordered list of loops and provides methods for analyzing the structure and relationships between courses in the knitted fabric.
|
|
18
20
|
"""
|
|
19
21
|
|
|
20
|
-
def __init__(self,
|
|
22
|
+
def __init__(self, knit_graph: Knit_Graph) -> None:
|
|
21
23
|
"""Initialize an empty course associated with a knit graph.
|
|
22
24
|
|
|
23
25
|
Args:
|
|
24
|
-
|
|
26
|
+
knit_graph (Knit_Graph): The knit graph that this course belongs to.
|
|
25
27
|
"""
|
|
26
|
-
self.
|
|
28
|
+
self._knit_graph: Knit_Graph = knit_graph
|
|
29
|
+
self._loops_in_order: list[Loop] = []
|
|
27
30
|
self._loop_set: set[Loop] = set()
|
|
28
31
|
|
|
32
|
+
@property
|
|
33
|
+
def loops_in_order(self) -> list[Loop]:
|
|
34
|
+
"""
|
|
35
|
+
Returns:
|
|
36
|
+
list[Loop]: The list of loops in this course.
|
|
37
|
+
"""
|
|
38
|
+
return self._loops_in_order
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def knit_graph(self) -> Knit_Graph:
|
|
42
|
+
"""
|
|
43
|
+
Returns:
|
|
44
|
+
Knit_Graph: The knit graph that this course belongs to.
|
|
45
|
+
"""
|
|
46
|
+
return self._knit_graph
|
|
47
|
+
|
|
29
48
|
def add_loop(self, loop: Loop, index: int | None = None) -> None:
|
|
30
49
|
"""Add a loop to the course at the specified index or at the end.
|
|
31
50
|
|
|
@@ -5,9 +5,10 @@ It manages the relationships between loops, yarns, and structural elements like
|
|
|
5
5
|
"""
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from typing import Any, cast
|
|
8
|
+
from typing import Any, Iterator, cast
|
|
9
|
+
|
|
10
|
+
from networkx import DiGraph
|
|
9
11
|
|
|
10
|
-
from knit_graphs._base_classes import _Base_Knit_Graph
|
|
11
12
|
from knit_graphs.artin_wale_braids.Crossing_Direction import Crossing_Direction
|
|
12
13
|
from knit_graphs.artin_wale_braids.Loop_Braid_Graph import Loop_Braid_Graph
|
|
13
14
|
from knit_graphs.artin_wale_braids.Wale import Wale
|
|
@@ -18,7 +19,7 @@ from knit_graphs.Pull_Direction import Pull_Direction
|
|
|
18
19
|
from knit_graphs.Yarn import Yarn
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
class Knit_Graph
|
|
22
|
+
class Knit_Graph:
|
|
22
23
|
"""A representation of knitted structures as connections between loops on yarns.
|
|
23
24
|
|
|
24
25
|
The Knit_Graph class is the main data structure for representing knitted fabrics.
|
|
@@ -28,7 +29,7 @@ class Knit_Graph(_Base_Knit_Graph):
|
|
|
28
29
|
|
|
29
30
|
def __init__(self) -> None:
|
|
30
31
|
"""Initialize an empty knit graph with no loops or yarns."""
|
|
31
|
-
|
|
32
|
+
self.stitch_graph: DiGraph = DiGraph()
|
|
32
33
|
self.braid_graph: Loop_Braid_Graph = Loop_Braid_Graph()
|
|
33
34
|
self._last_loop: None | Loop = None
|
|
34
35
|
self.yarns: set[Yarn] = set()
|
|
@@ -69,10 +70,46 @@ class Knit_Graph(_Base_Knit_Graph):
|
|
|
69
70
|
"""
|
|
70
71
|
self.stitch_graph.add_node(loop)
|
|
71
72
|
if loop.yarn not in self.yarns:
|
|
72
|
-
self.add_yarn(
|
|
73
|
+
self.add_yarn(loop.yarn)
|
|
73
74
|
if self._last_loop is None or loop > self._last_loop:
|
|
74
75
|
self._last_loop = loop
|
|
75
76
|
|
|
77
|
+
def remove_loop(self, loop: Loop) -> None:
|
|
78
|
+
"""
|
|
79
|
+
Remove the given loop from the knit graph.
|
|
80
|
+
Args:
|
|
81
|
+
loop (Loop): The loop to be removed.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
KeyError: If the loop is not in the knit graph.
|
|
85
|
+
|
|
86
|
+
"""
|
|
87
|
+
if loop not in self:
|
|
88
|
+
raise KeyError(f"Loop {loop} not on the knit graph")
|
|
89
|
+
self.braid_graph.remove_loop(loop) # remove any crossing associated with this loop.
|
|
90
|
+
# Remove any stitch edges involving this loop.
|
|
91
|
+
loop.remove_parent_loops()
|
|
92
|
+
if self.has_child_loop(loop):
|
|
93
|
+
child_loop = self.get_child_loop(loop)
|
|
94
|
+
assert isinstance(child_loop, Loop)
|
|
95
|
+
child_loop.remove_parent(loop)
|
|
96
|
+
self.stitch_graph.remove_node(loop)
|
|
97
|
+
# Remove loop from any floating positions
|
|
98
|
+
loop.remove_loop_from_front_floats()
|
|
99
|
+
loop.remove_loop_from_back_floats()
|
|
100
|
+
# Remove loop from yarn
|
|
101
|
+
yarn = loop.yarn
|
|
102
|
+
yarn.remove_loop(loop)
|
|
103
|
+
if len(yarn) == 0: # This was the only loop on that yarn
|
|
104
|
+
self.yarns.discard(yarn)
|
|
105
|
+
# Reset last loop
|
|
106
|
+
if loop is self.last_loop:
|
|
107
|
+
if len(self.yarns) == 0: # No loops left
|
|
108
|
+
assert len(self.stitch_graph.nodes) == 0
|
|
109
|
+
self._last_loop = None
|
|
110
|
+
else: # Set to the newest loop formed at the end of any yarns.
|
|
111
|
+
self._last_loop = max(y.last_loop for y in self.yarns if isinstance(y.last_loop, Loop))
|
|
112
|
+
|
|
76
113
|
def add_yarn(self, yarn: Yarn) -> None:
|
|
77
114
|
"""Add a yarn to the graph without adding its loops.
|
|
78
115
|
|
|
@@ -102,42 +139,31 @@ class Knit_Graph(_Base_Knit_Graph):
|
|
|
102
139
|
self.stitch_graph.add_edge(parent_loop, child_loop, pull_direction=pull_direction)
|
|
103
140
|
child_loop.add_parent_loop(parent_loop, stack_position)
|
|
104
141
|
|
|
105
|
-
def
|
|
106
|
-
"""Get
|
|
142
|
+
def get_wales_ending_with_loop(self, last_loop: Loop) -> set[Wale]:
|
|
143
|
+
"""Get all wales (vertical columns of stitches) that end at the specified loop.
|
|
107
144
|
|
|
108
145
|
Args:
|
|
109
|
-
|
|
146
|
+
last_loop (Loop): The last loop of the joined set of wales.
|
|
110
147
|
|
|
111
148
|
Returns:
|
|
112
|
-
Wale:
|
|
149
|
+
set[Wale]: The set of wales that end at this loop.
|
|
113
150
|
"""
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
assert isinstance(wale.last_loop, Loop)
|
|
119
|
-
wale.add_loop_to_end(cur_loop, self.get_pull_direction(wale.last_loop, cur_loop))
|
|
120
|
-
return wale
|
|
121
|
-
|
|
122
|
-
def get_wales_ending_with_loop(self, last_loop: Loop) -> list[Wale]:
|
|
123
|
-
"""Get all wales (vertical columns of stitches) that end at the specified loop.
|
|
151
|
+
if len(last_loop.parent_loops) == 0:
|
|
152
|
+
return {Wale(last_loop, self)}
|
|
153
|
+
ancestors = last_loop.ancestor_loops()
|
|
154
|
+
return {Wale(l, self) for l in ancestors}
|
|
124
155
|
|
|
125
|
-
|
|
126
|
-
|
|
156
|
+
def get_terminal_wales(self) -> dict[Loop, list[Wale]]:
|
|
157
|
+
"""
|
|
158
|
+
Get wale groups organized by their terminal loops.
|
|
127
159
|
|
|
128
160
|
Returns:
|
|
129
|
-
list[Wale]:
|
|
130
|
-
"""
|
|
131
|
-
|
|
132
|
-
for
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
cur_loop = top_stitch_parent
|
|
136
|
-
while len(cur_loop.parent_loops) == 1: # stop at split for decrease or start of wale
|
|
137
|
-
cur_loop = [*self.stitch_graph.predecessors(cur_loop)][0]
|
|
138
|
-
wale.add_loop_to_beginning(cur_loop, cast(Pull_Direction, self.get_pull_direction(cur_loop, cast(Loop, wale.first_loop))))
|
|
139
|
-
wales.append(wale)
|
|
140
|
-
return wales
|
|
161
|
+
dict[Loop, list[Wale]]: Dictionary mapping terminal loops to list of wales that terminate that wale.
|
|
162
|
+
"""
|
|
163
|
+
wale_groups = {}
|
|
164
|
+
for loop in self.terminal_loops():
|
|
165
|
+
wale_groups[loop] = [wale for wale in self.get_wales_ending_with_loop(loop)]
|
|
166
|
+
return wale_groups
|
|
141
167
|
|
|
142
168
|
def get_courses(self) -> list[Course]:
|
|
143
169
|
"""Get all courses (horizontal rows) in the knit graph in chronological order.
|
|
@@ -148,8 +174,8 @@ class Knit_Graph(_Base_Knit_Graph):
|
|
|
148
174
|
"""
|
|
149
175
|
courses = []
|
|
150
176
|
course = Course(self)
|
|
151
|
-
for loop in
|
|
152
|
-
for parent in
|
|
177
|
+
for loop in self.sorted_loops():
|
|
178
|
+
for parent in loop.parent_loops:
|
|
153
179
|
if parent in course: # start a new course
|
|
154
180
|
courses.append(course)
|
|
155
181
|
course = Course(self)
|
|
@@ -158,28 +184,47 @@ class Knit_Graph(_Base_Knit_Graph):
|
|
|
158
184
|
courses.append(course)
|
|
159
185
|
return courses
|
|
160
186
|
|
|
161
|
-
def get_wale_groups(self) ->
|
|
187
|
+
def get_wale_groups(self) -> set[Wale_Group]:
|
|
162
188
|
"""Get wale groups organized by their terminal loops.
|
|
163
189
|
|
|
164
190
|
Returns:
|
|
165
|
-
|
|
191
|
+
set[Wale_Group]: The set of wale-groups that lead to the terminal loops of this graph. Each wale group represents a collection of wales that end at the same terminal loop.
|
|
166
192
|
"""
|
|
167
|
-
|
|
168
|
-
for loop in self.stitch_graph.nodes:
|
|
169
|
-
if self.is_terminal_loop(loop):
|
|
170
|
-
wale_groups.update({loop: Wale_Group(wale, self) for wale in self.get_wales_ending_with_loop(loop)})
|
|
171
|
-
return wale_groups
|
|
193
|
+
return set(Wale_Group(l, self) for l in self.terminal_loops())
|
|
172
194
|
|
|
173
|
-
def __contains__(self, item: Loop) -> bool:
|
|
195
|
+
def __contains__(self, item: Loop | tuple[Loop, Loop]) -> bool:
|
|
174
196
|
"""Check if a loop is contained in the knit graph.
|
|
175
197
|
|
|
176
198
|
Args:
|
|
177
|
-
item (Loop): The loop being checked for in the graph.
|
|
199
|
+
item (Loop | tuple[Loop, Loop]): The loop being checked for in the graph or the parent-child stitch edge to check for in the knit graph.
|
|
178
200
|
|
|
179
201
|
Returns:
|
|
180
|
-
bool: True if the loop is in the graph, False otherwise.
|
|
202
|
+
bool: True if the given loop or stitch edge is in the graph, False otherwise.
|
|
181
203
|
"""
|
|
182
|
-
|
|
204
|
+
if isinstance(item, Loop):
|
|
205
|
+
return bool(self.stitch_graph.has_node(item))
|
|
206
|
+
else:
|
|
207
|
+
return bool(self.stitch_graph.has_edge(item[0], item[1]))
|
|
208
|
+
|
|
209
|
+
def __iter__(self) -> Iterator[Loop]:
|
|
210
|
+
"""
|
|
211
|
+
Returns:
|
|
212
|
+
Iterator[Loop]: An iterator over all loops in the knit graph.
|
|
213
|
+
"""
|
|
214
|
+
return cast(Iterator[Loop], iter(self.stitch_graph.nodes))
|
|
215
|
+
|
|
216
|
+
def __getitem__(self, item: int) -> Loop:
|
|
217
|
+
loop = next((l for l in self if l.loop_id == item), None)
|
|
218
|
+
if loop is None:
|
|
219
|
+
raise KeyError(f"Loop of id {item} not in knit graph")
|
|
220
|
+
return loop
|
|
221
|
+
|
|
222
|
+
def sorted_loops(self) -> list[Loop]:
|
|
223
|
+
"""
|
|
224
|
+
Returns:
|
|
225
|
+
list[Loop]: The list of loops in the stitch graph sorted from the earliest formed loop to the latest formed loop.
|
|
226
|
+
"""
|
|
227
|
+
return sorted(list(self.stitch_graph.nodes))
|
|
183
228
|
|
|
184
229
|
def get_pull_direction(self, parent: Loop, child: Loop) -> Pull_Direction | None:
|
|
185
230
|
"""Get the pull direction of the stitch edge between parent and child loops.
|
|
@@ -247,3 +292,10 @@ class Knit_Graph(_Base_Knit_Graph):
|
|
|
247
292
|
bool: True if the loop has no child loops and terminates a wale, False otherwise.
|
|
248
293
|
"""
|
|
249
294
|
return not self.has_child_loop(loop)
|
|
295
|
+
|
|
296
|
+
def terminal_loops(self) -> Iterator[Loop]:
|
|
297
|
+
"""
|
|
298
|
+
Returns:
|
|
299
|
+
Iterator[Loop]: An iterator over all terminal loops in the knit graph.
|
|
300
|
+
"""
|
|
301
|
+
return iter(l for l in self if self.is_terminal_loop(l))
|
|
@@ -5,12 +5,13 @@ Loops are the fundamental building blocks of knitted structures and maintain rel
|
|
|
5
5
|
"""
|
|
6
6
|
from __future__ import annotations
|
|
7
7
|
|
|
8
|
-
from typing import
|
|
8
|
+
from typing import TYPE_CHECKING
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from knit_graphs.Yarn import Yarn
|
|
11
12
|
|
|
12
13
|
|
|
13
|
-
class Loop
|
|
14
|
+
class Loop:
|
|
14
15
|
"""A class to represent a single loop structure for modeling a single loop in a knitting pattern.
|
|
15
16
|
|
|
16
17
|
The Loop class manages yarn relationships, parent-child connections for stitches, and float positioning for complex knitting structures.
|
|
@@ -23,15 +24,17 @@ class Loop(_Base_Loop):
|
|
|
23
24
|
back_floats (dict[Loop, set[Loop]]): A dictionary tracking loops that this loop floats behind.
|
|
24
25
|
"""
|
|
25
26
|
|
|
26
|
-
def __init__(self, loop_id: int, yarn:
|
|
27
|
+
def __init__(self, loop_id: int, yarn: Yarn) -> None:
|
|
27
28
|
"""Construct a Loop object with the specified identifier and yarn.
|
|
28
29
|
|
|
29
30
|
Args:
|
|
30
31
|
loop_id (int): A unique identifier for the loop, must be non-negative.
|
|
31
|
-
yarn (
|
|
32
|
+
yarn (Yarn): The yarn that creates and holds this loop.
|
|
32
33
|
"""
|
|
33
|
-
|
|
34
|
-
|
|
34
|
+
if loop_id < 0:
|
|
35
|
+
raise ValueError(f"Loop identifier must be non-negative but got {loop_id}")
|
|
36
|
+
self._loop_id: int = loop_id
|
|
37
|
+
self.yarn: Yarn = yarn
|
|
35
38
|
self.parent_loops: list[Loop] = []
|
|
36
39
|
self.front_floats: dict[Loop, set[Loop]] = {}
|
|
37
40
|
self.back_floats: dict[Loop, set[Loop]] = {}
|
|
@@ -44,7 +47,12 @@ class Loop(_Base_Loop):
|
|
|
44
47
|
Args:
|
|
45
48
|
u (Loop): The first loop in the float pair.
|
|
46
49
|
v (Loop): The second loop in the float pair.
|
|
50
|
+
|
|
51
|
+
Raises:
|
|
52
|
+
ValueError: If u and v are not on the same yarn.
|
|
47
53
|
"""
|
|
54
|
+
if u.yarn != v.yarn:
|
|
55
|
+
raise ValueError("Loops of a float must share a yarn.")
|
|
48
56
|
if u not in self.back_floats:
|
|
49
57
|
self.back_floats[u] = set()
|
|
50
58
|
if v not in self.back_floats:
|
|
@@ -52,6 +60,18 @@ class Loop(_Base_Loop):
|
|
|
52
60
|
self.back_floats[u].add(v)
|
|
53
61
|
self.back_floats[v].add(u)
|
|
54
62
|
|
|
63
|
+
def remove_loop_from_front_floats(self) -> None:
|
|
64
|
+
"""
|
|
65
|
+
Removes this loop from being in front of all marked floats. Mutates the yarns that own edges of those floats.
|
|
66
|
+
"""
|
|
67
|
+
visited: set[Loop] = set()
|
|
68
|
+
for u, v_loops in self.front_floats.items():
|
|
69
|
+
visited.add(u)
|
|
70
|
+
for v in v_loops:
|
|
71
|
+
if v not in visited and v in u.yarn: # float shares a yarn
|
|
72
|
+
u.yarn.loop_graph.edges[u, v]["Back_Loops"].remove(self)
|
|
73
|
+
self.front_floats = {}
|
|
74
|
+
|
|
55
75
|
def add_loop_behind_float(self, u: Loop, v: Loop) -> None:
|
|
56
76
|
"""Set this loop to be behind the float between loops u and v.
|
|
57
77
|
|
|
@@ -60,7 +80,12 @@ class Loop(_Base_Loop):
|
|
|
60
80
|
Args:
|
|
61
81
|
u (Loop): The first loop in the float pair.
|
|
62
82
|
v (Loop): The second loop in the float pair.
|
|
83
|
+
|
|
84
|
+
Raises:
|
|
85
|
+
ValueError: If u and v are not on the same yarn.
|
|
63
86
|
"""
|
|
87
|
+
if u.yarn != v.yarn:
|
|
88
|
+
raise ValueError("Loops of a float must share a yarn.")
|
|
64
89
|
if u not in self.front_floats:
|
|
65
90
|
self.front_floats[u] = set()
|
|
66
91
|
if v not in self.front_floats:
|
|
@@ -68,6 +93,18 @@ class Loop(_Base_Loop):
|
|
|
68
93
|
self.front_floats[u].add(v)
|
|
69
94
|
self.front_floats[v].add(u)
|
|
70
95
|
|
|
96
|
+
def remove_loop_from_back_floats(self) -> None:
|
|
97
|
+
"""
|
|
98
|
+
Removes this loop from being behind of all marked floats. Mutates the yarns that own edges of those floats.
|
|
99
|
+
"""
|
|
100
|
+
visited = set()
|
|
101
|
+
for u, v_loops in self.back_floats.items():
|
|
102
|
+
visited.add(u)
|
|
103
|
+
for v in v_loops:
|
|
104
|
+
if v not in visited and v in u.yarn: # float shares a yarn
|
|
105
|
+
u.yarn.loop_graph.edges[u, v]["Front_Loops"].remove(self)
|
|
106
|
+
self.back_floats = {}
|
|
107
|
+
|
|
71
108
|
def is_in_front_of_float(self, u: Loop, v: Loop) -> bool:
|
|
72
109
|
"""Check if this loop is positioned in front of the float between loops u and v.
|
|
73
110
|
|
|
@@ -98,19 +135,25 @@ class Loop(_Base_Loop):
|
|
|
98
135
|
Returns:
|
|
99
136
|
Loop | None: The prior loop on the yarn, or None if this is the first loop on the yarn.
|
|
100
137
|
"""
|
|
101
|
-
|
|
102
|
-
if loop is None:
|
|
103
|
-
return None
|
|
104
|
-
else:
|
|
105
|
-
return cast(Loop, loop)
|
|
138
|
+
return self.yarn.prior_loop(self)
|
|
106
139
|
|
|
107
|
-
def next_loop_on_yarn(self) -> Loop:
|
|
140
|
+
def next_loop_on_yarn(self) -> Loop | None:
|
|
108
141
|
"""Get the loop that follows this loop on the same yarn.
|
|
109
142
|
|
|
110
143
|
Returns:
|
|
111
|
-
Loop: The next loop on the yarn, or None if this is the last loop on the yarn.
|
|
144
|
+
Loop | None: The next loop on the yarn, or None if this is the last loop on the yarn.
|
|
112
145
|
"""
|
|
113
|
-
return
|
|
146
|
+
return self.yarn.next_loop(self)
|
|
147
|
+
|
|
148
|
+
def remove_parent_loops(self) -> list[Loop]:
|
|
149
|
+
"""
|
|
150
|
+
Removes the list of parent loops from this loop.
|
|
151
|
+
Returns:
|
|
152
|
+
list[Loop]: The list of parent loops that were removed.
|
|
153
|
+
"""
|
|
154
|
+
parents = self.parent_loops
|
|
155
|
+
self.parent_loops = []
|
|
156
|
+
return parents
|
|
114
157
|
|
|
115
158
|
def has_parent_loops(self) -> bool:
|
|
116
159
|
"""Check if this loop has any parent loops connected through stitch edges.
|
|
@@ -132,10 +175,82 @@ class Loop(_Base_Loop):
|
|
|
132
175
|
else:
|
|
133
176
|
self.parent_loops.append(parent)
|
|
134
177
|
|
|
135
|
-
def
|
|
136
|
-
"""
|
|
178
|
+
def remove_parent(self, parent: Loop) -> None:
|
|
179
|
+
"""
|
|
180
|
+
Removes the given parent loop from the set of parents of this loop.
|
|
181
|
+
If the given loop is not a parent of this loop, nothing happens.
|
|
182
|
+
Args:
|
|
183
|
+
parent (Loop): The parent loop to remove.
|
|
184
|
+
"""
|
|
185
|
+
if parent in self.parent_loops:
|
|
186
|
+
self.parent_loops.remove(parent)
|
|
187
|
+
|
|
188
|
+
def ancestor_loops(self) -> set[Loop]:
|
|
189
|
+
"""
|
|
190
|
+
Returns:
|
|
191
|
+
set[Loop]: The set of loops that initiate all wales that lead to this loop. The empty set if this loop has no parents.
|
|
192
|
+
"""
|
|
193
|
+
if not self.has_parent_loops():
|
|
194
|
+
return set()
|
|
195
|
+
ancestors = set()
|
|
196
|
+
for parent_loop in self.parent_loops:
|
|
197
|
+
if parent_loop.has_parent_loops():
|
|
198
|
+
ancestors.update(parent_loop.ancestor_loops())
|
|
199
|
+
else:
|
|
200
|
+
ancestors.add(parent_loop)
|
|
201
|
+
return ancestors
|
|
202
|
+
|
|
203
|
+
@property
|
|
204
|
+
def loop_id(self) -> int:
|
|
205
|
+
"""Get the unique identifier of this loop.
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
int: The id of the loop.
|
|
209
|
+
"""
|
|
210
|
+
return self._loop_id
|
|
211
|
+
|
|
212
|
+
def __hash__(self) -> int:
|
|
213
|
+
"""Return hash value based on loop_id for use in sets and dictionaries.
|
|
214
|
+
|
|
215
|
+
Returns:
|
|
216
|
+
int: Hash value of the loop_id.
|
|
217
|
+
"""
|
|
218
|
+
return self.loop_id
|
|
219
|
+
|
|
220
|
+
def __int__(self) -> int:
|
|
221
|
+
"""Convert loop to integer representation using loop_id.
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
int: The loop_id as an integer.
|
|
225
|
+
"""
|
|
226
|
+
return self.loop_id
|
|
227
|
+
|
|
228
|
+
def __eq__(self, other: Loop) -> bool:
|
|
229
|
+
"""Check equality with another base loop based on loop_id and type.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
other (Loop): The other loop to compare with.
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
bool: True if both loops have the same class and loop_id, False otherwise.
|
|
236
|
+
"""
|
|
237
|
+
return isinstance(other, other.__class__) and self.loop_id == other.loop_id
|
|
238
|
+
|
|
239
|
+
def __lt__(self, other: Loop | int) -> bool:
|
|
240
|
+
"""Compare loop_id with another loop or integer for ordering.
|
|
241
|
+
|
|
242
|
+
Args:
|
|
243
|
+
other (Loop | int): The other loop or integer to compare with.
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
bool: True if this loop's id is less than the other's id.
|
|
247
|
+
"""
|
|
248
|
+
return int(self.loop_id) < int(other)
|
|
249
|
+
|
|
250
|
+
def __repr__(self) -> str:
|
|
251
|
+
"""Return string representation of the loop.
|
|
137
252
|
|
|
138
253
|
Returns:
|
|
139
|
-
str: String representation showing
|
|
254
|
+
str: String representation showing "Loop {loop_id}".
|
|
140
255
|
"""
|
|
141
|
-
return f"{self.loop_id}
|
|
256
|
+
return f"Loop {self.loop_id}"
|