Tutorial:ACIS Tutorials (History Streams)
From DocR21
| Start | Fundamentals | Creating Applications | Topology | Math Classes | Geometry | Tolerances | Options | Memory Management | Exception Handling | History | Attributes | File Translation |
History and Roll
Contents |
This tutorial provides an introduction to the ACIS history mechanism. ACIS provides some rather complex mechanisms for managing the history of a model; however, in this tutorial we shall concentrate on the basic concepts. In particular we shall focus on a single history stream and we will not use the part manager. If after reading this tutorial you desire to learn more about these more advanced topics, refer to History and Roll and Part Management.
An Overview of the History Mechanism
As we have mentioned in previous tutorials, all operations that change the ACIS model must be within an API_BEGIN/END block. (By this we mean all model changes must be within a block of code surrounded by a pair of macros in the API_BEGIN/API_END family.) As we mentioned before these blocks can be nested. The outermost API_BEGIN statement denotes the beginning of a bulletin board and the outermost API_END statement denotes the end of the bulletin board. Every creation, modification, or deletion of an ENTITY during the operation will be recorded on a bulletin. Thus, a bulletin board contains a list of bulletins, each containing a record of the creation, modification, or deletion of one ENTITY. Because one operation (from an end user's perspective) may consist of multiple API_BEGIN/END blocks (perhaps as the result of multiple calls to ACIS API functions), bulletin boards may be grouped together into a single delta state representing the entire operation. A delta state is simply an ordered list of bulletin boards. A modeling session may consist of many operations, so there is a need for a higher level concept that may contain many delta states. This is the role of the history stream. A history stream contains an n-ary tree structure of delta states. If the modeling session contains no rolled back operations, the tree structure will be linear, essentially a linked list. If the modeling session had some rolled back operations and these operations were not pruned from the tree structure, the tree structure will contain branches. In other words, every state of the ACIS model during a modeling session can be maintained in the history stream. Of course a modeling session could be quite complicated, requiring thousands or millions of ENTITY changes, which could result in a very large history stream, which would require a very large amount of memory. Therefore, ACIS provides mechanisms to reduce the number of delta states and bulletin boards retained in the model.
The Data Structure
Bulletins
The lowest level object used in the history mechanism is the BULLETIN. (Although BULLETIN is in upper case, it is not derived from ENTITY. The same holds true for BULLETIN_BOARDs, DELTA_STATEs, and HISTORY_STREAMs.) A BULLETIN contains a record of an ENTITY being created, changed, or deleted. The BULLETIN contains a pointer to a copy of the ENTITY before the operation and a copy of the ENTITY after the operation. If the pointer to the "old" ENTITY (that is, the copy of the ENTITY before the operation) is NULL, then the BULLETIN is recording the creation of the ENTITY. The ENTITY did not exist before the operation. If the pointer to the "new" ENTITY (that is, the copy of the ENTITY after the operation) is NULL, then the BULLETIN is recording the deletion of the ENTITY. The ENTITY did not exist after the operation. If both pointers are non-NULL, implying the ENTITY existed before and after the operation, then the BULLETIN is recording a change to the ENTITY.
A BULLETIN also contains pointers to the "next" and "previous" BULLETINs in a doubly linked list, and a pointer to the BULLETIN_BOARD that contains the doubly linked list of BULLETINs; that is, the "owner" of the BULLETIN.
Bulletin Boards
The next lowest level object used in the history mechanism is the BULLETIN_BOARD. A BULLETIN_BOARD contains a doubly linked list of BULLETINs. Specifically, the BULLETIN_BOARD contains a pointer to the first and last BULLETINs in the BULLETIN_BOARD.
A BULLETIN_BOARD is the lowest level construct that can be independently rolled. If an exception occurs inside of an API_BEGIN/END block or an API_TRIAL_BEGIN/END block, the BULLETIN_BOARD associated with that operation will be rolled back. The BULLETIN_BOARD associated with an API_NOP_BEGIN/END block will always be rolled back.
BULLETIN_BOARDs are maintained in a singly linked list; therefore, each BULLETIN_BOARD contains a "next" pointer. In addition, each BULLETIN_BOARD contains a pointer to the DELTA_STATE that contains the singly linked list of BULLETIN_BOARDs; that is, the "owner" of the BULLETIN_BOARD.
Delta States
Whereas a BULLETIN_BOARD is the lowest level construct that can be independently rolled, a DELTA_STATE is the construct with which the end user would interact to roll the model backward and forward, to perform "undo" and "redo" operations on the model. A DELTA_STATE contains one or more BULLETIN_BOARDs and typically represents an entire operation from the end user's perspective. As its name implies, a DELTA_STATE records the difference between two states, not a state.
A DELTA_STATE contains a pointer to the first BULLETIN_BOARD in a singly linked list of BULLETIN_BOARDs. DELTA_STATEs are maintained in a tree structure. Each DELTA_STATE contains "next", "previous", and "partner" pointers. The "next" and "previous" pointers often seem reversed to new ACIS developers. "next" and "previous" are described with respect to rollback. The "partner" pointer is a "sibling" pointer in the n-ary tree structure. "partner" pointers allows branches in the history. If there were no "partner" pointers, the history would be a doubly linked list of DELTA_STATEs. Each DELTA_STATE also contains a pointer to the HISTORY_STREAM that contains the tree of DELTA_STATEs; that is, the "owner" of the DELTA_STATE. In addition, each DELTA_STATE contains a pointer to a character string that can be used to uniquely identify the DELTA_STATE.
BULLETIN_BOARDs are associated with API_BEGIN/END blocks. DELTA_STATEs are associated with calls to api_note_state. Whenever api_note_state is called, all the BULLETIN_BOARDs created since the previous call to api_note_state are stored in a DELTA_STATE. In other words, a DELTA_STATE represents the model changes between calls to api_note_state.
An application can roll a DELTA_STATE backward or forward. (In fact, below we will describe how an application can roll from one state to any another state.) If a DELTA_STATE has been rolled an odd number of times, its next roll will roll it forward. If a DELTA_STATE has been rolled an even number of times, its next roll will roll it backward. For example, if a DELTA_STATE has never been rolled, it will roll backward. A DELTA_STATE contains a flag that describes whether its next roll will be backward or forward.
Some necessary terminology:
During a modeling session there is often an "open" DELTA_STATE. This is the "current" DELTA_STATE. As BULLETIN_BOARDs are created they are added to the "current" DELTA_STATE. When api_note_state is called the "current" DELTA_STATE is "closed" and another DELTA_STATE is "opened." The newly opened DELTA_STATE becomes the "current" DELTA_STATE and the most recently closed DELTA_STATE becomes the "active" DELTA_STATE. The "active" DELTA_STATE is the DELTA_STATE that points to the current state of the model and records a backward change. Thus, when a DELTA_STATE is rolled the DELTA_STATE that was constructed immediately before the most recently rolled DELTA_STATE becomes the "active" DELTA_STATE. (For example, if there were three DELTA_STATEs and the third DELTA_STATE were rolled back, the second DELTA_STATE would become the "active" DELTA_STATE. If the third DELTA_STATE were rolled forward, it would again become the "active" DELTA_STATE.) The "current" DELTA_STATE is added directly after the "active" DELTA_STATE. In addition to the "current" and "active" DELTA_STATEs, the initial DELTA_STATE in the model is called the "root" DELTA_STATE. To permit the end user to move between specific states, the application must remember the various DELTA_STATEs.
History Streams
The HISTORY_STREAM is the highest level construct in the core ACIS history mechanism. A HISTORY_STREAM provides a pointer to the "current", "active", and "root" DELTA_STATEs.
In this tutorial we shall use a single HISTORY_STREAM for the construction of our models. Such a history stream is sufficient for most applications. ACIS does allow applications to use multiple history streams, but that is beyond the scope of this tutorial. ACIS provides an API function, api_get_default_history, which returns a pointer to the default history stream. If an application uses multiple history streams it is up to the application to remember the various history streams. (The HISTORY_STREAM_LIST, HISTORY_MANAGER, and StreamFinder classes may be of interest if you choose to use multiple history streams.)
Using the History Mechanism
There are four things an ACIS application developer should understand how to do with the ACIS history mechanism.
- Search a history stream to find specific BULLETINs or ENTITYs.
- Control the granularity of BULLETIN_BOARDs and DELTA_STATEs for exception handling and rollback.
- Roll to a specific state.
- Remove unwanted DELTA_STATEs from a history stream.
The ACIS history mechanism has many more capabilities than these, but these are the ones used most frequently by applications.
Searching a History Stream
With a single history stream, you can obtain the HISTORY_STREAM using api_get_default_history.
HISTORY_STREAM * hs = NULL; outcome result = api_get_default_history(hs);
The "root," "current," and "active" DELTA_STATEs can be obtained from a HISTORY_STREAM using member functions.
DELTA_STATE * root_ds = hs->get_root_ds( ); DELTA_STATE * current_ds = hs->get_current_ds( ); DELTA_STATE * active_ds = hs->get_active_ds( );
DELTA_STATEs within a single HISTORY_STREAM can be traversed using their "previous," "next," and "partner" pointers.
DELTA_STATE * prev_ds = ds->prev( ); DELTA_STATE * next_ds = ds->next( ); DELTA_STATE * partner_ds = ds->partner( );
The BULLETIN_BOARD pointer of a DELTA_STATE can be obtained using the member function: DELTA_STATE::bb.
BULLETIN_BOARD * bb = ds->bb( );
The currently open BULLETIN_BOARD can be obtained by the global function, current_bb.
BULLETIN_BOARD * curr_bb = current_bb( );
BULLETIN_BOARDs within a DELTA_STATE can be traversed using their "next" pointers.
BULLETIN_BOARD * next_bb = bb->next( );
The first or last BULLETIN pointer of a BULLETIN_BOARD can be obtained using the member functions: BULLETIN_BOARD::start_bulletin or BULLETIN_BOARD::end_bulletin.
BULLETIN * start_b = bb->start_bulletin( ); BULLETIN * end_b = bb->end_bulletin( );
BULLETINs within a BULLETIN_BOARD can be traversed using their "previous" and "next" pointers.
BULLETIN * prev_b = b->previous( ); BULLETIN * next_b = b->next( );
Three BULLETIN member functions are very useful for examining bulletins: new_entity_ptr, old_entity_pointer and type.
ENTITY * new_ent = b->new_entity_ptr(); ENTITY * old_ent = b->old_entity_ptr(); BULLETIN_TYPE b_type = b->type();
A BULLETIN_TYPE is an enumeration with four values: NO_BULLETIN, CREATE_BULLETIN, CHANGE_BULLETIN, and DELETE_BULLETIN. You should never see a NO_BULLETIN when traversing a bulletin board. In a CREATE_BULLETIN the old entity will always be NULL. In a DELETE_BULLETIN the new entity will always be NULL.
Every ENTITY also contains a pointer to its most recent BULLETIN. This may be obtained using the member function, rollback.
BULLETIN * b = ent->rollback( );
The first example program demonstrates how the above functions can be combined to interrogate a linear history stream to investigate what happened during a series of operations. Why would an application need to examine a history stream? There are many reasons. One of the most common is that an application will create a data structure that is connected to or parallels the ACIS data structure and the application examines the changes to the ACIS model after each operation to see what changed in the ACIS model. An application may also want to pause mid-operation to obtain information about changes to the model that are contained within a specific bulletin board. By controlling the granularity of bulletin boards one can make such interrogations easier.
Controlling the Granularity of Bulletin Boards and Delta States
If there are nested API_BEGIN/END blocks, bulletin boards are constructed for the outer most API_BEGIN/END block. If an API_BEGIN/END block is not nested, a bulletin board is constructed just for that block of code. In addition to your API_BEGIN/END blocks, each ACIS API function that changes the model will construct a single bulletin board. If you want several ACIS API functions or several of your API_BEGIN/END blocks to be in the same bulletin board, you can surround them with an API_BEGIN/END block. (This applies to API_TRIAL_BEGIN/END and API_NOP_BEGIN/END blocks, too.)
ACIS contains an option, compress_bb, that controls the compression (or merging) of bulletin boards on a delta state. If compress_bb is FALSE, the bulletin boards are not merged. If compress_bb is TRUE, a bulletin board will be merged with the previous bulletin board on the same delta state, if there is one and if the outcome of the bulletin board is successful. This occurs when the second bulletin board is closed on a delta state. If the outcome of any bulletin board is not successful, the model will be automatically rolled back to the state it was in at the API_BEGIN statement.
What is the difference between (a) creating a single bulletin board within a delta state and (b) creating multiple bulletin boards within a delta state that automatically get merged into one bulletin board? The difference is what happens when an exception occurs. If an operation is successful, there is no difference. If a failure occurs, only the unsuccessful bulletin boards is rolled back. This may allow an end user to get partial results from an operation, or it may allow an application to attempt multiple algorithms to complete an operation.
As we stated above a delta state is created when you call api_note_state – if model changes have occurred. If no model changes have occurred, api_note_state will return a pointer to an empty delta state. So, the granularity of delta states depends on the placement of calls to api_note_state. The granularity of delta states dictates the granularity of rollback functionality exposed to the end user, because programmatic rollback occurs at the delta state level.
Some compression and merging can occur after the fact. Multiple bulletin boards within a delta state can be merged after the delta states have been closed by calling DELTA_STATE::compress. Multiple delta states can be merged by calling api_merge_states. Merging delta states can reduce the size of a model, but it is also time consuming and restricts the granularity of rollback.
Rolling to a Specific State
A delta state captures the changes to the ACIS model that occurred between two states. Let's call them Staten-1 and Staten. If the model is at Staten it can be change to the Staten-1 by rolling back the delta state. If the model is at Staten-1 it can be change to the Staten by rolling the delta state forward. Rolling a delta state backward or forward is achieved by calling api_change_state(DELTA_STATE*). The direction of rolling depends on the current state of the model (in particular, the state of the delta state.)
An application can roll between states separated by multiple delta states by calling api_change_state(DELTA_STATE*) repeatedly, using the appropriate delta states. Alternatively, an application can call api_change_to_state(HISTORY_STREAM*,DELTA_STATE*) to change directly to the state of the model when api_note_state was called for the given DELTA_STATE. Another alternative is for the application to call api_roll_n_states(HISTORY_STREAM*, int n,...), which will roll the model forward or backward 'n' states, depending on whether 'n' is positive or negative.
We should mention that typically one rolls among the various states of the current session; however, it is possible to save an ACIS model including its history, so one could restore a model and roll it back to a state in a previous session. To provide this functionality the application should save the model and its history with api_save_entity_list_with_history and restore it with api_restore_entity_list_with_history, instead of saving the model with api_save_entity_list and restoring it with api_restore_entity_list.
Removing Unwanted Delta States
If a model has been rolled back and a set of delta states is no longer needed, the unneeded delta states can be deleted. Two relatively high level API functions provide functionality to accomplish this. api_prune_following prunes away all the delta states after the active delta state. (Remember the active delta state is the one before the one most recently rolled.) api_prune_history can be used to prune away specific portions of the history stream before or after the active delta state. Thus, in addition to removing future states that are no longer needed, an application can reduce the number of past states that are maintained in the history stream. Alternatively, the number of past states maintained by the history stream can be limited by calling HISTORY_STREAM::set_max_states_to_keep, which will cause one previous delta state to be pruned away as each new delta state is added to the history stream.
For the creation of branched history streams you should be familiar with the delete_forward_states option. This option controls whether or not branches are created. When the delete_forward_states option is TRUE, the default, delta states forward of the active delta state are deleted when a new delta state is added to the history stream. If you should choose to use branched history streams, you should be aware of HISTORY_STREAM::prune_inactive_branch(DELTA_STATE*), which will prune away all of the partners of the specified DELTA_STATE.
Two Programming Notes
One thing you should be aware of is how bulletins are created. Bulletins are created by a call to ENTITY::backup. This member function causes a bulletin to be constructed and added to the current bulletin board, if one is already open. As you recall, bulletin boards are constructed and opened by API_BEGIN, API_NOP_BEGIN, and API_TRIAL_BEGIN. If you attempt to construct a bulletin and add it to a non-existent bulletin board, an error will occur. Attempting to modify an ENTITY without first having called backup will cause significant problems, although not necessarily an immediate error. Calling backup more than once for an ENTITY does not cause a problem. All ACIS API functions and direct interface functions will call ENTITY::backup for you. If you create new attribute or entity classes, you will need to call backup in your member data setting functions. (This is described in the next tutorial, Tutorials (Attributes).)
You should also be aware that some query functions may cause changes to the model, so they must call backup, so they must be called within API_BEGIN/END blocks. For instance, obtaining the bounding box for a topological entity may cause bounding boxes to be calculated and cached on entities. Many queried quantities are now cached on entities (to enhance performance) which can lead to unanticipated model changes. For this reason new developers may want all direct interface calls that operate on ENTITYs to be within API_BEGIN/END blocks.
A Question
We have said that API_BEGIN/END blocks, API_TRIAL_BEGIN/END blocks, and API_NOP_BEGIN/END blocks create bulletin boards... so, how can the ACIS API functions mentioned above (such as, api_change_state) manipulate the history stream without generating unwanted bulletin boards?
. . .
In Tutorials (Exception Handling) we mentioned that there are four sets of macros in the API_BEGIN / API_END macro family. We did not describe the API_SYS_BEGIN and API_SYS_END macros because they are not for use by application developers. These macros do not generate bulletin boards; therefore, they are used to manipulate the history-related data structures.
C++ Example
The Program
We want to create a simple model using multiple operations so we can explore what happens within the history stream as the model is constructed. For the model we have chosen to use multiple copies of the sheet body created in the Geometry Tutorial. The construction of each sheet body will be considered to be a single operation. After the construction of each sheet body we call api_note_state thereby creating a delta state containing the bulletins and bulletin boards for the construction of that sheet body. The construction of each sheet body is performed by my_create_sheet_body. The construction of the entire model is performed by my_generate_model. For this example we construct three sheet bodies. The image below depicts the model generated in this example.
After generating the model we examine the contents of the history stream using my_debug_history_stream. my_debug_history_stream traverses through the delta states in the history stream, calling my_debug_delta_state for each one. The amount of debug information produced by my_debug_delta_state is controlled by the debug_level argument.
- If debug_level is set to 0, we do not examine the bulletins.
- If debug_level is set to 1, we examine the type of each bulletin and each ENTITY on each bulletin. We record creations, changes, and deletions of faces, edges, and coedges. (You can easily expand the types of ENTITYs recorded.)
- If debug_level is set to 2, we print out the type of bulletin and ENTITY for every bulletin.
This example demonstrates how one can temporarily set the compress_bb option to FALSE so that each delta state will contain multiple bulletin boards. my_create_sheet_body demonstrates the use of an API_BEGIN/END block to combine multiple API functions on a single bulletin board. my_create_sheet_body generates three bulletin boards despite calling four API functions. If the compress_bb option were set to TRUE (the default value) the bulletin boards on each delta state would be merged together, creating just one bulletin board on each delta state. This example also demonstrates how the ACIS EXCEPTION macros can be used outside of API_BEGIN/END blocks to provide exception handling capabilities where you do not want to affect the construction of bulletin boards. In fact, we could not use an API_BEGIN/END block in do_something because api_note_state is called repeatedly in my_generate_model.
| C++ Example |
|---|
#include <stdio.h> #include "acis.hxx" #include "api.hxx" #include "api.err" #include "kernapi.hxx" #include "cstrapi.hxx" #include "coverapi.hxx" #include "boolapi.hxx" #include "lists.hxx" #include "alltop.hxx" #include "get_top.hxx" #include "bulletin.hxx" // Declaration of the ACIS licensing function. void unlock_spatial_products_<NNN>(); // Declaration of our functions. void do_something(); outcome my_generate_model(int, ENTITY_LIST&); outcome my_create_sheet_body(double, BODY*&); void my_debug_history_stream(); void my_debug_delta_state(DELTA_STATE*,int); int my_initialization(); int my_termination(); // Define a macro to check the outcome of API functions. // In the event of an error this macro will // print an error message and propagate the error. #define CHECK_RESULT \ if (!result.ok()) { \ err_mess_type err_no = result.error_number(); \ printf("ACIS ERROR %d: %s\n", \ err_no, find_err_mess(err_no)); \ sys_error(err_no); \ } // Define a macro to check the outcome of API functions. // In the event of an error this macro will // print an error message and return the outcome. #define CHECK_RESULT2 \ if (!result.ok()) { \ err_mess_type err_no = result.error_number(); \ printf("ACIS ERROR %d: %s\n", \ err_no, find_err_mess(err_no)); \ return result; \ } // Define a macro to check the outcome of API functions. // In the event of an error this macro will // print an error message and return the error number. #define CHECK_RESULT3 \ if (!result.ok()) { \ err_mess_type err_no = result.error_number(); \ printf("ACIS ERROR %d: %s\n", \ err_no, find_err_mess(err_no)); \ return err_no; \ } // The main program... int main (int argc, char** argv) { int ret_val = my_initialization(); if (ret_val) return 1; do_something(); ret_val = my_termination(); if (ret_val) return 1; printf("Program completed successfully\n\n"); return 0; } void do_something(){ // Generate a number of bodies on separate Delta States // to simulate an ACIS modeling session. Then examine // the Delta States to see what they contain. EXCEPTION_BEGIN option_header * compress_bb_option = NULL; ENTITY_LIST bodies; EXCEPTION_TRY // Turn off Bulletin Board compression compress_bb_option = find_option("compress_bb"); compress_bb_option->push(FALSE); // Generate some bodies int num_bodies = 3; outcome result = my_generate_model(num_bodies, bodies); CHECK_RESULT // Examine the History Stream to see what was captured. my_debug_history_stream(); EXCEPTION_CATCH(TRUE) // Before exiting this function we should reset the option // and delete the bodies we created. compress_bb_option->pop(); for (int i = 0; i < bodies.count(); i++) api_delent(bodies[i]); EXCEPTION_END_NO_RESIGNAL return; } outcome my_generate_model( int num_bodies, // in: number of bodies to create ENTITY_LIST & bodies // out: list of bodies created ) { // This function simulates an ACIS modeling session. // It creates a number of bodies, each on a separate Delta State. outcome result; for (int i = 0; i < num_bodies; i++) { // Create a sheet body consisting of two faces. BODY * my_body = NULL; result = my_create_sheet_body((double)i, my_body); CHECK_RESULT2 // Append the sheet body to the list of bodies bodies.add(my_body); // Note the state of the model DELTA_STATE * ds; result = api_note_state(ds); CHECK_RESULT2 } return result; } outcome my_create_sheet_body( double z_coord, // in: z coordinate of the body's mid-point BODY *& my_body // out: created body ) { // This function creates a sheet body consisting of two // double-sided faces. It calls four API functions. // We shall surround the first two API functions with // API_BEGIN / API_END macros to combine these 2 APIs // into one Bulletin Board. FACE * my_face = NULL; // The single-sided face created by // the the API_BEGIN / API_END block. API_BEGIN // Create a polygonal wire body from an array of positions. // The first point is the same as the last to create a closed wire. // The wire body will contain 4 edges and 4 vertices. SPAposition * pos_array = ACIS_NEW SPAposition[5]; pos_array[0] = SPAposition(-5.0, -5.0, z_coord - 1.0); pos_array[1] = SPAposition(-5.0, 5.0, z_coord + 1.0); pos_array[2] = SPAposition( 5.0, 5.0, z_coord - 1.0); pos_array[3] = SPAposition( 5.0, -5.0, z_coord + 1.0); pos_array[4] = SPAposition(-5.0, -5.0, z_coord - 1.0); result = api_make_wire (my_body, 5, pos_array, my_body); CHECK_RESULT ACIS_DELETE [] pos_array; // De-allocate the array. // Convert the wire body into a solid body. // This is accomplished by covering the wire with a face. // The solid body will contain one single-sided face. WIRE * my_wire = my_body->lump()->shell()->wire(); result = api_cover_wire (my_wire, *(surface*)NULL_REF, my_face); CHECK_RESULT API_END if (!result.ok()) return result; // Convert the solid body into a sheet body. // The sheet body will contain one double-sided face. result = api_body_to_2d (my_body); CHECK_RESULT2 // Split the face into two faces along an isoparametric curve. // We will perform the split in the u-direction at the mid // parameter of the surface. result = api_split_face (my_face, TRUE, TRUE, 0.5); CHECK_RESULT2 return result; } void my_debug_history_stream() { // This function debugs the default History Stream. HISTORY_STREAM * hs = NULL; outcome result = api_get_default_history(hs); if (!result.ok()) return; printf ("\n******************************************************\n"); DELTA_STATE * root_ds = hs->get_root_ds(); printf ("The root Delta State is 0x%x\n", (long)root_ds); DELTA_STATE * current_ds = hs->get_current_ds(); printf ("The current Delta State is 0x%x\n", (long)current_ds); DELTA_STATE * active_ds = hs->get_active_ds(); printf ("The active Delta State is 0x%x\n\n", (long)active_ds); // Set the amount of debug information desired const int debug_level = 1; // Examine the Active Delta State and // follow the next pointers from it. // (The next pointers should point backward.) if (active_ds) { printf ("Details of the active Delta State\n\n"); my_debug_delta_state(active_ds, debug_level); // Follow the next Delta State pointers DELTA_STATE * this_ds = active_ds->next(); if (this_ds) printf ("Scanning the next Delta State pointers\n\n"); while (this_ds) { my_debug_delta_state(this_ds, debug_level); this_ds = this_ds->next(); } // Examine the Active Delta State's partner and // follow the next pointers from it. // (The next pointers should point forward.) if (active_ds != active_ds->partner()) { DELTA_STATE * partner_ds = active_ds->partner(); printf ("Details of the active Delta State's partner\n\n"); my_debug_delta_state(partner_ds, debug_level); // Follow the next Delta State pointers this_ds = partner_ds->next(); if (this_ds) printf ("Scanning the next Delta State pointers\n\n"); while (this_ds) { my_debug_delta_state(this_ds, debug_level); this_ds = this_ds->next(); } } } } void my_debug_delta_state( DELTA_STATE * ds, // in: The Delta State to debug int debug_level // in: Amount of debug information // 0 : No Bulletin info // 1 : Summary Bulletin info // 2 : Detailed Bulletin info ) { // This function prints information about a given Delta State. // If brief is TRUE, it prints information only about the Delta State. // If brief is FALSE, it prints additional information about the // Bulletin Boards and Bulletins within the Delta State. printf("Delta State 0x%x\n", (long)ds); printf("Previous Delta State 0x%x\n", (long)ds->prev()); printf("Next Delta State 0x%x\n", (long)ds->next()); printf("Partner Delta State 0x%x\n", (long)ds->partner()); printf("Rolls %s ", ds->backward() ? "backward" : "forward"); printf("to state %d ", (int)ds->to()); printf("from state %d\n", (int)ds->from()); BULLETIN_BOARD * bb = ds->bb(); if (bb == NULL) printf("\tContains No Bulletin Boards\n"); while (bb) { printf("\tBulletin Board 0x%x\n", (long)bb); if (debug_level) { BULLETIN * b = bb->start_bulletin(); // Counters for summary debugging. // We could have many more, // but this should demonstrate the concept. int created_faces = 0; int changed_faces = 0; int deleted_faces = 0; int created_edges = 0; int changed_edges = 0; int deleted_edges = 0; int created_coedges = 0; int changed_coedges = 0; int deleted_coedges = 0; while (b) { ENTITY * old_ent = b->old_entity_ptr(); ENTITY * new_ent = b->new_entity_ptr(); BULLETIN_TYPE b_type = b->type(); if (debug_level == 1) { switch (b_type) { case CREATE_BULLETIN: if (is_FACE(new_ent)) created_faces++; else if (is_EDGE(new_ent)) created_edges++; else if (is_COEDGE(new_ent)) created_coedges++; break; case CHANGE_BULLETIN: if (is_FACE(new_ent)) changed_faces++; else if (is_EDGE(new_ent)) changed_edges++; else if (is_COEDGE(new_ent)) changed_coedges++; break; case DELETE_BULLETIN: if (is_FACE(old_ent)) deleted_faces++; else if (is_EDGE(old_ent)) deleted_edges++; else if (is_COEDGE(old_ent)) deleted_coedges++; break; } } else if (debug_level == 2) { const char * ent_type_str = (old_ent) ? old_ent->type_name() : (new_ent) ? new_ent->type_name() : "unknown"; const char * b_type_str = (b_type == CREATE_BULLETIN) ? "CREATE" : (b_type == DELETE_BULLETIN) ? "DELETE" : "CHANGE"; printf("\t\tBulletin 0x%x\n", (long)b); printf("\t\t%s %s, old_ent = 0x%x, new_ent = 0x%x\n", b_type_str, ent_type_str, (long)old_ent, (long)new_ent); } b = b->next(); } if (debug_level == 1) { printf("\t\tCreated %d faces %d edges and %d coedges\n", created_faces, created_edges, created_coedges); printf("\t\tChanged %d faces %d edges and %d coedges\n", changed_faces, changed_edges, changed_coedges); printf("\t\tDeleted %d faces %d edges and %d coedges\n", deleted_faces, deleted_edges, deleted_coedges); } } bb = bb->next(); } printf("\n"); } int my_initialization() { // Start ACIS. outcome result = api_start_modeller(0); CHECK_RESULT3 // Call the licensing function to unlock ACIS. unlock_spatial_products_<NNN>(); // Initialize all necessary components. result = api_initialize_kernel(); CHECK_RESULT3 return 0; } int my_termination() { // Terminate all necessary components. outcome result = api_terminate_kernel(); CHECK_RESULT3 // Stop ACIS and release any allocated memory. result = api_stop_modeller(); CHECK_RESULT3 return 0; } |
The output of the program should look similar to the following.
| Program Output |
|---|
******************************************************
The root Delta State is 0x3ab0c8
The current Delta State is 0x0
The active Delta State is 0x28bbfa0
Details of the active Delta State
Delta State 0x28bbfa0
Previous Delta State 0x0
Next Delta State 0x28b42c8
Partner Delta State 0x28bbfa0
Rolls backward to state 4 from state 5
Bulletin Board 0x28c3c00
Created 1 faces 3 edges and 4 coedges
Changed 1 faces 4 edges and 4 coedges
Deleted 0 faces 0 edges and 0 coedges
Bulletin Board 0x28c0928
Created 0 faces 0 edges and 0 coedges
Changed 1 faces 0 edges and 0 coedges
Deleted 0 faces 0 edges and 0 coedges
Bulletin Board 0x28beac8
Created 1 faces 4 edges and 4 coedges
Changed 0 faces 0 edges and 0 coedges
Deleted 0 faces 0 edges and 0 coedges
Scanning the next Delta State pointers
Delta State 0x28b42c8
Previous Delta State 0x28bbfa0
Next Delta State 0x3ab138
Partner Delta State 0x28b42c8
Rolls backward to state 3 from state 4
Bulletin Board 0x28b8090
Created 1 faces 3 edges and 4 coedges
Changed 1 faces 4 edges and 4 coedges
Deleted 0 faces 0 edges and 0 coedges
Bulletin Board 0x28b8e58
Created 0 faces 0 edges and 0 coedges
Changed 1 faces 0 edges and 0 coedges
Deleted 0 faces 0 edges and 0 coedges
Bulletin Board 0x28ace08
Created 1 faces 4 edges and 4 coedges
Changed 0 faces 0 edges and 0 coedges
Deleted 0 faces 0 edges and 0 coedges
Delta State 0x3ab138
Previous Delta State 0x28b42c8
Next Delta State 0x3ab0c8
Partner Delta State 0x3ab138
Rolls backward to state 2 from state 3
Bulletin Board 0x28b0c58
Created 1 faces 3 edges and 4 coedges
Changed 1 faces 4 edges and 4 coedges
Deleted 0 faces 0 edges and 0 coedges
Bulletin Board 0x3abb20
Created 0 faces 0 edges and 0 coedges
Changed 1 faces 0 edges and 0 coedges
Deleted 0 faces 0 edges and 0 coedges
Bulletin Board 0x3ab1a8
Created 1 faces 4 edges and 4 coedges
Changed 0 faces 0 edges and 0 coedges
Deleted 0 faces 0 edges and 0 coedges
Delta State 0x3ab0c8
Previous Delta State 0x3ab138
Next Delta State 0x0
Partner Delta State 0x3ab0c8
Rolls backward to state 1 from state 2
Contains No Bulletin Boards
Program completed successfully
|
Executing Variations of the Program
Upon getting the example program to run successfully we would suggest running the program and capturing the output for six different cases. Run the program with values of 0, 1, and 2 for the debug_level. Then comment out the lines in do_something to push and pop the compress_bb option value. Then rerun the program with 0, 1, and 2 for the debug level.
Before reading our discussion below study the various output files. What do you observe in the various output files?
Discussion
The six output files illustrate quite a few concepts about ACIS behavior. When you compare the two output files created with debug_level set to 0, you should immediately notice that when the compress_bb option set to FALSE each delta state contains three bulletin boards; whereas the default behavior (with the compress_bb option set to TRUE) there is only one bulletin board in each delta state. The three bulletin boards have been merged into one bulletin board. Merging bulletin boards on a delta state does take some time, but it reduces the size of the history stream (which also makes SAT and SAB files saved with history smaller) and it makes subsequent rolling between states more efficient. Not merging bulletin boards saves a little time during operation and allows applications to examine the contents of each bulletin board independently. In addition to compressing the bulletin boards on each delta state as it is constructed, it is possible to compress the bulletin boards on a delta state after it has been closed using DELTA_STATE::compress.
When you compare the two output files created with debug_level set to 1, you should notice that there are no change bulletins in the history stream when the compress_bb option was TRUE. How is this possible? The create and change bulletins for a given entity have been merged into a single create bulletin. A bulletin board contains at most one bulletin for each entity. If two bulletin boards are being merged and there are bulletins for an entity on each bulletin board, the bulletins must be merged. A create and a change bulletin for an entity can be merged into a single create bulletin. Two change bulletins can be merged into one change bulletin. A change and a delete bulletin can be merged into a delete bulletin. And a create and a delete bulletin cancel each other out, meaning no record is kept if an entity is created and deleted in a single bulletin board. (This is an important fact to remember when searching through a history stream, looking for an entity that you were sure was created.)
When you look at the order of the bulletin boards on the various delta states with debug_level set to 1, what do you notice? (The three bulletin boards correspond to (1) generating a single-side sheet body, (2) converting the single sided face to a double-sided face, and (3) splitting the double-sided face.) The bulletin boards are in the reverse order of the order in which they were created. The most recently closed bulletin board is first in the delta state. You might also notice that if you read the output file from the bottom up, it represents the order in which the operations occurred. The reason that bulletin boards are in the reverse order is for rollback. To undo an operation the bulletin boards on a delta state are undone in the reverse order to which they were created. The most recent bulletin board is undone first.
Comparing the output with debug_level set to 2 can be a bit overwhelming. It is probably easiest just to look at one operation in each file. When looking at a delta state with compress_bb set to TRUE you should see there were only create bulletins and how many different types of entities were created. Looking at a delta state with compress_bb set to FALSE shows how much happened to the model during my_create_sheet_body. Let's look at one entity in one operation: the face that is created, made double-sided, and then split. If you can find the bulletin in the bulletin board that corresponds to the creation of the face, find the address of the face and search for all occurrences of this address in the file. You should find that the address of the face shows up three times in the file. The bulletins should look something like the following:
Bulletin 0x28af928
CHANGE face, old_ent = 0x28af8c0, new_ent = 0x3ab200
Bulletin 0x28acb48
CHANGE face, old_ent = 0x3ab3f0, new_ent = 0x3ab200
Bulletin 0x3ab2b0
CREATE face, old_ent = 0x0, new_ent = 0x3ab200
It may not be immediately obvious why the "new entity" in each case has the same address. Why do you think this is? Imagine for a moment that this represented three different operations instead of one operation. After the first operation your application captured a pointer to the face. After the second operation you would want the address to remain the same so your pointer would still be valid. Therefore, the "new entity" must have the same address as the entity did before the operation, which implies the copied entity is the "old entity," not the "new entity," in a change bulletin.
If you were to run this program in a debugger using debug_level 1 you could set a breakpoint in my_debug_delta_state where we were looking for a CHANGE_BULLETIN for a FACE. If you were to run this program with this breakpoint set, you could compare the "old entity" and the "new entity."
Note: For those of you who are running debuggers on non-Windows platforms, there are a number of functions declared in sg_debug.hxx that may be useful for debugging ACIS applications. Calling dbent(ENTITY const *) from the debugger will print the debug output for an entity. Calling dbhelp produces a list of the functions you can call.
Examining the two entities on a bulletin can be a bit confusing the first time you do it. You must remember that the bulletin boards on a delta state are in reversed order. You must also remember that the "new entity" pointer on each bulletin is the address of the current entity; therefore, it cannot represent the "after" state of each sub-operation. In addition, the "old entity" may contain addresses which are the current addresses of entities. However, despite these difficulties, you can compare the "old entities," and compare the final "old entity" with the "new entity."
Looking at a FACE with a CHANGE_BULLETIN in this example, you can see that initially there was no FACE. (The old_entity pointer was NULL on its CREATE_BULLETIN.) On the previous bulletin board (representing the next stage of the operation) the old FACE was single-sided and its containment bit was set (but meaningless.) This represents the FACE as it was constructed. On the previous bulletin board (representing the next stage of the operation) the old FACE was double-sided and its containment bit was set to 0 (meaning the containment was BOTH_OUTSIDE). Comparing the final old FACE with the current FACE, you can see that the loop pointer has changed. This occurred when the FACE was split. If we had examined the bulletins of the current bulletin board immediately after the API_END statement and each API function call in my_create_sheet_body, it would have been easier to determine the changes that had occurred in the model because the each new_entity pointer would have been current.
Could you create a list containing all of the faces that were created during an operation that exists on one delta state?
Could you create a list containing all of the bodies that currently exist in the model?
Modifying the Example
The Modified Program
In the second example we shall modify our previous program slightly to examine the effects of rolling between states. We shall make two sets of changes.
- Because we are rolling the model, we cannot trust the model to contain all of the bodies constructed by my_generate model. Therefore, we will create a new function, my_find_active_bodies that will scan the history stream and find all the bodies that still exist in the model. We will delete these bodies, rather than the bodies returned by my_generate model, before exiting the program. Typically an ACIS application will keep track of active top level entities for the end user, but our demo application does not, and this provides another opportunity to show how you can search the history stream. Because a body could be constructed on one bulletin board and deleted on another, my_find_active_bodies searches all of the bulletin boards for constructed and deleted bodies and returns only those bodies that were constructed and not deleted. Actually, my_find_active_bodies searches only the bulletin boards on delta states that have not been rolled back. These are the delta states between the root and active delta states.
- The other set of changes will be to do_something. do_something will be enhanced to allow you to more easily generate the initial model, roll backward and forward, generate additional bodies, turn on and off bulletin board compression, and turn on and off branched history. These capabilities will be controlled by logical variables so you do not have to comment out code to see the effects of various changes.
The only functions that will be changed are do_something, my_generate_model (which no longer returns an ENTITY_LIST containing the newly created bodies), and my_find_active_bodies. The other functions will be unchanged. Below is the revised program.
| Modified C++ Example |
|---|
#include <stdio.h> #include "acis.hxx" #include "api.hxx" #include "api.err" #include "kernapi.hxx" #include "cstrapi.hxx" #include "coverapi.hxx" #include "boolapi.hxx" #include "lists.hxx" #include "alltop.hxx" #include "get_top.hxx" #include "bulletin.hxx" // Declaration of the ACIS licensing function. void unlock_spatial_products_<NNN>(); // Declaration of our functions. void do_something(); outcome my_generate_model(int); outcome my_create_sheet_body(double, BODY*&); void my_debug_history_stream(); void my_debug_delta_state(DELTA_STATE*,int); void my_find_active_bodies(ENTITY_LIST&); int my_initialization(); int my_termination(); // Define a macro to check the outcome of API functions. // In the event of an error this macro will // print an error message and propagate the error. #define CHECK_RESULT \ if (!result.ok()) { \ err_mess_type err_no = result.error_number(); \ printf("ACIS ERROR %d: %s\n", \ err_no, find_err_mess(err_no)); \ sys_error(err_no); \ } // Define a macro to check the outcome of API functions. // In the event of an error this macro will // print an error message and return the outcome. #define CHECK_RESULT2 \ if (!result.ok()) { \ err_mess_type err_no = result.error_number(); \ printf("ACIS ERROR %d: %s\n", \ err_no, find_err_mess(err_no)); \ return result; \ } // Define a macro to check the outcome of API functions. // In the event of an error this macro will // print an error message and return the error number. #define CHECK_RESULT3 \ if (!result.ok()) { \ err_mess_type err_no = result.error_number(); \ printf("ACIS ERROR %d: %s\n", \ err_no, find_err_mess(err_no)); \ return err_no; \ } // The main program... int main (int argc, char** argv) { int ret_val = my_initialization(); if (ret_val) return 1; do_something(); ret_val = my_termination(); if (ret_val) return 1; printf("Program completed successfully\n\n"); return 0; } void do_something(){ // Generate a number of bodies on separate Delta States, // then optionally roll backward, roll forward, and generate // more bodies, to simulate an ACIS modeling session. Then // examine the History Stream to see what it contains. logical do_bb_compression = TRUE; logical allow_branched_history = FALSE; logical do_roll_backward = TRUE; logical do_roll_forward = FALSE; logical make_additional_bodies = FALSE; logical use_our_debugging = TRUE; logical use_acis_debugging = FALSE; EXCEPTION_BEGIN option_header * compress_bb_option = NULL; option_header * delete_forward_states_option = NULL; EXCEPTION_TRY // Turn off Bulletin Board compression if (do_bb_compression) { compress_bb_option = find_option("compress_bb"); compress_bb_option->push(FALSE); } // Allow Branched History Streams if (allow_branched_history) { delete_forward_states_option = find_option("delete_forward_states"); delete_forward_states_option->push(FALSE); } // Generate some initial bodies int num_bodies = 3; outcome result = my_generate_model(num_bodies); CHECK_RESULT // Alter the model and examine the History Stream HISTORY_STREAM * hs = NULL; result = api_get_default_history(hs); CHECK_RESULT if (do_roll_backward) { int n_actual; result = api_roll_n_states(hs, -2, n_actual); printf ("Actual number of states rolled = %d\n", n_actual); CHECK_RESULT } if (do_roll_forward) { int n_actual; result = api_roll_n_states(hs, 2, n_actual); printf ("Actual number of states rolled = %d\n", n_actual); CHECK_RESULT } if (make_additional_bodies) { num_bodies = 2; result = my_generate_model(num_bodies); CHECK_RESULT } // Note: Our History Stream debugging function // does not support branched History Streams. if (use_our_debugging) my_debug_history_stream(); // Note: For branched History Streams use HISTORY_STREAM::debug(). if (use_acis_debugging) hs->debug(0, 1, 1, stdout); EXCEPTION_CATCH(TRUE) // Before exiting this function we should reset the options // and delete all of the bodies we created. if (do_bb_compression) compress_bb_option->pop(); if (allow_branched_history) delete_forward_states_option->pop(); ENTITY_LIST bodies; my_find_active_bodies(bodies); for (int i = 0; i < bodies.count(); i++) api_delent(bodies[i]); EXCEPTION_END_NO_RESIGNAL return; } outcome my_generate_model( int num_bodies // in: number of bodies to create ) { // This function simulates an ACIS modeling session. // It creates a number of bodies, each on a separate Delta State. outcome result; for (int i = 0; i < num_bodies; i++) { // Create a sheet body consisting of two faces. BODY * my_body = NULL; result = my_create_sheet_body((double)i, my_body); CHECK_RESULT2 // Note the state of the model DELTA_STATE * ds; result = api_note_state(ds); CHECK_RESULT2 } return result; } outcome my_create_sheet_body( double z_coord, // in: z coordinate of the body's mid-point BODY *& my_body // out: created body ) { // This function creates a sheet body consisting of two // double-sided faces. It calls four API functions. // We shall surround the first two API functions with // API_BEGIN / API_END macros to combine these 2 APIs // into one Bulletin Board. FACE * my_face = NULL; // The single-sided face created by // the the API_BEGIN / API_END block. API_BEGIN // Create a polygonal wire body from an array of positions. // The first point is the same as the last to create a closed wire. // The wire body will contain 4 edges and 4 vertices. SPAposition * pos_array = ACIS_NEW SPAposition[5]; pos_array[0] = SPAposition(-5.0, -5.0, z_coord - 1.0); pos_array[1] = SPAposition(-5.0, 5.0, z_coord + 1.0); pos_array[2] = SPAposition( 5.0, 5.0, z_coord - 1.0); pos_array[3] = SPAposition( 5.0, -5.0, z_coord + 1.0); pos_array[4] = SPAposition(-5.0, -5.0, z_coord - 1.0); result = api_make_wire (my_body, 5, pos_array, my_body); CHECK_RESULT ACIS_DELETE [] pos_array; // De-allocate the array. // Convert the wire body into a solid body. // This is accomplished by covering the wire with a face. // The solid body will contain one single-sided face. WIRE * my_wire = my_body->lump()->shell()->wire(); result = api_cover_wire (my_wire, *(surface*)NULL_REF, my_face); CHECK_RESULT API_END if (!result.ok()) return result; // Convert the solid body into a sheet body. // The sheet body will contain one double-sided face. result = api_body_to_2d (my_body); CHECK_RESULT2 // Split the face into two faces along an isoparametric curve. // We will perform the split in the u-direction at the mid // parameter of the surface. result = api_split_face (my_face, TRUE, TRUE, 0.5); CHECK_RESULT2 return result; } void my_debug_history_stream() { // This function debugs the default History Stream. HISTORY_STREAM * hs = NULL; outcome result = api_get_default_history(hs); if (!result.ok()) return; printf ("\n******************************************************\n"); DELTA_STATE * root_ds = hs->get_root_ds(); printf ("The root Delta State is 0x%x\n", (long)root_ds); DELTA_STATE * current_ds = hs->get_current_ds(); printf ("The current Delta State is 0x%x\n", (long)current_ds); DELTA_STATE * active_ds = hs->get_active_ds(); printf ("The active Delta State is 0x%x\n\n", (long)active_ds); // Set the amount of debug information desired const int debug_level = 1; // Examine the Active Delta State and // follow the next pointers from it. // (The next pointers should point backward.) if (active_ds) { printf ("Details of the active Delta State\n\n"); my_debug_delta_state(active_ds, debug_level); // Follow the next Delta State pointers DELTA_STATE * this_ds = active_ds->next(); if (this_ds) printf ("Scanning the next Delta State pointers\n\n"); while (this_ds) { my_debug_delta_state(this_ds, debug_level); this_ds = this_ds->next(); } // Examine the Active Delta State's partner and // follow the next pointers from it. // (The next pointers should point forward.) if (active_ds != active_ds->partner()) { DELTA_STATE * partner_ds = active_ds->partner(); printf ("Details of the active Delta State's partner\n\n"); my_debug_delta_state(partner_ds, debug_level); // Follow the next Delta State pointers this_ds = partner_ds->next(); if (this_ds) printf ("Scanning the next Delta State pointers\n\n"); while (this_ds) { my_debug_delta_state(this_ds, debug_level); this_ds = this_ds->next(); } } } } void my_debug_delta_state( DELTA_STATE * ds, // in: The Delta State to debug int debug_level // in: Amount of debug information // 0 : No Bulletin info // 1 : Summary Bulletin info // 2 : Detailed Bulletin info ) { // This function prints information about a given Delta State. // If brief is TRUE, it prints information only about the Delta State. // If brief is FALSE, it prints additional information about the // Bulletin Boards and Bulletins within the Delta State. printf("Delta State 0x%x\n", (long)ds); printf("Previous Delta State 0x%x\n", (long)ds->prev()); printf("Next Delta State 0x%x\n", (long)ds->next()); printf("Partner Delta State 0x%x\n", (long)ds->partner()); printf("Rolls %s ", ds->backward() ? "backward" : "forward"); printf("to state %d ", (int)ds->to()); printf("from state %d\n", (int)ds->from()); BULLETIN_BOARD * bb = ds->bb(); if (bb == NULL) printf("\tContains No Bulletin Boards\n"); while (bb) { printf("\tBulletin Board 0x%x\n", (long)bb); if (debug_level) { BULLETIN * b = bb->start_bulletin(); // Counters for summary debugging. // We could have many more, // but this should demonstrate the concept. int created_faces = 0; int changed_faces = 0; int deleted_faces = 0; int created_edges = 0; int changed_edges = 0; int deleted_edges = 0; int created_coedges = 0; int changed_coedges = 0; int deleted_coedges = 0; while (b) { ENTITY * old_ent = b->old_entity_ptr(); ENTITY * new_ent = b->new_entity_ptr(); BULLETIN_TYPE b_type = b->type(); if (debug_level == 1) { switch (b_type) { case CREATE_BULLETIN: if (is_FACE(new_ent)) created_faces++; else if (is_EDGE(new_ent)) created_edges++; else if (is_COEDGE(new_ent)) created_coedges++; break; case CHANGE_BULLETIN: if (is_FACE(new_ent)) changed_faces++; else if (is_EDGE(new_ent)) changed_edges++; else if (is_COEDGE(new_ent)) changed_coedges++; break; case DELETE_BULLETIN: if (is_FACE(old_ent)) deleted_faces++; else if (is_EDGE(old_ent)) deleted_edges++; else if (is_COEDGE(old_ent)) deleted_coedges++; break; } } else if (debug_level == 2) { const char * ent_type_str = (old_ent) ? old_ent->type_name() : (new_ent) ? new_ent->type_name() : "unknown"; const char * b_type_str = (b_type == CREATE_BULLETIN) ? "CREATE" : (b_type == DELETE_BULLETIN) ? "DELETE" : "CHANGE"; printf("\t\tBulletin 0x%x\n", (long)b); printf("\t\t%s %s, old_ent = 0x%x, new_ent = 0x%x\n", b_type_str, ent_type_str, (long)old_ent, (long)new_ent); } b = b->next(); } if (debug_level == 1) { printf("\t\tCreated %d faces %d edges and %d coedges\n", created_faces, created_edges, created_coedges); printf("\t\tChanged %d faces %d edges and %d coedges\n", changed_faces, changed_edges, changed_coedges); printf("\t\tDeleted %d faces %d edges and %d coedges\n", deleted_faces, deleted_edges, deleted_coedges); } } bb = bb->next(); } printf("\n"); } void my_find_active_bodies( ENTITY_LIST & bodies // out: list of active bodies ) { // Scan the History Stream from the active state to the root state // and find all bodies that have been created but not deleted. // Create lists of bodies that have been created and deleted ENTITY_LIST all_created_bodies; ENTITY_LIST all_deleted_bodies; HISTORY_STREAM * hs = NULL; outcome result = api_get_default_history(hs); if (!result.ok()) return; // Because all Delta States from the active state to the root state // roll backward, we follow "next" Delta State pointers. DELTA_STATE * ds = hs->get_active_ds(); while (ds) { BULLETIN_BOARD * bb = ds->bb(); while (bb) { BULLETIN * b = bb->start_bulletin(); while (b) { ENTITY * old_ent = b->old_entity_ptr(); ENTITY * new_ent = b->new_entity_ptr(); BULLETIN_TYPE b_type = b->type(); if (b_type == CREATE_BULLETIN && is_BODY(new_ent)) all_created_bodies.add(new_ent); else if (b_type == DELETE_BULLETIN && is_BODY(old_ent)) all_deleted_bodies.add(old_ent); b = b->next(); } bb = bb->next(); } ds = ds->next(); } // Add all created but not deleted bodies to the list to be returned. bodies.clear(); for (int i = 0; i < all_created_bodies.count(); i++) { ENTITY * ent = all_created_bodies[i]; if (all_deleted_bodies.lookup(ent) == -1) bodies.add(ent); } } int my_initialization() { // Start ACIS. outcome result = api_start_modeller(0); CHECK_RESULT3 // Call the licensing function to unlock ACIS. unlock_spatial_products_<NNN>(); // Initialize all necessary components. result = api_initialize_kernel(); CHECK_RESULT3 return 0; } int my_termination() { // Terminate all necessary components. outcome result = api_terminate_kernel(); CHECK_RESULT3 // Stop ACIS and release any allocated memory. result = api_stop_modeller(); CHECK_RESULT3 return 0; } |
Discussion
The behavior of this program can be modified by modifying the logical variables at the top of do_something. If the initial model is generated, and no rolling is performed, and no additional bodies are generated, and our debug function is used, the output should be identical to the original program. The diagram below depicts the history stream after generating the three bodies. Notice that each delta state's "next" and "previous" pointers correspond to the direction that the delta state rolls, not to "future" and "past" delta states.
Modify the program to generate the initial model and roll back two states. (Actually, this is the state of the example program above.) The debug information obtained by executing the program should look similar to the original debug information; however, there are some significant differences. The active delta state is now the one representing the construction of the first body. The two delta states after the active state are now in a rolled back state. If they are rolled again they will roll forward. The contents of the bulletin boards and the order of the bulletin boards on these delta states have been reversed. When we rolled back the bulletin boards we deleted what we constructed on them and undid the changes we made on them. (If you are curious, you can change the debug_level to 2 and see how the bulletins have been changed.) The diagram below depicts the history stream after generating the three bodies and then rolling back two states.
If after rolling back two states we were to construct two additional bodies, the model would be very similar to our initial model, except the history stream would be slightly different. The diagram below depicts the history stream after generating the three bodies, then rolling back two states, and then generating two more bodies. Compare this diagram to the diagram of the original model. The two rolled back delta states have been deleted and two new delta states have been added.
Up to this point we have dealt exclusively with linear history streams. As we have said it is possible to use a branched history stream, which contains all of the changes made in a model (unless pieces of the history stream are pruned away.) The diagram below depicts the history stream after generating the three bodies, then rolling back two states, and then generating two more bodies, if branched history streams are allowed. As you can see all of the delta states are contained in this history stream. To generate the debug output for this case you should use the ACIS function for debugging the history stream rather than my_debug_history_stream because my_debug_history_stream cannot traverse a branched history stream.
By modifying the logical variables in do_something, modifying the debug_level in my_debug_history_stream, and modifying do_something to perform additional modifications to the model you can explore a wide range of possible history streams. Take some time and play with the program to simulate modeling sessions of interest. Are there any differences in the history stream between (a) generating three bodies, and (b) generating three bodies, rolling backward two states, and rolling forward two states? What happens if you generate three bodies, roll backward two states, and attempt to roll back two more states? (Can you roll back over the root state?)
Our example program does not demonstrate many history stream related functions. Feel free to modify the program to explore the behavior of the following functions we have discussed:
- api_logging
- api_prune_following
- api_prune_history
- api_change_state
- api_change_to_state
- HISTORY_STREAM::set_max_states_to_keep
We would recommend not using set_logging or api_delete_ds. set_logging can make it impossible to recover from exceptions. api_delete_ds can cause memory leaks if used improperly.
Related Topics
ACIS supports a tagging system for entities, which is a more persistent means to access entities than is possible with physical addresses. Refer to Tags and Tag Managers.
|
The ACIS Tutorials contain discussions and examples that will introduce you to ACIS concepts and terminology, and demonstrate how to successfully use ACIS. Each tutorial builds upon prior tutorials; therefore, we suggest that new users proceed through these tutorials sequentially. It is expected that new users will be familiar with programming in C++ and their development environment before beginning these tutorials. In addition, it is expected that new users will have at least a basic understanding of topology and geometry.
|
