Accessing layer and associatedMasterId in remote script / Core API


#1

I’m working on a build script to help manipulate a GlyphsApp source via the command line, and I’ve gotten fairly far in using the Glyphs Core API (https://docu.glyphsapp.com/Core/index.html).

However, I’m getting hung up at trying to access layers and their associatedMasterIds, in order to copy outlines into newly-added masters. The code I’m ultimately trying to get to is similar to what you helped me figure out in the thread " How might I trigger “Instance as master” from the API?. I have successfully accessed an interpolatedFont and added a master, but now I need to copy between layers, and I’m hitting a wall.

Here’s an example of code I’m using. I call it with a command like python scripts/SCRIPT.py sources/FONT.glyphs:

from Glyphs import *
import sys
import os
import objc

relPath = sys.argv[-1] # gets file path passed in with command
directory = os.getcwd()

document = Glyphs.open((str(directory + "/" + relPath)), False)
currentFont = document.font()


print(currentFont.glyphs()[4]) # 👍 GSGlyph <0x7ffcc505d960>: Abrevedotbelow

print(currentFont.glyphs()[4].layers) # 👍 <native-selector layers of GSGlyph <0x7ffcc505d960>: Abrevedotbelow>


# The following are ways I tried to access the layer object, and the errors I received.

# print(currentFont.glyphs()[4].layers[0]) # 🚫 TypeError: 'objc.native_selector' object is not subscriptable

# print(currentFont.glyphs()[4].layers_()) # 🚫 AttributeError: 'NSDistantObject' object has no attribute 'layers_'

# print(currentFont.glyphs()[4].layers_()[0]) # 🚫 AttributeError: 'NSDistantObject' object has no attribute 'layers_'

# print(currentFont.glyphs()[4].layers()) # 🚫 objc.error: NSInternalInconsistencyException - decodeObjectForKey: class "MGOrderedDictionary" not loaded

# print(currentFont.glyphs()[4].layers()[0]) # 🚫 objc.error: NSInternalInconsistencyException - decodeObjectForKey: class "MGOrderedDictionary" not loaded

Thanks so much for any insight on this. I feel so close to doing what I need to do!


#2

(I’m on Glyphs 2.5.2, Mojave 10.14)


#3

When script outside of Glyphs, the python wrapper is not available. So pretty much everything is a method and not a property. So you need to add parents behind everything.

print(currentFont.glyphs()[4].layers()[0])

#4

Thanks for the quick reply!

After some trial and error, and eventually reading the helpful PyObjC intro, I did figure out that things needed parentheses.

However, as my code example above shows, layers only works (somewhat) without (). Possibly, it’s a bug, or maybe I have the wrong version of something?

When I use your exact line of code:

from Glyphs import *
import sys
import os
import objc

relPath = sys.argv[-1]
directory = os.getcwd()
document = Glyphs.open((str(directory + "/" + relPath)), False)
currentFont = document.font()

print(currentFont.glyphs()[4].layers()[0])

…this is the full error I receive:

Traceback (most recent call last):
  File "scripts/helpers/git-remote-tests.py", line 14, in <module>
    print(currentFont.glyphs()[4].layers()[0])
objc.error: NSInternalInconsistencyException - decodeObjectForKey: class "MGOrderedDictionary" not loaded

#5

Then better use the accessor

glyph = currentFont.glyphs()[4]
master = currentFont.fontMasterAtIndex_(0)
glyph.layerForKey_(master.id())

#6

Thanks, @GeorgSeifert! This really came in handy, today.

(If anyone comes across this and is curious, I used it to access and decompose a specific glyph during a FontMake build process, in order to get around what seems to be a rendering bug on macOS. Script: decompose-oslash.py)


#7

@GeorgSeifert @thundernixon Can you explain how I make the Glyphs Core API module available outside Glyphs? Do I copy or system link something to Python packages folder?


#8

Please have a look at this: https://github.com/schriftgestalt/GlyphsSDK/tree/master/Glyphs%20remote%20scripts


#9

Thanks, working great! However I’m having difficulty with methods that aren’t documented in the Core API Docs. And using help() and dir() doesn’t reveal any useful information.

For example, I’d like to export the Font as a variable font, and can see there is an “export” method on the Font object, but cannot see what input it requires.


#10

The font object has not export method. What are you trying to do? Instances can be exported as the example above shows.


#11

Ok, I would like to export the font as a variable font using the Core API. Is there any way to achieve this? I use tools like fontmake as well but for a particular build I need the GX font from Glyphs and currently need to export it manually.


#12

The export() function now supports VARIABLE as export format:
https://docu.glyphsapp.com/#export-formats


#13

I’m confused … my remote script for decomposing a specific glyph has stopped working, even if I use the previous versions of GlyphsApp (e.g. Version 2.5.2 (1191)).

For instance, print(font.fontMasterAtIndex_(0)) yields this error:

Traceback (most recent call last):
  File "sources/scripts/helpers/decompose-oslash.py", line 27, in <module>
    print(font.fontMasterAtIndex_(0))
objc.error: NSInternalInconsistencyException - decodeObjectForKey: class "GSFontMaster" not loaded

As another example, the original code:

for index,master in enumerate(font.fontMasters()):
    print(master)
    print(index)
    master = font.fontMasterAtIndex_(index)
    print(glyph.layerForKey_(master.id()))
    glyph.layerForKey_(master.id()).decomposeComponents()

…results in:

Traceback (most recent call last):
  File "sources/scripts/helpers/decompose-oslash.py", line 30, in <module>
    for index,master in enumerate(font.fontMasters()):
objc.error: NSInternalInconsistencyException - decodeObjectForKey: class "GSFontMaster" not loaded

I know that my remote script still works to a degree, because I still get the correct index when I search for /oslash in the font:

def oslashIndexer():
    oslashIndex = 0
    for index,glyph in enumerate(font.glyphs()):
        if glyph.name() == "oslash":
            oslashIndex = index
            return oslashIndex

oslashIndex = oslashIndexer()
print(oslashIndex)

# result: 389

I’m (nearly) certain that this scripting worked on the 18th. I can’t think of anything I’ve changed in my environment or the source file that would have caused the difference.

Any idea what I might be missing?


#14

I played around with some the external scripting support but didn’t finish it. So I need to take it out for now. I’ll fix this.


#15

Thanks for the information! So, I guess some version of the app would probably work?

For now, I’m just manually decomposing the offending glyph, and leaving the composed version as duplicated layers, if someone needs to edit that in the future.