How to order grip objects based on their counterclockwise connection

Hi @dale if I have hole in a subd:


The vertices have no clockwise or counter clockwise order. How can I know which is where?
Basically I am looking for a way to sort these grip points based on their counter clockwise connection of open edges. Is this possible?

Hi @Gijs,

With the current RhinoCommon implementation, there may not be an easy way. Do you have a sample model and source code I can review?

– Dale

Hi @dale currently I have only the following script where I would like to use this for, to make holes circular, but have also other ideas in mind where it could be used. It kind of works if the points are enough in the ballpark. The attached model right hole works, the left will fail.

sample_subd-holes.3dm (126.1 KB)


import Rhino
import rhinoscriptsyntax as rs
import scriptcontext as sc
"""
This script planarizes a group of points and organizes them equally spaced in a circle
script by Gijs de Zwart
www.studiogijs.nl
"""
def circularize():
    
    go = Rhino.Input.Custom.GetObject()
    go.SetCommandPrompt("Select controlpoints to make circular")
    go.GeometryFilter = Rhino.DocObjects.ObjectType.Grip
    go.GetMultiple(1, 0)
    object_list = Rhino.Collections.TransformObjectList()
    object_list.AddObjects(go, True)

    #convert grips to list of 3d points
    gripPts=[grip.OriginalLocation for grip in object_list.GripArray()] 
    #fit circle through these points
    result, circle = Rhino.Geometry.Circle.TryFitCircleToPoints(gripPts)
    if not result:
        return
    circle = sc.doc.Objects.AddCircle(circle)
    #get first grip and match circle seam to this first point
    pt_0 = object_list.GripArray()[0].OriginalLocation
    param = rs.CurveClosestPoint(circle, pt_0)
    rs.CurveSeam(circle,param)
    #divide circle 
    segs= len(go.Objects())
    ptlist = rs.DivideCurve(circle, segs)
    #return
    for i in range(len(ptlist)):
        #find index of closest grip point to circle point i 
        j=Rhino.Collections.Point3dList.ClosestIndexInList(gripPts, ptlist[i])
        #select corresponding grip
        grip = object_list.GripArray()[j]
        
        #calculate translation vector to circle point
        vector_from = grip.OriginalLocation
        vector_to = ptlist[i]
        dir = rs.VectorSubtract(vector_to, vector_from)
        #move grip
        xform = Rhino.Geometry.Transform.Translation(dir)
        grip.Move(xform)

    for owner in object_list.GripOwnerArray():
        sc.doc.Objects.GripUpdate(owner, True)
    
    sc.doc.Views.Redraw()
circularize()

Hi @Gijs,

Here is my attempt:

import Rhino
import scriptcontext as sc

def test_circularize():
    
    go = Rhino.Input.Custom.GetObject()
    go.SetCommandPrompt("Select control points to make circular")
    go.GeometryFilter = Rhino.DocObjects.ObjectType.Grip
    go.GetMultiple(1, 0)
    if go.CommandResult() != Rhino.Commands.Result.Success:
        return
        
    object_list = Rhino.Collections.TransformObjectList()
    object_list.AddObjects(go, True)

    locations = [grip.OriginalLocation for grip in object_list.GripArray()] 
    result, circle = Rhino.Geometry.Circle.TryFitCircleToPoints(locations)
    if not result:
        return
        
    curve = Rhino.Geometry.ArcCurve(circle)
    
    for grip in object_list.GripArray():
        rc, t = curve.ClosestPoint(grip.OriginalLocation)
        if rc:
            grip.CurrentLocation = curve.PointAt(t)
    
    for owner in object_list.GripOwnerArray():
        sc.doc.Objects.GripUpdate(owner, True)
    
    sc.doc.Views.Redraw()
    
test_circularize()

– Dale

Thanks, unfortunately that gives less circular results since the points are just pulled towards their closest point on the circle:
image

I have another idea, and that is after fitting the circle, to first smooth out the points, then transform them to the circle. Is there a RhinoCommon version of _Smooth on a selection of points to accomplish this?

Hi @Gijs,

To get as circular as possible, use a minimum of 8 points and make sure the point count is always evenly divisible by 4.

– Dale

I know how to make a circle, that’s not the issue, it’s mainly about getting equal spacing between the points.
Your script doesn’t adjust this point spacing.
But my question remains: is there a smooth command equivalent in Rhino Common for a selection of grip objects? In that case I can make my script work more reliably.

Hi @Gijs,

No, there isn’t and SubD smoothing method in RhinoCommon yet. Since you’ve mentioned the Smooth command, have you had any success scripting it?

– Dale

I mentioned it because when I smooth the selected points first and then run my script it always works successfully :slight_smile:

Hi Gijs,

Ran into a Smooth method for edges today and thought of your quest for smoothing here.

How about constructing a polyline form the grip locations and run smooth on the polyline.
Then transpose the polyline back to the vertices.
https://developer.rhino3d.com/api/RhinoCommon/html/M_Rhino_Geometry_Polyline_Smooth.htm

I see this will work best with a proper ordering of the input points, but that might be achieved crudely by sorting to closest points in a remaining set of points.
pseudo:

new_order_points = inputs[0]
remains = inputs[1:]
while remains:
    closest_index = find_closest_index_from(new_order_points[-1], remains)
    new_order_points.append(remains[closest_index])
    remains.pop(closest_index)

-Willem

I doubt it will work reliably, in fact the way I am doing it now, searches for closest point already. When points are more or less evenly spaced, closest point works, but you can easily run into a situation where the first closest point is a clockwise and a second is a counterclockwise point, for example if the first point is in the middle of two points that are closer to the first point than all other points. If you then run a polyline through it, it will be a self intersecting polyline, and when smoothing that out, points will in the end ‘swap location’.

Hi Gijs,

Maybe I’m misunderstanding your reply, but
the closest point I’m referring to is to sort the input points where you start with an arbitrary first point and than look for it’s closest neighbor. Next you search the closest to that neighbor excluding the already sorted points.

I see how this can indeed not be reliable in all cases especially with regular grid spaced input.

-Willem

Yes that’s also how I do the sorting of the points. I run a circle through the points, then choose the first point of the list of points I want to transform and find closest point on the circle, then divide this circle, and for the next point on the divided circle, take the closest point of the subd, and remove that from the list. But here is an image of what I described where it can go wrong. The way I understand your described method is that this won’t make a difference with the way I am doing it now, am I right?

I see, so you would need an additional filtering based on the tangent angle where this with tangent angles close to 180deg are discarded…

That of course not be fail-proof either…

The reason I remembered your quest was that I did some digging to see if somehow the connected mesh edges for the selected grips could easily be retrieved. But I ran into dead ends in RhinoCommon.

Yeah, seems we need to be a bit more patient when it comes to subd Rhino Common access. I hope that for edge-loops (holes) there will eventually be a method to get something like EdgeLeftNeighbor, EdgeRightNeighbor, and similar for grips

2 Likes

I found this SubD or Mesh points to circle very usefull. But no tool command do this.

@Alen_Russ I think you are looking for a way to run this from a button?
See this guide: https://stevebaer.wordpress.com/2011/04/11/running-scripts-from-toolbar-buttons/

I know. I mean points to circle with python code.

Can you give an example how you would use this on points?

btw: it works on any set of grip points, also on those of a curve:

I cant do this. My idea is to select the points to approximate the circle, the circle bisects equal points, and then the points moves to the bisection points.