Hello @roman,
Thank you for reaching out to us. R21 has left the SDK support cycle, which primarily means we will not debug again such versions anymore and also do not fix bugs for it anymore. I have provided below a solution for your problem which has been tested with R25 because of that, but it should run fine in R21.
Your problem primarily is rooted in you simply reparenting the objects. Objects store their transformation - their position, scale and orientation - as a matrix relative to their parent. So, when you have the objects a, b, c with the local positions (0, 100, 0), (0, 50, 0), (0, 0, 0) and b being parented to a, then the effective global position of b is (0, 100, 0) + (0, 50, 0) = (0, 150, 0). When you then parent b to c, its position will change from (0, 150, 0) to (0, 50, 0) since c only contributes the null vector to the position of its children. The same principle applies to the scale and orientation stored in the local transform matrix of an object. You were also missing some undo-steps, at least I assumed you did not skip them intentionally.
The topic is also covered in the Python API Matrix Manual.
Cheers,
Ferdinand
The result:
reparent.gif
The script:
"""Moves the selected objects to a new layer and parent object.
Your problem primarily is rooted in you simply reparenting the objects.
Objects store their transformation - their position, scale and orientation -
as a matrix relative to their parent. So, when you have the objects `a, b, c`
with the local positions `(0, 100, 0), (0, 50, 0), (0, 0, 0)` and `b` being
parented to `a`, then the effective global position of `b` is `(0, 100, 0) +
(0, 50, 0) = (0, 150, 0)`. When you then parent `b` to `c`, its position will
change from `(0, 150, 0)` to `(0, 50, 0)` since `c` only contributes the null
vector to the position of its children. The same principle applies to the
scale and orientation stored in the local transform matrix of an object. You
were also missing some undo-steps, at least I assumed you did not skip them
intentionally.
"""
import c4d
def main():
"""Entry point.
"""
# Start an undo stack item.
doc.StartUndo()
# Add a new top-level layer
newLayer = c4d.documents.LayerObject()
layerRoot = doc.GetLayerObjectRoot()
newLayer.InsertUnder(layerRoot)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, newLayer)
# Let the user name the new layer
newName = c4d.gui.RenameDialog(newLayer.GetName())
newLayer [c4d.ID_BASELIST_NAME] = newName
# Create a null object to parent the objects moved to the new layer to.
newLayerNull = c4d.BaseObject(c4d.Onull)
newLayerNull [c4d.ID_LAYER_LINK] = newLayer
newLayerNull [c4d.ID_BASELIST_NAME] = newName
doc.InsertObject(newLayerNull)
doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, newLayerNull)
# Iterate over the selected objects and attach them both to the new layer
# and layer null-object.
for item in doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE):
# Set the layer of the current item, detach it from its previous
# parent and store its global matrix.
doc.AddUndo(c4d.UNDOTYPE_CHANGE, item)
item [c4d.ID_LAYER_LINK] = newLayer
itemMg = item.GetMg()
item.Remove()
# Attach the object to the null and set its global matrix to the old
# value.
item.SetMg(itemMg)
item.InsertUnder(newLayerNull)
# Get all materials attached with material tags to the item.
isTex = lambda item: item.IsInstanceOf(c4d.Ttexture)
for material in [t.GetMaterial() for t in item.GetTags() if isTex(t)]:
doc.AddUndo(c4d.UNDOTYPE_CHANGE, material)
material[c4d.ID_LAYER_LINK] = newLayer
# Close the undo item and push an update event to Cinema 4D.
doc.EndUndo()
c4d.EventAdd()
if __name__ == '__main__':
main()