Python flip curve (rs.ReverseCurve)

This should be so simple, and the forum is discouraging me from creating a new topic:

Your topic is similar to…

However, I’ve searched far and wide without finding an answer.

Here is my Python code:

import rhinoscriptsyntax as rs

if crv.ClosedCurveOrientation(P) == 'Clockwise':
    CW = crv
    CCW = rs.ReverseCurve(crv)
else:
    CW = rs.ReverseCurve(crv)
    CCW = crv

Both lines referring to ‘rs.ReverseCurve(crv)’ cause this error:

Runtime error (TypeErrorException): Parameter must be a Guid or string representing a Guid

Traceback:
line 890, in coerceguid, “C:\Users\josep\AppData\Roaming\McNeel\Rhinoceros\6.0\Plug-ins\IronPython (814d908a-e25c-493d-97e9-ee3861957f49)\settings\lib\rhinoscript\utility.py”
line 3709, in ReverseCurve, “C:\Users\josep\AppData\Roaming\McNeel\Rhinoceros\6.0\Plug-ins\IronPython (814d908a-e25c-493d-97e9-ee3861957f49)\settings\lib\rhinoscript\curve.py”
line 7, in script

Apparently ‘rs.ReverseCurve(crv)’ wants a Guid instead of the curve? Two issues with that:

  1. It’s ridiculous.

  2. Despite extensive searching, I haven’t found a way to obtain the Guid from the ‘crv’ param?

This really shouldn’t be so hard.

flip_crv_Python_2021_Jul24a.gh (7.6 KB)

GUIDs are what rhinoscriptsyntax is all about (as you may know). If you don’t want to deal with these references, simply exclusively use RhinoCommon, which by the way you are already doing in the first line of your if statement!

CW = crv
CCW = crv.Duplicate()
CCW.Reverse()

if crv.ClosedCurveOrientation(P) != 'Clockwise':
    CW = CCW
    CCW = crv

You can get the GUID like this, however the coercing functions are not really meant to be widely used, they are thus hidden.

crv_id = rs.coercecurve(crv)
print crv_id

It’s not, you just need to familiarize yourself with the intricacies of GHPython.

It’s not “the intricacies of GHPython” that stop me, I can always find answers to Python questions. It’s the Rhino APIs that trip me up time after time.

Thanks for your advice but as this comparison demonstrates, all efforts have failed so far.

The white group (“Show Curve Direction”) has three different methods to display curve direction and they always agree. The six Raw Crvs consist of four clockwise curves and two counterclockwise.


flip_crv_Python_2021_Jul24b.gh (20.0 KB)

The only thing that works as expected is the C# (green group) from @Baris which returns all curves with CCW direction.

The Python versions just flip all the curves. What’s wrong? (that’s putting it mildly…)

pyCW1 source:

if crv.ClosedCurveOrientation(P) == 'Clockwise':
    CW = crv
    CCW = crv.Duplicate()
    CCW.Reverse()
else:
    CCW = crv
    CW = crv.Duplicate()
    CW.Reverse()

pyCW2 source:

CW = crv
CCW = crv.Duplicate()
CCW.Reverse()

if crv.ClosedCurveOrientation(P) != 'Clockwise':
    CW = CCW
    CCW = crv

pyCW3 source:

import rhinoscriptsyntax as rs

crv_id = rs.coercecurve(crv)
if crv.ClosedCurveOrientation(P) == 'Clockwise':
    CW = crv
    CCW = rs.ReverseCurve(crv_id)
else:
    CW = rs.ReverseCurve(crv_id)
    CCW = crv

C# source:

if(crv.ClosedCurveOrientation(plane) == CurveOrientation.Clockwise) crv.Reverse();
A = crv;

Oh, I shouldn’t have assumed that your if statement is right! Check this! You should check against the enumeration members Undefined, Clockwise, or CounterClockwise, not a simple string!

Here’s my revised code:

import Rhino.Geometry as rg

CW = crv
CCW = crv.Duplicate()
CCW.Reverse()

if crv.ClosedCurveOrientation(P) != rg.CurveOrientation.Clockwise:
    CW = CCW
    CCW = crv

If you want to replicate what @Baris proposed, you can do this:

import Rhino.Geometry as rg

if crv.ClosedCurveOrientation(P) == rg.CurveOrientation.Clockwise:
    crv.Reverse()

a = crv

For this, set your crv input to Item Access, even for lists of curves.

flip_crv_Python_2021_Jul24c.gh (17.3 KB)

1 Like

Thanks. My ‘crv’ inputs were already set to Item Access, the Python default.

For anyone who follows, here is a simplified demo test and the Python I prefer:
flip_crv_Python_2021_Jul24d.gh (12.7 KB)

import Rhino.Geometry as rg

if crv.ClosedCurveOrientation(P) == rg.CurveOrientation.Clockwise:
    CW = crv
    CCW = crv.Duplicate()
    CCW.Reverse()
else:
    CCW = crv
    CW = crv.Duplicate()
    CW.Reverse()

NOTE: Does assigning an output twice trigger downstream components twice?

How many hours should it take to write a conditional flip curve in Python for someone with more than fifty years of programming experience? I guess I’m rusty at reading API docs, but also very dubious about poison in the Rhino Kool-Aid (coerce methods, gag!).

Granted, if you happen to start from here:

https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_Geometry_Curve_ClosedCurveOrientation_1.htm

you can find “Return Value Type: CurveOrientation” which lists the three possible values and specifies the ‘Rhino.Geometry’ Namespace, but there are no examples of usage in C#, VB or Python.

In any case, thanks again.

P.S. This doesn’t yet explain how to use ‘rs.ReverseCurve(crvId)’ when the ‘crv’ type hint is ‘Curve’?

Epilogue:

Finding a Python solution took so long that I almost forgot why I wanted it. But the really pathetic part is that a scripting solution wasn’t necessary at all. A ‘Curve | Primitive | Circle’ wired to the standard Flip Curve ‘G’ (Guide) input does the very same conditional flip crv job! :man_facepalming: :man_facepalming: :man_facepalming:


flip_crv_Python_2021_Jul24e.gh (15.0 KB)

What a day… :sob:

1 Like

You should keep in mind that there always is the possibility that crv.ClosedCurveOrientation() could return Undefined, which neither you nor I are currently taking into account. Currently, my script outputs the input status quo, and yours the reverse case, for Undefined. This isn’t necessarily a problem, but something that you should be aware of.

@Baris’ solution is probably the most elegant and efficient though. It’s brief, doesn’t create superfluous copies, but is currently also limited to output what gets input only.

Probably yes, but that shouldn’t be the case for your script, since the outputs are defined conditionally (either one or the other). And my script only assigns the same outputs twice in case of non-clockwise curves. It shouldn’t be a big deal, and if it is, you could create more variables and only output towards the end of the script, when everything has been computed.

Is it maybe the fifty years of programming experience that prevented you from taking a look at the documentation?
Where did you even get the idea for if crv.ClosedCurveOrientation(P) != 'Clockwise':?

This has been discussed ad absurdum, but let me try again!

If you’re using rhinoscriptsyntax, your effectively dealing with a sort of pseudo-functional programming philosophy that aims to make life easier for beginners by stripping away all the OOP-related stuff - that you have to deal with when working with the API (RhinoCommon) -, and handling it in the background for you.
The majority of the functions of rhinoscriptsyntax simply wrap API code. You can take a look at their definitions by for instance using the inspect module:

import rhinoscriptsyntax as rs
import inspect

print inspect.getsource(rs.AddPoint)

Now, since it’s managing the OOP-related stuff in the background, it needs some sort of system to keep track of the existing objects (e.g. geometries, etc.), and that’s where GUIDs come into play. They are unique identifiers, much like memory addresses in traditional programming, and let you reference an existing object. Simply put, it’s basically a system to keep track of existing stuff, without having to ceaselessly recreate already existing objects only to perform a new task on them.

rs.AddPoint() is a good example. If you utilize inspect to look at its source code, you’ll notice that you either can pass in an existing Rhino.Geometry.Point3d object or xyz-values.
In the first case, it looks for the reference of the existing point object to proceed and add it to the document. It already exists in memory and now just needs to get rendered to the screen.
In the latter case, it creates a new Rhino.Geometry.Point3d from the desired coordinates in memory, and fetches its GUID to proceed further.

Looking at the GHPython component, when you define specific Type Hints, it tries to interpret the concerned inputs as these API object types. A Grasshopper point for instance gets input as Rhino.Geometry.Point3d, when the Type Hint is set to “Point3d”. You’re hinting at its type!
If you want to work exclusively with rhinoscriptsyntax, simply leave the Type Hints as “ghdoc Object … (rhinoscriptsyntax)”, as this passes in the object references or GUIDs instead.

However, crv.ClosedCurveOrientation(P) won’t work any more, since you attempt to call the ClosedCurveOrientation() method of a Rhino.Geometry.Curve object, but your passing in the GUID of a curve object instead. There’s a type conflict!

Generally speaking, I think that seasoned users with programming experience, like yourself, should simply exclusively use the API. rhinoscriptsyntax was also initially meant for Python programming in Rhino, not Grasshopper.

Post mortem:

Yes, strictly speaking, the ‘Undefined’ result should be recognized and handled. By the time I was aware of three possible result values, I was too exasperated to care anymore.

Of greater concern and consequence is triggering downstream components more than once with multiple output assignments. A temp variable for ‘crv.Duplicate()(which returns a curve) is necessary because ‘crv.Reverse()’ acts on the ‘crv’ itself instead of returning a new curve like ‘crv.Duplicate()(and ‘rs.ReverseCurve(crv_id)’ ?, which never worked anyway).

For the casual dabbler in scripting, the differences between rhinoscriptsyntax and direct access to the APIs are not apparent. New Python components shove rhinoscriptsyntax at you by default:

import rhinoscriptsyntax as rs

Without knowing or caring about the long sordid history of how Rhino APIs evolved, it’s easy to get confused. As a lifelong programmer, standard GH components are a novel relief to me from the tedium and intricacies of conventional programming.

The most important take away from yesterday’s fiasco is that none of it was necessary since GH Flip Curve encapsulates the same functionality beautifully.

I agree.

Exactly.
And it was written to mimic the old VBscript RhinoScript scripts, that only could use Rhino objects in the document. Objects that you can see on the screen and run Rhino commands upon.
Hence the use of Guids, since any Rhino object in the documents has its own Guid.

When Python scripting for Rhino appeared, the rhinoscriptsyntax module was developed so that people could keep using the same operations.
And actually for simple tasks writing rhinoscriptsyntax scripts is way faster than using RhinoCommon.
You can also use both modules in a single script, choosing what your prefer for different parts of the script. Just remember that rhinoscriptsyntax deals with Guids and RhinoCommon deals with geometric objects (also).

All this in Rhino.

In Grasshopper rhinoscriptsyntax does not make a lot of sense IMHO, since you don’t have an Object Table with RhinoObjects and their Guids in GH.
And, AFAIK, GH makes up an auxiliary Rhino Document ( ghdoc ) only to let us use rhinoscriptsyntax functions.
… But you can choose to use the real Rhino document instead by setting the scriptcontext.doc variable to Rhino.RhinoDoc.ActiveDoc.
You also have to do that if you’re going to interact with objects in the document from a RhinoCommon script.

Exactly ! :slight_smile:

1 Like

Depends on the overall complexity of the Grasshopper definition in my opinion and whether it needs scalability, but again, yes, the downstream component re-computation could potentially be problematic.
As described above, this can be easily remedied by not using the component outputs as runtime level variables and only outputting towards the end of the script.

For example:

import Rhino.Geometry as rg

def flip(curve):
    """Returns a reversed copy of the input curve."""
    curve2 = curve.Duplicate()
    curve2.Reverse()
    return curve2

def get_orientated(curve, plane):
    """Returns the curve wound clockwise [0] in relation to 
       the plane, and the same curve but reversed [1]. 
       If the curve orientation can't be established,
       None [0] and None [1] get returned."""
    cw = curve
    ccw = flip(curve)
    
    rc = curve.ClosedCurveOrientation(plane)
    if rc != rg.CurveOrientation.Clockwise:
        temp_crv = ccw
        ccw = crv
        cw = temp_crv
        
        if rc == rg.CurveOrientation.Undefined:
            cw, ccw = None, None

    return cw, ccw 

if __name__ == "__main__":
    CW, CCW = get_orientated(crv, P)

You could even go further and control the re-computation of the GHPython component itself and thus that of the downstream components.

Yes, that’s really unfortunate.

Absolutely, that’s exactly what the myriad of discussions about this topic reflect. I guess scripting is not really a priority when it comes to general Rhino development, since most users only use Rhino as UI-based tool. Many don’t even touch Grasshopper.
I think that it is known that with C# and IronPython, Rhino was kind of manoeuvred into an impasse. CPython had faster development and has spawned a palette of strong and widely-used tools in the last decade, whereas the development of IronPython was sluggish at best.
I guess the hope at McNeel probably must have been for a higher adoption rate of C#, whereas it seems that more people are dabbling in Python now, at least for casual scripting. This also seems to be reflected by recent developments, like the release of Hops for remote CPython support. However, to me this seems like a band aid, a layer of complexity on top of everything. Now, you also need to learn/understand about Flask (Python web framework), running a web server, the differences between Iron- and CPython, rhino3DM, rhinoinside, etc.
It seems far too convoluted to me, especially if you look at the simplicity and elegance of scripting integrations in other CG programs, like for instance Blender and Houdini.

Interesting!

Wow, I didn’t know that. Does that mean that you take a performance hit when using rhinoscriptsyntax in Grasshopper? It seems so.

Looks like Steve is working on that, fortunately … :wink:

Not sure about that … :confused:
C# scripts are not even available for Rhino (but for GH only), which IMO is unfortunate when you have to (and can) use RhinoCommon, which is written in C#.
This also means that you cannot move C# scripts between Rhino and GH currently.

Anyway, let’s hope in CPython for Rhino 8, for both Rhino and GH … :slight_smile:

1 Like

Emilio’s statements are very accurate in this thread; thanks Emilio.

This is not true.

The intent for CPython support with Hops is not as a replacement for in-process scripting. There is a use case for out of process solving and remote solving made available from different computers just like there is a use case for in-process scripting support of CPython. The out of process support is available today while we are still working on the in-process support.

This is also being worked on as a core Rhino scripting option for V8 along side the CPython support.

6 Likes

Yes, I even wrote “remote CPython support” and at the moment it seems like a band aid, since there is no in-process alternative yet and thus the only way to use CPython libraries in Rhino 7. I was also primarily writing about the added complexity by using CPython through Hops, which is a fact! It’s great that local CPython support is on the horizon.

You’re welcome, Steve !
Gald to be helpful for fellow Rhino users. :slight_smile:

Good news !
Thanks RMA !