I found this Python version of implementing Gumball in Grasshopper.
import Rhino
from Rhino.Display import PointStyle
from Rhino.Input.Custom import GetTransform, PickContext, PickStyle
from Rhino.Input import GetResult
from Rhino.Commands import Result
from Rhino.UI.CursorStyle import Default
from Rhino.UI.Gumball import GumballObject, GumballDisplayConduit, GumballAppearanceSettings, GumballMode
from Rhino.Geometry import Transform, Line, Point3d,Plane, Vector3d
from Rhino.RhinoDoc import ActiveDoc
import scriptcontext as sc
from System.Windows.Forms import MessageBox
from System.Drawing import Color
from System.Windows.Forms.Cursors import Arrow
def gumball_appearance_settings():
"""Returns gumball appearance settings with only translation switched on"""
gas = GumballAppearanceSettings()
gas.ScaleXEnabled = False
gas.ScaleYEnabled = False
gas.ScaleZEnabled = False
gas.RotateXEnabled = False
gas.RotateYEnabled = False
gas.RotateZEnabled = False
gas.MenuEnabled = False
gas.RelocateEnabled = False
return gas
class CustomGetTransform(GetTransform):
"""
CustomGet class derived from GetTransform
"""
def __init__(self, pts_to_select_from, display_conduit):
self.pts_to_select_from = pts_to_select_from
self.dc = display_conduit
self.current_sel_index = None
#info is a dictionary storing (index of selected point, transform) tuples
self.info = {}
def CalculateTransform(self,viewport,point):
return self.dc.TotalTransform
def OnMouseDown(self, e):
#initial mouse down event
if self.dc.PickResult.Mode != GumballMode.None:
return
self.dc.PickResult.SetToDefault()
picker = PickContext()
picker.View = e.Viewport.ParentView
picker.PickStyle = PickStyle.PointPick
xform = e.Viewport.GetPickTransform(e.WindowPoint)
picker.SetPickTransform(xform)
# Point selection
for index, pt in enumerate(self.pts_to_select_from):
if picker.PickFrustumTest(pt)[0]:
self.current_sel_index = index
if index not in self.info:
self.info[index] = Transform.Identity
break
# Gumball
picker.PickLine = e.Viewport.GetFrustumLine(e.WindowPoint.X, e.WindowPoint.Y)[1]
picker.UpdateClippingPlanes()
self.dc.PickGumball(picker, self)
def update_selected_point(self,xform):
""" Update the selected point and the info dictionary"""
_pt = self.pts_to_select_from[self.current_sel_index]
_pt.Transform(xform)
self.info[self.current_sel_index] *= xform
def OnMouseMove(self, e):
""" Updates the Gumball in the display conduit"""
if self.dc.PickResult.Mode == GumballMode.None:
return
res, world_line = e.Viewport.GetFrustumLine(e.WindowPoint.X, e.WindowPoint.Y)
if not res: world_line = Line.Unset
rc = self.dc.UpdateGumball(e.Point, world_line)
if rc:
GetTransform.OnMouseMove(self,e)
def MoveGumball(self):
if self.dc.PreTransform != Transform.Identity:
self.HaveTransform = True
self.Transform = self.dc.PreTransform
self.SetBasePoint(self.dc.BaseGumball.Frame.Plane.Origin, False)
if self.Transform != Transform.Identity:
self.update_selected_point(self.Transform)
return self.Get(True)
def OnDynamicDraw(self,e):
for i in self.info.keys():
e.Display.DrawPoint(self.pts_to_select_from[i],PointStyle.X, 5, Color.Green)
def manipulate_points(pts_to_select_from):
conduit = GumballDisplayConduit()
base_gumball = GumballObject()
cgt = CustomGetTransform(pts_to_select_from, conduit)
cgt.SetCursor(Default)
cgt.Enabled = True
while 1:
if cgt.current_sel_index is not None:
current_sel_pt = pts_to_select_from[cgt.current_sel_index]
if conduit.PickResult.Mode == GumballMode.None:
base_gumball.SetFromPlane(Plane(current_sel_pt, Vector3d.ZAxis))
conduit.SetBaseGumball(base_gumball, gumball_appearance_settings())
conduit.Enabled = True
cgt.dc = conduit
cgt.SetCommandPrompt("Drag gumball or select another point. (<ESC> to exit)")
cgt.MoveGumball()
conduit.Enabled = False
else:
cgt.SetCommandPrompt("Select grasshopper points. (<ESC> to exit)")
cgt.Get(True)
sc.doc.Views.Redraw()
if cgt.CommandResult() != Result.Success:
cgt.Enabled = False
return cgt.info
if cgt.Result() == GetResult.Point:
# Update base gumball
gb_frame = conduit.Gumball.Frame
base_frame = base_gumball.Frame
base_frame.Plane = gb_frame.Plane
base_gumball.Frame = base_frame
def reset_toggle():
"""Turns off toggle (on) connected to this component"""
toggle = ghenv.Component.Params.Input[1].Sources[0]
toggle.Value = False
toggle.ExpireSolution(True)
if reset and "get_result" in sc.sticky:
sc.sticky["get_result"].clear()
if on:
#MessageBox Prompt
MessageBox.Show("Pick points in the rhino window")
#Can Ignore the MessageBox, not That Necessary
# Call manipulate_points
sc.sticky["get_result"] = manipulate_points(pts)
reset_toggle()
if "get_result" in sc.sticky:
for index, xform in sc.sticky["get_result"].iteritems():
pts[index].Transform(xform)
a = pts
I translated the Code to C# here
using System;
using System.Collections;
using System.Collections.Generic;
using Rhino;
using Rhino.Geometry;
using Grasshopper;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using System.Linq;
using Grasshopper.Kernel.Special;
using Grasshopper.Kernel.Types.Transforms;
using Rhino.Input.Custom;
using Rhino.Commands;
using Rhino.Display;
using Rhino.Input;
using Rhino.UI;
using System.Windows.Forms;
using System.Drawing;
using Rhino.UI.Gumball;
/// <summary>
/// This class will be instantiated on demand by the Script component.
/// </summary>
public class Script_Instance : GH_ScriptInstance
{
#region Utility functions
/// <summary>Print a String to the [Out] Parameter of the Script component.</summary>
/// <param name="text">String to print.</param>
private void Print(string text) { /* Implementation hidden. */ }
/// <summary>Print a formatted String to the [Out] Parameter of the Script component.</summary>
/// <param name="format">String format.</param>
/// <param name="args">Formatting parameters.</param>
private void Print(string format, params object[] args) { /* Implementation hidden. */ }
/// <summary>Print useful information about an object instance to the [Out] Parameter of the Script component. </summary>
/// <param name="obj">Object instance to parse.</param>
private void Reflect(object obj) { /* Implementation hidden. */ }
/// <summary>Print the signatures of all the overloads of a specific method to the [Out] Parameter of the Script component. </summary>
/// <param name="obj">Object instance to parse.</param>
private void Reflect(object obj, string method_name) { /* Implementation hidden. */ }
#endregion
#region Members
/// <summary>Gets the current Rhino document.</summary>
private readonly RhinoDoc RhinoDocument;
/// <summary>Gets the Grasshopper document that owns this script.</summary>
private readonly GH_Document GrasshopperDocument;
/// <summary>Gets the Grasshopper script component that owns this script.</summary>
private readonly IGH_Component Component;
/// <summary>
/// Gets the current iteration count. The first call to RunScript() is associated with Iteration==0.
/// Any subsequent call within the same solution will increment the Iteration count.
/// </summary>
private readonly int Iteration;
#endregion
/// <summary>
/// This procedure contains the user code. Input parameters are provided as regular arguments,
/// Output parameters as ref arguments. You don't have to assign output parameters,
/// they will have a default value.
/// </summary>
private void RunScript(List<Point3d> pts, bool on, object clear, ref object A)
{
List<Line> outLines = new List<Line>();
if (on)
{
stickyData = ManipulatePoints(pts);
var toggle = this.Component.Params.Input[1].Sources[0];
((GH_BooleanToggle) toggle).Value = false;
toggle.ExpireSolution(true);
}
if (stickyData.Count > 0)
{
if ((bool) clear) stickyData.Clear();
foreach (var i in stickyData.Keys)
pts[i].Transform(stickyData[i]);
}
A = pts;
}
// <Custom additional code>
Dictionary<int, Transform> stickyData = new Dictionary<int, Transform>();
private GumballAppearanceSettings CustomGumballAppearanceSettings()
{
//Return Gumball Appearance Settings with only Translation
var gas = new GumballAppearanceSettings
{
RotateXEnabled = false,
RotateYEnabled = false,
RotateZEnabled = false,
ScaleXEnabled = false,
ScaleYEnabled = false,
ScaleZEnabled = false,
TranslateXEnabled = true,
TranslateXYEnabled = true,
TranslateYEnabled = true,
TranslateYZEnabled = true,
TranslateZEnabled = true,
TranslateZXEnabled = true,
MenuEnabled = false,
RelocateEnabled = false
};
return gas;
}
class CustomGetTransform : GetTransform
{
public GumballDisplayConduit lineDc;
private readonly List<Point3d> ptsToSelectFrom;
public int currentSelIndex;
public readonly Dictionary<int, Transform> info;
public CustomGetTransform(GumballDisplayConduit inLineDc, List<Point3d> inPtsToSelectFrom)
{
lineDc = inLineDc;
ptsToSelectFrom = inPtsToSelectFrom;
currentSelIndex = -1;
info = new Dictionary<int, Transform>();
}
public override Transform CalculateTransform(RhinoViewport viewport, Point3d point)
{
// do we want this first line??
//if (lineDc.InRelocate) return lineDc.PreTransform;
return lineDc.TotalTransform;
}
protected override void OnMouseDown(GetPointMouseEventArgs e)
{
//initial mouse down event
if (lineDc.PickResult.Mode != GumballMode.None)
return;
lineDc.PickResult.SetToDefault();
var picker = new PickContext
{
View = e.Viewport.ParentView,
PickStyle = PickStyle.PointPick
};
var xform = e.Viewport.GetPickTransform(e.WindowPoint);
picker.SetPickTransform(xform);
//Point Selection
for (int i = 0; i < ptsToSelectFrom.Count; i++)
{
double depth;
double distance;
if (picker.PickFrustumTest(ptsToSelectFrom[i], out depth, out distance))
{
currentSelIndex = i;
if (!info.ContainsKey(i))
info.Add(i, Transform.Identity);
break;
}
}
//Gumball
Line pickLine;
e.Viewport.GetFrustumLine(e.WindowPoint.X, e.WindowPoint.Y, out pickLine);
picker.PickLine = pickLine;
picker.UpdateClippingPlanes();
lineDc.PickGumball(picker, this);
}
private void updateSelectedPoint(Transform xform)
{
//Update the Selected Point and info Dictionary
var pt = ptsToSelectFrom[currentSelIndex];
pt.Transform(xform);
info[currentSelIndex] *= xform;
}
protected override void OnMouseMove(GetPointMouseEventArgs e)
{
//Update the Gumball in the Display Conduit
if (lineDc.PickResult.Mode == GumballMode.None) return;
//lineDc.CheckShiftAndControlKeys();
Line worldLine;
var res = e.Viewport.GetFrustumLine(e.WindowPoint.X, e.WindowPoint.Y, out worldLine);
if (!res)
worldLine = Line.Unset;
var rc = lineDc.UpdateGumball(e.Point, worldLine);
if (rc)
base.OnMouseMove(e);
}
public GetResult MoveGumball()
{
// Get point on a MouseUp event
if (lineDc.PreTransform != Transform.Identity)
{
HaveTransform = true;
Transform = lineDc.PreTransform;
}
SetBasePoint(lineDc.BaseGumball.Frame.Plane.Origin, false);
// V5 uses a display conduit to provide display feedback
// so shaded objects move
//ObjectList.DisplayFeedbackEnabled = true;
if (this.Transform != Transform.Identity)
{
//ObjectList.UpdateDisplayFeedbackTransform(Transform);
updateSelectedPoint(this.Transform);
}
// Call Get with mouseUp set to true
var rc = this.Get(true);
// V5 uses a display conduit to provide display feedback
// so shaded objects move
//ObjectList.DisplayFeedbackEnabled = false;
return rc;
}
protected override void OnDynamicDraw(GetPointDrawEventArgs e)
{
foreach (var i in info.Keys)
e.Display.DrawPoint(ptsToSelectFrom[i], PointStyle.X, 5, System.Drawing.Color.Green);
}
}
private Dictionary<int, Transform> ManipulatePoints(List<Point3d> ptsToSelectFrom)
{
var conduit = new GumballDisplayConduit();
var baseGumball = new GumballObject();
var cgt = new CustomGetTransform(conduit, ptsToSelectFrom);
cgt.SetCursor(CursorStyle.Default);
cgt.lineDc.Enabled = true;
while (true)
{
if (cgt.currentSelIndex >= 0)
{
var currentSelPt = ptsToSelectFrom[cgt.currentSelIndex];
if (conduit.PickResult.Mode == GumballMode.None)
baseGumball.SetFromPlane(new Plane(currentSelPt, Vector3d.ZAxis));
conduit.SetBaseGumball(baseGumball, CustomGumballAppearanceSettings());
conduit.Enabled = true;
cgt.lineDc = conduit;
cgt.SetCommandPrompt("Drag gumball or select another point. (<ESC> to exit)");
cgt.MoveGumball();
conduit.Enabled = false;
}
else
{
cgt.SetCommandPrompt("Select grasshopper points. (<ESC> to exit)");
cgt.Get(true);
}
RhinoDoc.ActiveDoc.Views.Redraw();
if (cgt.CommandResult() != Result.Success)
{
cgt.lineDc.Enabled = false;
return cgt.info;
}
if (cgt.Result() == GetResult.Point)
{
// update base gumball
var gbFrame = conduit.Gumball.Frame;
var baseFrame = baseGumball.Frame;
baseFrame.Plane = gbFrame.Plane;
baseGumball.Frame = baseFrame;
}
}
}
// </Custom additional code>
}
The Code seems to be working all good and there is no exception thrown by the component.
I can Pick the point successfully, but when I move the Gumball the position of Point does not update.
Here is the Grasshopper script attached as well. Python to C# Gumball manipulate.gh (13.8 KB)
Can someone help me pointing out what could be the error. Thanks.