Example 7


Pro/TOOLKIT: Getting Along With UDFs

Pro/TOOLKIT is an Application Programmers Interface (API) which allows Pro/ENGINEER’s functionality to be augmented and/or customized to meet the specific needs of PTC's customer base using the "C" programming language. Specifically, Pro/TOOLKIT provides the ability to customize the standard Pro/ENGINEER user interface, automate processes involving repetitive steps, integrate proprietary or other external applications with Pro/ENGINEER and develop customized end-user application for model creation, design rule verification and drawing automation.

Unfortunately, Pro/TOOLKIT (deservingly) gained a reputation of being hard to work with because it is by no means easy to become familiar with a vast number of functions provided by PTC. Even though there is a large number of examples in the Pro/TOOLKIT User Guide that PTC’s programmers have written over the years to educate their users, it is still likely that a newcomer will feel overwhelmed when taking the first step. What is more troublesome is that some of the functionally is not illustrated by examples and a user is left only with a very brief descriptions of the functions in the User Guide and cross-linked function reference in the API browser.

In this article we will try to alleviate the above problem and provide detailed description of a set of API function calls that is not illustrated by any examples in the User Guide. We will discuss the automation of functionality which is used quite often in solid modeling with Pro/ENGINEER – a User Defined Feature (UDF). This feature is quite versatile but is not useful when involves a large number of repetitive steps. Here is where Pro/TOOLKIT can help.

Here, you will also find bits and pieces of how to transcend the boundaries of the C programming world that PTC supports today with regards to Pro/TOOLKIT. Whenever I can, I try to use a powerful Standard Template Library (STL) which is a usual accompaniment of any C++ compiler today. My development environment is MSVC++ .NET. I assume some knowledge of Pro/TOOLKIT.

What is a UDF?

Very simply described, a UDF concept arises from a need to quickly insert one or many already created features into a model. The need dictates that features should be created once in some model, stored in a file and later used for creating of the similar geometry in another part. This would reduce the time for building of a new model, but would still retain the flexibility of parametrically controlling the copied feature.

The above is exactly what UDFs mechanism is designed to achieve. You simply enter a menu to make your choices, select features to be grouped, designate how the existing reference for the features should be handled when inserting into another model and finally you store it all into a file with a .gph extension. In this fashion you can create a library of specialized reusable groups of features that can be used by anybody else. I suspect that this is a usual way in the industry to deal with repetitive tasks of creating similar geometry. However, even this can be a source of repetitive action which is why this article was written. The repetitiveness arises in cases when a large number of copied groups are needed that still allow for individual parametric adjustments. For example, the manufacturers of computer keyboards are faced with this problem often. Here is where we will take off, but first let us make a simple UDF that will illustrated all parts of the process.

Make a UDF

First make an assembly PLATE.ASM and create a fill pattern table of coordinate systems (one inch apart) as shown in Figure 1

Figure1. Assembly model.

Then insert a part that looks as shown on Figure 2.

Figure 2. Plate part in the plate.asm

Note a datum plane (TOP) which is a half inch offset from the surface of the plate. It is important that coordinate systems are in the assembly and that the TOP datum plane is in the plate model. These two references we will use later for our UDF and their location in the models will determine how they will need to be handled in our toolkit program.

The next thing to do is to create a simple protrusion as shown in Figure 3.

Figure 3. Template for our UDF feature.

DTM4 and DTM5 (or whatever they are called in your model) are placed at z and y axis of ACS0 coordinate system. The protrusion shown in red is centered at ACS0 and extruded from an ellipse with shown dimensions. The protrusion is extruded to the TOP plane which is important because this adds a reference to our UDF. After making the protrusion, and this is important as well, edit it and name two dimension as Yradius and Xradius to represent Ry and Rx respectively in our UDF.

Finally, activate PLATE.PRT and make a UDF with these three features (DTM4, DTM5 and the protrusion). In UDF menu choose Var Dim and pick Rx and Ry dimensions, and assign the prompts Xradius and Yradius, while for references type “coordinate system” and “top plane”. Choose UDF dependency independent, same dimensions, and display normal. Save UDF as bos.gph and put it into your UDF group directory. Make sure that you test whether everything is as expected when you try to place the UDF (do not forget to activate PLATE.PRT again).

Emperor’s invisible cloth

Now, you are ready to enter the world of Pro/TOOLKIT and expose the emperor’s cloth (since Pro/TOOLKIT’s documentation does not do it for us), but first you should have good understanding of the interactive creation of UDFs using Pro/ENGINEER. The process is reminiscent of an interactive session. The main goal is to create a udf data structure which describes needed information and to supply it to the ProUdfCreate() function that does the job. Your main concern will be how to properly add the necessary references to your UDF. The complete code is provided in the appendix for easy copying and pasting.

Let us first begin with obtaining proper part and assembly handles and needed references. This gives us a chance to practice some other things as well and use often neglected utility functions provided by PTC in protoolkit/protk_appls/pt_examples/pt_utils . My recommendation is that you immediately create a library projects with one .c file that contains

#include "TestCollect.c"

#include "TestError.c"

#include "TestQcr.c"

#include "TestRunmode.c"

#include "UtilArcEndPoints.c"

#include "UtilCable.c"

#include "UtilCollect.c"

#include "UtilCollectDtmpnt.c"

#include "UtilError.c"

#include "UtilFiles.c"

#include "UtilGroups.c"

#include "UtilIntfData.c"

#include "UtilMath.c"

#include "UtilMatrix.c"

#include "UtilMenu.c"

#include "UtilMessage.c"

#include "UtilNames.c"

#include "UtilString.c"

#include "UtilTree.c"

#include "UtilTypes.c"

#include "UtilVisit.c"

#include "TestDimension.c"

Then you can include this library in any of your projects and use the provided functions. You’ll see a couple of examples of using utility functions bellow.

As mentioned, we will first obtain the handle to our assembly, internal id for our TOP plane and ids for coordinate systems where we want our UDF placed.

void user_place_udf(){

// FOR DECLARATIONS OF VARIABLES SEE THE APPENDIX

ProMdlCurrentGet(&asmbly); // the main assembly handle

err_acomps = ProUtilCollectAsmcomp((ProAssembly)asmbly,&asm_comps);

Here is how you collect assembly components using utility functions; asm_coms is declared as a pointer to ProAsmcomp, but what the function returns is really an expandable array that contains pointers to assembly components. Today the expandable array ProArray is a bit obsolete because of STL’s vector object and I never use it except in the cases of Pro/TOOLKIT functions returning it. So the only thing you need to know is how to retrieve from that array which is done as follows.

ProArraySizeGet ((ProArray)asm_comps, &n_asm_comps);

for (int i=0; i<n_asm_comps; i++)

{

ProAsmcompMdlNameGet (& (asm_comps[i]), &mdl_type, mdl_name);

ProWstringToString(c_mdl_name,mdl_name);

string stl_mdl_name(c_mdl_name);

string::size_type pos = stl_mdl_name.find("PLATE");

if(pos != string::npos){ //found

ProAsmcompMdlGet((ProAsmcomp *) &(asm_comps[i]), &plate);

plate_feat_id = asm_comps[i].id;

break;

}

}

As shown above you examine all of the components of the assembly (in this case only one) by extracting their names and types. Observe how easy is to find some characters using STL’s object string and its method find. C++ allows you to declare any object anywhere. Once we found the object with name PLATE we store its handle and its internal id.

ProUtilFindFeatureByName((ProSolid)plate,

ProStringToWstring(w_string,"TOP"),&f_top_plane);

ProUtilFindFeatureGeomitemByName(&f_top_plane,PRO_SURFACE,

ProStringToWstring(w_string,"TOP"),&g_top_plane);

Here again we utilize utility functions to find our top plane geomitem structure. This is one aspect of Pro/TOOLKIT that you need to get familiar with. Features contain geometric items and they need to be extracted so that we can add needed references to our UDF later.

Finally, we get all of the coordinate systems we want the UDF to be placed at

err_csys = ProUtilCollectSolidCsys((ProSolid)asmbly,&p_csys);

ProArraySizeGet ((ProArray)p_csys, &n_csys);

for (int i=0; i<n_csys; i++){

ProModelitem mitem;

ProFeature feature;

string::size_type pos;

ProCsysToGeomitem((ProSolid)asmbly,p_csys[i],&mitem);

ProGeomitemFeatureGet(&mitem,&feature);

ProModelitemNameGet(&feature,w_name);

ProWstringToString(c_name,w_name);

string stl_name(c_name);

pos = stl_name.find("ACS");

if(pos != string::npos){ // FOUND

csys_ids.push_back(mitem.id);

}

}

Similarly, as we did before, we can use a utility function to collect the handles to all coordinate systems in the assembly model and extract the ones that start with characters ACS. I assume that that you used a default name when constructed the first coordinate system.

Notice a bit of gymnastics that needs to be done when converting to something that we can get a name of. The coordinate systems need to be converted to geomitems (or modelitems). Then those geomitems belong to the features whose name we can finally ask for. Again, notice how to use string objects and how you can store internal ids of your coordinate systems. csys_ids is another very useful (and easy to use) STL object “vector” declared as vector<int> csys_ids;. STL’s vector object allows for a dynamically created array that can contain any type of objects you want (in this case we needed integers). This is why I mentioned that you do not have to use PTC’s expandable arrays anymore.

Finally we are able to start building our udf data structure.

double Yradius = .2, Xradius = .4;

for (unsigned int i = 0; i<csys_ids.size(); i++){

err = ProUdfdataAlloc(&udf_data);

err = ProUdfdataNameSet(udf_data,ProStringToWstring(w_string,

prof_name),NULL);

err = ProUdfdataDependencySet(udf_data,PROUDFDEPENDENCY_INDEPENDENT);

err = ProUdfdataScaleSet(udf_data,PROUDFSCALETYPE_SAME_DIMS,0);

err = ProUdfdataDimdisplaySet(udf_data,PROUDFDIMDISP_NORMAL);

Since we are going to place our UDF at each assembly ACS* coordinate system, we will step through a list of ids. With each step you need to do what you need to do interactively. You will probably recognize above inputs to functions as prompts in Pro/E interactive UDF sessions.

Next, we need to add references to TOP datum plane and each coordinate system.

add_ref(asmbly,csys_ids[i],PRO_CSYS,PRO_B_TRUE,"coordinate

system",udf_data);

add_ref(plate,g_top_plane.id,PRO_SURFACE,PRO_B_FALSE,"top

plane",udf_data);

add_ref() is my little handy special utility which I will explain bellow, but for now observe the inputs to it. It takes a model handle where the reference is, internal id of the geomitem, its type, whether the geomitem is an external reference, the prompt and udf data structure which we are filling with the data. Now, since we are placing our udf into the PLATE.PRT, the coordinate systems which are in the assembly are external to it, while TOP plane is not because it is in PLATE.PRT. For that reason you see PRO_B_TRUE (for external) and PRO_B_FALSE (for not external) as inputs above.

Now, if you do not want default dimensions (the ones you assigned at the time of UDF creation), you need to set the dimension values as shown bellow. Notice how the names of dimensions are specified and added to the udf data structure.

ProUdfvardimAlloc(ProStringToWstring(w_string,"Yradius"),

Yradius,PROUDFVARTYPE_DIM,&vardim);

ProUdfdataUdfvardimAdd(udf_data,vardim);

ProUdfvardimAlloc(ProStringToWstring(w_string,"Xradius"),

Xradius,PROUDFVARTYPE_DIM,&vardim);

ProUdfdataUdfvardimAdd(udf_data,vardim);

If at the time of placement you do not need to perform the flips for the datum references it is possible to skip the following, but I like to have it so that I know what is going on.

for (int j=0; j<4; j++)

err=ProUdfdataOrientationAdd(udf_data,PROUDFORIENT_NO_FLIP);

I did not need to flip the arrows for my UDF and therefore the obvious choice of no flip above. However, be ware of the problems here. I have experienced that sometimes when a flip needs to be performed programmatically features do not get placed as expected. Therefore, this may be a potential issue for PTC’s technical support which I leave for you to explore in the future.

Finally, we are ready to submit our udf data structure to ProUdfCreate .

comp_id_table[0]=plate_feat_id;

ProAsmcomppathInit( (ProSolid) plate, comp_id_table, 1, &comp_path);

ProUdfCreate((ProSolid)plate,udf_data,&comp_path,NULL,0,&udf);

ProUdfdataFree(udf_data);

Notice that, because we have an external reference, we need to create a component path object that locates our PLATE.PRT in the assembly (which is why we needed that internal id of our part before) and provide it to ProUdfCreate() function. At the end you should free the memory by freeing the udf data structure.

Now I will describe my handy add_ref() function for which I already described the input. This is the most trickiest part of this exercise, because we need to create a selection object with proper reference to objects and wrong pointers or id may lead to crash of Pro/E here. For that reason you see bellow careful handling of the error at each step.

ProError add_ref(ProMdl handle,int item_id,

ProType item_type,

ProBoolean ref_bool,

char * c_prompt,

ProUdfdata udf_data){

ProModelitem modelitem;

ProSelection selection;

ProLine prompt;

ProUdfreference reference;

ProError err;

err=ProModelitemInit(handle,item_id,item_type,&modelitem);

if(PRO_TK_NO_ERROR != err) goto error;

err=ProSelectionAlloc(NULL,&modelitem,&selection);

if(PRO_TK_NO_ERROR != err) goto error;

ProStringToWstring(prompt,c_prompt);

err=ProUdfreferenceAlloc(prompt,selection,ref_bool,&reference);

if(PRO_TK_NO_ERROR != err) goto error;

err=ProUdfdataReferenceAdd(udf_data,reference);

if(PRO_TK_NO_ERROR != err) goto error;

err=ProSelectionFree(&selection);

error:

return err;

}

First the geomitem is created and a selection is allocated for it. Then a reference is allocated for this selection with information whether the reference is external to UDF or not. Finally, the allocated reference is added to udf data structure and selection is freed. This function should work in all cases when adding UDFs so most likely you can use it in the future as it is.

If everything went well you projects should compile now and execute without problems.

Conclusion

We have gone through a simple but very illustrative example of inserting UDFs programmatically. Several steps gave you a chance to see how to utilize some underused functionality provided by PTC. Also, in most of the cases the provided example should be the most complex one you would ever see when dealing with UDFs. The reason is that usually UDFs are placed into a part or assembly while avoiding external references due to difficulties involved. Therefore, this article gives you a unique chance to learn how external references should be handled.

This article also fills the gap that exists in the Pro/TOOLKIT’s documentation with regards to UDF functions since there are no examples provided whatsoever.

For your information, my projects are all C++. Even though PTC officially does not support usage of Pro/TOOLKIT with C++ I find it much faster for development. Once there is an issue it is easy to extract the problematic code and give it to PTC without C++ functionality. I hope that eventually we will have the abstract C++ layer provided by PTC which will make Pro/TOOLKIT programming easier. This, surprisingly, is not difficult to do. All that is needed is to create object classes and to wrap Pro/TOOLKIT functions into class members taking advantage of sophisticated C++ mechanisms such as overloading, polymorphism etc.

In the future I will write about how Objective Oriented Programming can make many Pro/TOOLKIT components transparent to users. Model handles, model items, selection objects, feature structures etc. can all be embedded into classes taking advantage of C++ mechanisms. Probability of the crash due to incorrect use of pointers can be minimized and Pro/TOOLKIT programming can become much easier to do than it is now.

Apendix

// CONTENT OF THE FILE DEVELOP.H

#ifndef DEVELOP_H

#define DEVELOP_H

extern "C"{ // extern directive because I want to work with C++

// since Pro/TOOLKIT stuff is all written in C it

// must be designated as C code

#include <ProToolkit.h>

#include <ProUtil.h>

#include <ProMenu.h>

#include <ProMenuBar.h>

#include <ProMessage.h>

#include <ProWindows.h>

#include <ProSurface.h>

#include <ProSelection.h>

#include <ProGeomitem.h>

#include <ProGraphic.h>

#include <ProGeomitemdata.h>

#include <UtilCollect.h>

#include <ProUdf.h>

// these 2 function must always be decreated and

// defined in your Pro/TOOLKIT dll application

int user_initialize(int argc,char ** argv);

void user_terminate();

}

static ProName dev_msg;

// this macro will be a time saver

#define MSGFILE ProStringToWstring(dev_msg,"develop_msg.txt")

//udf function

void user_place_udf();

#endif

// CONTENT OF THE FILE UDF.CPP

#include "develop.h"

#include <string>

#include <vector>

using namespace std;

ProError add_ref(ProMdl handle,int item_id,

ProType item_type,

ProBoolean ref_bool,

char * c_prompt,

ProUdfdata udf_data){

ProModelitem modelitem;

ProSelection selection;

ProLine prompt;

ProUdfreference reference;

ProError err;

err=ProModelitemInit(handle,item_id,item_type,&modelitem);

if(PRO_TK_NO_ERROR != err) goto error;

err=ProSelectionAlloc(NULL,&modelitem,&selection);

if(PRO_TK_NO_ERROR != err) goto error;

ProStringToWstring(prompt,c_prompt);

err=ProUdfreferenceAlloc(prompt,selection,ref_bool,&reference);

if(PRO_TK_NO_ERROR != err) goto error;

err=ProUdfdataReferenceAdd(udf_data,reference);

if(PRO_TK_NO_ERROR != err) goto error;

err=ProSelectionFree(&selection);

error:

return err;

}

void user_place_udf(){

ProError err_acomps,err,err_csys;

ProAsmcomp *asm_comps;

int n_asm_comps,plate_feat_id,n_csys;

ProFamilyName mdl_name;

char c_mdl_name[PRO_FAMILY_NAME_SIZE],c_name[PRO_FAMILY_NAME_SIZE];

ProMdl asmbly,plate;

ProMdlType mdl_type;

ProUdfdata udf_data;

char prof_name[]="bos";

ProLine w_name,w_string;

ProUdfvardim vardim;

ProFeature f_top_plane;

ProGeomitem g_top_plane;

ProIdTable comp_id_table;

ProAsmcomppath comp_path;

ProGroup udf;

ProCsys *p_csys;

vector<int> csys_ids;

ProMdlCurrentGet(&asmbly); // the main assembly handle

err_acomps = ProUtilCollectAsmcomp((ProAssembly) asmbly,&asm_comps);

ProArraySizeGet ((ProArray)asm_comps, &n_asm_comps);

for (int i=0; i<n_asm_comps; i++)

{

ProAsmcompMdlNameGet (& (asm_comps[i]), &mdl_type, mdl_name);

ProWstringToString(c_mdl_name,mdl_name);

string stl_mdl_name(c_mdl_name);

string::size_type pos = stl_mdl_name.find("PLATE");

if(pos != string::npos){ //found

ProMessageDisplay(MSGFILE, "DEVUSER %0s", c_mdl_name);

ProMessageClear();

ProAsmcompMdlGet((ProAsmcomp *) &(asm_comps[i]), &plate); // our plate

plate_feat_id = asm_comps[i].id;

break;

}

}

err = ProUtilFindFeatureByName((ProSolid)plate,ProStringToWstring(w_string,"TOP"),&f_top_plane);

err = ProUtilFindFeatureGeomitemByName(&f_top_plane,PRO_SURFACE ,ProStringToWstring(w_string,"TOP"),&g_top_plane);

err_csys = ProUtilCollectSolidCsys((ProSolid)asmbly,&p_csys);

ProArraySizeGet ((ProArray)p_csys, &n_csys);

for (int i=0; i<n_csys; i++)

{

ProModelitem mitem;

ProFeature feature;

string::size_type pos;

ProCsysToGeomitem((ProSolid)asmbly,p_csys[i],&mitem);

ProGeomitemFeatureGet(&mitem,&feature);

ProModelitemNameGet(&feature,w_name);

ProWstringToString(c_name,w_name);

string stl_name(c_name);

pos = stl_name.find("ACS");

if(pos != string::npos){ // FOUND

csys_ids.push_back(mitem.id);

}

}

double Yradius = .2, Xradius = .4;

for (unsigned int i = 0; i<csys_ids.size(); i++){

err = ProUdfdataAlloc(&udf_data);

err = ProUdfdataNameSet(udf_data,ProStringToWstring(w_string,prof_name),NULL);

err = ProUdfdataDependencySet(udf_data,PROUDFDEPENDENCY_INDEPENDENT);

err = ProUdfdataScaleSet(udf_data,PROUDFSCALETYPE_SAME_DIMS,0);

err = ProUdfdataDimdisplaySet(udf_data,PROUDFDIMDISP_NORMAL);

add_ref(asmbly,csys_ids[i],PRO_CSYS,PRO_B_TRUE,"coordinate system",udf_data);

add_ref(plate,g_top_plane.id,PRO_SURFACE,PRO_B_FALSE,"top plane",udf_data);

ProUdfvardimAlloc(ProStringToWstring(w_string,"Yradius"),Yradius,PROUDFVARTYPE_DIM,&vardim);

ProUdfdataUdfvardimAdd(udf_data,vardim);

ProUdfvardimAlloc(ProStringToWstring(w_string,"Xradius"),Xradius,PROUDFVARTYPE_DIM,&vardim);

ProUdfdataUdfvardimAdd(udf_data,vardim);

for (int j=0; j<4; j++)

err=ProUdfdataOrientationAdd(udf_data,PROUDFORIENT_NO_FLIP);

comp_id_table[0]=plate_feat_id;

ProAsmcomppathInit( (ProSolid) plate, comp_id_table, 1, &comp_path);

err=ProUdfCreate((ProSolid)plate,udf_data,&comp_path,NULL,0,&udf);

err=ProUdfdataFree(udf_data);

}

ProWindowRepaint(PRO_VALUE_UNUSED);

// clean up

if (PRO_TK_NO_ERROR == err_acomps) ProArrayFree ((ProArray*)&asm_comps);

if (PRO_TK_NO_ERROR == err_csys ) ProArrayFree ((ProArray*)&p_csys);

}