1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
|
# Authors: see git history
#
# Copyright (c) 2023 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
# Additional credits to: https://github.com/clsn/pyTartan
import re
from typing import TYPE_CHECKING, List, cast
import wx
from inkex import Color, ColorError
from .colors import string_to_color
if TYPE_CHECKING:
from ..gui.tartan.stripe_panel import StripePanel
class Palette:
"""Holds information about the tartan palette"""
def __init__(
self,
palette_code: str = '',
palette_stripes: List[list] = [[], []],
symmetry: bool = True,
equal_warp_weft: bool = True,
tt_unit: float = 0.5
) -> None:
"""
:param palette_code: the palette code
:param palette_stripes: the palette stripes, lists of warp and weft stripe dictionaries
:param symmetry: reflective sett (True) / repeating sett (False)
:param equal_warp_weft:wether warp and weft are equal or not
:param tt_unit: mm per thread (used for the scottish register threadcount)
"""
self.palette_code = palette_code
self.palette_stripes = palette_stripes
self.symmetry = symmetry
self.equal_warp_weft = equal_warp_weft
self.tt_unit = tt_unit
def __repr__(self) -> str:
return self.palette_code
def update_symmetry(self, symmetry: bool) -> None:
self.symmetry = symmetry
self.update_code()
def update_from_stripe_sizer(self, sizers: List[wx.BoxSizer], symmetry: bool = True, equal_warp_weft: bool = True) -> None:
"""
Update palette code from stripes (customize panel)
:param sizers: a list of the stripe sizers
:param symmetry: reflective sett (True) / repeating sett (False)
:param equal_warp_weft: wether warp and weft are equal or not
"""
self.symmetry = symmetry
self.equal_warp_weft = equal_warp_weft
self.palette_stripes = [[], []]
for i, outer_sizer in enumerate(sizers):
stripes = []
for stripe_sizer in outer_sizer.Children:
stripe = {'render': 1, 'color': '#000000', 'width': '5'}
stripe_panel = cast('StripePanel', stripe_sizer.GetWindow())
stripe['render'] = stripe_panel.visibility.Get3StateValue()
stripe['color'] = stripe_panel.colorpicker.GetColour().GetAsString(wx.C2S_HTML_SYNTAX) # type: ignore[attr-defined]
stripe['width'] = stripe_panel.stripe_width.GetValue()
stripes.append(stripe)
self.palette_stripes[i] = stripes
if self.equal_warp_weft:
self.palette_stripes[1] = stripes
break
self.update_code()
def update_from_code(self, code: str) -> None:
"""
Update stripes (customize panel) according to the code applied by the user
Converts code to valid Ink/Stitch code
:param code: the tartan pattern code to apply
"""
self.symmetry = True
if '...' in code:
self.symmetry = False
self.equal_warp_weft = True
if '|' in code:
self.equal_warp_weft = False
code = code.replace('/', '')
code = code.replace('...', '')
self.palette_stripes = [[], []]
if "Threadcount" in code:
self.parse_threadcount_code(code)
elif '(' in code:
self.parse_inkstitch_code(code)
else:
self.parse_simple_code(code)
if self.equal_warp_weft:
self.palette_stripes[1] = self.palette_stripes[0]
self.update_code()
def update_code(self) -> None:
"""Updates the palette code, reading from stripe settings (customize panel)"""
code = []
for i, direction in enumerate(self.palette_stripes):
for stripe in direction:
if stripe['render'] == 0:
render = '?'
elif stripe['render'] == 2:
render = '*'
else:
render = ''
code.append(f"({stripe['color']}){render}{stripe['width']}")
if i == 0 and self.equal_warp_weft is False:
code.append("|")
else:
break
if self.symmetry and len(code) > 0:
code[0] = code[0].replace(')', ')/')
code[-1] = code[-1].replace(')', ')/')
code_str = ' '.join(code)
if not self.symmetry:
code_str = f'...{code}...'
self.palette_code = code_str
def parse_simple_code(self, code: str) -> None:
"""Example code:
B24 W4 B24 R2 K24 G24 W2
Each letter stands for a color defined in .colors.py (if not recognized, defaults to black)
The number indicates the threadcount (width) of the stripe
The width of one thread is user defined
:param code: the tartan pattern code to apply
"""
stripes = []
stripe_info = re.findall(r'([a-zA-Z]+)(\?|\*)?([0-9.]*)', code)
for color, render, width in stripe_info:
if not width:
continue
color = string_to_color(color)
width = float(width) * self.tt_unit
if not color:
color = '#000000'
render = 0
elif render == '?':
render = 0
elif render == '*':
render = 2
else:
render = 1
stripes.append({'render': render, 'color': color, 'width': float(width)})
self.palette_stripes[0] = stripes
def parse_inkstitch_code(self, code_str: str) -> None:
"""Example code:
(#0000FF)/2.4 (#FFFFFF)0.4 (#0000FF)2.4 (#FF0000)0.2 (#000000)2.4 (#006400)2.4 (#FFFFFF)/0.2
| = separator warp and weft (if not equal)
/ = indicates a symmetric sett
... = indicates an asymmetric sett
:param code_str: the tartan pattern code to apply
"""
code = code_str.split('|')
for i, direction in enumerate(code):
stripes = []
stripe_info = re.findall(r'\(([0-9A-Za-z#]+)\)(\?|\*)?([0-9.]+)', direction)
for color, render, width in stripe_info:
try:
# on macOS we need to run wxpython color method inside the app otherwise
# the color picker has issues in some cases to accept our input
color = wx.Colour(color).GetAsString(wx.C2S_HTML_SYNTAX) # type: ignore[attr-defined]
except wx.PyNoAppError: # type: ignore[attr-defined]
# however when we render an embroidery element we do not want to open wx.App
try:
color = str(Color(color).to_named())
except ColorError:
color = None
if not color:
color = '#000000'
render = 0
elif render == '?':
render = 0
elif render == '*':
render = 2
else:
render = 1
stripes.append({'render': render, 'color': color, 'width': float(width)})
self.palette_stripes[i] = stripes
def parse_threadcount_code(self, code: str) -> None:
"""Read in and work directly from a tartanregister.gov.uk threadcount response
Example code:
Threadcount:
B24W4B24R2K24G24W2
Pallet:
B=0000FFBLUE;W=FFFFFFWHITE;R=FF0000RED;K=000000BLACK;G=289C18GREEN;
Threadcount given over a half sett with full count at the pivots.
Colors in the threadcount are defined by Letters. The Palette section declares the rgb value
:param code: the tartan pattern code to apply
"""
if 'full sett' in code:
self.symmetry = False
else:
self.symmetry = True
colors = []
color_dict = dict()
thread_code = ''
stripes = []
lines = code.splitlines()
i = 0
while i < len(lines):
line = lines[i].strip()
if 'Threadcount:' in line and len(lines) > i + 1:
thread_code = lines[i+1]
elif 'Pallet:' in line and len(lines) > i + 1:
palette = lines[i+1]
colors = re.findall(r'([A-Za-z]+)=#?([0-9afA-F]{6})', palette)
color_dict = dict(colors)
i += 1
stripe_info = re.findall(r'([a-zA-Z]+)([0-9.]*)', thread_code)
for color, width in stripe_info:
render = 1
try:
color = f'#{color_dict[color]}'
except KeyError:
color = '#000000'
render = 0
width = float(width) * self.tt_unit
stripes.append({'render': render, 'color': color, 'width': width})
self.palette_stripes[0] = stripes
def get_palette_width(self, scale: int, min_width: float, direction: int = 0) -> float:
"""
Get the rendered width of the tartan palette
:param scale: the scale value (percent) for the pattern
:param min_width: min stripe width (before it is rendered as running stitch).
Smaller stripes have 0 width.
:param direction: 0 (warp) or 1 (weft)
:returns: the width of all tartan stripes in given direction
"""
width = 0
stroke_width = 0
for stripe in self.palette_stripes[direction]:
stripe_width = stripe['width'] * (scale / 100)
if stripe_width >= min_width and stripe['render'] != 2:
width += stripe_width
elif stripe_width < min_width and stripe['render'] != 0:
stroke_width += stripe_width
if width == 0:
width = stroke_width
return width
|