1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
|
# 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 os
import sys
import lib.debug_utils as debug_utils
from pathlib import Path
SCRIPTDIR = Path(__file__).parent.absolute()
if len(sys.argv) < 2:
exit(1) # no arguments - prevent uncidentally running this script
running_as_frozen = getattr(sys, 'frozen', None) is not None # check if running from pyinstaller bundle
running_from_inkscape = '.ink.svg' not in sys.argv # inkscape never starts extension with .ink.svg file in args
# running_from_inkscape = True # for testing
debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace()) # check if debugger is active on startup
debug_file = SCRIPTDIR / "DEBUG"
debug_type = 'none'
profile_file = SCRIPTDIR / "PROFILE"
profile_type = 'none'
# print(f"debug_type:'{debug_type}' profile_type:'{profile_type}'", file=sys.stderr) # for testing
# if script was already started from debugger then don't read debug file
if not running_as_frozen and not debug_active and os.path.exists(debug_file):
debug_type = debug_utils.parse_file(debug_file) # read type of debugger from debug_file DEBUG
if debug_type == 'none': # for better backward compatibility
print(f"Debug file exists but no debugger type found in '{debug_file.name}'", file=sys.stderr)
if os.path.exists(profile_file):
profile_type = debug_utils.parse_file(profile_file) # read type of profiler from profile_file PROFILE
if profile_type == 'none': # for better backward compatibility
print(f"Profile file exists but no profiler type found in '{profile_file.name}'", file=sys.stderr)
if running_from_inkscape:
if debug_type.endswith('-script'): # if offline debugging just create script for later debugging
debug_utils.write_offline_debug_script(SCRIPTDIR)
debug_type = 'none' # do not start debugger when running from inkscape
else: # not running from inkscape
if debug_type.endswith('-script'): # remove '-script' to propely initialize debugger packages for each editor
debug_type = debug_type.replace('-script', '')
if not running_as_frozen:
# When running in development mode, we prefer inkex installed by pip, not the one bundled with Inkscape.
# - move inkscape extensions path to the end of sys.path
# - we compare PYTHONPATH with sys.path and move PYTHONPATH to the end of sys.path
# - also user inkscape extensions path is moved to the end of sys.path - may cause problems?
# - path for deprecated-simple are removed from sys.path, will be added later by importing inkex
# PYTHONPATH to list
pythonpath = os.environ.get('PYTHONPATH', '').split(os.pathsep)
# remove pythonpath from sys.path
sys.path = [p for p in sys.path if p not in pythonpath]
# remove deprecated-simple, it will be added later by importing inkex
pythonpath = [p for p in pythonpath if not p.endswith('deprecated-simple')]
# add pythonpath to the end of sys.path
sys.path.extend(pythonpath)
# >> should be removed after previous code was tested <<
# if sys.platform == "darwin":
# extensions_path = "/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions" # Mac
# else:
# extensions_path = "/usr/share/inkscape/extensions" # Linux
# # windows not solved
# move inkscape extensions path to the end of sys.path
# sys.path.remove(extensions_path)
# sys.path.append(extensions_path)
# >> ------------------------------------------------- <<
import logging
from argparse import ArgumentParser
from io import StringIO
from lib.exceptions import InkstitchException, format_uncaught_exception
from inkex import errormsg
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
# file DEBUG exists next to inkstitch.py - enabling debug mode depends on value of debug_type in DEBUG file
if debug_type != 'none':
debug.enable(debug_type)
# check if debugger is really activated
debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace())
# ignore warnings in releases - see warnings.warn()
if running_as_frozen or not debug_active:
import warnings
warnings.filterwarnings('ignore')
# set logger for shapely
logger = logging.getLogger('shapely.geos') # attach logger of shapely, from ver 2.0.0 all logs are exceptions
logger.setLevel(logging.DEBUG)
shapely_errors = StringIO() # in memory file to store shapely errors
ch = logging.StreamHandler(shapely_errors)
ch.setLevel(logging.DEBUG)
formatter = logging.Formatter('%(name)s - %(levelname)s - %(message)s')
ch.setFormatter(formatter)
logger.addHandler(ch)
# pop '--extension' from arguments and generate extension class name from extension name
parser = ArgumentParser()
parser.add_argument("--extension")
my_args, remaining_args = parser.parse_known_args()
extension_name = my_args.extension
# example: foo_bar_baz -> FooBarBaz
extension_class_name = extension_name.title().replace("_", "")
extension_class = getattr(extensions, extension_class_name)
extension = extension_class() # create instance of extension class - call __init__ method
# extension run(), but we differentiate between debug and normal mode
# - in debug or profile mode we run extension or profile extension
# - in normal mode we run extension in try/except block to catch all exceptions
if debug_active or profile_type != "none": # if debug or profile mode
print(f"Extension:'{extension_name}' Debug active:{debug_active} type:'{debug_type}' "
f"Profile type:'{profile_type}'", file=sys.stderr)
profile_path = SCRIPTDIR / "profile_stats"
if profile_type == 'none':
extension.run(args=remaining_args)
elif profile_type == 'cprofile':
import cProfile
import pstats
profiler = cProfile.Profile()
profiler.enable()
extension.run(args=remaining_args)
profiler.disable()
profiler.dump_stats(profile_path.with_suffix(".prof")) # can be read by 'snakeviz -s' or 'pyprof2calltree'
with open(profile_path, 'w') as stats_file:
stats = pstats.Stats(profiler, stream=stats_file)
stats.sort_stats(pstats.SortKey.CUMULATIVE)
stats.print_stats()
print(f"profiling stats written to '{profile_path.name}' and '{profile_path.name}.prof'", file=sys.stderr)
elif profile_type == 'profile':
import profile
import pstats
profiler = profile.Profile()
profiler.run('extension.run(args=remaining_args)')
profiler.dump_stats(profile_path.with_suffix(".prof")) # can be read by 'snakeviz' or 'pyprof2calltree' - seems broken
with open(profile_path, 'w') as stats_file:
stats = pstats.Stats(profiler, stream=stats_file)
stats.sort_stats(pstats.SortKey.CUMULATIVE)
stats.print_stats()
print(f"profiling stats written to '{profile_path.name}'", file=sys.stderr)
elif profile_type == 'pyinstrument':
import pyinstrument
profiler = pyinstrument.Profiler()
profiler.start()
extension.run(args=remaining_args)
profiler.stop()
profile_path = SCRIPTDIR / "profile_stats.html"
with open(profile_path, 'w') as stats_file:
stats_file.write(profiler.output_html())
print(f"profiling stats written to '{profile_path.name}'", file=sys.stderr)
else: # if not debug nor profile mode
save_stderr() # hide GTK spam
exception = None
try:
extension.run(args=remaining_args)
except (SystemExit, KeyboardInterrupt):
raise
except XMLSyntaxError:
msg = _("Ink/Stitch cannot read your SVG file. "
"This is often the case when you use a file which has been created with Adobe Illustrator.")
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:
errormsg(format_uncaught_exception())
sys.exit(1)
finally:
restore_stderr()
if shapely_errors.tell():
errormsg(shapely_errors.getvalue())
sys.exit(0)
|