.. _tut - implement an operator: ============================= Implementing an STMK operator ============================= .. contents:: .. highlight:: c++ .. role:: cxx(code) :language: c++ SMTK allows you to write operators that expose those provided by an underlying modeling kernel as well as operators that provide new functionality. This tutorial will cover writing an operator that provides new functionality: specifically, we will create an operator that counts the number of top-level cells in a model. Given a non-default option, it may instead count the number of top-level groups in a model. Operators in SMTK consist of 3 components: - a class that implements the action of the operator, as a subclass of :smtk:`smtk::model::Operator`. - an SMTK attribute definition that describes the parameters the operator accepts (both required and optional) - an SMTK attribute definition that describes information returned by the operator. The sections below detail each of these. ********************************* Subclassing smtk::model::Operator ********************************* We will name our example operator the CounterOperator and place it in the "ex" example namespace. .. literalinclude:: implement_an_operator.h :start-after: // ++ 1 ++ :end-before: // -- 1 -- :linenos: Our operator is a subclass of :smtk:`smtk::model::Operator`, which is managed using shared pointers (it uses the :smtk:`smtkEnableSharedPtr` macro to inherit :cpp:class:`enable_shared_from_this`), so we use the :smtk:`smtkCreateMacro` to provide a static method for creating a shared pointer to a new instance and the :smtk:`smtkSharedFromThisMacro` to override the base class' :c:func:`shared_from_this` method. Beyond these basic requirements, an operator should implement two inherited virtual methods + :smtk:`ableToOperate ` which is an opportunity for an operator to perform checks on the validity of input parameters that cannot be easily encoded using the attribute resource; and + :smtk:`operateInternal ` which implements the actual behavior of the operator. Because our operator is simple, we implement :smtk:`smtk::operation::Operation::ableToOperate` in the class header file. We only declare :smtk:`smtk::operation::Operation::operateInternal`: .. literalinclude:: implement_an_operator.h :start-after: // ++ 2 ++ :end-before: // -- 2 -- :linenos: By calling :smtk:`Operator::ensureSpecification` in :smtk:`Operator::ableToOperate`, we force the attribute resource to build an attribute instance which holds specifications for each parameter of this instance of the Operator. Then in :smtk:`Operator::operateInternal` we can refer to the specification without having to verify that it is non-null; an operator's :smtk:`Operator::operateInternal` method will never be called unless :smtk:`Operator::ableToOperate` returns true. The attribute constructed by :smtk:`Operator::ensureSpecification` is built using XML definitions we provide. We will cover the format of the XML definitions immediately below and then continue with the implementation of the operation. ********************************** Defining operator input parameters ********************************** The XML we used to declare the operator's parameters and results uses SMTK's attribute definition system. All operators should be *derived* definitions whose base definition (BaseType) is "operator": .. highlight:: xml .. literalinclude:: implement_an_operator.xml :start-after: :end-before: :linenos: .. highlight:: c++ Inheriting the base definition allows us to use to attribute system to easily enumerate the list of all operators. Also, you can see in the example that our operator takes a single integer parameter named "count groups". Its default value is 0, indicating that top-level cells (not groups) will be counted by default. We could also have made "count groups" a :smtk:`VoidItem` and tested for whether the attribute was enabled instead of testing its value. In the future, operators will have their "primary" operand expressed as an *association*: model topology to serve as the primary operand will be associated with an instance of the operator rather than declared as an item owned by the operator attribute. This will simplify use cases where an active selection exists and the user wishes to perform an operation on it; operators that can be associated with the selection will be enabled (while others will be disabled). Any further parameters may be specified after the user initiates the operation. *********************************** Defining operator output parameters *********************************** The XML description of an operator is not complete until both the input and output parameters have been specified. The output parameters are expressed as another :smtk:`smtk::attribute::Attribute` instance, this time defined as by inheriting the "result" BaseType. The result base type includes an integer item named "outcome" use to store one of the values in the :smtk:`OperatorOutcome` enum. The outcome indicates whether the operation was successful or not. .. highlight:: xml .. literalinclude:: implement_an_operator.xml :start-after: :end-before: :linenos: .. highlight:: c++ In addition to the outcome, our edge-counting operator also returns the number of edges it counted. Often, SMTK operators will return lists of new, modified, or removed model entities in one or more :smtk:`smtk::attribute::ModelEntityItem` instances. Both the input and output XML are typically maintained together in a single XML file. ********************************* Implementing the actual operation ********************************* Now that we have input and output parameters specified, the implementation of :smtk:`Operator::operateInternal` can simply fetch items from an instance of an attribute defined by the XML: .. literalinclude:: implement_an_operator.cxx :start-after: // ++ 2 ++ :end-before: // -- 2 -- :linenos: Note that the base class provides a method to create a result attribute for you with the "outcome" parameter set to a value you specify. In addition to implementing the operation, the only other thing you must do is register the operator with the proper session. This is done using the :smtk:`smtkImplementsModelOperator` macro: .. literalinclude:: implement_an_operator.cxx :start-after: // ++ 3 ++ :end-before: // -- 3 -- :linenos: .. WARNING:: This macro must always be invoked in the global namespace and your operator's class name fully qualified with the namespace in which it lives. This macro is very important because it ties several names together: + the C++ class name (:cpp:class:`ex::CounterOperator`), + the names and XML descriptions of the attribute definitions for input parameters ("counter") and result parameters "result(counter)", and + the SMTK component name used by the autoinitialization facility to force components (such as this operator) to be registered when initializing static variables. In this case the component name "ex_counter" can be used with the :smtk:`smtkComponentInitMacro` by adding this line: .. code-block:: c++ smtkComponentInitMacro(smtk_ex_counter_operator) to a compilation unit containing code that will be run by your application. Whenever the compilation unit's static variables are initialized, the operator will be registered with the session class and any sessions constructed afterwards will provide the operator. Note that "smtk\_" and "\_operator" wrap the name you pass to the :smtk:`smtkImplementsModelOperator` macro. To make maintaining the XML simpler, SMTK provides a macro for encoding an XML file as a variable in a C++ header. The variable :c:data:`implement_an_operator_xml` is generated in CMakeLists.txt by .. highlight:: cmake .. literalinclude:: CMakeLists.txt :start-after: # ++ 1 ++ :end-before: # -- 1 -- :linenos: .. highlight:: c++ and then used by including the resulting file in your C++ implementation: .. literalinclude:: implement_an_operator.cxx :start-after: // ++ 1 ++ :end-before: // -- 1 -- :linenos: