Set c4d.InExcludeData() has something didn't expect.
-
Hi !
Question :
I try to insert a plain effector into a cloner InExcludeData , It worked , but sometimes i have to click twice but no error ( seems sometimes it clear the InExcludeData ) .
How can I fix it ?
@Windows11 22H1 C4D R2023.0.0
Many Thanks
Code
from typing import Optional import c4d from math import radians doc: c4d.documents.BaseDocument # The active document op: Optional[c4d.BaseObject] # The active object, None if unselected def InsertNodeIntoInExclude(data : c4d.InExcludeData, node : c4d.BaseObject, index, isActive=True): """Inserts a node at a certain index into an InExcludeData.""" dataCount = data.GetObjectCount() doc = node.GetDocument() if index > dataCount: raise IndexError(f"The index {index} is out of bounds.") result, i = c4d.InExcludeData(), 0 while data.ObjectFromIndex(doc, i): if i == index: result.InsertObject(node, int(isActive)) item = data.ObjectFromIndex(doc, i) flags = data.GetFlags(i) result.InsertObject(item, flags) i += 1 return result # 量化旋转 def mogragh_quantize_rotation(doc :c4d.documents.BaseDocument, cloner : c4d.BaseObject) -> c4d.BaseObject : """Inserts a quantize rotation effector into selected cloner.""" doc.StartUndo() # Pass if selection is not a Cloner if cloner is None or cloner.GetType() != 1018544: raise RuntimeError("Select a cloner.") effector = c4d.BaseObject(1021337) # Plain doc.InsertObject(effector,pred=cloner) doc.AddUndo(c4d.UNDOTYPE_NEWOBJ,effector) # Set plain to cloner InExclude inExData = cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] if inExData: if inExData.GetObjectCount() == 0: try: emptyData = c4d.InExcludeData() emptyData.InsertObject(effector,1) cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] = emptyData except: raise RuntimeError("Insert failed , try again .") else: try: modifiedInExData = InsertNodeIntoInExclude(inExData, effector, 0) doc.AddUndo(c4d.UNDOTYPE_CHANGE, cloner) cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] = modifiedInExData except: raise RuntimeError("Insert failed , try again .") else: raise RuntimeError("Failed to get Includedata .") # FieldObject field_object = c4d.modules.mograph.FieldObject(440000281) # ramdom field object doc.InsertObject(field_object,parent=effector) doc.AddUndo(c4d.UNDOTYPE_NEWOBJ,field_object) # Set Field List fieldList = effector[c4d.FIELDS] layer = c4d.modules.mograph.FieldLayer(c4d.FLfield) layer.SetLinkedObject(field_object) fieldList.InsertLayer(layer) # Quantize Layer quantizeLayer = c4d.modules.mograph.FieldLayer(c4d.FLquantize) if quantizeLayer is None: raise MemoryError("Failed to create a Field Layer.") fieldList.InsertLayer(quantizeLayer) doc.AddUndo(c4d.UNDOTYPE_BITS, quantizeLayer) quantizeLayer.SetBit(c4d.BIT_ACTIVE) # Set effector.SetName(cloner.GetName() + ' ' + 'Plain') effector[c4d.ID_MG_BASEEFFECTOR_POSITION_ACTIVE] = False # Turn off Pos effector[c4d.ID_MG_BASEEFFECTOR_SCALE_ACTIVE] = False # Turn off Scale effector[c4d.ID_MG_BASEEFFECTOR_ROTATE_ACTIVE] = True # Turn On Pot effector[c4d.ID_MG_BASEEFFECTOR_ROTATION,c4d.VECTOR_X] = radians(360) # Set Rot at H(x) to 360 degrees field_object[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 1 # Set editor unvisible field_object.SetName(effector.GetName() + ' : ' + 'Quantize Rot') # Set Field Name to Effetor name + Function doc.AddUndo(c4d.UNDOTYPE_CHANGE, effector) effector[c4d.FIELDS] = fieldList doc.EndUndo() c4d.EventAdd() # Refresh Viewport return effector if __name__ == '__main__': doc = c4d.documents.GetActiveDocument() cloner = doc.GetActiveObject() mogragh_quantize_rotation(doc,cloner)
-
Hey @dunhou,
Thank you for reaching out to us and a special thanks for your postings which are always super clear. I think I already said it before, but it makes my life much easier.
There are sort of two problems in your code:
- You set your
inExData
in a bit odd place, which can lead to update problems in some special cases, when you then after that start constructing your field list setup related to the effector referenced by theInExcludeData inExData
. This should not cause problems in most cases, but for safety I would first construct my little effector/field rig in its entirety, and then reference it in theInExcludeData
. - Your
InsertNodeIntoInExclude
produced a few problems for me when I tried your script, I did not try to debug it, and instead just rewrote the function how I would write it.
I have provided below my solution and the script does what I would expect it to do. The "click twice" thing you encountered was likely not an update error, in the sense of Cinema 4D not updating, but an index error due to how you fashioned your
InsertNodeIntoInExclude
function and then effectively breaking early in it on theInExcludeData
iteration when there were some dangling links in it, in short, you made never sure thati
reacheddataCount - 1
and instead stopped when one of the links in the list returnedNone
.If this does not solve your problem, I will have to ask for a more precise explanation of what constitutes "sometimes", as for me it does now what I would expect it to do.
Cheers,
FerdinandThe code:
import c4d import typing doc: c4d.documents.BaseDocument # The active document op: typing.Optional[c4d.BaseObject] # The active object, None if unselected def GetUpdatedInexcludeData(data:c4d.InExcludeData, node:c4d.BaseObject, index:int, isActive:bool=True) -> c4d.InExcludeData: """Inserts a node at a certain index into an InExcludeData. Note: I had to rewrite this because there were some problems with how the function behaved in the case of no or a singular element in the list. """ # Bail when the user tries to add a node which is not part of a document and get the number # of elements in #data. doc: c4d.documents.BaseDocument = node.GetDocument() if not isinstance(doc, c4d.documents.BaseDocument): raise RuntimeError("Cannot add dangling node to InExcludeData") count: int = data.GetObjectCount() # Retrieve all objects in it and their flags as a list of (object, flags) pairs. Object access # can fail with ObjectFromIndex(), which is why we have to check if it returns not None, to # step over these "dangling" slots. This also implicitly assumes that all nodes in #data are # part of the same document #node is part of. To be more robust, you should pass in the document # of the node the InExcludeData #data was taken from, and then also test if #node is part of the # same document. rawData: list[tuple[c4d.BaseList2D, int]] = [ (data.ObjectFromIndex(doc, i), data.GetFlags(i)) for i in range(count) if data.ObjectFromIndex(doc, i) != None ] # Insert the new node at the desired location in the list. Since Booleans are just integers in # disguise, int(#isActive) is equal to #isActive alone, but one can add it for verbosity. newIndex: int = index if index < (len(rawData) - 1 ) else 0 rawData.insert(newIndex, (node, isActive)) # Construct the new InExcludeData where #node is at #index with the flag #isActive. result: c4d.InExcludeData = c4d.InExcludeData() for node, flags in rawData: result.InsertObject(node, flags) return result # 量化旋转 def mogragh_quantize_rotation(doc :c4d.documents.BaseDocument, cloner : c4d.BaseObject) -> c4d.BaseObject : """Inserts a quantize rotation effector into selected cloner.""" doc.StartUndo() # Pass if selection is not a Cloner if cloner is None or cloner.GetType() != 1018544: raise RuntimeError("Select a cloner.") effector = c4d.BaseObject(1021337) # Plain doc.InsertObject(effector,pred=cloner) doc.AddUndo(c4d.UNDOTYPE_NEWOBJ,effector) # --- Change: I removed here code and moved it towards the end of your script ------------------- # --- End of changes --------------------------------------------------------------------------- # FieldObject field_object = c4d.modules.mograph.FieldObject(440000281) # random field object doc.InsertObject(field_object,parent=effector) doc.AddUndo(c4d.UNDOTYPE_NEWOBJ,field_object) # Set Field List fieldList = effector[c4d.FIELDS] layer = c4d.modules.mograph.FieldLayer(c4d.FLfield) layer.SetLinkedObject(field_object) fieldList.InsertLayer(layer) # Quantize Layer quantizeLayer = c4d.modules.mograph.FieldLayer(c4d.FLquantize) if quantizeLayer is None: raise MemoryError("Failed to create a Field Layer.") fieldList.InsertLayer(quantizeLayer) doc.AddUndo(c4d.UNDOTYPE_BITS, quantizeLayer) quantizeLayer.SetBit(c4d.BIT_ACTIVE) # Set effector.SetName(cloner.GetName() + ' ' + 'Plain') effector[c4d.ID_MG_BASEEFFECTOR_POSITION_ACTIVE] = False # Turn off Pos effector[c4d.ID_MG_BASEEFFECTOR_SCALE_ACTIVE] = False # Turn off Scale effector[c4d.ID_MG_BASEEFFECTOR_ROTATE_ACTIVE] = True # Turn On Pot effector[c4d.ID_MG_BASEEFFECTOR_ROTATION,c4d.VECTOR_X] = c4d.utils.DegToRad(360) # Set Rot at H(x) to 360 degrees field_object[c4d.ID_BASEOBJECT_VISIBILITY_EDITOR] = 1 # Set editor unvisible field_object.SetName(effector.GetName() + ' : ' + 'Quantize Rot') # Set Field Name to Effetor name + Function doc.AddUndo(c4d.UNDOTYPE_CHANGE, effector) effector[c4d.FIELDS] = fieldList # Set plain to cloner InExclude inExData = cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] # --- Changes: Your modified and moved code ---------------------------------------------------- # I would add the effector here after you have created all the field object stuff, because it # makes a bit more sense to do it once you are done creating the little effector rig, but it # is not technically required in most cases. # I am not quite sure why you did all the exception handling stuff in your code here, I assume # because you wanted to to capture the case that inExData is not what you expect it to be. I # would simply type check here if you want to be sure. if not isinstance(inExData, c4d.InExcludeData): raise RuntimeError("Whoopsie: ID_MG_MOTIONGENERATOR_EFFECTORLIST does not hold InExcludeData.") # Use your function to construct an InExcludeData where #effector sits at index 0 while copying # over the old data. cloner[c4d.ID_MG_MOTIONGENERATOR_EFFECTORLIST] = GetUpdatedInexcludeData(inExData, effector, 0) doc.AddUndo(c4d.UNDOTYPE_CHANGE, cloner) # --- End of changes --------------------------------------------------------------------------- doc.EndUndo() c4d.EventAdd() # Refresh Viewport return effector if __name__ == '__main__': doc = c4d.documents.GetActiveDocument() cloner = doc.GetActiveObject() mogragh_quantize_rotation(doc,cloner)
- You set your
-
@ferdinand Thanks for that solution.
It's happy to hear that , I do try to make win-win clear post as I can to don't waste our time , hope it's a worthy work
I tested the new
GetUpdatedInexcludeData
function , it worked well , The "click twice" thing never happen again . It's theNone
condition I didn't realize before . It does happend when no or single object in the list .And for the "odd place" and the "exception ", it is a test to make sure all the things above work well( after that are all the settings ) . I forgot move to the right place . sorry to that
Thanks for the caring code for
c4d.utils.DegToRad(360)
, I haven't notice this before and just stupid import radians