Solved 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                            "";

Cheers
Tom

Hello @ThomasB,

Thank you for reaching out to us.

  1. 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.
  2. 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 the str 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 the c4d_strings.str file of res\{LANG_CODE} folder and then load them in with c4d.plugins.GeLoadString. Invoking for example c4d.plugins.GeLoadString(IDS_MY_STRING) will for example then always load in the localized variant of IDS_MY_STRING.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

Hello @ThomasB,

Thank you for reaching out to us.

  1. 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.
  2. 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 the str 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 the c4d_strings.str file of res\{LANG_CODE} folder and then load them in with c4d.plugins.GeLoadString. Invoking for example c4d.plugins.GeLoadString(IDS_MY_STRING) will for example then always load in the localized variant of IDS_MY_STRING.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

@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
Screenshot 2023-05-02 120456.png

if bars set to 3 it has to change the naming but uses the same ID´s
Screenshot 2023-05-02 120529.png

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

Cheers
Tom

Hello @ThomasB,

It depends on what you are exactly doing, but it looks a bit like you are picking the wrong approach.

  1. 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 using Nodedata.GetDEnabling.
    • The same can also be done for cycles by modifying c4d.DESC_CYCLE.
  2. 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 a ID_MYPLUG_RADIUS and a ID_MYPLUG_DIAMETER. The same goes for cycle values which should not be "repurposed".
  3. 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).
  4. 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:

Cheers,
Ferdinand

Code 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))

MAXON SDK Specialist
developers.maxon.net

@ferdinand

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

Cheers
Tom

Hey @ThomasB,

  1. Relabeling parameters will cause the plugin to have one value where it should have two or more.
    1. The user sets the parameter Radius (ID_RADIUS) in a node to the value 5.
    2. The user changes the parameter Mode to Diameter.
    3. The parameter formerly labeled as Radius is now labeled Parameter.
    4. But since it is the same paramater, its ID is still ID_RADIUS, the value is still 5.
    5. The user then changes the value to 10.
    6. The user changes the parameter Mode back to Radius.
    7. The parameter formerly labeled as Parameter is now labeled Radius.
    8. The parameter value is still 10 and the old Radius value has been lost.
  2. 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.
  3. 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

MAXON SDK Specialist
developers.maxon.net

@ferdinand

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

Cheers
Tom

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 overwrite NodeData.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 in NodeData.Init or via DESC_DEFAULT in case of dynamically added parameters of type LONG, REAL, or COLOR/VECTOR.

Cheers,
Ferdinand

MAXON SDK Specialist
developers.maxon.net

@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!

Cheers
Tom

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

MAXON SDK Specialist
developers.maxon.net

@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!

Cheers
Tom