Parameter linking aka relative references
-
Hello guys, I got a question related to the GetDataInstance() Module in Cinema4d's Python tag. I got this code that I found on an old youtube channel and I wanted to test it out
import c4d source = op[c4d.ID_USERDATA,1]; #define user data source target = op.GetObject(); #define tag owner def main(): oDat = source.GetDataInstance(); #get the linked object's data target.SetData(oDat) #set tag owner's data to source
The problem with the code is its inconsitency, it only updates the values when either you change the values of the target manually then it pops into place or if you pull the timelinebar away from zero and back to zero then it updates and you can play around sometimes with the source values and it should copy, but this is not the case if you use the same python tag for lets say a helix, it would not update automatically nor if you mess with the timeline bar, and so I was wondering if anyone could figure out if this was due to how cinema4d updates its values/object history over time or anything in particular.
I am aware parameter linking can be done manually, but GetDataInstance applies it to all parameters instead of you choosing individualy.
Here is the file:
ParameterLinking.c4d -
Hello @Jonas-Mortensen,
Welcome to the Plugin Café forum and the Cinema 4D development 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 Guidelines, as they line out details about the Maxon SDK Group support procedures. Of special importance are:
- Support Procedures: Scope of Support: Lines out the things we will do and what we will not do.
- Support Procedures: Confidential Data: Most questions should be accompanied by code but code cannot always be shared publicly. This section explains how to share code confidentially with Maxon.
- Forum Structure and Features: Lines out how the forum works.
- Structure of a Question: Lines out how to ask a good technical question. It is not mandatory to follow this exactly, but you should follow the idea of keeping things short and mentioning your primary question in a clear manner.
About your First Question
I think there are some misconceptions going one regarding to what tags do in Cinema 4D and why your code does what it does. We also had some troubles understanding your goals.
I am aware parameter linking can be done manually, but GetDataInstance applies it to all parameters instead of you choosing individualy.
That is not quite true, depending on how literal one is with the word "linking". You cannot establish a binding between two parameters in the API without implmenting that yourself, i.e., there is no API function for that.
I could speculate here more about your intentions, but I leave it to my code example to line out some details.
Cheers,
FerdinandPS: Please do not use semicolons as statement delimters in Python - it makes my skin crawl
Updated file: parameterlinking.c4d
Code:
"""Demonsstrates at three examples how and when expressions, i.e., tags are executed. """ import c4d op: c4d.BaseTag # The Python tag holding this code. def main(): # When you define these values outside of #main, i.e., in the module scope, they will not # update when the user changes the link or moves the tag. link: c4d.BaseList2D | None = op[c4d.ID_USERDATA, 1] # Our BaseLink host: c4d.BaseObject = op.GetMain() # And the object hosting the tag. mode: int = op[c4d.ID_USERDATA, 2] # User data to select between the three example sections. if mode == 0: # BaseLink fields can contain other things than objects. We should also avoid copying # data from one object sub-type to another, e.g., from Ocube to Osplinecircle for example. if not isinstance(link, c4d.BaseObject) or link.GetType() != host.GetType(): return # The implicit question of your is "why does this work?". And the answer is different than # what you might think. Calling BaseList2D.SetData will copy data from the input container, # so calling BaseList2D.GetDataInstance over BaseList2D.GetData is irrelevant. host.SetData(link.GetDataInstance()) return if mode == 1: # More over, using a BaseLink is irrelevant here too. We could change the code to this, and # it would still work. So, this does not work because you linked #link in the ui of #op. We # can demonstrate this by just blindly picking the first object in the document. link: c4d.BaseObject | None = op.GetDocument().GetMain() if not isinstance(link, c4d.BaseObject) or link.GetType() != host.GetType(): return host.SetData(link.GetData()) return if mode == 2: # There reason is that when you manipulate the parameters of the node #link, you will cause # the passes to be executed on the document, which means that caches are rebuilt, as for # example the geometry cache of #link to reflect its new parameter state, but also the # expression pass, i.e., all tags in the scene. Which will trigger then this code here. But # there are user actions which do not invoke the passess being invoked on a document. # We can demonstrate this by copying over the transform of #link to #host instead of its # data container. Manipulatiiong the transform of an object will not cause the scene to be # reevaluated, which is why we will see no change on #host when we transform #link if not isinstance(link, c4d.BaseObject): return host.SetMg(link.GetMg()) # Expressions, a.k.a., tags, are a dedicated state in the state machine that is Cinema 4D's # scene execution. For details see https://tinyurl.com/4r7v6wta . # # Neither can "parameter linking [...] be done manually" as you have put it, nor does an # expression link evvents of two entities. Expressions are executed when the expression pass # is called on a document. There are some exceptions: # # * Messages sent to objects are sometimes propagated to their hosted tags. # * Appart from pass execution, tags are also excuted on events being added, e.g., the user # changing the object selection in the object manager. # * The Python Programming tag has the dedicated parameter "Frame Dependent" in its "Basic" # tab to force tag execution on each frame on playback.
-
Hello Ferdinand,
I have now read the Guidelines, I sincerely apologize for that oversight, but nonetheless thank you for taking time to write back to me.
Yes I obviously have had a big misconception in how cinema4d worked with code and how linking would be done. I should have read what I send twice before doing so.
My idea is the "Relative references" you have in Houdini, where you can link two values to each other with a relative value to the host. The reason I said it could be done manually was me trying to explain that you can also do it with an Xpresso Driver. The reason for that was because you would have to assign the first driver to the input you would like to control to the other input that you want controlled by the first input. The reason I wanted it to be automatically was so it could be procedural and you would just need the tag and reference the "source" like doing something that clones all values in real time without having to manually set the drivers for each parameter.
Best regards
Jonas Mortensen -
Hey @Jonas-Mortensen,
first of all, you did nothing wrong We just put out this text to all new users because we know things are otherwise easy to miss.
And I got that you wanted to reference things in such manner, which is why I dissected it like this. And while Cinema 4D itself offers that functionality to some degree as pointed out by yourself, doing the same in the API is more complicated than one might assume.
In the end, Cinema 4D is very event/message oriented in its classic API, which makes it hard to implement things like scrubbing. A cleaner way to implement things (which does not work just accidentally in a certain sense), would be to implement a
MessageData
plugin or anythingGeDialog
based and implemtn theCoreMessage
method to listen forEVMSG_CHANGE
. It will be emitted on eachEventAdd()
and you will be able to track all scene change events with it, e.g., also a transform changing.But note that only the information will be emitted that
EVMSG_CHANGE
occurred and not what changed, you must figure that out yourself.EVMSG_CHANGE
will also not cover things like the user scrubbing the timeline, etc. But you could use the other event messages for that.Cheers,
Ferdinand -