How can I select a mesh vertex by a mouse click?

Hi, after uploading a mesh, it is possible for the user to add a point on the mesh by picking a vertex with a mouse click? Or, otherwise, to select a mesh vertex to obtain its 3D coordinates by picking the surface of the mesh with a mouse click?

Can you tell me more about your use case? Using the API it is possible to make the mesh clickable, but not its individual vertices if it is really a single mesh object. Depending on your workflow, you could for example add a point cloud of the mesh vertices to the scene and make each point clickable, but how useful that is really depends on what you plan to do with this vertex after it is selected.

In the meanwhile, find here a tutorial on clickable elements in the ShapeDiver viewer (for now only using the API).

Hi Mathieu, I need to pick 3points on a single mesh object, or, otherwise, add 3 points on the surface of the mesh. This is because the user, after uploading a custom mesh, have to identify 3 reference points that are needed for the workflow. In particular, I need the 3D coordinates of each picked point to design a circle centered in there. I think that a point cloud of the mesh vertices will be hard to manage due to the huge amount of generated points. I think I’ll need something such as the component “select mesh vertices” of the plugin Mesh+ (http://www.grasshopper3d.com/group/mesh).

I understand the use case. Since picking vertices of the mesh is not possible, I would recommend instead to work with draggable pointers, for example small spheres, which can be positioned on the mesh in the viewer and then have their positions sent to Grasshopper using the API.

We are currently working on making it possible to constrain draggable elements to a specific object of the scene, which will make it more convenient in this case to pick points directly on the mesh. This feature will be available in the next few weeks. In the meantime, it is always possible to drag points in the viewer and find an approximate position that is sent to Grasshopper, where it can be projected on the mesh for more accuracy.

Check out this link where I wrote a quick demo for this functionality. Only the projection of the point on the mesh is done in grasshopper. Again, with the new feature discussed above, a much simpler solution will be possible. I will post again here when the feature is ready.

Ciao!
This is a short c# method i’m using recently:
It cast a ray from camera to cursor location and search for intersections with the mesh.
I rearranged outputs so it give:
P - the actual 3d point
i - the index of the nearest mesh vertex
V - the 3d point of that vertex
As how this is done, it cast rays even if you click over the grasshopper canvas , so I disabled the case of a “miss”.
Give it a try.

cursor_mesh_point
cursor_mesh_point.gh (41.9 KB)

Hope it helps.

Code:

private void RunScript(Mesh mesh, bool On, bool reset, ref object P, ref object i, ref object V)
  {
    m = mesh;
    if(reset) index = -1;
    cursor_mesh_point();
    if(index != -1){ P = pt; V = v; }
    i = index;
    if(On){Component.ExpireSolution(true);} // Expiring solution to restart "RunScript"
  }

  // <Custom additional code> 
  public Rhino.Geometry.Mesh m;
  public Rhino.Geometry.Point3d pt;
  int index = -1;
  public Rhino.Geometry.Point3d v;

  public void cursor_mesh_point(){
    if(System.Windows.Forms.Control.MouseButtons.ToString() == "Left"){
      int X = System.Windows.Forms.Cursor.Position.X;
      int Y = System.Windows.Forms.Cursor.Position.Y;
      System.Drawing.Rectangle rec = RhinoDocument.Views.ActiveView.ScreenRectangle;
      int x0 = rec.Left;
      int x1 = rec.Right;
      int y0 = rec.Top;
      int y1 = rec.Bottom;
      Rhino.Geometry.Line line = new Rhino.Geometry.Line();
      if(X > x0 & X < x1 & Y > y0 & Y < y1){
        RhinoDocument.Views.ActiveView.ActiveViewport.GetFrustumLine((X - x0), (Y - y0), out line);
        Rhino.Geometry.Ray3d ray = new Rhino.Geometry.Ray3d(line.PointAt(1), -line.UnitTangent);
        double val = Rhino.Geometry.Intersect.Intersection.MeshRay(m, ray);

        if(val > 0){ // HIT
          pt = ray.PointAt(val);
          Rhino.Geometry.PointCloud pc = new Rhino.Geometry.PointCloud(m.Vertices.ToPoint3dArray());
          index = pc.ClosestPoint(pt);
          v = m.Vertices[index];
        }

        else{ //MISS
          // index = -1; // remove comment here to take in account "misses"
        }

      }
    }
  }
5 Likes

Thank you very much Mathieu

Thank you Riccardo!

Please note that Riccardo’s solution is an elegant way to do what you want to do but only inside Rhino/Grasshopper. It will not work in ShapeDiver’s online viewer as we have no way to replicate the script which overrides Rhino’s rendering conduit.

1 Like

@mathieu1 Yes, our model is denied, probably because of the scripts.
We need another solution for selecting points on mesh. Any ideas?

At the moment, I can’t offer anything significantly better than the solution I proposed in my answer above: https://jsfiddle.net/ShapeDiver/n0678uzc/

Did you try it? Does it not work in your case?

Hi Mathieu,
I’ve seen that the demo you posted (https://jsfiddle.net/ShapeDiver/n0678uzc/) has been updated: now it is possible picking three points directly on the mesh and then drag them on the mesh itself.
Can you also pass me a Grasshopper example with the input for these three points?
Thank you very much in advance.

Here is the grasshopper file for the demo.
This is still a temporary solution until we add the feature where points can be constrained to the mesh when dragging them. For now, the projection on the mesh is done in Grasshopper.drag_and_project.gh (281.2 KB)

Ok, thank you Mathieu!

1 Like

Hello Ricardo, thank you for this great solution. Is it also possible to click on multiple points and save them in a list? I can’t find a command to save the points directly in a list. So that I can pass and process them directly after, for example, confirming with Enter.

Many greetings,
Patrik

Hi,

I want to select multiple points on a mesh by a mouse click. Is it possible to adapt your method and select multiple mesh vertexes while not changing the vertex of the previous one selected?


cursor_mesh_points.gh (41.5 KB)

Thanks! Now in cull pattern I get the points. However, I would like to have the index of the selected points. Do you know how to manage this?

Your script works great, thanks for sharing. I did slight modification, added a selection limit, added point, normal and face index as output and changed mouse behavior from selection while pressed to just mouse clicks.

Unfortunatelly, the script constantly triggers a new solution down the stream (when selecting pts) and I don’t know if there is a way how to make this better. Someone has an idea?

cursor_mesh_points_2.gh (522.8 KB)

1 Like

Hi.
It was not a really good script…
Something like this is better:
mousedown.gh (3.8 KB)

private void RunScript(bool x, ref object A)
  {
    if(k == null) k = new TestMouseCallback();
    if(comp == null)comp = this.Component;
    if(k.Enabled != x)k.Enabled = x;
    if(!x)c = 0;
    A = c;
  }

  // <Custom additional code> 
  public static int c;
  public static IGH_Component comp;
  TestMouseCallback k;
  public class TestMouseCallback : Rhino.UI.MouseCallback{
    protected override void OnMouseDown(Rhino.UI.MouseCallbackEventArgs e){
      c++;
      comp.ExpireSolution(true);
    }
  }

Rhino.UI.MouseCallback class will shoot an even only when you click inside rhino viewports, othersiwe the .ExpireSolution thing is never called.

If you add a e.Cancel = true; inside the override void, it will prevent the “click” to do the default operation (like selecting things…)

Please do tell if you manage or need further help. This is interesting.

1 Like

Thank you for your hints, @maje90! Now it runs smooth and I really love the functionality. There are probably bugs and issues, but I am not a proper C# coder, so everyone feel free to improve & share.

using System;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
//using System.Drawing;

using Rhino;
using Rhino.UI;
using Rhino.Geometry;
using Rhino.Geometry.Collections;

using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;

public class Script_Instance : GH_ScriptInstance
{
    private void RunScript(
	Mesh M,
	int L,
	bool O,
	bool R,
	ref object P,
	ref object N,
	ref object I,
	ref object F)
    {   
        // Add description to inputs and outputs    
        Component.Params.Input[0].Description = "A mesh referenced from Rhino or Grasshopper mesh.";
        Component.Params.Input[1].Description = "Maximum number of points that can be selected, default is 1.";
        Component.Params.Input[1].Optional = true;
        Component.Params.Input[2].Description = "Activate selection. Set to true to start selecting points.";
        Component.Params.Input[3].Description = "Reset selection. Press a button to reset the selection process.";
        
        Component.Params.Output[0].Description = "Selected points on the mesh.";
        Component.Params.Output[1].Description = "Face normals of the selected points.";
        Component.Params.Output[2].Description = "Indices of the selected mesh faces.";
        Component.Params.Output[3].Description = "True when selection was finished.";

        // Set default value for L to 1
        if (Component.Params.Input[1].SourceCount == 0)
        {
            Component.Params.Input[1].AddVolatileData(new GH_Path(0), 0, 1);
        }

        m = M.DuplicateMesh();
        if (m.FaceNormals.Count == 0) m.FaceNormals.ComputeFaceNormals();
        meshFaceNormals = m.FaceNormals;
        if(comp == null) comp = this.Component;
        
        // Initialize MyMouseCallback 
        if(k == null) k = new MyMouseCallback();
        
        if(R) {
            selectedCount = 0;
            faceIndexes.Clear();
            points.Clear();
            normals.Clear();
            selectedCount = 0;
            k.Enabled = false;
            finished = false;
        } else if (O && selectedCount < L) {            
            k.Enabled = true;
            comp.Message = $"Select {selectedCount +1 } of {L}";
        } else if (selectedCount == L) {
            k.Enabled = false;
            finished = true;
            comp.Message = $"Complete";
        } else {
            comp.Message = null;
        }
        
        N = normals;
        P = points;
        I = faceIndexes;
        F = finished;
    }
    
    public bool finished = false;
    public static Mesh m;
    public static MeshFaceNormalList meshFaceNormals;
    public static List<Vector3d> normals = new List<Vector3d>();
    public static List<Point3d> points = new List<Point3d>();
    public static List<int> faceIndexes = new List<int>();    
    public static int selectedCount = 0;
    public static IGH_Component comp;
    public static RhinoDoc rd = RhinoDoc.ActiveDoc;
    MyMouseCallback k;

    public class MyMouseCallback : Rhino.UI.MouseCallback{
        protected override void OnMouseDown(Rhino.UI.MouseCallbackEventArgs e) {
            if (e.MouseButton == MouseButton.Left) {
                e.Cancel = true;
                
                Line line = new Line();
                rd.Views.ActiveView.ActiveViewport.GetFrustumLine(e.ViewportPoint.X, e.ViewportPoint.Y, out line);
                Ray3d ray = new Ray3d(line.PointAt(1), -line.UnitTangent);                
                double val = Rhino.Geometry.Intersect.Intersection.MeshRay(m, ray, out int[] faces);
                                
                if (val > 0) // HIT
                {
                    int faceIndex = faces[0];
                    points.Add(ray.PointAt(val));
                    normals.Add(meshFaceNormals[faceIndex]);
                    faceIndexes.Add(faceIndex);
                    selectedCount += 1;
                    comp.ExpireSolution(true);
                }                
            }            
        }
    }

    public override BoundingBox ClippingBox => BoundingBox.Empty;

    public override void DrawViewportMeshes(IGH_PreviewArgs args)
    {
    }

    public override void DrawViewportWires(IGH_PreviewArgs args)
    {
    }
}

cursor_mesh_points_3.gh (517.6 KB)

1 Like