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.
ā 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:
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.
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
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.
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.
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.
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.
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