Last active
August 26, 2019 03:33
-
-
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.
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 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() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Added CurvatureGraph