Custom Render Meshes broken in R8 SR23

@D-W and I are working with Custom Render Meshes. All worked well until R8 SR23 broke it.

Now, only the Raytraced display mode calls the RenderMeshes override. All other display modes ignore it.

@dale, @stevebaer, @CallumSykes, we’ve scanned through the changelog but nothing obvious stands out:

The minimal example below should replace any object with a sphere. Only raytraced works as expected:
Rendered:

Raytraced:

using Rhino;
using Rhino.Display;
using Rhino.DocObjects;
using Rhino.Geometry;
using Rhino.PlugIns;
using Rhino.Render;
using Rhino.Render.CustomRenderMeshes;
using System;
using System.Collections.Generic;

namespace CRM_Demo
{
    public class SphereRenderMeshProvider : RenderMeshProvider
    {
        public static SphereRenderMeshProvider Instance { get; private set; }

        public Guid TargetObjectId { get; set; } = Guid.Empty;
        private RenderMeshes _cachedRenderMeshes = null;
        private uint _currentCRC = 0;

        public SphereRenderMeshProvider()
        {
            Instance = this;
        }

        public override Guid ProviderId => new Guid("71731923-0978-45DD-9BA5-31C4EAAEDD8D");

        public override string Name => "Sphere Replacer";

        public override bool HasCustomRenderMeshes(
            MeshType mt,
            ViewportInfo vp,
            RhinoDoc doc,
            Guid objectId,
            ref Flags flags,
            PlugIn plugin,
            DisplayPipelineAttributes attrs)
        {
            return objectId == TargetObjectId;
        }

        public bool UpdateRenderMeshes(RhinoObject rhinoObject, RhinoDoc doc)
        {
            #region dummy geometry & material
            // Create a dummy sphere
            BoundingBox bbox = rhinoObject.Geometry.GetBoundingBox(true);
            if (!bbox.IsValid)
                return false;

            var sphere = new Sphere(bbox.Center, bbox.Diagonal.Length / 2.0);
            Mesh sphereMesh = Mesh.CreateFromSphere(sphere, 32, 32);
            if (sphereMesh == null)
                return false;
            
            sphereMesh.Normals.ComputeNormals();

            var displayMaterial = new Material();
            #endregion

            var customRenderMesh = new Rhino.Render.CustomRenderMeshes.Instance();
            customRenderMesh.Material = RenderMaterial.CreateBasicMaterial(displayMaterial, doc);
            customRenderMesh.Mesh = sphereMesh;

            uint hash = 0;
            if (_cachedRenderMeshes != null)
                hash = _cachedRenderMeshes.Hash + 10;

            _cachedRenderMeshes = new RenderMeshes(doc, rhinoObject.Id, ProviderId, hash, (uint)Flags.IsDocumentObject);
            _cachedRenderMeshes.AddInstance(customRenderMesh);

            _currentCRC = rhinoObject.Geometry.DataCRC(0);

            return true;
        }

        public override RenderMeshes RenderMeshes(
            MeshType mt,
            ViewportInfo vp,
            RhinoDoc doc,
            Guid objectId,
            List<InstanceObject> ancestry,
            ref Flags flags,
            RenderMeshes previousPrimitives,
            PlugIn plugin,
            DisplayPipelineAttributes attrs)
        {
            if (!HasCustomRenderMeshes(mt, vp, doc, objectId, ref flags, plugin, attrs))
                return null;

            var rhinoObject = doc?.Objects.FindId(objectId);
            if (rhinoObject.Geometry.DataCRC(0) != _currentCRC)
                UpdateRenderMeshes(rhinoObject, doc);

            return _cachedRenderMeshes;
        }
    }
}
using Rhino;
using Rhino.Commands;
using Rhino.Display;
using Rhino.Input.Custom;

namespace CRM_Demo
{
    public class ReplaceWithSphereCommand : Command
    {
        public override string EnglishName => "ReplaceObjectWithSphereMesh";

        protected override Result RunCommand(RhinoDoc doc, RunMode mode)
        {
            if (SphereRenderMeshProvider.Instance == null)
            {
                RhinoApp.WriteLine("Sphere Render Mesh Provider failed to load.");
                return Result.Failure;
            }

            var go = new GetObject();
            go.SetCommandPrompt("Select object to replace with a sphere render mesh");
            go.Get();

            if (go.CommandResult() != Result.Success)
            {
                return go.CommandResult();
            }

            var objRef = go.Object(0);
            var rhinoObject = objRef.Object();
            var objectId = objRef.ObjectId;

            if (rhinoObject != null)
            {
                var attributes = rhinoObject.Attributes.Duplicate();
                attributes.SetDisplayModeOverride(DisplayModeDescription.GetDisplayMode(DisplayModeDescription.RenderedId));
                doc.Objects.ModifyAttributes(rhinoObject.Id, attributes, true);
            }

            if (!SphereRenderMeshProvider.Instance.UpdateRenderMeshes(rhinoObject, doc))
                return Result.Failure;

            SphereRenderMeshProvider.Instance.TargetObjectId = objectId;

            doc.Views.Redraw();

            return Result.Success;
        }
    }
}

Hi Mariusz -

Thanks. Andy is working on this.
-wim

Thanks @wim!

In the meantime, could you please send me a link to the SR22 installer? I’d like to revert back and continue working until a hotfix comes out.

@nathanletwory, I also found a quirk with the Raytraced mode. When a bitmap assigned to a CRM gets updated, it doesn’t refresh in Raytraced mode. In the below clip, you can see how the texture correctly updates in Shaded display mode after each drag operation, but Raytraced doesn’t pick it up.

Any suggestions on how to trigger a refresh?

This will go through the custom render mesh mechanism, @andy will know if it is supposed to work like it does now or if it is a bug.

Thanks @nathanletwory!

@andy, I’m also having trouble creating CRMs for block instances. It works in basic scenarios, but they seem to be drawn after PostDrawObjects in a DisplayConduit. This is not the case for CRMs replacing non-instanced RhinoObjects.

Is this approach supported at all, or should each object constituting a block instance receive its own CRM in the InstanceDefinition?

I managed to pinpoint the issue. When an ObjectType.InstanceReference has a CRM attached to it AND there is an active DisplayConduit, PostDrawObjects is never called.

Object on the right is a regular mesh, object on the left is a block instance. With regular objects we can clearly see the CommandLine flooded with proper logs, but nothing happens with the block. All other channels work fine, only PostDrawObjects seems to be affected.

@stevebaer, @andy could you please have a look at this issue?

protected override void PostDrawObjects(DrawEventArgs e)
        {
            Rhino.RhinoApp.WriteLine("PostDraw");
            base.PostDrawObjects(e);
        }