add multicolor satin extension

pull/2863/head
Kaalleen 2024-04-27 07:52:54 +02:00
rodzic b218aac86c
commit ca24be7816
8 zmienionych plików z 556 dodań i 4 usunięć

Wyświetl plik

@ -48,6 +48,7 @@ from .preferences import Preferences
from .print_pdf import Print
from .remove_embroidery_settings import RemoveEmbroiderySettings
from .reorder import Reorder
from .satin_multicolor import SatinMulticolor
from .select_elements import SelectElements
from .selection_to_guide_line import SelectionToGuideLine
from .selection_to_pattern import SelectionToPattern
@ -108,6 +109,7 @@ __all__ = extensions = [ApplyPalette,
Print,
RemoveEmbroiderySettings,
Reorder,
SatinMulticolor,
SelectElements,
SelectionToGuideLine,
SelectionToPattern,

Wyświetl plik

@ -0,0 +1,53 @@
# Authors: see git history
#
# Copyright (c) 2023 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import sys
import wx
import wx.adv
from inkex import errormsg
from ..elements import SatinColumn
from ..gui import MultiColorSatinPanel
from ..gui.simulator import SplitSimulatorWindow
from ..i18n import _
from .base import InkstitchExtension
class SatinMulticolor(InkstitchExtension):
def __init__(self, *args, **kwargs):
self.elements = set()
self.cancelled = False
InkstitchExtension.__init__(self, *args, **kwargs)
def cancel(self):
self.cancelled = True
def effect(self):
self.get_elements()
satins = [element for element in self.elements if isinstance(element, SatinColumn)]
if not satins:
errormsg(_("Please select at least one satin column."))
return
metadata = self.get_inkstitch_metadata()
app = wx.App()
frame = SplitSimulatorWindow(
title=_("Ink/Stitch Tartan"),
panel_class=MultiColorSatinPanel,
elements=satins,
on_cancel=self.cancel,
metadata=metadata,
target_duration=1
)
frame.Show()
app.MainLoop()
if self.cancelled:
# This prevents the superclass from outputting the SVG, because we
# may have modified the DOM.
sys.exit(0)

Wyświetl plik

@ -8,3 +8,4 @@ from .electron import open_url
from .presets import PresetsPanel
from .simulator import PreviewRenderer
from .warnings import WarningPanel
from .satin_multicolor.main_panel import MultiColorSatinPanel

Wyświetl plik

@ -0,0 +1,260 @@
# Authors: see git history
#
# Copyright (c) 2023 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from time import time
import wx
from wx.lib.scrolledpanel import ScrolledPanel
from ...i18n import _
class ColorizePanel(ScrolledPanel):
def __init__(self, parent, panel):
self.panel = panel
ScrolledPanel.__init__(self, parent)
self.colorize_sizer = wx.BoxSizer(wx.VERTICAL)
general_settings_sizer = wx.FlexGridSizer(5, 2, 5, 5)
color_header_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.color_outer_sizer = wx.BoxSizer(wx.VERTICAL)
self.color_sizer = wx.BoxSizer(wx.VERTICAL)
# general settings
equististance_label = wx.StaticText(self, label=_("Equidistant colors"))
equististance_label.SetToolTip(_("Wether colors should be equidistant or have varying widths."))
self.equististance = wx.CheckBox(self)
self.equististance.SetValue(True)
self.equististance.Bind(wx.EVT_CHECKBOX, self._on_update_equidistance)
self.monochrome_width_label = wx.StaticText(self, label=_(" Monochrome color width"))
self.monochrome_width_label.SetToolTip(_("Adapt color width here when equidistane is enabled."))
self.monochrome_width = wx.SpinCtrlDouble(self, min=0, max=100, initial=100, inc=1, style=wx.SP_WRAP)
self.monochrome_width.SetDigits(2)
self.monochrome_width.Bind(wx.EVT_SPINCTRLDOUBLE, self._on_update_monochrome_width)
overflow_left_label = wx.StaticText(self, label=_("Overflow left"))
self.overflow_left = wx.SpinCtrlDouble(self, min=0, max=100, initial=0, inc=0.1, style=wx.SP_WRAP)
self.overflow_left.SetDigits(2)
self.overflow_left.Bind(wx.EVT_SPINCTRLDOUBLE, self._update)
overflow_right_label = wx.StaticText(self, label=_("Overflow right"))
self.overflow_right = wx.SpinCtrlDouble(self, min=0, max=100, initial=0, inc=0.1, style=wx.SP_WRAP)
self.overflow_right.SetDigits(2)
self.overflow_right.Bind(wx.EVT_SPINCTRLDOUBLE, self._update)
seed_label = wx.StaticText(self, label=_("Random seed"))
self.seed = wx.TextCtrl(self)
self.seed.SetValue(str(time()))
self.seed.Bind(wx.EVT_TEXT, self._update)
general_settings_headline = wx.StaticText(self, label=_("General Settings"))
general_settings_headline.SetFont(wx.Font().Bold())
color_settings_headline = wx.StaticText(self, label=_("Colors"))
color_settings_headline.SetFont(wx.Font().Bold())
self.total_width = wx.StaticText(self)
self.total_width.SetToolTip(_("Overflow excluded"))
self.add_color_button = wx.Button(self, label=_("Add"))
self.add_color_button.Bind(wx.EVT_BUTTON, self._add_color_event)
# Add to sizers
general_settings_sizer.Add(equististance_label, 0, wx.ALL, 10)
general_settings_sizer.Add(self.equististance, 0, wx.ALL | wx.EXPAND, 10)
general_settings_sizer.Add(self.monochrome_width_label, 0, wx.ALL, 10)
general_settings_sizer.Add(self.monochrome_width, 0, wx.ALL | wx.EXPAND, 10)
general_settings_sizer.Add(overflow_left_label, 0, wx.ALL, 10)
general_settings_sizer.Add(self.overflow_left, 0, wx.ALL | wx.EXPAND, 10)
general_settings_sizer.Add(overflow_right_label, 0, wx.ALL, 10)
general_settings_sizer.Add(self.overflow_right, 0, wx.ALL | wx.EXPAND, 10)
general_settings_sizer.Add(seed_label, 0, wx.ALL, 10)
general_settings_sizer.Add(self.seed, 0, wx.ALL | wx.EXPAND, 10)
general_settings_sizer.AddGrowableCol(1)
color_header_sizer.Add(color_settings_headline, 0, wx.ALL, 10)
color_header_sizer.Add((0, 0), 1, wx.ALL | wx.EXPAND, 10)
color_header_sizer.Add(self.total_width, 0, wx.ALL, 10)
self.colorize_sizer.Add(wx.StaticLine(self), 0, wx.ALL | wx.EXPAND, 10)
self.colorize_sizer.Add(general_settings_headline, 0, wx.ALL, 10)
self.colorize_sizer.Add(general_settings_sizer, 0, wx.ALL | wx.EXPAND, 10)
self.colorize_sizer.Add(wx.StaticLine(self), 0, wx.ALL | wx.EXPAND, 10)
self.colorize_sizer.Add(color_header_sizer, 0, wx.EXPAND | wx.ALL, 10)
self.colorize_sizer.Add(self.color_outer_sizer, 0, wx.EXPAND | wx.ALL, 10)
self.colorize_sizer.Add(self.color_sizer, 0, wx.EXPAND | wx.ALL, 10)
self.colorize_sizer.Add(self.add_color_button, 0, wx.ALIGN_RIGHT | wx.ALL, 10)
self.SetSizer(self.colorize_sizer)
def _on_update_monochrome_width(self, event):
equidistance = self.equististance.GetValue()
if not equidistance:
return
width = self.monochrome_width.GetValue()
num_colors = len(self.color_sizer.GetChildren())
margin = (100 - width * num_colors) / max(num_colors - 1, 1)
self._set_widget_width_value(width, margin)
self._update()
def _add_color_event(self, event):
self.add_color()
def add_color(self, color='black'):
colorsizer = wx.BoxSizer(wx.HORIZONTAL)
position = wx.Button(self, label='', style=wx.BU_EXACTFIT)
position.SetToolTip(_("Click to move color up."))
position.Bind(wx.EVT_BUTTON, self._move_color_up)
colorpicker = wx.ColourPickerCtrl(self, colour=wx.Colour(color))
colorpicker.SetToolTip(_("Select color"))
colorpicker.Bind(wx.EVT_COLOURPICKER_CHANGED, self._update)
color_margin_right = wx.SpinCtrlDouble(self, min=0, max=100, initial=0, style=wx.SP_WRAP)
color_margin_right.SetDigits(2)
color_margin_right.SetToolTip(_("Margin right (bicolor section). Can be changed individually when equidstance is disabled."))
color_margin_right.Bind(wx.EVT_SPINCTRLDOUBLE, self._update)
color_width = wx.SpinCtrlDouble(self, min=0, max=100, initial=0, style=wx.SP_WRAP)
color_width.SetDigits(2)
color_width.SetToolTip(_("Monochrome width. Can be changed individually when equidstance is disabled."))
color_width.Bind(wx.EVT_SPINCTRLDOUBLE, self._update)
remove_button = wx.Button(self, label='X')
remove_button.SetToolTip(_("Remove color"))
remove_button.Bind(wx.EVT_BUTTON, self._remove_color)
colorsizer.Add(position, 0, wx.CENTER | wx.RIGHT | wx.TOP | wx.RESERVE_SPACE_EVEN_IF_HIDDEN, 5)
colorsizer.Add(colorpicker, 0, wx.RIGHT | wx.TOP, 5)
colorsizer.Add(color_margin_right, 1, wx.RIGHT | wx.TOP | wx.RESERVE_SPACE_EVEN_IF_HIDDEN, 5)
colorsizer.Add(color_width, 1, wx.RIGHT | wx.TOP, 5)
colorsizer.Add(remove_button, 0, wx.CENTER | wx.TOP, 5)
self.color_sizer.Add(colorsizer, 0, wx.EXPAND | wx.ALL, 10)
if self.equististance.GetValue():
color_margin_right.Enable(False)
color_width.Enable(False)
else:
color_margin_right.Enable(True)
color_width.Enable(True)
self._update_colors()
color_margin_right.Show(False)
if len(self.color_sizer.GetChildren()) > 1:
self.color_sizer.GetChildren()[-2].GetSizer().GetChildren()[2].GetWindow().Show()
self._update()
self.FitInside()
self.Layout()
def _move_color_up(self, event):
color = event.GetEventObject()
sizer = color.GetContainingSizer()
main_sizer = self.color_sizer
for i, item in enumerate(main_sizer.GetChildren()):
if item.GetSizer() == sizer:
index = i
break
if index == len(main_sizer.GetChildren()) - 1:
last_sizer = main_sizer.GetChildren()[-2].GetSizer().GetChildren()
last_sizer[2].GetWindow().Show(False)
sizer.GetChildren()[2].GetWindow().Show()
index = max(0, (index - 1))
if index == 0:
previous_first = main_sizer.GetChildren()[0].GetSizer().GetChildren()
previous_first[0].GetWindow().Show()
sizer.GetChildren()[0].GetWindow().Show(False)
main_sizer.Detach(sizer)
main_sizer.Insert(index, sizer, 0, wx.EXPAND | wx.ALL, 10)
self.FitInside()
self._update()
self.Layout()
def _remove_color(self, event):
sizer = event.GetEventObject().GetContainingSizer()
sizer.Clear(True)
self.color_sizer.Remove(sizer)
self.FitInside()
self._update_colors()
self._update()
def _on_update_equidistance(self, event=None):
if self.equististance.GetValue():
self.monochrome_width_label.Enable(True)
self.monochrome_width.Enable(True)
self._set_widget_status(False)
self._update_colors()
else:
self.monochrome_width_label.Enable(False)
self.monochrome_width.Enable(False)
self._set_widget_status(True)
self._update()
def _set_widget_status(self, status):
for color in self.color_sizer.GetChildren():
inner_sizer = color.GetSizer()
for color_widget in inner_sizer:
widget = color_widget.GetWindow()
if isinstance(widget, wx.SpinCtrlDouble):
widget.Enable(status)
def _set_widget_width_value(self, value, margin=0):
first = True
for color in self.color_sizer.GetChildren():
inner_sizer = color.GetSizer()
for color_widget in inner_sizer:
widget = color_widget.GetWindow()
if first and widget.Label == "":
inner_sizer.Hide(widget)
first = False
if isinstance(widget, wx.SpinCtrlDouble):
widget.SetValue(margin)
widget.GetNextSibling().SetValue(value)
break
def get_total_width(self):
width = 0
colors = self.color_sizer.GetChildren()
for color in colors:
inner_sizer = color.GetSizer()
for color_widget in inner_sizer:
widget = color_widget.GetWindow()
if isinstance(widget, wx.SpinCtrlDouble):
width += widget.GetValue()
last_margin = inner_sizer.GetChildren()[2].GetWindow().GetValue()
width -= last_margin
return round(width, 2)
def _update(self, event=None):
width = self.get_total_width()
self.total_width.SetLabel(_(f"Total width: { width } %"))
if width > 100:
self.total_width.SetForegroundColour("red")
else:
self.total_width.SetForegroundColour(wx.NullColour)
self.panel.update_preview()
def _update_colors(self):
equidistance = self.equististance.GetValue()
num_colors = len(self.color_sizer.GetChildren())
if equidistance:
max_width = 100 / max(1, num_colors)
monochrome_value = self.monochrome_width.GetValue()
if monochrome_value > max_width:
self._set_widget_width_value(max_width)
else:
margin = (100 - monochrome_value * num_colors) / max(1, num_colors - 1)
self._set_widget_width_value(monochrome_value, margin)
self.monochrome_width.SetMax(max_width)
self.Refresh()
self._update()

Wyświetl plik

@ -0,0 +1,42 @@
# Authors: see git history
#
# Copyright (c) 2023 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import wx
from ...i18n import _
class HelpPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
help_sizer = wx.BoxSizer(wx.VERTICAL)
help_text = wx.StaticText(
self,
wx.ID_ANY,
_("This extension simulates a multicolor satin by creating colored copies of the selected satin(s)."),
style=wx.ALIGN_LEFT
)
help_text.Wrap(500)
help_sizer.Add(help_text, 0, wx.ALL, 8)
help_sizer.Add((20, 20), 0, 0, 0)
website_info = wx.StaticText(self, wx.ID_ANY, _("More information on our website:"))
help_sizer.Add(website_info, 0, wx.ALL, 8)
website_link = wx.adv.HyperlinkCtrl(
self,
wx.ID_ANY,
_("https://inkstitch.org/docs/satin-tools/#multicolor-satin"),
_("https://inkstitch.org/docs/satin-tools/#multicolor-satin")
)
website_link.Bind(wx.adv.EVT_HYPERLINK, self.on_link_clicked)
help_sizer.Add(website_link, 0, wx.ALL, 8)
self.SetSizer(help_sizer)
def on_link_clicked(self, event):
event.Skip()

Wyświetl plik

@ -0,0 +1,179 @@
# Authors: see git history
#
# Copyright (c) 2023 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from copy import copy
import inkex
import wx
import wx.adv
from ...elements import SatinColumn
from ...exceptions import InkstitchException, format_uncaught_exception
from ...i18n import _
from ...stitch_plan import stitch_groups_to_stitch_plan
from ...utils.threading import ExitThread, check_stop_flag
from .. import PreviewRenderer, WarningPanel
from .colorize import ColorizePanel
from .help_panel import HelpPanel
class MultiColorSatinPanel(wx.Panel):
def __init__(self, parent, simulator, elements, on_cancel=None, metadata=None, output_groups=[]):
self.parent = parent
self.simulator = simulator
self.elements = elements
self.cancel_hook = on_cancel
self.metadata = metadata or dict()
self.output_groups = []
super().__init__(parent, wx.ID_ANY)
self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE)
# preview
self.preview_renderer = PreviewRenderer(self.render_stitch_plan, self.on_stitch_plan_rendered)
# warnings
self.warning_panel = WarningPanel(self)
self.warning_panel.Hide()
# notebook
self.notebook_sizer = wx.BoxSizer(wx.VERTICAL)
self.notebook = wx.Notebook(self, wx.ID_ANY)
self.notebook_sizer.Add(self.warning_panel, 0, wx.EXPAND | wx.ALL, 10)
self.notebook_sizer.Add(self.notebook, 1, wx.EXPAND, 0)
# customize
self.colorize_panel = ColorizePanel(self.notebook, self)
self.notebook.AddPage(self.colorize_panel, _('Colorize'))
self.colorize_panel.SetupScrolling()
# help
help_panel = HelpPanel(self.notebook)
self.notebook.AddPage(help_panel, _("Help"))
# apply and cancel buttons
apply_sizer = wx.BoxSizer(wx.HORIZONTAL)
self.cancel_button = wx.Button(self, label=_("Cancel"))
self.cancel_button.Bind(wx.EVT_BUTTON, self.cancel)
self.apply_button = wx.Button(self, label=_("Apply"))
self.apply_button.Bind(wx.EVT_BUTTON, self.apply)
apply_sizer.Add(self.cancel_button, 0, wx.RIGHT | wx.BOTTOM, 5)
apply_sizer.Add(self.apply_button, 0, wx.RIGHT | wx.BOTTOM, 10)
self.notebook_sizer.Add(apply_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 10)
self.SetSizer(self.notebook_sizer)
self.colorize_panel.add_color(self.elements[0].color)
self.Layout()
self.SetMinSize(self.notebook_sizer.CalcMin())
def _hide_warning(self):
self.warning_panel.clear()
self.warning_panel.Hide()
self.Layout()
def _show_warning(self, warning_text):
self.warning_panel.set_warning_text(warning_text)
self.warning_panel.Show()
self.Layout()
def update_preview(self, event=None):
self.preview_renderer.update()
def close(self):
self.GetTopLevelParent().Close()
def cancel(self, event):
if self.cancel_hook:
self.cancel_hook()
self.close()
def apply(self, event):
self.update_satin_elements()
self.close()
def render_stitch_plan(self):
self.update_satin_elements()
stitch_groups = self._get_stitch_groups()
if stitch_groups:
stitch_plan = stitch_groups_to_stitch_plan(
stitch_groups,
collapse_len=self.metadata['collapse_len_mm'],
min_stitch_len=self.metadata['min_stitch_len_mm']
)
return stitch_plan
def _get_stitch_groups(self):
stitch_groups = []
for element in self.satin_elements:
try:
# copy the embroidery element to drop the cache
stitch_group = copy(SatinColumn(element)).embroider(None)
stitch_groups.extend(stitch_group)
except (SystemExit, ExitThread):
raise
except InkstitchException as exc:
wx.CallAfter(self._show_warning, str(exc))
except Exception:
wx.CallAfter(self._show_warning, format_uncaught_exception())
return stitch_groups
def update_satin_elements(self):
# empty old groups
for group in self.output_groups:
group.getparent().remove(group)
self.output_groups = []
overflow_left = self.colorize_panel.overflow_left.GetValue()
overflow_right = self.colorize_panel.overflow_right.GetValue()
seed = self.colorize_panel.seed.GetValue()
self.satin_elements = []
color_sizer = self.colorize_panel.color_sizer.GetChildren()
num_colors = len(color_sizer)
for element in self.elements:
check_stop_flag()
layer = element.node.getparent()
index = layer.index(element.node)
group = inkex.Group()
current_position = 0
previous_margin = overflow_left
for i, segment_sizer in enumerate(color_sizer):
segment = segment_sizer.GetSizer().GetChildren()
color = segment[1].GetWindow().GetColour().GetAsString(wx.C2S_HTML_SYNTAX)
if i == num_colors - 1:
margin = overflow_right
else:
margin = segment[2].GetWindow().GetValue()
width = segment[3].GetWindow().GetValue()
new_satin = copy(element.node)
new_satin.style['stroke'] = color
new_satin.set('inkstitch:random_seed', seed)
if i % 2 == 0:
new_satin.set('inkstitch:swap_satin_rails', False)
new_satin.set('inkstitch:random_width_increase_percent', f'{ margin } 0')
new_satin.set('inkstitch:random_width_decrease_percent', f'0 { -previous_margin }')
new_satin.set('inkstitch:pull_compensation_percent', f'{ current_position + width - 100} { -current_position }')
else:
new_satin.set('inkstitch:swap_satin_rails', True)
new_satin.set('inkstitch:random_width_increase_percent', f'0 { margin }')
new_satin.set('inkstitch:random_width_decrease_percent', f'{ -previous_margin } 0')
new_satin.set('inkstitch:pull_compensation_percent', f'{ -current_position } { current_position + width - 100}')
previous_margin = margin
current_position += width + margin
group.add(new_satin)
self.satin_elements.append(new_satin)
layer.insert(index + 1, group)
self.output_groups.append(group)
def on_stitch_plan_rendered(self, stitch_plan):
self.simulator.stop()
self.simulator.load(stitch_plan)
self.simulator.go()

Wyświetl plik

@ -6,7 +6,6 @@
import json
from copy import copy
import inkex
import wx
import wx.adv
@ -26,15 +25,14 @@ from . import CodePanel, CustomizePanel, EmbroideryPanel, HelpPanel
class TartanMainPanel(wx.Panel):
def __init__(self, parent, simulator, elements, on_cancel=None, metadata=None, background_color='white', output_groups=inkex.Group()):
def __init__(self, parent, simulator, elements, on_cancel=None, metadata=None, , background_color='white'):
self.parent = parent
self.simulator = simulator
self.elements = elements
self.cancel_hook = on_cancel
self.cancel_hook = on_cancelut
self.palette = Palette()
self.metadata = metadata or dict()
self.background_color = background_color
self.output_groups = output_groups
super().__init__(parent, wx.ID_ANY)

Wyświetl plik

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<inkscape-extension translationdomain="inkstitch" xmlns="http://www.inkscape.org/namespace/inkscape/extension">
<name>Multicolor Satin</name>
<id>org.{{ id_inkstitch }}.satin_multicolor</id>
<param name="extension" type="string" gui-hidden="true">satin_multicolor</param>
<effect needs-live-preview="false">
<object-type>all</object-type>
<effects-menu>
<submenu name="{{ menu_inkstitch }}" translatable="no">
<submenu name="Tools: Satin" />
</submenu>
</effects-menu>
</effect>
<script>
{{ command_tag | safe }}
</script>
</inkscape-extension>