Creo Elements/Direct Modeling COM & .NET API

Creo Elements/Direct Modeling COM & .NET API - Integrating add-ins and Lisp


Introduction

The .NET API provides functionality to deeply scan the model from all languages. The Lisp-based APIs in the Developer's Kit provide, among many other things, all the functionality to build user interface components in Creo Elements/Direct Modeling. This document describes integration functionality which serves as a bridge between Lisp and COM/.NET-based code implemented in add-ins, allowing to create sophisticated applications which combine the best of both worlds.

In this document, we will outline the basic steps for the following typical scenario:

For the following discussion, we will develop a simple add-in in several steps. The name of the add-in is "TestAddin".

Step 1: Loading Lisp code associated with the add-in

Activating the TestAddin

When the user activates an add-in (via the "Add-Ins" tab of the Application/Modules... dialog), Creo Elements/Direct Modeling will search for Lisp files which are associated with the add-in. To have your Lisp file automatically loaded, simply copy it to the same directory where the add-in's configuration file (here: TestAddin.cfg) was deployed to. See the section on Add-ins (internal clients) for details on add-in installation.

In our case, the Lisp file is called TestAddin.lsp and contains the definition of a simple user dialog. We will examine the dialog code in step 3. The dialog name is call_testaddin.


Step 2: Registering the Lisp code with an add-in menu button

Add-ins usually register one or several buttons which are displayed in the add-in menu. Our test add-in contains only one button. When the button is pressed, a Lisp command will be issued which starts the dialog.

Menus and menu buttons are usually registered in the add-in's OnConnection method:

Menu for TestAddin
 // Create the add-in menu
 _bstr_t menuTitle("TestAddin");
 if (SUCCEEDED(pIFrame->CreateMenu(m_pIThisAddin, menuTitle)) {
   // Add a menu button
   _bstr_t buttonTitle("Select");
   _bstr_t action("call_testaddin");
   hr = pIFrame->AddMenuButton(m_pIThisAddin, buttonTitle, 
          "", ActionType_LispForm, action);
 }

The key API in this example is IFrame::AddMenuButton. It can operate in two modes:

In the sample code above, we use the LispForm mode: When pressing the button, the string "call_testaddin" is sent to the Lisp interpreter. In the next step, we will see what we need to do so that Lisp knows how to interpret that string.

Step 3: Selecting objects using the dialog

When the user presses the Select button, the call_testaddin dialog will be started which is defined in TestAddin.lsp:

TestAddin dialog
(oli:sd-defdialog
  'call_testaddin :dialog-title "Call TestAddin"
  :variables
  '((OBJECTS :value-type :part-assembly
     :modifies :nil :multiple-items t
     :title "Objects"
     :prompt-text "Select parts and assemblies."))
  :ok-action
  '(.net:invoke-addin "TestAddin" "ProcessSelection" 
    (list :selection-item OBJECTS))
 )

This dialog code defines an input variable OBJECTS of type :part-assembly, which automatically generates the associated UI elements and the user interaction for selecting parts or assemblies by either picking them, specifying a name, or using any of Creo Elements/Direct Modeling's other selector options. (See the section on sd-defdialog in the Integration Kit reference documentation if you want to learn more about dialog features.)

As explained above, TestAddin.lsp will be loaded automatically when the add-in is activated. This registers the command call_testaddin, and so when this string is sent to the Lisp interpreter upon pressing the "Select" button, the Lisp interpreter knows which code to run.

The user can now click the "Select" button in the TestAddin menu, and this will run the command call_testaddin which displays the above dialog.

When the selection is done and the user presses the OK button in the dialog, the code transfers the selected data in OBJECTS to the add-in using invoke-addin. invoke-addin will invoke a command called "ProcessSelection" in the add-in.

In our case, we transfer the list of selected objects as parameters. Because of the special keyword :selection-item, invoke-addin knows that it needs to convert the Lisp structures of type SEL_ITEM into API objects of type ISelectionItem.

Step 4: Processing the selected objects in the add-in

Behind the scenes, invoke-addin queries the add-in for its default dispatch interface. If it has one, it assumes that there is a member called ProcessSelection in that interface, and calls it with the parameters which we passed, i.e. the list of selected items.

We recommend to use the .NET API add-in wizard in "Advanced UI integration via Lisp dialog" mode, which will set up a default dispatch interface for you. A typical structure in the wizard-generated main header file (CTestAddin.h) will look like this:

  // ... #include, #import and namespace statements omitted
		
  /**
    * Class managing the connection to Creo Elements/Direct Modeling
    */
  class ATL_NO_VTABLE CTestAddin:
    public CComObjectRootEx<CComSingleThreadModel>,
    public CComCoClass<CTestAddin, &CLSID_TestAddin>,
    public IDispatchImpl<IOsdmAddin>,
    public IDispatchImpl<IOsdmCommandTarget>,
    public IDispatchImpl<ITestAddin>
    {
      // ...
      BEGIN_COM_MAP(CTestAddin)
        COM_INTERFACE_ENTRY(IOsdmAddin)
        COM_INTERFACE_ENTRY(IOsdmCommandTarget)
        COM_INTERFACE_ENTRY2(IDispatch, ITestAddin)
      END_COM_MAP()
			
      // ...
			
      // Implementation for interface CoCreate::IOsdmAddin
      // ...
      // Implementation for interface CoCreate::IOsdmCommandTarget
      // ...
					
      // Implementation for interface ITestAddin
      STDMETHOD(MyCmd1)(SAFEARRAY* selitems, VARIANT* ret);
      // TODO: declare further automation-compatible functions
      //       defined in the custom interface ITestAddin

In this case, the default dispatch interface is ITestAddin, and the wizard has already generated a declaration for one interface member called MyCmd1. That function already has the right signature - it receives an array of selection items and returns a boolan value as a VARIANT; so we only need to rename the function to ProcessSelection, since that is the name referred to from our Lisp code.

The wizard also creates a stub function called MyCmd1 in CTestAddin.cpp. After renaming it and filling in the required processing code, the function could look like this:

STDMETHODIMP CTestAddin::ProcessSelection(SAFEARRAY *selitems, VARIANT *ret)
{
   VARIANT_BOOL result = VARIANT_TRUE;
	 
   CComSafeArray<VARIANT> sa(selitems);
   for (unsigned long i=0; i<sa.GetCount(); i++) {
     CComQIPtr<ISelectionItem> selItem = V_DISPATCH(&sa[(int)i]);
     if (selItem) {
       CComPtr<IComponent> pIComponent = selItem->IReferencedComponent;
       result = ProcessComponent(pIComponent);
     }
   }
	 
   // set return value
   VariantClear(ret);
   V_VT(ret) = VT_BOOL;
   V_BOOL(ret) = result;
   return S_OK;
}

In this example, the dialog passes so-called selection items to the add-in, i.e. the results of a selection in Creo Elements/Direct Modeling. Selection item objects implement the ISelectionItem interface. For each selection item, the example code inquires the component which it represents, and then processes it - for example, it could traverse the model tree starting from this component or directly inquire the component for additional information.

Step 5: Returning results to Lisp

The ProcessSelection function has a VARIANT return value of type VT_BOOL which it sets to VARIANT_FALSE if one of the passed items cannot be processed properly.

When the function returns, the boolean value will be converted into a corresponding Lisp data type. In our case, we're using a simple boolean return value, so the Lisp caller will receive a nil or t value.

The Lisp/COM interoperability layer in Creo Elements/Direct Modeling supports other return value types as well, such as integers (VT_I4, VT_I2...), floating-point values (VT_R8, VT_R4), strings (VT_BSTR), ISelectionItem interface pointers, and others. A complete list can be found in Lisp Interoperability.