SetPtMulti (Multi version of SetPt)

OK, so I made my first PythonScript attempting to extend Rhino’s std SetPt command.

The idea is to be able to set multiple points at once using a another object (Curve or Surface) as a “template” for where to move the Control Point positions. Each Point to be moved/pulled needs a matching “template” point to guide it’s target position.

The command allows for window selecting of Control Points.

In its first version it is recommended to select entire rows of Control Points, not only individual Points (use SetPt for that). The current version of the script tends to swap directions if not entire point rows are selected. The number of template target points must not be less than the pull points.

SetPtMulti can set 1, 2 or 3 directions (just like the original SetPt command) which then are applied for each Pull-Target pair.

SetPtMulti has been briefly tested to support matches of the following object types :

  • Curve -> Curve control points
  • Curve -> Surface control points
  • Surface to Surface control points
  • Surface to Curve control points

Video Clip
Below a 1.2 min video clip demonstrating how the command is used in its first version. If you have any suggestions, or want to contribute to extend and enhance the script, just let me know.

Video clip :

The Script (R0.9). Let the code review begin! ( :sweat_smile: ):

import rhinoscriptsyntax as rs

# ------------------------------------------------------------------
# Rolf Lampa, 2016 Rev 0.9
# SetPtMulti is intended as an extended version of Rhino's std SetPt 
# command.  It sets multiple points to corresponding target points in 
# pairs.
# Command line options to set: X, Y, Z, All, or Cancel
# -------------------------------------------------------------------

def SetPtMulti():
    
    def ExitProc(SelObj1=None, SelObj2=None):
        if SelObj1:
            rs.EnableObjectGrips(SelObj1, False)   # Hide Control points
        if SelObj2:
            rs.EnableObjectGrips(SelObj2, False)   # Hide Control points
        #rs.GetObjects()     # clear selection
    
    # ------------------------------------------------------------------
    # MAIN
    # ------------------------------------------------------------------
    # CONST
    X = 0
    Y = 1
    Z = 2
    ALL = 3
    CANCEL = 4
    
    # Select Object to Modify
    moveObj = rs.GetObject("Select object to Move", filter=4+8, select=True) # Curve, Surface
    if not moveObj: 
        return
    rs.EnableObjectGrips(moveObj)   # Show Control points

    moveObjIsSurface = rs.IsSurface(moveObj)

    # TARGET
    targetObj = rs.GetObject("Select Target object", filter=4+8, select=True) # Curve, Surface
    if not targetObj: 
         ExitProc(moveObj, None)
         return
    
    targetObjIsSurface = rs.IsSurface(targetObj)

    # Get POINTS to Move
    msg1 = "Select Points to Pull :"
    unsorted = rs.GetObjectGrips(message=msg1, select=True)
    if not unsorted:
        ExitProc(moveObj, None)
        return
    # SORT
    unsorted.sort(key = lambda g : g[1])
    movePoints = unsorted
    
    # Target POINTS
    msg1="Select target Points :"
    rs.EnableObjectGrips(targetObj)   # Show Control points
    unsorted = rs.GetObjectGrips(message=msg1, select=True)
    if not unsorted: 
        ExitProc(moveObj, None)
        return
    # SORT
    unsorted.sort(key = lambda g : g[1])
    trgPoints = unsorted
    if not trgPoints: 
        return
    
    # Points to move (grips) must be less or equal to target points.
    if len(movePoints) > len(trgPoints):
        rs.MessageBox("Number of points to move must be less or equal to target points!", buttons=16, title="Warning")
        return

    # DIRECTIONS select :
    uvn_params = ("X", "No", "Yes"), ("Y", "No", "Yes"), ("Z", "No", "Yes"), ("All", "No", "Yes"), ("Cancel", "No", "Yes")
    uvn_directions = rs.GetBoolean("Directions to modify:", uvn_params, (False, False, False, False, False) )
    if uvn_directions[CANCEL]:
        ExitProc(moveObj, targetObj)
        return
    elif (not (uvn_directions[X] or uvn_directions[Y] or uvn_directions[Z])
        and (not uvn_directions[ALL])):
        rs.MessageBox("No directions was selected! Try again.", buttons=64, title="Ops!")
        ExitProc(moveObj, targetObj)
        return
    
    # Set directions, individually (x,y,z) or "all" :
    is_valid_options = False
    if uvn_directions[ALL]:
        uvn_directions[X] = True
        uvn_directions[Y] = True
        uvn_directions[Z] = True
        is_valid_options = True
    else:
        for val in uvn_directions:
            if not is_valid_options and val:
                is_valid_options = True
                break
                
    if not is_valid_options: 
        rs.MessageBox("No directions was selected! Try again.", buttons=64, title="Ops!")
        return
        
    # Move any pairs of points between any object type 
    # (surface to curve, curve to curve, 
    #  curve to surface or surface to surface
    
    #rs.EnableRedraw(False)             # Lock screen
    i = 0
    for grip in movePoints:
        # Get points
        obj, ix, movePt = grip      
        obj, trgIx, trgPt = trgPoints[i] # trgPoints[i][2]
       
        # Copy target's pts to the object to be moved
        if uvn_directions[X]:
            movePt[X] = trgPt[X]
        if uvn_directions[Y]:
            movePt[Y] = trgPt[Y]
        if uvn_directions[Z]:
            movePt[Z] = trgPt[Z]
    
        if moveObjIsSurface:
            rs.ObjectGripLocation(moveObj, ix, movePt)
        else:
            rs.ObjectGripLocation(moveObj, ix, movePt)
        # next movePt
        i += 1
        
    ExitProc(moveObj, targetObj)
    rs.EnableRedraw(True)

SetPtMulti()

// Rolf

1 Like

Cool !

Thanks for sharing, Rolf !

I rarely saw a script so clearly organized …

First test : works fine :grinning:

Just one thing … you might have deleted the line that enables target object’s grips.

Then, as you say, there’s the problem of possibly reversed point lists.
What about using rs.CurveDirectionsMatch() , for example on two lines from first and second point of each point list ? … Just an idea …

Cheers

Hi @emilio, good to hear that you liked it.

Yes, I’m about to enhance the code with checking the directions. But until then, here’s an updated version (0.9.1) that does what it does much better than 0.9.0, and there’s the option to reverse directions if your first attempt reveals that it’s swapped.

In future versions the script should suggest (with a dialog) to reverse, and so avoiding a first failed attempt.

Feel free to suggest enhancements, preferably with working code… ( :slight_smile: ) because I found the script quite useful.

Rev 0.9.1:
Command line options: X, Y, Z, All, FlipUV, HideCps, Cancel
#
# Rev 0.9.0 2016-10-10 First release
# Rev 0.9.1 2016-10-14
# * + Added option FlipCrvDir (for curves only) (Restores any
# flipped curves directions on exit, though).
# * ~ Minor refactoring (removing comments, added extract methods)
# * ~ Presets modified (default hiding CPs on exit)
# * + Added Confirm for object selection, enabeling multiple attempts.

SetPtMulti.py (6.2 KB)

// Rolf

Hi Rolf

OK, here is my suggestion. :slight_smile:
I thought to allow target points reversal inside the script, without having to rerun it … ( sort of a preview check … )

import rhinoscriptsyntax as rs

# ------------------------------------------------------------------
# Author: Rolf Lampa 2016
# SetPtMulti is intended as an extended version of Rhino's std SetPt 
# command.  It sets multiple points to corresponding target points in 
# pairs.
# Command line options: X, Y, Z, All, FlipUV, HideCps, Cancel
#
# Rev 0.9.0     2016-10-10  First release
# Rev 0.9.1     2016-10-14
# * +   Added option FlipCrvDir (for curves only) (Restores any 
#       flipped curves directions on exit, though).
# * ~   Minor refactoring (removing comments, added extract methods)
# * ~   Presets modified (default hiding CPs on exit)
# * +   Added Confirm for object selection, enabeling multiple attempts.
# -------------------------------------------------------------------

def SetPtMulti():
    
    #C:\Users\RIL\AppData\Roaming\McNeel\Rhinoceros\5.0\Plug-ins\IronPython (814d908a-e25c-493d-97e9-ee3861957f49)\settings\lib\rhinoscript
    
    def LockScreen():
        rs.EnableRedraw(False)

    def UnlockScreen():
        rs.EnableRedraw(True)
        
    def ExitProc(SelObj1=None, SelObj2=None):
        # if not yet initialized (= command was cancelled)
        if (not xyz_directions) or (xyz_directions and xyz_directions[HIDECP]):
            if SelObj1:
                rs.EnableObjectGrips(SelObj1, False)   # Hide Control points
            if SelObj2:
                rs.EnableObjectGrips(SelObj2, False)   # Hide Control points

    def ShowGrips(obj):
        rs.EnableObjectGrips(obj)   # Hide Control points
        
    def HideGrips(obj):
        rs.EnableObjectGrips(obj, False)   # Hide Control points
        
    def RestoreFlippedCurves():
        if len(FlippedCrvList) > 0:
            for crv in FlippedCrvList:
                rs.ReverseCurve(crv)
            
    # ------------------------------------------------------------------
    # MAIN
    # ------------------------------------------------------------------
    # CONST
    X = 0
    Y = 1
    Z = 2
    ALL = 3
    FLIP_UV = 4
    HIDECP = 5
    CANCEL = 6
    
    pullDir = 0
    trgDir = 0
    
    xyz_directions = []
    
    # Select Object to PULL
    notOK = True
    while notOK:
        pullObj = rs.GetObject("Select object to Pull", filter=4+8, select=True) # Curve, Surface
        if not pullObj: 
            return
        notOK = rs.GetBoolean("Press ENTER to confirm", ("Retry", "No", "Yes"), False)[0]
    ShowGrips(pullObj)   # Show Control points

    # TARGET
    notOK = True
    while notOK:
        targObj = rs.GetObject("Select Target object", filter=4+8, select=True) # Curve, Surface
        if not targObj: 
            ExitProc(pullObj, None)
            return
        notOK = rs.GetBoolean("Press ENTER to confirm", ("Retry", "No", "Yes"), False)[0]
    HideGrips(targObj)   # Hide Control points
    
    # DIRECTIONS to modify -- Enable swapping points order ("fake direction")
    xyz_params = ("X", "No", "Yes"), ("Y", "No", "Yes"), ("Z", "No", "Yes"), ("All", "No", "Yes"), ("FlipCrvDir", "No", "Yes"), ("HideCPOnExit", "No", "Yes"), ("Cancel", "No", "Yes")
    xyz_presets = (False, False, False, True, False, True, False)

    # Get user response
    xyz_directions = rs.GetBoolean("Directions to modify:", xyz_params, xyz_presets )
    if xyz_directions[CANCEL]:
        ExitProc(pullObj, targObj)
        return
    if not (xyz_directions[X] or xyz_directions[Y] or xyz_directions[Z]) and (not xyz_directions[ALL]):
        rs.MessageBox("No directions was selected! Try again.", buttons=64, title="Ops!")
        ExitProc(pullObj, targObj)
        return

    # Flip UV
    FlippedCrvList = []
    if xyz_directions[FLIP_UV]:
        if rs.IsCurve(pullObj):
            rs.ReverseCurve(pullObj)
            FlippedCrvList.append(pullObj)
        elif rs.IsCurve(targObj):
            rs.ReverseCurve(targObj)
            FlippedCrvList.append(targObj)
        
    # Select Pull points
    msg1 = "Select Points to Pull :"
    pullPoints = rs.GetObjectGrips(message=msg1, select=True)
    if not pullPoints:
        ExitProc(pullObj, None)
        return
    HideGrips(pullObj)
    
    # Select Target Points
    msg1="Select target Points :"
    ShowGrips(targObj)
    unsorted = rs.GetObjectGrips(message=msg1, select=True)
    if not unsorted: 
        ExitProc(pullObj, targObj)
        return
    trgPoints = unsorted
    if not trgPoints: 
        return
    
    # Points to move (grips) must be less or equal to target points.
    if len(pullPoints) > len(trgPoints):
        rs.MessageBox("Number of points to move must be less or equal to target points!", buttons=16, title="Warning")
        return

    if xyz_directions[HIDECP]:
        UnlockScreen()
        HideGrips(pullObj)
        HideGrips(targObj)
    
    # Set directions, individually (x,y,z) or "all" :
    is_valid_options = False
    if xyz_directions[ALL]:
        xyz_directions[X] = True
        xyz_directions[Y] = True
        xyz_directions[Z] = True
        is_valid_options = True
    else:
        for val in xyz_directions:
            if not is_valid_options and val:
                is_valid_options = True
                break
                
    if not is_valid_options: 
        rs.MessageBox("No directions was selected! Try again.", buttons=64, title="Ops!")
        return

#### EDIT
    pullPointsCopy = pullPoints[ : ]
    while True:
#### /EDIT
        
#### INDENTED
      # Move any pairs of points between any object types
      LockScreen()             # Lock screen
      # Reenable as to be able to modify them
      rs.EnableObjectGrips(targObj)  
      rs.EnableObjectGrips(pullObj)
      i = 0
      for grip in pullPoints:
          # Get points
          obj, pullIx, pullPt = grip      
          obj, trgIx, trgPt = trgPoints[i] # trgPoints[i][2]
         
          # Copy target's pts to the object to be moved
          if xyz_directions[X]:
              pullPt[X] = trgPt[X]
          if xyz_directions[Y]:
              pullPt[Y] = trgPt[Y]
          if xyz_directions[Z]:
              pullPt[Z] = trgPt[Z]
      
          rs.ObjectGripLocation(pullObj, pullIx, pullPt)
          i += 1  # Next pullPt
#### /INDENTED
        
#### EDIT
      rs.EnableObjectGrips(targObj, False)  
      rs.EnableObjectGrips(pullObj, False)
      UnlockScreen()
      Reverse = rs.GetBoolean( 'Target points direction ?',  ( 'Reverse', 'No', 'Yes' ), ( False ) )[ 0 ]
      if Reverse:
        trgPoints.reverse()
        pullPoints = pullPointsCopy[ : ]
        LockScreen()
      else:
        break
#### /EDIT

    RestoreFlippedCurves()

    UnlockScreen()
    if xyz_directions[HIDECP]:
        HideGrips(pullObj)
        HideGrips(targObj)

SetPtMulti()

Regards

P.S.

… and Discourse formatting keeps doing weird things … Grrrrr …

Absolutely perfect! This made the script much better.

I reformatted the script, renamed all “flips” to “reversed” and added revision comments. Thank you very much!

Rev 9.2.0
...
# Rev 0.9.2	 2016-10-17
# * +   emilio: Added reversed direction detection with confirm.
# * +   rolf  : Renamed all occurences of "Flip" to "Reversed".

SetPtMulti.py (6.2 KB)

// Rolf

1 Like

Thanks Rolf !

Nice way to share scripts and make contributions from other scripters easy. :slight_smile:

Redards

1 Like

I find this very interesting. But then I don’t suffer from “not invented here” syndrome, which is a very common disease in the IT industry. :slight_smile:

I will learn Rhino scripting much faster this way. Thank you for your contribution.

// Rolf