summaryrefslogtreecommitdiff
path: root/lib/simulator.py
diff options
context:
space:
mode:
authorLex Neva <github.com@lexneva.name>2018-05-01 20:37:51 -0400
committerLex Neva <github.com@lexneva.name>2018-05-01 20:37:51 -0400
commit1b31806423c8fec4040fed6d1009db016860b763 (patch)
tree71ccac169471e76685a7fa0b9910f64555dc73a7 /lib/simulator.py
parent5b7f14d092456a941dbd189e61ed38d9b16d388b (diff)
rename inkstitch/ to lib/
You can't have a module and a package named the same thing. PyInstaller wants to import the main script as if it were a module, and this doesn't work unless there's no directory of the same name with a __init__.py in it.
Diffstat (limited to 'lib/simulator.py')
-rw-r--r--lib/simulator.py252
1 files changed, 252 insertions, 0 deletions
diff --git a/lib/simulator.py b/lib/simulator.py
new file mode 100644
index 00000000..cc9442ea
--- /dev/null
+++ b/lib/simulator.py
@@ -0,0 +1,252 @@
+import sys
+import numpy
+import wx
+import colorsys
+from itertools import izip
+
+from . import PIXELS_PER_MM
+from .svg import color_block_to_point_lists
+
+
+class EmbroiderySimulator(wx.Frame):
+ def __init__(self, *args, **kwargs):
+ stitch_plan = kwargs.pop('stitch_plan', None)
+ self.on_close_hook = kwargs.pop('on_close', None)
+ self.frame_period = kwargs.pop('frame_period', 80)
+ self.stitches_per_frame = kwargs.pop('stitches_per_frame', 1)
+ self.target_duration = kwargs.pop('target_duration', None)
+
+ self.margin = 10
+
+ screen_rect = wx.Display(0).ClientArea
+ self.max_width = kwargs.pop('max_width', screen_rect.GetWidth())
+ self.max_height = kwargs.pop('max_height', screen_rect.GetHeight())
+ self.scale = 1
+
+ wx.Frame.__init__(self, *args, **kwargs)
+
+ self.panel = wx.Panel(self, wx.ID_ANY)
+ self.panel.SetFocus()
+
+ self.load(stitch_plan)
+
+ 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.dc.SelectObject(self.buffer)
+ self.canvas = wx.GraphicsContext.Create(self.dc)
+
+ self.clear()
+
+ self.Bind(wx.EVT_SIZE, self.on_size)
+ self.panel.Bind(wx.EVT_PAINT, self.on_paint)
+ self.panel.Bind(wx.EVT_KEY_DOWN, self.on_key_down)
+
+ self.timer = None
+
+ self.last_pos = None
+
+ self.Bind(wx.EVT_CLOSE, self.on_close)
+
+ def load(self, stitch_plan=None):
+ if stitch_plan:
+ self.mirror = False
+ self.segments = self._stitch_plan_to_segments(stitch_plan)
+ else:
+ return
+
+ self.trim_margins()
+ self.calculate_dimensions()
+
+ def adjust_speed(self, duration):
+ self.frame_period = 1000 * float(duration) / len(self.segments)
+ self.stitches_per_frame = 1
+
+ while self.frame_period < 1.0:
+ self.frame_period *= 2
+ self.stitches_per_frame *= 2
+
+ def on_key_down(self, event):
+ keycode = event.GetKeyCode()
+
+ if keycode == ord("+") or keycode == ord("=") or keycode == wx.WXK_UP:
+ if self.frame_period == 1:
+ self.stitches_per_frame *= 2
+ else:
+ self.frame_period = self.frame_period / 2
+ elif keycode == ord("-") or keycode == ord("_") or keycode == wx.WXK_DOWN:
+ if self.stitches_per_frame == 1:
+ self.frame_period *= 2
+ else:
+ self.stitches_per_frame /= 2
+ elif keycode == ord("Q"):
+ self.Close()
+ elif keycode == ord('P'):
+ if self.timer.IsRunning():
+ self.timer.Stop()
+ else:
+ self.timer.Start(self.frame_period)
+ elif keycode == ord("R"):
+ self.stop()
+ self.clear()
+ self.go()
+
+ self.frame_period = max(1, self.frame_period)
+ self.stitches_per_frame = max(self.stitches_per_frame, 1)
+
+ if self.timer.IsRunning():
+ self.timer.Stop()
+ self.timer.Start(self.frame_period)
+
+ def _strip_quotes(self, string):
+ if string.startswith('"') and string.endswith('"'):
+ string = string[1:-1]
+
+ return string
+
+ def color_to_pen(self, color):
+ return wx.Pen(color.visible_on_white.rgb)
+
+ def _stitch_plan_to_segments(self, stitch_plan):
+ segments = []
+
+ 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):
+ # 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]
+
+ yield start
+ yield end
+
+ def trim_margins(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 = []
+
+ for segment in self.segments:
+ (start, end), color = segment
+
+ new_segment = (
+ (
+ (start[0] - min_x, start[1] - min_y),
+ (end[0] - min_x, end[1] - min_y),
+ ),
+ color
+ )
+
+ new_segments.append(new_segment)
+
+ self.segments = new_segments
+
+ def calculate_dimensions(self):
+ # 0.01 avoids a division by zero below for designs with no width or
+ # height (e.g. a straight vertical or horizontal line)
+ width = 0.01
+ height = 0.01
+
+ for x, y in self.all_coordinates():
+ width = max(width, x)
+ height = max(height, y)
+
+ self.width = width
+ self.height = height
+ self.scale = min(float(self.max_width) / width, float(self.max_height) / height)
+
+ # make room for decorations and the margin
+ self.scale *= 0.95
+
+ def go(self):
+ self.clear()
+
+ self.current_stitch = 0
+
+ if not self.timer:
+ self.timer = wx.PyTimer(self.draw_one_frame)
+
+ self.timer.Start(self.frame_period)
+
+ def on_close(self, event):
+ self.stop()
+
+ if self.on_close_hook:
+ self.on_close_hook()
+
+ # If we keep a reference here, wx crashes when the process exits.
+ self.canvas = None
+
+ self.Destroy()
+
+ def stop(self):
+ if self.timer:
+ self.timer.Stop()
+
+ def clear(self):
+ self.dc.SetBackground(wx.Brush('white'))
+ self.dc.Clear()
+ self.last_pos = None
+ self.Refresh()
+
+ def on_size(self, e):
+ # ensure that the whole canvas is visible
+ window_width, window_height = self.GetSize()
+ client_width, client_height = self.GetClientSize()
+
+ decorations_width = window_width - client_width
+ decorations_height = window_height - client_height
+
+ self.SetSize((self.width * self.scale + decorations_width + self.margin * 2,
+ self.height * self.scale + decorations_height + self.margin * 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)
+
+ 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)
+
+ 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)
+ except IndexError:
+ self.timer.Stop()