base-loom-server 1.2b2__tar.gz → 1.2b4__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.
- {base_loom_server-1.2b2/src/base_loom_server.egg-info → base_loom_server-1.2b4}/PKG-INFO +1 -1
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/version_history.md +8 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/weaving_tabby.md +1 -1
- base_loom_server-1.2b4/src/base_loom_server/compute_tabby.py +249 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/reduced_pattern.py +23 -11
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/testutils.py +4 -1
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/utils.py +10 -1
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/version.py +1 -1
- {base_loom_server-1.2b2 → base_loom_server-1.2b4/src/base_loom_server.egg-info}/PKG-INFO +1 -1
- base_loom_server-1.2b4/tests/test_compute_tabby.py +109 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/tests/test_utils.py +34 -14
- base_loom_server-1.2b2/src/base_loom_server/compute_tabby.py +0 -183
- base_loom_server-1.2b2/tests/test_compute_tabby.py +0 -98
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/.github/workflows/deploy_to_pypi.yml +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/.github/workflows/run_pytest.yml +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/.github/workflows/serve_docs.yml +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/.gitignore +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/.pre-commit-config.yaml +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/LICENSE.txt +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/Manifest.in +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/README.md +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/32-shaft twill.wif +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/coding.md +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/settings_safari_iphone_mini.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/settings_safari_macos.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/show_right_panel_icon.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/threading_safari_iphone_mini.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/threading_safari_macos.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/translate_icon.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/translate_panel.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/weaving_safari_iphone_mini.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/weaving_safari_macos.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/weaving_tabby_safari_iphone_mini.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/weaving_tabby_safari_macos.jpg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/index.md +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/installing.md +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/settings.md +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/threading.md +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/translations.md +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/weaving.md +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/mkdocs.yml +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/pyproject.toml +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/setup.cfg +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/__init__.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/app_runner.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/base_loom_server.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/base_mock_loom.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/check_translation_files.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/client_replies.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/constants.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/display.css +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/display.html +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/display.js +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/enums.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/example_loom_server.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/example_mock_loom.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/favicon-32x32.png +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Dansk.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Deutsch.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/English.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Espa/303/261ol.json" +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Fran/303/247ais.json" +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Italiano.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Nederlands.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Norsk.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Suomi.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Svenska.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/default.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/main.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/mock_streams.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/nmcli_wifi.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/pattern_database.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/py.typed +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/rename_crowdin_files.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/eighteen shaft liftplan.wif +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/many color liftplan and zeros.dtx +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/many color liftplan and zeros.wif +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/many color multiple treadles and zeros.dtx +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/many color multiple treadles and zeros.wif +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/many color multiple treadles and zeros.wpo +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/many color single treadles.dtx +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/many color single treadles.wif +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/many color single treadles.wpo +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/two color liftplan.dtx +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/two color liftplan.wif +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/two color multiple treadles.dtx +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/two color multiple treadles.wif +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/two color single treadles.dtx +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/pattern_files/two color single treadles.wif +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/test_data/translation_files/extra_keys.json +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/translations.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server.egg-info/SOURCES.txt +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server.egg-info/dependency_links.txt +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server.egg-info/entry_points.txt +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server.egg-info/requires.txt +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server.egg-info/top_level.txt +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/tests/test_loom_server.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/tests/test_mock_loom.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/tests/test_mock_streams.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/tests/test_pattern_database.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/tests/test_reduced_pattern.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/tests/test_translations.py +0 -0
- {base_loom_server-1.2b2 → base_loom_server-1.2b4}/tests/test_version.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: base_loom_server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2b4
|
|
4
4
|
Summary: Base package for web servers that control dobby multi-shaft looms
|
|
5
5
|
Author-email: Russell Owen <r3owen@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://pypi.org/project/base-loom-server/
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import functools
|
|
2
|
+
import itertools
|
|
3
|
+
import operator
|
|
4
|
+
|
|
5
|
+
from .utils import prune_duplicates
|
|
6
|
+
|
|
7
|
+
# Maximum number of starting points for iterations in compute_tabby_shaft_word1.
|
|
8
|
+
# Avoid very large values to keep compute time reasonable.
|
|
9
|
+
MAX_ITER_TABBY1 = 5
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def compute_num_transitions(tabby_shaft_word: int, threading: list[int]) -> int:
|
|
13
|
+
"""Compute the number of transitions produced by a tabby shaft word.
|
|
14
|
+
|
|
15
|
+
The largest possible value is len(threading_shaft_words) - 1.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
tabby_shaft_word: Tabby shaft word.
|
|
19
|
+
threading: Shaft index (0-based) for each warp end.
|
|
20
|
+
Negative values are ignored (and adjacent duplicate values
|
|
21
|
+
naturally do not affect the result).
|
|
22
|
+
"""
|
|
23
|
+
threading_shaft_words = [1 << shaft for shaft in threading if shaft >= 0]
|
|
24
|
+
return _compute_num_transitions_impl(
|
|
25
|
+
tabby_shaft_word=tabby_shaft_word, threading_shaft_words=threading_shaft_words
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _compute_num_transitions_impl(tabby_shaft_word: int, threading_shaft_words: list[int]) -> int:
|
|
30
|
+
"""Implementation of compute_num_transitions.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
tabby_shaft_word: Tabby shaft word.
|
|
34
|
+
threading_shaft_words: Shaft word for each warp end.
|
|
35
|
+
"""
|
|
36
|
+
is_up_list = [bool(tsw & tabby_shaft_word) for tsw in threading_shaft_words]
|
|
37
|
+
return sum(1 for val1, val2 in itertools.pairwise(is_up_list) if val1 != val2)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def compute_tabby_shaft_word1_simple(threading: list[int]) -> int:
|
|
41
|
+
"""Trivial tabby computation that may fail.
|
|
42
|
+
|
|
43
|
+
Handle the trivial case that all even threads are on different
|
|
44
|
+
shafts than all odd threads. Return 0 if that is not the case.
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
threading: Shaft index (0-based) for each warp end.
|
|
48
|
+
Negative values and adjacent duplicates are ignored.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Tabby shaft word, or 0 if the simple case is not satified.
|
|
52
|
+
"""
|
|
53
|
+
# Ignore unthreaded shafts and duplicates
|
|
54
|
+
pruned_threading = prune_duplicates([shaft for shaft in threading if shaft >= 0])
|
|
55
|
+
num_threaded_shafts = len(set(threading))
|
|
56
|
+
if len(threading) <= 1:
|
|
57
|
+
raise ValueError("Need at least 2 threaded warp ends to weave tabby.")
|
|
58
|
+
if num_threaded_shafts <= 1:
|
|
59
|
+
raise ValueError("Need at least 2 threaded shafts to weave tabby.")
|
|
60
|
+
|
|
61
|
+
threading_shaft_words = [1 << shaft for shaft in pruned_threading]
|
|
62
|
+
return _compute_tabby_shaft_word1_simple_impl(threading_shaft_words=threading_shaft_words)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _compute_tabby_shaft_word1_simple_impl(threading_shaft_words: list[int]) -> int:
|
|
66
|
+
"""Implementation of compute_tabby_shaft_word1_simple.
|
|
67
|
+
|
|
68
|
+
Handle the trivial case that all (threaded and non-duplicated)
|
|
69
|
+
even threads are on different shafts than all odd threads.
|
|
70
|
+
Return 0 if that is not the case.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
threading_shaft_words: Shaft words for each warp end.
|
|
74
|
+
Duplicates should be removed.
|
|
75
|
+
|
|
76
|
+
Returns:
|
|
77
|
+
Tabby shaft word, or 0 if the simple case is not satified.
|
|
78
|
+
"""
|
|
79
|
+
if len(threading_shaft_words) == 0:
|
|
80
|
+
return 0
|
|
81
|
+
|
|
82
|
+
tabby_shaft_word = 0
|
|
83
|
+
odd_ends_shaft_word = functools.reduce(operator.or_, threading_shaft_words[0::2], 0)
|
|
84
|
+
even_ends_shaft_word = functools.reduce(operator.or_, threading_shaft_words[1::2], 0)
|
|
85
|
+
|
|
86
|
+
if even_ends_shaft_word & odd_ends_shaft_word == 0:
|
|
87
|
+
min_shaft_word = min(threading_shaft_words)
|
|
88
|
+
tabby_shaft_word = (
|
|
89
|
+
even_ends_shaft_word if min_shaft_word & even_ends_shaft_word > 0 else odd_ends_shaft_word
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
return tabby_shaft_word
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def compute_tabby_shaft_word1(threading: list[int]) -> int:
|
|
96
|
+
"""Compute which shaft_set should go up for the best tabby pick 1.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
threading: Shaft index (0-based) for each warp end.
|
|
100
|
+
Negative values and adjacent duplicates are ignored.
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
shaft_word: Which shaft_set should be up for pick 1, 3, 5... of tabby.
|
|
104
|
+
|
|
105
|
+
Raises:
|
|
106
|
+
ValueError if there are fewer than 2 threaded warp ends,
|
|
107
|
+
or if the warp ends are threaded on fewer than 2 different shaft_set.
|
|
108
|
+
|
|
109
|
+
Notes:
|
|
110
|
+
The algorithm works by examining transitions between adjacent warp ends, as follows:
|
|
111
|
+
|
|
112
|
+
First make a pruned version of threading with no negative values
|
|
113
|
+
(warp ends that are not threaded) and no repeating ends.
|
|
114
|
+
|
|
115
|
+
Initialize tabby_shafts and seen_shafts to the first entry in the pruned threading.
|
|
116
|
+
For each shaft in the rest of the pruned threading:
|
|
117
|
+
If shaft is not in seen_shafts:
|
|
118
|
+
Add it to seen_shafts
|
|
119
|
+
If previous_shaft is not in tabby_shafts, put the new shaft in tabby_shafts.
|
|
120
|
+
Set previous_shaft to shaft
|
|
121
|
+
If the resulting tabby shaft word does not included the minimum threaded shaft,
|
|
122
|
+
invert it to provide more predictable results.
|
|
123
|
+
|
|
124
|
+
Try this several times, from different starting points in the threading,
|
|
125
|
+
and pick the tabby that gives the most consecutive transitions.
|
|
126
|
+
"""
|
|
127
|
+
# Ignore unthreaded shafts and duplicates
|
|
128
|
+
pruned_threading = prune_duplicates([shaft for shaft in threading if shaft >= 0])
|
|
129
|
+
num_threaded_shafts = len(set(threading))
|
|
130
|
+
if len(threading) <= 1:
|
|
131
|
+
raise ValueError("Need at least 2 threaded warp ends to weave tabby.")
|
|
132
|
+
if num_threaded_shafts <= 1:
|
|
133
|
+
raise ValueError("Need at least 2 threaded shafts to weave tabby.")
|
|
134
|
+
|
|
135
|
+
threading_shaft_words = [1 << shaft for shaft in pruned_threading]
|
|
136
|
+
|
|
137
|
+
# First try the simple algorithm. It is a common case, fast to evaluate,
|
|
138
|
+
# and gives maximum interlacement if it works at all.
|
|
139
|
+
try_tabby_word = _compute_tabby_shaft_word1_simple_impl(threading_shaft_words=threading_shaft_words)
|
|
140
|
+
if try_tabby_word != 0:
|
|
141
|
+
return try_tabby_word
|
|
142
|
+
|
|
143
|
+
# Try the harder and less ideal algorithm.
|
|
144
|
+
min_shaft_word = min(threading_shaft_words)
|
|
145
|
+
max_threaded_shaft = max(pruned_threading)
|
|
146
|
+
all_shafts_word = (1 << (max_threaded_shaft + 1)) - 1
|
|
147
|
+
|
|
148
|
+
best_num_transitions = 0
|
|
149
|
+
best_tabby_shaft_word = 0
|
|
150
|
+
|
|
151
|
+
# Try starting at several different points in the threading,
|
|
152
|
+
# in order to get a somewhat better result.
|
|
153
|
+
num_threads = len(pruned_threading)
|
|
154
|
+
start_interval = ((num_threads - 1) // MAX_ITER_TABBY1) + 1
|
|
155
|
+
if num_threads < MAX_ITER_TABBY1 * 2:
|
|
156
|
+
# Not many threads; don't worry about limiting the number of iterations.
|
|
157
|
+
start_interval = 1
|
|
158
|
+
niter = 0
|
|
159
|
+
for start_index in range(0, num_threads, start_interval):
|
|
160
|
+
niter += 1
|
|
161
|
+
tabby_shaft_word = threading_shaft_words[start_index]
|
|
162
|
+
seen_shafts_word = threading_shaft_words[start_index]
|
|
163
|
+
num_seen_shafts = 1
|
|
164
|
+
for prev_shaft_word, shaft_word in itertools.pairwise(
|
|
165
|
+
itertools.chain(threading_shaft_words[start_index:], threading_shaft_words[:start_index])
|
|
166
|
+
):
|
|
167
|
+
if shaft_word & seen_shafts_word > 0:
|
|
168
|
+
continue
|
|
169
|
+
seen_shafts_word |= shaft_word
|
|
170
|
+
num_seen_shafts += 1
|
|
171
|
+
if prev_shaft_word & tabby_shaft_word == 0:
|
|
172
|
+
# The previous thread's shaft word is not part of the tabby word
|
|
173
|
+
# so make this new thread's shaft part of the tabby word.
|
|
174
|
+
tabby_shaft_word |= shaft_word
|
|
175
|
+
if num_seen_shafts == num_threaded_shafts:
|
|
176
|
+
# We have seen all shafts; stop
|
|
177
|
+
break
|
|
178
|
+
|
|
179
|
+
# Pick the tabby word that includes the lowest numbered threaded shaft
|
|
180
|
+
if min_shaft_word & tabby_shaft_word == 0:
|
|
181
|
+
tabby_shaft_word = ~tabby_shaft_word & all_shafts_word
|
|
182
|
+
|
|
183
|
+
num_transitions = _compute_num_transitions_impl(
|
|
184
|
+
tabby_shaft_word=tabby_shaft_word,
|
|
185
|
+
threading_shaft_words=threading_shaft_words,
|
|
186
|
+
)
|
|
187
|
+
if num_transitions > best_num_transitions:
|
|
188
|
+
best_num_transitions = num_transitions
|
|
189
|
+
best_tabby_shaft_word = tabby_shaft_word
|
|
190
|
+
if num_transitions == len(pruned_threading) - 1:
|
|
191
|
+
break
|
|
192
|
+
return best_tabby_shaft_word
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def compute_tabby_shaft_word2(tabby_shaft_word1: int, threading: list[int]) -> int:
|
|
196
|
+
"""Compute tabby_shaft_word2 as the complement of tabby_shaft_word1.
|
|
197
|
+
|
|
198
|
+
Args:
|
|
199
|
+
tabby_shaft_word1: tabby shaft word computed by compute_tabby_shaft_word1.
|
|
200
|
+
threading: Shaft index (0-based) for each warp end.
|
|
201
|
+
Negative values and adjacent duplicates are ignored.
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
tabby_shaft_word2: which shaft_set should be up picks 2, 4, 6... of tabby.
|
|
205
|
+
The complement tabby_shaft_word1, will all shaft_set beyond max_threaded_shaft_number
|
|
206
|
+
set to 0.
|
|
207
|
+
|
|
208
|
+
Raises:
|
|
209
|
+
ValueError if max_threaded_shaft_number < 2.
|
|
210
|
+
|
|
211
|
+
Notes:
|
|
212
|
+
Takes threading instead of max_threaded_shaft_number for two reasons:
|
|
213
|
+
|
|
214
|
+
* It proved too easy to mis-compute the value. Threading is 0-based
|
|
215
|
+
and it was too easy to pass in max(threading), which is 1 too small.
|
|
216
|
+
* We may wish to switch to using a sparse mask of threaded shafts,
|
|
217
|
+
lowering all unused shafts, not just those beyond the last threaded shaft.
|
|
218
|
+
"""
|
|
219
|
+
if tabby_shaft_word1 < 1:
|
|
220
|
+
raise ValueError(f"{tabby_shaft_word1=} must be positive")
|
|
221
|
+
max_threaded_shaft_number = max(threading) + 1 # threading is 0-based
|
|
222
|
+
if max_threaded_shaft_number <= 1:
|
|
223
|
+
raise ValueError(f"{max_threaded_shaft_number=} must be > 1")
|
|
224
|
+
|
|
225
|
+
all_shafts_mask = (1 << max_threaded_shaft_number) - 1
|
|
226
|
+
tabby_shaft_word2 = ~tabby_shaft_word1 & all_shafts_mask
|
|
227
|
+
if tabby_shaft_word2 < 1:
|
|
228
|
+
raise ValueError(f"Result is invalid; check {tabby_shaft_word1=} and {threading=}")
|
|
229
|
+
return tabby_shaft_word2
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def compute_tabby_shaft_words(threading: list[int]) -> tuple[int, int]:
|
|
233
|
+
"""Compute tabby_shaft_words 1 and 2.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
threading: Shaft index (0-based) for each warp end.
|
|
237
|
+
Negative values and adjacent duplicates are ignored.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
(shaft_word1, shaft_word2): Which shaft_set should be up for the two picks of tabby
|
|
241
|
+
(word 1 is for picks 1, 3, 5.., word 2 is for picks 2, 4, 6...).
|
|
242
|
+
|
|
243
|
+
Raises:
|
|
244
|
+
ValueError if there are fewer than 2 threaded warp ends,
|
|
245
|
+
or if the warp ends are threaded on fewer than 2 different shaft_set.
|
|
246
|
+
"""
|
|
247
|
+
tabby_shaft_word1 = compute_tabby_shaft_word1(threading=threading)
|
|
248
|
+
tabby_shaft_word2 = compute_tabby_shaft_word2(tabby_shaft_word1=tabby_shaft_word1, threading=threading)
|
|
249
|
+
return (tabby_shaft_word1, tabby_shaft_word2)
|
|
@@ -38,7 +38,7 @@ class Pick:
|
|
|
38
38
|
|
|
39
39
|
Args:
|
|
40
40
|
color: Weft color, as an index into the color table.
|
|
41
|
-
shaft_word: A bit mask, with bit 1 = shaft 0.
|
|
41
|
+
shaft_word: A bit mask, with bit 1 = shaft number 1 (index 0).
|
|
42
42
|
The shaft is up if the bit is set.
|
|
43
43
|
"""
|
|
44
44
|
|
|
@@ -62,16 +62,27 @@ class ReducedPattern:
|
|
|
62
62
|
Contains just enough information to allow loom control,
|
|
63
63
|
with a simple display.
|
|
64
64
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
Warp ends that are not threaded on any shaft are omitted.
|
|
66
|
+
Warp ends that are threaded on more than one shaft are
|
|
67
|
+
only threaded on the lowest-numbered shaft.
|
|
68
|
+
Weft picks that are not treadled are omitted.
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
Values in threading and warp_colors, are 0-based indices:
|
|
71
|
+
|
|
72
|
+
* threading is a list of shaft indices (shaft index 0 is shaft number 1).
|
|
73
|
+
* warp_colors is a list of indices into color_table.
|
|
74
|
+
Likewise for the color attribute of Pick.
|
|
75
|
+
|
|
76
|
+
pick_number and end_number0/1 are 1-based (in general "number"
|
|
77
|
+
in the name indicates it is 1-based).
|
|
78
|
+
A value of 0 indicates "no such pick" or "no such end",
|
|
79
|
+
and is the initial state of weaving and threading,
|
|
80
|
+
as well as a separator gap between pattern repeats.
|
|
70
81
|
|
|
71
82
|
pick_number and end_number0/1 are within one pattern repeat,
|
|
72
83
|
and repeats are tracked with related attributes.
|
|
73
|
-
tabby_pick_number is
|
|
74
|
-
|
|
84
|
+
However, tabby_pick_number is absolute, because repeats of tabby
|
|
85
|
+
are not interesting, and so are not tracked.
|
|
75
86
|
"""
|
|
76
87
|
|
|
77
88
|
type: str = dataclasses.field(init=False, default="ReducedPattern")
|
|
@@ -473,8 +484,9 @@ def reduced_pattern_from_pattern_data(name: str, data: dtx_to_wif.PatternData) -
|
|
|
473
484
|
|
|
474
485
|
num_ends = max(data.threading.keys())
|
|
475
486
|
end_numbers = list(range(1, num_ends + 1))
|
|
487
|
+
# Shaft numbers in threading are 0-based
|
|
476
488
|
threading = [_smallest_shaft(data.threading.get(end_number, {0})) - 1 for end_number in end_numbers]
|
|
477
|
-
|
|
489
|
+
max_threaded_shaft_number = max(threading) + 1 # +1 because 1-based
|
|
478
490
|
|
|
479
491
|
num_picks = max(data.liftplan.keys()) if data.liftplan else max(data.treadling.keys())
|
|
480
492
|
pick_numbers = list(range(1, num_picks + 1))
|
|
@@ -493,10 +505,10 @@ def reduced_pattern_from_pattern_data(name: str, data: dtx_to_wif.PatternData) -
|
|
|
493
505
|
if len(shaft_sets) != len(weft_colors):
|
|
494
506
|
raise RuntimeError(f"{len(shaft_sets)=} != {len(weft_colors)=}\n{shaft_sets=}\n{weft_colors=}")
|
|
495
507
|
try:
|
|
496
|
-
|
|
508
|
+
max_raised_shaft_number = max(max(shaft_set) for shaft_set in shaft_sets if shaft_set)
|
|
497
509
|
except (ValueError, TypeError):
|
|
498
510
|
raise RuntimeError("No shafts are raised") from None
|
|
499
|
-
all_shafts = set(range(1,
|
|
511
|
+
all_shafts = set(range(1, max_raised_shaft_number + 1))
|
|
500
512
|
if data.is_rising_shed:
|
|
501
513
|
shaft_words = [bitmask_from_bits(shaft_set) for shaft_set in shaft_sets]
|
|
502
514
|
else:
|
|
@@ -519,7 +531,7 @@ def reduced_pattern_from_pattern_data(name: str, data: dtx_to_wif.PatternData) -
|
|
|
519
531
|
threading=threading,
|
|
520
532
|
picks=picks,
|
|
521
533
|
tabby_picks=tabby_picks,
|
|
522
|
-
num_shafts=max(
|
|
534
|
+
num_shafts=max(max_raised_shaft_number, max_threaded_shaft_number),
|
|
523
535
|
separate_weaving_repeats=len(picks) > NUM_ITEMS_FOR_REPEAT_SEPARATOR,
|
|
524
536
|
separate_threading_repeats=len(threading) > NUM_ITEMS_FOR_REPEAT_SEPARATOR,
|
|
525
537
|
)
|
|
@@ -445,8 +445,9 @@ class Client:
|
|
|
445
445
|
"""
|
|
446
446
|
expected_seen_types = {
|
|
447
447
|
"CommandDone",
|
|
448
|
-
"CurrentPickNumber",
|
|
449
448
|
"CurrentEndNumber",
|
|
449
|
+
"CurrentPickNumber",
|
|
450
|
+
"CurrentTabbyPickNumber",
|
|
450
451
|
"ReducedPattern",
|
|
451
452
|
"SeparateThreadingRepeats",
|
|
452
453
|
"SeparateWeavingRepeats",
|
|
@@ -486,6 +487,8 @@ class Client:
|
|
|
486
487
|
repeat_number=pattern_in_reply.pick_repeat_number,
|
|
487
488
|
repeat_len=len(pattern_in_reply.picks),
|
|
488
489
|
)
|
|
490
|
+
case "CurrentTabbyPickNumber":
|
|
491
|
+
assert reply.tabby_pick_number == pattern_in_reply.tabby_pick_number
|
|
489
492
|
case "CurrentEndNumber":
|
|
490
493
|
assert reply.end_number0 == pattern_in_reply.end_number0
|
|
491
494
|
assert reply.end_number1 == pattern_in_reply.end_number1
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import importlib
|
|
3
|
-
|
|
3
|
+
import itertools
|
|
4
|
+
from collections.abc import Iterable, Sequence
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
def bitmask_from_bits(bit_nums: Iterable[int]) -> int:
|
|
@@ -9,6 +10,7 @@ def bitmask_from_bits(bit_nums: Iterable[int]) -> int:
|
|
|
9
10
|
Repeated values are ignored (naturally).
|
|
10
11
|
"""
|
|
11
12
|
bit_set = set(bit_nums)
|
|
13
|
+
|
|
12
14
|
return sum(1 << bit_num - 1 for bit_num in bit_set if bit_num > 0)
|
|
13
15
|
|
|
14
16
|
|
|
@@ -93,6 +95,13 @@ def get_version(package_name: str) -> str:
|
|
|
93
95
|
return str(getattr(module, "__version__", "?"))
|
|
94
96
|
|
|
95
97
|
|
|
98
|
+
def prune_duplicates(data: Sequence[int]) -> list[int]:
|
|
99
|
+
"""Prune duplicate entries in an ordered sequence."""
|
|
100
|
+
if len(data) == 0:
|
|
101
|
+
return []
|
|
102
|
+
return [data[0]] + [val1 for val0, val1 in itertools.pairwise(data) if val1 != val0]
|
|
103
|
+
|
|
104
|
+
|
|
96
105
|
async def run_shell_command(command: str) -> str:
|
|
97
106
|
"""Run a shell command and return the result.
|
|
98
107
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: base_loom_server
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.2b4
|
|
4
4
|
Summary: Base package for web servers that control dobby multi-shaft looms
|
|
5
5
|
Author-email: Russell Owen <r3owen@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://pypi.org/project/base-loom-server/
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
|
|
3
|
+
from base_loom_server.compute_tabby import (
|
|
4
|
+
compute_num_transitions,
|
|
5
|
+
compute_tabby_shaft_word1,
|
|
6
|
+
compute_tabby_shaft_word2,
|
|
7
|
+
compute_tabby_shaft_words,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
# Dict of field name: default value
|
|
11
|
+
EXPECTED_DEFAULTS = dict(
|
|
12
|
+
pick_number=0,
|
|
13
|
+
pick_repeat_number=1,
|
|
14
|
+
end_number0=0,
|
|
15
|
+
end_number1=0,
|
|
16
|
+
end_repeat_number=1,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def test_known_values() -> None:
|
|
21
|
+
"""Get the shaft set for a specified 1-based pick_number."""
|
|
22
|
+
# Explicit values use 1-based shafts, for readability,
|
|
23
|
+
# but internally the threading array uses 0-based shafts.
|
|
24
|
+
for threading_1based, expected_shaft_word1, expected_num_transitions in (
|
|
25
|
+
# Fully interlaced
|
|
26
|
+
([1, 2], 0b01, 0),
|
|
27
|
+
([1, 2, 3, 4, 3, 2, 1], 0b0101, 0),
|
|
28
|
+
([1, 2, 3, 4, 1, 2, 3], 0b0101, 0),
|
|
29
|
+
([4, 3, 2, 1, 2, 3, 4], 0b0101, 0),
|
|
30
|
+
([2, 3, 4, 5, 4, 3, 2], 0b01010, 0),
|
|
31
|
+
([3, 4, 5, 6, 5, 4, 3], 0b010100, 0),
|
|
32
|
+
([1, 10, 3, 6], 0b0000000101, 0),
|
|
33
|
+
([2, 5, 4, 9], 0b000001010, 0),
|
|
34
|
+
([2, 1, 2, 3, 4, 5], 0b10101, 0),
|
|
35
|
+
# Some repeating warp ends; ignoring those
|
|
36
|
+
# the fabric is fully interlaced.
|
|
37
|
+
([6, 5, 4, 3, 3, 4, 5], 0b10100, 5),
|
|
38
|
+
([5, 4, 3, 2, 2, 3, 4], 0b01010, 5),
|
|
39
|
+
([1, 1, 2, 2], 0b01, 1),
|
|
40
|
+
([1, 1, 1, 2, 2, 3, 3, 3], 0b101, 2),
|
|
41
|
+
# Overshot (perfect interlacemet)
|
|
42
|
+
([1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 1, 4, 1], 0b0101, 0),
|
|
43
|
+
# Bronson lace (perfect interlacemet)
|
|
44
|
+
([1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4], 0b0001, 0),
|
|
45
|
+
# Canvas weave
|
|
46
|
+
([1, 2, 2, 1, 4, 3, 3, 4], 0b0101, 5),
|
|
47
|
+
# Non-trivial cases. The even warp ends and odd warp ends
|
|
48
|
+
# are not on unique sets of shafts (after purging repeating shafts).
|
|
49
|
+
# These are the only test cases that exercise the
|
|
50
|
+
# non-simple branch of the algorithm.
|
|
51
|
+
([1, 2, 3, 1, 2, 3], 0b101, 4),
|
|
52
|
+
([1, 2, 4, 2, 3, 1], 0b1101, 4),
|
|
53
|
+
):
|
|
54
|
+
threading = [shaft - 1 for shaft in threading_1based]
|
|
55
|
+
if expected_num_transitions == 0:
|
|
56
|
+
expected_num_transitions = len(threading) - 1 # noqa: PLW2901
|
|
57
|
+
|
|
58
|
+
max_shaft_number = max(threading) + 1
|
|
59
|
+
expected_shaft_word2 = ~expected_shaft_word1 & (2**max_shaft_number - 1)
|
|
60
|
+
|
|
61
|
+
tabby_shaft_word1 = compute_tabby_shaft_word1(threading=threading)
|
|
62
|
+
tabby_shaft_word2 = compute_tabby_shaft_word2(
|
|
63
|
+
tabby_shaft_word1=tabby_shaft_word1, threading=threading
|
|
64
|
+
)
|
|
65
|
+
num_transitions = compute_num_transitions(tabby_shaft_word1, threading=threading)
|
|
66
|
+
|
|
67
|
+
if tabby_shaft_word1 != expected_shaft_word1 or num_transitions != expected_num_transitions:
|
|
68
|
+
print( # noqa: T201
|
|
69
|
+
f"Failed for {threading_1based=}; {tabby_shaft_word1=:b}, "
|
|
70
|
+
f"{expected_shaft_word1=:b}, {num_transitions=}, {expected_num_transitions=}"
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
assert tabby_shaft_word1 == expected_shaft_word1
|
|
74
|
+
assert tabby_shaft_word2 == expected_shaft_word2
|
|
75
|
+
assert num_transitions == expected_num_transitions
|
|
76
|
+
|
|
77
|
+
assert expected_shaft_word1, expected_shaft_word2 == compute_tabby_shaft_words(threading)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def test_invalid_values() -> None:
|
|
81
|
+
# Use 0-based threading here, as readability is less important
|
|
82
|
+
for threading in (
|
|
83
|
+
# Need at least two threaded warp ends
|
|
84
|
+
[],
|
|
85
|
+
[0],
|
|
86
|
+
[1],
|
|
87
|
+
# Need at least two different threaded shafts
|
|
88
|
+
[0, 0],
|
|
89
|
+
[1, 1],
|
|
90
|
+
[5, 5],
|
|
91
|
+
):
|
|
92
|
+
with pytest.raises(ValueError):
|
|
93
|
+
compute_tabby_shaft_word1(threading=threading)
|
|
94
|
+
with pytest.raises(ValueError):
|
|
95
|
+
compute_tabby_shaft_words(threading=threading)
|
|
96
|
+
if len(threading) == 0:
|
|
97
|
+
# No warp ends are threaded
|
|
98
|
+
with pytest.raises(ValueError):
|
|
99
|
+
compute_tabby_shaft_word2(tabby_shaft_word1=0b1, threading=threading)
|
|
100
|
+
else:
|
|
101
|
+
# Raise no shafts on tabby 1
|
|
102
|
+
with pytest.raises(ValueError):
|
|
103
|
+
compute_tabby_shaft_word2(tabby_shaft_word1=0, threading=threading)
|
|
104
|
+
|
|
105
|
+
# Raise all shafts on tabby 1, so none are raised on tabby 2
|
|
106
|
+
max_threaded_shaft_number = max(threading) + 1
|
|
107
|
+
all_shafts_up = 2**max_threaded_shaft_number - 1
|
|
108
|
+
with pytest.raises(ValueError):
|
|
109
|
+
compute_tabby_shaft_word2(tabby_shaft_word1=all_shafts_up, threading=threading)
|
|
@@ -8,6 +8,7 @@ from base_loom_server.utils import (
|
|
|
8
8
|
compute_num_within_and_repeats,
|
|
9
9
|
compute_total_num,
|
|
10
10
|
get_version,
|
|
11
|
+
prune_duplicates,
|
|
11
12
|
)
|
|
12
13
|
|
|
13
14
|
|
|
@@ -39,20 +40,6 @@ def test_bitmask_functions() -> None:
|
|
|
39
40
|
assert bitmask == bitmask_from_bits(bits)
|
|
40
41
|
|
|
41
42
|
|
|
42
|
-
def test_compute_total_num() -> None:
|
|
43
|
-
for num_within, repeat_number, repeat_len in itertools.product(
|
|
44
|
-
(-50, -33, -1, 0, 1, 33, 50), (-1, 0, 1), (1, 21, 33, 50)
|
|
45
|
-
):
|
|
46
|
-
total_num = compute_total_num(num_within, repeat_number, repeat_len)
|
|
47
|
-
assert total_num == (repeat_number - 1) * repeat_len + num_within
|
|
48
|
-
|
|
49
|
-
with pytest.raises(ValueError):
|
|
50
|
-
compute_total_num(num_within, repeat_number, 0)
|
|
51
|
-
|
|
52
|
-
with pytest.raises(ValueError):
|
|
53
|
-
compute_total_num(num_within, repeat_number, -1)
|
|
54
|
-
|
|
55
|
-
|
|
56
43
|
def test_compute_num_within_and_repeats() -> None:
|
|
57
44
|
for num_within, repeat_number, repeat_len in itertools.product(
|
|
58
45
|
(-50, -33, -1, 0, 1, 33, 50), (-1, 0, 1), (1, 21, 33, 50)
|
|
@@ -83,6 +70,20 @@ def test_compute_num_within_and_repeats() -> None:
|
|
|
83
70
|
compute_num_within_and_repeats(total_num, -1)
|
|
84
71
|
|
|
85
72
|
|
|
73
|
+
def test_compute_total_num() -> None:
|
|
74
|
+
for num_within, repeat_number, repeat_len in itertools.product(
|
|
75
|
+
(-50, -33, -1, 0, 1, 33, 50), (-1, 0, 1), (1, 21, 33, 50)
|
|
76
|
+
):
|
|
77
|
+
total_num = compute_total_num(num_within, repeat_number, repeat_len)
|
|
78
|
+
assert total_num == (repeat_number - 1) * repeat_len + num_within
|
|
79
|
+
|
|
80
|
+
with pytest.raises(ValueError):
|
|
81
|
+
compute_total_num(num_within, repeat_number, 0)
|
|
82
|
+
|
|
83
|
+
with pytest.raises(ValueError):
|
|
84
|
+
compute_total_num(num_within, repeat_number, -1)
|
|
85
|
+
|
|
86
|
+
|
|
86
87
|
def test_get_version() -> None:
|
|
87
88
|
assert get_version("#_invalid_package_name") == "?"
|
|
88
89
|
|
|
@@ -92,3 +93,22 @@ def test_get_version() -> None:
|
|
|
92
93
|
assert get_version("base_loom_server") == "?"
|
|
93
94
|
else:
|
|
94
95
|
assert get_version("base_loom_server") == getattr(version, "__version__", "?")
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_prune_duplicates() -> None:
|
|
99
|
+
for data in (
|
|
100
|
+
[],
|
|
101
|
+
[1, 1, 1, 2, 3, 3, 0, 3, 2, -1, -1],
|
|
102
|
+
[-1, 2, 0, 1],
|
|
103
|
+
[0, 55, 55, 22, 22, -1, -1, 55],
|
|
104
|
+
):
|
|
105
|
+
pruned_data = prune_duplicates(data)
|
|
106
|
+
# compare to a different implementation
|
|
107
|
+
desired_pruned_data: list[int] = []
|
|
108
|
+
prev_val: int | None = None
|
|
109
|
+
for end in data:
|
|
110
|
+
if end != prev_val:
|
|
111
|
+
prev_val = end
|
|
112
|
+
desired_pruned_data.append(end)
|
|
113
|
+
|
|
114
|
+
assert pruned_data == desired_pruned_data
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
from itertools import pairwise
|
|
2
|
-
|
|
3
|
-
# Maximum starting points for walking through the threading
|
|
4
|
-
# in compute_tabby_shaft_word1. This keeps the compute time sane.
|
|
5
|
-
MAX_ITER = 5
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def compute_num_transitions(tabby_shaft_word: int, threading: list[int]) -> int:
|
|
9
|
-
"""Compute the number of transitions produced by a tabby shaft word.
|
|
10
|
-
|
|
11
|
-
The largest possible value is len(threading_shaft_words) - 1.
|
|
12
|
-
|
|
13
|
-
Args:
|
|
14
|
-
tabby_shaft_word: Tabby shaft word.
|
|
15
|
-
threading: Which shaft each warp end is on.
|
|
16
|
-
"""
|
|
17
|
-
threading_shaft_words = [1 << (shaft - 1) for shaft in threading if shaft > 0]
|
|
18
|
-
return _basic_compute_num_transitions(
|
|
19
|
-
tabby_shaft_word=tabby_shaft_word, threading_shaft_words=threading_shaft_words
|
|
20
|
-
)
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
def _basic_compute_num_transitions(tabby_shaft_word: int, threading_shaft_words: list[int]) -> int:
|
|
24
|
-
"""Implementation of compute_num_transitions.
|
|
25
|
-
|
|
26
|
-
Args:
|
|
27
|
-
tabby_shaft_word: Tabby shaft word.
|
|
28
|
-
threading_shaft_words: Shaft word for each warp end.
|
|
29
|
-
"""
|
|
30
|
-
is_up_list = [bool(tsw & tabby_shaft_word) for tsw in threading_shaft_words]
|
|
31
|
-
return sum(1 for val1, val2 in pairwise(is_up_list) if val1 != val2)
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
def compute_tabby_shaft_word1(threading: list[int]) -> int:
|
|
35
|
-
"""Compute which shaft_set should go up for the best tabby pick 1.
|
|
36
|
-
|
|
37
|
-
Args:
|
|
38
|
-
threading: List of 1-based shaft number for each warp end.
|
|
39
|
-
Ends with shaft < 1 are ignored.
|
|
40
|
-
|
|
41
|
-
Returns:
|
|
42
|
-
shaft_word: Which shaft_set should be up for pick 1, 3, 5... of tabby.
|
|
43
|
-
|
|
44
|
-
Raises:
|
|
45
|
-
ValueError if there are fewer than 2 threaded warp ends,
|
|
46
|
-
or if the warp ends are threaded on fewer than 2 different shaft_set.
|
|
47
|
-
|
|
48
|
-
Notes:
|
|
49
|
-
The algorithm is as follows:
|
|
50
|
-
|
|
51
|
-
First make a pruned version of the threading with no 0s
|
|
52
|
-
(warp ends that are not threaded) and no repeating ends.
|
|
53
|
-
|
|
54
|
-
Now check the most common case: if all even (pruned) warp ends
|
|
55
|
-
are threaded on a separate set of shafts than all odd warp ends,
|
|
56
|
-
(a case that allows optimal interlacement) then return
|
|
57
|
-
a tabby shaft word that raises every shaft in the odd warp ends set.
|
|
58
|
-
|
|
59
|
-
If that fails, use a harder and less ideal algorithm:
|
|
60
|
-
Loop through the (pruned) warp ends. For each shaft that
|
|
61
|
-
has not been seen before, append it to a list of seen shafts.
|
|
62
|
-
Then make a tabby shaft word that raises every other shaft
|
|
63
|
-
of the seen shafts.
|
|
64
|
-
|
|
65
|
-
Try this several times, from different starting points
|
|
66
|
-
in the threading, and pick the tabby shaft word that gives
|
|
67
|
-
the most consecutive transitions.
|
|
68
|
-
"""
|
|
69
|
-
nonzero_threading = [shaft for shaft in threading if shaft > 0]
|
|
70
|
-
num_threaded_shafts = len(set(nonzero_threading))
|
|
71
|
-
if len(nonzero_threading) <= 1:
|
|
72
|
-
raise ValueError("Need at least 2 threaded warp ends to weave tabby.")
|
|
73
|
-
if num_threaded_shafts <= 1:
|
|
74
|
-
raise ValueError("Need at least 2 threaded shafts to weave tabby.")
|
|
75
|
-
|
|
76
|
-
# Prune repeating duplicate ends
|
|
77
|
-
pruned_threading: list[int] = []
|
|
78
|
-
prev_end = 0
|
|
79
|
-
for end in nonzero_threading:
|
|
80
|
-
if end != prev_end:
|
|
81
|
-
prev_end = end
|
|
82
|
-
pruned_threading.append(end)
|
|
83
|
-
|
|
84
|
-
# Try the common case that the odd warp ends are on one set of shafts
|
|
85
|
-
# and the even warp ends are on another set of shafts, with no overlap
|
|
86
|
-
odd_ends_shaft_set = set(pruned_threading[::2])
|
|
87
|
-
even_ends_shaft_set = set(pruned_threading[1::2])
|
|
88
|
-
if even_ends_shaft_set & odd_ends_shaft_set == set():
|
|
89
|
-
tabby_shaft_word = 0
|
|
90
|
-
for shaft in odd_ends_shaft_set:
|
|
91
|
-
tabby_shaft_word |= 1 << (shaft - 1)
|
|
92
|
-
return tabby_shaft_word
|
|
93
|
-
|
|
94
|
-
# Try the harder and less ideal algorithm.
|
|
95
|
-
threading_shaft_words = [1 << (shaft - 1) for shaft in threading if shaft > 0]
|
|
96
|
-
|
|
97
|
-
best_num_transitions = 0
|
|
98
|
-
best_tabby_shaft_word = 0
|
|
99
|
-
|
|
100
|
-
# Try starting at several different points in the threading,
|
|
101
|
-
# in order to get a somewhat better result.
|
|
102
|
-
num_threads = len(pruned_threading)
|
|
103
|
-
start_interval = ((num_threads - 1) // MAX_ITER) + 1
|
|
104
|
-
if num_threads < MAX_ITER * 2:
|
|
105
|
-
# Not many threads; don't worry about limiting the number of iterations.
|
|
106
|
-
start_interval = 1
|
|
107
|
-
niter = 0
|
|
108
|
-
for start_index in range(0, num_threads, start_interval):
|
|
109
|
-
niter += 1
|
|
110
|
-
seen_shaft_set: set[int] = set()
|
|
111
|
-
seen_shaft_arr: list[int] = []
|
|
112
|
-
for shaft in pruned_threading[start_index:] + pruned_threading[:start_index]:
|
|
113
|
-
if shaft in seen_shaft_set:
|
|
114
|
-
continue
|
|
115
|
-
seen_shaft_set.add(shaft)
|
|
116
|
-
seen_shaft_arr.append(shaft)
|
|
117
|
-
if len(seen_shaft_set) == num_threaded_shafts:
|
|
118
|
-
# We have seen all the shafts; stop
|
|
119
|
-
break
|
|
120
|
-
|
|
121
|
-
tabby_shaft_word = 0
|
|
122
|
-
for shaft in seen_shaft_arr[::2]:
|
|
123
|
-
tabby_shaft_word |= 1 << (shaft - 1)
|
|
124
|
-
|
|
125
|
-
num_transitions = _basic_compute_num_transitions(
|
|
126
|
-
tabby_shaft_word=tabby_shaft_word, threading_shaft_words=threading_shaft_words
|
|
127
|
-
)
|
|
128
|
-
if num_transitions > best_num_transitions:
|
|
129
|
-
best_num_transitions = num_transitions
|
|
130
|
-
best_tabby_shaft_word = tabby_shaft_word
|
|
131
|
-
if num_transitions == len(pruned_threading) - 1:
|
|
132
|
-
# Unlikely, since the simple case failed
|
|
133
|
-
# but it's quick to check and we can't do better
|
|
134
|
-
break
|
|
135
|
-
return best_tabby_shaft_word
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
def compute_tabby_shaft_word2(tabby_shaft_word1: int, max_threaded_shaft: int) -> int:
|
|
139
|
-
"""Compute tabby_shaft_word2 as the complement of tabby_shaft_word1.
|
|
140
|
-
|
|
141
|
-
Args:
|
|
142
|
-
tabby_shaft_word1: tabby shaft word computed by compute_tabby_shaft_word1.
|
|
143
|
-
max_threaded_shaft: max(threading); the maximum shaft number (1-based)
|
|
144
|
-
that has any warp strings threaded on it. All bits beyond that
|
|
145
|
-
will be 0.
|
|
146
|
-
|
|
147
|
-
Returns:
|
|
148
|
-
tabby_shaft_word2: which shaft_set should be up picks 2, 4, 6... of tabby.
|
|
149
|
-
The complement tabby_shaft_word1, will all shaft_set beyond max_threaded_shaft
|
|
150
|
-
set to 0.
|
|
151
|
-
|
|
152
|
-
Raises:
|
|
153
|
-
ValueError if max_threaded_shaft < 2.
|
|
154
|
-
"""
|
|
155
|
-
if tabby_shaft_word1 < 1:
|
|
156
|
-
raise ValueError(f"{tabby_shaft_word1=} must be positive")
|
|
157
|
-
if max_threaded_shaft <= 1:
|
|
158
|
-
raise ValueError(f"{max_threaded_shaft=} must be > 1")
|
|
159
|
-
all_shaft_set_mask = (1 << max_threaded_shaft) - 1
|
|
160
|
-
return ~tabby_shaft_word1 & all_shaft_set_mask
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def compute_tabby_shaft_words(threading: list[int]) -> tuple[int, int]:
|
|
164
|
-
"""Compute tabby_shaft_words 1 and 2.
|
|
165
|
-
|
|
166
|
-
Args:
|
|
167
|
-
threading: List of 1-based shaft number for each warp end.
|
|
168
|
-
Ends with shaft < 1 are ignored.
|
|
169
|
-
|
|
170
|
-
Returns:
|
|
171
|
-
(shaft_word1, shaft_word2): Which shaft_set should be up for the two picks of tabby
|
|
172
|
-
(word 1 is for picks 1, 3, 5.., word 2 is for picks 2, 4, 6...).
|
|
173
|
-
|
|
174
|
-
Raises:
|
|
175
|
-
ValueError if there are fewer than 2 threaded warp ends,
|
|
176
|
-
or if the warp ends are threaded on fewer than 2 different shaft_set.
|
|
177
|
-
"""
|
|
178
|
-
tabby_shaft_word1 = compute_tabby_shaft_word1(threading=threading)
|
|
179
|
-
max_threaded_shaft = max(threading)
|
|
180
|
-
tabby_shaft_word2 = compute_tabby_shaft_word2(
|
|
181
|
-
tabby_shaft_word1=tabby_shaft_word1, max_threaded_shaft=max_threaded_shaft
|
|
182
|
-
)
|
|
183
|
-
return (tabby_shaft_word1, tabby_shaft_word2)
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import itertools
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
|
|
5
|
-
from base_loom_server.compute_tabby import (
|
|
6
|
-
compute_num_transitions,
|
|
7
|
-
compute_tabby_shaft_word1,
|
|
8
|
-
compute_tabby_shaft_word2,
|
|
9
|
-
compute_tabby_shaft_words,
|
|
10
|
-
)
|
|
11
|
-
|
|
12
|
-
# Dict of field name: default value
|
|
13
|
-
EXPECTED_DEFAULTS = dict(
|
|
14
|
-
pick_number=0,
|
|
15
|
-
pick_repeat_number=1,
|
|
16
|
-
end_number0=0,
|
|
17
|
-
end_number1=0,
|
|
18
|
-
end_repeat_number=1,
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def test_known_values() -> None:
|
|
23
|
-
"""Get the shaft set for a specified 1-based pick_number."""
|
|
24
|
-
for threading, expected_shaft_word1, expected_shaft_word2, expected_num_transitions in (
|
|
25
|
-
# Fully interlaced
|
|
26
|
-
([1, 2], 0b01, 0b10, 0),
|
|
27
|
-
([1, 2, 3, 4, 3, 2, 1], 0b0101, 0b1010, 0),
|
|
28
|
-
([1, 2, 3, 4, 1, 2, 3], 0b0101, 0b1010, 0),
|
|
29
|
-
([4, 3, 2, 1, 2, 3, 4], 0b1010, 0b0101, 0),
|
|
30
|
-
([2, 3, 4, 5, 4, 3, 2], 0b01010, 0b10101, 0),
|
|
31
|
-
([3, 4, 5, 6, 5, 4, 3], 0b010100, 0b101011, 0),
|
|
32
|
-
([1, 10, 3, 6], 0b0000000101, 0b1111111010, 0),
|
|
33
|
-
([2, 5, 4, 9], 0b000001010, 0b111110101, 0),
|
|
34
|
-
([2, 1, 2, 3, 4, 5], 0b01010, 0b10101, 0),
|
|
35
|
-
# Some repeating warp ends; ignoring those
|
|
36
|
-
# the fabric is fully interlaced.
|
|
37
|
-
([6, 5, 4, 3, 3, 4, 5], 0b101000, 0b010111, 5),
|
|
38
|
-
([5, 4, 3, 2, 2, 3, 4], 0b10100, 0b01011, 5),
|
|
39
|
-
([1, 1, 2, 2], 0b01, 0b10, 1),
|
|
40
|
-
([1, 1, 1, 2, 2, 3, 3, 3], 0b101, 0b010, 2),
|
|
41
|
-
# Overshot (perfect interlacemet)
|
|
42
|
-
([1, 2, 1, 2, 3, 2, 3, 4, 3, 4, 1, 4, 1], 0b0101, 0b1010, 0),
|
|
43
|
-
# Bronson lace (perfect interlacemet)
|
|
44
|
-
([1, 2, 1, 2, 1, 3, 1, 3, 1, 4, 1, 4], 0b0001, 0b1110, 0),
|
|
45
|
-
# Canvas weave
|
|
46
|
-
([1, 2, 2, 1, 4, 3, 3, 4], 0b0101, 0b1010, 5),
|
|
47
|
-
# Non-trivial cases. The even warp ends and odd warp ends
|
|
48
|
-
# are not on unique sets of shafts (after purging repeating shafts).
|
|
49
|
-
# These are the only test cases that exercise the
|
|
50
|
-
# non-simple branch of the algorithm.
|
|
51
|
-
([1, 2, 3, 1, 2, 3], 0b101, 0b010, 4),
|
|
52
|
-
([1, 2, 4, 2, 3, 1], 0b1001, 0b0110, 4),
|
|
53
|
-
):
|
|
54
|
-
if expected_num_transitions == 0:
|
|
55
|
-
expected_num_transitions = len(threading) - 1 # noqa: PLW2901
|
|
56
|
-
|
|
57
|
-
tabby_shaft_word1 = compute_tabby_shaft_word1(threading=threading)
|
|
58
|
-
|
|
59
|
-
max_threaded_shaft = max(threading)
|
|
60
|
-
tabby_shaft_word2 = compute_tabby_shaft_word2(
|
|
61
|
-
tabby_shaft_word1=tabby_shaft_word1, max_threaded_shaft=max_threaded_shaft
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
num_transitions = compute_num_transitions(tabby_shaft_word1, threading=threading)
|
|
65
|
-
|
|
66
|
-
assert tabby_shaft_word1 == expected_shaft_word1
|
|
67
|
-
assert tabby_shaft_word2 == expected_shaft_word2
|
|
68
|
-
assert num_transitions == expected_num_transitions
|
|
69
|
-
|
|
70
|
-
assert expected_shaft_word1, expected_shaft_word2 == compute_tabby_shaft_words(threading)
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def test_invalid_values() -> None:
|
|
74
|
-
for threading in (
|
|
75
|
-
# Need at least two threaded warp ends
|
|
76
|
-
[],
|
|
77
|
-
[0],
|
|
78
|
-
[1],
|
|
79
|
-
[0, 0],
|
|
80
|
-
[0, 1],
|
|
81
|
-
[1, 0],
|
|
82
|
-
# Need at least two different threaded shafts
|
|
83
|
-
[1, 1],
|
|
84
|
-
[5, 5],
|
|
85
|
-
):
|
|
86
|
-
with pytest.raises(ValueError):
|
|
87
|
-
compute_tabby_shaft_word1(threading=threading)
|
|
88
|
-
with pytest.raises(ValueError):
|
|
89
|
-
compute_tabby_shaft_words(threading=threading)
|
|
90
|
-
|
|
91
|
-
for tabby_shaft_word1, max_threaded_shaft in itertools.product((-1, 0, 1, 2), (-1, 0, 1, 2, 3)):
|
|
92
|
-
if tabby_shaft_word1 > 0 and max_threaded_shaft > 1:
|
|
93
|
-
# A valid combination
|
|
94
|
-
continue
|
|
95
|
-
with pytest.raises(ValueError):
|
|
96
|
-
compute_tabby_shaft_word2(
|
|
97
|
-
tabby_shaft_word1=tabby_shaft_word1, max_threaded_shaft=max_threaded_shaft
|
|
98
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/settings_safari_macos.jpg
RENAMED
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/show_right_panel_icon.jpg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/translate_icon.jpg
RENAMED
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/translate_panel.jpg
RENAMED
|
File without changes
|
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/docs/images/screen_shots/weaving_safari_macos.jpg
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/check_translation_files.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/example_loom_server.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Espa/303/261ol.json"
RENAMED
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Fran/303/247ais.json"
RENAMED
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Italiano.json
RENAMED
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/locales/Nederlands.json
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server/rename_crowdin_files.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server.egg-info/requires.txt
RENAMED
|
File without changes
|
{base_loom_server-1.2b2 → base_loom_server-1.2b4}/src/base_loom_server.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|