When a wblock clone operation is performed, AutoCAD constructs a valid database for the new drawing, which contains the named object dictionary, all the symbol tables, and the complete set of header variables. The following code approximates the default implementation of wblockClone(). The steps listed correspond to those listed in the section Overriding the deepClone() Function.
The wblockClone() function uses a filer of class AcDbWblockCloneFiler, which returns a list of the hard pointer and hard owner connections of the primary object. Before you call wblockClone() on these subobjects, you need to check the owner of the subobject. At this point, you'll do one of two things:
If pOwner is set to the database, wblockClone() must set the owner of the cloned object to the same owner as that of the original object. Then, when the references are translated by AutoCAD, it will update the owner reference to the cloned object in the new database.
It is important to ensure that all owning objects are cloned. AutoCAD always clones the symbol tables, named object dictionary, model space, and paper space (for clone contexts other than AcDb::kDcXrefBind) during wblock clone. Applications with owning objects are responsible for ensuring that these objects are cloned if necessary. If an owning object is not cloned and not found in the ID map, wblock clone aborts with AcDb::eOwnerNotSet.
You must pass in the database as the owner of an object when you are copying an entity that references a symbol table record. For example, suppose you are calling wblockClone() on a sphere object. A block table record is the hard owner of this sphere object. The sphere object contains a hard reference to the layer table.
First, at the beginDeepClone() phase, the new database is created and set up with the default elements. The following figure shows the model space block table record and the layer table, because they're relevant to this topic. The cloning that occurs at this stage always happens during a wblock operation.
At the beginWblock() stage, the selection set is cloned, as shown in the following figure. In this example, the sphere is cloned.
Because the sphere contains a hard pointer to Layer 1 , Layer 1 is cloned.
Next, pointers need to be translated to refer to the cloned objects, as shown in the following figure. The beginDeepCloneXlation() notification indicates the beginning of this stage.
The ID map for the previous figure at the time of beginDeepCloneXlation() is as follows:
ID map of the previous figure |
||||
---|---|---|---|---|
KEY |
VALUE |
isCloned |
isPrimary |
isOwnerXlated |
BTR1 |
BTR2 |
TRUE |
FALSE |
TRUE |
SPH1 |
SPH2 |
TRUE |
TRUE |
TRUE |
LT1 |
LT2 |
TRUE |
FALSE |
* |
LTR1 |
LTR2 |
TRUE |
FALSE |
FALSE** |
* The layer table's owner is the database itself, so this entry is meaningless.
** During translation, this setting indicates that the layer will have its owner translated from LayerTable 1 to LayerTable 2 .
The wblock clone process is used for xref bind as well as wblock. The needs of both are very similar, but there are a few differences that require special attention when overriding the wblockClone().
Wblock clones all selected entities. However, xref bind never clones entities that are in paper space. This leaves two things to consider when creating objects or entities, and using AcDbHardPointerIds. First, at the beginning of any AcDbEntity's wblockClone(), check to see if the cloning context is AcDb::kDcXrefBind and, if so, whether the entity is being cloned in paper space. If it is, then no cloning should be done and wblockClone() should return Acad::eOk.
If your custom class has any AcDbHardPointerIds that can point to entities (such as we do with AcDbGroup), then the entities might be in paper space and will therefore not get cloned. In that event, the AcDbHardPointerIds will be set to NULL.
Wblock does not follow hard pointer references across databases. However, xref bind does this all the time. For example, an entity in an xref drawing can be on a VISRETAIN layer in the host drawing. So, if you implement your wblockClone() with a loop to check for subobjects, and the subobject's database is not the same as that of the object being cloned, you must skip the subobject if the cloning context is not AcDb::kDcXrefBind. For example:
if(pSubObject->database() != database() && idMap.deepCloneContext() != AcDb::kDcXrefBind) { pSubObject->close(); continue; }
The following code shows overriding wblockClone()to implement it for a custom entity (AsdkPoly). This function is invoked with the code shown in Editor Reactor Notification Functions.
Acad::ErrorStatus AsdkPoly::wblockClone(AcRxObject* 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, it is set here as well. // pClonedObject = NULL; // If this is a fast wblock operation, no cloning // should take place, so we simply call the base class's // wblockClone() and return whatever it returns. // // For fast wblock, the source and destination databases // are the same, so we can use that as the test to see // if a fast wblock is in progress. // AcDbDatabase *pDest, *pOrig; idMap.destDb(pDest); idMap.origDb(pOrig); if (pDest == pOrig) return AcDbCurve::wblockClone(pOwner, pClonedObject, idMap, isPrimary); // If this is an xref bind operation and this AsdkPoly // entity is in paper space, then we don't want to // clone because xref bind doesn't support cloning // entities in paper space. Simply return Acad::eOk. // AcDbObjectId pspace; AcDbBlockTable *pTable; database()->getSymbolTable(pTable, AcDb::kForRead); pTable->getAt(ACDB_PAPER_SPACE, pspace); pTable->close(); if ( idMap.deepCloneContext() == AcDb::kDcXrefBind && ownerId() == pspace) return Acad::eOk; // 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; // The owner object can be either an AcDbObject or an // AcDbDatabase. AcDbDatabase is used if the caller is // not the owner of the object being cloned (because it // is being cloned as part of an AcDbHardPointerId // reference). In this case, the correct ownership // will be set during reference translation. If // the owner is an AcDbDatabase, then pOwn will be left // NULL here, and is used as a "flag" later. // AcDbObject *pOwn = AcDbObject::cast(pOwner); AcDbDatabase *pDb = AcDbDatabase::cast(pOwner); if (pDb == NULL) pDb = pOwn->database(); // Step 1: Create the clone. // AsdkPoly *pClone = (AsdkPoly*)isA()->create(); if (pClone != NULL) pClonedObject = pClone; // Set the return value. else return Acad::eOutOfMemory; // Step 2: If the owner is an AcDbBlockTableRecord, go ahead // and append the clone. If not, but we know who the // owner is, set the clone's ownerId to it. Otherwise, // we set the clone's ownerId to our own ownerId (in // other words, the original ownerId). This ID will // then be used later, in reference translation, as // a key to finding who the new owner should be. This // means that the original owner must also be cloned at // some point during the wblock operation. // EndDeepClone's reference translation aborts if the // owner is not found in the ID map. // // The most common situation where this happens is // AcDbEntity references to symbol table records, such // as the layer an entity is on. This is when you will // have to pass in the destination database as the owner // of the layer table record. Since all symbol tables // are always cloned in wblock, you do not need to make // sure that symbol table record owners are cloned. // // However, if the owner is one of your own classes, // then it is up to you to make sure that it gets // cloned. This is probably easiest to do at the end // of this function. Otherwise you may have problems // with recursion when the owner, in turn, attempts // to clone this object as one of its subobjects. // AcDbBlockTableRecord *pBTR = NULL; if (pOwn != NULL) pBTR = AcDbBlockTableRecord::cast(pOwn); if (pBTR != NULL && isPrimary) { pBTR->appendAcDbEntity(pClone); } else { pDb->addAcDbObject(pClonedObject); } // Step 3: The AcDbWblockCloneFiler makes a list of // AcDbHardOwnershipIds and AcDbHardPointerIds. These // are the references that must be cloned during a // wblock operation. // AcDbWblockCloneFiler filer; dwgOut(&filer); // Step 4: Rewind the filer and read the data into the clone. // filer.seek(0L, AcDb::kSeekFromStart); pClone->dwgIn(&filer); // Step 5: Add the new information to the ID map. // We must let the idMap entry know whether the clone's owner is // correct, or needs to be translated later. // idMap.assign(AcDbIdPair(objectId(), pClonedObject->objectId(), Adesk::kTrue, isPrim, (Adesk::Boolean)(pOwn != NULL) )); pClonedObject->setOwnerId((pOwn != NULL) ? pOwn->objectId() : ownerId()); // Step 6: // This must be called for all newly created objects // in wblockClone. It is turned off by endDeepClone() // after it has translated the references to their // new values. // pClone->setAcDbObjectIdsInFlux(); // Step 7: Using the filer list created above, find and clone // any hard references. // AcDbObjectId id; while (filer.getNextHardObject(id)) { AcDbObject *pSubObject; AcDbObject *pClonedSubObject; // Some object references may be set to NULL, // so don't try to clone them. // if (id == NULL) continue; // If the referenced object is from a different // database, such as an xref, do not clone it. // acdbOpenAcDbObject(pSubObject, id, AcDb::kForRead); if (pSubObject->database() != database()) { pSubObject->close(); continue; } // To find out if this is an AcDbHardPointerId // versus an AcDbHardOwnershipId, check if we are the // owner of the pSubObject. If we are not, // then we cannot pass our clone in as the owner // for the pSubObject's clone. In that case, we // pass in our clone's database (the destination // database). // // Note that "isPrimary" is set 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. // pClonedSubObject = NULL; if (pSubObject->ownerId() == objectId()) { pSubObject->wblockClone(pClone, pClonedSubObject, idMap, Adesk::kFalse); } else { pSubObject->wblockClone( pClone->database(), pClonedSubObject, idMap, Adesk::kFalse); } 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 there is no immediate // use for it now, the clone can be closed. // if (pClonedSubObject != NULL) pClonedSubObject->close(); } // Leave pClonedObject open for the caller. // return Acad::eOk; }
void AsdkWblockReactor::otherWblock( AcDbDatabase* pDestDb, AcDbIdMapping& idMap, AcDbDatabase* pSrcDb) { AcDbBlockTable *pDestBlockTable; AcDbBlockTableRecord *pDestBTR; pDestDb->getSymbolTable(pDestBlockTable, AcDb::kForRead); pDestBlockTable->getAt(ACDB_MODEL_SPACE, pDestBTR, AcDb::kForRead); pDestBlockTable->close(); // Now pDestBTR is pointing to pSrcDb database's model // space, not to the destination database's model space! // The code above is not correct! }
To find the destination model space, you must look it up in the ID map:
void AsdkWblockReactor::otherWblock( AcDbDatabase* pDestDb, AcDbIdMapping& idMap, AcDbDatabase* pSrcDb) { // To find the destination model space, you must look // it up in the ID map: AcDbBlockTable *pSrcBlockTable; pSrcDb->getSymbolTable(pSrcBlockTable, AcDb::kForRead); AcDbObjectId srcModelSpaceId; pSrcBlockTable->getAt(ACDB_MODEL_SPACE, srcModelSpaceId); pSrcBlockTable->close(); if (pDestDb == pSrcDb) { // It's a fastWblock, so we use the source objectId. destId = srcModelSpaceId; } else { AcDbIdPair idPair; idPair.setKey(srcModelSpaceId); idMap.compute(idPair); destId = idPair.value(); } AcDbBlockTableRecord *pDestBTR; acdbOpenAcDbObject((AcDbObject*&)pDestBTR, destId, AcDb::kForRead, Adesk::kTrue);