support svg objects (#643)

pull/690/head^2
Kaalleen 2020-05-16 23:01:00 +02:00 zatwierdzone przez GitHub
rodzic 4e95033241
commit a308db7ae1
Nie znaleziono w bazie danych klucza dla tego podpisu
ID klucza GPG: 4AEE18F83AFDEB23
19 zmienionych plików z 543 dodań i 100 usunięć

Wyświetl plik

@ -240,6 +240,14 @@ def is_command(node):
return CONNECTION_START in node.attrib or CONNECTION_END in node.attrib
def is_command_symbol(node):
symbol = None
xlink = node.get(XLINK_HREF, "")
if xlink.startswith("#inkstitch_"):
symbol = node.get(XLINK_HREF)[11:]
return symbol in COMMANDS
@cache
def symbols_path():
return os.path.join(get_bundled_dir("symbols"), "inkstitch.svg")
@ -280,7 +288,7 @@ def add_group(document, node, command):
node.getparent(),
SVG_GROUP_TAG,
{
"id": generate_unique_id(document, "group"),
"id": generate_unique_id(document, "command_group"),
INKSCAPE_LABEL: _("Ink/Stitch Command") + ": %s" % get_command_description(command),
"transform": get_correction_transform(node)
})
@ -298,7 +306,7 @@ def add_connector(document, symbol, element):
path = inkex.etree.Element(SVG_PATH_TAG,
{
"id": generate_unique_id(document, "connector"),
"id": generate_unique_id(document, "command_connector"),
"d": "M %s,%s %s,%s" % (start_pos[0], start_pos[1], end_pos.x, end_pos.y),
"style": "stroke:#000000;stroke-width:1px;stroke-opacity:0.5;fill:none;",
CONNECTION_START: "#%s" % symbol.get('id'),
@ -315,7 +323,7 @@ def add_connector(document, symbol, element):
def add_symbol(document, group, command, pos):
return inkex.etree.SubElement(group, SVG_USE_TAG,
{
"id": generate_unique_id(document, "use"),
"id": generate_unique_id(document, "command_use"),
XLINK_HREF: "#inkstitch_%s" % command,
"height": "100%",
"width": "100%",

Wyświetl plik

@ -1,7 +1,10 @@
from auto_fill import AutoFill
from clone import Clone
from element import EmbroideryElement
from fill import Fill
from image import ImageObject
from polyline import Polyline
from satin_column import SatinColumn
from stroke import Stroke
from text import TextObject
from utils import node_to_elements, nodes_to_elements

Wyświetl plik

@ -0,0 +1,169 @@
from copy import copy
from math import atan, degrees
from simpletransform import (applyTransformToNode, applyTransformToPoint,
computeBBox, parseTransform)
from ..commands import is_command, is_command_symbol
from ..i18n import _
from ..svg.path import get_node_transform
from ..svg.svg import find_elements
from ..svg.tags import (EMBROIDERABLE_TAGS, INKSTITCH_ATTRIBS,
SVG_POLYLINE_TAG, SVG_USE_TAG, XLINK_HREF)
from ..utils import cache
from .auto_fill import AutoFill
from .element import EmbroideryElement, param
from .fill import Fill
from .polyline import Polyline
from .satin_column import SatinColumn
from .stroke import Stroke
from .validation import ObjectTypeWarning, ValidationWarning
class CloneWarning(ValidationWarning):
name = _("Clone Object")
description = _("There are one or more clone objects in this document. "
"Ink/Stitch can work with single clones, but you are limited to set a very few parameters. ")
steps_to_solve = [
_("If you want to convert the clone into a real element, follow these steps:"),
_("* Select the clone"),
_("* Run: Edit > Clone > Unlink Clone (Alt+Shift+D)")
]
class CloneSourceWarning(ObjectTypeWarning):
name = _("Clone is not embroiderable")
description = _("There are one ore more clone objects in this document. A clone must be a direct child of an embroiderable element. "
"Ink/Stitch cannot embroider clones of groups or other not embroiderable elements (text or image).")
steps_to_solve = [
_("Convert the clone into a real element:"),
_("* Select the clone."),
_("* Run: Edit > Clone > Unlink Clone (Alt+Shift+D)")
]
class Clone(EmbroideryElement):
# A clone embroidery element is linked to an embroiderable element.
# It will be ignored if the source element is not a direct child of the xlink attribute.
element_name = "Clone"
def __init__(self, *args, **kwargs):
super(Clone, self).__init__(*args, **kwargs)
@property
@param('clone', _("Clone"), type='toggle', inverse=False, default=True)
def clone(self):
return self.get_boolean_param("clone")
@property
@param('angle',
_('Custom fill angle'),
tooltip=_("This setting will apply a custom fill angle for the clone."),
unit='deg',
type='float')
@cache
def clone_fill_angle(self):
return self.get_float_param('angle', 0)
def clone_to_element(self, node):
# we need to determine if the source element is polyline, stroke, fill or satin
element = EmbroideryElement(node)
if node.tag == SVG_POLYLINE_TAG:
return [Polyline(node)]
elif element.get_boolean_param("satin_column") and element.get_style("stroke"):
return [SatinColumn(node)]
else:
elements = []
if element.get_style("fill", 'black') and not element.get_style('fill-opacity', 1) == "0":
if element.get_boolean_param("auto_fill", True):
elements.append(AutoFill(node))
else:
elements.append(Fill(node))
if element.get_style("stroke"):
if not is_command(element.node):
elements.append(Stroke(node))
if element.get_boolean_param("stroke_first", False):
elements.reverse()
return elements
def to_patches(self, last_patch=None):
patches = []
source_node = get_clone_source(self.node)
if source_node.tag not in EMBROIDERABLE_TAGS:
return []
clone = copy(source_node)
# set id
clone_id = 'clone__%s__%s' % (self.node.get('id', ''), clone.get('id', ''))
clone.set('id', clone_id)
# apply transform
transform = get_node_transform(self.node)
applyTransformToNode(transform, clone)
# set fill angle. Use either
# a. a custom set fill angle
# b. calculated rotation for the cloned fill element to look exactly as it's source
param = INKSTITCH_ATTRIBS['angle']
if self.clone_fill_angle is not None:
angle = self.clone_fill_angle
else:
# clone angle
clone_mat = parseTransform(clone.get('transform', ''))
clone_angle = degrees(atan(-clone_mat[1][0]/clone_mat[1][1]))
# source node angle
source_mat = parseTransform(source_node.get('transform', ''))
source_angle = degrees(atan(-source_mat[1][0]/source_mat[1][1]))
# source node fill angle
source_fill_angle = source_node.get(param, 0)
angle = clone_angle + float(source_fill_angle) - source_angle
clone.set(param, str(angle))
elements = self.clone_to_element(clone)
for element in elements:
patches.extend(element.to_patches(last_patch))
return patches
def center(self, source_node):
xmin, xmax, ymin, ymax = computeBBox([source_node])
point = [(xmax-((xmax-xmin)/2)), (ymax-((ymax-ymin)/2))]
transform = get_node_transform(self.node)
applyTransformToPoint(transform, point)
return point
def validation_warnings(self):
source_node = get_clone_source(self.node)
if source_node.tag not in EMBROIDERABLE_TAGS:
point = self.center(source_node)
yield CloneSourceWarning(point)
else:
point = self.center(source_node)
yield CloneWarning(point)
def is_clone(node):
if node.tag == SVG_USE_TAG and node.get(XLINK_HREF) and not is_command_symbol(node):
return True
return False
def is_embroiderable_clone(node):
if is_clone(node) and get_clone_source(node).tag in EMBROIDERABLE_TAGS:
return True
return False
def get_clone_source(node):
source_id = node.get(XLINK_HREF)[1:]
xpath = ".//*[@id='%s']" % (source_id)
source_node = find_elements(node, xpath)[0]
return source_node

Wyświetl plik

@ -1,15 +1,18 @@
import sys
from copy import deepcopy
import cubicsuperpath
import tinycss2
import cubicsuperpath
from cspsubdiv import cspsubdiv
from ..commands import find_commands
from ..i18n import _
from ..svg import PIXELS_PER_MM, apply_transforms, convert_length, get_doc_size
from ..svg.tags import INKSCAPE_LABEL, INKSTITCH_ATTRIBS
from ..svg.tags import (INKSCAPE_LABEL, INKSTITCH_ATTRIBS, SVG_CIRCLE_TAG,
SVG_ELLIPSE_TAG, SVG_OBJECT_TAGS, SVG_RECT_TAG)
from ..utils import cache
from .svg_objects import circle_to_path, ellipse_to_path, rect_to_path
class Patch:
@ -181,6 +184,10 @@ class EmbroideryElement(object):
def stroke_scale(self):
svg = self.node.getroottree().getroot()
doc_width, doc_height = get_doc_size(svg)
# this is necessary for clones, since they are disconnected from the DOM
# it will result in a slighty wrong result for zig-zag stitches
if doc_width == 0:
return 1
viewbox = svg.get('viewBox', '0 0 %s %s' % (doc_width, doc_height))
viewbox = viewbox.strip().replace(',', ' ').split()
return doc_width / float(viewbox[2])
@ -236,7 +243,16 @@ class EmbroideryElement(object):
# In a path, each element in the 3-tuple is itself a tuple of (x, y).
# Tuples all the way down. Hasn't anyone heard of using classes?
d = self.node.get("d", "")
if self.node.tag in SVG_OBJECT_TAGS:
if self.node.tag == SVG_RECT_TAG:
d = rect_to_path(self.node)
elif self.node.tag == SVG_ELLIPSE_TAG:
d = ellipse_to_path(self.node)
elif self.node.tag == SVG_CIRCLE_TAG:
d = circle_to_path(self.node)
else:
d = self.node.get("d", "")
if not d:
self.fatal(_("Object %(id)s has an empty 'd' attribute. Please delete this object from your document.") % dict(id=self.node.get("id")))

Wyświetl plik

@ -27,9 +27,15 @@ class InvalidShapeError(ValidationError):
name = _("Border crosses itself")
description = _("Fill: Shape is not valid. This can happen if the border crosses over itself.")
steps_to_solve = [
_('* Path > Union (Ctrl++)'),
_('* Path > Break apart (Shift+Ctrl+K)'),
_('* (Optional) Recombine shapes with holes (Ctrl+K).')
_("1. Inkscape has a limit to how far it lets you zoom in. Sometimes there can be a little loop, "
"that's so small, you can't see it, but Ink/Stitch can. It's especially common for Inkscape's "
"Trace Bitmap to produce those tiny loops."),
_("* Delete the node"),
_("* Or try to adjust it's handles"),
_("2. If you can actually see a loop, run the following commands to seperate the crossing shapes:"),
_("* Path > Union (Ctrl++)"),
_("* Path > Break apart (Shift+Ctrl+K)"),
_("* (Optional) Recombine shapes with holes (Ctrl+K).")
]

Wyświetl plik

@ -0,0 +1,34 @@
from simpletransform import applyTransformToPoint
from ..i18n import _
from ..svg import get_node_transform
from .element import EmbroideryElement
from .validation import ObjectTypeWarning
class ImageTypeWarning(ObjectTypeWarning):
name = _("Image")
description = _("Ink/Stitch can't work with objects like images.")
steps_to_solve = [
_('* Convert your image into a path: Path > Trace Bitmap... (Shift+Alt+B) '
'(further steps might be required)'),
_('* Alternatively redraw the image with the pen (P) or bezier (B) tool')
]
class ImageObject(EmbroideryElement):
def center(self):
point = [float(self.node.get('x', 0)), float(self.node.get('y', 0))]
point = [(point[0]+(float(self.node.get('width', 0))/2)), (point[1]+(float(self.node.get('height', 0))/2))]
transform = get_node_transform(self.node)
applyTransformToPoint(transform, point)
return point
def validation_warnings(self):
yield ImageTypeWarning(self.center())
def to_patches(self, last_patch):
return []

Wyświetl plik

@ -3,12 +3,12 @@ from shapely import geometry as shgeo
from ..i18n import _
from ..utils import cache
from ..utils.geometry import Point
from .element import EmbroideryElement, Patch
from .element import EmbroideryElement, Patch, param
from .validation import ValidationWarning
class PolylineWarning(ValidationWarning):
name = _("Object is a PolyLine")
name = _("Polyline Object")
description = _("This object is an SVG PolyLine. Ink/Stitch can work with this shape, "
"but you can't edit it in Inkscape. Convert it to a manual stitch path "
"to allow editing.")
@ -32,6 +32,13 @@ class Polyline(EmbroideryElement):
# users use File -> Import to pull in existing designs they may have
# obtained, for example purchased fonts.
element_name = "Polyline"
@property
@param('polyline', _('Manual stitch along path'), type='toggle', inverse=True)
def satin_column(self):
return self.get_boolean_param("polyline")
@property
def points(self):
# example: "1,2 0,0 1.5,3 4,2"
@ -70,7 +77,7 @@ class Polyline(EmbroideryElement):
def color(self):
# EmbroiderModder2 likes to use the `stroke` property directly instead
# of CSS.
return self.get_style("stroke") or self.node.get("stroke")
return self.get_style("stroke", "#000000")
@property
def stitches(self):

Wyświetl plik

@ -0,0 +1,71 @@
def rect_to_path(node):
x = float(node.get('x', '0'))
y = float(node.get('y', '0'))
width = float(node.get('width', '0'))
height = float(node.get('height', '0'))
rx = None
ry = None
# rounded corners
# the following rules apply for radius calculations:
# if rx or ry is missing it has to take the value of the other one
# the radius cannot be bigger than half of the corresponding side
# (otherwise we receive an invalid path)
if node.get('rx') or node.get('ry'):
if node.get('rx'):
rx = float(node.get('rx', '0'))
ry = rx
if node.get('ry'):
ry = float(node.get('ry', '0'))
if not ry:
ry = rx
rx = min(width/2, rx)
ry = min(height/2, ry)
path = 'M %(startx)f,%(y)f ' \
'h %(width)f ' \
'q %(rx)f,0 %(rx)f,%(ry)f ' \
'v %(height)f ' \
'q 0,%(ry)f -%(rx)f,%(ry)f ' \
'h -%(width)f ' \
'q -%(rx)f,0 -%(rx)f,-%(ry)f ' \
'v -%(height)f ' \
'q 0,-%(ry)f %(rx)f,-%(ry)f ' \
'Z' \
% dict(startx=x+rx, x=x, y=y, width=width-(2*rx), height=height-(2*ry), rx=rx, ry=ry)
else:
path = "M %f,%f H %f V %f H %f Z" % (x, y, width+x, height+y, x)
return path
def ellipse_to_path(node):
rx = float(node.get('rx', "0")) or float(node.get('r', "0"))
ry = float(node.get('ry', "0")) or float(node.get('r', "0"))
cx = float(node.get('cx'))
cy = float(node.get('cy'))
path = 'M %(cx_r)f,%(cy)f' \
'C %(cx_r)f,%(cy_r)f %(cx)f,%(cy_r)f %(cx)f,%(cy_r)f ' \
'%(cxr)f,%(cy_r)f %(cxr)f,%(cy)f %(cxr)f,%(cy)f ' \
'%(cxr)f,%(cyr)f %(cx)f,%(cyr)f %(cx)f,%(cyr)f ' \
'%(cx_r)f,%(cyr)f %(cx_r)f,%(cy)f %(cx_r)f,%(cy)f ' \
'Z' \
% dict(cx=cx, cx_r=cx-rx, cxr=cx+rx, cy=cy, cyr=cy+ry, cy_r=cy-ry)
return path
def circle_to_path(node):
cx = float(node.get('cx'))
cy = float(node.get('cy'))
r = float(node.get('r'))
path = 'M %(xstart)f, %(cy)f ' \
'a %(r)f,%(r)f 0 1,0 %(rr)f,0 ' \
'a %(r)f,%(r)f 0 1,0 -%(rr)f,0 ' \
% dict(xstart=(cx-r), cy=cy, r=r, rr=(r*2))
return path

Wyświetl plik

@ -0,0 +1,32 @@
from simpletransform import applyTransformToPoint
from ..i18n import _
from ..svg import get_node_transform
from .element import EmbroideryElement
from .validation import ObjectTypeWarning
class TextTypeWarning(ObjectTypeWarning):
name = _("Text")
description = _("Ink/Stitch cannot work with objects like text.")
steps_to_solve = [
_('* Text: Create your own letters or try the lettering tool:'),
_('- Extensions > Ink/Stitch > Lettering')
]
class TextObject(EmbroideryElement):
def center(self):
point = [float(self.node.get('x', 0)), float(self.node.get('y', 0))]
transform = get_node_transform(self.node)
applyTransformToPoint(transform, point)
return point
def validation_warnings(self):
yield TextTypeWarning(self.center())
def to_patches(self, last_patch):
return []

Wyświetl plik

@ -1,40 +1,49 @@
from ..commands import is_command
from ..svg.tags import SVG_POLYLINE_TAG, SVG_PATH_TAG
from ..svg.tags import (EMBROIDERABLE_TAGS, SVG_IMAGE_TAG, SVG_POLYLINE_TAG,
SVG_TEXT_TAG)
from .auto_fill import AutoFill
from .clone import Clone, is_clone
from .element import EmbroideryElement
from .fill import Fill
from .image import ImageObject
from .polyline import Polyline
from .satin_column import SatinColumn
from .stroke import Stroke
from .text import TextObject
def node_to_elements(node):
def node_to_elements(node): # noqa: C901
if node.tag == SVG_POLYLINE_TAG:
return [Polyline(node)]
elif node.tag == SVG_PATH_TAG:
elif is_clone(node):
return [Clone(node)]
elif node.tag in EMBROIDERABLE_TAGS:
element = EmbroideryElement(node)
if element.get_boolean_param("satin_column") and element.get_style("stroke"):
return [SatinColumn(node)]
else:
elements = []
if element.get_style("fill", "black"):
if element.get_style("fill", 'black') and not element.get_style('fill-opacity', 1) == "0":
if element.get_boolean_param("auto_fill", True):
elements.append(AutoFill(node))
else:
elements.append(Fill(node))
if element.get_style("stroke"):
if not is_command(element.node):
elements.append(Stroke(node))
if element.get_boolean_param("stroke_first", False):
elements.reverse()
return elements
elif node.tag == SVG_IMAGE_TAG:
return [ImageObject(node)]
elif node.tag == SVG_TEXT_TAG:
return [TextObject(node)]
else:
return []

Wyświetl plik

@ -39,3 +39,13 @@ class ValidationWarning(ValidationMessage):
don't, Ink/Stitch will do its best to process the object.
"""
pass
class ObjectTypeWarning(ValidationMessage):
"""A shape is not a path and will not be embroidered.
Ink/Stitch only works with paths and ignores everything else.
The user might want the shape to be ignored, but if they
don't, they receive information how to change this behaviour.
"""
pass

Wyświetl plik

@ -1,18 +1,20 @@
from collections import MutableMapping
from copy import deepcopy
import json
import os
import re
from collections import MutableMapping
from copy import deepcopy
import inkex
from stringcase import snakecase
from ..commands import layer_commands
import inkex
from ..commands import is_command, layer_commands
from ..elements import EmbroideryElement, nodes_to_elements
from ..elements.clone import is_clone, is_embroiderable_clone
from ..i18n import _
from ..svg import generate_unique_id
from ..svg.tags import SVG_GROUP_TAG, INKSCAPE_GROUPMODE, SVG_DEFS_TAG, EMBROIDERABLE_TAGS
from ..svg.tags import (CONNECTOR_TYPE, EMBROIDERABLE_TAGS, INKSCAPE_GROUPMODE,
NOT_EMBROIDERABLE_TAGS, SVG_DEFS_TAG, SVG_GROUP_TAG)
SVG_METADATA_TAG = inkex.addNS("metadata", "svg")
@ -128,10 +130,9 @@ class InkstitchExtension(inkex.Effect):
else:
inkex.errormsg(_("There are no objects in the entire document that Ink/Stitch knows how to work with.") + "\n")
inkex.errormsg(_("Ink/Stitch only knows how to work with paths. It can't work with objects like text, rectangles, or circles.") + "\n")
inkex.errormsg(_("Tip: select some objects and use Path -> Object to Path to convert them to paths.") + "\n")
inkex.errormsg(_("Tip: Select some objects and use Path -> Object to Path to convert them to paths.") + "\n")
def descendants(self, node, selected=False):
def descendants(self, node, selected=False, troubleshoot=False): # noqa: C901
nodes = []
element = EmbroideryElement(node)
@ -148,6 +149,10 @@ class InkstitchExtension(inkex.Effect):
if node.tag == SVG_DEFS_TAG:
return []
# command connectors with a fill color set, will glitch into the elements list
if is_command(node) or node.get(CONNECTOR_TYPE):
return[]
if self.selected:
if node.get("id") in self.selected:
selected = True
@ -156,23 +161,26 @@ class InkstitchExtension(inkex.Effect):
selected = True
for child in node:
nodes.extend(self.descendants(child, selected))
nodes.extend(self.descendants(child, selected, troubleshoot))
if selected and node.tag in EMBROIDERABLE_TAGS:
nodes.append(node)
if selected:
if node.tag in EMBROIDERABLE_TAGS or is_embroiderable_clone(node):
nodes.append(node)
elif troubleshoot and (node.tag in NOT_EMBROIDERABLE_TAGS or is_clone(node)):
nodes.append(node)
return nodes
def get_nodes(self):
return self.descendants(self.document.getroot())
def get_nodes(self, troubleshoot=False):
return self.descendants(self.document.getroot(), troubleshoot=troubleshoot)
def get_elements(self):
self.elements = nodes_to_elements(self.get_nodes())
def get_elements(self, troubleshoot=False):
self.elements = nodes_to_elements(self.get_nodes(troubleshoot))
if self.elements:
return True
else:
if not troubleshoot:
self.no_elements_error()
return False
return False
def elements_to_patches(self, elements):
patches = []

Wyświetl plik

@ -1,19 +1,21 @@
# -*- coding: UTF-8 -*-
import os
import sys
from collections import defaultdict
from copy import copy
from itertools import groupby
import os
import sys
import wx
from wx.lib.scrolledpanel import ScrolledPanel
from ..commands import is_command
from ..elements import EmbroideryElement, Fill, AutoFill, Stroke, SatinColumn
from ..elements import (AutoFill, Clone, EmbroideryElement, Fill, Polyline,
SatinColumn, Stroke)
from ..elements.clone import is_clone
from ..gui import PresetsPanel, SimulatorPreview
from ..i18n import _
from ..svg.tags import SVG_POLYLINE_TAG
from ..utils import get_resource_dir
from .base import InkstitchExtension
@ -465,16 +467,18 @@ class Params(InkstitchExtension):
classes = []
if not is_command(node):
if element.get_style("fill", "black") is not None:
classes.append(AutoFill)
classes.append(Fill)
if element.get_style("stroke") is not None:
classes.append(Stroke)
if element.get_style("stroke-dasharray") is None:
classes.append(SatinColumn)
if node.tag == SVG_POLYLINE_TAG:
classes.append(Polyline)
elif is_clone(node):
classes.append(Clone)
else:
if element.get_style("fill", 'black') and not element.get_style("fill-opacity", 1) == "0":
classes.append(AutoFill)
classes.append(Fill)
if element.get_style("stroke") is not None:
classes.append(Stroke)
if element.get_style("stroke-dasharray") is None:
classes.append(SatinColumn)
return classes
def get_nodes_by_class(self):

Wyświetl plik

@ -1,6 +1,7 @@
import inkex
from ..commands import find_commands
from ..svg.svg import find_elements
from .base import InkstitchExtension
@ -12,6 +13,8 @@ class RemoveEmbroiderySettings(InkstitchExtension):
self.OptionParser.add_option("-d", "--del_print", dest="del_print", type="inkbool", default=False)
def effect(self):
self.svg = self.document.getroot()
if self.options.del_params:
self.remove_params()
if self.options.del_commands:
@ -21,7 +24,7 @@ class RemoveEmbroiderySettings(InkstitchExtension):
def remove_print_settings(self):
print_settings = "svg:metadata//*"
print_settings = self.find_elements(print_settings)
print_settings = find_elements(self.svg, print_settings)
for print_setting in print_settings:
if print_setting.prefix == "inkstitch":
self.remove_element(print_setting)
@ -29,7 +32,7 @@ class RemoveEmbroiderySettings(InkstitchExtension):
def remove_params(self):
if not self.selected:
xpath = ".//svg:path"
elements = self.find_elements(xpath)
elements = find_elements(self.svg, xpath)
self.remove_inkstitch_attributes(elements)
else:
for node in self.selected:
@ -41,7 +44,7 @@ class RemoveEmbroiderySettings(InkstitchExtension):
# we are not able to grab commands by a specific id
# so let's move through every object instead and see if it has a command
xpath = ".//svg:path|.//svg:circle|.//svg:rect|.//svg:ellipse"
elements = self.find_elements(xpath)
elements = find_elements(self.svg, xpath)
else:
elements = []
for node in self.selected:
@ -64,19 +67,14 @@ class RemoveEmbroiderySettings(InkstitchExtension):
def get_selected_elements(self, element_id):
xpath = ".//svg:g[@id='%(id)s']//svg:path|.//svg:g[@id='%(id)s']//svg:use" % dict(id=element_id)
elements = self.find_elements(xpath)
elements = find_elements(self.svg, xpath)
if not elements:
xpath = ".//*[@id='%s']" % element_id
elements = self.find_elements(xpath)
return elements
def find_elements(self, xpath):
svg = self.document.getroot()
elements = svg.xpath(xpath, namespaces=inkex.NSS)
elements = find_elements(self.svg, xpath)
return elements
def remove_elements(self, xpath):
elements = self.find_elements(xpath)
elements = find_elements(self.svg, xpath)
for element in elements:
self.remove_element(element)

Wyświetl plik

@ -9,6 +9,8 @@ class Simulator(InkstitchExtension):
InkstitchExtension.__init__(self)
def effect(self):
if not self.get_elements():
return
api_server = APIServer(self)
port = api_server.start_server()
electron = open_url("/simulator?port=%d" % port)

Wyświetl plik

@ -1,43 +1,48 @@
from itertools import chain
import textwrap
import inkex
from ..commands import add_layer_commands
from ..elements.validation import ValidationWarning, ValidationError
from ..elements.validation import (ObjectTypeWarning, ValidationError,
ValidationWarning)
from ..i18n import _
from ..svg import get_correction_transform
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL,
SODIPODI_ROLE, SVG_GROUP_TAG, SVG_PATH_TAG,
SVG_TEXT_TAG, SVG_TSPAN_TAG)
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SODIPODI_ROLE,
SVG_GROUP_TAG, SVG_PATH_TAG, SVG_TEXT_TAG,
SVG_TSPAN_TAG)
from .base import InkstitchExtension
class Troubleshoot(InkstitchExtension):
def effect(self):
if not self.get_elements():
return
self.create_troubleshoot_layer()
problem_types = set()
for element in self.elements:
for problem in chain(element.validation_errors(), element.validation_warnings()):
problem_types.add(type(problem))
self.insert_pointer(problem)
problem_types = {'error': set(), 'warning': set(), 'type_warning': set()}
if problem_types:
if self.get_elements(True):
for element in self.elements:
for problem in element.validation_errors():
problem_types['error'].add(type(problem))
self.insert_pointer(problem)
for problem in element.validation_warnings():
if isinstance(problem, ObjectTypeWarning):
problem_types['type_warning'].add(type(problem))
else:
problem_types['warning'].add(type(problem))
self.insert_pointer(problem)
if any(problem_types.values()):
self.add_descriptions(problem_types)
else:
svg = self.document.getroot()
svg.remove(self.troubleshoot_layer)
message = _("All selected shapes are valid!")
message = _("All selected shapes are valid! ")
message += "\n\n"
message += _("Tip: If you are still having an issue with an object not being rendered, "
"you might need to convert it it to a path (Path -> Object to Path) or check if it is possibly in an ignored layer.")
message += _("If you are still having trouble with a shape not being embroidered, "
"check if it is in a layer with an ignore command.")
inkex.errormsg(message)
def insert_pointer(self, problem):
@ -49,9 +54,12 @@ class Troubleshoot(InkstitchExtension):
elif isinstance(problem, ValidationError):
fill_color = "#ff0000"
layer = self.error_group
elif isinstance(problem, ObjectTypeWarning):
fill_color = "#ff9900"
layer = self.type_warning_group
pointer_style = "stroke:#ffffff;stroke-width:0.2;fill:%s;" % (fill_color)
text_style = "fill:%s;stroke:#ffffff;stroke-width:0.2;font-size:8px;text-align:center;text-anchor:middle" % (fill_color)
pointer_style = "stroke:#000000;stroke-width:0.2;fill:%s;" % (fill_color)
text_style = "fill:%s;stroke:#000000;stroke-width:0.2;font-size:8px;text-align:center;text-anchor:middle" % (fill_color)
path = inkex.etree.Element(
SVG_PATH_TAG,
@ -119,13 +127,23 @@ class Troubleshoot(InkstitchExtension):
})
layer.append(warning_group)
type_warning_group = inkex.etree.SubElement(
layer,
SVG_GROUP_TAG,
{
"id": '__validation_ignored__',
INKSCAPE_LABEL: _("Type Warnings"),
})
layer.append(type_warning_group)
self.troubleshoot_layer = layer
self.error_group = error_group
self.warning_group = warning_group
self.type_warning_group = type_warning_group
def add_descriptions(self, problem_types):
svg = self.document.getroot()
text_x = str(self.unittouu(svg.get('width')) + 5)
text_x = str(float(svg.get('viewBox', '0 0 800 0').split(' ')[2]) + 5.0)
text_container = inkex.etree.Element(
SVG_TEXT_TAG,
@ -138,23 +156,40 @@ class Troubleshoot(InkstitchExtension):
self.troubleshoot_layer.append(text_container)
text = [
[_("Troubleshoot"), "font-weight: bold; font-size: 6px;"],
[_("Troubleshoot"), "font-weight: bold; font-size: 8px;"],
["", ""]
]
for problem in problem_types:
text_color = "#ff0000"
if issubclass(problem, ValidationWarning):
for problem_type, problems in problem_types.items():
if problem_type == "error":
text_color = "#ff0000"
problem_type_header = _("Errors")
problem_type_description = _("Problems that will prevent the shape from being embroidered.")
elif problem_type == "warning":
text_color = "#ffdd00"
problem_type_header = _("Warnings")
problem_type_description = _("These are problems that won't prevent the shape from being embroidered. "
"You should consider to fix the warning, but if you don't, "
"Ink/Stitch will do its best to process the object.")
elif problem_type == "type_warning":
text_color = "#ff9900"
problem_type_header = _("Object Type Warnings")
problem_type_description = _("Ink/Stitch only knows how to works with paths and ignores everything else. "
"You might want these shapes to be ignored, but if you don't, "
"follow the instructions to change this behaviour.")
if problems:
text.append([problem_type_header, "font-weight: bold; fill: %s; text-decoration: underline; font-size: 7px;" % text_color])
text.append(["", ""])
text.append([problem_type_description, "fill:%s;" % text_color])
text.append(["", ""])
text.append([problem.name, "font-weight: bold; fill:%s;" % text_color])
description_parts = textwrap.wrap(problem.description, 60)
for description in description_parts:
text.append([description, "font-size: 3px;"])
text.append(["", ""])
for step in problem.steps_to_solve:
text.append([step, "font-size: 4px;"])
text.append(["", ""])
for problem in problems:
text.append([problem.name, "font-weight: bold; fill: %s;" % text_color])
text.append([problem.description, "font-size: 3px;"])
text.append(["", ""])
for step in problem.steps_to_solve:
text.append([step, "font-size: 4px;"])
text.append(["", ""])
explain_layer = _('It is possible, that one object contains more than one error, ' +
'yet there will be only one pointer per object. Run this function again, ' +
@ -162,7 +197,9 @@ class Troubleshoot(InkstitchExtension):
'"Troubleshoot" through the objects panel (Object -> Objects...).')
explain_layer_parts = textwrap.wrap(explain_layer, 60)
for description in explain_layer_parts:
text.append([description, "font-style: italic; font-size: 3px;"])
text.append([description, "font-style: italic; font-size: 4px;"])
text = self.split_text(text)
for text_line in text:
tspan = inkex.etree.Element(
@ -174,3 +211,14 @@ class Troubleshoot(InkstitchExtension):
)
tspan.text = text_line[0]
text_container.append(tspan)
def split_text(self, text):
splitted_text = []
for text_part, style in text:
if text_part:
description_parts = textwrap.wrap(text_part, 60)
for description in description_parts:
splitted_text.append([description, style])
else:
splitted_text.append(["", ""])
return splitted_text

Wyświetl plik

@ -36,6 +36,9 @@ def get_node_transform(node):
# combine this node's transform with all parent groups' transforms
transform = compose_parent_transforms(node, transform)
if node.get('id', '').startswith('clone_'):
transform = simpletransform.parseTransform(node.get('transform', ''))
# add in the transform implied by the viewBox
viewbox_transform = get_viewbox_transform(node.getroottree().getroot())
transform = simpletransform.composeTransform(viewbox_transform, transform)

Wyświetl plik

@ -1,4 +1,4 @@
from inkex import etree
from inkex import NSS, etree
from ..utils import cache
@ -24,3 +24,9 @@ def generate_unique_id(document_or_element, prefix="path"):
i += 1
return new_id
def find_elements(node, xpath):
document = get_document(node)
elements = document.xpath(xpath, namespaces=NSS)
return elements

Wyświetl plik

@ -7,12 +7,16 @@ inkex.NSS['inkstitch'] = 'http://inkstitch.org/namespace'
SVG_PATH_TAG = inkex.addNS('path', 'svg')
SVG_POLYLINE_TAG = inkex.addNS('polyline', 'svg')
SVG_RECT_TAG = inkex.addNS('rect', 'svg')
SVG_ELLIPSE_TAG = inkex.addNS('ellipse', 'svg')
SVG_CIRCLE_TAG = inkex.addNS('circle', 'svg')
SVG_TEXT_TAG = inkex.addNS('text', 'svg')
SVG_TSPAN_TAG = inkex.addNS('tspan', 'svg')
SVG_DEFS_TAG = inkex.addNS('defs', 'svg')
SVG_GROUP_TAG = inkex.addNS('g', 'svg')
SVG_SYMBOL_TAG = inkex.addNS('symbol', 'svg')
SVG_USE_TAG = inkex.addNS('use', 'svg')
SVG_IMAGE_TAG = inkex.addNS('image', 'svg')
EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG)
@ -30,12 +34,17 @@ SODIPODI_ROLE = inkex.addNS('role', 'sodipodi')
INKSTITCH_LETTERING = inkex.addNS('lettering', 'inkstitch')
EMBROIDERABLE_TAGS = (SVG_PATH_TAG, SVG_POLYLINE_TAG, SVG_RECT_TAG, SVG_ELLIPSE_TAG, SVG_CIRCLE_TAG)
NOT_EMBROIDERABLE_TAGS = (SVG_IMAGE_TAG, SVG_TEXT_TAG)
SVG_OBJECT_TAGS = (SVG_ELLIPSE_TAG, SVG_CIRCLE_TAG, SVG_RECT_TAG)
INKSTITCH_ATTRIBS = {}
# Fill
inkstitch_attribs = [
'ties',
'trim_after',
'stop_after',
# clone
'clone',
# fill
'angle',
'auto_fill',