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