Search code examples
pythonnurbs

Is this a bug in NURBS-python when updating the control points?


I used NURBS-python and found an interesting problem, and not sure this is done like this intentionally or it is simply a bug. I would like to introduce this with 2 codes.

The first one should output the same as the second one, while it is not. The first one successfully updated the control points of the NURBS curve and drawed a new curve,

The way to change the control points list influence the results.

import math
from geomdl import BSpline
from geomdl import NURBS
from geomdl import fitting
from geomdl import convert
from geomdl.visualization import VisMPL

P1=[[0, 0], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [0, 0]]
Degree=3
CPN=5

P2=[[0, 0], [0, 1], [2, 1], [2, 0], [2, -1], [0, -1], [0, 0]]
CP=[[0, 0], [1, 2], [2, 0], [1, -2], [0, 0]]

c1=NURBS.Curve()
c1.degree=Degree
c1.ctrlpts=P1
c1.weights=[1, 1, 1, 1, 1, 1, 1]
c1.knotvector=[0, 0, 0, 0, 0.25, 0.5, 0.75, 1, 1, 1, 1]

c1.vis=VisMPL.VisCurve2D()
c1.render()

c1.ctrlpts=P2
c1.vis=VisMPL.VisCurve2D()
c1.render()


c2=convert.bspline_to_nurbs(fitting.approximate_curve(P1, Degree, ctrlpts_size=CPN))

c2.vis=VisMPL.VisCurve2D()
c2.render()

c2.ctrlpts=CP

c2.vis=VisMPL.VisCurve2D()
c2.render()

while the second one simply update the control points, the curve itself didn't change at all.

import math
from geomdl import BSpline
from geomdl import NURBS
from geomdl import fitting
from geomdl import convert
from geomdl.visualization import VisMPL

P1=[[0, 0], [0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [0, 0]]
Degree=3
CPN=5

P2=[[0, 0], [0, 1], [2, 1], [2, 0], [2, -1], [0, -1], [0, 0]]
CP=[[0, 0], [1, 2], [2, 0], [1, -2], [0, 0]]

c1=NURBS.Curve()
c1.degree=Degree
c1.ctrlpts=P1
c1.weights=[1, 1, 1, 1, 1, 1, 1]
c1.knotvector=[0, 0, 0, 0, 0.25, 0.5, 0.75, 1, 1, 1, 1]

c1.vis=VisMPL.VisCurve2D()
c1.render()


for i in range(len(P2)):
    c1.ctrlpts[i]=P2[i]
c1.vis=VisMPL.VisCurve2D()
c1.render()

c2=convert.bspline_to_nurbs(fitting.approximate_curve(P1, Degree, ctrlpts_size=CPN))

c2.vis=VisMPL.VisCurve2D()
c2.render()

for i in range(len(CP)):
    c2.ctrlpts[i]=CP[i]

c2.vis=VisMPL.VisCurve2D()
c2.render()

Please help out, thanks.


Solution

  • Curve.ctrlpts is a property. When you access the getter, it returns the list of control points. Accessing the setter does a lot more, mostly consistency checking and necessary cleanups.

    When cpts = c1.ctrlpts is called, __get__ method of the property is called, reference of the list object that stores the control points inside the Curve instance is returned and assigned to cpts variable:

    >>> cpts = c1.ctrlpts
    >>> type(cpts)
    <class 'list'>
    

    When you loop through the list cpts, you actually loop through its reference in the Curve class instance. Since you are interacting with the list object directly, you don't access the ctrlpts setter, which actually does the cleanups by calling Curve.reset() method.

    Unfortunately, it is not possible to override methods of the built-in classes (the classes defined on the API level).

    >>> list.__getitem__ = my_getter_method
    TypeError: can't set attributes of built-in/extension type 'list'
    

    As a result, triggering the Curve.reset() method when the underlying list object changes becomes a little bit complicated (but not impossible).

    The best and the safest way to set control points is using the setter method of the property:

    my_ctrlpts = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
    c1.ctrlpts = my_ctrlpts
    

    If you would like to use the for loop, then you need to call the reset method manually:

    for i in range(len(P2)):
        c1.ctrlpts[i]=P2[i]
    c1.reset(evalpts=True)
    

    Note: I am the author of NURBS-Python (geomdl)