kopia lustrzana https://github.com/inkstitch/inkstitch
254 wiersze
10 KiB
Python
254 wiersze
10 KiB
Python
# Authors: see git history
|
|
#
|
|
# Copyright (c) 2010 Authors
|
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
|
|
|
import textwrap
|
|
|
|
import inkex
|
|
|
|
from ..commands import add_layer_commands
|
|
from ..elements.validation import (ObjectTypeWarning, ValidationError,
|
|
ValidationWarning)
|
|
from ..i18n import _
|
|
from ..svg import PIXELS_PER_MM
|
|
from ..svg.path import get_correction_transform
|
|
from ..svg.tags import (INKSCAPE_GROUPMODE, INKSCAPE_LABEL, SODIPODI_ROLE,
|
|
SVG_GROUP_TAG)
|
|
from .base import InkstitchExtension
|
|
|
|
|
|
class Troubleshoot(InkstitchExtension):
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
InkstitchExtension.__init__(self, *args, **kwargs)
|
|
self.arg_parser.add_argument("--notebook")
|
|
self.arg_parser.add_argument("-p", "--pointer-size", type=float, default=5, dest="pointer_size_mm")
|
|
self.arg_parser.add_argument("-f", "--font-size", type=float, default=2, dest="font_size_mm")
|
|
|
|
def effect(self):
|
|
self.create_troubleshoot_layer()
|
|
problem_types = {'error': set(), 'warning': set(), 'type_warning': set()}
|
|
|
|
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)
|
|
self.remove_empty_layers()
|
|
else:
|
|
self.troubleshoot_layer.delete()
|
|
|
|
message = _("All selected shapes are valid! ")
|
|
message += "\n\n"
|
|
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):
|
|
correction_transform = get_correction_transform(self.troubleshoot_layer)
|
|
pointer_size = self.options.pointer_size_mm * PIXELS_PER_MM
|
|
font_size = self.options.font_size_mm * PIXELS_PER_MM
|
|
|
|
if isinstance(problem, ValidationWarning):
|
|
fill_color = "#ffdd00"
|
|
layer = self.warning_group
|
|
elif isinstance(problem, ValidationError):
|
|
fill_color = "#ff0000"
|
|
layer = self.error_group
|
|
elif isinstance(problem, ObjectTypeWarning):
|
|
fill_color = "#ff9900"
|
|
layer = self.type_warning_group
|
|
|
|
group = self._get_or_create_group(layer, problem.name)
|
|
|
|
pointer_style = f'stroke:#000000;stroke-width:0.1;fill:{ fill_color }'
|
|
text_style = f'fill:{ fill_color };stroke:#000000;stroke-width:0.1;font-size:{ font_size }px;text-align:center;text-anchor:middle'
|
|
pointer_path = f'm {problem.position.x},{problem.position.y} {pointer_size / 5},{pointer_size} ' \
|
|
f'h -{pointer_size / 2.5} l {pointer_size / 5},-{pointer_size}'
|
|
|
|
path = inkex.PathElement(attrib={
|
|
"id": self.uniqueId("inkstitch__invalid_pointer__"),
|
|
"d": pointer_path,
|
|
"style": pointer_style,
|
|
INKSCAPE_LABEL: _('Invalid Pointer'),
|
|
"transform": correction_transform
|
|
})
|
|
group.insert(0, path)
|
|
|
|
text = inkex.TextElement(attrib={
|
|
"x": str(problem.position.x),
|
|
"y": str(float(problem.position.y) + pointer_size + font_size),
|
|
"transform": correction_transform,
|
|
"style": text_style
|
|
})
|
|
text.label = _("Description")
|
|
group.append(text)
|
|
|
|
tspan = inkex.Tspan()
|
|
tspan.text = problem.name
|
|
if problem.label:
|
|
tspan.text += " (%s)" % problem.label
|
|
text.append(tspan)
|
|
|
|
def _get_or_create_group(self, layer, label):
|
|
group = layer.xpath(f".//*[@inkscape:label='{label}']")
|
|
|
|
if not group:
|
|
group = inkex.Group()
|
|
group.label = label
|
|
layer.add(group)
|
|
else:
|
|
group = group[0]
|
|
return group
|
|
|
|
def create_troubleshoot_layer(self):
|
|
svg = self.document.getroot()
|
|
layer = svg.find(".//*[@id='__validation_layer__']")
|
|
|
|
if layer is not None:
|
|
# Remove the old layer - they may have used tranfsorms
|
|
# or moved it into an other group (which could lead to more transforms)
|
|
# We don't want to deal with it.
|
|
layer.delete()
|
|
|
|
layer = inkex.Group(attrib={
|
|
'id': '__validation_layer__',
|
|
INKSCAPE_LABEL: _('Troubleshoot'),
|
|
INKSCAPE_GROUPMODE: 'layer',
|
|
})
|
|
svg.append(layer)
|
|
|
|
add_layer_commands(layer, ["ignore_layer"])
|
|
|
|
error_group = inkex.Group(attrib={
|
|
"id": '__validation_errors__',
|
|
INKSCAPE_LABEL: _("Errors"),
|
|
})
|
|
layer.append(error_group)
|
|
|
|
warning_group = inkex.Group(attrib={
|
|
"id": '__validation_warnings__',
|
|
INKSCAPE_LABEL: _("Warnings"),
|
|
})
|
|
layer.append(warning_group)
|
|
|
|
type_warning_group = inkex.Group(attrib={
|
|
"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): # noqa: C901
|
|
svg = self.document.getroot()
|
|
|
|
# We could use svg.viewport_width, but then we would need to do unit conversions,
|
|
# so let's stay with parsing the viewbox by ourselves
|
|
# viewbox values are either separated through white space or commas
|
|
text_x = str(float(svg.get('viewBox', '0 0 800 0').replace(",", " ").split()[2]) + 5.0)
|
|
|
|
group = inkex.Group()
|
|
group.label = _("Problem descriptions")
|
|
|
|
text_container = inkex.TextElement(attrib={
|
|
"x": text_x,
|
|
"y": str(5),
|
|
"style": "fill:#000000;font-size:5px;line-height:1;"
|
|
})
|
|
group.append(text_container)
|
|
|
|
text = [
|
|
[_("Troubleshoot"), "font-weight: bold; font-size: 8px;"],
|
|
["", ""]
|
|
]
|
|
|
|
for problem_type, problems in list(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 = _("These objects may not work properly with Ink/Stitch. "
|
|
"Follow the instructions to correct unwanted 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(["", ""])
|
|
|
|
for problem in problems:
|
|
text.append([problem.name, "font-weight: bold; fill: %s;" % text_color])
|
|
text.append([problem.description, "font-size: 3px;"])
|
|
text.append(["", ""])
|
|
if problem.steps_to_solve:
|
|
text.append([_("Possible solutions"), "font-weight: bold; text-decoration: underline; font-size: 4px;"])
|
|
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, ' +
|
|
'when further errors occur. Remove pointers by deleting the layer named '
|
|
'"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: 4px;"])
|
|
|
|
text = self.split_text(text)
|
|
|
|
for text_line in text:
|
|
tspan = inkex.Tspan(attrib={
|
|
SODIPODI_ROLE: "line",
|
|
"style": text_line[1]
|
|
})
|
|
tspan.text = text_line[0]
|
|
text_container.append(tspan)
|
|
|
|
# we cannot really detect the text boudning_box. So we have to make a bad guesses
|
|
group.insert(
|
|
0,
|
|
inkex.PathElement(
|
|
d=f"M {float(text_x) - 5} {-5}, {float(text_x) + 160} {-5}, {float(text_x) + 160} {600}, {float(text_x) - 5} {600} Z",
|
|
style="fill: #51a888; stroke: red;"
|
|
)
|
|
)
|
|
self.troubleshoot_layer.add(group)
|
|
|
|
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
|
|
|
|
def remove_empty_layers(self):
|
|
for layer in self.troubleshoot_layer.iterchildren(SVG_GROUP_TAG):
|
|
if len(layer) == 0:
|
|
layer.delete()
|