[Bug] CustomMeshObject and Undo don't mix well

Ok, this is a long one, so bear with me!

My command basically takes two vertices of two different meshes, calculates their average and set these values back in the meshes. The problem I found is in the Undo support for this when using a CustomMeshObject.

If I run the command on normal meshes, the behavior is what you expect: mesh vertices get averaged and undo/redo works as they should.

If I use CustomMeshObject, however, if I undo the command and perform it again on a different location, the first change (which was undone) gets redone. Or, maybe, the mesh representations between what is in memory and what is in the document/on screen is out of sync. I don’t know.

To reproduce, see http://youtu.be/2Hzx_lbDiJw and

  1. run the command below once to put a mesh in the document
  2. run the command again and select two vertices. They get averaged
  3. Undo
  4. run the command again and select two different vertices. They get averaged and the first two get averaged too. I expected the first two not to get averaged.
private bool _hasRunOnce = false;

    protected override Result RunCommand(RhinoDoc doc, RunMode mode)
    {
        // if the command is run for the first time: put
        // two simple meshes in the document - slightly offset to each other
        if (!_hasRunOnce)
        {
            AddMeshes(doc);
            _hasRunOnce = true;
            return Result.Success;
        }

        // if the meshes are in the document perform this command,
        // undo it and perform it again (at a different position). 
        // The undone action
        // is performed again. But only if a CustomMeshObject is used.
        GetObject go = new GetObject();
        go.SetCommandPrompt("Select two mesh vertices to average");
        go.GeometryFilter = ObjectType.MeshVertex;

        ObjRef[] vRefs;
        while (true)
        {
            GetResult res = go.GetMultiple(2, 2);
            if (res == GetResult.Cancel) return Result.Cancel;
            if (res == GetResult.Object)
            {
                vRefs = go.Objects();
                break;
            }
        }

        if (null == vRefs || vRefs.Length != 2)
            return Result.Failure;

        Guid id0 = vRefs[0].ObjectId;
        Guid id1 = vRefs[1].ObjectId;

        if (id0 == id1)
            return Result.Failure;

        Mesh m0 = doc.Objects.Find(id0).Geometry as Mesh;
        Mesh m1 = doc.Objects.Find(id1).Geometry as Mesh;

        if (null == m0 || null == m1)
            return Result.Failure;

        m0.EnsurePrivateCopy();
        m1.EnsurePrivateCopy();

        ComponentIndex c0 = vRefs[0].GeometryComponentIndex;
        ComponentIndex c1 = vRefs[1].GeometryComponentIndex;

        if (c0.ComponentIndexType != ComponentIndexType.MeshTopologyVertex ||
            c1.ComponentIndexType != ComponentIndexType.MeshTopologyVertex)
            return Result.Failure;

        Point3f p0 = m0.TopologyVertices[c0.Index];
        Point3f p1 = m1.TopologyVertices[c1.Index];

        m0.TopologyVertices[c0.Index] = new Point3f(0.5f * (p0.X + p1.X), 0.5f * (p0.Y + p1.Y), 0.5f * (p0.Z + p1.Z));
        m1.TopologyVertices[c1.Index] = new Point3f(0.5f * (p0.X + p1.X), 0.5f * (p0.Y + p1.Y), 0.5f * (p0.Z + p1.Z));

        doc.Objects.Replace(id0, m0);
        doc.Objects.Replace(id1, m1);

        doc.Views.Redraw();
        return Result.Success;
    }

    private void AddMeshes(RhinoDoc doc)
    {
        Mesh m = new Mesh();
        Point3d[] pts = new[]
                            {
                                new Point3d(0, 0, 0),
                                new Point3d(1, 0, 0),
                                new Point3d(2, 0, 0),
                                new Point3d(0, 1, 0),
                                new Point3d(1, 1, 0),
                                new Point3d(2, 1, 0),
                                new Point3d(0, 2, 0),
                                new Point3d(1, 2, 0),
                                new Point3d(2, 2, 0)
                            };
        m.Vertices.AddVertices(pts);

        MeshFace[] faces = new[]
                                {
                                    new MeshFace(0, 1, 4, 3),
                                    new MeshFace(1, 2, 5, 4),
                                    new MeshFace(3, 4, 7, 6),
                                    new MeshFace(4, 5, 8, 7)
                                };
        m.Faces.AddFaces(faces);
        // no bug if added as normal mesh
        //doc.Objects.Add(m); 

        // bug if added as CustomMeshObject
        doc.Objects.AddRhinoObject(new MyMeshObject(m));

        Mesh m2 = new Mesh();
        m2.Vertices.AddVertices(pts);
        m2.Faces.AddFaces(faces);

        Transform translate = Transform.Translation(0.2, 0.2, 0.0);
        m2.Transform(translate);
        // no bug if added as normal mesh
        //doc.Objects.Add(m2);
        // bug if added as CustomMeshObject
        doc.Objects.AddRhinoObject(new MyMeshObject(m2));

        doc.Views.Redraw();
    }
}

public class MyMeshObject : CustomMeshObject
{
    public MyMeshObject()
    {
            
    }

    private BoundingBox _bb;
    public MyMeshObject(Mesh m) : base(m)
    {
        _bb = m.GetBoundingBox(true);
    }

    protected override void OnDuplicate(RhinoObject source)
    {
        MyMeshObject obj = source as MyMeshObject;
        if (null != obj)
            _bb = obj._bb;
        base.OnDuplicate(source);
    }

    protected override void OnTransform(Transform transform)
    {
        _bb.Transform(transform);
        base.OnTransform(transform);
    }

    protected override void OnDraw(DrawEventArgs e)
    {
        e.Display.DrawPoint(_bb.Center, Color.Red);
        base.OnDraw(e);
    }
}

A kind nudge in the hope someone at McNeel has any idea about this. @stevebaer or @dale if you could shed some light on this that would be awesome :smile:

We are able to repeat what you are seeing here. I’ve posted this as a bug.

http://mcneel.myjetbrains.com/youtrack/issue/RH-21339

We’re a little swamped at the moment. Hopefully we can take a serious look at this soon.

Thanks. As long as you can repeat the problem and file it as a bug in your system I’m happy that it will get fixed in a future release of rhino, hopefully in a service release. In the mean time I know not to create commands with this functionality.

Any news? I still see this problem in SR7.

Nothing new - still on the “to-do” list. The fix will probably not make it into SR8 - sorry.

Sorry this took so long; getting custom objects to work properly in RhinoCommon is a pretty big project and getting the wiring just right takes a lot of focus:)

This bug should be fixed in SR9 as well as a few other custom object related bugs that users have reported.