Confusing ToolData content



  • I am playing with the Attribute Manager, and tried reading out the ToolData of some selection tools using following script:

    # Read out the BaseContainer from ToolData
    import c4d
    
    def main():
        print " --- read ToolData BaseContainer ---"
        toolID = doc.GetAction()
        tool = c4d.plugins.FindPlugin(toolID, c4d.PLUGINTYPE_TOOL)
        print "Active tool = %s (%i)" % (tool.GetName(), toolID)
        # parse the tooldata
        toolbc = c4d.plugins.GetToolData(doc, toolID)
        i=0
        for elementID, value in toolbc:
            elementType = toolbc.GetType(elementID)
            print "%i ID: %i, Type: %i, Value: " % (i, elementID, elementType), value
            i+=1
    
    if __name__=='__main__':
        main()
    

    The output for the Live Selection tool was as expected. All attributes of the tool being visualized in the Attribute Manager can be found back in the printed output.
    But then I selected the Rectangle Selection and executed the script once more. To my surprise very few attributes were printed out. Same for the Lasso Selection and Polygon Selection.

     --- read ToolData BaseContainer ---
    Active tool = Live Selection (200000083)
    0 ID: 2108, Type: 15, Value:  1
    1 ID: 2109, Type: 15, Value:  10
    2 ID: 2200, Type: 15, Value:  0
    3 ID: 2124, Type: 15, Value:  0
    4 ID: 2125, Type: 19, Value:  1.0
    5 ID: 2169, Type: 15, Value:  1
    6 ID: 2163, Type: 15, Value:  0
    7 ID: 2164, Type: 15, Value:  0
    8 ID: 2201, Type: 15, Value:  0
    9 ID: 2202, Type: 19, Value:  100.0
    10 ID: 2203, Type: 19, Value:  1.0
    11 ID: 2204, Type: 15, Value:  0
    12 ID: 2205, Type: 15, Value:  0
    13 ID: 2206, Type: 15, Value:  0
    14 ID: 2207, Type: 19, Value:  0.5
    15 ID: 2209, Type: 15, Value:  1
    16 ID: 2208, Type: 1009060, Value:  <c4d.SplineData object at 0x000000B81496D250>
    17 ID: 2129, Type: 15, Value:  0
    18 ID: 2130, Type: 19, Value:  1.0
    19 ID: 2131, Type: 19, Value:  0.0
    20 ID: 2132, Type: 19, Value:  1.0
    21 ID: 20000, Type: 15, Value:  2
    22 ID: 20001, Type: 15, Value:  0
    23 ID: 20003, Type: 19, Value:  0.0
    24 ID: 20004, Type: 19, Value:  0.0
    25 ID: 20005, Type: 19, Value:  0.0
    26 ID: 20006, Type: 15, Value:  0
    27 ID: 705, Type: 15, Value:  1
    28 ID: 20002, Type: 15, Value:  0
     --- read ToolData BaseContainer ---
    Active tool = Rectangle Selection (200000084)
    0 ID: 2107, Type: 15, Value:  1
    1 ID: 2106, Type: 15, Value:  0
    2 ID: 705, Type: 15, Value:  1
     --- read ToolData BaseContainer ---
    Active tool = Lasso Selection (200000085)
    0 ID: 705, Type: 15, Value:  1
     --- read ToolData BaseContainer ---
    Active tool = Polygon Selection (200000086)
    0 ID: 705, Type: 15, Value:  1
    

    So, I am rather confused about the content of the ToolData for the last 3 selection tools.
    These printed output don't reflect the tool's attributes seen in the Attribute Manager. Are these attributes stored somewhere else perhaps?
    Or is my script wrong in some way?


  • Global Moderator

    Hi,

    no your script is not wrong. There's just a bit more to it.

    1. To state the obvious, there's no reason, why different tools should have the same ID for a parameter's data, even if these parameters are called identically in the user interface.
    2. Just like every other NodeData derived entity, a tool does not necessarily store it's parameters in the BaseContainer. Or it may store values in the BaseContainer, only, if it's different from default. Either way, you have no guarantee to find all parameters in the BaseContainer. Rather use GetParameter() (so in your case for example tool.GetParameter(2108, c4d.DESCFLAGS_GET_0))

    Cheers,
    Andreas



  • Thanks Andreas,
    I get your first point, and wasn't actually expecting same IDs. But in case of Lasso tool I didn't expect to only get a single attribute.
    However, for your second point. I can relate not all attributes to be available in the BaseContainer ... if that is how it was designed.
    And I am fine with using GetParameter if that gets the job done. But how am I supposed to know to get parameter with ID 2108 in your example?
    I mean, if I cannot browse through a BaseContainer in order to find out which attribute IDs are available, how could I then perform a GetParameter? How do I know which attributes are available to a tool?

    So, I guess the Live Selection was just a lucky shot, where everything was available in the BaseContainer obtained via GetToolData?


  • Global Moderator

    You could for example check the resource files for available parameters.
    Or you could call GetParameter() for a certain ID range, it will return None, the parameter doesn't exist.



  • Sorry, both are not an option in this case.
    The purpose is to automatically read out settings of a tool (plugin in general), store it, perform changes, use the tool, and reapply the original settings.


  • Global Moderator

    Hi,

    you can also use the Description.

    With a small loop, you can get information about a tool's parameters and their type:

        toolID = doc.GetAction()
        tool = c4d.plugins.FindPlugin(toolID, c4d.PLUGINTYPE_TOOL)
        desc = tool.GetDescription(c4d.DESCFLAGS_DESC_0)
        for bc, paramid, groupid in desc:              # Iterate over the parameters of the description
            print bc[c4d.DESC_NAME], paramid[0].dtype  # Print the current parameter name and type
    

    It sounds a bit, as if you try to create some kind of undo system for tools. Something Cinema 4D does not have currently. The standard undo system for example works by cloning instances, something that is not possible with tools. But of course the cloning automatically takes care of the entire instance's state, regardless, if parameters are stored in the BaseContainer or if the complete state is really defined by parameters.
    So, just be aware, even with the above approach, there might still be information stored inside of a tool, which may not be reflected by the Description, either. I have no examples for this, it is more a word of a warning, something like this might come up.

    Cheers,
    Andreas



  • I am looking into the GetDescription solution you discussed. It takes a little more to translate from Python to C++, but I see the possibilities. Although this introduces more clutter to sift through.
    I guess I was spoiled with the result I got from just testing out the Live Selection and Extrude tools, in order to see how to design the whole framework behind.
    Didn't think other (native) tools would be so problematic.

    I am not working on some kind of undo system, more a workflow enhancement.
    My Dials plugin allows to quickly set up a set of most used tools. People refer to it as a radial menu, but it definitely is not.
    One of its feature was the ability to provide for a set of "favorites" where most used settings for tool could easily be "cached" and quickly retrieved.
    From my modeling times (before I started spending most of my time on plugin development) I remember having constantly the need to have 2 or 3 bevel settings at hand. As such I needed to modify the values constantly. With the introduction of Dials and its favorites, I now would simply store a few settings and retrieve them on the fly.

    A quick work-in-progress demonstration (of the favorites idea) if I may ...
    Dials "favorites" WIP

    Hope this helps put the "get basecontainer" idea into context.


  • Global Moderator

    Hi,

    thanks for the insight. Sounds nice!
    I wasn't aware you were looking for a C++ solution, as the initial post contained Python code and als the thread's tag said Python... well, now you'll port it yourself. ;)

    If I may suggest something, after looking at the video. Why not simply show the description of the tool in your dialog (like the Active Object Manager example does)? Then the user could set all parameters in one step and save them to a preset with a click.

    Cheers,
    Andreas



  • Strange the thread has only a Python tag. I was almost sure to have only enabled R19, R20 and C++ tags.
    Maybe I was not fully awake when I created the thread ... or to focused on copying the C++ code into a Python script to demonstrate the purpose. (Updated the tags)

    Anyway. Converted your GetDescription example to C++ (or at least to the best of my knowledge).

    Your suggestion to show the description of the tool in a dialog is what I was trying to avoid.
    I would like to keep the dialog small, and want to avoid duplicating the Attribute Manager. So, the drag and drop is the better solution (in my opinion).
    Besides, if I would want to display the description, I would still need to parse it.

    Currently I store the basecontainer obtained from

    BasePlugin* const plug = static_cast<BasePlugin*>(draggedAtom);
    const Int32 id = plug->GetID();
    BaseContainer* tooldata = GetToolData(GetActiveDocument(), id);
    
    mDialWidget->StoreFavorite(tooldata);
    

    Guess I 'll need to do something similar, but with the obtained description.
    ... Will keep you posted.
    Thanks for thinking along.



  • Had a closer look at Description and while it was easy enough to work with BaseContainer (obtained from GetToolData), I see some troubles with the former.
    First, no default constructor is publicly available. This means I'll need to work with pointers (AutoAlloc). Not a big issue.
    But I also don't see any CopyTo nor CopyFrom available. Nor cloning.
    Furthermore, when I want to store the retrieved description from a tool, I would do so into a plugin container (GetWorldPluginData).

    Couldn't find anything related to a Description Manual, so I am not really visualizing what a Description actually is, besides a list of descriptions.
    I will need to investigate some more, and figure out how to actually "process" the obtained description in order to store and retrieve all of its data, and apply it back to a tool.
    Easy peasy with a basecontainer, not so with a description ...

    Still don't get the point why most tools have all stored into a single basecontainer, while lasso, rectangle and polygon selection tools deviate from this. There might be some others tools which deviate as well, but haven't encountered them yet.
    So, what's the point of having a GetToolData when it doesn't actually returns the tool data ???


  • Global Moderator

    @c4ds said in Confusing ToolData content:

    Couldn't find anything related to a Description Manual

    Hello,

    there is a Description Manual.

    best wishes,
    Sebastian


  • Global Moderator

    Regarding the embedded description GUI, I was just thinking loudly. I understand your design decision completely.
    And actually I didn't want to imply, you should store the Descriptions (after all these contain no values). I thought more of a combination of the GetParameter() approach (where you were lacking the information, which parameters exist) and the description code I suggested. So you could during your initialization (in your case probably, when the user drags a new tool onto the wheel) find out about all available parameters and with this prepare a BaseContainer to store whatever you need later on.

    Cheers,
    Andreas



  • In the meantime I am able to parse the Descriptions and iterate over the available entries and perform a GetParameter.
    A NodeData derived object can implement GetDEnabling to indicate if an element is to be set enabled or disabled. How would one go about retrieving this state from the Description elements?
    And what about the DESC_HIDE flag, is that accessible as well somewhere?

    I understand the Description Manual is meant for the regular way of using the descriptions (in a NodeData derived plugin), so obviously I will not find answers there related to my extreme use-case.


  • Global Moderator

    Hello,

    The information if a parameter is enabled is not stored in the Description object. You can use C4DAtom::GetEnabling() to check if the parameter is enabled (see C4DAtom Manual).

    DESC_HIDE is stored in the Description object as usual. See Description Settings Manual.

    Best wishes,
    Sebastian



  • Thanks for the links Sebastian.
    Had read these before, but still cannot figure out how to process the DESC_HIDE. I simply don't get it.

    I am trying with a Python script, as this is faster to prototype.

    import c4d
    
    def main():
        toolID = 431000015 # Bevel (id obtained from script log)
        
        tool = c4d.plugins.FindPlugin(toolID, c4d.PLUGINTYPE_TOOL)
        print "Tool = %s (%i)" % (tool.GetName(), toolID)
    
        # parse the description
        desc = tool.GetDescription(c4d.DESCFLAGS_DESC_0)
        for bc, paramid, groupid in desc:
            elementID = paramid[0].id
            
            # for testing purposes we are only interested in the "Apply" button
            if elementID != c4d.MDATA_APPLY:
                continue
            
            print bc[c4d.DESC_NAME], paramid, groupid
            print "Hidden = ", bc[c4d.DESC_HIDE]
    
    if __name__=='__main__':
        main()
    

    With the above I do get None as result for the DESC_HIDE.
    It might be explained in the manuals, I just don't get it. Sorry!


  • Global Moderator

    Hello,

    not every parameter description stores a value for DESC_HIDE. Typically a component only sets the value if it wants to hide a specific parameter.

    So depending on how to access the value, the access might fail. E.g.

    GeData data = bc->GetData(DESC_HIDE);
    
    if (data.GetType() == DA_LONG)
    {
      const Bool hide = data.GetBool();
      ApplicationOutput("DESC_HIDE: @", hide);
    }
    else
    {
      ApplicationOutput("DESC_HIDE not set"_s);
    }
    

    cannot read DESC_HIDE if the value is not stored in the BaseContainer.

    You can access BaseContainer values using functions like GetBool(). Such a function allows you to define a default value that is returned if no value is stored in the BaseContainer:

    const Bool hideStatus = bc->GetBool(DESC_HIDE, false);
    ApplicationOutput("DESC_HIDE status: @", hideStatus);
    

    See BaseContainer Manual and GeData Manual.

    Best wishes,
    Sebastian



  • I do get all you are trying to explain. It all makes sense ... it does.
    Still, I am unable to read out the attribute information I am looking for.

    import c4d
    
    def main():
        # get the current active tool
        toolID = doc.GetAction()
        tool = c4d.plugins.FindPlugin(toolID, c4d.PLUGINTYPE_TOOL)
        print "Tool = %s (%i)" % (tool.GetName(), toolID)
    
        # parse the description
        desc = tool.GetDescription(c4d.DESCFLAGS_DESC_0)
        for bc, paramid, groupid in desc:
            elementID = paramid[0].id
            
            # for testing purposes we are only interested in the "Apply" button
            if elementID != c4d.MDATA_APPLY:
                continue
            
            # the Bevel tool has the "Apply" button visible (sometimes disabled, but visible)
            # the Live Selection tool has the "Apply" button part of its description,
            # but this button is not visible, it should thus have its DESC_HIDE set to true ???
            
            # Select the bevel tool and execute this script => "No DESC_HIDE"
            # Select the live selection tool and execute this script => "No DESC_HIDE" !!!
            
            # >>> How to get the Live Selection's hidden state of MDATA_APPLY ???
                       
            gd = bc.GetData(c4d.DESC_HIDE)
            if gd == None:
                print "No DESC_HIDE"
            elif bc.GetType(c4d.DESC_HIDE) == c4d.D_LONG:
                print "DESC_HIDE = ", bc.GetBool(c4d.DESC_HIDE, False)
            else:
                print "DESC_HIDE not LONG"
    
    
    if __name__=='__main__':
        main()
    
    

  • Global Moderator

    Hello,

    the descriptions are based on description resource files (see Parameter Description Resources). In toolbase.res you see that MDATA_APPLY is part of the group MDATA_COMMANDGROUP:

    GROUP MDATA_COMMANDGROUP
    {
      DEFAULT 1;
      GROUP
      {
        BOOL MDATA_INTERACTIVE { }
    
        GROUP
        {
          COLUMNS 3;
          DEFAULT 1;
    
          BUTTON MDATA_APPLY { FIT_H; }
          BUTTON MDATA_NEWTRANSFORM { FIT_H; }
          BUTTON MDATA_DEFAULTVALUES { FIT_H; }
        }
      }
    }
    

    In the live selection's resource file toolliveselection.res you see that the base is included but the MDATA_COMMANDGROUP group is hidden:

    INCLUDE ToolBase;
    
    ...
    
    HIDE MDATA_COMMANDGROUP;
    

    Therefore, MDATA_APPLY has no DESC_HIDE setting. The whole group is hidden using DESC_HIDE.

    Inside Cinema this is checked like this:

    BaseContainer *groupData = desc->GetParameterI(MDATA_COMMANDGROUP, nullptr);
    if (groupData != nullptr && groupData->GetBool(DESC_HIDE))
      return true;
    
    BaseContainer *applyData = desc->GetParameterI(MDATA_APPLY, nullptr);
    if (applyData != nullptr && applyData->GetBool(DESC_HIDE))
      return true;
    

    best wishes,
    Sebastian



  • @s_bach said in Confusing ToolData content:

    The whole group is hidden using DESC_HIDE.

    Ah ... didn't even consider that one.
    This makes perfect sense.

    Thanks for going through all the trouble of explaining.