Skip to content

Instantly share code, notes, and snippets.

@pgolay
Last active August 26, 2019 03:33
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/650818763baef8921940596ac9e5b513 to your computer and use it in GitHub Desktop.
Save pgolay/650818763baef8921940596ac9e5b513 to your computer and use it in GitHub Desktop.
Match a curve to any location on another curve for position or tangency.
import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
import System
import math
"""
To Do:
See about curvature
Preserve other end (Change degree up to 5, insertKnot beyond
Figure out how to special case multi span - prolly just add knots, change degree on singles
Allow setting by pct or distance
"""
def ExteriorTangent(face, pt, edge):
crv = edge.ToNurbsCurve()
#curve tangent
vecTan = crv.TangentAt(crv.ClosestPoint(pt)[1])
parRC, parU, parV = face.ClosestPoint(pt)
vecNorm = face.NormalAt(parU, parV)
#Tangent vector
vecDir = Rhino.Geometry.Vector3d.CrossProduct(vecTan, vecNorm)
crv = edge.ToNurbsCurve()
brep = edge.Brep
idx = edge.EdgeIndex
trims = brep.Trims
#Check if the tangent points into or away from the face
#reverse if into
for trim in trims:
if trim.Edge.EdgeIndex == idx:
if trim.IsReversed():
vecDir.Reverse()
break
#return the tangent vector and the normal
return vecDir, vecNorm
def find_tangent(pt, crv, targ):
crv = crv.ToNurbsCurve()
cps = crv.Points
ptDist = cps[0].Location.DistanceTo(cps[1].Location)
rc, par = targ.ClosestPoint(pt)
# the curve frame
frameRC, frame = targ.FrameAt(par)
if rc:
vecTan = targ.TangentAt(par)
#return the scaled tangent vector and the frame Z axis
return vecTan*ptDist, frame.ZAxis
return None
def GetPointTangent(crv, targ, color):
show_graph = False
graph_scale = 100
if sc.sticky.has_key('SHOW_MATCH_GRAPH'):
show_graph = sc.sticky['SHOW_MATCH_GRAPH']
if sc.sticky.has_key('MATCH_GRAPH_SCALE'):
graph_scale = sc.sticky['MATCH_GRAPH_SCALE']
pi= math.pi
cps = crv.Points
lock_color = Rhino.ApplicationSettings.AppearanceSettings.LockedObjectColor
#constraint line
line = Rhino.Geometry.Line(cps[0].Location , cps[1].Location)
#draw the preview
def GetTanPointDynamicDrawFunc( sender, args ):
layer_color = sc.doc.Layers.CurrentLayer.Color
current_point = args.CurrentPoint
nc = crv.ToNurbsCurve()
nc.Points.SetPoint(1, current_point )
args.Display.DrawCurve(nc, color, 2)
args.Display.DrawPoint(current_point, color)
args.Display.DrawPoint(cps[0].Location, color)
args.Display.DrawCurve(crv, lock_color, 2)
if show_graph:
args.Display.DrawCurvatureGraph(nc, System.Drawing.Color.White, graph_scale)
args.Display.DrawCurvatureGraph(targ, System.Drawing.Color.White, graph_scale)
while True:
gp = Rhino.Input.Custom.GetPoint()
gp.Constrain(line)
gp.SetCommandPrompt("Set tangent length.Press Enter for default.")
opGraph = Rhino.Input.Custom.OptionToggle(show_graph, "No", "Yes")
gp.AddOptionToggle("ShowGraph",opGraph)
opScale = Rhino.Input.Custom.OptionInteger(graph_scale, 0, True)
gp.AddOptionInteger("GraphScale", opScale)
if show_graph:
opScale = Rhino.Input.Custom.OptionInteger(graph_scale,True, 0)
gp.AddOptionInteger("GraphScale", opScale)
gp.AcceptNumber(True, False)
gp.DynamicDraw += GetTanPointDynamicDrawFunc
rc = gp.Get()
if ( gp.CommandResult() != Rhino.Commands.Result.Success ):
return
if rc==Rhino.Input.GetResult.Point:
return gp.Point()
elif rc == Rhino.Input.GetResult.Option:
idxOp = gp.OptionIndex()
if idxOp == 1:
show_graph = opGraph.CurrentValue
sc.sticky['SHOW_MATCH_GRAPH'] = show_graph
elif idxOp ==2:
graph_scale = opScale.CurrentValue
sc.sticky['MATCH_GRAPH_SCALE'] = graph_scale
continue
elif rc == Rhino.Input.GetResult.Number:
num = int(gp.Number())
if num > 0:
graph_scale = num
sc.sticky['MATCH_GRAPH_SCALE'] = graph_scale
continue
def CustomGetPoint(crv, targ, color, targIsEdge):
flipFlag = False
pi = math.pi
# Draw the preview
def GetPointDynamicDrawFunc( sender, args ):
layer_color = sc.doc.Layers.CurrentLayer.Color
current_point = args.CurrentPoint
nc = crv.ToNurbsCurve()
cps = nc.Points
ptDist = cps[0].Location.DistanceTo(cps[1].Location)
#Continuity is Perp
if cont == 2:
if targIsEdge:
if sc.sticky.has_key('MATCH_BREP_EDGE'):
edge, face = sc.sticky['MATCH_BREP_EDGE']
brep = edge.Brep
#Get a unit tangent to the face that is perp to the edge
# and facing out away from the face
vecTan, ZAxis = ExteriorTangent(face, current_point, edge)
#Scale the tangent by the distance between first and second curve points
vecTan = vecTan*ptDist
else:
#get the scaled vector and an axis to rotate it
vecTan, zAxis = find_tangent(current_point, crv, targ)
vecTan.Rotate(pi/2, zAxis)
if flipFlag:
vecTan.Reverse()
#set the second curve point to the end of the scaled tangent vec.
nc.Points.SetPoint(1, current_point + vecTan)
#Continuity is position or tangent to the target
else:
vecTan, ZAxis = find_tangent(current_point, crv, targ)
if vecTan is not None:
if flipFlag: vecTan.Reverse()
if cont == 1:
#If tangent, set the second curve point to the end of the
#scaled tangent vector.
nc.Points.SetPoint(1, current_point + vecTan)
#Set the first point to the current point location
nc.Points.SetPoint(0, current_point)
#Draw the curve and the current point
args.Display.DrawCurve(nc, color, 2)
args.Display.DrawPoint(current_point, color)
#display the correct information about
#distance and % from each end.
if infoDisplay > 0:
targ_length = round(targ.GetLength(), 3)
splitRC, splitPar = targ.ClosestPoint(current_point)
splitCrvs = targ.Split(splitPar)
if len(splitCrvs) == 2:
len1 = str(round(splitCrvs[0].GetLength(), 3))
len2 = str(round(splitCrvs[1].GetLength(), 3))
pct1 = str(round((splitCrvs[0].GetLength()/targ_length)*100, 2)) + chr(37)
pct2 = str(round((splitCrvs[1].GetLength()/targ_length)*100, 2)) + chr(37)
p1 = splitCrvs[0].PointAt( splitCrvs[0].Domain.Mid)
p2 = splitCrvs[0].PointAt( splitCrvs[1].Domain.Mid)
if infoDisplay ==1:
strDot1 = len1
strDot2 = len2
elif infoDisplay ==2:
strDot1 = pct1
strDot2 = pct2
else:
strDot1 = len1 + '\n'+ pct1
strDot2 = len2 + '\n'+ pct2
if split:
args.Display.DrawCurve(splitCrvs[0], System.Drawing.Color.Chartreuse, 2)
args.Display.DrawCurve(splitCrvs[1], System.Drawing.Color.CornflowerBlue, 2)
else:
args.Display.DrawCurve(targ, System.Drawing.Color.Chartreuse, 2)
args.Display.DrawDot( p1, strDot1 )
args.Display.DrawDot( p2, strDot2 )
if show_graph:
args.Display.DrawCurvatureGraph( nc, System.Drawing.Color.White, graph_scale)
args.Display.DrawCurvatureGraph( targ, System.Drawing.Color.White, graph_scale)
while True:
cont = 1
if sc.sticky.has_key('MATCH_CONT'):
cont = sc.sticky['MATCH_CONT']
split = False
if sc.sticky.has_key('SPLIT_TARGET'):
split = sc.sticky['SPLIT_TARGET']
infoDisplay = 0
if sc.sticky.has_key('DISPLAY_INFO'):
infoDisplay = sc.sticky['DISPLAY_INFO']
show_graph = False
if sc.sticky.has_key('SHOW_MATCH_GRAPH'):
show_graph = sc.sticky['SHOW_MATCH_GRAPH']
graph_scale = 100
if sc.sticky.has_key('MATCH_GRAPH_SCALE'):
graph_scale = sc.sticky['MATCH_GRAPH_SCALE']
displayList = 'None', 'Distance', 'Percent', 'Both'
gp = Rhino.Input.Custom.GetPoint()
gp.Constrain(targ, False)
gp.PermitObjectSnap(True)
gp.SetCommandPrompt("Set match point.")
opFlip = gp.AddOption("Flip")
opSplit = Rhino.Input.Custom.OptionToggle(split, "No", "Yes")
gp.AddOptionToggle("SplitTargetCurve", opSplit)
opCont = gp.AddOptionList("Continuity", ["Position", "Tangency", "Perpendicular"], cont)
#opCont = Rhino.Input.Custom.OptionToggle(cont, "Position", "Tangency")
#gp.AddOptionToggle("Continuity", opCont)
opDisp = gp.AddOptionList("DisplayInfo", displayList, infoDisplay)
opGraph = Rhino.Input.Custom.OptionToggle(show_graph, "No", "Yes")
gp.AddOptionToggle("ShowGraph",opGraph)
if show_graph:
opScale = Rhino.Input.Custom.OptionInteger(graph_scale,True, 0)
gp.AddOptionInteger("GraphScale", opScale)
gp.AcceptNumber(True, False)
gp.DynamicDraw += GetPointDynamicDrawFunc
rc = gp.Get()
if ( gp.CommandResult() != Rhino.Commands.Result.Success ):
return
if rc==Rhino.Input.GetResult.Point:
return gp.Point(), flipFlag, split, cont
elif rc==Rhino.Input.GetResult.Option:
idxOp = gp.OptionIndex()
if idxOp == 1:
if flipFlag == False:
flipFlag = True
else:
flipFlag = False
elif idxOp == 2:
split = opSplit.CurrentValue
sc.sticky['SPLIT_TARGET'] = split
elif idxOp ==3:
#cont = opCont.CurrentValue
cont = gp.Option().CurrentListOptionIndex
sc.sticky['MATCH_CONT'] = cont
elif idxOp ==4:
infoDisplay = gp.Option().CurrentListOptionIndex
sc.sticky['DISPLAY_INFO'] = infoDisplay
elif idxOp == 5:
show_graph = opGraph.CurrentValue
sc.sticky['SHOW_MATCH_GRAPH'] = show_graph
elif idxOp == 6:
graph_scale = opScale.CurrentValue
sc.sticky['MATCH_GRAPH_SCALE'] = graph_scale
elif rc==Rhino.Input.GetResult.Number:
num = int(gp.Number())
if num > 0:
graph_scale = num
sc.sticky['MATCH_GRAPH_SCALE'] = graph_scale
continue
def MatchOnCrv():
pi = math.pi
if sc.sticky.has_key('MATCH_BREP_EDGE'):
sc.sticky.Remove('MATCH_BREP_EDGE')
def filter_open_curves(rhino_object, geometry, component_index):
if geometry.IsClosed:
return False
return True
def id_filter(rhino_object, geometry, component_index):
#don't allow selecting the curve to change
if rhino_object.Id == crvId:
if crvIsEdge:
if component_index.Index == crvObj.GeometryComponentIndex.Index:
return False
else:
return True
return True
crvObj = rs.GetObject("Select the curve to match near the end to change", 4, False, False, subobjects = True, custom_filter = filter_open_curves)
if not crvObj: return
copy = False
edge = None
brep = None
if crvObj.GeometryComponentIndex.Index != -1:
crvIsEdge=True
crvId = crvObj.ObjectId
pass
if crvObj.GeometryComponentIndex.ComponentIndexType == Rhino.Geometry.ComponentIndexType.BrepEdge:
crv =crvObj.Brep().Edges[crvObj.GeometryComponentIndex.Index].ToNurbsCurve()
copy=True
else:
crvId = crvObj.ObjectId
crv = crvObj.Geometry()
color = rs.ObjectColor(crvId)
rc, par = crv.ClosestPoint(crvObj.SelectionPoint())
if par > crv.Domain.Mid:
crv.Reverse()
targObj = None
gTarg = Rhino.Input.Custom.GetObject()
gTarg.GeometryFilter = Rhino.DocObjects.ObjectType.Curve|Rhino.DocObjects.ObjectType.EdgeFilter
targRC = gTarg.Get()
if gTarg.CommandResult() != Rhino.Commands.Result.Success:
return gTarg.CommandResult()
if targRC == Rhino.Input.GetResult.Object:
targObj = gTarg.Object(0)
if not targObj:return
rs.UnselectAllObjects()
#targObj = rs.GetObject("Select target curve", 4, subobjects = True, custom_filter = id_filter)
targIsEdge = False
targId = targObj.ObjectId
if targObj.GeometryComponentIndex.Index != -1:
targIsEdge=True
pass
if targObj.GeometryComponentIndex.ComponentIndexType == Rhino.Geometry.ComponentIndexType.BrepTrim:
brep = targObj.Brep()
face = brep.Trims[targObj.GeometryComponentIndex.Index].Face
edge = brep.Trims[targObj.GeometryComponentIndex.Index].Edge
targ = edge.ToNurbsCurve()
sc.sticky['MATCH_BREP_EDGE']= edge, face
else:
targ = rs.coercecurve(targId)
pass
info = CustomGetPoint(crv, targ, color, targIsEdge)
if info is None: return
pt, flipFlag, split, cont = info
vecTan, ZAxis = find_tangent(pt, crv, targ)
if vecTan is not None:
if flipFlag: vecTan.Reverse()
nc = crv.ToNurbsCurve()
if cont ==2:
cps = nc.Points
nc.Points.SetPoint(0, pt)
ptDist = cps[0].Location.DistanceTo(cps[1].Location)
if targIsEdge:
vecTan, ZAxis = ExteriorTangent(face, pt, edge)
vecTan = vecTan*ptDist
else:
#get the scaled vector and an axis to rotate it
vecTan, zAxis = find_tangent(pt, crv, targ)
vecTan.Rotate(pi/2, zAxis)
if flipFlag:
vecTan.Reverse()
nc.Points.SetPoint(1, pt + vecTan)
tPt = GetPointTangent(nc, targ, color)
if tPt:
nc.Points.SetPoint(1, tPt)
if cont == 1:
nc.Points.SetPoint(0, pt)
nc.Points.SetPoint(1, pt + vecTan)
tPt = GetPointTangent(nc, targ, color)
if tPt:
nc.Points.SetPoint(1, tPt)
if split:
pars = []
if targIsEdge:
targ = edge.ToNurbsCurve()
targ_domain = targ.Domain
if targ.IsClosed:
pars.append(targ_domain.Min)
splitRC, par = targ.ClosestPoint(pt)
if targ_domain.Min< par < targ_domain.Max:
pars.append(par)
splits = targ.Split(par)
splitIds = [sc.doc.Objects.AddCurve(split_crv) for split_crv in splits]
if len(splitIds) >1 :
print "Target curve split at match point."
rs.MatchObjectAttributes(splitIds, targId)
if not targIsEdge:rs.DeleteObject(targId)
else:
print "Cannot split closed target curve, or edge."
if copy:
sc.doc.Objects.AddCurve(nc)
else:
sc.doc.Objects.Replace(crvId, nc)
sc.doc.Views.Redraw()
if __name__ == '__main__': MatchOnCrv()
@pgolay
Copy link
Author

pgolay commented Aug 26, 2019

Added CurvatureGraph

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment