1
//---------------------------------------------------------------------------
3
// Project: OpenWalnut ( http://www.openwalnut.org )
5
// Copyright 2009 OpenWalnut Community, BSV@Uni-Leipzig and CNCF@MPI-CBS
6
// For more information see http://www.openwalnut.org/copying
8
// This file is part of OpenWalnut.
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.
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.
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/>.
23
//---------------------------------------------------------------------------
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
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
45
// Some rules to the inclusion of 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
55
#include <osg/ShapeDrawable>
58
#include <osg/Material>
59
#include <osg/StateAttribute>
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"
68
#include "WMTemplate.xpm"
69
#include "icons/bier.xpm"
70
#include "icons/wurst.xpm"
71
#include "icons/steak.xpm"
72
#include "WMTemplate.h"
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 )
77
WMTemplate::WMTemplate():
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.
86
WMTemplate::~WMTemplate()
91
boost::shared_ptr< WModule > WMTemplate::factory() const
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.
103
// Here is a short overview of the lifetime of a module instance:
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
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() );
123
const char** WMTemplate::getXPMIcon() const
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.
134
const std::string WMTemplate::getName() const
136
// Specify your module name here. This name must be UNIQUE!
140
const std::string WMTemplate::getDescription() const
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.";
146
void WMTemplate::connectors()
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).
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" )
166
// Lazy Programmer's Alternative:
167
// m_input = WModuleInputData< WDataSetSingle >::createAndAdd( shared_from_this(), "in", "The dataset to display" );
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.
173
// As properties, every connector needs to be added to the list of connectors.
174
addConnector( m_input );
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.
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" )
188
// As above: make it known.
189
addConnector( m_output );
191
// call WModule's initialization
192
WModule::connectors();
195
void WMTemplate::properties()
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.
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.
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?
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,
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 ) );
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.
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.
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 );
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(),
249
m_aMultiSelection = m_properties->addProperty( "I like", "What do you like.", m_possibleSelections->getSelectorAll(),
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.
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." );
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 );
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 );
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 );
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
276
m_hideButton = m_group2->addProperty( "(Un-)Hide", "Trigger Button Text.", WPVBaseTypes::PV_TRIGGER_READY,
277
boost::bind( &WMTemplate::hideButtonPressed, this ) );
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().
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 );
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 );
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 );
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 );
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.
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>?" );
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
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!
349
WModule::properties();
352
void WMTemplate::requirements()
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() );
362
void WMTemplate::moduleMain()
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.
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()";
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";
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
391
// Signal ready state. Now your module can be connected by the container, which owns the module.
393
debugLog() << "Module is now ready.";
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
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 );
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() )
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();
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 );
426
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
427
// After waking up, the module has to check whether the shutdownFlag fired. If yes, simply quit the module.
429
// woke up since the module is requested to finish
430
if( m_shutdownFlag() )
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.
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.
451
// To query whether an input was updated, simply ask the input:
452
bool dataUpdated = m_input->updated();
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.
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.
466
// do something with the data
467
if( dataUpdated && dataValid )
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.";
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.
479
debugLog() << "Data changed. No valid data anymore. Cleaning up.";
480
WKernel::getRunningKernel()->getGraphicsEngine()->getScene()->remove( m_rootNode );
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.
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() )
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.
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() << "\".";
497
// NOTE: be careful if you want to use dataSet here, as it might be unset. Verify data validity using dataUpdated && dataValid.
500
// m_aFile got handled above. Now, handle two properties which together are used as parameters for an operation.
501
if( m_aString->changed() )
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 );
507
// NOTE: be careful if you want to use dataSet here, as it might be unset. Verify data validity using dataUpdated && dataValid.
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 )
514
debugLog() << "Creating new OSG node";
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 );
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 );
526
debugLog() << "Number of Rows: " << rows;
527
debugLog() << "Radii: " << radii;
528
debugLog() << "Current dataset: " << dataSet->getFileName() << " with name: " << dataSet->getName();
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 )
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 ) ) );
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 );
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 ) );
561
// And insert the new node
562
m_rootNode->insert( m_geode );
565
// Now we updated the visualization after the dataset has changed. Your module might also calculate some other datasets basing on the
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 )
570
debugLog() << "Data changed. Recalculating output.";
572
// Calculate your new data here. This example just forwards the input to the output ;-).
573
boost::shared_ptr< WDataSetSingle > newData = dataSet;
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:
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 )
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!
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 );
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.
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 )
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.";
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:
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() );
625
// Update the output property
626
m_aTriggerOutput->set( WPVBaseTypes::PV_TRIGGER_TRIGGERED );
628
// Do something here. As above, do not forget to inform the user about your progress.
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 )
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 );
646
// Also update the information property.
647
m_aTriggerOutput->set( WPVBaseTypes::PV_TRIGGER_READY );
650
// This checks the selections.
651
if( m_aMultiSelection->changed() || m_aSingleSelection->changed() )
653
// The single selector allows only one selected item and requires one item to be selected all the time. So accessing it by index
655
WItemSelector s = m_aSingleSelection->get( true );
656
infoLog() << "The user likes " << s.at( 0 )->getName() << " the most.";
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 )
662
infoLog() << "The user likes " << s.at( i )->getName();
667
// At this point, the container managing this module signaled to shutdown. The main loop has ended and you should clean up:
669
// * remove allocated memory
670
// * remove all OSG nodes
671
// * stop any pending threads you may have started earlier
673
// NOTE: as the module gets disconnected prior to shutdown, most of the cleanup should have been done already.
676
void WMTemplate::SafeUpdateCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
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 )
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 );
688
traverse( node, nv );
691
void WMTemplate::TranslateCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
693
// Update the transformation matrix according to m_aPosition if it has changed.
694
if( m_module->m_aPosition->changed() || m_initialUpdate )
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 );
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 >() );
702
// and set the translation matrix
703
transform->setMatrix( translate );
706
m_initialUpdate = false;
708
traverse( node, nv );
711
bool WMTemplate::StringLength::accept( boost::shared_ptr< WPropertyVariable< WPVBaseTypes::PV_STRING > > /* property */,
712
WPVBaseTypes::PV_STRING value )
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.
717
// Note: always use WPVBaseTypes when specializing the WPropertyVariable template.
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 );
723
boost::shared_ptr< WPropertyVariable< WPVBaseTypes::PV_STRING >::PropertyConstraint > WMTemplate::StringLength::clone()
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 ) );
730
void WMTemplate::activate()
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() )
736
debugLog() << "Activate.";
740
debugLog() << "Deactivate.";
743
// The simpler way is by using WGEManagedGroupNode which deactivates itself according to m_active. See moduleMain for details.
745
// Always call WModule's activate!
749
void WMTemplate::hideButtonPressed()
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.
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 )
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() );
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.