Maxon Developers Maxon Developers
    • Documentation
      • Cinema 4D Python API
      • Cinema 4D C++ API
      • Cineware API
      • ZBrush GoZ API
      • Code Examples on Github
    • Forum
    • Downloads
    • Support
      • Support Procedures
      • Registered Developer Program
      • Plugin IDs
      • Contact Us
    • Categories
      • Overview
      • News & Information
      • Cinema 4D SDK Support
      • Cineware SDK Support
      • ZBrush 4D SDK Support
      • Bugs
      • General Talk
    • Unread
    • Recent
    • Tags
    • Users
    • Login

    How to Center the Axis of Objects

    Cinema 4D SDK
    python r21
    2
    4
    2.3k
    Loading More Posts
    • Oldest to Newest
    • Newest to Oldest
    • Most Votes
    Reply
    • Reply as topic
    Log in to reply
    This topic has been deleted. Only users with topic management privileges can see it.
    • ROMANR
      ROMAN @ferdinand
      last edited by ferdinand

      @ferdinand , thank you a lot!
      When i tried to fix my code i used Mike Udin's example, but i couldn't understand why this code didn't work with multiple objects. I've got next message: "AttributeError: 'NoneType' object has no attribute 'GetAllPoints'".

      I supposed my script to set selected objects' axis points in the center for each other. After that the script should create a null between all axis points.

      import c4d
      
      def main():
          
          ps = op.GetAllPoints() #Points in Local coordinates List
          m = op.GetMg() #Object Global Matrix
      
          center = op.GetMp() #Local coordinates center: https://tinyurl.com/wed88cn
          rad = op.GetRad() # Geometry radius: https://tinyurl.com/tb6vn64
          
          center -= c4d.Vector(0,rad.y,0) #Move down axis to lowest point. Comment this line to keep axis in a center 
          
          center *= m #Convert to Global coordinates center
      
          new_m = c4d.Matrix(m) #Copy matrix
          new_m.off = center #Change its center
      
          loc_m = ~new_m * m #Get local matrix
      
          op.SetAllPoints([loc_m.Mul(p) for p in ps])
          op.SetMg(new_m)
          
          op.Message(c4d.MSG_UPDATE)
          c4d.EventAdd()
      
      
      if name == 'main':
          main()
      

      edit: This topic has been forked from How to fix the bug of objects position in script by @ferdinand

      ferdinandF 1 Reply Last reply Reply Quote 0
      • ferdinandF
        ferdinand @ROMAN
        last edited by

        Hello @roman,

        I struggle a bit with understanding how your answer and the posted code is relevant for the rest of the posting. It is not that I would not understand what the code is supposed to do, I just do not understand how transforming the vertices of a point object is relevant for the rest of the posting.

        If the problem of this topic is not solved for you, I must ask you to explain the remaining problems again.

        Cheers,
        Ferdinand

        MAXON SDK Specialist
        developers.maxon.net

        ROMANR 1 Reply Last reply Reply Quote 0
        • ROMANR
          ROMAN @ferdinand
          last edited by ROMAN

          @ferdinand

          Script add a null in center of world coordinates. I want to change it, i described what i want to do in pictures.

          1.jpg

          I added Mike's code, because i wanted to make 2 options for Step 2:

          • First option — script add axis poins of selected objects in center.
          • Second option — script add axis poins of selected objects in center but in the lowest position in Y coordinate. Mike's script could do this but just for one object. If i select objects more than one i get a message: "AttributeError: 'NoneType' object has no attribute 'GetAllPoints'".

          I'm sorry for my English...

          ferdinandF 1 Reply Last reply Reply Quote 0
          • ferdinandF
            ferdinand @ROMAN
            last edited by ferdinand

            Hello @roman,

            I have forked this topic since this a new question. Please refer to the Forum Guidelines for details on forum procedures.

            The error message AttributeError: 'NoneType' object has no attribute 'GetAllPoints'. likely stems from you having no object selected when trying to run the script you posted. This is because the script simply assumes that op is populated, i.e., some kind of BaseObject, and even further it assumes that it is not only a BaseObject but also a PointObject. Both assumptions can prove false, as the user can have no selection, then op is None, or a selection which is not a PointObject, e.g., some parametric object.

            Your code will also not work for what you wanted to do regarding the last thread, as this new code only applies to a singular object. Your code is also a bit complicated for my taste, it could be done more cleanly like this:

            # Get the origin of the bounding box of the object, i.e., where
            # the axis should be.
            origin = node.GetMp()
            
            # The global matrix of the object.
            mg = node.GetMg()
            
            # The delta between where the axis is and where it should be
            delta = c4d.Matrix(off=origin)
            
            # Move all points by the inverse of that delta.
            node.SetAllPoints([(p * ~delta) for p in node.GetAllPoints()])
            
            # Set the new node position
            node.SetMg(mg * delta)
            

            I have provided an example solution in the context of your last topic. The major insight here is that you have to do things then in two steps. First you go over all selcted objects to set their layers and try to move their axis. And after that you can calaculate the position of the null. Please also note that:

            1. Moving the axis of an object can be quite the can of worms when you start touching orientation or scale, as you then also have to update tangent tags, normal tags and other data.
            2. The provided code is an example and might still contain bugs you must fix yourself. We cannot provide full solutions.

            Cheers,
            Ferdinand

            The result:
            origin.gif

            The test scene:
            test_scene.c4d

            The code:

            import c4d
            
            def main():
                """
                """
                # 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
            
                # Get the selected objects.
                selectedObjects = doc.GetActiveObjects(c4d.GETACTIVEOBJECTFLAGS_NONE)
            
                # Iterate over the selected objects to change their layer and the layer
                # of the attached materials and optionally center their axis.
                for node in selectedObjects:
                    # Attempt to center the axis for editable objects.
                    if isinstance(node, c4d.PointObject):
                        # Get the origin of the bounding box of the object, i.e., where
                        # the axis should be.
                        origin = node.GetMp()
            
                        # The global matrix of the object.
                        mg = node.GetMg()
            
                        # The delta between where the axis is and where it should be
                        delta = c4d.Matrix(off=origin)
            
                        # Move all points by the inverse that delta.
                        doc.AddUndo(c4d.UNDOTYPE_CHANGE, node)
                        node.SetAllPoints([(p * ~delta) for p in node.GetAllPoints()])
            
                        # Move the object by that delta.
                        node.SetMg(mg * delta)
            
                    # Set the layer of the current node.
                    doc.AddUndo(c4d.UNDOTYPE_CHANGE, node)
                    node[c4d.ID_LAYER_LINK] = newLayer
            
                    # Get all materials attached with material tags to the node.
                    isTex = lambda node: node.IsInstanceOf(c4d.Ttexture)
                    for material in [t.GetMaterial() for t in node.GetTags() if isTex(t)]:
                        doc.AddUndo(c4d.UNDOTYPE_CHANGE, material)
                        material[c4d.ID_LAYER_LINK] = newLayer
            
                # Compute the mean global offset for all objects in the selection.
                meanOffset = (sum((n.GetMg().off for n in selectedObjects)) * 
                              (1. / len(selectedObjects)))
            
                # Create a null object at that offset to parent the selected objects to.
                newLayerNull = c4d.BaseObject(c4d.Onull)
                newLayerNull[c4d.ID_LAYER_LINK] = newLayer
                newLayerNull[c4d.ID_BASELIST_NAME] = newName
                newLayerNull.SetMg(c4d.Matrix(off=meanOffset))
                doc.InsertObject(newLayerNull)
                doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, newLayerNull)
            
                # Loop again over all selected objects to parent them to the new null
                # object.
                for node in selectedObjects:
            
                    # Get the global matrix of the node and then remove it from the scene.
                    mg = node.GetMg()
                    doc.AddUndo(c4d.UNDOTYPE_CHANGE, node)
                    node.Remove()
            
                    # Attach the node to its new parent and set its global matrix to the 
                    # stored value.
                    node.InsertUnder(newLayerNull)
                    node.SetMg(mg)
            
                # Close the undo item and push an update event to Cinema 4D.
                doc.EndUndo()
                c4d.EventAdd()
            
            if __name__ == '__main__':
                main()
            

            MAXON SDK Specialist
            developers.maxon.net

            1 Reply Last reply Reply Quote 0
            • First post
              Last post