SOLVED Move just the axis of a polygonal object

I have a list with all the coordinates of the vertexes of a polygonal object, all in global coordinates, that I got with:

op_mg = child.GetMg()
points = op.GetAllPoints()
                
for p,pt in enumerate(points) :
    points[p]=pt*op_mg

After setting the global matrix of my polygonal object to the matrix that I want its axis to be, now I want to relocate all the points the location where they were previously (globally).
But I want it to be able to happen, no matter how deep inside a hierarchy my polygonal object is.
Meaning... I want to be able to relocate the axis of my polygonal object to a specific global location, even if my object is inside a hierarchy, but I want to keep all the vertexes in their initial global location.
How can I do this?!?
I have been working with matrixes but no matter what I do, the points of the object always end up moving.

It's actually the axis and the points that have to move.

Hello @rui_mac

you don't really need to deal with hierarchy as long as you are using global space.
you have to come back to local space with the new matrix, by multiplying the points position with the inverse matrix.
Be sure to also move the axis of the object as @mp5gosu stated and to ask the object to update itself.

        # Retrieves selected object
        op = doc.GetActiveObject()
        if op is None:
            raise TypeError("There's no object selected.")
    
        # Retrieves the matrix of the object
        opmg = op.GetMg()
    
        # Retrieves the point in global space
        pointsGlob = [p * opmg for p in op.GetAllPoints()]
    
        # Starts the undo process (the initial scene to be restored after an undo action)
        doc.StartUndo()
    
        # Notifies a change on the obj
        doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
    
        # Updates the matrix
        opmg.off = c4d.Vector(0)
        # Updates the axis of the object
        op.SetMg(opmg)
    
        # Notifies the object, that internal data changed and it needs to be updated
        op.Message(c4d.MSG_UPDATE)
    
        # Inverses the matrix to come back to local space
        invopmg = ~opmg
    
        # Retrieves points from global to local space
        newPoints = [p * invopmg for p in pointsGlob]
    
        # Updates the points
        doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
        op.SetAllPoints(newPoints)
    
        # Ends the undo process (the final scene state)
        doc.EndUndo()
    
        # Updates Cinema 4D
        c4d.EventAdd()
    

Cheers
Manuel

That is what I was trying to do, but it is still not working.

Lets assume I have a list to objects that are the objects that are all children of a Null. The objects can be children or childs of childs, or even deeper, but they all are inside a Null.
I want all the geometry of those children to remain in the same spacial location but I wan all the axis to move to the location of the Null (no matter how deep the objects are in the hierarchy.
My current code is as follows:

main_mg = op.GetMg() # the global matrix of the parent Null

# op_list is a list that contains all the childs of the Null.
# Some of them are childs of childs or even deeper.

doc.StartUndo()

for op in op_list:

    child_mg = op.GetMg()
    pointsG = [p * child_mg for p in op.GetAllPoints()]
    
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)

    op.SetMg(main_mg)
    op.Message(c4d.MSG_UPDATE)
    inv_mg = ~child_mg
    nPoints = [p * inv_mg for p in pointsG]
    
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
    op.SetAllPoints(nPoints)
    
doc.EndUndo()
c4d.EventAdd()

But it is not working!!
All the geometry still moves.

@rui_mac said in Move just the axis of a polygonal object:

inv_mg = ~child_mg

Be careful about the matrix you are using as reference to go from global to local.
The object new matrix now is the same as the null. If you are using the old one (child_mg), the points will move the same way you moved the axis. But if you are using the new one, the points be changed to global to local and in fact will not move. (and that's the goal)

inv_mg = ~main_mg

forget about the hierarchy in that case.
It's just matrix operation local to global and global to local.

@m_adam share his code, he think that could help you in your project.

Let me know if it's still not clear.

Cheers
Manuel

Thank you so much. It is clearer now.
It works!!

Spoke too soon.
It works, but not for childs of childs of childs...
This is my current code:

import c4d

op_list=[]

def Get_Op(op):
    global op_list

    while (op):
        op_list.append(op)
        Get_Op(op.GetDown())
        op = op.GetNext()
    return None


# Main function
def main():
    global op_list

    selected = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_0)
    if len(selected)!=1: return
    op = selected[0]
    
    main_mg = op.GetMg()

    child = op.GetDown()
    op_list = []

    if child != None:
        Get_Op(child)
        
        if len(op_list)!= 0:

            doc.StartUndo()

            for op in op_list:
                
                if op.GetType()==c4d.Opolygon:

                    child_mg = op.GetMg()
                    pointsGlob = [p * child_mg for p in op.GetAllPoints()]

                    doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
                    op.SetMg(main_mg)
                    op.Message(c4d.MSG_UPDATE)
                    
                    inv_mg = ~main_mg
                    newPoints = [p * inv_mg for p in pointsGlob]

                    doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)
                    op.SetAllPoints(newPoints)
                    op.Message(c4d.MSG_UPDATE)

            doc.EndUndo()

            c4d.EventAdd()

# Execute main()
if __name__=='__main__':
    main()

And it doesn't work with the Cap1 and Cap2 objects, in this hierarchy:

alt text

Everything remains in the same location, with the axis moved to the location of the outermost parent Null, except the Cap1 and Cap2 objects, as their geometry changes location.

after trying several solutions, i've change your function with this one for non recursive iteration. and using the same idea to local to global and global to local but with the child "locoal" matrix. (the one you get with GetMl)

import c4d

def GetNextObject(op):
    # Non recursive hierarchy iteration
    if op==None:
        return None
  
    if op.GetDown():
        return op.GetDown()
  
    while not op.GetNext() and op.GetUp():
        op = op.GetUp()
  
    return op.GetNext()
    
    
def UpdateObject (op, newpos):
    # Update the object by moving it to the new position 
    # and moving back the points to their old position

    # Store the old matrix of the object
    old_mg = op.GetMg()

    # Retrieve the points position in global space
    pointsGlob = [p * old_mg for p in op.GetAllPoints()]

    # Add an Undo state to the document
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)

    # update the object position matrix    
    op.SetMg(newpos)

    # Inverses the matrix to come back to local space
    inv_mg = ~newpos

    # Retrieves points from global to local space
    newPoints = [p * inv_mg for p in pointsGlob]

    # Add an Undo state to the document
    doc.AddUndo(c4d.UNDOTYPE_CHANGE, op)

     # Updates the points
    op.SetAllPoints(newPoints)

    # Notifies a change on the obj
    op.Message(c4d.MSG_UPDATE)

def UpdateChildsMatrix(op, oldMg, newMg):
    # Update the Childrend Matrix after the parent have been moved

    op = op.GetDown()
    # For All child of the object
    while op:
        # Store the local matrix to a global space with the old matrix object
        newChildMg = oldMg * op.GetMl()

        # Calculate the new local matrix of the object.
        newChildMg =  ~newMg * newChildMg

        # Set the new local matrix of the object        
        op.SetMl(newChildMg)

        # Notifies a change on the obj
        op.Message(c4d.MSG_UPDATE)    

        # Get the next Object if any
        op = op.GetNext()
    
    


# Main function
def main():
    # Get all Selected Object
    selected = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE)

    if len(selected) != 1:
        return

    mainOp = selected[0]

    op_list = []

    # Get the first object if any
    nextObj = GetNextObject(mainOp)
    while nextObj:
        # Add all object found in the array
        op_list.append(nextObj)
        # Retreive the next Object
        nextObj = GetNextObject(nextObj)

    # Retreive the global position of the main object
    main_mg = mainOp.GetMg()

    # if there's no child, just leave
    if len(op_list) == 0:
        return

    # Starts the undo process (the initial scene to be restored after an undo action)
    doc.StartUndo()

    for op in op_list:
        # Check if it's a polygon Object
        if op.GetType()==c4d.Opolygon:
            #save the matrix to have a chance to update the child
            savedMatrix = op.GetMg()

            # Move the object to the new location without moving the points
            UpdateObject(op,main_mg)

            # Because we moved the parent, we should move back the children.
            UpdateChildsMatrix(op, savedMatrix, main_mg)
                    
            

    # Ends the undo process (the final scene state)
    doc.EndUndo()
    
    # Updates Cinema 4D
    c4d.EventAdd()

# Execute main()
if __name__=='__main__':
    main()

Cheers
Manuel

Thank you so much, Manuel.
It was, mainly, that children matrix update that was missing.
Now, it works fine ☺

hi,

when you will be sure that it's solved, don't forget to mark this thread as solved please.

Cheers
Manuel