kopia lustrzana https://github.com/inkstitch/inkstitch
Add preferences button to simulator (#2992)
* split simulator panel files * add view panel to position view options at the side * fix single simulator start size (macOS)pull/2994/head dev-build-kaalleen-print-pdf-timeout
rodzic
39d9defef4
commit
dbdba2cda3
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 6.4 KiB |
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
width="256"
|
||||
height="256"
|
||||
viewBox="0 0 256 256"
|
||||
id="svg8375"
|
||||
version="1.1"
|
||||
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||
sodipodi:docname="settings.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs8377" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="2.1965384"
|
||||
inkscape:cx="101.06812"
|
||||
inkscape:cy="137.71669"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer1"
|
||||
showgrid="false"
|
||||
units="mm"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1011"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="32"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" />
|
||||
<metadata
|
||||
id="metadata8380">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1">
|
||||
<path
|
||||
id="circle1"
|
||||
style="fill:#000000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-linecap:round;stroke-linejoin:round"
|
||||
d="M 172.93174,131.65247 C 170.09359,158.92565 145.68354,178.73415 118.41035,175.896 91.137173,173.05785 71.328666,148.6478 74.166818,121.37461 77.004969,94.101435 101.41503,74.292931 128.68821,77.131082 155.96139,79.969232 175.76989,104.37929 172.93174,131.65247 Z M 228.65564,137.45131 C 227.90054,144.70738 205.00996,146.46855 203.33755,151.77089 201.66514,157.07324 199.48625,162.12905 196.86249,166.88834 194.23872,171.64764 208.94929,189.15069 204.58971,194.34927 200.23014,199.54785 195.38649,204.30883 190.13653,208.56913 184.88657,212.82942 167.6382,197.83642 162.89934,200.36687 158.16049,202.89734 153.16151,204.98136 147.96395,206.56899 142.76641,208.15661 141.0042,231.00077 134.01633,231.68885 127.02846,232.37694 119.86759,232.37499 112.61151,231.6199 105.35544,230.86481 103.59427,207.97423 98.291928,206.30181 92.989582,204.6294 87.933774,202.45051 83.174477,199.82675 78.415182,197.20298 60.912122,211.91355 55.713545,207.55398 50.514972,203.1944 45.753981,198.35075 41.49369,193.10079 37.2334,187.85082 52.226399,170.60246 49.695939,165.86361 47.16548,161.12475 45.081452,156.12577 43.493831,150.92821 41.906208,145.73067 19.062053,143.96846 18.373963,136.98059 17.685874,129.99272 17.687821,122.83185 18.442916,115.57577 19.198011,108.3197 42.088592,106.55853 43.761005,101.25619 45.433417,95.953841 47.6123,90.898033 50.236068,86.138737 52.859837,81.379442 38.149274,63.876381 42.508841,58.677807 46.868409,53.479232 51.712062,48.718242 56.962027,44.457951 62.21199,40.197661 79.460361,55.190661 84.19921,52.660201 88.938061,50.129742 93.937051,48.045714 99.1346,46.458093 104.33215,44.870472 106.09436,22.026316 113.08223,21.338226 120.0701,20.650138 127.23097,20.652087 134.48705,21.407181 141.74312,22.162276 143.50429,45.052856 148.80663,46.725269 154.10898,48.397682 159.16479,50.576564 163.92408,53.200332 168.68338,55.824101 186.18643,41.113538 191.38502,45.473106 196.58359,49.832675 201.34458,54.676327 205.60487,59.92629 209.86515,65.176253 194.87216,82.424623 197.40262,87.163474 199.93308,91.902323 202.0171,96.901313 203.60473,102.09886 205.19235,107.29641 228.03651,109.05862 228.72459,116.04649 229.41269,123.03436 229.41073,130.19523 228.65564,137.45131 Z"
|
||||
sodipodi:nodetypes="ssssssssssssssssssssssssssssssssssssss" />
|
||||
</g>
|
||||
</svg>
|
Po Szerokość: | Wysokość: | Rozmiar: 4.0 KiB |
Plik binarny nie jest wyświetlany.
Po Szerokość: | Wysokość: | Rozmiar: 5.4 KiB |
|
@ -30,8 +30,9 @@ class Simulator(InkstitchExtension):
|
|||
current_screen = wx.Display.GetFromPoint(wx.GetMousePosition())
|
||||
display = wx.Display(current_screen)
|
||||
screen_rect = display.GetClientArea()
|
||||
height = int(screen_rect[3] * 0.8)
|
||||
simulator = SimulatorWindow(size=(0, height), background_color=background_color)
|
||||
width = int(screen_rect.width * 0.8)
|
||||
height = int(screen_rect.height * 0.8)
|
||||
simulator = SimulatorWindow(size=(width, height), background_color=background_color)
|
||||
wx.CallLater(100, simulator.Centre)
|
||||
app.SetTopWindow(simulator)
|
||||
simulator.Show()
|
||||
|
|
1328
lib/gui/simulator.py
1328
lib/gui/simulator.py
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,14 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2024 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
|
||||
from .simulator_preferences import SimulatorPreferenceDialog
|
||||
from .simulator_slider import SimulatorSlider
|
||||
from .control_panel import ControlPanel
|
||||
from .view_panel import ViewPanel
|
||||
from .drawing_panel import DrawingPanel
|
||||
from .simulator_panel import SimulatorPanel
|
||||
from .simulator_renderer import PreviewRenderer
|
||||
from .simulator_window import SimulatorWindow
|
||||
from .split_simulator_window import SplitSimulatorWindow
|
|
@ -0,0 +1,343 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2024 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
import os
|
||||
|
||||
import wx
|
||||
from wx.lib.intctrl import IntCtrl
|
||||
|
||||
from ...debug.debug import debug
|
||||
from ...i18n import _
|
||||
from ...utils import get_resource_dir
|
||||
from . import SimulatorSlider
|
||||
|
||||
|
||||
class ControlPanel(wx.Panel):
|
||||
""""""
|
||||
|
||||
@debug.time
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
""""""
|
||||
self.parent = parent
|
||||
self.stitch_plan = kwargs.pop('stitch_plan', None)
|
||||
self.detach_callback = kwargs.pop('detach_callback', None)
|
||||
self.target_stitches_per_second = kwargs.pop('stitches_per_second')
|
||||
self.target_duration = kwargs.pop('target_duration')
|
||||
kwargs['style'] = wx.BORDER_SUNKEN
|
||||
wx.Panel.__init__(self, parent, *args, **kwargs)
|
||||
|
||||
self.drawing_panel = None
|
||||
self.num_stitches = 0
|
||||
self.current_stitch = 0
|
||||
self.speed = 1
|
||||
self.direction = 1
|
||||
self._last_color_block_end = 0
|
||||
|
||||
self.icons_dir = get_resource_dir("icons")
|
||||
|
||||
# Widgets
|
||||
self.button_size = self.GetTextExtent("M").y * 2
|
||||
self.button_style = wx.BU_EXACTFIT | wx.BU_NOTEXT
|
||||
self.btnMinus = wx.Button(self, -1, style=self.button_style)
|
||||
self.btnMinus.Bind(wx.EVT_BUTTON, self.animation_slow_down)
|
||||
self.btnMinus.SetBitmap(self.load_icon('slower'))
|
||||
self.btnMinus.SetToolTip(_('Slow down (arrow down)'))
|
||||
self.btnPlus = wx.Button(self, -1, style=self.button_style)
|
||||
self.btnPlus.Bind(wx.EVT_BUTTON, self.animation_speed_up)
|
||||
self.btnPlus.SetBitmap(self.load_icon('faster'))
|
||||
self.btnPlus.SetToolTip(_('Speed up (arrow up)'))
|
||||
self.btnBackwardStitch = wx.Button(self, -1, style=self.button_style)
|
||||
self.btnBackwardStitch.Bind(wx.EVT_BUTTON, self.animation_one_stitch_backward)
|
||||
self.btnBackwardStitch.SetBitmap(self.load_icon('backward_stitch'))
|
||||
self.btnBackwardStitch.SetToolTip(_('Go backward one stitch (-)'))
|
||||
self.btnForwardStitch = wx.Button(self, -1, style=self.button_style)
|
||||
self.btnForwardStitch.Bind(wx.EVT_BUTTON, self.animation_one_stitch_forward)
|
||||
self.btnForwardStitch.SetBitmap(self.load_icon('forward_stitch'))
|
||||
self.btnForwardStitch.SetToolTip(_('Go forward one stitch (+)'))
|
||||
self.btnBackwardCommand = wx.Button(self, -1, style=self.button_style)
|
||||
self.btnBackwardCommand.Bind(wx.EVT_BUTTON, self.animation_one_command_backward)
|
||||
self.btnBackwardCommand.SetBitmap(self.load_icon('backward_command'))
|
||||
self.btnBackwardCommand.SetToolTip(_('Go backward one command (page-down)'))
|
||||
self.btnForwardCommand = wx.Button(self, -1, style=self.button_style)
|
||||
self.btnForwardCommand.Bind(wx.EVT_BUTTON, self.animation_one_command_forward)
|
||||
self.btnForwardCommand.SetBitmap(self.load_icon('forward_command'))
|
||||
self.btnForwardCommand.SetToolTip(_('Go forward one command (page-up)'))
|
||||
self.btnDirection = wx.Button(self, -1, style=self.button_style)
|
||||
self.btnDirection.Bind(wx.EVT_BUTTON, self.on_direction_button)
|
||||
self.btnDirection.SetBitmap(self.load_icon('direction'))
|
||||
self.btnDirection.SetToolTip(_('Switch animation direction (arrow left, arrow right)'))
|
||||
self.btnPlay = wx.BitmapToggleButton(self, -1, style=self.button_style)
|
||||
self.btnPlay.Bind(wx.EVT_TOGGLEBUTTON, self.on_play_button)
|
||||
self.btnPlay.SetBitmap(self.load_icon('play'))
|
||||
self.btnPlay.SetToolTip(_('Play (P)'))
|
||||
self.btnRestart = wx.Button(self, -1, style=self.button_style)
|
||||
self.btnRestart.Bind(wx.EVT_BUTTON, self.animation_restart)
|
||||
self.btnRestart.SetBitmap(self.load_icon('restart'))
|
||||
self.btnRestart.SetToolTip(_('Restart (R)'))
|
||||
self.slider = SimulatorSlider(self, -1, value=1, minValue=1, maxValue=2)
|
||||
self.slider.Bind(wx.EVT_SLIDER, self.on_slider)
|
||||
self.stitchBox = IntCtrl(self, -1, value=1, min=1, max=2, limited=True, allow_none=True,
|
||||
size=((100, -1)), style=wx.TE_PROCESS_ENTER)
|
||||
self.stitchBox.Clear()
|
||||
self.stitchBox.Bind(wx.EVT_LEFT_DOWN, self.on_stitch_box_focus)
|
||||
self.stitchBox.Bind(wx.EVT_SET_FOCUS, self.on_stitch_box_focus)
|
||||
self.stitchBox.Bind(wx.EVT_TEXT_ENTER, self.on_stitch_box_focusout)
|
||||
self.stitchBox.Bind(wx.EVT_KILL_FOCUS, self.on_stitch_box_focusout)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.on_stitch_box_focusout)
|
||||
self.totalstitchText = wx.StaticText(self, -1, label="")
|
||||
extent = self.totalstitchText.GetTextExtent("0000000")
|
||||
self.totalstitchText.SetMinSize(extent)
|
||||
|
||||
# Layout
|
||||
self.hbSizer1 = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.hbSizer1.Add(self.slider, 1, wx.EXPAND | wx.RIGHT, 10)
|
||||
self.hbSizer1.Add(self.stitchBox, 0, wx.ALIGN_TOP | wx.TOP, 25)
|
||||
self.hbSizer1.Add((1, 1), 0, wx.RIGHT, 10)
|
||||
self.hbSizer1.Add(self.totalstitchText, 0, wx.ALIGN_TOP | wx.TOP, 25)
|
||||
self.hbSizer1.Add((1, 1), 0, wx.RIGHT, 10)
|
||||
|
||||
self.controls_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Controls")), wx.HORIZONTAL)
|
||||
self.controls_inner_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.controls_inner_sizer.Add(self.btnBackwardCommand, 0, wx.EXPAND | wx.ALL, 2)
|
||||
self.controls_inner_sizer.Add(self.btnBackwardStitch, 0, wx.EXPAND | wx.ALL, 2)
|
||||
self.controls_inner_sizer.Add(self.btnForwardStitch, 0, wx.EXPAND | wx.ALL, 2)
|
||||
self.controls_inner_sizer.Add(self.btnForwardCommand, 0, wx.EXPAND | wx.ALL, 2)
|
||||
self.controls_inner_sizer.Add(self.btnDirection, 0, wx.EXPAND | wx.ALL, 2)
|
||||
self.controls_inner_sizer.Add(self.btnPlay, 0, wx.EXPAND | wx.ALL, 2)
|
||||
self.controls_inner_sizer.Add(self.btnRestart, 0, wx.EXPAND | wx.ALL, 2)
|
||||
self.controls_sizer.Add((1, 1), 1)
|
||||
self.controls_sizer.Add(self.controls_inner_sizer, 0, wx.ALIGN_CENTER_VERTICAL | wx.ALL, 10)
|
||||
self.controls_sizer.Add((1, 1), 1)
|
||||
|
||||
self.speed_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Speed")), wx.VERTICAL)
|
||||
|
||||
self.speed_buttons_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
self.speed_buttons_sizer.Add((1, 1), 1)
|
||||
self.speed_buttons_sizer.Add(self.btnMinus, 0, wx.ALL, 2)
|
||||
self.speed_buttons_sizer.Add(self.btnPlus, 0, wx.ALL, 2)
|
||||
self.speed_buttons_sizer.Add((1, 1), 1)
|
||||
self.speed_sizer.Add(self.speed_buttons_sizer, 0, wx.EXPAND | wx.ALL)
|
||||
self.speed_text = wx.StaticText(self, wx.ID_ANY, label="", style=wx.ALIGN_CENTRE_HORIZONTAL | wx.ST_NO_AUTORESIZE)
|
||||
self.speed_text.SetFont(wx.Font(wx.FontInfo(10).Bold()))
|
||||
extent = self.speed_text.GetTextExtent(self.format_speed_text(100000))
|
||||
self.speed_text.SetMinSize(extent)
|
||||
self.speed_sizer.Add(self.speed_text, 0, wx.EXPAND | wx.ALL, 5)
|
||||
|
||||
# A normal BoxSizer can only make child components the same or
|
||||
# proportional size. A FlexGridSizer can split up the available extra
|
||||
# space evenly among all growable columns.
|
||||
self.control_row2_sizer = wx.FlexGridSizer(cols=3, vgap=0, hgap=5)
|
||||
self.control_row2_sizer.AddGrowableCol(0)
|
||||
self.control_row2_sizer.AddGrowableCol(1)
|
||||
self.control_row2_sizer.AddGrowableCol(2)
|
||||
self.control_row2_sizer.Add(self.controls_sizer, 0, wx.EXPAND)
|
||||
self.control_row2_sizer.Add(self.speed_sizer, 0, wx.EXPAND)
|
||||
|
||||
self.vbSizer = vbSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
vbSizer.Add(self.hbSizer1, 1, wx.EXPAND | wx.ALL, 10)
|
||||
vbSizer.Add(self.control_row2_sizer, 0, wx.EXPAND | wx.LEFT | wx.RIGHT | wx.BOTTOM, 10)
|
||||
self.SetSizerAndFit(vbSizer)
|
||||
|
||||
# wait for layouts so that panel size is set
|
||||
if self.stitch_plan:
|
||||
wx.CallLater(50, self.load, self.stitch_plan)
|
||||
|
||||
def set_drawing_panel(self, drawing_panel):
|
||||
self.drawing_panel = drawing_panel
|
||||
self.drawing_panel.set_speed(self.speed)
|
||||
|
||||
def _set_num_stitches(self, num_stitches):
|
||||
if num_stitches < 2:
|
||||
# otherwise the slider and intctrl get mad
|
||||
num_stitches = 2
|
||||
self.num_stitches = num_stitches
|
||||
self.stitchBox.SetValue(1)
|
||||
self.stitchBox.SetMax(num_stitches)
|
||||
self.slider.SetMax(num_stitches)
|
||||
self.totalstitchText.SetLabel(f"/ { num_stitches }")
|
||||
self.choose_speed()
|
||||
|
||||
def clear(self):
|
||||
self.stitches = []
|
||||
self._set_num_stitches(0)
|
||||
self.slider.clear()
|
||||
self.stitchBox.Clear()
|
||||
self.totalstitchText.SetLabel("")
|
||||
|
||||
def load(self, stitch_plan):
|
||||
self.clear()
|
||||
self.stitches = []
|
||||
self._set_num_stitches(stitch_plan.num_stitches)
|
||||
|
||||
stitch_num = 0
|
||||
last_block_end = 1
|
||||
for color_block in stitch_plan.color_blocks:
|
||||
self.stitches.extend(color_block.stitches)
|
||||
|
||||
start = stitch_num + 1
|
||||
end = start + color_block.num_stitches - 1
|
||||
self.slider.add_color_section(color_block.color.rgb, last_block_end, end)
|
||||
last_block_end = end
|
||||
|
||||
for stitch_num, stitch in enumerate(color_block.stitches, start):
|
||||
if stitch.trim:
|
||||
self.slider.add_marker("trim", stitch_num)
|
||||
elif stitch.stop:
|
||||
self.slider.add_marker("stop", stitch_num)
|
||||
elif stitch.jump:
|
||||
self.slider.add_marker("jump", stitch_num)
|
||||
elif stitch.color_change:
|
||||
self.slider.add_marker("color_change", stitch_num)
|
||||
|
||||
def is_dark_theme(self):
|
||||
return wx.SystemSettings().GetAppearance().IsDark()
|
||||
|
||||
def load_icon(self, icon_name):
|
||||
if self.is_dark_theme():
|
||||
icon = wx.Image(os.path.join(self.icons_dir, f"{icon_name}_dark.png"))
|
||||
else:
|
||||
icon = wx.Image(os.path.join(self.icons_dir, f"{icon_name}.png"))
|
||||
icon.Rescale(self.button_size, self.button_size, wx.IMAGE_QUALITY_HIGH)
|
||||
return icon.ConvertToBitmap()
|
||||
|
||||
def choose_speed(self):
|
||||
if self.target_duration:
|
||||
self.set_speed(int(self.num_stitches / float(self.target_duration)))
|
||||
else:
|
||||
self.set_speed(self.target_stitches_per_second)
|
||||
|
||||
def animation_forward(self, event=None):
|
||||
self.drawing_panel.forward()
|
||||
self.direction = 1
|
||||
self.update_speed_text()
|
||||
|
||||
def animation_reverse(self, event=None):
|
||||
self.drawing_panel.reverse()
|
||||
self.direction = -1
|
||||
self.update_speed_text()
|
||||
|
||||
def on_direction_button(self, event):
|
||||
if self.direction == -1:
|
||||
self.animation_forward()
|
||||
else:
|
||||
self.animation_reverse()
|
||||
|
||||
def set_speed(self, speed):
|
||||
self.speed = int(max(speed, 1))
|
||||
self.update_speed_text()
|
||||
|
||||
if self.drawing_panel:
|
||||
self.drawing_panel.set_speed(self.speed)
|
||||
|
||||
def format_speed_text(self, speed):
|
||||
return _('%d stitches/sec') % speed
|
||||
|
||||
def update_speed_text(self):
|
||||
self.speed_text.SetLabel(self.format_speed_text(self.speed * self.direction))
|
||||
|
||||
def on_slider(self, event):
|
||||
self.animation_pause()
|
||||
stitch = event.GetEventObject().GetValue()
|
||||
self.stitchBox.SetValue(stitch)
|
||||
|
||||
if self.drawing_panel:
|
||||
self.drawing_panel.set_current_stitch(stitch)
|
||||
|
||||
self.parent.SetFocus()
|
||||
|
||||
def on_current_stitch(self, stitch, command):
|
||||
if self.current_stitch != stitch:
|
||||
self.current_stitch = stitch
|
||||
self.slider.SetValue(stitch)
|
||||
self.stitchBox.SetValue(stitch)
|
||||
|
||||
def on_stitch_box_focus(self, event):
|
||||
self.animation_pause()
|
||||
self.parent.SetAcceleratorTable(wx.AcceleratorTable([]))
|
||||
event.Skip()
|
||||
|
||||
def on_stitch_box_focusout(self, event):
|
||||
self.parent.SetAcceleratorTable(self.parent.accel_table)
|
||||
stitch = self.stitchBox.GetValue()
|
||||
# We now want to remove the focus from the stitchBox.
|
||||
# In Windows it won't work if we set focus to self.parent, while setting the focus to the
|
||||
# top level would work. This in turn would activate the trim button in Linux. So let's
|
||||
# set the focus on the slider instead where it doesn't cause any harm in any of the operating systems
|
||||
self.slider.SetFocus()
|
||||
|
||||
if stitch is None:
|
||||
stitch = 1
|
||||
self.stitchBox.SetValue(1)
|
||||
|
||||
self.slider.SetValue(stitch)
|
||||
|
||||
if self.drawing_panel:
|
||||
self.drawing_panel.set_current_stitch(stitch)
|
||||
event.Skip()
|
||||
|
||||
def animation_slow_down(self, event):
|
||||
""""""
|
||||
self.set_speed(self.speed / 2.0)
|
||||
|
||||
def animation_speed_up(self, event):
|
||||
""""""
|
||||
self.set_speed(self.speed * 2.0)
|
||||
|
||||
def animation_pause(self, event=None):
|
||||
self.drawing_panel.stop()
|
||||
|
||||
def animation_start(self, event=None):
|
||||
self.drawing_panel.go()
|
||||
|
||||
def on_start(self):
|
||||
self.btnPlay.SetValue(True)
|
||||
|
||||
def on_stop(self):
|
||||
self.btnPlay.SetValue(False)
|
||||
|
||||
def on_play_button(self, event):
|
||||
play = self.btnPlay.GetValue()
|
||||
if play:
|
||||
self.animation_start()
|
||||
else:
|
||||
self.animation_pause()
|
||||
|
||||
def play_or_pause(self, event):
|
||||
if self.drawing_panel.animating:
|
||||
self.animation_pause()
|
||||
else:
|
||||
self.animation_start()
|
||||
|
||||
def animation_one_stitch_forward(self, event):
|
||||
self.animation_pause()
|
||||
self.drawing_panel.one_stitch_forward()
|
||||
|
||||
def animation_one_stitch_backward(self, event):
|
||||
self.animation_pause()
|
||||
self.drawing_panel.one_stitch_backward()
|
||||
|
||||
def animation_one_command_backward(self, event):
|
||||
self.animation_pause()
|
||||
stitch_number = self.current_stitch - 1
|
||||
while stitch_number >= 1:
|
||||
# stitch number shown to the user starts at 1
|
||||
stitch = self.stitches[stitch_number - 1]
|
||||
if stitch.jump or stitch.trim or stitch.stop or stitch.color_change:
|
||||
break
|
||||
stitch_number -= 1
|
||||
self.drawing_panel.set_current_stitch(stitch_number)
|
||||
|
||||
def animation_one_command_forward(self, event):
|
||||
self.animation_pause()
|
||||
stitch_number = self.current_stitch + 1
|
||||
while stitch_number <= self.num_stitches:
|
||||
# stitch number shown to the user starts at 1
|
||||
stitch = self.stitches[stitch_number - 1]
|
||||
if stitch.jump or stitch.trim or stitch.stop or stitch.color_change:
|
||||
break
|
||||
stitch_number += 1
|
||||
self.drawing_panel.set_current_stitch(stitch_number)
|
||||
|
||||
def animation_restart(self, event):
|
||||
self.drawing_panel.restart()
|
|
@ -0,0 +1,423 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2024 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
import time
|
||||
|
||||
import wx
|
||||
from numpy import split
|
||||
|
||||
from ...i18n import _
|
||||
from ...svg import PIXELS_PER_MM
|
||||
from ...utils.settings import global_settings
|
||||
|
||||
# L10N command label at bottom of simulator window
|
||||
COMMAND_NAMES = [_("STITCH"), _("JUMP"), _("TRIM"), _("STOP"), _("COLOR CHANGE")]
|
||||
|
||||
STITCH = 0
|
||||
JUMP = 1
|
||||
TRIM = 2
|
||||
STOP = 3
|
||||
COLOR_CHANGE = 4
|
||||
|
||||
|
||||
class DrawingPanel(wx.Panel):
|
||||
""""""
|
||||
|
||||
# render no faster than this many frames per second
|
||||
TARGET_FPS = 30
|
||||
|
||||
# It's not possible to specify a line thickness less than 1 pixel, even
|
||||
# though we're drawing anti-aliased lines. To get around this we scale
|
||||
# the stitch positions up by this factor and then scale down by a
|
||||
# corresponding amount during rendering.
|
||||
PIXEL_DENSITY = 10
|
||||
|
||||
def __init__(self, parent, *args, **kwargs):
|
||||
""""""
|
||||
self.parent = parent
|
||||
self.stitch_plan = kwargs.pop('stitch_plan', None)
|
||||
kwargs['style'] = wx.BORDER_SUNKEN
|
||||
|
||||
wx.Panel.__init__(self, parent, *args, **kwargs)
|
||||
|
||||
self.control_panel = parent.cp
|
||||
self.view_panel = parent.vp
|
||||
|
||||
# Drawing panel can really be any size, but without this wxpython likes
|
||||
# to allow the status bar and control panel to get squished.
|
||||
self.SetMinSize((300, 300))
|
||||
self.SetBackgroundColour('#FFFFFF')
|
||||
self.SetDoubleBuffered(True)
|
||||
|
||||
self.animating = False
|
||||
self.target_frame_period = 1.0 / self.TARGET_FPS
|
||||
self.last_frame_duration = 0
|
||||
self.direction = 1
|
||||
self.current_stitch = 0
|
||||
self.black_pen = wx.Pen((128, 128, 128))
|
||||
self.width = 0
|
||||
self.height = 0
|
||||
self.loaded = False
|
||||
|
||||
# desired simulation speed in stitches per second
|
||||
self.speed = 16
|
||||
|
||||
self.Bind(wx.EVT_PAINT, self.OnPaint)
|
||||
self.Bind(wx.EVT_SIZE, self.choose_zoom_and_pan)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.on_left_mouse_button_down)
|
||||
self.Bind(wx.EVT_MOUSEWHEEL, self.on_mouse_wheel)
|
||||
self.Bind(wx.EVT_SIZE, self.on_resize)
|
||||
|
||||
# wait for layouts so that panel size is set
|
||||
if self.stitch_plan:
|
||||
wx.CallLater(50, self.load, self.stitch_plan)
|
||||
|
||||
def on_resize(self, event):
|
||||
self.choose_zoom_and_pan()
|
||||
self.Refresh()
|
||||
|
||||
def clamp_current_stitch(self):
|
||||
if self.current_stitch < 1:
|
||||
self.current_stitch = 1
|
||||
elif self.current_stitch > self.num_stitches:
|
||||
self.current_stitch = self.num_stitches
|
||||
|
||||
def stop_if_at_end(self):
|
||||
if self.direction == -1 and self.current_stitch == 1:
|
||||
self.stop()
|
||||
elif self.direction == 1 and self.current_stitch == self.num_stitches:
|
||||
self.stop()
|
||||
|
||||
def start_if_not_at_end(self):
|
||||
if self.direction == -1 and self.current_stitch > 1:
|
||||
self.go()
|
||||
elif self.direction == 1 and self.current_stitch < self.num_stitches:
|
||||
self.go()
|
||||
|
||||
def animate(self):
|
||||
if not self.animating:
|
||||
return
|
||||
|
||||
frame_time = max(self.target_frame_period, self.last_frame_duration)
|
||||
|
||||
# No sense in rendering more frames per second than our desired stitches
|
||||
# per second.
|
||||
frame_time = max(frame_time, 1.0 / self.speed)
|
||||
|
||||
stitch_increment = int(self.speed * frame_time)
|
||||
|
||||
self.set_current_stitch(self.current_stitch + self.direction * stitch_increment)
|
||||
wx.CallLater(int(1000 * frame_time), self.animate)
|
||||
|
||||
def OnPaint(self, e):
|
||||
dc = wx.PaintDC(self)
|
||||
|
||||
if not self.loaded:
|
||||
dc.Clear()
|
||||
return
|
||||
|
||||
canvas = wx.GraphicsContext.Create(dc)
|
||||
|
||||
self.draw_stitches(canvas)
|
||||
self.draw_scale(canvas)
|
||||
|
||||
def draw_stitches(self, canvas):
|
||||
canvas.BeginLayer(1)
|
||||
|
||||
transform = canvas.GetTransform()
|
||||
transform.Translate(*self.pan)
|
||||
transform.Scale(self.zoom / self.PIXEL_DENSITY, self.zoom / self.PIXEL_DENSITY)
|
||||
canvas.SetTransform(transform)
|
||||
|
||||
stitch = 0
|
||||
last_stitch = None
|
||||
|
||||
start = time.time()
|
||||
for pen, stitches, jumps in zip(self.pens, self.stitch_blocks, self.jumps):
|
||||
canvas.SetPen(pen)
|
||||
if stitch + len(stitches) < self.current_stitch:
|
||||
stitch += len(stitches)
|
||||
if len(stitches) > 1:
|
||||
self.draw_stitch_lines(canvas, pen, stitches, jumps)
|
||||
self.draw_needle_penetration_points(canvas, pen, stitches)
|
||||
last_stitch = stitches[-1]
|
||||
else:
|
||||
stitches = stitches[:self.current_stitch - stitch]
|
||||
if len(stitches) > 1:
|
||||
self.draw_stitch_lines(canvas, pen, stitches, jumps)
|
||||
self.draw_needle_penetration_points(canvas, pen, stitches)
|
||||
last_stitch = stitches[-1]
|
||||
break
|
||||
self.last_frame_duration = time.time() - start
|
||||
|
||||
if last_stitch:
|
||||
self.draw_crosshair(last_stitch[0], last_stitch[1], canvas, transform)
|
||||
|
||||
canvas.EndLayer()
|
||||
|
||||
def draw_crosshair(self, x, y, canvas, transform):
|
||||
x, y = transform.TransformPoint(float(x), float(y))
|
||||
canvas.SetTransform(canvas.CreateMatrix())
|
||||
crosshair_radius = 10
|
||||
canvas.SetPen(self.black_pen)
|
||||
canvas.StrokeLines(((x - crosshair_radius, y), (x + crosshair_radius, y)))
|
||||
canvas.StrokeLines(((x, y - crosshair_radius), (x, y + crosshair_radius)))
|
||||
|
||||
def draw_scale(self, canvas):
|
||||
canvas.BeginLayer(1)
|
||||
|
||||
canvas_width, canvas_height = self.GetClientSize()
|
||||
|
||||
one_mm = PIXELS_PER_MM * self.zoom
|
||||
scale_width = one_mm
|
||||
max_width = min(canvas_width * 0.5, 300)
|
||||
|
||||
while scale_width > max_width:
|
||||
scale_width /= 2.0
|
||||
|
||||
while scale_width < 50:
|
||||
scale_width += one_mm
|
||||
|
||||
scale_width_mm = int(scale_width / self.zoom / PIXELS_PER_MM)
|
||||
|
||||
# The scale bar looks like this:
|
||||
#
|
||||
# | |
|
||||
# |_____|_____|
|
||||
|
||||
scale_lower_left_x = 20
|
||||
scale_lower_left_y = canvas_height - 30
|
||||
|
||||
canvas.StrokeLines(((scale_lower_left_x, scale_lower_left_y - 6),
|
||||
(scale_lower_left_x, scale_lower_left_y),
|
||||
(scale_lower_left_x + scale_width / 2.0, scale_lower_left_y),
|
||||
(scale_lower_left_x + scale_width / 2.0, scale_lower_left_y - 3),
|
||||
(scale_lower_left_x + scale_width / 2.0, scale_lower_left_y),
|
||||
(scale_lower_left_x + scale_width, scale_lower_left_y),
|
||||
(scale_lower_left_x + scale_width, scale_lower_left_y - 6)))
|
||||
|
||||
canvas.SetFont(wx.Font(12, wx.DEFAULT, wx.NORMAL, wx.NORMAL), wx.Colour((0, 0, 0)))
|
||||
canvas.DrawText("%s mm" % scale_width_mm, scale_lower_left_x, scale_lower_left_y + 5)
|
||||
|
||||
canvas.EndLayer()
|
||||
|
||||
def draw_stitch_lines(self, canvas, pen, stitches, jumps):
|
||||
render_jumps = self.view_panel.btnJump.GetValue()
|
||||
if render_jumps:
|
||||
canvas.StrokeLines(stitches)
|
||||
else:
|
||||
stitch_blocks = split(stitches, jumps)
|
||||
for i, block in enumerate(stitch_blocks):
|
||||
if len(block) > 1:
|
||||
canvas.StrokeLines(block)
|
||||
|
||||
def draw_needle_penetration_points(self, canvas, pen, stitches):
|
||||
if self.view_panel.btnNpp.GetValue():
|
||||
npp_size = global_settings['simulator_npp_size'] * PIXELS_PER_MM * self.PIXEL_DENSITY
|
||||
npp_pen = wx.Pen(pen.GetColour(), width=int(npp_size))
|
||||
canvas.SetPen(npp_pen)
|
||||
canvas.StrokeLineSegments(stitches, [(stitch[0] + 0.001, stitch[1]) for stitch in stitches])
|
||||
|
||||
def clear(self):
|
||||
self.loaded = False
|
||||
self.Refresh()
|
||||
|
||||
def load(self, stitch_plan):
|
||||
self.current_stitch = 1
|
||||
self.direction = 1
|
||||
self.last_frame_duration = 0
|
||||
self.minx, self.miny, self.maxx, self.maxy = stitch_plan.bounding_box
|
||||
self.width = self.maxx - self.minx
|
||||
self.height = self.maxy - self.miny
|
||||
self.num_stitches = stitch_plan.num_stitches
|
||||
self.parse_stitch_plan(stitch_plan)
|
||||
self.choose_zoom_and_pan()
|
||||
self.set_current_stitch(0)
|
||||
self.loaded = True
|
||||
self.go()
|
||||
|
||||
def choose_zoom_and_pan(self, event=None):
|
||||
# ignore if EVT_SIZE fired before we load the stitch plan
|
||||
if not self.width and not self.height and event is not None:
|
||||
return
|
||||
|
||||
panel_width, panel_height = self.GetClientSize()
|
||||
|
||||
# add some padding to make stitches at the edge more visible
|
||||
width_ratio = panel_width / float(self.width + 10)
|
||||
height_ratio = panel_height / float(self.height + 10)
|
||||
self.zoom = max(min(width_ratio, height_ratio), 0.01)
|
||||
|
||||
# center the design
|
||||
self.pan = ((panel_width - self.zoom * self.width) / 2.0,
|
||||
(panel_height - self.zoom * self.height) / 2.0)
|
||||
|
||||
def stop(self):
|
||||
self.animating = False
|
||||
self.control_panel.on_stop()
|
||||
|
||||
def go(self):
|
||||
if not self.loaded:
|
||||
return
|
||||
|
||||
if not self.animating:
|
||||
self.animating = True
|
||||
self.animate()
|
||||
self.control_panel.on_start()
|
||||
|
||||
def color_to_pen(self, color):
|
||||
line_width = global_settings['simulator_line_width'] * PIXELS_PER_MM * self.PIXEL_DENSITY
|
||||
return wx.Pen(list(map(int, color.visible_on_white.rgb)), int(line_width))
|
||||
|
||||
def update_pen_size(self):
|
||||
line_width = global_settings['simulator_line_width'] * PIXELS_PER_MM * self.PIXEL_DENSITY
|
||||
for pen in self.pens:
|
||||
pen.SetWidth(int(line_width))
|
||||
|
||||
def parse_stitch_plan(self, stitch_plan):
|
||||
self.pens = []
|
||||
self.stitch_blocks = []
|
||||
self.jumps = []
|
||||
|
||||
# There is no 0th stitch, so add a place-holder.
|
||||
self.commands = [None]
|
||||
|
||||
for color_block in stitch_plan:
|
||||
pen = self.color_to_pen(color_block.color)
|
||||
stitch_block = []
|
||||
jumps = []
|
||||
stitch_index = 0
|
||||
|
||||
for stitch in color_block:
|
||||
# trim any whitespace on the left and top and scale to the
|
||||
# pixel density
|
||||
stitch_block.append((self.PIXEL_DENSITY * (stitch.x - self.minx),
|
||||
self.PIXEL_DENSITY * (stitch.y - self.miny)))
|
||||
|
||||
if stitch.trim:
|
||||
self.commands.append(TRIM)
|
||||
elif stitch.jump:
|
||||
self.commands.append(JUMP)
|
||||
jumps.append(stitch_index)
|
||||
elif stitch.stop:
|
||||
self.commands.append(STOP)
|
||||
elif stitch.color_change:
|
||||
self.commands.append(COLOR_CHANGE)
|
||||
else:
|
||||
self.commands.append(STITCH)
|
||||
|
||||
if stitch.trim or stitch.stop or stitch.color_change:
|
||||
self.pens.append(pen)
|
||||
self.stitch_blocks.append(stitch_block)
|
||||
stitch_block = []
|
||||
self.jumps.append(jumps)
|
||||
jumps = []
|
||||
stitch_index = 0
|
||||
else:
|
||||
stitch_index += 1
|
||||
|
||||
if stitch_block:
|
||||
self.pens.append(pen)
|
||||
self.stitch_blocks.append(stitch_block)
|
||||
self.jumps.append(jumps)
|
||||
|
||||
def set_speed(self, speed):
|
||||
self.speed = speed
|
||||
|
||||
def forward(self):
|
||||
self.direction = 1
|
||||
self.start_if_not_at_end()
|
||||
|
||||
def reverse(self):
|
||||
self.direction = -1
|
||||
self.start_if_not_at_end()
|
||||
|
||||
def set_current_stitch(self, stitch):
|
||||
self.current_stitch = stitch
|
||||
self.clamp_current_stitch()
|
||||
command = self.commands[self.current_stitch]
|
||||
self.control_panel.on_current_stitch(self.current_stitch, command)
|
||||
statusbar = self.GetTopLevelParent().statusbar
|
||||
statusbar.SetStatusText(_("Command: %s") % COMMAND_NAMES[command], 1)
|
||||
self.stop_if_at_end()
|
||||
self.Refresh()
|
||||
|
||||
def restart(self):
|
||||
if self.direction == 1:
|
||||
self.current_stitch = 1
|
||||
elif self.direction == -1:
|
||||
self.current_stitch = self.num_stitches
|
||||
|
||||
self.go()
|
||||
|
||||
def one_stitch_forward(self):
|
||||
self.set_current_stitch(self.current_stitch + 1)
|
||||
|
||||
def one_stitch_backward(self):
|
||||
self.set_current_stitch(self.current_stitch - 1)
|
||||
|
||||
def on_left_mouse_button_down(self, event):
|
||||
if self.loaded:
|
||||
self.CaptureMouse()
|
||||
self.drag_start = event.GetPosition()
|
||||
self.drag_original_pan = self.pan
|
||||
self.Bind(wx.EVT_MOTION, self.on_drag)
|
||||
self.Bind(wx.EVT_MOUSE_CAPTURE_LOST, self.on_drag_end)
|
||||
self.Bind(wx.EVT_LEFT_UP, self.on_drag_end)
|
||||
|
||||
def on_drag(self, event):
|
||||
if self.HasCapture() and event.Dragging():
|
||||
delta = event.GetPosition()
|
||||
offset = (delta[0] - self.drag_start[0], delta[1] - self.drag_start[1])
|
||||
self.pan = (self.drag_original_pan[0] + offset[0], self.drag_original_pan[1] + offset[1])
|
||||
self.Refresh()
|
||||
|
||||
def on_drag_end(self, event):
|
||||
if self.HasCapture():
|
||||
self.ReleaseMouse()
|
||||
|
||||
self.Unbind(wx.EVT_MOTION)
|
||||
self.Unbind(wx.EVT_MOUSE_CAPTURE_LOST)
|
||||
self.Unbind(wx.EVT_LEFT_UP)
|
||||
|
||||
def on_mouse_wheel(self, event):
|
||||
if event.GetWheelRotation() > 0:
|
||||
zoom_delta = 1.03
|
||||
else:
|
||||
zoom_delta = 0.97
|
||||
|
||||
# If we just change the zoom, the design will appear to move on the
|
||||
# screen. We have to adjust the pan to compensate. We want to keep
|
||||
# the part of the design under the mouse pointer in the same spot
|
||||
# after we zoom, so that we appear to be zooming centered on the
|
||||
# mouse pointer.
|
||||
|
||||
# This will create a matrix that takes a point in the design and
|
||||
# converts it to screen coordinates:
|
||||
matrix = wx.AffineMatrix2D()
|
||||
matrix.Translate(*self.pan)
|
||||
matrix.Scale(self.zoom, self.zoom)
|
||||
|
||||
# First, figure out where the mouse pointer is in the coordinate system
|
||||
# of the design:
|
||||
pos = event.GetPosition()
|
||||
inverse_matrix = wx.AffineMatrix2D()
|
||||
inverse_matrix.Set(*matrix.Get())
|
||||
inverse_matrix.Invert()
|
||||
pos = inverse_matrix.TransformPoint(*pos)
|
||||
|
||||
# Next, see how that point changes position on screen before and after
|
||||
# we apply the zoom change:
|
||||
x_old, y_old = matrix.TransformPoint(*pos)
|
||||
matrix.Scale(zoom_delta, zoom_delta)
|
||||
x_new, y_new = matrix.TransformPoint(*pos)
|
||||
x_delta = x_new - x_old
|
||||
y_delta = y_new - y_old
|
||||
|
||||
# Finally, compensate for that change in position:
|
||||
self.pan = (self.pan[0] - x_delta, self.pan[1] - y_delta)
|
||||
|
||||
self.zoom *= zoom_delta
|
||||
|
||||
self.Refresh()
|
|
@ -0,0 +1,95 @@
|
|||
# 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 wx
|
||||
|
||||
from . import ControlPanel, DrawingPanel, ViewPanel
|
||||
|
||||
|
||||
class SimulatorPanel(wx.Panel):
|
||||
""""""
|
||||
|
||||
def __init__(self, parent, stitch_plan=None, background_color='white', target_duration=5, stitches_per_second=16, detach_callback=None):
|
||||
""""""
|
||||
super().__init__(parent, style=wx.BORDER_SUNKEN)
|
||||
|
||||
self.cp = ControlPanel(
|
||||
self,
|
||||
stitch_plan=stitch_plan,
|
||||
stitches_per_second=stitches_per_second,
|
||||
target_duration=target_duration,
|
||||
detach_callback=detach_callback
|
||||
)
|
||||
|
||||
self.vp = ViewPanel(
|
||||
self,
|
||||
detach_callback
|
||||
)
|
||||
self.dp = DrawingPanel(self, stitch_plan=stitch_plan)
|
||||
self.cp.set_drawing_panel(self.dp)
|
||||
self.vp.set_drawing_panel(self.dp)
|
||||
self.vp.set_background_color(wx.Colour(background_color))
|
||||
|
||||
dvSizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
|
||||
vbSizer = wx.BoxSizer(wx.VERTICAL)
|
||||
vbSizer.Add(self.dp, 1, wx.EXPAND | wx.ALL, 2)
|
||||
vbSizer.Add(self.cp, 0, wx.EXPAND | wx.ALL, 2)
|
||||
|
||||
dvSizer.Add(vbSizer, 1, wx.EXPAND | wx.ALL, 2)
|
||||
dvSizer.Add(self.vp, 0, wx.ALL, 2)
|
||||
|
||||
self.SetSizerAndFit(dvSizer)
|
||||
|
||||
# Keyboard Shortcuts
|
||||
shortcut_keys = [
|
||||
(wx.ACCEL_NORMAL, wx.WXK_RIGHT, self.cp.animation_forward),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_NUMPAD_RIGHT, self.cp.animation_forward),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_LEFT, self.cp.animation_reverse),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_NUMPAD_LEFT, self.cp.animation_reverse),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_UP, self.cp.animation_speed_up),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_NUMPAD_UP, self.cp.animation_speed_up),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_DOWN, self.cp.animation_slow_down),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_NUMPAD_DOWN, self.cp.animation_slow_down),
|
||||
(wx.ACCEL_NORMAL, ord('+'), self.cp.animation_one_stitch_forward),
|
||||
(wx.ACCEL_NORMAL, ord('='), self.cp.animation_one_stitch_forward),
|
||||
(wx.ACCEL_SHIFT, ord('='), self.cp.animation_one_stitch_forward),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_ADD, self.cp.animation_one_stitch_forward),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_NUMPAD_ADD, self.cp.animation_one_stitch_forward),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_NUMPAD_UP, self.cp.animation_one_stitch_forward),
|
||||
(wx.ACCEL_NORMAL, ord('-'), self.cp.animation_one_stitch_backward),
|
||||
(wx.ACCEL_NORMAL, ord('_'), self.cp.animation_one_stitch_backward),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_SUBTRACT, self.cp.animation_one_stitch_backward),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_NUMPAD_SUBTRACT, self.cp.animation_one_stitch_backward),
|
||||
(wx.ACCEL_NORMAL, ord('r'), self.cp.animation_restart),
|
||||
(wx.ACCEL_NORMAL, ord('p'), self.cp.play_or_pause),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_SPACE, self.cp.play_or_pause),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_PAGEDOWN, self.cp.animation_one_command_backward),
|
||||
(wx.ACCEL_NORMAL, wx.WXK_PAGEUP, self.cp.animation_one_command_forward),
|
||||
(wx.ACCEL_NORMAL, ord('o'), self.vp.on_toggle_npp_shortcut)
|
||||
]
|
||||
|
||||
self.accel_entries = []
|
||||
|
||||
for shortcut_key in shortcut_keys:
|
||||
eventId = wx.NewIdRef()
|
||||
self.accel_entries.append((shortcut_key[0], shortcut_key[1], eventId))
|
||||
self.Bind(wx.EVT_MENU, shortcut_key[2], id=eventId)
|
||||
|
||||
self.accel_table = wx.AcceleratorTable(self.accel_entries)
|
||||
self.SetAcceleratorTable(self.accel_table)
|
||||
|
||||
def go(self):
|
||||
self.dp.go()
|
||||
|
||||
def stop(self):
|
||||
self.dp.stop()
|
||||
|
||||
def load(self, stitch_plan):
|
||||
self.dp.load(stitch_plan)
|
||||
self.cp.load(stitch_plan)
|
||||
|
||||
def clear(self):
|
||||
self.dp.clear()
|
||||
self.cp.clear()
|
|
@ -0,0 +1,71 @@
|
|||
# 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 wx
|
||||
|
||||
from ...i18n import _
|
||||
from ...utils.settings import global_settings
|
||||
|
||||
|
||||
class SimulatorPreferenceDialog(wx.Dialog):
|
||||
"""A dialog to set simulator preferences
|
||||
"""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(SimulatorPreferenceDialog, self).__init__(*args, **kwargs)
|
||||
self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE)
|
||||
|
||||
self.view_panel = self.GetParent()
|
||||
self.drawing_panel = self.view_panel.drawing_panel
|
||||
|
||||
self.line_width_value = global_settings['simulator_line_width']
|
||||
self.npp_size_value = global_settings['simulator_npp_size']
|
||||
|
||||
sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
settings_sizer = wx.FlexGridSizer(2, 2, 5, 5)
|
||||
line_width_label = wx.StaticText(self, label=_("Line width (mm)"))
|
||||
self.line_width = wx.SpinCtrlDouble(self, min=0.03, max=2, initial=0.1, inc=0.01, style=wx.SP_WRAP | wx.SP_ARROW_KEYS)
|
||||
self.line_width.SetDigits(2)
|
||||
self.line_width.SetValue(self.line_width_value)
|
||||
self.line_width.Bind(wx.EVT_SPINCTRLDOUBLE, lambda event: self.on_change("simulator_line_width", event))
|
||||
npp_size_label = wx.StaticText(self, label=_("Needle penetration point size (mm)"))
|
||||
self.npp_size = wx.SpinCtrlDouble(self, min=0.03, max=2, initial=0.5, inc=0.01, style=wx.SP_WRAP | wx.SP_ARROW_KEYS)
|
||||
self.npp_size.SetDigits(2)
|
||||
self.npp_size.SetValue(self.npp_size_value)
|
||||
self.npp_size.Bind(wx.EVT_SPINCTRLDOUBLE, lambda event: self.on_change("simulator_npp_size", event))
|
||||
settings_sizer.Add(line_width_label, 0, wx.ALIGN_CENTRE | wx.ALL, 10)
|
||||
settings_sizer.Add(self.line_width, 0, wx.EXPAND | wx.ALL, 10)
|
||||
settings_sizer.Add(npp_size_label, 0, wx.ALIGN_CENTRE | wx.ALL, 10)
|
||||
settings_sizer.Add(self.npp_size, 0, wx.EXPAND | wx.ALL, 10)
|
||||
|
||||
button_sizer = wx.BoxSizer(wx.HORIZONTAL)
|
||||
btn_cancel = wx.Button(self, id=wx.ID_CANCEL, label=_('Cancel'))
|
||||
btn_cancel.Bind(wx.EVT_BUTTON, self.on_cancel)
|
||||
btn_apply = wx.Button(self, id=wx.ID_OK, label=_('Apply'))
|
||||
btn_apply.Bind(wx.EVT_BUTTON, self.on_apply)
|
||||
button_sizer.Add(btn_cancel, 0, wx.RIGHT, 10)
|
||||
button_sizer.Add(btn_apply, 0, wx.RIGHT, 10)
|
||||
|
||||
sizer.Add(settings_sizer, 1, wx.ALL, 10)
|
||||
sizer.Add(button_sizer, 0, wx.ALIGN_RIGHT | wx.ALL, 10)
|
||||
self.SetSizerAndFit(sizer)
|
||||
|
||||
def on_change(self, attribute, event):
|
||||
global_settings[attribute] = event.EventObject.GetValue()
|
||||
if attribute == 'simulator_line_width':
|
||||
self.drawing_panel.update_pen_size()
|
||||
self.drawing_panel.Refresh()
|
||||
|
||||
def on_apply(self, event):
|
||||
global_settings['simulator_line_width'] = self.line_width.GetValue()
|
||||
global_settings['simulator_npp_size'] = self.npp_size.GetValue()
|
||||
self.Destroy()
|
||||
|
||||
def on_cancel(self, event):
|
||||
global_settings['simulator_line_width'] = self.line_width_value
|
||||
global_settings['simulator_npp_size'] = self.npp_size_value
|
||||
self.drawing_panel.update_pen_size()
|
||||
self.drawing_panel.Refresh()
|
||||
self.Destroy()
|
|
@ -0,0 +1,64 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2024 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
from threading import Event, Thread
|
||||
|
||||
import wx
|
||||
|
||||
from ...debug.debug import debug
|
||||
from ...utils.threading import ExitThread
|
||||
|
||||
|
||||
class PreviewRenderer(Thread):
|
||||
"""Render stitch plan in a background thread."""
|
||||
|
||||
def __init__(self, render_stitch_plan_hook, rendering_completed_hook):
|
||||
super(PreviewRenderer, self).__init__()
|
||||
self.daemon = True
|
||||
self.refresh_needed = Event()
|
||||
|
||||
self.render_stitch_plan_hook = render_stitch_plan_hook
|
||||
self.rendering_completed_hook = rendering_completed_hook
|
||||
|
||||
# This is read by utils.threading.check_stop_flag() to abort stitch plan
|
||||
# generation.
|
||||
self.stop = Event()
|
||||
|
||||
def update(self):
|
||||
"""Request to render a new stitch plan.
|
||||
|
||||
self.render_stitch_plan_hook() will be called in a background thread, and then
|
||||
self.rendering_completed_hook() will be called with the resulting stitch plan.
|
||||
"""
|
||||
|
||||
if not self.is_alive():
|
||||
self.start()
|
||||
|
||||
self.stop.set()
|
||||
self.refresh_needed.set()
|
||||
|
||||
def run(self):
|
||||
while True:
|
||||
self.refresh_needed.wait()
|
||||
self.refresh_needed.clear()
|
||||
self.stop.clear()
|
||||
|
||||
try:
|
||||
debug.log("update_patches")
|
||||
self.render_stitch_plan()
|
||||
except ExitThread:
|
||||
debug.log("ExitThread caught")
|
||||
self.stop.clear()
|
||||
|
||||
def render_stitch_plan(self):
|
||||
try:
|
||||
stitch_plan = self.render_stitch_plan_hook()
|
||||
if stitch_plan:
|
||||
# rendering_completed() will be called in the main thread.
|
||||
wx.CallAfter(self.rendering_completed_hook, stitch_plan)
|
||||
except ExitThread:
|
||||
raise
|
||||
except: # noqa: E722
|
||||
import traceback
|
||||
debug.log("unhandled exception in PreviewRenderer.render_stitch_plan(): " + traceback.format_exc())
|
|
@ -0,0 +1,234 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2024 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
import os
|
||||
import sys
|
||||
|
||||
import wx
|
||||
|
||||
from ...debug.debug import debug
|
||||
from ...utils import get_resource_dir
|
||||
|
||||
|
||||
class SimulatorSlider(wx.Panel):
|
||||
PROXY_EVENTS = (wx.EVT_SLIDER,)
|
||||
|
||||
def __init__(self, parent, id=wx.ID_ANY, minValue=1, maxValue=2, **kwargs):
|
||||
super().__init__(parent, id)
|
||||
self.control_panel = parent
|
||||
|
||||
kwargs['style'] = wx.SL_HORIZONTAL | wx.SL_VALUE_LABEL | wx.SL_TOP | wx.ALIGN_TOP
|
||||
|
||||
self._height = self.GetTextExtent("M").y * 6
|
||||
self.SetMinSize((self._height, self._height))
|
||||
|
||||
self.marker_lists = {
|
||||
"trim": MarkerList("trim"),
|
||||
"jump": MarkerList("jump", 0.17),
|
||||
"stop": MarkerList("stop", 0.34),
|
||||
"color_change": MarkerList("color_change", 0.34),
|
||||
}
|
||||
self.marker_pen = wx.Pen(wx.Colour(0, 0, 0))
|
||||
self.color_sections = []
|
||||
self.margin = 15
|
||||
self.tab_start = 0
|
||||
self.tab_width = 0.15
|
||||
self.tab_height = 0.15
|
||||
self.color_bar_start = 0.22
|
||||
self.color_bar_thickness = 0.17
|
||||
self.marker_start = self.color_bar_start
|
||||
self.marker_end = 0.5
|
||||
self.marker_icon_start = 0.5
|
||||
self.marker_icon_size = self._height // 6
|
||||
|
||||
self._min = minValue
|
||||
self._max = maxValue
|
||||
self._value = 0
|
||||
self._tab_rect = None
|
||||
|
||||
if sys.platform == "darwin":
|
||||
self.margin = 8
|
||||
|
||||
self.Bind(wx.EVT_PAINT, self.on_paint)
|
||||
self.Bind(wx.EVT_ERASE_BACKGROUND, self.on_erase_background)
|
||||
self.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
|
||||
self.Bind(wx.EVT_LEFT_UP, self.on_mouse_up)
|
||||
self.Bind(wx.EVT_MOTION, self.on_mouse_motion)
|
||||
|
||||
def SetMax(self, value):
|
||||
self._max = value
|
||||
self.Refresh()
|
||||
|
||||
def SetMin(self, value):
|
||||
self._min = value
|
||||
self.Refresh()
|
||||
|
||||
def SetValue(self, value):
|
||||
self._value = value
|
||||
self.Refresh()
|
||||
|
||||
def GetValue(self):
|
||||
return self._value
|
||||
|
||||
def clear(self):
|
||||
self.color_sections = []
|
||||
self._min = 1
|
||||
self._max = 2
|
||||
self._value = 0
|
||||
self._tab_rect = None
|
||||
|
||||
for marker_list in self.marker_lists.values():
|
||||
marker_list.clear()
|
||||
|
||||
def add_color_section(self, color, start, end):
|
||||
self.color_sections.append(ColorSection(color, start, end))
|
||||
|
||||
def add_marker(self, name, location):
|
||||
self.marker_lists[name].append(location)
|
||||
self.Refresh()
|
||||
|
||||
def enable_marker_list(self, name, enabled=True):
|
||||
self.marker_lists[name].enabled = enabled
|
||||
self.Refresh()
|
||||
|
||||
def disable_marker_list(self, name):
|
||||
self.marker_lists[name].enabled = False
|
||||
self.Refresh()
|
||||
|
||||
def toggle_marker_list(self, name):
|
||||
self.marker_lists[name].enabled = not self.marker_lists[name].enabled
|
||||
self.Refresh()
|
||||
|
||||
def on_paint(self, event):
|
||||
dc = wx.BufferedPaintDC(self)
|
||||
if not sys.platform.startswith("win"):
|
||||
# Without this, the background color will be white.
|
||||
background_brush = wx.Brush(self.GetTopLevelParent().GetBackgroundColour(), wx.SOLID)
|
||||
dc.SetBackground(background_brush)
|
||||
dc.Clear()
|
||||
gc = wx.GraphicsContext.Create(dc)
|
||||
|
||||
if self._value < self._min:
|
||||
return
|
||||
|
||||
width, height = self.GetSize()
|
||||
min_value = self._min
|
||||
max_value = self._max
|
||||
spread = max_value - min_value
|
||||
|
||||
def _value_to_x(value):
|
||||
return (value - min_value) * (width - 2 * self.margin) / spread + self.margin
|
||||
|
||||
gc.SetPen(wx.NullPen)
|
||||
for color_section in self.color_sections:
|
||||
gc.SetBrush(color_section.brush)
|
||||
|
||||
start_x = _value_to_x(color_section.start)
|
||||
end_x = _value_to_x(color_section.end)
|
||||
gc.DrawRectangle(start_x, height * self.color_bar_start,
|
||||
end_x - start_x, height * self.color_bar_thickness)
|
||||
|
||||
if self.control_panel.is_dark_theme():
|
||||
gc.SetPen(wx.Pen(wx.Colour(0, 0, 0), 1))
|
||||
gc.SetBrush(wx.Brush(wx.Colour(255, 255, 255)))
|
||||
else:
|
||||
gc.SetPen(wx.Pen(wx.Colour(255, 255, 255), 1))
|
||||
gc.SetBrush(wx.Brush(wx.Colour(0, 0, 0)))
|
||||
|
||||
value_x = _value_to_x(self._value)
|
||||
tab_height = self.tab_height * height
|
||||
tab_width = self.tab_width * height
|
||||
tab_x = value_x - tab_width / 2
|
||||
tab_y = height * self.tab_start
|
||||
self._tab_rect = wx.Rect(round(tab_x), round(tab_y), round(tab_width), round(tab_height))
|
||||
gc.DrawRectangle(
|
||||
value_x - 1.5, 0,
|
||||
3, height * (self.color_bar_start + self.color_bar_thickness))
|
||||
gc.SetPen(wx.NullPen)
|
||||
gc.DrawRectangle(value_x - tab_width/2, height * self.tab_start,
|
||||
tab_width, tab_height)
|
||||
|
||||
gc.SetPen(self.marker_pen)
|
||||
for marker_list in self.marker_lists.values():
|
||||
if marker_list.enabled:
|
||||
for value in marker_list:
|
||||
x = _value_to_x(value)
|
||||
gc.StrokeLine(
|
||||
x, height * self.marker_start,
|
||||
x, height * (self.marker_end + marker_list.offset)
|
||||
)
|
||||
gc.DrawBitmap(
|
||||
marker_list.icon,
|
||||
x - self.marker_icon_size / 2, height * (self.marker_icon_start + marker_list.offset),
|
||||
self.marker_icon_size, self.marker_icon_size
|
||||
)
|
||||
|
||||
def on_erase_background(self, event):
|
||||
# supposedly this prevents flickering?
|
||||
pass
|
||||
|
||||
def is_in_tab(self, point):
|
||||
return self._tab_rect and self._tab_rect.Inflate(2).Contains(point)
|
||||
|
||||
def set_value_from_position(self, point):
|
||||
width, height = self.GetSize()
|
||||
min_value = self._min
|
||||
max_value = self._max
|
||||
spread = max_value - min_value
|
||||
value = round((point.x - self.margin) * spread / (width - 2 * self.margin))
|
||||
value = max(value, self._min)
|
||||
value = min(value, self._max)
|
||||
self.SetValue(round(value))
|
||||
|
||||
event = wx.CommandEvent(wx.wxEVT_COMMAND_SLIDER_UPDATED, self.GetId())
|
||||
event.SetInt(value)
|
||||
event.SetEventObject(self)
|
||||
self.GetEventHandler().ProcessEvent(event)
|
||||
|
||||
def on_mouse_down(self, event):
|
||||
click_pos = event.GetPosition()
|
||||
if self.is_in_tab(click_pos):
|
||||
debug.log("drag start")
|
||||
self.CaptureMouse()
|
||||
self.set_value_from_position(click_pos)
|
||||
self.Refresh()
|
||||
else:
|
||||
width, height = self.GetSize()
|
||||
relative_y = click_pos.y / height
|
||||
if relative_y > self.color_bar_start and relative_y - self.color_bar_start < self.color_bar_thickness:
|
||||
self.set_value_from_position(click_pos)
|
||||
self.Refresh()
|
||||
|
||||
def on_mouse_motion(self, event):
|
||||
if self.HasCapture() and event.Dragging() and event.LeftIsDown():
|
||||
self.set_value_from_position(event.GetPosition())
|
||||
self.Refresh()
|
||||
|
||||
def on_mouse_up(self, event):
|
||||
if self.HasCapture():
|
||||
self.ReleaseMouse()
|
||||
self.set_value_from_position(event.GetPosition())
|
||||
self.Refresh()
|
||||
|
||||
|
||||
class MarkerList(list):
|
||||
def __init__(self, icon_name, offset=0, stitch_numbers=()):
|
||||
super().__init__(self)
|
||||
icons_dir = get_resource_dir("icons")
|
||||
self.icon_name = icon_name
|
||||
self.icon = wx.Image(os.path.join(icons_dir, f"{icon_name}.png")).ConvertToBitmap()
|
||||
self.offset = offset
|
||||
self.enabled = False
|
||||
self.extend(stitch_numbers)
|
||||
|
||||
def __repr__(self):
|
||||
return f"MarkerList({self.icon_name})"
|
||||
|
||||
|
||||
class ColorSection:
|
||||
def __init__(self, color, start, end):
|
||||
self.color = color
|
||||
self.start = start
|
||||
self.end = end
|
||||
self.brush = wx.Brush(wx.Colour(*color))
|
|
@ -0,0 +1,55 @@
|
|||
# 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 wx
|
||||
|
||||
from ...i18n import _
|
||||
from . import SimulatorPanel
|
||||
|
||||
|
||||
class SimulatorWindow(wx.Frame):
|
||||
def __init__(self, panel=None, parent=None, **kwargs):
|
||||
background_color = kwargs.pop('background_color', 'white')
|
||||
super().__init__(None, title=_("Embroidery Simulation"), **kwargs)
|
||||
|
||||
self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE)
|
||||
|
||||
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
self.statusbar = self.CreateStatusBar(2)
|
||||
self.statusbar.SetStatusWidths((0, -1))
|
||||
|
||||
if panel and parent:
|
||||
self.is_child = True
|
||||
self.panel = panel
|
||||
self.parent = parent
|
||||
self.panel.Reparent(self)
|
||||
self.sizer.Add(self.panel, 1, wx.EXPAND)
|
||||
self.panel.Show()
|
||||
else:
|
||||
self.is_child = False
|
||||
self.panel = SimulatorPanel(self, background_color=background_color)
|
||||
self.sizer.Add(self.panel, 1, wx.EXPAND)
|
||||
|
||||
self.SetSizer(self.sizer)
|
||||
self.Layout()
|
||||
|
||||
self.SetMinSize(self.sizer.CalcMin())
|
||||
|
||||
if self.is_child:
|
||||
self.Bind(wx.EVT_CLOSE, self.on_close)
|
||||
else:
|
||||
self.Maximize()
|
||||
|
||||
def detach_simulator_panel(self):
|
||||
self.sizer.Detach(self.panel)
|
||||
|
||||
def on_close(self, event):
|
||||
self.parent.attach_simulator()
|
||||
|
||||
def load(self, stitch_plan):
|
||||
self.panel.load(stitch_plan)
|
||||
|
||||
def go(self):
|
||||
self.panel.go()
|
|
@ -0,0 +1,122 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2024 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
import os
|
||||
|
||||
import wx
|
||||
|
||||
from ...debug.debug import debug
|
||||
from ...utils import get_resource_dir
|
||||
from ...utils.settings import global_settings
|
||||
from . import SimulatorPanel, SimulatorWindow
|
||||
|
||||
|
||||
class SplitSimulatorWindow(wx.Frame):
|
||||
def __init__(self, panel_class, title, target_duration=None, **kwargs):
|
||||
super().__init__(None, title=title)
|
||||
|
||||
self.SetWindowStyle(wx.FRAME_FLOAT_ON_PARENT | wx.DEFAULT_FRAME_STYLE)
|
||||
|
||||
self.statusbar = self.CreateStatusBar(2)
|
||||
|
||||
self.detached_simulator_frame = None
|
||||
self.splitter = wx.SplitterWindow(self, style=wx.SP_LIVE_UPDATE)
|
||||
background_color = kwargs.pop('background_color', 'white')
|
||||
self.cancel_hook = kwargs.pop('on_cancel', None)
|
||||
self.simulator_panel = SimulatorPanel(
|
||||
self.splitter,
|
||||
background_color=background_color,
|
||||
target_duration=target_duration,
|
||||
detach_callback=self.toggle_detach_simulator
|
||||
)
|
||||
self.settings_panel = panel_class(self.splitter, simulator=self.simulator_panel, **kwargs)
|
||||
|
||||
self.splitter.SplitVertically(self.settings_panel, self.simulator_panel)
|
||||
self.splitter.SetMinimumPaneSize(100)
|
||||
|
||||
icon = wx.Icon(os.path.join(get_resource_dir("icons"), "inkstitch256x256.png"))
|
||||
self.SetIcon(icon)
|
||||
|
||||
self.sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
self.sizer.Add(self.splitter, 1, wx.EXPAND)
|
||||
self.SetSizer(self.sizer)
|
||||
|
||||
self.SetMinSize(self.sizer.CalcMin())
|
||||
|
||||
self.simulator_panel.SetFocus()
|
||||
self.Maximize()
|
||||
self.Show()
|
||||
wx.CallLater(100, self.set_sash_position)
|
||||
|
||||
self.Bind(wx.EVT_SPLITTER_SASH_POS_CHANGING, self.splitter_resize)
|
||||
self.Bind(wx.EVT_CLOSE, self.cancel)
|
||||
|
||||
if global_settings['pop_out_simulator']:
|
||||
self.detach_simulator()
|
||||
|
||||
def splitter_resize(self, event):
|
||||
self.statusbar.SetStatusWidths((self.simulator_panel.GetScreenPosition()[0], -1))
|
||||
|
||||
def set_sash_position(self):
|
||||
settings_panel_min_size = self.settings_panel.GetSizer().CalcMin()
|
||||
debug.log(f"{settings_panel_min_size=}")
|
||||
self.splitter.SetSashPosition(settings_panel_min_size.width)
|
||||
self.statusbar.SetStatusWidths((settings_panel_min_size.width, -1))
|
||||
|
||||
def cancel(self, event=None):
|
||||
if self.cancel_hook:
|
||||
self.cancel_hook()
|
||||
self.close(None)
|
||||
|
||||
def close(self, event=None):
|
||||
self.simulator_panel.stop()
|
||||
if self.detached_simulator_frame:
|
||||
self.detached_simulator_frame.Destroy()
|
||||
self.Destroy()
|
||||
|
||||
def toggle_detach_simulator(self):
|
||||
if self.detached_simulator_frame:
|
||||
self.attach_simulator()
|
||||
else:
|
||||
self.detach_simulator()
|
||||
|
||||
def attach_simulator(self):
|
||||
self.detached_simulator_frame.detach_simulator_panel()
|
||||
self.simulator_panel.Reparent(self.splitter)
|
||||
self.splitter.SplitVertically(self.settings_panel, self.simulator_panel)
|
||||
|
||||
self.GetStatusBar().SetStatusText(self.detached_simulator_frame.GetStatusBar().GetStatusText(1), 1)
|
||||
|
||||
self.detached_simulator_frame.Destroy()
|
||||
self.detached_simulator_frame = None
|
||||
self.Maximize()
|
||||
self.splitter.UpdateSize()
|
||||
self.simulator_panel.SetFocus()
|
||||
self.Raise()
|
||||
wx.CallLater(100, self.set_sash_position)
|
||||
global_settings['pop_out_simulator'] = False
|
||||
|
||||
def detach_simulator(self):
|
||||
self.splitter.Unsplit()
|
||||
self.detached_simulator_frame = SimulatorWindow(panel=self.simulator_panel, parent=self)
|
||||
self.splitter.SetMinimumPaneSize(100)
|
||||
|
||||
current_screen = wx.Display.GetFromPoint(wx.GetMousePosition())
|
||||
display = wx.Display(current_screen)
|
||||
screen_rect = display.GetClientArea()
|
||||
settings_panel_size = self.settings_panel.GetSizer().CalcMin()
|
||||
self.SetMinSize(settings_panel_size)
|
||||
self.Maximize(False)
|
||||
self.SetSize((settings_panel_size.width, screen_rect.height))
|
||||
self.SetPosition((screen_rect.left, screen_rect.top))
|
||||
|
||||
self.detached_simulator_frame.SetSize((screen_rect.width - settings_panel_size.width, screen_rect.height))
|
||||
self.detached_simulator_frame.SetPosition((settings_panel_size.width, screen_rect.top))
|
||||
|
||||
self.detached_simulator_frame.GetStatusBar().SetStatusText(self.GetStatusBar().GetStatusText(1), 1)
|
||||
self.GetStatusBar().SetStatusText("", 1)
|
||||
|
||||
self.detached_simulator_frame.Show()
|
||||
|
||||
global_settings['pop_out_simulator'] = True
|
|
@ -0,0 +1,117 @@
|
|||
# Authors: see git history
|
||||
#
|
||||
# Copyright (c) 2024 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
import wx
|
||||
from wx.lib.scrolledpanel import ScrolledPanel
|
||||
|
||||
from ...debug.debug import debug
|
||||
from ...i18n import _
|
||||
from . import SimulatorPreferenceDialog
|
||||
|
||||
|
||||
class ViewPanel(ScrolledPanel):
|
||||
""""""
|
||||
|
||||
@debug.time
|
||||
def __init__(self, parent, detach_callback):
|
||||
""""""
|
||||
self.parent = parent
|
||||
self.detach_callback = detach_callback
|
||||
ScrolledPanel.__init__(self, parent)
|
||||
self.SetupScrolling(scroll_y=True, scroll_x=False)
|
||||
|
||||
self.button_style = wx.BU_EXACTFIT | wx.BU_NOTEXT
|
||||
|
||||
self.control_panel = parent.cp
|
||||
|
||||
self.btnNpp = wx.BitmapToggleButton(self, -1, style=self.button_style)
|
||||
self.btnNpp.Bind(wx.EVT_TOGGLEBUTTON, self.toggle_npp)
|
||||
self.btnNpp.SetBitmap(self.control_panel.load_icon('npp'))
|
||||
self.btnNpp.SetToolTip(_('Display needle penetration point (O)'))
|
||||
self.btnJump = wx.BitmapToggleButton(self, -1, style=self.button_style)
|
||||
self.btnJump.SetToolTip(_('Show jump stitches'))
|
||||
self.btnJump.SetBitmap(self.control_panel.load_icon('jump'))
|
||||
self.btnJump.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.on_marker_button('jump', event))
|
||||
self.btnTrim = wx.BitmapToggleButton(self, -1, style=self.button_style)
|
||||
self.btnTrim.SetToolTip(_('Show trims'))
|
||||
self.btnTrim.SetBitmap(self.control_panel.load_icon('trim'))
|
||||
self.btnTrim.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.on_marker_button('trim', event))
|
||||
self.btnStop = wx.BitmapToggleButton(self, -1, style=self.button_style)
|
||||
self.btnStop.SetToolTip(_('Show stops'))
|
||||
self.btnStop.SetBitmap(self.control_panel.load_icon('stop'))
|
||||
self.btnStop.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.on_marker_button('stop', event))
|
||||
self.btnColorChange = wx.BitmapToggleButton(self, -1, style=self.button_style)
|
||||
self.btnColorChange.SetToolTip(_('Show color changes'))
|
||||
self.btnColorChange.SetBitmap(self.control_panel.load_icon('color_change'))
|
||||
self.btnColorChange.Bind(wx.EVT_TOGGLEBUTTON, lambda event: self.on_marker_button('color_change', event))
|
||||
|
||||
self.btnBackgroundColor = wx.ColourPickerCtrl(self, -1, colour='white', size=((40, -1)))
|
||||
self.btnBackgroundColor.SetToolTip(_("Change background color"))
|
||||
self.btnBackgroundColor.Bind(wx.EVT_COLOURPICKER_CHANGED, self.on_update_background_color)
|
||||
|
||||
self.btnSettings = wx.BitmapButton(self, -1, style=self.button_style)
|
||||
self.btnSettings.SetToolTip(_('Open settings dialog'))
|
||||
self.btnSettings.SetBitmap(self.control_panel.load_icon('settings'))
|
||||
self.btnSettings.Bind(wx.EVT_BUTTON, self.on_settings_button)
|
||||
|
||||
if self.detach_callback:
|
||||
self.btnDetachSimulator = wx.BitmapButton(self, -1, style=self.button_style)
|
||||
self.btnDetachSimulator.SetToolTip(_('Detach/attach simulator window'))
|
||||
self.btnDetachSimulator.SetBitmap(self.control_panel.load_icon('detach_window'))
|
||||
self.btnDetachSimulator.Bind(wx.EVT_BUTTON, lambda event: self.control_panel.detach_callback())
|
||||
|
||||
outer_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
|
||||
show_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Show")), wx.VERTICAL)
|
||||
show_inner_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
show_inner_sizer.Add(self.btnNpp, 0, wx.ALL, 2)
|
||||
show_inner_sizer.Add(self.btnJump, 0, wx.ALL, 2)
|
||||
show_inner_sizer.Add(self.btnTrim, 0, wx.ALL, 2)
|
||||
show_inner_sizer.Add(self.btnStop, 0, wx.ALL, 2)
|
||||
show_inner_sizer.Add(self.btnColorChange, 0, wx.ALL, 2)
|
||||
show_sizer.Add(0, 2, 0)
|
||||
show_sizer.Add(show_inner_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2)
|
||||
show_sizer.Add(0, 2, 0)
|
||||
outer_sizer.Add(show_sizer)
|
||||
outer_sizer.Add(0, 10, 0)
|
||||
|
||||
settings_sizer = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, _("Settings")), wx.VERTICAL)
|
||||
settings_inner_sizer = wx.BoxSizer(wx.VERTICAL)
|
||||
settings_inner_sizer.Add(self.btnBackgroundColor, 0, wx.EXPAND | wx.ALL, 2)
|
||||
settings_inner_sizer.Add(self.btnSettings, 0, wx.EXPAND | wx.ALL, 2)
|
||||
if self.detach_callback:
|
||||
settings_inner_sizer.Add(self.btnDetachSimulator, 0, wx.ALL, 2)
|
||||
settings_sizer.Add(0, 2, 0)
|
||||
settings_sizer.Add(settings_inner_sizer, 0, wx.ALIGN_CENTER_HORIZONTAL | wx.ALL, 2)
|
||||
settings_sizer.Add(0, 2, 0)
|
||||
outer_sizer.Add(settings_sizer)
|
||||
|
||||
self.SetSizerAndFit(outer_sizer)
|
||||
|
||||
def set_drawing_panel(self, drawing_panel):
|
||||
self.drawing_panel = drawing_panel
|
||||
|
||||
def on_update_background_color(self, event):
|
||||
self.set_background_color(event.Colour)
|
||||
|
||||
def set_background_color(self, color):
|
||||
self.btnBackgroundColor.SetColour(color)
|
||||
self.drawing_panel.SetBackgroundColour(color)
|
||||
self.drawing_panel.Refresh()
|
||||
|
||||
def on_toggle_npp_shortcut(self, event):
|
||||
self.btnNpp.SetValue(not self.btnNpp.GetValue())
|
||||
self.toggle_npp(event)
|
||||
|
||||
def toggle_npp(self, event):
|
||||
self.drawing_panel.Refresh()
|
||||
|
||||
def on_marker_button(self, marker_type, event):
|
||||
self.control_panel.slider.enable_marker_list(marker_type, event.GetEventObject().GetValue())
|
||||
if marker_type == 'jump':
|
||||
self.drawing_panel.Refresh()
|
||||
|
||||
def on_settings_button(self, event):
|
||||
simulator_panel = SimulatorPreferenceDialog(self, title=_('Simulator Preferences'))
|
||||
simulator_panel.Show()
|
|
@ -14,6 +14,8 @@ DEFAULT_METADATA = {
|
|||
DEFAULT_SETTINGS = {
|
||||
"cache_size": 100,
|
||||
"pop_out_simulator": False,
|
||||
"simulator_line_width": 0.1,
|
||||
"simulator_npp_size": 0.5
|
||||
}
|
||||
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue