inkstitch/inkstitch.py

221 wiersze
9.2 KiB
Python
Czysty Zwykły widok Historia

2021-03-12 04:17:19 +00:00
# Authors: see git history
#
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
2023-12-17 22:03:39 +00:00
import os
import sys
2023-12-17 22:03:39 +00:00
from pathlib import Path
2023-12-29 15:25:17 +00:00
import configparser
import lib.debug_utils as debug_utils
2023-12-17 22:03:39 +00:00
SCRIPTDIR = Path(__file__).parent.absolute()
if len(sys.argv) < 2:
2023-12-29 15:25:17 +00:00
# no arguments - prevent accidentally running this script
print("No arguments given, continue without arguments?")
answer = input("Continue? [y/N] ")
if answer.lower() != 'y':
exit(1)
2023-12-29 15:25:17 +00:00
running_as_frozen = getattr(sys, 'frozen', None) is not None # check if running from pyinstaller bundle
2023-12-29 15:25:17 +00:00
ini = configparser.ConfigParser()
ini.read(SCRIPTDIR / "DEVEL.ini") # read DEVEL.ini file if exists
2023-12-17 22:03:39 +00:00
2023-12-29 15:25:17 +00:00
# prefer pip installed inkex over inkscape bundled inkex, pip version is bundled with Inkstitch
prefere_pip_inkex = ini.getboolean("LIBRARY","prefer_pip_inkex", fallback=True)
# check if running from inkscape, given by environment variable
if os.environ.get('INKSTITCH_OFFLINE_SCRIPT', '').lower() in ['true', '1', 'yes', 'y']:
running_from_inkscape = False
else:
running_from_inkscape = True
2023-12-17 22:03:39 +00:00
debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace()) # check if debugger is active on startup
debug_type = 'none'
profile_type = 'none'
if not running_as_frozen: # debugging/profiling only in development mode
2023-12-29 15:25:17 +00:00
# define names of files used by offline Bash script
bash_file_base = ini.get("DEBUG","bash_file_base", fallback="debug_inkstitch")
bash_name = Path(bash_file_base).with_suffix(".sh") # Path object
bash_svg = Path(bash_file_base).with_suffix(".svg") # Path object
# specify debugger type
# - if script was already started from debugger then don't read debug file
2023-12-29 15:25:17 +00:00
if not debug_active:
debug_type = ini.get("DEBUG","debugger", fallback="none") # debugger type vscode, pycharm, pydevd, none
2023-12-29 15:25:17 +00:00
# specify profiler type
profile_type = ini.get("PROFILE","profiler", fallback="none") # profiler type cprofile, profile, pyinstrument, none
# process creation of the Bash script
if running_from_inkscape:
2023-12-29 15:25:17 +00:00
if ini.getboolean("DEBUG","create_bash_script", fallback=False): # create script only if enabled in DEVEL.ini
debug_utils.write_offline_debug_script(SCRIPTDIR, bash_name, bash_svg)
2023-12-29 15:25:17 +00:00
# disable debugger when running from inkscape
disable_from_inkscape = ini.getboolean("DEBUG","disable_from_inkscape", fallback=False)
if disable_from_inkscape:
debug_type = 'none' # do not start debugger when running from inkscape
if prefere_pip_inkex and 'PYTHONPATH' in os.environ:
# see static void set_extensions_env() in inkscape/src/inkscape-main.cpp
# 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')]
# remove nonexisting paths
pythonpath = [p for p in pythonpath if os.path.exists(p)]
# 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 ?
# move inkscape extensions path to the end of sys.path
# sys.path.remove(extensions_path)
# sys.path.append(extensions_path)
# >> ------------------------------------------------- <<
2023-12-17 22:03:39 +00:00
import logging
from argparse import ArgumentParser
from io import StringIO
2023-09-07 17:25:47 +00:00
from lib.exceptions import InkstitchException, format_uncaught_exception
from inkex import errormsg
from lxml.etree import XMLSyntaxError
2019-03-28 18:47:05 +00:00
import lib.debug as debug
2020-04-20 18:52:50 +00:00
from lib import extensions
from lib.i18n import _
2023-09-07 17:25:47 +00:00
from lib.utils import restore_stderr, save_stderr
2023-12-17 22:03:39 +00:00
# file DEBUG exists next to inkstitch.py - enabling debug mode depends on value of debug_type in DEBUG file
if debug_type != 'none':
2023-12-29 15:25:17 +00:00
debug_file = ini.get("DEBUG","debug_file", fallback="debug.log")
wait_attach = ini.getboolean("DEBUG","wait_attach", fallback=True) # currently only for vscode
debug.enable(debug_type, debug_file, wait_attach)
2023-12-17 22:03:39 +00:00
# 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:
2022-01-11 22:11:57 +00:00
import warnings
warnings.filterwarnings('ignore')
2023-12-17 22:03:39 +00:00
# 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)
2023-12-17 22:03:39 +00:00
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)
2023-12-17 22:03:39 +00:00
# pop '--extension' from arguments and generate extension class name from extension name
2018-04-29 02:14:23 +00:00
parser = ArgumentParser()
parser.add_argument("--extension")
my_args, remaining_args = parser.parse_known_args()
2018-04-29 02:14:23 +00:00
extension_name = my_args.extension
2018-07-29 00:40:14 +00:00
# example: foo_bar_baz -> FooBarBaz
extension_class_name = extension_name.title().replace("_", "")
extension_class = getattr(extensions, extension_class_name)
2023-12-17 22:03:39 +00:00
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 and hide GTK spam
2023-12-17 22:03:39 +00:00
if debug_active or profile_type != "none": # if debug or profile mode
2023-12-29 15:25:17 +00:00
profile_file_base = ini.get("PROFILE","profile_file_base", fallback="debug_profile")
profile_path = SCRIPTDIR / profile_file_base # Path object
2023-12-17 22:03:39 +00:00
if profile_type == 'none':
extension.run(args=remaining_args)
elif profile_type == 'cprofile':
import cProfile
import pstats
profiler = cProfile.Profile()
2023-12-17 22:03:39 +00:00
profiler.enable()
extension.run(args=remaining_args)
profiler.disable()
2023-12-17 22:03:39 +00:00
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()
2023-12-29 15:25:17 +00:00
print(f"profiling stats written to '{profile_path.name}' and '{profile_path.name}.prof'. Use snakeviz to see it.", file=sys.stderr)
2023-12-17 22:03:39 +00:00
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())
2023-12-29 15:25:17 +00:00
print(f"profiling stats written to '{profile_path.name}'. Use browser to see it.", file=sys.stderr)
2023-12-17 22:03:39 +00:00
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)
2023-09-07 17:25:47 +00:00
except InkstitchException as exc:
errormsg(str(exc))
except Exception:
2023-09-07 17:25:47 +00:00
errormsg(format_uncaught_exception())
sys.exit(1)
finally:
restore_stderr()
if shapely_errors.tell():
errormsg(shapely_errors.getvalue())
2023-09-07 17:25:47 +00:00
sys.exit(0)