User-Defined Control

You can use the User-Defined control to perform operations that you can't do with the controls and shapes defined by the Graphical Cell Compiler.

The User-Defined control actually fills the gap for two types of operations:

Using the User-Defined control requires a knowledge of programing in AEL. You supply an AEL function that accepts whatever parameters you want to supply the shape with and returns the (x,y) point data for the created/modified shapes. You can create almost anything. If the AEL code can be written to do it, a PAM can be created to insert it.

The general flow for the setup of a User-Defined control is the same as that for other controls:

The function field of the User-Defined control is basically just like any other expression field in any of the other controls except that there are a few more restrictions on it. In the other controls the expressions must evaluate to a simple numeric (integer or real) or boolean type. In this case, the results of the expression generally must be (x,y) point data for a shape, or possibly the data for a list of shapes.

Parameters

The parameters to the user-written function can be anything allowed by AEL syntax: constants, variables, expressions, etc. As with the other controls, any unrecognized variable is taken as a component parameter to the macro.

In addition, a number of pre-defined variables (like the ones available in the Polar control) can be passed to the user-written function:

_cline_data

This is 2-element array of (x,y) values (as in [[x1, y1], [x2, y2]]) which define the construction line (if included, that is selected, when the control was defined) for this control. The position and slope of the construction line can then be used to control how the function operates on the shape, much in the same way that the construction line specifies the direction of movement in the Stretch control or the direction of copy in the Repeat control.

For example, using _cline_data to compute the slope of the construction line could look something like:

decl x1, y1, x2, y2;

decl slope;

x1 = _cline_data[0,0];

y1 = _cline_data[0,1];

x2 = _cline_data[1,0];

y2 = _cline_data[1,1];

if ((x2 - x1) == 0.0)

      slope = 999999.      \* vertical line, infinite slope */

else

      slope = (y2 - y1) / (x2 - x1);

_shape_type

The type of shape being operated on. One of the following pre-defined AEL constants:

This is probably most useful as an error check to make sure the selected shape is appropriate for the user-written function. For example, it may well be an error case to pass a text element (which only has one (x,y) point) to a function which operates on polygons (which must have at least three (x,y) points).

An error check might look something like:

if (_shape_type != PAM_POLYGON_TYPE) {
      de_error_dialog("Shape is not a polygon");
      return(NULL);
      }

_shape_data

The current (x,y) data points for the shape being operated on. This data can be modified from the original shape if it has been operated on by previous controls (such as, Stretch).

You would pass the _shape_data parameter to the user-written function if your function was designed to modify a graphic shape. For example, let's say the function was written to rotate a shape about a user-defined point rather than about the origin (0,0) as the Rotate/Move/Mirror control does. Such a function would take the current _shape_data, perform the rotation operation on it controlled by some number of additional parameters, and return the modified data points.

On the other hand, if the function was written to create a new graphic shape based on the other parameters passed to it (an ellipse for example) then it would not need the _shape_data parameter at all. Or, it might only use the _shape_data parameter to get an initial (x,y) location for the creation of the new shape but ignore the rest of the data points.

If the _shape_data parameter is passed and how the data points contained in it are used is totally up to the requirements of the user-written function.

The shape_data parameter is a two-dimensional array of values. The first dimension is the data point number (from 0 to the number of points - 1) and the second dimension indexes the x and y value (0 for x, 1 for y). The _array_upperBound() function is used to access the number of data points in the array. So, a simple iterative loop to add a fixed (x,y) offset to each point of a passed in shape might look something like this:

decl i;
 for (i=0; i<=array_upperBound(_shape_data, 1); i++) {

       _shape_data[i,0] += x_offset;
        _shape_data[i,1] += y_offset;
        }

 return(_shape_data);            \* return the modified data

_shape_init

The initial (x,y) data points for the shape as it originally existed in the source layout. This structure is never modified by the PAM so it is always possible to get the original points if needed.

These points are important because the other controls that use a construction line as a reference (Stretch, Repeat, Polar) compare the ORIGINAL shape data with the construction line to determine direction, orientation, intersection, etc. That way the specified action is not changed because a shape was moved on or off a construction line by other controls.

If you are creating a control that you want to work in the same way as the built-in controls then you will want to use the _shape_init point data for any tests with respect to the reference construction line.

On the other hand, you may specifically want a control which will act on a shape based on previous actions performed on it. In that case you would want to use the _shape_data (described above) for all point data tests with respect to the reference construction line.

_shape_list

The current list of sets of (x,y) data points (the result of a Repeat or Polar control) for the shape being operated on.

Passing the _shape_list parameter to the user-written function would happen when the purpose of the function is to modify a list of shapes that have been created by another control. For example, one may want to take the shapes generated by a Repeat control and rotate each one by some amount dependent on which copy it is.

The shape_list parameter is a list of two-dimensional arrays (like _shape_data). The _listlen() function will provide the length of the list (number of shapes) and the nth() function will access the individual shapes (zero relative). So, a simple loop to access all the shapes passed to a function in the _shape_list parameter might look something like:

decl n;

      decl i;
      decl shape;
      decl shapes;              \* list of modified shapes to return */
      shapes = NULL;
n = listlen(_shape_list);

for(i=0; i&lt;n; i++) {
      shape = nth(i, _shape_list);
      \* operate on array of (x,y) points in shape */
      shapes = append(shapes, list(shape));
}
      return(shapes);

_shape_layer

The ID number for the layer the shape is on.

Knowing the Layer ID for a shape allows the AEL function to perform layer-specific operation on the shapes. It may also be useful if a layer mapping function is to be performed, taking the initial layer, mapping it to a different layer, and returning that value to be use when the shape is inserted (see Return Value).

There is one important point to be aware of in using the above shape variables. While AEL syntax does allow for any function parameter to be passed by-reference (using the "&name" syntax) doing so for any of the above variables will NOT allow you to modify the actual shape's values. That is because the above variables are already copies of the shape's data and even if they were modified the contents are NOT copied back to the original shape's values.

Function Call

The function call as defined in the User-Defined dialog is really just any AEL expression that generates an array of (x,y) points (for the case of a simple shape) or a list of shapes (for the list case). In most cases this will be a function call since it will require multiple lines of AEL code to perform the desired operation. But there are cases where the expression entered could be simpler:

NULL

This would have the effect of deleting the shape or list of shapes. This could be used as part of a conditional expression (using the ?: syntax) to optionally delete a shape, for example:

(delete == TRUE) ? NULL : _shape_data
Which is functionally equivalent to a function that did:
      if (delete == TRUE)
                return(NULL);
else
      return(_shape_data);

This would test for the parameter "delete" being set to TRUE. If it is then a NULL would be returned essentially deleting the shape. If "delete" is FALSE then it would return the un-modified shape data, basically doing no operation to the data.

Constant

Using a constant would have the same effect as defining the shape in the source layout and not performing any operation on it. This would define it's location and shape programatically rather than graphically. So defining a fixed-sized circle this way would have something like the following in the dialog field:

[ [0.0,0.0], [100.0, 0.0] ]

Which would define a circle of radius 100 centered at the origin.

Variables

This would be similar to the use of a constant, but would allow the values to be specified by the user as component parameters. So a user-defined circle might look like:

[ [center_x, center_y], [center_x + radius, center_y] ]

where the user would supply the values for center (x,y) and radius at component insertion time.

Function

The most general purpose usage is to call a function. A simple example might look like:

modify_shape(_shape_data, length, width)

Where modify_shape is the user-written function. It receives three arguments: the current (x,y) array of data points for the shape, and a length and width value which are component parameters specified by the user at component insertion time.

Return Value

The type of value returned by the function is specified in the User-Defined dialog by the "Function returns:" radio buttons. This informs the compiler what is being returned and what to do with it. The four choices are:

There is one more very important issue to be aware of when deciding what the return type is. If the user-defined function is making simple modifications to the (x,y) data points (as in a stretch or similar action) then the resulting shape would be returned as type "Shape". But if the function is adding or deleting (x,y) data points (as the Polar controller adds points to a path) then the resulting shape must be returned as a "Replace List" or "Append List".

The reason for this is that the Initial and Current shape data arrays must always be of the same size (same number of (x,y) points). Several of the controls rely on this fact in processing the shape. For example, in the Stretch control, it is comparing the Initial array with the construction line to decide which points will be modified. If it decides that the shape's fourth point is to be modified then there had better be a fourth point in the Current structure to apply the modification to.

For those controls using both the Initial and Current arrays their size is checked during initialization and an error is displayed if they aren't equal.

This also helps demonstrate why certain controls (like Stretch) should appear before other controls (like Repeat or Polar). For more information see Chapter 3, Control Precedence.

So if the user-defined function is modifying the (x,y) data points of a shape you should set the return type to "Shape". But if the function is adding or deleting (x,y) data points then you should set the return type to either "Replace List" or "Append List".

Function Implementation

As already stated the user must supply the actual AEL function called by the User-Defined control. The function my use any parameters the user wants including any of the pre-defined ones listed above. the output must be either an (x,y) array of shape data points, a list of shapes as described above, or a layer ID. What's in between is totally up to you.

If the function does not exist in the system at compile time then a warning message will be issued by the compiler. This is fine as long as the function exists by the time the PAM is executed. If it doesn't exist then executing the macro will generate an AEL error.

During development and testing of the function the best way to load it into the system is by executing a load() command from the Command Line dialog (accessed from the main window with the "Options/Command Line" menu pick). This allows you to load the AEL file to test the function, then make changes to it and re-load it without the need to exit ADS. See the AEL manual for details on the load() command.

If you want the AEL function to be visible to the compiler to eliminate the warning messages, it must be loaded into the system before the compile is done. The function does not need to be complete at this time. In fact it can be a NULL function, that is, a function definition with no actual code. Simply defining the function's name will be enough to prevent the PAM compile warning. Of course the function must be fully implemented when the PAM is inserted in order to get the desired results.

After the AEL function is completed and tested, you should make sure it's loaded each time ADS is started. You can use the "USER_AEL" directive in the de_sim.cfg file (see the AEL Guide for details). Basically, the procedure is:

First, edit your $HOME/hpeesof/config/de_sim.cfg file and add a line like this:

USER_AEL=$HOME/user.ael

which will load the file user.ael from your home directory. That file should be a list of load() commands for all the AEL function you have written that you wish to load as part of the system. It might look something like:

load("/users/brett/my_models/spiral.ael");
load("/users/brett/my_models/couple.ael");
load("/users/brett/my_models/square.ael");
fputs(stderr, "End Loading user.ael");

Obviously the path and file names can be anything you want such that the organization of your PAM support code makes sense.

The fputs() line is simply a way to output a message to the ADS startup window as a sort of verification that the file did run and load all the support functions. It can easily be commented out once satisfied that everything is working the way you want.

Now each time ADS is started, the user.ael file will be executed which will load all the other AEL files which define the various user-written functions needed.

A Simple Example

This section provides a simple example of how to use a User-Defined control to perform an action that would be very hard to do with the default controls. While the example may not be very realistic, it does demonstrate how easy it is to extend the Graphical Cell Compiler with user-written AEL code.

For this example we assume that you want to make an NxM grid of rectangles. But it's not just a simple grid, you want it to look like a checker-board (every other shape is missing).

Starting with the source layout, add a simple rectangle with two construction lines for use by the various controls:

Next add two Stretch controls to set the length and width of the rectangle:

Control: Stretch
  Direction: Both
  Length: length
  Offset: 50.0 mil


Control: Stretch
  Direction: Positive
  Length: width
  Offset: 50.0 mil

Then add a Repeat control (selecting the horizontal construction line) to build the grid:

Control: Repeat
  Direction: Both
  Parallel
Number: x_num
Distance: length
  Perpendicular
Number: y_num
Distance: width

If this model is compiled and inserted you'll get an x_num by y_num grid of rectangles (size length by width) with no space between the individual shapes. If the fill-type is set to filled for that layer it will look like one big rectangle.

Now add a User-Defined control to call an AEL function which will do the work of removing the shapes needed to create the final structure.

Control: User-Defined
  Returns: List Replace
  Function: remove_some(x_num, y_num, _shape_list)

The purpose of the user-written function remove_some() is to take the list of shapes generated by the Repeat control, iterate over it removing the ones no longer needed, and return the modified list. We pass the function three parameters: the X and Y dimension using the same names as in the Repeat control, and the pre-defined variable _shape_list which is the generates list of shapes for the rectangle.

An example of the code for remove_some() is:

defun remove_some(x, y, shapes)
{
decl i, j;
decl odd;
decl keep;
keep = list();      /* initialize the return list      */

odd = 1;      /* removing odd/even elements                        */

for (i=0; i&lt;x; i++) {      /* iterate over X axis      */

      for (j=0; j&lt;y; j++)      /* iterate over Y axis      */

            if ((j%2) != odd)

                   keep = append(keep, list(nth(j*x+i, shapes)));

      odd = odd^1;      /* toggle for the next column      */
      }

return(keep);      /* return reduced list      */

}

The Repeat controller iterates over X (Parallel) and then Y (Perpendicular) which is a Column Major Order. Therefore, we need to process the generated list the same way in order to know which (x,y) rectangle is being considered for removal (that is, NOT to be copied to the output list).

The final artwork created by the PAM as defined above would look something like:

Of course the parameters defined give full control over the size of the rectangles and the number in each axis.

 

Privacy Statement  | Terms of Use  | Legal | Contact Us  | © Agilent 2000-2008 

Contents
Additional Resources