Get end points of line

I know I can use GetObjects to get all the curves in a selection but want to drill down further. I would like to determine if the curve is a line and get its end point coordinates.

Whatā€™s your lingo of choice?

Python

To check if a curve is a line:
https://developer.rhino3d.com/api/RhinoScriptSyntax/#curve-IsLine
To get the end point:
https://developer.rhino3d.com/api/RhinoScriptSyntax/#curve-CurveEndPoint
There is also a start point equivalent.

I spoke to soon. Iā€™m starting with a polycurve. Iā€™m trying to determine if it is rectangle and get its coordinates.

Is there a method that would return to me the following?

  • Is the polycurve a rectangle (4 line segments, 2 horizontal and 2 vertical )

  • The XYZ location of the end points of the four lines.

Eric

Hi @eric.bunn,

A rectangle is a closed, planar polyline with 5 points (the first and last points are identical), and the angle between segments is identical.

SampleCsClassifyCurve.cs

ā€“ Dale

HI @eric.bunn,

Continuing @daleā€™s ideas, hereā€™s a method for determining whether a curve is a rectangle, or not:

import Rhino.Geometry as rg
import math

def is_rectangle(crv):
    
    rc, plane = crv.TryGetPlane()
    if not rc:
        return (False, "nonplanar")
    
    if not crv.IsClosed:
        return (False, "not closed")
    
    try:
        pline = crv.ToPolyline()
    except:
        return (False, "not a polyline")
    
    if pline.Count != 5:
        return (False, "not quadrilateral")
    
    segments = pline.GetSegments()
    angles = []
    
    for i in xrange(pline.SegmentCount):
        v0 = segments[i].UnitTangent
        
        if i < pline.SegmentCount - 1:
            v1 = segments[i+1].UnitTangent
        else:
            v1 = segments[0].UnitTangent

        delta = rg.Vector3d.VectorAngle(v0, v1, plane)
        reflex = (2 * math.pi) - abs(delta)
        
        angles.append(min([delta, reflex]))
        
    if len(list(set(angles))) > 1:
        return (False, "not equiangular")
    
    return (True, "rectangular")


results = []
messages = []

for i in xrange(len(C)):
    test, msg = is_rectangle(C[i])
    print "Curve {0} is {1}.".format(i, msg)
    results.append(test)
    messages.append(msg)
    
# Outputs
R = results

If you start from a curve, you can simply try to convert it to a polyline (like in the script above). A polyline is basically a collection of ordered points, where the first and last point are equal. You can access the points, like you access list values in Python (e.g. pline[0]).

is_rectangle_v1.gh (9.6 KB)

I have also checked for rectangles in the following way -

  • Curve
  • Closed
  • Planar
  • Polyline (or possible conversion to)
  • 4 sides (5 points)
  • 2 equal length opposite sides (within tolerance)
  • 2 equal length diagonals (within tolerance)

(instead of checking corner angles)

Hereā€™s a slightly different approach, using some casting back and forth between types:

import Rhino as rc

def curveToRectangle(curve):
    
    """ Cast curve to rectangle if possible """
    
    # Cast curve to polyline and proceed if possibly
    test,crvPl = curve.TryGetPolyline()
    if test:
        
        # Cast curve polyline to rectangle and check it is valid
        rect = rc.Geometry.Rectangle3d.CreateFromPolyline(crvPl)
        if rect.IsValid:
            
            # Cast rectangle to polyline and check vertex count
            rectPl = rect.ToPolyline()
            if crvPl.Count == rectPl.Count:
                
                # Compute polylines vertex pair distances and set flag
                distanceOkay = True
                for crvPt,rectPt in zip(crvPl,rectPl):
                    d = crvPt.DistanceTo(rectPt)
                    if d > rc.RhinoMath.SqrtEpsilon:
                        distanceOkay = False
                        break
                        
                # Return rectangle
                if distanceOkay:
                    return rect

Rectangle = curveToRectangle(Curve)

Implemented in GHPython:

200226_CurveToRectangle_GHPython_02.gh (7.2 KB)

1 Like

This is a lot to digest but I will and thank you all for the great responses. Iā€™m sure I can get what need from the examples.

Eric

Iā€™m leaning towards converting polycurve to polyline and then capturing the segments using get segments. (Should only be 4) How would I just simply capture the start and end points of each segment.

Hi,

Iā€™m trying to run your code example in the Python Editor. I put a GetObjects (C = rs.GetObjects("", rs.filter.curve,False,True,False)) command in there to select a simple shape inside rhino, in this case it is a square. Iā€™m doing something fundamentally wrong because the code crashes when it tries to run the function ā€œis_rectangleā€. What am I doing wrong?

ERROR: Message: ā€˜Guidā€™ object has no attribute ā€˜TryGetPlaneā€™

Ultimately I want to select a group of objects and have it look at each item in the group and return the rectangles back to me.

Thanks for the help.

Eric

import Rhino.Geometry as rg
import math
import rhinoscriptsyntax as rs

def is_rectangle(crv):

rc, plane = crv.TryGetPlane()
if not rc:
    return (False, "nonplanar")

if not crv.IsClosed:
    return (False, "not closed")

try:
    pline = crv.ToPolyline()
except:
    return (False, "not a polyline")

if pline.Count != 5:
    return (False, "not quadrilateral")

segments = pline.GetSegments()
angles = []

for i in xrange(pline.SegmentCount):
    v0 = segments[i].UnitTangent
    
    if i < pline.SegmentCount - 1:
        v1 = segments[i+1].UnitTangent
    else:
        v1 = segments[0].UnitTangent

    delta = rg.Vector3d.VectorAngle(v0, v1, plane)
    reflex = (2 * math.pi) - abs(delta)
    
    angles.append(min([delta, reflex]))
    
if len(list(set(angles))) > 1:
    return (False, "not equiangular")

return (True, "rectangular")

C = rs.GetObjects("", rs.filter.curve,False,True,False)

results =
messages =

for i in xrange(lenĀ©):
test, msg = is_rectangle(C[i])
print ā€œCurve {0} is {1}.ā€.format(i, msg)
results.append(test)
messages.append(msg)

Outputs

R = results

Some more experimentation yielded the following using just RhinoScript calls. It gets me the points. Once I have those I can use them to analyze the shape.

I still would like to determine how to access and use the Rhino Common calls on curves. As I said in the previous reply, Get Object just gets me the ID but I canā€™t seem to use that with the code examples that were posted.

Hereā€™s my RhinoScript Code that will work for what I am trying to do:

import rhinoscriptsyntax as rs

obj = rs.GetObject(ā€œSelect a polycurveā€)

if rs.IsPolyline(obj):
print ā€œThe object is a polyline.ā€
points = rs.PolylineVertices(obj)
print(points)
else:
print ā€œThe object is not a polyline.ā€
if rs.IsCurve(obj):
polyline = rs.ConvertCurveToPolyline(obj)
if polyline:
rs.SelectObject(polyline)
points = rs.PolylineVertices(polyline)
print(points)
rs.DeleteObject(polyline)

OK, so I mostly use Grasshopper, also for Python development. I find it much more intuitive than developing ā€œblindlyā€ in Rhino. You can for instance visualise data easily.
You should have been more precise when asking your question, since there is RhinoPython and GHPython, but I take 50% of the blame for assuming that you wanted this as a Grasshopper component. :wink:

OK, so the error is caused by combining rhinoscriptsyntax with rhinocommon. rhinoscriptsyntax is basically a wrapper for the API (aka rhinocommon), to make scripting easier for beginners! It generally doesnā€™t deal with geometry objects, but with references to a geometry. These references are called GUIDs.
In rhinocommon, you deal directly with geometry objects and it is faster.

The error ā€˜Guidā€™ object has no attribute ā€˜TryGetPlaneā€™ is caused, because you are trying to call TryGetPlane() on a reference, which is impossible! TryGetPlane() is a method of a Rhino.Geometry.Curve object.

I hope you can see why mixing both is a bad idea.

There are two possibilities now. Continue with using rhinocommon, or translate my entire script to rhinoscriptsyntax. Which do you prefer?

Instead of obj = rs.GetObject(ā€œSelect a polycurveā€), you could try:

import Rhino

ACTIVE_DOC = Rhino.RhinoDoc.ActiveDoc # get the active Rhino document

curves = ACTIVE_DOC.Objects.FindByObjectType(Rhino.DocObjects.ObjectType.Curve)

ā€¦ to get all curves from your Rhino document.

And you can get rid of all of this, because in my script, you already have a polyline, called pline:

polyline = rs.ConvertCurveToPolyline(obj)
if polyline:
rs.SelectObject(polyline)
points = rs.PolylineVertices(polyline)

As already mentioned above, you can simply access its points like items in a list.

for pt in pline:
    print pt

p1r4t3b0y,

I apologize. Yes I should have been more clear. Iā€™ll certainly do that next time. I did find the way you described for RhinoScript and will use it for now. That being said I come from a Solidworks API background and I like to dig deeper that what I think I will be allowed to in standard RhinoScript. Thus the interest in Rhino Common API. I was Solidworks trained but unfortunately with Rhino I am self trained which leads to mistakes. Again I apologize.

Eric

1 Like

@diff-arch

Sorry, but I do have to disagree with some of what you said.

It is a bad idea if you mistake rhinoscriptsyntax obtained GUIDā€™s for RhinoCommon geometry objects. However is is not a ā€˜bad ideaā€™ to mix the two in a script (I do it all the time) if you know what is what.

This is also probably where this idea comes from, and I would have a tendency to agree, in a GH Python component, itā€™s maybe better to stick to RhinoCommon, as you are already in the world of RC being in Grasshopper. But for scripts running in the normal Rhino environment, itā€™s no problem at all to run either rhinoscriptsyntax, RhinoCommon or both together. You do need to know the difference between the two and how to go back and forth between the rhinoscriptsyntax GUID of an object found in the document to RC ā€˜virtualā€™ geometry and back out to the Rhino document as a GUID (via scriptcontext).

Personally, I develop only in the Python editor in Rhino so there is not the layer of GH in between me and what the script does. Plus, in general my scripts end up getting integrated into Rhino commands via aliases or toolbar buttons.

As as scripting beginner, I recommend trying to learn rhinoscriptsyntax in the Rhino (not GH ) environment. That is my own opinion of course, YMMV.

2 Likes

I didnā€™t intend to cause so much controversy. Sorry about that. I do want to say that this forum is excellent and the information I get from here is invaluable. I could have never gotten as far as I have in really only two months of using Rhinoscript without the help of the people that respond back to me on all of my questions. Thank you all again. Itā€™s all good.

Eric.

Donā€™t worry about that at all. This forum is for discussion and learning. Whatā€™s cool about it is that discussions here are generally respectful, helpful and constructive, even if people donā€™t always agree. Thatā€™s in sharp contrast to a lot of other places on the 'net.

2 Likes

Thereā€™s no shame in or downside to being self-taught! My guess is that most people around here, including myself, are at least 50% self-taught.

Whatā€™s the Solidworks API like? What did you use it for? Iā€™m just curious.

Donā€™t be sorry! No offence taken. :slight_smile:

Letā€™s agree to disagree here. I correct myself that it is indeed possible, however I think that combing the two only leads to inconveniences like the whole coercing business, which renders the coding experience even more complex for beginners.
Much experienced users, much like yourself, might know some of the tricks to navigate these muddy waters, but for beginners, like maybe @eric.bunn, itā€™s probably best and easier to stick to a more transparent strategy, possibly rhinoscriptsyntax only.

Itā€™s no problem in Grasshopper either, if you know what you do.

Sure, sounds great! Me describing how I favour using GHPython, is strictly my personal preference, since I - as also stated above - mostly use Grasshopper for development anyways.
Furthermore, I imagine many people welcome that Grasshopper mitigates some of the raw abstraction that the coding in the Rhino Python editor brings with it, especially if you are not a developer in the classical sense.

I agree. First learn rhinoscriptsyntax, and then move on to rhinocommon if you get annoyed with it.

No worries!! And stop apologising. :slight_smile:

So true, and youā€™re welcome!

FWIW, here is my rectangle selector only with rhinoscriptsyntax:

"""
Selects "rectangle" closed polylines (including squares).
Test for "rectangularity":
- planar, closed
- opposite sides equal length within model tolerance
- both diagonals equal length within model tolerance
"""

import rhinoscriptsyntax as rs

def RectangleTestRS(crvID,tol):
    if rs.IsCurvePlanar(crvID,tol) and rs.IsCurveClosed(crvID):
        #covers polylines and degree 1 curves
        if rs.CurveDegree(crvID)==1:
            verts=rs.CurvePoints(crvID)
            if len(verts)==5:
                s1=verts[0].DistanceTo(verts[1])
                s2=verts[1].DistanceTo(verts[2])
                s3=verts[2].DistanceTo(verts[3])
                s4=verts[3].DistanceTo(verts[0])
                d1=verts[0].DistanceTo(verts[2])
                d2=verts[1].DistanceTo(verts[3])
                #check opposite side lengths to see if equal within tolerance
                #plus the two diagonals also need to be the same length
                if abs(s1-s3)<tol and abs(s2-s4)<tol and abs(d1-d2)<tol:
                    #all conditions met if you get here
                    return True
    return False

entity="rectangle"
err_msg="No {} objects added to selection".format(entity+"s") 
objs=rs.ObjectsByType(4,state=1)
if objs:
    tol=rs.UnitAbsoluteTolerance()
    select=[obj for obj in objs if RectangleTestRS(obj,tol)]
    if select:
        rs.EnableRedraw(False)
        rs.SelectObjects(select)
        if len(select)>1: s="s"
        else: s=""
        print "{} {}{} added to selection".format(len(select),entity,s)
    else: print err_msg
else: print err_msg
1 Like