This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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