I’m doing a lot of copy/paste history and getting annoyed that something in the history chain wasn’t selectable. I’m rusty with python, but ChatGPT helped me out a bit. It got a few things wrong, like not knowing about scriptcontext.sticky[ ... ] (used doc user text), but all in all, it was actually helpful.
There are four scripts:
AllSelectable.py - turn on/unlock all layers, then _Show/_Unlock. It saves the existing state.
SelAllParents.py - Pick what you want to copy, and it will find all its parents and isolate them. You can manually hide things you don’t want to be included in the next step.
CopyWithHistory.py - Copies it to the clipboard, hides the original, pastes, and selects objs without history. This lets you move the pasted stuff to a new location.
You also have the option to change layer names. This lets you copy something from layer X and paste to layer Y.
UndoAllSelectable.py - Restores the original selectability of things before you ran AllSelectable.py
First, there is a shared ‘eLib.py’ to share between all the scripts:
Surprisingly, Rhino doesn’t have a command to make everything in a file selectable (turn on/unlock all layers, then _Show/_Unlock all objects), but since I had to make it, I got to add a way to preserve the current state so you can undo it later.
Thanks to @stevebaer for _TestDumpSvgs in the latest WIP. I am upgrading by icon game.
v8 SVG:
v7 PNG:
import rhinoscriptsyntax as rs
import scriptcontext as sc
import eLib as e
def AllSelectable():
# always skip turning on or off specific layers from a template file
layersToSkip = [
'1ced3d60-c6a7-48b5-81f1-4daef190945f', # Layout::MM::Grandpappy
'67bfd040-c5c5-4493-9b0e-8061b7d8c488' # 0
]
# get a list of all layers in the document
layerIDs = rs.LayerIds()
# create an empty list to store the layer information
layerStates = []
# loop through each layer and get its visibility and lock state
for layerID in layerIDs:
if str(layerID) not in layersToSkip:
# get the layer's visibility and lock state
wasVisible = rs.LayerVisible(layerID, True, False)
wasLocked = rs.LayerLocked(layerID, False)
state = e.LayerState(layerID, wasVisible, wasLocked)
# add the dictionary to the list
layerStates.append(state)
else:
print("Skipping " + rs.LayerName(layerID))
objects = rs.AllObjects()
# create an empty list to store the hidden objects
hiddenObjs = []
lockedObjs = []
# loop through each object and get its visibility
for obj in objects:
# get the object's current visibility
if rs.IsObjectHidden(obj):
hiddenObjs.append(obj)
if rs.IsObjectLocked(obj):
lockedObjs.append(obj)
rs.ShowObjects(hiddenObjs)
rs.UnlockObjects(lockedObjs)
print("Showed " + str(len(hiddenObjs)) + " objects.")
print("Unlocked " + str(len(lockedObjs)) + " objects.")
# save the hidden objects list to the Rhino script context's sticky dictionary
sc.sticky[e.hiddenObjsKey] = hiddenObjs
sc.sticky[e.lockedObjsKey] = lockedObjs
sc.sticky[e.layerStatesKey] = layerStates
if (__name__ == "__main__"):
AllSelectable()
# print(str(layerID) + ", " + rs.LayerName(layerID))
""" if not wasVisible:
print(rs.LayerName(layerID))
print(layerID) """
Once you pick the child you want to copy, it recursively grabs parents until it has them all. Then it will isolate them, allowing you to pick and choose how much of the history tree to copy.
v8 SVG:
v7 PNG:
import rhinoscriptsyntax as rs
import scriptcontext as sc
import eLib as e
def SelAllParents():
objs = rs.GetObjects("Select objects to find all their parents", preselect=True)
if objs:
count = len(objs)
lastcount = 0
while lastcount < count:
lastcount = count
rs.Command("_SelParents", False)
count = len(rs.SelectedObjects(False, False))
count = len(rs.SelectedObjects(False, False))
if 0 < count:
rs.Command("Isolate", True)
if (__name__ == "__main__"):
SelAllParents()
Sometimes I want to copy/paste history to the same or a different layer. This gives you the choice. The easiest way I could think of is to change the current layer name before it copies, change it back, then paste. If you have child layers involved, change only the parent layer first. You will get two layers with an identical sub-tree structure.
I put this on the right-click for the SelAllParents.py button (that’s what the plus sign is for).
import rhinoscriptsyntax as rs
import scriptcontext as sc
import eLib as e
def renameLayers():
layersToRename = []
prompt = "Should pasted objects be on a different layer?"
while True:
answer = rs.GetString(prompt, "No", ["Yes"])
if answer == "Yes":
layerID = rs.GetLayer("Select layer to rename:")
if not layerID:
break
oldName = rs.LayerName(layerID)
newName = rs.GetString("New Layer name for pasted objects", e.removeLayerParents(oldName) + "-Copy")
newName = str.replace(oldName, e.removeLayerParents(oldName), newName)
if not newName:
break
layersToRename.append(e.LayerName(layerID, oldName, newName))
prompt = "Would you like to change any other layers?"
else:
break
return layersToRename
def CopyWithHistory():
objs = rs.GetObjects("Select objects to copy with history", preselect=True)
if objs:
layersToRename = renameLayers()
if 0 < len(layersToRename):
for layer in layersToRename:
e.renameLayer(layer.oldName, layer.newName)
rs.Command("_CopyToClipboard", False)
rs.HideObjects(objs)
if 0 < len(layersToRename):
for layer in layersToRename:
e.renameLayer(layer.newName, layer.oldName)
rs.Command("_Paste", False)
pastedCount = len(rs.SelectedObjects())
rs.UnselectAllObjects()
rs.Command("_SelObjectsWithHistory", False)
parentCount = len(rs.SelectedObjects())
rs.InvertSelectedObjects()
strResponse = "Copied {0:d} with history. {1:d} root parents without history are selected and can be moved to a new location.".format(pastedCount, parentCount)
print(strResponse)
if (__name__ == "__main__"):
CopyWithHistory()
This reverts the selectability of everything to before you ran AllSelectable.py
I put this on the right-click for the AllSelectable.py button (that’s what the red arrow is for).
import rhinoscriptsyntax as rs
import scriptcontext as sc
import eLib as e
def UndoAllSelectable():
if sc.sticky.has_key(e.layerStatesKey):
layerStates = sc.sticky[e.layerStatesKey]
if isinstance(layerStates, list):
for layer in layerStates:
if isinstance(layer, e.LayerState) and rs.IsLayer(layer.layerId):
rs.LayerVisible(layer.layerId, layer.visible)
rs.LayerLocked(layer.layerId, layer.locked)
del sc.sticky[e.layerStatesKey]
if sc.sticky.has_key(e.hiddenObjsKey):
hiddenObjs = sc.sticky[e.hiddenObjsKey]
if isinstance(hiddenObjs, list):
rs.HideObjects(hiddenObjs)
print("Hid " + str(len(hiddenObjs)) + " objects.")
del sc.sticky[e.hiddenObjsKey]
if sc.sticky.has_key(e.lockedObjsKey):
lockedObjs = sc.sticky[e.lockedObjsKey]
if isinstance(lockedObjs, list):
rs.LockObjects(lockedObjs)
print("Locked " + str(len(lockedObjs)) + " objects.")
del sc.sticky[e.lockedObjsKey]
if (__name__ == "__main__"):
UndoAllSelectable()