diff options
Diffstat (limited to 'lib/gui/tartan/main_panel.py')
| -rw-r--r-- | lib/gui/tartan/main_panel.py | 271 |
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() |
