Document doesn't take objects when switching files

Hi all,

I have 2 different saved 3dm.file which both has plugin objects. When I try to switch one file to another file in same instance of Rhino by using Open in menu. Upcoming file doesn’t get Objects, Groups etc… Why is it possibly happening?

Let’s say,

private uint DocUint { get; set; }
private RhinoDoc Doc => RhinoDoc.FromRuntimeSerialNumber(DocUint);

/// Registering by command in Rhino (top object)
public MainPanel(uint documentRuntimeSerialNumber)
{
    DocUint = documentRuntimeSerialNumber;
    MyPluginController = new MyPluginController(Doc);
    RegisterEvents();
}

private void RegisterEvents()
{
    // This event can catch switching file reaction
    RhinoDoc.BeginOpenDocument += RhinoDoc_BeginOpenDocument;
    // This event didn't catch switching file reaction
    RhinoDoc.ActiveDocumentChanged += RhinoDoc_ActiveDocumentChanged;
}

// This event can catch switching file reaction
private void RhinoDoc_BeginOpenDocument(object sender, DocumentOpenEventArgs e)
{
    MyPluginController.Doc = e.Document;
}

// This event didn't catch switching file reaction
private void RhinoDoc_RhinoDoc_ActiveDocumentChanged(object sender, DocumentEventArgs e)
{
    MyPluginController.Doc = e.Document;
}

When I switch file like this, MainPanel recreate itself most probably not disposing old one and still using old Document object. And Rhino is getting reaaaaaaly slow. Most probably it duplicates MainPanel object, it is causing many nested event subscriptions, I guess. I encounter this issue before when I try to change Language changes, because I needed to initalize content panel again.

Any insight?
Thanks in advance.
-Oğuzhan

Hi @oguzhankoral,

If you’ve registered your panel as a per-document panel, like this:

Panels.RegisterPanel(MyPlugIn, typeof(MyPanel), "MyPanel", icon, PanelType.PerDoc);

then a new panel instance will get created when a new document gets created on Windows. So there should be no need to pay attention to document open/close/active changed events.

Note, PanelType.PerDoc is the default panel registration.

The callback to the panel, that you’ve registered, are going to get garbage collected at some point when the panel is destroyed. You should unhook your panel events before the panel is disposed. You can also define the callbacks as static so the handlers are shared among instances.

Hope this helps.

– Dale

Hi @dale,

Thanks for your clarification. I dug into a bit more to understand after your post. Then I changed the PanelType to PanelType.System which is not initialize MainPanel when I switch document. Then I used events to get new document object and assign it to my Controller which contains events like Idle, AddObject… etc. It this case I used something like this;

private uint docUint;
/// <summary>
/// Rhino Document number
/// </summary>
private uint DocUint
{
    get => docUint;
    set
    {
        docUint = value;
        // when Rhino opens with empty file docuint turns to "0".
        // that's why doc object is coming null first time
        var doc = RhinoDoc.FromRuntimeSerialNumber(docUint);
        if (doc == null)
        {
            Doc = RhinoDoc.ActiveDoc;
        }
        else
            Doc = doc;
    }
}

private RhinoDoc doc;
/// <summary>
/// Rhino Document
/// </summary>
private RhinoDoc Doc
{
    get => doc;
    set
    {
        doc = value;
        // Contols here Contoller initialized or not before
        if(Controller != null)
            Controller.Doc = doc;
    }
}

/// Registering by command in Rhino (top object)
public MainPanel(uint documentRuntimeSerialNumber)
{
    DocUint = documentRuntimeSerialNumber;
    Controller = new Controller(Doc);
    RegisterEvents();
}

private void RegisterEvent()
{
    RhinoDoc.BeginOpenDocument += RhinoDoc_BeginOpenDocument;
    RhinoDoc.CloseDocument += RhinoDoc_CloseDocument;
}

private void RhinoDoc_CloseDocument(object sender, DocumentEventArgs e)
{
    // In order to get rid of previous document's events. 
    Controller.UnhookAllEvents();        
}

private void RhinoDoc_BeginOpenDocument(object sender, DocumentOpenEventArgs e)
{
    DocUint = e.DocumentSerialNumber;
    // Referenced property for every layer of plugin 
    Controller.Doc = Doc;
    // In order to hook events for new document
    Controller.HookAllEvents();
}

With this structure, when I select object in the new document which is opened without any error, Doc objects appears in code still previous one. There is 2 question in my mind;

  1. Why my events like Idle still triggers and search new document’s objects in the old document’s object table although I unhook them before closing?
  2. When should I prefer PanelType as PerDoc or System? Because it effects the structure of MainPanel. Is there any suggestion to commercial plugins which one can be useful?

If I understand correctly PanelType should effect my plugin structure like this;

Best,
-Oğuzhan

Hi @oguzhankoral,

If your panel is doing document things, then you should register it as “per document”. It makes it much easier to manage Rhino’s document, plus it will also work on the multi-doc Mac.

Here is a pattern I use when making document-related panels. This might also be in the developer samples repo.

SamplePanel.zip (11.4 KB)

– Dale

1 Like

Hi @dale,

Thanks for your sample, I was trying to implement this structure to my design. I achieve more or less what I want thanks to your sample. I just confused about data in the .3dm file. In this topic, you suggest two different option that I want to use second one, not singleton. (BTW with singleton I achieve before).

var my_table = doc.RuntimeData.GetValue(typeof(MyDataTable), rhinoDoc => new MyDataTable(rhinoDoc.RuntimeSerialNumber));

With this line you were trying to get data table from runtime serial number but the data in the .3dm file is not only available in the overriden methods in the PlugIn? With below one, how can I pass data from PlugIn to ViewModel. Most probably I am confusing the order of PlugIn object, MainPanel and ViewModel.

protected override void ReadDocument(RhinoDoc doc, BinaryArchiveReader archive, FileReadOptions options)
{
    Doc = doc;
    archive.Read3dmChunkVersion(out var major, out var minor);
    if (MAJOR == major && MINOR == minor)
    {
        // For singleton
        PluginDictionaryTable pluginDictionaryTable = PluginDictionaryTable.Instance;
        pluginDictionaryTable.ReadDocument(doc, archive, options);
    }
}

How should I use this overriden method with your SamplePanel plugin sample?

Best,
-Oğuzhan

The plug-in wizard adds a static Instance property to your plug-in object. This way, you can something like this from anywere:

var data = SamplePanelPlugIn.Instance.MyData;
// todo...

– Dale

1 Like