rewrite of Embroidery Params into a full GUI app

The Embroidery Params filter now pops up a full GTK dialog.  This alows it to
load existing values in the selected shapes and present them to the user.  The
user can also load and save presets.

If selected shapes have differing values for a given param, the values are
presented in a dropdown so the user can select one to apply to all.
pull/2/merge
Lex Neva 2016-11-17 15:17:55 -05:00
rodzic 1fedbc11b5
commit a7bfc17e7c
4 zmienionych plików z 700 dodań i 1260 usunięć

Wyświetl plik

@ -50,12 +50,52 @@ SVG_DEFS_TAG = inkex.addNS('defs', 'svg')
SVG_GROUP_TAG = inkex.addNS('g', 'svg')
class EmbroideryElement(object):
class Param(object):
def __init__(self, name, description, unit=None, values=[], type=None, group=None, inverse=False, default=None):
self.name = name
self.description = description
self.unit = unit
self.values = values or [""]
self.type = type
self.group = group
self.inverse = inverse
self.default = default
def __init__(self, node, options):
def __repr__(self):
return "Param(%s)" % vars(self)
# Decorate a member function or property with information about
# the embroidery parameter it corresponds to
def param(*args, **kwargs):
p = Param(*args, **kwargs)
def decorator(func):
func.param = p
return func
return decorator
class EmbroideryElement(object):
def __init__(self, node, options=None):
self.node = node
self.options = options
@property
def id(self):
return self.node.get('id')
@classmethod
def get_params(cls):
params = []
for attr in dir(cls):
prop = getattr(cls, attr)
if isinstance(prop, property):
# The 'param' attribute is set by the 'param' decorator defined above.
if hasattr(prop.fget, 'param'):
params.append(prop.fget.param)
return params
@cache
def get_param(self, param, default):
value = self.node.get("embroider_" + param, "").strip()
@ -99,6 +139,9 @@ class EmbroideryElement(object):
return value
def set_param(self, name, value):
self.node.set("embroider_%s" % name, value)
@cache
def get_style(self, style_name):
style = simplestyle.parseStyle(self.node.get("style"))
@ -184,11 +227,16 @@ class EmbroideryElement(object):
class Fill(EmbroideryElement):
def __init__(self, *args, **kwargs):
super(Fill, self).__init__(*args, **kwargs)
@property
@param('auto_fill', 'Manually routed fill stitching', type='toggle', inverse=True, default=True)
def auto_fill(self):
return self.get_boolean_param('auto_fill', True)
@property
@param('angle', 'Angle of lines of stitches', unit='deg', type='float')
@cache
def angle(self):
return math.radians(self.get_float_param('angle', 0))
@ -198,18 +246,22 @@ class Fill(EmbroideryElement):
return self.get_style("fill")
@property
@param('flip', 'Flip fill (start right-to-left)', type='boolean')
def flip(self):
return self.get_boolean_param("flip", False)
@property
@param('row_spacing_mm', 'Spacing between rows', unit='mm', type='float')
def row_spacing(self):
return self.get_float_param("row_spacing_mm")
@property
@param('max_stitch_length_mm', 'Maximum fill stitch length', unit='mm', type='float')
def max_stitch_length(self):
return self.get_float_param("max_stitch_length_mm")
@property
@param('staggers', 'Stagger rows this many times before repeating', type='int')
def staggers(self):
return self.get_int_param("staggers", 4)
@ -478,6 +530,11 @@ class Fill(EmbroideryElement):
class AutoFill(Fill):
@property
@param('auto_fill', 'Automatically routed fill stitching', type='toggle', default=True)
def auto_fill(self):
return self.get_boolean_param('auto_fill', True)
@property
@cache
def outline(self):
@ -493,14 +550,17 @@ class AutoFill(Fill):
return False
@property
@param('running_stitch_length_mm', 'Running stitch length (traversal between sections)', unit='mm', type='float')
def running_stitch_length(self):
return self.get_float_param("running_stitch_length_mm")
@property
@param('fill_underlay', 'Underlay', type='toggle', group='AutoFill Underlay')
def fill_underlay(self):
return self.get_boolean_param("fill_underlay")
@property
@param('fill_underlay_angle', 'Fill angle (default: fill angle + 90 deg)', unit='deg', group='AutoFill Underlay', type='float')
@cache
def fill_underlay_angle(self):
underlay_angle = self.get_float_param("fill_underlay_angle")
@ -511,11 +571,13 @@ class AutoFill(Fill):
return self.angle + math.pi / 2.0
@property
@param('fill_underlay_row_spacing_mm', 'Row spacing (default: 3x fill row spacing)', unit='mm', group='AutoFill Underlay', type='float')
@cache
def fill_underlay_row_spacing(self):
return self.get_float_param("fill_underlay_row_spacing_mm") or self.row_spacing * 3
@property
@param('fill_underlay_max_stitch_length_mm', 'Max stitch length', unit='mm', group='AutoFill Underlay', type='float')
@cache
def fill_underlay_max_stitch_length(self):
return self.get_float_param("fill_underlay_max_stitch_length_mm" or self.max_stitch_length)
@ -607,7 +669,7 @@ class AutoFill(Fill):
return self.section_to_patch(section, angle, row_spacing, max_stitch_length)
def auto_fill(self, angle, row_spacing, max_stitch_length, starting_point=None):
def do_auto_fill(self, angle, row_spacing, max_stitch_length, starting_point=None):
rows_of_segments = self.intersect_region_with_grating(angle, row_spacing)
sections = self.pull_runs(rows_of_segments)
@ -637,15 +699,19 @@ class AutoFill(Fill):
last_stitch = last_patch.stitches[-1]
if self.fill_underlay:
patches.extend(self.auto_fill(self.fill_underlay_angle, self.fill_underlay_row_spacing, self.fill_underlay_max_stitch_length, last_stitch))
patches.extend(self.do_auto_fill(self.fill_underlay_angle, self.fill_underlay_row_spacing, self.fill_underlay_max_stitch_length, last_stitch))
last_stitch = patches[-1].stitches[-1]
patches.extend(self.auto_fill(self.angle, self.row_spacing, self.max_stitch_length, last_stitch))
patches.extend(self.do_auto_fill(self.angle, self.row_spacing, self.max_stitch_length, last_stitch))
return patches
class Stroke(EmbroideryElement):
@property
@param('satin_column', 'Satin along paths', type='toggle', inverse=True)
def satin_column(self):
return self.get_boolean_param("satin_column")
@property
def color(self):
@ -666,15 +732,18 @@ class Stroke(EmbroideryElement):
return self.get_style("stroke-dasharray") is not None
@property
@param('running_stitch_length_mm', 'Running stitch length', unit='mm', type='float')
def running_stitch_length(self):
return self.get_float_param("running_stitch_length_mm")
@property
@param('zigzag_spacing_mm', 'Zig-zag spacing (peak-to-peak)', unit='mm', type='float')
@cache
def zigzag_spacing(self):
return self.get_float_param("zigzag_spacing_mm")
@property
@param('repeats', 'Repeats', type='int')
def repeats(self):
return self.get_int_param("repeats", 1)
@ -751,25 +820,26 @@ class Stroke(EmbroideryElement):
class SatinColumn(EmbroideryElement):
def __init__(self, *args, **kwargs):
super(SatinColumn, self).__init__(*args, **kwargs)
self.csp = self.parse_path()
self.flattened_beziers = self.get_flattened_paths()
# print >> dbg, "flattened beziers", self.flattened_beziers
@property
@param('satin_column', 'Custom satin column', type='toggle')
def satin_column(self):
return self.get_boolean_param("satin_column")
@property
def color(self):
return self.get_style("stroke")
@property
@param('zigzag_spacing_mm', 'Zig-zag spacing (peak-to-peak)', unit='mm', type='float')
def zigzag_spacing(self):
# peak-to-peak distance between zigzags
return self.get_float_param("zigzag_spacing_mm")
@property
@param('pull_compensation_mm', 'Pull compensation', unit='mm', type='float')
def pull_compensation(self):
# In satin stitch, the stitches have a tendency to pull together and
# narrow the entire column. We can compensate for this by stitching
@ -777,42 +847,50 @@ class SatinColumn(EmbroideryElement):
return self.get_float_param("pull_compensation_mm", 0)
@property
@param('contour_underlay', 'Contour underlay', type='toggle', group='Contour Underlay')
def contour_underlay(self):
# "Contour underlay" is stitching just inside the rectangular shape
# of the satin column; that is, up one side and down the other.
return self.get_boolean_param("contour_underlay")
@property
@param('contour_underlay_stitch_length_mm', 'Stitch length', unit='mm', group='Contour Underlay', type='float')
def contour_underlay_stitch_length(self):
# use "contour_underlay_stitch_length", or, if not set, default to "stitch_length"
return self.get_float_param("contour_underlay_stitch_length_mm") or self.get_float_param("running_stitch_length_mm")
@property
@param('contour_underlay_inset_mm', 'Contour underlay inset amount', unit='mm', group='Contour Underlay', type='float')
def contour_underlay_inset(self):
# how far inside the edge of the column to stitch the underlay
return self.get_float_param("contour_underlay_inset_mm", 0.4)
@property
@param('center_walk_underlay', 'Center-walk underlay', type='toggle', group='Center-Walk Underlay')
def center_walk_underlay(self):
# "Center walk underlay" is stitching down and back in the centerline
# between the two sides of the satin column.
return self.get_boolean_param("center_walk_underlay")
@property
@param('center_walk_underlay_stitch_length_mm', 'Stitch length', unit='mm', group='Center-Walk Underlay', type='float')
def center_walk_underlay_stitch_length(self):
# use "center_walk_underlay_stitch_length", or, if not set, default to "stitch_length"
return self.get_float_param("center_walk_underlay_stitch_length_mm") or self.get_float_param("running_stitch_length_mm")
@property
@param('zigzag_underlay', 'Zig-zag underlay', type='toggle', group='Zig-zag Underlay')
def zigzag_underlay(self):
return self.get_boolean_param("zigzag_underlay")
@property
@param('zigzag_underlay_spacing_mm', 'Zig-Zag spacing (peak-to-peak)', unit='mm', group='Zig-zag Underlay', type='float')
def zigzag_underlay_spacing(self):
# peak-to-peak distance between zigzags in zigzag underlay
return self.get_float_param("zigzag_underlay_spacing_mm", 1)
@property
@param('zigzag_underlay_inset', 'Inset amount (default: half of contour underlay inset)', unit='mm', group='Zig-zag Underlay', type='float')
def zigzag_underlay_inset(self):
# how far in from the edge of the satin the points in the zigzags
# should be
@ -823,7 +901,14 @@ class SatinColumn(EmbroideryElement):
# the edges of the satin column.
return self.get_float_param("zigzag_underlay_inset_mm") or self.contour_underlay_inset / 2.0
def get_flattened_paths(self):
@property
@cache
def csp(self):
return self.parse_path()
@property
@cache
def flattened_beziers(self):
# Given a pair of paths made up of bezier segments, flatten
# each individual bezier segment into line segments that approximate
# the curves. Retain the divisions between beziers -- we'll use those
@ -1088,8 +1173,48 @@ class SatinColumn(EmbroideryElement):
return patches
class Patch:
def detect_classes(node):
element = EmbroideryElement(node)
if element.get_boolean_param("satin_column"):
return [SatinColumn]
else:
classes = []
if element.get_style("fill"):
if element.get_boolean_param("auto_fill", True):
classes.append(AutoFill)
else:
classes.append(Fill)
if element.get_style("stroke"):
classes.append(Stroke)
if element.get_boolean_param("stroke_first", False):
classes.reverse()
return classes
def descendants(node):
nodes = []
element = EmbroideryElement(node)
if element.has_style('display') and element.get_style('display') is None:
return []
if node.tag == SVG_DEFS_TAG:
return []
for child in node:
nodes.extend(descendants(child))
if node.tag == SVG_PATH_TAG:
nodes.append(node)
return nodes
class Patch:
def __init__(self, color=None, stitches=None):
self.color = color
self.stitches = stitches or []
@ -1227,41 +1352,11 @@ class Embroider(inkex.Effect):
def handle_node(self, node):
print >> dbg, "handling node", node.get('id'), node.get('tag')
element = EmbroideryElement(node, self.options)
if element.has_style('display') and element.get_style('display') is None:
return
if node.tag == SVG_DEFS_TAG:
return
for child in node:
self.handle_node(child)
if node.tag != SVG_PATH_TAG:
return
# dbg.write("Node: %s\n"%str((id, etree.tostring(node, pretty_print=True))))
if element.get_boolean_param("satin_column"):
self.elements.append(SatinColumn(node, self.options))
else:
elements = []
if element.get_style("fill"):
if element.get_boolean_param("auto_fill", True):
elements.append(AutoFill(node, self.options))
else:
elements.append(Fill(node, self.options))
if element.get_style("stroke"):
elements.append(Stroke(node, self.options))
if element.get_boolean_param("stroke_first", False):
elements.reverse()
self.elements.extend(elements)
nodes = descendants(node)
for node in nodes:
classes = detect_classes(node)
print >> dbg, "classes:", classes
self.elements.extend(cls(node, self.options) for cls in classes)
def get_output_path(self):
svg_filename = self.document.getroot().get(inkex.addNS('docname', 'sodipodi'))

Plik diff jest za duży Load Diff

Wyświetl plik

@ -4,28 +4,6 @@
<id>jonh.embroider.params</id>
<dependency type="executable" location="extensions">embroider_params.py</dependency>
<dependency type="executable" location="extensions">inkex.py</dependency>
<param name="zigzag_spacing_mm" type="string" _gui-text="Zigzag spacing (mm)"></param>
<param name="running_stitch_length_mm" type="string" _gui-text="Running stitch length (mm)"></param>
<param name="row_spacing_mm" type="string" _gui-text="Row spacing (mm)"></param>
<param name="max_stitch_length_mm" type="string" _gui-text="Maximum stitch length for fills (mm)"></param>
<param name="repeats" type="string" _gui-text="Repeats (stroke only)"></param>
<param name="angle" type="string" _gui-text="Angle for lines in fills (degrees)"></param>
<param name="pull_compensation_mm" type="string" _gui-text="Pull compensation for satin column (mm)"></param>
<param name="flip" type="string" _gui-text="Flip fill? (yes/no)"></param>
<param name="satin_column" type="string" _gui-text="Satin Column? (yes/no)"></param>
<param name="contour_underlay" type="string" _gui-text="Edge-walk underlay for satin Column? (yes/no)"></param>
<param name="contour_underlay_inset_mm" type="string" _gui-text="Inset for satin colum underlay (mm)"></param>
<param name="contour_underlay_stitch_length_mm" type="string" _gui-text="Stitch length (mm) for contour underlay (defaults to running stitch length)"></param>
<param name="center_walk_underlay" type="string" _gui-text="Satin center walk underlay? (yes/no)"></param>
<param name="center_walk_underlay_stitch_length_mm" type="string" _gui-text="Stitch length (mm) for center walk underlay (defaults to running stitch length)"></param>
<param name="zigzag_underlay" type="string" _gui-text="Zig-Zag underlay for satin Column? (yes/no)"></param>
<param name="zigzag_spacing_mm" type="string" _gui-text="Zigzag underlay spacing (mm)"></param>
<param name="zigzag_underlay_inset_mm" type="string" _gui-text="Inset for zigzag underlay (mm)"></param>
<param name="stroke_first" type="string" _gui-text="Stitch stroke before fill? (yes/no)"></param>
<param name="fill_underlay" type="string" _gui-text="Underlay for fill stitch? (yes/no)"></param>
<param name="fill_underlay_angle" type="string" _gui-text="Angle for lines in fill underlay (degrees)"></param>
<param name="fill_underlay_row_spacing_mm" type="string" _gui-text="Row spacing for fill underlay (mm)"></param>
<param name="fill_underlay_max_stitch_length_mm" type="string" _gui-text="Maximum stitch length for fill underlay (mm)"></param>
<effect>
<object-type>all</object-type>
<effects-menu>

Wyświetl plik

@ -1,59 +1,573 @@
#!/usr/bin/python
#
# Set embroidery parameter attributes on all selected objects. If an option
# value is blank, the parameter is created as blank on all objects that don't
# already have it. If an option value is given, any existing node parameter
# values are overwritten on all selected objects.
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
import sys
sys.path.append("/usr/share/inkscape/extensions")
import os
import sys
import json
from cStringIO import StringIO
import wx
from wx.lib.scrolledpanel import ScrolledPanel
from collections import defaultdict
import inkex
from embroider import Param, EmbroideryElement, Fill, AutoFill, Stroke, SatinColumn, descendants
from functools import partial
from itertools import groupby
class EmbroiderParams(inkex.Effect):
def presets_path():
try:
import appdirs
config_path = appdirs.user_config_dir('inkscape-embroidery')
except ImportError:
config_path = os.path.expanduser('~/.inkscape-embroidery')
if not os.path.exists(config_path):
os.makedirs(config_path)
return os.path.join(config_path, 'presets.json')
def load_presets():
try:
with open(presets_path(), 'r') as presets:
presets = json.load(presets)
return presets
except:
return {}
def save_presets(presets):
with open(presets_path(), 'w') as presets_file:
json.dump(presets, presets_file)
def load_preset(name):
return load_presets().get(name)
def save_preset(name, data):
presets = load_presets()
presets[name] = data
save_presets(presets)
def delete_preset(name):
presets = load_presets()
presets.pop(name, None)
save_presets(presets)
def confirm_dialog(parent, question, caption = 'inkscape-embroidery'):
dlg = wx.MessageDialog(parent, question, caption, wx.YES_NO | wx.ICON_QUESTION)
result = dlg.ShowModal() == wx.ID_YES
dlg.Destroy()
return result
def info_dialog(parent, message, caption = 'inkscape-embroidery'):
dlg = wx.MessageDialog(parent, message, caption, wx.OK | wx.ICON_INFORMATION)
dlg.ShowModal()
dlg.Destroy()
class ParamsTab(ScrolledPanel):
def __init__(self, *args, **kwargs):
inkex.Effect.__init__(self)
self.params = kwargs.pop('params', [])
self.name = kwargs.pop('name', None)
self.nodes = kwargs.pop('nodes')
kwargs["style"] = wx.TAB_TRAVERSAL
ScrolledPanel.__init__(self, *args, **kwargs)
self.SetupScrolling()
self.params = ["zigzag_spacing_mm",
"running_stitch_length_mm",
"row_spacing_mm",
"max_stitch_length_mm",
"repeats",
"angle",
"flip",
"satin_column",
"stroke_first",
"pull_compensation_mm",
"contour_underlay",
"contour_underlay_inset_mm",
"contour_underlay_stitch_length_mm",
"center_walk_underlay",
"center_walk_underlay_stitch_length_mm",
"zigzag_underlay",
"zigzag_underlay_inset_mm",
"fill_underlay",
"fill_underlay_angle",
"fill_underlay_row_spacing_mm",
"fill_underlay_max_stitch_length_mm",
]
self.changed_inputs = set()
self.dependent_tabs = []
self.parent_tab = None
self.param_inputs = {}
self.paired_tab = None
self.disable_notify_pair = False
toggles = [param for param in self.params if param.type == 'toggle']
if toggles:
self.toggle = toggles[0]
self.params.remove(self.toggle)
self.toggle_checkbox = wx.CheckBox(self, label=self.toggle.description)
value = any(self.toggle.values)
if self.toggle.inverse:
value = not value
self.toggle_checkbox.SetValue(value)
self.toggle_checkbox.Bind(wx.EVT_CHECKBOX, self.update_toggle_state)
self.toggle_checkbox.Bind(wx.EVT_CHECKBOX, self.changed)
self.param_inputs[self.toggle.name] = self.toggle_checkbox
else:
self.toggle = None
self.settings_grid = wx.FlexGridSizer(rows=0, cols=3, hgap=10, vgap=10)
self.settings_grid.AddGrowableCol(0, 1)
self.settings_grid.SetFlexibleDirection(wx.HORIZONTAL)
self.__set_properties()
self.__do_layout()
if self.toggle:
self.update_toggle_state()
# end wxGlade
def pair(self, tab):
# print self.name, "paired with", tab.name
self.paired_tab = tab
self.update_description()
def add_dependent_tab(self, tab):
self.dependent_tabs.append(tab)
self.update_description()
def set_parent_tab(self, tab):
self.parent_tab = tab
def update_toggle_state(self, event=None, notify_pair=True):
enable = self.toggle_checkbox.IsChecked()
# print self.name, "update_toggle_state", enable
for child in self.settings_grid.GetChildren():
widget = child.GetWindow()
if widget:
child.GetWindow().Enable(enable)
if notify_pair and self.paired_tab:
self.paired_tab.pair_changed(self.toggle_checkbox.IsChecked())
for tab in self.dependent_tabs:
tab.dependent_enable(enable)
if event:
event.Skip()
def pair_changed(self, value):
# print self.name, "pair_changed", value
new_value = not value
if self.toggle_checkbox.IsChecked() != new_value:
self.set_toggle_state(not value)
self.toggle_checkbox.changed = True
self.update_toggle_state(notify_pair=False)
def dependent_enable(self, enable):
if enable:
self.toggle_checkbox.Enable()
else:
self.set_toggle_state(False)
self.toggle_checkbox.Disable()
self.toggle_checkbox.changed = True
self.update_toggle_state()
def set_toggle_state(self, value):
self.toggle_checkbox.SetValue(value)
def get_values(self):
values = {}
if self.toggle:
checked = self.toggle_checkbox.IsChecked()
if self.toggle_checkbox in self.changed_inputs:
values[self.toggle.name] = checked
if not checked:
# Ignore params on this tab if the toggle is unchecked,
# because they're grayed out anyway.
return values
for name, input in self.param_inputs.iteritems():
if input in self.changed_inputs:
values[name] = input.GetValue()
return values
def apply(self):
values = self.get_values()
for node in self.nodes:
print >> sys.stderr, node.id, values
for name, value in values.iteritems():
node.set_param(name, value)
def changed(self, event):
self.changed_inputs.add(event.GetEventObject())
event.Skip()
def load_preset(self, preset):
preset_data = preset.get(self.name, {})
for name, value in preset_data.iteritems():
if name in self.param_inputs:
self.param_inputs[name].SetValue(value)
self.changed_inputs.add(self.param_inputs[name])
self.update_toggle_state()
def save_preset(self, storage):
preset = storage[self.name] = {}
for name, input in self.param_inputs.iteritems():
preset[name] = input.GetValue()
def update_description(self):
description = "These settings will be applied to %d object%s." % \
(len(self.nodes), "s" if len(self.nodes) != 1 else "")
if any(len(param.values) > 1 for param in self.params):
description += "\n • Some settings had different values across objects. Select a value from the dropdown or enter a new one."
if self.dependent_tabs:
description += "\n • Disabling this tab will disable the following %d tabs." % len(self.dependent_tabs)
if self.paired_tab:
description += "\n • Enabling this tab will disable %s and vice-versa." % self.paired_tab.name
self.description_text = description
def resized(self, event):
if not hasattr(self, 'rewrap_timer'):
self.rewrap_timer = wx.Timer()
self.rewrap_timer.Bind(wx.EVT_TIMER, self.rewrap)
# If we try to rewrap every time we get EVT_SIZE then a resize is
# extremely slow.
self.rewrap_timer.Start(50, oneShot=True)
event.Skip()
def rewrap(self, event=None):
self.description.SetLabel(self.description_text)
self.description.Wrap(self.GetSize().x - 20)
self.description_container.Layout()
if event:
event.Skip()
def __set_properties(self):
# begin wxGlade: SatinPane.__set_properties
# end wxGlade
pass
def __do_layout(self):
# just to add space around the settings
box = wx.BoxSizer(wx.VERTICAL)
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, style=wx.TE_WORDWRAP)
self.update_description()
self.description.SetLabel(self.description_text)
self.description_container = box
self.Bind(wx.EVT_SIZE, self.resized)
sizer.Add(self.description, proportion=0, flag=wx.EXPAND|wx.ALL, border=5)
box.Add(sizer, proportion=0, flag=wx.ALL, border=5)
if self.toggle:
box.Add(self.toggle_checkbox, proportion=0, flag=wx.BOTTOM, border=10)
for param in self.params:
self.OptionParser.add_option("--%s" % param, default="")
self.settings_grid.Add(wx.StaticText(self, label=param.description), proportion=1, flag=wx.EXPAND|wx.RIGHT, border=40)
if param.type == 'boolean':
values = list(set(param.values))
if len(values) > 1:
input = wx.CheckBox(self, style=wx.CHK_3STATE)
input.Set3StateValue(wx.CHK_UNDETERMINED)
elif values:
input = wx.CheckBox(self)
input.SetValue(values[0])
input.Bind(wx.EVT_CHECKBOX, self.changed)
elif len(param.values) > 1:
input = wx.ComboBox(self, wx.ID_ANY, choices=param.values, style=wx.CB_DROPDOWN | wx.CB_SORT)
input.Bind(wx.EVT_COMBOBOX, self.changed)
input.Bind(wx.EVT_TEXT, self.changed)
else:
value = param.values[0] if param.values else ""
input = wx.TextCtrl(self, wx.ID_ANY, value=value)
input.Bind(wx.EVT_TEXT, self.changed)
self.param_inputs[param.name] = input
self.settings_grid.Add(input, proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
self.settings_grid.Add(wx.StaticText(self, label=param.unit or ""), proportion=1, flag=wx.ALIGN_CENTER_VERTICAL)
box.Add(self.settings_grid, proportion=1, flag=wx.ALL, border=10)
self.SetSizer(box)
self.Layout()
# end of class SatinPane
class SettingsFrame(wx.Frame):
def __init__(self, *args, **kwargs):
# begin wxGlade: MyFrame.__init__
self.tabs_factory = kwargs.pop('tabs_factory', [])
wx.Frame.__init__(self, None, wx.ID_ANY,
"Embroidery Params"
)
self.notebook = wx.Notebook(self, wx.ID_ANY)
self.tabs = self.tabs_factory(self.notebook)
self.presets_box = wx.StaticBox(self, wx.ID_ANY, label="Presets")
self.preset_chooser = wx.ComboBox(self, wx.ID_ANY, style=wx.CB_SORT)
self.update_preset_list()
self.load_preset_button = wx.Button(self, wx.ID_ANY, "Load")
self.load_preset_button.Bind(wx.EVT_BUTTON, self.load_preset)
self.add_preset_button = wx.Button(self, wx.ID_ANY, "Add")
self.add_preset_button.Bind(wx.EVT_BUTTON, self.add_preset)
self.overwrite_preset_button = wx.Button(self, wx.ID_ANY, "Overwrite")
self.overwrite_preset_button.Bind(wx.EVT_BUTTON, self.overwrite_preset)
self.delete_preset_button = wx.Button(self, wx.ID_ANY, "Delete")
self.delete_preset_button.Bind(wx.EVT_BUTTON, self.delete_preset)
self.cancel_button = wx.Button(self, wx.ID_ANY, "Cancel")
self.cancel_button.Bind(wx.EVT_BUTTON, self.close)
self.apply_button = wx.Button(self, wx.ID_ANY, "Apply and Quit")
self.apply_button.Bind(wx.EVT_BUTTON, self.apply)
self.__set_properties()
self.__do_layout()
# end wxGlade
def update_preset_list(self):
self.preset_chooser.SetItems(load_presets().keys())
def get_preset_name(self):
preset_name = self.preset_chooser.GetValue().strip()
if preset_name:
return preset_name
else:
info_dialog(self, "Please enter or select a preset name first.", caption='Preset')
return
def check_and_load_preset(self, preset_name):
preset = load_preset(preset_name)
if not preset:
info_dialog(self, 'Preset "%s" not found.' % preset_name, caption='Preset')
return preset
def get_preset_data(self):
preset = {}
current_tab = self.tabs[self.notebook.GetSelection()]
while current_tab.parent_tab:
current_tab = current_tab.parent_tab
tabs = [current_tab]
if current_tab.paired_tab:
tabs.append(current_tab.paired_tab)
tabs.extend(current_tab.dependent_tabs)
for tab in tabs:
tab.save_preset(preset)
return preset
def add_preset(self, event, overwrite=False):
preset_name = self.get_preset_name()
if not preset_name:
return
if not overwrite and load_preset(preset_name):
info_dialog(self, 'Preset "%s" already exists. Please use another name or press "Overwrite"' % preset_name, caption='Preset')
save_preset(preset_name, self.get_preset_data())
self.update_preset_list()
event.Skip()
def overwrite_preset(self, event):
self.add_preset(event, overwrite=True)
def load_preset(self, event):
preset_name = self.get_preset_name()
if not preset_name:
return
preset = self.check_and_load_preset(preset_name)
if not preset:
return
for tab in self.tabs:
tab.load_preset(preset)
event.Skip()
def delete_preset(self, event):
preset_name = self.get_preset_name()
if not preset_name:
return
preset = self.check_and_load_preset(preset_name)
if not preset:
return
delete_preset(preset_name)
self.update_preset_list()
self.preset_chooser.SetValue("")
event.Skip()
def apply(self, event):
for tab in self.tabs:
tab.apply()
self.Close()
def close(self, event):
self.Close()
def __set_properties(self):
# begin wxGlade: MyFrame.__set_properties
self.SetTitle("frame_1")
self.notebook.SetMinSize((800, 400))
self.preset_chooser.SetSelection(-1)
# end wxGlade
def __do_layout(self):
# begin wxGlade: MyFrame.__do_layout
sizer_1 = wx.BoxSizer(wx.VERTICAL)
#self.sizer_3_staticbox.Lower()
sizer_2 = wx.StaticBoxSizer(self.presets_box, wx.HORIZONTAL)
sizer_3 = wx.BoxSizer(wx.HORIZONTAL)
for tab in self.tabs:
self.notebook.AddPage(tab, tab.name)
sizer_1.Add(self.notebook, 1, wx.EXPAND|wx.LEFT|wx.TOP|wx.RIGHT, 10)
sizer_2.Add(self.preset_chooser, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
sizer_2.Add(self.load_preset_button, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
sizer_2.Add(self.add_preset_button, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
sizer_2.Add(self.overwrite_preset_button, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
sizer_2.Add(self.delete_preset_button, 0, wx.ALIGN_CENTER_VERTICAL|wx.ALL, 5)
sizer_1.Add(sizer_2, 0, flag=wx.EXPAND|wx.ALL, border=10)
sizer_3.Add(self.cancel_button, 0, wx.ALIGN_RIGHT|wx.RIGHT, 5)
sizer_3.Add(self.apply_button, 0, wx.ALIGN_RIGHT|wx.RIGHT|wx.BOTTOM, 5)
sizer_1.Add(sizer_3, 0, wx.ALIGN_RIGHT, 0)
self.SetSizer(sizer_1)
sizer_1.Fit(self)
self.Layout()
# end wxGlade
class EmbroiderParams(inkex.Effect):
def get_nodes(self):
if self.selected:
nodes = []
for node in self.document.getroot().iter():
if node.get("id") in self.selected:
nodes.extend(descendants(node))
else:
nodes = descendants(self.document.getroot())
return nodes
def embroidery_classes(self, node):
element = EmbroideryElement(node)
classes = []
if element.get_style("fill"):
classes.append(AutoFill)
classes.append(Fill)
elif element.get_style("stroke"):
classes.append(Stroke)
if element.get_style("stroke-dasharray") is None:
classes.append(SatinColumn)
return classes
def get_nodes_by_class(self):
nodes = self.get_nodes()
nodes_by_class = defaultdict(list)
for node in self.get_nodes():
for cls in self.embroidery_classes(node):
nodes_by_class[cls].append(node)
return sorted(nodes_by_class.items(), key=lambda (cls, nodes): cls.__name__)
def get_values(self, param, nodes):
getter = 'get_param'
if param.type in ('toggle', 'boolean'):
getter = 'get_boolean_param'
elif param.type:
getter = 'get_%s_param' % param.type
values = filter(lambda item: item is not None,
(getattr(node, getter)(param.name, param.default) for node in nodes))
if param.type in ('int', 'float'):
values = [str(value) for value in values]
return values
def group_params(self, params):
def by_group(param):
return param.group
return groupby(sorted(params, key=by_group), by_group)
def create_tabs(self, parent):
tabs = []
for cls, nodes in self.get_nodes_by_class():
nodes = [cls(node) for node in nodes]
params = cls.get_params()
for param in params:
param.values = self.get_values(param, nodes)
parent_tab = None
new_tabs = []
for group, params in self.group_params(params):
tab = ParamsTab(parent, id=wx.ID_ANY, name=group or cls.__name__, params=list(params), nodes=nodes)
new_tabs.append(tab)
if group is None:
parent_tab = tab
for tab in new_tabs:
if tab != parent_tab:
parent_tab.add_dependent_tab(tab)
tab.set_parent_tab(parent_tab)
tabs.extend(new_tabs)
for tab in tabs:
if tab.toggle and tab.toggle.inverse:
for other_tab in tabs:
if other_tab != tab and other_tab.toggle.name == tab.toggle.name:
tab.pair(other_tab)
other_tab.pair(tab)
return tabs
def effect(self):
for node in self.selected.itervalues():
for param in self.params:
value = getattr(self.options, param).strip()
param = "embroider_" + param
app = wx.App()
frame = SettingsFrame(tabs_factory=self.create_tabs)
frame.Show()
app.MainLoop()
if node.get(param) is not None and not value:
# only overwrite existing params if they gave a value
continue
else:
node.set(param, value)
# end of class MyFrame
if __name__ == "__main__":
# GTK likes to spam stderr, which inkscape will show in a dialog.
null = open('/dev/null', 'w')
stderr_dup = os.dup(sys.stderr.fileno())
os.dup2(null.fileno(), 2)
stderr_backup = sys.stderr
sys.stderr = StringIO()
if __name__ == '__main__':
e = EmbroiderParams()
e.affect()
os.dup2(stderr_dup, 2)
stderr_backup.write(sys.stderr.getvalue())
sys.stderr = stderr_backup