Hi @m_adam ,
of course, stupid me. I knew it was something simple I’m missing here. Seems I didn’t see the elephant in the room.
This works like a charm now. Thank you!
Allow one follow up question though. If I now want to translate this code to a TagData Plugin I’m pretty sure I don’t wanna use a global variable, do I? Would it be sufficient to make that variable a member variable inside of def __init__(self) of the Tag Plugin class?
as I'm pretty sure I found a way to achieve what I'm after I thought I update this thread. Maybe this will help others as well.
After taking some time to make NodeData.CopyTo() work, I ended up not getting it to work at all.
So I thought about how I could achieve what I'm after a different way. Long story short, I ended up implementing a MessageData plugin as some kind of watchdog for a document. Since its CoreMessage runs on the main thread I can happily insert and delete materials as much as I wish to. (At least I'm hoping so )
The idea behind this goes as follows. I have a timer running and in addition to that I listen for c4d.EVMSG_CHANGE and do some checking to see if the scene needs to update. In my case it's comparing the amount of a specific object against the amount of "specific" materials. If there's a difference I use that difference to delete or insert materials until there's no difference. Once there's no difference I can assign the materials to the objects and let each object control its own material.
To distinguish between materials responsible for my plugin and the ones that aren't I make sure to put a unique plugin id inside the base container of the material I can then check for.
Here's a code snippet of that MessageData:
PLUGIN_ID = "Use your own unique one"
PLUGIN_NAME = "A MessageData plugin."
PLUGIN_INFO = 0
self._time = 1000
def SetTimer(self, time):
self._time = time
is_mainthread = c4d.threading.GeIsMainThread()
check_running = (
is_running = any(item is True for item in check_running)
return is_mainthread and not is_running
def CoreMessage(self, mid, mdata):
if not self.should_execute:
doc = c4d.documents.GetActiveDocument()
# SceneHandler is a custom class I delegate the whole creation and comparing stuff to.
objs, mats = ..., ...
scene = SceneHandler(objs, mats)
# Check for a change and start the timer again. But only if the scene should update. Otherwise the timer would run all the time.
if mid == c4d.EVMSG_CHANGE:
# If we get a timer event we update the scene as long as it shouldn't update anymore. We can then stop the timer.
if mid == c4d.MSG_TIMER:
if not scene.should_update:
Maybe this will help others. Since I found a solution for my problem this thread can be marked solved.
no worries, you have for sure a load to do around here. Thanks for taking the time.
Okay I see, NodeData.CopyTo() then.
Regarding the "document-getting-thing". I don’t get the document via GetActiveDocument() nor via node.GetDocument(). I use the data argument of NodeData.Message()instead.
I don’t recall if I figured this out for myself or where I got this from. Or even do I know if it is allowed at all.
BUT it is working. This way I can reliably get the document.
after fiddling around some time on my own, I feel it's finally time to reach out to you.
The Situation is as follows. I have an Object Data Plugin that has a texture tag on it with a material assigned to it. I create the tag and the material in def message of the Object Plugin checking forc4d.MSG_MENUPREPARE.
So this is workinig and all good and well. But when I copy the object in the Object Manager the material does not get copied. So I end up with multiple Objects that share the same material. However creating a new instance of the plugin by getting it from the plugins menu, will of course create a new material since c4d.MSG_MENUPREPARE gets called here.
While I'm pretty sure this behaviour is fine for most cases, my plugin is relying on the fact that each instance gets its own material (the node's parameters changes values of the material).
So how can I achieve that every instance of the plugin gets its own material?
ah, okay I see. I was afraid that this would be the case.
However, thank you for clarification. You can close this thread if you like.
@m_adam alrighty got it working.
Thanks again. You can close this thread if you like.
I have a problem with filling a dropdown cycle dynamically with options. Right now I’m doing this with userdata for the sake of simplicity. But in the end I want to use it in a Tag Plugin…
So, the setup is as follows. I have a cube (just to have a dummy host object to test the code with) that holds a python tag. The python tag holds the userdata dropdown cycle with the options I want to choose from. Inside the code I swap out the entries of the dropdown cycle based on the condition whether the fillet switch of the cube is active.
So the endgame here is to react to changes of the host object.
While this is all nice and dandy I encounter a problem I can’t seem to resolve. When ever I switch the fillet of the cube the dropdown cycle entries are changed accordingly BUT after the switch the dropdown cycle somehow seems to remember the last selected index and tries to set an entry with this remembered index as the "selected" one. This obviously fails if the entries being switched are of different length. So the entries are there but the dropdown cycle appears so to speak blank.
So question here is, how can I tell the dropdown cycle after the switch to select from the "new" indices. I’m probably missing something simple here.
#Welcome to the world of Python
options1 = [
options2 = [
cube = op.GetObject()
fillet = cube[c4d.PRIM_CUBE_DOFILLET]
bc = c4d.GetCustomDatatypeDefault(c4d.DTYPE_LONG)
cycle = c4d.BaseContainer()
if not fillet:
for i, option in enumerate(options1):
for i, option in enumerate(options2):
op.SetUserDataContainer([c4d.ID_USERDATA, 1], bc)
I tried your code as well but still no luck…
However in R20 it is working as expected. So maybe after all it is R18 related?
I’m sorry, but nope that doesn’t change anything. I mean you’re right Python 2.7 is doing integer division by default but forcing a float division doesn’t do it for me here. As you can see in my screencast the UI is kind of blocked when I hit the button for „do something longer“. The progress bar only shows up if I comment self.StopProgress(). BUT it is then showing up completely full and not constantly updating like in Manuels example.
By now I slowly get the feeling that it is simply not working this way. To me it looks like the UI is blocking the update process. But then again I don’t understand why it is working in Manuels example. Since he is clearly executing the code on macOS as I do.
hmm… that’s weird. Tried that code as well but the progress bar is still not showing up.
I recorded a small screen capture for you to see what i meant by saying: “…I don’t even get a bar to see…“
Maybe this will make things clearer.