inkstitch/embroider_simulate.py

286 wiersze
8.7 KiB
Python

import sys
import os
import numpy
import wx
import inkex
import simplestyle
import colorsys
from itertools import izip
import inkstitch
from inkstitch.extensions import InkstitchExtension
from inkstitch import PIXELS_PER_MM
from inkstitch.stitch_plan import patches_to_stitch_plan
from inkstitch.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()
class SimulateEffect(InkstitchExtension):
def __init__(self):
InkstitchExtension.__init__(self)
self.OptionParser.add_option("-P", "--path",
action="store", type="string",
dest="path", default=".",
help="Directory in which to store output file")
def effect(self):
if not self.get_elements():
return
patches = self.elements_to_patches(self.elements)
stitch_plan = patches_to_stitch_plan(patches)
app = wx.App()
frame = EmbroiderySimulator(None, -1, _("Embroidery Simulation"), wx.DefaultPosition, size=(1000, 1000), stitch_plan=stitch_plan)
app.SetTopWindow(frame)
frame.Show()
wx.CallAfter(frame.go)
app.MainLoop()
if __name__ == "__main__":
effect = SimulateEffect()
effect.affect()
sys.exit(0)