~ubuntu-branches/ubuntu/precise/openwalnut/precise

« back to all changes in this revision

Viewing changes to src/modules/template/WMTemplate.cpp

  • Committer: Bazaar Package Importer
  • Author(s): Sebastian Eichelbaum
  • Date: 2011-06-21 10:26:54 UTC
  • Revision ID: james.westby@ubuntu.com-20110621102654-rq0zf436q949biih
Tags: upstream-1.2.5
ImportĀ upstreamĀ versionĀ 1.2.5

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//---------------------------------------------------------------------------
 
2
//
 
3
// Project: OpenWalnut ( http://www.openwalnut.org )
 
4
//
 
5
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
 
6
// For more information see http://www.openwalnut.org/copying
 
7
//
 
8
// This file is part of OpenWalnut.
 
9
//
 
10
// OpenWalnut is free software: you can redistribute it and/or modify
 
11
// it under the terms of the GNU Lesser General Public License as published by
 
12
// the Free Software Foundation, either version 3 of the License, or
 
13
// (at your option) any later version.
 
14
//
 
15
// OpenWalnut is distributed in the hope that it will be useful,
 
16
// but WITHOUT ANY WARRANTY; without even the implied warranty of
 
17
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
18
// GNU Lesser General Public License for more details.
 
19
//
 
20
// You should have received a copy of the GNU Lesser General Public License
 
21
// along with OpenWalnut. If not, see <http://www.gnu.org/licenses/>.
 
22
//
 
23
//---------------------------------------------------------------------------
 
24
 
 
25
 
 
26
 
 
27
 
 
28
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
29
// How to create your own module in OpenWalnut? Here are the steps to take:
 
30
//   * copy the template module directory
 
31
//   * think about a name for your module
 
32
//   * rename the files from WMTemplate.cpp and WMTemplate.h to WMYourModuleName.cpp and WMYourModuleName.h
 
33
//   * rename the class inside these files to WMYourModuleName
 
34
//   * rename the class inside "W_LOADABLE_MODULE" to WMYourModuleName
 
35
//   * change WMYourModuleName::getName() to a unique name, like "Your Module Name"
 
36
//   * add a your module to src/modules/CMakeLists.txt
 
37
//     * analogously to the other modules, add yours
 
38
//   * run CMake and compile
 
39
//   * read the documentation in this module and modify it to your needs
 
40
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
41
 
 
42
 
 
43
 
 
44
 
 
45
// Some rules to the inclusion of headers:
 
46
//  * Ordering:
 
47
//    * C Headers
 
48
//    * C++ Standard headers
 
49
//    * External Lib headers (like OSG or Boost headers)
 
50
//    * headers of other classes inside OpenWalnut
 
51
//    * your own header file
 
52
 
 
53
#include <string>
 
54
 
 
55
#include <osg/ShapeDrawable>
 
56
#include <osg/Group>
 
57
#include <osg/Geode>
 
58
#include <osg/Material>
 
59
#include <osg/StateAttribute>
 
60
 
 
61
#include "core/kernel/WKernel.h"
 
62
#include "core/common/WColor.h"
 
63
#include "core/common/WPathHelper.h"
 
64
#include "core/common/WPropertyHelper.h"
 
65
#include "core/graphicsEngine/WGEUtils.h"
 
66
#include "core/graphicsEngine/WGERequirement.h"
 
67
 
 
68
#include "WMTemplate.xpm"
 
69
#include "icons/bier.xpm"
 
70
#include "icons/wurst.xpm"
 
71
#include "icons/steak.xpm"
 
72
#include "WMTemplate.h"
 
73
 
 
74
// This line is needed by the module loader to actually find your module. You need to add this to your module too. Do NOT add a ";" here.
 
75
W_LOADABLE_MODULE( WMTemplate )
 
76
 
 
77
WMTemplate::WMTemplate():
 
78
    WModule()
 
79
{
 
80
    // In the constructor, you can initialize your members and all this stuff. You must not initialize connectors or properties here! You also
 
81
    // should avoid doing computationally expensive stuff, since every module has its own thread which is intended to be used for such calculations.
 
82
    // Please keep in mind, that every member initialized here is also initialized in the prototype, which may be a problem if the member is large,
 
83
    // and therefore, wasting a lot of memory in your module's prototype instance.
 
84
}
 
85
 
 
86
WMTemplate::~WMTemplate()
 
87
{
 
88
    // Cleanup!
 
89
}
 
90
 
 
91
boost::shared_ptr< WModule > WMTemplate::factory() const
 
92
{
 
93
    // To properly understand what this is, we need to have a look at how module instances get created. At first, if you are not familiar with the
 
94
    // design patterns "Prototype", "Abstract Factory" and "Factory Method" you should probably read about them first. For short: while the kernel
 
95
    // is starting up, it also creates an instance of WModuleFactory, which creates a prototype instance of every module that can be loaded.
 
96
    // These prototypes are then used to create new instances of modules, check compatibility of modules and identify the type of modules.
 
97
    // If someone, in most cases the module container, wants a new instance of a module with a given prototype, it asks the factory class for it,
 
98
    // which uses the prototype's factory() method. Since the method is virtual, it returns a module instance, created with the correct type.
 
99
    // A prototype itself is an instance of your module, with the constructor run, as well as connectors() and properties(). What does this mean
 
100
    // to your module? Unlike the real "Prototype"- Design pattern, the module prototypes do not get cloned to retrieve a new instance,
 
101
    // they get constructed using "new" and this factory method.
 
102
    //
 
103
    // Here is a short overview of the lifetime of a module instance:
 
104
    //
 
105
    //    * constructor
 
106
    //    * connectors()
 
107
    //    * properties()
 
108
    //    * now isInitialized() will return true
 
109
    //    * the module will be associated with a container
 
110
    //    * now isAssociated() will return true
 
111
    //          o isUsable() will return true
 
112
    //    * after it got added, moduleMain() will be called
 
113
    //    * run, run, run, run
 
114
    //    * notifyStop gets called
 
115
    //    * moduleMain() should end
 
116
    //    * destructor
 
117
    //
 
118
    // So you always have to write this method and always return a valid pointer to an object of your module class.
 
119
    // Never initialize something else in here!
 
120
    return boost::shared_ptr< WModule >( new WMTemplate() );
 
121
}
 
122
 
 
123
const char** WMTemplate::getXPMIcon() const
 
124
{
 
125
    // The template_xpm char array comes from the template.xpm file as included above.
 
126
    // Such char arrays, i.e. files, can be easily created using an image manipulation program
 
127
    // like GIMP. Be aware that the xpm file is a simple header file. Thus it contains real
 
128
    // code. This code can be manipulated by hand. Unfortunately, you really have to fix the
 
129
    // xpm files produced by gimp. You need to make the char array const in order to prevent
 
130
    // compiler warnings or even errors.
 
131
    return template_xpm;
 
132
}
 
133
 
 
134
const std::string WMTemplate::getName() const
 
135
{
 
136
    // Specify your module name here. This name must be UNIQUE!
 
137
    return "Template";
 
138
}
 
139
 
 
140
const std::string WMTemplate::getDescription() const
 
141
{
 
142
    // Specify your module description here. Be detailed. This text is read by the user.
 
143
    return "This module is intended to be a module template and an example for writing modules.";
 
144
}
 
145
 
 
146
void WMTemplate::connectors()
 
147
{
 
148
    // How will your module know on which data it should work? Through its input connector(s). How will other modules get to know about your
 
149
    // calculated output data? Through your output connector(s). Simple isn't it? You may assume your module as some kind of function, as in
 
150
    // common programming languages, where your connectors denote its function signature. The method "connectors()" is for initializing your
 
151
    // connectors, your function signature. Now, a short excursion on how the module container and kernel knows which connector can be connected
 
152
    // to which. Generally, there are only two types of connectors available for your usage: WModuleInputData and WModuleOutputData and they can
 
153
    // only be connected to each other. So, it is not possible to connect an input with an input, nor an output with an output. Both of them are
 
154
    // template classes and therefore are associated with a type. This type determines if an input connector is compatible with an output connector.
 
155
    // A simple example: assume you have a class hierarchy:
 
156
    // Initialize your connectors here. Give them proper names and use the type your module will create or rely on. Do not use types unnecessarily
 
157
    // high in class hierarchy. The list of your connectors is fixed after connectors() got called. As in common imperative programming languages
 
158
    // the function signature can not be changed during runtime (which, in our case, means after connectors() got called).
 
159
 
 
160
    // Here is an example of how to create connectors. This module wants to have an input connector. This connector is defined by the type of
 
161
    // data that should be transferred, an module-wide unique name and a proper description:
 
162
    m_input = boost::shared_ptr< WModuleInputData < WDataSetSingle  > >(
 
163
        new WModuleInputData< WDataSetSingle >( shared_from_this(),
 
164
                                                               "in", "The dataset to display" )
 
165
        );
 
166
    // Lazy Programmer's Alternative:
 
167
    // m_input = WModuleInputData< WDataSetSingle >::createAndAdd( shared_from_this(), "in", "The dataset to display" );
 
168
 
 
169
    // This creates an input connector which can receive WDataSetSingle. It will never be able to connect to output connectors providing just a
 
170
    // WDataSet (which is the father class of WDataSetSingle), but it will be able to be connected to an output connector with a type derived
 
171
    // from WDataSetSingle.
 
172
 
 
173
    // As properties, every connector needs to be added to the list of connectors.
 
174
    addConnector( m_input );
 
175
 
 
176
    // For all the lazy programmers, the creation and addition of the connector can be simplified to one type-less-compatible step:
 
177
    // m_input = WModuleInputData< WDataSetSingle >::createAndAdd( shared_from_this(), "in", "The dataset to display" );
 
178
    // This is fully equivalent to the above calls and works for output connectors too.
 
179
 
 
180
    // Now, lets add an output connector. We want to provide data calculated here to other modules. The output connector is initialized the same
 
181
    // way as input connectors. You need the type, the module-wide unique name and the description. The type you specify here also determines
 
182
    // which input connectors can be connected to this output connector: only connectors with a type equal or lower in class hierarchy.
 
183
    m_output = boost::shared_ptr< WModuleOutputData < WDataSetSingle  > >(
 
184
        new WModuleOutputData< WDataSetSingle >( shared_from_this(),
 
185
                                                               "out", "The calculated dataset" )
 
186
        );
 
187
 
 
188
    // As above: make it known.
 
189
    addConnector( m_output );
 
190
 
 
191
    // call WModule's initialization
 
192
    WModule::connectors();
 
193
}
 
194
 
 
195
void WMTemplate::properties()
 
196
{
 
197
    // Every module can provide properties to the outside world. These properties can be changed by the user in the GUI or simply by other
 
198
    // modules using yours. Properties NEED to be created and added here. Doing this outside this function will lead to severe problems.
 
199
    //
 
200
    // Theoretically, you can specify properties of every type possible in C++. Therefore, see WPropertyVariable. But in most cases, the
 
201
    // predefined properties (WPropertyTypes.h) are enough, besides being the only properties shown and supported by the GUI.
 
202
    //
 
203
    // To create and add a new property, every module has a member m_properties. It is a set of properties this module provides to the outer
 
204
    // world. As with connectors, a property which not has been added to m_properties is not visible for others. Now, how to add a new property?
 
205
 
 
206
    m_propCondition = boost::shared_ptr< WCondition >( new WCondition() );
 
207
    m_aTrigger         = m_properties->addProperty( "Do it now!",               "Trigger Button Text.", WPVBaseTypes::PV_TRIGGER_READY,
 
208
                                                    m_propCondition );
 
209
 
 
210
    m_enableFeature    = m_properties->addProperty( "Enable feature",           "Description.", true );
 
211
    m_anInteger        = m_properties->addProperty( "Number of shape rows",     "Number of shape rows.", 10, m_propCondition );
 
212
    m_anIntegerClone   = m_properties->addProperty( "CLONE!Number of shape rows",
 
213
                                                    "A property which gets modified if \"Number of shape rows\" gets modified.", 10 );
 
214
    m_aDouble          = m_properties->addProperty( "Shape radii",              "Shape radii.", 20.0, m_propCondition );
 
215
    m_aString          = m_properties->addProperty( "A string",                 "Something.", std::string( "hello" ), m_propCondition );
 
216
    m_aFile            = m_properties->addProperty( "A filename",              "Description.", WPathHelper::getAppPath(), m_propCondition );
 
217
    m_aColor           = m_properties->addProperty( "A color",                  "Description.", WColor( 1.0, 0.0, 0.0, 1.0 ) );
 
218
    m_aPosition        = m_properties->addProperty( "Somewhere",                "Description.", WPosition( 0.0, 0.0, 0.0 ) );
 
219
 
 
220
    // These lines create some new properties and add them to the property list of this module. The specific type to create is determined by the
 
221
    // initial value specified in the third argument. The first argument is the name of the property, which needs to be unique among all
 
222
    // properties of this group and must not contain any slashes (/). The second argument is a description. A nice feature is the possibility
 
223
    // to specify an own condition, which gets fired when the property gets modified. This is especially useful to wake up the module's thread
 
224
    // on property changes. So, the property m_anInteger will wake the module thread on changes. m_enableFeature and m_aColor should not wake up
 
225
    // the module thread. They get read by the update callback of this modules OSG node, to update the color. m_aTrigger is a property which can
 
226
    // be used to trigger costly operations. The GUI shows them as buttons with the description as button text. The user can then press them and
 
227
    // the WPropTrigger will change its state to PV_TRIGGER_TRIGGERED. In the moduleMain documentation, you'll find a more detailed description
 
228
    // of how to use trigger properties. Be aware, that these kind of properties should be used carefully. They somehow inhibit the update flow
 
229
    // through the module graph.
 
230
    //
 
231
    // m_anIntegerClone has a special purpose in this example. It shows that you can simply update properties from within your module whilst the
 
232
    // GUI updates itself. You can, for example, set constraints or simply modify values depending on input data, most probably useful to set
 
233
    // nice default values or min/max constraints.
 
234
 
 
235
    // All these above properties are not that usable for selections. Assume the following situation. Your module allows two different kinds of
 
236
    // algorithms to run on some data and you want the user to select which one should do the work. This might be done with integer properties but it
 
237
    // is simply ugly. Therefore, properties of type WPropSelection are available. First you need to define a list of alternatives:
 
238
    m_possibleSelections = boost::shared_ptr< WItemSelection >( new WItemSelection() );
 
239
    m_possibleSelections->addItem( "Beer", "Cold and fresh.", template_bier_xpm );          // NOTE: you can add XPM images here.
 
240
    m_possibleSelections->addItem( "Steaks", "Medium please.",  template_steak_xpm );
 
241
    m_possibleSelections->addItem( "Sausages", "With Sauerkraut.", template_wurst_xpm );
 
242
 
 
243
    // This list of alternatives is NOT the actual property value. It is the list on which so called "WItemSelector" instances work. These
 
244
    // selectors are the actual property. After you created the first selector instance from the list, it can't be modified anymore. This ensures
 
245
    // that it is consistent among multiple threads and selection instances. The following two lines create two selectors as initial value and
 
246
    // create the property:
 
247
    m_aSingleSelection = m_properties->addProperty( "I like most",  "Do you like the most?", m_possibleSelections->getSelectorFirst(),
 
248
                                                    m_propCondition );
 
249
    m_aMultiSelection  = m_properties->addProperty( "I like", "What do you like.", m_possibleSelections->getSelectorAll(),
 
250
                                                    m_propCondition );
 
251
 
 
252
    // Adding a lot of properties might confuse the user. Using WPropGroup, you have the possibility to group your properties together. A
 
253
    // WPropGroup needs a name and can provide a description. As with properties, the name should not contain any "/" and must be unique.
 
254
 
 
255
    m_group1        = m_properties->addPropertyGroup( "First Group",  "A nice group for grouping stuff." );
 
256
    m_group1a       = m_group1->addPropertyGroup(     "Group 1a", "A group nested into \"Group 1\"." );
 
257
    m_group2        = m_properties->addPropertyGroup( "Second Group",  "Another nice group for grouping stuff." );
 
258
 
 
259
    // To understand how the groups can be used, you should consider that m_properties itself is a WPropGroup! This means, you can use your newly
 
260
    // created groups exactly in the same way as you would use m_properties.
 
261
    m_group1Bool    = m_group1->addProperty( "Funny stuff", "A grouped property", true );
 
262
 
 
263
    // You even can add one property multiple times to different groups:
 
264
    m_group2->addProperty( m_aColor );
 
265
    m_group1a->addProperty( m_aDouble );
 
266
    m_group1a->addProperty( m_enableFeature );
 
267
 
 
268
    // Properties can be hidden on the fly. The GUI updates automatically. This is a very useful feature. You can create properties which depend
 
269
    // on a current selection and blend them in our out accordingly.
 
270
    m_aHiddenInt = m_group2->addProperty( "Hide me please", "A property used to demonstrate the hidden feature.", 1, true );
 
271
    m_aHiddenGroup = m_group2->addPropertyGroup( "Hide me please too", "A property used to demonstrate the hidden feature.", true );
 
272
 
 
273
    // Add another button to group2. But this time, we do not want to wake up the main thread. We handle this directly. Fortunately,
 
274
    // WPropertyVariable offers you the possibility to specify your own change callback. This callback is used for hiding the m_aColor property
 
275
    // on the fly.
 
276
    m_hideButton = m_group2->addProperty( "(Un-)Hide", "Trigger Button Text.", WPVBaseTypes::PV_TRIGGER_READY,
 
277
                                          boost::bind( &WMTemplate::hideButtonPressed, this ) );
 
278
 
 
279
    // How can the values of the properties be changed? You can take a look at moduleMain where this is shown. For short: m_anInteger->set( 2 )
 
280
    // and m_anInteger->get().
 
281
 
 
282
    // The properties offer another nice feature: property constraints. You can enforce your properties to be in a special range, to not be
 
283
    // empty, to contain a valid directory name and so on. This is done with the class WPropertyVariable< T >::WPropertyConstraint. There are
 
284
    // several predefined you can use directly: WPropertyConstraintTypes.h. The constants defined there can be used as namespace in
 
285
    // WPropertyHelper. As an example, we want the property m_aFile to only contain existing directories:
 
286
    WPropertyHelper::PC_PATHEXISTS::addTo( m_aFile );
 
287
    WPropertyHelper::PC_ISDIRECTORY::addTo( m_aFile );
 
288
 
 
289
    // Thats it. To set minimum and maximum value for a property the convenience methods setMin and setMax are defined. setMin and setMax are
 
290
    // allowed for all property types with defined <= and >= operator.
 
291
    m_anInteger->setMin( 1 );
 
292
    m_anInteger->setMax( 15 );
 
293
    m_aDouble->setMin( 5.0 );
 
294
    m_aDouble->setMax( 50.0 );
 
295
 
 
296
    // we also want to constraint the both selection properties. One should not allow selecting multiple elements. But both require at least one
 
297
    // element to be selected:
 
298
    WPropertyHelper::PC_SELECTONLYONE::addTo( m_aSingleSelection );
 
299
    WPropertyHelper::PC_NOTEMPTY::addTo( m_aSingleSelection );
 
300
    WPropertyHelper::PC_NOTEMPTY::addTo( m_aMultiSelection );
 
301
 
 
302
    // The most amazing feature is: custom constraints. Similar to OSG update callbacks, you just need to write your own PropertyConstraint class
 
303
    // to define the allowed values for your constraint. Take a look at the StringLength class in this module's code on how to do it.
 
304
    m_aString->addConstraint( boost::shared_ptr< StringLength >( new StringLength ) );
 
305
    WPropertyHelper::PC_NOTEMPTY::addTo( m_aString );
 
306
 
 
307
    // One last thing to mention is the active property. This property is available in all modules and represents the activation state of the
 
308
    // module. In the GUI this is simply a checkbox beneath the module. The active property should be taken into account in ALL modules.
 
309
    // Visualization modules should turn off their graphics. There are basically three ways to react on changes in m_active, which is the member
 
310
    // variable for this property.
 
311
    // 1: overwrite WModule::activate() in your module
 
312
    // 2: register your own handler: m_active->getCondition()->subscribeSignal( boost::bind( &WMTemplate::myCustomActiveHandler, this ) );
 
313
    // 3: react during your module main loop using the moduleState: m_moduleState.add( m_active->getCondition );
 
314
    // Additionally, your can also use the m_active variable directly in your update callbacks to en-/disable some OSG nodes.
 
315
    // This template module uses method number 1. This might be the easiest and most commonly used way.
 
316
 
 
317
    // Sometimes it is desirable to provide some values, statistics, counts, times, ... to the user. This would be possible by using a property
 
318
    // and set the value to the value you want to show the user. Nice, but the user can change this value. PropertyConstraints can't help here,
 
319
    // as they would forbid writing any value to the property (regardless if the module or the user wants to set it). In other words, these
 
320
    // special properties serve another purpose. They are used for information output. Your module already provides another property list only
 
321
    // for these kind of properties. m_infoProperties can be used in the same way as m_properties. The only difference is that each property and
 
322
    // property group added here can't be modified from the outside world. Here is an example:
 
323
    m_aIntegerOutput = m_infoProperties->addProperty( "Run count", "Number of run cycles the module made so far.", 0 );
 
324
    // Later on, we will use this property to provide the number of run cycles to the user.
 
325
    // In more detail, the purpose type of the property gets set to PV_PURPOSE_INFORMATION automatically by m_infoProperties. You can, of course,
 
326
    // add information properties to your custom groups or m_properties too. There, you need to set the purpose flag of the property manually:
 
327
    std::string message = std::string( "Hey you! Besides all these parameters, you also can print values, " ) +
 
328
                          std::string( "<font color=\"#00f\" size=15>html</font> formatted strings, colors and " ) +
 
329
                          std::string( "so on using <font color=\"#ff0000\">properties</font>! Isn't it <b>amazing</b>?" );
 
330
 
 
331
    m_aStringOutput = m_group1a->addProperty( "A message", "A message to the user.", message );
 
332
    m_aStringOutput->setPurpose( PV_PURPOSE_INFORMATION );
 
333
    // This adds the property m_aStringOutput to your group and sets its purpose. The default purpose for all properties is always
 
334
    // "PV_PURPOSE_PARAMETER". It simply denotes the meaning of the property - its meant to be used as modifier for the module's behavior; a
 
335
    // parameter.
 
336
    //
 
337
    // Some more examples. Please note: Although every property type can be used as information property, not everything is really useful.
 
338
    m_infoProperties->addProperty( m_aStringOutput );   // we can also re-add properties
 
339
    m_aTriggerOutput = m_infoProperties->addProperty( "A trigger", "Trigger As String", WPVBaseTypes::PV_TRIGGER_READY );
 
340
    m_aDoubleOutput = m_infoProperties->addProperty( "Some double", "a Double. Nice isn't it?", 3.1415 );
 
341
    m_aIntOutput = m_infoProperties->addProperty( "Some int", "a int. Nice isn't it?", 123456 );
 
342
    m_aColorOutput = m_infoProperties->addProperty( "A color", "Some Color. Nice isn't it?", WColor( 0.5, 0.5, 1.0, 1.0 ) );
 
343
    m_aFilenameOutput = m_infoProperties->addProperty( "Nice file", "a Double. Nice isn't it?", WPathHelper::getAppPath() );
 
344
    m_aSelectionOutput = m_infoProperties->addProperty( "A selection", "Selection As String",  m_possibleSelections->getSelectorFirst() );
 
345
    // One important note regarding information properties. If a property gets added in a group which is an information property-group, then
 
346
    // each added property does NOT contain any constraints. If a property gets an information property AFTER its creation, like m_aStringOutput,
 
347
    // then it keeps its constraints!
 
348
 
 
349
    WModule::properties();
 
350
}
 
351
 
 
352
void WMTemplate::requirements()
 
353
{
 
354
    // This method allows modules to specify what they need to run properly. This module, for example, needs the WGE. It therefore adds the
 
355
    // WGERequirement to the list of requirements. Modules only get started if all the requirements of it are met by the current running
 
356
    // OpenWalnut. This is a very handy tool for NO-GUI versions or script versions of OpenWalnut where there simply is no graphics engine
 
357
    // running. This way, the kernel can ensure that only modules are allowed to run who do not require the WGE.
 
358
    // Another useful example are module containers. Usually, they need several other modules to work properly.
 
359
    m_requirements.push_back( new WGERequirement() );
 
360
}
 
361
 
 
362
void WMTemplate::moduleMain()
 
363
{
 
364
    // This is the modules working thread. Its the most important part of your module.
 
365
    // When you enter this method, all connectors and properties the module provides are fixed. They get initialized in connectors() and
 
366
    // properties(). You always can assume the kernel, the GUI, the graphics engine and the data handler to be initialized and ready. Please keep
 
367
    // in mind, that this method is running in its own thread.
 
368
 
 
369
    // You can output log messages everywhere and any time in your module. The WModule base class therefore provides debugLog, infoLog, warnLog
 
370
    // and errorLog. You can use them very similar to the common std::cout streams.
 
371
    debugLog() << "Entering moduleMain()";
 
372
 
 
373
    // Your module can notify everybody that it is ready to be used. The member function ready() does this for you. The ready state is especially
 
374
    // useful whenever your module needs to do long operations to initialize. No other module can connect to your module before it signals its
 
375
    // ready state. You can assume the code before ready() to be some kind of initialization code.
 
376
    debugLog() << "Doing time consuming operations";
 
377
    sleep( 2 );
 
378
 
 
379
    // Your module can use an moduleState variable to wait for certain events. Most commonly, these events are new data on input connectors or
 
380
    // changed properties. You can decide which events the moduleState should handle. Therefore, use m_moduleState.add( ... ) to insert every
 
381
    // condition you want to wait on. As every input connector provides an changeCondition, we now add this condition to the moduleState:
 
382
    m_moduleState.setResetable( true, true );
 
383
    m_moduleState.add( m_input->getDataChangedCondition() );
 
384
    // Remember the condition provided to some properties in properties()? The condition can now be used with this condition set.
 
385
    m_moduleState.add( m_propCondition );
 
386
    // One note about "setResetable": It might happen, that a condition fires and your thread does not currently waits on it. This would mean,
 
387
    // that your thread misses the event. The resettable flag for those condition sets can help here. Whenever a condition, managed by the
 
388
    // condition set, fires, the moduleState variable remembers it. So, the next call to m_moduleState.wait() will immediately return and reset
 
389
    // the "memory" of the moduleState. For more details, see: http://berkeley.informatik.uni-leipzig.de/trac/ow-public/wiki/HowtoWaitCorrectly
 
390
 
 
391
    // Signal ready state. Now your module can be connected by the container, which owns the module.
 
392
    ready();
 
393
    debugLog() << "Module is now ready.";
 
394
 
 
395
    // Most probably, your module will be a module providing some kind of visual output. In this case, the WGEManagedGroupNode is very handy.
 
396
    // It allows you to insert several nodes and transform them as the WGEGroupNode (from which WGEManagedGroupNode is derived from) is also
 
397
    // an osg::MatrixTransform. The transformation feature comes in handy if you want to transform your whole geometry according to a dataset
 
398
    // coordinate system for example. Another nice feature in WGEManagedGroupNode is that it can handle the m_active flag for you. Read the
 
399
    // documentation of WMTemplate::activate for more details.
 
400
    // First, create the node and add it to the main scene. If you insert something into the scene, you HAVE TO remove it after your module
 
401
    // has finished!
 
402
    m_rootNode = new WGEManagedGroupNode( m_active );
 
403
    // Set a new callback for this node which basically transforms the geometry according to m_aPosition. Update callbacks are the thread safe
 
404
    // way to manipulate an OSG node while it is inside the scene. This module contains several of these callbacks as an example. The one used
 
405
    // here is to translate the root node coordinate system in space according to m_aPosition:
 
406
    m_rootNode->addUpdateCallback( new TranslateCallback( this ) );
 
407
    // Insert to the scene:
 
408
    WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->insert( m_rootNode );
 
409
 
 
410
    // Normally, you will have a loop which runs as long as the module should not shutdown. In this loop you can react on changing data on input
 
411
    // connectors or on changed in your properties.
 
412
    debugLog() << "Entering main loop";
 
413
    while( !m_shutdownFlag() )
 
414
    {
 
415
        // Now, the moduleState variable comes into play. The module can wait for the condition, which gets fired whenever the input receives data
 
416
        // or an property changes. The main loop now waits until something happens.
 
417
        debugLog() << "Waiting ...";
 
418
        m_moduleState.wait();
 
419
 
 
420
        // As you might remember, this property is an information property to provide the number of run cycles to the outside world. It won't be
 
421
        // modified but the module can modify it. This is useful to provide statistics, counts, times or even a "hello world" string to the user
 
422
        // as an information or status report. Please do not abuse these information properties as progress indicators. A short overview on how
 
423
        // to make progress indicators is provided some lines below. Here, we simply increase the value.
 
424
        m_aIntegerOutput->set( m_aIntegerOutput->get() + 1 );
 
425
 
 
426
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
427
        // After waking up, the module has to check whether the shutdownFlag fired. If yes, simply quit the module.
 
428
 
 
429
        // woke up since the module is requested to finish
 
430
        if( m_shutdownFlag() )
 
431
        {
 
432
            break;
 
433
        }
 
434
 
 
435
        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
 
436
        // The next part is the collection part. We collect the information we need and check whether they changed.
 
437
        // Do not recalculate everything in every loop. Always check whether the data changed or some property and handle those cases properly.
 
438
        // After collection, the calculation work can be done.
 
439
 
 
440
 
 
441
        // Now, we can check the input, whether there is an update enqueued for us. But first, we need to understand the meaning of an update on
 
442
        // an input connector:
 
443
        //  * a module updates its output connector with some new data -> updated
 
444
        //  * a module triggers an update on its output connector without an actual data change -> updated
 
445
        //  * our own input connector got connected to an output connector -> updated
 
446
        //  * our own input connector got DISconnected from an output connector -> NO update
 
447
        // You now might ask: "Why can modules trigger updates if they did not change the data?". The answer is simple. Some modules change the
 
448
        // grid without actually changing the data for example. They translate the grid in space. This results in an update although the actual
 
449
        // data stayed the same.
 
450
 
 
451
        // To query whether an input was updated, simply ask the input:
 
452
        bool dataUpdated = m_input->updated();
 
453
 
 
454
        // Remember the above criteria. We now need to check if the data is valid. After a connect-update, it might be NULL.
 
455
        boost::shared_ptr< WDataSetSingle > dataSet = m_input->getData();
 
456
        bool dataValid = ( dataSet );
 
457
        // After calling getData(), the update flag is reset and false again. Please keep in mind, that the module lives in an multi-threaded
 
458
        // world where the update flag and data can change at any time. DO NEVER use getData directly in several places of your module as the
 
459
        // data returned may change between two consecutive calls! Always grab it into a local variable and use this variable.
 
460
 
 
461
        // Another important hint. For grabbing the data, use a local variable wherever possible. If you use a member variable, the data might
 
462
        // never be freed if nobody uses the data anymore because your module holds the last reference. If you need to use a member variable for
 
463
        // the received data, subscribe the your input's disconnect signal or overwrite WModule::notifyConnectionClosed and reset your variable
 
464
        // there to ensure its proper deletion.
 
465
 
 
466
        // do something with the data
 
467
        if( dataUpdated && dataValid )
 
468
        {
 
469
            // The data is valid and we received an update. The data is not NULL but may be the same as in previous loops.
 
470
            debugLog() << "Received Data.";
 
471
        }
 
472
 
 
473
        // If there is no data, this might have the following reasons: the connector never has been connected or it got disconnected. Especially
 
474
        // in the case of a disconnect, you should always clean up your renderings and internal states. A disconnected module should not render
 
475
        // anything anymore. Locally stored referenced to the old input data have to be reset to. Only this way, it is guaranteed that not used
 
476
        // data gets deleted properly.
 
477
        if( !dataValid )
 
478
        {
 
479
            debugLog() << "Data changed. No valid data anymore. Cleaning up.";
 
480
            WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( m_rootNode );
 
481
        }
 
482
 
 
483
        // Here we collect our properties. You, as with input connectors, always check if a property really has changed. You most probably do not
 
484
        // want to check properties which are used exclusively inside the update callback of your OSG node. As the properties are thread-safe, the
 
485
        // update callback can check them and apply it correctly to your visualization.
 
486
        //
 
487
        // To check whether a property changed, WPropertyVariable provides a changed() method which is true whenever the property has changed.
 
488
        // Please note: creating the property with addProperty( ... ) will set changed to true.
 
489
        if( m_aFile->changed() )
 
490
        {
 
491
            // To reset the changed flag, supply a "true" to the get method. This resets the changed-flag and next loop you can again check
 
492
            // whether it has been changed externally.
 
493
 
 
494
            // This is a simple example for doing an operation which is not depending on any other property.
 
495
            debugLog() << "Doing an operation on the file \"" << m_aFile->get( true ).file_string() << "\".";
 
496
 
 
497
            // NOTE: be careful if you want to use dataSet here, as it might be unset. Verify data validity using dataUpdated && dataValid.
 
498
        }
 
499
 
 
500
        // m_aFile got handled above. Now, handle two properties which together are used as parameters for an operation.
 
501
        if( m_aString->changed() )
 
502
        {
 
503
            // This is a simple example for doing an operation which is depends on all, but m_aFile,  properties.
 
504
            debugLog() << "Doing an operation basing on m_aString ... ";
 
505
            debugLog() << "m_aString: " << m_aString->get( true );
 
506
 
 
507
            // NOTE: be careful if you want to use dataSet here, as it might be unset. Verify data validity using dataUpdated && dataValid.
 
508
        }
 
509
 
 
510
        // This example code now shows how to modify your OSG nodes basing on changes in your dataset or properties.
 
511
        // The if statement also checks for data validity as it uses the data! You should also always do that.
 
512
        if( ( m_anInteger->changed() || m_aDouble->changed() || dataUpdated  ) && dataValid )
 
513
        {
 
514
            debugLog() << "Creating new OSG node";
 
515
 
 
516
            // You should grab your values at the beginning of such calculation blocks, since the property might change at any time!
 
517
            int rows = m_anInteger->get( true );
 
518
            double radii = m_aDouble->get( true );
 
519
 
 
520
            // You can set other properties here. This example simply sets the value of m_anIntegerClone. The set command allows an additional
 
521
            // parameter. If it is true, the specified property condition does not fire if it is set. This is useful if your module main loop
 
522
            // waits for the condition of the property you want to set. Setting the property without suppressing the notification would cause
 
523
            // another loop in your module.
 
524
            m_anIntegerClone->set( m_anInteger->get(), true );
 
525
 
 
526
            debugLog() << "Number of Rows: " << rows;
 
527
            debugLog() << "Radii: " << radii;
 
528
            debugLog() << "Current dataset: " << dataSet->getFileName() << " with name: " << dataSet->getName();
 
529
 
 
530
            // This block will be executed whenever we have a new dataset or the m_anInteger property has changed. This example codes produces
 
531
            // some shapes and replaces the existing root node by a new (updated) one. Therefore, a new root node is needed:
 
532
            osg::ref_ptr< osg::Geode > newGeode = new osg::Geode();
 
533
            // When working with the OpenSceneGraph, always use ref_ptr to store pointers to OSG objects. This allows OpenSceneGraph to manage
 
534
            // its resources automatically.
 
535
            for( int32_t i = 0; i < rows; ++i )
 
536
            {
 
537
                newGeode->addDrawable(
 
538
                        new osg::ShapeDrawable( new osg::Box(             osg::Vec3(  25, 128, i * 15 ), radii ) ) );
 
539
                newGeode->addDrawable(
 
540
                        new osg::ShapeDrawable( new osg::Sphere(          osg::Vec3(  75, 128, i * 15 ), radii ) ) );
 
541
                newGeode->addDrawable(
 
542
                        new osg::ShapeDrawable( new osg::Cone(            osg::Vec3( 125, 128, i * 15 ), radii, radii ) ) );
 
543
                newGeode->addDrawable(
 
544
                        new osg::ShapeDrawable( new osg::Cylinder(        osg::Vec3( 175, 128, i * 15 ), radii, radii ) ) );
 
545
                newGeode->addDrawable(
 
546
                        new osg::ShapeDrawable( new osg::Capsule(         osg::Vec3( 225, 128, i * 15 ), radii, radii ) ) );
 
547
            }
 
548
 
 
549
            // The old root node needs to be removed safely. The OpenSceneGraph traverses the graph at every frame. This traversal is done in a
 
550
            // separate thread. Therefore, adding a node directly may cause the OpenSceneGraph to crash. Thats why the Group node (WGEGroupNode)
 
551
            // offers safe remove and insert methods. Use them to manipulate the scene node.
 
552
            // First remove the old node:
 
553
            m_rootNode->remove( m_geode );
 
554
            m_geode = newGeode;
 
555
 
 
556
            // OSG allows you to add custom callbacks. These callbacks get executed on each update traversal. They can be used to modify several
 
557
            // attributes and modes of existing nodes. You do not want to remove the node and recreate another one to simply change some color,
 
558
            // right? Setting the color can be done in such an update callback. See in the header file, how this class is defined.
 
559
            m_geode->addUpdateCallback( new SafeUpdateCallback( this ) );
 
560
 
 
561
            // And insert the new node
 
562
            m_rootNode->insert( m_geode );
 
563
        }
 
564
 
 
565
        // Now we updated the visualization after the dataset has changed. Your module might also calculate some other datasets basing on the
 
566
        // input data.
 
567
        // To ensure that all datasets are valid, check dataUpdated and dataValid. If both are true, you can safely use the data.
 
568
        if( dataUpdated && dataValid )
 
569
        {
 
570
            debugLog() << "Data changed. Recalculating output.";
 
571
 
 
572
            // Calculate your new data here. This example just forwards the input to the output ;-).
 
573
            boost::shared_ptr< WDataSetSingle > newData = dataSet;
 
574
 
 
575
            // Doing a lot of work without notifying the user visually is not a good idea. So how is it possible to report progress? Therefore,
 
576
            // the WModule class provides a member m_progress which is of type WPropgressCombiner. You can create own progress objects and count
 
577
            // them individually. The m_progress combiner provides this information to the GUI and the user.
 
578
            // Here is a simple example:
 
579
            int steps = 10;
 
580
            boost::shared_ptr< WProgress > progress1 = boost::shared_ptr< WProgress >( new WProgress( "Doing work 1", steps ) );
 
581
            m_progress->addSubProgress( progress1 );
 
582
            for( int i = 0; i < steps; ++i )
 
583
            {
 
584
                ++*progress1;
 
585
                sleep( 1 );
 
586
            }
 
587
            progress1->finish();
 
588
            // This creates a progress object with a name and a given number of steps. Your work loop can now increment the progress object. The
 
589
            // progress combiner m_progress collects the progress and provides it to the GUI. When finished, the progress MUST be marked as
 
590
            // finished using finish(). It is no problem to have several progress objects at the same time!
 
591
 
 
592
            // Sometimes, the number of steps is not known. WProgress can also handle this. Simply leave away the last parameter (the number of
 
593
            // steps. As with the other progress, you need to add it to the modules progress combiner and you need to mark it as finished with
 
594
            // finish() if you are done with your work.
 
595
            boost::shared_ptr< WProgress > progress2 = boost::shared_ptr< WProgress >( new WProgress( "Doing work 2" ) );
 
596
            m_progress->addSubProgress( progress2 );
 
597
            sleep( 2 );
 
598
            progress2->finish();
 
599
 
 
600
            // How to set the data to the output and how to notify other modules about it?
 
601
            m_output->updateData( newData );
 
602
            // This sets the new data to the output connector and automatically notifies all modules connected to your output.
 
603
        }
 
604
 
 
605
        // As we provided our condition to m_aTrigger too, the main thread will wake up if it changes. The GUI can change the trigger only to the
 
606
        // state "PV_TRIGGER_TRIGGERED" (this is the case if the user presses the button).
 
607
        if( m_aTrigger->get( true ) == WPVBaseTypes::PV_TRIGGER_TRIGGERED )
 
608
        {
 
609
            // Now that the trigger has the state "triggered", a time consuming operation can be done here.
 
610
            debugLog() << "User triggered an important and time consuming operation.";
 
611
 
 
612
            // We can exchange the list used for selection properties. This of course invalidates the current user selection. You should avoid
 
613
            // changing this too often and too fast as it might confuse the user.
 
614
            m_possibleSelections->addItem( "Beer2", "Cold and fresh.", template_bier_xpm );          // NOTE: you can add XPM images here.
 
615
            m_possibleSelections->addItem( "Steaks2", "Medium please.",  template_steak_xpm );
 
616
            m_possibleSelections->addItem( "Sausages2", "With Sauerkraut.", template_wurst_xpm );
 
617
            // Each of the above write operations trigger an invalidation of all currently exiting selectors. You can create a new selector
 
618
            // basing on an old invalid one and set it as new value for the selections:
 
619
 
 
620
            // Now we set the new selection and selector. Calling newSelector without any argument copies the old selector and tries to resize
 
621
            // the selection to match the new size
 
622
            m_aSingleSelection->set( m_aSingleSelection->get().newSelector() );
 
623
            m_aMultiSelection->set( m_aMultiSelection->get().newSelector() );
 
624
 
 
625
            // Update the output property
 
626
            m_aTriggerOutput->set( WPVBaseTypes::PV_TRIGGER_TRIGGERED );
 
627
 
 
628
            // Do something here. As above, do not forget to inform the user about your progress.
 
629
            int steps = 10;
 
630
            boost::shared_ptr< WProgress > progress1 = boost::shared_ptr< WProgress >( new WProgress( "Doing something important", steps ) );
 
631
            m_progress->addSubProgress( progress1 );
 
632
            for( int i = 0; i < steps; ++i )
 
633
            {
 
634
                ++*progress1;
 
635
                sleep( 1 );
 
636
            }
 
637
            progress1->finish();
 
638
 
 
639
            // As long as the module does not reset the trigger to "ready", the GUI disables the trigger button. This is very useful to avoid
 
640
            // that a user presses the button multiple times during an operation. When setting the property back to "ready", the GUI re-enables
 
641
            // the button and the user can press it again.
 
642
            // To avoid the moduleMain- loop to awake every time we reset the trigger, provide a second parameter to the set() method. It denotes
 
643
            // whether the change notification should be fired or not. In our case, we avoid this by providing false to the second parameter.
 
644
            m_aTrigger->set( WPVBaseTypes::PV_TRIGGER_READY, false );
 
645
 
 
646
            // Also update the information property.
 
647
            m_aTriggerOutput->set( WPVBaseTypes::PV_TRIGGER_READY );
 
648
        }
 
649
 
 
650
        // This checks the selections.
 
651
        if( m_aMultiSelection->changed() ||  m_aSingleSelection->changed() )
 
652
        {
 
653
            // The single selector allows only one selected item and requires one item to be selected all the time. So accessing it by index
 
654
            // is trivial:
 
655
            WItemSelector s = m_aSingleSelection->get( true );
 
656
            infoLog() << "The user likes " << s.at( 0 )->getName() << " the most.";
 
657
 
 
658
            // The multi property allows the selection of several items. So, iteration needs to be done here:
 
659
            s = m_aMultiSelection->get( true );
 
660
            for( size_t i = 0; i < s.size(); ++i )
 
661
            {
 
662
                infoLog() << "The user likes " << s.at( i )->getName();
 
663
            }
 
664
        }
 
665
    }
 
666
 
 
667
    // At this point, the container managing this module signaled to shutdown. The main loop has ended and you should clean up:
 
668
    //
 
669
    //  * remove allocated memory
 
670
    //  * remove all OSG nodes
 
671
    //  * stop any pending threads you may have started earlier
 
672
    //  * ...
 
673
    //  NOTE: as the module gets disconnected prior to shutdown, most of the cleanup should have been done already.
 
674
}
 
675
 
 
676
void WMTemplate::SafeUpdateCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
 
677
{
 
678
    // One note about m_aColor: As you might have notices, changing one of the properties, which recreate the OSG node, causes the material to be
 
679
    // gray again. This is simply caused by m_aColor->changed() is still false! To resolve this problem, use some m_osgNeedsUpdate boolean which
 
680
    // gets set in your thread main and checked here or, as done in this module, by checking whether the callback is called the first time.
 
681
    if( m_module->m_aColor->changed() || m_initialUpdate )
 
682
    {
 
683
        // Set the diffuse color and material:
 
684
        osg::ref_ptr< osg::Material > mat = new osg::Material();
 
685
        mat->setDiffuse( osg::Material::FRONT, m_module->m_aColor->get( true ) );
 
686
        node->getOrCreateStateSet()->setAttribute( mat, osg::StateAttribute::ON );
 
687
    }
 
688
    traverse( node, nv );
 
689
}
 
690
 
 
691
void WMTemplate::TranslateCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
 
692
{
 
693
    // Update the transformation matrix according to m_aPosition if it has changed.
 
694
    if( m_module->m_aPosition->changed() || m_initialUpdate )
 
695
    {
 
696
        // The node to which this callback has been attached needs to be an osg::MatrixTransform:
 
697
        osg::ref_ptr< osg::MatrixTransform > transform = static_cast< osg::MatrixTransform* >( node );
 
698
 
 
699
        // Build a translation matrix (to comfortably convert between WPosition and osg::Vec3 use the WVector3XXX methods)
 
700
        osg::Matrixd translate = osg::Matrixd::translate( m_module->m_aPosition->get( true  ).as< osg::Vec3d >() );
 
701
 
 
702
        // and set the translation matrix
 
703
        transform->setMatrix( translate );
 
704
 
 
705
        // First update done
 
706
        m_initialUpdate = false;
 
707
    }
 
708
    traverse( node, nv );
 
709
}
 
710
 
 
711
bool WMTemplate::StringLength::accept( boost::shared_ptr< WPropertyVariable< WPVBaseTypes::PV_STRING > > /* property */,
 
712
                                       WPVBaseTypes::PV_STRING value )
 
713
{
 
714
    // This method gets called every time the m_aString property is going to be changed. It can decide whether the new value is valid or not. If
 
715
    // the method returns true, the new value is set. If it returns false, the value is rejected.
 
716
    //
 
717
    // Note: always use WPVBaseTypes when specializing the WPropertyVariable template.
 
718
 
 
719
    // simple example: just accept string which are at least 5 chars long and at most 10.
 
720
    return ( value.length() <= 10 ) && ( value.length() >= 5 );
 
721
}
 
722
 
 
723
boost::shared_ptr< WPropertyVariable< WPVBaseTypes::PV_STRING >::PropertyConstraint > WMTemplate::StringLength::clone()
 
724
{
 
725
    // If you write your own constraints, you NEED to provide a clone function. It creates a new copied instance of the constraints with the
 
726
    // correct runtime type.
 
727
    return boost::shared_ptr< WPropertyVariable< WPVBaseTypes::PV_STRING >::PropertyConstraint >( new WMTemplate::StringLength( *this ) );
 
728
}
 
729
 
 
730
void WMTemplate::activate()
 
731
{
 
732
    // This method gets called, whenever the m_active property changes. Your module should always handle this if you do not use the
 
733
    // WGEManagedGroupNode for your scene. The user can (de-)activate modules in his GUI and you can handle this case here:
 
734
    if( m_active->get() )
 
735
    {
 
736
        debugLog() << "Activate.";
 
737
    }
 
738
    else
 
739
    {
 
740
        debugLog() << "Deactivate.";
 
741
    }
 
742
 
 
743
    // The simpler way is by using WGEManagedGroupNode which deactivates itself according to m_active. See moduleMain for details.
 
744
 
 
745
    // Always call WModule's activate!
 
746
    WModule::activate();
 
747
}
 
748
 
 
749
void WMTemplate::hideButtonPressed()
 
750
{
 
751
    // This method is called whenever m_hideButton changes its value. You can use such callbacks to avoid waking-up or disturbing the module
 
752
    // thread for certain operations.
 
753
 
 
754
    // If the button was triggered, switch the hide-state of m_aColor and m_aHiddenInt.
 
755
    if( m_hideButton->get( true ) == WPVBaseTypes::PV_TRIGGER_TRIGGERED )
 
756
    {
 
757
        // switch the hide flag of the color prop.
 
758
        m_aColor->setHidden( !m_aColor->isHidden() );
 
759
        m_aHiddenInt->setHidden( !m_aHiddenInt->isHidden() );
 
760
        m_aHiddenGroup->setHidden( !m_aHiddenGroup->isHidden() );
 
761
 
 
762
        // never forget to reset a trigger. If not done, the trigger is disabled in the GUI and can't be used again.
 
763
        m_hideButton->set( WPVBaseTypes::PV_TRIGGER_READY );
 
764
        // NOTE: this again triggers an update, which is why we need to check the state of the trigger in this if-clause.
 
765
    }
 
766
}