summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/elements/clone.py2
-rw-r--r--lib/extensions/apply_palette.py12
-rw-r--r--lib/extensions/generate_palette.py5
-rw-r--r--lib/extensions/palette_split_text.py12
-rw-r--r--lib/extensions/palette_to_text.py12
-rw-r--r--lib/extensions/stroke_to_lpe_satin.py2
-rw-r--r--lib/extensions/tartan.py13
-rw-r--r--lib/gui/warnings.py2
-rw-r--r--lib/stitches/ripple_stitch.py3
-rw-r--r--lib/tartan/svg.py28
-rw-r--r--lib/tartan/utils.py145
-rw-r--r--templates/stitch_plan_preview.xml6
-rw-r--r--templates/zip.xml6
13 files changed, 185 insertions, 63 deletions
diff --git a/lib/elements/clone.py b/lib/elements/clone.py
index 55f6465b..9a89b6ff 100644
--- a/lib/elements/clone.py
+++ b/lib/elements/clone.py
@@ -195,7 +195,7 @@ class Clone(EmbroideryElement):
transform = Transform(self.node.composed_transform())
path = path.transform(transform)
path = path.to_superpath()
- return MultiLineString(path)
+ return MultiLineString(path[0])
def center(self, source_node):
transform = get_node_transform(self.node.getparent())
diff --git a/lib/extensions/apply_palette.py b/lib/extensions/apply_palette.py
index ce6c8f5c..cd8c4c94 100644
--- a/lib/extensions/apply_palette.py
+++ b/lib/extensions/apply_palette.py
@@ -3,7 +3,7 @@
# Copyright (c) 2024 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-from ..elements import FillStitch
+from ..elements import Clone, FillStitch
from ..threads import ThreadCatalog, ThreadColor
from .base import InkstitchExtension
@@ -29,6 +29,16 @@ class ApplyPalette(InkstitchExtension):
# Iterate through the color blocks to apply colors
for element in self.elements:
+ if isinstance(element, Clone):
+ # clones use the color of their source element
+ continue
+ elif hasattr(element, 'gradient') and element.gradient is not None:
+ # apply colors to each gradient stop
+ for i, gradient_style in enumerate(element.gradient.stop_styles):
+ color = gradient_style['stop-color']
+ gradient_style['stop-color'] = palette.nearest_color(ThreadColor(color)).to_hex_str()
+ continue
+
nearest_color = palette.nearest_color(ThreadColor(element.color))
if isinstance(element, FillStitch):
element.node.style['fill'] = nearest_color.to_hex_str()
diff --git a/lib/extensions/generate_palette.py b/lib/extensions/generate_palette.py
index f5d7661c..b87bc179 100644
--- a/lib/extensions/generate_palette.py
+++ b/lib/extensions/generate_palette.py
@@ -62,7 +62,10 @@ class GeneratePalette(InkstitchExtension):
def _get_color_from_elements(self, elements):
colors = []
for element in elements:
- if 'fill' not in element.style.keys() or not isinstance(element, inkex.TextElement): # type(element) != inkex.TextElement:
+ if element.TAG == 'g':
+ colors.extend(self._get_color_from_elements(element.getchildren()))
+ continue
+ if 'fill' not in element.style.keys() or not isinstance(element, inkex.TextElement):
continue
color = inkex.Color(element.style['fill']).to_rgb()
diff --git a/lib/extensions/palette_split_text.py b/lib/extensions/palette_split_text.py
index 19b70782..67549f4f 100644
--- a/lib/extensions/palette_split_text.py
+++ b/lib/extensions/palette_split_text.py
@@ -3,10 +3,13 @@
# Copyright (c) 2022 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+from tempfile import TemporaryDirectory
+
import inkex
from ..i18n import _
from .base import InkstitchExtension
+from .utils.inkex_command import inkscape
class PaletteSplitText(InkstitchExtension):
@@ -30,7 +33,14 @@ class PaletteSplitText(InkstitchExtension):
transform = text.transform
text.pop('transform')
- bbox = text.get_inkscape_bbox()
+ # the inkex command `bbox = text.get_inkscape_bbox()` is causing problems for our pyinstaller bundled
+ # releases, this code block is taken from inkex/elements/_text
+ with TemporaryDirectory(prefix="inkscape-command") as tmpdir:
+ svg_file = inkex.command.write_svg(text.root, tmpdir, "input.svg")
+ bbox = inkscape(svg_file, "-X", "-Y", "-W", "-H", query_id=text.get_id())
+ bbox = list(map(text.root.viewport_to_unit, bbox.splitlines()))
+ bbox = inkex.BoundingBox.new_xywh(*bbox[1:])
+
x = bbox.left
y = bbox.bottom
height = bbox.height / (len(lines))
diff --git a/lib/extensions/palette_to_text.py b/lib/extensions/palette_to_text.py
index db0c50cf..729c92fc 100644
--- a/lib/extensions/palette_to_text.py
+++ b/lib/extensions/palette_to_text.py
@@ -8,6 +8,7 @@ import os
import inkex
from ..i18n import _
+from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
from ..threads.palette import ThreadPalette
from .base import InkstitchExtension
@@ -31,11 +32,15 @@ class PaletteToText(InkstitchExtension):
inkex.errormsg(_("Cannot read palette: invalid GIMP palette header"))
return
- current_layer = self.svg.get_current_layer()
+ layer = inkex.Group(attrib={
+ 'id': '__inkstitch_palette__',
+ INKSCAPE_LABEL: _('Thread Palette') + f': {thread_palette.name}',
+ INKSCAPE_GROUPMODE: 'layer',
+ })
+ self.svg.append(layer)
x = 0
y = 0
- pos = 0
for color in thread_palette:
line = "%s %s" % (color.name, color.number)
element = inkex.TextElement()
@@ -43,10 +48,9 @@ class PaletteToText(InkstitchExtension):
element.style = "fill:%s;font-size:4px;" % color.to_hex_str()
element.set('x', x)
element.set('y', str(y))
- current_layer.insert(pos, element)
+ layer.append(element)
y = float(y) + 5
- pos += 1
if __name__ == '__main__':
diff --git a/lib/extensions/stroke_to_lpe_satin.py b/lib/extensions/stroke_to_lpe_satin.py
index 3f6e87a2..a71bbb71 100644
--- a/lib/extensions/stroke_to_lpe_satin.py
+++ b/lib/extensions/stroke_to_lpe_satin.py
@@ -171,7 +171,7 @@ class SatinPattern:
def get_path(self, add_rungs, min_width, max_width, length, to_unit):
# scale the pattern path to fit the unit of the current svg
- scale_factor = scale_factor = 1 / inkex.units.convert_unit('1mm', f'{to_unit}')
+ scale_factor = 1 / inkex.units.convert_unit('1mm', f'{to_unit}')
pattern_path = inkex.Path(self.path).transform(inkex.Transform(f'scale({scale_factor})'), True)
# create a path element
diff --git a/lib/extensions/tartan.py b/lib/extensions/tartan.py
index 8c3c8c5f..3acb659c 100644
--- a/lib/extensions/tartan.py
+++ b/lib/extensions/tartan.py
@@ -28,11 +28,8 @@ class Tartan(InkstitchExtension):
def get_tartan_elements(self):
if self.svg.selection:
- self._get_elements()
-
- def _get_elements(self):
- for node in self.svg.selection:
- self.get_selection(node)
+ for node in self.svg.selection:
+ self.get_selection(node)
def get_selection(self, node):
if node.TAG == 'g' and not node.get_id().startswith('inkstitch-tartan'):
@@ -40,13 +37,13 @@ class Tartan(InkstitchExtension):
self.get_selection(child_node)
else:
node = self.get_outline(node)
- if node.tag in EMBROIDERABLE_TAGS and node.style('fill'):
+ if node.tag in EMBROIDERABLE_TAGS and node.style('fill') is not None:
self.elements.add(node)
def get_outline(self, node):
# existing tartans are marked through their outline element
# we have either selected the element itself or some other element within a tartan group
- if node.get(INKSTITCH_TARTAN, None):
+ if node.get(INKSTITCH_TARTAN, None) is not None:
return node
if node.get_id().startswith('inkstitch-tartan'):
for element in node.iterchildren(EMBROIDERABLE_TAGS):
@@ -55,7 +52,7 @@ class Tartan(InkstitchExtension):
for group in node.iterancestors(SVG_GROUP_TAG):
if group.get_id().startswith('inkstitch-tartan'):
for element in group.iterchildren(EMBROIDERABLE_TAGS):
- if element.get(INKSTITCH_TARTAN, None):
+ if element.get(INKSTITCH_TARTAN, None) is not None:
return element
# if we don't find an existing tartan, return node
return node
diff --git a/lib/gui/warnings.py b/lib/gui/warnings.py
index c8359fc9..18d91532 100644
--- a/lib/gui/warnings.py
+++ b/lib/gui/warnings.py
@@ -20,7 +20,7 @@ class WarningPanel(wx.Panel):
self.warning = wx.StaticText(self)
self.warning.SetLabel(_("An error occurred while rendering the stitch plan:"))
self.warning.SetForegroundColour(wx.Colour(255, 25, 25))
- self.main_sizer.Add(self.warning, 1, wx.LEFT | wx.BOTTOM | wx.EXPAND, 10)
+ self.main_sizer.Add(self.warning, 0, wx.LEFT | wx.BOTTOM | wx.EXPAND, 10)
tc_style = wx.TE_MULTILINE | wx.TE_READONLY | wx.VSCROLL | wx.TE_RICH2
self.warning_text = wx.TextCtrl(self, size=(300, 300), style=tc_style)
diff --git a/lib/stitches/ripple_stitch.py b/lib/stitches/ripple_stitch.py
index a354166c..e150945e 100644
--- a/lib/stitches/ripple_stitch.py
+++ b/lib/stitches/ripple_stitch.py
@@ -24,6 +24,9 @@ def ripple_stitch(stroke):
If more sublines are present interpolation will take place between the first two.
'''
+ if stroke.as_multi_line_string().length < 0.1:
+ return []
+
is_linear, helper_lines = _get_helper_lines(stroke)
num_lines = len(helper_lines[0])
diff --git a/lib/tartan/svg.py b/lib/tartan/svg.py
index 739315cf..93d33253 100644
--- a/lib/tartan/svg.py
+++ b/lib/tartan/svg.py
@@ -24,7 +24,7 @@ from ..stitches.auto_fill import (PathEdge, build_fill_stitch_graph,
from ..svg import PIXELS_PER_MM, get_correction_transform
from ..utils import DotDict, ensure_multi_line_string
from .palette import Palette
-from .utils import sort_fills_and_strokes, stripes_to_shapes
+from .utils import sort_fills_and_strokes, stripes_to_shapes, get_palette_width
class TartanSvgGroup:
@@ -51,6 +51,8 @@ class TartanSvgGroup:
self.symmetry = self.palette.symmetry
self.stripes = self.palette.palette_stripes
self.warp, self.weft = self.stripes
+ self.warp_width = get_palette_width(settings)
+ self.weft_width = get_palette_width(settings)
if self.palette.get_palette_width(self.scale, self.min_stripe_width) == 0:
self.warp = []
if self.palette.get_palette_width(self.scale, self.min_stripe_width, 1) == 0:
@@ -80,8 +82,16 @@ class TartanSvgGroup:
index = parent_group.index(outline)
parent_group.insert(index, group)
- outline_shape = FillStitch(outline).shape
transform = get_correction_transform(outline)
+ outline_shapes = FillStitch(outline).shape
+ for outline_shape in outline_shapes.geoms:
+ self._generate_tartan_group_elements(group, outline_shape, transform)
+
+ # set outline invisible
+ outline.style['display'] = 'none'
+ group.append(outline)
+
+ def _generate_tartan_group_elements(self, group, outline_shape, transform):
dimensions, rotation_center = self._get_dimensions(outline_shape)
warp = stripes_to_shapes(
@@ -129,11 +139,6 @@ class TartanSvgGroup:
for element in stroke_elements:
group.append(element)
- # set outline invisible
- outline.style['display'] = 'none'
- group.append(outline)
- return group
-
def _get_command_position(self, fill: FillStitch, point: Tuple[float, float]) -> Point:
"""
Shift command position out of the element shape
@@ -492,6 +497,8 @@ class TartanSvgGroup:
"""
Calculates the dimensions for the tartan pattern.
Make sure it is big enough for pattern rotations.
+ We also need additional space to ensure fill stripes go to their full extend, this might be problematic if
+ start or end stripes use render mode 2 (stroke spacing).
:param outline: the shape to be filled with a tartan pattern
:returns: [0] a list with boundaries and [1] the center point (for rotations)
@@ -509,6 +516,13 @@ class TartanSvgGroup:
miny = center.y - min_radius
maxx = center.x + min_radius
maxy = center.y + min_radius
+
+ extra_space = max(self.warp_width * PIXELS_PER_MM, self.weft_width * PIXELS_PER_MM)
+ minx -= extra_space
+ maxx += extra_space
+ miny -= extra_space
+ maxy += extra_space
+
return (float(minx), float(miny), float(maxx), float(maxy)), center
def _polygon_to_path(
diff --git a/lib/tartan/utils.py b/lib/tartan/utils.py
index 4f64fc6f..7949505d 100644
--- a/lib/tartan/utils.py
+++ b/lib/tartan/utils.py
@@ -46,63 +46,138 @@ def stripes_to_shapes(
:returns: a dictionary with shapes grouped by color
"""
+ full_sett = _stripes_to_sett(stripes, symmetry, scale, min_stripe_width)
+
minx, miny, maxx, maxy = dimensions
shapes: defaultdict = defaultdict(list)
- original_stripes = stripes
- if len(original_stripes) == 0:
+ if len(full_sett) == 0:
return shapes
left = minx
top = miny
- add_to_stroke = 0
- add_to_fill = 0
i = -1
while True:
i += 1
- stripes = original_stripes
-
- segments = stripes
- if symmetry and i % 2 != 0 and len(stripes) > 1:
- segments = list(reversed(stripes[1:-1]))
- for stripe in segments:
- width = stripe['width'] * PIXELS_PER_MM * (scale / 100)
+ for stripe in full_sett:
+ width = stripe['width']
right = left + width
bottom = top + width
- if ((top > maxy and weft) or (left > maxx and not weft) or
- (add_to_stroke > maxy and weft) or (add_to_stroke > maxx and not weft)):
+ if (top > maxy and weft) or (left > maxx and not weft):
return _merge_polygons(shapes, outline, intersect_outline)
- if stripe['render'] == 0:
- left = right + add_to_stroke
- top = bottom + add_to_stroke
- add_to_stroke = 0
- continue
- elif stripe['render'] == 2:
- add_to_stroke += width
+ if stripe['color'] is None or not stripe['render']:
+ left = right
+ top = bottom
continue
shape_dimensions = [top, bottom, left, right, minx, miny, maxx, maxy]
- if width <= min_stripe_width * PIXELS_PER_MM:
- add_to_fill = add_to_stroke
- shape_dimensions[0] += add_to_stroke
- shape_dimensions[2] += add_to_stroke
+ if stripe['is_stroke']:
linestrings = _get_linestrings(outline, shape_dimensions, rotation, rotation_center, weft)
shapes[stripe['color']].extend(linestrings)
- add_to_stroke += width
- continue
- add_to_stroke = 0
+ else:
+ polygon = _get_polygon(shape_dimensions, rotation, rotation_center, weft)
+ shapes[stripe['color']].append(polygon)
+ left = right
+ top = bottom
+ return shapes
+
+
+def _stripes_to_sett(
+ stripes: List[dict],
+ symmetry: bool,
+ scale: int,
+ min_stripe_width: float,
+) -> List[dict]:
+ """
+ Builds a full sett for easier conversion into elements
+
+ :param stripes: a list of dictionaries with stripe information
+ :param symmetry: reflective sett (True) / repeating sett (False)
+ :param scale: the scale value (percent) for the pattern
+ :param min_stripe_width: min stripe width before it is rendered as running stitch
+ :returns: a list of dictionaries with stripe information (color, width, is_stroke, render)
+ """
- # add the space of the lines to the following object to avoid bad symmetry
- shape_dimensions[1] += add_to_fill
- shape_dimensions[3] += add_to_fill
+ last_fill_color = _get_last_fill_color(stripes, scale, min_stripe_width, symmetry)
+ first_was_stroke = False
+ last_was_stroke = False
+ add_width = 0
+ sett = []
+ for stripe in stripes:
+ width = stripe['width'] * PIXELS_PER_MM * (scale / 100)
+ is_stroke = width <= min_stripe_width * PIXELS_PER_MM
+ render = stripe['render']
+
+ if render == 0:
+ sett.append({'color': None, 'width': width + add_width, 'is_stroke': False, 'render': False})
+ last_fill_color = None
+ add_width = 0
+ last_was_stroke = False
+ continue
+
+ if render == 2:
+ sett.append({'color': last_fill_color, 'width': width + add_width, 'is_stroke': False, 'render': True})
+ add_width = 0
+ last_was_stroke = False
+ continue
+
+ if is_stroke:
+ if len(sett) == 0:
+ first_was_stroke = True
+ width /= 2
+ sett.append({'color': last_fill_color, 'width': width + add_width, 'is_stroke': False, 'render': True})
+ sett.append({'color': stripe['color'], 'width': 0, 'is_stroke': True, 'render': True})
+ add_width = width
+ last_was_stroke = True
+ else:
+ sett.append({'color': stripe['color'], 'width': width + add_width, 'is_stroke': False, 'render': True})
+ last_fill_color = stripe['color']
+ last_was_stroke = False
+
+ if add_width > 0:
+ sett.append({'color': last_fill_color, 'width': add_width, 'is_stroke': False, 'render': True})
+
+ # For symmetric setts we want to mirror the sett and append to receive a full sett
+ # We do not repeat at pivot points, which means we exclude the first and the last list item from the mirror
+ if symmetry:
+ reversed_sett = list(reversed(sett[1:-1]))
+ if first_was_stroke:
+ reversed_sett = reversed_sett[:-1]
+ if last_was_stroke:
+ reversed_sett = reversed_sett[1:]
+ sett.extend(reversed_sett)
+
+ return sett
+
+
+def _get_last_fill_color(stripes: List[dict], scale: int, min_stripe_width: float, symmetry: bool,) -> List[dict]:
+ '''
+ Returns the first fill color of a pattern to substitute spaces if the pattern starts with strokes or
+ stripes with render mode 2
- polygon = _get_polygon(shape_dimensions, rotation, rotation_center, weft)
- shapes[stripe['color']].append(polygon)
- left = right + add_to_fill
- top = bottom + add_to_fill
- add_to_fill = 0
+ :param stripes: a list of dictionaries with stripe information
+ :param scale: the scale value (percent) for the pattern
+ :param min_stripe_width: min stripe width before it is rendered as running stitch
+ :param symmetry: reflective sett (True) / repeating sett (False)
+ :returns: a list with fill colors or a list with one None item if there are no fills
+ '''
+ fill_colors = []
+ for stripe in stripes:
+ if stripe['render'] == 0:
+ fill_colors.append(None)
+ elif stripe['render'] == 2:
+ continue
+ elif stripe['width'] * (scale / 100) > min_stripe_width:
+ fill_colors.append(stripe['color'])
+ if len(fill_colors) == 0:
+ fill_colors = [None]
+
+ if symmetry:
+ return fill_colors[0]
+ else:
+ return fill_colors[-1]
def _merge_polygons(
diff --git a/templates/stitch_plan_preview.xml b/templates/stitch_plan_preview.xml
index 92507b27..f7180915 100644
--- a/templates/stitch_plan_preview.xml
+++ b/templates/stitch_plan_preview.xml
@@ -37,6 +37,12 @@
<page name="info" gui-text="Help">
<label>Use this extension to render the stitch plan into the canvas.</label>
<spacer />
+ <label>
+ Please use realistic output options with care. Realistic render outputs can cause Inkscape to slow massivley down.
+ Designs with a lot of stitches shouldn't be rendered with vector, while the vector option can be prefered if the design
+ is huge. For huge designs with a lot of stitches consider to render in chunks or choose simple as the render mode.
+ </label>
+ <spacer />
<label>More information on our website</label>
<label appearance="url">https://inkstitch.org/docs/visualize/#stitch-plan-preview</label>
</page>
diff --git a/templates/zip.xml b/templates/zip.xml
index 448819bc..c34027eb 100644
--- a/templates/zip.xml
+++ b/templates/zip.xml
@@ -21,8 +21,8 @@
{%- endif %}
{%- endfor %}
<param name="format-threadlist" type="boolean" gui-text=".TXT: Threadlist [COLOR]">false</param>
- <param name="format-png_realistic" type="boolean" gui-text=".PNG: Portable Network Graphics (Realistic) [COLOR]">false</param>
- <param name="format-png_simple" type="boolean" gui-text=".PNG: Portable Network Graphics (Simple) [COLOR]">false</param>
+ <param name="format-png_realistic" type="boolean" gui-text=".PNG: Portable Network Graphics (Realistic) [IMAGE]">false</param>
+ <param name="format-png_simple" type="boolean" gui-text=".PNG: Portable Network Graphics (Simple) [IMAGE]">false</param>
<param name="png_simple_line_width" type="float" precision="2" min="0.01" max="5" gui-text="Line width (mm)" indent="4">0.3</param>
<param name="format-svg" type="boolean" gui-text=".SVG: Scalable Vector Graphic">false</param>
<param name="extension" type="string" gui-hidden="true">zip</param>
@@ -39,7 +39,7 @@
<label appearance="header">Zip File Export</label>
<label>Export multiple embroidery file formats at once.</label>
<separator />
- <label>Read more on our webiste:</label>
+ <label>Read more on our website:</label>
<label appearance="url">https://inkstitch.org/docs/import-export/#batch-export</label>
</page>
</param>