diff options
| -rw-r--r-- | dbus/select_elements.py | 2 | ||||
| -rw-r--r-- | electron/src/renderer/assets/js/simulator.js | 8 | ||||
| -rw-r--r-- | lib/elements/stroke.py | 38 | ||||
| -rw-r--r-- | lib/extensions/lettering.py | 7 | ||||
| -rw-r--r-- | lib/extensions/params.py | 9 | ||||
| -rw-r--r-- | lib/extensions/select_elements.py | 8 | ||||
| -rw-r--r-- | lib/extensions/stroke_to_lpe_satin.py | 93 | ||||
| -rw-r--r-- | lib/gui/simulator.py | 5 | ||||
| -rw-r--r-- | templates/select_elements.xml | 10 | ||||
| -rw-r--r-- | templates/stroke_to_lpe_satin.xml | 2 |
10 files changed, 138 insertions, 44 deletions
diff --git a/dbus/select_elements.py b/dbus/select_elements.py index 2b663aed..ad016751 100644 --- a/dbus/select_elements.py +++ b/dbus/select_elements.py @@ -49,7 +49,7 @@ class DBusActions: # start dbus dbus = DBusActions() # give it some time to start -sleep(0.2) +sleep(0.5) # clear previous selection dbus.run_action('select-clear', None) # select with the list of ids diff --git a/electron/src/renderer/assets/js/simulator.js b/electron/src/renderer/assets/js/simulator.js index 0e60e1c2..bb290838 100644 --- a/electron/src/renderer/assets/js/simulator.js +++ b/electron/src/renderer/assets/js/simulator.js @@ -611,11 +611,11 @@ export default { this.svg.on('zoom', this.resizeCursor) this.resizeCursor() + inkStitch.get('page_specs').then(response => { + this.page_specs = response.data + this.generatePage() + }) this.start() }) - inkStitch.get('page_specs').then(response => { - this.page_specs = response.data - this.generatePage() - }) } } diff --git a/lib/elements/stroke.py b/lib/elements/stroke.py index 258bf737..fd5983d5 100644 --- a/lib/elements/stroke.py +++ b/lib/elements/stroke.py @@ -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 math import ceil + import shapely.geometry from inkex import Transform @@ -104,7 +106,7 @@ class Stroke(EmbroideryElement): @property @param('running_stitch_length_mm', _('Running stitch length'), - tooltip=_('Length of stitches in running stitch mode.'), + tooltip=_('Length of stitches. Stitches can be shorter according to the stitch tolerance setting.'), unit='mm', type='float', select_items=[('stroke_method', 'running_stitch'), ('stroke_method', 'ripple_stitch')], @@ -115,7 +117,7 @@ class Stroke(EmbroideryElement): @property @param('running_stitch_tolerance_mm', - _('Running stitch tolerance'), + _('Stitch tolerance'), tooltip=_('All stitches must be within this distance from the path. ' + 'A lower tolerance means stitches will be closer together. ' + 'A higher tolerance means sharp corners may be rounded.'), @@ -128,6 +130,20 @@ class Stroke(EmbroideryElement): return max(self.get_float_param("running_stitch_tolerance_mm", 0.2), 0.01) @property + @param('max_stitch_length_mm', + _('Max stitch length'), + tooltip=_('Split stitches longer than this.'), + unit='mm', + type='float', + select_items=[('stroke_method', 'manual_stitch')], + sort_index=4) + def max_stitch_length(self): + max_length = self.get_float_param("max_stitch_length_mm", None) + if not max_length or max_length <= 0: + return + return max_length + + @property @param('zigzag_spacing_mm', _('Zig-zag spacing (peak-to-peak)'), tooltip=_('Length of stitches in zig-zag mode.'), @@ -396,6 +412,21 @@ class Stroke(EmbroideryElement): return StitchGroup(self.color, repeated_stitches, lock_stitches=self.lock_stitches, force_lock_stitches=self.force_lock_stitches) + def apply_max_stitch_length(self, path): + # apply max distances + max_len_path = [path[0]] + for points in zip(path[:-1], path[1:]): + line = shapely.geometry.LineString(points) + dist = line.length + if dist > self.max_stitch_length: + num_subsections = ceil(dist / self.max_stitch_length) + additional_points = [Point(coord.x, coord.y) + for coord in [line.interpolate((i/num_subsections), normalized=True) + for i in range(1, num_subsections + 1)]] + max_len_path.extend(additional_points) + max_len_path.append(points[1]) + return max_len_path + def ripple_stitch(self): return StitchGroup( color=self.color, @@ -422,6 +453,9 @@ class Stroke(EmbroideryElement): path = [Point(x, y) for x, y in path] # manual stitch if self.stroke_method == 'manual_stitch': + if self.max_stitch_length: + path = self.apply_max_stitch_length(path) + if self.force_lock_stitches: lock_stitches = self.lock_stitches else: diff --git a/lib/extensions/lettering.py b/lib/extensions/lettering.py index a396765b..b304a6f9 100644 --- a/lib/extensions/lettering.py +++ b/lib/extensions/lettering.py @@ -31,9 +31,11 @@ class LetteringFrame(wx.Frame): DEFAULT_FONT = "small_font" def __init__(self, *args, **kwargs): - # begin wxGlade: MyFrame.__init__ self.group = kwargs.pop('group') self.cancel_hook = kwargs.pop('on_cancel', None) + self.metadata = kwargs.pop('metadata', []) + + # begin wxGlade: MyFrame.__init__ wx.Frame.__init__(self, None, wx.ID_ANY, _("Ink/Stitch Lettering") ) @@ -492,8 +494,9 @@ class Lettering(CommandsExtension): return group def effect(self): + metadata = self.get_inkstitch_metadata() app = wx.App() - frame = LetteringFrame(group=self.get_or_create_group(), on_cancel=self.cancel) + frame = LetteringFrame(group=self.get_or_create_group(), on_cancel=self.cancel, metadata=metadata) # position left, center current_screen = wx.Display.GetFromPoint(wx.GetMousePosition()) diff --git a/lib/extensions/params.py b/lib/extensions/params.py index a85b16f7..fb13c223 100644 --- a/lib/extensions/params.py +++ b/lib/extensions/params.py @@ -478,9 +478,11 @@ class SettingsFrame(wx.Frame): lc = wx.Locale() lc.Init(wx.LANGUAGE_DEFAULT) - # begin wxGlade: MyFrame.__init__ self.tabs_factory = kwargs.pop('tabs_factory', []) self.cancel_hook = kwargs.pop('on_cancel', None) + self.metadata = kwargs.pop('metadata', []) + + # begin wxGlade: MyFrame.__init__ wx.Frame.__init__(self, None, wx.ID_ANY, _("Embroidery Params") ) @@ -786,8 +788,11 @@ class Params(InkstitchExtension): def effect(self): try: app = wx.App() + metadata = self.get_inkstitch_metadata() frame = SettingsFrame( - tabs_factory=self.create_tabs, on_cancel=self.cancel) + tabs_factory=self.create_tabs, + on_cancel=self.cancel, + metadata=metadata) # position left, center current_screen = wx.Display.GetFromPoint(wx.GetMousePosition()) diff --git a/lib/extensions/select_elements.py b/lib/extensions/select_elements.py index 8fa9ca9d..896e04b0 100644 --- a/lib/extensions/select_elements.py +++ b/lib/extensions/select_elements.py @@ -21,6 +21,7 @@ class SelectElements(InkstitchExtension): pars.add_argument("--info", type=str, dest="info") pars.add_argument("--select-running-stitch", type=Boolean, dest="running", default=False) + pars.add_argument("--running-stitch-condition", type=str, dest="running_stitch_condition", default="all") pars.add_argument("--select-ripples", type=Boolean, dest="ripples", default=False) pars.add_argument("--select-zigzag", type=Boolean, dest="zigzag", default=False) pars.add_argument("--select-manual", type=Boolean, dest="manual", default=False) @@ -101,7 +102,7 @@ class SelectElements(InkstitchExtension): def _select_stroke(self, element): select = False method = element.stroke_method - if self.options.running and method == 'running_stitch': + if self.options.running and method == 'running_stitch' and self._running_condition(element): select = True if self.options.ripples and method == 'ripple_stitch': select = True @@ -130,6 +131,11 @@ class SelectElements(InkstitchExtension): select = True return select + def _running_condition(self, element): + element_id = element.node.get_id() or '' + conditions = {'all': True, 'autorun-top': element_id.startswith('autorun'), 'autorun-underpath': element_id.startswith('underpath')} + return conditions[self.options.running_stitch_condition] + def _select_fill_underlay(self, element): underlay = {'all': True, 'no': not element.fill_underlay, 'yes': element.fill_underlay} return underlay[self.options.fill_underlay] diff --git a/lib/extensions/stroke_to_lpe_satin.py b/lib/extensions/stroke_to_lpe_satin.py index 4052207a..d162539b 100644 --- a/lib/extensions/stroke_to_lpe_satin.py +++ b/lib/extensions/stroke_to_lpe_satin.py @@ -26,6 +26,7 @@ class StrokeToLpeSatin(InkstitchExtension): self.arg_parser.add_argument("-l", "--length", type=float, default=15, dest="length") self.arg_parser.add_argument("-t", "--stretched", type=inkex.Boolean, default=False, dest="stretched") self.arg_parser.add_argument("-r", "--rungs", type=inkex.Boolean, default=False, dest="add_rungs") + self.arg_parser.add_argument("-s", "--path-specific", type=inkex.Boolean, default=True, dest="path_specific") def effect(self): if not self.svg.selection or not self.get_elements(): @@ -52,48 +53,79 @@ class StrokeToLpeSatin(InkstitchExtension): pattern_path = pattern_obj.get_path(self.options.add_rungs, min_width, max_width, length, self.svg.unit) pattern_node_type = pattern_obj.node_types + if not self.options.path_specific: + lpe = self._create_lpe_element(pattern, pattern_path, pattern_node_type) + + for element in self.elements: + if self.options.path_specific: + lpe = self._create_lpe_element(pattern, pattern_path, pattern_node_type, element) + if isinstance(element, SatinColumn): + self._process_satin_column(element, lpe) + elif isinstance(element, Stroke): + self._process_stroke(element, lpe) + + def _create_lpe_element(self, pattern, pattern_path, pattern_node_type, element=None): + # define id for the lpe path + if not element: + lpe_id = f'inkstitch-effect-{pattern}' + else: + lpe_id = f'inkstitch-effect-{pattern}-{element.id}' + + # it is possible, that there is already a path effect with this id, if so, use it + previous_lpe = self.svg.getElementById(lpe_id) + if previous_lpe is not None: + return previous_lpe + # the lpe 'pattern along path' has two options to repeat the pattern, get user input copy_type = 'repeated' if self.options.stretched is False else 'repeated_stretched' + lpe = inkex.PathEffect(attrib={'id': lpe_id, + 'effect': "skeletal", + 'is_visible': "true", + 'lpeversion': "1", + 'pattern': pattern_path, + 'copytype': copy_type, + 'prop_scale': "1", + 'scale_y_rel': "false", + 'spacing': "0", + 'normal_offset': "0", + 'tang_offset': "0", + 'prop_units': "false", + 'vertical_pattern': "false", + 'hide_knot': "false", + 'fuse_tolerance': "0.02", + 'pattern-nodetypes': pattern_node_type}) # add the path effect element to the defs section - self.lpe = inkex.PathEffect(attrib={'id': f'inkstitch-effect-{pattern}', - 'effect': "skeletal", - 'is_visible': "true", - 'lpeversion': "1", - 'pattern': pattern_path, - 'copytype': copy_type, - 'prop_scale': "1", - 'scale_y_rel': "false", - 'spacing': "0", - 'normal_offset': "0", - 'tang_offset': "0", - 'prop_units': "false", - 'vertical_pattern': "false", - 'hide_knot': "false", - 'fuse_tolerance': "0.02", - 'pattern-nodetypes': pattern_node_type}) - self.svg.defs.add(self.lpe) + self.svg.defs.add(lpe) + return lpe - for element in self.elements: - if isinstance(element, SatinColumn): - self._process_satin_column(element) - elif isinstance(element, Stroke): - self._process_stroke(element) + def _process_stroke(self, element, lpe): + element = self._ensure_path_element(element, lpe) - def _process_stroke(self, element): previous_effects = element.node.get(PATH_EFFECT, None) if not previous_effects: - element.node.set(PATH_EFFECT, self.lpe.get_id(as_url=1)) + element.node.set(PATH_EFFECT, lpe.get_id(as_url=1)) element.node.set(ORIGINAL_D, element.node.get('d', '')) else: - url = previous_effects + ';' + self.lpe.get_id(as_url=1) + url = previous_effects + ';' + lpe.get_id(as_url=1) element.node.set(PATH_EFFECT, url) element.node.pop('d') element.set_param('satin_column', 'true') element.node.style['stroke-width'] = self.svg.viewport_to_unit('0.756') - def _process_satin_column(self, element): + def _ensure_path_element(self, element, lpe): + # elements other than paths (rectangle, circles, etc.) can be handled by inkscape for lpe + # but they are way easier to handle for us if we turn them into paths + if element.node.TAG == 'path': + return element + + path_element = element.node.to_path_element() + parent = element.node.getparent() + parent.replace(element.node, path_element) + return Stroke(path_element) + + def _process_satin_column(self, element, lpe): current_effects = element.node.get(PATH_EFFECT, None) # there are possibly multiple path effects, let's check if inkstitch-effect is among them if not current_effects or 'inkstitch-effect' not in current_effects: @@ -106,10 +138,11 @@ class StrokeToLpeSatin(InkstitchExtension): inkstitch_effect = current_effects[inkstitch_effect_position][1:] # get the path effect element old_effect_element = self.svg.getElementById(inkstitch_effect) - # remove the old inkstitch-effect - old_effect_element.getparent().remove(old_effect_element) - # update the path effect link - current_effects[inkstitch_effect_position] = self.lpe.get_id(as_url=1) + # remove the old inkstitch-effect if it is path specific + if inkstitch_effect.endswith(element.id): + old_effect_element.getparent().remove(old_effect_element) + # update path effect link + current_effects[inkstitch_effect_position] = lpe.get_id(as_url=1) element.node.set(PATH_EFFECT, ';'.join(current_effects)) element.node.pop('d') diff --git a/lib/gui/simulator.py b/lib/gui/simulator.py index d9f51b48..2c1ccc2a 100644 --- a/lib/gui/simulator.py +++ b/lib/gui/simulator.py @@ -805,7 +805,10 @@ class SimulatorPreview(Thread): return if patches and not self.refresh_needed.is_set(): - stitch_plan = stitch_groups_to_stitch_plan(patches) + metadata = self.parent.metadata + collapse_len = metadata['collapse_len_mm'] + min_stitch_len = metadata['min_stitch_len_mm'] + stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, min_stitch_len=min_stitch_len) # GUI stuff needs to happen in the main thread, so we ask the main # thread to call refresh_simulator(). diff --git a/templates/select_elements.xml b/templates/select_elements.xml index fee1f467..a4ed148a 100644 --- a/templates/select_elements.xml +++ b/templates/select_elements.xml @@ -8,7 +8,15 @@ <page name="stitch-type" gui-text="Select options"> <label appearance="header">Select Stitch Type</label> <label>Stroke type</label> + <hbox> <param indent="1" name="select-running-stitch" type="boolean" gui-text="Running Stitch">false</param> + <param indent="1" name="running-stitch-condition" type="optiongroup" appearance="combo" gui-text="Select" + gui-description="Only select specific running stitches"> + <option value="all">All</option> + <option value="autorun-top">Auto-Run Top Stitching</option> + <option value="autorun-underpath">Auto-Run Underpath</option> + </param> + </hbox> <param indent="1" name="select-ripples" type="boolean" gui-text="Ripples">false</param> <param indent="1" name="select-zigzag" type="boolean" gui-text="ZigZag Stitch">false</param> <param indent="1" name="select-manual" type="boolean" gui-text="Manual Stitch">false</param> @@ -53,7 +61,7 @@ <label>If this isn't working for you, you may need to insert your path to a python executable manualy.</label> <spacer /> <label>* Windows: Open the "Command Prompt" and type "where python". Copy the path and paste it here.</label> - <label>* Linux: Open the command line and type "which python". Copy the path and paste it here.</label> + <label>* Linux: Open the command line and type "which python3". Copy the path and paste it here.</label> <label>* macOS: doesn't work, sorry</label> <param name="python-path" type="string" gui-text="Python Path"></param> </page> diff --git a/templates/stroke_to_lpe_satin.xml b/templates/stroke_to_lpe_satin.xml index 6e493d08..e0c480a7 100644 --- a/templates/stroke_to_lpe_satin.xml +++ b/templates/stroke_to_lpe_satin.xml @@ -20,6 +20,8 @@ <param name="length" type="float" precision="1" min="0.1" max="100" gui-text="Pattern Length (mm)">15</param> <param name="stretched" type="boolean" gui-text="Stretched">false</param> <param name="rungs" type="boolean" gui-text="Add rungs">true</param> + <param name="path-specific" type="boolean" gui-text="Path specific" + description="When disabled, changes on the path effect will apply to all elements with the same path effect.">true</param> </page> <page name="info" gui-text="Help"> <label appearance="header">This extension converts a stroke into a satin column using the path effect "pattern along path".</label> |
