def onStrokeAdded( self, stroke ): "Watches for Strokes that look like an arrow to Annotate" ep1 = stroke.Points[0] ep2 = stroke.Points[-1] isArrowHead = False GeomUtils.ellipseAxisRatio(stroke) #Match single-stroke arrows tip, tail = _isSingleStrokeArrow(stroke) if tip is None or tail is None: revpts = list(stroke.Points) revpts.reverse() tip, tail = _isSingleStrokeArrow(Stroke(revpts)) if tip is not None and tail is not None: isArrowHead = False anno = ArrowAnnotation( tip, tail ) BoardSingleton().AnnotateStrokes( [stroke], anno) else: return if _isArrowHead(stroke, self.arrowHeadMatcher): #We've matched an arrowhead head = stroke isArrowHead = True strokeNorm = GeomUtils.strokeNormalizeSpacing(stroke, numpoints = 5) tip = strokeNorm.Points[2] #Middle normalized point is the tip #Match it to any tails we have if isArrowHead: matchedTails = self._matchHeadtoTail(head = stroke, point = tip) for headpoint, tail in matchedTails: #Orient the tail correctly if tail.Points[0] == headpoint: endpoint = tail.Points[-1] else: endpoint = tail.Points[0] anno = ArrowAnnotation(tip, endpoint) BoardSingleton().AnnotateStrokes([head, tail],anno) #Match it like a tail even if we think it's an arrowhead. Oh ambiguity! matchedHeads = self._matchHeadtoTail(tail = stroke, point = ep1) for tip, head in matchedHeads: anno = ArrowAnnotation(tip, ep2) BoardSingleton().AnnotateStrokes([head, stroke],anno) matchedHeads = self._matchHeadtoTail(tail = stroke, point = ep2) for tip, head in matchedHeads: anno = ArrowAnnotation(tip, ep1) BoardSingleton().AnnotateStrokes([head, stroke],anno) #Add this stroke to the pool for future evaluation self._endpoints.append( (ep1, stroke) ) self._endpoints.append( (ep2, stroke) ) if isArrowHead: self._arrowHeads.append( (tip, stroke) )