NURBS curves in the pyqt

I was trying to make a nurbs curve with the qt painter or with some class alike. But I can’t seem to find evidence of it being possible. Is it really missing or there is a way to do it and I am not finding it?

Hi

what is a nurb curve?
you can draw bezier curves, but nurbs, I don’t know

but if you have mathematics knowledge about it, calculate points and draw a path.

Grum999

A nurbs curve are like a bezier curves but without handles. It uses the other points nearby to sorta act like handles themselves.

You can make circles easy with just 4 points if they are all at the same distance per example. Bezier curves would need editing the handles also which would make things harder for the user I think though having much more control on the curve.

Ussualy you have nurbs and bezier side by side.

It looks kinda like this

I don’t have knowledge how to do that mathematically, at least not yet. But you can use a function to plot a path? Just like that? I haven’t tackled beziers yet because I have had no need yet. I just expected nurbs around considering the existence of beziers.

with Qt, you can use QPainterPath
It’s easy to use, you can combine lines, arcs, bézier… in a path

Here an example I made for newspaper plugin

  1. create path
                path = QPainterPath(QPointF(0, outputHeight/2))
                path.cubicTo(QPointF(outputWidth * 0.125, outputHeight * 0.25), QPointF(outputWidth * 0.375, outputHeight * 0.25), QPointF(outputWidth/2, outputHeight/2))
                path.cubicTo(QPointF(outputWidth * 0.625, outputHeight * 0.75), QPointF(outputWidth * 0.875, outputHeight * 0.75), QPointF(outputWidth, outputHeight/2))
                path.cubicTo(QPointF(outputWidth * 0.625, outputHeight * 0.65), QPointF(outputWidth * 0.875, outputHeight * 0.65), QPointF(outputWidth/2, outputHeight/2))
                path.cubicTo(QPointF(outputWidth * 0.125, outputHeight * 0.35), QPointF(outputWidth * 0.375, outputHeight * 0.35), QPointF(0, outputHeight/2))
                path.closeSubpath()
  1. draw/fil path on painter
                canvas = QPainter()
                canvas.begin(self.__pixmapStylePreviewCMYK[currentMode])
                canvas.fillRect(QRectF(0,0,outputWidth, outputHeight), gradient);
                canvas.setBrush(QBrush(QColor(Qt.black), Qt.SolidPattern))
                canvas.setPen(QPen(Qt.NoPen))
                canvas.setRenderHint(QPainter.Antialiasing, True)
                canvas.fillPath(path, QBrush(QColor(Qt.black), Qt.SolidPattern))
                canvas.end()

In composition helper plgin, I also use path to build the spiral helper.

After, you need to understand how arc or bezier works with Qt.
But it’s not difficult, just do some simple tests to understand how to use it, but documentation is clear and contain examples.

The most simple is to start with simple lines :slight_smile:

Grum999

1 Like

It looks like bézier curves…

Maybe the quadratic bézier curve function is what you want :wink:

Grum999

1 Like

was taking a looking at math about it.

give me a bit to digest that code, and the other functions of that class.

1 Like

The video provides very good and simple explanation about what is a quadratic curve and a bézier curve.

But with provided Qt function I don’t think you need to calculate curve points by yourself.

Except if you want to use it to define a setting curve (like Color Adjustment Curve filter), In this case, use it curve for rendering it, and keep mathematical calculation of point in curve only for data processing.

Grum999

Was doing some crazy experiments with it:

like it does the circles but they look a bit wonky

def paintEvent(self, event):
    # Start Qpainter
    painter = QPainter()
    painter.begin(self)
    painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
    # Pen and Brush
    painter.setPen(QPen(QColor(147,174,67), 2, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin))
    painter.setBrush(QBrush(QColor(190,53,67)))
    # Painter Path
    path = QPainterPath(QPoint(100,200))
    # Cubic Method
    path.cubicTo(100,200,100,100,200,100)
    path.cubicTo(200,100,300,100,300,200)
    path.cubicTo(300,200,300,300,200,300)
    path.cubicTo(200,300,100,300,100,200)
    # Quad Method
    path.moveTo(400, 200)
    path.quadTo(400,100, 500,100)
    path.quadTo(600,100, 600,200)
    path.quadTo(600,300, 500,300)
    path.quadTo(400,300, 400,200)

    painter.drawPath(path)

    # Finish QPainter
    painter.end()
    self.update()

it does closer what I wanted as a nurbs behaviour but would still be off. I guess the only better option is to make a draw ellipse but for what I wanted I dont think that flies. the ideal scenario would be a circle with 4 control points only as Nurbs do. I will keep exploring this class there must be a way.

For the cubic segments you are putting the first control point in the same place as the first point.
A circle can only be approximated with cubic bezier curves. You can try:

path.cubicTo(100,150,150,100,200,100)
path.cubicTo(250,100,300,150,300,200)
path.cubicTo(300,250,250,300,200,300)
path.cubicTo(150,300,100,250,100,200)

Here you can find more info and a more precise offset for the control points:
http://www.whizkidtech.redprince.net/bezier/circle/

2 Likes

@Deif_Lou Sadley it becomes a bit wonky too.
circles2

I was reading information on nurbs curves in math books now to try and see if I can understand any of it.
I think it might be a better choice for my case to just make a for loop and create a poly path with a variable level of detail for it’s size. that or I just hardcode the interpolation values that make up a circle and are dependant of the plane control points.

What are you trying to achieve?
Yeah, you can not make a perfect circle with cubic splines, you can only approximate it.
Have you tried the values for the control points suggested in the links I posted? They are the ones that better approximate the circle with a 4 point cubic bezier spline. These are images taken from that stackoverflow thread. As you can see in the second image, the approximation is almost perfect:


Yes I did pass through that one too. but what i made from adjusting those values only remade what I already did before the results were not that great.
Unless I tottally used it all wrong… that is also a possibility I am still getting the hang of this class.

As for my objective, in Blender I can show the NURBs curves in action while in edit mode.
I want to set up a circle made up with 4 control points making up a circle (does not need to be super perfect just not too wonky) so I distort it in the way shown in the gif.
nurbs_4cvs

you can take a look on what I have now too (still wip). I have a Polygon triangle and a square reacting in such a way but they have no curvy curves. I was trying to make the Circle happen next or perhaps a tear shape object but reacting in the same sort of fashion.
objective

my testing that quadTo does not work for my case, it gets or pointy or too squary. This happens because the control point is shared:

quadTo

    # Default Values
    self.event_x = 0
    self.event_y = 0

def mousePressEvent(self, event):
    self.Mouse(event)
def mouseMoveEvent(self, event):
    self.Mouse(event)
def Mouse(self, event):
    self.event_x = event.x()
    self.event_y = event.y()

def paintEvent(self, event):
    # Start Qpainter
    painter = QPainter()
    painter.begin(self)
    painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
    # Pen and Brush
    painter.setPen(QPen(QColor(147,174,67), 2, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin))
    painter.setBrush(QBrush(QColor(190,53,67)))
    # Painter Path
    path = QPainterPath()
    # # Cubic
    # path.cubicTo(100,200,100,100,200,100)
    # path.cubicTo(200,100,300,100,300,200)
    # path.cubicTo(300,200,300,300,200,300)
    # path.cubicTo(200,300,100,300,100,200)
    # # Quad
    # path.moveTo(400, 200)
    # path.quadTo(400,100, 500,100)
    # path.quadTo(600,100, 600,200)
    # path.quadTo(600,300, 500,300)
    # path.quadTo(400,300, 400,200)

    # # Deif_Lou
    # path.moveTo(100,200)
    # path.cubicTo(100,150,150,100,200,100)
    # path.cubicTo(250,100,300,150,300,200)
    # path.cubicTo(300,250,250,300,200,300)
    # path.cubicTo(150,300,100,250,100,200)

    # Quad
    P1 = [100,100]
    P2 = [500,100]
    P3 = [500,500]
    P4 = [100,500]
    PM = self.Math_2D_Centroid_Square(P1[0],P1[1],P2[0],P2[1],P3[0],P3[1],P4[0],P4[1])
    dist_x = self.event_x - PM[0]
    dist_y = self.event_y - PM[1]
    P12 = [self.LERP(P1[0],P2[0],0.5), self.LERP(P1[1],P2[1],0.5)]
    P23 = [self.LERP(P2[0],P3[0],0.5), self.LERP(P2[1],P3[1],0.5)]
    P34 = [self.LERP(P3[0],P4[0],0.5), self.LERP(P3[1],P4[1],0.5)]
    P41 = [self.LERP(P4[0],P1[0],0.5), self.LERP(P4[1],P1[1],0.5)]

    # path.moveTo(P41[0],P41[1])
    # path.quadTo(P1[0],P1[1], P12[0],P12[1])
    # path.quadTo(P2[0],P2[1], P23[0],P23[1])
    # path.quadTo(P3[0],P3[1], P34[0],P34[1])
    # path.quadTo(P4[0],P4[1], P41[0],P41[1])

    path.moveTo(P41[0],P41[1])
    path.quadTo(PM[0]-dist_x,PM[1]+dist_y, P12[0],P12[1])
    path.quadTo(self.event_x,self.event_y, P23[0],P23[1])
    path.quadTo(PM[0]+dist_x,PM[1]-dist_y, P34[0],P34[1])
    path.quadTo(PM[0]-dist_x,PM[1]-dist_y, P41[0],P41[1])


    painter.drawPath(path)

    painter.drawLine(0,self.event_y, 1000,self.event_y)
    painter.drawLine(self.event_x,0, self.event_x,1000)
    painter.drawLine(P2[0],P2[1], P4[0],P4[1])


    # Finish QPainter
    painter.end()
    self.update()

    self.layout.label_1.setText(str(self.event_x))
    self.layout.label_2.setText(str(self.event_y))

def LERP(self, v0, v1, t):
    return (v0+t*(v1-v0))
def Math_2D_Centroid_Square(self, a1, a2, b1, b2, c1, c2, d1, d2):
    cx = (a1+b1+c1+d1)/4
    cy = (a2+b2+c2+d2)/4
    return [cx, cy]

I am gonna play with cubicTo next.

So I was doing the same for cubicTo and I found my mistake it was in the order of input and their meanning for the bezier curve plotting. Now I should be able to use the value you sent me @Deif_Lou
cubicTo

This one does little lotus flowers if you want to click on it for fun

    # Default Values
    self.event_x = 0
    self.event_y = 0

def mousePressEvent(self, event):
    self.Mouse(event)
def mouseMoveEvent(self, event):
    self.Mouse(event)
def Mouse(self, event):
    self.event_x = event.x()
    self.event_y = event.y()

def paintEvent(self, event):
    # Start Qpainter
    painter = QPainter()
    painter.begin(self)
    painter.setRenderHint(QtGui.QPainter.Antialiasing, True)
    # Pen and Brush
    painter.setPen(QPen(QColor(147,174,67), 2, Qt.SolidLine, Qt.SquareCap, Qt.MiterJoin))
    painter.setBrush(QBrush(QColor(190,53,67)))
    # Painter Path
    path = QPainterPath()
    # # Cubic
    # path.cubicTo(100,200,100,100,200,100)
    # path.cubicTo(200,100,300,100,300,200)
    # path.cubicTo(300,200,300,300,200,300)
    # path.cubicTo(200,300,100,300,100,200)
    # # Quad
    # path.moveTo(400, 200)
    # path.quadTo(400,100, 500,100)
    # path.quadTo(600,100, 600,200)
    # path.quadTo(600,300, 500,300)
    # path.quadTo(400,300, 400,200)

    # # Deif_Lou
    # path.moveTo(100,200)
    # path.cubicTo(100,150,150,100,200,100)
    # path.cubicTo(250,100,300,150,300,200)
    # path.cubicTo(300,250,250,300,200,300)
    # path.cubicTo(150,300,100,250,100,200)

    # Quad
    P1 = [100,100]
    P2 = [500,100]
    P3 = [500,500]
    P4 = [100,500]
    P12 = [self.LERP(P1[0],P2[0],0.5), self.LERP(P1[1],P2[1],0.5)]
    P23 = [self.LERP(P2[0],P3[0],0.5), self.LERP(P2[1],P3[1],0.5)]
    P34 = [self.LERP(P3[0],P4[0],0.5), self.LERP(P3[1],P4[1],0.5)]
    P41 = [self.LERP(P4[0],P1[0],0.5), self.LERP(P4[1],P1[1],0.5)]

    dist_x = self.event_x - P41[0]
    dist_y = self.event_y - P41[1]

    path.moveTo(P41[0],P41[1])
    path.cubicTo(self.event_x,self.event_y, P12[0]+dist_y,P12[1]+dist_x, P12[0],P12[1])
    path.cubicTo(P12[0]-dist_y,P12[1]+dist_x, P23[0]-dist_x,P23[1]+dist_y, P23[0],P23[1])
    path.cubicTo(P23[0]-dist_x,P23[1]-dist_y, P34[0]-dist_y,P34[1]-dist_x, P34[0],P34[1])
    path.cubicTo(P34[0]+dist_y,P34[1]-dist_x, P41[0]+dist_x,P41[1]-dist_y, P41[0],P41[1])

    painter.drawPath(path)

    painter.drawLine(0,self.event_y, 1000,self.event_y)
    painter.drawLine(self.event_x,0, self.event_x,1000)
    painter.drawLine(P1[0],P1[1], P3[0],P3[1])


    # Finish QPainter
    painter.end()
    self.update()

    self.layout.label_1.setText(str(self.event_x))
    self.layout.label_2.setText(str(self.event_y))

def LERP(self, v0, v1, t):
    return (v0+t*(v1-v0))
def Math_2D_Centroid_Square(self, a1, a2, b1, b2, c1, c2, d1, d2):
    cx = (a1+b1+c1+d1)/4
    cy = (a2+b2+c2+d2)/4
    return [cx, cy]

But I must say it was fun making an interactive sort of function to check the correct factor for the input.
It was much faster to make than checking the variations of the behavior of a static display.

I’ve made a test. Here you set the corners of a quadrilateral and a cubic bezier spline is fitted inside, choosing the control points in a way such that the spline approximates a circle or a ellipse if the quadrilateral is a square or a rectangle. It doesn’t make perspective correction so sometimes it can appear a bit off visually:

void window::paintEvent(QPaintEvent*)
{
    QPainter painter(this);
    painter.setRenderHint(QPainter::Antialiasing);

    // lerp function
    auto lerp = [](const QPointF &p1, const QPointF &p2, qreal t)
    {
        return (1.0 - t) * p1 + t * p2;
    };

    // get bezier spline points from quadrilateral control points
    QPointF bezierPoints[4];
    bezierPoints[0] = lerp(m_controlPoints[0], m_controlPoints[1], 0.5);
    bezierPoints[1] = lerp(m_controlPoints[1], m_controlPoints[2], 0.5);
    bezierPoints[2] = lerp(m_controlPoints[2], m_controlPoints[3], 0.5);
    bezierPoints[3] = lerp(m_controlPoints[3], m_controlPoints[0], 0.5);
    // get bezier spline control points
    const qreal optimalDistanceToControlPoint = 0.552284749831;
    QPointF bezierControlPoints[4][2];
    bezierControlPoints[0][0] = lerp(bezierPoints[0], m_controlPoints[1], optimalDistanceToControlPoint);
    bezierControlPoints[0][1] = lerp(bezierPoints[1], m_controlPoints[1], optimalDistanceToControlPoint);
    bezierControlPoints[1][0] = lerp(bezierPoints[1], m_controlPoints[2], optimalDistanceToControlPoint);
    bezierControlPoints[1][1] = lerp(bezierPoints[2], m_controlPoints[2], optimalDistanceToControlPoint);
    bezierControlPoints[2][0] = lerp(bezierPoints[2], m_controlPoints[3], optimalDistanceToControlPoint);
    bezierControlPoints[2][1] = lerp(bezierPoints[3], m_controlPoints[3], optimalDistanceToControlPoint);
    bezierControlPoints[3][0] = lerp(bezierPoints[3], m_controlPoints[0], optimalDistanceToControlPoint);
    bezierControlPoints[3][1] = lerp(bezierPoints[0], m_controlPoints[0], optimalDistanceToControlPoint);
    
    // draw enclosing quadrilateral
    painter.setPen(QPen(QColor(0, 0, 0, 128), 1, Qt::DotLine));
    painter.drawPolygon(m_controlPoints, 4);

    // draw spline
    painter.setPen(Qt::red);
    painter.setBrush(Qt::NoBrush);
    QPainterPath path(bezierPoints[0]);
    path.cubicTo(bezierControlPoints[0][0], bezierControlPoints[0][1], bezierPoints[1]);
    path.cubicTo(bezierControlPoints[1][0], bezierControlPoints[1][1], bezierPoints[2]);
    path.cubicTo(bezierControlPoints[2][0], bezierControlPoints[2][1], bezierPoints[3]);
    path.cubicTo(bezierControlPoints[3][0], bezierControlPoints[3][1], bezierPoints[0]);
    painter.drawPath(path);

    // draw quadrilateral control points
    painter.setPen(Qt::black);
    painter.setBrush(Qt::white);
    for (const QPointF &p: m_controlPoints)
    {
        painter.drawEllipse(p, 5, 5);
    }

    // draw spline points
    painter.setPen(Qt::black);
    painter.setBrush(Qt::white);
    for (const QPointF &p: bezierPoints)
    {
        painter.drawEllipse(p, 2, 2);
    }

    // draw spline control points
    painter.setPen(Qt::black);
    painter.setBrush(Qt::white);
    for (const QPointF *p : bezierControlPoints)
    {
        painter.drawEllipse(p[0], 1, 1);
        painter.drawEllipse(p[1], 1, 1);
    }
}

Another way you can try to make this is by moving the bezier points and computing the tangents and control points like in catmul-rom splines.

1 Like

uuu that is exactly the sort of effect I am trying to achieve :smile:

I think that distortion is more of a boon than a hinder for the user as you will be able to do some odd selections with it. I have been looking at analog artists gamut masks and they sometimes do some weird shapes to the mask, perspective rules are not required for this case.

So I did the circle as you did but I realized my folly in the concept I had in mind. it made a really good circle but it would not allow me touch the edge of the picker with the node. so I experimented a bit more and on the second version I came up with this variation.
Circle

it holds up the shape pretty well until you disarrange the natural order of the nodes making them do arcs into the void. strangely I think still passes no?
Green node is my 1st node, is editable.
Grey are the other nodes, is editable.
Red nodes are construction node, not editable and will be hidden on final version.

what you guys think? @Grum999 @Deif_Lou

    if self.shape == "P1_S1":
        # Points for User
        P1 = [self.P1_S1[0], self.P1_S1[1]]
        P2 = [self.P1_S1[2], self.P1_S1[3]]
        P3 = [self.P1_S1[4], self.P1_S1[5]]
        P4 = [self.P1_S1[6], self.P1_S1[7]]
        # Point of the Centroid
        # PM = self.Math_2D_Centroid_Square(P1[0],P1[1], P2[0],P2[1], P3[0],P3[1], P4[0],P4[1])
        PM = self.Math_2D_Points_Lines_Intersection(P1[0],P1[1], P3[0],P3[1], P2[0],P2[1], P4[0],P4[1])


        # # Points Orthogonal Decomposition Considering the Centroid
        # dist_P1 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], P1[0],P1[1])
        # dist_P2 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], P2[0],P2[1])
        # dist_P3 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], P3[0],P3[1])
        # dist_P4 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], P4[0],P4[1])
        # # Bridge Points for the Control Vectors
        # P12 = [P1[0]+dist_P2[0], P1[1]+dist_P2[1]]
        # P23 = [P2[0]+dist_P3[0], P2[1]+dist_P3[1]]
        # P34 = [P3[0]+dist_P4[0], P3[1]+dist_P4[1]]
        # P41 = [P4[0]+dist_P1[0], P4[1]+dist_P1[1]]

        # Bridge Points
        B12 = [self.Math_1D_LERP(P1[0],P2[0],0.5), self.Math_1D_LERP(P1[1],P2[1],0.5)]
        B23 = [self.Math_1D_LERP(P2[0],P3[0],0.5), self.Math_1D_LERP(P2[1],P3[1],0.5)]
        B34 = [self.Math_1D_LERP(P3[0],P4[0],0.5), self.Math_1D_LERP(P3[1],P4[1],0.5)]
        B41 = [self.Math_1D_LERP(P4[0],P1[0],0.5), self.Math_1D_LERP(P4[1],P1[1],0.5)]
        # Bridge Components
        dist_B12 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], B12[0],B12[1])
        dist_B23 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], B23[0],B23[1])
        dist_B34 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], B34[0],B34[1])
        dist_B41 = self.Math_2D_Ortogonal_Components(PM[0],PM[1], B41[0],B41[1])


        # Intermediate Points
        scalar = 2
        P12 = [PM[0]+self.value1*dist_B12[0], PM[1]+self.value1*dist_B12[1]]
        P23 = [PM[0]+self.value2*dist_B23[0], PM[1]+self.value2*dist_B23[1]]
        P34 = [PM[0]+self.value3*dist_B34[0], PM[1]+self.value3*dist_B34[1]]
        P41 = [PM[0]+self.value4*dist_B41[0], PM[1]+self.value4*dist_B41[1]]



        # Painter Path Object
        path = QPainterPath()
        a = 0.551915024494
        b = 1 - 0.551915024494
        path.moveTo(P1[0], P1[1])
        path.cubicTo(
            QPoint( self.Math_1D_LERP(P1[0],P12[0],a), self.Math_1D_LERP(P1[1],P12[1],a) ),
            QPoint( self.Math_1D_LERP(P12[0],P2[0],b), self.Math_1D_LERP(P12[1],P2[1],b) ),
            QPoint(P2[0],P2[1]))
        path.cubicTo(
            QPoint( self.Math_1D_LERP(P2[0],P23[0],a), self.Math_1D_LERP(P2[1],P23[1],a) ),
            QPoint( self.Math_1D_LERP(P23[0],P3[0],b), self.Math_1D_LERP(P23[1],P3[1],b) ),
            QPoint(P3[0],P3[1]))
        path.cubicTo(
            QPoint( self.Math_1D_LERP(P3[0],P34[0],a), self.Math_1D_LERP(P3[1],P34[1],a) ),
            QPoint( self.Math_1D_LERP(P34[0],P4[0],b), self.Math_1D_LERP(P34[1],P4[1],b) ),
            QPoint(P4[0],P4[1]))
        path.cubicTo(
            QPoint( self.Math_1D_LERP(P4[0],P41[0],a), self.Math_1D_LERP(P4[1],P41[1],a) ),
            QPoint( self.Math_1D_LERP(P41[0],P1[0],b), self.Math_1D_LERP(P41[1],P1[1],b) ),
            QPoint(P1[0],P1[1]))
        painter.drawPath(path)

        # Display Subjective Primaries
        painter.setPen(QtCore.Qt.NoPen)
        painter.setBrush(QBrush(QColor(0,150,0)))
        painter.drawEllipse(self.P1_S1[0]-self.circle, self.P1_S1[1]-self.circle, self.circle*2,self.circle*2)
        painter.setBrush(QBrush(QColor(150,150,150)))
        painter.drawEllipse(self.P1_S1[2]-self.circle, self.P1_S1[3]-self.circle, self.circle*2,self.circle*2)
        painter.drawEllipse(self.P1_S1[4]-self.circle, self.P1_S1[5]-self.circle, self.circle*2,self.circle*2)
        painter.drawEllipse(self.P1_S1[6]-self.circle, self.P1_S1[7]-self.circle, self.circle*2,self.circle*2)
        # Display Subjective Neutral
        painter.setBrush(QBrush(QColor(150,150,150)))
        painter.drawEllipse(self.centroid[0]-self.circle, self.centroid[1]-self.circle, self.circle*2,self.circle*2)
        # Intermediate
        painter.setBrush(QBrush(QColor(150,0,0)))
        painter.drawEllipse(P12[0]-self.circle, P12[1]-self.circle, self.circle*2,self.circle*2)
        painter.drawEllipse(P23[0]-self.circle, P23[1]-self.circle, self.circle*2,self.circle*2)
        painter.drawEllipse(P34[0]-self.circle, P34[1]-self.circle, self.circle*2,self.circle*2)
        painter.drawEllipse(P41[0]-self.circle, P41[1]-self.circle, self.circle*2,self.circle*2)
1 Like

It looks great although I think it would be nice if it allowed better concave shapes, more rounded.

my first try was this one but it kinda works as an approximation not exact values for the shape but it allows better concaves. I am not sure if it worth the trade off of the fuzzy control gives.
Circle_aproximation

as before the grey nodes are control points and red nodes are circle construction points.

My counter argument was the idea I wanted to place of the points show the color of the location they are as they become the new primary colors of the created gamut as stated by the color theory law XD
For the Triangle and for the Square I can do this with no issues as it is a polygon but with the circle I would have to ignore it if I placed these controls.

Also a tad harder to set a single node to the edge and have the shape touch the edge still needs a bit of help of another node to squish it to touch.

However I am a bit neutral.

Or I should make a behaviour change from one to another on given conditions?