Calling the button to create an object cannot add it to the undo queue
-
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)
-
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. -
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!