SDK:Parametric Custom Control Points
.SDK|SDK ..SDK:Types|SDK Types ..SDK:Using the SDK|Using the SDK ..VCOM:VCOM (Vectorworks Component Object Model)|VCOM Basics ..VCOM:Class Reference|VCOM Class Reference
What's that
The blue handles that appear when object is selected and "2D Selection" tool is active are called control points.
The object sub-type provides standard control points. See SDK:Plug-in Object Resources#PExt. For example linear object would have two control points controlling the starting and ending point of the linear object.
The simplest way of creating more control points is by defining them as parameters (a pair of kFieldCoordLocX and kFieldCoordLocY in the parametric resources):
resource 'Prm#' (128) { { ... /* 23 */ "ControlPoint01X", "__DO_NOT_CHANGE", "0", 10, 0, /* kFieldCoordLocX */ /* 24 */ "ControlPoint01Y", "__DO_NOT_CHANGE", "0", 11, 0, /* kFieldCoordLocY */ /* 25 */ "ControlPoint02X", "__DO_NOT_CHANGE", "0", 10, 0, /* kFieldCoordLocX */ /* 26 */ "ControlPoint02Y", "__DO_NOT_CHANGE", "0", 11, 0, /* kFieldCoordLocY */
The universal name must be "ControlPoint..X", where ".." is the number of the control point starting from '01'. "__" (double underscore) in the localized name will prevent those parameter from showing in the info pane.
The code reads those as normal parameters (double). It also can control visibility VCOM:VectorWorks:ISDK::SetCustomObjectControlPointVisibility.
However this approach doesn't allow the parametric dynamically to define the count of the control points.
The control points example below dynamically creates control points, and allow the user to specify them:
Dynamic Control Points
Note This mechanism only exist for 2D currently.
Defining a parametric with custom sub-type
The parametric object must be custom sub-type in order Vectorworks to be able to use its custom control points:
resource 'PExt' (128, "") { /* Object sub-type */ 50, /*kCustomSubtype*/ /* Reset on move flag */ noResetOnMove, /* Reset on rotate flag */ noResetOnRotate };
Setup parametric
For performance purposes the parametric needs to be set as permanent in the memory. Otherwise there will be significant overhead while dragging the control points due to the event calls to the parametric.
case kParametricInitGlobals : { ... const long kParametricNoErrorKeepLoaded = -12; reply = kParametricNoErrorKeepLoaded; // Keep this object's Code loaded
Setup Extended properties
Extended properties must be setup so it allows eventing from the custom control:
case kObjOnInitXProperties: { CodeRefID objectID = (CodeRefID) message; // obtain the interface for accessing the extended properties using namespace VectorWorks::PluginSupport; VCOMPtr<IExtendedProps> pExtProps( IID_ExtendedProps ); pExtProps->SetObjectProperty( objectID, kObjXPropCustomCursorResize, true );
Creating the control points
During each reset the object may decide to recreate it's control points. This is not necessary done on each reset. Once the control points are created they will be the same until removed, and added again.
Here is example of adding a list of control points:
case kParametricRecalculate : { ... if ( <Control Points Need To Be Recreated> ) { // remove the current list of control points gSDK->CustomObjectControlPtsRemove( hParametricObj ); // create the new list if ( gSDK->CustomObjectControlPtsCreate( hParametricObj,controlPtsCnt, NULL, NULL ) ) { // set the control points data for(size_t i=0; i<controlPtsCnt; i++) { gSDK->CustomObjectControlPtSet( hParametricObj, i, controlPtsArray[i], true, false, i); } } }
This example uses the index of the points array as control point ID so they can be accessed later.
Events received for the control points
This is list of events passed to the parametric object's main function:
case kObjOnCursor_Draw: case kObjOnCursor_MouseDown: case kObjOnCursor_Complete: case kObjOnCursor_Cancel: case kObjOnCursor_MouseMove:
During these events you can use tool related functions. For example VCOM:VectorWorks:ISDK::GetToolPtCurrent2D to access the current cursor position.
kObjOnCursor_MouseDown
Sent when the user clicks on a control point to drag it. The 'message' contains the ID of the control point being dragged:
case kObjOnCursor_MouseDown: { controlPtIndex = message;
The ID may not be quite what you expect. If you statically define the control points in the Prm# resource (rather than dynamically with CustomObjectControlPtsCreate), the ID will be 1-based index of the control point's X resource. For example, given the resource:
resource 'Prm#' (128) { { "text", "Text", "Default Text", 4, 0, "ControlPoint01X", "__DO_NOT_CHANGE", "1mm", 10, 0, "ControlPoint01Y", "__DO_NOT_CHANGE", "1mm", 11, 0, } };
then 'message' would contain 2 (not 1) when the first control point is clicked, even though the control point is named "ControlPoint01." Subsequent points will show up as 4, 6, 8, etc. in the message.
kObjOnCursor_Draw
Sent while dragging a control point. During this event any tool related drawing function is accepted. (VCOM:VectorWorks:ISDK::DrawCoordLine)
kObjOnCursor_MouseMove
Sent while mouse moves while dragging.
kObjOnCursor_Cancel
Sent if the user have canceled the reposition of the control point.
kObjOnCursor_Complete
Sent when the user have moved the control point.
Example
struct SUserData { long fControlPtIndex; size_t fControlPtsCnt; WorldPt3 fControlPtsArray[10]; SUserData() { fControlPtIndex = -1; fControlPtsCnt = 0; } }; long ParametricMain(long action, MCObjectHandle hParametricObj, long message, long &userData) { long reply = kParametricNoErr; SUserData* pUserData = (SUserData*) userData; switch (action) { case kParametricInitGlobals : { // Construct explicitly with 'placement new'. Get memory from the app because // memory allocated with normal 'new' disappears when the dll unloads. void* pRawMemory = ::GS_NewPtr( gCBP, sizeof(SUserData) ); if ( pRawMemory ) { pUserData = (SUserData*) new(pRawMemory) SUserData(); userData = (long) pUserData; } pUserData->fControlPtsCnt = 0; pUserData->fControlPtsArray[ pUserData->fControlPtsCnt ].Set( 10, 0, 0 ); pUserData->fControlPtsCnt++; pUserData->fControlPtsArray[ pUserData->fControlPtsCnt ].Set( 0, 10, 0 ); pUserData->fControlPtsCnt++; pUserData->fControlPtsArray[ pUserData->fControlPtsCnt ].Set( 10, 10, 0 ); const long kParametricNoErrorKeepLoaded = -12; reply = kParametricNoErrorKeepLoaded; // Keep this object's Code loaded break; } case kParametricDisposeGlobals : { // Destruct explicitly when using 'placement new' if ( pUserData ) { pUserData->~SUserData(); ::GS_DisposePtr( gCBP, pUserData ); pUserData = NULL; userData = 0; } break; } case kParametricPreference : { reply = kParametricNotImplemented; break; } case kObjOnInitXProperties: { CodeRefID objectID = (CodeRefID) message; // obtain the interfact for accessing the extended properties using namespace VectorWorks::PluginSupport; VCOMPtr<IExtendedProps> pExtProps( IID_ExtendedProps ); // enable custom UI for that plug-in parametric type // all instances of that parametric will have the custom UI defined here pExtProps->SetObjectProperty( objectID, kObjXPropHasUIOverride, true ); // define custom UI for that parametric type IWidgetsEditProvider* pWidgetsProvider = NULL; if ( VCOM_SUCCEEDED( pExtProps->GetObjComponentTypeWidgets( objectID, kObjectRootComponentTypeID, pWidgetsProvider ) ) ) { pWidgetsProvider->AddWidget( 111, kWidgetButton, "Add Control Point" ); pWidgetsProvider->AddWidget( 222, kWidgetButton, "Remove Control Point" ); } pExtProps->SetObjectProperty( objectID, kObjXPropCustomCursorResize, true ); break; } case kObjOnObjectUIButtonHit: { if ( message == 111 ) { pUserData->fControlPtsCnt++; pUserData->fControlPtsArray[ pUserData->fControlPtsCnt ] = pUserData->fControlPtsArray[ pUserData->fControlPtsCnt - 1 ]; pUserData->fControlPtsArray[ pUserData->fControlPtsCnt ].x += 1; pUserData->fControlPtsArray[ pUserData->fControlPtsCnt ].y += 1; gSDK->ResetObject( hParametricObj ); } else if ( message == 222 ) { if ( pUserData->fControlPtsCnt > 1 ) { pUserData->fControlPtsCnt--; gSDK->ResetObject( hParametricObj ); } } break; } case kParametricRecalculate : { gSDK->CreateTextBlock( "Some text", WorldPt(0, 0), false, 0); WorldPt pt( 0, 0 ); for(size_t i=0; i<pUserData->fControlPtsCnt; i++) { const WorldPt3 dataPt = pUserData->fControlPtsArray[i]; gSDK->CreateLine( pt, WorldPt(dataPt.x, dataPt.y) ); } gSDK->CustomObjectControlPtsRemove( hParametricObj ); if ( gSDK->CustomObjectControlPtsCreate( hParametricObj, pUserData->fControlPtsCnt, NULL, NULL ) ) { for(size_t i=0; i<pUserData->fControlPtsCnt; i++) { gSDK->CustomObjectControlPtSet( hParametricObj, i, pUserData->fControlPtsArray[i], true, false, i); } } break; } case kObjOnCursor_Draw: { if ( pUserData->fControlPtIndex >= 0 ) { TransformMatrix matrix; gSDK->GetEntityMatrix( hParametricObj, matrix ); WorldPt currPt; gSDK->GetToolPtCurrent2D( currPt ); gSDK->DrawCoordLine( WorldPt(matrix.P().x, matrix.P().y), currPt ); } reply = kObjectEventHandled; break; } case kObjOnCursor_MouseDown: { pUserData->fControlPtIndex = message; reply = kObjectEventHandled; break; } case kObjOnCursor_Complete: { reply = kObjectEventNotHandled; if ( pUserData->fControlPtIndex >= 0 ) { TransformMatrix matrix; gSDK->GetEntityMatrix( hParametricObj, matrix ); WorldPt currPt; gSDK->GetToolPtCurrent2D( currPt ); WorldPt3 localPt( currPt, 0 ); InversePointTransformN( localPt, matrix, localPt); pUserData->fControlPtsArray[ pUserData->fControlPtIndex ] = localPt; gSDK->ResetObject( hParametricObj ); reply = kObjectEventHandled; } break; } case kObjOnCursor_Cancel: { reply = kObjectEventHandled; break; } case kObjOnCursor_MouseMove: { reply = kObjectEventHandled; break; } } return reply; }
The example demonstrates simple parametric object that has two buttons in it's info pane. These buttons can be used to create or remove control points.
User data keeps the control points (very primitivelly as fixed array. You can use STL array like vwallocator from VWFC Library. See SDK:Global Data.)
Each control point determines a line going for the center object to the control points position.
Each control point position is inside the parametrics local coordinate space. That is why the code must convert tool points to local coordinates.