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.