|
Pro/TOOLKIT: Getting Along With UDFs
by Vin Jovanovic, Smith International
Pro/TOOLKIT is an Application Programmers Interface (API) that helps you augment or enhance Pro/ENGINEER’s functionality to meet specific user needs. Specifically, Pro/TOOLKIT lets you customize the standard Pro/ENGINEER user interface, automate processes involving repetitive steps, integrate proprietary and other external applications, and develop customized applications for model creation, design rule verification, and drawing automation.
Unfortunately, Pro/TOOLKIT has gained a (deserved) reputation of being hard to work with. Although PTC’s programmers have added a large number of examples to the Pro/TOOLKIT User Guide, newcomers are likely to feel overwhelmed. What is more troublesome is that some of the functionality is only briefly described in the User Guide and cross-linked references in the API browser.
To address this problem, this article describes a set of API function calls that is not illustrated by examples in the User Guideautomation of a User Defined Feature (UDF). While quite versatile, this commonly used feature in Pro/ENGINEER is not helpful if it involves a large number of repetitive steps. This is where Pro/TOOLKIT can help.
Here you will also find bits and pieces about how to transcend the boundaries of the C programming world that PTC supports for Pro/TOOLKIT. Whenever possible, 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.
The following material assumes some knowledge of Pro/TOOLKIT. If you are completely unfamiliar with this functionality, please read my previous articles here first.
What is a UDF?
Very simply, the need for a user defined feature arises from having to quickly insert one or more existing features into a model. Efficiency dictates that features be created once in some model, stored in a file, and later used for creating similar geometry in another part. This reduces the time for building a new model while still keeping parametric control of the copied feature.
This is exactly what UDFs are 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 inserted into another model, and then store it all in a file with a .gph extension. In this way, you can create a library of specialized groups of features that anyone can reuse. I suspect that this is the way industry deals with repeatedly creating similar geometry.
However, even this can be a source of repetition if a large number of copied groups are needed that still allow for individual parametric adjustments. For example, the manufacturers of computer keyboards face this problem. Here is where we will take off, but first let’s make a simple UDF to illustrate all the steps in the process.
Making a UDF
1. Make an assembly PLATE.ASM, and create a fill pattern table of coordinate systems one inch apart (Fig. 1).
Figure 1. Assembly model.
2. Insert a part that looks like that shown in Figure 2.
Figure 2. Plate part in the plate.asm.
Note a datum plane (TOP) that is one-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. We will use these two references later for our UDF and their location in the models will determine how they need to be handled in Pro/TOOLKIT.
3. Now create a simple protrusion as shown in Figure 3.

Figure 3. Template for UDF feature.
DTM4 and DTM5 (or whatever they are called in your model) are placed at z and y axes of ACS0 coordinate system. The protrusion shown in red is centered at ACS0 and extruded from an ellipse with the dimensions shown. The protrusion is extruded to the TOP plane, which is important because this adds a reference to our UDF.
4. After making the protrusionand this is important as welledit it and name two dimensions as Yradius and Xradius to represent Ry and Rx, respectively, in our UDF.
5. Activate PLATE.PRT and make a UDF with these three features (DTM4, DTM5 and the protrusion). In the UDF menu, choose Var Dim, pick Rx and Ry dimensions, and assign the prompts Xradius and Yradius. For references, type “coordinate system” and “top plane.”
6. Choose UDF dependency “independent,” same dimensions, and display normal.
7. Save UDF as bos.gph and put it into your UDF group directory. Be sure to test whether everything is as expected when you try to place the UDF. Don’t forget to activate PLATE.PRT again.
The Emperor’s New Clothes
Now you are ready to enter the world of Pro/TOOLKIT and expose the emperor’s new clothes (which Pro/TOOLKIT’s documentation fails to do). But first you need a 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 that describes needed information and supplies it to the ProUdfCreate() function that does the job. Your main concern is how to properly add the necessary references to your UDF. The complete code is provided (click here) for easy copying and pasting.
Let’s begin by obtaining proper part and assembly handles and needed references. This will give you a chance to practice some other things and use often-neglected functions in protoolkit/protk_appls/pt_examples/pt_utils. I recommend that you immediately create a library project 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"
You can include this library in any of your projects and use the provided functions. A couple of examples of using utility functions are provided below.
As mentioned, we first obtain the handle to our assembly, internal id for our TOP plane, and ids for coordinate systems where we want to place our UDF.
void user_place_udf(){
// FOR DECLARATIONS OF VARIABLES, SEE APPENDIX
ProMdlCurrentGet(&asmbly); // the main assembly handle
err_acomps = ProUtilCollectAsmcomp((ProAssembly)asmbly,&asm_comps);
…
To 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 containing pointers to assembly components. The expandable array ProArray is now a bit obsolete because of STL’s vector object, and I never use it except in cases where Pro/TOOLKIT functions return 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;
}
}
You can examine all components of the assembly (in this case, only one) by extracting their names and types. Observe how easy it is to find some characters using STL’s object string and its “find” method ( find_mdl_name.find("PLATE") ). C++ allows you to declare any object anywhere. Once we find the object with the 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 use utility functions to find our top plane geomitem structure. This is an aspect of Pro/TOOLKIT that you need to become familiar with. Features contain geometric items and they need to be extracted so that you can add needed references to your UDF later.
Finally, we get all of the coordinate systems for placing the UDF:
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);
}
}
As before, we can use a utility function to collect the handles to all coordinate systems in the assembly model and then extract the ones that start with the characters ACS. (This assumes you used a default name when constructing the first coordinate system.
A bit of gymnastics is necessary when converting to something that we can get a name of. The coordinate systems must be converted to geomitems (or modelitems). Those geomitems belong to the features whose names 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 object you want (in this case, integers). This is why you don’t have to use PTC’s expandable arrays.
We can now start to build 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 work interactively. You will probably recognize above inputs to functions as prompts in Pro/ENGINEER 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 special little utility, which is explained below. For now, just 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, and the prompt and UDF data structure that we are filling with the data.
Now, since we are placing our UDF into the PLATE.PRT, the coordinate systems in the assembly are external to it, but 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.
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 below. Note 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, you can skip the following step. 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);
Since I did not need to flip the arrows for my UDF, the obvious choice is “no flip.” But beware of problems here. Sometimes when a flip is necessary, features do not get placed as expected. (This may be an issue for PTC’s technical support that I leave to you to explore.)
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 previously needed that internal id for our part) and provide it to the ProUdfCreate() function. At the end, you should free the memory by freeing the UDF data structure.
The Trick Part of This Tip
Now I will describe my handy add_ref() function. This is the trickiest part of the exercise, because we need to create a selection object with proper reference to objects. Since wrong pointers or id may lead to a Pro/ENGINEER crash here, this is how to handle 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. A reference is then allocated for this selection with information whether the reference is external to the 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 you can probably use it as is.
If everything went well, your projects should compile now and execute without problems.
Conclusion
We have gone through a simple but illustrative example of inserting UDFs programmatically. Several steps give you a chance to see how to take advantage of some underutilized functionality. In most cases, this 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 because of the difficulties they involve. This article therefore provides a unique chance to learn how to handle external references.
This article also fills the gap that exists in the Pro/TOOLKIT’s documentation with regards to UDF functions.
A Final Note on C++
Even though PTC does not officially support use of Pro/TOOLKIT with C++, I find it much faster for development and use it for all my projects. 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 PTC will provide the abstract C++ layer to make Pro/TOOLKIT programming easier. This is surprisingly easy 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, and so on.
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. This would not only minimize the likelihood of a crash due to incorrect use of pointers, but also make Pro/TOOLKIT programming much easier than it is now. 
Vin Jovanovic is a senior engineer at Smith International, Inc. in Houston, Texas. More Pro/TOOLKIT programming examples can be found at his website www.purplerose.biz. He can be reached by email at fractal97@hotmail.com.
|