Alpha_1 Programmer's Manual


C_shape_edit Programmer's Guide

This document details procedures for using the Model Library, known as libModel, in application programs. In terms of classes, this page documents the public interface to the model_obj class. This is used to incorporate c_shape_edit modeling capabilities into any application program. Before continuing with this document you should see the C_shape_edit Programmer's Overview for a brief description of c_shape_edit, libModel, and the relevant C++ classes. If you are interested in extending or modifying c_shape_edit functionality, see the C_Shape_Edit Developer's Guide.

1.0 The Main Program

There are a number of decisions the application writer must make as to what type of modeling application is being developed. There are a number of options:
Terminal application
A "terminal" application runs the standard c_shape_edit toploop using stdin and stdout. The toploop reads Scl expressions, parses and evaluates them, and prints the results.
Server application
A "server" application creates a network socket and accepts connections from client applications. Each client subsequently communicates with the server using two new sockets.
Graphics application
A "graphics" application is a modeling application that also includes graphical display list windows and presumably a graphical user interface. This is in contrast to using a client process for graphics (e.g., motif3d or tk3d).
A modeling application can be any or all of the above. For example, the "c_shape_edit" application is a "terminal" and a "server" application. The new "shape3d" application is a "terminal" and a "graphics" application. The "fsketch" application also falls into this category. There is currently no example of an application that falls into all three categories, but there is library support for it.

Also, it is not necessary to be a "terminal" application to use the Scl parser and interpreter. There is a C++ interface to these functions in addition to the stdin toploop. This means you can have a graphical interface (i.e., text widgets) to the Scl interpreter.

Once you have decided which type of application to write, the job of the main program is to perform the required initializations.

Interpreter Initialization

Regardless of application type, the function symbol table must be initialized. The constructor functions used in libModel (e.g.: lineThru2Pts) are grouped into packages (see model_pkg_type) in the library. Before any model can be constructed or restored from a file, the library has to create a mapping from symbolic function names (i.e., strings) to function pointers. This is called the function symbol table. The main program does this by creating an array of packages and then calling an initialization function to create the symbol table. This allows individual applications to limit or extend the set of constructions available in the Scl interpreter.

First, create two static variables for the array of packages and its size in the file containing the main program. Using the current "standard" set of packages you would have this:

/****************************************************************
 * TAG( pkgs )
 */
static model_pkg_type *pkgs[] =
{
    &anchor_pkg,
    &io_pkg,
    &feature_pkg,
    &fsketch_pkg,
    &bushing_pkg,
    &arithmetic_pkg,
    &array_pkg,
    &prop_pkg,
    &stock_pkg,
    &xform_pkg,
    &geom_pkg,
    &mfg_pkg,
    &pt_pkg,
    &vec_pkg,
    &ln_pkg,
    &link_pkg,
    &circ_pkg,
    &arc_pkg,
    &number_pkg,
    &curve_pkg,
    &srf_pkg,
    &sym_pkg,
    &prim_pkg,
    &setop_pkg,
    &group_pkg,
    &assembly_pkg,
    &render_pkg,
    &poly_pkg,
    &plane_pkg,
    &shape_op_pkg,
    &nc_help_pkg,
    &dim_pkg
};

/****************************************************************
 * TAG( n_pkgs )
 */
static int n_pkgs = sizeof( pkgs ) / sizeof( model_pkg_type * );
Once these variables are defined, you initialize the symbol table in the main program with a static member function of the model_obj class:
    model_obj::load_pkgs( pkgs, n_pkgs );
The following includes are required:
#include <model/model_pkg.h> 
#include <model/_model.h> 

Terminal Applications

For "terminal" applications that are not "graphics" applications, the setup is very simple. First, a global variable print_concise should be set to use a "better" printer function for the toploop:
    /* Set toploop printer function pointer. */
    print_concise = pr_concise;
This function resides in the library. This requires the following includes:
#include <obj_info.h>
#include <model/pr_concise.h>
After initializing the symbol table and the print_concise variable, the main program calls the static member function "shape_edit" to process stdin:
    model_obj::shape_edit();
    exit( 0 );
This function does not return until it reads an EOF on stdin or the user types quit; to the interpreter.

See c_shape_edit.C for an example of a "terminal" application.

Terminal and Graphics Applications

For graphics applications that also are terminal applications, the setup is a little more complicated because the graphics libraries usually require control of the application event loop (toploop).

In this case there is usually a way to specify a file handler callback to handle Scl input on stdin. Using the Tcl/Tk toolkit for graphics applications, for example, you use:

    /* Stdin file handler feeds c_shape_edit toploop. */
    Tk_CreateFileHandler( 0, TK_READABLE, scl_handle_stdin, NULL );
    model_obj::print_banner();	/* Print initial prompt. */
    Tk_MainLoop();
The callback scl_handle_stdin is in the libTCL library and looks like this:
void
scl_handle_stdin( ClientData, int )
{
    if ( model_obj::read_and_eval() == 0 )
	exit( 0 );
    dlm.flush_dev();
}
The static member function model_obj::read_and_eval reads stdin until a full expression has been parsed, evaluates it, prints the result on stdout, and prints the next prompt. The function returns 0 if an EOF is encountered or the user types quit;.

Before calling the graphics package event handler, the "c_shape_edit" welcome banner and the first prompt is printed by model_obj::print_banner():

Alpha1 C++ Shape Editor
1 shape_edit> 
For more, see the Shape3d section in Writing Alpha_1 Tck/Tk Applications.

Graphics Application Event Hooks

There are three hook functions that can be set in libModel to inform graphics application when certain events occur. These are set by calling the static member functions:
class model_obj ...
{
    ...
  public:
    static void	    	    	register_new_hook( model_hook_fp_type fp );
    static void	    	    	register_update_hook( model_hook_fp_type fp );
    static void	    	    	register_hint_hook( hint_hook_fp_type fp );
    ...
};
The "new hook" is called whenever a new model_obj is created. The "update hook" is called whenever a model_obj is modified (directly or by dependency propagation). The "hint hook" is called by the Scl functions "show", "unshow", "highlight", and "unhighlight." The libTCL library provides an update hook (scl_update_hook) and a hint hook (scl_handle_hint) for Tcl/Tk based applications to use.

Server Applications

The static member function model_obj::set_up_server is used to set up a network socket to accept client connections:
class model_obj ...
{
    ...
  public:
    static void			set_up_server( int serv_num = 0, 
					       boolean_type fg = TRUE );
    ...
};
This uses the libMI model_server_type object called ms to provide the server functionality. As the program exits the following should be called:
    ...
    ms.quit_server();
    exit( 0 );
For "terminal" applications, this is all that is needed. The "shape_edit" toploop will handle all client connections and requests.

For "graphics" applications that also wish to be servers, there is a polling function model_obj::polling_toploop which can be used as a "work procedure" or "idle procedure" with graphics application event toploops. This capability has only be experimented with and there is no current application that uses it.

Examples

There are currently three modeling applications that can be used as examples. See c_shape_edit.C (c_shape_edit main program), shape3d.C (shape3d main program), and fsketch.C (fksetch main program).

2.0 Modeling

The remainder of the public model_obj methods are for creating, modifying, accessing, saving and restoring model_obj dependency graphs.

Scl Interpreter

If the application does not use stdin for Scl input (i.e., it's not a "terminal" application), the Scl interpreter can be used by passing strings to the static member function model_obj::eval_string:
class model_obj ...
{
    ...
  public:
    static string_type      eval_string( string_type expr, 
				         value_obj **return_result = NULL );
    ...
};
The input string must be a complete Scl expression. If return_result is provided, the return value of the Scl interpreter is passed back via this pointer. There is currently no public helper function for printing this result. The return value of the model_obj::eval_string function is NULL if it is successful, and an error message otherwise.

Model Construction and Modification

Model objects are nodes in dependency graphs (directed acyclic graphs). The main components are the prerequisites list, the constructor function, the parameter value (result of applying the function to the arguments), and the dependents list. A model object must have a value (param slot), but the rest is optional depending on how it is constructed and subsequently used.

Constructors

Due to some obscure reasons surrounding exception handling, application programs construct model objects using static member functions instead of actual C++ constructors (the C++ constructors are private). There are some public constructors (the default, and copying constructor) but these are not generally used by application programs. The static "constructors" are as follows:
class model_obj ...
{
    ...
  public:
    static model_obj   	    	*new_model( object_type *param );
    static model_obj   	    	*new_model( const string_type cons_fn, 
					    prereq_obj *args );
    static model_obj   	    	*new_model( const string_type obj_name );
    ...
};
The first creates a model object with a value, but no prerequisites or constructor function. The resulting model object can be used as a prerequisite for future model objects and its value can later be updated (and propagated to its dependents). The model object "owns" the param argument and it does not copy it, so the application must provide the copy if required.

The second "constructor" takes a function and a list of arguments and creates a model object. The function names are the same as used in Scl (they must be all lower case, however). See section below on constructing argument lists. The resulting model object "owns" the argument list.

The last "constructor" loads a named object and makes a model object from it (with a value but no prerequisites). This "constructor" names the object as well, binding it into the Scl symbol table.

All of the these function return NULL if there is an error. An error message can be found in the static data member model_obj::emsg

Here are some examples:

model_obj *
make_model( object_type *o )
{
    /* Somebody else owns "o". */
    return model_obj::new_model( o->cp_obj() );
}

model_obj *
make_pt( real_type x, real_type y )
{
    prereq_obj *args = NULL;

    append_fl_arg( x, args );
    append_fl_arg( y, args );

    model_obj *m = model_obj::new_model( "pt", args );
    if ( !m )
	SCREAM( "Model error: %s\n", model_obj::emsg );
    return m;
}

model_obj *
load_named_obj( const string_type name )
{
    return model_obj::new_model( name );
}
Scl Object Names
The first two constructors create model objects that are not named in the Scl symbol table. To give an object a name, or rename an object use model_obj::rename. To lookup a model object given a name use model_obj::model_from_name.
class model_obj ...
{
    ...
  public:
    static model_obj            *model_from_name( const string_type name );
    void			rename( const string_type new_name );
    ...
};

Modification

Once a model object is constructed there are two member functions for updating the model and propagating changes through the dependency graph:
class model_obj ...
{
    ...
  public:
    boolean_type    	    	update_model( object_type *param );
    boolean_type    	    	update_model( const string_type cons_fn, 
					      prereq_obj *args );
    ...
};
These roughly correspond to the two types of constructors available. Either update method can be called on any model object. It does not matter how it was constructed or what its current state is. The first method updates the model object's value and propagates the change through the graph. It removes any links to the old prerequisites and the old constructor function (if there was one). The second one constructs a new value for the model object from the given constructor function and arguments and then propagates the change. The update method "owns" the argument list (args). Both return FALSE if there is an error and the static model_obj::emsg contains the error message (a text buffer).

Deleting

Model objects can be deleted with the standard C++ destructor. If a model object has prerequisites it is simply unlinked from them (it is no longer a dependent of them). If a model object has dependents, each dependent is "orphaned." This means the dependents are left with a value but no prerequisites or constructor function since they can no longer be recomputed through dependency propagation. The "orphans" can later be updated with a new constructor function and prerequisites list.

Argument List Construction

Argument lists (class prereq_obj) are used to construct and modify model objects. This is essentially what the Scl parser does as it turns Scl text into model objects. There are some helper functions for doing this in the library. Arguments can in integers, real numbers, strings, boolean values, or model objects. There is a function to append or prepend each type to an existing argument list:
extern void
append_int_arg( int i, prereq_obj *&arglist );

extern void
append_fl_arg( real_type fl, prereq_obj *&arglist );

extern void
append_bool_arg( boolean_type b, prereq_obj *&arglist );

extern void
append_str_arg( string_type str, prereq_obj *&arglist );

extern void
append_model_arg( model_obj *obj, prereq_obj *&arglist );

extern void
prepend_int_arg( int i, prereq_obj *&arglist );

extern void
prepend_fl_arg( real_type fl, prereq_obj *&arglist );

extern void
prepend_bool_arg( boolean_type b, prereq_obj *&arglist );

extern void
prepend_str_arg( string_type str, prereq_obj *&arglist );

extern void
prepend_model_arg( model_obj *obj, prereq_obj *&arglist );
Start with a NULL list and then use the above functions:
    prereq_obj *args = NULL;
    append_int_arg( 1, args );
    append_fl_arg( 3.14159, args );
    ...
Array Arguments
Numerous Scl functions take arbitrary arrays (lists) for arguments (i.e., "dumpA1File", "profile", "outlineCrv", "objTransform", etc.). To construct these from the C++ programming interface use the Scl constructor function name "array." This is equivalent to {}'s in Scl code. For example, suppose you had some list of model objects that was suitable for an "outlineCrv" construction. The construction is a two step process, first make an "array" as a separate model object, then call "outlineCrv" with the array and the "open" flag as the two arguments:
    model_obj  *curr_model;
    prereq_obj *args = NULL;

    /* Traverse some list of model objects. */
    while ( ... )
	append_model_arg( curr_model, args );

    model_obj *array = model_obj::new_model( "array", args );

    args = NULL;	/* Now make the "outlineCrv" argument list. */
    append_model_arg( array, args );
    append_bool_arg( open_flag, args );

    model_obj::new_model( "outlinecrv", args );
    ...
If possible, it is recommended that constructions be done through the Scl parser and interpreter as this avoids the somewhat error prone process of constructing argument lists in C++. The trade-off is that the interpreter (model_obj::eval_string) requires the use of names whereas direct construction make "anonymous" unnamed objects.

Save and Restore

There are a number of methods for saving and restoring model object dependency graphs. These are wrappers on the basic dp_obj and ld_obj polymorphic methods. The wrappers are necessary to deal with the fact the these are graphs with shared structure and we want to provide operations on various subgraphs. Here are the method declarations:
class model_obj ...
{
    ...
  public:
    void    	    	    	save_model( string_type fname );
    static void  	    	save_model_list( prereq_obj *models,
						 a1_stream_type *a1_stream );
    static void  	    	save_model_list( model_list_obj models,
						 a1_stream_type *a1_stream );
    static void  	    	save_model_list( prereq_obj *models,
						 const cstring_type fname );
    static void  	    	save_model_list( model_list_obj models,
						 const cstring_type fname );
    static model_list_obj	*restore_model( a1_stream_type *a1_stream );
    static model_list_obj	*restore_model( const cstring_type fname );
    static model_list_obj	*restore_model( FILE *fp );
    ...
};

Flags

There are a number of static boolean flags that control the interpreter and provide some debugging information. These can be set directly (they are public) or there are command and/or side-effect functions in Scl to set most of them.
class model_obj ...
{
    ...
  public:
    static boolean_type	    	demand_eval, trace_changes, propagate_changes,
    	    	    	    	parse_echo, interp_echo, type_matching;
    ...
};
The descriptions are:
demand_eval (default: off)
If on, model objects are only recomputed when necessary.
trace_changes (default: off)
If on, print debugging information about dependency propagation.
propagate_changes (default: on)
If off, do not propagate dependencies.
parse_echo (default: off)
If on, echo the output of the parser.
interp_echo (default: on)
If off, do not print and output from the Scl interpreter.
type_matching (default: on)
If off, allow objects to change type even if they have dependents.

Alpha_1 Programmer's Manual Home Page
Alpha_1 Programmer's Manual. Version 95.06.
Copyright © 1995, University of Utah
alpha1@gr.utah.edu