Changing material projection in takes using Python
-
Hi,
I've been trying to change material projection for the materials added to take system, but it doesn't seem to be working. I can generate takes, add material tags, but when trying to change default spherical projection into uwv (using textag[c4d.TEXTURETAG_PROJECTION] = 6) - there is no effect. I also tried to move the code after group tag, and even make the current take active, and then modify the value of the projection - still the projection stays the same as before modification. I spent a few hours trying to find what's wrong , but unable to find any solution
Here is my code snippet. Would be grateful if anyone could help me, by pointing me in the right direction.if len(listOfMaterials2Swap) > 0: for mat in listOfMaterials2Swap: matOnTheScene = doc.SearchMaterial(mat) if matOnTheScene is None: print ("Mat : " + mat + " not found on the scene") quit() else: #print ("Creating take for mat: " + mat) if takeData is not None: materialTake = takeData.AddTake(roomName + "_" + mat, parentTake, None) if materialTake != None: group = materialTake.AddOverrideGroup() group.SetName("Take for " + roomName + "_" + mat) group.SetEditorMode(c4d.MODE_ON) group.SetRenderMode(c4d.MODE_ON) textag = c4d.TextureTag() for currentMeshName in listOfMeshes4MatSwap: currentMesh = doc.SearchObject(currentMeshName) currentMesh.KillTag(5616) currentMesh.InsertTag(textag) #Remove texture tag for current object, if already there textag[c4d.TEXTURETAG_PROJECTION] = 6 #Change current projection to UVW group.AddToGroup(takeData, currentMesh) group.AddTag(takeData,c4d.Ttexture,matOnTheScene)
-
Hi @Futurium,
thank you for reaching out to us. It is rather hard to untangle what is going wrong there for you, since you only show a snippet and do not tell us in which environment it does run. I assume this is a script manager script? The way you remove the old tag and then add a new take seems a bit fishy to me, you might have to update the scene graph there, but I would have to try that myself.
However, it seems also to be a bit overkill for what you are trying to do. Why not change just the projection of the existing tag? Below you will find an example for creating a take for a projection change and the scene file I did ran it on. @m_adam also wrote a bunch of nice take system examples, where I did ninja most of my code from
Cheers,
Ferdinandexample file: pc13077_scene.c4d
"""Will create a take entry for a projection change to TEXTURETAG_PROJECTION_UVW on the selected objects texture tag. """ import c4d def main(): # Checks if there is an active object. if op is None: raise ValueError("op is none, please select one object.") # Gets the TakeData from the active document (holds all information about # Takes) takeData = doc.GetTakeData() if takeData is None: raise RuntimeError("Failed to retrieve the take data.") # Gets the active Take and check it's not the main one take = takeData.GetCurrentTake() if take.IsMain(): raise RuntimeError("The selected take is already the main one.") # Gets the material tag of the cube. tag = op.GetTag(c4d.Ttexture) if tag is None: raise RuntimeError("Blah, no texture tag on selected object.") # The single level DescId for the projection parameter. descId = c4d.DescID( c4d.DescLevel(c4d.TEXTURETAG_PROJECTION, c4d.DTYPE_LONG, 0)) newValue = c4d.TEXTURETAG_PROJECTION_UVW # Add an override if this parameter is not already overridden, otherwise # returns the already existing override. overrideNode = take.FindOrAddOverrideParam( takeData, tag, descId, newValue) if overrideNode is None: raise RuntimeError("Failed to find the override node.") # Updates the scene with the new Take overrideNode.UpdateSceneNode(takeData, descId) # Pushes an update event to Cinema 4D c4d.EventAdd() if __name__ == '__main__': main()
-
@zipit said in Changing material projection in takes using Python:
TEXTURETAG_PROJECTION_UVW on the selected objects texture tag. """ import c4d def main(): # Checks if there is an active object. if op is None: raise ValueError("op is none, please select one object.") # Gets the TakeData from the active document (holds all information about # Takes) takeData = doc.GetTakeData() if takeData is None: raise RuntimeError("Failed to retrieve the take data.") # Gets the active Take and check it's not the main one take = takeData.GetCurrentTake() if take.IsMain(): raise RuntimeError("The selected take is already the main one.") # Gets the material tag of the cube. tag = op.GetTag(c4d.Ttexture) if tag is None: raise RuntimeError("Blah, no texture tag on selected object.") # The single level DescId for the projection parameter. descId = c4d.DescID( c4d.DescLevel(c4d.TEXTURETAG_PROJECTION, c4d.DTYPE_LONG, 0)) newValue = c4d.TEXTURETAG_PROJECTION_UVW # Add an override if this parameter is not already overridden, otherwise # returns the already existing override. overrideNode = take.FindOrAddOverrideParam( takeData, tag, descId, newValue) if overrideNode is None: raise RuntimeError("Failed to find the override node.") # Updates the scene with the new Take overrideNode.UpdateSceneNode(takeData, descId) # Pushes an update event to Cinema 4D c4d.EventAdd() if __name__ == '__main__': main()```
Thank you @zipit
I've tried as suggested but still doesn't seems to be working the way I wanted.
I modified my code and included your suggestions, as well as my test scene, so you can see what's my issue. I bet it's something simple ;-).import c4d import os from c4d import documents listOfParentTakes = [] listOfChildTakes = [] objects = ["Plane","Plane.1","Plane.2"] def GetListOfParentTakes(): takeData = doc.GetTakeData() if takeData is None: return mainTake = takeData.GetMainTake() take = mainTake.GetDown() while take is not None: listOfParentTakes.append((take)) take = take.GetNext() def GetListOfChildrenTakes(): for parent in listOfParentTakes: take = parent.GetDown() while take is not None: listOfChildTakes.append(take) take = take.GetNext() # Main function def main(): c4d.CallCommand(13957) # Clear Console doc = documents.GetActiveDocument() takeData = doc.GetTakeData() GetListOfParentTakes() GetListOfChildrenTakes() if takeData is not None: for el in listOfChildTakes: print ("Current take name : "+ el.GetName()) takeData.SetCurrentTake(el) for currentObject in objects: obj = doc.SearchObject(currentObject) if obj is not None: # Gets the material tag of the cube. tag = obj.GetTag(c4d.Ttexture) if tag is None: raise RuntimeError("Blah, no texture tag on selected object.") # The single level DescId for the projection parameter. descId = c4d.DescID(c4d.DescLevel(c4d.TEXTURETAG_PROJECTION, c4d.DTYPE_LONG, 0)) newValue = c4d.TEXTURETAG_PROJECTION_UVW # Add an override if this parameter is not already overridden, otherwise # returns the already existing override. overrideNode = el.FindOrAddOverrideParam( takeData, tag, descId, newValue) if overrideNode is None: raise RuntimeError("Failed to find the override node.") # Execute main() if __name__=='__main__': main()
-
Hi @Futurium,
first of all your code is missing the instructions to actually update the take graph (see lines 62 and 63 in the attached example), and secondly you should try to include error messages/exceptions in the future, so that we know that we are talking about the same thing. When I run your code with a default startup Cinema 4D configuration, it will raise
Failed to find the override node.
defined in your code, meaningel.FindOrAddOverrideParam
on line 57 failed. This is caused by Cinema not being inLock Overrides
mode by default, enabling it will let your code complete on my machine.If you meant something else with "doesn't seems to be working the way I wanted", I would have to ask you to clarify what you would consider not to be working.
Cheers,
Ferdinandimport c4d import os from c4d import documents listOfParentTakes = [] listOfChildTakes = [] objects = ["Plane","Plane.1","Plane.2"] def GetListOfParentTakes(): takeData = doc.GetTakeData() if takeData is None: return mainTake = takeData.GetMainTake() take = mainTake.GetDown() while take is not None: listOfParentTakes.append((take)) take = take.GetNext() def GetListOfChildrenTakes(): for parent in listOfParentTakes: take = parent.GetDown() while take is not None: listOfChildTakes.append(take) take = take.GetNext() # Main function def main(): c4d.CallCommand(13957) # Clear Console doc = documents.GetActiveDocument() takeData = doc.GetTakeData() GetListOfParentTakes() GetListOfChildrenTakes() print (listOfChildTakes) if takeData is not None: for el in listOfChildTakes: print ("Current take name : "+ el.GetName()) takeData.SetCurrentTake(el) for currentObject in objects: obj = doc.SearchObject(currentObject) if obj is not None: # Gets the material tag of the cube. tag = obj.GetTag(c4d.Ttexture) if tag is None: raise RuntimeError("Blah, no texture tag on selected object.") # The single level DescId for the projection parameter. descId = c4d.DescID(c4d.DescLevel(c4d.TEXTURETAG_PROJECTION, c4d.DTYPE_LONG, 0)) newValue = c4d.TEXTURETAG_PROJECTION_UVW # Add an override if this parameter is not already overridden, otherwise # returns the already existing override. overrideNode = el.FindOrAddOverrideParam( takeData, tag, descId, newValue) if overrideNode is None: raise RuntimeError("Failed to find the override node.") # Lines your code was missing overrideNode.UpdateSceneNode(takeData, descId) c4d.EventAdd() # Execute main() if __name__=='__main__': main()
-
Hi @zipit,
I'm sorry if I wasn't clear what I was trying to achieve. I used your updated script and recorded a short gif, which shows, the projection materials I'm trying to change in takes stays the same after running the script. I'm expecting the projection to change from current Spherical to new UVW Mapping. What am I missing?Best regards,
Tomasz -
Hi @Futurium,
we have seen your issue and I will report back when we have an answer, which might take a while, because I had to reach out to the animation team.
Cheers,
Ferdinand -
Hi @zipit Not sure what do you mean. The problem is not related to animation.
I recorded my screen when tried your solution, and shared with you the results of the script, which were not what I was expecting - the projection stays "Spherical" after running the script in the Script Manager. -
Hi @Futurium,
your problem is related to the Take System which is being handled by Maxon's animation development team. I do understand your problem, and you can reproduce it in Cinema 4D itself (without Python). I just reached out to the animation team so ask them if what you are trying to do is simply not intended to be done or a bug.
I will give you a heads up here when I did hear back from the animation team.
Cheers,
Ferdinand -
Hi @zipit
Thank you for your clarification.
Is there any other way of doing that? Currently, I'm trying to modify projection for existing takes, but if there is a way to do it from scratch when the take is created - that could work for now.
Basically what I'm trying to do is to create multiple different takes per room, and for every take - apply the same material to different floor meshes (so if you're in bedroom1 and you change carpet - the carpet material would change on all other defined the rooms)
I don' mind to send you my current code + files we use to generate the takes, but that's not something I would be able to share on the public forum.
Best regards,
Tomasz -
Hi @Futurium,
The problem here for us at the SDK-Team is that we are not so sure if what you are trying to do is encountering a bug or simply not intended to be done. I spoke a bit with one of the devs and they will have a look at that odd behavior (which can be reproduced in Cinema without any code), but it will probably take us some time, because we are currently quite busy.
The "problem" with your approach is that you are trying to pile a parameter override on top of tag which is crated by an override group. This is not how it is usually done in Cinema's Take system, you normally just edit that "virtual tag". If you try to do manually what your script does, Cinema will simply delete these overrides once you change the current take (which is also what happens in your script, the last take will contain the UVW-override, but it will not be respected by Cinema).
I personally would say this level of complexity - to create an attribute override for such "virtual tags" - was never intended in Cinema's take system, but I might be wrong. You can of course still do it in Python like you would normally do it Cinema, i.e., simply edit the override group tag. I have attached a script example based on your last script which does that. It will change the projection of your material tags to UVW, but won't do it with the same implied complexity as your example does it (by creating a specific attribute override for it).
Happy holidays,
Ferdinand"""Example for "working around" the limitations of take system override groups as discussed in: https://developers.maxon.net/forum/topic/13077 """ import c4d def yieldSecondLevelTakes(): """Yields all second level take nodes in the scenes take graph. Your functions, just slightly refactored. Kept this way for clarity, but could be done in just one function instead of your two function design. """ def yieldTopLevelTakeNodes(): """Yields the top level take nodes of a document. """ takeData = doc.GetTakeData() if takeData is None: return mainTake = takeData.GetMainTake() take = mainTake.GetDown() while take is not None: yield take take = take.GetNext() for take in yieldTopLevelTakeNodes(): chldTake = take.GetDown() while chldTake is not None: yield chldTake chldTake = chldTake.GetNext() def main(): """Entry point. """ # Clear Console c4d.CallCommand(13957) # Not necessary, doc is predefined in a script. # doc = documents.GetActiveDocument() # Terminate branches early so that you have less indented code (and it # technically also runs a bit faster). takeData = doc.GetTakeData() if takeData is None: raise RuntimeError("No take data in the document.") # Loop over all second level take nodes like you want to do. for take in yieldSecondLevelTakes(): print ("Current take name : " + take.GetName()) # Not needed # takeData.SetCurrentTake(take) # This is not necessary in your scene, since all your objects # have the same override group tag, i.e. changing a parameter # on one tag will change the respective parameters on all other # 'instances' of the tag. # for currentObject in objects: # obj = doc.SearchObject(currentObject) # ... # This should be technically moved outside of the loop for # performance reasons, kept here for parity reasons with your script. node = doc.SearchObject("Plane") if node is None: raise RuntimeError("Could not find targeted object node.") # The first override group, there could be more of course in a more # complex scene. overrideGroup = take.GetFirstOverrideGroup() if overrideGroup is None: continue # Get the material tag associated with this override group. tag = overrideGroup.GetTag(c4d.Ttexture) if tag is None: continue # The "workaround" here is to simply write to the tag. This of # course will not give you the same complexity as one could # imagine with takes, but since the tag is a BaseOverride tag, # i.e. a tag that itself is bound to a Take, your data will # still be conditional in that sense. tag[c4d.TEXTURETAG_PROJECTION] = c4d.TEXTURETAG_PROJECTION_UVW # Notify Cinema 4D for updates. c4d.EventAdd() # Execute main() if __name__=='__main__': main()
-
Hi,
without further feedback, we will consider this thread as solved by Wednesday and flag it accordingly.
Cheers,
Ferdinand -
Thank you @zipit
I adopted your solution into our code and it works exactly as expected.
Thank you for your help.
Best regards,
Tomasz