summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLex Neva <lexelby@users.noreply.github.com>2023-09-07 13:25:47 -0400
committerGitHub <noreply@github.com>2023-09-07 19:25:47 +0200
commitaf96d720e9340e02b1ec6dafe10bf9a47e045804 (patch)
treea49d9b1570050db02ff8f17b754e54ec7b9a6e1a
parent3db335b0f41ebfcc694914bba5bf81aed9ae98d4 (diff)
improve params errors (#2437)
-rw-r--r--electron/src/renderer/assets/js/simulator.js20
-rw-r--r--electron/src/renderer/assets/style/simulator.css4
-rw-r--r--electron/src/renderer/components/Simulator.vue13
-rw-r--r--inkstitch.py24
-rw-r--r--lib/api/stitch_plan.py20
-rw-r--r--lib/elements/element.py55
-rw-r--r--lib/elements/fill_stitch.py70
-rw-r--r--lib/exceptions.py30
-rw-r--r--lib/extensions/params.py20
-rw-r--r--lib/gui/warnings.py21
-rw-r--r--lib/stitches/meander_fill.py7
11 files changed, 167 insertions, 117 deletions
diff --git a/electron/src/renderer/assets/js/simulator.js b/electron/src/renderer/assets/js/simulator.js
index 8a0423f3..e751f137 100644
--- a/electron/src/renderer/assets/js/simulator.js
+++ b/electron/src/renderer/assets/js/simulator.js
@@ -54,7 +54,9 @@ export default {
showNeedlePenetrationPoints: false,
renderJumps: true,
showRealisticPreview: false,
- showCursor: true
+ showCursor: true,
+ error: false,
+ error_message: ""
}
},
watch: {
@@ -543,6 +545,9 @@ export default {
zoomPage () {
this.svg.viewbox(this.page_specs.bbox.x, this.page_specs.bbox.y - 50, this.page_specs.bbox.width + 100, this.page_specs.bbox.height + 100)
this.resizeCursor()
+ },
+ close () {
+ window.close()
}
},
created: function () {
@@ -642,6 +647,19 @@ export default {
})
this.start()
+ }).catch(error => {
+ this.loading = false
+ if (error.response) {
+ // Stitch plan generation had an error. Show it to the user.
+ this.error_message = error.response.data.error_message
+ } else if (error.request) {
+ // We sent the request and didn't get a response.
+ this.error_message = "Stitch plan generation failed."
+ } else {
+ // Something weird happened in axios.
+ this.error_message = error.message
+ }
+ this.error = true
})
}
}
diff --git a/electron/src/renderer/assets/style/simulator.css b/electron/src/renderer/assets/style/simulator.css
index e938cbe3..1ce61865 100644
--- a/electron/src/renderer/assets/style/simulator.css
+++ b/electron/src/renderer/assets/style/simulator.css
@@ -26,12 +26,12 @@
padding: 1rem;
}
-button {
+.controls button {
color: rgb(0, 51, 153);
align-items: flex-start;
text-align: center;
cursor: default;
- background: linear-gradient(0deg, rgba(169,169,169,1) 0%, rgba(255,255,255,1) 68%, rgba(227,227,227,1) 100%);
+ background: linear-gradient(0deg, rgba(169, 169, 169, 1) 0%, rgba(255, 255, 255, 1) 68%, rgba(227, 227, 227, 1) 100%);
box-sizing: border-box;
padding: 2px 6px 3px;
border-width: 2px;
diff --git a/electron/src/renderer/components/Simulator.vue b/electron/src/renderer/components/Simulator.vue
index 6d76b133..e2d65ed8 100644
--- a/electron/src/renderer/components/Simulator.vue
+++ b/electron/src/renderer/components/Simulator.vue
@@ -306,6 +306,19 @@
</div>
</div>
</loading>
+ <v-dialog v-model="error" width="auto">
+ <v-card flat>
+ <v-card-title class="pa-4">
+ Error Generating Stitch Plan
+ </v-card-title>
+ <v-card-text>
+ <p style="white-space: pre-wrap;">{{ error_message }}</p>
+ </v-card-text>
+ <v-card-actions class="justify-center">
+ <v-btn color="primary" variant="text" class="dialog-button" @click="close">Close</v-btn>
+ </v-card-actions>
+ </v-card>
+ </v-dialog>
</div>
</template>
diff --git a/inkstitch.py b/inkstitch.py
index 1dc5a3e3..91a0f18a 100644
--- a/inkstitch.py
+++ b/inkstitch.py
@@ -7,10 +7,11 @@ import pstats
import logging
import os
import sys
-import traceback
from argparse import ArgumentParser
from io import StringIO
+from lib.exceptions import InkstitchException, format_uncaught_exception
+
if getattr(sys, 'frozen', None) is None:
# When running in development mode, we want to use the inkex installed by
# pip install, not the one bundled with Inkscape which is not new enough.
@@ -28,7 +29,7 @@ from lxml.etree import XMLSyntaxError
import lib.debug as debug
from lib import extensions
from lib.i18n import _
-from lib.utils import restore_stderr, save_stderr, version
+from lib.utils import restore_stderr, save_stderr
# ignore warnings in releases
if getattr(sys, 'frozen', None):
@@ -90,24 +91,15 @@ else:
msg += "\n\n"
msg += _("Try to import the file into Inkscape through 'File > Import...' (Ctrl+I)")
errormsg(msg)
+ except InkstitchException as exc:
+ errormsg(str(exc))
except Exception:
- exception = traceback.format_exc()
+ errormsg(format_uncaught_exception())
+ sys.exit(1)
finally:
restore_stderr()
if shapely_errors.tell():
errormsg(shapely_errors.getvalue())
- if exception:
- errormsg(_("Ink/Stitch experienced an unexpected error. This means it is a bug in Ink/Stitch.") + "\n")
- errormsg(_("If you'd like to help please\n"
- "- copy the entire error message below\n"
- "- save your SVG file and\n"
- "- create a new issue at https://github.com/inkstitch/inkstitch/issues") + "\n")
- errormsg(_("Include the error description and also (if possible) "
- "the svg file.") + "\n")
- errormsg(version.get_inkstitch_version() + "\n")
- errormsg(exception)
- sys.exit(1)
- else:
- sys.exit(0)
+ sys.exit(0)
diff --git a/lib/api/stitch_plan.py b/lib/api/stitch_plan.py
index c70efd98..5e9a57c1 100644
--- a/lib/api/stitch_plan.py
+++ b/lib/api/stitch_plan.py
@@ -5,9 +5,9 @@
from flask import Blueprint, g, jsonify
+from ..exceptions import InkstitchException, format_uncaught_exception
from ..stitch_plan import stitch_groups_to_stitch_plan
-
stitch_plan = Blueprint('stitch_plan', __name__)
@@ -16,10 +16,14 @@ def get_stitch_plan():
if not g.extension.get_elements():
return dict(colors=[], stitch_blocks=[], commands=[])
- metadata = g.extension.get_inkstitch_metadata()
- collapse_len = metadata['collapse_len_mm']
- min_stitch_len = metadata['min_stitch_len_mm']
- patches = g.extension.elements_to_stitch_groups(g.extension.elements)
- stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, min_stitch_len=min_stitch_len)
-
- return jsonify(stitch_plan)
+ try:
+ metadata = g.extension.get_inkstitch_metadata()
+ collapse_len = metadata['collapse_len_mm']
+ min_stitch_len = metadata['min_stitch_len_mm']
+ patches = g.extension.elements_to_stitch_groups(g.extension.elements)
+ stitch_plan = stitch_groups_to_stitch_plan(patches, collapse_len=collapse_len, min_stitch_len=min_stitch_len)
+ return jsonify(stitch_plan)
+ except InkstitchException as exc:
+ return jsonify({"error_message": str(exc)}), 500
+ except Exception:
+ return jsonify({"error_message": format_uncaught_exception()}), 500
diff --git a/lib/elements/element.py b/lib/elements/element.py
index 43cbc8a2..963653af 100644
--- a/lib/elements/element.py
+++ b/lib/elements/element.py
@@ -3,6 +3,7 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import sys
+from contextlib import contextmanager
from copy import deepcopy
import inkex
@@ -11,6 +12,7 @@ from inkex import bezier
from ..commands import find_commands
from ..debug import debug
+from ..exceptions import InkstitchException, format_uncaught_exception
from ..i18n import _
from ..marker import get_marker_elements_cache_key_data
from ..patterns import apply_patterns, get_patterns_cache_key_data
@@ -546,23 +548,25 @@ class EmbroideryElement(object):
def embroider(self, last_stitch_group):
debug.log(f"starting {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}")
- if last_stitch_group:
- previous_stitch = last_stitch_group.stitches[-1]
- else:
- previous_stitch = None
- stitch_groups = self._load_cached_stitch_groups(previous_stitch)
- if not stitch_groups:
- self.validate()
+ with self.handle_unexpected_exceptions():
+ if last_stitch_group:
+ previous_stitch = last_stitch_group.stitches[-1]
+ else:
+ previous_stitch = None
+ stitch_groups = self._load_cached_stitch_groups(previous_stitch)
+
+ if not stitch_groups:
+ self.validate()
- stitch_groups = self.to_stitch_groups(last_stitch_group)
- apply_patterns(stitch_groups, self.node)
+ stitch_groups = self.to_stitch_groups(last_stitch_group)
+ apply_patterns(stitch_groups, self.node)
- if stitch_groups:
- stitch_groups[-1].trim_after = self.has_command("trim") or self.trim_after
- stitch_groups[-1].stop_after = self.has_command("stop") or self.stop_after
+ if stitch_groups:
+ stitch_groups[-1].trim_after = self.has_command("trim") or self.trim_after
+ stitch_groups[-1].stop_after = self.has_command("stop") or self.stop_after
- self._save_cached_stitch_groups(stitch_groups, previous_stitch)
+ self._save_cached_stitch_groups(stitch_groups, previous_stitch)
debug.log(f"ending {self.node.get('id')} {self.node.get(INKSCAPE_LABEL)}")
return stitch_groups
@@ -575,14 +579,27 @@ class EmbroideryElement(object):
else:
name = id
- # L10N used when showing an error message to the user such as
- # "Failed on PathLabel (path1234): Satin column: One or more of the rungs doesn't intersect both rails."
- error_msg = "%s %s: %s" % (_("Failed on "), name, message)
+ error_msg = f"{name}: {message}"
if point_to_troubleshoot:
error_msg += "\n\n%s" % _("Please run Extensions > Ink/Stitch > Troubleshoot > Troubleshoot objects. "
- "This will indicate the errorneus position.")
- inkex.errormsg(error_msg)
- sys.exit(1)
+ "This will show you the exact location of the problem.")
+
+ raise InkstitchException(error_msg)
+
+ @contextmanager
+ def handle_unexpected_exceptions(self):
+ try:
+ # This runs the code in the `with` body so that we can catch
+ # exceptions.
+ yield
+ except (InkstitchException, SystemExit, KeyboardInterrupt):
+ raise
+ except Exception:
+ if hasattr(sys, 'gettrace') and sys.gettrace():
+ # if we're debugging, let the exception bubble up
+ raise
+
+ raise InkstitchException(format_uncaught_exception())
def validation_errors(self):
"""Return a list of errors with this Element.
diff --git a/lib/elements/fill_stitch.py b/lib/elements/fill_stitch.py
index b93d7ff5..c7c3c640 100644
--- a/lib/elements/fill_stitch.py
+++ b/lib/elements/fill_stitch.py
@@ -6,8 +6,6 @@
import logging
import math
import re
-import sys
-import traceback
import numpy as np
from inkex import Transform
@@ -25,9 +23,8 @@ from ..stitches.meander_fill import meander_fill
from ..svg import PIXELS_PER_MM, get_node_transform
from ..svg.clip import get_clip_path
from ..svg.tags import INKSCAPE_LABEL
-from ..utils import cache, version
+from ..utils import cache
from ..utils.param import ParamOption
-from ..utils.threading import ExitThread
from .element import EmbroideryElement, param
from .validation import ValidationError, ValidationWarning
@@ -706,30 +703,25 @@ class FillStitch(EmbroideryElement):
for shape in self.shape.geoms:
start = self.get_starting_point(previous_stitch_group)
- try:
- if self.fill_underlay:
- underlay_shapes = self.underlay_shape(shape)
- for underlay_shape in underlay_shapes.geoms:
- underlay_stitch_groups, start = self.do_underlay(underlay_shape, start)
- stitch_groups.extend(underlay_stitch_groups)
-
- fill_shapes = self.fill_shape(shape)
- for i, fill_shape in enumerate(fill_shapes.geoms):
- if self.fill_method == 'contour_fill':
- stitch_groups.extend(self.do_contour_fill(fill_shape, previous_stitch_group, start))
- elif self.fill_method == 'guided_fill':
- stitch_groups.extend(self.do_guided_fill(fill_shape, previous_stitch_group, start, end))
- elif self.fill_method == 'meander_fill':
- stitch_groups.extend(self.do_meander_fill(fill_shape, shape, i, start, end))
- elif self.fill_method == 'circular_fill':
- stitch_groups.extend(self.do_circular_fill(fill_shape, previous_stitch_group, start, end))
- else:
- # auto_fill
- stitch_groups.extend(self.do_auto_fill(fill_shape, previous_stitch_group, start, end))
- except ExitThread:
- raise
- except Exception:
- self.fatal_fill_error()
+ if self.fill_underlay:
+ underlay_shapes = self.underlay_shape(shape)
+ for underlay_shape in underlay_shapes.geoms:
+ underlay_stitch_groups, start = self.do_underlay(underlay_shape, start)
+ stitch_groups.extend(underlay_stitch_groups)
+
+ fill_shapes = self.fill_shape(shape)
+ for i, fill_shape in enumerate(fill_shapes.geoms):
+ if self.fill_method == 'contour_fill':
+ stitch_groups.extend(self.do_contour_fill(fill_shape, previous_stitch_group, start))
+ elif self.fill_method == 'guided_fill':
+ stitch_groups.extend(self.do_guided_fill(fill_shape, previous_stitch_group, start, end))
+ elif self.fill_method == 'meander_fill':
+ stitch_groups.extend(self.do_meander_fill(fill_shape, shape, i, start, end))
+ elif self.fill_method == 'circular_fill':
+ stitch_groups.extend(self.do_circular_fill(fill_shape, previous_stitch_group, start, end))
+ else:
+ # auto_fill
+ stitch_groups.extend(self.do_auto_fill(fill_shape, previous_stitch_group, start, end))
previous_stitch_group = stitch_groups[-1]
return stitch_groups
@@ -885,28 +877,6 @@ class FillStitch(EmbroideryElement):
else:
return guide_lines['stroke'][0]
- def fatal_fill_error(self):
- if hasattr(sys, 'gettrace') and sys.gettrace():
- # if we're debugging, let the exception bubble up
- raise
-
- # for an uncaught exception, give a little more info so that they can create a bug report
- message = ""
- message += _("Error during autofill! This means it is a bug in Ink/Stitch.")
- message += "\n\n"
- # L10N this message is followed by a URL: https://github.com/inkstitch/inkstitch/issues/new
- message += _("If you'd like to help please\n"
- "- copy the entire error message below\n"
- "- save your SVG file and\n"
- "- create a new issue at")
- message += " https://github.com/inkstitch/inkstitch/issues/new\n\n"
- message += _("Include the error description and also (if possible) the svg file.")
- message += '\n\n\n'
- message += version.get_inkstitch_version() + '\n'
- message += traceback.format_exc()
-
- self.fatal(message)
-
def do_circular_fill(self, shape, last_patch, starting_point, ending_point):
# get target position
command = self.get_command('ripple_target')
diff --git a/lib/exceptions.py b/lib/exceptions.py
index a9820ac3..3a6b456c 100644
--- a/lib/exceptions.py
+++ b/lib/exceptions.py
@@ -2,6 +2,36 @@
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+import traceback
+
class InkstitchException(Exception):
pass
+
+
+def format_uncaught_exception():
+ """Format the current exception as a request for a bug report.
+
+ Call this inside an except block so that there is an exception that we can
+ call traceback.format_exc() on.
+ """
+
+ # importing locally to avoid any possibility of circular import
+ from lib.utils import version
+ from .i18n import _
+
+ message = ""
+ message += _("Ink/Stitch experienced an unexpected error. This means it is a bug in Ink/Stitch.")
+ message += "\n\n"
+ # L10N this message is followed by a URL: https://github.com/inkstitch/inkstitch/issues/new
+ message += _("If you'd like to help please\n"
+ "- copy the entire error message below\n"
+ "- save your SVG file and\n"
+ "- create a new issue at")
+ message += " https://github.com/inkstitch/inkstitch/issues/new\n\n"
+ message += _("Include the error description and also (if possible) the svg file.")
+ message += '\n\n\n'
+ message += version.get_inkstitch_version() + '\n'
+ message += traceback.format_exc()
+
+ return message
diff --git a/lib/extensions/params.py b/lib/extensions/params.py
index 540cc7bb..1ba144b2 100644
--- a/lib/extensions/params.py
+++ b/lib/extensions/params.py
@@ -7,7 +7,6 @@
import os
import sys
-import traceback
from collections import defaultdict
from copy import copy
from itertools import groupby, zip_longest
@@ -20,6 +19,7 @@ from ..commands import is_command, is_command_symbol
from ..elements import (Clone, EmbroideryElement, FillStitch, Polyline,
SatinColumn, Stroke)
from ..elements.clone import is_clone
+from ..exceptions import InkstitchException, format_uncaught_exception
from ..gui import PresetsPanel, SimulatorPreview, WarningPanel
from ..i18n import _
from ..svg.tags import SVG_POLYLINE_TAG
@@ -544,24 +544,22 @@ class SettingsFrame(wx.Frame):
patches.extend(copy(node).embroider(None))
check_stop_flag()
- except SystemExit:
- wx.CallAfter(self._show_warning)
+ except (SystemExit, ExitThread):
raise
- except ExitThread:
- raise
- except Exception as e:
- # Ignore errors. This can be things like incorrect paths for
- # satins or division by zero caused by incorrect param values.
- traceback.print_exception(e, file=sys.stderr)
- pass
+ except InkstitchException as exc:
+ wx.CallAfter(self._show_warning, str(exc))
+ except Exception:
+ wx.CallAfter(self._show_warning, format_uncaught_exception())
return patches
def _hide_warning(self):
+ self.warning_panel.clear()
self.warning_panel.Hide()
self.Layout()
- def _show_warning(self):
+ def _show_warning(self, warning_text):
+ self.warning_panel.set_warning_text(warning_text)
self.warning_panel.Show()
self.Layout()
diff --git a/lib/gui/warnings.py b/lib/gui/warnings.py
index 48788652..eda1ca2e 100644
--- a/lib/gui/warnings.py
+++ b/lib/gui/warnings.py
@@ -15,14 +15,25 @@ class WarningPanel(wx.Panel):
def __init__(self, parent, *args, **kwargs):
wx.Panel.__init__(self, parent, wx.ID_ANY, *args, **kwargs)
- self.warning_box = wx.StaticBox(self, wx.ID_ANY)
+ self.main_sizer = wx.BoxSizer(wx.VERTICAL)
self.warning = wx.StaticText(self)
- self.warning.SetLabel(_("Cannot load simulator.\nClose Params to get full error message."))
+ self.warning.SetLabel(_("An error occurred while rendering the stitch plan:"))
self.warning.SetForegroundColour(wx.Colour(255, 25, 25))
+ self.main_sizer.Add(self.warning, 1, wx.LEFT | wx.BOTTOM | wx.EXPAND, 10)
- warning_sizer = wx.StaticBoxSizer(self.warning_box, wx.HORIZONTAL)
- warning_sizer.Add(self.warning, 1, wx.LEFT | wx.BOTTOM | wx.EXPAND, 10)
+ tc_style = wx.TE_MULTILINE | wx.TE_READONLY | wx.VSCROLL | wx.TE_RICH2
+ self.warning_text = wx.TextCtrl(self, size=(300, 100), style=tc_style)
+ font = self.warning_text.GetFont()
+ font.SetFamily(wx.FONTFAMILY_TELETYPE)
+ self.warning_text.SetFont(font)
+ self.main_sizer.Add(self.warning_text, 3, wx.LEFT | wx.BOTTOM | wx.EXPAND, 10)
- self.SetSizerAndFit(warning_sizer)
+ self.SetSizerAndFit(self.main_sizer)
self.Layout()
+
+ def set_warning_text(self, text):
+ self.warning_text.SetValue(text)
+
+ def clear(self):
+ self.warning_text.SetValue("")
diff --git a/lib/stitches/meander_fill.py b/lib/stitches/meander_fill.py
index f6106606..c1308bf4 100644
--- a/lib/stitches/meander_fill.py
+++ b/lib/stitches/meander_fill.py
@@ -1,7 +1,6 @@
from itertools import combinations
import networkx as nx
-from inkex import errormsg
from shapely.geometry import LineString, MultiPoint, Point
from shapely.ops import nearest_points
@@ -30,10 +29,8 @@ def meander_fill(fill, shape, original_shape, shape_index, starting_point, endin
graph = tile.to_graph(shape, fill.meander_scale, fill.meander_angle)
if not graph:
- label = fill.node.label or fill.node.get_id()
- errormsg(_('%s: Could not build graph for meander stitching. Try to enlarge your shape or '
- 'scale your meander pattern down.') % label)
- return []
+ fill.fatal(_('Could not build graph for meander stitching. Try to enlarge your shape or '
+ 'scale your meander pattern down.'))
debug.log_graph(graph, 'Meander graph')
ensure_connected(graph)