diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/extensions/__init__.py | 2 | ||||
| -rw-r--r-- | lib/extensions/lettering.py | 88 | ||||
| -rw-r--r-- | lib/extensions/lettering_generate_json.py | 36 | ||||
| -rw-r--r-- | lib/extensions/lettering_update_json_glyphlist.py | 41 | ||||
| -rw-r--r-- | lib/extensions/zip.py | 39 | ||||
| -rwxr-xr-x | lib/inx/extensions.py | 2 | ||||
| -rw-r--r-- | lib/lettering/categories.py | 30 | ||||
| -rw-r--r-- | lib/lettering/font.py | 2 | ||||
| -rw-r--r-- | lib/lettering/font_info.py (renamed from lib/lettering/kerning.py) | 14 | ||||
| -rw-r--r-- | lib/stitch_plan/color_block.py | 23 | ||||
| -rw-r--r-- | lib/stitch_plan/stitch.py | 10 | ||||
| -rw-r--r-- | lib/stitch_plan/stitch_plan.py | 9 | ||||
| -rw-r--r-- | lib/threads/color.py | 19 |
13 files changed, 269 insertions, 46 deletions
diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py index 25d3214c..d0900f2d 100644 --- a/lib/extensions/__init__.py +++ b/lib/extensions/__init__.py @@ -32,6 +32,7 @@ from .lettering_custom_font_dir import LetteringCustomFontDir from .lettering_force_lock_stitches import LetteringForceLockStitches from .lettering_generate_json import LetteringGenerateJson from .lettering_remove_kerning import LetteringRemoveKerning +from .lettering_update_json_glyphlist import LetteringUpdateJsonGlyphlist from .letters_to_font import LettersToFont from .object_commands import ObjectCommands from .object_commands_toggle_visibility import ObjectCommandsToggleVisibility @@ -84,6 +85,7 @@ __all__ = extensions = [StitchPlanPreview, AutoRun, Lettering, LetteringGenerateJson, + LetteringUpdateJsonGlyphlist, LetteringRemoveKerning, LetteringCustomFontDir, LetteringForceLockStitches, diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py index fec48100..0879c30f 100644 --- a/lib/extensions/lettering.py +++ b/lib/extensions/lettering.py @@ -18,13 +18,14 @@ from ..elements import nodes_to_elements from ..gui import PresetsPanel, SimulatorPreview, info_dialog from ..i18n import _ from ..lettering import Font, FontError +from ..lettering.categories import FONT_CATEGORIES, FontCategory from ..svg import get_correction_transform from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_LETTERING, SVG_GROUP_TAG, SVG_PATH_TAG) from ..utils import DotDict, cache, get_bundled_dir, get_resource_dir +from ..utils.threading import ExitThread from .commands import CommandsExtension from .lettering_custom_font_dir import get_custom_font_dir -from ..utils.threading import ExitThread class LetteringFrame(wx.Frame): @@ -52,19 +53,32 @@ class LetteringFrame(wx.Frame): self.font_chooser = wx.adv.BitmapComboBox(self, wx.ID_ANY, style=wx.CB_READONLY | wx.CB_SORT) self.font_chooser.Bind(wx.EVT_COMBOBOX, self.on_font_changed) - self.font_filter = fs.FloatSpin(self, min_val=0, max_val=None, increment=1, value="0") - self.font_filter.SetFormat("%f") - self.font_filter.SetDigits(2) - self.font_filter.Bind(fs.EVT_FLOATSPIN, self.on_filter_changed) - self.font_filter.SetToolTip(_("Font size filter (mm). 0 for all sizes.")) - - self.update_font_list() - self.set_font_list() + self.font_size_filter = fs.FloatSpin(self, min_val=0, max_val=None, increment=1, value="0") + self.font_size_filter.SetFormat("%f") + self.font_size_filter.SetDigits(2) + self.font_size_filter.Bind(fs.EVT_FLOATSPIN, self.on_filter_changed) + self.font_size_filter.SetToolTip(_("Font size filter (mm). 0 for all sizes.")) + + self.font_glyph_filter = wx.CheckBox(self, label=_("Glyphs")) + self.font_glyph_filter.Bind(wx.EVT_CHECKBOX, self.on_filter_changed) + self.font_glyph_filter.SetToolTip(_("Filter fonts by available glyphs.")) + + self.font_category_filter = wx.ComboBox(self, wx.ID_ANY, choices=[], style=wx.CB_DROPDOWN | wx.CB_READONLY) + unfiltered = FontCategory('unfiltered', "---") + self.font_category_filter.Append(unfiltered.name, unfiltered) + for category in FONT_CATEGORIES: + self.font_category_filter.Append(category.name, category) + self.font_category_filter.SetToolTip(_("Filter fonts by category.")) + self.font_category_filter.SetSelection(0) + self.font_category_filter.Bind(wx.EVT_COMBOBOX, self.on_filter_changed) # font details self.font_description = wx.StaticText(self, wx.ID_ANY) self.Bind(wx.EVT_SIZE, self.resize) + # font filter + self.filter_box = wx.StaticBox(self, wx.ID_ANY, label=_("Font Filter")) + # options self.options_box = wx.StaticBox(self, wx.ID_ANY, label=_("Options")) @@ -95,6 +109,10 @@ class LetteringFrame(wx.Frame): self.apply_button = wx.Button(self, wx.ID_ANY, _("Apply and Quit")) self.apply_button.Bind(wx.EVT_BUTTON, self.apply) + # set font list + self.update_font_list() + self.set_font_list() + self.__do_layout() self.load_settings() @@ -165,10 +183,26 @@ class LetteringFrame(wx.Frame): self.fonts = {} self.fonts_by_id = {} - filter_size = self.font_filter.GetValue() + # font size filter value + filter_size = self.font_size_filter.GetValue() + filter_glyph = self.font_glyph_filter.GetValue() + filter_category = self.font_category_filter.GetSelection() - 1 + + # glyph filter string without spaces + glyphs = [*self.text_editor.GetValue().replace(" ", "").replace("\n", "")] + for font in self.font_list: + if filter_glyph and glyphs and not set(glyphs).issubset(font.available_glyphs): + continue + + if filter_category != -1: + category = FONT_CATEGORIES[filter_category].id + if category not in font.keywords: + continue + if filter_size != 0 and (filter_size < font.size * font.min_scale or filter_size > font.size * font.max_scale): continue + self.fonts[font.marked_custom_font_name] = font self.fonts_by_id[font.marked_custom_font_id] = font @@ -208,7 +242,7 @@ class LetteringFrame(wx.Frame): try: font = self.fonts_by_id[font_id].marked_custom_font_name except KeyError: - font = self.default_font.name + font = self.default_font.marked_custom_font_name self.font_chooser.SetValue(font) self.on_font_changed() @@ -222,6 +256,8 @@ class LetteringFrame(wx.Frame): def on_change(self, attribute, event): self.settings[attribute] = event.GetEventObject().GetValue() + if attribute == "text" and self.font_glyph_filter.GetValue() is True: + self.on_filter_changed() self.preview.update() def on_trim_option_change(self, event=None): @@ -232,7 +268,7 @@ class LetteringFrame(wx.Frame): font = self.fonts.get(self.font_chooser.GetValue(), self.default_font) self.settings.font = font.marked_custom_font_id - filter_size = self.font_filter.GetValue() + filter_size = self.font_size_filter.GetValue() self.scale_spinner.SetRange(int(font.min_scale * 100), int(font.max_scale * 100)) if filter_size != 0: self.scale_spinner.SetValue(int(filter_size / font.size * 100)) @@ -245,7 +281,7 @@ class LetteringFrame(wx.Frame): pass # Update font description - color = (0, 0, 0) + color = wx.NullColour description = font.description if len(font_variants) == 0: color = (255, 0, 0) @@ -271,17 +307,17 @@ class LetteringFrame(wx.Frame): if not self.fonts: # No fonts for filtered size self.font_chooser.Clear() - self.filter_label.SetForegroundColour("red") + self.filter_box.SetForegroundColour("red") return else: - self.filter_label.SetForegroundColour("black") + self.filter_box.SetForegroundColour(wx.NullColour) - filter_size = self.font_filter.GetValue() + filter_size = self.font_size_filter.GetValue() previous_font = self.font_chooser.GetValue() self.set_font_list() font = self.fonts.get(previous_font, self.default_font) - self.font_chooser.SetValue(font.name) - if font.name != previous_font: + self.font_chooser.SetValue(font.marked_custom_font_name) + if font.marked_custom_font_name != previous_font: self.on_font_changed() elif filter_size != 0: self.scale_spinner.SetValue(int(filter_size / font.size * 100)) @@ -396,19 +432,27 @@ class LetteringFrame(wx.Frame): font_selector_sizer = wx.StaticBoxSizer(self.font_selector_box, wx.VERTICAL) font_selector_box = wx.BoxSizer(wx.HORIZONTAL) font_selector_box.Add(self.font_chooser, 4, wx.EXPAND | wx.TOP | wx.BOTTOM | wx.RIGHT, 10) - self.filter_label = wx.StaticText(self, wx.ID_ANY, _("Filter")) - font_selector_box.Add(self.filter_label, 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 0) - font_selector_box.Add(self.font_filter, 1, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 5) font_selector_sizer.Add(font_selector_box, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) font_selector_sizer.Add(self.font_description, 1, wx.EXPAND | wx.ALL, 10) outer_sizer.Add(font_selector_sizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) + # filter fon list + filter_sizer = wx.StaticBoxSizer(self.filter_box, wx.HORIZONTAL) + filter_size_label = wx.StaticText(self, wx.ID_ANY, _("Size")) + filter_sizer.Add(filter_size_label, 0, wx.LEFT | wx.TOP | wx.BOTTOM, 10) + filter_sizer.AddSpacer(5) + filter_sizer.Add(self.font_size_filter, 1, wx.RIGHT | wx.TOP | wx.BOTTOM, 10) + filter_sizer.AddSpacer(5) + filter_sizer.Add(self.font_glyph_filter, 1, wx.RIGHT | wx.TOP | wx.BOTTOM, 10) + filter_sizer.Add(self.font_category_filter, 1, wx.RIGHT | wx.TOP | wx.BOTTOM, 10) + outer_sizer.Add(filter_sizer, 0, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 10) + # options left_option_sizer = wx.BoxSizer(wx.VERTICAL) left_option_sizer.Add(self.back_and_forth_checkbox, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT, 5) trim_option_sizer = wx.BoxSizer(wx.HORIZONTAL) - trim_option_sizer.Add(wx.StaticText(self, wx.ID_ANY, _("Add trims")), 0, wx.LEFT | wx.ALIGN_CENTRE_VERTICAL, 5) + trim_option_sizer.Add(wx.StaticText(self, wx.ID_ANY, _("Add trims")), 0, wx.LEFT | wx.ALIGN_TOP, 5) trim_option_sizer.Add(self.trim_option_choice, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) trim_option_sizer.Add(self.use_trim_symbols, 1, wx.EXPAND | wx.LEFT | wx.TOP | wx.RIGHT | wx.BOTTOM, 5) left_option_sizer.Add(trim_option_sizer, 0, wx.ALIGN_LEFT, 5) diff --git a/lib/extensions/lettering_generate_json.py b/lib/extensions/lettering_generate_json.py index 85c918b2..a884ccac 100644 --- a/lib/extensions/lettering_generate_json.py +++ b/lib/extensions/lettering_generate_json.py @@ -10,7 +10,8 @@ import sys from inkex import Boolean from ..i18n import _ -from ..lettering.kerning import FontKerning +from ..lettering.categories import FONT_CATEGORIES +from ..lettering.font_info import FontFileInfo from .base import InkstitchExtension @@ -20,6 +21,11 @@ class LetteringGenerateJson(InkstitchExtension): ''' def __init__(self, *args, **kwargs): InkstitchExtension.__init__(self, *args, **kwargs) + self.arg_parser.add_argument("--options") + self.arg_parser.add_argument("--general") + self.arg_parser.add_argument("--settings") + self.arg_parser.add_argument("--kerning") + self.arg_parser.add_argument("-n", "--font-name", type=str, default="Font", dest="font_name") self.arg_parser.add_argument("-d", "--font-description", type=str, default="Description", dest="font_description") self.arg_parser.add_argument("-s", "--auto-satin", type=Boolean, default="true", dest="auto_satin") @@ -35,6 +41,9 @@ class LetteringGenerateJson(InkstitchExtension): self.arg_parser.add_argument("-w", "--word-spacing", type=int, default=26, dest="word_spacing") self.arg_parser.add_argument("-p", "--font-file", type=str, default="", dest="path") + for category in FONT_CATEGORIES: + self.arg_parser.add_argument(f"--{category.id}", type=Boolean, default="false", dest=category.id) + def effect(self): # file paths path = self.options.path @@ -43,20 +52,20 @@ class LetteringGenerateJson(InkstitchExtension): return output_path = os.path.join(os.path.dirname(path), 'font.json') - # kerning - kerning = FontKerning(path) + # font info (kerning, glyphs) + font_info = FontFileInfo(path) - horiz_adv_x = kerning.horiz_adv_x() - hkern = kerning.hkern() + horiz_adv_x = font_info.horiz_adv_x() + hkern = font_info.hkern() custom_leading = self.options.use_custom_leading custom_spacing = self.options.use_custom_spacing - word_spacing = kerning.word_spacing() + word_spacing = font_info.word_spacing() # use user input in case that the default word spacing is not defined # in the svg file or the user forces custom values if custom_spacing or not word_spacing: word_spacing = self.options.word_spacing - letter_spacing = kerning.letter_spacing() - units_per_em = kerning.units_per_em() or self.options.leading + letter_spacing = font_info.letter_spacing() + units_per_em = font_info.units_per_em() or self.options.leading # use units_per_em for leading (line height) if defined in the font file, # unless the user wishes to overwrite the value if units_per_em and not custom_leading: @@ -64,9 +73,17 @@ class LetteringGenerateJson(InkstitchExtension): else: leading = self.options.leading + glyphs = font_info.glyph_list() + + keywords = [] + for category in FONT_CATEGORIES: + if getattr(self.options, category.id): + keywords.append(category.id) + # collect data data = {'name': self.options.font_name, 'description': self.options.font_description, + 'keywords': keywords, 'leading': leading, 'auto_satin': self.options.auto_satin, 'reversible': self.options.reversible, @@ -79,7 +96,8 @@ class LetteringGenerateJson(InkstitchExtension): 'horiz_adv_x_space': word_spacing, 'units_per_em': units_per_em, 'horiz_adv_x': horiz_adv_x, - 'kerning_pairs': hkern + 'kerning_pairs': hkern, + 'glyphs': glyphs } # write data to font.json into the same directory as the font file diff --git a/lib/extensions/lettering_update_json_glyphlist.py b/lib/extensions/lettering_update_json_glyphlist.py new file mode 100644 index 00000000..4dea6d51 --- /dev/null +++ b/lib/extensions/lettering_update_json_glyphlist.py @@ -0,0 +1,41 @@ +# Authors: see git history +# +# Copyright (c) 2010 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +import json +import os +import sys + +from ..i18n import _ +from ..lettering.font_info import FontFileInfo +from .base import InkstitchExtension + + +class LetteringUpdateJsonGlyphlist(InkstitchExtension): + ''' + This extension helps font creators to generate the json file for the lettering tool + ''' + def __init__(self, *args, **kwargs): + InkstitchExtension.__init__(self, *args, **kwargs) + self.arg_parser.add_argument("-f", "--font-file", type=str, default="", dest="font_file") + self.arg_parser.add_argument("-j", "--json-file", type=str, default="", dest="json_file") + + def effect(self): + # file paths + font_file = self.options.font_file + json_file = self.options.json_file + if not os.path.isfile(font_file) or not os.path.isfile(json_file): + print(_("Please verify file locations."), file=sys.stderr) + return + + glyphs = FontFileInfo(font_file).glyph_list() + + with open(json_file, 'r') as font_data: + data = json.load(font_data) + + data['glyphs'] = glyphs + + # write data to font.json into the same directory as the font file + with open(json_file, 'w', encoding="utf8") as font_data: + json.dump(data, font_data, indent=4, ensure_ascii=False) diff --git a/lib/extensions/zip.py b/lib/extensions/zip.py index e80bc34c..b3183a9a 100644 --- a/lib/extensions/zip.py +++ b/lib/extensions/zip.py @@ -9,15 +9,17 @@ import tempfile from copy import deepcopy from zipfile import ZipFile +from inkex import Boolean from lxml import etree import pyembroidery -from inkex import Boolean from ..i18n import _ from ..output import write_embroidery_file from ..stitch_plan import stitch_groups_to_stitch_plan +from ..svg import PIXELS_PER_MM from ..threads import ThreadCatalog +from ..utils.geometry import Point from .base import InkstitchExtension @@ -25,6 +27,11 @@ class Zip(InkstitchExtension): def __init__(self, *args, **kwargs): InkstitchExtension.__init__(self) + self.arg_parser.add_argument('--notebook', type=Boolean, default=True) + self.arg_parser.add_argument('--file-formats', type=Boolean, default=True) + self.arg_parser.add_argument('--panelization', type=Boolean, default=True) + self.arg_parser.add_argument('--output-options', type=Boolean, default=True) + # it's kind of obnoxious that I have to do this... self.formats = [] for format in pyembroidery.supported_formats(): @@ -33,10 +40,17 @@ class Zip(InkstitchExtension): self.arg_parser.add_argument('--format-%s' % extension, type=Boolean, dest=extension) self.formats.append(extension) self.arg_parser.add_argument('--format-svg', type=Boolean, dest='svg') - self.arg_parser.add_argument('--format-threadlist', type=Boolean, dest='threadlist') self.formats.append('svg') + self.arg_parser.add_argument('--format-threadlist', type=Boolean, dest='threadlist') self.formats.append('threadlist') + self.arg_parser.add_argument('--x-repeats', type=int, dest='x_repeats', default=1) + self.arg_parser.add_argument('--y-repeats', type=int, dest='y_repeats', default=1) + self.arg_parser.add_argument('--x-spacing', type=float, dest='x_spacing', default=100) + self.arg_parser.add_argument('--y-spacing', type=float, dest='y_spacing', default=100) + + self.arg_parser.add_argument('--custom-file-name', type=str, dest='custom_file_name', default='') + def effect(self): if not self.get_elements(): return @@ -47,7 +61,10 @@ class Zip(InkstitchExtension): patches = self.elements_to_stitch_groups(self.elements) stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, min_stitch_len=min_stitch_len) - base_file_name = self.get_base_file_name() + if self.options.x_repeats != 1 or self.options.y_repeats != 1: + stitch_plan = self._make_offsets(stitch_plan) + + base_file_name = self._get_file_name() path = tempfile.mkdtemp() files = [] @@ -93,6 +110,22 @@ class Zip(InkstitchExtension): # don't let inkex output the SVG! sys.exit(0) + def _get_file_name(self): + if self.options.custom_file_name: + base_file_name = self.options.custom_file_name + else: + base_file_name = self.get_base_file_name() + return base_file_name + + def _make_offsets(self, stitch_plan): + dx = self.options.x_spacing * PIXELS_PER_MM + dy = self.options.y_spacing * PIXELS_PER_MM + offsets = [] + for x in range(self.options.x_repeats): + for y in range(self.options.y_repeats): + offsets.append(Point(x * dx, y * dy)) + return stitch_plan.make_offsets(offsets) + def get_threadlist(self, stitch_plan, design_name): ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette']) thread_used = [] diff --git a/lib/inx/extensions.py b/lib/inx/extensions.py index 0ff3e889..30fca4da 100755 --- a/lib/inx/extensions.py +++ b/lib/inx/extensions.py @@ -8,6 +8,7 @@ import pyembroidery from ..commands import (COMMANDS, GLOBAL_COMMANDS, LAYER_COMMANDS, OBJECT_COMMANDS) from ..extensions import Input, Output, extensions +from ..lettering.categories import FONT_CATEGORIES from ..threads import ThreadCatalog from .outputs import pyembroidery_output_formats from .utils import build_environment, write_inx_file @@ -51,6 +52,7 @@ def generate_extension_inx_files(): write_inx_file(name, template.render(formats=pyembroidery_output_formats(), debug_formats=pyembroidery_debug_formats(), threadcatalog=threadcatalog(), + font_categories=FONT_CATEGORIES, layer_commands=layer_commands(), object_commands=object_commands(), global_commands=global_commands())) diff --git a/lib/lettering/categories.py b/lib/lettering/categories.py new file mode 100644 index 00000000..40b41529 --- /dev/null +++ b/lib/lettering/categories.py @@ -0,0 +1,30 @@ +# Authors: see git history +# +# Copyright (c) 2023 Authors +# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. + +from ..i18n import _ + + +class FontCategory: + def __init__(self, cat_id=None, name=None): + self.id: str = cat_id + self.name: str = name + + def __repr__(self): + return "FontCategory(%s, %s)" % (self.id, self.name) + + +FONT_CATEGORIES = [ + FontCategory('applique', _("Applique")), + FontCategory('crossstitch', _("Crossstitch")), + FontCategory('display', _('Display')), + FontCategory('handwriting', _("Handwriting")), + FontCategory('italic', _("Italic")), + FontCategory('monogram', _("Monogram")), + FontCategory('multicolor', _('Multicolor')), + FontCategory('running_stitch', _('Running Stitch')), + FontCategory('sans_serif', _("Sans Serif")), + FontCategory('serif', _("Serif")), + FontCategory('tiny', _("Tiny")) +] diff --git a/lib/lettering/font.py b/lib/lettering/font.py index 77f17e7f..fb17f760 100644 --- a/lib/lettering/font.py +++ b/lib/lettering/font.py @@ -111,6 +111,7 @@ class Font(object): name = localized_font_metadata('name', '') description = localized_font_metadata('description', '') + keywords = font_metadata('keywords', '') letter_case = font_metadata('letter_case', '') default_glyph = font_metadata('default_glyph', "�") leading = font_metadata('leading', 100) @@ -119,6 +120,7 @@ class Font(object): min_scale = font_metadata('min_scale', 1.0) max_scale = font_metadata('max_scale', 1.0) size = font_metadata('size', 0) + available_glyphs = font_metadata('glyphs', []) # use values from SVG Font, example: # <font horiz-adv-x="45" ... <glyph .... horiz-adv-x="49" glyph-name="A" /> ... <hkern ... k="3"g1="A" g2="B" /> .... /> diff --git a/lib/lettering/kerning.py b/lib/lettering/font_info.py index 5596ce8a..398786a4 100644 --- a/lib/lettering/kerning.py +++ b/lib/lettering/font_info.py @@ -5,9 +5,10 @@ from inkex import NSS from lxml import etree +from ..svg.tags import INKSCAPE_LABEL -class FontKerning(object): +class FontFileInfo(object): """ This class reads kerning information from an SVG file """ @@ -123,3 +124,14 @@ class FontKerning(object): xpath = "string(.//svg:missing-glyph/@*[name()='horiz-adv-x'])" return float(self.svg.xpath(xpath, namespaces=NSS)) """ + + def glyph_list(self): + """ + Returns a list of available glyphs in the font file + """ + glyphs = [] + glyph_layers = self.svg.xpath(".//svg:g[starts-with(@inkscape:label, 'GlyphLayer-')]", namespaces=NSS) + for layer in glyph_layers: + glyph_name = layer.attrib[INKSCAPE_LABEL].replace("GlyphLayer-", "", 1) + glyphs.append(glyph_name) + return glyphs diff --git a/lib/stitch_plan/color_block.py b/lib/stitch_plan/color_block.py index 9d474f80..fdef5eb8 100644 --- a/lib/stitch_plan/color_block.py +++ b/lib/stitch_plan/color_block.py @@ -3,10 +3,12 @@ # Copyright (c) 2010 Authors # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. -from .stitch import Stitch +from typing import List + +from ..svg import PIXELS_PER_MM from ..threads import ThreadColor from ..utils.geometry import Point -from ..svg import PIXELS_PER_MM +from .stitch import Stitch class ColorBlock(object): @@ -155,3 +157,20 @@ class ColorBlock(object): maxy = max(stitch.y for stitch in self) return minx, miny, maxx, maxy + + def make_offsets(self, offsets: List[Point]): + first_final_stitch = len(self.stitches) + while (first_final_stitch > 0 and self.stitches[first_final_stitch-1].is_terminator): + first_final_stitch -= 1 + if first_final_stitch == 0: + return self + final_stitches = self.stitches[first_final_stitch:] + block_stitches = self.stitches[:first_final_stitch] + + out = ColorBlock(self.color) + for i, offset in enumerate(offsets): + out.add_stitches([s.offset(offset) for s in block_stitches]) + if i != len(offsets) - 1: + out.add_stitch(trim=True) + out.add_stitches(final_stitches) + return out diff --git a/lib/stitch_plan/stitch.py b/lib/stitch_plan/stitch.py index 90af58c0..8ad699c7 100644 --- a/lib/stitch_plan/stitch.py +++ b/lib/stitch_plan/stitch.py @@ -68,6 +68,10 @@ class Stitch(Point): if value or base_stitch is None: setattr(self, attribute, value) + @property + def is_terminator(self) -> bool: + return self.trim or self.stop or self.color_change + def add_tags(self, tags): for tag in tags: self.add_tag(tag) @@ -93,6 +97,12 @@ class Stitch(Point): def copy(self): return Stitch(self.x, self.y, self.color, self.jump, self.stop, self.trim, self.color_change, self.tags) + def offset(self, offset: Point): + out = self.copy() + out.x += offset.x + out.y += offset.y + return out + def __json__(self): attributes = dict(vars(self)) attributes['tags'] = list(attributes['tags']) diff --git a/lib/stitch_plan/stitch_plan.py b/lib/stitch_plan/stitch_plan.py index 25571578..caea9c09 100644 --- a/lib/stitch_plan/stitch_plan.py +++ b/lib/stitch_plan/stitch_plan.py @@ -4,13 +4,15 @@ # Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details. from sys import exit +from typing import List from inkex import errormsg from ..i18n import _ from ..svg import PIXELS_PER_MM -from .color_block import ColorBlock +from ..utils.geometry import Point from ..utils.threading import check_stop_flag +from .color_block import ColorBlock def stitch_groups_to_stitch_plan(stitch_groups, collapse_len=None, min_stitch_len=0.1, disable_ties=False): # noqa: C901 @@ -207,3 +209,8 @@ class StitchPlan(object): return self.color_blocks[-1] else: return None + + def make_offsets(self, offsets: List[Point]): + out = StitchPlan() + out.color_blocks = [block.make_offsets(offsets) for block in self] + return out diff --git a/lib/threads/color.py b/lib/threads/color.py index 8dc1ea01..c75778d2 100644 --- a/lib/threads/color.py +++ b/lib/threads/color.py @@ -6,19 +6,24 @@ import colorsys import re -import tinycss2.color3 -from pyembroidery.EmbThread import EmbThread - from inkex import Color +from pyembroidery.EmbThread import EmbThread class ThreadColor(object): hex_str_re = re.compile('#([0-9a-z]{3}|[0-9a-z]{6})', re.I) def __init__(self, color, name=None, number=None, manufacturer=None, description=None, chart=None): - # set colors with a gradient to black (avoiding an error message) - if type(color) == str and color.startswith('url'): + ''' + avoid error messages: + * set colors with a gradient to black + * currentColor should not just be black, but we want to avoid error messages + until inkex will be able to handle this css property + ''' + if type(color) == str and color.startswith(('url', 'currentColor')): color = None + elif type(color) == str and color.startswith('rgb'): + color = tuple(int(value) for value in color[4:-1].split(',')) if color is None: self.rgb = (0, 0, 0) @@ -31,9 +36,7 @@ class ThreadColor(object): self.rgb = (color.get_red(), color.get_green(), color.get_blue()) return elif isinstance(color, str): - self.rgb = tinycss2.color3.parse_color(color) - # remove alpha channel and multiply with 255 - self.rgb = tuple(channel * 255.0 for channel in list(self.rgb)[:-1]) + self.rgb = Color.parse_str(color)[1] elif isinstance(color, (list, tuple)): self.rgb = tuple(color) elif self.hex_str_re.match(color): |
