summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/api/install.py3
-rw-r--r--lib/elements/element.py41
-rw-r--r--lib/extensions/base.py6
-rw-r--r--lib/extensions/lettering.py16
-rw-r--r--lib/extensions/lettering_custom_font_dir.py2
-rw-r--r--lib/extensions/lettering_generate_json.py32
-rw-r--r--lib/extensions/lettering_remove_kerning.py12
-rw-r--r--lib/extensions/params.py26
-rw-r--r--lib/extensions/zip.py2
-rw-r--r--lib/gui/simulator.py3
-rw-r--r--lib/lettering/font.py5
-rw-r--r--lib/lettering/kerning.py25
-rw-r--r--lib/stitch_plan/stitch.py23
-rw-r--r--lib/stitch_plan/stitch_plan.py6
-rw-r--r--lib/stitch_plan/ties.py7
-rw-r--r--lib/threads/palette.py2
16 files changed, 134 insertions, 77 deletions
diff --git a/lib/api/install.py b/lib/api/install.py
index 242b55b4..654f8171 100644
--- a/lib/api/install.py
+++ b/lib/api/install.py
@@ -30,8 +30,11 @@ def palettes():
if sys.platform == "win32":
# If we try to just use shutil.copy it says the operation requires elevation.
def copy_files(files, dest):
+ import pythoncom
import winutils
+ pythoncom.CoInitialize()
+
if not os.path.exists(dest):
os.makedirs(dest)
diff --git a/lib/elements/element.py b/lib/elements/element.py
index b45604d2..9b894d89 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -22,11 +22,12 @@ from ..utils import Point, cache
class Patch:
"""A raw collection of stitches with attached instructions."""
- def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, stitch_as_is=False):
+ def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, tie_modus=0, stitch_as_is=False):
self.color = color
self.stitches = stitches or []
self.trim_after = trim_after
self.stop_after = stop_after
+ self.tie_modus = tie_modus
self.stitch_as_is = stitch_as_is
def __add__(self, other):
@@ -47,7 +48,8 @@ class Patch:
class Param(object):
- def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False, default=None, tooltip=None, sort_index=0):
+ def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False,
+ options=[], default=None, tooltip=None, sort_index=0):
self.name = name
self.description = description
self.unit = unit
@@ -55,6 +57,7 @@ class Param(object):
self.type = type
self.group = group
self.inverse = inverse
+ self.options = options
self.default = default
self.tooltip = tooltip
self.sort_index = sort_index
@@ -79,14 +82,21 @@ class EmbroideryElement(object):
def __init__(self, node):
self.node = node
+ # update legacy embroider_ attributes to namespaced attributes
legacy_attribs = False
for attrib in self.node.attrib:
if attrib.startswith('embroider_'):
- # update embroider_ attributes to namespaced attributes
self.replace_legacy_param(attrib)
legacy_attribs = True
+ # convert legacy tie setting
+ legacy_tie = self.get_param('ties', None)
+ if legacy_tie == "True":
+ self.set_param('ties', 0)
+ elif legacy_tie == "False":
+ self.set_param('ties', 3)
+
+ # defaut setting for fill_underlay has changed
if legacy_attribs and not self.get_param('fill_underlay', ""):
- # defaut setting for fill_underlay has changed
self.set_param('fill_underlay', False)
@property
@@ -239,14 +249,17 @@ class EmbroideryElement(object):
@property
@param('ties',
- _('Ties'),
- tooltip=_('Add ties. Manual stitch will not add ties.'),
- type='boolean',
- default=True,
+ _('Allow lock stitches'),
+ tooltip=_('Tie thread at the beginning and/or end of this object. Manual stitch will not add lock stitches.'),
+ type='dropdown',
+ # Ties: 0 = Both | 1 = Before | 2 = After | 3 = Neither
+ # L10N options to allow lock stitch before and after objects
+ options=[_("Both"), _("Before"), _("After"), _("Neither")],
+ default=0,
sort_index=4)
@cache
def ties(self):
- return self.get_boolean_param("ties", True)
+ return self.get_int_param("ties", 0)
@property
def path(self):
@@ -312,11 +325,8 @@ class EmbroideryElement(object):
def get_command(self, command):
commands = self.get_commands(command)
- if len(commands) == 1:
+ if commands:
return commands[0]
- elif len(commands) > 1:
- raise ValueError(_("%(id)s has more than one command of type '%(command)s' linked to it") %
- dict(id=self.node.get('id'), command=command))
else:
return None
@@ -353,9 +363,8 @@ class EmbroideryElement(object):
patches = self.to_patches(last_patch)
- if not self.ties:
- for patch in patches:
- patch.stitch_as_is = True
+ for patch in patches:
+ patch.tie_modus = self.ties
if patches:
patches[-1].trim_after = self.has_command("trim") or self.trim_after
diff --git a/lib/extensions/base.py b/lib/extensions/base.py
index 1d83be59..44c7ec88 100644
--- a/lib/extensions/base.py
+++ b/lib/extensions/base.py
@@ -155,7 +155,7 @@ class InkstitchExtension(inkex.Effect):
# command connectors with a fill color set, will glitch into the elements list
if is_command(node) or node.get(CONNECTOR_TYPE):
- return[]
+ return []
if self.svg.selected:
if node.get("id") in self.svg.selected:
@@ -168,7 +168,9 @@ class InkstitchExtension(inkex.Effect):
nodes.extend(self.descendants(child, selected, troubleshoot))
if selected:
- if getattr(node, "get_path", None):
+ if node.tag == SVG_GROUP_TAG:
+ pass
+ elif getattr(node, "get_path", None):
nodes.append(node)
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.tag in EMBROIDERABLE_TAGS or is_clone(node)):
nodes.append(node)
diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py
index 8fc3168a..c57348ac 100644
--- a/lib/extensions/lettering.py
+++ b/lib/extensions/lettering.py
@@ -6,6 +6,7 @@
import json
import os
import sys
+from base64 import b64decode
import appdirs
import inkex
@@ -91,12 +92,17 @@ class LetteringFrame(wx.Frame):
"scale": 100
})
- try:
- if INKSTITCH_LETTERING in self.group.attrib:
+ if INKSTITCH_LETTERING in self.group.attrib:
+ try:
self.settings.update(json.loads(self.group.get(INKSTITCH_LETTERING)))
- return
- except (TypeError, ValueError):
- pass
+ except json.decoder.JSONDecodeError:
+ # legacy base64 encoded (changed in v2.0)
+ try:
+ self.settings.update(json.loads(b64decode(self.group.get(INKSTITCH_LETTERING))))
+ except (TypeError, ValueError):
+ pass
+ except (TypeError, ValueError):
+ pass
def apply_settings(self):
"""Make the settings in self.settings visible in the UI."""
diff --git a/lib/extensions/lettering_custom_font_dir.py b/lib/extensions/lettering_custom_font_dir.py
index e2af3d47..b73d23a6 100644
--- a/lib/extensions/lettering_custom_font_dir.py
+++ b/lib/extensions/lettering_custom_font_dir.py
@@ -33,6 +33,8 @@ class LetteringCustomFontDir(InkstitchExtension):
config_path = appdirs.user_config_dir('inkstitch')
except ImportError:
config_path = os.path.expanduser('~/.inkstitch')
+ if not os.path.exists(config_path):
+ os.makedirs(config_path)
config_path = os.path.join(config_path, 'custom_dirs.json')
with open(config_path, 'w', encoding="utf8") as font_data:
diff --git a/lib/extensions/lettering_generate_json.py b/lib/extensions/lettering_generate_json.py
index fd1eab30..3822b0c2 100644
--- a/lib/extensions/lettering_generate_json.py
+++ b/lib/extensions/lettering_generate_json.py
@@ -25,9 +25,12 @@ class LetteringGenerateJson(InkstitchExtension):
self.arg_parser.add_argument("-s", "--auto-satin", type=Boolean, default="true", dest="auto_satin")
self.arg_parser.add_argument("-r", "--reversible", type=Boolean, default="true", dest="reversible")
self.arg_parser.add_argument("-g", "--default-glyph", type=str, default="", dest="default_glyph")
- self.arg_parser.add_argument("-i", "--min-scale", type=float, default=1, dest="min_scale")
- self.arg_parser.add_argument("-a", "--max-scale", type=float, default=1, dest="max_scale")
+ self.arg_parser.add_argument("-i", "--min-scale", type=float, default=1.0, dest="min_scale")
+ self.arg_parser.add_argument("-a", "--max-scale", type=float, default=1.0, dest="max_scale")
+ self.arg_parser.add_argument("-c", "--use-custom-leading", type=Boolean, default="false", dest="use_custom_leading")
+ self.arg_parser.add_argument("-b", "--use-custom-spacing", type=Boolean, default="false", dest="use_custom_spacing")
self.arg_parser.add_argument("-l", "--leading", type=int, default=0, dest="leading")
+ 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")
def effect(self):
@@ -43,19 +46,18 @@ class LetteringGenerateJson(InkstitchExtension):
horiz_adv_x = kerning.horiz_adv_x()
hkern = kerning.hkern()
+ custom_leading = self.options.use_custom_leading
+ custom_spacing = self.options.use_custom_spacing
word_spacing = kerning.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()
- # missing_glyph_spacing = kerning.missing_glyph_spacing()
-
- # if letter spacing returns 0, it hasn't been specified in the font file
- # Ink/Stitch will calculate the width of each letter automatically
- if letter_spacing == 0:
- letter_spacing = None
-
- # if leading (line height) is set to 0, the font author wants Ink/Stitch to use units_per_em
- # if units_per_em is not defined in the font file a default value will be returned
- if self.options.leading == 0:
+ units_per_em = kerning.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:
leading = units_per_em
else:
leading = self.options.leading
@@ -67,8 +69,8 @@ class LetteringGenerateJson(InkstitchExtension):
'auto_satin': self.options.auto_satin,
'reversible': self.options.reversible,
'default_glyph': self.options.default_glyph,
- 'min_scale': self.options.min_scale,
- 'max_scale': self.options.max_scale,
+ 'min_scale': round(self.options.min_scale, 1),
+ 'max_scale': round(self.options.max_scale, 1),
'horiz_adv_x_default': letter_spacing,
'horiz_adv_x_space': word_spacing,
'units_per_em': units_per_em,
diff --git a/lib/extensions/lettering_remove_kerning.py b/lib/extensions/lettering_remove_kerning.py
index 1e30e872..21a8eda3 100644
--- a/lib/extensions/lettering_remove_kerning.py
+++ b/lib/extensions/lettering_remove_kerning.py
@@ -28,8 +28,10 @@ class LetteringRemoveKerning(InkstitchExtension):
with open(path, 'r+', encoding="utf-8") as fontfile:
svg = etree.parse(fontfile)
xpath = ".//svg:font[1]"
- kerning = svg.xpath(xpath, namespaces=NSS)[0]
- kerning.getparent().remove(kerning)
- fontfile.seek(0)
- fontfile.write(etree.tostring(svg).decode('utf-8'))
- fontfile.truncate()
+ kerning = svg.xpath(xpath, namespaces=NSS)
+ if kerning:
+ kerning = kerning[0]
+ kerning.getparent().remove(kerning)
+ fontfile.seek(0)
+ fontfile.write(etree.tostring(svg).decode('utf-8'))
+ fontfile.truncate()
diff --git a/lib/extensions/params.py b/lib/extensions/params.py
index 87b9c3bf..10cc6e3c 100644
--- a/lib/extensions/params.py
+++ b/lib/extensions/params.py
@@ -14,7 +14,7 @@ from itertools import groupby
import wx
from wx.lib.scrolledpanel import ScrolledPanel
-from ..commands import is_command
+from ..commands import is_command, is_command_symbol
from ..elements import (AutoFill, Clone, EmbroideryElement, Fill, Polyline,
SatinColumn, Stroke)
from ..elements.clone import is_clone
@@ -158,7 +158,11 @@ class ParamsTab(ScrolledPanel):
for name, input in self.param_inputs.items():
if input in self.changed_inputs and input != self.toggle_checkbox:
- values[name] = input.GetValue()
+ try:
+ values[name] = input.GetValue()
+ except AttributeError:
+ # dropdown
+ values[name] = input.GetSelection()
return values
@@ -188,7 +192,10 @@ class ParamsTab(ScrolledPanel):
for name, value in preset_data.items():
if name in self.param_inputs:
- self.param_inputs[name].SetValue(value)
+ try:
+ self.param_inputs[name].SetValue(value)
+ except AttributeError:
+ self.param_inputs[name].SetSelection(int(value))
self.changed_inputs.add(self.param_inputs[name])
self.update_toggle_state()
@@ -276,6 +283,10 @@ class ParamsTab(ScrolledPanel):
input.SetValue(param.values[0])
input.Bind(wx.EVT_CHECKBOX, self.changed)
+ elif param.type == 'dropdown':
+ input = wx.Choice(self, wx.ID_ANY, choices=param.options)
+ input.SetSelection(int(param.values[0]))
+ input.Bind(wx.EVT_CHOICE, self.changed)
elif len(param.values) > 1:
input = wx.ComboBox(self, wx.ID_ANY, choices=sorted(str(value) for value in param.values), style=wx.CB_DROPDOWN)
input.Bind(wx.EVT_COMBOBOX, self.changed)
@@ -454,6 +465,13 @@ class SettingsFrame(wx.Frame):
sizer_1.Add(sizer_3, 0, wx.ALIGN_RIGHT, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
+
+ # prevent the param dialog to become smaller than 500*400
+ # otherwise the scrollbar jumps to the side and produces a
+ # deprecation warning in wxpythons scrolledpanel.py line 225 -
+ # which is expecting an integer, but uses previously a division
+ self.SetSizeHints(500, 400)
+
self.Layout()
# end wxGlade
@@ -471,7 +489,7 @@ class Params(InkstitchExtension):
element = EmbroideryElement(node)
classes = []
- if not is_command(node):
+ if not is_command(node) and not is_command_symbol(node):
if node.tag == SVG_POLYLINE_TAG:
classes.append(Polyline)
elif is_clone(node):
diff --git a/lib/extensions/zip.py b/lib/extensions/zip.py
index d0c56ba2..605b4573 100644
--- a/lib/extensions/zip.py
+++ b/lib/extensions/zip.py
@@ -60,7 +60,7 @@ class Zip(InkstitchExtension):
svg.write(etree.tostring(document).decode('utf-8'))
elif format == 'threadlist':
output_file = os.path.join(path, "%s_%s.txt" % (base_file_name, _("threadlist")))
- output = open(output_file, 'w')
+ output = open(output_file, 'w', encoding='utf-8')
output.write(self.get_threadlist(stitch_plan, base_file_name))
output.close()
else:
diff --git a/lib/gui/simulator.py b/lib/gui/simulator.py
index c4100804..27b9a97e 100644
--- a/lib/gui/simulator.py
+++ b/lib/gui/simulator.py
@@ -11,10 +11,10 @@ from threading import Event, Thread
import wx
from wx.lib.intctrl import IntCtrl
+from .dialogs import info_dialog
from ..i18n import _
from ..stitch_plan import patches_to_stitch_plan, stitch_plan_from_file
from ..svg import PIXELS_PER_MM
-from .dialogs import info_dialog
# L10N command label at bottom of simulator window
COMMAND_NAMES = [_("STITCH"), _("JUMP"), _("TRIM"), _("STOP"), _("COLOR CHANGE")]
@@ -686,7 +686,6 @@ class EmbroiderySimulator(wx.Frame):
stitch_plan = kwargs.pop('stitch_plan', None)
stitches_per_second = kwargs.pop('stitches_per_second', 16)
target_duration = kwargs.pop('target_duration', None)
- size = kwargs.get('size', (0, 0))
wx.Frame.__init__(self, *args, **kwargs)
self.statusbar = self.CreateStatusBar(2)
self.statusbar.SetStatusWidths([250, -1])
diff --git a/lib/lettering/font.py b/lib/lettering/font.py
index 7b479072..b1979067 100644
--- a/lib/lettering/font.py
+++ b/lib/lettering/font.py
@@ -14,7 +14,6 @@ from ..elements import nodes_to_elements
from ..exceptions import InkstitchException
from ..i18n import _, get_languages
from ..stitches.auto_satin import auto_satin
-from ..svg import PIXELS_PER_MM
from ..svg.tags import INKSCAPE_LABEL, SVG_GROUP_TAG, SVG_PATH_TAG
from ..utils import Point
from .font_variant import FontVariant
@@ -108,7 +107,7 @@ class Font(object):
name = localized_font_metadata('name', '')
description = localized_font_metadata('description', '')
default_glyph = font_metadata('defalt_glyph', "�")
- leading = font_metadata('leading', 5, multiplier=PIXELS_PER_MM)
+ leading = font_metadata('leading', 100)
kerning_pairs = font_metadata('kerning_pairs', {})
auto_satin = font_metadata('auto_satin', True)
min_scale = font_metadata('min_scale', 1.0)
@@ -124,7 +123,7 @@ class Font(object):
horiz_adv_x_default = font_metadata('horiz_adv_x_default')
# Define by <glyph glyph-name="space" unicode=" " horiz-adv-x="22" />, Example font.json : "horiz_adv_x_space":22,
- word_spacing = font_metadata('horiz_adv_x_space', 0)
+ word_spacing = font_metadata('horiz_adv_x_space', 20)
reversible = font_metadata('reversible', True)
diff --git a/lib/lettering/kerning.py b/lib/lettering/kerning.py
index 6db9ba1e..6380f1a6 100644
--- a/lib/lettering/kerning.py
+++ b/lib/lettering/kerning.py
@@ -12,7 +12,7 @@ class FontKerning(object):
This class reads kerning information from an SVG file
"""
def __init__(self, path):
- with open(path) as svg:
+ with open(path, 'r', encoding="utf-8") as svg:
self.svg = etree.parse(svg)
# horiz_adv_x defines the wdith of specific letters (distance to next letter)
@@ -51,21 +51,30 @@ class FontKerning(object):
# the space character
def word_spacing(self):
xpath = "string(.//svg:glyph[@glyph-name='space'][1]/@*[name()='horiz-adv-x'])"
- word_spacing = self.svg.xpath(xpath, namespaces=NSS) or 26
- return int(word_spacing)
+ word_spacing = self.svg.xpath(xpath, namespaces=NSS)
+ try:
+ return int(word_spacing)
+ except ValueError:
+ return None
# default letter spacing
def letter_spacing(self):
xpath = "string(.//svg:font[@horiz-adv-x][1]/@*[name()='horiz-adv-x'])"
- letter_spacing = self.svg.xpath(xpath, namespaces=NSS) or 0
- return int(letter_spacing)
+ letter_spacing = self.svg.xpath(xpath, namespaces=NSS)
+ try:
+ return int(letter_spacing)
+ except ValueError:
+ return None
# this value will be saved into the json file to preserve it for later font edits
# additionally it serves to automatically define the line height (leading)
- def units_per_em(self, default=100):
+ def units_per_em(self):
xpath = "string(.//svg:font-face[@units-per-em][1]/@*[name()='units-per-em'])"
- units_per_em = self.svg.xpath(xpath, namespaces=NSS) or default
- return int(units_per_em)
+ units_per_em = self.svg.xpath(xpath, namespaces=NSS)
+ try:
+ return int(units_per_em)
+ except ValueError:
+ return None
"""
def missing_glyph_spacing(self):
diff --git a/lib/stitch_plan/stitch.py b/lib/stitch_plan/stitch.py
index a5938c7b..ae6fa480 100644
--- a/lib/stitch_plan/stitch.py
+++ b/lib/stitch_plan/stitch.py
@@ -7,7 +7,7 @@ from ..utils.geometry import Point
class Stitch(Point):
- def __init__(self, x, y=None, color=None, jump=False, stop=False, trim=False, color_change=False, no_ties=False):
+ def __init__(self, x, y=None, color=None, jump=False, stop=False, trim=False, color_change=False, tie_modus=0, no_ties=False):
self.x = x
self.y = y
self.color = color
@@ -15,6 +15,7 @@ class Stitch(Point):
self.trim = trim
self.stop = stop
self.color_change = color_change
+ self.tie_modus = tie_modus
self.no_ties = no_ties
# Allow creating a Stitch from a Point
@@ -24,18 +25,18 @@ class Stitch(Point):
self.y = point.y
def __repr__(self):
- return "Stitch(%s, %s, %s, %s, %s, %s, %s, %s)" % (self.x,
- self.y,
- self.color,
- "JUMP" if self.jump else " ",
- "TRIM" if self.trim else " ",
- "STOP" if self.stop else " ",
- "NO TIES" if self.no_ties else " ",
- "COLOR CHANGE" if self.color_change else " "
- )
+ return "Stitch(%s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.x,
+ self.y,
+ self.color,
+ "JUMP" if self.jump else " ",
+ "TRIM" if self.trim else " ",
+ "STOP" if self.stop else " ",
+ "TIE MODUS" if self.tie_modus else " ",
+ "NO TIES" if self.no_ties else " ",
+ "COLOR CHANGE" if self.color_change else " ")
def copy(self):
- return Stitch(self.x, self.y, self.color, self.jump, self.stop, self.trim, self.color_change, self.no_ties)
+ return Stitch(self.x, self.y, self.color, self.jump, self.stop, self.trim, self.color_change, self.tie_modus, self.no_ties)
def __json__(self):
return vars(self)
diff --git a/lib/stitch_plan/stitch_plan.py b/lib/stitch_plan/stitch_plan.py
index 47e8b203..01463aba 100644
--- a/lib/stitch_plan/stitch_plan.py
+++ b/lib/stitch_plan/stitch_plan.py
@@ -19,7 +19,9 @@ def patches_to_stitch_plan(patches, collapse_len=None, disable_ties=False):
* adds jump-stitches between patches if necessary
"""
- collapse_len = (collapse_len or 3.0) * PIXELS_PER_MM
+ if collapse_len is None:
+ collapse_len = 3.0
+ collapse_len = collapse_len * PIXELS_PER_MM
stitch_plan = StitchPlan()
color_block = stitch_plan.new_color_block(color=patches[0].color)
@@ -45,7 +47,7 @@ def patches_to_stitch_plan(patches, collapse_len=None, disable_ties=False):
if len(color_block) and (patch.stitches[0] - color_block.stitches[-1]).length() > collapse_len:
color_block.add_stitch(patch.stitches[0], jump=True)
- color_block.add_stitches(patch.stitches, no_ties=patch.stitch_as_is)
+ color_block.add_stitches(stitches=patch.stitches, tie_modus=patch.tie_modus, no_ties=patch.stitch_as_is)
if patch.trim_after:
color_block.add_stitch(trim=True)
diff --git a/lib/stitch_plan/ties.py b/lib/stitch_plan/ties.py
index d54b0f0f..c649ee44 100644
--- a/lib/stitch_plan/ties.py
+++ b/lib/stitch_plan/ties.py
@@ -36,11 +36,14 @@ def add_tie(stitches, tie_path):
def add_tie_off(stitches):
- add_tie(stitches, stitches[-1:-3:-1])
+ # tie_modus: 0 = both | 1 = before | 2 = after | 3 = neither
+ if stitches[-1].tie_modus not in [1, 3]:
+ add_tie(stitches, stitches[-1:-3:-1])
def add_tie_in(stitches, upcoming_stitches):
- add_tie(stitches, upcoming_stitches)
+ if stitches[0].tie_modus not in [2, 3]:
+ add_tie(stitches, upcoming_stitches)
def add_ties(stitch_plan):
diff --git a/lib/threads/palette.py b/lib/threads/palette.py
index f1ff6cb4..29d2a6dd 100644
--- a/lib/threads/palette.py
+++ b/lib/threads/palette.py
@@ -62,7 +62,7 @@ class ThreadPalette(Set):
thread = ThreadColor(thread_color, thread_name, thread_number, manufacturer=self.name)
self.threads[thread] = convert_color(sRGBColor(*thread_color, is_upscaled=True), LabColor)
- except ValueError:
+ except (ValueError, IndexError):
continue
def __contains__(self, thread):