Call function outside of a plugin

It’s possible to call a function used in a plugin outside, in the macro panel for example?

class myPalettePlugin(PalettePlugin):
	@objc.python_method
	def settings(self):
		self.name = 'myPalettePlugin'

	@objc.python_method
	def start(self):
        pass

	def myFunction(self, color):
        print(f"Hello {color}")

Something like :

Glyphs.myPalettePlugin.myFunction("blue")

I am not at my computer so I cannot verify, but it should get you in a close direction of you dig into NSClassFromString and store that cass in a variable. Then you might be able to call its functions. (Typed on a phone)

What are you trying to do?

The palette plugin class will get instantiated for every document window, so there is not a single myPalettePlugin object in your example.

I made a Palette plugin with a button to display different layers in a new tab.
I would like to write a Python script to trigger the same function as one of my buttons.

Write a global/static/class function and place the logic there.

Then call that function from either your palette plugin or from a script/macro code.

Can I ask where to put that function and how to call it?

But importing one into the other is tricky. So using the plugin class might be easier.
make that method a class method, then you can call it like this:

pluginClass = NSClassFromString("MyClassName")
pluginClass.myMethod()

I have this method in my plugin :

def TEST(self):
    print("TEST")
    print(self.font)

In my macro I run :

pluginClass = NSClassFromString("MyClassName")
pluginClass.TEST(pluginClass)

It works, but how do you deal with methods that use self variables?

AttributeError: No attribute {attributeName}

With a palette plugin, each document gets its own instance of the plugin class. If you want to call a method that takes self as an argument (not a class method), you first need to get the plugin instance that you want to call the method on.

For example, when two documents are open, there are two instances of your plugin. What do you want to happen? For which of those two instances do you want to call the method? Or do you want to call the method on both instances? If so, in what order? What should happen if no document is open?

This all depends heavily on your specific use case and in general I don’t think it is a good idea to call palette plugin methods from an outside script. Perhaps the functionality can be moved out of the plugin method so it no longer relies on self and can thus be called by both the plugin method and your script.

1 Like

I wouldn’t instantiate the plugin. It might load some view and stuff. Thats why I recommended to use a class method:

@classmethod
def TEST(cls):
    print("TEST")
    # print(self.font) # you can’t access instances variables. 

to run:

pluginClass = NSClassFromString("MyClassName")
pluginClass.TEST()

It depends on what you like to do. There are the problems that Florian mentioned. If you need to do something with the font object, you need to acquire that in your script and add it as an argument to TEST(). It could work like this:

@classmethod
def processFont(cls, font):
    print("TEST")
    print(font)

@python_method
def pluginMethod(self):
    MyClassName.processFont(self.font)

And in the script:

pluginClass = NSClassFromString("MyClassName")
pluginClass.processFont(Glyphs.font)

That way, you can reuse the code from both the plugin and the script.

2 Likes

I’ve used something similar to enable/disable a plugin from the macro window.

In my main module class:

cp_key = "de.kutilek.corretto"

class Corretto(object):
    @staticmethod
    def disable():
        """Disable post-processing globally."""
        Glyphs.defaults["%s.active" % cp_key] = False
        Glyphs.showNotification("Corretto Postprocessor", "Corretto is disabled.")

    @staticmethod
    def enable():
        """Enable post-processing globally."""
        active = Glyphs.defaults["%s.active" % cp_key]
        if active is None or active:
            print("Corretto already was enabled.")
        Glyphs.defaults["%s.active" % cp_key] = True
        Glyphs.showNotification("Corretto Postprocessor", "Corretto is enabled.")

and in plugin.py, I add the static methods as builtin functions:

from corretto import Corretto


class CorrettoPlugin(GeneralPlugin):
    @objc.python_method
    def start(self):
        # Convenience functions to enable/disable Corretto globally
        builtins.disable_corretto = Corretto.disable
        builtins.enable_corretto = Corretto.enable

(Code shortened for clarity)

In the macro panel, you can then just call

disable_corretto()

or

enable_corretto()

(Off topic — A word of caution: Try to avoid periods in defaults keys. If a key contains a period, you cannot reliably observe changes to that value, which you might want to do in the future. Instead, use keys like MyPluginSomeKeyName.)

1 Like

(jumping on the off-topic)
@FlorianPircher What about underscores instead of PascalCase?
Georg once told me about this, where my keys used to be of type "com.markfromberg.plugin.somekey" (which as a practice I took over from the Glyphs SDK and old python plugins like Rainer’s back in the early 2016-ish days), and went to use "com_markfromberg_plugin_somekey" instead.

I still have both versions flying around because I have so many plugins around. I hesitate to change all of them, because users will then suddenly being dropped to default once. Maybe that’s not too bad.

Underscores work as well. I needed to observe default values in my Guten Tag plugin where I originally used periods in the defaults keys. So I wrote migration code that checks for the old defaults keys, and, if present, copies the values to the new key and deletes the value for the old key:

Oh, that’s a good idea! Thanks :blush: