Data Model highlights
All packages of Analysis Situs can be divided logically to the backend and the frontend parts. The contents of each part are schematically shown in the figure below.
To represent Data Model, Active Data framework is used. The Active Data framework is based on OCAF module of OpenCascade kernel. The architecture of an OCAF-based application resembles a typical architecture of an Enterprise application constructed on the top of a database engine. In our case, we have OCAF (Active Data) serving as a hierarchical no-sql in-memory database. At the layer above, all necessary Data Access Objects (DAO) reside. These DAOs bring an object-oriented abstraction to the Data Model. The Data Access Objects are also called Data Cursors (or Data Interfaces) as they do not store any data in their member fields and only point to the corresponding persistent entity (OCAF label) which can be used to read and write actual data. Finally, at the top level of Data Model, we have Services (mentioned as API in the figure above) which drive the business logic of the application. It is the role of Service layer to update the contents of the Data Model according to the user inputs.
Programmatically, the DAO layer of the architecture is realized in asiData library. In this library, all object interfaces are declared and implemented. For the Service layer, we have a dedicated asiEngine library. In the Service library, one can find classes like asiEngine_Part or asiEngine_IV which contain the business logic relevant to a specific object type (Part and Imperative Viewer for the mentioned classes).
For a Part object, a piece of business logic may assume recomputation of accelerating structures or AAG in case if the Part gets another B-Rep shape to store. In most cases, it is a good idea to work with a Part (and other object types) via its Service API (i.e., asiEngine_Part) instead of using the DAOs class (i.e., asiData_Part) directly. Calling API functions ensures that the Data Model remains consistent.
An object in Analysis Situs is called a Node following the convention of the Active Data framework. A Node is defined with a list of its Parameters which can be observed in the Parameter Editor panel of the desktop application. Additionally, a Node may have different relations to other Nodes, including:
- Reference (with optional back-reference).
- Parent-child relation.
- Functional dependency (for parametric models).
The functional dependencies are optional in the whole framework. All operations in Analysis Situs, except the reverse engineering workflow, assume no automatic dependency execution.
A Data Node may have one of the following states in runtime:
- Detached (not connected to the database, i.e., to OCAF);
- Attached, bad-formed (connected to the database, but not entirely initialized);
- Attached, well-formed (connected to the database and fully initialized.
Only the Nodes having the "well-formed" status can be used to read and write application data. If a Node is not in its well-formed state, Object Browser renders its name with red background in the project hierarchy:
Invalid Nodes usually appear when you make mistakes while programming your extensions to Analysis Situs. There should never be any bad-formed Nodes in a well-written module. At the same time, the "detached" state of a Data Node is perfectly valid. A Node goes to a detached state right after construction, if not initialized with any OCAF label.
The visualization module (implemented in asiVisu library) is tightly bounded to the Data Model backbone. For a Node to be represented in a 3D scene, the corresponding Presentation class is created. There is a one-to-one correspondence between the Node types and the Presentation types. Practically, it means that whenever a new Node type is introduced, the dual Presentation class should be created. A Presentation is essentially a collection of Pipelines, where a Pipeline is an abstraction to represent the similar notion of VTK visualization library [Schroeder, 2006].
A Pipeline starts with a Data Source which is aware of geometric primitives such as curves, surfaces, meshes, etc. At the same time, a Data Source is not aware of OCAF. The independency from OCAF at a Data Source level allows us reusing the predefined set of Pipelines and/or Data Sources in situations when no persistent storage is used or when the storage is variable (e.g., you may store a parametric curve in different Node types while the visualization Pipeline is just the same).
It is also possible to reuse Data Sources in different Pipelines. The latter is especially useful when dealing with large objects. It is a Pipeline object which creates all used data sources, algorithms (VTK "filters"), mappers and actors. By convention, a Pipeline may have only one actor (check VTK documentation for the overview about filters, mappers and actors).
The persistent data is transferred from OCAF to a visualization Pipeline by means of the so called Data Providers. A Data Provider is used to handle variability in the Data Model representations of a specific object which needs to be rendered in a 3D scene. Thanks to the abstract Data Providers, all Pipelines can be kept OCAF-free. The Pipelines only rely on the abstract interfaces of the corresponding Data Providers to feed their Data Sources properly. All details related to the Data Model, specific Node and Parameter types, and the relations between the data objects are encapsulated within the implementations of the specific Data Providers. The correspondence between Pipelines and their Data Providers is managed by Presentation classes. The Presentation classes construct Pipelines, Data Providers and associate them with each other.
All Parameters of all Nodes store their last modification time. This information is used to perform lazy visualization updates. Each Pipeline stores its modification time as a member field. If the modification time of the sourced Parameters is more recent than the modification time of a Pipeline, the Pipeline should be (re)executed to bring data to the up-to-date state. Technically, since a Pipeline is not aware of OCAF and Data Model Parameters, the timestamp check is done by a Data Provider.
To visualize a Node, you need to construct a Presentation object for it. Additionally, it is necessary to specify the 3D viewer where this Presentation will render its Pipelines. The associativity between the Presentations and the Nodes together with a link to the rendering window is managed by the so called Presentation Manager. The Presentation Manager provides the following common services:
- Actualizes Presentation of a Node (asiVisu_PrsManager::Actualize() method).
- Performs interactive picking (asiVisu_PrsManager::Pick() method).
- Manages all Presentations (allows to create, access and remove them).
There is a one-to-one correspondence between Presentation Managers and 3D viewers. To visualize a Node in a certain viewer, you need this viewer to be managed by a dedicated Presentation Manager class. In Analysis Situs, the base class for all 3D viewers is asiUI_Viewer. This class holds a reference to its corresponding Presentation Manager.
Data Model facade
There is a single entry point to the Data Model which is asiEngine_Model class. Using this class, you can iterate all Nodes in the project, find Nodes, delete and copy/paste them. This class is also responsible for such basic operations as Open/Save, Undo/Redo and compatibility conversion between different versions of project files. On start-up, the empty Data Model is created and populated with the predefined structure of Nodes.
Analysis Situs is based on Qt framework, hence it fully exploits the mechanism of signals-slots to organize the communication between the UI elements. The principal components comprising the user interface are listed below:
- Common facilities instance: set of widgets and singleton service objects.
- Widgets: controls, panels, dialogs.
- Listeners: providers of business logic for widgets.
The widgets are used to display the controls and the listeners define the reactions of the widgets to the user events. It is the responsibility of a listener to organize computations, detach and monitor threads, supply the algorithms with all necessary data and make the outputs available to the user. Most listeners can be overridden so that for a single view (widget) it is possible to have various behaviour depending on how the listener is implemented.
The so-called common facilities object is a singleton representing the UI desktop of the application. It stores the pointers to the main UI controls (object browser, viewers, logger, console, etc.), and is generally accessible from within any UI component. While in principle such common access can jeopardize the consistency of the application state, we think that having such a singleton class is much better than passing only the essential UI controls to particular components of the program. Indeed, a true object-oriented encapsulation becomes impossible once you start encapsulating pointers to your shared resources, such as an Object Browser, etc. Think of this for a moment: the pointer is passed to a component class externally, so unless you make a deep copy (which is nonsense), you are not the only owner of the referenced object. Indeed, syntactical isolation (putting the member fields in a private section) of components does not prevent them from changing the global state of the software which is, in fact, a common problem in object-oriented architectures. Therefore, we decided not to make the illusion of isolation but instead to give all controls full access to the desktop elements. In our opinion, such an approach does not make the architecture any worse, but it makes the communication at least more transparent: anything can access anything (at a certain architectural level). A natural alternative could be to communicate all components via messages (like Qt signals and slots), and this approach found a limited use in Analysis Situs (an example is the relationship between the widgets and their listeners). We think that using the signals & slots mechanism alone (i.e., without direct pointers) is a bad property of an over-architectured software system which is hard to understand and maintain.
The vast majority of algorithms provided within Analysis Situs is accessible via the Active Script console. The geometry processing and modeling algorithms cannot be unified in a simple way, so there is no single rule of thumb for their organization and launching. At the same time, in the recent developments the following architecture is being followed:
- An algorithm is initialized with a Tcl command or clicking a GUI button.
- An algorithm gets a persistent representation in the Data Model and starts to listen to the parametric updates.
Some examples of the algorithms organized into the Tree Functions include:
The parametric nature of a persistent algorithm makes it convenient for experimenting with the input data. In most cases, a persistent algorithm is represented with a specific Data Node in the project tree.