Hello @leah.hayes,
Welcome to the Maxon developers forum and its community, it is great to have you with us!
Getting Started
Before creating your next postings, we would recommend making yourself accustomed with our forum and support procedures. You did not do anything wrong, we point all new users to these rules.
Forum Overview: Provides a broad overview of the fundamental structure and rules of this forum, such as the purpose of the different sub-forums or the fact that we will ban users who engage in hate speech or harassment.
Support Procedures: Provides a more in detail overview of how we provide technical support for APIs here. This topic will tell you how to ask good questions and limits of our technical support.
Forum Features: Provides an overview of the technical features of this forum, such as Markdown markup or file uploads.
It is strongly recommended to read the first two topics carefully, especially the section Support Procedures: How to Ask Questions.
About your First Question
It is a bit difficult to answer you first question, as it is multiple questions of which some are open ended, please have a look at Support Procedures: How to Ask Questions for future postings.
Where is the Python code of the built-in character templates ...
They are stored in {c4d installation folder}/library.zip/characters.
Is there a supported way to extract or view a built-in template (for example through c4d.modules.character.builder.Template) so I can study how the auto-adjustment is implemented?
I am not quite sure that I follow. All these rigs are just our standard CA component system decorated with some Xpresso and Python. Since there can be literally hundreds of nodes in a rig, I attached below a little helper script to scan for specific Python content or just the scripts in general that are embedded somewhere in the rig.
Is there a public API entry point to trigger or implement this kind of auto-adjustment for other templates?
That auto adjustment seems to be some kind of Python script which the authors of this rig provided. These CA rigs have been created by (technical) artists and not the members of the Maxon development team. See the print out below for details on the artist code for "auto-adjustment".
Cheers,
Ferdinand
Result
====================================================================================================
Node: Character Component (Character Component) | Attribute: (2158, 130, 1022113)
Value:
import c4d
from c4d import gui
import c4d.modules.character as ca
import c4d.utils as utils
#Welcome to the world of Python
def Bl2DIterator(bl2D):
while bl2D:
yield bl2D
for bl2DChild in Bl2DIterator(bl2D.GetDown()):
yield bl2DChild
bl2D = bl2D.GetNext()
def SearchInHierarchy(obj, name):
allChildrenWithName = [bl2D for bl2D in Bl2DIterator(obj.GetDown()) if bl2D.GetName() == name]
return allChildrenWithName[0]
def ShowLayer(layersList, showName):
for layer in layersList:
layerName = layer[c4d.ID_BASELIST_NAME]
if showName in layerName:
layer[c4d.ID_LAYER_VIEW] = True
layer[c4d.ID_LAYER_MANAGER] = True
def HideLayer(layersList, hideName):
for layer in layersList:
layerName = layer[c4d.ID_BASELIST_NAME]
if hideName in layerName:
layer[c4d.ID_LAYER_VIEW] = False
layer[c4d.ID_LAYER_MANAGER] = False
def CharacterAnimateMode(characterObject):
characterObject[c4d.ID_CA_CHARACTER_COMPONENT_HIGHLIGHT_OVER] = 4
characterObject[c4d.ID_CA_CHARACTER_COMPONENT_MOUSEOVER] = 0
characterObject[c4d.ID_CA_CHARACTER_COMPONENT_VISIBLE] = 2
characterObject[c4d.ID_CA_CHARACTER_OM_DISPLAY] = 5
characterObject[c4d.ID_CA_CHARACTER_LOCK_AM] = False
def CharacterEditMode(characterObject):
characterObject[c4d.ID_CA_CHARACTER_COMPONENT_HIGHLIGHT_OVER] = 3
characterObject[c4d.ID_CA_CHARACTER_COMPONENT_MOUSEOVER] = 4
characterObject[c4d.ID_CA_CHARACTER_COMPONENT_VISIBLE] = 3
characterObject[c4d.ID_CA_CHARACTER_OM_DISPLAY] = 1
characterObject[c4d.ID_CA_CHARACTER_LOCK_AM] = True
def CharacterBindMode(characterObject):
characterObject[c4d.ID_CA_CHARACTER_COMPONENT_HIGHLIGHT_OVER] = 4
characterObject[c4d.ID_CA_CHARACTER_COMPONENT_MOUSEOVER] = 4
characterObject[c4d.ID_CA_CHARACTER_COMPONENT_VISIBLE] = 1
characterObject[c4d.ID_CA_CHARACTER_OM_DISPLAY] = 5
characterObject[c4d.ID_CA_CHARACTER_LOCK_AM] = True
def CatchEmAll(obj, stop, listy):
if obj is None: return
#Actions go here
if obj.GetType()==1019362:
listy.append(obj)
if not stop:
if obj.GetDown():
CatchEmAll(obj.GetDown(), stop, listy)
if obj.GetNext():
CatchEmAll(obj.GetNext(), stop, listy)
return listy
def SetGlobalRotation(obj, rot):
"""
Please remember, Cinema 4D handles rotation in radians.
Example for H=10, P=20, B=30:
import c4d
from c4d import utils
#...
hpb = c4d.Vector(utils.Rad(10), utils.Rad(20), utils.Rad(30))
SetGlobalRotation(obj, hpb) #object's rotation is 10, 20, 30
"""
m = obj.GetMg()
pos = m.off
scale = c4d.Vector( m.v1.GetLength(),
m.v2.GetLength(),
m.v3.GetLength())
m = utils.HPBToMatrix(rot)
m.off = pos
m.v1 = m.v1.GetNormalized() * scale.x
m.v2 = m.v2.GetNormalized() * scale.y
m.v3 = m.v3.GetNormalized() * scale.z
obj.SetMg(m)
def SetGlobalScale(obj, scale):
m = obj.GetMg()
m.v1 = m.v1.GetNormalized() * scale.x
m.v2 = m.v2.GetNormalized() * scale.y
m.v3 = m.v3.GetNormalized() * scale.z
obj.SetMg(m)
def ModeChanged():
characterObject = charop.GetObject()
#Turning Off the Annotation
# Main function
leftAnnotationTag = SearchInHierarchy(characterObject, "LeftLeg_bind").GetLastTag()
print(leftAnnotationTag)
if leftAnnotationTag.GetType() == c4d.Tannotation:
leftAnnotationTag[c4d.ANNOTATIONTAG_VIEWPORT_SHOW] = False
#Hiding the Rig's Layers
root = doc.GetLayerObjectRoot() #Gets the layer manager
layersList = root.GetChildren() #Get Layer list
for layer in layersList:
layerName = layer[c4d.ID_BASELIST_NAME]
if "Mixamo_Rig" in layerName:
layer[c4d.ID_LAYER_VIEW] = False
layer[c4d.ID_LAYER_MANAGER] = False
mixamoLayers = layer.GetChildren()
for layer in mixamoLayers:
layerName = layer[c4d.ID_BASELIST_NAME]
if "Retarget_Hierarchy" in layerName or \
"ANIMDATA_Nulls" in layerName or \
"Control Hierarchy" in layerName:
layer[c4d.ID_LAYER_VIEW] = False
layer[c4d.ID_LAYER_MANAGER] = False
masterControl = SearchInHierarchy(characterObject, "Master_con+")
hips = SearchInHierarchy(characterObject, "Hips")
if nmode==c4d.ID_CA_CHARACTER_MODE_BUILD or \
nmode==c4d.ID_CA_CHARACTER_MODE_ADJUST:
CharacterEditMode(characterObject)
if nmode==c4d.ID_CA_CHARACTER_MODE_BIND:
CharacterBindMode(characterObject)
if nmode==c4d.ID_CA_CHARACTER_MODE_ADJUST and omode==c4d.ID_CA_CHARACTER_MODE_BUILD:
#Changed search method to only search in Character Object
#hips=doc.SearchObject("Hips")
#masterControl=doc.SearchObject("Master_con+")
#added Namespace Code
nameSpace=masterControl[c4d.ID_USERDATA,16]
results=CatchEmAll(hips, hips.GetNext(), [])
for result in results:
mixamo=doc.SearchObject(nameSpace+result.GetName())
if mixamo:
matR = result.GetMg()
matM = mixamo.GetMg()
matR.off = matM.off
matR.v1 = matM.v1.GetNormalized()
matR.v2 = matM.v2.GetNormalized()
matR.v3 = matM.v3.GetNormalized()
result.SetMg(matR)
if result.GetName()=='Neck1':
#neck=doc.SearchObject(nameSpace+'Neck')
#head=doc.SearchObject(nameSpace+'Head')
neck = SearchInHierarchy(characterObject, nameSpace+'Neck')
head = SearchInHierarchy(characterObject, nameSpace+'Head')
if head and neck:
result.SetMg((neck.GetMg()+head.GetMg())/2)
print("Auto-Adjustment Complete")
#Turning On the Annotation
if leftAnnotationTag.GetType() == c4d.Tannotation:
leftAnnotationTag[c4d.ANNOTATIONTAG_VIEWPORT_SHOW] = True
if omode==c4d.ID_CA_CHARACTER_MODE_ADJUST and nmode!=c4d.ID_CA_CHARACTER_MODE_BUILD:
L_Leg=doc.SearchObject("Left_IK_parent_rot_algn")
#L_Leg = charop.FindObject("Left_IK_parent_rot_algn")
if L_Leg:
conTag=L_Leg.GetFirstTag()
conTag[c4d.EXPRESSION_ENABLE]=False
R_Leg=doc.SearchObject("Right_IK_parent_rot_algn")
#R_Leg = charop.FindObject("Right_IK_parent_rot_algn")
if R_Leg:
conTag=R_Leg.GetFirstTag()
conTag[c4d.EXPRESSION_ENABLE]=False
if nmode==c4d.ID_CA_CHARACTER_MODE_ANIMATE:
CharacterAnimateMode(characterObject)
doc.SetActiveObject(masterControl, c4d.SELECTION_NEW)
c4d.EventAdd()
Code
"""Scans the document for all nodes that have multi-line string parameters that contain the word "adjustment"
(case-insensitive) and prints the node name, type, parameter ID, and value to the console.
"""
import c4d
import mxutils
doc: c4d.documents.BaseDocument
def main() -> None:
"""
"""
node: c4d.BaseList2D
pid: c4d.DescID
# Iterate over all objects and tags in the document that live outside of caches and within the
# object branch of the document.
for node in mxutils.RecurseGraph(doc.GetFirstObject(), yieldBranches=True, yieldHierarchy=True,
branchFilter=[c4d.Obase, c4d.Tbase]):
# Iterate over the description, i.e., parameters of the node.
for data, pid, _ in node.GetDescription(c4d.DESCFLAGS_DESC_NONE):
# The first level of this parameter is not of type string or does not use the
# multi-line string GUI, so we skip it.
if pid[0].dtype != c4d.DTYPE_STRING or data[c4d.DESC_CUSTOMGUI] != c4d.CUSTOMGUI_STRINGMULTI:
continue
# Try to read the value, the exception block is needed as not all parameter types are
# accessible in Python (and this could be some kind of exotic multi level string data type).
try:
value: str | None = str(node[pid]).strip()
if not isinstance(value, str) or not value:
continue
except Exception:
continue
# Check if the value contains the word "adjustment" (case-insensitive), if not, skip it.
if "adjustment" not in value.lower():
continue
# Print the match.
print ("\n\n" + ("=" * 100))
print (f"Node: {node.GetName()} ({node.GetTypeName()}) | Attribute: {pid}\n\nValue:\n\n{value}")
# For good measure, select the node and break the loop.
(doc.SetActiveObject(node, c4d.SELECTION_NEW)
if isinstance(node, c4d.BaseObject) else
doc.SetActiveTag(node, c4d.SELECTION_NEW))
break
c4d.EventAdd()
if __name__ == '__main__':
main()