Rhino 5.0 Status:

Lightweight Extrusion Objects for Developers

Plug-ins written using C++ SDK, .NET SDK or Rhino common might fail to select the new lightweight extrusion objects depending on the programming style. This is why it is very important for plug-in developers (of Rhino 5.0) to test their product with lightweight extrusions and fix any bugs or crashes.

Overview

In general, the CRhinoObjRef (object reference) that you get from CRhinoGetObject (selecting a polysurface, faces or edges) is a special object reference, especially when you select lightweight extrusion object. The reference holds a pointer to the extrusion object and also a proxy brep of that object. Consider the following sample to select a face:

  CRhinoGetObject go;
  go.SetGeometryFilter( ON:surface_object );
  go.GetObject(1,1);
  CRhinoObjRef original_objref = go.Object(0);

Now suppose you picked a box face where the box was actually a lightweight extrusion. Object reference “original_objref” is a special one. It gives valid object, brep and brepface as in the following:

  const CRhinoObject* original_obj = original_objref.Object();
  const ON_Brep original_brep = original_objref.Brep();
  const ON_BrepFace* original_face = original_objref.Face();
  int face_component_index = original_objref.m_component_index

Note that “original_obj” is not a “CRhinoBrepObject” and cannot be casted as one. In this case, it is actually a “CRhinoExtrusionObject”. This one special object reference from the getter “original_objref” has a proxy ON_Brep and therefore the brep and brep_face are both valid as long as the original_objref does not go out of scope. To be safe, always keep the getter in scope or keep a copy of the object reference.

For help, please post questions to Rhino developer newsgroup. Please don't hesitate to ask.

Following are examples where the code will fail when selecting extrusion objects and suggested solutions.

Casting CRhinoObject as CRhinoBrepObject

Consider the following example to select a polysurface:

  CRhinoGetObject go;
  go.SetGeometryFilter( CRhinoGetObject::polysrf_object );
  go.SetCommandPrompt( L"Select a polysurface" );
  go.GetObjects(1,1);
  if( go.CommandResult() != CRhinoCommand::success )
    return go.CommandResult();
  const CRhinoObject* obj = go.Object(0).Object();
  if( 0 == obj )
    return CRhinoCommand::failure;

Problem: Casting will be a problem if you select extrusion object:

  const CRhinoBrepObject* brep_obj = CRhinoBrepObject ::Cast(obj);
  //
  //Note: if you have selected an extrusion object, the pointer will be null
  //
  if( 0 == brep_obj )
    return CRhinoCommand::failure;
  const ON_Brep* org_brep = brep_obj->Brep();

Solution: To make your code work with extrusion objects, get your brep directly from the object reference:

  const ON_Brep* input_brep = go.Object(0).Brep();

The original getter goes out of scope

If you pass a pointer to an extrusion object from a function or block of code and throw away the object reference, then you will not be able to get to the “brep”, brep_face“ or “brep_edge” anymore. The following functions illustrate different cases when the code will not work or crash:

MyGetEdge1() Function:

CRhinoCommand::result MyGetEdge1( const CRhinoObject*& selected_object, int& edge_index)
{
  CRhinoGetObject go;
  go.SetGeometryFilter( CRhinoGetObject::curve_object );
  go.SetGeometryAttributeFilter( CRhinoGetObject::edge_curve );
  go.GetObjects(1,1);
  if( go.CommandResult() != CRhinoCommand::success )
    return go.CommandResult();
  const ON_Brep* brep = go.Object(0).Brep();
  if( 0 == brep )
    return CRhinoCommand::failure;
  const ON_BrepEdge* edge = go.Object(0).Edge();
  if( 0 == edge)
    return CRhinoCommand::failure;
  //
  //At this point we have verified that our geometry is valid
  //
  selected_object = go.Object(0).Object();
  edge_index = edge->m_edge_index;
  return CRhinoCommand::success;
}

MyGetEdge2() function:

CRhinoCommand::result MyGetEdge2( const ON_Brep*& selected_brep, int& edge_index )
{
  CRhinoGetObject go;
  go.SetGeometryFilter( CRhinoGetObject::curve_object );
  go.SetGeometryAttributeFilter( CRhinoGetObject::edge_curve );
  go.GetObjects(1,1);
  if( go.CommandResult() != CRhinoCommand::success )
    return go.CommandResult();
  const ON_Brep* brep = go.Object(0).Brep();
  if( 0 == brep )
    return CRhinoCommand::failure;
  const ON_BrepEdge* edge = go.Object(0).Edge();
  if( 0 == edge)
    return CRhinoCommand::failure;
  //
  //At this point we have verified that our geometry is valid
  //
  selected_brep = brep;
  edge_index = edge->m_edge_index;
  return CRhinoCommand::success;
}

MyGetEdge3() function:

CRhinoCommand::result MyGetEdge3( const ON_BrepEdge*& selected_edge )
{
  CRhinoGetObject go;
  go.SetGeometryFilter( CRhinoGetObject::curve_object );
  go.SetGeometryAttributeFilter( CRhinoGetObject::edge_curve );
  go.GetObjects(1,1);
  if( go.CommandResult() != CRhinoCommand::success )
    return go.CommandResult();
  const ON_Brep* brep = go.Object(0).Brep();
  if( 0 == brep )
    return CRhinoCommand::failure;
  const ON_BrepEdge* edge = go.Object(0).Edge();
  if( 0 == edge)
    return CRhinoCommand::failure;
  //
  //At this point we have verified that our geometry is valid
  //
  selected_edge = edge;
  return CRhinoCommand::success;
}

MyGetEdgeGUID() function:

CRhinoCommand::result MyGetEdgeGUID( ON_UUID& guid, ON_COMPONENT_INDEX& component_index)
{
  CRhinoGetObject go;
  go.SetGeometryFilter( CRhinoGetObject::curve_object );
  go.SetGeometryAttributeFilter( CRhinoGetObject::edge_curve );
  go.GetObjects(1,1);
  if( go.CommandResult() != CRhinoCommand::success )
    return go.CommandResult();
  const ON_Brep* brep = go.Object(0).Brep();
  if( 0 == brep )
    return CRhinoCommand::failure;
  const ON_BrepEdge* edge = go.Object(0).Edge();
  if( 0 == edge)
    return CRhinoCommand::failure;
  //
  //At this point we have verified that our geometry is valid
  //
  guid = go.Object(0).ObjectUuid();
  component_index = edge->ComponentIndex();
  return CRhinoCommand::success;
  }

MyMainCommand() - the main caller function illustrating where things might go wrong:

CRhinoCommand::result MyMainCommand()
{
  //initialize some variables
  const CRhinoBrepObject* brep_obj = 0;
  const CRhinoObject* obj = 0;
  const ON_Brep* brep = 0;
  const ON_BrepEdge* edge = 0;
  int edge_index = -1;
  CRhinoCommand::result rc = CRhinoCommand::failure;

Problem case # 1: When call a selection function that returns a pointer to input object and edge index.

  rc = MyGetEdge1(obj, edge_index);
  if( rc != CRhinoCommand::success )
    return rc;
  CRhinoObjRef new_ref = CRhinoObjRef(obj);
  //
  //brep will be a null or invalid pointer. Fail at the next linet
  //
  brep = new_ref.Brep();
  if( 0 == brep || !brep->IsValid())
    return CRhinoCommand::failure;
  //
  //
  //If you cast object as follows, you will still fail because brep_obj will be null
  //
  brep_obj = CRhinoBrepObject::Cast(obj);
  if( 0 == brep_obj )
    return CRhinoCommand::failure;

Problem case # 2: When call a selection function that returns a pointer to input brep and edge index

  rc = MyGetEdge2(brep, edge_index);
  if( rc != CRhinoCommand::success )
    return rc;
  if( 0 == brep )
    return CRhinoCommand::failure;
  //
  //Next line will crash or return null or invalid pointer. brep is now pointing to invalid memory 
  // because it was destroyed once the getter went out of scope when exiting MyGetFace2 function
  //
  edge = brep->Edge(edge_index);
  if( 0 == edge || !edge->IsValid())
    return CRhinoCommand::failure;

Problem case # 3: When call a selection function that returns a pointer to input edge.

  rc = MyGetEdge3( edge );
  if( rc != CRhinoCommand::success )
    return rc;
  if( 0 == edge)
  return CRhinoCommand::failure;
  //
  //Next line will crash or return null or invalid pointer. edge is now pointing to invalid memory 
  //because it was dietroyed once the getter went out of scope
  //
  brep = edge->Brep();
  if( 0 == brep || !brep->IsValid())
    return CRhinoCommand::failure;

Problem case # 4: When the getter is inside a block or a loop.

  {
    CRhinoGetObject go; //the getter will be destoyed once exiting the block
    go.SetGeometryFilter( CRhinoGetObject::curve_object );
    go.SetGeometryAttributeFilter( CRhinoGetObject::edge_curve );
    go.GetObjects(1,1);
    if( go.CommandResult() != CRhinoCommand::success )
      return go.CommandResult();
    brep = go.Object(0).Brep();
    if( 0 == brep )
      return CRhinoCommand::failure;
    edge = go.Object(0).Edge();
    if( 0 == edge)
      return CRhinoCommand::failure;
  }
  //Using the edge pointer will give invalid index or crash
  edge_index = edge->m_edge_index;

Problem case # 5: Using Object GUID and ComponentIndex to reference objects and sub-objects.

  ON_UUID uuid = ON_nil_uuid;
  ON_COMPONENT_INDEX component_index;
  rc = MyGetEdgeGUID(uuid, component_index);
    if( rc != CRhinoCommand::success )
  return rc;
  //
  //The following reference will not have proxy brep information
  //
  CRhinoObjRef ref = CRhinoObjRef(uuid);
  //
  //brep will be invalid (null pointer)
  //
  brep = ref.Brep();
  if( 0 == brep)
    return CRhinoCommand::failure;
  edge = brep->Edge(component_index);
  if( 0 == edge)
    return CRhinoCommand::failure;
  return CRhinoCommand::success;
}

Solution: You need to either instantiate and pass the getter by reference from the main function; or, you can also return a copy of the object reference, as follows.

CRhinoCommand::result MyGetFace_work( CRhinoObjRef& obj_ref)
{
  CRhinoGetObject go;
  go.SetGeometryFilter( CRhinoGetObject::curve_object );
  go.SetGeometryAttributeFilter( CRhinoGetObject::edge_curve );
  go.GetObjects(1,1);
  if( go.CommandResult() != CRhinoCommand::success )
    return go.CommandResult();
  const ON_Brep* brep = go.Object(0).Brep();
  if( 0 == brep )
    return CRhinoCommand::failure;
  const ON_BrepEdge* edge = go.Object(0).Edge();
  if( 0 == edge)
    return CRhinoCommand::failure;
  //
  //At this point we have verified that our geometry is valid
  //Save a copy of the object reference and return success
  //
  obj_ref = go.Object(0);
  return CRhinoCommand::success;
}
CRhinoCommand::result MyMainCaller()
{
  CRhinoObjRef obj_ref;
  CRhinoCommand::result rc = MyGetFace_work(obj_ref);
  if( rc != CRhinoCommand::success )
    return rc;
  //
  //Both the following pointers will be valid and good to use 
  //whether input in brep object or extrusion object
  //
  const ON_Brep* brep = obj_ref.Brep();
  const ON_BrepEdge* edge = obj_ref.Edge();
  return CRhinoCommand::success;
}

Extending CRhinoGetObject

You need to pay special attention when extending CRhinogetObject. Here is an example of what might go wrong and how to fix it:

Special class to use for picking the Face

class CMyGetFace : public CRhinoGetObject
{
public:
  CMyGetFace(){};
  ~CMyGetFace(){};
  //
  // virtual CRhinoGetObject override
  //
  bool CustomGeometryFilter(
               const CRhinoObject* object,
               const ON_Geometry* geometry,
               ON_COMPONENT_INDEX component_index
               ) const;
  //
  //virtual CRhinoGetObject override
  //
  bool PassesGeometryAttributeFilter(
               const CRhinoObject* object,
               const ON_Geometry* geometry,
               ON_COMPONENT_INDEX component_index
               ) const;
};
bool CMyGetFace::CustomGeometryFilter(
               const CRhinoObject* object,
               const ON_Geometry* geometry,
               ON_COMPONENT_INDEX component_index
               ) const
{

Problem: The following will fail with extrusion objects because the passed object is of type CRhinoExtrusionObject.

  const CRhinoBrepObject* brep_object = CRhinoBrepObject::Cast(object);
  if ( 0 == brep_object )
    return false;
  const ON_Brep* brep = brep_object->Brep();
  if ( 0 == brep )
    return false;
  const ON_BrepFace* Face = brep->Face(component_index.m_index);
  if ( 0 == Face )
    return false;

Solution: The following will work with extrusion objects. Just remember to cast the geometry rather than the object.

  //first, make sure that the component index belongs to a brep
  //
  if( !component_index.IsBrepComponentIndex() )
    return false;
  //
  //Cast geometry as ON_BrepFace
  //
  const ON_BrepFace* face = ON_BrepFace::Cast(geometry);
  if ( 0 == face )
    return false;
  return true;
}
bool CMyGetFace::PassesGeometryAttributeFilter(
               const CRhinoObject* object,
               const ON_Geometry* geometry,
               ON_COMPONENT_INDEX component_index
               ) const
{
  bool rc = CRhinoGetObject::PassesGeometryAttributeFilter(object,geometry,component_index);
  if (rc)
  {
    rc = CustomGeometryFilter(object,geometry,component_index);
  }
  return rc;
}