In this exercise, we will be writing functions using only the functionality of "compass" and "straightedge", which in Rhino translates to being restricted to drawing circles and lines, and being able to find points of intersections between them.
Warning: this is only meaningful in 2D - please check that all your points have z = 0
As usual, you will have to make sure to use the rhinoscript package
import rhinoscriptsyntax as rs
Drawing a line or circle can be done in the following way:
p1 = [1,12,0] p2 = [-2,2,0] #a line that connects two points can be defined and drawn # (note that this defines the line as a segment beginning at p1 # and ending at p2) line1 = rs.AddLine(p1,p2) #as can a circle defined by a point and a radius circ1 = rs.AddCircle(p1, 7)
To find the intersection between two curves (whether that is a two line segments or a line segment and a circle or a circle and a circle), we will make use of the following function:
# Input: 2 curves (if it is a line, it must be defined as a line segment; # eg. lineIn = rs.AddLine(p1, p2) # Output: a list of intersection points or None if there is no intersection def findIntersection(curve1, curve2): pAttempt = rs.CurveCurveIntersection(curve1,curve2) #if intersection exists if len(pAttempt) != 0: pOut = list() for i in range(len(pAttempt)): pInt = pAttempt[i] pOut.append(pInt[1]) return pOut else: return None
The first construction that we will do is to construct a perpendicular line given a line segment l and a point p (not necessarily on the line l). It is highly advisable that you first develop an algorithm by precise illustration and description on paper.
As a first take, let's do the "typical case", where we are assume that the user will pick a point for which there is a solution, and also where the perpendicular line doesn't hit either of the endpoints of the line segment. This does, however, take into account the case when the point may fall on l.
Algorithm (Make PerpendicularLine)
Input: Point P, Line l
- Draw a sufficiently large circle around P that intersects l at 2 points. Call these A and B. (to make this more precise, we can just take the circle to have radius of the minimum of the distance between P and A (|PA|) and the distance between P and B (|PB|)
- At A and B, draw circles of radius > |PA| (radius = |PA| can't handle the case when P is on l). Call these points of intersection Q and R
- Connect Q and R
#since we will be specifying the distance between points, we will #need the following function def dist(p1, p2): return math.sqrt((p2[0]-p1[0])**2+(p2[1]-p1[1])**2+(p2[2]-p1[2])**2) #We assume the line is given, and that the point is specified by the user p1 = [10,0,0] p2 = [-15,25,0] line = p1,p2 #interpreted as an infinite line rs.AddLine(line[0], line[1]) p = rs.GetPoint("Select point", rs.filter.point) #Input: a line (right now, it requires a line specified as above) and a point p #Output: a perpendicular line drawn on the Rhino canvas def MakePerpLineCS(lineIn, pIn): rad1 = min(dist(pIn, lineIn[0]), dist(pIn, lineIn[1])) circ = rs.AddCircle(pIn, rad1) lineSegment = rs.AddLine(lineIn[0], lineIn[1]) pInt = findIntersection(lineSegment, circ) if pInt != None: rad2 = dist(pIn, pInt[0])+0.1 circ1 = rs.AddCircle(pInt[0], rad2) circ2 = rs.AddCircle(pInt[1], rad2) pInt2 = findIntersection(circ1, circ2) if pInt2 != None: rs.AddLine(pInt2[0],pInt2[1]) rs.DeleteObjects([circ,circ1,circ2]) MakePerpLineCS(line, p)
Of course, if you are allowed to deploy the full toolkit of the rhinoscript package, you can reduce this function to two lines of code:
def MakePerpLine(lineIn, pIn): closest = rs.LineClosestPoint(lineIn, pIn) rs.AddLine(closest, pIn)
We continue on to construct some familiar plane transformations. Let's start with "Mirror", for which we can take as a point of departure the algorithm for "MakePerpendicularLine". As before, we assume the "typical case", where the user will pick a point for which there is a solution and does not hit either of the endpoints of the given line segment.
Algorithm (Mirror)
Input: Point P, Line l
- Check to see if P is on l. If it is on l, then return P. Otherwise continue.
- Take the minimum of the distance between P and A (|PA|) and the distance between P and B (|PB|) and call this number rad1. Draw a circle around P with radius rad1. Notice that this will l at 2 points. Call these A and B.
- At A and B, draw circles of radius rad1 (unlike in MakePerp, we do not have to make an exception for when p is on l). Call these points of intersection Q and R
- Now one of Q and R is P and the other one is the mirrored point. We can do this by checking to see which one of Q and R has the minimum distance to P (computationally, avoid trying to check whether something is equal to 0).
def MirrorCS(lineIn, pIn): closest = rs.LineClosestPoint(lineIn, pIn) if dist(closest, pIn) > rs.UnitAbsoluteTolerance(10**(-5), True): rad1 = min(dist(pIn, lineIn[0]), dist(pIn, lineIn[1])) circ = rs.AddCircle(pIn, rad1) lineSegment = rs.AddLine(lineIn[0], lineIn[1]) pIntersect = findIntersection(lineSegment, circ) circ0 = rs.AddCircle(pIntersect[0], rad1) circ1 = rs.AddCircle(pIntersect[1], rad1) pIntersect2 = findIntersection(circ0, circ1) rs.DeleteObjects([circ,circ0,circ1]) if dist(pIntersect2[0], pIn) < dist(pIntersect2[1], pIn): return pIntersect2[1] else: return pIntersect2[0] else: return pIn pMirror = MirrorCS(line, p) rs.AddPoint(pMirror)
The key to the algorithm for "Move" is to draw the parallelogram that forms between a start point (S) and an end point (E) which represents the desired translation, the input point (P) and the translated point (P'). Notice that by definition, the distance between S and E, and the distance between S and P will determine the position of P'. Namely, P' must be distance |SE| away from P and must be distance |SP| away from E. This can be translated into code by drawing circles of these radii centered at P and E.
Algorithm (Move)
Input: Point S (start point), Point E (end point), Point P
- Draw a circle of radius |SE| centered at P
- Draw a circle of radius |SP| centered at E
- Find the intersection between these two circles.
- One of these intersection points is P'. To choose the right one, check to see that the line PP' is parallel to SE.
def MoveCS(startPoint, endPoint, pIn): rad1 = dist(startPoint, pIn) rad2 = dist(startPoint, endPoint) circ1 = rs.AddCircle(endPoint, rad1) circ2 = rs.AddCircle(pIn, rad2) pIntersect = findIntersection(circ1, circ2) compare0 = [pIntersect[0][0]-endPoint[0], pIntersect[0][1]-endPoint[1], 0] compare1 = [pIntersect[1][0]-endPoint[0], pIntersect[1][1]-endPoint[1], 0] orig = [pIn[0]-startPoint[0], pIn[1]-startPoint[1], 0] rs.DeleteObjects([circ1,circ2]) if dist(compare0, orig) < dist(compare1, orig): return pIntersect[0] else: return pIntersect[1]