From 5cb47e36929d9e9c64be92507e67d30d0aaf3588 Mon Sep 17 00:00:00 2001 From: Andrea Settimi Date: Mon, 18 Nov 2024 14:03:35 +0100 Subject: [PATCH] General PR Patch (#138) * FIX: got rid of cloudtocloud away from publication * FIX:catch up with main * ADD-WIP: unsure about this commit, leftover mods * FIX-ADD: no more distances in csv export, added distances export option as separate * ADD: extra commodity for automatic added bool toggles * MILESTONE-ADD: new category results with import/export/inspect results + load cloud fix * FIX: convert pickle to JSON serialization * FIX: missing import * FIX: correct exception RML message --- assets/icon_pool/icon_export_ply.xcf | Bin 0 -> 10683 bytes .../icon_pool/icon_export_serialization.xcf | Bin 0 -> 10671 bytes .../icon_export_serialization_v2.xcf | Bin 0 -> 11852 bytes .../icon_import_serialization_v1.xcf | Bin 0 -> 13621 bytes assets/icon_pool/icon_inspect_results.xcf | Bin 0 -> 8822 bytes assets/icon_pool/icon_results.xcf | Bin 0 -> 11852 bytes assets/icon_pool/normal_v1.png | Bin 0 -> 8949 bytes deps/eigen | 2 +- deps/pybind11 | 2 +- doc/gh_DFExportCloudToFile.rst | 8 + doc/gh_DFExportResults.rst | 8 + doc/gh_DFImportResults.rst | 8 + doc/gh_DFInspectResults.rst | 8 + doc/gh_components.rst | 16 +- pyproject.toml | 9 +- src/diffCheck/IOManager.cc | 9 + src/diffCheck/IOManager.hh | 7 + src/diffCheck/geometry/DFPointCloud.cc | 6 + src/diffCheck/geometry/DFPointCloud.hh | 7 + src/diffCheckBindings.cc | 2 + src/gh/components/DF_csv_exporter/code.py | 146 ++++++++++-- .../components/DF_csv_exporter/metadata.json | 12 + .../DF_export_cloud_to_file/code.py | 74 ++++++ .../DF_export_cloud_to_file/icon.png | Bin 0 -> 10448 bytes .../DF_export_cloud_to_file/metadata.json | 55 +++++ src/gh/components/DF_export_results/code.py | 58 +++++ src/gh/components/DF_export_results/icon.png | Bin 0 -> 11600 bytes .../DF_export_results/metadata.json | 55 +++++ src/gh/components/DF_import_results/code.py | 15 ++ src/gh/components/DF_import_results/icon.png | Bin 0 -> 11459 bytes .../DF_import_results/metadata.json | 40 ++++ src/gh/components/DF_inspect_results/code.py | 19 ++ src/gh/components/DF_inspect_results/icon.png | Bin 0 -> 11884 bytes .../DF_inspect_results/metadata.json | 104 +++++++++ .../DF_load_cloud_from_file/code.py | 6 +- src/gh/diffCheck/diffCheck/df_cvt_bindings.py | 28 +++ .../diffCheck/df_error_estimation.py | 97 +++++++- src/gh/diffCheck/diffCheck/df_geometries.py | 212 +++++++++++++++--- .../pybinds_tests/test_pybind_units.py | 9 + tests/unit_tests/DFPointCloudTest.cc | 10 + 40 files changed, 970 insertions(+), 62 deletions(-) create mode 100644 assets/icon_pool/icon_export_ply.xcf create mode 100644 assets/icon_pool/icon_export_serialization.xcf create mode 100644 assets/icon_pool/icon_export_serialization_v2.xcf create mode 100644 assets/icon_pool/icon_import_serialization_v1.xcf create mode 100644 assets/icon_pool/icon_inspect_results.xcf create mode 100644 assets/icon_pool/icon_results.xcf create mode 100644 assets/icon_pool/normal_v1.png create mode 100644 doc/gh_DFExportCloudToFile.rst create mode 100644 doc/gh_DFExportResults.rst create mode 100644 doc/gh_DFImportResults.rst create mode 100644 doc/gh_DFInspectResults.rst create mode 100644 src/gh/components/DF_export_cloud_to_file/code.py create mode 100644 src/gh/components/DF_export_cloud_to_file/icon.png create mode 100644 src/gh/components/DF_export_cloud_to_file/metadata.json create mode 100644 src/gh/components/DF_export_results/code.py create mode 100644 src/gh/components/DF_export_results/icon.png create mode 100644 src/gh/components/DF_export_results/metadata.json create mode 100644 src/gh/components/DF_import_results/code.py create mode 100644 src/gh/components/DF_import_results/icon.png create mode 100644 src/gh/components/DF_import_results/metadata.json create mode 100644 src/gh/components/DF_inspect_results/code.py create mode 100644 src/gh/components/DF_inspect_results/icon.png create mode 100644 src/gh/components/DF_inspect_results/metadata.json diff --git a/assets/icon_pool/icon_export_ply.xcf b/assets/icon_pool/icon_export_ply.xcf new file mode 100644 index 0000000000000000000000000000000000000000..4b91e29b81217208031349d4cba4fa9769625198 GIT binary patch literal 10683 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 Cw(hV1 literal 0 HcmV?d00001 diff --git a/assets/icon_pool/icon_export_serialization.xcf b/assets/icon_pool/icon_export_serialization.xcf new file mode 100644 index 0000000000000000000000000000000000000000..8537fe7826893a3cca51e933d294d6b94f0fee6b GIT binary patch literal 10671 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

hJPYe|Sg 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 literal 0 HcmV?d00001 diff --git a/assets/icon_pool/icon_export_serialization_v2.xcf b/assets/icon_pool/icon_export_serialization_v2.xcf new file mode 100644 index 0000000000000000000000000000000000000000..a9482bd349e8336b6cf314abb614884bb659b9b8 GIT binary patch literal 11852 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 literal 0 HcmV?d00001 diff --git a/assets/icon_pool/icon_import_serialization_v1.xcf b/assets/icon_pool/icon_import_serialization_v1.xcf new file mode 100644 index 0000000000000000000000000000000000000000..486ce23fcb67ac557a21027ec030790bbeaa78f8 GIT binary patch literal 13621 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 literal 0 HcmV?d00001 diff --git a/assets/icon_pool/icon_inspect_results.xcf b/assets/icon_pool/icon_inspect_results.xcf new file mode 100644 index 0000000000000000000000000000000000000000..79d231286af05cd1df08f20017a1dbe2507504e2 GIT binary patch literal 8822 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 O?Ssx6+6T!E?LPqQptD8* literal 0 HcmV?d00001 diff --git a/assets/icon_pool/icon_results.xcf b/assets/icon_pool/icon_results.xcf new file mode 100644 index 0000000000000000000000000000000000000000..575248228096a4528e69a0b58d1130abe9067d69 GIT binary patch literal 11852 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 literal 0 HcmV?d00001 diff --git a/deps/eigen b/deps/eigen index 7ad7c1d5..8b4efc8e 160000 --- a/deps/eigen +++ b/deps/eigen @@ -1 +1 @@ -Subproject commit 7ad7c1d5c59ab0bf87f83003283f0cc8357789bd +Subproject commit 8b4efc8ed8a65415e248d54fbc9afdd964c94f64 diff --git a/deps/pybind11 b/deps/pybind11 index 7e418f49..0ed20f26 160000 --- a/deps/pybind11 +++ b/deps/pybind11 @@ -1 +1 @@ -Subproject commit 7e418f49243bb7d13fa92cf2634af1eeac386465 +Subproject commit 0ed20f26acee626ac989568ecc6347e159ddbb47 diff --git a/doc/gh_DFExportCloudToFile.rst b/doc/gh_DFExportCloudToFile.rst new file mode 100644 index 00000000..164331a4 --- /dev/null +++ b/doc/gh_DFExportCloudToFile.rst @@ -0,0 +1,8 @@ +.. image:: ../src/gh/components/DF_export_cloud_to_file/icon.png + :align: left + :width: 40px + +``DFExportCloudToFile`` component +================================= + +.. ghcomponent_to_rst:: ../src/gh/components/DF_export_cloud_to_file \ No newline at end of file diff --git a/doc/gh_DFExportResults.rst b/doc/gh_DFExportResults.rst new file mode 100644 index 00000000..bf5fb73e --- /dev/null +++ b/doc/gh_DFExportResults.rst @@ -0,0 +1,8 @@ +.. image:: ../src/gh/components/DF_export_results/icon.png + :align: left + :width: 40px + +``DFExportResults`` component +============================= + +.. ghcomponent_to_rst:: ../src/gh/components/DF_export_results \ No newline at end of file diff --git a/doc/gh_DFImportResults.rst b/doc/gh_DFImportResults.rst new file mode 100644 index 00000000..affbc98a --- /dev/null +++ b/doc/gh_DFImportResults.rst @@ -0,0 +1,8 @@ +.. image:: ../src/gh/components/DF_import_results/icon.png + :align: left + :width: 40px + +``DFImportResults`` component +============================= + +.. ghcomponent_to_rst:: ../src/gh/components/DF_import_results \ No newline at end of file diff --git a/doc/gh_DFInspectResults.rst b/doc/gh_DFInspectResults.rst new file mode 100644 index 00000000..5d5e243c --- /dev/null +++ b/doc/gh_DFInspectResults.rst @@ -0,0 +1,8 @@ +.. image:: ../src/gh/components/DF_inspect_results/icon.png + :align: left + :width: 40px + +``DFInspectResults`` component +============================== + +.. ghcomponent_to_rst:: ../src/gh/components/DF_inspect_results \ No newline at end of file diff --git a/doc/gh_components.rst b/doc/gh_components.rst index 08828e02..e668b9d8 100644 --- a/doc/gh_components.rst +++ b/doc/gh_components.rst @@ -81,6 +81,16 @@ DF has a Grasshopper_ plugin with a set of components that allows the user to in - .. image:: ../src/gh/components/DF_cloud_voxel_downsample/icon.png - `gh_DFCloudVoxelDownsample `_ + * - .. image:: ../src/gh/components/DF_export_cloud_to_file/icon.png + - `DFExportCloudToFile `_ + - .. image:: ../src/gh/components/DF_export_results/icon.png + - `DFExportResults `_ + + * - .. image:: ../src/gh/components/DF_import_results/icon.png + - `DFImportResults `_ + - .. image:: ../src/gh/components/DF_inspect_results/icon.png + - `DFInspectResults `_ + .. toctree:: :maxdepth: 1 @@ -113,4 +123,8 @@ DF has a Grasshopper_ plugin with a set of components that allows the user to in gh_DFColorizeCloud gh_DFBrepToCloud gh_DFRemoveStatisticalOutliers - gh_DFMergeAssemblies \ No newline at end of file + gh_DFMergeAssemblies + gh_DFExportCloudToFile + gh_DFExportResults + gh_DFImportResults + gh_DFInspectResults \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 0fb1af09..1b7616fe 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,10 +29,13 @@ module = "pefile" ignore_missing_imports = true [[tool.mypy.overrides]] -module = "diffCheck" -ignore_undefined_attributes = true - +module = "df_geometries" +disable_error_code = "annotation-unchecked" +check_untyped_defs = false +[[tool.mypy.overrides]] +module = "src.gh.diffCheck.diffCheck.df_geometries" +disable_error_code = "annotation-unchecked" [tool.ruff] exclude = [ diff --git a/src/diffCheck/IOManager.cc b/src/diffCheck/IOManager.cc index 8abc2970..2a857950 100644 --- a/src/diffCheck/IOManager.cc +++ b/src/diffCheck/IOManager.cc @@ -28,6 +28,15 @@ namespace diffCheck::io return mesh; } + void WritePLYPointCloud(const std::shared_ptr &pointCloud, const std::string &filename) + { + auto open3dPointCloud = pointCloud->Cvt2O3DPointCloud(); + open3d::io::WritePointCloudToPLY( + filename, + *open3dPointCloud, + open3d::io::WritePointCloudOption()); + } + std::string GetTestDataDir() { // for github action conviniency diff --git a/src/diffCheck/IOManager.hh b/src/diffCheck/IOManager.hh index 95a745cf..e22c029d 100644 --- a/src/diffCheck/IOManager.hh +++ b/src/diffCheck/IOManager.hh @@ -25,6 +25,13 @@ namespace diffCheck::io */ std::shared_ptr ReadPLYMeshFromFile(const std::string &filename); + /** + * @brief Write a point cloud to a file as a PLY + * + * @param pointCloud the point cloud to write + * @param filename the path to the file with the extension + */ + void WritePLYPointCloud(const std::shared_ptr &pointCloud, const std::string &filename); ////////////////////////////////////////////////////////////////////////// // IO for test suite and tests data diff --git a/src/diffCheck/geometry/DFPointCloud.cc b/src/diffCheck/geometry/DFPointCloud.cc index 5a4bd742..d00fac65 100644 --- a/src/diffCheck/geometry/DFPointCloud.cc +++ b/src/diffCheck/geometry/DFPointCloud.cc @@ -277,6 +277,12 @@ namespace diffCheck::geometry this->Normals = cloud->Normals; } + void DFPointCloud::SaveToPLY(const std::string &path) + { + auto cloud_ptr = std::make_shared(this->Points, this->Colors, this->Normals); + diffCheck::io::WritePLYPointCloud(cloud_ptr, path); + } + std::vector DFPointCloud::ComputeDistance(std::shared_ptr target) { std::vector errors; diff --git a/src/diffCheck/geometry/DFPointCloud.hh b/src/diffCheck/geometry/DFPointCloud.hh index 62f512d4..b3f0a3be 100644 --- a/src/diffCheck/geometry/DFPointCloud.hh +++ b/src/diffCheck/geometry/DFPointCloud.hh @@ -153,6 +153,13 @@ namespace diffCheck::geometry */ void LoadFromPLY(const std::string &path); + /** + * @brief Save a point cloud to a file as a PLY + * + * @param filename the path to the file with the extension + */ + void SaveToPLY(const std::string &path); + public: ///< Distance calculations /** * @brief Compute the distance between two point clouds. diff --git a/src/diffCheckBindings.cc b/src/diffCheckBindings.cc index 606e31d7..434f5da2 100644 --- a/src/diffCheckBindings.cc +++ b/src/diffCheckBindings.cc @@ -56,6 +56,8 @@ PYBIND11_MODULE(diffcheck_bindings, m) { py::arg("nb_neighbors"), py::arg("std_ratio")) .def("load_from_PLY", &diffCheck::geometry::DFPointCloud::LoadFromPLY) + .def("save_to_PLY", &diffCheck::geometry::DFPointCloud::SaveToPLY) + .def("add_points", &diffCheck::geometry::DFPointCloud::AddPoints) .def("get_tight_bounding_box", &diffCheck::geometry::DFPointCloud::GetTightBoundingBox) diff --git a/src/gh/components/DF_csv_exporter/code.py b/src/gh/components/DF_csv_exporter/code.py index 0f1e4ca0..877944dd 100644 --- a/src/gh/components/DF_csv_exporter/code.py +++ b/src/gh/components/DF_csv_exporter/code.py @@ -1,11 +1,45 @@ #! python3 +import System +import csv +import os +import typing from ghpythonlib.componentbase import executingcomponent as component +import Grasshopper as gh -from diffCheck.df_error_estimation import DFInvalidData -import csv -import os +from diffCheck.df_error_estimation import DFInvalidData, DFVizResults + + +def add_bool_toggle(self, + nickname: str, + indx: int, + X_param_coord: float, + Y_param_coord: float, + X_offset: int=87 + ) -> None: + """ + Adds a boolean toggle to the component input + + :param nickname: the nickname of the value list + :param indx: the index of the input parameter + :param X_param_coord: the x coordinate of the input parameter + :param Y_param_coord: the y coordinate of the input parameter + :param X_offset: the offset of the value list from the input parameter + """ + param = ghenv.Component.Params.Input[indx] # noqa: F821 + if param.SourceCount == 0: + toggle = gh.Kernel.Special.GH_BooleanToggle() + toggle.NickName = nickname + toggle.Description = "Toggle the value to use with DFVizSettings" + toggle.CreateAttributes() + toggle.Attributes.Pivot = System.Drawing.PointF( + X_param_coord - (toggle.Attributes.Bounds.Width) - X_offset, + Y_param_coord - (toggle.Attributes.Bounds.Height / 2 + 0.1) + ) + toggle.Attributes.ExpireLayout() + gh.Instances.ActiveCanvas.Document.AddObject(toggle, False) + ghenv.Component.Params.Input[indx].AddSource(toggle) # noqa: F821 class DFCsvExporter(component): @@ -14,7 +48,28 @@ def __init__(self): self.prefix = "" self.counter = 0 - def _get_id(self, idx, i_result): + ghenv.Component.ExpireSolution(True) # noqa: F821 + ghenv.Component.Attributes.PerformLayout() # noqa: F821 + params = getattr(ghenv.Component.Params, "Input") # noqa: F821 + for j in range(len(params)): + Y_cord = params[j].Attributes.InputGrip.Y + 1 + X_cord = params[j].Attributes.Pivot.X + 10 + input_indx = j + if "i_export_seperate_files" == params[j].NickName: + add_bool_toggle( + ghenv.Component, # noqa: F821 + "export_asfiles", + input_indx, X_cord, Y_cord) + if "i_export_distances" == params[j].NickName: + add_bool_toggle( + ghenv.Component, # noqa: F821 + "export_dist", + input_indx, X_cord, Y_cord) + + def _get_id(self, + idx: int, + i_result: DFVizResults + ) -> str: """ Get the ID of the element """ counter = 0 @@ -34,15 +89,18 @@ def _get_id(self, idx, i_result): return f"{idx_b}--{idx_j}--{idx_f}" counter += 1 - def _write_csv(self, file_path, rows): - """ Write the CSV file """ - with open(file_path, mode='w', newline='') as file: - writer = csv.writer(file) - writer.writerow([f"{self.prefix} id", "distances", "min_deviation", "max_deviation", "std_deviation", "rmse", "mean"]) - writer.writerows(rows) + def _prepare_row(self, + idx: int, + i_result: DFVizResults + ) -> typing.Dict: + """ + Convert the results contained in the DFVizResults object to a dict to be written in the CSV file - def _prepare_row(self, idx, i_result): - """ Prepare a row for the CSV file """ + :param idx: Index of the element + :param i_result: DFVizResults object containing all the values + + :return: Dict of values containng as keys the header and as items the values to be written in the CSV file + """ if i_result.sanity_check[idx].value != DFInvalidData.VALID.value: invalid_type = i_result.sanity_check[idx].name return [self._get_id(idx, i_result), invalid_type, invalid_type, invalid_type, invalid_type, invalid_type, invalid_type] @@ -53,16 +111,57 @@ def _prepare_row(self, idx, i_result): std_dev = round(i_result.distances_sd_deviation[idx], 4) rmse = round(i_result.distances_rmse[idx], 4) mean = round(i_result.distances_mean[idx], 4) - distances_str = ";".join(map(str, distances)) - return [self._get_id(idx, i_result), distances_str, min_dev, max_dev, std_dev, rmse, mean] + + row: typing.Dict = { + f"{self.prefix} id": self._get_id(idx, i_result), + "distances": distances, + "min_deviation": min_dev, + "max_deviation": max_dev, + "std_deviation": std_dev, + "rmse": rmse, + "mean": mean + } + return row + + def _write_csv(self, + csv_path: str, + rows: typing.List[typing.Dict], + is_writing_only_distances: bool = False + ) -> None: + """ + Write the CSV file + + :param csv_path: Path of the CSV file + :param rows: Dict of values to be written in the CSV file + :param is_writing_only_distances: Flag to check if to write ONLY distances or the whole analysis + + :return: None + """ + with open(csv_path, mode='w', newline='') as file: + writer = csv.writer(file, quoting=csv.QUOTE_MINIMAL) + if is_writing_only_distances: + writer.writerow(list(rows[0].keys())[:2]) # header + element_id = [row[f"{self.prefix} id"] for row in rows] + dist_rows = [row["distances"] for row in rows] + for idx, dist_row in enumerate(dist_rows): + for dist in dist_row: + writer.writerow([element_id[idx], dist]) + else: + rows = [{k: v for k, v in row.items() if k != "distances"} for row in rows] # no distances + writer.writerow(list(rows[0].keys())) # header + writer.writerows([list(row.values()) for row in rows]) def RunScript(self, i_dump: bool, i_export_dir: str, i_file_name: str, i_export_seperate_files: bool, + i_export_distances: bool, i_result): + csv_analysis_path: str = None + csv_distances_path: str = None + if i_dump: os.makedirs(i_export_dir, exist_ok=True) @@ -75,10 +174,17 @@ def RunScript(self, if i_export_seperate_files: for idx in range(len(i_result.source)): - element_id = self._get_id( idx, i_result) - file_path = os.path.join(i_export_dir, f"{i_file_name}_{self.prefix}_{element_id}.csv") - self._write_csv(file_path, [self._prepare_row(idx, i_result)]) + element_id = self._get_id(idx, i_result) + csv_analysis_path = os.path.join(i_export_dir, f"{i_file_name}_{self.prefix}_{element_id}.csv") + rows = [self._prepare_row(idx, i_result)] + self._write_csv(csv_analysis_path, rows) + if i_export_distances: + csv_distances_path = os.path.join(i_export_dir, f"{i_file_name}_{self.prefix}_{element_id}_distances.csv") + self._write_csv(csv_distances_path, rows, is_writing_only_distances=True) else: - file_path = os.path.join(i_export_dir, f"{i_file_name}.csv") - rows = [self._prepare_row(idx, i_result) for idx in range(len(i_result.source))] - self._write_csv(file_path, rows) + csv_analysis_path = os.path.join(i_export_dir, f"{i_file_name}.csv") + merged_rows = [self._prepare_row(idx, i_result) for idx in range(len(i_result.source))] + self._write_csv(csv_analysis_path, merged_rows) + if i_export_distances: + csv_distances_path = os.path.join(i_export_dir, f"{i_file_name}_distances.csv") + self._write_csv(csv_distances_path, merged_rows, is_writing_only_distances=True) diff --git a/src/gh/components/DF_csv_exporter/metadata.json b/src/gh/components/DF_csv_exporter/metadata.json index 149de386..d1cfbc99 100644 --- a/src/gh/components/DF_csv_exporter/metadata.json +++ b/src/gh/components/DF_csv_exporter/metadata.json @@ -61,6 +61,18 @@ "sourceCount": 0, "typeHintID": "bool" }, + { + "name": "i_export_distances", + "nickname": "i_export_distances", + "description": "Whether to export the calculated error distances for each point of the analysed point cloud.", + "optional": true, + "allowTreeAccess": true, + "showTypeHints": true, + "scriptParamAccess": "item", + "wireDisplay": "default", + "sourceCount": 0, + "typeHintID": "bool" + }, { "name": "i_result", "nickname": "i_result", diff --git a/src/gh/components/DF_export_cloud_to_file/code.py b/src/gh/components/DF_export_cloud_to_file/code.py new file mode 100644 index 00000000..a81ab7ef --- /dev/null +++ b/src/gh/components/DF_export_cloud_to_file/code.py @@ -0,0 +1,74 @@ +#! python3 + +import System + +import Rhino # noqa: F401 +import Rhino.Geometry as rg +from ghpythonlib.componentbase import executingcomponent as component + +from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML +import Grasshopper as gh + +from diffCheck import diffcheck_bindings +from diffCheck import df_cvt_bindings + + +def add_button(self, + nickname: str, + indx: int, + X_param_coord: float, + Y_param_coord: float, + X_offset: int=45 + ) -> None: + """ + Adds a button to the component input + + :param nickname: the nickname of the button + :param indx: the index of the input parameter + :param X_param_coord: the x coordinate of the input parameter + :param Y_param_coord: the y coordinate of the input parameter + :param X_offset: the offset of the button from the input parameter + """ + param = ghenv.Component.Params.Input[indx] # noqa: F821 + if param.SourceCount == 0: + button = gh.Kernel.Special.GH_ButtonObject() + button.NickName = "" + button.Description = "" + button.CreateAttributes() + button.Attributes.Pivot = System.Drawing.PointF( + X_param_coord - (button.Attributes.Bounds.Width) - X_offset, + Y_param_coord - (button.Attributes.Bounds.Height / 2 - 0.1) + ) + button.Attributes.ExpireLayout() + gh.Instances.ActiveCanvas.Document.AddObject(button, False) + ghenv.Component.Params.Input[indx].AddSource(button) # noqa: F821 + +class DFExportCloudToFile(component): + def __init__(self): + super(DFExportCloudToFile, self).__init__() + ghenv.Component.ExpireSolution(True) # noqa: F821 + ghenv.Component.Attributes.PerformLayout() # noqa: F821 + params = getattr(ghenv.Component.Params, "Input") # noqa: F821 + for j in range(len(params)): + X_cord = params[j].Attributes.Pivot.X + Y_cord = params[j].Attributes.InputGrip.Y + if params[j].Name == "i_dump": + add_button(self, "", j, X_cord, Y_cord) + + def RunScript(self, + i_dump: bool, + i_file_path: str, + i_cloud: rg.PointCloud) -> None: + if i_dump is None or i_file_path is None or i_cloud is None: + return None + + # check that the i_file_path is a valid path and it has the .ply extension + if not i_file_path.endswith(".ply"): + ghenv.Component.AddRuntimeMessage(RML.Warning, "Attention: the format should be .ply") # noqa: F821 + return None + + if i_dump: + df_cloud: diffcheck_bindings.dfb_geometry.DFPointCloud = df_cvt_bindings.cvt_rhcloud_2_dfcloud(i_cloud) + df_cloud.save_to_PLY(i_file_path) + + return None diff --git a/src/gh/components/DF_export_cloud_to_file/icon.png b/src/gh/components/DF_export_cloud_to_file/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..091afe791d76d8c278dfb38e49c1160825067463 GIT binary patch literal 10448 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 None: + """ + Adds a button to the component input + + :param nickname: the nickname of the button + :param indx: the index of the input parameter + :param X_param_coord: the x coordinate of the input parameter + :param Y_param_coord: the y coordinate of the input parameter + :param X_offset: the offset of the button from the input parameter + """ + param = ghenv.Component.Params.Input[indx] # noqa: F821 + if param.SourceCount == 0: + button = gh.Kernel.Special.GH_ButtonObject() + button.NickName = "" + button.Description = "" + button.CreateAttributes() + button.Attributes.Pivot = System.Drawing.PointF( + X_param_coord - (button.Attributes.Bounds.Width) - X_offset, + Y_param_coord - (button.Attributes.Bounds.Height / 2 - 0.1) + ) + button.Attributes.ExpireLayout() + gh.Instances.ActiveCanvas.Document.AddObject(button, False) + ghenv.Component.Params.Input[indx].AddSource(button) # noqa: F821 + +class DFExportResults(component): + def __init__(self): + super(DFExportResults, self).__init__() + ghenv.Component.ExpireSolution(True) # noqa: F821 + ghenv.Component.Attributes.PerformLayout() # noqa: F821 + params = getattr(ghenv.Component.Params, "Input") # noqa: F821 + for j in range(len(params)): + X_cord = params[j].Attributes.Pivot.X + Y_cord = params[j].Attributes.InputGrip.Y + if params[j].Name == "i_dump": + add_button(self, "", j, X_cord, Y_cord) + + def RunScript(self, i_dump: bool, i_export_dir: str, i_results): + if i_dump is None or i_export_dir is None or i_results is None: + return None + + if i_dump: + i_results.dump_serialization(i_export_dir) + + return None diff --git a/src/gh/components/DF_export_results/icon.png b/src/gh/components/DF_export_results/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..43163e2ec01c78f9178cf273e6b3541afab8fb51 GIT binary patch literal 11600 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 VczaPQ;O8AgOHEI;O4&B-zW}(F#w7p% literal 0 HcmV?d00001 diff --git a/src/gh/components/DF_export_results/metadata.json b/src/gh/components/DF_export_results/metadata.json new file mode 100644 index 00000000..15978295 --- /dev/null +++ b/src/gh/components/DF_export_results/metadata.json @@ -0,0 +1,55 @@ +{ + "name": "DFExportResults", + "nickname": "DFExportResults", + "category": "diffCheck", + "subcategory": "Results", + "description": "It saves the computed DF results in a file .diffCheck to be exported later. This is done by pickling/serializing the DFVizResults object.", + "exposure": 4, + "instanceGuid": "fbc0c1be-1485-4167-98f4-9c6106852a90", + "ghpython": { + "hideOutput": true, + "hideInput": true, + "isAdvancedMode": true, + "marshalOutGuids": true, + "iconDisplay": 2, + "inputParameters": [ + { + "name": "i_dump", + "nickname": "i_dump", + "description": "Save it!", + "optional": false, + "allowTreeAccess": true, + "showTypeHints": true, + "scriptParamAccess": "item", + "wireDisplay": "default", + "sourceCount": 0, + "typeHintID": "bool" + }, + { + "name": "i_export_dir", + "nickname": "i_export_dir", + "description": "The directory where the .diffCheck file will be saved.", + "optional": true, + "allowTreeAccess": true, + "showTypeHints": true, + "scriptParamAccess": "item", + "wireDisplay": "default", + "sourceCount": 0, + "typeHintID": "str" + }, + { + "name": "i_results", + "nickname": "i_results", + "description": "The DFVizResults object to be saved.", + "optional": false, + "allowTreeAccess": true, + "showTypeHints": true, + "scriptParamAccess": "item", + "wireDisplay": "default", + "sourceCount": 0, + "typeHintID": "ghdoc" + } + ], + "outputParameters": [] + } +} \ No newline at end of file diff --git a/src/gh/components/DF_import_results/code.py b/src/gh/components/DF_import_results/code.py new file mode 100644 index 00000000..2ded2a2c --- /dev/null +++ b/src/gh/components/DF_import_results/code.py @@ -0,0 +1,15 @@ +#! python3 + +from ghpythonlib.componentbase import executingcomponent as component + +from diffCheck.df_error_estimation import DFVizResults + + +class DFImportResults(component): + def RunScript(self, i_import_path: str): + if i_import_path is None: + return None + + o_results = DFVizResults.load_serialization(i_import_path) + + return o_results diff --git a/src/gh/components/DF_import_results/icon.png b/src/gh/components/DF_import_results/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e6a315a988cee25f7770b15153a508e990c7946b GIT binary patch literal 11459 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 rg.PointCloud: - # import and convert to Rhino Cloud + if i_path is None: + return None + if i_scalef is None: + i_scalef = 1.0 + df_cloud = diffcheck_bindings.dfb_geometry.DFPointCloud() df_cloud.load_from_PLY(i_path) rh_cloud = df_cvt_bindings.cvt_dfcloud_2_rhcloud(df_cloud) diff --git a/src/gh/diffCheck/diffCheck/df_cvt_bindings.py b/src/gh/diffCheck/diffCheck/df_cvt_bindings.py index c6016b37..59e34c27 100644 --- a/src/gh/diffCheck/diffCheck/df_cvt_bindings.py +++ b/src/gh/diffCheck/diffCheck/df_cvt_bindings.py @@ -260,3 +260,31 @@ def cvt_ndarray_2_rh_transform(matrix) -> rg.Transform: transfo.M32 = matrix[3, 2] transfo.M33 = matrix[3, 3] return transfo + + +def cvt_dfcloud_2_dict(df_cloud: diffcheck_bindings.dfb_geometry.DFPointCloud) -> dict: + """ + Convert a diffCheck cloud to a dictionary mainly for pickling and serialization. + + :param df_cloud: diffCheck cloud + :return cloud_dict: the cloud dictionary + """ + cloud_dict = { + "points": df_cloud.points, + "normals": df_cloud.normals, + "colors": df_cloud.colors + } + return cloud_dict + +def cvt_dict_2_dfcloud(cloud_dict: dict) -> diffcheck_bindings.dfb_geometry.DFPointCloud: + """ + Convert a dictionary to a diffCheck cloud mainly for pickling and deserialization. + + :param cloud_dict: the cloud dictionary + :return df_cloud: diffCheck cloud + """ + df_cloud = diffcheck_bindings.dfb_geometry.DFPointCloud() + df_cloud.points = cloud_dict["points"] + df_cloud.normals = cloud_dict["normals"] + df_cloud.colors = cloud_dict["colors"] + return df_cloud diff --git a/src/gh/diffCheck/diffCheck/df_error_estimation.py b/src/gh/diffCheck/diffCheck/df_error_estimation.py index 7a9776f1..4607e1e6 100644 --- a/src/gh/diffCheck/diffCheck/df_error_estimation.py +++ b/src/gh/diffCheck/diffCheck/df_error_estimation.py @@ -5,17 +5,29 @@ import typing from enum import Enum +from datetime import datetime +import os + +import json import numpy as np import Rhino import Rhino.Geometry as rg +from Rhino.FileIO import SerializationOptions from diffCheck import diffcheck_bindings # type: ignore from diffCheck import df_cvt_bindings from diffCheck.df_geometries import DFAssembly +class NumpyEncoder(json.JSONEncoder): + """ Special json encoder for numpy ndarray types. """ + def default(self, obj): + if isinstance(obj, np.ndarray): + return obj.tolist() + return super().default(obj) + class DFInvalidData(Enum): """ Enum to define the type of invalid data for joint or assembly analysis @@ -31,11 +43,16 @@ class DFVizResults: """ This class compiles the resluts of the error estimation into one object """ + __serial_file_extenion: str = ".diffCheck" def __init__(self, assembly): - self.source = [] - self.target = [] + self.assembly: DFAssembly = assembly + self.source: typing.List[diffcheck_bindings.dfb_geometry.DFPointCloud] = [] + self.target: typing.List[Rhino.Geometry.Mesh] = [] + + self.sanity_check: typing.List[DFInvalidData] = [] + self._is_source_cloud = True # if False it's a mesh self.distances_mean = [] self.distances_rmse = [] @@ -43,11 +60,81 @@ def __init__(self, assembly): self.distances_min_deviation = [] self.distances_sd_deviation = [] self.distances = [] - self.assembly = assembly - self.sanity_check = [] + def __repr__(self): + return f"DFVizResults of({self.assembly})" + + def __getstate__(self): + state = self.__dict__.copy() + if "assembly" in state and state["assembly"] is not None: + state["assembly"] = self.assembly.__getstate__() + if "source" in state and state["source"] is not None: + state["source"] = [df_cvt_bindings.cvt_dfcloud_2_dict(pcd) for pcd in state["source"]] + if "target" in state and state["target"] is not None: + state["target"] = [mesh.ToJSON(SerializationOptions()) for mesh in state["target"]] + if "sanity_check" in state and state["sanity_check"] is not None: + state["sanity_check"] = [s.value if isinstance(s, DFInvalidData) else s for s in self.sanity_check] + return state + + def __setstate__(self, state: typing.Dict): + if "assembly" in state and state["assembly"] is not None: + assembly = DFAssembly.__new__(DFAssembly) + assembly.__setstate__(state["assembly"]) + state["assembly"] = assembly + if "source" in state and state["source"] is not None: + source = [] + for pcd_dict in state["source"]: + pcd = diffcheck_bindings.dfb_geometry.DFPointCloud() + pcd = df_cvt_bindings.cvt_dict_2_dfcloud(pcd_dict) + source.append(pcd) + state["source"] = source + if "target" in state and state["target"] is not None: + target = [] + for mesh_json in state["target"]: + mesh = rg.Mesh() + mesh = mesh.FromJSON(mesh_json) + target.append(mesh) + state["target"] = target + if "sanity_check" in state and state["sanity_check"] is not None: + state["sanity_check"] = [DFInvalidData(s) for s in state["sanity_check"]] + self.__dict__.update(state) + + def dump_serialization(self, dir: str) -> str: + """ Dump the results into a JSON file for serialization """ + if not os.path.exists(os.path.dirname(dir)): + try: + os.makedirs(os.path.dirname(dir)) + except OSError as exc: + raise exc + + timestamp: str = datetime.now().strftime("%Y%m%d_%H%M%S") + assembly_name: str = self.assembly.name + serial_file_path = os.path.join(dir, f"{assembly_name}_{timestamp}{self.__serial_file_extenion}") + + try: + with open(serial_file_path, "w") as f: + json.dump(self.__getstate__(), f, cls=NumpyEncoder) + except Exception as e: + raise e + + return serial_file_path + + @staticmethod + def load_serialization(file_path: str) -> 'DFVizResults': + """ Load the results from a JSON file """ + if not os.path.exists(file_path): + raise FileNotFoundError(f"File {file_path} not found") + if not file_path.endswith(DFVizResults.__serial_file_extenion): + raise ValueError(f"File {file_path} is not a valid diffCheck file") + try: + with open(file_path, "r") as f: + state = json.load(f) + obj = DFVizResults.__new__(DFVizResults) + obj.__setstate__(state) + except Exception as e: + raise e + return obj - self._is_source_cloud = True # if False it's a mesh def add(self, source, target, distances, sanity_check: DFInvalidData = DFInvalidData.VALID): diff --git a/src/gh/diffCheck/diffCheck/df_geometries.py b/src/gh/diffCheck/diffCheck/df_geometries.py index f87701fd..44e502a6 100644 --- a/src/gh/diffCheck/diffCheck/df_geometries.py +++ b/src/gh/diffCheck/diffCheck/df_geometries.py @@ -9,6 +9,7 @@ import Rhino import Rhino.Geometry as rg +from Rhino.FileIO import SerializationOptions from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML @@ -35,6 +36,12 @@ def __post_init__(self): self.__uuid = uuid.uuid4().int + def __getstate__(self): + return self.__dict__ + + def __setstate__(self, state: typing.Dict): + self.__dict__.update(state) + def __repr__(self): return f"Vertex: X={self.x}, Y={self.y}, Z={self.z}" @@ -83,17 +90,40 @@ class DFFace: joint_id: Optional[int] = None def __post_init__(self): - self.all_loops = self.all_loops - - self.joint_id = self.joint_id + self.all_loops: typing.List[typing.List[DFVertex]] = self.all_loops + self.joint_id: Optional[int] = self.joint_id self.__is_joint = False self.__uuid = uuid.uuid4().int - # if df_face is created from a rhino brep face, we store the rhino brep face - self._rh_brepface = None - + self._rh_brepface: rg.BrepFace = None self.is_roundwood = False + def __getstate__(self): + state = self.__dict__.copy() + if "all_loops" in state and state["all_loops"] is not None: + state["all_loops"] = [[vertex.__getstate__() for vertex in loop] for loop in state["all_loops"]] + # note: rg.BrepFaces cannot be serialized, so we need to convert it to a Surface >> JSON >> brep >> brepface (and vice versa) + if "_rh_brepface" in state and state["_rh_brepface"] is not None: + state["_rh_brepface"] = self.to_brep_face().DuplicateFace(True).ToJSON(SerializationOptions()) + return state + + def __setstate__(self, state: typing.Dict): + if "all_loops" in state and state["all_loops"] is not None: + all_loops = [] + for loop_state in state["all_loops"]: + loop = [DFVertex.__new__(DFVertex) for _ in loop_state] + for vertex, vertex_state in zip(loop, loop_state): + vertex.__setstate__(vertex_state) + all_loops.append(loop) + state["all_loops"] = all_loops + # note: rg.BrepFaces cannot be serialized, so we need to convert it to a Surface >> JSON >> brep >> brepface (and vice versa) + if "_rh_brepface" in state and state["_rh_brepface"] is not None: + state["_rh_brepface"] = rg.Surface.FromJSON(state["_rh_brepface"]).Faces[0] + self.__dict__.update(state) + if self._rh_brepface is not None: + self.from_brep_face(self._rh_brepface, self.joint_id) + + def __repr__(self): return f"Face id: {(self.id)}, IsJoint: {self.is_joint} Loops: {len(self.all_loops)}" @@ -227,6 +257,22 @@ def __post_init__(self): # this is an automatic identifier self.__uuid = uuid.uuid4().int + def __getstate__(self): + state = self.__dict__.copy() + if "faces" in state and state["faces"] is not None: + state["faces"] = [face.__getstate__() for face in self.faces] + return state + + def __setstate__(self, state: typing.Dict): + if "faces" in state and state["faces"] is not None: + faces = [] + for face_state in state["faces"]: + face = DFFace.__new__(DFFace) + face.__setstate__(face_state) + faces.append(face) + state["faces"] = faces + self.__dict__.update(state) + def __repr__(self): return f"Joint id: {self.id}, Faces: {len(self.faces)}" @@ -277,28 +323,86 @@ class DFBeam: faces: typing.List[DFFace] def __post_init__(self): - self.name = self.name or "Unnamed Beam" - self.faces = self.faces or [] - self.is_roundwood = False + self.name: str = self.name or "Unnamed Beam" + self.faces: typing.List[DFFace] = self.faces or [] + self.is_roundwood: bool = False - self._joint_faces = [] - self._side_faces = [] - self._vertices = [] + self._joint_faces: typing.List[DFFace] = [] + self._side_faces: typing.List[DFFace] = [] + self._vertices: typing.List[DFVertex] = [] - self._joints = [] + self._joints: typing.List[DFJoint] = [] # this should be used like a hash identifier self.__uuid = uuid.uuid4().int # this index is assigned only when the an beam is added to an assembly - self._index_assembly = None + self._index_assembly: int = None - self._center = None + self._center: rg.Point3d = None self.__id = uuid.uuid4().int + def __getstate__(self): + state = self.__dict__.copy() + if "faces" in state and state["faces"] is not None: + state["faces"] = [face.__getstate__() for face in self.faces] + if "_joint_faces" in state and state["_joint_faces"] is not None: + state["_joint_faces"] = [face.__getstate__() for face in state["_joint_faces"]] + if "_side_faces" in state and state["_side_faces"] is not None: + state["_side_faces"] = [face.__getstate__() for face in state["_side_faces"]] + if "_vertices" in state and state["_vertices"] is not None: + state["_vertices"] = [vertex.__getstate__() for vertex in state["_vertices"]] + if "_joints" in state and state["_joints"] is not None: + state["_joints"] = [joint.__getstate__() for joint in state["_joints"]] + if "_center" in state and state["_center"] is not None: + state["_center"] = self._center.ToJSON(SerializationOptions()) + return state + + def __setstate__(self, state: typing.Dict): + if "faces" in state and state["faces"] is not None: + faces = [] + for face_state in state["faces"]: + face = DFFace.__new__(DFFace) + face.__setstate__(face_state) + faces.append(face) + state["faces"] = faces + if "_joint_faces" in state and state["_joint_faces"] is not None: + joint_faces = [] + for face_state in state["_joint_faces"]: + face = DFFace.__new__(DFFace) + face.__setstate__(face_state) + joint_faces.append(face) + state["_joint_faces"] = joint_faces + if "_side_faces" in state and state["_side_faces"] is not None: + side_faces = [] + for face_state in state["_side_faces"]: + face = DFFace.__new__(DFFace) + face.__setstate__(face_state) + side_faces.append(face) + state["_side_faces"] = side_faces + if "_vertices" in state and state["_vertices"] is not None: + vertices = [] + for vertex_state in state["_vertices"]: + vertex = DFVertex.__new__(DFVertex) + vertex.__setstate__(vertex_state) + vertices.append(vertex) + state["_vertices"] = vertices + if "_joints" in state and state["_joints"] is not None: + joints = [] + for joint_state in state["_joints"]: + joint = DFJoint.__new__(DFJoint) + joint.__setstate__(joint_state) + joints.append(joint) + state["_joints"] = joints + if "_center" in state and state["_center"] is not None: + state["_center"] = rg.Point3d.FromJSON(state["_center"]) + self.__dict__.update(state) + + def __repr__(self): + return f"Beam: {self.name}, Faces: {len(self.faces)}" + def deepcopy(self): return DFBeam(self.name, [face.deepcopy() for face in self.faces]) - @classmethod def from_brep_face(cls, brep, is_roundwood=False): """ @@ -346,9 +450,6 @@ def to_mesh(self, max_edge_length): mesh.Compact() return mesh - def __repr__(self): - return f"Beam: {self.name}, Faces: {len(self.faces)}" - @property def uuid(self): return self.__uuid @@ -408,27 +509,78 @@ class DFAssembly: name: str def __post_init__(self): - self.beams = self.beams + self.beams: typing.List[DFBeam] = self.beams for idx, beam in enumerate(self.beams): beam._index_assembly = idx - self.__uuid = uuid.uuid4().int + self.__uuid: int = uuid.uuid4().int - self.name = self.name or "Unnamed Assembly" + self.name: str = self.name or "Unnamed Assembly" self._all_jointfaces: typing.List[DFFace] = [] self._all_sidefaces: typing.List[DFFace] = [] self._all_vertices: typing.List[DFVertex] = [] self._all_joints: typing.List[DFJoint] = [] - for beam in self.beams: - if beam.is_roundwood: - self.contains_cylinders = True - break - else: - self.contains_cylinders = False - - self._mass_center = None + self.contains_cylinders: bool = any(beam.is_roundwood for beam in self.beams) + + self._mass_center: rg.Point3d = None + + def __getstate__(self): + state = self.__dict__.copy() + if "beams" in state and state["beams"] is not None: + state["beams"] = [beam.__getstate__() for beam in self.beams] + if "_mass_center" in state and state["_mass_center"] is not None: + state["_mass_center"] = self._mass_center.ToJSON(SerializationOptions()) + if "_all_jointfaces" in state and state["_all_jointfaces"] is not None: + state["_all_jointfaces"] = [face.__getstate__() for face in state["_all_jointfaces"]] + if "_all_sidefaces" in state and state["_all_sidefaces"] is not None: + state["_all_sidefaces"] = [face.__getstate__() for face in state["_all_sidefaces"]] + if "_all_vertices" in state and state["_all_vertices"] is not None: + state["_all_vertices"] = [vertex.__getstate__() for vertex in state["_all_vertices"]] + if "_all_joints" in state and state["_all_joints"] is not None: + state["_all_joints"] = [joint.__getstate__() for joint in state["_all_joints"]] + return state + + def __setstate__(self, state: typing.Dict): + if "beams" in state and state["beams"] is not None: + beams = [] + for beam_state in state["beams"]: + beam = DFBeam.__new__(DFBeam) + beam.__setstate__(beam_state) + beams.append(beam) + state["beams"] = beams + if "_mass_center" in state and state["_mass_center"] is not None: + state["_mass_center"] = rg.Point3d.FromJSON(state["_mass_center"]) + if "_all_jointfaces" in state and state["_all_jointfaces"] is not None: + joint_faces = [] + for face_state in state["_all_jointfaces"]: + face = DFFace.__new__(DFFace) + face.__setstate__(face_state) + joint_faces.append(face) + state["_all_jointfaces"] = joint_faces + if "_all_sidefaces" in state and state["_all_sidefaces"] is not None: + side_faces = [] + for face_state in state["_all_sidefaces"]: + face = DFFace.__new__(DFFace) + face.__setstate__(face_state) + side_faces.append(face) + state["_all_sidefaces"] = side_faces + if "_all_vertices" in state and state["_all_vertices"] is not None: + vertices = [] + for vertex_state in state["_all_vertices"]: + vertex = DFVertex.__new__(DFVertex) + vertex.__setstate__(vertex_state) + vertices.append(vertex) + state["_all_vertices"] = vertices + if "_all_joints" in state and state["_all_joints"] is not None: + joints = [] + for joint_state in state["_all_joints"]: + joint = DFJoint.__new__(DFJoint) + joint.__setstate__(joint_state) + joints.append(joint) + state["_all_joints"] = joints + self.__dict__.update(state) def __repr__(self): return f"Assembly: {self.name}, Beams: {len(self.beams)}" diff --git a/tests/integration_tests/pybinds_tests/test_pybind_units.py b/tests/integration_tests/pybinds_tests/test_pybind_units.py index 5397f4ec..6caed2d9 100644 --- a/tests/integration_tests/pybinds_tests/test_pybind_units.py +++ b/tests/integration_tests/pybinds_tests/test_pybind_units.py @@ -90,6 +90,15 @@ def test_DFPointCloud_load_from_PLY(): assert pc.normals.__len__() == 7379, "DFPointCloud should have 7379 normals" assert pc.colors.__len__() == 7379, "DFPointCloud should have 7379 colors" +def test_DFPointCloud_save_to_PLY(): + pc = dfb.dfb_geometry.DFPointCloud() + pc.load_from_PLY(get_ply_cloud_roof_quarter_path()) + + temp_ply_path = os.path.join(os.path.dirname(__file__), "temp_ply.ply") + pc.save_to_PLY(temp_ply_path) + assert os.path.exists(temp_ply_path), "The PLY file should be saved to the specified path" + os.remove(temp_ply_path) + @pytest.fixture def create_DFPointCloudSampleRoof(): df_pcd = dfb.dfb_geometry.DFPointCloud() diff --git a/tests/unit_tests/DFPointCloudTest.cc b/tests/unit_tests/DFPointCloudTest.cc index 6cd19791..d6d669d5 100644 --- a/tests/unit_tests/DFPointCloudTest.cc +++ b/tests/unit_tests/DFPointCloudTest.cc @@ -59,6 +59,16 @@ TEST_F(DFPointCloudTestFixture, LoadFromPLY) { EXPECT_EQ(dfPointCloud.GetNumNormals(), 7379); } +TEST_F(DFPointCloudTestFixture, SaveToPLY) { + std::string path = "./test_save.ply"; + dfPointCloud.SaveToPLY(path); + diffCheck::geometry::DFPointCloud dfPointCloud2; + dfPointCloud2.LoadFromPLY(path); + EXPECT_EQ(dfPointCloud.GetNumPoints(), dfPointCloud2.GetNumPoints()); + EXPECT_EQ(dfPointCloud.GetNumColors(), dfPointCloud2.GetNumColors()); + EXPECT_EQ(dfPointCloud.GetNumNormals(), dfPointCloud2.GetNumNormals()); +} + //------------------------------------------------------------------------- // properties //-------------------------------------------------------------------------