Hello.
I’m trying find a proper way have the following:
- Prompt user to pick multiple objects.
- If any objects were already preselected, ability to add/remove in that selection.
- Have command line options that can be changed during object picking without losing previous selection.
I’ve been over this for hours and can only make it satisfy any 2 out of 3 items but not all 3.
using Rhino;
using Rhino.Commands;
using Rhino.DocObjects;
using Rhino.Input;
using Rhino.Input.Custom;
using System;
using System.Collections.Generic;
using System.Linq;
public class SelectWithOptionsCommand : Command
{
public override string EnglishName => "MRE_SelectWithOptions";
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
{
// ── Option state ─────────────────────────────────────────────────
var toggle = new OptionToggle(false, "No", "Yes");
// ── Phase A: capture any pre-selected objects ─────────────────────
var goPre = new GetObject();
goPre.GeometryFilter = ObjectType.Curve;
goPre.EnablePreSelect(true, false);
goPre.EnablePostSelect(false);
goPre.EnableUnselectObjectsOnExit(false);
goPre.AcceptNothing(true);
goPre.GetMultiple(0, 0);
var collectedIds = new HashSet<Guid>();
var collectedRefs = new Dictionary<Guid, ObjRef>();
for (int i = 0; i < goPre.ObjectCount; i++)
{
var r = goPre.Object(i);
if (collectedIds.Add(r.ObjectId))
collectedRefs[r.ObjectId] = r;
}
// Keep pre-selected objects visually highlighted
foreach (var id in collectedIds)
doc.Objects.Select(id, true);
// ── Phase B: interactive add / remove ─────────────────────────────
// We want:
// - Click unselected object → add to collection (highlight it)
// - Click selected object → remove from collection (unhighlight it)
// - Toggle option → update option, keep selection intact
// - Press Enter → confirm and exit loop
//
// PROBLEM: no approach we've tried reliably detects the "click to
// deselect" case. See notes at top of file.
int iteration = 0;
while (true)
{
var go = new GetObject();
go.SetCommandPrompt("Select curves. Press Enter when done");
go.GeometryFilter = ObjectType.Curve;
go.SubObjectSelect = false;
go.EnablePreSelect(false, false); // don't auto-accept
go.DeselectAllBeforePostSelect = false; // keep highlights
go.AcceptNothing(true);
go.EnableUnselectObjectsOnExit(false);
go.AddOptionToggle("MyOption", ref toggle);
var res = go.Get();
RhinoApp.WriteLine($"iter {++iteration}: result={res} ObjectCount={go.ObjectCount}");
if (res == GetResult.Cancel)
{
foreach (var id in collectedIds) doc.Objects.Select(id, false);
doc.Views.Redraw();
return Result.Cancel;
}
if (res == GetResult.Nothing)
{
// ── PROBLEM: if user clicked an already-selected object,
// Rhino silently deselects it and returns Nothing here.
// We cannot distinguish that from a genuine Enter press.
// Checking IsSelected below sometimes catches it, sometimes not.
var silentDeselects = collectedIds
.Where(id => (doc.Objects.FindId(id)?.IsSelected(false) ?? 0) == 0)
.ToList();
if (silentDeselects.Count > 0)
{
// Treat as deselect click, not Enter — but is this reliable?
foreach (var id in silentDeselects)
{
collectedIds.Remove(id);
collectedRefs.Remove(id);
RhinoApp.WriteLine($" silent-deselect: {id.ToString()[..8]}");
}
doc.Views.Redraw();
continue; // keep loop going
}
break; // genuine Enter
}
if (res == GetResult.Option)
{
RhinoApp.WriteLine($" option: MyOption={toggle.CurrentValue}");
continue;
}
if (res == GetResult.Object)
{
var r = go.Object(0);
var pid = r.ObjectId;
if (collectedIds.Contains(pid))
{
// ── This branch is never reached in practice ──
// Clicking an already-highlighted object never returns
// GetResult.Object with that object; it goes through
// the silent-deselect path above (or does nothing).
collectedIds.Remove(pid);
collectedRefs.Remove(pid);
doc.Objects.Select(pid, false);
RhinoApp.WriteLine($" toggle-off: {pid.ToString()[..8]}");
}
else
{
collectedIds.Add(pid);
collectedRefs[pid] = r;
doc.Objects.Select(pid, true);
RhinoApp.WriteLine($" toggle-on: {pid.ToString()[..8]}");
}
doc.Views.Redraw();
}
}
RhinoApp.WriteLine($"Final selection: {collectedIds.Count} object(s)");
doc.Views.Redraw();
return Result.Success;
}
}
It seems to be quiet trivial requirements but I must be missing something here.
Any help?
Thank you!