Hi all!
I am working on a Rhino 7 plugin that has a simple UI that I am developing with Eto Forms (A dialog box that contains a simple checkbox with two states). I have a noob question about Eto as this is the first project where I am implementing it.
The idea is to select some meshes and perform a boolean union, however, before adding it to the rhino file I would like to preview it and decide if I want to go forward (and add the resulting mesh to the file) or abort the command without adding the geometry. I am facing some issues:
I am calling the Eto Dialog box at some point in my command execution, right after I performed the object selection. I am calling the Eto form as SemiModal, this blocks further code execution until I close the dialog so my data does not get passed to the preview method until I close the window at the moment which is not great.
I am not very familiar with Eto or Events, I managed to write some text in the Rhino Command line when the checkbox is clicked but I am not sure if I can pass values to a method (that creates the preview) using the event. This is because the event does not know what the selection is when I create it as it is not in the scope.
I am not sure if the issue I am having is related to the fact that I am not running the Eto asynchronously or if it can be solved with events and handlers only.
I am attaching the code of my command to this message. Any kind of help, even if it is pointing me in the right direction would be amazing. I tested all the Eto developer samples but could not find what I am looking for.
namespace TestEtoCheckbox
{
public class TestEtoCheckboxCommand : Command
{
public TestEtoCheckboxCommand()
{
// Rhino only creates one instance of each command class defined in a
// plug-in, so it is safe to store a refence in a static property.
Instance = this;
}
///<summary>The only instance of this command.</summary>
public static TestEtoCheckboxCommand Instance { get; private set; }
///<returns>The command name as it appears on the Rhino command line.</returns>
public override string EnglishName => "TestEtoCheckboxCommand";
public bool Preview { get; set; } = false;
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
#region 1.Selection
// Perform Selection
List<Rhino.DocObjects.ObjRef> my_selection = new List<Rhino.DocObjects.ObjRef>();
//Set Filter
bool isMeshCheck(Rhino.DocObjects.RhinoObject rhObject, GeometryBase geometry, ComponentIndex componentIndex)
{
bool is_Mesh = false;
if (geometry.ObjectType == Rhino.DocObjects.ObjectType.Mesh)
{
is_Mesh = true;
}
return is_Mesh;
}
var gm = new GetObject();
gm.SetCommandPrompt("Select Meshes to Join");
gm.SetCustomGeometryFilter(isMeshCheck);
gm.GetMultiple(1, 0);
gm.EnablePreSelect(true, false);
gm.DeselectAllBeforePostSelect = false;
if (gm.CommandResult() != Result.Success)
{
return gm.CommandResult();
}
else
{
for (int i = 0; i < gm.ObjectCount; i++)
{
var tempGeo = gm.Object(i);
my_selection.Add(tempGeo);
}
}
List<Mesh> MeshSelection = new List<Mesh>();
foreach (var obj in my_selection)
{
if((obj.Geometry().ObjectType) == Rhino.DocObjects.ObjectType.Mesh)
{
MeshSelection.Add(obj.Mesh());
}
}
#endregion
#region 2. Eto Form
var args = new my_Args
{
Preview = Preview
};
var dlg = new my_Dialog(args);
var res = dlg.ShowSemiModal(doc, Rhino.UI.RhinoEtoApp.MainWindow);
if (res)
{
args = dlg.Results;
Preview = args.Preview;
}
if (dlg.Result == false)
{
return Result.Cancel;
}
#endregion
#region 3. Perform Boolean Union
Mesh meshResult = new Mesh();
if (MeshSelection != null)
{
if (MeshSelection.Count > 1)
{
meshResult = Mesh.CreateBooleanUnion(MeshSelection)[0];
}
else
{
meshResult = MeshSelection[0];
}
}
doc.Objects.AddMesh(meshResult);
#endregion
#region 4. Preview Result
//Setup Preview?
var conduit = new DrawBlueMeshConduit(meshResult);
if (Preview == true)
{
conduit.Enabled = true;
doc.Views.Redraw();
}
else
{
conduit.Enabled = false;
doc.Views.Redraw();
}
#endregion
// ---
doc.Views.Redraw();
return Result.Success;
}
}
#region Define DisplayConduit
class DrawBlueMeshConduit : DisplayConduit
{
readonly Mesh m_mesh;
readonly Color m_color;
readonly DisplayMaterial m_material;
readonly BoundingBox m_bbox;
public DrawBlueMeshConduit(Mesh mesh)
{
// set up as much data as possible so we do the minimum amount of work possible inside
// the actual display code
m_mesh = mesh;
m_color = System.Drawing.Color.Blue;
m_material = new DisplayMaterial();
m_material.Diffuse = m_color;
if (m_mesh != null && m_mesh.IsValid)
m_bbox = m_mesh.GetBoundingBox(true);
}
// this is called every frame inside the drawing code so try to do as little as possible
// in order to not degrade display speed. Don't create new objects if you don't have to as this
// will incur an overhead on the heap and garbage collection.
protected override void CalculateBoundingBox(CalculateBoundingBoxEventArgs e)
{
base.CalculateBoundingBox(e);
// Since we are dynamically drawing geometry, we needed to override
// CalculateBoundingBox. Otherwise, there is a good chance that our
// dynamically drawing geometry would get clipped.
// Union the mesh's bbox with the scene's bounding box
e.IncludeBoundingBox(m_bbox);
}
protected override void PreDrawObjects(DrawEventArgs e)
{
base.PreDrawObjects(e);
var vp = e.Display.Viewport;
if (vp.DisplayMode.EnglishName.ToLower() == "wireframe")
e.Display.DrawMeshWires(m_mesh, m_color);
else
e.Display.DrawMeshShaded(m_mesh, m_material);
}
}
#endregion
#region Define Eto Form
//Define Eto Form
internal class my_Args
{
public bool Preview { get; set; } = false;
}
internal class my_Dialog : Eto.Forms.Dialog<bool>
{
my_Args m_args;
Eto.Forms.CheckBox m_Preview_checkbox;
public my_Dialog(my_Args args)
{
m_args = args ?? throw new ArgumentNullException(nameof(args));
Title = "Test";
Padding = new Eto.Drawing.Padding(5);
var layout = new Eto.Forms.DynamicLayout
{
Padding = new Eto.Drawing.Padding(5),
Spacing = new Eto.Drawing.Size(5, 5)
};
layout.AddRow(null); // spacer
layout.AddRow(CreateCheckBoxes());
layout.AddRow(null); // spacer
layout.AddRow(CreateButtons());
Content = layout;
}
public my_Args Results => m_args;
private Eto.Forms.DynamicLayout CreateCheckBoxes()
{
m_Preview_checkbox = new Eto.Forms.CheckBox
{
Text = "Preview",
Checked = m_args.Preview,
ThreeState = false
};
m_Preview_checkbox.CheckedChanged += ChangedValPreview;
var layout = new Eto.Forms.DynamicLayout { Spacing = new Eto.Drawing.Size(5, 5) };
layout.AddRow(m_Preview_checkbox);
return layout;
}
private Eto.Forms.DynamicLayout CreateButtons()
{
DefaultButton = new Eto.Forms.Button { Text = "OK" };
DefaultButton.Click += DefaultButton_Click;
AbortButton = new Eto.Forms.Button { Text = "Cancel" };
AbortButton.Click += AbortButton_Click;
var layout = new Eto.Forms.DynamicLayout { Spacing = new Eto.Drawing.Size(5, 5) };
layout.AddRow(null, DefaultButton, AbortButton, null);
return layout;
}
private void DefaultButton_Click(object sender, EventArgs e)
{
m_args.Preview = (bool)m_Preview_checkbox.Checked;
Close(true);
}
private void AbortButton_Click(object sender, EventArgs e)
{
Close(false);
}
private void ChangedValPreview(object sender, EventArgs e)
{
RhinoApp.WriteLine("Value has changed");
}
}
#endregion
}
Thank you very much and keep the good work in this amazing community!
TestEtoCheckboxCommand.cs (8.4 KB)