Shift closed polyline start point method?

If I have a Rhino.Geometry.Polyline object which is closed and I want to shift the start point a certain number of indices along the curve, is there a simple method?

Not finding anything like a “Change seam” or “Shift list” method in the Polyline class, I improvised this - which is more than a bit of a kludge:

#orig_polyline is already a polyline and I want to shift the start point, say 2

#new blank polyline
plx=Rhino.Geometry.Polyline()
new_start index=2
#add from index to end of list-1 (but not end point!)
for i in range(new_start index,orig_polyline.Count-1):
                plx.Add(orig_polyline[i])
#add the beginning of the original polyline list to the end of the new list
for j in range(curr_index):
    plx.Add(orig_polyline[j])
#add the first point as the last point so it is closed
plx.Add(plx[0])

But that seems pretty crazy and messy. There must be something better that I missed.

I’ve needed this as well. And although still kludgey, I like to use collections.deque for shifting lists. Here’s a function that offsets the polyline vertex order to start a given input point:

import Rhino as rc
import collections

def setPolylineStart(polyline,point):
    
    """ Offset polyline vertex order to start at point """
    
    vts = polyline.ToArray()
    vts = collections.deque(vts)
    vts.rotate(len(vts)-polyline.ClosestIndex(point))
    pl = rc.Geometry.Polyline(vts)
    
    return pl

Interesting, but this doesn’t appear to work with closed polylines, as the start and end points are repeated. By ‘rotating’ the list you end up with a duplicate point somewhere in the middle (the old start/end) and the thus created polyline is invalid and cannot be added to the document. That’s why I excluded the last (end) point in my scriptlet above and added a new one - corresponding to the new start point - at the end.

Ah yes sorry, I missed the closed requirement (at home with covid :face_with_spiral_eyes:). Adding to the kludge, one could check and remove/add the last vertex before/after the rotation. Something like this should work I think:

import Rhino as rc
import collections

def setPolylineStart(polyline,point):
    
    """ Offset polyline vertex order to start at point """
    
    vts = collections.deque(polyline)
    if polyline.IsClosed:
        del vts[-1]
    vts.rotate(len(vts)-polyline.ClosestIndex(point))
    if polyline.IsClosed:
        vts.append(vts[0])
    pl = rc.Geometry.Polyline(vts)
    
    return pl

OK, yeah, that’s cool, thanks!

Also interesting - if I give it a number, deque.rotate seems to rotate “backwards” from the end of the list… So if I want it to shift x indices “forward” from the polyline start, I need to feed it -x

However the advantage of using deque.rotate is that you can ‘rotate’ more than the length of the list, whereas my list slicing method will error out if I feed it a number to shift greater than the list length.

Ouch.

1 Like

Hi @Helvetosaur,

I use rs.CurveSeam() for this exact purpose. Digging its function definition, it seems to call the Curve.ChangeClosedCurveSeam method

Best regards,
Tim

def CurveSeam(curve_id, parameter):
    """Adjusts the seam, or start/end, point of a closed curve.
    Parameters:
      curve_id (guid): identifier of the curve object
      parameter (number): The parameter of the new start/end point.
                  Note, if successful, the resulting curve's
                  domain will start at `parameter`.
    Returns:
      bool: True or False indicating success or failure.
    Example:
      import rhinoscriptsyntax as rs
      obj = rs.GetObject("Select closed curve", rs.filter.curve)
      if rs.IsCurveClosed(obj):
          domain = rs.CurveDomain(obj)
          parameter = (domain[0] + domain[1])/2.0
          rs.CurveSeam( obj, parameter )
    See Also:
      IsCurve
      IsCurveClosed
    """
    curve = rhutil.coercecurve(curve_id, -1, True)
    if (not curve.IsClosed or not curve.Domain.IncludesParameter(parameter)):
        return False
    dupe = curve.Duplicate()
    if dupe:
        dupe.ChangeClosedCurveSeam(parameter)
        curve_id = rhutil.coerceguid(curve_id)
        dupe_obj = scriptcontext.doc.Objects.Replace(curve_id, dupe)
        return dupe_obj is not None
    return False

Yes, thanks, I looked at that, but the idea was to stay in polyline form (which is just a list of points basically) rather than converting to a polyline curve, figuring out which parameter is the next n points along the curve, changing the curve seam then converting the result back to a polyline.