Move Points In Plugin The Same Way As Rhino Interface

Hello All,

I am working on a plugin that involves moving points on a mesh. Is it possible for a plugin to support a flow such as:

  1. The user selects a point (1 point selected)
  2. The user moves selected point
  3. The user selects another point (2 points selected now)
  4. The user moves both points.
  5. User Confirms Moves (Edit: Not necessary)

I would also like for a user to be able to deselect any selected points as well. Ultimately I want to do this with no prompts to the user in between the steps laid out above. Also, how would one make the vertices of the mesh visible from within a plugin (as opposed to PointsOn in the command line)?

Here is an example of the operation I want to perform in Rhino (outside of the plugin):

This is a video of what I currently do inside of my plugin. It is based on the Gumball examples in the Rhino github (rhino-developer-samples/cpp/SampleCommands at 6 · mcneel/rhino-developer-samples · GitHub):

Here is the relevant code related to how the plugin has the user select points and then move them:

CRhinoCommand::result move_vertices(MeshSurfaces* a_MeshSurfaces, const CRhinoCommandContext& a_Context)
{
	//Grab mesh vertex to move
	CRhinoGetObject t_GetVerts;
	t_GetVerts.SetCommandPrompt(L"Select Mesh Vertex/Vertices to Move");
	t_GetVerts.SetGeometryFilter(CRhinoObject::meshvertex_filter);
	t_GetVerts.GetObjects(1, 0);
	if (t_GetVerts.CommandResult() != CRhinoCommand::success)
		return t_GetVerts.CommandResult();

	int t_NumThreads = 1;
	VertexGumballGetXForm t_VG(a_MeshSurfaces, t_GetVerts, t_NumThreads);

	while (true)
	{
		t_VG.setBaseGumball();
		t_VG.EnableGumballDraw(true);
		t_VG.Enable();
		a_Context.Document()->Redraw();

		if (t_VG.PreTransform().IsIdentity())
		{
			t_VG.SetCommandPrompt(L"Drag gumball");
			t_VG.AcceptNothing(FALSE);
		}
		else
		{
			t_VG.SetCommandPrompt(L"Drag gumball. Press Enter when done");
			t_VG.AcceptNothing(TRUE);
		}

		t_VG.moveGumball();
		t_VG.EnableGumballDraw(false);
		t_VG.Disable();

		if (t_VG.CommandResult() != CRhinoCommand::success)
			break;

		CRhinoGet::result t_Result = t_VG.Result();
		if (t_Result == CRhinoGet::point)
		{
			t_VG.updateGumball();
			continue;
		}

		break;
	}

	t_VG.EnableGumballDraw(false);
	t_VG.Disable();

	if (!t_VG.PreTransform().IsIdentity())
	{
		t_VG.updateMeshSurfaces();
	}

	a_Context.Document()->Redraw();
	return CRhinoCommand::success;
}

VertexGumballGetXForm.cpp

CRhinoGet::result VertexGumballGetXForm::moveGumball()
{
	if (m_GumballDC == 0)
		return CRhinoGet::cancel;

	if (!m_GumballDC->PreTransform().IsIdentity())
	{
		m_bHaveXform = TRUE;
		m_xform = m_GumballDC->PreTransform();
	}

	SetBasePoint(m_GumballDC->BaseGumball().m_frame.m_plane.Origin(), false);

	const_cast<CRhinoXformObjectList&>(ObjectList()).EnableDisplayFeedback(true);
	if (!m_xform.IsIdentity())
		const_cast<CRhinoXformObjectList&>(ObjectList()).UpdateDisplayFeedbackXform(m_xform);

	SetGetPointCursor(RhinoApp().m_default_cursor);
	CRhinoGet::result t_Result = GetPoint(0, true);

	const_cast<CRhinoXformObjectList&>(ObjectList()).EnableDisplayFeedback(false);

	return t_Result;
}

void VertexGumballGetXForm::Disable()
{
	m_GumballDC->Disable();
}

void VertexGumballGetXForm::Enable()
{
	m_GumballDC->Enable(m_Context.Document()->RuntimeSerialNumber());
}

void VertexGumballGetXForm::EnableGumballDraw(bool a_Enable)
{
	m_GumballDC->EnableGumballDraw(a_Enable);
}

void VertexGumballGetXForm::updateGumball()
{
	if (!m_GumballDC->m_drag_settings.m_bRelocateGumball)
	{
		ON_Xform xform = m_GumballDC->TotalTransform();
		m_GumballDC->SetPreTransform(xform);
	}

	// update location of base gumball
	CRhinoGumballFrame t_GumballDCFrame = m_GumballDC->Gumball().m_frame;
	CRhinoGumballFrame t_GumballFrame = m_Gumball->m_frame;
	t_GumballFrame.m_plane = t_GumballDCFrame.m_plane;
	t_GumballFrame.m_scale_grip_distance = t_GumballDCFrame.m_scale_grip_distance;
	m_Gumball->m_frame = t_GumballFrame;
}

/*
 *  Input:  CRhinoViewport: Rhino Viewport
 *			ON_3dPoint: The 3d location of the mouse pointer
 *			ON_Xform: The resulting Xform is stored here
 *
 *	Output: BOOL: TRUE for Xform != IdentityTransform
 */
BOOL VertexGumballGetXForm::CalculateTransform(CRhinoViewport& vp, const ON_3dPoint& pt, ON_Xform& xform)
{
	if (m_GumballDC == 0)
		return FALSE;

	if (m_GumballDC->m_drag_settings.m_bRelocateGumball)
		xform = m_GumballDC->PreTransform();
	else
		xform = m_GumballDC->TotalTransform();

	m_TranslationDirection = ON_3dVector(xform.m_xform[0][3], xform.m_xform[1][3], xform.m_xform[2][3]);

	return xform.IsValid() ? TRUE : FALSE;
}

void VertexGumballGetXForm::OnMouseDown(CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const ON_2iPoint* p)
{
	if (p == 0 || m_GumballDC == 0 || m_GumballDC->m_pick_result.m_gumball_mode != gb_mode_nothing)
		return;

	m_GumballDC->m_pick_result.SetToDefaultPickResult();

	CRhinoPickContext t_PickContext;
	t_PickContext.m_view = vp.ParentView();
	t_PickContext.m_pick_style = CRhinoPickContext::point_pick;
	if (vp.SetClippingRegionTransformation(p->x, p->y, t_PickContext.m_pick_region))
	{
		vp.VP().GetFrustumLine(p->x, p->y, t_PickContext.m_pick_line);
		t_PickContext.UpdateClippingPlanes();
		m_GumballDC->PickGumball(t_PickContext, this);
	}
}

/*
 *	Create Nurbs Surfaces each time the mouse pointer is moved
 */
void VertexGumballGetXForm::OnMouseMove(CRhinoViewport& vp, UINT flags, const ON_3dPoint& pt, const ON_2iPoint* p)
{
	if (p == 0 || m_GumballDC == 0 || m_GumballDC->m_pick_result.m_gumball_mode == gb_mode_nothing)
		return;

	m_GumballDC->CheckShiftAndControlKeys();

	ON_Line t_WorldLine;
	if (!vp.VP().GetFrustumLine(p->x, p->y, t_WorldLine))
		t_WorldLine = ON_Line(ON_UNSET_POINT, ON_UNSET_POINT);

	bool t_Success = m_GumballDC->UpdateGumball(pt, t_WorldLine);
	if (t_Success)
	{
		ON_Xform t_Xform;
		m_Draw = false;

		if (CalculateTransform(vp, pt, t_Xform))
		{
			updateUpdatedNurbs();
			m_Draw = true;
		}
		CRhinoGetXform::OnMouseMove(vp, flags, pt, p);
	}

	vp.SetClippingPlanes(ON_BoundingBox(ON_3dPoint(-1000, -1000, -1000), ON_3dPoint(1000, 1000, 1000)));
}

/*
 *	Draw the Mesh and Nurbs Surfaces (with Zebras) to the viewport
 */
void VertexGumballGetXForm::DynamicDraw(CRhinoDisplayPipeline& dp, const ON_3dPoint& pt)
{
	if (m_Draw)
	{
		if (!m_Hidden)
		{
			hide();
		}

		//TODO: Draw only modified portion of Mesh
		// Draw Mesh
		dp.DrawMesh(m_OnMesh, true, false, nullptr);

		// Draw Surfaces
		const CRhinoZebraAnalysisSettings& t_ZebraSettings = RhinoApp().AppSettings().ZebraAnalysisSettings();
		const ON_MeshParameters& t_MeshParameters = m_Context.Document()->Properties().AnalysisMeshSettings();
		const ON_Color& t_Color = RhinoApp().AppSettings().SelectedObjectColor();

		for (NurbsSurfaceToHandlePair t_Current : m_UpdatedNurbs)
		{
			ON_Brep* t_Brep = t_Current.m_NurbsSurface.BrepForm();
			dp.DrawZebraPreview(t_Brep, t_ZebraSettings, t_MeshParameters, t_Color, nullptr);
			delete t_Brep;
		}
	}

	CRhinoGetXform::DynamicDraw(dp, pt);
}

The reason I want to figure out how this can be done is because it will make the user experience more fluid. Any help is appreciated.

Thanks

Hi @robert.beck12569,

The same grips you are using in your native rhino video can also be created from your plugIn and used by users.

I have implemented this in one plugIn of mine which is written in c#, so I can’t give you the exact steps or classes in c++ but i found this Post:

So I think you will have to derive from both CRhinoGripObject and CRhinoGripObjects similar as to shown in the C# sample found here:

Hope to help