* several thread palette extension fixes
* fix svg tartan when original shape is invalid
* tartan stroke spaces
* style
* fix tartan color substituion at pattern start
* ripple: do not render too small paths
* use less space for params warning headline
* fix clone shape path
* zip export template fix (typo)
* add realistic stitch plan output warning (help tab)
pull/3033/head dev-build-claudine-a_few_tartan_fonts_correction
Kaalleen 2024-06-30 22:49:18 +02:00 zatwierdzone przez GitHub
rodzic 7c06dee44a
commit e52886a64a
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
13 zmienionych plików z 185 dodań i 63 usunięć

Wyświetl plik

@ -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())

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()

Wyświetl plik

@ -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))

Wyświetl plik

@ -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__':

Wyświetl plik

@ -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

Wyświetl plik

@ -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

Wyświetl plik

@ -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)

Wyświetl plik

@ -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])

Wyświetl plik

@ -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(

Wyświetl plik

@ -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
# 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
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
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)
"""
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
: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(

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>