Add copy of foreground shapes to background, without overwriting background, in script

It’s super easy to copy the foreground layer to its background in a script… but only if I wish to completely overwrite the background with the foreground. What if I want to simply add to the existing background?

Here’s my actual goal:

  • Copy the foreground paths to the background
  • Copy the foreground paths to the background, again, and shift the second copy, in order to create a “double stroke” or mimic a “wider flat-nibbed pen” as a guide for editing the foreground into a bold glyph

Problem: I can get most of the way there, but when I make a second copy of the foreground shapes, they become somehow “entangled” between background and foreground, so that editing nodes in one also edits those nodes in the other.

I’ve search around the API docs and forum, but haven’t found any answers yet. I’m guessing I might be missing something pretty easy…

Here’s my code so far:

# MenuTitle: Prep bold template in background
__doc__ = """
    For all selected glyphs, copy the foreground to background, then copy again and translate it, 
    to give a suggested starting point for bold outline adjustments.
"""

from AppKit import NSAffineTransform
from copy import copy

# Starting point for Kyrios: over 100 units, up 50 units
shiftX, shiftY = 100, 50

# get current font
font = Glyphs.font

# get foreground layer of current glyph
selectedLayer = font.selectedLayers[0]

# overwrite background with foreground
selectedLayer.background = selectedLayer.copy()

## translate copy by shiftX, shiftY

# set up a transformation object, and add a transformation
transform = NSAffineTransform.new()
transform.translateXBy_yBy_(shiftX, shiftY)

# apply the transformation to all background path nodes
for path in selectedLayer.background.paths:
    for node in path.nodes:
        node.position = transform.transformPoint_(node.position)

# QUESTION: here’s where things break down....

# GOAL: copy foreground a second time, but don’t override background
selectedLayer.background.shapes += copy(selectedLayer.shapes)

# RESULT: it copies the foreground path, but the background and foreground versions
    # of the paths are "entangled" – when I edit one, the points in the other layer also move. Whyyyy?

This is almost getting me what I want… but yeah, any edits to the foreground also change the background copy, which then renders the background template useless.

What’s more, if I try to do a “scaling edit” on the foreground layer – holding control+option while move points to scale connected off-curves, the moved points transform into a weight 0% scale:

What might I be missing here?

Thanks so much for any pointers!

I have figured out a version of the script that seems to work, but I’m fairly sure it is way more complicated than it has to be:

# MenuTitle: Prep bold template in bg, take 2
__doc__ = """
    For all selected glyphs, copy the foreground to background, then copy again and translate it, 
    to give a suggested starting point for bold outline adjustments.
	
	Adapts function from
	https://github.com/mekkablue/Glyphs-Scripts/blob/1933522ae026c311ac6e5ef62527357ef227d18a/Interpolation/Copy%20Layer%20to%20Layer.py#L13

    This seems to work, but is probably more complicated than it has to be.
"""

from GlyphsApp import Glyphs, GSPath, GSComponent
from AppKit import NSAffineTransform

# Starting point for Kyrios: over 100 units, up 50 units
shiftX, shiftY = 100, 50


def copyPathsFromLayerToLayer(sourceLayer, targetLayer, keepOriginal=False):
    """Copies all paths from sourceLayer to targetLayer"""
    numberOfPathsInSource = len(sourceLayer.paths)
    numberOfPathsInTarget = len(targetLayer.paths)

    if numberOfPathsInTarget != 0 and not keepOriginal:
        print("- Deleting %i paths in target layer" % numberOfPathsInTarget)
        try:
            # GLYPHS 3
            for i in reversed(range(len(targetLayer.shapes))):
                if isinstance(targetLayer.shapes[i], GSPath):
                    del targetLayer.shapes[i]
        except:
            # GLYPHS 2
            targetLayer.paths = None

    if numberOfPathsInSource > 0:
        print("- Copying paths")
        for thisPath in sourceLayer.paths:
            newPath = thisPath.copy()
            try:
                # GLYPHS 3
                targetLayer.shapes.append(newPath)
            except:
                # GLYPHS 2
                targetLayer.paths.append(newPath)


# This should be the active selection, not necessarily the selection on the inputted fonts
Font = Glyphs.font
selectedGlyphs = [
    layer.parent for layer in Font.selectedLayers if layer.parent.name is not None
]

for thisGlyph in selectedGlyphs:
    try:
        print("🔠 %s" % thisGlyph.name)
        sourcelayer = Font.selectedLayers[0]

        targetlayer = sourcelayer.background

        copyPathsFromLayerToLayer(sourcelayer, targetlayer, keepOriginal=True)

        ## translate copy by shiftX, shiftY

        # set up a transformation object, and add a transformation
        transform = NSAffineTransform.new()
        transform.translateXBy_yBy_(shiftX, shiftY)

        # apply the transformation to all background path nodes
        for path in targetlayer.paths:
            for node in path.nodes:
                node.position = transform.transformPoint_(node.position)

        copyPathsFromLayerToLayer(sourcelayer, targetlayer, keepOriginal=True)

    except Exception as e:
        Glyphs.showMacroWindow()
        print("\n⚠️ Script Error:\n")
        import traceback

        print(traceback.format_exc())

This should work:

# MenuTitle: Prep bold template in bg, take 2
__doc__ = """
For all selected glyphs, copy the foreground to background and translate it, 
to give a suggested starting point for bold outline adjustments.

Adapts function from
https://github.com/mekkablue/Glyphs-Scripts/blob/1933522ae026c311ac6e5ef62527357ef227d18a/Interpolation/Copy%20Layer%20to%20Layer.py#L13
"""

from GlyphsApp import Glyphs, GSPath, GSComponent
from AppKit import NSAffineTransform

# Starting point for Kyrios: over 100 units, up 50 units
shiftX, shiftY = 100, 50


def copyPathsFromLayerToLayer(sourceLayer, targetLayer, offset=None, keepOriginal=False):
    """Copies all paths from sourceLayer to targetLayer"""
    numberOfPathsInSource = len(sourceLayer.paths)
    numberOfPathsInTarget = len(targetLayer.paths)

    if numberOfPathsInTarget != 0 and not keepOriginal:
        print("- Deleting %i paths in target layer" % numberOfPathsInTarget)
        try:
            # GLYPHS 3
            for i in reversed(range(len(targetLayer.shapes))):
                if isinstance(targetLayer.shapes[i], GSPath):
                    del targetLayer.shapes[i]
        except:
            # GLYPHS 2
            targetLayer.paths = None

    if numberOfPathsInSource > 0:
        if offset:
            transform = NSAffineTransform.new()
            transform.translateXBy_yBy_(offset[0], offset[1])
        print("- Copying paths")
        for thisPath in sourceLayer.paths:
            newPath = thisPath.copy()
            if offset:
                newPath.transform_(transform)
            try:
                # GLYPHS 3
                targetLayer.shapes.append(newPath)
            except:
                # GLYPHS 2
                targetLayer.paths.append(newPath)


# This should be the active selection, not necessarily the selection on the inputted fonts
Font = Glyphs.font
selectedGlyphs = [
    layer.parent for layer in Font.selectedLayers if layer.parent.name is not None
]

for thisGlyph in selectedGlyphs:
    try:
        print("🔠 %s" % thisGlyph.name)
        sourcelayer = Font.selectedLayers[0]

        targetlayer = sourcelayer.background

        copyPathsFromLayerToLayer(sourcelayer, targetlayer, (shiftX, shiftY), keepOriginal=True)

    except Exception as e:
        Glyphs.showMacroWindow()
        print("\n⚠️ Script Error:\n")
        import traceback
        print(traceback.format_exc())

I don’t think you should care about Glyphs 2 any more

Thanks so much! Seems like a good idea to combine the transformation in the copy function.

Also, good point, I haven’t touched glyphs 2 for years, so I can probably simplify by removing that logic.

I’ll try to update this if I improve it further. I appreciate the advice!