Inkscape.org
Creating New Extensions Debugging Python Extension
  1. #1
    gamesdad gamesdad @gamesdad
    😀

    Is there a way to use a debugger for python extensions as they run within Inkscape? So far, I have successfully written an .inx file and associated .py and got them working on the Extension menu in Inkscape. But I am working in just a text editor and using message boxes to observe the variables in python! Previously, I have used PyCharm where I can set a breakpoint, view the variables and find my mistakes much more easily, and run my code one line at a time etc. Also, I have used VBA within Excel, Word and Corel, which again allows breakpoints and inspecting variables etc.

  2. #2
    Arvid22 Arvid22 @Arvid22
    *

    I also need the solution of this.😊

  3. #3
    gamesdad gamesdad @gamesdad

    The following is partial solution to my own question. It allows me to run my Inkscape extension within the PyCharm debugger. I think there must be better ways to do this. The following worked for me, but it is not good style.

    my_test_extension.py and my_test_extension.inx are provided below.

    Put them into your Inkscape ‘User extensions’ folder (as shown in Inkscape > Preferences > System)

    Restart Inkscape. Create a text object in your drawing and check the extension converts it to uppercase.

    So far, this is just an ordinary Inkscape extension. The next thing is to run it within PyCharm.

    Open PyCharm and create a New Project, and set its Location to your Inkscape User extensions folder. Confirm that you want to import the existing files.

    Configure this PyCharm project such that the ‘Script path’ points to: my_test_extension.py within your User extensions folder.

    Try running it in PyCharm. It will probably fail because it can’t find inkex

    Copy the inkex folder (~47 small files inc 3 subfolders) from C:\Program Files\Inkscape\share\inkscape\extensions to your Inscape ‘User extensions’ folder. (I am sure there must be a better way to do this! I am just telling you what worked for me.)

    Try running it again in PyCharm. It will probably fail because it can’t find lxml

    Install the lxlm package (Pycharm > Settings > Project Interpreter - add it to the Package list)

    Now it should run. Or at least is does for me! You will need to change the filename 'D:\MyTestDrawing.svg' toward the bottom of my_test_extension.py

    With this configuration, I can run my extension either within Inkscape or PyCharm. I can edit and debug it in PyCharm and then test that it still works in Inkscape. I can test it on the same svg file that is open in Inkscape and use File > Revert to reload the svg from disk. I can set break points in PyCharm and ‘see’ what is going on. I can use the Step Over and Step Into buttons in PyCharm to see more.

    Good luck

     

    #!/usr/bin/env python
    """
    This py file can run as an Inkscape extension OR within PyCharm.
    When running in Inkscape, it works on the drawing that is open in Inkscape.
    When running in PyCharm, it works on the svg file (hard coded below).
    During testing, the svg file can be open in Inkscape.
    (Use the Save and Revert buttons on the File menu.)
    
    This demo converts any text in the drawing to uppercase.
    """
    
    from inkex import EffectExtension
    
    
    class MyTestExtension(EffectExtension):
        # Here we are extending the functionality of EffectExtension,
        # which is a class already defined in inkex.
    
        def effect(self):
            # Override inkex's definition of effect,
            # so that it does what we want in this case.
            nodes = self.svg.selected or {None: self.document.getroot()}
            # When running in PyCharm, the above will always return the whole document.
            for elem in nodes.values():
                self.process_element(elem)
    
        def process_element(self, node):
            # Having this as a separate method allows it to call itself recursively,
            # which allows it to work through the XML tag layers.
    
            if node.text is not None:
                node.text = node.text.upper()
    
            for child in node:
                self.process_element(child)
    
    
    if __name__ == '__main__':
        # This is the main entry point where execution should start
        # when run either as an Inkscape extension or within PyCharm.
    
        # If running in PyCharm we need to provide an svg file to work on.
        # To determine if we are running in PyCharm, we can look at __file__
        # If running in PyCharm, __file__ will be a full path, which will have a / separator.
        # If running in Inkscape, __file__ will just be the file name with no path and no /
        if '/' in __file__:
            # We are running in PyCharm
            input_file = r'D:\MyTestDrawing.svg'
            output_file = input_file
            MyTestExtension().run([input_file, '--output=' + output_file])
        else:
            # We are running in Inkscape
            MyTestExtension().run()
    
    
    <?xml version="1.0" encoding="UTF-8"?>
    <inkscape-extension xmlns="http://www.inkscape.org/namespace/inkscape/extension">
      <name>My Test Extension</name>
      <id>org.none</id>
      <effect>
        <object-type>all</object-type>
        <effects-menu>
          <submenu name="My Extensions">
          </submenu>
        </effects-menu>
      </effect>
      <script>
        <command location="inx" interpreter="python">my_test_extension.py</command>
      </script>
    </inkscape-extension>

     

  4. #4
    gustavo.nunes.goretkin gustavo.nunes.goretkin @gustavo.nunes.goretkin

    In any Python extension, i added

    import sys
    inkex.utils.errormsg(inkex.__file__)
    inkex.utils.errormsg(sys.executable)
    inkex.utils.errormsg(sys.path)
     

    and when I run that extension, I see (on macOS):

    /Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions/inkex/__init__.py
    /Applications/Inkscape.app/Contents/Resources/bin/python3
    ['/Users/goretkin/Library/Application Support/org.inkscape.Inkscape/config/inkscape/extensions/inkscape-applytransforms', '/Users/goretkin/Library/Application Support/org.inkscape.Inkscape/config/inkscape/extensions', '/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions', '/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions/inkex/deprecated-simple', '/Applications/Inkscape.app/Contents/Frameworks/Python.framework/Versions/3.8/lib/python38.zip', '/Applications/Inkscape.app/Contents/Frameworks/Python.framework/Versions/3.8/lib/python3.8', '/Applications/Inkscape.app/Contents/Frameworks/Python.framework/Versions/3.8/lib/python3.8/lib-dynload', '/Applications/Inkscape.app/Contents/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages', '/Applications/Inkscape.app/Contents/Resources/lib/python3.8/site-packages', '/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions/inkex/deprecated-simple']

    If you run that python interpreter and see its `sys.path`:

    $ /Applications/Inkscape.app/Contents/Resources/bin/python3
    Python 3.8.5 (default, Sep  8 2020, 23:22:40)
    [Clang 11.0.0 (clang-1100.0.33.17)] on darwin
    Type "help", "copyright", "credits" or "license" for more information.
    >>> import sys
    >>> sys.path
    ['', '/Applications/Inkscape.app/Contents/Frameworks/Python.framework/Versions/3.8/lib/python38.zip', '/Applications/Inkscape.app/Contents/Frameworks/Python.framework/Versions/3.8/lib/python3.8', '/Applications/Inkscape.app/Contents/Frameworks/Python.framework/Versions/3.8/lib/python3.8/lib-dynload', '/Applications/Inkscape.app/Contents/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages', '/Applications/Inkscape.app/Contents/Resources/lib/python3.8/site-packages']
    >>>

     

    The difference in this case appears to be

    >>> set(path_ref).difference(set(sys.path))
    {'/Users/goretkin/Library/Application Support/org.inkscape.Inkscape/config/inkscape/extensions/inkscape-applytransforms', '/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions', '/Users/goretkin/Library/Application Support/org.inkscape.Inkscape/config/inkscape/extensions', '/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions/inkex/deprecated-simple'}

     

    The crucial difference being `'/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions'` So if you want to get a python environment that has the same python modules available as an inkscape extension, do (again this is specific to macOS, but generalizes to other systems):

     

    >>> sys.path.insert(0, '/Applications/Inkscape.app/Contents/Resources/share/inkscape/extensions')
    >>> import inkex

     

    There is a great feature that I hope will be available soon, to make this better: https://gitlab.com/inkscape/inkscape/-/merge_requests/417

     

  5. #5
    Andreas_Martin Andreas_Martin @Andreas_Martin

    This works perfectly for me. On Ubuntu 20.04, just adding

    import sys
    sys.path.insert(0, "/usr/share/inkscape/extensions")
     

    to the top of the file allows me to debug into my extension, in my case via VS Code.

  6. #6
    alexgrey alexgrey @alexgrey

    Hey! Yes there is a way to do so and I found it on the internet while I was working on one of my client's websites that was developed in Python. I am sharing the simple steps below:

    Put them into your Inkscape ‘User extensions’ folder (as shown in Inkscape > Preferences > System)

    Restart Inkscape. Create a text object in your drawing and check the extension converts it to uppercase.

    So far, this is just an ordinary Inkscape extension. The next thing is to run it within PyCharm.

    Open PyCharm and create a New Project, and set its Location to your Inkscape User extensions folder. Confirm that you want to import the existing files.

    Configure this PyCharm project such that the ‘Script path’ points to: my_test_extension.py within your User extensions folder.

    Try running it in PyCharm. It will probably fail because it can’t find inkex

    Copy the inkex folder (~47 small files inc 3 subfolders) from C:\Program Files\Inkscape\share\inkscape\extensions to your Inscape ‘User extensions’ folder. (I am sure there must be a better way to do this! I am just telling you what worked for me.)

    Try running it again in PyCharm. It will probably fail because it can’t find lxml

    Install the lxlm package (Pycharm > Settings > Project Interpreter - add it to the Package list)

  7. #7
    Victor Westmann Victor Westmann @victorwestmann

    Hi Alexgrey, thank you so much for all your instructions! I have an idea for an extension and I'm trying to develop it myself as well!

    However, I got confused on one of your steps:
    "Configure this PyCharm project such that the ‘Script path’ points to: my_test_extension.py within your User extensions folder."

    Is it possible you do a simple and short video or maybe put some screenshots? Only if you have time, of course. It would be super helpful for us amateurs who are trying to learn how to develop new extensions for Inkcape 1.2 onwards. Thank you so much! If I make any progress I will also make sure to come back here and share it with you as well as the extensions channel in Inkscape chat room (chat.inskcape.org).

  8. #8
    Victor Westmann Victor Westmann @victorwestmann

    The error message I got (on Windows 10 Version 21H2, 64 bits, using PyCharm 2019.2.6)

    C:\Users\victo\AppData\Local\Programs\Python\Python39\python.exe C:/Users/victo/Documents/_portableApps/inkscape-1.2-beta_2022-04-05_1b65182ce9-x64/share/inkscape/extensions/timeline.py
    Traceback (most recent call last):
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\timeline.py", line 25, in <module>
        import inkex
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\__init__.py", line 11, in <module>
        from .extensions import *
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\extensions.py", line 34, in <module>
        from .elements import (
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\elements\__init__.py", line 10, in <module>
        from ._base import ShapeElement, BaseElement
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\elements\_base.py", line 38, in <module>
        from ..styles import Style, Classes
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\styles.py", line 33, in <module>
        from .css import ConditionalRule
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\css.py", line 27, in <module>
        import cssselect
    ModuleNotFoundError: No module named 'cssselect'

    Process finished with exit code 1

  9. #9
    Victor Westmann Victor Westmann @victorwestmann
    *

    I was able to run

    pip install cssselect

    and

    pip install numpy

    and now the error changed to:

    C:\Users\victo\AppData\Local\Programs\Python\Python39\python.exe C:/Users/victo/Documents/_portableApps/inkscape-1.2-beta_2022-04-05_1b65182ce9-x64/share/inkscape/extensions/timeline.py
    Traceback (most recent call last):
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\timeline.py", line 29, in <module>
        class Timeline(inkex.GenerateExtension):
    NameError: name 'inkex' is not defined

    Process finished with exit code 1

    So I guess things are slowly improving! Timeline is the name of my extension in case I have not mentioned this before. :)

  10. #10
    Victor Westmann Victor Westmann @victorwestmann

    Now when I run my extension (named timeline.py) it does not give me ANY erorr. Which is super strange. At least when I ran it through CMD on Windows OS.

    However, when I interrupt the execution of the program I get the following:

    C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions>timeline.py
    Traceback (most recent call last):
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\timeline.py", line 79, in <module>
        Timeline().run()
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\base.py", line 230, in run
        self.load_raw()
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\base.py", line 246, in load_raw
        document = self.load(self.options.input_file)
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\base.py", line 471, in load
        document = load_svg(stream)
      File "C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions\inkex\elements\_parser.py", line 95, in load_svg
        return etree.parse(stream, parser=SVG_PARSER)
      File "src\lxml\etree.pyx", line 3521, in lxml.etree.parse
      File "src\lxml\parser.pxi", line 1880, in lxml.etree._parseDocument
      File "src\lxml\parser.pxi", line 1900, in lxml.etree._parseFilelikeDocument
      File "src\lxml\parser.pxi", line 1795, in lxml.etree._parseDocFromFilelike
      File "src\lxml\parser.pxi", line 1201, in lxml.etree._BaseParser._parseDocFromFilelike
      File "src\lxml\parser.pxi", line 615, in lxml.etree._ParserContext._handleParseResultDoc
      File "src\lxml\parser.pxi", line 721, in lxml.etree._handleParseResult
      File "src\lxml\etree.pyx", line 318, in lxml.etree._ExceptionContext._raise_if_stored
      File "src\lxml\parser.pxi", line 370, in lxml.etree._FileReaderContext.copyToBuffer
    KeyboardInterrupt
    ^C
    C:\Users\victo\Documents\_portableApps\inkscape-1.2-beta_2022-04-05_1b65182ce9-x64\share\inkscape\extensions>

  11. #11
    Victor Westmann Victor Westmann @victorwestmann
    *

    It seems I was able to install the missing python packages. Now I'm just studying the inkscape extension SVG Calendar (Extensions > Render > Calendar) source files:

    • svgcalendar.inx: with the frontend elements so the user can poke around the values of the components so they can be passed to the backend (next file).
    • svgcalendar.py: which is where the real intelligence and the implementation of the extension happens.

    I am going to attach here what I was able to achieve so far: (which is not a lot to be honest).

     

  12. #12
    inklinea inklinea @inklinea⛰️

    Can you show an example of what a completed timeline looks like ? 

  13. #13
    Victor Westmann Victor Westmann @victorwestmann

    Sure thing!

    Product Launch Plan Swimlanes
  14. #14
    XRoemer XRoemer @XRoemer

    Hi,

    I use PyDev in Eclipse for debugging. It works very well for me.

    With Pydev installed in Eclipse, the code looks like this:

    import sys
    platform = sys.platform
    from traceback import format_exc as tb
    
    class pydevBrk():  
        # adjust your path to PyDevs Folder pysrc
        if platform == 'linux':
            sys.path.append('/opt/eclipse/plugins/org.python.pydev_3.8.0.201409251235/pysrc')  
        else:
            sys.path.append(r'C:\Users\Boon\.p2\pool\plugins\org.python.pydev.core_9.3.0.202203051235\pysrc')  
    
        def run(self):
            from pydevd import settrace
            settrace('localhost', port=5678, stdoutToServer=True, stderrToServer=True) 
    pd = pydevBrk().run

     

    When you start the PyDev server you can put pd() anywhere in your code for inspecting.

    Regards,

    Xaver

  15. #15
    FrankI4466 FrankI4466 @FrankI4466

    Hi, 

    In vscode adding in setting.json (or local setting.json):

     

        "python.autoComplete.extraPaths": [

            "C:\\Program Files\\Inkscape\\share\\inkscape\\extensions"

        ],

        "python.analysis.extraPaths": [

            "C:\\Program Files\\Inkscape\\share\\inkscape\\extensions"

        ],

  16. #16
    Mic_c Mic_c @Mic_c

    Hi, I have a question. After launching the code in Pycharm, how can I make it interact with Inkscape, for example, stopping at the corresponding breakpoint when selecting an object in Inkscape?

  17. #17
    nine1seven3oh nine1seven3oh @nine1seven3oh

    Followed the description of gamesdad and got everything working in pycharm, thanks! Passing the svg in the if __main__ bit of the script was what I was missing

    I added the inkscape extensions path to the python interpreter paths list instead of copying the entire inkex directory. Adding sys.path.insert(0, "/usr/share/inkscape/extensions") works to let the code run, but pycharm prefers to have it in the interpreter paths.

    Mic_c - I think the pycharm runs the script on the svg file, not actually using inkscape, so selecting an item won't work. I'm sure I read somewhere that you can use inkex in python without needing inkscape at all.

    But if you open your file in Inkscape and get the id of your object, you can temporarily set self.svg.selection to the item you want to debug as if you selected it, for example;

    self.svg.selection = self.svg.getElementById("g177")

     

  18. #18
    jahan.paisley jahan.paisley @jahan.paisley

    @XRoemer Thanks for posting an answer, it solved my problem, appreciate it 🙏