diff --git a/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp b/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp index 712b3801a0..57481158cb 100644 --- a/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp +++ b/src/Plugins/SimplnxCore/wrapping/python/simplnxpy.cpp @@ -689,16 +689,44 @@ PYBIND11_MODULE(simplnx, mod) py::class_> rectGridGeom(mod, "RectGridGeom"); py::class_> iNodeGeometry0D(mod, "INodeGeometry0D"); + iNodeGeometry0D.def( + "resize_vertices", + [](INodeGeometry0D& nodeGeometry0D, usize size) { + nodeGeometry0D.resizeVertexList(size); + nodeGeometry0D.getVertexAttributeMatrix()->resizeTuples({size}); + }, + "This will resize the shared vertex list and also resize the associated attribute matrix"); py::class_> vertexGeom(mod, "VertexGeom"); py::class_> iNodeGeometry1D(mod, "INodeGeometry1D"); + iNodeGeometry1D.def( + "resize_edges", + [](INodeGeometry1D& nodeGeometry1D, usize size) { + nodeGeometry1D.resizeEdgeList(size); + nodeGeometry1D.getEdgeAttributeMatrix()->resizeTuples({size}); + }, + "This will resize the shared edge list and also resize the associated attribute matrix"); py::class_> edgeGeom(mod, "EdgeGeom"); py::class_> iNodeGeometry2D(mod, "INodeGeometry2D"); + iNodeGeometry2D.def( + "resize_faces", + [](INodeGeometry2D& nodeGeometry2D, usize size) { + nodeGeometry2D.resizeFaceList(size); + nodeGeometry2D.getEdgeAttributeMatrix()->resizeTuples({size}); + }, + "This will resize the shared triangle list and also resize the associated attribute matrix"); py::class_> triangleGeom(mod, "TriangleGeom"); py::class_> quadGeom(mod, "QuadGeom"); py::class_> iNodeGeometry3D(mod, "INodeGeometry3D"); + iNodeGeometry3D.def( + "resize_polyhedra", + [](INodeGeometry3D& nodeGeometry3D, usize size) { + nodeGeometry3D.resizePolyhedraList(size); + nodeGeometry3D.getPolyhedraAttributeMatrix()->resizeTuples({size}); + }, + "This will resize the shared polyhedra list and also resize the associated attribute matrix"); py::class_> tetrahedralGeom(mod, "TetrahedralGeom"); py::class_> hexahedralGeom(mod, "HexahedralGeom"); diff --git a/src/nxrunner/src/nxrunner.cpp b/src/nxrunner/src/nxrunner.cpp index 67971499e1..c7c93d438d 100644 --- a/src/nxrunner/src/nxrunner.cpp +++ b/src/nxrunner/src/nxrunner.cpp @@ -481,8 +481,11 @@ std::vector GetPythonPluginList() { return {}; } - - return StringUtilities::split(var, ';'); +#if defined(Q_OS_WIN) + return nx::core::StringUtilities::split(var, ';'); +#else + return nx::core::StringUtilities::split(var, ':'); +#endif } #endif } // namespace diff --git a/src/simplnx/Filter/IFilter.cpp b/src/simplnx/Filter/IFilter.cpp index f10553811b..47a17c04a0 100644 --- a/src/simplnx/Filter/IFilter.cpp +++ b/src/simplnx/Filter/IFilter.cpp @@ -225,18 +225,21 @@ IFilter::ExecuteResult IFilter::execute(DataStructure& dataStructure, const Argu return {MakeErrorResult(-1, "Filter cancelled")}; } - Result<> validGeometryAndAttributeMatrices = MergeResults(dataStructure.validateGeometries(), dataStructure.validateAttributeMatrices()); - validGeometryAndAttributeMatrices = MergeResults(validGeometryAndAttributeMatrices, executeImplResult); - Result<> preflightActionsExecuteResult = MergeResults(std::move(preflightActionsResult), std::move(validGeometryAndAttributeMatrices)); + Result<> preflightActionsExecuteResult = MergeResults(std::move(preflightActionsResult), std::move(executeImplResult)); if(preflightActionsExecuteResult.invalid()) { return ExecuteResult{std::move(preflightActionsExecuteResult), std::move(preflightResult.outputValues)}; } - + // Apply any deferred actions Result<> deferredActionsResult = outputActions.applyDeferred(dataStructure, IDataAction::Mode::Execute); - Result<> finalResult = MergeResults(std::move(preflightActionsExecuteResult), std::move(deferredActionsResult)); + // Validate the Geometry and Attribute Matrix objects + Result<> validGeometryAndAttributeMatrices = MergeResults(dataStructure.validateGeometries(), dataStructure.validateAttributeMatrices()); + validGeometryAndAttributeMatrices = MergeResults(validGeometryAndAttributeMatrices, deferredActionsResult); + + // Merge all the results together. + Result<> finalResult = MergeResults(std::move(preflightActionsExecuteResult), std::move(validGeometryAndAttributeMatrices)); return ExecuteResult{std::move(finalResult), std::move(preflightResult.outputValues)}; } diff --git a/wrapping/python/docs/source/ReleaseNotes_127.rst b/wrapping/python/docs/source/ReleaseNotes_127.rst index c880fea9be..a4b4443b21 100644 --- a/wrapping/python/docs/source/ReleaseNotes_127.rst +++ b/wrapping/python/docs/source/ReleaseNotes_127.rst @@ -14,7 +14,10 @@ Version 1.2.7 API Changes & Additions 1.2.7 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -The ColorTableParameter API has changed. Please see either the developer or user documentation for more details. +- The ColorTableParameter API has changed. Please see either the developer or user documentation for more details. +- A few filters have changed their name +- DataPath has had more API take from parts of PathLib. See the documentation for the new API additions +- Node based geometries allow the resizing of their internal data structures using the `resize_*` methods. Change Log 1.2.7 ^^^^^^^^^^^^^^^^^^^^ diff --git a/wrapping/python/docs/source/Writing_A_New_Python_Filter.rst b/wrapping/python/docs/source/Writing_A_New_Python_Filter.rst index 1cfd6ac8a4..c146a0a066 100644 --- a/wrapping/python/docs/source/Writing_A_New_Python_Filter.rst +++ b/wrapping/python/docs/source/Writing_A_New_Python_Filter.rst @@ -452,4 +452,38 @@ performed. message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Calculating Histogram Counts and Bin Bounds...')) -For more Python filter examples, check out the `ExamplePlugin `_. \ No newline at end of file +For more Python filter examples, check out the `ExamplePlugin `_. + +10. Debugging the Python Filter. +-------------------------------- + + Running the python filter through the DREAM3D-NX user interface will not allow you any opportunity to use a debugger to inspect troublesome code. For this + you will need to implement a separate python file that dynamically loads the python based plugin and then executes your filter with the proper arguments. + The below code is the bare minimum that you will need to implement. + + .. code-block:: python + + from typing import List + import simplnx as nx + + # ------------------------------------------------------------------------------ + # Replace NAME_OF_YOUR_PLUGIN with the actual name of your plugin + # Replace FILTER_NAME with the name of your filter that you would like to debug + + import NAME_OF_YOUR_PLUGIN + nx.load_python_plugin(NAME_OF_YOUR_PLUGIN) + import NAME_OF_YOUR_PLUGIN.FILTER_NAME + + # Create a Data Structure + data_structure = nx.DataStructure() + # Wrap the python filter in this "proxy" class from the target plugin so we can use it. + pynx_filter = nx.PyFilter(NAME_OF_YOUR_PLUGIN.FILTER_NAME()) + # Execute the filter and check the result. We use the `execute2()` method to run the filter. + # Make sure to use all appropriate arguments to your filter. The named arguments are the values + # of each of the parameter keys that are defined at the top of the filter. For instance if you + # have this line: + # INPUT_IMAGE_ARRAY_KEY = 'input_image_array' + # then you would use 'input_image_array' as the named argument in the call to `execute2()` method + result = pynx_filter.execute2(data_structure=data_structure, + ..... ) + diff --git a/wrapping/python/plugins/DataAnalysisToolkit/CliReaderFilter.py b/wrapping/python/plugins/DataAnalysisToolkit/CliReaderFilter.py index 138888368d..5daccc131c 100644 --- a/wrapping/python/plugins/DataAnalysisToolkit/CliReaderFilter.py +++ b/wrapping/python/plugins/DataAnalysisToolkit/CliReaderFilter.py @@ -57,6 +57,11 @@ def preflight_impl(self, data_structure: nx.DataStructure, args: dict, message_h shared_vertices_array_name: str = args[CliReaderFilter.SHARED_VERTICES_ARRAY_NAME] shared_edges_array_name: str = args[CliReaderFilter.SHARED_EDGES_ARRAY_NAME] + + # Here we create the Edge Geometry (and the 2 internal Attribute Matrix to hold vertex and edge data arrays.) + # Because this is a "reader" type of filter we do not know (at least in this reader implementation) + # the number of vertices or edges at preflight time. During execute we will need to ensure that + # everything is sized correctly. output_actions = nx.OutputActions() output_actions.append_action(nx.CreateEdgeGeometryAction(geometry_path=output_edge_geom_path, num_edges=1, num_vertices=1, vertex_attribute_matrix_name=output_vertex_attrmat_name, edge_attribute_matrix_name=output_edge_attrmat_name, shared_vertices_name=shared_vertices_array_name, shared_edges_name=shared_edges_array_name)) @@ -87,6 +92,7 @@ def execute_impl(self, data_structure: nx.DataStructure, args: dict, message_han output_edge_geom_path: nx.DataPath = args[CliReaderFilter.OUTPUT_EDGE_GEOM_PATH] output_edge_attrmat_name: str = args[CliReaderFilter.OUTPUT_EDGE_ATTRMAT_NAME] output_feature_attrmat_name: str = args[CliReaderFilter.OUTPUT_FEATURE_ATTRMAT_NAME] + output_vertex_attrmat_name: str = args[CliReaderFilter.OUTPUT_VERTEX_ATTRMAT_NAME] layer_features = [] @@ -124,33 +130,43 @@ def execute_impl(self, data_structure: nx.DataStructure, args: dict, message_han edge_geom: nx.EdgeGeom = data_structure[output_edge_geom_path] + # Tell the Edge Geometry to resize the shared vertex list so that we can + # copy in the vertices. message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Saving Vertex List...')) vertex_list = [item for pair in zip(start_vertices, end_vertices) for item in pair] + edge_geom.resize_vertices(len(vertex_list)) + vertices_array = edge_geom.vertices - vertices_array.resize_tuples([len(vertex_list)]) vertices_view = vertices_array.store.npview() vertices_view[:] = vertex_list + # Tell the Edge Geometry to resize the shared edge list so that we can + # copy in the edge list and also copy in all the edge arrays message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f'Saving Edges...')) + edge_geom.resize_edges(num_of_hatches) edges_array = edge_geom.edges - edges_array.resize_tuples([num_of_hatches]) edges_view = edges_array.store.npview() edges_view[:] = [[i, i+1] for i in range(0, len(vertex_list), 2)] + # Get the nx.DataPath to the Edge Attribute Matrix edge_attr_mat_path = output_edge_geom_path.create_child_path(output_edge_attrmat_name) + # Copy the all the edge data into the edge attribute matrix for array_name, values in data_arrays.items(): message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f"Saving Cell Array '{array_name}'...")) array_path = edge_attr_mat_path.create_child_path(array_name) array: nx.IDataArray = data_structure[array_path] - array.resize_tuples([num_of_hatches]) values_arr = np.array(values) values_arr = values_arr.reshape([len(values)] + array.cdims) array_view = array.store.npview() array_view[:] = values_arr + + # Save the feature level data feature_attr_mat_path = output_edge_geom_path.create_child_path(output_feature_attrmat_name) label_feature_array_path = feature_attr_mat_path.create_child_path(self.LABEL_ARRAY_NAME) label_feature_array: nx.StringArray = data_structure[label_feature_array_path] message_handler(nx.IFilter.Message(nx.IFilter.Message.Type.Info, f"Saving Feature Array '{self.LABEL_ARRAY_NAME}'...")) label_feature_array.initialize_with_list(list(hatch_labels.values())) + + # Filter is complete, return the results. return nx.Result() \ No newline at end of file diff --git a/wrapping/python/plugins/ExamplePlugin/ExampleFilter1.py b/wrapping/python/plugins/ExamplePlugin/ExampleFilter1.py index 6c14731728..799e96bde0 100644 --- a/wrapping/python/plugins/ExamplePlugin/ExampleFilter1.py +++ b/wrapping/python/plugins/ExamplePlugin/ExampleFilter1.py @@ -79,7 +79,7 @@ def clone(self): """ return ExampleFilter1() - def preflight_impl(self, data_structure: nx.DataStructure, args: dict, message_handler: nx.IFilter.MessageHandler, should_cancel: nx.AtomicBoolProxy) -> nx.IFilter.PreflightResult: + def parameters(self) -> nx.Parameters: """This function defines the parameters that are needed by the filter. Parameters collect the values from the user or through a pipeline file. """ diff --git a/wrapping/python/plugins/debugging_helper.py b/wrapping/python/plugins/debugging_helper.py new file mode 100644 index 0000000000..647e5aa311 --- /dev/null +++ b/wrapping/python/plugins/debugging_helper.py @@ -0,0 +1,99 @@ +""" +This code can be used to debug your python based DREAM3D-NX filter. There are a +number of bits of code that you will need to change in order for you to be able +to debug. + + +""" +from typing import List +import simplnx as nx + +# ------------------------------------------------------------------------------ +# This NEEDS to be executed here so that we can load the python based plugin +# +# You will need to REPLACE the name of the plugin and the name of the filter +# that you are trying to debug in the next 3 lines of code +# ------------------------------------------------------------------------------ +import DataAnalysisToolkit +nx.load_python_plugin(DataAnalysisToolkit) +import DataAnalysisToolkit.CliReaderFilter + + +""" +The below are convenience functions that you can use to check the result of running +preflight or execute on the filter or execute on a loaded pipeline. You should +NOT have to change anything these functions. +""" +# ------------------------------------------------------------------------------ +# check_filter_execute_result +# ------------------------------------------------------------------------------ +def check_filter_preflight_result(filter: nx.IFilter, result: nx.IFilter.PreflightResult) -> None: + """This function will check the result of a filter's preflight method""" + has_errors = len(result.get_result()) != 0 + if has_errors: + print(f'{filter.name()} :: Errors: {result.get_result()}') + raise RuntimeError(result) + + print(f"{filter.name()} :: No errors preflighting the filter") + +# ------------------------------------------------------------------------------ +# check_filter_execute_result +# ------------------------------------------------------------------------------ +def check_filter_execute_result(filter: nx.IFilter, result: nx.IFilter.ExecuteResult) -> None: + """This function will check the result of a filter's execute method.""" + if len(result.warnings) != 0: + print(f'{filter.name()} :: Warnings: {result.warnings}') + + has_errors = len(result.errors) != 0 + if has_errors: + print(f'{filter.name()} :: Errors: {result.errors}') + raise RuntimeError(result) + + print(f"{filter.name()} :: No errors running the filter") + +# ------------------------------------------------------------------------------ +# check_pipeline_execute_result +# ------------------------------------------------------------------------------ +def check_pipeline_execute_result(result: nx.IFilter.ExecuteResult) -> None: + """This method will check the result of a pipeline's execute method""" + if len(result.warnings) != 0: + print(f'{filter.name()} :: Warnings: {result.warnings}') + + has_errors = len(result.errors) != 0 + if has_errors: + print(f'{filter.name()} :: Errors: {result.errors}') + raise RuntimeError(result) + + print(f"Pipeline :: No errors running the pipeline") + + +# ***************************************************************************** +# This section is where you will need to programmatically execute what ever +# needs to be done to prep your filter to run. This may involve programmatically +# running filters one after another or loading a pipeline to prep the DataStructure +# and then running your filter. Take a look at the Examples/scripts and +# Examples/pipelines for examples to do that. +# ***************************************************************************** + + +# Create a Data Structure +data_structure = nx.DataStructure() + +# Wrap the python filter in this "proxy" class from the target plugin so we can use it. +pynx_filter = nx.PyFilter(DataAnalysisToolkit.CliReaderFilter()) + +# Execute the filter and check the result. We use the `execute2()` method to +# run the filter. +result = pynx_filter.execute2(data_structure=data_structure, + cli_file_path="/paht/to/input/file.cli") +check_filter_execute_result(pynx_filter, result) + +# ------------------------------------------------------------------------------ +# If we want to check the results of the filter, we can save this file to a +# dream3d file and load the .dream3d file directly into DREAM3D-NX to see the +# immediate results. +# ------------------------------------------------------------------------------ +result = nx.WriteDREAM3DFilter.execute(data_structure=data_structure, + export_file_path="/path/to/output/file.dream3d", + write_xdmf_file=False) +check_filter_execute_result(nx.WriteDREAM3DFilter, result)