Add preferences button to simulator ()

* 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
Kaalleen 2024-06-14 09:49:57 +02:00 zatwierdzone przez GitHub
rodzic 39d9defef4
commit dbdba2cda3
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: B5690EEEBB952194
16 zmienionych plików z 1607 dodań i 1330 usunięć

BIN
icons/settings.png 100644

Plik binarny nie jest wyświetlany.

Po

Szerokość:  |  Wysokość:  |  Rozmiar: 6.4 KiB

64
icons/settings.svg 100644
Wyświetl plik

@ -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

Wyświetl plik

@ -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()

Plik diff jest za duży Load Diff

Wyświetl plik

@ -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

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()

Wyświetl plik

@ -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()

Wyświetl plik

@ -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())

Wyświetl plik

@ -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))

Wyświetl plik

@ -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()

Wyświetl plik

@ -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

Wyświetl plik

@ -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()

Wyświetl plik

@ -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
}