inkstitch/lib/tartan/palette.py

266 wiersze
9.9 KiB
Python

# 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 List
import wx
from inkex import Color
from .colors import string_to_color
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_info = stripe_sizer.GetSizer()
for color in stripe_info.GetChildren():
widget = color.GetWindow()
if isinstance(widget, wx.CheckBox):
# in embroidery it is ok to have gaps between the stripes
stripe['render'] = widget.Get3StateValue()
elif isinstance(widget, wx.ColourPickerCtrl):
stripe['color'] = widget.GetColour().GetAsString(wx.C2S_HTML_SYNTAX)
elif isinstance(widget, wx.SpinCtrlDouble):
stripe['width'] = widget.GetValue()
elif isinstance(widget, wx.Button) or isinstance(widget, wx.StaticText):
continue
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)
except wx.PyNoAppError:
# however when we render an embroidery element we do not want to open wx.App
color = str(Color(color).to_named())
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