Merge pull request #1254 from inkstitch/kaalleen/satin-patterns

Satin pattern and split stitch
pull/1322/head
Lex Neva 2021-08-16 19:40:44 -04:00 zatwierdzone przez GitHub
commit 3ebc238561
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
34 zmienionych plików z 688 dodań i 308 usunięć

Wyświetl plik

@ -5,7 +5,7 @@
from flask import Blueprint, g, jsonify
from ..stitch_plan import patches_to_stitch_plan
from ..stitch_plan import stitch_groups_to_stitch_plan
stitch_plan = Blueprint('stitch_plan', __name__)
@ -18,7 +18,7 @@ def get_stitch_plan():
metadata = g.extension.get_inkstitch_metadata()
collapse_len = metadata['collapse_len_mm']
patches = g.extension.elements_to_patches(g.extension.elements)
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
patches = g.extension.elements_to_stitch_groups(g.extension.elements)
stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len)
return jsonify(stitch_plan)

Wyświetl plik

@ -44,7 +44,6 @@ COMMANDS = {
# L10N command attached to an object
"satin_cut_point": N_("Satin cut point (use with Cut Satin Column)"),
# L10N command that affects a layer
"ignore_layer": N_("Ignore layer (do not stitch any objects in this layer)"),

Wyświetl plik

@ -9,13 +9,14 @@ import traceback
from shapely import geometry as shgeo
from .element import param
from .fill import Fill
from .validation import ValidationWarning
from ..i18n import _
from ..stitch_plan import StitchGroup
from ..stitches import auto_fill
from ..svg.tags import INKSCAPE_LABEL
from ..utils import cache, version
from .element import Patch, param
from .fill import Fill
from .validation import ValidationWarning
class SmallShapeWarning(ValidationWarning):
@ -212,8 +213,8 @@ class AutoFill(Fill):
else:
return None
def to_patches(self, last_patch):
stitches = []
def to_stitch_groups(self, last_patch):
stitch_groups = []
starting_point = self.get_starting_point(last_patch)
ending_point = self.get_ending_point()
@ -221,29 +222,40 @@ class AutoFill(Fill):
try:
if self.fill_underlay:
for i in range(len(self.fill_underlay_angle)):
stitches.extend(auto_fill(self.underlay_shape,
self.fill_underlay_angle[i],
self.fill_underlay_row_spacing,
self.fill_underlay_row_spacing,
self.fill_underlay_max_stitch_length,
self.running_stitch_length,
self.staggers,
self.fill_underlay_skip_last,
starting_point,
underpath=self.underlay_underpath))
starting_point = stitches[-1]
underlay = StitchGroup(
color=self.color,
tags=("auto_fill", "auto_fill_underlay"),
stitches=auto_fill(
self.underlay_shape,
self.fill_underlay_angle[i],
self.fill_underlay_row_spacing,
self.fill_underlay_row_spacing,
self.fill_underlay_max_stitch_length,
self.running_stitch_length,
self.staggers,
self.fill_underlay_skip_last,
starting_point,
underpath=self.underlay_underpath))
stitch_groups.append(underlay)
stitches.extend(auto_fill(self.fill_shape,
self.angle,
self.row_spacing,
self.end_row_spacing,
self.max_stitch_length,
self.running_stitch_length,
self.staggers,
self.skip_last,
starting_point,
ending_point,
self.underpath))
starting_point = underlay.stitches[-1]
stitch_group = StitchGroup(
color=self.color,
tags=("auto_fill", "auto_fill_top"),
stitches=auto_fill(
self.fill_shape,
self.angle,
self.row_spacing,
self.end_row_spacing,
self.max_stitch_length,
self.running_stitch_length,
self.staggers,
self.skip_last,
starting_point,
ending_point,
self.underpath))
stitch_groups.append(stitch_group)
except Exception:
if hasattr(sys, 'gettrace') and sys.gettrace():
# if we're debugging, let the exception bubble up
@ -261,18 +273,19 @@ class AutoFill(Fill):
self.fatal(message)
return [Patch(stitches=stitches, color=self.color)]
return stitch_groups
def validation_warnings(self):
if self.shape.area < 20:
label = self.node.get(INKSCAPE_LABEL) or self.node.get("id")
yield SmallShapeWarning(self.shape.centroid, label)
if self.shrink_or_grow_shape(self.expand, True).is_empty:
yield ExpandWarning(self.shape.centroid)
def validation_warnings(self):
if self.shape.area < 20:
label = self.node.get(INKSCAPE_LABEL) or self.node.get("id")
yield SmallShapeWarning(self.shape.centroid, label)
if self.shrink_or_grow_shape(-self.fill_underlay_inset, True).is_empty:
yield UnderlayInsetWarning(self.shape.centroid)
if self.shrink_or_grow_shape(self.expand, True).is_empty:
yield ExpandWarning(self.shape.centroid)
for warning in super(AutoFill, self).validation_warnings():
yield warning
if self.shrink_or_grow_shape(-self.fill_underlay_inset, True).is_empty:
yield UnderlayInsetWarning(self.shape.centroid)
for warning in super(AutoFill, self).validation_warnings():
yield warning

Wyświetl plik

@ -93,7 +93,7 @@ class Clone(EmbroideryElement):
return elements
def to_patches(self, last_patch=None):
def to_stitch_groups(self, last_patch=None):
patches = []
source_node = get_clone_source(self.node)
@ -123,7 +123,7 @@ class Clone(EmbroideryElement):
elements = self.clone_to_element(self.node)
for element in elements:
patches.extend(element.to_patches(last_patch))
patches.extend(element.to_stitch_groups(last_patch))
return patches

Wyświetl plik

@ -11,40 +11,13 @@ from inkex import bezier
from ..commands import find_commands
from ..i18n import _
from ..patterns import apply_patterns
from ..svg import (PIXELS_PER_MM, apply_transforms, convert_length,
get_node_transform)
from ..svg.tags import INKSCAPE_LABEL, INKSTITCH_ATTRIBS
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, 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):
if isinstance(other, Patch):
return Patch(self.color, self.stitches + other.stitches)
else:
raise TypeError("Patch can only be added to another Patch")
def __len__(self):
# This method allows `len(patch)` and `if patch:
return len(self.stitches)
def add_stitch(self, stitch):
self.stitches.append(stitch)
def reverse(self):
return Patch(self.color, self.stitches[::-1])
class Param(object):
def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False,
options=[], default=None, tooltip=None, sort_index=0):
@ -328,13 +301,14 @@ class EmbroideryElement(object):
def stop_after(self):
return self.get_boolean_param('stop_after', False)
def to_patches(self, last_patch):
raise NotImplementedError("%s must implement to_patches()" % self.__class__.__name__)
def to_stitch_groups(self, last_patch):
raise NotImplementedError("%s must implement to_stitch_groups()" % self.__class__.__name__)
def embroider(self, last_patch):
self.validate()
patches = self.to_patches(last_patch)
patches = self.to_stitch_groups(last_patch)
apply_patterns(patches, self.node)
for patch in patches:
patch.tie_modus = self.ties

Wyświetl plik

@ -23,5 +23,5 @@ class EmptyDObject(EmbroideryElement):
label = self.node.get(INKSCAPE_LABEL) or self.node.get("id")
yield EmptyD((0, 0), label)
def to_patches(self, last_patch):
def to_stitch_groups(self, last_patch):
return []

Wyświetl plik

@ -10,12 +10,13 @@ import re
from shapely import geometry as shgeo
from shapely.validation import explain_validity
from .element import EmbroideryElement, param
from .validation import ValidationError
from ..i18n import _
from ..stitch_plan import StitchGroup
from ..stitches import legacy_fill
from ..svg import PIXELS_PER_MM
from ..utils import cache
from .element import EmbroideryElement, Patch, param
from .validation import ValidationError
class UnconnectedError(ValidationError):
@ -189,7 +190,7 @@ class Fill(EmbroideryElement):
else:
yield InvalidShapeError((x, y))
def to_patches(self, last_patch):
def to_stitch_groups(self, last_patch):
stitch_lists = legacy_fill(self.shape,
self.angle,
self.row_spacing,
@ -198,4 +199,4 @@ class Fill(EmbroideryElement):
self.flip,
self.staggers,
self.skip_last)
return [Patch(stitches=stitch_list, color=self.color) for stitch_list in stitch_lists]
return [StitchGroup(stitches=stitch_list, color=self.color) for stitch_list in stitch_lists]

Wyświetl plik

@ -29,5 +29,5 @@ class ImageObject(EmbroideryElement):
def validation_warnings(self):
yield ImageTypeWarning(self.center())
def to_patches(self, last_patch):
def to_stitch_groups(self, last_patch):
return []

Wyświetl plik

@ -0,0 +1,33 @@
# 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 inkex
from ..i18n import _
from .element import EmbroideryElement
from .validation import ObjectTypeWarning
class PatternWarning(ObjectTypeWarning):
name = _("Pattern Element")
description = _("This element will not be embroidered. "
"It will appear as a pattern applied to objects in the same group as it. "
"Objects in sub-groups will be ignored.")
steps_to_solve = [
_("To disable pattern mode, remove the pattern marker:"),
_('* Open the Fill and Stroke panel (Objects > Fill and Stroke)'),
_('* Go to the Stroke style tab'),
_('* Under "Markers" choose the first (empty) option in the first dropdown list.')
]
class PatternObject(EmbroideryElement):
def validation_warnings(self):
repr_point = next(inkex.Path(self.parse_path()).end_points)
yield PatternWarning(repr_point)
def to_stitch_groups(self, last_patch):
return []

Wyświetl plik

@ -6,11 +6,12 @@
from inkex import Path
from shapely import geometry as shgeo
from .element import EmbroideryElement, param
from .validation import ValidationWarning
from ..i18n import _
from ..stitch_plan import StitchGroup
from ..utils import cache
from ..utils.geometry import Point
from .element import EmbroideryElement, Patch, param
from .validation import ValidationWarning
class PolylineWarning(ValidationWarning):
@ -100,8 +101,8 @@ class Polyline(EmbroideryElement):
def validation_warnings(self):
yield PolylineWarning(self.points[0])
def to_patches(self, last_patch):
patch = Patch(color=self.color)
def to_stitch_groups(self, last_patch):
patch = StitchGroup(color=self.color)
for stitch in self.stitches:
patch.add_stitch(Point(*stitch))

Wyświetl plik

@ -11,11 +11,12 @@ from shapely import affinity as shaffinity
from shapely import geometry as shgeo
from shapely.ops import nearest_points
from .element import EmbroideryElement, param
from .validation import ValidationError, ValidationWarning
from ..i18n import _
from ..stitch_plan import StitchGroup
from ..svg import line_strings_to_csp, point_lists_to_csp
from ..utils import Point, cache, collapse_duplicate_point, cut
from .element import EmbroideryElement, Patch, param
from .validation import ValidationError, ValidationWarning
class SatinHasFillError(ValidationError):
@ -80,6 +81,14 @@ class SatinColumn(EmbroideryElement):
def e_stitch(self):
return self.get_boolean_param("e_stitch")
@property
@param('max_stitch_length_mm',
_('Maximum stitch length'),
tooltip=_('Maximum stitch length for split stitches.'),
type='float', unit="mm")
def max_stitch_length(self):
return self.get_float_param("max_stitch_length_mm") or None
@property
def color(self):
return self.get_style("stroke")
@ -708,7 +717,10 @@ class SatinColumn(EmbroideryElement):
# other.
forward, back = self.plot_points_on_rails(self.contour_underlay_stitch_length,
-self.contour_underlay_inset)
return Patch(color=self.color, stitches=(forward + list(reversed(back))))
return StitchGroup(
color=self.color,
tags=("satin_column", "satin_column_underlay", "satin_contour_underlay"),
stitches=(forward + list(reversed(back))))
def do_center_walk(self):
# Center walk underlay is just a running stitch down and back on the
@ -717,7 +729,10 @@ class SatinColumn(EmbroideryElement):
# Do it like contour underlay, but inset all the way to the center.
forward, back = self.plot_points_on_rails(self.center_walk_underlay_stitch_length,
-100000)
return Patch(color=self.color, stitches=(forward + list(reversed(back))))
return StitchGroup(
color=self.color,
tags=("satin_column", "satin_column_underlay", "satin_center_walk"),
stitches=(forward + list(reversed(back))))
def do_zigzag_underlay(self):
# zigzag underlay, usually done at a much lower density than the
@ -730,7 +745,7 @@ class SatinColumn(EmbroideryElement):
# "German underlay" described here:
# http://www.mrxstitch.com/underlay-what-lies-beneath-machine-embroidery/
patch = Patch(color=self.color)
patch = StitchGroup(color=self.color)
sides = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0,
-self.zigzag_underlay_inset)
@ -745,6 +760,7 @@ class SatinColumn(EmbroideryElement):
for point in chain.from_iterable(zip(*sides)):
patch.add_stitch(point)
patch.add_tags(("satin_column", "satin_column_underlay", "satin_zigzag_underlay"))
return patch
def do_satin(self):
@ -756,7 +772,10 @@ class SatinColumn(EmbroideryElement):
# print >> dbg, "satin", self.zigzag_spacing, self.pull_compensation
patch = Patch(color=self.color)
if self.max_stitch_length:
return self.do_split_stitch()
patch = StitchGroup(color=self.color)
sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
@ -764,6 +783,7 @@ class SatinColumn(EmbroideryElement):
for point in chain.from_iterable(zip(*sides)):
patch.add_stitch(point)
patch.add_tags(("satin_column", "satin_column_edge"))
return patch
def do_e_stitch(self):
@ -774,7 +794,7 @@ class SatinColumn(EmbroideryElement):
# print >> dbg, "satin", self.zigzag_spacing, self.pull_compensation
patch = Patch(color=self.color)
patch = StitchGroup(color=self.color)
sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
@ -785,16 +805,50 @@ class SatinColumn(EmbroideryElement):
patch.add_stitch(right)
patch.add_stitch(left)
patch.add_tags(("satin_column", "e_stitch"))
return patch
def to_patches(self, last_patch):
def do_split_stitch(self):
# stitches exceeding the maximum stitch length will be divided into equal parts through additional stitches
patch = StitchGroup(color=self.color)
sides = self.plot_points_on_rails(self.zigzag_spacing, self.pull_compensation)
for i, (left, right) in enumerate(zip(*sides)):
patch.add_stitch(left)
patch.stitches[-1].add_tags(("satin_column", "satin_column_edge"))
points, count = self._get_split_points(left, right)
for point in points:
patch.add_stitch(point)
patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch"))
patch.add_stitch(right)
patch.stitches[-1].add_tags(("satin_column", "satin_column_edge"))
# it is possible that the way back has a different length from the first
# but it looks ugly if the points differ too much
# so let's make sure they have at least the same amount of divisions
if not i+1 >= len(sides[0]):
points, count = self._get_split_points(right, sides[0][i+1], count)
for point in points:
patch.add_stitch(point)
patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch"))
return patch
def _get_split_points(self, left, right, count=None):
points = []
distance = left.distance(right)
split_count = count or int(-(-distance // self.max_stitch_length))
for i in range(split_count):
line = shgeo.LineString((left, right))
split_point = line.interpolate((i+1)/split_count, normalized=True)
points.append(Point(split_point.x, split_point.y))
return [points, split_count]
def to_stitch_groups(self, last_patch):
# Stitch a variable-width satin column, zig-zagging between two paths.
# The algorithm will draw zigzags between each consecutive pair of
# beziers. The boundary points between beziers serve as "checkpoints",
# allowing the user to control how the zigzags flow around corners.
patch = Patch(color=self.color)
patch = StitchGroup(color=self.color)
if self.center_walk_underlay:
patch += self.do_center_walk()

Wyświetl plik

@ -7,11 +7,12 @@ import sys
import shapely.geometry
from .element import EmbroideryElement, param
from ..i18n import _
from ..stitch_plan import StitchGroup
from ..stitches import bean_stitch, running_stitch
from ..svg import parse_length_with_units
from ..utils import Point, cache
from .element import EmbroideryElement, Patch, param
warned_about_legacy_running_stitch = False
@ -190,15 +191,15 @@ class Stroke(EmbroideryElement):
stitches = running_stitch(repeated_path, stitch_length)
return Patch(self.color, stitches)
return StitchGroup(self.color, stitches)
def to_patches(self, last_patch):
def to_stitch_groups(self, last_patch):
patches = []
for path in self.paths:
path = [Point(x, y) for x, y in path]
if self.manual_stitch_mode:
patch = Patch(color=self.color, stitches=path, stitch_as_is=True)
patch = StitchGroup(color=self.color, stitches=path, stitch_as_is=True)
elif self.is_running_stitch():
patch = self.running_stitch(path, self.running_stitch_length)

Wyświetl plik

@ -29,5 +29,5 @@ class TextObject(EmbroideryElement):
def validation_warnings(self):
yield TextTypeWarning(self.pointer())
def to_patches(self, last_patch):
def to_stitch_groups(self, last_patch):
return []

Wyświetl plik

@ -4,6 +4,7 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from ..commands import is_command
from ..patterns import is_pattern
from ..svg.tags import (EMBROIDERABLE_TAGS, SVG_IMAGE_TAG, SVG_PATH_TAG,
SVG_POLYLINE_TAG, SVG_TEXT_TAG)
from .auto_fill import AutoFill
@ -12,6 +13,7 @@ from .element import EmbroideryElement
from .empty_d_object import EmptyDObject
from .fill import Fill
from .image import ImageObject
from .pattern import PatternObject
from .polyline import Polyline
from .satin_column import SatinColumn
from .stroke import Stroke
@ -28,6 +30,9 @@ def node_to_elements(node): # noqa: C901
elif node.tag == SVG_PATH_TAG and not node.get('d', ''):
return [EmptyDObject(node)]
elif is_pattern(node):
return [PatternObject(node)]
elif node.tag in EMBROIDERABLE_TAGS:
element = EmbroideryElement(node)

Wyświetl plik

@ -30,6 +30,7 @@ from .params import Params
from .print_pdf import Print
from .remove_embroidery_settings import RemoveEmbroiderySettings
from .reorder import Reorder
from .selection_to_pattern import SelectionToPattern
from .simulator import Simulator
from .stitch_plan_preview import StitchPlanPreview
from .zip import Zip
@ -42,6 +43,7 @@ __all__ = extensions = [StitchPlanPreview,
Output,
Zip,
Flip,
SelectionToPattern,
ObjectCommands,
LayerCommands,
GlobalCommands,

Wyświetl plik

@ -16,6 +16,7 @@ from ..commands import is_command, layer_commands
from ..elements import EmbroideryElement, nodes_to_elements
from ..elements.clone import is_clone
from ..i18n import _
from ..patterns import is_pattern
from ..svg import generate_unique_id
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG)
@ -160,9 +161,10 @@ class InkstitchExtension(inkex.Effect):
if selected:
if node.tag == SVG_GROUP_TAG:
pass
elif getattr(node, "get_path", None):
elif (node.tag in EMBROIDERABLE_TAGS or is_clone(node)) and not is_pattern(node):
nodes.append(node)
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or node.tag in EMBROIDERABLE_TAGS or is_clone(node)):
# add images, text and patterns for the troubleshoot extension
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or is_pattern(node)):
nodes.append(node)
return nodes
@ -186,7 +188,7 @@ class InkstitchExtension(inkex.Effect):
selected.append(node)
return selected
def elements_to_patches(self, elements):
def elements_to_stitch_groups(self, elements):
patches = []
for element in elements:
if patches:

Wyświetl plik

@ -30,9 +30,11 @@ class LetteringFrame(wx.Frame):
def __init__(self, *args, **kwargs):
# This is necessary because of https://github.com/inkstitch/inkstitch/issues/1186
if sys.platform.startswith('win'):
if sys.platform.startswith('win32'):
import locale
locale.setlocale(locale.LC_ALL, "C")
lc = wx.Locale()
lc.Init(wx.LANGUAGE_DEFAULT)
# begin wxGlade: MyFrame.__init__
self.group = kwargs.pop('group')

Wyświetl plik

@ -8,7 +8,7 @@ import sys
import tempfile
from ..output import write_embroidery_file
from ..stitch_plan import patches_to_stitch_plan
from ..stitch_plan import stitch_groups_to_stitch_plan
from .base import InkstitchExtension
@ -52,8 +52,8 @@ class Output(InkstitchExtension):
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False))
patches = self.elements_to_stitch_groups(self.elements)
stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, disable_ties=self.settings.get('laser_mode', False))
temp_file = tempfile.NamedTemporaryFile(suffix=".%s" % self.file_extension, delete=False)

Wyświetl plik

@ -331,9 +331,11 @@ class ParamsTab(ScrolledPanel):
class SettingsFrame(wx.Frame):
def __init__(self, *args, **kwargs):
# This is necessary because of https://github.com/inkstitch/inkstitch/issues/1186
if sys.platform.startswith('win'):
if sys.platform.startswith('win32'):
import locale
locale.setlocale(locale.LC_ALL, "C")
lc = wx.Locale()
lc.Init(wx.LANGUAGE_DEFAULT)
# begin wxGlade: MyFrame.__init__
self.tabs_factory = kwargs.pop('tabs_factory', [])

Wyświetl plik

@ -23,7 +23,7 @@ from werkzeug.serving import make_server
from ..gui import open_url
from ..i18n import get_languages
from ..i18n import translation as inkstitch_translation
from ..stitch_plan import patches_to_stitch_plan
from ..stitch_plan import stitch_groups_to_stitch_plan
from ..svg import render_stitch_plan
from ..svg.tags import INKSCAPE_GROUPMODE
from ..threads import ThreadCatalog
@ -302,8 +302,8 @@ class Print(InkstitchExtension):
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
patches = self.elements_to_stitch_groups(self.elements)
stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len)
palette = ThreadCatalog().match_and_apply_palette(stitch_plan, self.get_inkstitch_metadata()['thread-palette'])
overview_svg, color_block_svgs = self.render_svgs(stitch_plan, realistic=False)

Wyświetl plik

@ -0,0 +1,63 @@
# Authors: see git history
#
# Copyright (c) 2021 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import inkex
from lxml import etree
from ..i18n import _
from ..svg.tags import EMBROIDERABLE_TAGS, SVG_DEFS_TAG
from .base import InkstitchExtension
class SelectionToPattern(InkstitchExtension):
def effect(self):
if not self.get_elements():
return
if not self.svg.selected:
inkex.errormsg(_("Please select at least one object to be marked as a pattern."))
return
for pattern in self.get_nodes():
if pattern.tag in EMBROIDERABLE_TAGS:
self.set_marker(pattern)
def set_marker(self, node):
xpath = ".//marker[@id='inkstitch-pattern-marker']"
pattern_marker = self.document.xpath(xpath)
if not pattern_marker:
# get or create def element
defs = self.document.find(SVG_DEFS_TAG)
if defs is None:
defs = etree.SubElement(self.document, SVG_DEFS_TAG)
# insert marker
marker = """<marker
refX="10"
refY="5"
orient="auto"
id="inkstitch-pattern-marker">
<g
id="inkstitch-pattern-group">
<path
style="fill:#fafafa;stroke:#ff5500;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:1, 1;stroke-dashoffset:0;stroke-opacity:1;fill-opacity:0.8;"
d="M 10.12911,5.2916678 A 4.8374424,4.8374426 0 0 1 5.2916656,10.12911 4.8374424,4.8374426 0 0 1 0.45422399,5.2916678 4.8374424,4.8374426 0 0 1 5.2916656,0.45422399 4.8374424,4.8374426 0 0 1 10.12911,5.2916678 Z"
id="inkstitch-pattern-marker-circle" />
<path
style="fill:none;stroke:#000000;stroke-width:0.4;stroke-linecap:round;stroke-miterlimit:4;"
id="inkstitch-pattern-marker-spiral"
d="M 4.9673651,5.7245662 C 4.7549848,5.7646159 4.6247356,5.522384 4.6430021,5.3419847 4.6765851,5.0103151 5.036231,4.835347 5.3381858,4.8987426 5.7863901,4.9928495 6.0126802,5.4853625 5.9002872,5.9065088 5.7495249,6.4714237 5.1195537,6.7504036 4.5799191,6.5874894 3.898118,6.3816539 3.5659013,5.6122905 3.7800789,4.9545192 4.0402258,4.1556558 4.9498996,3.7699484 5.7256318,4.035839 6.6416744,4.3498087 7.0810483,5.4003986 6.7631909,6.2939744 6.395633,7.3272552 5.2038143,7.8204128 4.1924535,7.4503931 3.0418762,7.0294421 2.4948761,5.6961604 2.9171752,4.567073 3.3914021,3.2991406 4.8663228,2.6982592 6.1130974,3.1729158 7.4983851,3.7003207 8.1531869,5.3169977 7.6260947,6.6814205 7.0456093,8.1841025 5.2870784,8.8928844 3.8050073,8.3132966 2.1849115,7.6797506 1.4221671,5.7793073 2.0542715,4.1796074 2.7408201,2.4420977 4.7832541,1.6253548 6.5005435,2.310012 8.3554869,3.0495434 9.2262638,5.2339874 8.4890181,7.0688861 8.4256397,7.2266036 8.3515789,7.379984 8.2675333,7.5277183" />
</g>
</marker>""" # noqa: E501
defs.append(etree.fromstring(marker))
# attach marker to node
style = node.get('style') or ''
style = style.split(";")
style = [i for i in style if not i.startswith('marker-start')]
style.append('marker-start:url(#inkstitch-pattern-marker)')
node.set('style', ";".join(style))

Wyświetl plik

@ -3,7 +3,7 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from ..stitch_plan import patches_to_stitch_plan
from ..stitch_plan import stitch_groups_to_stitch_plan
from ..svg import render_stitch_plan
from .base import InkstitchExtension
@ -23,8 +23,8 @@ class StitchPlanPreview(InkstitchExtension):
realistic = False
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
patches = self.elements_to_stitch_groups(self.elements)
stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len)
render_stitch_plan(svg, stitch_plan, realistic)
# translate stitch plan to the right side of the canvas

Wyświetl plik

@ -16,7 +16,7 @@ import pyembroidery
from ..i18n import _
from ..output import write_embroidery_file
from ..stitch_plan import patches_to_stitch_plan
from ..stitch_plan import stitch_groups_to_stitch_plan
from ..threads import ThreadCatalog
from .base import InkstitchExtension
@ -43,8 +43,8 @@ class Zip(InkstitchExtension):
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
patches = self.elements_to_patches(self.elements)
stitch_plan = patches_to_stitch_plan(patches, collapse_len=collapse_len)
patches = self.elements_to_stitch_groups(self.elements)
stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len)
base_file_name = self.get_base_file_name()
path = tempfile.mkdtemp()

Wyświetl plik

@ -11,7 +11,7 @@ import wx
from wx.lib.intctrl import IntCtrl
from ..i18n import _
from ..stitch_plan import patches_to_stitch_plan, stitch_plan_from_file
from ..stitch_plan import stitch_groups_to_stitch_plan, stitch_plan_from_file
from ..svg import PIXELS_PER_MM
# L10N command label at bottom of simulator window
@ -733,7 +733,7 @@ class SimulatorPreview(Thread):
The parent is expected to be a wx.Window and also implement the following methods:
def generate_patches(self, abort_event):
Produce an list of Patch instances. This method will be
Produce an list of StitchGroup instances. This method will be
invoked in a background thread and it is expected that it may
take awhile.
@ -789,7 +789,7 @@ class SimulatorPreview(Thread):
return
if patches and not self.refresh_needed.is_set():
stitch_plan = patches_to_stitch_plan(patches)
stitch_plan = stitch_groups_to_stitch_plan(patches)
# GUI stuff needs to happen in the main thread, so we ask the main
# thread to call refresh_simulator().

106
lib/patterns.py 100644
Wyświetl plik

@ -0,0 +1,106 @@
# 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 inkex
from shapely import geometry as shgeo
from .stitch_plan import Stitch
from .svg.tags import EMBROIDERABLE_TAGS
from .utils import Point
def is_pattern(node):
if node.tag not in EMBROIDERABLE_TAGS:
return False
style = node.get('style') or ''
return "marker-start:url(#inkstitch-pattern-marker)" in style
def apply_patterns(patches, node):
patterns = _get_patterns(node)
_apply_stroke_patterns(patterns['stroke_patterns'], patches)
_apply_fill_patterns(patterns['fill_patterns'], patches)
def _apply_stroke_patterns(patterns, patches):
for pattern in patterns:
for patch in patches:
patch_points = []
for i, stitch in enumerate(patch.stitches):
patch_points.append(stitch)
if i == len(patch.stitches) - 1:
continue
intersection_points = _get_pattern_points(stitch, patch.stitches[i+1], pattern)
for point in intersection_points:
patch_points.append(Stitch(point, tags=('pattern_point',)))
patch.stitches = patch_points
def _apply_fill_patterns(patterns, patches):
for pattern in patterns:
for patch in patches:
patch_points = []
for i, stitch in enumerate(patch.stitches):
if not shgeo.Point(stitch).within(pattern):
# keep points outside the fill pattern
patch_points.append(stitch)
elif i - 1 < 0 or i >= len(patch.stitches) - 1:
# keep start and end points
patch_points.append(stitch)
elif stitch.has_tag('fill_row_start') or stitch.has_tag('fill_row_end'):
# keep points if they are the start or end of a fill stitch row
patch_points.append(stitch)
elif stitch.has_tag('auto_fill') and not stitch.has_tag('auto_fill_top'):
# keep auto-fill underlay
patch_points.append(stitch)
elif stitch.has_tag('auto_fill_travel'):
# keep travel stitches (underpath or travel around the border)
patch_points.append(stitch)
elif stitch.has_tag('satin_column') and not stitch.has_tag('satin_split_stitch'):
# keep satin column stitches unless they are split stitches
patch_points.append(stitch)
patch.stitches = patch_points
def _get_patterns(node):
from .elements import EmbroideryElement
from .elements.fill import Fill
from .elements.stroke import Stroke
fills = []
strokes = []
xpath = "./parent::svg:g/*[contains(@style, 'marker-start:url(#inkstitch-pattern-marker)')]"
patterns = node.xpath(xpath, namespaces=inkex.NSS)
for pattern in patterns:
if pattern.tag not in EMBROIDERABLE_TAGS:
continue
element = EmbroideryElement(pattern)
fill = element.get_style('fill')
stroke = element.get_style('stroke')
if fill is not None:
fill_pattern = Fill(pattern).shape
fills.append(fill_pattern)
if stroke is not None:
stroke_pattern = Stroke(pattern).paths
line_strings = [shgeo.LineString(path) for path in stroke_pattern]
strokes.append(shgeo.MultiLineString(line_strings))
return {'fill_patterns': fills, 'stroke_patterns': strokes}
def _get_pattern_points(first, second, pattern):
points = []
intersection = shgeo.LineString([first, second]).intersection(pattern)
if isinstance(intersection, shgeo.Point):
points.append(Point(intersection.x, intersection.y))
if isinstance(intersection, shgeo.MultiPoint):
for point in intersection:
points.append(Point(point.x, point.y))
# sort points after their distance to first
points.sort(key=lambda point: point.distance(first))
return points

Wyświetl plik

@ -3,6 +3,8 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from .stitch_plan import patches_to_stitch_plan, StitchPlan, ColorBlock
from .stitch_plan import stitch_groups_to_stitch_plan, StitchPlan
from .color_block import ColorBlock
from .stitch_group import StitchGroup
from .stitch import Stitch
from .read_file import stitch_plan_from_file

Wyświetl plik

@ -0,0 +1,143 @@
# Authors: see git history
#
# 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 ..threads import ThreadColor
from ..utils.geometry import Point
from ..svg import PIXELS_PER_MM
class ColorBlock(object):
"""Holds a set of stitches, all with the same thread color."""
def __init__(self, color=None, stitches=None):
self.color = color
self.stitches = stitches or []
def __iter__(self):
return iter(self.stitches)
def __len__(self):
return len(self.stitches)
def __repr__(self):
return "ColorBlock(%s, %s)" % (self.color, self.stitches)
def __getitem__(self, item):
return self.stitches[item]
def __delitem__(self, item):
del self.stitches[item]
def __json__(self):
return dict(color=self.color, stitches=self.stitches)
def has_color(self):
return self._color is not None
@property
def color(self):
return self._color
@color.setter
def color(self, value):
if isinstance(value, ThreadColor):
self._color = value
elif value is None:
self._color = None
else:
self._color = ThreadColor(value)
@property
def last_stitch(self):
if self.stitches:
return self.stitches[-1]
else:
return None
@property
def num_stitches(self):
"""Number of stitches in this color block."""
return len(self.stitches)
@property
def num_trims(self):
"""Number of trims in this color block."""
return sum(1 for stitch in self if stitch.trim)
@property
def stop_after(self):
if self.last_stitch is not None:
return self.last_stitch.stop
else:
return False
@property
def trim_after(self):
# If there's a STOP, it will be at the end. We still want to return
# True.
for stitch in reversed(self.stitches):
if stitch.stop or stitch.jump:
continue
elif stitch.trim:
return True
else:
break
return False
def filter_duplicate_stitches(self):
if not self.stitches:
return
stitches = [self.stitches[0]]
for stitch in self.stitches[1:]:
if stitches[-1].jump or stitch.stop or stitch.trim or stitch.color_change:
# Don't consider jumps, stops, color changes, or trims as candidates for filtering
pass
else:
length = (stitch - stitches[-1]).length()
if length <= 0.1 * PIXELS_PER_MM:
# duplicate stitch, skip this one
continue
stitches.append(stitch)
self.stitches = stitches
def add_stitch(self, *args, **kwargs):
if not args:
# They're adding a command, e.g. `color_block.add_stitch(stop=True)``.
# Use the position from the last stitch.
if self.last_stitch:
args = (self.last_stitch.x, self.last_stitch.y)
else:
raise ValueError("internal error: can't add a command to an empty stitch block")
self.stitches.append(Stitch(*args, **kwargs))
if isinstance(args[0], Stitch):
self.stitches.append(args[0])
elif isinstance(args[0], Point):
self.stitches.append(Stitch(args[0].x, args[0].y, *args[1:], **kwargs))
def add_stitches(self, stitches, *args, **kwargs):
for stitch in stitches:
if isinstance(stitch, (Stitch, Point)):
self.add_stitch(stitch, *args, **kwargs)
else:
self.add_stitch(*stitch, *args, **kwargs)
def replace_stitches(self, stitches):
self.stitches = stitches
@property
def bounding_box(self):
minx = min(stitch.x for stitch in self)
miny = min(stitch.y for stitch in self)
maxx = max(stitch.x for stitch in self)
maxy = max(stitch.y for stitch in self)
return minx, miny, maxx, maxy

Wyświetl plik

@ -4,12 +4,25 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from ..utils.geometry import Point
from copy import deepcopy
class Stitch(Point):
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
"""A stitch is a Point with extra information telling how to sew it."""
def __init__(self, x, y=None, color=None, jump=False, stop=False, trim=False, color_change=False, tie_modus=0, no_ties=False, tags=None):
if isinstance(x, Stitch):
# Allow creating a Stitch from another Stitch. Attributes passed as
# arguments will override any existing attributes.
vars(self).update(deepcopy(vars(x)))
elif isinstance(x, Point):
# Allow creating a Stitch from a Point
point = x
self.x = point.x
self.y = point.y
else:
Point.__init__(self, x, y)
self.color = color
self.jump = jump
self.trim = trim
@ -17,12 +30,9 @@ class Stitch(Point):
self.color_change = color_change
self.tie_modus = tie_modus
self.no_ties = no_ties
self.tags = set()
# Allow creating a Stitch from a Point
if isinstance(x, Point):
point = x
self.x = point.x
self.y = point.y
self.add_tags(tags or [])
def __repr__(self):
return "Stitch(%s, %s, %s, %s, %s, %s, %s, %s, %s)" % (self.x,
@ -35,8 +45,32 @@ class Stitch(Point):
"NO TIES" if self.no_ties else " ",
"COLOR CHANGE" if self.color_change else " ")
def add_tags(self, tags):
for tag in tags:
self.add_tag(tag)
def add_tag(self, tag):
"""Store arbitrary information about a stitch.
Tags can be used to store any information about a stitch. This can be
used by other parts of the code to keep track of where a Stitch came
from. The Stitch treats tags as opaque.
Use strings as tags. Python automatically optimizes this kind of
usage of strings, and it doesn't have to constantly do string
comparisons. More details here:
https://stackabuse.com/guide-to-string-interning-in-python
"""
self.tags.add(tag)
def has_tag(self, tag):
return tag in self.tags
def copy(self):
return Stitch(self.x, self.y, self.color, self.jump, self.stop, self.trim, self.color_change, self.tie_modus, 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, self.tags)
def __json__(self):
return vars(self)
attributes = dict(vars(self))
attributes['tags'] = list(attributes['tags'])
return attributes

Wyświetl plik

@ -0,0 +1,64 @@
# Authors: see git history
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from .stitch import Stitch
class StitchGroup:
"""A collection of Stitch objects with attached instructions and attributes.
StitchGroups will later be combined to make ColorBlocks, which in turn are
combined to make a StitchPlan. Jump stitches are allowed between
StitchGroups, but not between stitches inside a StitchGroup. This means
that EmbroideryElement classes should produce multiple StitchGroups only if
they want to allow for the possibility of jump stitches to be added in
between them by the stitch plan generation code.
"""
def __init__(self, color=None, stitches=None, trim_after=False, stop_after=False, tie_modus=0, stitch_as_is=False, tags=None):
self.color = color
self.trim_after = trim_after
self.stop_after = stop_after
self.tie_modus = tie_modus
self.stitch_as_is = stitch_as_is
self.stitches = []
if stitches:
self.add_stitches(stitches)
if tags:
self.add_tags(tags)
def __add__(self, other):
if isinstance(other, StitchGroup):
return StitchGroup(self.color, self.stitches + other.stitches)
else:
raise TypeError("StitchGroup can only be added to another StitchGroup")
def __len__(self):
# This method allows `len(patch)` and `if patch:
return len(self.stitches)
def add_stitches(self, stitches):
for stitch in stitches:
self.add_stitch(stitch)
def add_stitch(self, stitch):
if not isinstance(stitch, Stitch):
# probably a Point
stitch = Stitch(stitch)
self.stitches.append(stitch)
def reverse(self):
return StitchGroup(self.color, self.stitches[::-1])
def add_tags(self, tags):
for stitch in self.stitches:
stitch.add_tags(tags)
def add_tag(self, tag):
for stitch in self.stitches:
stitch.add_tag(tag)

Wyświetl plik

@ -3,56 +3,54 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from ..svg import PIXELS_PER_MM
from ..threads import ThreadColor
from ..utils.geometry import Point
from .stitch import Stitch
from .ties import add_ties
from .color_block import ColorBlock
from ..svg import PIXELS_PER_MM
def patches_to_stitch_plan(patches, collapse_len=None, disable_ties=False): # noqa: C901
def stitch_groups_to_stitch_plan(stitch_groups, collapse_len=None, disable_ties=False): # noqa: C901
"""Convert a collection of inkstitch.element.Patch objects to a StitchPlan.
"""Convert a collection of StitchGroups to a StitchPlan.
* applies instructions embedded in the Patch such as trim_after and stop_after
* applies instructions embedded in the StitchGroup such as trim_after and stop_after
* adds tie-ins and tie-offs
* adds jump-stitches between patches if necessary
* adds jump-stitches between stitch_group if necessary
"""
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)
color_block = stitch_plan.new_color_block(color=stitch_groups[0].color)
for patch in patches:
if not patch.stitches:
for stitch_group in stitch_groups:
if not stitch_group.stitches:
continue
if color_block.color != patch.color:
if color_block.color != stitch_group.color:
if len(color_block) == 0:
# We just processed a stop, which created a new color block.
# We'll just claim this new block as ours:
color_block.color = patch.color
color_block.color = stitch_group.color
else:
# end the previous block with a color change
color_block.add_stitch(color_change=True)
# make a new block of our color
color_block = stitch_plan.new_color_block(color=patch.color)
color_block = stitch_plan.new_color_block(color=stitch_group.color)
# always start a color with a JUMP to the first stitch position
color_block.add_stitch(patch.stitches[0], jump=True)
color_block.add_stitch(stitch_group.stitches[0], jump=True)
else:
if len(color_block) and (patch.stitches[0] - color_block.stitches[-1]).length() > collapse_len:
color_block.add_stitch(patch.stitches[0], jump=True)
if len(color_block) and (stitch_group.stitches[0] - color_block.stitches[-1]).length() > collapse_len:
color_block.add_stitch(stitch_group.stitches[0], jump=True)
color_block.add_stitches(stitches=patch.stitches, tie_modus=patch.tie_modus, no_ties=patch.stitch_as_is)
color_block.add_stitches(stitches=stitch_group.stitches, tie_modus=stitch_group.tie_modus, no_ties=stitch_group.stitch_as_is)
if patch.trim_after:
if stitch_group.trim_after:
color_block.add_stitch(trim=True)
if patch.stop_after:
if stitch_group.stop_after:
color_block.add_stitch(stop=True)
color_block = stitch_plan.new_color_block(color_block.color)
@ -168,141 +166,3 @@ class StitchPlan(object):
return self.color_blocks[-1]
else:
return None
class ColorBlock(object):
"""Holds a set of stitches, all with the same thread color."""
def __init__(self, color=None, stitches=None):
self.color = color
self.stitches = stitches or []
def __iter__(self):
return iter(self.stitches)
def __len__(self):
return len(self.stitches)
def __repr__(self):
return "ColorBlock(%s, %s)" % (self.color, self.stitches)
def __getitem__(self, item):
return self.stitches[item]
def __delitem__(self, item):
del self.stitches[item]
def __json__(self):
return dict(color=self.color, stitches=self.stitches)
def has_color(self):
return self._color is not None
@property
def color(self):
return self._color
@color.setter
def color(self, value):
if isinstance(value, ThreadColor):
self._color = value
elif value is None:
self._color = None
else:
self._color = ThreadColor(value)
@property
def last_stitch(self):
if self.stitches:
return self.stitches[-1]
else:
return None
@property
def num_stitches(self):
"""Number of stitches in this color block."""
return len(self.stitches)
@property
def num_trims(self):
"""Number of trims in this color block."""
return sum(1 for stitch in self if stitch.trim)
@property
def stop_after(self):
if self.last_stitch is not None:
return self.last_stitch.stop
else:
return False
@property
def trim_after(self):
# If there's a STOP, it will be at the end. We still want to return
# True.
for stitch in reversed(self.stitches):
if stitch.stop or stitch.jump:
continue
elif stitch.trim:
return True
else:
break
return False
def filter_duplicate_stitches(self):
if not self.stitches:
return
stitches = [self.stitches[0]]
for stitch in self.stitches[1:]:
if stitches[-1].jump or stitch.stop or stitch.trim or stitch.color_change:
# Don't consider jumps, stops, color changes, or trims as candidates for filtering
pass
else:
length = (stitch - stitches[-1]).length()
if length <= 0.1 * PIXELS_PER_MM:
# duplicate stitch, skip this one
continue
stitches.append(stitch)
self.stitches = stitches
def add_stitch(self, *args, **kwargs):
if not args:
# They're adding a command, e.g. `color_block.add_stitch(stop=True)``.
# Use the position from the last stitch.
if self.last_stitch:
args = (self.last_stitch.x, self.last_stitch.y)
else:
raise ValueError("internal error: can't add a command to an empty stitch block")
if isinstance(args[0], Stitch):
self.stitches.append(args[0])
elif isinstance(args[0], Point):
self.stitches.append(Stitch(args[0].x, args[0].y, *args[1:], **kwargs))
else:
if not args and self.last_stitch:
args = (self.last_stitch.x, self.last_stitch.y)
self.stitches.append(Stitch(*args, **kwargs))
def add_stitches(self, stitches, *args, **kwargs):
for stitch in stitches:
if isinstance(stitch, (Stitch, Point)):
self.add_stitch(stitch, *args, **kwargs)
else:
self.add_stitch(*(list(stitch) + args), **kwargs)
def replace_stitches(self, stitches):
self.stitches = stitches
@property
def bounding_box(self):
minx = min(stitch.x for stitch in self)
miny = min(stitch.y for stitch in self)
maxx = max(stitch.x for stitch in self)
maxy = max(stitch.y for stitch in self)
return minx, miny, maxx, maxy

Wyświetl plik

@ -14,6 +14,7 @@ from shapely.ops import snap
from shapely.strtree import STRtree
from ..debug import debug
from ..stitch_plan import Stitch
from ..svg import PIXELS_PER_MM
from ..utils.geometry import Point as InkstitchPoint
from ..utils.geometry import line_string_to_point_list
@ -592,9 +593,12 @@ def travel(travel_graph, start, end, running_stitch_length, skip_last):
"""Create stitches to get from one point on an outline of the shape to another."""
path = networkx.shortest_path(travel_graph, start, end, weight='weight')
path = [InkstitchPoint(*p) for p in path]
path = [Stitch(*p) for p in path]
stitches = running_stitch(path, running_stitch_length)
for stitch in stitches:
stitch.add_tag('auto_fill_travel')
# The path's first stitch will start at the end of a row of stitches. We
# don't want to double that last stitch, so we'd like to skip it.
if skip_last and len(path) > 2:
@ -619,7 +623,7 @@ def path_to_stitches(path, travel_graph, fill_stitch_graph, angle, row_spacing,
# If the very first stitch is travel, we'll omit it in travel(), so add it here.
if not path[0].is_segment():
stitches.append(InkstitchPoint(*path[0].nodes[0]))
stitches.append(Stitch(*path[0].nodes[0]))
for edge in path:
if edge.is_segment():

Wyświetl plik

@ -7,6 +7,7 @@ import math
import shapely
from ..stitch_plan import Stitch
from ..svg import PIXELS_PER_MM
from ..utils import Point as InkstitchPoint
from ..utils import cache
@ -65,16 +66,13 @@ def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, stagge
# tile with each other. That's important because we often get
# abutting fill regions from pull_runs().
beg = InkstitchPoint(*beg)
end = InkstitchPoint(*end)
beg = Stitch(*beg, tags=('fill_row_start',))
end = Stitch(*end, tags=('fill_row_end',))
row_direction = (end - beg).unit()
segment_length = (end - beg).length()
# only stitch the first point if it's a reasonable distance away from the
# last stitch
if not stitches or (beg - stitches[-1]).length() > 0.5 * PIXELS_PER_MM:
stitches.append(beg)
stitches.append(beg)
first_stitch = adjust_stagger(beg, angle, row_spacing, max_stitch_length, staggers)
@ -85,7 +83,7 @@ def stitch_row(stitches, beg, end, angle, row_spacing, max_stitch_length, stagge
offset = (first_stitch - beg).length()
while offset < segment_length:
stitches.append(beg + offset * row_direction)
stitches.append(Stitch(beg + offset * row_direction, tags=('fill_row')))
offset += max_stitch_length
if (end - stitches[-1]).length() > 0.1 * PIXELS_PER_MM and not skip_last:

Wyświetl plik

@ -73,22 +73,22 @@ class Point:
return vars(self)
def __add__(self, other):
return Point(self.x + other.x, self.y + other.y)
return self.__class__(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return Point(self.x - other.x, self.y - other.y)
return self.__class__(self.x - other.x, self.y - other.y)
def mul(self, scalar):
return Point(self.x * scalar, self.y * scalar)
return self.__class__(self.x * scalar, self.y * scalar)
def __mul__(self, other):
if isinstance(other, Point):
# dot product
return self.x * other.x + self.y * other.y
elif isinstance(other, (int, float)):
return Point(self.x * other, self.y * other)
return self.__class__(self.x * other, self.y * other)
else:
raise ValueError("cannot multiply Point by %s" % type(other))
raise ValueError("cannot multiply %s by %s" % (type(self), type(other)))
def __neg__(self):
return self * -1
@ -97,16 +97,16 @@ class Point:
if isinstance(other, (int, float)):
return self.__mul__(other)
else:
raise ValueError("cannot multiply Point by %s" % type(other))
raise ValueError("cannot multiply %s by %s" % (type(self), type(other)))
def __div__(self, other):
if isinstance(other, (int, float)):
return self * (1.0 / other)
else:
raise ValueError("cannot divide Point by %s" % type(other))
raise ValueError("cannot divide %s by %s" % (type(self), type(other)))
def __repr__(self):
return "Point(%s,%s)" % (self.x, self.y)
return "%s(%s,%s)" % (type(self), self.x, self.y)
def length(self):
return math.sqrt(math.pow(self.x, 2.0) + math.pow(self.y, 2.0))
@ -118,13 +118,13 @@ class Point:
return self.mul(1.0 / self.length())
def rotate_left(self):
return Point(-self.y, self.x)
return self.__class__(-self.y, self.x)
def rotate(self, angle):
return Point(self.x * math.cos(angle) - self.y * math.sin(angle), self.y * math.cos(angle) + self.x * math.sin(angle))
return self.__class__(self.x * math.cos(angle) - self.y * math.sin(angle), self.y * math.cos(angle) + self.x * math.sin(angle))
def as_int(self):
return Point(int(round(self.x)), int(round(self.y)))
return self.__class__(int(round(self.x)), int(round(self.y)))
def as_tuple(self):
return (self.x, self.y)

Wyświetl plik

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>{% trans %}Selection to pattern{% endtrans %}</name>
<id>org.inkstitch.selection_to_pattern.{{ locale }}</id>
<param name="extension" type="string" gui-hidden="true">selection_to_pattern</param>
<effect>
<object-type>all</object-type>
<effects-menu>
<submenu name="Ink/Stitch">
<submenu name="{% trans %}Edit{% endtrans %}" />
</submenu>
</effects-menu>
</effect>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>