diff options
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/extensions/params.py | 10 | ||||
| -rw-r--r-- | lib/simulator.py | 378 |
2 files changed, 261 insertions, 127 deletions
diff --git a/lib/extensions/params.py b/lib/extensions/params.py index c464e044..6d1464a3 100644 --- a/lib/extensions/params.py +++ b/lib/extensions/params.py @@ -287,7 +287,6 @@ class ParamsTab(ScrolledPanel): summary_box = wx.StaticBox(self, wx.ID_ANY, label=_("Inkscape objects")) sizer = wx.StaticBoxSizer(summary_box, wx.HORIZONTAL) -# sizer = wx.BoxSizer(wx.HORIZONTAL) self.description = wx.StaticText(self) self.update_description() self.description.SetLabel(self.description_text) @@ -423,6 +422,15 @@ class SettingsFrame(wx.Frame): if self.simulate_window: self.simulate_window.stop() self.simulate_window.load(stitch_plan=stitch_plan) + self.simulate_window.calculate_dimensions() + + children = self.simulate_window.GetChildren() + for child in children: + if isinstance(child, wx.Slider): + child.Destroy() + self.simulate_window.set_slider() + + self.simulate_window.Layout() else: params_rect = self.GetScreenRect() simulator_pos = params_rect.GetTopRight() diff --git a/lib/simulator.py b/lib/simulator.py index 3df72a08..530f6ebf 100644 --- a/lib/simulator.py +++ b/lib/simulator.py @@ -1,10 +1,9 @@ import sys -import numpy import wx -import colorsys +import time from itertools import izip -from .svg import PIXELS_PER_MM, color_block_to_point_lists +from .svg import color_block_to_point_lists from .i18n import _ class EmbroiderySimulator(wx.Frame): @@ -27,21 +26,32 @@ class EmbroiderySimulator(wx.Frame): if self.max_width < self.min_width: self.max_width = self.min_width + self.load(stitch_plan) + wx.Frame.__init__(self, *args, **kwargs) self.panel = wx.Panel(self, wx.ID_ANY) + self.panel.SetBackgroundStyle(wx.BG_STYLE_PAINT) - self.panel.SetDoubleBuffered(True) + self.SetBackgroundColour('white') - self.sizer = wx.BoxSizer(wx.VERTICAL) - self.button_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.slider_sizer = wx.BoxSizer(wx.HORIZONTAL) + self.set_slider() + self.button_sizer = wx.StdDialogButtonSizer() self.button_label = ( - [_("Speed up"), _('Press + or arrow up to speed up'), self.animation_speed_up], - [_("Slow down"), _('Press - or arrow down to slow down'), self.animation_slow_down], - [_("Pause"), _("Press P to pause the animation"), self.animation_pause], - [_("Restart"), _("Press R to restart the animation"), self.animation_restart], - [_("Quit"), _("Press Q to close the simulation window"), self.animation_quit]) + # Switch direction button (currently not in use - would this be better?) + #[_('>>'), _('Switch direction | Play reverse (arrow left) | Play forward (arrow right)'), self.animation_switch_direction], + [_('<<'), _('Play reverse (arrow left)'), self.animation_reverse], + [_('-'), _('Play one frame backward (+)'), self.animation_one_frame_back], + [_('+'), _('Play one frame forward (+)'), self.animation_one_frame_forward], + [_('>>'), _('Play forward (arrow right)'), self.animation_forward], + [_('^'), _('Speed up (arrow up)'), self.animation_speed_up], + [_('v'), _('Slow down (arrow down)'), self.animation_slow_down], + [_('Pause'), _('Pause (P)'), self.animation_pause], + [_('Restart'), _('Restart (R)'), self.animation_restart], + [_('Quit'), _('Close (Q)'), self.animation_quit]) + self.buttons = [] for i in range(0, len(self.button_label)): self.buttons.append(wx.Button(self, -1, self.button_label[i][0])) @@ -49,40 +59,53 @@ class EmbroiderySimulator(wx.Frame): self.buttons[i].SetToolTip(self.button_label[i][1]) self.buttons[i].Bind(wx.EVT_BUTTON, self.button_label[i][2]) + self.sizer = wx.BoxSizer(wx.VERTICAL) + self.sizer.Add(self.panel, 1, wx.EXPAND) + self.sizer.Add(self.slider_sizer, 0, wx.EXPAND) self.sizer.Add(self.button_sizer, 0, wx.EXPAND) self.SetSizer(self.sizer) - self.load(stitch_plan) + self.calculate_dimensions() if self.target_duration: self.adjust_speed(self.target_duration) - self.buffer = wx.Bitmap(self.width * self.scale + self.margin * 2, self.height * self.scale + self.margin * 2) - self.dc = wx.MemoryDC() + self.buffer = wx.Bitmap( + self.width * self.scale + self.margin * 2, + self.height * self.scale + self.margin * 2) + self.dc = wx.BufferedDC() self.dc.SelectObject(self.buffer) self.canvas = wx.GraphicsContext.Create(self.dc) self.clear() - self.set_stitch_counter(1) + self.current_frame = 0 + self.animation_direction = 1 + self.set_stitch_counter(0) shortcut_keys = [ - (wx.ACCEL_NORMAL, ord('+'), self.animation_speed_up), - (wx.ACCEL_NORMAL, ord('='), self.animation_speed_up), - (wx.ACCEL_SHIFT, ord('='), self.animation_speed_up), - (wx.ACCEL_NORMAL, wx.WXK_ADD, self.animation_speed_up), - (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_ADD, self.animation_speed_up), - (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_UP, self.animation_speed_up), + (wx.ACCEL_NORMAL, wx.WXK_RIGHT, self.animation_forward), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_RIGHT, self.animation_forward), + (wx.ACCEL_NORMAL, wx.WXK_LEFT, self.animation_reverse), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_LEFT, self.animation_reverse), (wx.ACCEL_NORMAL, wx.WXK_UP, self.animation_speed_up), - (wx.ACCEL_NORMAL, ord('-'), self.animation_slow_down), - (wx.ACCEL_NORMAL, ord('_'), self.animation_slow_down), - (wx.ACCEL_NORMAL, wx.WXK_SUBTRACT, self.animation_slow_down), - (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_SUBTRACT, self.animation_slow_down), - (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_DOWN, self.animation_slow_down), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_UP, self.animation_speed_up), (wx.ACCEL_NORMAL, wx.WXK_DOWN, self.animation_slow_down), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_DOWN, self.animation_slow_down), + (wx.ACCEL_NORMAL, ord('+'), self.animation_one_frame_forward), + (wx.ACCEL_NORMAL, ord('='), self.animation_one_frame_forward), + (wx.ACCEL_SHIFT, ord('='), self.animation_one_frame_forward), + (wx.ACCEL_NORMAL, wx.WXK_ADD, self.animation_one_frame_forward), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_ADD, self.animation_one_frame_forward), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_UP, self.animation_one_frame_forward), + (wx.ACCEL_NORMAL, ord('-'), self.animation_one_frame_back), + (wx.ACCEL_NORMAL, ord('_'), self.animation_one_frame_back), + (wx.ACCEL_NORMAL, wx.WXK_SUBTRACT, self.animation_one_frame_back), + (wx.ACCEL_NORMAL, wx.WXK_NUMPAD_SUBTRACT, self.animation_one_frame_back), (wx.ACCEL_NORMAL, ord('r'), self.animation_restart), (wx.ACCEL_NORMAL, ord('p'), self.animation_pause), + (wx.ACCEL_NORMAL, wx.WXK_SPACE, self.animation_pause), (wx.ACCEL_NORMAL, ord('q'), self.animation_quit)] accel_entries = [] @@ -97,6 +120,7 @@ class EmbroiderySimulator(wx.Frame): self.Bind(wx.EVT_SIZE, self.on_size) self.Bind(wx.EVT_CLOSE, self.on_close) + self.Bind(wx.EVT_SLIDER, self.on_slider) self.panel.Bind(wx.EVT_PAINT, self.on_paint) self.panel.SetFocus() @@ -114,36 +138,73 @@ class EmbroiderySimulator(wx.Frame): def load(self, stitch_plan=None): if stitch_plan: self.mirror = False - self.segments = self._stitch_plan_to_segments(stitch_plan) - else: + self.stitch_plan_to_lines(stitch_plan) + self.move_to_top_left() return - self.trim_margins() - self.calculate_dimensions() - def adjust_speed(self, duration): - self.frame_period = 1000 * float(duration) / len(self.segments) + self.frame_period = 1000 * float(duration) / len(self.lines) self.stitches_per_frame = 1 while self.frame_period < 1.0: self.frame_period *= 2 self.stitches_per_frame *= 2 - def animation_speed_up(self, event): - if self.frame_period == 1: - self.stitches_per_frame *= 2 + # Switch direction button (currently not in use - would this be better?) + def animation_switch_direction(self, event): + direction_button = event.GetEventObject() + lbl = direction_button.GetLabel() + if self.animation_direction == 1: + self.animation_reverse('backward') + direction_button.SetLabel('<<') else: - self.frame_period = self.frame_period / 2 + self.animation_forward('forward') + direction_button.SetLabel('>>') + + def animation_forward(self, event): + self.animation_direction = 1 + if not self.timer.IsRunning(): + self.timer.StartOnce(self.frame_period) + + def animation_reverse(self, event): + self.animation_direction = -1 + if not self.timer.IsRunning(): + self.timer.StartOnce(self.frame_period) + + def animation_one_frame_forward(self, event): + if self.current_frame < len(self.lines): + self.timer.Stop() + self.current_frame = self.current_frame + 1 + self.draw_one_frame() + self.set_stitch_counter(self.current_frame) + self.set_stitch_slider(self.current_frame) + + def animation_one_frame_back(self, event): + if self.current_frame > 1: + self.timer.Stop() + self.current_frame = self.current_frame - 1 + self.draw_one_frame() + self.set_stitch_counter(self.current_frame) + self.set_stitch_slider(self.current_frame) + + def animation_speed_up(self, event): + if self.stitches_per_frame <= 1280: + if self.frame_period == 1: + self.stitches_per_frame *= 2 + else: + self.frame_period = self.frame_period / 2 self.animation_update_timer() def animation_slow_down(self, event): - if self.stitches_per_frame == 1: - self.frame_period *= 2 - else: - self.stitches_per_frame /= 2 + if self.frame_period <= 1280: + if self.stitches_per_frame == 1: + self.frame_period *= 2 + else: + self.stitches_per_frame /= 2 self.animation_update_timer() def animation_restart(self, event): + self.current_frame = 1 self.stop() self.clear() self.go() @@ -152,7 +213,7 @@ class EmbroiderySimulator(wx.Frame): if self.timer.IsRunning(): self.timer.Stop() else: - self.timer.Start(self.frame_period) + self.timer.StartOnce(self.frame_period) def animation_quit(self, event): self.Close() @@ -160,19 +221,32 @@ class EmbroiderySimulator(wx.Frame): def animation_update_timer(self): self.frame_period = max(1, self.frame_period) self.stitches_per_frame = max(self.stitches_per_frame, 1) + self.set_stitch_counter(self.current_frame) if self.timer.IsRunning(): self.timer.Stop() - self.timer.Start(self.frame_period) + self.timer.StartOnce(self.frame_period) - def set_stitch_counter(self, current_stitch): - if hasattr(self.panel, 'stitch_counter'): - self.panel.stitch_counter.SetLabel(_("Stitch # ") + str(current_stitch) + ' / ' + str(len(self.segments) + 1)) - else: - self.font = wx.Font(9, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL) - self.panel.stitch_counter = wx.StaticText(self, label=_("Stitch #") + '1 / ' + str(len(self.segments)), pos=(30, 10)) - self.panel.stitch_counter.SetFont(self.font) - self.panel.stitch_counter.SetForegroundColour('red') - self.panel.stitch_counter.SetBackgroundColour('white') + def set_stitch_counter(self, current_frame): + self.dc.SetTextForeground('red') + stitch_counter_text = _("Stitch # ") + \ + str(current_frame) + ' / ' + str(len(self.lines)) + self.dc.DrawText(stitch_counter_text, 30, 5) + + def on_slider(self, event): + self.panel.SetFocus() + self.draw_one_frame() + obj = event.GetEventObject() + self.current_frame = obj.GetValue() + self.animation_update_timer() + + def set_slider(self): + self.stitch_slider = wx.Slider( + self, value=1, minValue=1, maxValue=len( + self.lines), style=wx.SL_HORIZONTAL | wx.SL_LABELS) + self.slider_sizer.Add(self.stitch_slider, 1, wx.EXPAND) + + def set_stitch_slider(self, val): + self.stitch_slider.SetValue(val) def _strip_quotes(self, string): if string.startswith('"') and string.endswith('"'): @@ -183,55 +257,51 @@ class EmbroiderySimulator(wx.Frame): def color_to_pen(self, color): return wx.Pen(color.visible_on_white.rgb) - def _stitch_plan_to_segments(self, stitch_plan): - segments = [] + def stitch_plan_to_lines(self, stitch_plan): + self.pens = [] + self.lines = [] for color_block in stitch_plan: pen = self.color_to_pen(color_block.color) - for point_list in color_block_to_point_lists(color_block): + for i, point_list in enumerate( + color_block_to_point_lists(color_block)): + if i == 0: + # add the first stitch + first_x, first_y = point_list[0] + self.lines.append((first_x, first_y, first_x, first_y)) + self.pens.append(pen) + # if there's only one point, there's nothing to do, so skip if len(point_list) < 2: continue for start, end in izip(point_list[:-1], point_list[1:]): - segments.append(((start, end), pen)) - - return segments - - def all_coordinates(self): - for segment in self.segments: - start, end = segment[0] + line = (start[0], start[1], end[0], end[1]) + self.lines.append(line) + self.pens.append(pen) - yield start - yield end - - def trim_margins(self): + def move_to_top_left(self): """remove any unnecessary whitespace around the design""" - min_x = sys.maxint - min_y = sys.maxint - - for x, y in self.all_coordinates(): - min_x = min(min_x, x) - min_y = min(min_y, y) - - new_segments = [] + min_x = sys.maxsize + min_y = sys.maxsize - for segment in self.segments: - (start, end), color = segment + for x1, y1, x2, y2 in self.lines: + min_x = min(min_x, x2) + min_y = min(min_y, y2) - new_segment = ( - ( - (start[0] - min_x, start[1] - min_y), - (end[0] - min_x, end[1] - min_y), - ), - color - ) + new_lines = [] - new_segments.append(new_segment) + for line in self.lines: + (start, end, start1, end1) = line + new_lines.append( + (start - min_x, + end - min_y, + start1 - min_x, + end1 - min_y)) - self.segments = new_segments + self.lines = new_lines def calculate_dimensions(self): # 0.01 avoids a division by zero below for designs with no width or @@ -239,26 +309,51 @@ class EmbroiderySimulator(wx.Frame): width = 0.01 height = 0.01 - for x, y in self.all_coordinates(): - width = max(width, x) - height = max(height, y) + for x1, y1, x2, y2 in self.lines: + width = max(width, x2) + height = max(height, y2) self.width = width self.height = height - self.scale = min(float(self.max_width - self.margin * 2) / width, float(self.max_height - self.margin * 2 - 40) / height) + + button_width, button_height = self.buttons[0].GetSize() + slider_width, slider_height = self.stitch_slider.GetSize() + self.controls_height = button_height + slider_height + + self.scale = min( + float( + self.max_width - + self.margin * + 2) / + width, + float( + self.max_height - + self.margin * + 2 - + self.controls_height) / + height) # make room for decorations and the margin self.scale *= 0.95 + for i, point in enumerate(self.lines): + x1, x2, y1, y2 = point + x1 = x1 * self.scale + self.margin + y1 = y1 * self.scale + self.margin + x2 = x2 * self.scale + self.margin + y2 = y2 * self.scale + self.margin + + self.lines[i] = (x1, x2, y1, y2) + def go(self): self.clear() - self.current_stitch = 0 + self.current_frame = 0 if not self.timer: - self.timer = wx.PyTimer(self.draw_one_frame) + self.timer = wx.PyTimer(self.iterate_frames) - self.timer.Start(self.frame_period) + self.timer.StartOnce(self.frame_period) def on_close(self, event): self.stop() @@ -287,57 +382,88 @@ class EmbroiderySimulator(wx.Frame): client_width, client_height = self.GetClientSize() decorations_width = window_width - client_width - decorations_height = window_height - client_height + 40 + decorations_height = window_height - client_height - setsize_window_width = self.width * self.scale + decorations_width + self.margin * 2 - setsize_window_height = (self.height) * self.scale + decorations_height + self.margin * 2 + setsize_window_width = self.width * self.scale + \ + decorations_width + self.margin * 2 + setsize_window_height = self.height * self.scale + \ + decorations_height + self.controls_height + self.margin * 2 # set minimum width (force space for control buttons) if setsize_window_width < self.min_width: setsize_window_width = self.min_width - self.SetSize(( setsize_window_width, setsize_window_height)) + self.SetSize((setsize_window_width, setsize_window_height)) # center the simulation on screen if not called by params # else center vertically - if self.x_position == None: + if self.x_position is None: self.Centre() else: display_rect = self.get_current_screen_rect() - self.SetPosition((self.x_position, display_rect[3] / 2 - setsize_window_height / 2)) + self.SetPosition( + (self.x_position, + display_rect[3] / + 2 - + setsize_window_height / + 2)) e.Skip() def on_paint(self, e): - dc = wx.PaintDC(self.panel) - dc.Blit(0, 0, self.buffer.GetWidth(), self.buffer.GetHeight(), self.dc, 0, 0) + dc = wx.AutoBufferedPaintDC(self.panel) + dc.Blit( + 0, + 0, + self.buffer.GetWidth(), + self.buffer.GetHeight(), + self.dc, + 0, + 0) + + self.last_pos_x, self.last_pos_y, self.last_pos_x1, self.last_pos_y1 = self.lines[0] + + if hasattr(self, 'visible_lines'): + if len(self.visible_lines) > 0: + self.last_pos_x1, self.last_pos_y1, self.last_pos_x, self.last_pos_y = self.visible_lines[-1] + + dc.DrawLine( + self.last_pos_x - 10, + self.last_pos_y, + self.last_pos_x + 10, + self.last_pos_y) + dc.DrawLine( + self.last_pos_x, + self.last_pos_y - 10, + self.last_pos_x, + self.last_pos_y + 10) + + def iterate_frames(self): + self.current_frame += self.stitches_per_frame * self.animation_direction + + if self.current_frame <= len(self.lines) and self.current_frame >= 1: + # calculate time_to_next_frame + start = time.time() + self.draw_one_frame() + duration = time.time() - start + duration_ms = int(duration * 1000) + time_to_next_frame = self.frame_period - duration_ms + time_to_next_frame = max(1, time_to_next_frame) + self.timer.StartOnce(time_to_next_frame) + elif self.current_frame > len(self.lines): + self.current_frame = len(self.lines) + self.draw_one_frame() + elif self.current_frame < 1: + self.current_frame = 1 + self.draw_one_frame() + else: + self.timer.Stop() - if self.last_pos: - dc.DrawLine(self.last_pos[0] - 10, self.last_pos[1], self.last_pos[0] + 10, self.last_pos[1]) - dc.DrawLine(self.last_pos[0], self.last_pos[1] - 10, self.last_pos[0], self.last_pos[1] + 10) + self.set_stitch_counter(self.current_frame) + self.set_stitch_slider(self.current_frame) def draw_one_frame(self): - for i in xrange(self.stitches_per_frame): - try: - ((x1, y1), (x2, y2)), color = self.segments[self.current_stitch] - - if self.mirror: - y1 = self.height - y1 - y2 = self.height - y2 - - x1 = x1 * self.scale + self.margin - y1 = y1 * self.scale + self.margin - x2 = x2 * self.scale + self.margin - y2 = y2 * self.scale + self.margin - - self.canvas.SetPen(color) - self.canvas.DrawLines(((x1, y1), (x2, y2))) - self.Refresh() - - self.current_stitch += 1 - self.last_pos = (x2, y2) - - self.set_stitch_counter(self.current_stitch + 1) - - except IndexError: - self.timer.Stop() + self.clear() + self.visible_lines = self.lines[:self.current_frame] + self.dc.DrawLineList(self.visible_lines, + self.pens[:self.current_frame]) |
