Spline User Data Presets



  • Hello forum,

    In my User Data, I have a customized shape as my Spline DataType. I want to keep this custom shape and be able to rebuild as a preset in my plugin. It would function like the 'Save Preset...' and 'Load Preset...' buttons.

    There must be a way to save all the points and tangents in the spline DataType to rebuild the it with python? How would I do this?

    Thank You for any thoughts.



  • Hi,

    I have recently shown here how to react to user data button clicks in a scripting tag. You could (de-)serialize your c4d.SplineData with the buildin Python module json (link). Also the c4d.SplineData type already does 50% of the work for you as GetKnots() returns a dictionary which you could serialize directly via json.dumps().

    Cheers
    zipit



  • Ah, GetKnots() has the data that I need. Thank you for that.
    I also see I SetKnot() is read only. The only way to get around that is sending the GetKey data to json then back again?
    There must be a simpler way of using GetKnots() data and pasting on to a new Spline DataType.



  • or can I place the GetKnots() data into my .res file? That would work for my plugin too.



  • Hi,

    I am sorry, I did overlook that you were writing a plugin, because I answered a few scripting questions before and you were talking about user data.

    A few assumptions:

    1. I assume you are writing as description resource based plugin data type like or ObjectData or TagData and not a dialog resource based type like CommandData or Tooldata.
    2. I am still not quite clear on what you want to do, but i assume you want to be able to store N amount of spline presets for your plugin, right?

    A few answers:

    1. If you only want to store exactly one preset and do use a description resource, you could actually just let the user use the build in feature "save as default" of the attribute manger.
    2. No, you can not define any storage logic in a res file. They are purely meant to define the interface layer of your plugin. Anything data/logic layer related goes into your code.
    3. You could either store your plugin settings globally in the WorldContainer. Check outc4d.SetWorldContainer() and c4d.GetWorldContainer() for that.
    4. Or you could store the settings locally (per document) via BaseDocument.GetDocumentData() and BaseDocument.SetDocumentData().
    5. In both cases (3. and 4.) you need to register a plugin ID under which you can store your settings.
    6. Serializing the data yourself could still be advantageous depending on what you are trying to do.

    Cheers
    zipit



  • Thank You for the thorough reply.

    Yes, I am writing a python tag and running it through Prototype Converter. The goal is to create a plugin tag that adds custom animation curves to the transforms of objects such as cubes and circles etc.
    I haven't looked into it yet but WorldContainer looks like its getting me there. I am assuming there is an easy way to save all of the carefully preset UserData (GUISplines) that I have created and keep it in the plugin for a new animator to use out of the box.

    (Sorry, I'm learning a lot all at once here coming from C# in Unity3D. )



  • Hello,
    thanks again for @zipit and the answer.

    I would like to add a couple of words.

    The best way should be to store them in the Content Browser, but it's not available in Python, only C++

    So that depend if you want your setting to be available only for the document (use Document BaseContainer). For setting that are available independently from the document, you can use the WorldContainer (even if it's to store "preferences" and not librairies)

    you can also retrieves each knots (SplineData.GetKnots()) and save them to an Hyperfile (@zipit talked about json but hyperfile are also a good way)

    Of course, If you want just one default "starting" state you can do that in your Init() function

    Don't forget that with cinema4D, users can also use "Set to default" or save Tags presets. And as you said, they can also use the regular load preset and save preset

    And you can also share content librairies.

    also about how to use the forum :

    For your next threads, please help us keeping things organised and clean. I know it's not your priority but it really simplify our work here.

    I've added the tags and marked this thread as a question so when you considered it as solved, please change the state :

    Cheers,
    Manuel



  • Thank You @zipit and @m_magalhaes,

    You have given me some great leads to solve my problem. It's pretty clear I am in over my head. More code samples in the API would really help a beginner like me. I have done a lot of searching around Hyperfiles and BaseContainers but not finding a solution, mostly due to my lack of familiarity of the sdk.

    This is the last problem I need to solve in order to use my Tag Plugin.

    Here is another take:Screen Shot 2019-08-26 at 9.52.55 PM.png
    I can easily save the splinedata to a file and load it into a blank splineGui like a charm. Can I somehow keep that file in my plugin directory and load it onto the specific user data on initialization of the plugin?

    Thanks again.



  • Hello,
    About the examples, with the release of R21 there will be a bit more examples. You can still have a look at the c++ examples, you will have some class name and function that can be translate to python.

    you can't because there's a dialogbox opened by this command. You can't avoid it.

    About the hyperfile you have an example on the hyperfile documentation page

    I did write an quick example with splinedata, you can simply use ReadData/WriteData
    I didn't add comment, you just pick the data and write it. (of course you don't need the LoadDialog)

    def Read():
        path = st.LoadDialog(type=c4d.FILESELECTTYPE_ANYTHING, title="Please Select the file")  
        hf = st.HyperFile()
        #spdata = op[c4d.ID_USERDATA,1]
        spdata = None
        if hf.Open(ident=0, filename=path, mode=c4d.FILEOPEN_READ, error_dialog=c4d.FILEDIALOG_NONE):
            spdata = hf.ReadData()
        else:
            raise ValueError("can't open file")
        op[c4d.ID_USERDATA,1] = spdata
        c4d.EventAdd()
        
            
    def Write():
        path = st.SaveDialog(type=c4d.FILESELECTTYPE_ANYTHING, title="Please Select the file")  
        hf = st.HyperFile()
        spdata = op[c4d.ID_USERDATA,1]
        if hf.Open(ident=0, filename=path, mode=c4d.FILEOPEN_WRITE, error_dialog=c4d.FILEDIALOG_NONE):
            writeDone = hf.WriteData(spdata)
        else:
            raise ValueError("error loading the file")
    

    I did try to open directly the file saved with the command but the result is not totally an hyperfile.

    Cheers,
    Manuel



  • Hello again,

    After digging a bit you can add a key parameter on your Open function. And write the Version of cinema4D retrieved with GetC4DVersion
    That way, you can read/write hyperfile to save your SplineData with script and/or using the load/save menu (when right clicking on the parameter)

    Of course on that example the selected object (op) have a UserData (id 1) that is a spline type.

    import c4d
    from c4d import gui
    from c4d import storage as st, gui
    # Welcome to the world of Python
    
    
    def Read(path):
        hf = st.HyperFile()
        spdata = None
        if hf.Open(ident=1885434477, filename=path, mode=c4d.FILEOPEN_READ, error_dialog=c4d.FILEDIALOG_NONE):
            c4dVersion = hf.ReadInt32()
            spdata = hf.ReadData()
        else:
            raise ValueError("can't open file")
        op[c4d.ID_USERDATA,1] = spdata
        
    
    def Write(path):
        hf = st.HyperFile()
        spdata = op[c4d.ID_USERDATA,1]
        if hf.Open(ident=1885434477, filename=path, mode=c4d.FILEOPEN_WRITE, error_dialog=c4d.FILEDIALOG_NONE):
            hf.WriteInt32(c4d.GetC4DVersion())
            writeDone = hf.WriteData(spdata)
        else:
            raise ValueError("error openging the file")
    
    
    # Execute main()
    if __name__=='__main__':
        path = st.SaveDialog(type=c4d.FILESELECTTYPE_ANYTHING, title="Please Select the file")  
        Write(path)
        spdata = op[c4d.ID_USERDATA,1]
        spdata.DeleteAllPoints()
        op[c4d.ID_USERDATA,1] = spdata
        Read(path)
        c4d.EventAdd()
    

    Cheers
    Manuel



  • Thank You @m_magalhaes very helpful! I succeeded. I should have been able to figure this out from the example in the API.
    I've used the code basically as you have it with my spline datas saved in different hyperfiles. I then load that data from my tag.

    Thanks again.