I would like to create a material which is quite complex.
(It means it can have variable number of sub-components, and some of the sub-components can have sub-sub-components.)
My idea was to create classes for these sub-components (based on RenderContent) and connect these via child slots to my material. But it is not possible to make a class from RenderContent only from RenderMaterial.
So OK, what about to create these custom sub-components from RenderMaterial? There is an option to make my custom RenderContent (RenderMaterial) private
[CustomRenderContent(IsBuiltIn = true, IsPrivate = true)]
which means it is not visible for users, right?
My question is, is it a feasible solution? Is it OK to define many RenderMaterial to use them only as sub-components for my “real” material? Or does it cause some unexpected results I am not prepared for:)?
In the API comment I can see
“Do not call this method to add children in your constructor. If you want
to add default children, you should override Initialize() and add them there.”
But RenderContent Initialize is not a virtual function. What is the correct way?
Edit:
And another question.
In case of double sided materials, the user can click on the 2 material slots and go to the child material to edit it.
How can I do it programmatically? So user clicks a button and one of the child becomes active in editor. Should I just select it?
Edit2:
I add the sub-material now in the constructor and it behaves correctly it seems… (?)
and I use mySubMaterial.OpenInEditor() to step into the submaterial for the users, and it seems good to me in Modeless mode. In case I do the same under Modal dialog it doesn’t step into the sub-material. But what I can do in this case is to call mySubMaterial.Edit() and it opens the sub-material in an upcoming modal dialog.
The method you are using to open a material in the editor is deprecated in the C++ SDK but the C# interface is still active. We have to rethink how this works and to that end I have filed a bug: https://mcneel.myjetbrains.com/youtrack/issue/RH-56652. I don’t think this will solve your problem because at the moment the deprecated function still works.
The issue you are having about the Modal Editor is by design. Once a Modal Editor opens, all child editing is done in new Modal Editors.
When you want undo to work with render contents, it is not enough to start and end an undo record; you also have to create a content undo for the type of change. I’m afraid I don’t know the syntax for C# but In C++ you would write something like this (pseudocode):
The above code should work. There might be a bug? Maybe the undo does not work with SetChild or then it is possible that I do not understand completely why a content undo is needed? I will also test this on Windows.
My undo code that I tried (on the Mac WIP) is similar and it worked. I have make more tests.
Another solution to the problem might be the following. The UI section has a ViewModel (IRdkViewModel) and that view model has the following undo datasources (Rhino.Render.RdkUndoRecord and Rhino.Render.RdkUndo). However to make the usage of them easier I have a helper method and helper class.
This helper method can be added to the UI section (EtoCollapsibleSection)
public NewUndoRecord NewUndoHelper(string description)
{
return new NewUndoRecord(description, ViewModel);
}
This helper class wraps the undo
//This class is intended to be used in a using construct - so that undo
//records are created neatly around property setting areas.
public class NewUndoRecord : IDisposable
{
bool _disposed;
Rhino.Render.RdkUndoRecord _undo_record;
Rhino.Render.RdkUndo _undo;
IRdkViewModel _view_model;
public NewUndoRecord(string description, IRdkViewModel viewModel)
{
_view_model = viewModel;
_undo = viewModel.GetData(Rhino.UI.Controls.DataSource.ProviderIds.Undo, false, true) as Rhino.Render.RdkUndo;
if(_undo != null)
_undo_record = _undo.NewUndoRecord();
if(_undo_record != null)
_undo_record.SetDescription(description);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
~NewUndoRecord()
{
Dispose(false);
}
protected virtual void Dispose(bool disposing)
{
if (_disposed)
return;
if (_undo_record != null)
{
_undo_record.Dispose();
}
_disposed = true;
}
}
(Pseudocode)Then one can use it with a simple using statement like below. The using statement creates and destroys the undo record in a nice way.
Rhino.Render.RenderContentCollection collection = ContentSelectionForSetParamsWriter;
Rhino.Render.ContentCollectionIterator iterator = collection.Iterator();
using (var u = NewUndoHelper("Change some value"))
{
Rhino.Render.RenderContent content = iterator.First();
while (content != null)
{
content.SetParameter("some-value", (bool)some_value);
content = iterator.Next();
}
Commit(ProviderIds.ContentSelectionForSetParams);
iterator.DeleteThis();
}
The helper class and method should be made available through the sdk. I will check where it should be placed, but for now you could just copy these and see if they work for you.
The selection solution should also work. So if you select the child and commit the selection datasource, then the same editor should open it for editing.
Ok, so forget what I wrote about the undo datasources.
This is the correct way to go. I have translated John C. Cpp pseudocode. Below is the same code in C#.
using (var cu = new Rhino.Render.ContentUndoHelper(Rhino.RhinoDoc.ActiveDoc, "new layer"))
{
cu.AddContent(contentLayer, contentMaterial);
contentMaterial.AddChild(contentLayer,"layer")
}
@maxsoder In the underlying C++, SetField() includes content undo. SetChild() is a lower level function and it does not include undo which is why clients must add it to their code. Perhaps it would be possible to include the undo inside the C# SetChild() – but maybe that would break existing code. Anyway, for now, the client must type it
Thanks, I had implemented now the ContentUndoHelper method and it seems it works fine! At least for adding child content (I haven’t tried if inverse works too)