diff --git a/lib/extensions/__init__.py b/lib/extensions/__init__.py
index 56949b509..f1837c596 100644
--- a/lib/extensions/__init__.py
+++ b/lib/extensions/__init__.py
@@ -15,6 +15,7 @@ from .convert_to_satin import ConvertToSatin
from .convert_to_stroke import ConvertToStroke
from .cut_satin import CutSatin
from .cutwork_segmentation import CutworkSegmentation
+from .density_map import DensityMap
from .duplicate_params import DuplicateParams
from .embroider_settings import EmbroiderSettings
from .flip import Flip
@@ -39,13 +40,16 @@ 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 .selection_to_guide_line import SelectionToGuideLine
+from .selection_to_pattern import SelectionToPattern
from .simulator import Simulator
from .stitch_plan_preview import StitchPlanPreview
+from .stitch_plan_preview_undo import StitchPlanPreviewUndo
from .zip import Zip
__all__ = extensions = [StitchPlanPreview,
+ StitchPlanPreviewUndo,
+ DensityMap,
Install,
Params,
Print,
diff --git a/lib/extensions/density_map.py b/lib/extensions/density_map.py
new file mode 100644
index 000000000..9d2ecb19c
--- /dev/null
+++ b/lib/extensions/density_map.py
@@ -0,0 +1,142 @@
+# 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 numpy as np
+from scipy.spatial import KDTree
+
+import inkex
+
+from ..i18n import _
+from ..stitch_plan import stitch_groups_to_stitch_plan
+from ..svg import PIXELS_PER_MM
+from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SVG_GROUP_TAG
+from ..svg.units import get_viewbox_transform
+from ..utils import cache
+from .base import InkstitchExtension
+
+
+class DensityMap(InkstitchExtension):
+ def __init__(self, *args, **kwargs):
+ InkstitchExtension.__init__(self, *args, **kwargs)
+ self.arg_parser.add_argument("-v", "--layer-visibility", type=int, default=0, dest="layer_visibility")
+ self.arg_parser.add_argument("-l", "--num-neighbors-red", type=int, default=6, dest="num_neighbors_red")
+ self.arg_parser.add_argument("-r", "--density-radius-red", type=float, default=0.5, dest="radius_red")
+ self.arg_parser.add_argument("-m", "--num-neighbors-yellow", type=int, default=3, dest="num_neighbors_yellow")
+ self.arg_parser.add_argument("-s", "--density-radius-yellow", type=float, default=0.5, dest="radius_yellow")
+
+ def effect(self):
+ # delete old stitch plan
+ svg = self.document.getroot()
+ reset_density_plan(svg)
+
+ # create new stitch plan
+ if not self.get_elements():
+ return
+
+ self.metadata = self.get_inkstitch_metadata()
+ collapse_len = self.metadata['collapse_len_mm']
+ patches = self.elements_to_stitch_groups(self.elements)
+ stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len)
+
+ layer = svg.find(".//*[@id='__inkstitch_density_plan__']")
+ color_groups = create_color_groups(layer)
+ density_options = [{'max_neighbors': self.options.num_neighbors_red, 'radius': self.options.radius_red},
+ {'max_neighbors': self.options.num_neighbors_yellow, 'radius': self.options.radius_yellow}]
+ color_block_to_density_markers(svg, color_groups, stitch_plan, density_options)
+
+ # update layer visibility 0 = unchanged, 1 = hidden, 2 = lower opacity
+ groups = self.document.getroot().findall(SVG_GROUP_TAG)
+ if self.options.layer_visibility == 1:
+ self.hide_all_layers()
+ layer.style['display'] = "inline"
+ elif self.options.layer_visibility == 2:
+ for g in groups:
+ style = g.specified_style()
+ # check groupmode and exclude density layer
+ # exclude objects which are not displayed at all or already have opacity < 0.4
+ if (g.get(INKSCAPE_GROUPMODE) == "layer" and not g == layer and
+ float(style.get('opacity', 1)) > 0.4 and not style.get('display', 'inline') == 'none'):
+ g.style['opacity'] = 0.4
+
+
+def reset_density_plan(svg):
+ layer = svg.find(".//*[@id='__inkstitch_density_plan__']")
+ if layer is None:
+ layer = inkex.Group(attrib={
+ 'id': '__inkstitch_density_plan__',
+ INKSCAPE_LABEL: _('Density Plan'),
+ INKSCAPE_GROUPMODE: 'layer'
+ })
+ svg.append(layer)
+ else:
+ # delete old density plan
+ del layer[:]
+
+ # make sure the layer is visible
+ layer.set('style', 'display:inline')
+
+
+def create_color_groups(layer):
+ color_groups = []
+ colors = [_("Red"), _("Yellow"), _("Green")]
+ for color in colors:
+ color_group = inkex.Group(attrib={
+ 'id': '__%s_density_layer__' % color.lower(),
+ INKSCAPE_LABEL: _('%s density') % color,
+ })
+ layer.append(color_group)
+ color_groups.append(color_group)
+ return color_groups
+
+
+def color_block_to_density_markers(svg, groups, stitch_plan, density_options):
+ num_neighbors = []
+ for option in density_options:
+ radius = option['radius'] * PIXELS_PER_MM
+ num_neighbors.append(get_stitch_density(stitch_plan, radius))
+
+ red_group, yellow_group, green_group = groups
+ for red_neighbors, yellow_neighbors, coord in zip(num_neighbors[0][0], num_neighbors[1][0], num_neighbors[0][1]):
+ color = "green" # green
+ group = green_group
+ if density_options[0]['max_neighbors'] < red_neighbors:
+ color = "yellow"
+ group = yellow_group
+ elif density_options[1]['max_neighbors'] < yellow_neighbors:
+ color = "red"
+ group = red_group
+ density_marker = inkex.Circle(attrib={
+ 'id': svg.get_unique_id("density_marker"),
+ 'style': "fill: %s; stroke: #7e7e7e; stroke-width: 0.02%%;" % color,
+ 'cx': "%s" % coord[0],
+ 'cy': "%s" % coord[1],
+ 'r': str(0.5),
+ 'transform': get_correction_transform(svg)
+ })
+ group.append(density_marker)
+
+
+def get_stitch_density(stitch_plan, radius):
+ stitches = []
+ for color_block in stitch_plan:
+ for stitch in color_block:
+ stitches.append((stitch.x, stitch.y))
+
+ # get density per stitch
+ tree = KDTree(np.array(stitches))
+ neighbors = tree.query_ball_tree(tree, radius)
+ density = [len(i) for i in neighbors], stitches
+
+ return density
+
+
+@cache
+def get_correction_transform(svg):
+ transform = get_viewbox_transform(svg)
+
+ # we need to correct for the viewbox
+ transform = -inkex.transforms.Transform(transform)
+
+ return str(transform)
diff --git a/lib/extensions/stitch_plan_preview.py b/lib/extensions/stitch_plan_preview.py
index e5e570fb3..2a4287814 100644
--- a/lib/extensions/stitch_plan_preview.py
+++ b/lib/extensions/stitch_plan_preview.py
@@ -3,14 +3,16 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-from inkex import Boolean, Style
from lxml import etree
+from inkex import Boolean, Style
+
from ..stitch_plan import stitch_groups_to_stitch_plan
from ..svg import render_stitch_plan
-from ..svg.tags import (INKSCAPE_GROUPMODE, SVG_DEFS_TAG, SVG_GROUP_TAG,
- SVG_PATH_TAG)
+from ..svg.tags import (INKSCAPE_GROUPMODE, INKSTITCH_ATTRIBS, SVG_DEFS_TAG,
+ SVG_GROUP_TAG, SVG_PATH_TAG)
from .base import InkstitchExtension
+from .stitch_plan_preview_undo import reset_stitch_plan
class StitchPlanPreview(InkstitchExtension):
@@ -23,37 +25,37 @@ class StitchPlanPreview(InkstitchExtension):
def effect(self):
# delete old stitch plan
svg = self.document.getroot()
- layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
- if layer is not None:
- del layer[:]
+ reset_stitch_plan(svg)
# create new stitch plan
if not self.get_elements():
return
realistic = False
+ visual_commands = True
self.metadata = self.get_inkstitch_metadata()
collapse_len = self.metadata['collapse_len_mm']
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)
+ render_stitch_plan(svg, stitch_plan, realistic, visual_commands)
# apply options
layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
# update layer visibility 0 = unchanged, 1 = hidden, 2 = lower opacity
+ groups = self.document.getroot().findall(SVG_GROUP_TAG)
if self.options.layer_visibility == 1:
+ self.set_invisible_layers_attribute(groups, layer)
self.hide_all_layers()
- layer.set('style', None)
+ layer.style['display'] = "inline"
elif self.options.layer_visibility == 2:
- for g in self.document.getroot().findall(SVG_GROUP_TAG):
+ for g in groups:
style = g.specified_style()
# check groupmode and exclude stitch_plan layer
# exclude objects which are not displayed at all or already have opacity < 0.4
if (g.get(INKSCAPE_GROUPMODE) == "layer" and not g == layer and
float(style.get('opacity', 1)) > 0.4 and not style.get('display', 'inline') == 'none'):
- style += Style('opacity:0.4')
- g.set("style", style)
+ g.style['opacity'] = 0.4
# translate stitch plan to the right side of the canvas
if self.options.move_to_side:
@@ -65,10 +67,17 @@ class StitchPlanPreview(InkstitchExtension):
if self.options.needle_points:
markers = 'marker-mid:url(#inkstitch-needle-point);marker-start:url(#inkstitch-needle-point);marker-end:url(#inkstitch-needle-point)'
for element in layer.iterdescendants(SVG_PATH_TAG):
- style = ';'.join([element.get('style'), markers])
+ style = element.style + Style(markers)
element.set('style', style)
self.ensure_marker()
+ def set_invisible_layers_attribute(self, groups, layer):
+ invisible_layers = []
+ for g in groups:
+ if g.get(INKSCAPE_GROUPMODE) == "layer" and 'display' in g.style and g.style['display'] == 'none':
+ invisible_layers.append(g.get_id())
+ layer.set(INKSTITCH_ATTRIBS['invisible_layers'], ",".join(invisible_layers))
+
def ensure_marker(self):
xpath = ".//svg:marker[@id='inkstitch-needle-point']"
point_marker = self.document.getroot().xpath(xpath)
diff --git a/lib/extensions/stitch_plan_preview_undo.py b/lib/extensions/stitch_plan_preview_undo.py
new file mode 100644
index 000000000..381cf5495
--- /dev/null
+++ b/lib/extensions/stitch_plan_preview_undo.py
@@ -0,0 +1,31 @@
+# Authors: see git history
+#
+# Copyright (c) 2022 Authors
+# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+
+from ..svg.tags import INKSCAPE_GROUPMODE, INKSTITCH_ATTRIBS, SVG_GROUP_TAG
+from .base import InkstitchExtension
+
+
+class StitchPlanPreviewUndo(InkstitchExtension):
+ def effect(self):
+ reset_stitch_plan(self.document.getroot())
+
+
+def reset_stitch_plan(svg):
+ # delete old stitch plan
+ layer = svg.find(".//*[@id='__inkstitch_stitch_plan__']")
+ # get previously invisible layers (they still should be hidden afterwards)
+ if layer is not None:
+ invisible_layers = layer.get(INKSTITCH_ATTRIBS['invisible_layers'], '').split(",")
+ layer.getparent().remove(layer)
+
+ # if there are layers with reduced opacity, remove opacity attribute or unhide hidden layers
+ for g in svg.findall(SVG_GROUP_TAG):
+ style = g.specified_style()
+ if (g.get(INKSCAPE_GROUPMODE) == "layer" and float(style.get('opacity', 1)) == 0.4 or style.get('display', 'inline') == 'none'):
+ if g.get_id() in invisible_layers:
+ continue
+
+ g.style['opacity'] = 1
+ g.style['display'] = 'inline'
diff --git a/lib/svg/rendering.py b/lib/svg/rendering.py
index 2b638ebfa..64090554b 100644
--- a/lib/svg/rendering.py
+++ b/lib/svg/rendering.py
@@ -8,10 +8,10 @@ from math import pi
import inkex
-from .tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, INKSTITCH_ATTRIBS)
-from .units import PIXELS_PER_MM, get_viewbox_transform
from ..i18n import _
from ..utils import Point, cache
+from .tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL, INKSTITCH_ATTRIBS
+from .units import PIXELS_PER_MM, get_viewbox_transform
# The stitch vector path looks like this:
# _______
diff --git a/lib/svg/tags.py b/lib/svg/tags.py
index f0dc69bc9..98edb6049 100644
--- a/lib/svg/tags.py
+++ b/lib/svg/tags.py
@@ -112,6 +112,8 @@ inkstitch_attribs = [
'e_stitch',
'pull_compensation_mm',
'stroke_first',
+ # stitch_plan
+ 'invisible_layers',
# Legacy
'trim_after',
'stop_after',
diff --git a/templates/density_map.xml b/templates/density_map.xml
new file mode 100644
index 000000000..d8348cbb3
--- /dev/null
+++ b/templates/density_map.xml
@@ -0,0 +1,30 @@
+
+
+ Density Map
+ org.inkstitch.density_map
+ density_map
+
+ all
+
+
+
+
+
+
+
+ 6
+ 0.5
+
+
+ 3
+ 0.5
+
+
+
+
+
+
+
+
diff --git a/templates/stitch_plan_preview.xml b/templates/stitch_plan_preview.xml
index ba602aaf4..c78089ee0 100644
--- a/templates/stitch_plan_preview.xml
+++ b/templates/stitch_plan_preview.xml
@@ -18,8 +18,7 @@
false
-
-
+
diff --git a/templates/stitch_plan_preview_undo.xml b/templates/stitch_plan_preview_undo.xml
new file mode 100644
index 000000000..bb5e2308e
--- /dev/null
+++ b/templates/stitch_plan_preview_undo.xml
@@ -0,0 +1,17 @@
+
+
+ Undo Stitch Plan Preview
+ org.inkstitch.stitch_plan_preview_undo
+ stitch_plan_preview_undo
+
+ all
+
+
+
+
+
+
+
+