Rhino 9 adds an SDK for in-viewport user interface objects - widgets that draw inside a viewport and respond to the mouse. Instead of pushing users out to a command line or panel, you can put the control right next to what it controls.
We have been using this internally for a while now and feel it is worth mentioning. If you have run the Rhino 9 BETA you may have already used it:
ArrayCrvAdvanced- the on-curve span grips, the size and spacing arrows, the rotation/pitch/roll rings.Patch- the continuity badges on each constraint curve; click to step through G0/G1/G2GlobalEdgeContinuity- the numbered, color-coded dot at each evaluated edge; click one to select and zoom to that edgeScaleEach/RotateEach- the in-viewport sliders you drag to set the start and end factorSubDMatch- the bulge direction arrowsMarkup- the whole floating tool HUDGrasshopper's Curve Widget and Angular WidgetcomponentsEditHatchGradient- drag the gradient anchors and color stops right on the hatchSetHatchBaseAndRotation- the on-screen protractor
Two kinds of UI objects
World-space objects live at a 3d point in the model and pan and zoom with the scene. The grip classes can additionally be dragged in 3d, constrained to a curve, and osnapped.
Screen-space controls are 2d widgets aligned to the viewport. They can optionally “stick” to a 3d point so they follow an object around while staying screen-aligned and screen-sized.
Hello UI Widget
Here’s a basic sample that adds a grip widget to the document that users can drag around.
using Rhino;
using Rhino.Commands;
using Rhino.Geometry;
using Rhino.UI;
class MyGrip : GripUserInterfaceObject
{
public MyGrip(Point3d location) : base(location)
{
GripFillColor = System.Drawing.Color.Orange;
}
protected override void OnDrag(Point3d newLocation, MouseState mouse)
{
base.OnDrag(newLocation, mouse);
RhinoApp.WriteLine($"grip is at {GripLocation}");
}
}
public class SampleGripCommand : Command
{
public override string EnglishName => "SampleGrip";
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
// If the grip has already been added, remove it. Otherwise, add one
// The id is used to define a group of user interface objects.
// Using the command's Id is a good convention
bool gripExisted = doc.ViewUserInterface.RemoveByGroupId(this.Id) > 0;
if (!gripExisted)
{
doc.ViewUserInterface.Add(new MyGrip(new Point3d(0, 0, 0)), this.Id);
}
// Note that the command returns immediately for this sample and the grip stays "live"
doc.Views.Redraw();
return Result.Success;
}
}
Screen controls
var btn = new UserInterfaceButton
{
Text = "Bake",
Location = new System.Drawing.Point(10, 100),
HorizontalAlignment = ControlHorizontalAlignment.Left,
VerticalAlignment = ControlVerticalAlignment.Bottom
};
// Instead of text, you can also set an SVG string to the button
btn.Click += (s, e) => RhinoApp.WriteLine("clicked");
doc.ViewUserInterface.Add(btn, Id);
var slider = new UserInterfaceSlider
{
Location = new System.Drawing.Point(10, 140),
HorizontalAlignment = ControlHorizontalAlignment.Left,
VerticalAlignment = ControlVerticalAlignment.Bottom,
Range = new Interval(0, 10),
Value = 5,
DigitPrecision = 0 // 0 == integers only
};
slider.ValueChanged += (s, e) => RhinoApp.WriteLine($"{slider.Value}");
doc.ViewUserInterface.Add(slider, Id);
Modifying the document from a mouse handler
You are inside a mouse callback, not a command, so undo records and getters are not set up the way you would expect. In C++ there is an explicit path for this — call RunCommand(mouse) and do your work in the `OnRunCommand` override, which runs in proper command scope.
C++
The same API is available in the C++ SDK, in rhinoSdkUserInterfaceObject.h
| RhinoCommon | C++ |
|---|---|
UserInterfaceObjectBase |
CRhinoUserInterfaceObject |
GripUserInterfaceObject |
CRhinoGripUserInterfaceObject |
DirectionGripUserInterfaceObject |
CRhinoDirectionGripUserInterfaceObject |
RotationGripUserInterfaceObject |
CRhinoRotationGripUserInterfaceObject |
TextDotUserInterfaceObject |
CRhinoTextDotUserInterfaceObject |
UserInterfaceControl |
CRhinoUserInterfaceControl |
UserInterfaceButton / Slider / Icon |
CRhinoUserInterfaceButton / Slider / Icon |
doc.ViewUserInterface.Add(...) |
CRhinoDoc::AddUserInterfaceObject(...) |
RemoveByGroupId(...) |
CRhinoDoc::RemoveUserInterfaceObjects(groupId) |
More to come
Building out an entire user interface library is a massive undertaking. We’ll continue to add controls, but it needs to have priority. Please let us know what you need so we can prioritize.
