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.