Efficient and stable way to compute moments of NURBS curve

Good morning everyone,

I’m currently working on something, where I’d like to make use of the rhinoinside for python from Rhino 7.

  • What do I want to do
    Modelling a 2D - NURBS - Curve starting from the cartesian origin at (0.00,0.00), going on in the (x,y)-plane and integrating it from x = 0 to a variable upperBound x to get the area and the centroid (1st moment). A *.gh script is attached for reference. integrateNurbsCurveFromZeroToUpperBound.gh (17.2 KB)

  • How do I do it
    Since RhinoCommon only provides AreaMassProperties for closed curves, the intersection of a vertical Line at the upperBound x starting from the x - axis with the NURBS curve is computed via an optimization problem. While I am totally aware of the CurveLineIntersection capabilities of RhinoCommon, this is the easier way for this application. After determining the point of intersection and projecting it down to the x-axis, a horizontal Line from the origin to the upperBound is created. The NURBS - Curve is trimmed after obtaining the curve parameter, where the intersection takes place. All Curves are put into a list and joined subsequently, allowing me to obtain the moments.

  • Where is the problem?
    While above mentioned approach works for polynomial degree = 1 perfectly fine, the joinCuves() method does not seem to work properly when degree > 1 returning an empty array. Hence computing the AreaMassProperties fails. The point of Intersection on the curve still seems correct, but joining fails.

  • Remarks on the code attached

  1. Strain is the x - axis, Stress is the y - axis, parametricStrain is the curve parameter
  2. Intersection is computed with scipy and brentq()
  3. Run it with degree = 1, it returns the correct area = 2.5 for integration between 0 and x = 2.00
  4. Run it with degree = 2, it returns print(‘areaMassProperties.Area:\t\t’,areaMassProperties2.Area) AttributeError: ‘NoneType’ object has no attribute ‘Area’
  • What would I like to know?
  1. What is the best way to compute an area and its centroid for an open curve?
  2. What am I doing wrong at the joining operation?
  3. How to improve the code regarding computational efficiency?
import rhinoinside
rhinoinside.load()
import System
import Rhino.Geometry as rg
import numpy as np
import matplotlib.pyplot as plt
from scipy import optimize as opt
import datetime
plt.close('all')



x = [0.00, 1.00, 2.00,  3.0,  4.0,  5.0]
y = [0.00, 2.00, 1.00 ,15.0, 10.0, 20.0]

pointCount  = 5
degree      = 1 #change this to 2

nc = rg.NurbsCurve(dimension = 2, rational = True, order =  degree + 1, pointCount = pointCount)

numAllKnots = pointCount + degree - 1
numExtKnots = degree
numIntKnots = numAllKnots - 2 * degree
didSucceed = nc.Knots.CreateUniformKnots(1/(numIntKnots+1))

for k in nc.Knots:
    print(k)

for i in range(nc.Points.Count):
    nc.Points.SetPoint(index = i, x = x[i], y = y[i], z = 0.0)
    nc.Points.SetWeight(index = i, weight = 1.0)

print('nc.Domain.Min:\t', nc.Domain.Min)
print('nc.Domain.Max:\t', nc.Domain.Max)
print('nc.IsValid:\t', nc.IsValid)
print('nc.SpanCount:\t', nc.SpanCount)

t = np.linspace(nc.Domain.Min, nc.Domain.Max,101)
for i in t:
    pt = nc.PointAt(t = i)
    plt.scatter(x = pt.X, y = pt.Y)

#----------------------------------------------------------------------
def getParametricStrain(realStrain):
    def checkParametricStrain(assumedParametricStrain):
        return realStrain - nc.PointAt(assumedParametricStrain).X #realStrainResidual
    parametricStrain = opt.brentq(checkParametricStrain, a = nc.Domain.Min, b = nc.Domain.Max)
    return parametricStrain

#------------------------------------------------------------------------
realStrain = 2.00

parametricStrain = getParametricStrain(realStrain)
residualStrain = realStrain - nc.PointAt(parametricStrain).X
realStress = nc.PointAt(parametricStrain).Y

currentDomain = rg.Interval(0.0,parametricStrain)
currentNurbsCurve = nc.Trim(currentDomain)

pointOnCurve = rg.Point3d(realStrain,realStress,0.0)
projectedPoint = rg.Point3d(realStrain,0.0,0.0)
origin = rg.Point3d(0.0,0.0,0.0)

auxVertical   = rg.LineCurve(pointOnCurve, projectedPoint)
auxHorizontal   = rg.LineCurve(projectedPoint, origin)

crv = System.Collections.Generic.List\[rg.Curve\]() 
crv.Add(currentNurbsCurve)
crv.Add(auxVertical.ToNurbsCurve())
crv.Add(auxHorizontal.ToNurbsCurve())

joinedCurve = rg.Curve.JoinCurves(crv)
areaMassProperties2 = rg.AreaMassProperties.Compute(joinedCurve, area = True, firstMoments = True, secondMoments = False, productMoments = False)

print('----------------------------------------------------------------------')
print('areaMassProperties.Area:\t\t',areaMassProperties2.Area)
print('areaMassProperties.AreaError:\t\t',areaMassProperties2.AreaError)
print('areaMassProperties.Centroid:\t\t',areaMassProperties2.Centroid.X)
print('areaMassProperties.CentroidError:\t',areaMassProperties2.CentroidError.X)
print('----------------------------------------------------------------------')

FYI, I edited your post to have proper codeblock markup which ensures indentation remains intact.

@GregArden - is this something you can help with?

1 Like
import rhinoinside
rhinoinside.load()
import System
import Rhino

x = [0.00,0.00,2.00 ]
y = [0.00,2.00,2.00 ]
w = [1.00,1.00,1.00 ]

pointCount  = 3
degree      = 1

numAllKnots = pointCount + degree - 1
numExtKnots = degree
numIntKnots = numAllKnots - 2 * degree

if degree >= pointCount:
    raise ValueError('curveDegree must be less than numberControlPoints')

nc = Rhino.Geometry.NurbsCurve(dimension = 2, rational = True, order =  degree + 1, pointCount = pointCount)
nc.Knots.CreateUniformKnots(1/(numIntKnots+1))

for i in range(nc.Points.Count):
    nc.Points.SetPoint(index = i, x = x[i], y = y[i], z = 0.0)
    nc.Points.SetWeight(index = i, weight = w[i])

pts0 = Rhino.Collections.Point3dList(initialCapcity = 3)
ref = nc.PointAt(nc.Domain.Max)
pts0.Add(ref.X, ref.Y, 0.0)
pts0.Add(ref.X, 0.0,  0.0)
pts0.Add(0.0, 0.0, 0.0)

pl0 = Rhino.Geometry.PolylineCurve(pts0)

otherList = Rhino.Collections.CurveList(initialCapacity = 2)
otherList.Add(nc)
otherList.Add(pl0)

joinedCurve = Rhino.Geometry.Curve.JoinCurves(otherList, tolerance = 1E-6, preserveDirection = False)
print(joinedCurve)
print(joinedCurve[0])

Here is a MWE. The problem arises before this, but only becomes visible when calculating the AreaMassProperties, as a NoneType is returned when joining and is also processed further without any problems. Only when an attempt is made to query the area is it noticed that it does not exist.

I also tried joining PolyCurves, Polylines, Line, CurveLines. Everything works out as it is supposed to do, but only for the NURBS degree = 1.

Find attached the process of closing the NURBS curve.