Unification of diagnostic tools in OCCT

For many algorithms and tools to be usable in real applications, it is essential that these algorithms provide detailed information on the encountered problematic situations. In case of error, such details can be critically important for the user to be able to recognize the reason of the problem, and then either eliminate or bypass it.

Currently OCCT includes multiple implementation of diagnostic systems, each specific to its own component. Yet many important algorithms have no or poor diagnostics, e.g. Boolean Operations that are the main target of the current improvements. Instead of creating ad-hoc diagnostic system for each case, it can be reasonable to design an unified set of diagnostic tools and then use them throughout OCCT, eventually replacing existing ones.

Existing tools

Here is a superficial overview of various existing approaches to error reporting found in OCCT classes.

Ad-hoc enum-based error reporting

This is simplest implementation when all possible errors are enumerated and the class contains a field storing the value of the enum possibly indicating the error (or no error).

This approach is used in many places, e.g. these are found by search of "enum" and "error":

  • BRepLib classes
  • BRepBuilderAPI classes
  • BRepFeat classes
  • BRepOffset_MakeOffset and BRepOffset_Offset
  • BRepMesh_DiscretFactory
  • ChFiDS and ChFi2d classes
  • Draft_Modification and BRepOffsetAPI_DraftAngle
  • FilletSurf_Builder
  • gce classes
  • GeomFill and BRepFill classes
  • GeomLib_Interpolate
  • Geom2dHatch_Hatcher and Geom2dHatch_Hatching
  • LocalAnalysis classes
  • math algorithms
  • StepToTopoDS classes (most of error enums are reduced to OK / KO)
  • TopoDSToStep classes
  • Storage, StlAPI, VrmlAPI tools

DE diagnostic tools

Class Interface_Check is used to record possible status messages attached to any entity in the model representing data in the corresponding format (e.g. IGES or STEP). It contains three lists of messages: informational, warnings, and fails. Each message is stored in textual form, in two variants: source string, usually containing placeholder for argument where entity's ID should be put, and complete string with entity ID put in place of the argument.

Such messages generated during file loading process (and rarely during write) are used to detect failures; some get attached to the entities in the model and can be queried later.

Translation process (from model to OCCT shapes and back) uses classes inheriting Transfer_Binder to store all results of translation of a given entity or shape. Transfer_Binder contains instance of Interface_Check where all warnings are put.

There are tools (see DRAW commands data and tpstat) outputting the collected messages in different formats, either on per-entity or per-message basis, with different kinds of filtering.

BRepCheck_Analyzer

Error statuses are defined by enumeration (BRepCheck_Status) and can be assigned to either single sub-shape or sub-shape in context of its parent shape.

Class BRepCheck_Result stores data map of shapes and list of statuses assigned to them. For each subshape of the shape being processed, class BRepCheck_Analyzer creates instance of appropriate class descendant of BRepCheck_Result, which performs relevant checks and stores the result.

Statuses are thus stored in a structure of nested maps: upper-level map stores result for each sub-shape, and result contains map of parent shape to list of error statuses.

DRAW command checkshape can generate text report on recorded statuses in two forms:

  • as recorded, per combination of subshape and parent shape
  • per status, with count of relevant shapes

In both variants, relevant shapes are also reported (stored in DRAW variables).

Shape Healing

Enum ShapeExtend_Status defines 16 status flags, separated in two groups - DONE and FAIL. Algorithmic classes in ShapeAnalysis and ShapeFix packages use these flags to record information on occurred situations in a bit field. The meaning of each flag is specific to the algorithm (and rarely well documented, which makes this approach error-prone).

Statuses are used as means of communication between tools in ShapeAnalysis package and ShapeFix algorithms that call them.

Class ShapeExtend_MsgRegistrator contains map of shape or transient to textual message (Message_Msg). It is used in ShapeFix and ShapeProcess packages to record warnings and errors attached to shapes.

There is no connection between statuses and messages except that usually when message is generated, then relevant status is also set.

Class ShapeProcess_ShapeContext implements both history of shape transformations and association of messages with shapes, including propagation of messages to descendant shapes.

Message_Algorithm

Package Message provides class Message_Algorithm implementing diagnostic tools designed with intent to be universal and reusable.

This component originates from tools developed for use in commercial project (and successfully used there), which are in turn based on ideas borrowed from DE and ShapeHealing reporting tools mentioned above. It is used in at least one other project, but not in OCCT (apart of single class, TObj_CheckModel).

The root class Message_Algorithm contains an integer field Status that is a bit field that can contain up to 32 status flags, separated in 4 groups (Information, Warning, Alarm, Fail). The meaning of each flag is specific to an algorithm that uses it.

The class maintains two collections of parameters for each status flag - integers and strings. When the same status is recorded multiple times, the parameter of each occurrence is recorded in that collection.

Message_Algorithm implements a method generating textual report for the recorded statuses (sent to Message_Messenger instance stored as a field). The text of the messages is taken from resources maintained by Message_MsgFile class (thus can be localized); dynamic type of the current algorithm class (descendant of Message_Algorithm) combined with status name is used as a key for the message. Message can have single argument that is substituted by digest of parameters collected for that status (either a full list or list of a few first parameters and total count).

Design of the new system

 

Requirements

A. Requirements to messages generation:

  1. Extensibility: it should be possible to add new types of messages (or remove existing) without conflicts with other classes / components.
  2. Possibility to attach any (application specific) data to the message. Support of standard data (integers, reals, strings, shapes) should be available out of the box.
  3. Minimal footprint. In practice messages are rarely used, and overheads related to their generation and storage must be minimized (especially for the case when there are no or very few messages).
  4. Optimized for recording. Additional processing should be postponed as much as possible till the time of report generation.
  5. Support of different levels of diagnostics, to distinguish fatal and non-fatal errors, warnings, informative messages etc.
  6. Integration with shape history tools: it should be possible to collect and query messages over the sequence of shape modifications.
  7. Thread safety for recording (adding messages in parallel threads).

B. Requirements to reporting tools:

  1. Standard approach to obtain a message as a text string.
  2. Dumping text report into a C++ stream or Message_Messenger.
  3. Support of localization of text messages.
  4. Possibility to avoid duplication of the same messages.

Location

It is logical to place the root classes of the new system into the package Message: it is located in TKernel thus available to all descendants, and initially intended for this purpose.

Particular messages should be defined in packages where they are generated; some reusable messages can be defined in common packages.

Definition of messages

Messages will be defined as classes manipulated by handle. This will allow independent addition / removal of message classes without conflicts (which would be difficult to avoid if enums are used), and also allow each message to store additional data as necessary depending on its nature.

Base class name proposed is Message_Alert. The name "Alert" better matches the intent of diagnostics (not just a text message) and is not yet used across OCCT, thus it should be easy to distinguish new classes from existing ones.

Alert should provide unique text identifier that can be used to distinguish particular type of alerts, e.g. to get a text message string describing it. This is supported by method GetMessageKey(); by default, dynamic type name is used.

Alert can contain some data. To avoid excessive storage of duplicated alerts of the same type, new alert can be merged with existing one of the same type. Method SupportsMerge() should return true if merge is supported; method Merge() should do the merge if possible, returning true in that case or false otherwise.

Storage of messages

Class Message_Report is intended to be a container for alert messages, sorted in five groups according to their gravity (Trace, Info, Warning, Alarm, Fail, as defined by enum Message_Gravity).

For each gravity level, alerts are stored in simple list. If alert being added can be merged with another alert of the same type already in the list, it is merged and not added to the list.

This class is intended to be used as follows:

  • In the process of execution, algorithm fills report by alert objects using methods AddAlert().
  • The result can be queried for presence of particular alert using methods HasAlert().
  • The reports produced by nested or sequentially executed algorithms can be combined in one using method Merge().
  • The report can be shown to the user either as plain text with method Dump() or in more advanced way, by iterating over lists returned by GetAlerts().
  • Report can be cleared by methods Clear() (usually after reporting).

First implementation

The first implementation of the new system is available in branch CR28786_5 of the OCCT repository. It implements the basic classes and uses them to introduce diagnostics in the Boolean Operations algorithms.

In addition to the base class Message_Alert, an alert containing a shape is defined by the class TopoDS_AlertWithShape. For both classes we have a helper macro that can be used to add new specific alert inheriting the base one in single line of code.

See example in BOPAlgo_Alerts.hxx:


...
DEFINE_SIMPLE_ALERT(BOPAlgo_AlertNullInputShapes)
DEFINE_SIMPLE_ALERT(BOPAlgo_AlertPostTreatFF)
DEFINE_SIMPLE_ALERT(BOPAlgo_AlertSolidBuilderFailed)
DEFINE_SIMPLE_ALERT(BOPAlgo_AlertTooFewArguments)
DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertSelfInterferingShape)
DEFINE_ALERT_WITH_SHAPE(BOPAlgo_AlertNotSplittableEdge)
...

The algorithm creates instance of Message_Report class and then adds alerts during execution, like this:

 
myReport->AddAlert (Message_Warning, new BOPAlgo_AlertSelfInterferingShape (aShape));

The textual messages associated with alerts are defined in resource file, see e.g. BOPAlgo.msg:

 
...
.BOPAlgo_AlertSelfInterferingShape
Warning: Some of the arguments are self-interfering shapes
 
.BOPAlgo_AlertTooSmallEdge
Warning: Some edges are too small and have no valid range
...
 

In the end, the caller can check presence of alerts of particular gravity or dump text messages corresponding to the generated alerts, for instance:

 
anAlgo->Perform(); // run the algorithm (may generate alerts)
anAlgo->GetReport()->Dump(std::cout); // dump alert messages to cout
if (anAlgo->GetReport()->HasAlert(Message_Fail)) // check if we have a fail recorded
{
// treat failure
}
 

Pending points

There are some points to be completed and / or discussed:

  • Add more specific basic alerts with data: single integer, string, real, map of unique integers (IDs), sequence of strings…
  • Decide on whether base alert should define some generic interface (virtual method) to get a textual message describing it (or possibly not just a text). Current approach assumes that generation of messages is performed on application level where it can use full knowledge of the context, not available at the low level where base alerts are defined. On the basic level, class Message_Report uses functionality provided by Message_Msg to get the text message describing the alert.
  • Elaborate better approach to storing alerts produced by nested algorithms, to make full results of execution of the complex algorithms be available to the caller, including results of nested algorithms structured according to the call graph.

Discussion

We will be glad to receive feedback of OCCT community members regarding the proposed unification of diagnostic systems. Will it be useful for you? Will the proposed functionality and features be sufficient? Have an idea on how to implement it in a better way? Your suggestions and opinions are welcome!