Your browser does not seem to support JavaScript. As a result, your viewing experience will be diminished, and you have been placed in read-only mode.
Please download a browser that supports JavaScript, or enable it if it's disabled (i.e. NoScript).
Hi !
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
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:
inExData
InExcludeData inExData
InExcludeData
InsertNodeIntoInExclude
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 the InExcludeData iteration when there were some dangling links in it, in short, you made never sure that i reached dataCount - 1 and instead stopped when one of the links in the list returned None.
i
dataCount - 1
None
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, Ferdinand
The 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)
@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 the None condition I didn't realize before . It does happend when no or single object in the list .
GetUpdatedInexcludeData
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
c4d.utils.DegToRad(360)