kopia lustrzana https://github.com/inkstitch/inkstitch
Porównaj commity
13 Commity
7fcc69bbca
...
13b879d100
Autor | SHA1 | Data |
---|---|---|
karnigen | 13b879d100 | |
karnigen | 5cbe8268a4 | |
karnigen | c08f4600d4 | |
karnigen | b2325f9436 | |
karnigen | cb1eb506f3 | |
karnigen | c63086307a | |
karnigen | 7c2e6d286f | |
karnigen | 47fdfa41cf | |
karnigen | 1080eceb14 | |
karnigen | 5f6989886b | |
karnigen | 306c7c3425 | |
karnigen | 42c03aa80f | |
karnigen | 6bf31a67ac |
|
@ -6,6 +6,7 @@ __pycache__
|
||||||
*.zip
|
*.zip
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.po
|
*.po
|
||||||
|
*.log
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
locales/
|
locales/
|
||||||
|
@ -22,10 +23,15 @@ flaskserverport.json
|
||||||
electron/yarn.lock
|
electron/yarn.lock
|
||||||
|
|
||||||
# debug and profile files
|
# debug and profile files
|
||||||
/DEBUG.ini
|
logs/
|
||||||
|
/DEBUG.toml
|
||||||
|
/LOGGING.toml
|
||||||
|
/LOGGING[0-9]*.toml
|
||||||
/debug*
|
/debug*
|
||||||
/.debug*
|
/.debug*
|
||||||
# old debug files
|
|
||||||
|
# old debug files - to be removed
|
||||||
|
/DEBUG*.ini
|
||||||
/DEBUG
|
/DEBUG
|
||||||
/PROFILE
|
/PROFILE
|
||||||
/profile*
|
/profile*
|
||||||
|
|
|
@ -1,46 +0,0 @@
|
||||||
[LIBRARY]
|
|
||||||
;;; use the pip installed version of inkex.py, default: True
|
|
||||||
; prefer_pip_inkex = False
|
|
||||||
|
|
||||||
[DEBUG]
|
|
||||||
;;; select one active debug_type, default: none
|
|
||||||
; debug_type = vscode
|
|
||||||
; debug_type = pycharm
|
|
||||||
; debug_type = pydev
|
|
||||||
|
|
||||||
;;; enable debugger, see cmd line arg -d, default: False
|
|
||||||
; debug_enable = True
|
|
||||||
|
|
||||||
;;; debug log output to file even if debugger is not enabled, default: False
|
|
||||||
; debug_to_file = True
|
|
||||||
|
|
||||||
;;; disable debugger when calling from inkscape, default: False
|
|
||||||
; disable_from_inkscape = True
|
|
||||||
|
|
||||||
;;; wait for debugger to attach (vscode), default: True
|
|
||||||
; wait_attach = False
|
|
||||||
|
|
||||||
;;; debug log file, default: debug.log
|
|
||||||
; debug_log_file = debug.log
|
|
||||||
|
|
||||||
;;; debug file for graph related things, default: debug.svg
|
|
||||||
; debug_svg_file = debug.svg
|
|
||||||
|
|
||||||
;;; creation of bash script, default: False
|
|
||||||
; create_bash_script = True
|
|
||||||
|
|
||||||
;;; base name for bash script, default: debug_inkstitch
|
|
||||||
; bash_file_base = debug_inkstitch
|
|
||||||
|
|
||||||
[PROFILE]
|
|
||||||
;;; select one active profiler_type, default: none
|
|
||||||
; profiler_type = cprofile
|
|
||||||
; profiler_type = profile
|
|
||||||
; profiler_type = pyinstrument
|
|
||||||
|
|
||||||
;;; enable profiler, see cmd line arg -p, default: False
|
|
||||||
; profile_enable = True
|
|
||||||
|
|
||||||
;;; base name for profile output files, default: debug_profile
|
|
||||||
; profile_file_base = debug_profile
|
|
||||||
|
|
|
@ -0,0 +1,51 @@
|
||||||
|
### customize this file and save as DEBUG.toml
|
||||||
|
|
||||||
|
[LIBRARY]
|
||||||
|
### use the pip installed version of inkex.py, default: true
|
||||||
|
# prefer_pip_inkex = false
|
||||||
|
|
||||||
|
[LOGGING]
|
||||||
|
### logging configuration file, default: none - use implicit DEBUG logging to inkstitch.log
|
||||||
|
### we may have multiple configurations: LOGGING.toml, LOGGING1.toml, LOGGING2.toml, etc.
|
||||||
|
# log_config_file = "LOGGING.toml"
|
||||||
|
|
||||||
|
### disable globally logging: default: false - using logging.disable()
|
||||||
|
### reenable logging.disable(0)
|
||||||
|
# disable_logging = true
|
||||||
|
|
||||||
|
[DEBUG]
|
||||||
|
### simulate frozen mode, overwrite running_as_frozen in inkstitch.py, default: false
|
||||||
|
# force_frozen = true
|
||||||
|
|
||||||
|
### select one active debug_type, default: "none"
|
||||||
|
# debug_type = "vscode"
|
||||||
|
# debug_type = "pycharm"
|
||||||
|
# debug_type = "pydev"
|
||||||
|
|
||||||
|
### enable debugger, see cmd line arg -d, default: false
|
||||||
|
# debug_enable = true
|
||||||
|
|
||||||
|
### disable debugger when calling from inkscape, default: false
|
||||||
|
# disable_from_inkscape = true
|
||||||
|
|
||||||
|
### wait for debugger to attach (vscode), default: true
|
||||||
|
# wait_attach = false
|
||||||
|
|
||||||
|
### creation of bash script, default: false
|
||||||
|
# create_bash_script = true
|
||||||
|
|
||||||
|
### base name for bash script, default: "debug_inkstitch"
|
||||||
|
# bash_file_base = "debug_inkstitch"
|
||||||
|
|
||||||
|
[PROFILE]
|
||||||
|
### select one active profiler_type, default: "none"
|
||||||
|
# profiler_type = "cprofile"
|
||||||
|
# profiler_type = "profile"
|
||||||
|
# profiler_type = "pyinstrument"
|
||||||
|
|
||||||
|
### enable profiler, see cmd line arg -p, default: false
|
||||||
|
# profile_enable = true
|
||||||
|
|
||||||
|
### base name for profile output files, default: "debug_profile"
|
||||||
|
# profile_file_base = "logs/debug_profile"
|
||||||
|
|
|
@ -0,0 +1,69 @@
|
||||||
|
### customize this file and save as LOGGING.toml
|
||||||
|
### logging/warning template for inkstitch
|
||||||
|
### format: toml
|
||||||
|
### enable config file in DEBUG.toml: log_config_file
|
||||||
|
|
||||||
|
### warnings.simplefilter(action), default: "default"
|
||||||
|
### - possible values: "error", "ignore", "always", "default", "module", "once"
|
||||||
|
warnings_action = "default"
|
||||||
|
|
||||||
|
### logging.captureWarnings() default: true
|
||||||
|
### - possible values: true, false
|
||||||
|
warnings_capture = true
|
||||||
|
|
||||||
|
### mandatory, must be an integer and 1
|
||||||
|
version = 1
|
||||||
|
|
||||||
|
### disable loggers activated before the configuration is loaded
|
||||||
|
disable_existing_loggers = false
|
||||||
|
|
||||||
|
### define the loggers, handlers, formatters, filters
|
||||||
|
[filters]
|
||||||
|
|
||||||
|
[formatters.simple]
|
||||||
|
format = "%(asctime)s [%(levelname)s]: %(filename)s.%(funcName)s: %(message)s"
|
||||||
|
|
||||||
|
[formatters.debug]
|
||||||
|
format = "%(asctime)s %(message)s"
|
||||||
|
|
||||||
|
|
||||||
|
[handlers.file_inkstitch]
|
||||||
|
class = "logging.FileHandler"
|
||||||
|
formatter = "simple"
|
||||||
|
filename = "%(SCRIPTDIR)s/logs/inkstitch.log"
|
||||||
|
mode = "w"
|
||||||
|
|
||||||
|
[handlers.file_inkstitch_debug]
|
||||||
|
class = "logging.FileHandler"
|
||||||
|
formatter = "debug"
|
||||||
|
filename = "%(SCRIPTDIR)s/logs/inkstitch_debug.log"
|
||||||
|
mode = "w"
|
||||||
|
|
||||||
|
[handlers.file_root]
|
||||||
|
class = "logging.FileHandler"
|
||||||
|
formatter = "simple"
|
||||||
|
filename = "%(SCRIPTDIR)s/logs/inkstitch_root.log"
|
||||||
|
mode = "w"
|
||||||
|
|
||||||
|
|
||||||
|
### used for: logger = logging.getLogger("inkstitch")
|
||||||
|
### logger = logging.getLogger("inkstitch.xxx") where xxx is not specified in this config file
|
||||||
|
### - highest level logger for all 'inkstitch.*' loggers
|
||||||
|
[loggers.inkstitch]
|
||||||
|
level = "DEBUG"
|
||||||
|
handlers = [ "file_inkstitch",]
|
||||||
|
propagate = false
|
||||||
|
|
||||||
|
### used for: logger = logging.getLogger("inkstitch.debug")
|
||||||
|
### - use quotes for the logger name with dots, otherwise it will be treated as a table subsection
|
||||||
|
### - [loggers.inkstitch.debug] is not the same as [loggers.'inkstitch.debug']
|
||||||
|
[loggers.'inkstitch.debug']
|
||||||
|
level = "DEBUG" # to enable the logger, seems to be the default
|
||||||
|
# level = "CRITICAL" # to disable the logger
|
||||||
|
handlers = [ "file_inkstitch_debug",]
|
||||||
|
propagate = false
|
||||||
|
|
||||||
|
### root - loggers not specified in this config file will be managed by this logger
|
||||||
|
[loggers.root]
|
||||||
|
level = "DEBUG"
|
||||||
|
handlers = [ "file_root",]
|
92
inkstitch.py
92
inkstitch.py
|
@ -6,15 +6,46 @@
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path # to work with paths as objects
|
from pathlib import Path # to work with paths as objects
|
||||||
import configparser # to read DEBUG.ini
|
|
||||||
from argparse import ArgumentParser # to parse arguments and remove --extension
|
from argparse import ArgumentParser # to parse arguments and remove --extension
|
||||||
|
|
||||||
import lib.debug_utils as debug_utils
|
if sys.version_info >= (3, 11):
|
||||||
|
import tomllib # built-in in Python 3.11+
|
||||||
|
else:
|
||||||
|
import tomli as tomllib
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
import lib.debug.utils as debug_utils
|
||||||
|
import lib.debug.logging as debug_logging
|
||||||
|
from lib.debug.utils import safe_get # mimic get method of dict with default value
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
SCRIPTDIR = Path(__file__).parent.absolute()
|
SCRIPTDIR = Path(__file__).parent.absolute()
|
||||||
|
|
||||||
|
logger = logging.getLogger("inkstitch") # create module logger with name 'inkstitch'
|
||||||
|
|
||||||
|
# TODO --- temporary --- catch old DEBUG.ini file and inform user to reformat it to DEBUG.toml
|
||||||
|
old_debug_ini = SCRIPTDIR / "DEBUG.ini"
|
||||||
|
if old_debug_ini.exists():
|
||||||
|
print("ERROR: old DEBUG.ini exists, please reformat it to DEBUG.toml and remove DEBUG.ini file", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
# --- end of temporary ---
|
||||||
|
|
||||||
|
debug_toml = SCRIPTDIR / "DEBUG.toml"
|
||||||
|
if debug_toml.exists():
|
||||||
|
with debug_toml.open("rb") as f:
|
||||||
|
ini = tomllib.load(f) # read DEBUG.toml file if exists, otherwise use default values in ini object
|
||||||
|
else:
|
||||||
|
ini = {}
|
||||||
|
# --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
running_as_frozen = getattr(sys, 'frozen', None) is not None # check if running from pyinstaller bundle
|
running_as_frozen = getattr(sys, 'frozen', None) is not None # check if running from pyinstaller bundle
|
||||||
|
|
||||||
|
if not running_as_frozen: # override running_as_frozen from DEBUG.toml - for testing
|
||||||
|
if safe_get(ini, "DEBUG", "force_frozen", default=False):
|
||||||
|
running_as_frozen = True
|
||||||
|
|
||||||
if len(sys.argv) < 2:
|
if len(sys.argv) < 2:
|
||||||
# no arguments - prevent accidentally running this script
|
# no arguments - prevent accidentally running this script
|
||||||
msg = "No arguments given, exiting!" # without gettext localization see _()
|
msg = "No arguments given, exiting!" # without gettext localization see _()
|
||||||
|
@ -26,13 +57,14 @@ if len(sys.argv) < 2:
|
||||||
dlg.ShowModal()
|
dlg.ShowModal()
|
||||||
dlg.Destroy()
|
dlg.Destroy()
|
||||||
except ImportError:
|
except ImportError:
|
||||||
print(msg)
|
print(msg, file=sys.stderr)
|
||||||
else:
|
else:
|
||||||
print(msg)
|
print(msg, file=sys.stderr)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
ini = configparser.ConfigParser()
|
# activate logging - must be done before any logging is done
|
||||||
ini.read(SCRIPTDIR / "DEBUG.ini") # read DEBUG.ini file if exists
|
debug_logging.activate_logging(running_as_frozen, ini, SCRIPTDIR)
|
||||||
|
# --------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
# check if running from inkscape, given by environment variable
|
# check if running from inkscape, given by environment variable
|
||||||
if os.environ.get('INKSTITCH_OFFLINE_SCRIPT', '').lower() in ['true', '1', 'yes', 'y']:
|
if os.environ.get('INKSTITCH_OFFLINE_SCRIPT', '').lower() in ['true', '1', 'yes', 'y']:
|
||||||
|
@ -40,6 +72,7 @@ if os.environ.get('INKSTITCH_OFFLINE_SCRIPT', '').lower() in ['true', '1', 'yes'
|
||||||
else:
|
else:
|
||||||
running_from_inkscape = True
|
running_from_inkscape = True
|
||||||
|
|
||||||
|
# initialize debug and profiler type
|
||||||
debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace()) # check if debugger is active on startup
|
debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace()) # check if debugger is active on startup
|
||||||
debug_type = 'none'
|
debug_type = 'none'
|
||||||
profiler_type = 'none'
|
profiler_type = 'none'
|
||||||
|
@ -48,54 +81,42 @@ if not running_as_frozen: # debugging/profiling only in development mode
|
||||||
# specify debugger type
|
# specify debugger type
|
||||||
# but if script was already started from debugger then don't read debug type from ini file or cmd line
|
# but if script was already started from debugger then don't read debug type from ini file or cmd line
|
||||||
if not debug_active:
|
if not debug_active:
|
||||||
debug_type = debug_utils.resole_debug_type(ini) # read debug type from ini file or cmd line
|
debug_type = debug_utils.resolve_debug_type(ini) # read debug type from ini file or cmd line
|
||||||
|
|
||||||
profile_type = debug_utils.resole_profile_type(ini) # read profile type from ini file or cmd line
|
profiler_type = debug_utils.resolve_profiler_type(ini) # read profile type from ini file or cmd line
|
||||||
|
|
||||||
if running_from_inkscape:
|
if running_from_inkscape:
|
||||||
# process creation of the Bash script - should be done before sys.path is modified, see below in prefere_pip_inkex
|
# process creation of the Bash script - should be done before sys.path is modified, see below in prefere_pip_inkex
|
||||||
if ini.getboolean("DEBUG", "create_bash_script", fallback=False): # create script only if enabled in DEBUG.ini
|
if safe_get(ini, "DEBUG", "create_bash_script", default=False): # create script only if enabled in DEBUG.toml
|
||||||
debug_utils.write_offline_debug_script(SCRIPTDIR, ini)
|
debug_utils.write_offline_debug_script(SCRIPTDIR, ini)
|
||||||
|
|
||||||
# disable debugger when running from inkscape
|
# disable debugger when running from inkscape
|
||||||
disable_from_inkscape = ini.getboolean("DEBUG", "disable_from_inkscape", fallback=False)
|
disable_from_inkscape = safe_get(ini, "DEBUG", "disable_from_inkscape", default=False)
|
||||||
if disable_from_inkscape:
|
if disable_from_inkscape:
|
||||||
debug_type = 'none' # do not start debugger when running from inkscape
|
debug_type = 'none' # do not start debugger when running from inkscape
|
||||||
|
|
||||||
# prefer pip installed inkex over inkscape bundled inkex, pip version is bundled with Inkstitch
|
# prefer pip installed inkex over inkscape bundled inkex, pip version is bundled with Inkstitch
|
||||||
# - must be be done before importing inkex
|
# - must be be done before importing inkex
|
||||||
prefere_pip_inkex = ini.getboolean("LIBRARY", "prefer_pip_inkex", fallback=True)
|
prefere_pip_inkex = safe_get(ini, "LIBRARY", "prefer_pip_inkex", default=True)
|
||||||
if prefere_pip_inkex and 'PYTHONPATH' in os.environ:
|
if prefere_pip_inkex and 'PYTHONPATH' in os.environ:
|
||||||
debug_utils.reorder_sys_path()
|
debug_utils.reorder_sys_path()
|
||||||
|
|
||||||
# enabling of debug depends on value of debug_type in DEBUG.ini file
|
# enabling of debug depends on value of debug_type in DEBUG.toml file
|
||||||
if debug_type != 'none':
|
if debug_type != 'none':
|
||||||
from lib.debug import debug # import global variable debug - don't import whole module
|
from lib.debug.debugger import init_debugger
|
||||||
debug.enable(debug_type, SCRIPTDIR, ini)
|
init_debugger(debug_type, ini)
|
||||||
# check if debugger is really activated
|
# check if debugger is really activated
|
||||||
debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace())
|
debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace())
|
||||||
|
|
||||||
# warnings are used by some modules, we want to ignore them all in release
|
# activate logging for svg
|
||||||
# - see warnings.warn()
|
# we need to import only after possible modification of sys.path, we disable here flake8 E402
|
||||||
if running_as_frozen or not debug_active:
|
from lib.debug import debug # noqa: E402 # import global variable debug - don't import whole module
|
||||||
import warnings
|
debug.enable() # perhaps it would be better to find a more relevant name; in fact, it's about logging and svg creation.
|
||||||
warnings.filterwarnings('ignore')
|
|
||||||
|
|
||||||
# TODO - check if this is still needed for shapely, apparently shapely now uses only exceptions instead of io.
|
# log startup info
|
||||||
# all logs were removed from version 2.0.0 and above
|
debug_logging.startup_info(logger, SCRIPTDIR, running_as_frozen, running_from_inkscape, debug_active, debug_type, profiler_type)
|
||||||
# ---- plan to remove this in future ----
|
|
||||||
# import logging # to set logger for shapely
|
# --------------------------------------------------------------------------------------------
|
||||||
# from io import StringIO # to store shapely errors
|
|
||||||
# set logger for shapely - for old versions of shapely
|
|
||||||
# logger = logging.getLogger('shapely.geos') # attach logger of shapely
|
|
||||||
# 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)
|
|
||||||
# ---- plan to remove this in future ----
|
|
||||||
|
|
||||||
# pop '--extension' from arguments and generate extension class name from extension name
|
# pop '--extension' from arguments and generate extension class name from extension name
|
||||||
# example: --extension=params will instantiate Params() class from lib.extensions.
|
# example: --extension=params will instantiate Params() class from lib.extensions.
|
||||||
|
@ -151,7 +172,4 @@ else: # if not debug nor profile mode
|
||||||
finally:
|
finally:
|
||||||
restore_stderr()
|
restore_stderr()
|
||||||
|
|
||||||
# if shapely_errors.tell(): # see above plan to remove this in future for shapely
|
|
||||||
# errormsg(shapely_errors.getvalue())
|
|
||||||
|
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
import errno
|
import errno
|
||||||
import logging
|
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
@ -66,9 +65,6 @@ class APIServer(Thread):
|
||||||
self.flask_server.shutdown()
|
self.flask_server.shutdown()
|
||||||
self.server_thread.join()
|
self.server_thread.join()
|
||||||
|
|
||||||
def disable_logging(self):
|
|
||||||
logging.getLogger('werkzeug').setLevel(logging.ERROR)
|
|
||||||
|
|
||||||
# https://github.com/aluo-x/Learning_Neural_Acoustic_Fields/blob/master/train.py
|
# https://github.com/aluo-x/Learning_Neural_Acoustic_Fields/blob/master/train.py
|
||||||
# https://github.com/pytorch/pytorch/issues/71029
|
# https://github.com/pytorch/pytorch/issues/71029
|
||||||
def find_free_port(self):
|
def find_free_port(self):
|
||||||
|
@ -77,8 +73,6 @@ class APIServer(Thread):
|
||||||
return s.getsockname()[1]
|
return s.getsockname()[1]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.disable_logging()
|
|
||||||
|
|
||||||
self.host = "127.0.0.1"
|
self.host = "127.0.0.1"
|
||||||
self.port = self.find_free_port()
|
self.port = self.find_free_port()
|
||||||
self.flask_server = make_server(self.host, self.port, self.app)
|
self.flask_server = make_server(self.host, self.port, self.app)
|
||||||
|
|
421
lib/debug.py
421
lib/debug.py
|
@ -1,421 +0,0 @@
|
||||||
# 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 atexit # to save svg file on exit
|
|
||||||
import socket # to check if debugger is running
|
|
||||||
import time # to measure time of code block, use time.monotonic() instead of time.time()
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
from contextlib import contextmanager # to measure time of with block
|
|
||||||
import configparser # to read DEBUG.ini
|
|
||||||
from pathlib import Path # to work with paths as objects
|
|
||||||
|
|
||||||
import inkex
|
|
||||||
from lxml import etree # to create svg file
|
|
||||||
|
|
||||||
from .svg import line_strings_to_path
|
|
||||||
from .svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
|
|
||||||
|
|
||||||
|
|
||||||
# decorator to check if debugging is enabled
|
|
||||||
# - if debug is not enabled then decorated function is not called
|
|
||||||
def check_enabled(func):
|
|
||||||
def decorated(self, *args, **kwargs):
|
|
||||||
if self.enabled:
|
|
||||||
return func(self, *args, **kwargs)
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
|
|
||||||
|
|
||||||
# unwrapping = provision for functions as arguments
|
|
||||||
# - if argument is callable then it is called and return value is used as argument
|
|
||||||
# otherwise argument is returned as is
|
|
||||||
def _unwrap(arg):
|
|
||||||
if callable(arg):
|
|
||||||
return arg()
|
|
||||||
else:
|
|
||||||
return arg
|
|
||||||
|
|
||||||
|
|
||||||
# decorator to unwrap arguments if they are callable
|
|
||||||
# eg: if argument is lambda function then it is called and return value is used as argument
|
|
||||||
def unwrap_arguments(func):
|
|
||||||
def decorated(self, *args, **kwargs):
|
|
||||||
unwrapped_args = [_unwrap(arg) for arg in args]
|
|
||||||
unwrapped_kwargs = {name: _unwrap(value) for name, value in kwargs.items()}
|
|
||||||
|
|
||||||
return func(self, *unwrapped_args, **unwrapped_kwargs)
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
|
|
||||||
|
|
||||||
class Debug(object):
|
|
||||||
"""Tools to help debug Ink/Stitch
|
|
||||||
|
|
||||||
This class contains methods to log strings and SVG elements. Strings are
|
|
||||||
logged to debug.log, and SVG elements are stored in debug.svg to aid in
|
|
||||||
debugging stitch algorithms.
|
|
||||||
|
|
||||||
All functionality is gated by self.enabled. If debugging is not enabled,
|
|
||||||
then debug calls will consume very few resources. Any method argument
|
|
||||||
can be a callable, in which case it is called and the return value is
|
|
||||||
logged instead. This way one can log potentially expensive expressions
|
|
||||||
by wrapping them in a lambda:
|
|
||||||
|
|
||||||
debug.log(lambda: some_expensive_function(some_argument))
|
|
||||||
|
|
||||||
The lambda is only called if debugging is enabled.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
self.debugger = None
|
|
||||||
self.wait_attach = True
|
|
||||||
self.enabled = False
|
|
||||||
self.last_log_time = None
|
|
||||||
self.current_layer = None
|
|
||||||
self.group_stack = []
|
|
||||||
|
|
||||||
def enable(self, debug_type, debug_dir: Path, ini: configparser.ConfigParser):
|
|
||||||
# initilize file names and other parameters from DEBUG.ini file
|
|
||||||
self.debug_dir = debug_dir # directory where debug files are stored
|
|
||||||
self.debug_log_file = ini.get("DEBUG", "debug_log_file", fallback="debug.log")
|
|
||||||
self.debug_svg_file = ini.get("DEBUG", "debug_svg_file", fallback="debug.svg")
|
|
||||||
self.wait_attach = ini.getboolean("DEBUG", "wait_attach", fallback=True) # currently only for vscode
|
|
||||||
|
|
||||||
if debug_type == 'none':
|
|
||||||
return
|
|
||||||
|
|
||||||
self.debugger = debug_type
|
|
||||||
self.enabled = True
|
|
||||||
self.init_log()
|
|
||||||
self.init_debugger()
|
|
||||||
self.init_svg()
|
|
||||||
|
|
||||||
def init_log(self):
|
|
||||||
self.log_file = self.debug_dir / self.debug_log_file
|
|
||||||
# delete old content
|
|
||||||
with self.log_file.open("w"):
|
|
||||||
pass
|
|
||||||
self.log("Debug logging enabled.")
|
|
||||||
|
|
||||||
# we intentionally disable flakes C901 - function is too complex, beacuse it is used only for debugging
|
|
||||||
# currently complexity is set 10 see 'make style', this means that function can have max 10 nested blocks, here we have more
|
|
||||||
# flake8: noqa: C901
|
|
||||||
def init_debugger(self):
|
|
||||||
# ### General debugging notes:
|
|
||||||
# 1. to enable debugging or profiling copy DEBUG_template.ini to DEBUG.ini and edit it
|
|
||||||
|
|
||||||
# ### How create bash script for offline debugging from console
|
|
||||||
# 1. in DEBUG.ini set create_bash_script = True
|
|
||||||
# 2. call inkstitch.py extension from inkscape to create bash script named by bash_file_base in DEBUG.ini
|
|
||||||
# 3. run bash script from console
|
|
||||||
|
|
||||||
# ### Enable debugging
|
|
||||||
# 1. set debug_type to one of - vscode, pycharm, pydev, see below for details
|
|
||||||
# debug_type = vscode - 'debugpy' for vscode editor
|
|
||||||
# debug_type = pycharm - 'pydevd-pycharm' for pycharm editor
|
|
||||||
# debug_type = pydev - 'pydevd' for eclipse editor
|
|
||||||
# 2. set debug_enable = True in DEBUG.ini
|
|
||||||
# or use command line argument -d in bash script
|
|
||||||
# or set environment variable INKSTITCH_DEBUG_ENABLE = True or 1 or yes or y
|
|
||||||
|
|
||||||
# ### Enable profiling
|
|
||||||
# 1. set profiler_type to one of - cprofile, profile, pyinstrument
|
|
||||||
# profiler_type = cprofile - 'cProfile' profiler
|
|
||||||
# profiler_type = profile - 'profile' profiler
|
|
||||||
# profiler_type = pyinstrument- 'pyinstrument' profiler
|
|
||||||
# 2. set profile_enable = True in DEBUG.ini
|
|
||||||
# or use command line argument -p in bash script
|
|
||||||
# or set environment variable INKSTITCH_PROFILE_ENABLE = True or 1 or yes or y
|
|
||||||
|
|
||||||
# ### Miscelaneous notes:
|
|
||||||
# - to disable debugger when running from inkscape set disable_from_inkscape = True in DEBUG.ini
|
|
||||||
# - to write debug output to file set debug_to_file = True in DEBUG.ini
|
|
||||||
# - to change various output file names see DEBUG.ini
|
|
||||||
# - to disable waiting for debugger to attach (vscode editor) set wait_attach = False in DEBUG.ini
|
|
||||||
# - to prefer inkscape version of inkex module over pip version set prefer_pip_inkex = False in DEBUG.ini
|
|
||||||
|
|
||||||
# ###
|
|
||||||
|
|
||||||
# ### How to debug Ink/Stitch with LiClipse:
|
|
||||||
#
|
|
||||||
# 1. Install LiClipse (liclipse.com) -- no need to install Eclipse first
|
|
||||||
# 2. Start debug server as described here: http://www.pydev.org/manual_adv_remote_debugger.html
|
|
||||||
# * follow the "Note:" to enable the debug server menu item
|
|
||||||
# 3. Copy and edit a file named "DEBUG.ini" from "DEBUG_template.ini" next to inkstitch.py in your git clone
|
|
||||||
# and set debug_type = pydev
|
|
||||||
# 4. Run any extension and PyDev will start debugging.
|
|
||||||
|
|
||||||
# ###
|
|
||||||
|
|
||||||
# ### To debug with PyCharm:
|
|
||||||
|
|
||||||
# You must use the PyCharm Professional Edition and _not_ the Community
|
|
||||||
# Edition. Jetbrains has chosen to make remote debugging a Pro feature.
|
|
||||||
# To debug Inkscape python extensions, the extension code and Inkscape run
|
|
||||||
# independently of PyCharm, and only communicate with the debugger via a
|
|
||||||
# TCP socket. Thus, debugging is "remote," even if it's on the same machine,
|
|
||||||
# connected via the loopback interface.
|
|
||||||
#
|
|
||||||
# 1. pip install pydev_pycharm
|
|
||||||
#
|
|
||||||
# pydev_pycharm is versioned frequently. Jetbrains suggests installing
|
|
||||||
# a version at least compatible with the current build. For example, if your
|
|
||||||
# PyCharm build, as found in menu PyCharm -> About Pycharm is 223.8617.48,
|
|
||||||
# you could do:
|
|
||||||
# pip install pydevd-pycharm~=223.8617.48
|
|
||||||
#
|
|
||||||
# 2. From the Pycharm "Run" menu, choose "Edit Configurations..." and create a new
|
|
||||||
# configuration. Set "IDE host name:" to "localhost" and "Port:" to 5678.
|
|
||||||
# You can leave the default settings for all other choices.
|
|
||||||
#
|
|
||||||
# 3. Touch a file named "DEBUG.ini" at the top of your git repo, as above
|
|
||||||
# set debug_type = pycharm
|
|
||||||
#
|
|
||||||
# 4. Create a symbolic link in the Inkscape extensions directory to the
|
|
||||||
# top-level directory of your git repo. On a mac, for example:
|
|
||||||
# cd ~/Library/Application\ Support/org.inkscape.Inkscape/config/inkscape/extensions/
|
|
||||||
# ln -s <full path to the top level of your Ink/Stitch git repo>
|
|
||||||
# On other architectures it may be:
|
|
||||||
# cd ~/.config/inkscape/extensions
|
|
||||||
# ln -s <full path to the top level of your Ink/Stitch git repo>
|
|
||||||
# Remove any other Ink/Stitch files or references to Ink/Stitch from the
|
|
||||||
# extensions directory, or you'll see duplicate entries in the Ink/Stitch
|
|
||||||
# extensions menu in Inkscape.
|
|
||||||
#
|
|
||||||
# 5. In Pycharm, either click on the green "bug" icon if visible in the upper
|
|
||||||
# right or press Ctrl-D to start debugging.The PyCharm debugger pane will
|
|
||||||
# display the message "Waiting for process connection..."
|
|
||||||
#
|
|
||||||
# 6. Do some action in Inkscape which invokes Ink/Stitch extension code, and the
|
|
||||||
# debugger will be triggered. If you've left "Suspend after connect" checked
|
|
||||||
# in the Run configuration, PyCharm will pause in the "self.log("Enabled
|
|
||||||
# PyDev debugger.)" statement, below. Uncheck the box to have it continue
|
|
||||||
# automatically to your first set breakpoint.
|
|
||||||
|
|
||||||
# ###
|
|
||||||
|
|
||||||
# ### To debug with VS Code
|
|
||||||
# see: https://code.visualstudio.com/docs/python/debugging#_command-line-debugging
|
|
||||||
# https://code.visualstudio.com/docs/python/debugging#_debugging-by-attaching-over-a-network-connection
|
|
||||||
#
|
|
||||||
# 1. Install the Python extension for VS Code
|
|
||||||
# pip install debugpy
|
|
||||||
# 2. create .vscode/launch.json containing:
|
|
||||||
# "configurations": [ ...
|
|
||||||
# {
|
|
||||||
# "name": "Python: Attach",
|
|
||||||
# "type": "python",
|
|
||||||
# "request": "attach",
|
|
||||||
# "connect": {
|
|
||||||
# "host": "localhost",
|
|
||||||
# "port": 5678
|
|
||||||
# }
|
|
||||||
# }
|
|
||||||
# ]
|
|
||||||
# 3. Touch a file named "DEBUG.ini" at the top of your git repo, as above
|
|
||||||
# set debug_type = vscode
|
|
||||||
# 4. Start the debug server in VS Code by clicking on the debug icon in the left pane
|
|
||||||
# select "Python: Attach" from the dropdown menu and click on the green arrow.
|
|
||||||
# The debug server will start and connect to already running python processes,
|
|
||||||
# but immediately exit if no python processes are running.
|
|
||||||
#
|
|
||||||
# Notes:
|
|
||||||
# to see flask server url routes:
|
|
||||||
# - comment out the line self.disable_logging() in run() of lib/api/server.py
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.debugger == 'vscode':
|
|
||||||
import debugpy
|
|
||||||
elif self.debugger == 'pycharm':
|
|
||||||
import pydevd_pycharm
|
|
||||||
elif self.debugger == 'pydev':
|
|
||||||
import pydevd
|
|
||||||
elif self.debugger == 'file':
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise ValueError(f"unknown debugger: '{self.debugger}'")
|
|
||||||
|
|
||||||
except ImportError:
|
|
||||||
self.log(f"importing debugger failed (debugger disabled) for {self.debugger}")
|
|
||||||
|
|
||||||
# pydevd likes to shout about errors to stderr whether I want it to or not
|
|
||||||
with open(os.devnull, 'w') as devnull:
|
|
||||||
stderr = sys.stderr
|
|
||||||
sys.stderr = devnull
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.debugger == 'vscode':
|
|
||||||
debugpy.listen(('localhost', 5678))
|
|
||||||
if self.wait_attach:
|
|
||||||
print("Waiting for debugger attach")
|
|
||||||
debugpy.wait_for_client() # wait for debugger to attach
|
|
||||||
debugpy.breakpoint() # stop here to start normal debugging
|
|
||||||
elif self.debugger == 'pycharm':
|
|
||||||
pydevd_pycharm.settrace('localhost', port=5678, stdoutToServer=True,
|
|
||||||
stderrToServer=True)
|
|
||||||
elif self.debugger == 'pydev':
|
|
||||||
pydevd.settrace()
|
|
||||||
elif self.debugger == 'file':
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
raise ValueError(f"unknown debugger: '{self.debugger}'")
|
|
||||||
|
|
||||||
except socket.error as error:
|
|
||||||
self.log("Debugging: connection to pydevd failed: %s", error)
|
|
||||||
self.log(f"Be sure to run 'Start debugging server' in {self.debugger} to enable debugging.")
|
|
||||||
else:
|
|
||||||
self.log(f"Enabled '{self.debugger}' debugger.")
|
|
||||||
|
|
||||||
sys.stderr = stderr
|
|
||||||
|
|
||||||
def init_svg(self):
|
|
||||||
self.svg = etree.Element("svg", nsmap=inkex.NSS)
|
|
||||||
atexit.register(self.save_svg)
|
|
||||||
|
|
||||||
def save_svg(self):
|
|
||||||
tree = etree.ElementTree(self.svg)
|
|
||||||
debug_svg = self.debug_dir / self.debug_svg_file
|
|
||||||
tree.write(str(debug_svg)) # lxml <5.0.0 does not support Path objects
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def add_layer(self, name="Debug"):
|
|
||||||
layer = etree.Element("g", {
|
|
||||||
INKSCAPE_GROUPMODE: "layer",
|
|
||||||
INKSCAPE_LABEL: name,
|
|
||||||
"style": "display: none"
|
|
||||||
})
|
|
||||||
self.svg.append(layer)
|
|
||||||
self.current_layer = layer
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def open_group(self, name="Group"):
|
|
||||||
group = etree.Element("g", {
|
|
||||||
INKSCAPE_LABEL: name
|
|
||||||
})
|
|
||||||
|
|
||||||
self.log_svg_element(group)
|
|
||||||
self.group_stack.append(group)
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def close_group(self):
|
|
||||||
if self.group_stack:
|
|
||||||
self.group_stack.pop()
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def log(self, message, *args):
|
|
||||||
if self.last_log_time:
|
|
||||||
message = "(+%s) %s" % (datetime.now() - self.last_log_time, message)
|
|
||||||
|
|
||||||
self.raw_log(message, *args)
|
|
||||||
|
|
||||||
def raw_log(self, message, *args):
|
|
||||||
now = datetime.now()
|
|
||||||
timestamp = now.isoformat()
|
|
||||||
self.last_log_time = now
|
|
||||||
|
|
||||||
with self.log_file.open("a") as logfile:
|
|
||||||
print(timestamp, message % args, file=logfile)
|
|
||||||
logfile.flush()
|
|
||||||
|
|
||||||
# decorator to measure time of function
|
|
||||||
def time(self, func):
|
|
||||||
def decorated(*args, **kwargs):
|
|
||||||
if self.enabled:
|
|
||||||
self.raw_log("entering %s()", func.__name__)
|
|
||||||
start = time.monotonic()
|
|
||||||
|
|
||||||
result = func(*args, **kwargs)
|
|
||||||
|
|
||||||
if self.enabled:
|
|
||||||
end = time.monotonic()
|
|
||||||
self.raw_log("leaving %s(), duration = %s", func.__name__, round(end - start, 6))
|
|
||||||
|
|
||||||
return result
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def log_svg_element(self, element):
|
|
||||||
if self.current_layer is None:
|
|
||||||
self.add_layer()
|
|
||||||
|
|
||||||
if self.group_stack:
|
|
||||||
self.group_stack[-1].append(element)
|
|
||||||
else:
|
|
||||||
self.current_layer.append(element)
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def log_line_string(self, line_string, name=None, color=None):
|
|
||||||
"""Add a Shapely LineString to the SVG log."""
|
|
||||||
self.log_line_strings([line_string], name, color)
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def log_line_strings(self, line_strings, name=None, color=None):
|
|
||||||
path = line_strings_to_path(line_strings)
|
|
||||||
path.set('style', str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})))
|
|
||||||
|
|
||||||
if name is not None:
|
|
||||||
path.set(INKSCAPE_LABEL, name)
|
|
||||||
|
|
||||||
self.log_svg_element(path)
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def log_line(self, start, end, name="line", color=None):
|
|
||||||
self.log_svg_element(etree.Element("path", {
|
|
||||||
"d": "M%s,%s %s,%s" % (start + end),
|
|
||||||
"style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})),
|
|
||||||
INKSCAPE_LABEL: name
|
|
||||||
}))
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def log_point(self, point, name="point", color=None):
|
|
||||||
self.log_svg_element(etree.Element("circle", {
|
|
||||||
"cx": str(point.x),
|
|
||||||
"cy": str(point.y),
|
|
||||||
"r": "1",
|
|
||||||
"style": str(inkex.Style({"fill": "#000000"})),
|
|
||||||
}))
|
|
||||||
|
|
||||||
@check_enabled
|
|
||||||
@unwrap_arguments
|
|
||||||
def log_graph(self, graph, name="Graph", color=None):
|
|
||||||
d = ""
|
|
||||||
|
|
||||||
for edge in graph.edges:
|
|
||||||
d += "M%s,%s %s,%s" % (edge[0] + edge[1])
|
|
||||||
|
|
||||||
self.log_svg_element(etree.Element("path", {
|
|
||||||
"d": d,
|
|
||||||
"style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})),
|
|
||||||
INKSCAPE_LABEL: name
|
|
||||||
}))
|
|
||||||
|
|
||||||
# decorator to measure time of with block
|
|
||||||
@contextmanager
|
|
||||||
def time_this(self, label="code block"):
|
|
||||||
if self.enabled:
|
|
||||||
start = time.monotonic()
|
|
||||||
self.raw_log("begin %s", label)
|
|
||||||
|
|
||||||
yield
|
|
||||||
|
|
||||||
if self.enabled:
|
|
||||||
self.raw_log("completed %s, duration = %s", label, time.monotonic() - start)
|
|
||||||
|
|
||||||
|
|
||||||
# global debug object
|
|
||||||
debug = Debug()
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Authors: see git history
|
||||||
|
#
|
||||||
|
# Copyright (c) 2010 Authors
|
||||||
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
|
|
||||||
|
# this enable:
|
||||||
|
# from .debug import debug
|
||||||
|
# from ..debug import debug
|
||||||
|
from .debug import debug
|
||||||
|
|
|
@ -0,0 +1,251 @@
|
||||||
|
# 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 atexit # to save svg file on exit
|
||||||
|
import time # to measure time of code block, use time.monotonic() instead of time.time()
|
||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from contextlib import contextmanager # to measure time of with block
|
||||||
|
from pathlib import Path # to work with paths as objects
|
||||||
|
|
||||||
|
import inkex
|
||||||
|
from lxml import etree # to create svg file
|
||||||
|
|
||||||
|
from ..svg import line_strings_to_path
|
||||||
|
from ..svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logger = logging.getLogger("inkstitch.debug") # create module logger with name 'inkstitch.debug'
|
||||||
|
|
||||||
|
# to log messages if previous debug logger is not enabled
|
||||||
|
logger_inkstich = logging.getLogger("inkstitch") # create module logger with name 'inkstitch'
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------------------------
|
||||||
|
# decorator to check if debugging is enabled
|
||||||
|
# - if debug is not enabled then decorated function is not called
|
||||||
|
def check_enabled(func):
|
||||||
|
def decorated(self, *args, **kwargs):
|
||||||
|
if self.enabled:
|
||||||
|
return func(self, *args, **kwargs)
|
||||||
|
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
# unwrapping = provision for functions as arguments
|
||||||
|
# - if argument is callable then it is called and return value is used as argument
|
||||||
|
# otherwise argument is returned as is
|
||||||
|
def _unwrap(arg):
|
||||||
|
if callable(arg):
|
||||||
|
return arg()
|
||||||
|
else:
|
||||||
|
return arg
|
||||||
|
|
||||||
|
|
||||||
|
# decorator to unwrap arguments if they are callable
|
||||||
|
# eg: if argument is lambda function then it is called and return value is used as argument
|
||||||
|
def unwrap_arguments(func):
|
||||||
|
def decorated(self, *args, **kwargs):
|
||||||
|
unwrapped_args = [_unwrap(arg) for arg in args]
|
||||||
|
unwrapped_kwargs = {name: _unwrap(value) for name, value in kwargs.items()}
|
||||||
|
|
||||||
|
return func(self, *unwrapped_args, **unwrapped_kwargs)
|
||||||
|
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
class Debug(object):
|
||||||
|
"""Tools to help debug Ink/Stitch
|
||||||
|
|
||||||
|
This class contains methods to log strings and SVG elements. Strings are
|
||||||
|
logged to debug.log, and SVG elements are stored in debug.svg to aid in
|
||||||
|
debugging stitch algorithms.
|
||||||
|
|
||||||
|
All functionality is gated by self.enabled. If debugging is not enabled,
|
||||||
|
then debug calls will consume very few resources. Any method argument
|
||||||
|
can be a callable, in which case it is called and the return value is
|
||||||
|
logged instead. This way one can log potentially expensive expressions
|
||||||
|
by wrapping them in a lambda:
|
||||||
|
|
||||||
|
debug.log(lambda: some_expensive_function(some_argument))
|
||||||
|
|
||||||
|
The lambda is only called if debugging is enabled.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.enabled = False
|
||||||
|
self.last_log_time = None
|
||||||
|
self.current_layer = None
|
||||||
|
self.group_stack = []
|
||||||
|
self.svg_filename = None
|
||||||
|
|
||||||
|
def enable(self):
|
||||||
|
# determine svg filename from logger
|
||||||
|
if len(logger.handlers) > 0 and isinstance(logger.handlers[0], logging.FileHandler):
|
||||||
|
# determine filename of svg file from logger
|
||||||
|
filename = Path(logger.handlers[0].baseFilename)
|
||||||
|
self.svg_filename = filename.with_suffix(".svg")
|
||||||
|
self.svg_filename.unlink(missing_ok=True) # remove existing svg file
|
||||||
|
|
||||||
|
# self.log is activated by active logger
|
||||||
|
# - enabled only if logger first handler is FileHandler
|
||||||
|
# to disable "inkstitch.debug" simply set logging level to CRITICAL
|
||||||
|
if logger.isEnabledFor(logging.INFO) and self.svg_filename is not None:
|
||||||
|
self.enabled = True
|
||||||
|
self.log(f"Logging enabled with svg file: {self.svg_filename}")
|
||||||
|
self.init_svg()
|
||||||
|
|
||||||
|
else:
|
||||||
|
# use alternative logger to log message if logger has no handlers
|
||||||
|
logger_inkstich.info("No handlers in logger, cannot enable logging and svg file creation")
|
||||||
|
|
||||||
|
def init_svg(self):
|
||||||
|
self.svg = etree.Element("svg", nsmap=inkex.NSS)
|
||||||
|
atexit.register(self.save_svg)
|
||||||
|
|
||||||
|
def save_svg(self):
|
||||||
|
if self.enabled and self.svg_filename is not None:
|
||||||
|
self.log(f"Writing svg file: {self.svg_filename}")
|
||||||
|
tree = etree.ElementTree(self.svg)
|
||||||
|
tree.write(str(self.svg_filename)) # lxml <5.0.0 does not support Path objects, requires string
|
||||||
|
else:
|
||||||
|
# use alternative logger to log message if logger has no handlers
|
||||||
|
logger_inkstich.info(f"Saving to svg file is not activated {self.svg_filename=}")
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def add_layer(self, name="Debug"):
|
||||||
|
layer = etree.Element("g", {
|
||||||
|
INKSCAPE_GROUPMODE: "layer",
|
||||||
|
INKSCAPE_LABEL: name,
|
||||||
|
"style": "display: none"
|
||||||
|
})
|
||||||
|
self.svg.append(layer)
|
||||||
|
self.current_layer = layer
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def open_group(self, name="Group"):
|
||||||
|
group = etree.Element("g", {
|
||||||
|
INKSCAPE_LABEL: name
|
||||||
|
})
|
||||||
|
|
||||||
|
self.log_svg_element(group)
|
||||||
|
self.group_stack.append(group)
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def close_group(self):
|
||||||
|
if self.group_stack:
|
||||||
|
self.group_stack.pop()
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def log(self, message, *args):
|
||||||
|
if self.last_log_time:
|
||||||
|
message = "(+%s) %s" % (datetime.now() - self.last_log_time, message)
|
||||||
|
|
||||||
|
self.raw_log(message, *args)
|
||||||
|
|
||||||
|
def raw_log(self, message, *args):
|
||||||
|
now = datetime.now()
|
||||||
|
self.last_log_time = now
|
||||||
|
|
||||||
|
msg = message % args
|
||||||
|
logger.info(msg)
|
||||||
|
|
||||||
|
# decorator to measure time of function
|
||||||
|
def time(self, func):
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
if self.enabled:
|
||||||
|
self.raw_log("entering %s()", func.__name__)
|
||||||
|
start = time.monotonic()
|
||||||
|
|
||||||
|
result = func(*args, **kwargs)
|
||||||
|
|
||||||
|
if self.enabled:
|
||||||
|
end = time.monotonic()
|
||||||
|
self.raw_log("leaving %s(), duration = %s", func.__name__, round(end - start, 6))
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
return decorated
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def log_svg_element(self, element):
|
||||||
|
if self.current_layer is None:
|
||||||
|
self.add_layer()
|
||||||
|
|
||||||
|
if self.group_stack:
|
||||||
|
self.group_stack[-1].append(element)
|
||||||
|
else:
|
||||||
|
self.current_layer.append(element)
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def log_line_string(self, line_string, name=None, color=None):
|
||||||
|
"""Add a Shapely LineString to the SVG log."""
|
||||||
|
self.log_line_strings([line_string], name, color)
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def log_line_strings(self, line_strings, name=None, color=None):
|
||||||
|
path = line_strings_to_path(line_strings)
|
||||||
|
path.set('style', str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})))
|
||||||
|
|
||||||
|
if name is not None:
|
||||||
|
path.set(INKSCAPE_LABEL, name)
|
||||||
|
|
||||||
|
self.log_svg_element(path)
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def log_line(self, start, end, name="line", color=None):
|
||||||
|
self.log_svg_element(etree.Element("path", {
|
||||||
|
"d": "M%s,%s %s,%s" % (start + end),
|
||||||
|
"style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})),
|
||||||
|
INKSCAPE_LABEL: name
|
||||||
|
}))
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def log_point(self, point, name="point", color=None):
|
||||||
|
self.log_svg_element(etree.Element("circle", {
|
||||||
|
"cx": str(point.x),
|
||||||
|
"cy": str(point.y),
|
||||||
|
"r": "1",
|
||||||
|
"style": str(inkex.Style({"fill": "#000000"})),
|
||||||
|
}))
|
||||||
|
|
||||||
|
@check_enabled
|
||||||
|
@unwrap_arguments
|
||||||
|
def log_graph(self, graph, name="Graph", color=None):
|
||||||
|
d = ""
|
||||||
|
|
||||||
|
for edge in graph.edges:
|
||||||
|
d += "M%s,%s %s,%s" % (edge[0] + edge[1])
|
||||||
|
|
||||||
|
self.log_svg_element(etree.Element("path", {
|
||||||
|
"d": d,
|
||||||
|
"style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})),
|
||||||
|
INKSCAPE_LABEL: name
|
||||||
|
}))
|
||||||
|
|
||||||
|
# decorator to measure time of with block
|
||||||
|
@contextmanager
|
||||||
|
def time_this(self, label="code block"):
|
||||||
|
if self.enabled:
|
||||||
|
start = time.monotonic()
|
||||||
|
self.raw_log("begin %s", label)
|
||||||
|
|
||||||
|
yield
|
||||||
|
|
||||||
|
if self.enabled:
|
||||||
|
self.raw_log("completed %s, duration = %s", label, time.monotonic() - start)
|
||||||
|
|
||||||
|
|
||||||
|
# global debug object
|
||||||
|
debug = Debug()
|
|
@ -0,0 +1,192 @@
|
||||||
|
# Authors: see git history
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024 Authors
|
||||||
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
|
|
||||||
|
# ### General debugging notes:
|
||||||
|
# 1. to enable debugging or profiling copy DEBUG_template.toml to DEBUG.toml and edit it
|
||||||
|
|
||||||
|
# ### How create bash script for offline debugging from console
|
||||||
|
# 1. in DEBUG.toml set create_bash_script = true
|
||||||
|
# 2. call inkstitch.py extension from inkscape to create bash script named by bash_file_base in DEBUG.toml
|
||||||
|
# 3. run bash script from console
|
||||||
|
|
||||||
|
# ### Enable debugging
|
||||||
|
# 1. set debug_type to one of - vscode, pycharm, pydev, see below for details
|
||||||
|
# debug_type = vscode - 'debugpy' for vscode editor
|
||||||
|
# debug_type = pycharm - 'pydevd-pycharm' for pycharm editor
|
||||||
|
# debug_type = pydev - 'pydevd' for eclipse editor
|
||||||
|
# 2. set debug_enable = true in DEBUG.toml
|
||||||
|
# or use command line argument -d in bash script
|
||||||
|
# or set environment variable INKSTITCH_DEBUG_ENABLE = True or 1 or yes or y
|
||||||
|
|
||||||
|
# ### Enable profiling
|
||||||
|
# 1. set profiler_type to one of - cprofile, profile, pyinstrument
|
||||||
|
# profiler_type = cprofile - 'cProfile' profiler
|
||||||
|
# profiler_type = profile - 'profile' profiler
|
||||||
|
# profiler_type = pyinstrument- 'pyinstrument' profiler
|
||||||
|
# 2. set profile_enable = true in DEBUG.toml
|
||||||
|
# or use command line argument -p in bash script
|
||||||
|
# or set environment variable INKSTITCH_PROFILE_ENABLE = True or 1 or yes or y
|
||||||
|
|
||||||
|
# ### Miscelaneous notes:
|
||||||
|
# - to disable debugger when running from inkscape set disable_from_inkscape = true in DEBUG.toml
|
||||||
|
# - to change various output file names see DEBUG.toml
|
||||||
|
# - to disable waiting for debugger to attach (vscode editor) set wait_attach = false in DEBUG.toml
|
||||||
|
# - to prefer inkscape version of inkex module over pip version set prefer_pip_inkex = false in DEBUG.toml
|
||||||
|
|
||||||
|
# ###
|
||||||
|
|
||||||
|
# ### How to debug Ink/Stitch with LiClipse:
|
||||||
|
#
|
||||||
|
# 1. Install LiClipse (liclipse.com) -- no need to install Eclipse first
|
||||||
|
# 2. Start debug server as described here: http://www.pydev.org/manual_adv_remote_debugger.html
|
||||||
|
# * follow the "Note:" to enable the debug server menu item
|
||||||
|
# 3. Copy and edit a file named "DEBUG.toml" from "DEBUG_template.toml" next to inkstitch.py in your git clone
|
||||||
|
# and set debug_type = pydev
|
||||||
|
# 4. Run any extension and PyDev will start debugging.
|
||||||
|
|
||||||
|
# ###
|
||||||
|
|
||||||
|
# ### To debug with PyCharm:
|
||||||
|
|
||||||
|
# You must use the PyCharm Professional Edition and _not_ the Community
|
||||||
|
# Edition. Jetbrains has chosen to make remote debugging a Pro feature.
|
||||||
|
# To debug Inkscape python extensions, the extension code and Inkscape run
|
||||||
|
# independently of PyCharm, and only communicate with the debugger via a
|
||||||
|
# TCP socket. Thus, debugging is "remote," even if it's on the same machine,
|
||||||
|
# connected via the loopback interface.
|
||||||
|
#
|
||||||
|
# 1. pip install pydev_pycharm
|
||||||
|
#
|
||||||
|
# pydev_pycharm is versioned frequently. Jetbrains suggests installing
|
||||||
|
# a version at least compatible with the current build. For example, if your
|
||||||
|
# PyCharm build, as found in menu PyCharm -> About Pycharm is 223.8617.48,
|
||||||
|
# you could do:
|
||||||
|
# pip install pydevd-pycharm~=223.8617.48
|
||||||
|
#
|
||||||
|
# 2. From the Pycharm "Run" menu, choose "Edit Configurations..." and create a new
|
||||||
|
# configuration. Set "IDE host name:" to "localhost" and "Port:" to 5678.
|
||||||
|
# You can leave the default settings for all other choices.
|
||||||
|
#
|
||||||
|
# 3. Touch a file named "DEBUG.toml" at the top of your git repo, as above
|
||||||
|
# set debug_type = pycharm
|
||||||
|
#
|
||||||
|
# 4. Create a symbolic link in the Inkscape extensions directory to the
|
||||||
|
# top-level directory of your git repo. On a mac, for example:
|
||||||
|
# cd ~/Library/Application\ Support/org.inkscape.Inkscape/config/inkscape/extensions/
|
||||||
|
# ln -s <full path to the top level of your Ink/Stitch git repo>
|
||||||
|
# On other architectures it may be:
|
||||||
|
# cd ~/.config/inkscape/extensions
|
||||||
|
# ln -s <full path to the top level of your Ink/Stitch git repo>
|
||||||
|
# Remove any other Ink/Stitch files or references to Ink/Stitch from the
|
||||||
|
# extensions directory, or you'll see duplicate entries in the Ink/Stitch
|
||||||
|
# extensions menu in Inkscape.
|
||||||
|
#
|
||||||
|
# 5. In Pycharm, either click on the green "bug" icon if visible in the upper
|
||||||
|
# right or press Ctrl-D to start debugging.The PyCharm debugger pane will
|
||||||
|
# display the message "Waiting for process connection..."
|
||||||
|
#
|
||||||
|
# 6. Do some action in Inkscape which invokes Ink/Stitch extension code, and the
|
||||||
|
# debugger will be triggered. If you've left "Suspend after connect" checked
|
||||||
|
# in the Run configuration, PyCharm will pause in the "self.log("Enabled
|
||||||
|
# PyDev debugger.)" statement, below. Uncheck the box to have it continue
|
||||||
|
# automatically to your first set breakpoint.
|
||||||
|
|
||||||
|
# ###
|
||||||
|
|
||||||
|
# ### To debug with VS Code
|
||||||
|
# see: https://code.visualstudio.com/docs/python/debugging#_command-line-debugging
|
||||||
|
# https://code.visualstudio.com/docs/python/debugging#_debugging-by-attaching-over-a-network-connection
|
||||||
|
#
|
||||||
|
# 1. Install the Python extension for VS Code
|
||||||
|
# pip install debugpy
|
||||||
|
# 2. create .vscode/launch.json containing:
|
||||||
|
# "configurations": [ ...
|
||||||
|
# {
|
||||||
|
# "name": "Python: Attach",
|
||||||
|
# "type": "python",
|
||||||
|
# "request": "attach",
|
||||||
|
# "connect": {
|
||||||
|
# "host": "localhost",
|
||||||
|
# "port": 5678
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
# 3. Touch a file named "DEBUG.toml" at the top of your git repo, as above
|
||||||
|
# set debug_type = vscode
|
||||||
|
# 4. Start the debug server in VS Code by clicking on the debug icon in the left pane
|
||||||
|
# select "Python: Attach" from the dropdown menu and click on the green arrow.
|
||||||
|
# The debug server will start and connect to already running python processes,
|
||||||
|
# but immediately exit if no python processes are running.
|
||||||
|
#
|
||||||
|
# Notes:
|
||||||
|
# to see flask server url routes:
|
||||||
|
# - comment out the line self.disable_logging() in run() of lib/api/server.py
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import socket # to check if debugger is running
|
||||||
|
|
||||||
|
from .utils import safe_get # mimic get method of dict with default value
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logger = logging.getLogger("inkstitch")
|
||||||
|
|
||||||
|
# we intentionally disable flakes C901 - function is too complex, beacuse it is used only for debugging
|
||||||
|
# currently complexity is set 10 see 'make style', this means that function can have max 10 nested blocks, here we have more
|
||||||
|
# flake8: noqa: C901
|
||||||
|
def init_debugger(debug_type:str, ini: dict):
|
||||||
|
if debug_type == 'none':
|
||||||
|
return
|
||||||
|
|
||||||
|
wait_attach = safe_get(ini, "DEBUG", "wait_attach", default=True) # currently only for vscode
|
||||||
|
debugger = debug_type
|
||||||
|
|
||||||
|
try:
|
||||||
|
if debugger == 'vscode':
|
||||||
|
import debugpy
|
||||||
|
elif debugger == 'pycharm':
|
||||||
|
import pydevd_pycharm
|
||||||
|
elif debugger == 'pydev':
|
||||||
|
import pydevd
|
||||||
|
elif debugger == 'file':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError(f"unknown debugger: '{debugger}'")
|
||||||
|
|
||||||
|
except ImportError:
|
||||||
|
logger.info(f"importing debugger failed (debugger disabled) for {debugger}")
|
||||||
|
|
||||||
|
# pydevd likes to shout about errors to stderr whether I want it to or not
|
||||||
|
with open(os.devnull, 'w') as devnull:
|
||||||
|
stderr = sys.stderr
|
||||||
|
sys.stderr = devnull
|
||||||
|
|
||||||
|
try:
|
||||||
|
if debugger == 'vscode':
|
||||||
|
debugpy.listen(('localhost', 5678))
|
||||||
|
if wait_attach:
|
||||||
|
print("Waiting for debugger attach", file=sys.stderr)
|
||||||
|
debugpy.wait_for_client() # wait for debugger to attach
|
||||||
|
debugpy.breakpoint() # stop here to start normal debugging
|
||||||
|
elif debugger == 'pycharm':
|
||||||
|
pydevd_pycharm.settrace('localhost', port=5678, stdoutToServer=True,
|
||||||
|
stderrToServer=True)
|
||||||
|
elif debugger == 'pydev':
|
||||||
|
pydevd.settrace()
|
||||||
|
elif debugger == 'file':
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise ValueError(f"unknown debugger: '{debugger}'")
|
||||||
|
|
||||||
|
except socket.error as error:
|
||||||
|
logger.info("Debugging: connection to pydevd failed: %s", error)
|
||||||
|
logger.info(f"Be sure to run 'Start debugging server' in {debugger} to enable debugging.")
|
||||||
|
else:
|
||||||
|
logger.info(f"Enabled '{debugger}' debugger.")
|
||||||
|
|
||||||
|
sys.stderr = stderr
|
|
@ -0,0 +1,294 @@
|
||||||
|
# Authors: see git history
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024 Authors
|
||||||
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
|
# basic info for inkstitch logging:
|
||||||
|
# ---------------------------------
|
||||||
|
# some idea can be found in: Modern Python logging - https://www.youtube.com/watch?v=9L77QExPmI0
|
||||||
|
#
|
||||||
|
# logging vs warnings
|
||||||
|
# -------------------
|
||||||
|
# warnings - see https://docs.python.org/3/library/warnings.html
|
||||||
|
# logging - see https://docs.python.org/3/library/logging.html
|
||||||
|
#
|
||||||
|
# In simplified terms, use "warning" to alert that a specific function is deprecated, and in all other cases, use "logging".
|
||||||
|
# Warnings are primarily intended for libraries where there's a newer solution available, but for backward compatibility
|
||||||
|
# reasons, the old functionality is retained.
|
||||||
|
#
|
||||||
|
# In complex applications like Inkstitch, it might be sensible to exclusively use one method, namely "logging",
|
||||||
|
# to unify and simplify the messaging system of such a system.
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# root logger:
|
||||||
|
# ------------
|
||||||
|
# - The primary logger orchestrates all other loggers through logging.xxx() calls.
|
||||||
|
# - It should only be utilized at the application's highest level to manage the logging of all loggers.
|
||||||
|
# - It can easily disable all loggers by invoking logging.disable() and channel all warnings to logging
|
||||||
|
# by setting logging.captureWarnings(True) with the level WARNING.
|
||||||
|
# - The configuration of all loggers can be achieved via a file, and logging.config.dictConfig(logging_dict).
|
||||||
|
#
|
||||||
|
|
||||||
|
# module logger:
|
||||||
|
# --------------
|
||||||
|
# - Instantiate the logger by invoking logger=getLogger(name).
|
||||||
|
# Avoid using __name__ as the name, as it generates numerous loggers per application.
|
||||||
|
# The logger name persists globally throughout the application.
|
||||||
|
# - Avoid configuring the module logger within the module itself;
|
||||||
|
# instead, utilize the top-level application configuration with logging.config.
|
||||||
|
# This allows users of the application to customize it according to their requirements.
|
||||||
|
|
||||||
|
# example of module logger:
|
||||||
|
# -------------------------
|
||||||
|
# import logging
|
||||||
|
# logger = logging.getLogger('inkstitch') # create module logger with name 'inkstitch', but configure it at top level of app
|
||||||
|
# ...
|
||||||
|
# logger.debug('debug message') # example of using module logger
|
||||||
|
# ...
|
||||||
|
|
||||||
|
# top level of the application:
|
||||||
|
# ----------------------------
|
||||||
|
# - configure root and other loggers
|
||||||
|
# - best practice is to configure from a file: eg logging.config.fileConfig('logging.conf')
|
||||||
|
# - consider toml format for logging configuration (json, yaml, xml, dict are also possible)
|
||||||
|
#
|
||||||
|
|
||||||
|
# list of loggers in inkstitch (not complete):
|
||||||
|
# -------------------------------------------
|
||||||
|
# - root - main logger that controls all other loggers
|
||||||
|
# - inkstitch - suggested name for inkstitch
|
||||||
|
# - inkstitch.debug - uses in debug module with svg file saving
|
||||||
|
#
|
||||||
|
# third-party loggers:
|
||||||
|
# --------------------
|
||||||
|
# - werkzeug - is used by flask
|
||||||
|
# - shapely.geos - was used by shapely but currently replaced by exceptions and warnings
|
||||||
|
#
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------------------------
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
if sys.version_info >= (3, 11):
|
||||||
|
import tomllib # built-in in Python 3.11+
|
||||||
|
else:
|
||||||
|
import tomli as tomllib
|
||||||
|
|
||||||
|
import warnings # to control python warnings
|
||||||
|
import logging # to configure logging
|
||||||
|
import logging.config # to configure logging from dict
|
||||||
|
|
||||||
|
from .utils import safe_get # mimic get method of dict with default value
|
||||||
|
|
||||||
|
logger = logging.getLogger('inkstitch')
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------------------------
|
||||||
|
# activate_logging - configure logging for inkstitch application
|
||||||
|
def activate_logging(running_as_frozen: bool, ini: dict, SCRIPTDIR: Path):
|
||||||
|
if running_as_frozen: # in release mode
|
||||||
|
activate_for_frozen()
|
||||||
|
else: # in development
|
||||||
|
activate_for_development(ini, SCRIPTDIR)
|
||||||
|
|
||||||
|
|
||||||
|
# Configure logging in frozen (release) mode of application:
|
||||||
|
# in release mode normally we want to ignore all warnings and logging, but we can enable it by setting environment variables
|
||||||
|
# - INKSTITCH_LOGLEVEL - logging level:
|
||||||
|
# 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
|
||||||
|
# - PYTHONWARNINGS, -W - warnings action controlled by python
|
||||||
|
# actions: 'error', 'ignore', 'always', 'default', 'module', 'once'
|
||||||
|
def activate_for_frozen():
|
||||||
|
loglevel = os.environ.get('INKSTITCH_LOGLEVEL') # read log level from environment variable or None
|
||||||
|
docpath = os.environ.get('DOCUMENT_PATH') # read document path from environment variable (set by inkscape) or None
|
||||||
|
|
||||||
|
if docpath is not None and loglevel is not None and loglevel.upper() in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
|
||||||
|
|
||||||
|
# The end user enabled logging and warnings are redirected to the input_svg.inkstitch.log file.
|
||||||
|
|
||||||
|
vars = {
|
||||||
|
'loglevel': loglevel.upper(),
|
||||||
|
'logfilename': Path(docpath).with_suffix('.inkstitch.log') # log file is created in document path
|
||||||
|
}
|
||||||
|
config = expand_variables(frozen_config, vars)
|
||||||
|
|
||||||
|
# dictConfig has access to top level variables, dict contains: ext://__main__.var
|
||||||
|
# - restriction: variable must be last token in string - very limited functionality, avoid using it
|
||||||
|
|
||||||
|
# After this operation, logging will be activated, so we can use the logger.
|
||||||
|
logging.config.dictConfig(config) # configure root logger from dict
|
||||||
|
|
||||||
|
logging.captureWarnings(True) # capture all warnings to log file with level WARNING
|
||||||
|
else:
|
||||||
|
logging.disable() # globally disable all logging of all loggers
|
||||||
|
warnings.simplefilter('ignore') # ignore all warnings
|
||||||
|
|
||||||
|
|
||||||
|
# in development mode we want to use configuration from some LOGGING.toml file
|
||||||
|
def activate_for_development(ini: dict, SCRIPTDIR: Path):
|
||||||
|
logging_config_file = safe_get(ini, "LOGGING", "log_config_file", default=None)
|
||||||
|
vars = {'SCRIPTDIR': SCRIPTDIR} # dynamic data for logging configuration
|
||||||
|
|
||||||
|
if logging_config_file is not None:
|
||||||
|
logging_config_file = Path(logging_config_file)
|
||||||
|
if logging_config_file.exists():
|
||||||
|
with open(logging_config_file, "rb") as f:
|
||||||
|
devel_config = tomllib.load(f) # -> dict
|
||||||
|
else:
|
||||||
|
raise FileNotFoundError(f"{logging_config_file} file not found")
|
||||||
|
else: # if LOGGING.toml file does not exist, use default logging configuration
|
||||||
|
vars['loglevel'] = 'DEBUG' # set log level to DEBUG
|
||||||
|
vars['logfilename'] = SCRIPTDIR / "inkstitch.log" # log file is created in current directory
|
||||||
|
devel_config = development_config # get TOML configuration from module
|
||||||
|
|
||||||
|
configure_logging(devel_config, ini, vars) # initialize and activate logging configuration
|
||||||
|
|
||||||
|
logger.info("Running in development mode")
|
||||||
|
logger.info(f"Using logging configuration from file: {logging_config_file}")
|
||||||
|
logger.debug(f"Logging configuration: {devel_config = }")
|
||||||
|
|
||||||
|
|
||||||
|
# --------------------------------------------------------------------------------------------
|
||||||
|
# configure logging from dictionary:
|
||||||
|
# - capture all warnings to log file with level WARNING - depends on warnings_capture
|
||||||
|
# - set action for warnings: 'error', 'ignore', 'always', 'default', 'module', 'once' - depends on warnings_action
|
||||||
|
def configure_logging(config: dict, ini: dict, vars: dict):
|
||||||
|
config = expand_variables(config, vars)
|
||||||
|
|
||||||
|
# After this operation, logging will be activated, so we can use the logger.
|
||||||
|
logging.config.dictConfig(config) # configure loggers from dict - using loglevel, logfilename
|
||||||
|
|
||||||
|
warnings_capture = config.get('warnings_capture', True)
|
||||||
|
logging.captureWarnings(warnings_capture) # capture warnings to log file with level WARNING
|
||||||
|
warnings_action = config.get('warnings_action', 'default').lower()
|
||||||
|
warnings.simplefilter(warnings_action) # set action for warnings: 'error', 'ignore', 'always', ...
|
||||||
|
|
||||||
|
disable_logging = safe_get(ini, "LOGGING", "disable_logging", default=False)
|
||||||
|
if disable_logging:
|
||||||
|
logger.warning(f"Logging is disabled by configuration in ini file. {disable_logging = }")
|
||||||
|
logging.disable() # globally disable all logging of all loggers
|
||||||
|
|
||||||
|
|
||||||
|
# Evaluate evaluation of variables in logging configuration:
|
||||||
|
# "handlers": {
|
||||||
|
# "file": {
|
||||||
|
# "filename": "%(SCRIPTDIR)s/xxx.log", # <--- replace %(SCRIPTDIR)s -> script path
|
||||||
|
# "%(logfilename)s", # <--- replace %(logfilename)s -> log file name
|
||||||
|
# ...
|
||||||
|
# "loggers": {
|
||||||
|
# "inkstitch": {
|
||||||
|
# "level": "%(loglevel)s", # <--- replace %(loglevel)s -> log level
|
||||||
|
# ...
|
||||||
|
# - for external configuration file (eg. LOGGING.toml) we cannot pass parameters such as the current directory.
|
||||||
|
# - we do: filename = "%(SCRIPTDIR)s/inkstitch.log" -> filename = "path/inkstitch.log"
|
||||||
|
# - safety: we can use only predefined variables in myvars, otherwise we get KeyError
|
||||||
|
# - return modified configuration
|
||||||
|
# - create logging directory if not exists, directory cannot end with ":" to avoid error with ext:// keys
|
||||||
|
def expand_variables(cfg: dict, vars: dict):
|
||||||
|
for k, v in cfg.get('loggers', {}).items():
|
||||||
|
if 'level' in v: # replace level in logger
|
||||||
|
cfg['loggers'][k]['level'] = v['level'] % vars
|
||||||
|
|
||||||
|
for k, v in cfg.get('handlers', {}).items():
|
||||||
|
if 'filename' in v: # replace filename in handler
|
||||||
|
orig_filename = v['filename'] # original filename for later comparison
|
||||||
|
cfg['handlers'][k]['filename'] = v['filename'] % vars
|
||||||
|
# create logging directory only if substitution was done, we need to avoid ext:// cfg:// keys
|
||||||
|
if orig_filename != cfg['handlers'][k]['filename']:
|
||||||
|
dirname = Path(cfg['handlers'][k]['filename']).parent
|
||||||
|
if not dirname.exists():
|
||||||
|
# inform user about creating logging directory, otherwise it is silent, logging is not yet active
|
||||||
|
print(f"DEBUG: Creating logging directory: {dirname} ", file=sys.stderr)
|
||||||
|
dirname.mkdir(parents=True, exist_ok=True)
|
||||||
|
return cfg
|
||||||
|
|
||||||
|
|
||||||
|
def startup_info(logger: logging.Logger, SCRIPTDIR: Path, running_as_frozen: bool, running_from_inkscape: bool,
|
||||||
|
debug_active: bool, debug_type: str, profiler_type: str):
|
||||||
|
logger.info(f"Running as frozen: {running_as_frozen}")
|
||||||
|
logger.info(f"Running from inkscape: {running_from_inkscape}")
|
||||||
|
logger.info(f"Debugger active: {debug_active}")
|
||||||
|
logger.info(f"Debugger type: {debug_type!r}")
|
||||||
|
logger.info(f"Profiler type: {profiler_type!r}")
|
||||||
|
|
||||||
|
# log Python version, platform, command line arguments, sys.path
|
||||||
|
import sys
|
||||||
|
import platform
|
||||||
|
|
||||||
|
logger.info(f"Python version: {sys.version}")
|
||||||
|
logger.info(f"Platform: {platform.platform()}")
|
||||||
|
logger.info(f"Command line arguments: {sys.argv}")
|
||||||
|
logger.debug(f"sys.path: {sys.path}")
|
||||||
|
|
||||||
|
|
||||||
|
# example of logger configuration for release mode:
|
||||||
|
# ------------------------------------------------
|
||||||
|
# - logger suitable for release mode, where we assume that the directory of the input SVG file allows writing the log file.
|
||||||
|
# - in inkstitch.py we check release mode and environment variable INKSTITCH_LOGLEVEL
|
||||||
|
# - this config redirect all loggers to file svg_file.inkstitch.log to directory of svg file
|
||||||
|
# - set loglevel and logfilename in inkstitch.py before calling logging.config.dictConfig(frozen_config)
|
||||||
|
frozen_config = {
|
||||||
|
"version": 1, # mandatory key and value (int) is 1
|
||||||
|
"disable_existing_loggers": False, # false enable all loggers not defined here, true disable
|
||||||
|
"filters": {}, # no filters
|
||||||
|
"formatters": {
|
||||||
|
"simple": { # formatter name (https://docs.python.org/3/library/logging.html#logging.LogRecord)
|
||||||
|
"format": '%(asctime)s [%(levelname)s]: %(filename)s.%(funcName)s: %(message)s' # format string
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"file": {
|
||||||
|
"class": "logging.FileHandler", # type - file output
|
||||||
|
"formatter": "simple", # use formatter 'simple' for handler 'file'
|
||||||
|
"filename": "%(logfilename)s", # access variable logfilename
|
||||||
|
"mode": "w" # create new file
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"loggers": {
|
||||||
|
"root": { # top level logger
|
||||||
|
"level": "%(loglevel)s", # access variable loglevel
|
||||||
|
"handlers": ["file"], # use handler 'file' for logger
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------
|
||||||
|
# example of implicit developer logger configuration:
|
||||||
|
# ---------------------------------------------------
|
||||||
|
# - configured two loggers: root and inkstitch loggers
|
||||||
|
# - output is redirected to file 'logfilename' in the directory of inkstitch.py
|
||||||
|
# - this configuration uses only one log level 'loglevel for both the root and inkstitch loggers.
|
||||||
|
# - set loglevel and logfilename in inkstitch.py before calling logging.config.dictConfig(development_config)
|
||||||
|
development_config = {
|
||||||
|
"warnings_action": "default", # dafault action for warnings
|
||||||
|
"warnings_capture": True, # capture warnings to log file with level WARNING
|
||||||
|
|
||||||
|
"version": 1, # mandatory key and value (int) is 1
|
||||||
|
"disable_existing_loggers": False, # false enable all loggers not defined here, true disable
|
||||||
|
"filters": {}, # no filters
|
||||||
|
"formatters": {
|
||||||
|
"simple": { # formatter name (https://docs.python.org/3/library/logging.html#logging.LogRecord)
|
||||||
|
"format": '%(asctime)s [%(levelname)s]: %(filename)s.%(funcName)s: %(message)s' # format string
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"handlers": {
|
||||||
|
"file": {
|
||||||
|
"class": "logging.FileHandler", # type - file output
|
||||||
|
"formatter": "simple", # use formatter 'simple' for handler 'file'
|
||||||
|
"filename": "%(logfilename)s", # ext: --> access variable logfilename
|
||||||
|
"mode": "w" # create new file
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"loggers": { # configure loggers
|
||||||
|
"inkstitch": { # specific logger to inkstitch application
|
||||||
|
"level": "%(loglevel)s", # ext: --> access variable loglevel
|
||||||
|
"handlers": ["file"], # use handler 'file' for logger
|
||||||
|
"propagate": False, # don't propagate to root logger - otherwise all will be logged twice
|
||||||
|
},
|
||||||
|
"root": { # top level logger
|
||||||
|
"level": "%(loglevel)s", # ext: --> access variable loglevel
|
||||||
|
"handlers": ["file"], # use handler 'file' for logger
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
|
@ -1,28 +1,42 @@
|
||||||
# Authors: see git history
|
# Authors: see git history
|
||||||
#
|
#
|
||||||
# Copyright (c) 2010 Authors
|
# Copyright (c) 2024 Authors
|
||||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from pathlib import Path # to work with paths as objects
|
|
||||||
import configparser # to read DEBUG.ini
|
|
||||||
|
|
||||||
# this file is without: import inkex
|
# this file is without: import inkex
|
||||||
# - we need dump argv and sys.path as is on startup from inkscape
|
# - we need dump argv and sys.path as is on startup from inkscape
|
||||||
# - later sys.path may be modified that influences importing inkex (see prefere_pip_inkex)
|
# - later sys.path may be modified that influences importing inkex (see prefere_pip_inkex)
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
from pathlib import Path # to work with paths as objects
|
||||||
|
import logging
|
||||||
|
|
||||||
def write_offline_debug_script(debug_script_dir: Path, ini: configparser.ConfigParser):
|
logger = logging.getLogger("inkstitch")
|
||||||
|
|
||||||
|
|
||||||
|
# safe_get - get value from nested dictionary, return default if key does not exist
|
||||||
|
# - to read nested values from dict - mimic get method of dict with default value
|
||||||
|
# example: safe_get({'a': {'b': 1}}, 'a', 'b') -> 1
|
||||||
|
# safe_get({'a': {'b': 1}}, 'a', 'c', default=2) -> 2
|
||||||
|
def safe_get(dictionary: dict, *keys, default=None):
|
||||||
|
for key in keys:
|
||||||
|
if key not in dictionary:
|
||||||
|
return default
|
||||||
|
dictionary = dictionary[key]
|
||||||
|
return dictionary
|
||||||
|
|
||||||
|
|
||||||
|
def write_offline_debug_script(debug_script_dir: Path, ini: dict):
|
||||||
'''
|
'''
|
||||||
prepare Bash script for offline debugging from console
|
prepare Bash script for offline debugging from console
|
||||||
arguments:
|
arguments:
|
||||||
- debug_script_dir - Path object, absolute path to directory of inkstitch.py
|
- debug_script_dir - Path object, absolute path to directory of inkstitch.py
|
||||||
- ini - see DEBUG.ini
|
- ini - see DEBUG.toml
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# define names of files used by offline Bash script
|
# define names of files used by offline Bash script
|
||||||
bash_file_base = ini.get("DEBUG", "bash_file_base", fallback="debug_inkstitch")
|
bash_file_base = safe_get(ini, "DEBUG", "bash_file_base", default="debug_inkstitch")
|
||||||
bash_name = Path(bash_file_base).with_suffix(".sh") # Path object
|
bash_name = Path(bash_file_base).with_suffix(".sh") # Path object
|
||||||
bash_svg = Path(bash_file_base).with_suffix(".svg") # Path object
|
bash_svg = Path(bash_file_base).with_suffix(".svg") # Path object
|
||||||
|
|
||||||
|
@ -125,7 +139,6 @@ def reorder_sys_path():
|
||||||
'''
|
'''
|
||||||
change sys.path to prefer pip installed inkex over inkscape bundled inkex
|
change sys.path to prefer pip installed inkex over inkscape bundled inkex
|
||||||
'''
|
'''
|
||||||
|
|
||||||
# see static void set_extensions_env() in inkscape/src/inkscape-main.cpp
|
# see static void set_extensions_env() in inkscape/src/inkscape-main.cpp
|
||||||
# what we do:
|
# what we do:
|
||||||
# - move inkscape extensions path to the end of sys.path
|
# - move inkscape extensions path to the end of sys.path
|
||||||
|
@ -147,34 +160,30 @@ def reorder_sys_path():
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# try to resolve debugger type from ini file or cmd line of bash
|
# try to resolve debugger type from ini file or cmd line of bash
|
||||||
def resole_debug_type(ini: configparser.ConfigParser):
|
def resolve_debug_type(ini: dict):
|
||||||
# enable/disable debugger from bash: -d
|
# enable/disable debugger from bash: -d
|
||||||
if os.environ.get('INKSTITCH_DEBUG_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
|
if os.environ.get('INKSTITCH_DEBUG_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
|
||||||
debug_enable = True
|
debug_enable = True
|
||||||
else:
|
else:
|
||||||
debug_enable = ini.getboolean("DEBUG", "debug_enable", fallback=False) # enable debugger on startup from ini
|
debug_enable = safe_get(ini, "DEBUG", "debug_enable", default=False) # enable debugger on startup from ini
|
||||||
|
|
||||||
debug_type = ini.get("DEBUG", "debug_type", fallback="none") # debugger type vscode, pycharm, pydevd
|
debug_type = safe_get(ini, "DEBUG", "debug_type", default="none") # debugger type vscode, pycharm, pydevd
|
||||||
if not debug_enable:
|
if not debug_enable:
|
||||||
debug_type = 'none'
|
debug_type = 'none'
|
||||||
|
|
||||||
debug_to_file = ini.getboolean("DEBUG", "debug_to_file", fallback=False) # write debug output to file
|
|
||||||
if debug_to_file and debug_type == 'none':
|
|
||||||
debug_type = 'file'
|
|
||||||
|
|
||||||
return debug_type
|
return debug_type
|
||||||
|
|
||||||
|
|
||||||
# try to resolve profiler type from ini file or cmd line of bash
|
# try to resolve profiler type from ini file or cmd line of bash
|
||||||
def resole_profile_type(ini: configparser.ConfigParser):
|
def resolve_profiler_type(ini: dict):
|
||||||
# enable/disable profiling from bash: -p
|
# enable/disable profiling from bash: -p
|
||||||
if os.environ.get('INKSTITCH_PROFILE_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
|
if os.environ.get('INKSTITCH_PROFILE_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
|
||||||
profile_enable = True
|
profile_enable = True
|
||||||
else:
|
else:
|
||||||
profile_enable = ini.getboolean("PROFILE", "profile_enable", fallback=False) # read from ini
|
profile_enable = safe_get(ini, "PROFILE", "profile_enable", default=False) # read from ini
|
||||||
|
|
||||||
# specify profiler type
|
# specify profiler type
|
||||||
profiler_type = ini.get("PROFILE", "profiler_type", fallback="none") # profiler type cprofile, profile, pyinstrument
|
profiler_type = safe_get(ini, "PROFILE", "profiler_type", default="none") # profiler type cprofile, profile, pyinstrument
|
||||||
if not profile_enable:
|
if not profile_enable:
|
||||||
profiler_type = 'none'
|
profiler_type = 'none'
|
||||||
|
|
||||||
|
@ -189,13 +198,19 @@ def resole_profile_type(ini: configparser.ConfigParser):
|
||||||
# - pyinstrument - profiler with nice html output
|
# - pyinstrument - profiler with nice html output
|
||||||
|
|
||||||
|
|
||||||
def profile(profiler_type, profile_dir: Path, ini: configparser.ConfigParser, extension, remaining_args):
|
def profile(profiler_type, profile_dir: Path, ini: dict, extension, remaining_args):
|
||||||
'''
|
'''
|
||||||
profile with cProfile, profile or pyinstrument
|
profile with cProfile, profile or pyinstrument
|
||||||
'''
|
'''
|
||||||
profile_file_base = ini.get("PROFILE", "profile_file_base", fallback="debug_profile")
|
profile_file_base = safe_get(ini, "PROFILE", "profile_file_base", default="debug_profile")
|
||||||
profile_file_path = profile_dir / profile_file_base # Path object
|
profile_file_path = profile_dir / profile_file_base # Path object
|
||||||
|
|
||||||
|
# create directory if not exists
|
||||||
|
dirname = profile_file_path.parent
|
||||||
|
if not dirname.exists():
|
||||||
|
logger.debug(f"Creating directory for profile output: {dirname}")
|
||||||
|
dirname.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
if profiler_type == 'cprofile':
|
if profiler_type == 'cprofile':
|
||||||
with_cprofile(extension, remaining_args, profile_file_path)
|
with_cprofile(extension, remaining_args, profile_file_path)
|
||||||
elif profiler_type == 'profile':
|
elif profiler_type == 'profile':
|
||||||
|
@ -206,7 +221,7 @@ def profile(profiler_type, profile_dir: Path, ini: configparser.ConfigParser, ex
|
||||||
raise ValueError(f"unknown profiler type: '{profiler_type}'")
|
raise ValueError(f"unknown profiler type: '{profiler_type}'")
|
||||||
|
|
||||||
|
|
||||||
def with_cprofile(extension, remaining_args, profile_file_path):
|
def with_cprofile(extension, remaining_args, profile_file_path: Path):
|
||||||
'''
|
'''
|
||||||
profile with cProfile
|
profile with cProfile
|
||||||
'''
|
'''
|
||||||
|
@ -227,7 +242,7 @@ def with_cprofile(extension, remaining_args, profile_file_path):
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
def with_profile(extension, remaining_args, profile_file_path):
|
def with_profile(extension, remaining_args, profile_file_path: Path):
|
||||||
'''
|
'''
|
||||||
profile with profile
|
profile with profile
|
||||||
'''
|
'''
|
||||||
|
@ -246,7 +261,7 @@ def with_profile(extension, remaining_args, profile_file_path):
|
||||||
file=sys.stderr)
|
file=sys.stderr)
|
||||||
|
|
||||||
|
|
||||||
def with_pyinstrument(extension, remaining_args, profile_file_path):
|
def with_pyinstrument(extension, remaining_args, profile_file_path: Path):
|
||||||
'''
|
'''
|
||||||
profile with pyinstrument
|
profile with pyinstrument
|
||||||
'''
|
'''
|
|
@ -3,7 +3,6 @@
|
||||||
# Copyright (c) 2010 Authors
|
# Copyright (c) 2010 Authors
|
||||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
import logging
|
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
import inkex
|
import inkex
|
||||||
|
@ -92,12 +91,7 @@ class BreakApart(InkstitchExtension):
|
||||||
return polygons
|
return polygons
|
||||||
|
|
||||||
def geom_is_valid(self, geom):
|
def geom_is_valid(self, geom):
|
||||||
# Don't complain about invalid shapes, we just want to know
|
|
||||||
logger = logging.getLogger('shapely.geos')
|
|
||||||
level = logger.level
|
|
||||||
logger.setLevel(logging.CRITICAL)
|
|
||||||
valid = geom.is_valid
|
valid = geom.is_valid
|
||||||
logger.setLevel(level)
|
|
||||||
return valid
|
return valid
|
||||||
|
|
||||||
def ensure_minimum_size(self, polygons, size):
|
def ensure_minimum_size(self, polygons, size):
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||||
|
|
||||||
import json
|
import json
|
||||||
import logging
|
|
||||||
import os
|
import os
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
|
@ -159,9 +158,6 @@ class PrintPreviewServer(Thread):
|
||||||
self.flask_server.shutdown()
|
self.flask_server.shutdown()
|
||||||
self.server_thread.join()
|
self.server_thread.join()
|
||||||
|
|
||||||
def disable_logging(self):
|
|
||||||
logging.getLogger('werkzeug').setLevel(logging.ERROR)
|
|
||||||
|
|
||||||
# https://github.com/aluo-x/Learning_Neural_Acoustic_Fields/blob/master/train.py
|
# https://github.com/aluo-x/Learning_Neural_Acoustic_Fields/blob/master/train.py
|
||||||
# https://github.com/pytorch/pytorch/issues/71029
|
# https://github.com/pytorch/pytorch/issues/71029
|
||||||
def find_free_port(self):
|
def find_free_port(self):
|
||||||
|
@ -170,8 +166,6 @@ class PrintPreviewServer(Thread):
|
||||||
return s.getsockname()[1]
|
return s.getsockname()[1]
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self.disable_logging()
|
|
||||||
|
|
||||||
self.host = "127.0.0.1"
|
self.host = "127.0.0.1"
|
||||||
self.port = self.find_free_port()
|
self.port = self.find_free_port()
|
||||||
# exporting the port number for languages to work in electron vuejs part of inkstitch
|
# exporting the port number for languages to work in electron vuejs part of inkstitch
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
|
# Assuming pyembroidery is installed in parent directory of inkstitch.
|
||||||
|
# Maybe it would be better to install PyEmbroidery as a submodule and automatically update it,
|
||||||
|
# but the .gitignore file should be updated to ignore the build directory.
|
||||||
./pyembroidery
|
./pyembroidery
|
||||||
|
|
||||||
# get up to date inkex version (Febuary 10, 2024)
|
# get up to date inkex version (Febuary 10, 2024)
|
||||||
inkex @ git+https://gitlab.com/inkscape/extensions.git@8d51d7449d73096382c2f39e726eddc4f9bbcfc4
|
inkex @ git+https://gitlab.com/inkscape/extensions.git@8d51d7449d73096382c2f39e726eddc4f9bbcfc4
|
||||||
|
|
||||||
|
# for linux user it may be tricky to install wxPython from sources
|
||||||
|
# prebuilt packages: https://wxpython.org/pages/downloads/index.html
|
||||||
|
# https://extras.wxpython.org/wxPython4/extras/linux/gtk3/
|
||||||
wxPython>=4.1.1
|
wxPython>=4.1.1
|
||||||
|
|
||||||
backports.functools_lru_cache
|
backports.functools_lru_cache
|
||||||
|
@ -14,6 +20,10 @@ numpy
|
||||||
jinja2>2.9
|
jinja2>2.9
|
||||||
requests
|
requests
|
||||||
|
|
||||||
|
# toml release 0.10.2 still buggy for heterogenous arrays
|
||||||
|
# tomli is built as tomllib in python 3.11 and higher
|
||||||
|
tomli
|
||||||
|
|
||||||
# colormath - last official release: 3.0.0
|
# colormath - last official release: 3.0.0
|
||||||
# we need already submitted fixes - so let's grab them from the github repository
|
# we need already submitted fixes - so let's grab them from the github repository
|
||||||
colormath @ git+https://github.com/gtaylor/python-colormath.git@4a076831fd5136f685aa7143db81eba27b2cd19a
|
colormath @ git+https://github.com/gtaylor/python-colormath.git@4a076831fd5136f685aa7143db81eba27b2cd19a
|
||||||
|
|
Ładowanie…
Reference in New Issue