Overriding the deepClone() Function

The sample code in this section is an approximation of the default behavior of deepClone(). The deep clone operation has two main stages:

During the cloning stage in this example, information about the old object is copied to the new object using a specific type of filer to write out the object and read it back. The filer keeps track of objects owned by the primary object so that they can be copied as well.

To complete the cloning stage

  1. Create a new object of the same type as the old one.
  2. Append the new object to its owner.
    • If the object is an entity, its owner is a block table record and you can use the appendAcDbEntity() function.
    • If the object is an AcDbObject, its owner is an AcDbDictionary and you can use the setAt() function to add it to the dictionary.

      If this is not a primary object, you would normally add it to the database using addAcDbObject() and then identify its owner using setOwnerId(). To establish ownership, the owner must file out the ID of the owned object using the appropriate ownership type.

  3. Call dwgOut() on the original object, using a deep clone filer (AcDbDeepCloneFiler) to write out the object. (Or, if you are overriding the wblockClone() function, use an AcDbWblockCloneFiler.)
  4. Rewind the filer and then call dwgIn() on the new object.
  5. Call setObjectIdsInFlux() on each new object before you add its value to the object ID map. This important step is used to indicate that the newly created object is part of a deep clone operation and its object ID is subject to change as part of the translation stage. This flag is automatically turned off when translation is complete.
  6. Add the new information to the idMap. The idMap contains AcDbIdPairs, which are pairs of old (original) and new (cloned) object IDs. The constructor for the ID pair sets the original object ID and the isPrimary flag. At this point, you set the object ID for the cloned object, set the isCloned flag to TRUE, and add (assign) it to the idMap.
  7. Clone the owned objects. (This step is recursive.)
    • Ask the filer if there are any more owned objects. (For wblock clone, ask if there are any more hard objects.)
    • To clone a subobject, obtain its ID and open the object for read.
    • Call deepClone() on the object. (Note that isPrimary is set to FALSE, because this is an owned object.) The deepClone() function clones the object and sets its owner. It also adds a record to the ID map.
    • Close the subobject if it was created at this time.

The following sample code illustrates these steps:

Acad::ErrorStatus
AsdkPoly::subDeepClone(AcDbObject*    pOwner,
                    AcDbObject*&   pClonedObject,
                    AcDbIdMapping& idMap,
                    Adesk::Boolean isPrimary) const
{
    // You should always pass back pClonedObject == NULL
    // if, for any reason, you do not actually clone it
    // during this call.  The caller should pass it in
    // as NULL, but to be safe, we set it here as well.
    //
    pClonedObject = NULL;

    // If this object is in the idMap and is already
    // cloned, then return.
    //
    bool isPrim = false;
    if (isPrimary)
        isPrim = true;
    AcDbIdPair idPair(objectId(), (AcDbObjectId)NULL,
                      false, isPrim);
    if (idMap.compute(idPair) && (idPair.value() != NULL))
        return Acad::eOk;    

    // Create the clone
    //
    AsdkPoly *pClone = (AsdkPoly*)isA()->create();
    if (pClone != NULL)
        pClonedObject = pClone;    // set the return value
    else
        return Acad::eOutOfMemory;

    AcDbDeepCloneFiler filer;
    dwgOut(&filer);

    filer.seek(0L, AcDb::kSeekFromStart);
    pClone->dwgIn(&filer);
    bool bOwnerXlated = false;
    if (isPrimary)
    {
        AcDbBlockTableRecord *pBTR =
            AcDbBlockTableRecord::cast(pOwner);
        if (pBTR != NULL)
        {
            pBTR->appendAcDbEntity(pClone);
            bOwnerXlated = true;
        }
        else
        {
            pOwner->database()->addAcDbObject(pClone);
        }
    } else {
        pOwner->database()->addAcDbObject(pClone);
        pClone->setOwnerId(pOwner->objectId());
        bOwnerXlated = true;
    }

    // This must be called for all newly created objects
    // in deepClone.  It is turned off by endDeepClone()
    // after it has translated the references to their
    // new values.
    //
    pClone->setAcDbObjectIdsInFlux();
    pClone->disableUndoRecording(true);


    // Add the new information to the idMap.  We can use
    // the idPair started above.
    //
    idPair.setValue(pClonedObject->objectId());
    idPair.setIsCloned(Adesk::kTrue);
    idPair.setIsOwnerXlated(bOwnerXlated);
    idMap.assign(idPair);

    // Using the filer list created above, find and clone
    // any owned objects.
    //
    AcDbObjectId id;
    while (filer.getNextOwnedObject(id)) {

        AcDbObject *pSubObject;
        AcDbObject *pClonedSubObject;

        // Some object's references may be set to NULL, 
        // so don't try to clone them.
        //
        if (id == NULL)
            continue;

        // Open the object and clone it.  Note that we now
        // set "isPrimary" to kFalse here because the object
        // is being cloned, not as part of the primary set,
        // but because it is owned by something in the
        // primary set.
        //
        acdbOpenAcDbObject(pSubObject, id, AcDb::kForRead);
        pClonedSubObject = NULL;
        pSubObject->deepClone(pClonedObject,
                              pClonedSubObject,
                              idMap, Adesk::kFalse);

        // If this is a kDcInsert context, the objects
        // may be "cheapCloned".  In this case, they are
        // "moved" instead of cloned.  The result is that
        // pSubObject and pClonedSubObject will point to
        // the same object.  So, we only want to close
        // pSubObject if it really is a different object
        // than its clone.
        //
        if (pSubObject != pClonedSubObject)
            pSubObject->close();
        
        // The pSubObject may either already have been
        // cloned, or for some reason has chosen not to be
        // cloned.  In that case, the returned pointer will
        // be NULL.  Otherwise, since we have no immediate
        // use for it now, we can close the clone.
        //
        if (pClonedSubObject != NULL)
            pClonedSubObject->close();
    }

    // Leave pClonedObject open for the caller
    //
    return Acad::eOk;
  }