Embedded and Encapsulating Objects

There are two ways in which an object can be embedded in another object: either the encapsulating object has a data member that is an actual embedded object, or the encapsulating object has a pointer to an object (in which case the object is considered to be embedded). In either case, the encapsulating object is responsible for allocating and deallocating the embedded object. The encapsulating object must also forward all calls to the embedded object's methods because AutoCAD is unaware of the embedded object. In order to display the embedded object, the encapsulating object's subWorldDraw() must make a call to the embedded object's worldDraw().

For subGetGripPoints() and subGetStretchPoints(), you must call the embedded object's getGripPoints() and getStretchPoints() methods last so that its points end up with indices higher than those of the encapsulating entity. Then, in subMoveGripPointsAt() and subMoveStretchPointsAt(), if any of the indices passed in are above the range of those of the encapsulating entity, you subtract the highest index value of the encapsulating entity and pass the result into the embedded object's moveGripPointsAt() or moveStretchPointsAt(). For example, the following code from a version of the jblob sample has been updated to have an embedded AcDbCircle entity:

Acad::ErrorStatus
Jblob::subGetGripPoints(
    AcGePoint3dArray& gripPoints,
    AcDbIntArray& osnapMasks,
    AcDbIntArray& geomIds) const
{
    assertReadEnabled();
    gripPoints.append(mp);
    gripPoints.append(mp + 0.5 * (mpblob - mp));
    gripPoints.append(mpblob);
    AcGeVector3d xoff(mrblob, 0, 0);
    AcGeVector3d yoff(0, mrblob, 0);
    gripPoints.append(mpblob + xoff);
    gripPoints.append(mpblob + yoff);
    gripPoints.append(mpblob - xoff);
    gripPoints.append(mpblob - yoff);
    return circle.getGripPoints(gripPoints, osnapMasks, geomIds);
}
Acad::ErrorStatus
Jblob::subMoveGripPointsAt(
    const AcDbIntArray& indices,
    const AcGeVector3d& offset)
{
    AcGePoint3d oldquad, newquad;
    assertWriteEnabled();
    AcDbIntArray circleIndices;
    for (int i = 0; i < indices.length(); i++) {
        int idx = indices[i];
        switch(idx) {
        case 0:
            mp += offset;
            continue; // stretch begin point
        case 1:
            mp += offset;
            mpblob += offset;
            continue; // move
        case 2:
            mpblob += offset;
            continue; // stretch blob center
        // stretch blob radius:
        //
        case 3:
            oldquad = mpblob + AcGeVector3d(mrblob, 0, 0);
            break;
        case 4:
            oldquad = mpblob + AcGeVector3d(0, mrblob, 0);
            break;
        case 5:
            oldquad = mpblob - AcGeVector3d(mrblob, 0, 0);
            break;
        case 6:
            oldquad = mpblob - AcGeVector3d(0, mrblob, 0);
            break;
        default:
            if (idx > 6)
                circleIndices.append(idx - 7);
            continue;
        }
        newquad = oldquad + offset;
        mrblob = newquad.distanceTo(mpblob);
    }
    if (circleIndices.length() > 0)
        return circle.moveGripPointsAt(circleIndices, offset);
    else
        return Acad::eOk;
}

For filing purposes, the encapsulating object must call the embedded object's dwgOutFields(), dwgInFields(), dxfOutFields(), and dxfInFields() methods from within its own version of these methods.

For DWG filing, the call to the embedded object's dwgOutFields() and dwgInFields() methods can occur at any point in the corresponding methods of the encapsulating object (after the call to the base class's method). However, the call must occur in the same place in both dwgOutFields() and dwgInFields(). The following code is from the updated jblob sample program:

Acad::ErrorStatus
Jblob::dwgInFields(AcDbDwgFiler* filer)
{
    assertWriteEnabled();
    AcDbEntity::dwgInFields(filer);
    filer->readItem(&mp);
    filer->readItem(&mpblob);
    filer->readItem(&mrblob);
    filer->readItem(&mnormal);
    return circle.dwgInFields(filer);
}
Acad::ErrorStatus
Jblob::dwgOutFields(AcDbDwgFiler* filer) const
{
    assertReadEnabled();
    AcDbEntity::dwgOutFields(filer);
    filer->writeItem(mp);
    filer->writeItem(mpblob);
    filer->writeItem(mrblob);
    filer->writeItem(mnormal);
    return circle.dwgOutFields(filer);
}

For DXF filing, the embedded object must be filed out and in after all the data of the encapsulating object has been filed out and in; therefore, the call to the embedded object's dxfOutFields() and dxfInFields() methods should come last in the encapsulating object's dxfOutFields() and dxfInFields() methods. A separator is needed between the encapsulating object's data and the subsequent embedded object's data. The separator must be similar in function to the group 0 or 100 in that it must cause the filer to stop reading data. The normal DXF group code 0 cannot be used because DXF proxies use it to determine when to stop reading data. The group code 100 could have been used, but it might have caused confusion when manually reading a DXF file, and there was a need to distinguish when an embedded object is about to be written out in order to do some internal bookkeeping. Therefore, the DXF group code 101 was introduced.

The AcDb::DxfCode enum value for DXF group code 101 is AcDb::kDxfEmbeddedObjectStart. The data string Embedded Object is written out by the filer for this DXF group code.

Two methods were also added to the AcDbDxfFiler class:

The following code demonstrates how these methods are used in the updated jblob sample program:

Acad::ErrorStatus
Jblob::dxfInFields(AcDbDxfFiler* filer)
{
    assertWriteEnabled();
    struct resbuf rb;
    Acad::ErrorStatus es = AcDbEntity::dxfInFields(filer);
    if (es != Acad::eOk) {
        return es;
    }
    if (!filer->atSubclassData(kClassName)) {
        return Acad::eBadDxfSequence;
    }
    mnormal = AcGeVector3d(0, 0, 1); // set default value:
    while (es == Acad::eOk) {
        if ((es = filer->readItem(&rb)) == Acad::eOk) {
            switch(rb.restype) {
            case AcDb::kDxfXCoord:
                mp.set(rb.resval.rpoint[X], rb.resval.rpoint[Y],
                    rb.resval.rpoint[Z]);
                break;
            case AcDb::kDxfXCoord+1:
                mpblob.set(rb.resval.rpoint[X], rb.resval.rpoint[Y],
                    rb.resval.rpoint[Z]);
                break;
            case AcDb::kDxfReal:
                mrblob = rb.resval.rreal;
                break;
            case AcDb::kDxfNormalX:
                mnormal.set(rb.resval.rpoint[X], rb.resval.rpoint[Y],
                    rb.resval.rpoint[Z]);
            }
        }
    }
    if (filer->atEmbeddedObjectStart())
        return circle.dxfInFields(filer);
    else {
        filer->setError(Acad::eMissingDxfField,
            "missing expected embeddedObject marker");
        return filer->filerStatus();
    }
}
Acad::ErrorStatus
Jblob::dxfOutFields(AcDbDxfFiler* filer) const
{
    assertReadEnabled();
    AcDbEntity::dxfOutFields(filer);
    filer->writeItem(AcDb::kDxfSubclass, kClassName);
    filer->writeItem(AcDb::kDxfXCoord, mp);
    filer->writeItem(AcDb::kDxfXCoord + 1, mpblob);
    filer->writeItem(AcDb::kDxfReal, mrblob);
    if (filer->includesDefaultValues()
        || mnormal != AcGeVector3d(0,0,1))
    {
        filer->writeItem(AcDb::kDxfNormalX, mnormal);
    }
    filer->writeEmbeddedObjectStart();
    return circle.dxfOutFields(filer);
}

The following example shows the output in a DXF file (with the 310 group data strings shortened for readability):

0
JBLOB
  5
52
330
19
100
AcDbEntity
  8
0
 92
      256
310
00010000040000003C0000000600000002000000...
310
000000000000000000000000000000F03F700000...
310
0000
100
Jblob
 10
4.026791
 20
3.172968
 30
0.0
 11
5.916743
 21
5.299622
 31
0.0
 40
1.458724
101
Embedded Object
100
AcDbEntity
100
AcDbCircle
 10
5.916743
 20
5.299622
 30
0.0
 40
0.729362