This example shows a custom EditingTool which uses the font’s italic angle to constrain point movements – a useful tool when working on slanted designs.

The tool subclasses EditingTool._mouseDragged and disables its Shift constraint, before adding its own Shift behaviour.

Off-curve points are constrained in relation to their on-curve point, not the last mouse down point.

from math import tan, radians
from mojo.events import EditingTool, installTool
from defcon import Point

class ConstrainAngleEditingTool(EditingTool):
    
    def getToolbarTip(self):
        return "Constrain Angle"    
    
    def modifyDeltaForAngle(self, delta, angle):
        '''Constrain delta to a given angle.'''
        _tan1 = tan(radians(angle + 90))
        _tan2 = tan(radians(angle))
        if abs(_tan1) < abs(_tan2):
            delta.x = -_tan1 * delta.y
        else:
            delta.y = _tan2 * delta.x
                    
    def modifyDraggingPoint(self, point, delta):
        '''Constrain on-curve points to italic angle.'''

        # get italic angle from font!
        f = CurrentFont()
        self.angle = f.info.italicAngle - 90

        # calculate delta for current mouse down point
        if self.mouseDownPoints:
            fx, fy = self.mouseDownPoints[-1]
        else:
            fx, fy = point

        delta.x = point.x - fx
        delta.y = point.y - fy
        
        # constrain delta to angle
        if self.shiftDown:
            self.modifyDeltaForAngle(delta, self.angle)
            
        return point, delta
    
    def _mouseDragged(self, point, delta):
        '''Constrain off-curve points to italic angle.'''

        # apply default _mouseDragged behavior first
        shiftdown = self.shiftDown
        self.shiftDown = False
        super()._mouseDragged(point, delta)
        self.shiftDown = shiftdown

        # handle single off-curve point selection
        if self.shiftDown and self.selection.containsSingleOffCurve():

            # get selected off-curve point
            offcurve = self.selection.selectedPoints[0]

            # get related on-curve point
            info = self.selection.selectionDataForPoint(offcurve)
            anchor = info["anchor"]
            
            # calculate delta for off-curve point
            offcurveDelta = Point((offcurve.x - anchor.x, offcurve.y - anchor.y))

            # constrain delta to angle
            self.modifyDeltaForAngle(offcurveDelta, self.angle)

            # update position of off-curve point
            offcurve.x = anchor.x + offcurveDelta.x
            offcurve.y = anchor.y + offcurveDelta.y


installTool(ConstrainAngleEditingTool())
Last edited on 01/09/2021