description.SetParameter() Question
-
Hi, sorry for this stupid question,
I have a question about the description.SetParameter() Method:
I do not provide the complete code because it is not necessary for this example.First I checked the language the user is using, and depending on that I changed the description parameter c4d.DESC_NAME of c4d.PY_1BARS_LEFTHOR
I have to change the name dynamically because, I want to use the parameter for more functionalities and have to rename it depending on what method the user is choosing.So my question is: Do I have to use the line below, when I am just changing the DESC_NAME ?
because it also works without this line or do I just need it, when I inserted a new one
It's probably better programming style to cover all eventualities and always add this method, isn't it?if not description.SetParameter(sub_d, db, c4d.DESCID_ROOT): return False
Here is the code-snippet from the "def GetDDescription(self, op, description, flags)" :
def GetDDescription(self, op, description, flags): if not description.LoadDescription(op.GetType()): return False german = False index = 0 while True: lang = c4d.GeGetLanguage(index) if lang["default_language"]: break if lang is None: break index += 1 if c4d.GeGetLanguage(index)["name"] != "Deutsch": german = False else: german = True single_id = description.GetSingleDescID() sub_d = c4d.DescID(c4d.PY_1BARS_LEFTHOR) if single_id is None or sub_d.IsPartOf(single_id)[0]: db = description.GetParameterI(sub_d) if op[c4d.PY_1BARS_LEFT] == 3: if not german: db.SetString(c4d.DESC_NAME, "Segments") else: db.SetString(c4d.DESC_NAME, "Segmente") else: if not german: db.SetString(c4d.DESC_NAME, "horizontal") else: db.SetString(c4d.DESC_NAME, "horizontal") # Do I need this line in this case ========== if not description.SetParameter(sub_d, db, c4d.DESCID_ROOT): return False
Here the entry in the res-file of the PY_1BARS_LEFTHOR:
GROUP { DEFAULT 1; COLUMNS 2; LONG PY_1BARS_LEFT {CYCLE {PY_1BARS_LEFTDEACTIVATED~1060829; PY_1BARS_LEFTMETHOD1~1061024; PY_1BARS_LEFTMETHOD2~1061025;PY_1BARS_LEFTMETHOD3~1061026;}} REAL PY_1BARS_LEFTMETHOD2DISTANCE {MIN 0; MAXSLIDER 100; STEP 0.1; UNIT METER; CUSTOMGUI REALSLIDER;} LONG PY_1BARS_LEFTVERT {MIN 0; STEP 1; MAX 10;} LONG PY_1BARS_LEFTHOR {MIN 0; STEP 1; MAX 10;} }
Here the entry in the german .str file:
PY_1BARS_LEFTHOR "";
Here the entry in the englisch .str file:
PY_1BARS_LEFTHOR "";
-
Hello @ThomasB,
Thank you for reaching out to us.
- No, the Description.SetParameter call in your code is not necessary because the Description.GetParameterI call you use returns the parameter data container instance. Description.GetParameter on the other hand returns a copy, and such container must be written back with
Description.SetParameter
when changes to it should be reflected in the node. - Side stepping the localization system of Cinma 4D should be avoided. But you have the variable
german
in your code, indicating in which language your plugin is running. These strings also are static, so I do not understand why you circumvent thestr
files of the plugin. In case you want these parameter names to be semi-programmatically defined, I would recommend still using the localization system of Cinema 4D. Define thec4d_strings.str
file ofres\{LANG_CODE}
folder and then load them in with c4d.plugins.GeLoadString. Invoking for examplec4d.plugins.GeLoadString(IDS_MY_STRING)
will for example then always load in the localized variant ofIDS_MY_STRING
.
Cheers,
Ferdinand - No, the Description.SetParameter call in your code is not necessary because the Description.GetParameterI call you use returns the parameter data container instance. Description.GetParameter on the other hand returns a copy, and such container must be written back with
-
@ferdinand said in description.SetParameter() Question:
c4d.plugins.GeLoadString(IDS_MY_STRING)
Thank you Ferdinand,
Well, this ID/parameter originally controls the number of horizontal bars. But if the user selects the other bars method from the drop-down menu, I would have to create a new ID in res, header and str-files. I didn't want that because I can also use this int parameter to subdivide a circle. so I used the same ID and just renamed it to make it easier for the user and not get confused.
I did it like this with several parameters...
This ID controls 2 different parameters, so to speak, and has to change its name dynamically depending on the method.So here you can see paramter height, vertical and horizontal.
If Bars is set to 0,1,2 it shows this naming
if bars set to 3 it has to change the naming but uses the same ID´s
I actually only asked on time in the code which language the user has set and write this into a variable, to be able to use it over the whole description and depending on this and the type of method the user chooses, I change DESC_NAME. There are over 6 different types and I had to do that a lot. So in the left window the user chooses method 1 in the right window he chooses method 4 all simultanously. Hmmm
So you mean that I should create an additional str. file with the changed names and then read the parameters from this str. file? So I end up with 2 string files for german and 2 string-files for english? I haven't been using this c4d.plugins.GeLoadString(IDS_MY_STRING).
Cheers
-
Hello @ThomasB,
It depends on what you are exactly doing, but it looks a bit like you are picking the wrong approach.
- In general, it is very uncommon to modify the name of a parameter at runtime unless the parameter has been generated dynamically or the parameter name must be truly dynamic as for example Length Cube.1, i.e., dependent on some scene element.
- When you just want to have condition A under which parameter x is shown, and condition B under which parameter y is shown, you should simply hide and unhide parameters using
c4d.DESC_HIDE
, or alternatively grey them out usingNodedata.GetDEnabling
. - The same can also be done for cycles by modifying
c4d.DESC_CYCLE
.
- When you just want to have condition A under which parameter x is shown, and condition B under which parameter y is shown, you should simply hide and unhide parameters using
- In consequence this means that different things should not share a parameter ID, as this can lead to many problems. So, when an object can have a Radius or a Diameter based on the condition x, these parameters should not share the ID
ID_MYPLUG_RADIUS_OR_DIAMETER
, and instead there should be aID_MYPLUG_RADIUS
and aID_MYPLUG_DIAMETER
. The same goes for cycle values which should not be "repurposed". - Any form of localization should be done with this localization system of Cinema 4D. For description resources that would be simply the description string files (the ones you have simply nulled).
- When parameter names must be dynamic, you can use
c4d.plugins.GeLoadString
as shown below. But that does not seem to be what you should do here, because you do not seem to want to have a parameter name which for example reflects an object name, but rather change the purpose of a parameter at runtime by renaming it. Which is not recommended. See the example at the end for details.
See also:
- How to dynamically hide parameters: Shows how to show and hide parameters at runtime.
- CUSTOMGUI_CYCLE: SetContainer: Shows how to populate a cycle container at the example of user data, but for description data provided in
NodeData.GetDDescription
it is the same.
Cheers,
FerdinandCode for loading localized strings with
c4d.plugins.GeLoadString
.# This pseudo code operates on the assumption of the following folder structure: # # + {root} # The root directory of the plugin. # + res # The resources of the plugin. # + description # The description definition resources of the plugin. # + dialogs # The dialog definition resources of the plugin. # + string_en-US # The English string definitions for the plugin. # + description # The description string definitions for the plugin. # + dialogs # The dialog string definitions for the plugin. # - c4d_string.str # The generic string definitions for the plugin. # + string_de-De # The German string definitions for the plugin. # * ... # + string_fr-FR # The French string definitions for the plugin. # * ... # - c4d_symbols.h # The dialog and generic symbols for the resources. # - somenode.pyp # The plugin entry point file. # The symbols file `res/c4d_symbols.h`: # # #ifndef C4D_SYMBOLS_H__ # #define C4D_SYMBOLS_H__ # # enum # { # IDS_SEGMENTS = 1000, # IDS_HORIZONTAL, # # // Dummy # ___DEFDUMMY_ # }; #endif // C4D_SYMBOLS_H__ # The string definitions for generic English strings `res/string_en-US/c4d_string.str`: # # STRINGTABLE # { # IDS_SEGMENTS "Segments"; # IDS_HORIZONTAL "Horizontal"; # } # The string definitions for generic German strings `res/string_de-De/c4d_string.str`: # # STRINGTABLE # { # IDS_SEGMENTS "Segmente"; # IDS_HORIZONTAL "Horizontal"; # } # The string definitions for generic French strings `res/string_fr-FR /c4d_string.str`: # # STRINGTABLE # { # IDS_SEGMENTS "segments"; # IDS_HORIZONTAL "horizontale"; # } # -------------------------------------------------------------------------------------------------- # Exposing symbols for dialog and general resources does not work out of the box in Python as it # does for descriptions. You must either redefine the integer values for the symbols, e.g., do # this (assuming these were the values defined in your 'c4d_symbols.h'): # ... IDS_SEGMENTS: int = 1000 IDS_HORIZONTAL: int = 1001 # ... import os # Alternatively, you can use the symbol parser. Note that there is a bug with exporting symbols to # the local scope in the current version. Exporting symbols to the global scope works fine and the # bug has been fixed for the next major release of Cinema 4D. # # For more information, see: # https://developers.maxon.net/docs/py/2023_2/manuals/foundation/symbols.html # # There are however also some errors in the current documentation of the symbol parser (will also # be fixed in the next release) # Parse the symbols of this plugin into the global scope, would expose for example IDS_SEGMENTS and # IDS_HORIZONTAL as done above manually. import symbol_parser path: str = os.path.join(os.path.dirname(__file__), "res", "c4d_symbols.h") symbol_parser.parse_and_export_in_caller(path) import c4d class SomeNode(c4d.plugins.NodeData): """ """ def GetDDescription(*args) -> tuple[bool, int]: """ """ if op[c4d.PY_1BARS_LEFT] == 3: # Load in the string for "Segments" based on the locale Cinema 4D is running in and the # translations your plugin does provide. When the user is in a local we did not provide, # e.g., Spanish (es-ES) or Japanese (ja-JP), Cinema 4D will fall back to the default # language en-Us. db.SetString(c4d.DESC_NAME, c4d.plugins.GeLoadString(IDS_SEGMENTS)) else: # Load in the string for "Horizontal" db.SetString(c4d.DESC_NAME, c4d.plugins.GeLoadString(IDS_HORIZONTAL))
- In general, it is very uncommon to modify the name of a parameter at runtime unless the parameter has been generated dynamically or the parameter name must be truly dynamic as for example Length Cube.1, i.e., dependent on some scene element.
-
thank you very much I understand,
but I do not understand complaining about sharing the same ID and just renaming it.
I mean it just changes the name not the value. I need the value in Method 1-3 and also in Method 4? So basically it doesn´t matter how it is called. It should only be a visual confirmation so the user can tell it apart.
I gave it a blank "" string in the string-files, otherwise the SetString() Method doesn´t work.Of course, each object type that I create with the plugin has its own bar method and ID's. And all use their own ID's. Object 1 has this bars method with height, vertical and horizontal,
and Object_2 has its own so the name is then only changed for the corresponding Object.Object_1 c4d_PY_1BARS_LEFT
Object_2 c4d.PY_2BARS_LEFT
Etc.
I did it in this manner because if I use seperate ID´s, just for the height, vertical and horizontal parameter to rename it in radius, streaks and subd, I end up in 15 or more ID´s to setup just for another name. Because I have more Object-Types and each type has this bars-method, also multiple times...
Too bad actually
Cheers
-
Hey @ThomasB,
- Relabeling parameters will cause the plugin to have one value where it should have two or more.
- The user sets the parameter Radius (
ID_RADIUS
) in a node to the value5
. - The user changes the parameter Mode to Diameter.
- The parameter formerly labeled as Radius is now labeled Parameter.
- But since it is the same paramater, its ID is still
ID_RADIUS
, the value is still5
. - The user then changes the value to
10
. - The user changes the parameter Mode back to Radius.
- The parameter formerly labeled as Parameter is now labeled Radius.
- The parameter value is still
10
and the old Radius value has been lost.
- The user sets the parameter Radius (
- Hiding or disabling parameters will not have that issue, you can even tie here setting the radius to setting the diameter if you wanted to, so that both values are always correctly in sync.
- Based on 1., this can also introduce problems when users load presets for a node. Because your node is incapable of expressing a valid state for all parameter combinations (since some are repurposed on the fly), the user could load in a preset which looks and works fine. But as soon as he or she changes the value of the parameter Mode to Diameter, the output of the plugin does not make sense anymore. Only when a node expresses all its states as parameters, can these states also be saved as a preset asset.
Cheers,
Ferdinand - Relabeling parameters will cause the plugin to have one value where it should have two or more.
-
Yes, I know that, but that doesn't matter in this case.
I've already taken that into account, but since you can set a keyframe in the case of an animation, it doesn't really matter with my plugin.But this raises another question for me...
Is it then possible if I use the same ID that when the user toggles the method then set the value to another e.g. to a default value in the message method?
For instance with c4d.MSG_DESCRIPTION_POSTSETPARAMETER
So when the user toggles the drop-down menue to another value and the parameter switches it's label to "radius" that it gets a default value.
Or can I also set the DEFAULT in the res-file and when the user switches the drop-down-menu it sets it to the DEFAULT value which is set in the res-File. How is that working?Cheers
-
Hey @ThomasB,
Yes, I know that, but that doesn't matter in this case.
Maxon is quite off-hands when it comes to UX and GUI guidelines for third parties. We enforce only a few things and most of the time take a 'if it works for you approach'.
From our point of view, it does matter because you are breaking a central interface paradigm of Cinema 4D. Every parameter is unique and therefore has its own editing history. Users are accustomed to this. We will not try to enforce any guidelines here, but I must point out that you are leaving the grounds of scope of support. When you actively decide to ignore our advice, you are welcome to do that (truly, without any saltiness from our side), but you then also own the consequences.
With that being said, you can do all sorts of things, as there are multiple parameter related node messages, and
MSG_DESCRIPTION_POSTSETPARAMETER
is being sent after a parameter has changed. You could also overwriteNodeData.GetDParameter
and.SetDParameter
to more directly mess with the parameter handling, and for example restore a default parameter or even properly emulate the Cinema 4D behavior and keep an editing history of both Radius and Diameter around. But doing all this is not recommended when it is not required.Also, the
DEFAULT
flag can be used to set the initial toggle state of group in a description. It cannot be used to set the default value of the atomic parameter types listed in the description resource manual. The default values of a node must be set inNodeData.Init
or viaDESC_DEFAULT
in case of dynamically added parameters of typeLONG
,REAL
, orCOLOR/VECTOR
.Cheers,
Ferdinand -
@ferdinand
I understand thank you for this detailed explanation.
hm then I have to create 36 new ID's and change the 4600 * 3 lines of code (multi version support), instead to give it a different name.Well it's no use, it has to be.
Thank you!
-
Hey @ThomasB,
then I have to create 36 new ID's and change the 4600 * 3 lines of code
when I said
when you actively decide to ignore our advice, you are welcome to do that (truly, without any saltiness from our side)
I truly meant that. I personally would sometimes also choose my own path because it is less work for me. I personally would not take your route in this case, but different people, different development goals. I simply must point out things like scope of support because cases like this can become a bottomless pit in development. And it is IMHO better to let developers know early when we will not touch certain topics.
I would say the risk that things go south is only small to medium in this case, but I cannot guarantee that. In the end it is your decision.
Cheers,
Ferdinand -
At first I wanted to do it exactly like you recommended, but then I thought why, if you save 36 IDs, it's certainly more memory-efficient. And when you just rename it, you've already learned something again.
Actually, I wanted to do it with separate IDs. But then I recognized that I need the exact same 3 parameters, just with other Labels. but since renaming was faster, I decided to do it. It's an update of the previous version of the plugin and it was easiest to just change the labels.
I'll think about it, I've got time.Thank you because my English is not always the best, I sometimes find it difficult to read your texts. They are already very extensive in some cases.
Good evening!