Constrain to angles other than 0, 90, 45.



  • Hey there!

    I'm drawing a weird slanted thing with angled extremes and it's getting annoying to align everything by hand to guidelines.

    So I was wondering if there's already a tool to constrain handles to an arbitrary angle (other than horizontal, vertical and 45º).

    Thought I'd ask before I dive into trying to write one myself.


  • admin

    I dont know of such a tool... and the current EditingTool has 0°, 90° and optionally 45° backed in.

    Maybe RF should add the italic angle as an optional constrain. I remember testing multiple constrains but it didnt work out good (I dont know why, probably to complex from the users point of view)

    But a single tool with a single constrain is not that hard! :)


  • admin

    hello @ro-hernandezz,

    here’s a custom editing tool which uses the font’s italic angle to constrain point movements. it’s actually very useful!

    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())
    

    handling off-curve points was a bit harder because they are constrained in relation to their on-curve point, not the last mouse down point. thanks @frederik for showing how to do it.

    have fun drawing weird slanted things!



  • @gferreira @frederik Oh wow! Thanks so so much!

    I was wondering how to write such a thing since I've never done an interactive tool, but my timeline seems to go much slower than Gustavo's!

    It seems I wasn't totally lost, but would've got stuck in sooo many places.

    Thanks again! I will def use this a lot.



  • @gferreira why _mouseDragged instead of mouseDragged?


  • admin

    hello @colinmford,

    that part was done by @frederik. if I understand it correctly, _mouseDragged is the internal method provided by the BaseEventTool superclass, which is called externally by the mouseDragged method in the EditingTool class that inherits from it. in this case, we’re modifying the internal behavior.

    it’s a bit like the _moveTo and moveTo methods in a BasePen

    I hope this is correct and makes sense!


  • admin

    Its indeed similar to a pen. This allows easy subclassing of internal tools by simply adding behaviour to a tool.

    This tool is subclassing _mouseDragged as it needs the same behaviour but with the shift constrain disabled, as this tool will perform his own shift behaviour.