C++ GRX SDK · Custom Entities

Implementing AcDbGripData & Custom OSNAP Modes in C++ GRX (dev-cookbook-16)

Deep-dive C++ GRX guide to overriding subGetGripPoints(), subMoveGripPointsAt(), and AcDbCustomOsnapMode for custom parametric entities in GstarCAD 2027.

1. Architecture of AppData-Driven Grips vs. Legacy Index-Based Grips

When porting custom C++ entities (`AcDbEntity` or `AcDbCurve` subclasses) to GstarCAD 2027 via the GRX SDK, developers can choose between two virtual grip protocols: the legacy integer index array (`subGetGripPoints(AcGePoint3dArray&, AcDbIntArray&, AcDbIntArray&)`) or the modern object-oriented AcDbGripDataPtrArray signature. For multi-function engineering primitives—such as structural steel gusset plates, precast concrete embeds, or HVAC dampers—the `AcDbGripData` architecture is strongly preferred because each grip carries an opaque `void* appData` pointer, custom hover/hot viewport draw callbacks (`GripWorldDrawPtr`), and per-grip right-click context menus.

In GstarCAD 2027's AC1032 graphics pipeline, whenever a user selects a custom entity in Model Space, the host grip manager queries `subGetGripPoints()` with unit view vectors and pixel scaling factors. When the user drags a hot grip, `subMoveGripPointsAt()` receives the `AcDbVoidPtrArray` of active `appData` identifiers and the 3D displacement vector (`const AcGeVector3d& offset`), updating the entity within an active database drag transaction.

2. Production C++ GRX Implementation: Multi-Mode `AcDbGripData` Override

The following C++ implementation demonstrates a custom structural bracket entity (`GcSteelBracket`) that exposes three distinct grip behaviors: a base insertion grip (`kGripOrigin`), a rotational orientation grip (`kGripAngle`), and a flange length stretch grip (`kGripLength`) constrained along the bracket's local X-axis:

#include "grxHeaders.h"
#include "dbmain.h"
#include "dbgrip.h"

enum BracketGripId : intptr_t {
    kGripOrigin = 1,
    kGripLength = 2,
    kGripAngle  = 3
};

Acad::ErrorStatus GcSteelBracket::subGetGripPoints(
    AcDbGripDataPtrArray& grips,
    const double curViewUnitSize,
    const int gripSize,
    const AcGeVector3d& curViewDir,
    const int bitflags) const
{
    assertReadEnabled();

    // 1. Origin Translation Grip
    AcDbGripData* pOriginGrip = new AcDbGripData();
    pOriginGrip->setGripPoint(m_origin);
    pOriginGrip->setAppData(reinterpret_cast<void*>(kGripOrigin));
    grips.append(pOriginGrip);

    // 2. Constrained Flange Length Stretch Grip
    AcGePoint3d tipPt = m_origin + (m_dirX.normal() * m_flangeLength);
    AcDbGripData* pLenGrip = new AcDbGripData();
    pLenGrip->setGripPoint(tipPt);
    pLenGrip->setAppData(reinterpret_cast<void*>(kGripLength));
    grips.append(pLenGrip);

    return Acad::eOk;
}

Acad::ErrorStatus GcSteelBracket::subMoveGripPointsAt(
    const AcDbVoidPtrArray& gripAppData,
    const AcGeVector3d& offset,
    const int bitflags)
{
    assertWriteEnabled();
    if (offset.isZeroLength()) return Acad::eOk;

    for (int i = 0; i < gripAppData.length(); ++i) {
        const auto gripType = reinterpret_cast<intptr_t>(gripAppData[i]);
        if (gripType == kGripOrigin) {
            m_origin += offset;
        } else if (gripType == kGripLength) {
            // Project 3D drag vector onto local bracket X-axis and clamp to [50mm, 2500mm]
            double delta = offset.dotProduct(m_dirX.normal());
            m_flangeLength = std::clamp(m_flangeLength + delta, 50.0, 2500.0);
        }
    }
    recordGraphicsModified(true);
    return Acad::eOk;
}

3. Registering Custom Object Snap Modes (`AcDbCustomOsnapMode`)

Standard CAD object snaps (`kOsModeEnd`, `kOsModeMid`, `kOsModeCen`) often fail to capture domain-specific engineering points—such as the center of gravity (CoG) of an asymmetric precast panel or the weld root throat of a structural joint. Using `AcDbCustomOsnapManager` (`acdbCustomOsnapManager()`), your `.grx` plugin can register a custom OSNAP keyword (e.g., `_COG`) complete with a custom `AcGiGlyph` viewport marker:

GRX Virtual Method / Class Execution Context Critical Memory & Threading Rule
AcDbEntity::subGetOsnapPoints() Called on mouse hover when `OSMODE` bitmask is active Must call assertReadEnabled(); never open or modify other database objects inside an OSNAP query.
AcDbCustomOsnapInfo::getOsnapInfo() Protocol extension attached to `AcDbEntity::desc()` Unregister via acdbCustomOsnapManager()->removeCustomOsnapMode() in `On_kUnloadAppMsg` to avoid dangling vtable pointers.
AcGiGlyph::subViewportDraw() Renders custom snap aperture symbol in device pixels Use AcGiViewportGeometry::polygonDc() or polylineDc() so glyph size remains invariant during zoom.

4. Debugging Custom Grip HoverCallbacks & Multi-Grip Stretch Combinations

When an engineer holds the Shift key in GstarCAD 2027 to select multiple grips simultaneously on your custom entity before clicking a hot grip, `subMoveGripPointsAt()` receives all selected `appData` pointers inside the `gripAppData` array in a single call. Your C++ handler should inspect `gripAppData.length()`: if both `kGripOrigin` and `kGripLength` are hot simultaneously, apply a rigid-body translation (`transformBy(AcGeMatrix3d::translation(offset))`) rather than stretching the flange length twice.

Additionally, if you attach a custom `GripWorldDrawPtr` callback to `AcDbGripData::setWorldDraw()`, verify that your custom grip glyph returns `true` when drawing world-space vectors or `false` if delegating to `GripViewportDrawPtr` for pixel-invariant screen icons. Always test custom grips under both 2D Wireframe (`SHADEMODE = 2D`) and Hardware-Accelerated Realistic modes (`3DCONFIG`) to confirm transient grip buffers clear cleanly upon `ESC` (`GripOpStatus::kGripAbort`).

Validate This Workflow in GstarCAD 2027

Engineered by Suzhou Gstarsoft Co., Ltd. (SSE: 688657), GstarCAD 2027 delivers 100% native AC1032 DWG binary fidelity, perpetual licensing sovereignty, and zero-day AutoCAD command parity.

Download Free 30-Day Evaluation → Explore AutoCAD Migration Hub Take Skill Certification Quiz