jtanadi / CheckParallelTool

Simple tool to check handles in RF
7 stars 0 forks source link

make it your self easier :) #1

Closed typemytype closed 5 years ago

typemytype commented 5 years ago

hope this helps!!

import math

from mojo.events import EditingTool, installTool
import mojo.drawingTools as ctx

def distance(line):
    p1, p2 = line
    return math.sqrt((p2.x - p1.x)**2 + (p2.y - p1.y)**2)

def isPointInLine(point, line, scale):
    p1, p2 = line
    d1 = distance((p1, point))
    d2 = distance((point, p2))
    totalDistance = distance((p1, p2))
    return abs(d1 + d2 - totalDistance) < 10 * scale

def areTheyParallel(line1, line2, tolerance=2):
    """
    Checks if 2 lines are parallel by comparing their slopes
    line1 and line2 should be a tuple of tuples each: ((x0, y0), (x1, y1))
    tolerance defaults to 0
    """
    p0, p1 = line1
    p2, p3 = line2

    # atan returns rads, so convert to angle
    angle1 = abs(math.degrees(math.atan2((p1.y - p0.y), (p1.x - p0.x))))
    angle2 = abs(math.degrees(math.atan2((p3.y - p2.y), (p3.x - p2.x))))

    # instead of checking for absolute equality,
    # allow for some tolerance
    return abs(angle1 - angle2) <= tolerance

class MyTool(EditingTool):

    def setup(self):
        self._selectedSegments = []
        self._selectedSegment = None

    def _mouseDown(self, point, clickCount):
        scale = self.getNSView().getGlyphViewScale()

        self._selectedSegment = None
        for p1, h1, h2, p2 in self._selectedSegments:
            if isPointInLine(point, (h1, h2), scale):
                self._selectedSegment = p1, h1, h2, p2

        if not self._selectedSegment:
            super()._mouseDown(point, clickCount)  
            self.extractSelection()

    def mouseDragged(self, point, delta):
        if self._selectedSegment:
            # edit the point here
            # but only the segment wich is close to the mouse
            # rubberbanding other segments is weird...
            pass

    def mouseUp(self, point):
        self.extractSelection()

    def keyUp(self, event):
        self.extractSelection()

    def canSelectWithMarque(self):
        if self._selectedSegments:
            return False
        return super().canSelectWithMarque()

    def dragSelection(self, point, delta):
        if not self._selectedSegments:
            super().dragSelection(point, delta)  

    def draw(self, scale):        
        ctx.save()
        ctx.strokeWidth(1 * scale)
        for p1, h1, h2, p2 in self._selectedSegments:
            ctx.stroke(1, 0, 0)
            ctx.line((p1.x, p1.y), (p2.x, p2.y))
            if areTheyParallel((p1, p2), (h1, h2)):
                ctx.stroke(0, 0, 1)    
            if self._selectedSegment == (p1, h1, h2, p2):
                ctx.strokeWidth(2 * scale)
            else:
                ctx.strokeWidth(1 * scale)
            ctx.line((h1.x, h1.y), (h2.x, h2.y))
        ctx.restore()

    def extractSelection(self):
        selection = []
        for contour in self.getGlyph():
            segments = contour.segments
            for i, segment in enumerate(segments):
                if segment.selected and segment.type in ["curve", "qcurve"]:
                    prev = segments[i - 1].onCurve
                    h1, h2, onCurve = segment
                    selection.append((prev, h1, h2, onCurve))

        if selection != self._selectedSegments:
            self._selectedSegments = selection
            self.refreshView()

installTool(MyTool())
jtanadi commented 5 years ago

@typemytype this is amazing. Thanks much! I did feel like it was bloated, and I was gonna spend some time refactoring, so this is really helpful. I'll try to integrate what you wrote.

This is how I calculated where the new BCPs would go in mouseDragged(), but I'm not sure if it's a good way to go since controlling it is kind of weird.

# self.pt2Pos and self.pt3Pos are 2 selected BCPs
bcp0X, bcp0Y = self.pt2Pos
bcp1X, bcp1y = self.pt3Pos

# New point = current point + delta (for now)
bcp0XtoUse = bcp0X + delta.x
bcp0YtoUse = bcp0Y + delta.y
bcp1XtoUse = bcp1X + delta.x
bcp1YtoUse = bcp1y + delta.y

# First BCP
if self.slope0 == 0:
    bcp0YtoUse = bcp0Y

# Vertical line, so new x == old x
elif self.slope0 is None:
    bcp0XtoUse = bcp0X

# Angled line, use y=mx+b to find out new x & y,
# using x, y calculated above... this seems weird.
else:
    bcp0XtoUse = (bcp0YtoUse - self.intercept0) / self.slope0
    bcp0YtoUse = self.slope0 * bcp0XtoUse + self.intercept0

# Repeat for the other BCP

self.pt2.position = (round(bcp0XtoUse), round(bcp0YtoUse))
self.pt3.position = (round(bcp1XtoUse), round(bcp1YtoUse))

Also, I can't believe I never thought of your version of isPointInLine()...

typemytype commented 5 years ago

wild guess: calculate the new off curve point while dragging based on the start position of the off curve on mouse down.

jtanadi commented 5 years ago

hmm ok, will look into that. thanks!