Displaying distances between selected points in the glyph editor ↩
This example shows how to display distances between selected points in the glyph editor using Subscriber
and Merz
.
from math import hypot
from mojo.subscriber import Subscriber, registerGlyphEditorSubscriber
from mojo.roboFont import RPoint
RED = 1, 0, 0, 1
WHITE = 1, 1, 1, 1
class PointDistanceController(Subscriber):
debug = True
def build(self):
glyphEditor = self.getGlyphEditor()
self.container = glyphEditor.extensionContainer(
identifier="com.roboFont.PointDistanceController.foreground",
location="foreground",
clear=True
)
self.distancesLayer = self.container.appendBaseSublayer()
def glyphEditorDidSetGlyph(self, info):
self.drawMeasurements(info)
def glyphDidChangeSelection(self, info):
self.drawMeasurements(info)
def drawMeasurements(self, info):
# get the glyph from the notification
glyph = info["glyph"]
# get the selection
selection = glyph.selectedPoints + glyph.selectedAnchors
# check if the selection is more than 1 and less than 6
if 2 <= len(selection) <= 5:
self.distancesLayer.setVisible(True)
self.distancesLayer.clearSublayers()
done = []
# if 2 points selected, add one to get horizontal/vertical measurements
if len(selection) == 2:
pos = selection[0].x, selection[1].y
# only add the point if it doesn’t match with an existing one
if not any([pos == p.position for p in selection]):
helperPoint = RPoint()
helperPoint.position = pos
selection = set(selection + (helperPoint,))
# loop over all the points in the selection
for p in selection:
# loope in a the loop again over all the points in the selection
for p2 in selection:
# check if the point is not the same
if p == p2:
continue
# check if we already handled the point
if set([p, p2]) in done:
continue
# add a line to the distances layer
self.distancesLayer.appendLineSublayer(
startPoint=(p.x, p.y),
endPoint=(p2.x, p2.y),
strokeWidth=1,
strokeColor=RED
)
# calculate the center point
cx = p.x + (p2.x - p.x) * .5
cy = p.y + (p2.y - p.y) * .5
# calculate the distance
dist = hypot(p2.x - p.x, p2.y - p.y)
# store connections to avoid duplicates
done.append(set([p, p2]))
# create the label layer
self.distancesLayer.appendTextLineSublayer(
position=(cx, cy),
backgroundColor=RED,
text=f"{dist:.0f}" if dist % 1 == 0 else f"{dist:.2f}",
font="system",
weight="bold",
pointSize=12,
padding=(4, 1),
cornerRadius=4,
fillColor=WHITE,
horizontalAlignment='center',
verticalAlignment='center',
)
else:
self.distancesLayer.setVisible(False)
if __name__ == '__main__':
registerGlyphEditorSubscriber(PointDistanceController)
If you want to be able to toggle the visualization on and off, you can get some help from a WindowController. Use the callback of a vanilla checkbox to register or unregister the subscriber object.
#!/usr/bin/env python3
from math import hypot
from mojo.subscriber import Subscriber, WindowController
from mojo.subscriber import registerGlyphEditorSubscriber, unregisterGlyphEditorSubscriber
from mojo.roboFont import OpenWindow
from vanilla import FloatingWindow, CheckBox
RED = 1, 0, 0, 1
WHITE = 1, 1, 1, 1
class PointDistanceControllerPalette(WindowController):
debug = True
def build(self):
self.w = FloatingWindow((200, 40), "PointDistanceController")
self.w.checkBox = CheckBox((10, 10, -10, 20), "Display Distances",
callback=self.checkBoxCallback, value=True)
self.w.open()
def started(self):
PointDistanceController.controller = self
registerGlyphEditorSubscriber(PointDistanceController)
def destroy(self):
unregisterGlyphEditorSubscriber(PointDistanceController)
PointDistanceController.controller = None
def checkBoxCallback(self, sender):
if sender.get():
PointDistanceController.controller = self
registerGlyphEditorSubscriber(PointDistanceController)
else:
PointDistanceController.controller = None
unregisterGlyphEditorSubscriber(PointDistanceController)
class PointDistanceController(Subscriber):
debug = True
def build(self):
glyphEditor = self.getGlyphEditor()
self.container = glyphEditor.extensionContainer(
identifier="com.roboFont.PointDistanceController.foreground",
location="foreground",
clear=True
)
self.distancesLayer = self.container.appendBaseSublayer()
def destroy(self):
self.container.clearSublayers()
def glyphDidChangeSelection(self, info):
# get the glyph from the notification
glyph = info["glyph"]
# get the selection
selection = glyph.selection
# check if the selection is more than 1 and less than 6
if 2 <= len(selection) <= 5:
self.distancesLayer.setVisible(True)
self.distancesLayer.clearSublayers()
done = []
# loop over all the points in the selection
for p in selection:
# loope in a the loop again over all the points in the selection
for p2 in selection:
# check if the point is not the same
if p == p2:
continue
# check if we already handled the point
if set([p, p2]) in done:
continue
# add a line to the distances layer
self.distancesLayer.appendLineSublayer(
startPoint=(p.x, p.y),
endPoint=(p2.x, p2.y),
strokeWidth=1,
strokeColor=RED
)
# calculate the center point
cx = p.x + (p2.x - p.x) * .5
cy = p.y + (p2.y - p.y) * .5
# calculate the distance
dist = hypot(p2.x - p.x, p2.y - p.y)
# store connections to avoid duplicates
done.append(set([p, p2]))
# create the label layer
self.distancesLayer.appendTextLineSublayer(
position=(cx, cy),
backgroundColor=RED,
text=f"{dist:.0f}" if dist % 1 == 0 else f"{dist:.2f}",
font="system",
weight="bold",
pointSize=12,
padding=(4, 1),
cornerRadius=4,
fillColor=WHITE,
horizontalAlignment='center',
verticalAlignment='center',
)
else:
self.distancesLayer.setVisible(False)
if __name__ == '__main__':
OpenWindow(PointDistanceControllerPalette)