- Parameters should be generally broken down into "Input Parameters", "Required Data Objects", "Created Data Objects". There can be exceptions to this.
- ChoicesParameter selections should be an enumeration defined in the filer header
- Documentation copied from SIMPL Repo and updated (if necessary)
- Parameter argument variables are
k_CamelCase_Key
- Parameter argument strings are
lower_snake_case
static inline constexpr StringLiteral k_AlignmentType_Key = "alignment_type";
- Filters should have both the Filter class and Algorithm class for anything beyond trivial needs
QString => std::string
QVector<> => std::vector<>
QMap<> => std::map<>
QByteArray => std::array<int8> or std::vector<int8>
SIMPL
setErrorCondition(nx::core::StlConstants::k_ErrorOpeningFile, "Error opening STL file");
SIMPLNX
Result<> result = MakeErrorResult(nx::core::StlConstants::k_ErrorOpeningFile, "Error opening STL file")
then you can optionally return the result
variable if needed
There are some substitutions for the QString operations. See https://en.cppreference.com/w/cpp/string/basic_string for more information about std::string
There is a file simplnx/Utilities/StringUtilities.hpp
that has some QString functionality that is needed.
If you know the path to the Geometry:
DataPath triangleGeometryDataPath = pParentDataGroupPath.createChildPath(pGeometryName);
TriangleGeom& triangleGeom = dataStructure.getDataRefAs<TriangleGeom>(triangleGeometryDataPath);
If your codes specifically resize the AttributeMatrix, this is not needed anymore.
Use the format
library
QString msg = QString("Error reading Triangle '%1'. Object Count was %2 and should have been %3").arg(t, objsRead, k_StlElementCount);
std::string msg = fmt::format("Error reading Triangle '{}}'. Object Count was {} and should have been {}", t, objsRead, k_StlElementCount);
Example of getting an array and summing the values using range based for loop.
// Let's sum up all the areas.
Float64Array& faceAreasArray = dataGraph.getDataRefAs<Float64Array>(triangleAreasDataPath);
AbstractFloat64DataStore& faceAreas = faceAreasArray.getDataStoreRef();
double sumOfAreas = 0.0;
for(const auto& area : faceAreas)
{
sumOfAreas += area;
}
- When iterating over values, either to read or write, use the reference returned by DataArray::getDataStoreRef().
- When writing values in a multi-threaded function, use the getValue and setValue methods in AbstractDataStore to ensure that values being both read and written at the same time. The [] operators are not capable of protecting against data corruption.
- In situation where values are only being read from the array, the [] operators are both safe and faster to use.
DataPath triangleAreasDataPath = geometryPath.createChildPath(triangleFaceDataGroupName).createChildPath("Triangle Areas");
auto preflightResult = filter.preflight(dataGraph, args);
if(preflightResult.outputActions.invalid())
{
for(const auto& error : preflightResult.outputActions.errors())
{
std::cout << error.code << ": " << error.message << std::endl;
}
}
Previously inside of SIMPL one would have done the following to get the raw pointer to the data stored in a DataArray:
float* vertex = triangleGeom->getVertexPointer(0);
and then used the []
notation to get and set values. With the possibility of out-of-core
being added there is no guarantee that the data would exist at a given pointer offset in memory.
Instead the developer should use:
AbstractGeometry::SharedVertexList& vertex = *(triangleGeom->getVertices());
Note the use of a Reference Variable instead of the pointer. The developer can still use
code such as vertex[index]
to get/set a value but the code vertex = i
to move a pointer
will not work.
If you need to have the user select a Geometry then you should use a GeometrySelectionParameter
.
params.insert(std::make_unique<GeometrySelectionParameter>(k_GridGeomPath_Key, "Input Image Geometry", "DataPath to input Image Geometry", DataPath{},
GeometrySelectionParameter::AllowedTypes{IGeometry::Type::Image}));
There are several filters (those that create a new geometry from an existing one) where the user is allowed to "transfer" data from the source geometry onto the newly created geometry. QuickSurfaceMeshFilter and PointSampleTriangleGeometryFilter both are examples of how to perform this transfer of data.
There are several classes that can be used to help the developer write parallel algorithms.
simplnx/Utilities/ParallelAlgorithm
and simplnx/Utilities/ParallelTaskAlgorithm
are the two main classes depending
on the situation. AlignSections.cpp
and CropImageGeometryFilter.cpp
both use a task based
parallelism. RotateSampleRefFrameFilter.cpp
shows an example
of using ParallelData3DAlgorithm.
#include "simplnx/Common/Numbers.hpp"
and use it this way:
double foo = nx::core::numbers::k_180OverPi * 232.0;
All filters give you access to the MessageHandler class that sends status, progress, error and warning messages back to the user.
This example uses the fmt
library to format a message of type Info
and send it back to the user interface.
m_MessageHandler(IFilter::Message::Type::Info, fmt::format("Iteration {} of {}", q, m_InputValues->pIterationSteps));
This example shows how to send back progress. The integer argument is a value between 0 and 100 where 0 is just starting and 100 is fully complete.
m_MessageHandler(IFilter::Message::Type::Progress, progressMessage, static_cast<int32_t>(progressInt));
If you have a filter that needs to create an array in something like a cell attribute matrix or a feature attribute matrix then the following filters have examples.
- TriangleNormalFilter
- ComputeFeatureSizesFilter
You have code that does this:
EXECUTE_FUNCTION_TEMPLATE(this, Detail::ExecuteTemplate, m_InArrayPtr.lock(), this, m_InArrayPtr.lock());
and now you are porting that to simplnx
. The old Detail::ExecuteTemplate
needs to be converted into a "struct" based
functor like the following:
struct ExecuteTemplate
{
template <typename T>
void operator()([ARGUMENTS GO HERE], const std::atomic_bool& shouldCancel, const IFilter::MessageHandler& messageHandler)
{
.... your code goes here
}
then you replace the macro with the following template function:
ExecuteDataFunction(ExecuteTemplate{}, srcIDataArray.getDataType(), [ARGUMENTS GO HERE], m_ShouldCancel, m_MessageHandler);
The first 2 arguments to the above function are used by the function, any additional arguments are passed directly to your functor implementation.
- Create Filter class in "PLUGIN_NAME/src/PLUGIN_NAME/Filters/xxxxFilter[.hpp|.cpp]"
- Update Plugin's top level CMakeLists.txt to include the filter
- Create Algorithm class in "PLUGIN_NAME/src/PLUGIN_NAME/Filters/Algorithms/xxxxFilter[.hpp|.cpp]"
- Update Plugin's top level CMakeLists.txt to include the algorithm
- Ensure the UUID is the proper UUID from the know mappings file.
Use proper grouping in the parameters to help the User Interface.
There are potentially 3 sections of parameters:
params.insertSeparator(Parameters::Separator{"Input Parameter(s)"});
params.insertSeparator(Parameters::Separator{"Input Data Objects"});
params.insertSeparator(Parameters::Separator{"Output Output Data Objects"});
these should be used as needed by the filter.
Sometimes a filter needs allow the user to process it's geometry "in place" in order to ease the number of filters that are needed to remove temporary DataObjects. If your filter needs this kind of capability, then take a look at the "CropImageGeometryFilter" or "RotateSampleRefFrame" filters.