import rhinoscriptsyntax as rs
from Rhino.Geometry import Point3d
from math import sqrt
P3d = Point3d
#
# Creates tray with 4 feet in one surface.
#
def Main():
	# Radius of 3 construction circles.
	c1r,c2r,c3r = 380.,328.,204.
	# Minor and major axes for ellipses.
	ra1,rb1 = 55., 80. # Defines shape of bottom of foot.
	ra2,rb2 = 62., 94. # Controls taper of foot.
	ra3,rb3 = 77.,118. # Helps pick out starting point on base for loft curves.
	# Offset from outer edge of tray.  Keeps construction curves on tray.
	offset = 0.35
	# Z altitude of 2nd circle, 3rd circle, center of base, ellipse 2, bottom of foot.
	Z1,Z2,Z3,Z4,Z5 = 73.,112.,132.,120.,217.
	# Clear the screen.
	rs.DeleteObjects(rs.AllObjects())
	# Speed up processing 10X by turning off redraw.
	rs.EnableRedraw(False) # Comment this out to see steps on screen
	# Shade the object so it is easier to view.
	for view in rs.ViewNames(): rs.ViewDisplayMode(view, 'Shaded')
	view_at_start = rs.CurrentView(None, True)
	# Script needs to run in other than Front or Right views.  Used Top.
	rs.CurrentView('Top')
	# Adjust zoom to show all of tray.
	rs.ZoomBoundingBox([[-c1r,-c1r,-Z5], [c1r,-c1r,-Z5], [c1r,c1r,-Z5], [-c1r,c1r,-Z5], [-c1r,-c1r,Z5], [c1r,-c1r,Z5], [c1r,c1r,Z5], [-c1r,c1r,Z5]], None, True)# Circle radius.
	# Adjust grid to enclose object.
	rs.Command('NoEcho _Grid _ApplyTo=_AllViewport _Extents {} _EnterEnd'.format(c1r*1.5))
	# Define circles that control shape of radial profile curve.
	c1 = rs.AddCircle([0,0,0],c1r)
	c2 = rs.AddCircle([0,0,Z1],c2r)
	c3 = rs.AddCircle([0,0,Z2],c3r)
	plane = rs.WorldXYPlane()
	# Add radial profile curve for base.
	r1 = rs.AddInterpCurve([[-c1r,0,0],[-c2r,0,Z1],[-c3r,0,Z2],[0,0,Z3]],3,0,[0,0,1],[1,0,0])
	r1 = rs.RotateObject(r1,[0,0,0],-22.5,None,False) # Position for revolve.
	#
	# Rotate radial profile to create 1/4 of tray surface.
	#
	base = rs.AddRevSrf(r1,((0,0,0), (0,0,1)),0,90)
	# Add ellipse for bottom of foot.
	e1 = rs.AddEllipse(plane, ra1, rb1)
	e1c = [-c1r+ra1+offset,0.,Z5] # Center of ellipse.
	e1b = [-c1r+ra1+offset,0,0.] # Center projected to Z = 0.
	e1 = rs.MoveObject(e1,e1c)
	# Add ellipse part way up foot to control taper.
	e2 = rs.AddEllipse(plane, ra2, rb2)
	e2 = rs.MoveObject(e2,[-c1r+ra2+offset,0,Z4])
	# Add asymmetric ellipse to help pick starting point on base.
	e3 = addGuideCurve(P3d(ra3,0,0),P3d(0,rb3,0))
	e3 = rs.MoveObject(e3,[-c1r+ra3+offset,0,Z5])
	e4 = rs.ProjectCurveToSurface([e3],[base],[0,0,-1])
	rs.DeleteObject(e3)
	#
	# Build 1 foot on West side of base with loft around 12 base-to-foot curves.
	#
	nn = add_Curves(base,e1,e2,e4,c1r)
	# Create foot.
	foot = rs.AddLoftSrf(nn,None,None,0,0,None,True)
	rs.MoveObject(foot,[0,0,-1.]) # Move down slightly to fully intersect base.
	# Union foot with base.
	qtr = rs.BooleanUnion([foot,base]) # Also deletes inputs.
	# Close hole in foot.
	rs.CapPlanarHoles(qtr)
	#
	# Copy the 1/4 tray to finish it.
	#
	qtrs = [qtr]
	for i in range(3):
		qtrs.append(rs.RotateObject(qtr, [0,0,0], (i+1)*90, (0,0,1), copy=True))
	rs.UnitAbsoluteTolerance(0.01)
	# Join into one surface.
	qtrs = rs.JoinSurfaces(qtrs,True) # Also deletes inputs.
	rs.ObjectColor(qtrs, [191,225,255])  # Steel blue color.
	# Cleanup.
	rs.DeleteObjects([c1,c2,c3,e1,e2,e4,r1]); rs.DeleteObjects(nn)
	# Show everything and return to view at start.
	rs.ZoomExtents(all=True); rs.CurrentView(view_at_start); rs.EnableRedraw(True)
#
# Creates curves from base-to-foot for use in creation of loft surface.
#
def add_Curves(base,e1,e2,e4,c1r):
	matrix = [[1,0,0,0],[0,-1,0,0],[0,0,1,0],[0,0,0,1]] # Y becomes -Y.
	# Assign curve locations by dividing ellipse e2 into even number of pieces.
	num_pts = 12 # Boolean in Main script works for 12 or more points.
	points = rs.DivideCurve(e2,num_pts,False,True)
	nn = []
	# For each point on e2, find corresponding points on e1 and e4.
	for p1 in points:
		# Find point on foot ellipse e1 closest to point on ellipse e2.
		p2 = rs.EvaluateCurve(e1,rs.CurveClosestPoint(e1,p1))
		# Project p1-p2 line onto base to find p0 and start tangent for curve.
		try:
			line = rs.AddLine([p2.X,p2.Y,0.],[p1.X,p1.Y,0.])
			tangent = rs.CurveTangent(line,rs.CurveDomain(line)[1])
			m = rs.VectorLength(rs.VectorSubtract([p2.X,p2.Y,0],[p1.X,p1.Y,0]))
			pt = rs.VectorAdd(p1,rs.VectorScale(tangent,10*m))
			line_extended = rs.AddLine(p1,pt)
			line_on_base = rs.ProjectCurveToSurface([line_extended],base,[0,0,-1])
			p0 = rs.CurveCurveIntersection(e4,line_on_base)[0][1]
			start_Tan = rs.CurveTangent(line_on_base,rs.CurveClosestPoint(line_on_base,p0))
			rs.DeleteObjects([line,line_extended,line_on_base])
		except: # Lands here when line is undefined (vertical case).
			p0 = rs.EvaluateCurve(e4, rs.CurveClosestPoint(e4,[-c1r,0,0]))
		# Create curve from base-to-foot for use in creation of loft surface.
		n1 = rs.AddInterpCurve([p0,p1,p2],3,0,start_Tan,[0,0,1])
		nn.append(n1) # Collect all the curves.
		#rs.AddPoint(p0) # Uncomment this to see p0 start points on e4.
	return nn
#
# Creates closed curve to guide placing points on base.
# Curve supports 90 ellipse + straight + 90 ellipse.
# For this case the straight section has zero length.
#
def addGuideCurve(p1,p2):
	# Create right side of closed curve (towards center).
	w = 1.
	pc = [w*p1.X,w*p2.Y,w*p1.Z] # Corner point at XZ of p1 and Y of p2.
	matrix = [[1,0,0,0],[0,-1,0,0],[0,0,1,0],[0,0,0,1]] # Y becomes -Y.
	[p1m,p2m,pcm] = rs.PointArrayTransform([p1,p2,pc], matrix)
	pts = [p2m,pcm,p1m,[p1.X,p1.Y,0],p1,pc,p2] # 90 ellipse,straight,90 ellipse.
	crvr = rs.AddNurbsCurve(pts,[0,0,1,1,2,2,3,3],2,[1.0,w,1.0,1.0,1.0,w,1.0])
	# Create left side of closed curve (towards outside).
	p1.X = -p1.X; p2.X = -p2.X # Negate X in p1 and p2 to create left side.
	pc = [w*p1.X,w*p2.Y,w*p1.Z]
	matrix = [[1,0,0,0],[0,-1,0,0],[0,0,1,0],[0,0,0,1]] # Y becomes -Y.
	[p1m,p2m,pcm] = rs.PointArrayTransform([p1,p2,pc], matrix)
	pts = [p2m,pcm,p1m,[p1.X,p1.Y,0],p1,pc,p2] # 90 ellipse,straight,90 ellipse.
	crvl = rs.AddNurbsCurve(pts,[0,0,1,1,2,2,3,3],2,[1.0,w,1.0,1.0,1.0,w,1.0])
	crv = rs.JoinCurves([crvl,crvr],True)
	return crv

	# Execute it...
if( __name__ == '__main__' ):
	Main()