# -*- python -*-
# The first line tells emacs to use python mode for editing



#
# roundedWire.py
# Produces wire traces with rounded corners.
# Usable as a drop-in replacement for previous versions of the wire() function.
#
# The set of code contained in this file was created entirely by:
#
#   Steve Leibman
#   October 8, 2007
#
# It is not particularly useful without access to cad.py and a file which uses
# these functions, so all content is intended for contribution to the cad.py 
# project (if they want it), and would be covered by the following copyright:
# 
#
#   Neil Gershenfeld
#   CBA MIT 
#   (c) Massachusetts Institute of Technology 2007
#   Permission granted for experimental and personal use;
#   license for commercial sale available from MIT.
#

import math

def rotateToMatchVector(part,vecx,vecy):
   # part will be rotated theta degrees, where theta is the angle
   # between the supplied vector and the x axis.
   # Equivalent to drawing the vector [1,0], and then rotating both the
   # part and the [1,0] vector until it aligns with the supplied vector.
   length = sqrt(vecx*vecx + vecy*vecy)
   if (length == 0):
      return part
   part = replace(part,'X','((COSANGLE)*X+(SINANGLE)*y)')
   part = replace(part,'Y','(-(SINANGLE)*X+(COSANGLE)*y)')
   part = replace(part,'y','Y')
   part = replace(part,'COSANGLE',str(vecx/length))
   part = replace(part,'SINANGLE',str(vecy/length))
   return part

def lineWithThickness(x0,y0,x1,y1,w):
   length = sqrt((y1-y0)*(y1-y0) + (x1-x0)*(x1-x0))
   line = rectangle(0,length,-w/2,w/2)
   line = rotateToMatchVector(line,x1-x0,y1-y0)
   line = move(line,x0,y0)
   return line

def getPointOnLine(point0,point1,distance):
   # returns a point which is the specified distance away from point0,
   # in the direction from point0 to point1.
   # Currently ignores z coordinate
   x0 = point0.x
   y0 = point0.y
   z0 = point0.z
   x1 = point1.x
   y1 = point1.y
   z1 = point1.z
   length = sqrt((x1-x0)*(x1-x0) + (y1-y0)*(y1-y0))
   xdiff = x1-x0
   ydiff = y1-y0
   x = xdiff*distance/length
   y = ydiff*distance/length
   x = x + x0
   y = y + y0
   return point(x,y,z0)
   
def getPointInDirection(point0,angle,distance):
   # returns a point which is the specified distance away from point0,
   # in the direction specified by the angle.
   # Currently ignores z coordinate
   angle = positiveAngle(angle)
   angle = angle*pi/180
   x0 = point0.x
   y0 = point0.y
   z0 = point0.z
   x = distance*cos(angle)
   y = distance*sin(angle)
   x = x + x0
   y = y + y0
   return point(x,y,z0)   

def arc(centerX,centerY,innerRad,outerRad,startAngle,endAngle):
   # Creates an arc formed by concentric curves.
   # outerRad must be greater than innerRad.
   # startAngle must be less than endAngle
   # Both angles are specified in degrees (CCW from the x axis).
   # endAngle - startAngle must be less than or equal to 180
   outer = circle(0,0,outerRad)
   inner = circle(0,0,innerRad)
   block1 = rectangle(-outerRad,outerRad,-outerRad,0)
   block2 = rectangle(-outerRad,outerRad,0,outerRad)
   block1 = rotate(block1,startAngle)
   block2 = rotate(block2,endAngle)
   part = subtract(outer,inner)
   part = subtract(part,block1)
   part = subtract(part,block2)
   part = move(part,centerX,centerY)
   return part

def positiveAngle(angle):
   # Remaps angle (in degrees) to a number in the range 0 to 360.
   return (angle - 360 * floor(angle/360))

def polyline(width,cornerRadius,*points):
   # Creates one or more line segments, sequentially connecting the points in the list.
   # Corners will be rounded, with the inner radius of the curve equal to the specified
   # cornerRadius, and the outer radius equal to inner radius plus the width of the
   # line.
   # width must be greater than zero.
   # cornerRadius must be greater than or equal to zero.
   part = ""

   # Store the end of the previous line, truncated to allow for arc
   previousEndPoint = None

   # Store the angle of the previous line
   previousLineAngle = None
   
   for i in range(1,len(points)):
      x0 = points[i-1].x
      y0 = points[i-1].y
      z0 = points[i-1].z
      x1 = points[i].x
      y1 = points[i].y
      z1 = points[i].z

      # omitFromStart and omitFromEnd are given positive numbers in order to shrink the trace
      # the appropriate amount to allow for curved corners, and given negative numbers in order
      # to extend the trace a half-wdith past the specified terminus point.
      
      if (i==1):
         omitFromStart = -width/2
      else:
         omitFromStart = cornerRadius + width/2
      if (i == len(points)-1):
         omitFromEnd = -width/2
      else:
         omitFromEnd = cornerRadius + width/2
      startPoint = getPointOnLine(points[i-1],points[i],omitFromStart)
      endPoint = getPointOnLine(points[i],points[i-1],omitFromEnd)
      if (i==1):
         part = lineWithThickness(startPoint.x,startPoint.y,endPoint.x,endPoint.y,width)
      else:
         part = add(part,lineWithThickness(startPoint.x,startPoint.y,endPoint.x,endPoint.y,width))
      nextLineAngle = (math.atan2(y1-y0,x1-x0))*180/pi
      if (previousLineAngle != None):
         nextLineAngle = positiveAngle(nextLineAngle)
         previousLineAngle = positiveAngle(previousLineAngle)
         if (positiveAngle(nextLineAngle - previousLineAngle) < 180):
            # Left turn
            arcCenter = getPointInDirection(previousEndPoint,previousLineAngle+90,cornerRadius + width/2)
            startAngle = previousLineAngle-90
            endAngle = nextLineAngle-90
         else:
            # Right turn
            arcCenter = getPointInDirection(previousEndPoint,previousLineAngle-90,cornerRadius + width/2)
            startAngle = nextLineAngle+90
            endAngle = previousLineAngle+90
         part = add(part,arc(arcCenter.x,arcCenter.y,cornerRadius,cornerRadius+width,startAngle,endAngle))
      previousLineAngle = nextLineAngle
      previousEndPoint = endPoint
      
   return part

def wire(pcb,width,*points):
   # The following logic adds intermediate points in order to avoid diagonals, and then calls
   # the polyline function, specifying the width of the trace as the inner radius of curvature
   # for corners
   newpoints = [points[0]]
   for i in range(1,len(points)):
      x0 = points[i-1].x
      y0 = points[i-1].y
      z0 = points[i-1].z
      x1 = points[i].x
      y1 = points[i].y
      z1 = points[i].z
      if (x0 != x1 and y0 != y1):
         newpoints.append(point(x1,y0,z0))
      newpoints.append(point(x1,y1,z0))
   pcb.board = add(pcb.board,polyline(width,width,*newpoints))
   return pcb