McNeel Wiki
Info: Rhino 4.0 plug-in user data
edit · print · help · all topics
Main Pages

AccuRender

Bongo

Brazil r/s

Developer

Flamingo

Penguin

Rhino Blogs

Rhino

Rhino Labs

Search

Languages

Česky

Deutsch

English

Español

Français

Italiano

Polish

日本語

한국어

中文(繁體)

 
.
DeveloperC++
VersionRhino 4

Overview

There are two basic ways plug-ins can store information in Rhino .3dm files, document user data and object user data.

For example, a rendering plug-in might save a scene descriptions as document user data and use object user data to attach rendering material information to individual surfaces.

Document User Data

To save document user data your plug-in must override three CRhinoPlugIn base class functions:

  CRhinoPlugIn::CallWriteDocument
  CRhinoPlugIn::WriteDocument
  CRhinoPlugIn::ReadDocument

When Rhino writes a .3dm file, it goes through all the plug-ins that are currently loaded. First Rhino calls CallWriteDocument() to see if the plug-in wants to save document user data. If CallWriteDocument() returns true, Rhino saves information that identifies the plug-in and then calls WriteDocument() when it is time for the plug-in to save its "document" user data.

When Rhino reads a .3dm file and it encounters document user data, it uses the plug-in identification information to load the plug-in and then calls the plug-in's ReadDocument() to read the plug-in's "document" user data.

A Sample of this is provided at SdkDocumentData

Object User Data

Object user data can be attached to things like layers, materials, geometry objects, and object attributes. In fact object user data can be attached to any class derived from ON_Object. This user data is stored in a linked list on ON_Object and can be copied, transformed and saved along with the parent object. For example, you could attach object user data to a mesh. When the mesh is copied the object user data would be copied and attached to the copy. When the mesh is transformed, the transformation would be recored by the object user data. When the mesh is saved in a .3dm file, the object user data will be saved too.

All object user data is saved in a class you write that is derived from the class ON_UserData. The first example, CMyUserData1, shows how to make a simple piece of user data that is not save in .3dm files. The second example, CMyUserData2, shows how to make a piece of user data that is saved in .3dm files.

Examples

The source code for the next two the examples can be downloaded here.

Example: CMyUserData1

  class CMyUserData1 : public ON_UserData
  {
  public:


    /*
    Returns:
      Uuid used to identify this type of user data.
      This is the value saved in m_userdata_uuid and
      passed to ON_Object::GetUserData().
    */
    static ON_UUID Id(); 


    CMyUserData1();
    ~CMyUserData1();


    ... download example for more details


    ON_wString m_my_string;
  };

The CMyUserData1 class must always be on the heap ( constructed by calling the C++ new operator ). The class's constuctor must initialize two ON_UserData fields, m_userdata_uuid, m_application_uuid. In addition, if you want the object user data to be copied whenever its parent object is copied, you have to set m_userdata_copycount to one.

  ON_UUID CMyUserData1::Id()
  {
    // Use Microsoft's guidgen to get a unique id.


    // {1129B130-B840-...}
    static const ON_UUID id = { 0x1129b130, 0xb840, ... };
    return id;
  }


  CMyUserData1::CMyUserData1()
  {
    m_userdata_uuid = CMyUserData1::Id();
    m_application_uuid = MyPlugIn().PlugInID();
    m_userdata_copycount = 1; // enable copying
  }


  ... download example for more details

Attaching object user data to a parent object is a simple matter.

  ON_Mesh* pMesh = ...;
  CMyUserData1* pMyUserData1 = new CMyUserData1();
  pMyUserData1->m_my_string = L"How, now, brown cow?";
  if ( pMesh->AttachUserData(pMyUserData1) )
  {
    // It worked.  The mesh class will manage the user
    // data and delete it at the appropriate time.
    RhinoApp().Print("User data attached.\n"):
  }
  else
  {
    // It didn't work.  This usually means that
    // the parent object already has this kind 
    // of user data attached.
    RhinoApp().Print("User data not attached.\n");
    delete pMyUserData;
  }
  pMyUserData = 0; 

The user data's uuid is used to retrieve the user data from a parent object.

  ON_Mesh* pMesh = ...;
  ON_UserData1* pUserData = pMesh->GetUserData(CMyUserData1::Id());
  CMyUserData1* pMyUserData1 = static_cast<CMyUserData1*>(pUserData);
  if ( pMyUserData1 )
  {
    RhinoApp().Print("Got user data.\n"):
  }
  else
  {
    RhinoApp().Print("My user data is not on the mesh.\n"):
  }

Example: CMyUserData2

Make sure you completely understand the CMyUserData1 example before studying this example.

In order to have your user data saved in files, you have to add several IO related functions and add full opennurbs object support to your class.

  class CMyUserData2 : public ON_UserData
  {
    // Opennurbs classes that are saved in .3dm files require
    // an ON_OBJECT_DECLARE call in their declaration.
    ON_OBJECT_DECLARE(CMyUserData2);
  public:


    static ON_UUID Id();


    CMyUserData2();
    ~CMyUserData2();


    // override virtual ON_UserData::Archive()
    BOOL Archive() const; 


    // override virtual ON_UserData::Write()
    BOOL Write(
           ON_BinaryArchive& binary_archive
         ) const;


    // override virtual ON_UserData::Read()
    BOOL Read(
           ON_BinaryArchive& binary_archive
         );


    ... download example for more details


    ON_wString m_my_string;
    ON_3dPoint m_my_point;
  };

Again, the CMyUserData2 class must always be on the heap ( constructed by calling the C++ new operator ). In order to support file IO, the definition of CMyUserData2 must contain the ON_OBJECT_DECLARE macro.

The cpp file where the class is implemented must contain the ON_OBJECT_IMPLEMENT macro shown below.

  ON_OBJECT_IMPLEMENT(CMyUserData2,ON_UserData,"0D8FA7AB-F8A4-...");

The construction code for CMyUserData2 looks like.

  ON_UUID CMyUserData2::Id()
  {
    return CMyUserData2::m_CMyUserData2_class_id.Uuid();
  }


  CMyUserData2::CMyUserData2()
  {
    m_userdata_uuid = CMyUserData2::Id();
    m_application_uuid = MyPlugIn().PlugInID();
    m_userdata_copycount = 1; // enable copying


    // initialize your data members here
    m_my_point.Set(0.0,0.0,0.0);
  }

The CMyUserData2::Archive(), CMyUserData2::Read(), CMyUserData2::Write() functions look something like this. Download the example for more details.

  BOOL CMyUserData2::Archive() const 
  {
    // If false is returned, nothing will be saved in 3dm archives.
    return true;
  }
  BOOL CMyUserData2::Write(
         ON_BinaryArchive& binary_archive
       ) const
  {
    ... download example for more details about adding version support


    int minor_version = 0;
    bool rc = binary_archive.BeginWrite3dmChunk(
                  TCODE_ANONYMOUS_CHUNK,
  1,
                  minor_version
                  );
    if (!rc)
      return false;


    // Write class members like this
    for (;;)
    {
      // version 1.0 fields
      rc = binary_archive.WriteString(m_my_string);
      if (!rc) break;


      rc = binary_archive.WritePoint(m_my_point);
      if (!rc) break;


      break;
    }


    if ( !binary_archive.EndWrite3dmChunk() )
      rc = false;


    return rc;
  }
  BOOL CMyUserData2::Read(
         ON_BinaryArchive& binary_archive
       )
  {
    ... download example for more details about adding version support


    int major_version = 0;
    int minor_version = 0;
    bool rc = binary_archive.BeginRead3dmChunk(
                  TCODE_ANONYMOUS_CHUNK,
                  &major_version,
                  &minor_version
                  );
    if (!rc)
      return false;


    // Read class members like this
    for (;;)
    {
      rc == ( 1 == major_version );
      if (!rc) break;


      // version 1.0 fields
      rc = binary_archive.ReadString(m_my_string);
      if (!rc) break;


      rc = binary_archive.ReadPoint(m_my_point);
      if (!rc) break;


      break;
    }


    // If BeginRead3dmChunk() returns true,
    // then EndRead3dmChunk() must be called, 
    // even if a read operation failed.
    if ( !binary_archive.EndRead3dmChunk() )
      rc = false;


    return rc;
  }

Attaching Object User Data to CRhinoObjects

The Rhino geometry table is where the bulk of the Rhino model is stored. This is where the points, curves, meshes, surface, polysurfaces, annotation, render lights, and so on are stored. Each member of the geometry table is derived from the CRhinoObject class. There are three places you can attach object user data to a CRhinoObject: the CRhinoObject geometry class, the CRhinoObject attributes class, and the CRhinoObject itself. Object user data attached to the geometry and attributes class persists through copys, transformations, and file IO. Object user data attached directly on the CRhinoObject class is never saved in .3dm file.

  • To attach object user data to the CRhinoObject geometry use CRhinoObject::AttachGeometryUserData(). This call attaches the user data to the CRhinoObject.m_geometry member. This user data will persist when the object is copied, transformed, or saved in .3dm files.
  • To attach object user data to the CRhinoObject attributes, use CRhinoObject::AttachAttributeUserData(). This call attaches the user data to the CRhinoObject.m_attributes member. This user data will persist when the object is copied, transformed, or saved in .3dm files.
  • To attach object user data to the CRhinoObject itself, use CRhinoObject::AttachUserData(). This user data is attached to the CRhinoObject class itself and will never be saved in .3dm files. It is a good place to attach runtime information that applies only to the specific class it is attached to. In general, object user data attached directly to a CRhinoObject class will have m_userdata_copycount = 0 and will have an Archive() function that returns false.

User data operator= and copy construction.

In general, you do not need to explictly override the default copy constructor and operator= that C++ generates for you. Incorrectly implemented copy constructors and operator=s are a common source of bugs. The C++ defaults will work perfectly if every member of your class is a built-in data type (int, double, ...) or a class that properly handles copy construction and operator= (ON_NurbsCurve, ON_3dPoint, ON_Simple/ClassArray<>, ...).

The main reason you have to explicitly implement a copy constructor and operator= is to handle fields that involve explicit heap allocation and deallocation. In the example below, the field m_i_array is an array of integers that is on the heap.

  class CMyUserData3 : public ON_UserData
  {
    ON_OBJECT_DECLARE(CMyUserData3);
  public:
    static ON_UUID Id();
    static ON_UUID ApplicationId();
    static CMyUserData3* Get(const ON_Object*);


    CMyUserData3();
    ~CMyUserData3();
    CMyUserData3(const CMyUserData3& src);
    CMyUserData3& operator=(const CMyUserData3& src);


    ...


    int m_i_count;
    int* m_i_array;
  };


  ON_OBJECT_IMPLEMENT(CMyUserData3,
                      ON_UserData,
                      <<guidgen generated uuid here>>
                     );


  ON_UUID CMyUserData3::Id()
  {
    return CMyUserData3::m_CMyUserData3_class_id.Uuid();
  }


  ON_UUID CMyUserData3::ApplicationId()
  {
    return <<your plug-in name>>().PlugInID();
  }


  CMyUserData3* CMyUserData3::Get(const ON_Object* object)
  {
    return CMyUserData3::Cast( (object != 0)
                               ? object->GetUserData( CMyUserData3::Id() ) 
                               : 0 
                             );
  }


  CMyUserData3::CMyUserData3()
  {
    m_userdata_uuid = CMyUserData3::Id();
    m_application_uuid = CMyUserData3::ApplicationId();
    m_userdata_copycount = 1;


    // Initialize your class's fields
    m_i_count = 0;
    m_i_array = 0;
  }


  CMyUserData3::~CMyUserData3()
  {
    // virtual function
    if ( 0 != m_i_array )
      onfree(m_i_array);
  }

When the copy constructor is called, the memory for "this" is not initialized. You must call the base class copy constructor, initialize all your class's fields, and then copy your class's fields.

  CMyUserData3::CMyUserData3(const CMyUserData3& src ) 
  : ON_UserData(src) // critical - must call base class copy constructor
  {
    m_userdata_uuid = CMyUserData3::Id();
    m_application_uuid = CMyUserData3::ApplicationId();
    m_transform_count = 0;


    // DO NOT SET OTHER ON_UserData fields
    // In particular, do not set m_userdata_copycount


    // Copy you class's fields
    m_i_count = 0;
    m_i_array = 0;
    if( src.m_i_count > 0 && src.m_i_array != 0 )
    {
      m_i_array = (int*)onmalloc(src.m_i_count*sizeof(m_i_array[0]));
      if ( m_i_array != 0 )
      {
        memcpy( m_i_array, 
                src.m_i_array, 
                src.m_i_count*sizeof(m_i_array[0]) 
              );
        m_i_count = src.m_i_count;
      }
    }
  }

When operator= is called, "this" has already been constructed and may already have been used. You must destroy any existing information, call the the base class operator=, and then copy your class's fields.

  CMyUserData3& CMyUserData3::operator=(const CMyUserData3& src)
  {
    if ( this != &src )
    {
      // Destroy your class's existing information 
      // (Otherwise you will leak memory if "this"
      // has been used before.)
      m_i_count = 0;
      if ( m_i_array != 0 )
      {
        onfree(m_i_array);
        m_i_array = 0
      }


      // Use the base class operator=() to correctly
      // copy all ON_UserData fields.
      ON_UserData::operator=(src);


      // Copy your class's fields
      if( src.m_i_count > 0 && src.m_i_array != 0 )
      {
        m_i_array = (int*)onmalloc(src.m_i_count*sizeof(m_i_array[0]));
        if ( m_i_array != 0 )
        {
          memcpy( m_i_array, 
                  src.m_i_array, 
                  src.m_i_count*sizeof(m_i_array[0]) 
                );
          m_i_count = src.m_i_count;
        }
      }
    }
    return *this;
  }
rename · changes · history · subscriptions · lost and found · references · file upload