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
|
||||
*.tar.gz
|
||||
*.po
|
||||
*.log
|
||||
dist/
|
||||
build/
|
||||
locales/
|
||||
|
@ -22,10 +23,15 @@ flaskserverport.json
|
|||
electron/yarn.lock
|
||||
|
||||
# debug and profile files
|
||||
/DEBUG.ini
|
||||
logs/
|
||||
/DEBUG.toml
|
||||
/LOGGING.toml
|
||||
/LOGGING[0-9]*.toml
|
||||
/debug*
|
||||
/.debug*
|
||||
# old debug files
|
||||
|
||||
# old debug files - to be removed
|
||||
/DEBUG*.ini
|
||||
/DEBUG
|
||||
/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 sys
|
||||
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
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
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:
|
||||
# no arguments - prevent accidentally running this script
|
||||
msg = "No arguments given, exiting!" # without gettext localization see _()
|
||||
|
@ -26,13 +57,14 @@ if len(sys.argv) < 2:
|
|||
dlg.ShowModal()
|
||||
dlg.Destroy()
|
||||
except ImportError:
|
||||
print(msg)
|
||||
print(msg, file=sys.stderr)
|
||||
else:
|
||||
print(msg)
|
||||
print(msg, file=sys.stderr)
|
||||
exit(1)
|
||||
|
||||
ini = configparser.ConfigParser()
|
||||
ini.read(SCRIPTDIR / "DEBUG.ini") # read DEBUG.ini file if exists
|
||||
# activate logging - must be done before any logging is done
|
||||
debug_logging.activate_logging(running_as_frozen, ini, SCRIPTDIR)
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
# check if running from inkscape, given by environment variable
|
||||
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:
|
||||
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_type = 'none'
|
||||
profiler_type = 'none'
|
||||
|
@ -48,54 +81,42 @@ if not running_as_frozen: # debugging/profiling only in development mode
|
|||
# specify debugger type
|
||||
# but if script was already started from debugger then don't read debug type from ini file or cmd line
|
||||
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:
|
||||
# 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)
|
||||
|
||||
# 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:
|
||||
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
|
||||
# - 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:
|
||||
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':
|
||||
from lib.debug import debug # import global variable debug - don't import whole module
|
||||
debug.enable(debug_type, SCRIPTDIR, ini)
|
||||
from lib.debug.debugger import init_debugger
|
||||
init_debugger(debug_type, ini)
|
||||
# check if debugger is really activated
|
||||
debug_active = bool((gettrace := getattr(sys, 'gettrace')) and gettrace())
|
||||
|
||||
# warnings are used by some modules, we want to ignore them all in release
|
||||
# - see warnings.warn()
|
||||
if running_as_frozen or not debug_active:
|
||||
import warnings
|
||||
warnings.filterwarnings('ignore')
|
||||
# activate logging for svg
|
||||
# we need to import only after possible modification of sys.path, we disable here flake8 E402
|
||||
from lib.debug import debug # noqa: E402 # import global variable debug - don't import whole module
|
||||
debug.enable() # perhaps it would be better to find a more relevant name; in fact, it's about logging and svg creation.
|
||||
|
||||
# TODO - check if this is still needed for shapely, apparently shapely now uses only exceptions instead of io.
|
||||
# all logs were removed from version 2.0.0 and above
|
||||
# ---- 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 ----
|
||||
# log startup info
|
||||
debug_logging.startup_info(logger, SCRIPTDIR, running_as_frozen, running_from_inkscape, debug_active, debug_type, profiler_type)
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
||||
# pop '--extension' from arguments and generate extension class name from extension name
|
||||
# example: --extension=params will instantiate Params() class from lib.extensions.
|
||||
|
@ -151,7 +172,4 @@ else: # if not debug nor profile mode
|
|||
finally:
|
||||
restore_stderr()
|
||||
|
||||
# if shapely_errors.tell(): # see above plan to remove this in future for shapely
|
||||
# errormsg(shapely_errors.getvalue())
|
||||
|
||||
sys.exit(0)
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
|
||||
import errno
|
||||
import logging
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
|
@ -66,9 +65,6 @@ class APIServer(Thread):
|
|||
self.flask_server.shutdown()
|
||||
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/pytorch/pytorch/issues/71029
|
||||
def find_free_port(self):
|
||||
|
@ -77,8 +73,6 @@ class APIServer(Thread):
|
|||
return s.getsockname()[1]
|
||||
|
||||
def run(self):
|
||||
self.disable_logging()
|
||||
|
||||
self.host = "127.0.0.1"
|
||||
self.port = self.find_free_port()
|
||||
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
|
||||
#
|
||||
# Copyright (c) 2010 Authors
|
||||
# Copyright (c) 2024 Authors
|
||||
# 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
|
||||
# - 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)
|
||||
|
||||
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
|
||||
arguments:
|
||||
- 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
|
||||
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_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
|
||||
'''
|
||||
|
||||
# see static void set_extensions_env() in inkscape/src/inkscape-main.cpp
|
||||
# what we do:
|
||||
# - 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
|
||||
def resole_debug_type(ini: configparser.ConfigParser):
|
||||
def resolve_debug_type(ini: dict):
|
||||
# enable/disable debugger from bash: -d
|
||||
if os.environ.get('INKSTITCH_DEBUG_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
|
||||
debug_enable = True
|
||||
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:
|
||||
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
|
||||
|
||||
|
||||
# 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
|
||||
if os.environ.get('INKSTITCH_PROFILE_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
|
||||
profile_enable = True
|
||||
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
|
||||
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:
|
||||
profiler_type = 'none'
|
||||
|
||||
|
@ -189,13 +198,19 @@ def resole_profile_type(ini: configparser.ConfigParser):
|
|||
# - 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_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
|
||||
|
||||
# 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':
|
||||
with_cprofile(extension, remaining_args, profile_file_path)
|
||||
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}'")
|
||||
|
||||
|
||||
def with_cprofile(extension, remaining_args, profile_file_path):
|
||||
def with_cprofile(extension, remaining_args, profile_file_path: Path):
|
||||
'''
|
||||
profile with cProfile
|
||||
'''
|
||||
|
@ -227,7 +242,7 @@ def with_cprofile(extension, remaining_args, profile_file_path):
|
|||
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
|
||||
'''
|
||||
|
@ -246,7 +261,7 @@ def with_profile(extension, remaining_args, profile_file_path):
|
|||
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
|
||||
'''
|
|
@ -3,7 +3,6 @@
|
|||
# Copyright (c) 2010 Authors
|
||||
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
|
||||
|
||||
import logging
|
||||
from copy import copy
|
||||
|
||||
import inkex
|
||||
|
@ -92,12 +91,7 @@ class BreakApart(InkstitchExtension):
|
|||
return polygons
|
||||
|
||||
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
|
||||
logger.setLevel(level)
|
||||
return valid
|
||||
|
||||
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.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
import sys
|
||||
|
@ -159,9 +158,6 @@ class PrintPreviewServer(Thread):
|
|||
self.flask_server.shutdown()
|
||||
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/pytorch/pytorch/issues/71029
|
||||
def find_free_port(self):
|
||||
|
@ -170,8 +166,6 @@ class PrintPreviewServer(Thread):
|
|||
return s.getsockname()[1]
|
||||
|
||||
def run(self):
|
||||
self.disable_logging()
|
||||
|
||||
self.host = "127.0.0.1"
|
||||
self.port = self.find_free_port()
|
||||
# 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
|
||||
|
||||
# get up to date inkex version (Febuary 10, 2024)
|
||||
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
|
||||
|
||||
backports.functools_lru_cache
|
||||
|
@ -14,6 +20,10 @@ numpy
|
|||
jinja2>2.9
|
||||
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
|
||||
# we need already submitted fixes - so let's grab them from the github repository
|
||||
colormath @ git+https://github.com/gtaylor/python-colormath.git@4a076831fd5136f685aa7143db81eba27b2cd19a
|
||||
|
|
Ładowanie…
Reference in New Issue