SDK:Parametric Custom Control Points

From Vectorworks Developer
Jump to navigation Jump to search

.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

By Vladislav Stanev

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.

See also

SDK:Plug-in Object Events | SDK:Parametric Object Plug-in