From 9339c6e2fd297983c4ff6cd0dbd22a6e9a57b549 Mon Sep 17 00:00:00 2001 From: Jan Lebert Date: Sun, 21 Jan 2024 22:17:19 -0800 Subject: [PATCH] Add pathlib support for IO functions --- cpp/pybind/io/class_io.cpp | 121 +++++++++++----------- cpp/pybind/open3d_pybind.h | 3 + cpp/pybind/pybind_filesystem.h | 109 +++++++++++++++++++ cpp/pybind/t/io/class_io.cpp | 27 +++-- cpp/pybind/visualization/renderoption.cpp | 8 +- cpp/pybind/visualization/utility.cpp | 8 +- python/test/io/test_pathlib.py | 21 ++++ 7 files changed, 215 insertions(+), 82 deletions(-) create mode 100644 cpp/pybind/pybind_filesystem.h create mode 100644 python/test/io/test_pathlib.py diff --git a/cpp/pybind/io/class_io.cpp b/cpp/pybind/io/class_io.cpp index 0602f93e577a..4c450d8ea341 100644 --- a/cpp/pybind/io/class_io.cpp +++ b/cpp/pybind/io/class_io.cpp @@ -105,10 +105,10 @@ void pybind_class_io(py::module &m_io) { // open3d::geometry::Image m_io.def( "read_image", - [](const std::string &filename) { + [](const fs::path &filename) { py::gil_scoped_release release; geometry::Image image; - ReadImage(filename, image); + ReadImage(filename.string(), image); return image; }, "Function to read Image from file", "filename"_a); @@ -117,10 +117,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_image", - [](const std::string &filename, const geometry::Image &image, + [](const fs::path &filename, const geometry::Image &image, int quality) { py::gil_scoped_release release; - return WriteImage(filename, image, quality); + return WriteImage(filename.string(), image, quality); }, "Function to write Image to file", "filename"_a, "image"_a, "quality"_a = kOpen3DImageIODefaultQuality); @@ -130,11 +130,12 @@ void pybind_class_io(py::module &m_io) { // open3d::geometry::LineSet m_io.def( "read_line_set", - [](const std::string &filename, const std::string &format, + [](const fs::path &filename, const std::string &format, bool print_progress) { py::gil_scoped_release release; geometry::LineSet line_set; - ReadLineSet(filename, line_set, format, print_progress); + ReadLineSet(filename.string(), line_set, format, + print_progress); return line_set; }, "Function to read LineSet from file", "filename"_a, @@ -144,11 +145,11 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_line_set", - [](const std::string &filename, const geometry::LineSet &line_set, + [](const fs::path &filename, const geometry::LineSet &line_set, bool write_ascii, bool compressed, bool print_progress) { py::gil_scoped_release release; - return WriteLineSet(filename, line_set, write_ascii, compressed, - print_progress); + return WriteLineSet(filename.string(), line_set, write_ascii, + compressed, print_progress); }, "Function to write LineSet to file", "filename"_a, "line_set"_a, "write_ascii"_a = false, "compressed"_a = false, @@ -159,12 +160,12 @@ void pybind_class_io(py::module &m_io) { // open3d::geometry::PointCloud m_io.def( "read_point_cloud", - [](const std::string &filename, const std::string &format, + [](const fs::path &filename, const std::string &format, bool remove_nan_points, bool remove_infinite_points, bool print_progress) { py::gil_scoped_release release; geometry::PointCloud pcd; - ReadPointCloud(filename, pcd, + ReadPointCloud(filename.string(), pcd, {format, remove_nan_points, remove_infinite_points, print_progress}); return pcd; @@ -202,13 +203,12 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_point_cloud", - [](const std::string &filename, - const geometry::PointCloud &pointcloud, + [](const fs::path &filename, const geometry::PointCloud &pointcloud, const std::string &format, bool write_ascii, bool compressed, bool print_progress) { py::gil_scoped_release release; return WritePointCloud( - filename, pointcloud, + filename.string(), pointcloud, {format, write_ascii, compressed, print_progress}); }, "Function to write PointCloud to file", "filename"_a, @@ -246,14 +246,14 @@ void pybind_class_io(py::module &m_io) { // open3d::geometry::TriangleMesh m_io.def( "read_triangle_mesh", - [](const std::string &filename, bool enable_post_processing, + [](const fs::path &filename, bool enable_post_processing, bool print_progress) { py::gil_scoped_release release; geometry::TriangleMesh mesh; ReadTriangleMeshOptions opt; opt.enable_post_processing = enable_post_processing; opt.print_progress = print_progress; - ReadTriangleMesh(filename, mesh, opt); + ReadTriangleMesh(filename.string(), mesh, opt); return mesh; }, "Function to read TriangleMesh from file", "filename"_a, @@ -263,12 +263,12 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_triangle_mesh", - [](const std::string &filename, const geometry::TriangleMesh &mesh, + [](const fs::path &filename, const geometry::TriangleMesh &mesh, bool write_ascii, bool compressed, bool write_vertex_normals, bool write_vertex_colors, bool write_triangle_uvs, bool print_progress) { py::gil_scoped_release release; - return WriteTriangleMesh(filename, mesh, write_ascii, + return WriteTriangleMesh(filename.string(), mesh, write_ascii, compressed, write_vertex_normals, write_vertex_colors, write_triangle_uvs, print_progress); @@ -283,12 +283,12 @@ void pybind_class_io(py::module &m_io) { // open3d::visualization::rendering::TriangleMeshModel (Model.h) m_io.def( "read_triangle_model", - [](const std::string &filename, bool print_progress) { + [](const fs::path &filename, bool print_progress) { py::gil_scoped_release release; visualization::rendering::TriangleMeshModel model; ReadTriangleModelOptions opt; opt.print_progress = print_progress; - ReadTriangleModel(filename, model, opt); + ReadTriangleModel(filename.string(), model, opt); return model; }, "Function to read visualization.rendering.TriangleMeshModel from " @@ -300,11 +300,11 @@ void pybind_class_io(py::module &m_io) { // open3d::geometry::VoxelGrid m_io.def( "read_voxel_grid", - [](const std::string &filename, const std::string &format, + [](const fs::path &filename, const std::string &format, bool print_progress) { py::gil_scoped_release release; geometry::VoxelGrid voxel_grid; - ReadVoxelGrid(filename, voxel_grid, format); + ReadVoxelGrid(filename.string(), voxel_grid, format); return voxel_grid; }, "Function to read VoxelGrid from file", "filename"_a, @@ -314,12 +314,11 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_voxel_grid", - [](const std::string &filename, - const geometry::VoxelGrid &voxel_grid, bool write_ascii, - bool compressed, bool print_progress) { + [](const fs::path &filename, const geometry::VoxelGrid &voxel_grid, + bool write_ascii, bool compressed, bool print_progress) { py::gil_scoped_release release; - return WriteVoxelGrid(filename, voxel_grid, write_ascii, - compressed, print_progress); + return WriteVoxelGrid(filename.string(), voxel_grid, + write_ascii, compressed, print_progress); }, "Function to write VoxelGrid to file", "filename"_a, "voxel_grid"_a, "write_ascii"_a = false, "compressed"_a = false, @@ -330,10 +329,10 @@ void pybind_class_io(py::module &m_io) { // open3d::geometry::Octree m_io.def( "read_octree", - [](const std::string &filename, const std::string &format) { + [](const fs::path &filename, const std::string &format) { py::gil_scoped_release release; geometry::Octree octree; - ReadOctree(filename, octree, format); + ReadOctree(filename.string(), octree, format); return octree; }, "Function to read Octree from file", "filename"_a, @@ -343,9 +342,9 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_octree", - [](const std::string &filename, const geometry::Octree &octree) { + [](const fs::path &filename, const geometry::Octree &octree) { py::gil_scoped_release release; - return WriteOctree(filename, octree); + return WriteOctree(filename.string(), octree); }, "Function to write Octree to file", "filename"_a, "octree"_a); docstring::FunctionDocInject(m_io, "write_octree", @@ -354,10 +353,10 @@ void pybind_class_io(py::module &m_io) { // open3d::camera m_io.def( "read_pinhole_camera_intrinsic", - [](const std::string &filename) { + [](const fs::path &filename) { py::gil_scoped_release release; camera::PinholeCameraIntrinsic intrinsic; - ReadIJsonConvertible(filename, intrinsic); + ReadIJsonConvertible(filename.string(), intrinsic); return intrinsic; }, "Function to read PinholeCameraIntrinsic from file", "filename"_a); @@ -366,10 +365,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_pinhole_camera_intrinsic", - [](const std::string &filename, + [](const fs::path &filename, const camera::PinholeCameraIntrinsic &intrinsic) { py::gil_scoped_release release; - return WriteIJsonConvertible(filename, intrinsic); + return WriteIJsonConvertible(filename.string(), intrinsic); }, "Function to write PinholeCameraIntrinsic to file", "filename"_a, "intrinsic"_a); @@ -378,10 +377,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "read_pinhole_camera_parameters", - [](const std::string &filename) { + [](const fs::path &filename) { py::gil_scoped_release release; camera::PinholeCameraParameters parameters; - ReadIJsonConvertible(filename, parameters); + ReadIJsonConvertible(filename.string(), parameters); return parameters; }, "Function to read PinholeCameraParameters from file", "filename"_a); @@ -390,10 +389,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_pinhole_camera_parameters", - [](const std::string &filename, + [](const fs::path &filename, const camera::PinholeCameraParameters ¶meters) { py::gil_scoped_release release; - return WriteIJsonConvertible(filename, parameters); + return WriteIJsonConvertible(filename.string(), parameters); }, "Function to write PinholeCameraParameters to file", "filename"_a, "parameters"_a); @@ -402,10 +401,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "read_pinhole_camera_trajectory", - [](const std::string &filename) { + [](const fs::path &filename) { py::gil_scoped_release release; camera::PinholeCameraTrajectory trajectory; - ReadPinholeCameraTrajectory(filename, trajectory); + ReadPinholeCameraTrajectory(filename.string(), trajectory); return trajectory; }, "Function to read PinholeCameraTrajectory from file", "filename"_a); @@ -414,10 +413,11 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_pinhole_camera_trajectory", - [](const std::string &filename, + [](const fs::path &filename, const camera::PinholeCameraTrajectory &trajectory) { py::gil_scoped_release release; - return WritePinholeCameraTrajectory(filename, trajectory); + return WritePinholeCameraTrajectory(filename.string(), + trajectory); }, "Function to write PinholeCameraTrajectory to file", "filename"_a, "trajectory"_a); @@ -427,10 +427,10 @@ void pybind_class_io(py::module &m_io) { // open3d::registration m_io.def( "read_feature", - [](const std::string &filename) { + [](const fs::path &filename) { py::gil_scoped_release release; pipelines::registration::Feature feature; - ReadFeature(filename, feature); + ReadFeature(filename.string(), feature); return feature; }, "Function to read registration.Feature from file", "filename"_a); @@ -439,10 +439,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_feature", - [](const std::string &filename, + [](const fs::path &filename, const pipelines::registration::Feature &feature) { py::gil_scoped_release release; - return WriteFeature(filename, feature); + return WriteFeature(filename.string(), feature); }, "Function to write Feature to file", "filename"_a, "feature"_a); docstring::FunctionDocInject(m_io, "write_feature", @@ -450,10 +450,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "read_pose_graph", - [](const std::string &filename) { + [](const fs::path &filename) { py::gil_scoped_release release; pipelines::registration::PoseGraph pose_graph; - ReadPoseGraph(filename, pose_graph); + ReadPoseGraph(filename.string(), pose_graph); return pose_graph; }, "Function to read PoseGraph from file", "filename"_a); @@ -462,10 +462,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_pose_graph", - [](const std::string &filename, + [](const fs::path &filename, const pipelines::registration::PoseGraph pose_graph) { py::gil_scoped_release release; - WritePoseGraph(filename, pose_graph); + WritePoseGraph(filename.string(), pose_graph); }, "Function to write PoseGraph to file", "filename"_a, "pose_graph"_a); @@ -475,9 +475,10 @@ void pybind_class_io(py::module &m_io) { #ifdef BUILD_AZURE_KINECT m_io.def( "read_azure_kinect_sensor_config", - [](const std::string &filename) { + [](const fs::path &filename) { AzureKinectSensorConfig config; - bool success = ReadIJsonConvertibleFromJSON(filename, config); + bool success = + ReadIJsonConvertibleFromJSON(filename.string(), config); if (!success) { utility::LogWarning( "Invalid sensor config {}, using default instead", @@ -493,9 +494,8 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_azure_kinect_sensor_config", - [](const std::string &filename, - const AzureKinectSensorConfig config) { - return WriteIJsonConvertibleToJSON(filename, config); + [](const fs::path &filename, const AzureKinectSensorConfig config) { + return WriteIJsonConvertibleToJSON(filename.string(), config); }, "Function to write Azure Kinect sensor config to file", "filename"_a, "config"_a); @@ -504,9 +504,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "read_azure_kinect_mkv_metadata", - [](const std::string &filename) { + [](const fs::path &filename) { MKVMetadata metadata; - bool success = ReadIJsonConvertibleFromJSON(filename, metadata); + bool success = ReadIJsonConvertibleFromJSON(filename.string(), + metadata); if (!success) { utility::LogWarning( "Invalid mkv metadata {}, using default instead", @@ -521,8 +522,8 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_azure_kinect_mkv_metadata", - [](const std::string &filename, const MKVMetadata metadata) { - return WriteIJsonConvertibleToJSON(filename, metadata); + [](const fs::path &filename, const MKVMetadata metadata) { + return WriteIJsonConvertibleToJSON(filename.string(), metadata); }, "Function to write Azure Kinect metadata to file", "filename"_a, "config"_a); diff --git a/cpp/pybind/open3d_pybind.h b/cpp/pybind/open3d_pybind.h index 32380c5af9c6..0d6caaa4b028 100644 --- a/cpp/pybind/open3d_pybind.h +++ b/cpp/pybind/open3d_pybind.h @@ -40,6 +40,9 @@ // every compilation unit. #include "pybind/core/tensor_type_caster.h" +// Replace with when we require C++17. +#include "pybind_filesystem.h" + namespace py = pybind11; using namespace py::literals; diff --git a/cpp/pybind/pybind_filesystem.h b/cpp/pybind/pybind_filesystem.h new file mode 100644 index 000000000000..85028761057a --- /dev/null +++ b/cpp/pybind/pybind_filesystem.h @@ -0,0 +1,109 @@ +// ---------------------------------------------------------------------------- +// - Open3D: www.open3d.org - +// ---------------------------------------------------------------------------- +// Copyright (c) 2018-2023 www.open3d.org +// SPDX-License-Identifier: MIT +// ---------------------------------------------------------------------------- + +// Adapted from to support C++14. +// Original attribution: +// Copyright (c) 2021 The Pybind Development Team. +// All rights reserved. Use of this source code is governed by a +// BSD-style license. + +#pragma once + +#include +#include +#include +#include +#include + +#include + +#ifdef WIN32 +#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING +#endif +#ifdef __APPLE__ +#include +namespace fs = std::__fs::filesystem; +#else +#include +namespace fs = std::experimental::filesystem; +#endif + +namespace pybind11 { +namespace detail { + +template +struct path_caster { +private: + static PyObject *unicode_from_fs_native(const std::string &w) { +#if !defined(PYPY_VERSION) + return PyUnicode_DecodeFSDefaultAndSize(w.c_str(), ssize_t(w.size())); +#else + // PyPy mistakenly declares the first parameter as non-const. + return PyUnicode_DecodeFSDefaultAndSize(const_cast(w.c_str()), + ssize_t(w.size())); +#endif + } + + static PyObject *unicode_from_fs_native(const std::wstring &w) { + return PyUnicode_FromWideChar(w.c_str(), ssize_t(w.size())); + } + +public: + static handle cast(const T &path, return_value_policy, handle) { + if (auto py_str = unicode_from_fs_native(path.native())) { + return module_::import("pathlib") + .attr("Path")(reinterpret_steal(py_str)) + .release(); + } + return nullptr; + } + + bool load(handle handle, bool) { + // PyUnicode_FSConverter and PyUnicode_FSDecoder normally take care of + // calling PyOS_FSPath themselves, but that's broken on PyPy (PyPy + // issue #3168) so we do it ourselves instead. + PyObject *buf = PyOS_FSPath(handle.ptr()); + if (!buf) { + PyErr_Clear(); + return false; + } + PyObject *native = nullptr; + if (std::is_same::value) { + if (PyUnicode_FSConverter(buf, &native) != 0) { + if (auto *c_str = PyBytes_AsString(native)) { + // AsString returns a pointer to the internal buffer, which + // must not be free'd. + value = c_str; + } + } + } else if (std::is_same::value) { + if (PyUnicode_FSDecoder(buf, &native) != 0) { + if (auto *c_str = PyUnicode_AsWideCharString(native, nullptr)) { + // AsWideCharString returns a new string that must be + // free'd. + value = c_str; // Copies the string. + PyMem_Free(c_str); + } + } + } + Py_XDECREF(native); + Py_DECREF(buf); + if (PyErr_Occurred()) { + PyErr_Clear(); + return false; + } + return true; + } + + PYBIND11_TYPE_CASTER(T, const_name("os.PathLike")); +}; + +template <> +struct type_caster : public path_caster {}; + +} // namespace detail +} // namespace pybind11 diff --git a/cpp/pybind/t/io/class_io.cpp b/cpp/pybind/t/io/class_io.cpp index e91b6d350951..0c0087ce7fc1 100644 --- a/cpp/pybind/t/io/class_io.cpp +++ b/cpp/pybind/t/io/class_io.cpp @@ -71,10 +71,10 @@ void pybind_class_io(py::module &m_io) { // open3d::t::geometry::Image m_io.def( "read_image", - [](const std::string &filename) { + [](const fs::path &filename) { py::gil_scoped_release release; geometry::Image image; - ReadImage(filename, image); + ReadImage(filename.string(), image); return image; }, "Function to read image from file.", "filename"_a); @@ -83,10 +83,10 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_image", - [](const std::string &filename, const geometry::Image &image, + [](const fs::path &filename, const geometry::Image &image, int quality) { py::gil_scoped_release release; - return WriteImage(filename, image, quality); + return WriteImage(filename.string(), image, quality); }, "Function to write Image to file.", "filename"_a, "image"_a, "quality"_a = kOpen3DImageIODefaultQuality); @@ -96,12 +96,12 @@ void pybind_class_io(py::module &m_io) { // open3d::t::geometry::PointCloud m_io.def( "read_point_cloud", - [](const std::string &filename, const std::string &format, + [](const fs::path &filename, const std::string &format, bool remove_nan_points, bool remove_infinite_points, bool print_progress) { py::gil_scoped_release release; t::geometry::PointCloud pcd; - ReadPointCloud(filename, pcd, + ReadPointCloud(filename.string(), pcd, {format, remove_nan_points, remove_infinite_points, print_progress}); return pcd; @@ -114,12 +114,12 @@ void pybind_class_io(py::module &m_io) { m_io.def( "write_point_cloud", - [](const std::string &filename, + [](const fs::path &filename, const t::geometry::PointCloud &pointcloud, bool write_ascii, bool compressed, bool print_progress) { py::gil_scoped_release release; return WritePointCloud( - filename, pointcloud, + filename.string(), pointcloud, {write_ascii, compressed, print_progress}); }, "Function to write PointCloud with tensor attributes to file.", @@ -131,14 +131,14 @@ void pybind_class_io(py::module &m_io) { // open3d::geometry::TriangleMesh m_io.def( "read_triangle_mesh", - [](const std::string &filename, bool enable_post_processing, + [](const fs::path &filename, bool enable_post_processing, bool print_progress) { py::gil_scoped_release release; t::geometry::TriangleMesh mesh; open3d::io::ReadTriangleMeshOptions opt; opt.enable_post_processing = enable_post_processing; opt.print_progress = print_progress; - ReadTriangleMesh(filename, mesh, opt); + ReadTriangleMesh(filename.string(), mesh, opt); return mesh; }, "Function to read TriangleMesh from file", "filename"_a, @@ -178,13 +178,12 @@ The following example reads a triangle mesh with the .ply extension:: m_io.def( "write_triangle_mesh", - [](const std::string &filename, - const t::geometry::TriangleMesh &mesh, bool write_ascii, - bool compressed, bool write_vertex_normals, + [](const fs::path &filename, const t::geometry::TriangleMesh &mesh, + bool write_ascii, bool compressed, bool write_vertex_normals, bool write_vertex_colors, bool write_triangle_uvs, bool print_progress) { py::gil_scoped_release release; - return WriteTriangleMesh(filename, mesh, write_ascii, + return WriteTriangleMesh(filename.string(), mesh, write_ascii, compressed, write_vertex_normals, write_vertex_colors, write_triangle_uvs, print_progress); diff --git a/cpp/pybind/visualization/renderoption.cpp b/cpp/pybind/visualization/renderoption.cpp index daf88cc72de9..903e2db6135c 100644 --- a/cpp/pybind/visualization/renderoption.cpp +++ b/cpp/pybind/visualization/renderoption.cpp @@ -27,16 +27,16 @@ void pybind_renderoption(py::module &m) { }) .def( "load_from_json", - [](RenderOption &ro, const std::string &filename) { - io::ReadIJsonConvertible(filename, ro); + [](RenderOption &ro, const fs::path &filename) { + io::ReadIJsonConvertible(filename.string(), ro); }, "Function to load RenderOption from a JSON " "file.", "filename"_a) .def( "save_to_json", - [](RenderOption &ro, const std::string &filename) { - io::WriteIJsonConvertible(filename, ro); + [](RenderOption &ro, const fs::path &filename) { + io::WriteIJsonConvertible(filename.string(), ro); }, "Function to save RenderOption to a JSON " "file.", diff --git a/cpp/pybind/visualization/utility.cpp b/cpp/pybind/visualization/utility.cpp index 417438b1bffc..603e78bb9475 100644 --- a/cpp/pybind/visualization/utility.cpp +++ b/cpp/pybind/visualization/utility.cpp @@ -154,12 +154,12 @@ void pybind_visualization_utility_methods(py::module &m) { [](const std::vector> &geometry_ptrs, const std::string &window_name, int width, int height, int left, - int top, const std::string &json_filename) { + int top, const fs::path &json_filename) { std::string current_dir = utility::filesystem::GetWorkingDirectory(); DrawGeometriesWithCustomAnimation(geometry_ptrs, window_name, width, height, left, top, - json_filename); + json_filename.string()); utility::filesystem::ChangeWorkingDirectory(current_dir); }, "Function to draw a list of geometry::Geometry objects with a GUI " @@ -251,9 +251,9 @@ void pybind_visualization_utility_methods(py::module &m) { m.def( "read_selection_polygon_volume", - [](const std::string &filename) { + [](const fs::path &filename) { SelectionPolygonVolume vol; - io::ReadIJsonConvertible(filename, vol); + io::ReadIJsonConvertible(filename.string(), vol); return vol; }, "Function to read SelectionPolygonVolume from file", "filename"_a); diff --git a/python/test/io/test_pathlib.py b/python/test/io/test_pathlib.py new file mode 100644 index 000000000000..ad3313a6067b --- /dev/null +++ b/python/test/io/test_pathlib.py @@ -0,0 +1,21 @@ +# ---------------------------------------------------------------------------- +# - Open3D: www.open3d.org - +# ---------------------------------------------------------------------------- +# Copyright (c) 2018-2023 www.open3d.org +# SPDX-License-Identifier: MIT +# ---------------------------------------------------------------------------- + +from pathlib import Path + +import open3d as o3d + + +def test_pathlib_support(): + pcd_pointcloud = o3d.data.PCDPointCloud() + assert isinstance(pcd_pointcloud.path, str) + + pcd = o3d.io.read_point_cloud(pcd_pointcloud.path) + assert pcd.has_points() + + pcd = o3d.io.read_point_cloud(Path(pcd_pointcloud.path)) + assert pcd.has_points() \ No newline at end of file