Actually, one last thought on this: using a bSpline basis, but with duplicated end points, probably comes closer to matching the shape of the Nurbs. Here's a version of the hacky network that lets you pick the basis:
import Gaffer
import GafferScene
import IECore
import imath
Gaffer.Metadata.registerValue( parent, "serialiser:milestoneVersion", 1, persistent=False )
Gaffer.Metadata.registerValue( parent, "serialiser:majorVersion", 5, persistent=False )
Gaffer.Metadata.registerValue( parent, "serialiser:minorVersion", 1, persistent=False )
Gaffer.Metadata.registerValue( parent, "serialiser:patchVersion", 0, persistent=False )
__children = {}
__children["HackilyFixBadNurbs"] = Gaffer.Box( "HackilyFixBadNurbs" )
parent.addChild( __children["HackilyFixBadNurbs"] )
__children["HackilyFixBadNurbs"].addChild( GafferScene.ScenePlug( "in", flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( GafferScene.MergeScenes( "MergeScenes" ) )
__children["HackilyFixBadNurbs"]["MergeScenes"]["in"].resize( 3 )
__children["HackilyFixBadNurbs"]["MergeScenes"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( GafferScene.ScenePlug( "out", direction = Gaffer.Plug.Direction.Out, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( Gaffer.BoxIn( "BoxIn" ) )
__children["HackilyFixBadNurbs"]["BoxIn"].setup( GafferScene.ScenePlug( "out", ) )
__children["HackilyFixBadNurbs"]["BoxIn"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( Gaffer.BoxOut( "BoxOut" ) )
__children["HackilyFixBadNurbs"]["BoxOut"].setup( GafferScene.ScenePlug( "in", ) )
__children["HackilyFixBadNurbs"]["BoxOut"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( Gaffer.Expression( "Expression" ) )
__children["HackilyFixBadNurbs"]["Expression"]["__in"].addChild( Gaffer.ObjectPlug( "p0", defaultValue = IECore.NullObject(), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"]["Expression"]["__in"].addChild( Gaffer.StringPlug( "p1", defaultValue = '', flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"]["Expression"]["__out"].addChild( Gaffer.ObjectPlug( "p0", direction = Gaffer.Plug.Direction.Out, defaultValue = IECore.NullObject(), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"]["Expression"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( GafferScene.Isolate( "Isolate" ) )
__children["HackilyFixBadNurbs"]["Isolate"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( Gaffer.BoxIn( "BoxIn1" ) )
__children["HackilyFixBadNurbs"]["BoxIn1"].setup( GafferScene.FilterPlug( "out", defaultValue = 7, minValue = 0, maxValue = 7, ) )
__children["HackilyFixBadNurbs"]["BoxIn1"].addChild( Gaffer.V2fPlug( "__uiPosition", defaultValue = imath.V2f( 0, 0 ), flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( GafferScene.FilterPlug( "filter", defaultValue = 7, minValue = 0, maxValue = 7, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( Gaffer.BoolPlug( "enabled", defaultValue = True, flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
__children["HackilyFixBadNurbs"].addChild( Gaffer.StringPlug( "basis", defaultValue = '', flags = Gaffer.Plug.Flags.Default | Gaffer.Plug.Flags.Dynamic, ) )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"], 'uiEditor:emptySections', IECore.StringVectorData( [ ] ) )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"], 'uiEditor:emptySectionIndices', IECore.IntVectorData( [ ] ) )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["in"], 'plugValueWidget:type', '' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["in"], 'layout:section', 'Settings' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["in"], 'layout:index', 1 )
__children["HackilyFixBadNurbs"]["MergeScenes"]["in"][0].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"] )
__children["HackilyFixBadNurbs"]["MergeScenes"]["in"][1].setInput( __children["HackilyFixBadNurbs"]["Isolate"]["out"] )
__children["HackilyFixBadNurbs"]["MergeScenes"]["objectMode"].setValue( 1 )
__children["HackilyFixBadNurbs"]["MergeScenes"]["__uiPosition"].setValue( imath.V2f( 451.980682, -132.734924 ) )
__children["HackilyFixBadNurbs"]["out"].setInput( __children["HackilyFixBadNurbs"]["BoxOut"]["__out"] )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["out"], 'description', 'The processed output scene.' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["out"], 'nodule:type', 'GafferUI::StandardNodule' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["out"], 'layout:section', 'Settings' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["out"], 'layout:index', 2 )
__children["HackilyFixBadNurbs"]["BoxIn"]["__in"].setInput( __children["HackilyFixBadNurbs"]["in"] )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["BoxIn"]["__in"], 'plugValueWidget:type', '' )
__children["HackilyFixBadNurbs"]["BoxIn"]["__uiPosition"].setValue( imath.V2f( 448.980682, -108.49192 ) )
__children["HackilyFixBadNurbs"]["BoxOut"]["in"].setInput( __children["HackilyFixBadNurbs"]["MergeScenes"]["out"] )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["BoxOut"]["__out"], 'description', 'The processed output scene.' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["BoxOut"]["__out"], 'nodule:type', 'GafferUI::StandardNodule' )
__children["HackilyFixBadNurbs"]["BoxOut"]["passThrough"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"] )
__children["HackilyFixBadNurbs"]["BoxOut"]["enabled"].setInput( __children["HackilyFixBadNurbs"]["enabled"] )
__children["HackilyFixBadNurbs"]["BoxOut"]["__uiPosition"].setValue( imath.V2f( 453.479828, -141.066956 ) )
__children["HackilyFixBadNurbs"]["__uiPosition"].setValue( imath.V2f( 451.345306, -129.621979 ) )
__children["HackilyFixBadNurbs"]["Expression"]["__in"]["p0"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["object"] )
__children["HackilyFixBadNurbs"]["Expression"]["__in"]["p1"].setInput( __children["HackilyFixBadNurbs"]["basis"] )
__children["HackilyFixBadNurbs"]["Expression"]["__uiPosition"].setValue( imath.V2f( 463.193054, -120.06765 ) )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["bound"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["bound"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["transform"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["transform"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["attributes"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["attributes"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["object"].setInput( __children["HackilyFixBadNurbs"]["Expression"]["__out"]["p0"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["childNames"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["childNames"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["globals"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["globals"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["setNames"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["setNames"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["set"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["set"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["exists"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["exists"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["childBounds"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["childBounds"] )
__children["HackilyFixBadNurbs"]["Isolate"]["in"]["__sortedChildNames"].setInput( __children["HackilyFixBadNurbs"]["BoxIn"]["out"]["__sortedChildNames"] )
__children["HackilyFixBadNurbs"]["Isolate"]["filter"].setInput( __children["HackilyFixBadNurbs"]["BoxIn1"]["out"] )
__children["HackilyFixBadNurbs"]["Isolate"]["__uiPosition"].setValue( imath.V2f( 460.611237, -124.570862 ) )
__children["HackilyFixBadNurbs"]["BoxIn1"]["name"].setValue( 'filter' )
__children["HackilyFixBadNurbs"]["BoxIn1"]["__in"].setInput( __children["HackilyFixBadNurbs"]["filter"] )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["BoxIn1"]["__in"], 'nodule:type', 'GafferUI::StandardNodule' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["BoxIn1"]["__in"], 'description', 'The filter used to control which parts of the scene are\nprocessed. A Filter node should be connected here.' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["BoxIn1"]["__in"], 'noduleLayout:section', 'right' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["BoxIn1"]["__in"], 'plugValueWidget:type', 'GafferSceneUI.FilterPlugValueWidget' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["BoxIn1"]["out"], 'noduleLayout:section', 'left' )
__children["HackilyFixBadNurbs"]["BoxIn1"]["__uiPosition"].setValue( imath.V2f( 473.924438, -124.571579 ) )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["filter"], 'nodule:type', 'GafferUI::StandardNodule' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["filter"], 'description', 'The filter used to control which parts of the scene are\nprocessed. A Filter node should be connected here.' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["filter"], 'noduleLayout:section', 'right' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["filter"], 'plugValueWidget:type', 'GafferSceneUI.FilterPlugValueWidget' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["filter"], 'layout:section', 'Settings' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["filter"], 'layout:index', 3 )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["enabled"], 'layout:section', 'Node' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["enabled"], 'layout:index', 4 )
__children["HackilyFixBadNurbs"]["basis"].setValue( 'bSpline' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["basis"], 'nodule:type', '' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["basis"], 'plugValueWidget:type', 'GafferUI.PresetsPlugValueWidget' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["basis"], 'layout:section', 'Settings' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["basis"], 'layout:index', 0 )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["basis"], 'preset:bSpline', 'bSpline' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["basis"], 'preset:catmullRom', 'catmullRom' )
Gaffer.Metadata.registerValue( __children["HackilyFixBadNurbs"]["basis"], 'preset:linear', 'linear' )
__children["HackilyFixBadNurbs"]["Expression"]["__engine"].setValue( 'python' )
__children["HackilyFixBadNurbs"]["Expression"]["__expression"].setValue( 'import IECoreScene\n\ncurves = parent["__in"]["p0"]\nif type( curves ) == IECoreScene.CurvesPrimitive:\n\tcurves.setTopology( curves.verticesPerCurve(), IECore.CubicBasisf.linear(), curves.periodic() )\n\t\n\ttargetBasis = IECore.CubicBasisf.linear()\n\tif parent["__in"]["p1"] == "bSpline":\n\t\ttargetBasis = IECore.CubicBasisf.bSpline()\n\telif parent["__in"]["p1"] == "catmullRom":\n\t\ttargetBasis = IECore.CubicBasisf.catmullRom()\n\tcurves = IECoreScene.CurvesAlgo.updateEndpointMultiplicity( curves, targetBasis )\nparent["__out"]["p0"] = curves\n\n' )
del __children