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

    Calling the button to create an object cannot add it to the undo queue

    Cinema 4D SDK
    python
    2
    3
    513
    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.
    • kangddanK
      kangddan
      last edited by

      Hello everyone, when creating a splineIK tag, I dynamically call c4d.CallButton to create an IkHandle. At this point, an empty object is generated. I am now able to correctly retrieve it, but I can't add it to the undo queue. Since c4d.CallButton has no return value, I retrieve the empty object from the current scene each time I create an IkHandle. I'm not sure if there's a more proper way to do this

      import c4d
      
      def createJoints(jointCount):
          jntList = []
          for i in range(jointCount):
              jnt = c4d.BaseObject(c4d.Ojoint)
              jnt.SetName('splineIK_{}'.format(i))
              doc.InsertObject(jnt)
              doc.AddUndo(c4d.UNDOTYPE_NEW, jnt)
              jntList.append(jnt)
              if i > 0:
                  jnt.InsertUnder(jntList[i-1])
          return jntList
      
      def getAllNulls():
          return [obj for obj in doc.GetObjects() if obj.GetType() == c4d.Onull]
      
      def addSplineTag(startJnt, endJnt, spline):
          tag = c4d.BaseTag(1019862)
          doc.AddUndo(c4d.UNDOTYPE_NEW, tag)
          startJnt.InsertTag(tag)
          tag[c4d.ID_CA_IKSPLINE_TAG_TYPE] = 2
          tag[c4d.ID_CA_IKSPLINE_TAG_TWIST_TYPE] = 1
          tag[c4d.ID_CA_IKSPLINE_TAG_SPLINE] = spline
          tag[c4d.ID_CA_IKSPLINE_TAG_END] = endJnt
          
          splineIkHandles = []
          for i in range(spline.GetPointCount()):
              c4d.CallButton(tag, c4d.ID_CA_IKSPLINE_HANDLE_ADD)
              c4d.CallButton(tag, c4d.ID_CA_IKSPLINE_HANDLE_CREATE)
              
              null = getAllNulls()[0]
              doc.AddUndo(c4d.UNDOTYPE_NEW, null) # add undo
              splineIkHandles.append(null)
              
          return splineIkHandles
      
      def createSplineIK(jointCount=10):
          sel = doc.GetSelection()
          if not sel: return
          spline = sel[0] if sel[0].CheckType(c4d.Ospline) else None
          if spline is None: return
      
          doc.StartUndo()
          # ---------------------------------
          jnts = createJoints(jointCount)
          splineIkHandles = addSplineTag(jnts[0], jnts[-1], spline)
          for i in splineIkHandles:
              print(i.GetName())
          # ---------------------------------
          doc.EndUndo()
          c4d.EventAdd()
      
      if __name__ == '__main__':
          createSplineIK(jointCount=10)
      

      https://github.com/kangddan

      1 Reply Last reply Reply Quote 0
      • M
        m_adam
        last edited by

        Hi @kangddan sadly there is no real workaround except to re-implement what the button does.
        Find bellow a script that show the logic behind each button. Note that HandleCreate, is not a 100% match since some internal functions and helper classes are used which hare not exposed so I had to re-use what was exposed. This mean you may find some behavior change, especially in how the null are rotated along the spline.

        import c4d
        
        doc: c4d.documents.BaseDocument  # The currently active document.
        op: c4d.BaseObject | None  # The primary selected object in `doc`. Can be `None`.
        
        IKHANDLE_ELEMENT = 10
        
        IKHANDLE_INDEX = 0
        IKHANDLE_LINK = 1
        IKHANDLE_TWIST = 2
        IKHANDLE_LENGTH = 3
        IKHANDLE_OFFSET = 5
        
        
        def HandleAdd(tag):
            bc = tag.GetDataInstance()
            cnt = bc.GetInt32(c4d.ID_CA_IKSPLINE_HANDLE_COUNT)
            if cnt + 1 > 999:
                return
            
            bc.SetInt32(c4d.ID_CA_IK_TAG_END_COUNT, cnt + 1)
            
            baseId = c4d.ID_CA_IK_TAG_END_COUNT + 1 + cnt * IKHANDLE_ELEMENT
            bc.SetInt32(baseId, cnt)
            bc.SetData(baseId + IKHANDLE_LINK, None)
            bc.SetBool(baseId + IKHANDLE_TWIST, True)
            bc.SetFloat(baseId + IKHANDLE_LENGTH, 10.0)
            bc.SetBool(baseId + IKHANDLE_OFFSET, False)
            
            tag.SetDirty(c4d.DIRTYFLAGS_DATA)
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag)
            
        def HandleRemove(tag):
            bc = tag.GetDataInstance()
            newCnt = bc.GetInt32(c4d.ID_CA_IKSPLINE_HANDLE_COUNT) - 1
            if newCnt < 0:
                return
            
            bc.SetInt32(c4d.ID_CA_IK_TAG_END_COUNT, newCnt)
            
            if newCnt < 999:
                baseId = c4d.ID_CA_IK_TAG_END_COUNT + 1 + newCnt * IKHANDLE_ELEMENT
                bc.RemoveData(baseId + IKHANDLE_ELEMENT)
                bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_LINK)
                bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_TWIST)
                bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_LENGTH)
                bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_OFFSET)
                bc.RemoveData(baseId + IKHANDLE_ELEMENT + IKHANDLE_LINK)
                        
            tag.SetDirty(c4d.DIRTYFLAGS_DATA)
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag)
            
        def HandleCreate(tag):
            doc = tag.GetDocument()
            if doc is None:
                return
            
            bc = tag.GetDataInstance()
            handleCnt = bc.GetInt32(c4d.ID_CA_IKSPLINE_HANDLE_COUNT)
            for i in range(handleCnt):
                hLink = bc.GetObjectLink(c4d.ID_CA_IKSPLINE_HANDLE_COUNT + i * IKHANDLE_ELEMENT + IKHANDLE_TWIST, doc)
                if hLink is None:
                    break
                
            if i >= handleCnt:
                return
            
            pSpline = bc.GetLink(c4d.ID_CA_IKSPLINE_TAG_SPLINE, doc, c4d.Ospline)
            if pSpline is None:
                return
            
            pCnt = pSpline.GetPointCount()
            pindex = bc.GetInt32(c4d.ID_CA_IKSPLINE_HANDLE_COUNT + i * IKHANDLE_ELEMENT + IKHANDLE_LINK)
            if pindex < 0 or pindex >= pCnt:
                return
            
            null = c4d.BaseObject(c4d.Onull)
            if null is None:
                return
            
            null.SetName(f"{null.GetName()}.{pindex}") 
            doc.InsertObject(null)
            
            doc.AddUndo(c4d.UNDOTYPE_NEWOBJ, null)
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, tag.GetObject())
            doc.AddUndo(c4d.UNDOTYPE_CHANGE, pSpline)
            
            bc.SetLink(c4d.ID_CA_IKSPLINE_HANDLE_COUNT + i * IKHANDLE_ELEMENT + IKHANDLE_TWIST, null)
            haveTangents = pSpline.GetInterpolationType() == c4d.SPLINETYPE_BEZIER
            if (haveTangents):
                v1Length = pSpline.GetTangent(pindex)["vr"].GetLength()
                v2Length = pSpline.GetTangent(pindex)["vl"].GetLength()
                bc.SetFloat(c4d.ID_CA_IKSPLINE_HANDLE_COUNT + i * IKHANDLE_ELEMENT + 1 + IKHANDLE_LENGTH, max(v1Length, v2Length))
                
            # This is a simplified part here since internally we have some helper that are not exposed, but the code bellow should do appromitavely the same thing
            sh = c4d.utils.SplineHelp()
            upVector: c4d.Vector
            
            alignValue = bc.GetInt32(c4d.ID_CA_IKSPLINE_TAG_ALIGN_AXIS)
            if alignValue == c4d.ID_CA_IKSPLINE_TAG_ALIGN_X:
                upVector = c4d.Vector(1.0, 0.0, 0.0)
            if alignValue == c4d.ID_CA_IKSPLINE_TAG_ALIGN_Y:
                upVector = c4d.Vector(0.0, 1.0, 1.0)
            if alignValue == c4d.ID_CA_IKSPLINE_TAG_ALIGN_Z:
                upVector = c4d.Vector(0.0, 0.0, 1.0)
            sh.InitSplineWithUpVector(pSpline, upVector)
            
            m = sh.GetVertexMatrix(sh.SplineToLineIndex(pindex))
            null.SetMg(m)
            
            c4d.EventAdd()
            
            
        
        def main() -> None:
            doc.StartUndo()
            
            ikSpline = op.GetTag(c4d.Tcaikspline)
            HandleAdd(ikSpline)
            HandleCreate(ikSpline)
            # HandleRemove(ikSpline)
            
            
            doc.EndUndo()
            c4d.EventAdd()
        
        
        if __name__ == '__main__':
            main()
        

        Cheers,
        Maxime.

        MAXON SDK Specialist

        Development Blog, MAXON Registered Developer

        kangddanK 1 Reply Last reply Reply Quote 0
        • kangddanK
          kangddan @m_adam
          last edited by

          Thank you @m_adam It seems that the best approach is to manually create a null object and calculate its correct rotation matrix through certain methods to achieve the same effect. Once again, thank you for your code; it has been very useful to me!

          https://github.com/kangddan

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