Skip to content

Instantly share code, notes, and snippets.

@pgolay
Last active December 29, 2019 01:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pgolay/e953ac43f211ee9c6e51d28497472f61 to your computer and use it in GitHub Desktop.
Save pgolay/e953ac43f211ee9c6e51d28497472f61 to your computer and use it in GitHub Desktop.
import rhinoscriptsyntax as rs
import Rhino
import scriptcontext as sc
import math
def AveragePoint3d(aPts):
X = Y = Z = 0
L = len(aPts)
for i in range(len(aPts)):
X += aPts[i].X
Y += aPts[i].Y
Z += aPts[i].Z
return Rhino.Geometry.Point3d(X/L, Y/L, Z/L)
def FilletNonPlanar():
tol = sc.doc.ModelAbsoluteTolerance
pi = math.pi
def SortCurveEnds(objRefs):
id1 =objRefs[0].ObjectId
id2 =objRefs[1].ObjectId
crv1 = objRefs[0].Geometry()
crv2 = objRefs[1].Geometry()
par1 = objRefs[0].CurveParameter()[1]
par2 = objRefs[1].CurveParameter()[1]
max1 = crv1.Domain.Length
max1 = crv2.Domain.Length
if id1 != id2:
intTest = Rhino.Geometry.Intersect.Intersection.CurveCurve(crv1, crv2, tol, tol)
if len(intTest) > 0:
info = []
for test in intTest:
if test.IsPoint:
info.append( [test.PointA, test.PointB, test.ParameterA,test.ParameterB])
for item in info:
if abs(item[2]-par1) < max1:
max1 = abs(item[2]-par1)
p1 = item[0]
p2 = item[1]
else:
rc, p1, p2 = crv1.ClosestPoints(crv2)
pts = [p1,p2]
else:
rc, p1, p2 = crv1.ClosestPoints(crv2)
pts = [p1,p2]
rc, closePars1 = crv1.ClosestPoint(pts[0])
rc, closePars2 = crv2.ClosestPoint(pts[1])
pass
dom1 = crv1.Domain
dom2 = crv2.Domain
split = crv1.Split(closePars1)
if not split:
sub1 = crv1
else:
if par1<= closePars1:
sub1 = split[0]
else:
sub1 = split[1]
split = crv2.Split(closePars2)
if not split:
sub2 = crv2
else:
if par2<= closePars2:
sub2 = split[0]
else:
sub2 = split[1]
return sub1,sub2, pts
def RebuildArc(arc):
deg = arc.AngleDegrees
if deg <=45:
rebuild = [3,4]
elif 70 >= deg > 45:
rebuild = [4,5]
elif 90 >= deg > 70:
rebuild = [5,6]
else:
rebuild = [5, int(deg/15)]
return arc.Rebuild(rebuild[1], rebuild[0], True)
count = 0
objRefs = []
while True:
#defaults
rad = 1
if sc.sticky.has_key('NP_RAD'):
rad = sc.sticky['NP_RAD']
blnTrim = True
if sc.sticky.has_key('FLT_TRIM'):
blnTrim = sc.sticky['FLT_TRIM']
blnJoin = True
if sc.sticky.has_key('FLT_JOIN'):
blnJoin = sc.sticky['FLT_JOIN']
go = Rhino.Input.Custom.GetObject()
if count == 0:
promptString = "Select the first curve near the corner to fillet"
else:
promptString = "Select the second curve near the corner to fillet"
go.SetCommandPrompt(promptString)
#options
opRad = Rhino.Input.Custom.OptionDouble(rad)
go.AddOptionDouble("Radius", opRad)
opTrim = Rhino.Input.Custom.OptionToggle(blnTrim, "No","Yes")
go.AddOptionToggle("Trim",opTrim)
opJoin = Rhino.Input.Custom.OptionToggle(blnJoin, "No","Yes")
go.AddOptionToggle("Join",opJoin)
go.AcceptNumber(True, False)
go.GeometryFilter = Rhino.DocObjects.ObjectType.Curve
go.EnablePreSelect(False, True)
rc = go.Get()
if go.CommandResult()!=Rhino.Commands.Result.Success:
return go.CommandResult()
if rc==Rhino.Input.GetResult.Object:
objRefs.append(go.Object(0))
count += 1
if count > 1:
break
else:
continue
elif rc==Rhino.Input.GetResult.Number:
rad = go.Number()
sc.sticky["NP_RAD"]= rad
continue
elif rc==Rhino.Input.GetResult.Option:
rad = opRad.CurrentValue
sc.sticky["NP_RAD"]= rad
blnTrim = opTrim.CurrentValue
blnJoin = opJoin.CurrentValue
sc.sticky['FLT_TRIM'] = blnTrim
sc.sticky['FLT_JOIN']= blnJoin
continue
id1 = objRefs[0].ObjectId
id2 = objRefs[1].ObjectId
par1 = objRefs[0].CurveParameter()[1]
par2 = objRefs[1].CurveParameter()[1]
#are the inputs the same curve? If so, some special-casing...
same = False
if id1 == id2:
same = True
pts = []
sub1 = objRefs[0].Geometry()
sub2 = objRefs[1].Geometry()
pts = [objRefs[0].SelectionPoint(), objRefs[1].SelectionPoint()]
else:
sub1, sub2, pts = SortCurveEnds(objRefs)
crv1 = objRefs[0].Geometry()
crv2 = objRefs[1].Geometry()
o = AveragePoint3d(pts)
crvTan1 = sub1.TangentAt(sub1.ClosestPoint(pts[0])[1])
crvTan2 = sub2.TangentAt(sub2.ClosestPoint(pts[1])[1])
myPlane = Rhino.Geometry.Plane(o, crvTan1, crvTan2)
pull1 = Rhino.Geometry.Curve.ProjectToPlane(sub1,myPlane)
pull2 = Rhino.Geometry.Curve.ProjectToPlane(sub2,myPlane)
ft = Rhino.Geometry.Curve.CreateFillet(pull1, pull2, rad, par1, par2)
if not ft: return
crv = RebuildArc(Rhino.Geometry.ArcCurve(ft))
cPts = crv.Points
#get plane normal lines from the ends of the fillets and
# intersect with the input curves to find where to place the
# rebuilt fillet ends and tangent directions
bb = crv1.GetBoundingBox(False)
bb.Union(crv2.GetBoundingBox(False))
norm = Rhino.Geometry.Line(crv.PointAtStart,crv.PointAtStart + myPlane.ZAxis)
norm.ExtendThroughBox(bb)
norm = Rhino.Geometry.LineCurve(norm)
intPt = Rhino.Geometry.Intersect.Intersection.CurveCurve(sub1, norm, tol, tol)
tanPt1 = intPt[0].PointA
tanPar1 = sub1.ClosestPoint(tanPt1)[1]
tan1 = sub1.TangentAt(tanPar1)
norm = Rhino.Geometry.Line(crv.PointAtEnd,crv.PointAtEnd + myPlane.ZAxis)
norm.ExtendThroughBox(bb)
norm = Rhino.Geometry.LineCurve(norm)
intPt = Rhino.Geometry.Intersect.Intersection.CurveCurve(sub2, norm, tol, tol)
tanPt2 = intPt[0].PointA
tanPar2 = sub2.ClosestPoint(tanPt2)[1]
tan2 = sub2.TangentAt(tanPar2)
# decide which way the tangents should go.
tanStart = cPts[1].Location-cPts[0].Location
tanEnd = cPts[cPts.Count-2].Location - cPts[cPts.Count-1].Location
if Rhino.Geometry.Vector3d.VectorAngle(tanStart,tan1) > pi/2:
tan1.Reverse()
if Rhino.Geometry.Vector3d.VectorAngle(tanEnd,tan2) > pi/2:
tan2.Reverse()
rc, cp1 = crv1.ClosestPoint(crv.PointAtStart)
rc, cp2 = crv2.ClosestPoint(crv.PointAtEnd)
d1 = cPts[0].Location.DistanceTo(cPts[1].Location)
d2 = cPts[cPts.Count-1].Location.DistanceTo(cPts[cPts.Count-2].Location)
tan1 = tan1*d1
tan2 = tan2*d2
cPts.SetPoint(0, sub1.PointAt(cp1))
cPts.SetPoint(1, sub1.PointAt(cp1) + tan1)
cPts.SetPoint(cPts.Count-1, sub2.PointAt(cp2))
cPts.SetPoint(cPts.Count-2, sub2.PointAt(cp2)+ tan2)
if cPts.Count > 4:
#move the plane to the second cp location:
myPlane.Origin = cPts[cPts.Count-2].Location
#find the direction vector and unitize it
vecDir = myPlane.ClosestPoint(cPts[1].Location) - cPts[1].Location
#vecDir.Unitize()
vecDir.Reverse()
#get curve point locations in plane coorinates
xform = Rhino.Geometry.Transform.PlaneToPlane( myPlane,Rhino.Geometry.Plane.WorldXY )
locs = [xform*pt.Location for pt in cPts]
#get the locations as projected to the plane
planePts = [myPlane.ClosestPoint(cPt.Location) for cPt in cPts]
#get the fillet curve params for the tangent points
crvDom = crv.Domain
ftPar1 = crv.ClosestPoint(cPts[1].Location)[1]
ftPar2 = crv.ClosestPoint(cPts[cPts.Count-2].Location)[1]
#make a domain between the tangent points
domPts = Rhino.Geometry.Interval(ftPar1, ftPar2)
for i in range(2,cPts.Count-2):
ptPar = crv.ClosestPoint(cPts[i].Location)[1]
# ptPar = ptPar - crvDom.Min #<<< par in domPts
# get the normalized param of the point in
# the sub-domain
nPar = domPts.NormalizedParameterAt(ptPar)
z = (math.cos(nPar * pi)+1)/2
targ = planePts[i]+vecDir*z
cPts.SetPoint(i, targ)
idFt = sc.doc.Objects.AddCurve(crv)
if blnTrim:
if same:
split = sub1.Split([tanPar1, tanPar2])
if (split[0].PointAt(split[0].Domain.Mid).DistanceTo(crv.PointAt(crv.Domain.Mid)) >
split[1].PointAt(split[1].Domain.Mid).DistanceTo(crv.PointAt(crv.Domain.Mid))):
sc.doc.Objects.Replace(objRefs[0],split[0])
else:
sc.doc.Objects.Replace(objRefs[0],split[1])
if blnJoin:
idFt = rs.JoinCurves([idFt,objRefs[0].ObjectId], True)
pass
else:
split = sub1.Split(tanPar1)
if split:
parM1 = split[0].Domain.Mid
parM2 = split[1].Domain.Mid
parEnd = sub1.ClosestPoint(pts[0])[1]
if abs(parEnd - parM1) < abs(parEnd - parM2):
sub1 = split[1]
else:
sub1 = split[0]
split = sub2.Split(tanPar2)
if split:
parM1 = split[0].Domain.Mid
parM2 = split[1].Domain.Mid
parEnd = sub2.ClosestPoint(pts[1])[1]
if abs(parEnd - parM1) < abs(parEnd - parM2):
sub2 = split[1]
else:
sub2 = split[0]
sc.doc.Objects.Replace(objRefs[0],sub1)
sc.doc.Objects.Replace(objRefs[1],sub2)
if blnJoin:
idFt = rs.JoinCurves([idFt,objRefs[0].ObjectId,objRefs[1].ObjectId], True)
rs.SelectObject(idFt)
sc.doc.Views.Redraw
if __name__== '__main__': FilletNonPlanar()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment