1. NodeBox 1
    1. Homepage
    2. NodeBox 3Node-based app for generative design and data visualization
    3. NodeBox OpenGLHardware-accelerated cross-platform graphics library
    4. NodeBox 1Generate 2D visuals using Python code (Mac OS X only)
  2. Gallery
  3. Documentation
  4. Forum
  5. Blog

Conical connector snippet

Posted by Giorgio O. on Dec 21, 2008



Hello,

just a quick post to share this snippet. the code is not really polished but it works pretty decently.

given 2 circles, it draws a 'conical' connector between them.
it also uses the NSBezierPAth arc function.

I had to develop this while trying to skin some graph networks.

from math import sin, radians, cos, atan2, degrees, pi
from AppKit import NSBezierPath
 
size(400,400)
background(0.6, 0.5, 0.3)
 
 
class Point:
    """ Simple point class"""
    def __init__(self, x, y, name=''):
        self.x = x
        self.y = y
        self.name = name
        
class Circle:
    """ A circle that doesn't display automatically"""
    def __init__(self, p, r):
        self.d = r*2
        self.r = r       
        self.x = p.x-self.r
        self.y = p.y-self.r
        
    def display(self):
        oval(self.x, self.y, self.d, self.d)  
 
class drawConnection():
    """ Draws the 'conical' connection """
    def __init__(self, p1, r1, p2, r2):
        a = angle(p1.x, p1.y, p2.x, p2.y)
        half_pi = pi*0.5
        ap = a+half_pi
        am = a-half_pi
        p1a = Point(p1.x+cos(ap)*r1, p1.y+sin(ap)*r1)
        p1b = Point(p1.x+cos(am)*r1, p1.y+sin(am)*r1)
        p2b = Point(p2.x+cos(ap)*r2, p2.y+sin(ap)*r2)
        p2a = Point(p2.x+cos(am)*r2, p2.y+sin(am)*r2)
        
        c = BezierPath()
        arc( c, p1.x, p1.y, r1, degrees(ap), degrees(am), 0 )  
        arc( c, p2.x, p2.y, r2, degrees(am), degrees(ap), 0 ) 
        c.closepath()
        c.draw() 
        
        # debug lines, comment at your wish
        line(p1.x, p1.y, p2.x, p2.y)
        line( p1a.x, p1a.y,  p1b.x, p1b.y)
        line( p2a.x, p2a.y,  p2b.x, p2b.y)
    
 
def arc(self, originx, originy, radius, startangle, endangle, clockwise=True):
        """Draw an arc"""
        self._segment_cache = None
        self.inheritFromContext() 
        if clockwise: # the clockwise direction is relative to the orientation of the axis, so it looks flipped compared to the normal Cartesial Plane
            self._nsBezierPath.appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_clockwise_( (originx, originy), radius, startangle, endangle, 1)
        else:
            self._nsBezierPath.appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_( (originx, originy), radius, startangle, endangle)
        
 
def drawAxis():
    """ Draw the x,y axis as a reference"""
    nofill()
    strokewidth(1)
    stroke(1)
    line(0, -HEIGHT/2, 0, HEIGHT/2-20)
    line(-WIDTH/2, 0, WIDTH/2-20, 0)
    fill(0)
    fontsize(10)
    text("X", WIDTH/2-12, 3)   
    text("Y", -4, HEIGHT/2-5)   
    push()
    rotate(-45)
    nostroke()
    fill(1)  
    arrow(WIDTH/2-20, -5, 10, type=FORTYFIVE)
    rotate(-90)
    arrow(5, HEIGHT/2-30, 10, type=FORTYFIVE)
    pop()
    
    
def angle(x1, y1, x2, y2):
    """ Calculates the angle between p1 and p2"""
    return atan2(y2-y1, x2-x1)
 
def ovalCentered(x, y, d):
    """ draws circle from origin, radius"""
    r = d*0.5
    oval(x-r, y-r, d, d)  
 
translate(WIDTH/2, HEIGHT/2)
drawAxis()
font("Helvetica", fontsize=12)
 
# 1
var("x1", NUMBER, 100, -WIDTH/2, WIDTH/2)
var("y1", NUMBER, 100, -HEIGHT/2, HEIGHT/2)
var("r1", NUMBER, 10, 1, 80)
 
# 2
var("x2", NUMBER, -50, -WIDTH/2, WIDTH/2)
var("y2", NUMBER, -40, -HEIGHT/2, HEIGHT/2)
var("r2", NUMBER, 30, 1, 80)
 
fill(0, 0.4)
p1 = Point(x1, y1, 'P1')
p2 = Point(x2, y2, 'P2')
c1 = Circle(p1, r1)
c2 = Circle(p2, r2)
c1.display()
c2.display()
 
 
fill(0.4, 0.9, 0, 0.4)
stroke(1)
drawConnection(p1, r1, p2, r2)


 
Posted by Giorgio O. on Dec 22, 2008

obviously the class drawConnection should just be a function... absent-minded copy-paste :-)



Posted by Giorgio O. on Dec 22, 2008

so here you get the clean version

from math import pi, degrees, atan2
half_pi = pi/2.0
 
 
size(500, 500)
 
def angle(x1, y1, x2, y2):
    """ Calculates the angle between p1 and p2"""
    return atan2(y2-y1, x2-x1)
    
    
def arc(self, originx, originy, radius, startangle, endangle, clockwise=True):
    """
    Draw an arc, the clockwise direction is relative to the orientation of the axis, 
    so it looks flipped compared to the normal Cartesial Plane
    """
    self._segment_cache = None
    self.inheritFromContext() 
    if clockwise: 
        self._nsBezierPath.appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_clockwise_( (originx, originy), radius, startangle, endangle, 1)
    else:
        self._nsBezierPath.appendBezierPathWithArcWithCenter_radius_startAngle_endAngle_( (originx, originy), radius, startangle, endangle)
 
    
def drawConnection(x1, y1, r1, x2, y2, r2):
        a = angle(x1, y1, x2, y2)
        ap = a+half_pi
        am = a-half_pi 
        c = BezierPath()
        arc( c, x1, y1, r1, degrees(ap), degrees(am), 0 )  
        arc( c, x2, y2, r2, degrees(am), degrees(ap), 0 ) 
        c.closepath()
        c.draw() 
        
        
fill(0)
drawConnection(100, 100, 10, 300, 300, 60)