====== 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 [[news://news.mcneel.com/rhino.plug-ins |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; }