Saving custom property class
toby.allsopp
Posts: 20
I've defined a custom property class, which inherits from DzProperty and defined a custom widget class to display it, and it seems to work OK until I go to save the scene with one of these custom properties attached. I get "Failed to save a file. See the log for more details". The log does not contain more details - it literally only has the message telling me to see the log for more details.
What do I need to do to allow saving and loading of custom property classes?
Comments
I don't have any experience with this, but I suspect you need to look at the Custom Elements Sample in the SDK Documentation. Specifically, mycustomshape.{cpp,h}
As Omniflux implies, I think your problem is actually with Saving and Loading your custom widget and not with Saving and Loading the property.
It may help you if you base your custom widget on an existing DzPropertyWgt sub class. You may need to delve into defining specific IO functionality if your custom widget class is significantly different from one of theDzPropertyWgt sub classes i.e.define the Json serialization for your widget object
Hmm, I'm definitely missing something. My custom widget class doesn't have any state that would need to be saved or loaded - it gets instantiated by Daz when the property needs to be displayed. My custom widget class inherits from DzStyledPropertyWgt.
Assuming I do need to defined JSON serialization for the widget class, how would I go about that? The examples use specific macros like DZ_PLUGIN_REGISTER_GEOMETRY_EXTRA_OBJECT_IO and I can't see one that seems applicable to properties or property widgets.
Currently I have:
It doesn't seem right that I'm using DZ_PLUGIN_REGISTER_MODIFIER_EXTRA_OBJECT_IO there, but I don't know what I should use instead.
Edit: I originally had the IO class and the property class around the wrong way in the register macro, but fixing that didn't help.
You have based your class on DzProperty instead of one of its subclasses.
DzProperty::save(DzOutFile* file) is pure virtual so I assume that you have defined save(DzOutFile* file)override in your custom class???
When I have created custom properties they have been associated with one or more custom Node type. The IO class for my custom node primarily did the serialising (save & load) of the node however I also used it to serialising (save & load) the associated custom property.
Thus my IO class and the DZ_PLUGIN_REGISTER_MODIFIER_EXTRA_OBJECT_IO have been for my custom node and not specifically for the custom property.
I don't know how you are going to get the DzNode's IO class to cast the property channel data to your custom property type, without defining a custom node class.
With my custom nodes the node's class setPointer(---) function recognises the custom property's sectionID and casts it to the custom property class e.g. ::setPointer(...){... else if(sectionID == MYCUSTOMNODE_MYCUSTOMPROPERTYNAME_SECTION){ propertyObjectInMyCustomNode = DZ_ASSERT_CAST(ptr, MyCustomPropertyClassName);...}
The custom node class handles the creation of the widget in which to display the property's value. All my widgets are compiled from standard QT objects, so trivial.
As my custom properties are based on DzProperty subclasses, the save seriation is handled by the subclass e.g. if based on DzBoolProperty the data saved is "channel" : { "id" : "MyCustomPropertyName", "type" : "bool", "value" : false...
My custom properties don't need to save any additional data other than what is saved by the base DzProperty subclass so I don't make use of a Context* in my IO class.
I should review before I hit the submit button.
I see a couple of points that may need clarification (ok call them inaccuracies if you wish :).
DzProperty::save(DzOutFile* file) is not strickly "pure virtual" rather it is incomplete, if you are using DzProperty as a base class you will most likely need to override save (and many other functions) with your own e.g. setPointer and possible loadSection. So that they can handle the seriation and casting of your property.
With my custom nodes the custom property's seriation is handled by the base class, the custom node handles the deseriation and cast to my custom property. On review this is not recommended, I should handle all property's functions within the custom property class. Another task to add to my long todo list :)
Reading through old project comments and test results it looks like I used the custom node class to do the the deseriation because at the time I was unable to successfully develop a compound property widget and instead used the custom node class to orchestrate the on-create interaction between multiplte properties.
Hi @surreal, thanks for your help.
I have tried overriding DzProperty::save(DzOutFile* file) and it doesn't appear to be called - my understanding was that this is for the old binary file format and is not used any more.
I've tried inheriting from DzStringProperty instead of DzProperty and that gets me a lot further. Now the property is saved in the .duf file, but it is not loaded. I can't see any difference in the .duf file between this property and other string properties that I add to the same node withouit using my custom class, but clearly something is not quite right.
I might have to try a custom node class at this rate, but it's not really what I want as I'd ideally like to be able to attach my custom property class to materials and possibly other things.
You are correct, DzProperty::save(DzOutFile* file) is depreciated with v4.
I did a bit of testing and I was wrong about setPointer as well, it and loadSection are also depreciated. The custom node is saved and loaded by IO class and DZ_PLUGIN_REGISTER_NODE_EXTRA_OBJECT_IO
My IO classes are trivial except for createNode() which returns a pointer to a new instance of the custom class. So effectively DzExtraNodeIO does the work of recognising the custom node's data and casting it to the custom class. So I guess just need to find equivalent IO base class for property instead of node.
Hopefully I will have some time tomorrow afternoon and I will do a bit more testing to see if I can work out how to use DzElementPropertyValueIO, DzAssetElementPropertyValueIO, DzAssetElementPropertyValuesIO to save and load custom property data.
Please do report back if you're able to customize IO for a custom DzProperty subclass!
The next think I'm going to try when I get back onto this is to use the DzProperty method "setWidgetClassOverride" on a plain DzStringProperty to see if I can get the effect I want with that. The widget class override methods are not in SDK headers but are exposed for scripting so hopefully I can get at them using invokeMethod.
Life gets in the way, as it usually does :)
Am using DZ_PLUGIN_REGISTER_NODE_EXTRA_OBJECT_IO("My_Custom_Property", MyCustomPropertyNodeIO, DzNode); to write and read property information to/from scene file. MyCustomPropertyNodeIO is based DzExtraNodeIO class.
Write to file in MyCustomPropertyNodeIO.writeExtraDefinition and MyCustomPropertyNodeIO.writeExtraInstance, using subclass of DzElementPropertyValueIO (and or DzAssetElementPropertyValueIO). Although have not got around to finish writing and testing that as yet.
To read from file, will use MyCustomPropertyNodeIO.startInstanceRead and MyCustomPropertyNodeIO.applyInstanceToObject
Not sure if that is the best way. Will know more once I have done testing with subclassed DzProperty types and compound custom properties. Most of my custom nodes require translation information however one does not, so it would be a good test candidate to be implemented as a custom property instead of as a custom node.
Hopefully will get some time this weekend.
As I said, not sure if this is the best solution and this is not fully tested, however should be enough to get you started. I may have sometime mid next week to run it through a decent test to confirm some of my issue resolutions.
My test MyCustomStrProperty is based on DzStringProperty and does not save any double/int/float type parameters, so have not needed to implemented ReadMyCustomStrPropertyChannelParameters::addMember(const QString& name, double val). If your property has parameters of those types you will have to implement it. The QString and bool addMember(...) function will show you how.
My choice of asset element format is modelled on channel's for better or worse.
example of the asset file data
...
"extra" : [
{
"type" : "studio/node/MyCustomStr_Property_Element",
"version" : 0.1,
"channels" : [
{
"channel" : {
"id" : "my_test_str",
"type" : "MyCustomStrProperty",
"label" : "my_test_str",
"current_value" : "testtest_y"
},
"group" : "/General"
}
]
},
{
"type" : "studio_node_channels",
...
Oops! The MyCustomStrPropertyNodeIO destructor needs to be in .cpp instead of the .h. Have edited the above code snipets.
Ah, interesting, thank you @surreal! I would not have guessed that it was allowed to register an IO class for DzNode, but I guess that's why it has "extra" in the name.
Do you have any ideas for how to resolve your TODO 5 - i.e. how to remove the custom property from the default serialization of properties? Maybe it doesn't really matter too much?
TODO 4&5 was on my mind all Sunday so burnt a bit of the midnight oil last night.
After some testing I think my choice to use Node instead of Property was the correct choice.
I have updated the code snipet above, with latest findings before I terminated my testing.
My TODO list is a bit longer with a few updates I should do to my old custom nodes. The oldest source file has a 2012 creation date so they are overdue for a review. Code reuse has its advantages but also has its pit falls. We all need a bit of a refresher now and again :)
Best of luck with your project.