summaryrefslogtreecommitdiff
path: root/lib/gui/tartan/main_panel.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gui/tartan/main_panel.py')
-rw-r--r--lib/gui/tartan/main_panel.py271
1 files changed, 271 insertions, 0 deletions
diff --git a/lib/gui/tartan/main_panel.py b/lib/gui/tartan/main_panel.py
new file mode 100644
index 00000000..238c8901
--- /dev/null
+++ b/lib/gui/tartan/main_panel.py
@@ -0,0 +1,271 @@
+# 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 json
+from copy import copy
+
+import inkex
+import wx
+import wx.adv
+
+from ...elements import FillStitch, nodes_to_elements
+from ...exceptions import InkstitchException, format_uncaught_exception
+from ...i18n import _
+from ...stitch_plan import stitch_groups_to_stitch_plan
+from ...svg.tags import INKSTITCH_TARTAN
+from ...tartan.fill_element import prepare_tartan_fill_element
+from ...tartan.palette import Palette
+from ...tartan.svg import TartanSvgGroup
+from ...utils import DotDict
+from ...utils.threading import ExitThread, check_stop_flag
+from .. import PresetsPanel, PreviewRenderer, WarningPanel
+from . import CodePanel, CustomizePanel, EmbroideryPanel, HelpPanel
+
+
+class TartanMainPanel(wx.Panel):
+
+ def __init__(self, parent, simulator, elements, on_cancel=None, metadata=None, output_groups=inkex.Group()):
+ self.parent = parent
+ self.simulator = simulator
+ self.elements = elements
+ self.cancel_hook = on_cancel
+ self.palette = Palette()
+ self.metadata = metadata or dict()
+ self.output_groups = 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)
+ self.presets_panel = PresetsPanel(self)
+ # 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.customize_panel = CustomizePanel(self.notebook, self)
+ self.notebook.AddPage(self.customize_panel, _('Customize'))
+ self.customize_panel.SetupScrolling() # scroll_x=False)
+ # code
+ self.code_panel = CodePanel(self.notebook, self)
+ self.notebook.AddPage(self.code_panel, _("Palette Code"))
+ # embroidery settings
+ self.embroidery_panel = EmbroideryPanel(self.notebook, self)
+ self.notebook.AddPage(self.embroidery_panel, _("Embroidery Settings"))
+ # 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(self.presets_panel, 0, wx.EXPAND | wx.ALL, 10)
+ self.notebook_sizer.Add(apply_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 10)
+
+ self.SetSizer(self.notebook_sizer)
+
+ self.load_settings()
+ self.apply_settings()
+
+ self.Layout()
+ self.SetMinSize(self.notebook_sizer.CalcMin())
+
+ def update_from_code(self):
+ self.palette.update_from_code(self.code_panel.threadcount_text.GetValue())
+ self.customize_panel.symmetry_checkbox.SetValue(self.palette.symmetry)
+ self.customize_panel.warp_weft_checkbox.SetValue(self.palette.equal_warp_weft)
+ self.code_panel.threadcount_text.SetValue(self.palette.palette_code)
+ self.customize_panel.update_stripes(self.palette.palette_stripes)
+ self.customize_panel.update_symmetry()
+ self.customize_panel.update_warp_weft()
+ self.customize_panel.FitInside()
+ self.update_preview()
+
+ def update_from_stripes(self):
+ sizers = [self.customize_panel.warp_sizer]
+ if not self.customize_panel.warp_weft_checkbox.GetValue():
+ sizers.append(self.customize_panel.weft_sizer)
+ self.palette.update_from_stripe_sizer(
+ sizers,
+ self.customize_panel.symmetry_checkbox.GetValue(),
+ self.customize_panel.warp_weft_checkbox.GetValue()
+ )
+ self.update_code_text()
+ self.update_preview()
+
+ def update_code_text(self):
+ self.code_panel.threadcount_text.SetValue(self.palette.palette_code)
+ self.settings['palette'] = self.palette.palette_code
+
+ def load_settings(self):
+ """Load the settings saved into the SVG element"""
+ self.settings = DotDict({
+ "symmetry": True,
+ "equal_warp_weft": True,
+ "rotate": 0.0,
+ "scale": 100,
+ "offset_x": 0.0,
+ "offset_y": 0.0,
+ "palette": "K/10 W/?10",
+ "output": "embroidery",
+ "stitch_type": "legacy_fill",
+ "row_spacing": 1.0,
+ "angle_warp": 0.0,
+ "angle_weft": 90.0,
+ "min_stripe_width": 1.0,
+ "bean_stitch_repeats": 0
+ })
+
+ try:
+ self.settings.update(json.loads(self.elements[0].get(INKSTITCH_TARTAN)))
+ except (TypeError, ValueError, IndexError):
+ pass
+
+ def apply_settings(self):
+ """Make the settings in self.settings visible in the UI."""
+ self.customize_panel.rotate.SetValue(self.settings.rotate)
+ self.customize_panel.scale.SetValue(int(self.settings.scale))
+ self.customize_panel.offset_x.SetValue(self.settings.offset_x)
+ self.customize_panel.offset_y.SetValue(self.settings.offset_y)
+ self.code_panel.threadcount_text.SetValue(self.settings.palette)
+ self.embroidery_panel.set_output(self.settings.output)
+ self.embroidery_panel.set_stitch_type(self.settings.stitch_type)
+ self.embroidery_panel.svg_row_spacing.SetValue(self.settings.row_spacing)
+ self.embroidery_panel.angle_warp.SetValue(self.settings.angle_warp)
+ self.embroidery_panel.angle_weft.SetValue(self.settings.angle_weft)
+ self.embroidery_panel.min_stripe_width.SetValue(self.settings.min_stripe_width)
+ self.embroidery_panel.svg_bean_stitch_repeats.SetValue(self.settings.bean_stitch_repeats)
+ self.embroidery_panel.stitch_angle.SetValue(self.elements[0].get('inkstitch:tartan_angle', -45))
+ self.embroidery_panel.rows_per_thread.SetValue(self.elements[0].get('inkstitch:rows_per_thread', 2))
+ self.embroidery_panel.row_spacing.SetValue(self.elements[0].get('inkstitch:row_spacing_mm', 0.25))
+ underlay = self.elements[0].get('inkstitch:fill_underlay', "True").lower() in ('yes', 'y', 'true', 't', '1')
+ self.embroidery_panel.underlay.SetValue(underlay)
+ self.embroidery_panel.herringbone.SetValue(self.elements[0].get('inkstitch:herringbone_width_mm', 0))
+ self.embroidery_panel.bean_stitch_repeats.SetValue(self.elements[0].get('inkstitch:bean_stitch_repeats', '0'))
+
+ self.update_from_code()
+
+ self.customize_panel.symmetry_checkbox.SetValue(bool(self.settings.symmetry))
+ self.palette.update_symmetry(self.settings.symmetry)
+ self.customize_panel.warp_weft_checkbox.SetValue(bool(self.settings.equal_warp_weft))
+ self.customize_panel.update_warp_weft()
+
+ def save_settings(self):
+ """Save the settings into the SVG elements."""
+ for element in self.elements:
+ element.set(INKSTITCH_TARTAN, json.dumps(self.settings))
+
+ def get_preset_data(self):
+ # called by self.presets_panel
+ settings = dict(self.settings)
+ return settings
+
+ 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 apply_preset_data(self, preset_data):
+ settings = DotDict(preset_data)
+ self.settings = settings
+ self.apply_settings()
+
+ def get_preset_suite_name(self):
+ # called by self.presets_panel
+ return "tartan"
+
+ 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_tartan()
+ self.save_settings()
+ self.close()
+
+ def render_stitch_plan(self):
+ if self.settings['output'] == 'svg':
+ self.update_tartan()
+ stitch_groups = self._get_svg_stitch_groups()
+ else:
+ self.save_settings()
+ stitch_groups = []
+ previous_stitch_group = None
+ for element in self.elements:
+ try:
+ # copy the embroidery element to drop the cache
+ stitch_groups.extend(copy(FillStitch(element)).embroider(previous_stitch_group))
+ if stitch_groups:
+ previous_stitch_group = stitch_groups[-1]
+ 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())
+
+ if stitch_groups:
+ return stitch_groups_to_stitch_plan(
+ stitch_groups,
+ collapse_len=self.metadata['collapse_len_mm'],
+ min_stitch_len=self.metadata['min_stitch_len_mm']
+ )
+
+ def _get_svg_stitch_groups(self):
+ stitch_groups = []
+ previous_stitch_group = None
+ for element in self.elements:
+ parent = element.getparent()
+ embroidery_elements = nodes_to_elements(parent.iterdescendants())
+ for embroidery_element in embroidery_elements:
+ check_stop_flag()
+ if embroidery_element.node == element:
+ continue
+ try:
+ # copy the embroidery element to drop the cache
+ stitch_groups.extend(copy(embroidery_element).embroider(previous_stitch_group))
+ if stitch_groups:
+ previous_stitch_group = stitch_groups[-1]
+ except InkstitchException:
+ pass
+ except Exception:
+ pass
+ return stitch_groups
+
+ def update_tartan(self):
+ for element in self.elements:
+ check_stop_flag()
+ if self.settings['output'] == 'svg':
+ TartanSvgGroup(self.settings).generate(element)
+ else:
+ prepare_tartan_fill_element(element)
+
+ def on_stitch_plan_rendered(self, stitch_plan):
+ self.simulator.stop()
+ self.simulator.load(stitch_plan)
+ self.simulator.go()