1

I'm developing a plugin for Inkscape. Some versions:

  • Inkscape v0.92.3
  • Windows 10, version 1803 (build 17134.165)
  • Python 3.7 explicitly installed
  • MonoDevelop Version 7.7 Preview (7.7) Extra versions below

Installation Locations:

  • Inkscape: C:\Program Files\Inkscape
  • Extension: C:\Program Files\Inkscape\share\extensions
    • Contains: myplugin.inx, myplugin.py, MyPlugin.exe

I've made a plugin which, for development reasons, works as currently intended.
Most important of all, it runs when I run it either from MonoDevelop, or the built exe itself (both with the generated .dll's etc in the same location, or with only the exe copied to a different location).

I use (a slightly edited version of) SugarPillStudio's python script to run the .exe file. However, when I run that python script by invoking the extension, the .exe is not launched. Inkscape blinks a message that says 'MyPlugin is launching...' and closes that as fast as it opens.

I know that the python script works, because I have it print debugging lines to a .log file on my desktop. I know that the .exe doesn't launch because I have it also writing lines to the same .log file, first thing when the main() is invoked. When I (successfully) run the .exe it does print to the file, when I run the extension it doesn't.

This leads me to believe there's a problem with the python script in invoking the .exe. Any help?

Python Script:

#!/usr/bin/env python
'''
sugarpillstudios.com/wp/?p=142
'''
import os, sys, subprocess, datetime

f=open("C:\Users\Diamundo\Documents\plugin.log", "a+")
f.write("[PYT] %s Python script called at: %s.\n" % (datetime.datetime.now().isoformat(), os.getcwd() ) )

argv = []  
for arg in sys.argv[:]:  
  if arg.startswith("--executable="):  
    executable = arg.split("=")[1]  
  else:  
    argv.append(arg)
argv[0] = executable  
f.write("[PYT] %s %s\n" % ( datetime.datetime.now().isoformat(), executable ) )
process = subprocess.Popen(argv,shell=False,stdout=subprocess.PIPE)
print process.communicate()[0]

Plugin.inx:

<inkscape-extension>
    <name>MyPlugin</name>
    <id>name.space.plugin.main</id>
    <param name="executable" type="string" gui-hidden="true">MyPlugin.exe</param>
    <effect>
        <object-type>all</object-type>
        <effects-menu>
            <submenu _name="MyPlugin"/>
        </effects-menu>
    </effect>
    <script>
        <command reldir="extensions" interpreter="python">myplugin.py</command>
    </script>
</inkscape-extension>

Extra Monodevelop versions:

Runtime:
    Microsoft .NET 4.0.30319.42000
    GTK+ 2.24.26 (Light theme)
    GTK# 2.12.45

NuGet 
Version: 4.3.1.4445

.NET Core
Runtime: C:\Program Files\dotnet\dotnet.exe
Runtime Versions:
    2.0.9
    2.0.5
SDK: C:\Program Files\dotnet\sdk\2.1.202\Sdks
SDK Versions:
    2.1.202
    2.1.4
MSBuild SDKs: Not installed
Diamundo
  • 148
  • 11

2 Answers2

0

Inkscape uses Python 2.7, which it brings with it, unless you set that differently in the settings file (edit manually).

If you want to write an Inkscape extension, you can learn how to do this by:

Moini
  • 1,239
  • 9
  • 10
  • I know about Python that comes with Inkscape, however I additionally installed 3.7 to see if that would fix the issue - apparently not. – Diamundo Aug 02 '18 at 17:12
  • Furthermore about your linked pages, I'm already familiar with them, I just have constraints to use C#/Mono to work with, so solely using Python unfortunately is out of the question... – Diamundo Aug 02 '18 at 17:13
  • If you need to use a different python executable, you need to set it in your settings file manually, i.e. open with a text editor. More info here: http://wiki.inkscape.org/wiki/index.php/Extension_Interpreters – Moini Aug 02 '18 at 23:08
  • Additionally, I don't understand where the sys.argv[:] should come from. How do you invoke a python extension with parameters?... – Moini Aug 02 '18 at 23:10
  • Running other programs outside of Inkscape should work just like the example given in the link to the pathops extension. – Moini Aug 02 '18 at 23:12
  • Well, the parameters for the python script in your example, are parsed at the Class definition, starting at line 210. Inkscape generates those from the fields in the .inx menufile, and with inkex.py the OptionParser class is used. – Diamundo Aug 03 '18 at 08:07
  • But thanks, I might look into this and perhaps alter the Python script to use inkex before calling the exe... I'll report back how and if it works :) – Diamundo Aug 03 '18 at 08:08
  • Yes, please! I hope you find a way. – Moini Aug 04 '18 at 00:00
  • See the answer I just posted. If you want to feature it somewhere in the Wiki, feel free to do so :) (if you need me to host it on GitLab/GitHub, I can arrange that too). – Diamundo Aug 06 '18 at 10:08
  • Thanks! Would you like Wiki access to add the info yourself? I've tried to fix the 'his' in your answer to the appropriate 'her' but stackoverflow doesn't allow me to make three-letter edits, and I've got no other improvements to make on your answer... Can you fix this, please, @Diamundo? – Moini Aug 06 '18 at 21:53
  • Whoops, sorry! I've altered it now. As for wiki access, I'm not that interested but thanks anyway :) – Diamundo Aug 08 '18 at 07:21
0

Loosely based on the pathops.py file, linked by Moini in her answer, I've come up with the following file.

About

It uses the inkex.py (source on GitLab) library to declare an Inkscape Effect. The Effect class uses the OptionParser library to parse the default given parameters (e.g. --id=$$ for selected nodes where $$ is the XML node's 'id' tag's value). By adding the custom executable option, we can also parse this.

Parsing arguments

After the OptionParser is done parsing, the values will be visible in self.options, i.e. our executable now lives in self.options.executable (because of the action="store" and dest="executable" parameters).
Furthermore, the temporary SVG-file as created by Inkscape, can be found in self.svg_file.

Saving edits

As previously said, Inkscape makes a temporary file with the contents of the SVG in its then current state. Any edits you(r plugin) make(s) should not be saved back to this file, but returned to Inkscape itself - this is the premise of the Effect class: it edits an SVG and returns the edit to Inkscape. Further reading here.

Instead, in your plugin you should (readonly) open the file, read its contents, and then edit it. When you're done editing, write the entire SVG to your commandline.
Then, the line out, err = process.communicate(None) will grab your plugin's output and error-output. These are used to return information to Inkscape.

Notes

The structure of the cmd array is of no importance, except the fact that the executable should come as the very first element. All other array-elements can be anything in any order, I just added '--id=$$' to every ID because that's the way Inkscape uses, and this way it looks the same as if there's no Python middleware present. The same goes for the self.svg_file which I placed last, Inkscape does the same in its arguments - you could also make '--file='+self.svg_file from it for clarity.

Source

#!/usr/bin/env python

import os
from subprocess import Popen, PIPE
import time

try:
    import inkex_local as inkex
except ImportError:
    import inkex
#import simplestyle

class MyPlugin(inkex.Effect):
    def __init__(self):
        inkex.Effect.__init__(self)
        self.OptionParser.add_option("--executable", action="store", type="string", dest="executable", default="MyPlugin.exe")

    def effect(self):
        out = err = None

        cmd = []
        cmd.append(self.options.executable)
        for id in self.options.ids:
            cmd.append("--id=" + id)
        cmd.append(self.svg_file)
        #inkex.debug(cmd);

        process = Popen(cmd, shell=False, stdin=PIPE, stdout=PIPE, stderr=PIPE)
        out, err = process.communicate(None)

        if process.returncode == 0:
            print out
        elif err is not None:
            inkex.errormsg(err)

if __name__ == '__main__':
    myplugin = MyPlugin()
    myplugin.affect()
Diamundo
  • 148
  • 11