Add debug variable to enable sew stack elements (#3476)

pull/3481/head dev-build-kaalleen-density_map_without_scipy
Kaalleen 2025-02-03 22:37:36 +01:00 zatwierdzone przez GitHub
rodzic e1c6d8c595
commit 497fbcfab5
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
15 zmienionych plików z 141 dodań i 53 usunięć

Wyświetl plik

@ -37,6 +37,9 @@
### base name for bash script, default: "debug_inkstitch"
# bash_file_base = "debug_inkstitch"
### enable sew_stack elements, default: false
# enable_sew_stack = true
[PROFILE]
### select one active profiler_type, default: "none"
# profiler_type = "cprofile"

Wyświetl plik

@ -86,7 +86,7 @@ if not running_as_frozen: # debugging/profiling only in development mode
profiler_type = debug_utils.resolve_profiler_type(ini) # read profile type from ini file or cmd line
if running_from_inkscape:
# process creation of the Bash script - should be done before sys.path is modified, see below in prefere_pip_inkex
# process creation of the Bash script - should be done before sys.path is modified, see below in prefer_pip_inkex
if safe_get(ini, "DEBUG", "create_bash_script", default=False): # create script only if enabled in DEBUG.toml
debug_utils.write_offline_debug_script(SCRIPTDIR, ini)
@ -97,8 +97,8 @@ if not running_as_frozen: # debugging/profiling only in development mode
# prefer pip installed inkex over inkscape bundled inkex, pip version is bundled with Inkstitch
# - must be be done before importing inkex
prefere_pip_inkex = safe_get(ini, "LIBRARY", "prefer_pip_inkex", default=True)
if prefere_pip_inkex and 'PYTHONPATH' in os.environ:
prefer_pip_inkex = safe_get(ini, "LIBRARY", "prefer_pip_inkex", default=True)
if prefer_pip_inkex and 'PYTHONPATH' in os.environ:
debug_utils.reorder_sys_path()
# enabling of debug depends on value of debug_type in DEBUG.toml file

Wyświetl plik

@ -29,7 +29,7 @@ class EmptyDObject(EmbroideryElement):
@property
def first_stitch(self):
return
return None
def to_stitch_groups(self, last_stitch_group, next_element=None):
return []

Wyświetl plik

@ -20,6 +20,7 @@ class ImageTypeWarning(ObjectTypeWarning):
class ImageObject(EmbroideryElement):
name = "Image"
def center(self):
transform = get_node_transform(self.node.getparent())
@ -31,3 +32,6 @@ class ImageObject(EmbroideryElement):
def to_stitch_groups(self, last_stitch_group):
return []
def first_stitch(self):
return None

Wyświetl plik

@ -23,6 +23,7 @@ class MarkerWarning(ObjectTypeWarning):
class MarkerObject(EmbroideryElement):
name = "Marker"
def validation_warnings(self):
repr_point = next(inkex.Path(self.parse_path()).end_points)
@ -30,3 +31,6 @@ class MarkerObject(EmbroideryElement):
def to_stitch_groups(self, last_stitch_group, next_element=None):
return []
def first_stitch(self):
return None

Wyświetl plik

@ -31,3 +31,6 @@ class TextObject(EmbroideryElement):
def to_stitch_groups(self, last_stitch_group, next_element=None):
return []
def first_stitch(self):
return None

Wyświetl plik

@ -3,15 +3,19 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from lxml.etree import Comment
from typing import List, Optional
from inkex import BaseElement
from lxml.etree import Comment
from ..commands import is_command, layer_commands
from ..debug.utils import safe_get
from ..marker import has_marker
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
NOT_EMBROIDERABLE_TAGS, SVG_CLIPPATH_TAG, SVG_DEFS_TAG,
SVG_GROUP_TAG, SVG_MASK_TAG, SVG_IMAGE_TAG, SVG_TEXT_TAG)
SVG_GROUP_TAG, SVG_IMAGE_TAG, SVG_MASK_TAG,
SVG_TEXT_TAG)
from ..utils.paths import get_ini
from .clone import Clone, is_clone
from .element import EmbroideryElement
from .empty_d_object import EmptyDObject
@ -54,7 +58,8 @@ def node_to_elements(node, clone_to_element=False) -> List[EmbroideryElement]:
if element.get_boolean_param("stroke_first", False):
elements.reverse()
elements.append(sew_stack)
if safe_get(get_ini(), "DEBUG", "sew_stack_enable", default=False):
elements.append(sew_stack)
return elements

Wyświetl plik

@ -10,12 +10,12 @@ import inkex
import wx
import wx.adv
from ...elements import nodes_to_elements
from ...elements.utils import iterate_nodes, nodes_to_elements
from ...i18n import _
from ...lettering import FontError, get_font_list
from ...lettering.categories import FONT_CATEGORIES
from ...stitch_plan import stitch_groups_to_stitch_plan
from ...svg.tags import INKSCAPE_LABEL, INKSTITCH_LETTERING, SVG_PATH_TAG
from ...svg.tags import INKSCAPE_LABEL, INKSTITCH_LETTERING
from ...utils import DotDict, cache
from ...utils.threading import ExitThread, check_stop_flag
from .. import PresetsPanel, PreviewRenderer, info_dialog
@ -324,7 +324,8 @@ class LetteringPanel(wx.Panel):
try:
self.update_lettering()
elements = nodes_to_elements(self.group.iterdescendants(SVG_PATH_TAG))
nodes = iterate_nodes(self.group)
elements = nodes_to_elements(nodes)
last_stitch_group = None
next_elements = [None]

Wyświetl plik

@ -5,6 +5,7 @@
import json
import os
from collections import defaultdict
from copy import deepcopy
from random import randint
@ -17,12 +18,12 @@ from ..extensions.lettering_custom_font_dir import get_custom_font_dir
from ..i18n import _, get_languages
from ..marker import MARKER, ensure_marker, has_marker, is_grouped_with_marker
from ..stitches.auto_satin import auto_satin
from ..svg.clip import get_clips
from ..svg.tags import (CONNECTION_END, CONNECTION_START, EMBROIDERABLE_TAGS,
INKSCAPE_LABEL, INKSTITCH_ATTRIBS, SVG_GROUP_TAG,
SVG_PATH_TAG, SVG_USE_TAG, XLINK_HREF)
from ..utils import Point
from .font_variant import FontVariant
from collections import defaultdict
class FontError(InkstitchException):
@ -599,6 +600,21 @@ class Font(object):
or element.get_id().startswith('command_connector')):
continue
clips = get_clips(element)
if len(clips) > 1:
# multiple clips: wrap the element into clipped groups
parent = element.getparent()
index = parent.index(element)
for clip in clips:
new_group = inkex.Group()
new_group.clip = clip
parent.insert(index, new_group)
new_group.append(element)
element = new_group
elif len(clips) == 1:
# only one clip: we can apply the clip directly to the element
element.clip = clips[0]
# get glyph group to calculate transform
glyph_group = None
for ancestor in element.ancestors(group):

Wyświetl plik

@ -4,6 +4,7 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import os
from collections import defaultdict
import inkex
@ -99,15 +100,20 @@ class FontVariant(object):
group.attrib.pop('display', None)
def _apply_transforms(self, svg):
self.clip_transforms = defaultdict(list)
# apply transforms to paths and use tags
for element in svg.iterdescendants((SVG_PATH_TAG, SVG_USE_TAG)):
for element in svg.iterdescendants((SVG_PATH_TAG, SVG_USE_TAG, SVG_GROUP_TAG)):
transform = element.composed_transform()
if element.clip is not None:
self.clip_transforms[element.clip] = element.composed_transform()
if element.tag == SVG_GROUP_TAG:
continue
if element.tag == SVG_PATH_TAG:
path = element.path.transform(transform)
element.set_path(path)
element.attrib.pop("transform", None)
if element.tag == SVG_USE_TAG:
elif element.tag == SVG_USE_TAG:
oldx = element.get('x', 0)
oldy = element.get('y', 0)
newx, newy = transform.apply_to_point((oldx, oldy))
@ -115,6 +121,13 @@ class FontVariant(object):
element.set('y', newy)
element.attrib.pop("transform", None)
for clip, transform in self.clip_transforms.items():
for element in clip.iterdescendants():
if element.tag == SVG_PATH_TAG:
path = element.path.transform(transform)
element.set_path(path)
element.attrib.pop("transform", None)
# remove transforms after they have been applied
for group in svg.iterdescendants(SVG_GROUP_TAG):
group.attrib.pop('transform', None)

Wyświetl plik

@ -11,6 +11,14 @@ from ..utils import ensure_multi_polygon
from .tags import SVG_GROUP_TAG, SVG_PATH_TAG
def get_clips(node):
clips = []
for element in node.iterancestors(SVG_GROUP_TAG):
if element.clip is not None:
clips.append(element.clip)
return clips
def get_clip_path(node):
# get clip and apply node transform
clip = _clip_paths(node)

Wyświetl plik

@ -3,12 +3,18 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import sys
import os
import sys
from os.path import dirname, realpath
from pathlib import Path
import appdirs
if sys.version_info >= (3, 11):
import tomllib # built-in in Python 3.11+
else:
import tomli as tomllib
def get_bundled_dir(name=None):
if getattr(sys, 'frozen', None) is not None:
@ -42,3 +48,13 @@ def get_user_dir(name=None):
path = os.path.join(path, name)
return path
def get_ini():
debug_toml = Path(get_bundled_dir("DEBUG.toml"))
if debug_toml.exists():
with debug_toml.open("rb") as f:
ini = tomllib.load(f) # read DEBUG.toml file if exists, otherwise use default values in ini object
else:
ini = {}
return ini

Wyświetl plik

@ -1,14 +1,17 @@
from lib.elements import Clone, EmbroideryElement, FillStitch
from lib.commands import add_commands
from lib.svg.tags import INKSTITCH_ATTRIBS, SVG_RECT_TAG, INKSCAPE_LABEL
from lib.utils import cache_module
from inkex import SvgDocumentElement, Rectangle, Circle, Group, Use, Transform, TextElement
from math import sqrt
from typing import Optional
from inkex import (Circle, Group, Rectangle, SvgDocumentElement, TextElement,
Transform, Use)
from inkex.tester import TestCase
from inkex.tester.svg import svg
from typing import Optional
from lib.commands import add_commands
from lib.elements import Clone, EmbroideryElement, FillStitch
from lib.svg.tags import INKSCAPE_LABEL, INKSTITCH_ATTRIBS, SVG_RECT_TAG
from lib.utils import cache_module
from math import sqrt
from .utils import element_count
def element_fill_angle(element: EmbroideryElement) -> Optional[float]:
@ -76,7 +79,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertAlmostEqual(element_fill_angle(elements[0]), 30)
def test_hidden_cloned_elements_not_embroidered(self):
@ -107,7 +110,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertEqual(elements[0].node.get(INKSCAPE_LABEL), "NotHidden")
def test_angle_rotated(self):
@ -123,7 +126,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), 10)
def test_angle_flipped(self):
@ -139,7 +142,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -30)
def test_angle_flipped_rotated(self):
@ -155,7 +158,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
# Fill angle goes from 30 -> -30 after flip -> -50 after rotate
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -50)
@ -175,7 +178,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
# Slope of the stitching goes from tan(30deg) = 1/sqrt(3) to -sqrt(3)/sqrt(3) = tan(-45deg),
# then rotated another -10 degrees to -55
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -55)
@ -200,7 +203,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
# Angle goes from 30 -> 40 (g1 -> g2) -> 29 (use)
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), 29)
@ -218,7 +221,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 3) # One for the stroke, one for the fill, one for the SewStack
self.assertEqual(len(elements), element_count()+1) # One for the stroke, one for the fill, one for the SewStack
self.assertEqual(elements[0].node, elements[1].node)
# Angle goes from 0 -> -30
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -30)
@ -237,7 +240,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2) # One for the stroke, one for the fill, one for the SewStack
self.assertEqual(len(elements), element_count()) # One for the stroke, one for the fill, one for the SewStack
self.assertIsNone(elements[0].get_param("angle", None)) # Angle as not set, as this isn't a fill
def test_style_inherits(self):
@ -253,7 +256,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
style = elements[0].node.cascaded_style()
# Source style takes precedence over any attributes specified in the clone
self.assertEqual(style["stroke"], "skyblue")
@ -276,7 +279,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertTransformEqual(
elements[0].node.composed_transform(),
Transform().add_translate((5, 10)).add_scale(2, 2))
@ -297,7 +300,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertTransformEqual(
elements[0].node.composed_transform(),
Transform().add_translate((5, 10)) # use
@ -325,7 +328,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 4) # FillStitch, SewStack, FillStitch, SewStack
self.assertEqual(len(elements), element_count()*2) # FillStitch, SewStack, FillStitch, SewStack
self.assertTransformEqual(
elements[0].node.composed_transform(),
Transform().add_translate((1, 2)).add_scale(0.5, 1) # g2
@ -334,7 +337,7 @@ class CloneElementTest(TestCase):
.add_scale(2, 2), # rect
5)
self.assertTransformEqual(
elements[2].node.composed_transform(),
elements[element_count()].node.composed_transform(),
Transform().add_translate((1, 2)).add_scale(0.5, 1) # g2
.add_translate((5, 10)) # use
.add_translate((0, 5)).add_rotate(5), # g1
@ -370,7 +373,7 @@ class CloneElementTest(TestCase):
self.assertEqual(clone.clone_fill_angle, 42)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), 42)
def test_angle_manually_flipped(self):
@ -388,7 +391,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
self.assertTrue(clone.flip_angle)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -10)
# Recursive use tests
@ -414,7 +417,7 @@ class CloneElementTest(TestCase):
with clone.clone_elements() as elements:
# There should be two elements cloned from u3, two rects, one corresponding to rect and one corresponding to u1.
# Their transforms should derive from the elements they href.
self.assertEqual(len(elements), 4)
self.assertEqual(len(elements), element_count()*2)
self.assertEqual(type(elements[0]), FillStitch)
self.assertEqual(elements[0].node.tag, SVG_RECT_TAG)
self.assertTransformEqual(elements[0].node.composed_transform(),
@ -422,9 +425,9 @@ class CloneElementTest(TestCase):
.add_translate(0, 20).add_scale(0.5, 0.5) # u2
)
self.assertEqual(type(elements[2]), FillStitch)
self.assertEqual(elements[2].node.tag, SVG_RECT_TAG)
self.assertTransformEqual(elements[2].node.composed_transform(),
self.assertEqual(type(elements[element_count()]), FillStitch)
self.assertEqual(elements[element_count()].node.tag, SVG_RECT_TAG)
self.assertTransformEqual(elements[element_count()].node.composed_transform(),
Transform().add_translate((0, 30)) # u3
.add_translate((0, 20)).add_scale(0.5, 0.5) # u2
.add_translate((20, 0)) # u1
@ -443,7 +446,7 @@ class CloneElementTest(TestCase):
clone = Clone(u1)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
# Angle goes from 30 -> -30
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -30)
@ -454,7 +457,7 @@ class CloneElementTest(TestCase):
clone = Clone(u2)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
# Angle goes from -30 -> -20 (u1 -> g)
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -20)
@ -464,7 +467,7 @@ class CloneElementTest(TestCase):
clone = Clone(u3)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
# Angle goes from -20 -> -27
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -27)
@ -475,7 +478,7 @@ class CloneElementTest(TestCase):
clone = Clone(u4)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
# Angle goes from -30 -> -37
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -37)
@ -501,7 +504,7 @@ class CloneElementTest(TestCase):
clone = Clone(u3)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
# Angle goes from 0 (g -> u2) -> -7 (u3)
self.assertAngleAlmostEqual(element_fill_angle(elements[0]), -7)
@ -526,7 +529,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
cmd_orig = original.get_command("ending_point")
cmd_clone = elements[0].get_command("ending_point")
self.assertIsNotNone(cmd_clone)
@ -553,7 +556,7 @@ class CloneElementTest(TestCase):
clone = Clone(use)
with clone.clone_elements() as elements:
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
cmd_orig = original.get_command("ending_point")
cmd_clone = elements[0].get_command("ending_point")
self.assertIsNotNone(cmd_clone)

Wyświetl plik

@ -1,8 +1,11 @@
from lib.elements import utils, FillStitch
from inkex import Rectangle, Group, Style
from inkex import Group, Rectangle, Style
from inkex.tester import TestCase
from inkex.tester.svg import svg
from lib.elements import FillStitch, utils
from .utils import element_count
class ElementsUtilsTest(TestCase):
# These tests test two functions at once, but they're sort of complimentary.
@ -28,7 +31,7 @@ class ElementsUtilsTest(TestCase):
}))
elements = utils.nodes_to_elements(utils.iterate_nodes(g))
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertEqual(type(elements[0]), FillStitch)
self.assertEqual(elements[0].node, rect)
@ -41,7 +44,7 @@ class ElementsUtilsTest(TestCase):
}))
elements = utils.nodes_to_elements(utils.iterate_nodes(rect))
self.assertEqual(len(elements), 2)
self.assertEqual(len(elements), element_count())
self.assertEqual(type(elements[0]), FillStitch)
self.assertEqual(elements[0].node, rect)

9
tests/utils.py 100644
Wyświetl plik

@ -0,0 +1,9 @@
from lib.debug.utils import safe_get
from lib.utils.paths import get_ini
def element_count():
element_count = 1
if safe_get(get_ini(), "DEBUG", "sew_stack_enable", default=False):
element_count = 2
return element_count