Unhandled exception in Layers table when adding layers programmatically

Hi everyone,

I’m converting one of our plugins from Rhino 7 to Rhino 8 and my users get an exception thrown by Rhino (not the plugin) - I would love to understand if it’s a problem created by the plugin or an unhandled exception on the Rhino side maybe ?

Steps to crash are as follows :

  1. User open the WPF plugin in a tab
  2. User clicks on button in plugin to import some geometries (OSM streets and buildings)
  3. The plugin creates the necessary layers, creates the geometry, sends the geometry on new layers.
  4. User switch to the Layers tab
  5. Rhino crash with the exception below

Useful notes

  1. The crash does not happen in Rhino 7 (same code)
  2. The crash does not happen in Rhino 8 DEBUG mode, BUT the layer table does not refresh (even after multiple redraw) in the Rhino UI
  3. The crash does happen in Rhino 8 in RELEASE mode
  4. The function to create geometry and layers are nested into larger background tasks
  5. I run 3 views.redraw() after the background tasks has finished

The bits of codes used in this bug

 // function to create the layer if it does not exist  
 internal static int LayerByName(string layerName, Color c)
        {
            Layer newLayer = RhinoDoc.ActiveDoc.Layers.FindName(layerName);
            int idx = -1;
            if (newLayer == null)
            {
                newLayer = new Layer();
                newLayer.Name = layerName;
                newLayer.Color = c;
                idx = RhinoDoc.ActiveDoc.Layers.Add(newLayer);
// here i have also added a 500ms delay in testing but it does not change anything
            }
            else
                idx = newLayer.LayerIndex;

            return idx;
        }

// function to create the object
internal void ToRhino(bool temp = false, bool force3d = false)
        {
            // Set the object's attributes
            ObjectAttributes attributes = new ObjectAttributes();
            attributes.Name = Name;
            attributes.ObjectId = new Guid();

            int layerIndex = -1;
 
             attributes.ObjectColor = ColorUtil.GisObjectColor(Category);
             if(Category == null)
                    Category = "";

            layerIndex = RhinoUtil.LayerByName($"OSM Context - {Category}", attributes.ObjectColor);
      

            attributes.LayerIndex = layerIndex;

            // Add the object to the document
            RhinoDoc doc = RhinoDoc.ActiveDoc;

            if (this.Outline != null)
            {
                if (Tags.ContainsKey("building"))
                {
                    double dist = (double)GetValue(this, "building:height", true);

                    if (dist == 0 && !force3d)
                    {
                        RhinoApp.WriteLine("No height found.");
                        doc.Objects.AddCurve(Outline, attributes);
                        return;
                    }

       etc etc
        }
    }

.
.
.
.

Rhino crash log


[ERROR] FATAL UNHANDLED EXCEPTION: System.InvalidOperationException: An ItemsControl is inconsistent with its items source.\n  See the inner exception for more information.
 ---> System.Exception: Information for developers (use Text Visualizer to read this):
This exception was thrown because the generator for control 'Eto.Wpf.Forms.Controls.EtoDataGrid Items.Count:9' with name '(unnamed)' has received sequence of CollectionChanged events that do not agree with the current state of the Items collection.  The following differences were detected:
  Accumulated count 6 is different from actual count 9.  [Accumulated count is (Count at last Reset + #Adds - #Removes since last Reset).]
  At index 0:  Generator's item 'Rhino.UI.DialogPanels.LayerItem' is different from actual item 'Rhino.UI.DialogPanels.LayerItem'.
  At index 1:  Generator's item 'Rhino.UI.DialogPanels.LayerItem' is different from actual item 'Rhino.UI.DialogPanels.LayerItem'.
  At index 2:  Generator's item 'Rhino.UI.DialogPanels.LayerItem' is different from actual item 'Rhino.UI.DialogPanels.LayerItem'.
  (... 3 more instances ...)

One or more of the following sources may have raised the wrong events:
     System.Windows.Controls.ItemContainerGenerator
      System.Windows.Controls.ItemCollection
       System.Windows.Data.ListCollectionView
  *     Eto.CustomControls.TreeController
(The starred sources are considered more likely to be the cause of the problem.)

The most common causes are (a) changing the collection or its Count without raising a corresponding event, and (b) raising an event with an incorrect index or item parameter.

The exception's stack trace describes how the inconsistencies were detected, not how they occurred.  To get a more timely exception, set the attached property 'PresentationTraceSources.TraceLevel' on the generator to value 'High' and rerun the scenario.  One way to do this is to run a command similar to the following:\n   System.Diagnostics.PresentationTraceSources.SetTraceLevel(myItemsControl.ItemContainerGenerator, System.Diagnostics.PresentationTraceLevel.High)
from the Immediate window.  This causes the detection logic to run after every CollectionChanged event, so it will slow down the application.

   --- End of inner exception stack trace ---
   at System.Windows.Controls.ItemContainerGenerator.Verify()
   at System.Windows.Controls.VirtualizingStackPanel.MeasureChild(IItemContainerGenerator& generator, IContainItemStorage& itemStorageProvider, IContainItemStorage& parentItemStorageProvider, Object& parentItem, Boolean& hasUniformOrAverageContainerSizeBeenSet, Double& computedUniformOrAverageContainerSize, Double& computedUniformOrAverageContainerPixelSize, Boolean& computedAreContainersUniformlySized, Boolean& hasAnyContainerSpanChanged, IList& items, Object& item, IList& children, Int32& childIndex, Boolean& visualOrderChanged, Boolean& isHorizontal, Size& childConstraint, Rect& viewport, VirtualizationCacheLength& cacheSize, VirtualizationCacheLengthUnit& cacheUnit, Int64& scrollGeneration, Boolean& foundFirstItemInViewport, Double& firstItemInViewportOffset, Size& stackPixelSize, Size& stackPixelSizeInViewport, Size& stackPixelSizeInCacheBeforeViewport, Size& stackPixelSizeInCacheAfterViewport, Size& stackLogicalSize, Size& stackLogicalSizeInViewport, Size& stackLogicalSizeInCacheBeforeViewport, Size& stackLogicalSizeInCacheAfterViewport, Boolean& mustDisableVirtualization, Boolean isBeforeFirstItem, Boolean isAfterFirstItem, Boolean isAfterLastItem, Boolean skipActualMeasure, Boolean skipGeneration, Boolean& hasBringIntoViewContainerBeenMeasured, Boolean& hasVirtualizingChildren)
   at System.Windows.Controls.VirtualizingStackPanel.MeasureOverrideImpl(Size constraint, Nullable`1& lastPageSafeOffset, List`1& previouslyMeasuredOffsets, Nullable`1& lastPagePixelSize, Boolean remeasure)
   at System.Windows.Controls.VirtualizingStackPanel.MeasureOverride(Size constraint)
   at System.Windows.Controls.Primitives.DataGridRowsPresenter.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at MS.Internal.Helper.MeasureElementWithSingleChild(UIElement element, Size constraint)
   at System.Windows.Controls.ScrollContentPresenter.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Grid.MeasureCell(Int32 cell, Boolean forceInfinityV)
   at System.Windows.Controls.Grid.MeasureCellsGroup(Int32 cellsHead, Size referenceSize, Boolean ignoreDesiredSizeU, Boolean forceInfinityV, Boolean& hasDesiredSizeUChanged)
   at System.Windows.Controls.Grid.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.ScrollViewer.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Border.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Control.MeasureOverride(Size constraint)
   at System.Windows.Controls.DataGrid.MeasureOverride(Size availableSize)
   at Eto.Wpf.Forms.WpfFrameworkElement`3.MeasureOverride(Size constraint, Func`2 measure) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\WpfFrameworkElement.cs:line 114
   at Eto.Wpf.Forms.Controls.EtoDataGrid.MeasureOverride(Size constraint) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\Controls\GridHandler.cs:line 24
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Grid.MeasureCell(Int32 cell, Boolean forceInfinityV)
   at System.Windows.Controls.Grid.MeasureCellsGroup(Int32 cellsHead, Size referenceSize, Boolean ignoreDesiredSizeU, Boolean forceInfinityV, Boolean& hasDesiredSizeUChanged)
   at System.Windows.Controls.Grid.MeasureOverride(Size constraint)
   at Eto.Wpf.Forms.EtoGrid.MeasureOverride(Size constraint) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\TableLayoutHandler.cs:line 65
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Border.MeasureOverride(Size constraint)
   at Eto.Wpf.Forms.WpfFrameworkElement`3.MeasureOverride(Size constraint, Func`2 measure) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\WpfFrameworkElement.cs:line 114
   at Eto.Wpf.Forms.TableLayoutHandler.MeasureOverride(Size constraint, Func`2 measure) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\TableLayoutHandler.cs:line 189
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Grid.MeasureCell(Int32 cell, Boolean forceInfinityV)
   at System.Windows.Controls.Grid.MeasureCellsGroup(Int32 cellsHead, Size referenceSize, Boolean ignoreDesiredSizeU, Boolean forceInfinityV, Boolean& hasDesiredSizeUChanged)
   at System.Windows.Controls.Grid.MeasureOverride(Size constraint)
   at Eto.Wpf.Forms.EtoGrid.MeasureOverride(Size constraint) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\TableLayoutHandler.cs:line 65
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Border.MeasureOverride(Size constraint)
   at Eto.Wpf.Forms.WpfFrameworkElement`3.MeasureOverride(Size constraint, Func`2 measure) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\WpfFrameworkElement.cs:line 114
   at Eto.Wpf.Forms.TableLayoutHandler.MeasureOverride(Size constraint, Func`2 measure) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\TableLayoutHandler.cs:line 189
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Border.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Border.MeasureOverride(Size constraint)
   at Eto.Wpf.Forms.WpfFrameworkElement`3.MeasureOverride(Size constraint, Func`2 measure) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\WpfFrameworkElement.cs:line 114
   at Eto.Wpf.Forms.WpfContainer`3.MeasureOverride(Size constraint, Func`2 measure) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\WpfContainer.cs:line 39
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Border.MeasureOverride(Size constraint)
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.Controls.Border.MeasureOverride(Size constraint)
   at Eto.Wpf.Forms.WpfFrameworkElement`3.MeasureOverride(Size constraint, Func`2 measure) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\WpfFrameworkElement.cs:line 114
   at Eto.Wpf.Forms.WpfContainer`3.MeasureOverride(Size constraint, Func`2 measure) in D:\BuildAgent\work\dujour\src4\DotNetSDK\Eto\src\Eto.Wpf\Forms\WpfContainer.cs:line 39
   at System.Windows.FrameworkElement.MeasureCore(Size availableSize)
   at System.Windows.UIElement.Measure(Size availableSize)
   at System.Windows.ContextLayoutManager.UpdateLayout()
   at System.Windows.ContextLayoutManager.UpdateLayoutCallback(Object arg)
   at System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()
   at System.Windows.Media.MediaContext.RenderMessageHandlerCore(Object resizedCompositionTarget)
   at System.Windows.Media.MediaContext.RenderMessageHandler(Object resizedCompositionTarget)
   at System.Windows.Threading.ExceptionWrapper.InternalRealCall(Delegate callback, Object args, Int32 numArgs)
   at System.Windows.Threading.ExceptionWrapper.TryCatchWhen(Object source, Delegate callback, Object args, Int32 numArgs, Delegate catchHandler)
[END ERROR]

Hi @Romain_Bigare,

Is there any way I can get your plug-in, or a sample plug-in that repeat this?

Thanks,

– Dale

Hi @dale ,

Thanks for coming back to me so quickly.

I’ve created a GitHub repo with the simplest project to trigger the crash. There’s a yak package and all the source code.

Notes on usage :

  1. There’s a built yak package that can be installed for Rhino 8 on windows
  2. Command is TryCrash, there is a WPF panel that will appear with a button
  3. The layers tab must be hidden at the time of clicking the button
  4. When switching back to the layers tab, Rhino will crash

Problem narrowed down
While recreating the simplest app possible, ive narrowed down the problem, see the code below :

private void Button_Click(object sender, RoutedEventArgs e)
        {
            // this creates the bug. This function is in UI >  MyEmbeddedWPFPanel.xaml.cs

            Task.Run(()=>
            {
                CreateRandomObjects();
            });

            // this does not create the bug
            CreateRandomObjects();
        }

        private async Task CreateRandomObjects()
        {
            // create 300 random objects to populate in the rhino model
            // The objects category can be buildings, roads, parks, etc.
            // If building, it will have a height and will be extruded
            // Otherwise it will be a planar curve
            // The layer is chosen in the RhinoUtil.LayerByName method called from the data.ToRhino()


            var doc = RhinoDoc.ActiveDoc;
            if (doc == null)
                return;

            Random random = new Random();
            for (int i = 0; i < 300; i++)
            {
                SomeDataObject data = new SomeDataObject();
                data.Category = Categories[random.Next(Categories.Count)];
                if (data.Category == "Buildings")
                    data.Height = random.Next(1, 100);
                data.Name = Names[random.Next(Names.Count)];
                data.Outline = RhinoUtil.RandomCurve();

                // layer creation happens in the method ToRhino()
                data.ToRhino();
            }

            doc.Views.Redraw();
        }

Romain

Hi @Romain_Bigare,

private void Button_Click(object sender, RoutedEventArgs e)
{
    // this creates the bug

    Task.Run(()=>
    {
        CreateRandomObjects();
    });

    // this does not create the bug
    CreateRandomObjects();
}

The Rhino document is not thread safe. You should only add objects to the document on the main thread.

– Dale

3 Likes

Noted Dale, apologies for ringing the bells for nothing!
Thanks for your time on this.