SubObjectSelectionEnabled works for polylines, not solid faces

The attached 3DM file contains two solids, two polylines, and four TextDots. The script uses the TextDot positions as the four viewport pick positions.

I am calling ObjectTable.PickObjects with otherwise identical PickContexts:

  • face 1 and edge 1 use SubObjectSelectionEnabled = False
  • face 2 and edge 2 use SubObjectSelectionEnabled = True.

Script and test model
TestPickObjects.3dm (64.6 KB)
TestPickObjects.py (5.1 KB)

Expected selection after running the script

Actual selection after running the script

The SubObjectSelectionEnabled option works for the polyline: the returned component changes from InvalidType, -1 to PolycurveSegment, 2.

For the solid, both picks return Extrusion, InvalidType, -1, so the whole solid is selected in both cases instead of selecting the face.

The attached script and model reproduce this.
This appears to be a bug in subobject picking for solids.


Why do I need this?

I am building an integration testing framework that opens a _start.3dm file, runs Rhino command macros, saves the result, and compares it with an _expected.3dm file.

Some tested commands pause for object or subobject selections. For these cases I am developing a transparent SelObject command that can run while another command is active. It receives a 3D point, or finds a named TextDot in the test model, projects that point into the active viewport, and creates the corresponding selection. For example:

SelObject Object 10,20,30         select a solid by picking at 10,20,30

SelObject Object Dot "solid 1"    select a solid by picking at the position
                                  of the TextDot with text "solid 1"

SelObject SubObject Dot "face 2"  select a solid face with a similar pick

The goal is to reproduce normal viewport selections from scripted tests without requiring a person to click.

Hi @Stefano_Menci,

The two solids in your file are lightweight extrusions, and extrusions don’t have sub-objects - no faces, no edges. When you sub-object select one in a command, Rhino cooks up a proxy Brep from the extrusion and hands back a component index into that. That machinery is driven by the GetObject running the pick, which is PickContext.GetObjectUsed - read-only, and only filled in when Rhino itself is doing the picking.

On a PickContext you create yourself there’s nothing to drive it, so you get the top-level object back.

For what you’re doing, you might let PickObjects return the whole object and build the proxy yourself. ObjRef.SelectionPoint() gives you where the pick ray hit, and Brep.ClosestPoint turns that into a ComponentIndex that SelectSubObject accepts:

def component_at_pick(obj_ref, tolerance=1.0):
    geometry = obj_ref.Object().Geometry
    if isinstance(geometry, Rhino.Geometry.Extrusion):
        geometry = geometry.ToBrep(True)
    if not isinstance(geometry, Rhino.Geometry.Brep):
        return Rhino.Geometry.ComponentIndex.Unset
    ok, _, ci, _, _, _ = geometry.ClosestPoint(obj_ref.SelectionPoint(), tolerance)
    return ci if ok else Rhino.Geometry.ComponentIndex.Unset


ci = component_at_pick(face_2_refs[0])
face_2_refs[0].Object().SelectSubObject(ci, True, True, True)

– Dale