Calculating control point directions

I’ve received a few questions lately on how to calculate the U, V, and N directions of a NURBS curve control point. Perhaps developers are wanting to mimic the behavior of Rhino’s MoveUVN command.

The following sample function will calculate these directions.

/// <summary>
/// Calculates the u, v, n directions of a NURBS curve control point
/// similar to the method used by the MoveUVN command.
/// </summary>
/// <param name="nurb">The NURBS curve to evaluate.</param>
/// <param name="cvIndex">The index of the control point to evaluate.</param>
/// <param name="uDir">The u direction.</param>
/// <param name="vDir">The v direction.</param>
/// <param name="nDir">The n direction.</param>
/// <returns>true if successful, false otherwise</returns>
static bool GetNurbsCurveControlPointDirections(
  NurbsCurve nurb, 
  int cvIndex, 
  out Vector3d uDir, 
  out Vector3d vDir, 
  out Vector3d nDir
  uDir = vDir = nDir = Vector3d.Unset;
  var rc = false;
  if (null != nurb && cvIndex >= 0 && cvIndex < nurb.Points.Count)
    var t = nurb.GrevilleParameter(cvIndex);
    if (RhinoMath.IsValidDouble(t))
      if (t < nurb.Domain.Min)
        t += nurb.Domain.Length;

      uDir = nurb.TangentAt(t);

      var kappa = nurb.CurvatureAt(t);
      if (nurb.TryGetPlane(out Plane plane))
        nDir = plane.ZAxis;
        vDir = Vector3d.CrossProduct(nDir, uDir);
      else if (kappa.Unitize())
        vDir = kappa;
        nDir = Vector3d.CrossProduct(uDir, vDir);
        nDir = Vector3d.CrossProduct(uDir, vDir);

      const double tol = 1E-15;

      if (Math.Abs(uDir.X) <= tol)
        uDir.X = 0.0;
      if (Math.Abs(uDir.Y) <= tol)
        uDir.Y = 0.0;
      if (Math.Abs(uDir.Z) <= tol)
        uDir.Z = 0.0;

      if (Math.Abs(vDir.X) <= tol)
        vDir.X = 0.0;
      if (Math.Abs(vDir.Y) <= tol)
        vDir.Y = 0.0;
      if (Math.Abs(vDir.Z) <= tol)
        vDir.Z = 0.0;

      if (Math.Abs(nDir.X) <= tol)
        nDir.X = 0.0;
      if (Math.Abs(nDir.Y) <= tol)
        nDir.Y = 0.0;
      if (Math.Abs(nDir.Z) <= tol)
        nDir.Z = 0.0;

      rc = true;

  return rc;

Let me know if you have any questions.

– Dale


Hello Dale,
I am new to Grasshopper developement and I am trying to adapt this code for nurbssurfaces. The directions I get there do however not correspond to the directions computed in rhino using the MoveUVN command. Afterward this appears logic to me as the GrevillePoint of a nurbssurface control point doesn’t ly on the surface, so getting the curvature or the normal at the greville point coordinates does not lead to the awaited result (x, y not u, v).
So I then tried using the control point’s closest point on the surface. Here to, the computed directions do not match the MoveUVN directions.
My question: what point on an input surface does MoveUVN command use to compute tangent and normal directions?
Thank you for any support.

Hi @Moonpick,

The Rhino 7 WIP has these new MoveUVN-like methods:



Perhaps these help?

– Dale

Thank you for the quick feedback. I am sure they will!
By now unfortunately, I get at runtime a System.MissingMethodException: ‘Methode not found: “Boolean Rhino.Geometry.Collections.NurbsSurfacePointList.UVNDirectionsAt(Int32, Int32, Rhino.Geometry.Vector3d ByRef, Rhino.Geometry.Vector3d ByRef, Rhino.Geometry.Vector3d ByRef)”.’

Same with NurbsSurface.UVNDirectionsAt.
I previously installed Rhino WIP and refered the correspondant rhinocommon library in my project. So I’d say it is all allright at my end. Looks like the library is still not complete there?

Hi @Moonpick,

This works in the current Rhino 7 WIP:

import Rhino
import rhinoscriptsyntax as rs

srf_id = rs.GetObject("Select surface", rs.filter.surface)
if srf_id:
    srf = rs.coercesurface(srf_id)
    ns = srf.ToNurbsSurface()
    for u in range(0, ns.Points.CountU):
        for v in range(0, ns.Points.CountV):
            rc = ns.Points.UVNDirectionsAt(u, v)
            if rc:
                print('CV ({},{}):'.format(u, v))
                print('  UDir: ({}):'.format(rc[1]))
                print('  VDir: ({}):'.format(rc[2]))
                print('  NDir: ({}):'.format(rc[3]))

I’m not sure I understand this. But if you are referencing the Rhino WIP’s RhinoCommon.dll in a Rhino 6 project, then don’t.

– Dale

You got it. I was running (or trying to) my custom component based on the wip dll in Rhino6. With the wip Rhino it works. Thanks for the tips!