diff --git a/Modules/Simulation/Testing/mitkSimulationTemplateTest.cpp b/Modules/Simulation/Testing/mitkSimulationTemplateTest.cpp index 8ee52220dc..2bbc329b96 100644 --- a/Modules/Simulation/Testing/mitkSimulationTemplateTest.cpp +++ b/Modules/Simulation/Testing/mitkSimulationTemplateTest.cpp @@ -1,72 +1,150 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include #include #include -static mitk::DataNode::Pointer CreateDataNode(mitk::SimulationTemplate::Pointer simulationTemplate) +#define _MITK_TEST_FOR_EXCEPTION(STATEMENT, EXCEPTION, MESSAGE) \ + MITK_TEST_OUTPUT_NO_ENDL(<< MESSAGE) \ + try \ + { \ + STATEMENT; \ + MITK_TEST_OUTPUT(<< " [FAILED]") \ + mitk::TestManager::GetInstance()->TestFailed(); \ + } \ + catch (const EXCEPTION& e) \ + { \ + MITK_TEST_OUTPUT(<< "\n " << e.GetDescription() << " [PASSED]") \ + mitk::TestManager::GetInstance()->TestPassed(); \ + } + +static mitk::DataNode::Pointer CreateDataNode(mitk::SimulationTemplate::Pointer simulationTemplate, bool setProperties = false) { + if (simulationTemplate.IsNull()) + mitkThrow() << "Invalid argument (null pointer!)"; + mitk::DataNode::Pointer dataNode = mitk::DataNode::New(); dataNode->SetData(simulationTemplate); + + if (setProperties) + simulationTemplate->SetProperties(dataNode); + return dataNode; } -static void CreateSimulation_NotInitialized_ReturnsEmptyString() +static void Parse_InputIsEmpty_ReturnsTrue() { mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); - MITK_TEST_CONDITION(simulationTemplate->CreateSimulation().empty(), "CreateSimulation_NotInitialized_ReturnsEmptyString") + MITK_TEST_CONDITION(simulationTemplate->Parse(""), "Parse_InputIsEmpty_ReturnsTrue") +} + +static void Parse_AlreadyInitialized_ReturnsFalse() +{ + mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); + simulationTemplate->Parse(""); + MITK_TEST_CONDITION(!simulationTemplate->Parse(""), "Parse_AlreadyInitialized_ReturnsFalse") +} + +static void Parse_EOFBeforeClosingBrace_ThrowsException() +{ + mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); + _MITK_TEST_FOR_EXCEPTION(simulationTemplate->Parse("{id='A' default='1'"), mitk::Exception, "Parse_EOFBeforeClosingBrace_ThrowsException") +} + +static void Parse_OpeningBraceBeforeClosingBrace_ThrowsException() +{ + mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); + _MITK_TEST_FOR_EXCEPTION(simulationTemplate->Parse("{id='A' default='1' {id='B' default='2'}}"), mitk::Exception, "Parse_OpeningBraceBeforeClosingBrace_ThrowsException") } static void SetProperties_InputIsNull_ReturnsFalse() { mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); MITK_TEST_CONDITION(!simulationTemplate->SetProperties(NULL), "SetProperties_InputIsNull_ReturnsFalse") } static void SetProperties_NotInitialized_ReturnsFalse() { mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); mitk::DataNode::Pointer dataNode = CreateDataNode(simulationTemplate); MITK_TEST_CONDITION(!simulationTemplate->SetProperties(dataNode), "SetProperties_NotInitialized_ReturnsFalse") } +static void SetProperties_InputIsNotOwner_ReturnsFalse() +{ + mitk::SimulationTemplate::Pointer simulationTemplate1 = mitk::SimulationTemplate::New(); + simulationTemplate1->Parse("{id='A' default='1'}"); + mitk::SimulationTemplate::Pointer simulationTemplate2 = mitk::SimulationTemplate::New(); + simulationTemplate2->Parse("{id='B' default='2'}"); + mitk::DataNode::Pointer dataNode1 = CreateDataNode(simulationTemplate1); + MITK_TEST_CONDITION(!simulationTemplate2->SetProperties(dataNode1), "SetProperties_InputIsNotOwner_ReturnsFalse") +} + static void SetProperties_ContainsTemplateAndReference_SetsPropertyAndReturnsTrue() { mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); simulationTemplate->Parse("{id='Atoll' default='Bora'} {'Atoll'}"); mitk::DataNode::Pointer dataNode = CreateDataNode(simulationTemplate); bool boolResult = simulationTemplate->SetProperties(dataNode); std::size_t numProperties = dataNode->GetPropertyList()->GetMap()->size(); std::string stringResult; dataNode->GetStringProperty("Atoll", stringResult); MITK_TEST_CONDITION(boolResult && numProperties == 1 && stringResult == "Bora", "SetProperties_ContainsTemplateAndReference_SetsPropertyAndReturnsTrue") } +static void CreateSimulation_NotInitialized_ReturnsEmptyString() +{ + mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); + MITK_TEST_CONDITION(simulationTemplate->CreateSimulation().empty(), "CreateSimulation_NotInitialized_ReturnsEmptyString") +} + +static void CreateSimulation_ContainsInvalidReference_ReturnsEmptyString() +{ + mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); + simulationTemplate->Parse("{'Invalid'}"); + MITK_TEST_CONDITION(simulationTemplate->CreateSimulation().empty(), "CreateSimulation_ContainsInvalidReference_ReturnsEmptyString") +} + +static void CreateSimulation_ContainsTemplateAndReference_ReturnsExpectedString() +{ + mitk::SimulationTemplate::Pointer simulationTemplate = mitk::SimulationTemplate::New(); + simulationTemplate->Parse("{id='Atoll' default='Bora'} {'Atoll'}"); + MITK_TEST_CONDITION(simulationTemplate->CreateSimulation() == "Bora Bora", "CreateSimulation_ContainsTemplateAndReference_ReturnsExpectedString") +} + int mitkSimulationTemplateTest(int, char* []) { mitk::RegisterSimulationObjectFactory(); MITK_TEST_BEGIN("mitkSimulationTemplateTest") - CreateSimulation_NotInitialized_ReturnsEmptyString(); + Parse_InputIsEmpty_ReturnsTrue(); + Parse_AlreadyInitialized_ReturnsFalse(); + Parse_EOFBeforeClosingBrace_ThrowsException(); + Parse_OpeningBraceBeforeClosingBrace_ThrowsException(); SetProperties_InputIsNull_ReturnsFalse(); SetProperties_NotInitialized_ReturnsFalse(); + SetProperties_InputIsNotOwner_ReturnsFalse(); SetProperties_ContainsTemplateAndReference_SetsPropertyAndReturnsTrue(); + CreateSimulation_NotInitialized_ReturnsEmptyString(); + CreateSimulation_ContainsInvalidReference_ReturnsEmptyString(); + CreateSimulation_ContainsTemplateAndReference_ReturnsExpectedString(); + MITK_TEST_END() } diff --git a/Modules/Simulation/mitkSimulationTemplate.cpp b/Modules/Simulation/mitkSimulationTemplate.cpp index 57888996fc..5ce5c0afea 100644 --- a/Modules/Simulation/mitkSimulationTemplate.cpp +++ b/Modules/Simulation/mitkSimulationTemplate.cpp @@ -1,321 +1,419 @@ /*=================================================================== The Medical Imaging Interaction Toolkit (MITK) Copyright (c) German Cancer Research Center, Division of Medical and Biological Informatics. All rights reserved. This software is distributed WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See LICENSE.txt or http://www.mitk.org for details. ===================================================================*/ #include "mitkSimulationTemplate.h" #include #include +#include typedef std::vector > TemplateIndex; typedef std::vector > VariableContents; static TemplateIndex CreateTemplateIndex(const std::string& contents) { TemplateIndex templateIndex; std::string::size_type begin = 0; - begin = contents.find_first_of('{', 0); + begin = contents.find('{', 0); while (begin != std::string::npos) { - std::string::size_type end = contents.find_first_of('}', begin); + std::string::size_type end = contents.find('}', begin); if (end == std::string::npos) - mitkThrow() << "Expected closing brace before end of file!"; + mitkThrow() << "Could not create template index: Expected closing brace before end of file!"; + + if (contents.substr(begin + 1, end).find('{') != std::string::npos) + mitkThrow() << "Could not create template index: Expected closing brace before opening brace!"; templateIndex.push_back(std::make_pair(begin, ++end - begin)); - begin = contents.find_first_of('{', end); + begin = contents.find('{', end); } return templateIndex; } static std::vector ParseStaticContents(const std::string& contents, const TemplateIndex& templateIndex) { std::vector staticContents; std::string::size_type index = 0; for (TemplateIndex::const_iterator it = templateIndex.begin(); it != templateIndex.end(); ++it) { staticContents.push_back(contents.substr(index, it->first - index)); index = it->first + it->second; } staticContents.push_back(contents.substr(index)); return staticContents; } +static std::size_t CountSingleQuotationMarks(const std::string& string) +{ + if (string.empty()) + return 0; + + const std::string::size_type length = string.length(); + std::size_t n = string[0] == '\'' ? 1 : 0; + + for (std::string::size_type i = 1; i != length; ++i) + { + if (string[i] == '\'' && string[i - 1] != '\\') + ++n; + } + + return n; +} + +static bool IsEven(std::size_t number) +{ + return number % 2 == 0; +} + +static bool ValueExists(const std::string& templ, const std::string& valueName) +{ + std::string::size_type index = templ.find(valueName); + std::string::size_type definiteIndex = std::string::npos; + + while (index != std::string::npos) + { + if (IsEven(CountSingleQuotationMarks(templ.substr(0, index)))) + { + definiteIndex = index; + break; + } + + index = templ.find(valueName, index + 1); + } + + return definiteIndex != std::string::npos; +} + +static std::string UnescapeSingleQuotationMarks(const std::string& escapedString) +{ + std::string::size_type length = escapedString.length(); + std::string::size_type max_i = length - 2; + + std::vector unescapedString; + unescapedString.reserve(length); + + for (std::string::size_type i = 0; i != length; ++i) + { + char c = escapedString[i]; + + if (c == '\\' && i <= max_i && escapedString[i + 1] == '\'') + continue; + + unescapedString.push_back(c); + } + + return std::string(unescapedString.begin(), unescapedString.end()); +} + static std::string ParseValue(const std::string& templ, const std::string& valueName) { const char* spaces = " \t\n"; + std::string::size_type index = templ.find(valueName); + std::string::size_type definiteIndex = std::string::npos; - if (index != std::string::npos) + while (index != std::string::npos) { - index += valueName.length(); - index = templ.find_first_not_of(spaces, index); - - if (index != std::string::npos && templ[index] == '=') + if (IsEven(CountSingleQuotationMarks(templ.substr(0, index)))) { - ++index; - index = templ.find_first_not_of(spaces, index); + if (definiteIndex != std::string::npos) + mitkThrow() << "Could not parse " << templ << ": " << valueName << " is ambiguous!"; - if (index != std::string::npos && templ[index] == '\'') - { - ++index; - std::string::size_type length = templ.find_first_of('\'', index); - - if (length != std::string::npos) - { - length -= index; - return templ.substr(index, length); - } - } + definiteIndex = index; + } + + index = templ.find(valueName, index + 1); + } + + if (definiteIndex == std::string::npos) + mitkThrow() << "Could not parse " << templ << ": " << valueName << " not found!"; + + index = templ.find_first_not_of(spaces, definiteIndex + valueName.length()); + + if (index == std::string::npos || templ[index] != '=') + mitkThrow() << "Could not parse " << templ << ": Expected assignment to " << valueName << "!"; + + index = templ.find_first_not_of(spaces, index + 1); + + if (index == std::string::npos || templ[index] != '\'') + mitkThrow() << "Could not parse " << templ << ": Value of " << valueName << " is not enclosed within single quotation marks!"; + + std::string::size_type index2 = std::string::npos; + std::string::size_type length = templ.length(); + + for (std::string::size_type i = ++index; i != length; ++i) + { + if (templ[i] == '\'' && templ[i - 1] != '\\') + { + index2 = i; + break; } } - return ""; + if (index2 == std::string::npos) + mitkThrow() << "Could not parse " << templ << ": Value of " << valueName << " is not enclosed within single quotation marks!"; + + return UnescapeSingleQuotationMarks(templ.substr(index, index2 - index)); } -template T FromString(const std::string& string) +template +static T lexical_cast(const std::string& string) { - std::istringstream stream(string); - + std::istringstream sstream(string); T value; - stream >> value; + + sstream >> value; + + if (sstream.fail()) + mitkThrow() << "Lexical cast failed for '" << string << "'!"; return value; } static std::pair ParseReference(const std::string& ref) { - std::string id = "{ref}"; - mitk::StringProperty::Pointer property = mitk::StringProperty::New(ref.substr(2, ref.length() - 4)); + std::string id = "{}"; + std::string string = ref.substr(2, ref.length() - 4); - return std::make_pair(id, property); + if (string.empty()) + mitkThrow() << "Could not parse " << ref << ": Reference is empty!"; + + return std::make_pair(id, mitk::StringProperty::New(string)); } static std::pair ParseTemplate(const std::string& templ) { - if (templ.length() > 4 && templ[1] == '\'') + std::string::size_type length = templ.length(); + + if (length < 4) // {''} + mitkThrow() << "Could not parse " << templ << ": Too short to be reference or template!"; + + if(!IsEven(CountSingleQuotationMarks(templ))) + mitkThrow() << "Could not parse " << templ << ": Number of single quotation marks is odd!"; + + if(templ[1] == '\'') { + if (templ[length - 2] != '\'') + mitkThrow() << "Could not parse " << templ << ": Reference does not end with '}!"; + return ParseReference(templ); } - else - { - std::string id = ParseValue(templ, "id"); - if (!id.empty()) - { - std::string type = ParseValue(templ, "type"); + if (length < 7) // {id=''} + mitkThrow() << "Could not parse " << templ << ": Too short to be template!"; - if (type.empty()) - type = "string"; + std::string id = ParseValue(templ, "id"); - mitk::BaseProperty::Pointer property; - std::string defaultValue = ParseValue(templ, "default"); + if (id.empty()) + mitkThrow() << "Could not parse " << templ << ": Id is empty!"; - if (type == "float") - { - float value = !defaultValue.empty() - ? FromString(defaultValue) - : 0.0; + std::string type = ValueExists(templ, "type") + ? ParseValue(templ, "type") + : "string"; - property = mitk::FloatProperty::New(value); - } - else if (type == "int") - { - int value = !defaultValue.empty() - ? FromString(defaultValue) - : 0.0; + std::string defaultValue = ValueExists(templ, "default") + ? ParseValue(templ, "default") + : ""; - property = mitk::IntProperty::New(value); - } - else if (type == "string") - { - std::string value = !defaultValue.empty() - ? defaultValue - : ""; + mitk::BaseProperty::Pointer property; - property = mitk::StringProperty::New(value); - } + if (type == "float") + { + float value = !defaultValue.empty() + ? lexical_cast(defaultValue) + : 0.0; - if (property.IsNotNull()) - return std::make_pair(id, property); - } + property = mitk::FloatProperty::New(value); } + else if (type == "int") + { + int value = !defaultValue.empty() + ? lexical_cast(defaultValue) + : 0.0; - std::string emptyString; - mitk::BaseProperty::Pointer nullPointer; + property = mitk::IntProperty::New(value); + } + else if (type == "string") + { + std::string value = !defaultValue.empty() + ? defaultValue + : ""; + + property = mitk::StringProperty::New(value); + } - return std::make_pair(emptyString, nullPointer); + if (property.IsNull()) + mitkThrow() << "Could not parse " << templ << ": Unknown type '" << type << "'!"; + + return std::make_pair(id, property); } static VariableContents ParseVariableContents(const std::string& contents, const TemplateIndex& templateIndex) { VariableContents variableContents; for (TemplateIndex::const_iterator it = templateIndex.begin(); it != templateIndex.end(); ++it) { std::string templ = contents.substr(it->first, it->second); - std::pair variableContent = ParseTemplate(templ); - - if (variableContent.first.empty() || variableContent.second.IsNull()) - mitkThrow() << "Could not parse " << templ << "!"; - - variableContents.push_back(variableContent); + variableContents.push_back(ParseTemplate(templ)); } return variableContents; } template class FirstEqualTo { public: FirstEqualTo(const T1& value) : m_Value(value) { } bool operator()(const std::pair& pair) const { return pair.first == m_Value; } private: T1 m_Value; }; mitk::SimulationTemplate::SimulationTemplate() : m_IsInitialized(false) { } mitk::SimulationTemplate::~SimulationTemplate() { } std::string mitk::SimulationTemplate::CreateSimulation() const { if (!m_IsInitialized) { MITK_DEBUG << "Simulation template is not initialized!"; return ""; } std::string contents; for (VariableContents::size_type i = 0; i < m_VariableContents.size(); ++i) { contents += m_StaticContents[i]; - if (m_VariableContents[i].first == "{ref}") + if (m_VariableContents[i].first == "{}") { VariableContents::const_iterator it = std::find_if(m_VariableContents.begin(), m_VariableContents.end(), FirstEqualTo(m_VariableContents[i].second->GetValueAsString())); if (it == m_VariableContents.end()) { MITK_DEBUG << "Template '" << m_VariableContents[i].second << "' not found!"; return ""; } contents += it->second->GetValueAsString(); } else { contents += m_VariableContents[i].second->GetValueAsString(); } } contents += m_StaticContents.back(); return contents; } bool mitk::SimulationTemplate::Parse(const std::string& contents) { if (m_IsInitialized) { MITK_DEBUG << "Simulation template is already initialized!"; return false; } TemplateIndex templateIndex = CreateTemplateIndex(contents); std::vector staticContents = ParseStaticContents(contents, templateIndex); VariableContents variableContents = ParseVariableContents(contents, templateIndex); std::swap(m_StaticContents, staticContents); std::swap(m_VariableContents, variableContents); m_IsInitialized = true; return true; } bool mitk::SimulationTemplate::RequestedRegionIsOutsideOfTheBufferedRegion() { return false; } bool mitk::SimulationTemplate::SetProperties(mitk::DataNode::Pointer dataNode) const { if (dataNode.IsNull()) { MITK_DEBUG << "Data node does not exist!"; return false; } if (!m_IsInitialized) { MITK_DEBUG << "Simulation template is not initialized!"; return false; } if (dynamic_cast(dataNode->GetData()) != this) { MITK_DEBUG << "Data node does not own this simulation template!"; return false; } for(VariableContents::const_iterator it = m_VariableContents.begin(); it != m_VariableContents.end(); ++it) { - if (it->first != "{ref}") + if (it->first != "{}") dataNode->SetProperty(it->first.c_str(), it->second.GetPointer()); } return true; } void mitk::SimulationTemplate::SetRequestedRegion(const itk::DataObject*) { } void mitk::SimulationTemplate::SetRequestedRegionToLargestPossibleRegion() { } void mitk::SimulationTemplate::UpdateOutputInformation() { if (this->GetSource().IsNotNull()) this->GetSource()->UpdateOutputInformation(); } bool mitk::SimulationTemplate::VerifyRequestedRegion() { return true; } diff --git a/Plugins/org.mitk.gui.qt.simulation/documentation/UserManual/org_mitk_gui_qt_simulation.dox b/Plugins/org.mitk.gui.qt.simulation/documentation/UserManual/org_mitk_gui_qt_simulation.dox index 012887c77c..d2a5a4846f 100644 --- a/Plugins/org.mitk.gui.qt.simulation/documentation/UserManual/org_mitk_gui_qt_simulation.dox +++ b/Plugins/org.mitk.gui.qt.simulation/documentation/UserManual/org_mitk_gui_qt_simulation.dox @@ -1,103 +1,108 @@ /** \page org_mitk_gui_qt_simulation The Simulation Plugin \image html org_mitk_gui_qt_simulation.png "Icon of the Simulation Plugin." Available sections: - \ref org_mitk_gui_qt_simulationOverview - \ref org_mitk_gui_qt_simulationUsage - \ref org_mitk_gui_qt_simulationAdvancedUsage - \ref org_mitk_gui_qt_simulationSOFAPlugins - \ref org_mitk_gui_qt_simulationSimulationTemplates \section org_mitk_gui_qt_simulationOverview Overview The Simulation View allows you to run SOFA simulations. Its layout is very similar to the runSofa application and if you used SOFA before, you should immediately feel comfortable. Currently you can animate your simulation, step through with arbitrary time steps, and reset your simulation scene. Rendering options, e.g. rendering force fields or visual mappings, are represented by properties of a simulation data node. SOFA plugins can be loaded via the simulation preferences. You can easily take snapshots of your simulation scene, which are represented by 3D surfaces, or record your simulation scene as 3D+t surface. An advantage over runSofa is the ability to load multiple simulation scenes in parallel. There is also support for a kind of predefined editing of simulation scene files by writing simulation scene templates, which can be adjusted in the Properties View. \section org_mitk_gui_qt_simulationUsage Usage Simulation scenes are easily recognizable in the Data Manager by their SOFA icon. \image html DataManager_SimulationNodes.png "Data Manager with three simulation scenes." Selection of simulation scenes in the Data Manager does not affect the active simulation, which must be explicitly selected in the Simulation View. However, rendering properties, which can be seen in the Properties View, refer to the selected simulation scene in the Data Manager. \image html Properties_Simulation.png "Properties of a simulation scene." The appearance of the Simulation View changes slightly depending on which operations are currently possible or make sense, i.e. when no simulation scene was loaded, most of the Simulation View controls are disabled. \image html SimulationView_Inactive_Active.png "The Simulation View enables its controls depending on which of them can be executed at the moment." You can take snapshots of the active simulation scene by pressing the Take Snapshot button. You can toggle the Record button at any time to record all following simulation steps until you toggle it again. The number of recorded steps is shown during recording. \image html SimulationView_Recording.png "Recording simulation steps." Snapshots and records are appended to the corresponding simulation scene in the Data Manager. Snapshots are represented by 3D surfaces and recordings with more than a single frame as 3D+t surfaces. You can step through a 3D+t surface by using the Time slider of the Image Navigator View. \image html DataManager_Snapshots_Record.png "The caduceus simulation scene has two snapshots and a record." \section org_mitk_gui_qt_simulationAdvancedUsage Advanced Usage \subsection org_mitk_gui_qt_simulationSOFAPlugins SOFA Plugins SOFA plugins are supported by MITK and can be loaded during runtime. For more information, see \ref SimulationManualSOFAPluginBuildInstructions. \subsection org_mitk_gui_qt_simulationSimulationTemplates Simulation Templates -Simulation templates are extended SOFA scene files with the file extension .scn.template. +Simulation templates are extended SOFA scene files and have the file extension .scn.template. They contain special strings which are parsed and displayed as properties in the Properties View. -You can create a true simulation scene from a simulation template via its context menu in the Data Manager. +You can create a simulation scene from a simulation template via its context menu in the Data Manager. Behind the scenes all templates of a simulation template file are replaced by concrete values. This simulation scene is saved in the same directory as the simulation template and automatically loaded. \image html DataManager_CreateSimulation.png "A simulation can be created from a simulation template via its context menu in the Data Manager."
\image html Properties_SimulationTemplate.png "A simulation template typically has several properties which can be adjusted before creating a simulation." The syntax for templates is as follows: \code {id='Collision.Alarm Distance' type='int' default='5'} \endcode id must be unique and is parsed as property name, i.e. use periods to arrange properties in tree order. type is optional and its default is string. Other valid types are int and float. -default is also optional and specify the default value of the property. +default is also optional and specifies the default value of the property. Since IDs must be unique but some values are supposed to appear in multiple places in a simulation template, you can specify references to templates as follows: \code {'Collision.Alarm Distance'} \endcode +\warning +Braces are only allowed to open or close templates and references. +References must begin with {' and end with '}'. +You must escape single quotation marks C-style, e.g. {id='Flinders\'s Cat' default='Trim'}. + A good practice is to list all templates within XML comments at the beginning of the simulation template file and to just reference them as needed. \code \endcode */