diff --git a/.github/workflows/bionic_build.yml b/.github/workflows/bionic_build.yml
index 928f84e3..98ab22ec 100644
--- a/.github/workflows/bionic_build.yml
+++ b/.github/workflows/bionic_build.yml
@@ -20,7 +20,7 @@ jobs:
ROS_REPO: main
NOT_TEST_BUILD: true
UPSTREAM_WORKSPACE: 'dependencies_ros1.rosinstall'
- ROSDEP_SKIP_KEYS: "iwyu cmake_common_scripts"
+ ROSDEP_SKIP_KEYS: "iwyu ros_industrial_cmake_boilerplate"
CCACHE_DIR: "/home/runner/work/noether/noether/Bionic-Build/.ccache"
TARGET_CMAKE_ARGS: "-DNURBS_FOUND=TRUE"
DOCKER_IMAGE: "rosindustrial/noether:melodic"
diff --git a/.github/workflows/focal_build.yml b/.github/workflows/focal_build.yml
index 1c565e0a..f44a8527 100644
--- a/.github/workflows/focal_build.yml
+++ b/.github/workflows/focal_build.yml
@@ -20,7 +20,7 @@ jobs:
ROS_REPO: main
NOT_TEST_BUILD: true
UPSTREAM_WORKSPACE: 'dependencies_ros1.rosinstall'
- ROSDEP_SKIP_KEYS: "iwyu cmake_common_scripts"
+ ROSDEP_SKIP_KEYS: "iwyu ros_industrial_cmake_boilerplate"
CCACHE_DIR: "/home/runner/work/noether/noether/Focal-Build/.ccache"
TARGET_CMAKE_ARGS: "-DNURBS_FOUND=FALSE"
DOCKER_IMAGE: "rosindustrial/noether:noetic"
diff --git a/.github/workflows/xenial_build.yml b/.github/workflows/xenial_build.yml
index 3d612031..0c2e8966 100644
--- a/.github/workflows/xenial_build.yml
+++ b/.github/workflows/xenial_build.yml
@@ -20,7 +20,7 @@ jobs:
ROS_REPO: main
NOT_TEST_BUILD: true
UPSTREAM_WORKSPACE: 'dependencies_ros1.rosinstall'
- ROSDEP_SKIP_KEYS: "iwyu cmake_common_scripts"
+ ROSDEP_SKIP_KEYS: "iwyu ros_industrial_cmake_boilerplate"
CCACHE_DIR: "/home/runner/work/noether/noether/Xenial-Build/.ccache"
TARGET_CMAKE_ARGS: "-DNURBS_FOUND=TRUE"
DOCKER_IMAGE: "rosindustrial/noether:kinetic"
diff --git a/dependencies_ros1.rosinstall b/dependencies_ros1.rosinstall
index 564833af..1f414c8b 100644
--- a/dependencies_ros1.rosinstall
+++ b/dependencies_ros1.rosinstall
@@ -1,3 +1,3 @@
# PCL ros tools, this is required because using custom version of PCL and VTK
- git: {local-name: perception_pcl, uri: 'https://github.com/ros-perception/perception_pcl.git', version: 1.7.1}
-- git: {local-name: cmake_common_scripts, uri: 'https://github.com/ros-industrial/cmake_common_scripts.git', version: master}
+- git: {local-name: ros_industrial_cmake_boilerplate, uri: 'https://github.com/ros-industrial/ros_industrial_cmake_boilerplate.git', version: master}
diff --git a/noether/CMakeLists.txt b/noether/CMakeLists.txt
index 6c75a629..e910315d 100644
--- a/noether/CMakeLists.txt
+++ b/noether/CMakeLists.txt
@@ -31,6 +31,7 @@ find_package(catkin REQUIRED COMPONENTS
pcl_conversions
roscpp
tool_path_planner
+ smooth_pose_traj
)
find_package(vtk_viewer REQUIRED)
@@ -52,6 +53,7 @@ catkin_package(
pcl_conversions
roscpp
tool_path_planner
+ smooth_pose_traj
DEPENDS
VTK
PCL
diff --git a/noether/package.xml b/noether/package.xml
index 127ee008..21200fad 100644
--- a/noether/package.xml
+++ b/noether/package.xml
@@ -20,5 +20,5 @@
actionlib
noether_conversions
noether_filtering
-
+ smooth_pose_traj
diff --git a/noether/src/tool_path_planner_server.cpp b/noether/src/tool_path_planner_server.cpp
index e7d6229f..96722a0d 100644
--- a/noether/src/tool_path_planner_server.cpp
+++ b/noether/src/tool_path_planner_server.cpp
@@ -11,6 +11,7 @@
#include
#include
#include
+#include
static const double FEEDBACK_PUBLISH_PERIOD = 1.0; // seconds
static const std::string GENERATE_TOOL_PATHS_ACTION = "generate_tool_paths";
@@ -97,6 +98,7 @@ class TppServer
const noether_msgs::ToolPathConfig& config = goal->path_configs[i];
const shape_msgs::Mesh& mesh = goal->surface_meshes[i];
boost::optional tool_paths = boost::none;
+ double pt_spacing, raster_spacing;
ROS_INFO("Planning path");
if (config.type == noether_msgs::ToolPathConfig::SURFACE_WALK_RASTER_GENERATOR)
{
@@ -105,6 +107,8 @@ class TppServer
tool_path_planner::toSurfaceWalkConfig(path_config, config.surface_walk_generator);
path_gen->setConfiguration(path_config);
generator = path_gen;
+ pt_spacing = path_config.point_spacing;
+ raster_spacing = path_config.raster_spacing;
}
else if (config.type == noether_msgs::ToolPathConfig::PLANE_SLICER_RASTER_GENERATOR)
{
@@ -113,6 +117,9 @@ class TppServer
tool_path_planner::toPlaneSlicerConfig(path_config, config.plane_slicer_generator);
path_gen->setConfiguration(path_config);
generator = path_gen;
+ pt_spacing = path_config.point_spacing;
+ raster_spacing = path_config.raster_spacing;
+ smooth_pose_arrays_ = path_config.smooth_rasters;
}
else if (config.type == noether_msgs::ToolPathConfig::EIGEN_VALUE_EDGE_GENERATOR)
{
@@ -121,6 +128,7 @@ class TppServer
tool_path_planner::toEigenValueConfig(path_config, config.eigen_value_generator);
path_gen->setConfiguration(path_config);
generator = path_gen;
+ pt_spacing = path_config.merge_dist;
}
else if (config.type == noether_msgs::ToolPathConfig::HALFEDGE_EDGE_GENERATOR)
{
@@ -129,6 +137,7 @@ class TppServer
tool_path_planner::toHalfedgeConfig(path_config, config.halfedge_generator);
path_gen->setConfiguration(path_config);
generator = path_gen;
+ pt_spacing = path_config.point_dist;
}
else
{
@@ -144,7 +153,6 @@ class TppServer
if (tool_paths.is_initialized())
{
- noether_msgs::ToolPaths trp;
for (const auto& tool_path : tool_paths.get())
{
noether_msgs::ToolPath tp;
@@ -157,9 +165,15 @@ class TppServer
tf::poseEigenToMsg(p, pose);
seg.poses.push_back(pose);
}
+ if (smooth_pose_arrays_)
+ {
+ smooth_pose_traj::SmoothPoseTraj smoother(seg, pt_spacing, true);
+ smoother.process(seg);
+ }
tp.segments.push_back(seg);
}
- trp.paths.push_back(tp);
+
+ result.tool_paths[i].paths.push_back(tp);
}
result.tool_path_validities[i] = true;
@@ -229,6 +243,7 @@ class TppServer
ros::NodeHandle nh_;
GenPathActionServer as_;
std::mutex goal_process_mutex_;
+ bool smooth_pose_arrays_;
};
} // namespace tpp_path_gen
diff --git a/noether_filtering/CMakeLists.txt b/noether_filtering/CMakeLists.txt
index a5e02f76..cfd33660 100644
--- a/noether_filtering/CMakeLists.txt
+++ b/noether_filtering/CMakeLists.txt
@@ -46,6 +46,7 @@ list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_std_14 CXX_FEATURE_FOUND)
add_library(${PROJECT_NAME} SHARED
src/filter_group.cpp
src/filter_manager.cpp
+ src/utils.cpp
)
target_link_libraries(${PROJECT_NAME} PUBLIC
${Boost_LIBRARIES}
diff --git a/noether_filtering/include/noether_filtering/utils.h b/noether_filtering/include/noether_filtering/utils.h
index 2e7959a4..ad7c5303 100644
--- a/noether_filtering/include/noether_filtering/utils.h
+++ b/noether_filtering/include/noether_filtering/utils.h
@@ -8,10 +8,14 @@
#ifndef INCLUDE_NOETHER_FILTERING_UTILS_H_
#define INCLUDE_NOETHER_FILTERING_UTILS_H_
-#include
+#include
#include
#include
-#include
+#include
+
+#include
+#include
+#include
namespace noether_filtering
{
@@ -28,6 +32,16 @@ static std::string getClassName()
return (status == 0) ? res.get() : mangled_name;
}
+/**
+ * @brief vtk2TriangleMesh - pcl's vtk2mesh function can produce a PolygonMesh containing 1-point
+ * or 2-point 'polygons', as well as squares and so on. Many applications assume a mesh containing
+ * only triangles. This function converts the output to only include triangles.
+ * @param poly_data - input - the VTK mesh
+ * @param mesh - output - the resulting pcl polygonmesh containing only triangles
+ * @return - number of points contained in pcl::PolygonMesh, as vtk2mesh does.
+ */
+int vtk2TriangleMesh(const vtkSmartPointer& poly_data, pcl::PolygonMesh& mesh);
+
} // namespace utils
} // namespace noether_filtering
diff --git a/noether_filtering/src/mesh/clean_data.cpp b/noether_filtering/src/mesh/clean_data.cpp
index a28f5a81..6d64829b 100644
--- a/noether_filtering/src/mesh/clean_data.cpp
+++ b/noether_filtering/src/mesh/clean_data.cpp
@@ -45,7 +45,7 @@ bool CleanData::filter(const pcl::PolygonMesh& mesh_in, pcl::PolygonMesh& mesh_o
mesh_data = cleanPolyData->GetOutput();
std::size_t num_end_points = mesh_data->GetNumberOfPoints();
CONSOLE_BRIDGE_logInform("Removed duplicate points, retained %lu points from %lu", num_end_points, num_start_points);
- VTKUtils::vtk2mesh(mesh_data, mesh_out);
+ utils::vtk2TriangleMesh(mesh_data, mesh_out);
return true;
}
diff --git a/noether_filtering/src/mesh/fill_holes.cpp b/noether_filtering/src/mesh/fill_holes.cpp
index e22fee26..63ea6924 100644
--- a/noether_filtering/src/mesh/fill_holes.cpp
+++ b/noether_filtering/src/mesh/fill_holes.cpp
@@ -88,7 +88,7 @@ bool FillHoles::filter(const pcl::PolygonMesh& mesh_in, pcl::PolygonMesh& mesh_o
mesh_data = normals_rectifier->GetOutput();
std::size_t end_num_polys = mesh_data->GetNumberOfPolys();
CONSOLE_BRIDGE_logInform("Filled %lu polygons", start_num_polys - end_num_polys);
- VTKUtils::vtk2mesh(mesh_data, mesh_out);
+ utils::vtk2TriangleMesh(mesh_data, mesh_out);
return true;
}
diff --git a/noether_filtering/src/mesh/windowed_sinc_smoothing.cpp b/noether_filtering/src/mesh/windowed_sinc_smoothing.cpp
index f7a43704..fbee4c61 100644
--- a/noether_filtering/src/mesh/windowed_sinc_smoothing.cpp
+++ b/noether_filtering/src/mesh/windowed_sinc_smoothing.cpp
@@ -91,7 +91,7 @@ bool WindowedSincSmoothing::filter(const pcl::PolygonMesh& mesh_in, pcl::Polygon
smoother->Update();
mesh_data = smoother->GetOutput();
- VTKUtils::vtk2mesh(mesh_data, mesh_out);
+ utils::vtk2TriangleMesh(mesh_data, mesh_out);
return true;
}
diff --git a/noether_filtering/src/utils.cpp b/noether_filtering/src/utils.cpp
new file mode 100644
index 00000000..7193161a
--- /dev/null
+++ b/noether_filtering/src/utils.cpp
@@ -0,0 +1,106 @@
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+namespace noether_filtering
+{
+namespace utils
+{
+int vtk2TriangleMesh(const vtkSmartPointer& poly_data, pcl::PolygonMesh& mesh)
+{
+ mesh.polygons.clear();
+ mesh.cloud.data.clear();
+ mesh.cloud.width = mesh.cloud.height = 0;
+ mesh.cloud.is_dense = true;
+
+ vtkSmartPointer mesh_points = poly_data->GetPoints();
+ vtkIdType nr_points = mesh_points->GetNumberOfPoints();
+ vtkIdType nr_polygons = poly_data->GetNumberOfPolys();
+
+ if (nr_points == 0)
+ return 0;
+
+ vtkUnsignedCharArray* poly_colors = nullptr;
+ if (poly_data->GetPointData() != nullptr)
+ poly_colors = vtkUnsignedCharArray::SafeDownCast(poly_data->GetPointData()->GetScalars("Colors"));
+
+ // Some applications do not save the name of scalars (including PCL's native vtk_io)
+ if (!poly_colors)
+ poly_colors = vtkUnsignedCharArray::SafeDownCast(poly_data->GetPointData()->GetScalars("scalars"));
+
+ // TODO: currently only handles rgb values with 3 components
+ if (poly_colors && (poly_colors->GetNumberOfComponents() == 3))
+ {
+ pcl::PointCloud::Ptr cloud_temp(new pcl::PointCloud());
+ cloud_temp->points.resize(nr_points);
+ double point_xyz[3];
+ unsigned char point_color[3];
+ for (vtkIdType i = 0; i < mesh_points->GetNumberOfPoints(); ++i)
+ {
+ mesh_points->GetPoint(i, &point_xyz[0]);
+ (*cloud_temp)[i].x = static_cast(point_xyz[0]);
+ (*cloud_temp)[i].y = static_cast(point_xyz[1]);
+ (*cloud_temp)[i].z = static_cast(point_xyz[2]);
+
+ poly_colors->GetTupleValue(i, &point_color[0]);
+ (*cloud_temp)[i].r = point_color[0];
+ (*cloud_temp)[i].g = point_color[1];
+ (*cloud_temp)[i].b = point_color[2];
+ }
+ cloud_temp->width = cloud_temp->size();
+ cloud_temp->height = 1;
+ cloud_temp->is_dense = true;
+
+ pcl::toPCLPointCloud2(*cloud_temp, mesh.cloud);
+ }
+ else // in case points do not have color information:
+ {
+ pcl::PointCloud::Ptr cloud_temp(new pcl::PointCloud());
+ cloud_temp->points.resize(nr_points);
+ double point_xyz[3];
+ for (vtkIdType i = 0; i < mesh_points->GetNumberOfPoints(); ++i)
+ {
+ mesh_points->GetPoint(i, &point_xyz[0]);
+ (*cloud_temp)[i].x = static_cast(point_xyz[0]);
+ (*cloud_temp)[i].y = static_cast(point_xyz[1]);
+ (*cloud_temp)[i].z = static_cast(point_xyz[2]);
+ }
+ cloud_temp->width = cloud_temp->size();
+ cloud_temp->height = 1;
+ cloud_temp->is_dense = true;
+
+ pcl::toPCLPointCloud2(*cloud_temp, mesh.cloud);
+ }
+
+ mesh.polygons.reserve(nr_polygons);
+#ifdef VTK_CELL_ARRAY_V2
+ vtkIdType const* cell_points;
+#else
+ vtkIdType* cell_points;
+#endif
+ vtkIdType nr_cell_points;
+ vtkCellArray* mesh_polygons = poly_data->GetPolys();
+ mesh_polygons->InitTraversal();
+ int id_poly = 0;
+ while (mesh_polygons->GetNextCell(nr_cell_points, cell_points))
+ {
+ if (nr_cell_points == 3 && cell_points[0] != cell_points[1] && cell_points[1] != cell_points[2] &&
+ cell_points[2] != cell_points[0])
+ {
+ mesh.polygons.emplace_back(pcl::Vertices());
+ mesh.polygons[id_poly].vertices.resize(nr_cell_points);
+ for (vtkIdType i = 0; i < nr_cell_points; ++i)
+ mesh.polygons[id_poly].vertices[i] = static_cast(cell_points[i]);
+ ++id_poly;
+ }
+ }
+
+ return (static_cast(nr_points));
+}
+
+} // namespace utils
+} // namespace noether_filtering
diff --git a/noether_msgs/CMakeLists.txt b/noether_msgs/CMakeLists.txt
index dcf35486..33c8ec7d 100644
--- a/noether_msgs/CMakeLists.txt
+++ b/noether_msgs/CMakeLists.txt
@@ -23,6 +23,7 @@ add_message_files(
SurfaceWalkRasterGeneratorConfig.msg
EigenValueEdgeGeneratorConfig.msg
HalfedgeEdgeGeneratorConfig.msg
+ HeatRasterGeneratorConfig.msg
SegmentationConfig.msg
ToolPathConfig.msg
ToolPath.msg
diff --git a/noether_msgs/msg/EigenValueEdgeGeneratorConfig.msg b/noether_msgs/msg/EigenValueEdgeGeneratorConfig.msg
index f4949429..37669d7c 100644
--- a/noether_msgs/msg/EigenValueEdgeGeneratorConfig.msg
+++ b/noether_msgs/msg/EigenValueEdgeGeneratorConfig.msg
@@ -27,3 +27,6 @@ int32 max_intersecting_voxels
# Any two consecutive points with a shortest distance smaller than this value are merged
float64 merge_dist
+
+# flag to break edge paths by diagonal axes
+bool split_by_axes
diff --git a/noether_msgs/msg/HalfedgeEdgeGeneratorConfig.msg b/noether_msgs/msg/HalfedgeEdgeGeneratorConfig.msg
index 07892f88..e4f72346 100644
--- a/noether_msgs/msg/HalfedgeEdgeGeneratorConfig.msg
+++ b/noether_msgs/msg/HalfedgeEdgeGeneratorConfig.msg
@@ -20,3 +20,9 @@ int32 point_spacing_method
# Point distance parameter used in conjunction with the spacing method
float64 point_dist
+
+# flag to break edge paths by diagonal axes
+bool split_by_axes
+
+# Size (meters) of peninsulas or inlets to skip over, default of 0 will not bridge any gaps, effectively off
+float64 max_bridge_distance
diff --git a/noether_msgs/msg/HeatRasterGeneratorConfig.msg b/noether_msgs/msg/HeatRasterGeneratorConfig.msg
new file mode 100644
index 00000000..167b9d5a
--- /dev/null
+++ b/noether_msgs/msg/HeatRasterGeneratorConfig.msg
@@ -0,0 +1,7 @@
+float64 point_spacing # Required spacing between path points
+float64 raster_spacing # Offset between two rasters
+float64 tool_offset # How far off the surface the tool needs to be
+float64 min_hole_size # A path passes over smaller holes breaks into two on larger wholes
+float64 min_segment_size # The minimum segment size to allowed
+float64 raster_rot_offset # use this angle when no sources are povided
+bool generate_extra_rasters # Add extra strokes off the boundaries (NOT YET IMPLEMENTED BY HEAT METHOD)
\ No newline at end of file
diff --git a/noether_msgs/msg/PlaneSlicerRasterGeneratorConfig.msg b/noether_msgs/msg/PlaneSlicerRasterGeneratorConfig.msg
index ca33e269..5a073f8f 100644
--- a/noether_msgs/msg/PlaneSlicerRasterGeneratorConfig.msg
+++ b/noether_msgs/msg/PlaneSlicerRasterGeneratorConfig.msg
@@ -1,6 +1,21 @@
-float64 raster_spacing # Offset between two rasters
-float64 point_spacing # Required spacing between path points
-float64 raster_rot_offset # Specifies the direction of the raster path in radians
-float64 min_segment_size # The minimum segment size to allow when finding intersections; small segments will be discarded
-float64 min_hole_size # A path may pass over holes smaller than this, but must be broken when larger holes are encounterd.
-float64 tool_offset # How far off the surface the tool needs to be
+float64 raster_spacing # Offset between two rasters
+float64 point_spacing # Required spacing between path points
+float64 raster_rot_offset # Specifies the direction of the raster path in radians. Rotates about short dimension of bounding box.
+float64 min_segment_size # The minimum segment size to allow when finding intersections; small segments will be discarded
+float64 min_hole_size # A path may pass over holes smaller than this, but must be broken when larger holes are encounterd.
+float64 tool_offset # How far off the surface the tool needs to be
+float64 search_radius # radius over which the normals are averaged
+bool generate_extra_rasters # Whether an extra raster stroke should be added to the beginning and end of the raster pattern
+bool interleave_rasters # create a second set of rasters interleaved inbetween first set
+bool smooth_rasters # post process poses by fitting a cubic b-spline and re-sampling
+int32 raster_style # 0,1,2 for mow_style, paint_style and read_style
+
+# When set to TRUE: initial cuts are determined using an axis-aligned bounding box. (global axes)
+# When set to FALSE: initial cuts are determined using an object-oriented bounding box and the
+# principal axes of the part. (Note that ROS sets message fields to 0/False by default.)
+bool raster_wrt_global_axes
+
+# Normal of the cutting planes. Points from the first raster to the second raster, not the
+# direction from the first point in one raster to the second point of the same raster. Specified
+# in the frame of the input mesh. Overrides raster_wrt_global_axes
+geometry_msgs/Vector3 raster_direction
diff --git a/noether_msgs/msg/ToolPathConfig.msg b/noether_msgs/msg/ToolPathConfig.msg
index a5723625..49d3d358 100644
--- a/noether_msgs/msg/ToolPathConfig.msg
+++ b/noether_msgs/msg/ToolPathConfig.msg
@@ -3,10 +3,13 @@ uint8 PLANE_SLICER_RASTER_GENERATOR=1
uint8 HEAT_RASTER_GENERATOR=2
uint8 EIGEN_VALUE_EDGE_GENERATOR=3 # This does not have a config
uint8 HALFEDGE_EDGE_GENERATOR=4
+uint8 PAINT_RASTER_STYLE=0
+uint8 MOW_RASTER_STYLE=1
+uint8 READ_RASTER_STYLE=2
uint8 type
noether_msgs/SurfaceWalkRasterGeneratorConfig surface_walk_generator
noether_msgs/PlaneSlicerRasterGeneratorConfig plane_slicer_generator
-#noether_msgs/HeatRasterPathGeneratorConfig heat_generator
+noether_msgs/HeatRasterGeneratorConfig heat_generator
noether_msgs/EigenValueEdgeGeneratorConfig eigen_value_generator
noether_msgs/HalfedgeEdgeGeneratorConfig halfedge_generator
diff --git a/path_sequence_planner/src/simple_path_sequence_planner.cpp b/path_sequence_planner/src/simple_path_sequence_planner.cpp
index 72780f02..b0b271a1 100644
--- a/path_sequence_planner/src/simple_path_sequence_planner.cpp
+++ b/path_sequence_planner/src/simple_path_sequence_planner.cpp
@@ -120,7 +120,10 @@ long SimplePathSequencePlanner::findNextNearestPath(tool_path_planner::ToolPaths
bool front)
{
Eigen::Isometry3d last_pt;
+
// find next nearest point
+ // This should technically be guarded against last_path or its components being empty, but an
+ // empty path should not have been previously selected.
if (front)
last_pt = paths[last_path].front().front();
else
@@ -136,17 +139,28 @@ long SimplePathSequencePlanner::findNextNearestPath(tool_path_planner::ToolPaths
continue;
// get first and last point of line j
- Eigen::Isometry3d pt1 = paths[j].front().front();
- double dist1 = (pt1.translation() - last_pt.translation()).norm();
+ if (!paths[j].empty())
+ {
+ double dist1 = std::numeric_limits::max();
+ if (!paths[j].front().empty())
+ {
+ Eigen::Isometry3d pt1 = paths[j].front().front();
+ dist1 = (pt1.translation() - last_pt.translation()).norm();
+ }
- // find distance between last point and the end points of the next line
- Eigen::Isometry3d pt2 = paths[j].back().back();
- double dist2 = (pt2.translation() - last_pt.translation()).norm();
+ // find distance between last point and the end points of the next line
+ double dist2 = std::numeric_limits::max();
+ if (!paths[j].back().empty())
+ {
+ Eigen::Isometry3d pt2 = paths[j].back().back();
+ double dist2 = (pt2.translation() - last_pt.translation()).norm();
+ }
- if (dist1 < min_dist || dist2 < min_dist)
- {
- min_index = static_cast(j);
- min_dist = (dist1 < dist2 ? dist1 : dist2);
+ if (dist1 < min_dist || dist2 < min_dist)
+ {
+ min_index = static_cast(j);
+ min_dist = (dist1 < dist2 ? dist1 : dist2);
+ }
}
}
diff --git a/tool_path_planner/CMakeLists.txt b/tool_path_planner/CMakeLists.txt
index ca38f2c5..3d2e7d25 100644
--- a/tool_path_planner/CMakeLists.txt
+++ b/tool_path_planner/CMakeLists.txt
@@ -48,6 +48,7 @@ catkin_package(
geometry_msgs
noether_msgs
rosconsole
+ roscpp
shape_msgs
DEPENDS
EIGEN3
diff --git a/tool_path_planner/include/tool_path_planner/eigen_value_edge_generator.h b/tool_path_planner/include/tool_path_planner/eigen_value_edge_generator.h
index 459f16e9..d217edff 100644
--- a/tool_path_planner/include/tool_path_planner/eigen_value_edge_generator.h
+++ b/tool_path_planner/include/tool_path_planner/eigen_value_edge_generator.h
@@ -99,6 +99,9 @@ class EigenValueEdgeGenerator : public PathGenerator
double merge_dist =
0.01; /** @brief any two consecutive points with a shortest distance smaller than this value are merged */
+
+ bool split_by_axes =
+ true; /** @brief flag indicating whether returned toolpaths should be split along the major and middle axis */
};
EigenValueEdgeGenerator() = default;
diff --git a/tool_path_planner/include/tool_path_planner/halfedge_edge_generator.h b/tool_path_planner/include/tool_path_planner/halfedge_edge_generator.h
index 5be0e000..a8b415b0 100644
--- a/tool_path_planner/include/tool_path_planner/halfedge_edge_generator.h
+++ b/tool_path_planner/include/tool_path_planner/halfedge_edge_generator.h
@@ -66,6 +66,12 @@ class HalfedgeEdgeGenerator : public PathGenerator
/**@brief point distance parameter used in conjunction with the spacing method */
double point_dist = 0.01;
+
+ /** @brief flag indicating whether returned toolpaths should be split along the major and middle axis */
+ bool split_by_axes = true;
+
+ /** @brief Size of peninsulas or inlets to skip over */
+ double max_bridge_distance = 0.0;
};
HalfedgeEdgeGenerator() = default;
diff --git a/tool_path_planner/include/tool_path_planner/path_generator.h b/tool_path_planner/include/tool_path_planner/path_generator.h
index 6816684b..910e5cfe 100644
--- a/tool_path_planner/include/tool_path_planner/path_generator.h
+++ b/tool_path_planner/include/tool_path_planner/path_generator.h
@@ -13,6 +13,13 @@
namespace tool_path_planner
{
+enum RasterStyle : int
+{
+ KEEP_ORIENTATION_ON_REVERSE_STROKES = 0, // painting style, back and fourth without flipping
+ FLIP_ORIENTATION_ON_REVERSE_STROKES = 1, // mowing style, back and fourth, but flip orientation at turns
+ PROCESS_FORWARD_DIRECTION_ONLY = 2, // reading style left to right and return
+};
+
/** @brief A set of contiguous waypoints that lie on the same line created by a "slice" through a surface */
using ToolPathSegment = std::vector>;
/** @brief A set of tool path segments that lie on the same line created by a tool path "slice",
diff --git a/tool_path_planner/include/tool_path_planner/plane_slicer_raster_generator.h b/tool_path_planner/include/tool_path_planner/plane_slicer_raster_generator.h
index 14e22573..604f112f 100644
--- a/tool_path_planner/include/tool_path_planner/plane_slicer_raster_generator.h
+++ b/tool_path_planner/include/tool_path_planner/plane_slicer_raster_generator.h
@@ -22,16 +22,20 @@
#ifndef INCLUDE_PLANE_SLICER_RASTER_GENERATOR_H_
#define INCLUDE_PLANE_SLICER_RASTER_GENERATOR_H_
-#include
-#include
-#include
#include
+#include
+#include
+#include
+#include
#include
-#include
#include
#include
-#include
-#include
+#include
+#include
+#include
+
+#include
+#include
#include
@@ -39,12 +43,17 @@ namespace tool_path_planner
{
class PlaneSlicerRasterGenerator : public PathGenerator
{
+ static constexpr RasterStyle DEFAULT_RASTER_STYLE = KEEP_ORIENTATION_ON_REVERSE_STROKES;
static constexpr double DEFAULT_RASTER_SPACING = 0.04;
static constexpr double DEFAULT_POINT_SPACING = 0.01;
static constexpr double DEFAULT_RASTER_ROT_OFFSET = 0.0;
static constexpr double DEFAULT_MIN_SEGMENT_SIZE = 0.01;
static constexpr double DEFAULT_SEARCH_RADIUS = 0.01;
+ static constexpr bool DEFAULT_INTERLEAVE_RASTERS = false;
+ static constexpr bool DEFAULT_SMOOTH_RASTERS = false;
static constexpr double DEFAULT_MIN_HOLE_SIZE = 1e-2;
+ static constexpr bool DEFAULT_RASTER_WRT_GLOBAL_AXES = false;
+ static constexpr bool DEFAULT_GENERATE_EXTRA_RASTERS = true;
public:
struct Config
@@ -55,7 +64,12 @@ class PlaneSlicerRasterGenerator : public PathGenerator
double min_segment_size{ DEFAULT_MIN_SEGMENT_SIZE };
double search_radius{ DEFAULT_SEARCH_RADIUS };
double min_hole_size{ DEFAULT_MIN_HOLE_SIZE };
-
+ bool interleave_rasters{ DEFAULT_INTERLEAVE_RASTERS };
+ bool smooth_rasters{ DEFAULT_SMOOTH_RASTERS };
+ bool raster_wrt_global_axes{ DEFAULT_RASTER_WRT_GLOBAL_AXES };
+ bool generate_extra_rasters{ DEFAULT_GENERATE_EXTRA_RASTERS };
+ Eigen::Vector3d raster_direction{ Eigen::Vector3d::Zero() };
+ RasterStyle raster_style{ DEFAULT_RASTER_STYLE };
Json::Value toJson() const
{
Json::Value jv(Json::ValueType::objectValue);
@@ -64,7 +78,15 @@ class PlaneSlicerRasterGenerator : public PathGenerator
jv["raster_rot_offset"] = raster_rot_offset;
jv["min_segment_size"] = min_segment_size;
jv["search_radius"] = search_radius;
+ jv["interleave_rasters"] = interleave_rasters;
+ jv["smooth_rasters"] = smooth_rasters;
jv["min_hole_size"] = min_hole_size;
+ jv["raster_wrt_global_axes"] = raster_wrt_global_axes;
+ Json::Value raster_dir(Json::ValueType::objectValue);
+ raster_dir["x"] = raster_direction.x();
+ raster_dir["y"] = raster_direction.y();
+ raster_dir["z"] = raster_direction.z();
+ jv["raster_direction"] = raster_dir;
return jv;
}
@@ -73,14 +95,14 @@ class PlaneSlicerRasterGenerator : public PathGenerator
{
if (jv.isNull())
{
- ROS_ERROR("Json value is null");
+ CONSOLE_BRIDGE_logError("Json value is null");
return false;
}
if (jv.type() != Json::ValueType::objectValue)
{
- ROS_ERROR("Json type %i is invalid, only '%i' is allowed",
- static_cast(jv.type()),
- static_cast(Json::ValueType::objectValue));
+ CONSOLE_BRIDGE_logError("Json type %i is invalid, only '%i' is allowed",
+ static_cast(jv.type()),
+ static_cast(Json::ValueType::objectValue));
return false;
}
auto validate = [](const Json::Value& jv, const std::string& name_, const Json::ValueType& type_) -> bool {
@@ -98,8 +120,33 @@ class PlaneSlicerRasterGenerator : public PathGenerator
DEFAULT_MIN_SEGMENT_SIZE;
search_radius = validate(jv, "search_radius", Json::ValueType::realValue) ? jv["search_radius"].asDouble() :
DEFAULT_SEARCH_RADIUS;
+ interleave_rasters = validate(jv, "interleave_rasters", Json::ValueType::booleanValue) ?
+ jv["interleave_rasters"].asBool() :
+ DEFAULT_INTERLEAVE_RASTERS;
+ smooth_rasters = validate(jv, "smooth_rasters", Json::ValueType::booleanValue) ? jv["smooth_rasters"].asBool() :
+ DEFAULT_SMOOTH_RASTERS;
min_hole_size = validate(jv, "min_hole_size", Json::ValueType::realValue) ? jv["min_hole_size"].asDouble() :
DEFAULT_MIN_HOLE_SIZE;
+ raster_wrt_global_axes = validate(jv, "raster_wrt_global_axes", Json::ValueType::booleanValue) ?
+ jv["raster_wrt_global_axes"].asBool() :
+ DEFAULT_RASTER_WRT_GLOBAL_AXES;
+ if (jv["raster_direction"].isNull() || jv["raster_direction"].type() != Json::ValueType::objectValue)
+ {
+ CONSOLE_BRIDGE_logError("Malformed Raster Direction in Json value");
+ return false;
+ }
+ if (validate(jv["raster_direction"], "x", Json::ValueType::objectValue) &&
+ validate(jv["raster_direction"], "y", Json::ValueType::objectValue) &&
+ validate(jv["raster_direction"], "z", Json::ValueType::objectValue))
+ {
+ raster_direction.x() = jv["raster_direction"]["x"].asDouble();
+ raster_direction.y() = jv["raster_direction"]["y"].asDouble();
+ raster_direction.z() = jv["raster_direction"]["z"].asDouble();
+ }
+ else
+ {
+ raster_direction = Eigen::Vector3d::UnitY();
+ }
return true;
}
@@ -121,10 +168,23 @@ class PlaneSlicerRasterGenerator : public PathGenerator
ss << "raster_rot_offset: " << raster_rot_offset << std::endl;
ss << "min_segment_size: " << min_segment_size << std::endl;
ss << "search_radius: " << search_radius << std::endl;
+ ss << "interleave_rasters: " << interleave_rasters << std::endl;
+ ss << "smooth_rasters: " << smooth_rasters << std::endl;
+ ss << "raster_wrt_global_axes: " << raster_wrt_global_axes << std::endl;
+ ss << "raster_direction: " << std::endl;
+ ss << " x: " << raster_direction.x() << std::endl;
+ ss << " y: " << raster_direction.y() << std::endl;
+ ss << " z: " << raster_direction.z() << std::endl;
return ss.str();
}
};
+ struct RasterConstructData
+ {
+ std::vector> raster_segments;
+ std::vector segment_lengths;
+ };
+
PlaneSlicerRasterGenerator() = default;
/**
@@ -134,11 +194,8 @@ class PlaneSlicerRasterGenerator : public PathGenerator
*/
void setConfiguration(const Config& config);
- void setInput(pcl::PolygonMesh::ConstPtr mesh) override;
-
- void setInput(vtkSmartPointer mesh) override;
-
void setInput(const shape_msgs::Mesh& mesh) override;
+ void setInput(pcl::PolygonMesh::ConstPtr mesh) override;
vtkSmartPointer getInput() override;
@@ -147,12 +204,25 @@ class PlaneSlicerRasterGenerator : public PathGenerator
std::string getName() const override;
private:
+ void setInput(vtkSmartPointer mesh) override;
+
bool insertNormals(const double search_radius, vtkSmartPointer& data);
+ tool_path_planner::ToolPaths convertToPoses(const std::vector& rasters_data);
+ void computePoseData(const vtkSmartPointer& polydata,
+ int idx,
+ Eigen::Vector3d& p,
+ Eigen::Vector3d& vx,
+ Eigen::Vector3d& vy,
+ Eigen::Vector3d& vz);
+
vtkSmartPointer mesh_data_;
vtkSmartPointer kd_tree_;
vtkSmartPointer cell_locator_;
Config config_;
+ boost::shared_ptr> mls_mesh_normals_ptr_;
+ std::vector vertex_normals_;
+ std::vector face_normals_;
};
} /* namespace tool_path_planner */
diff --git a/tool_path_planner/include/tool_path_planner/utilities.h b/tool_path_planner/include/tool_path_planner/utilities.h
index d2e38d82..6b463b14 100644
--- a/tool_path_planner/include/tool_path_planner/utilities.h
+++ b/tool_path_planner/include/tool_path_planner/utilities.h
@@ -28,6 +28,7 @@
#include
#include
#include
+#include
#include
#include
@@ -43,6 +44,9 @@
#include
#include
+#include
+#include
+
namespace tool_path_planner
{
/**
@@ -53,10 +57,24 @@ void flipPointOrder(ToolPath& path);
/**
* @brief conversion function. Not well tested yet
- * @param paths The toolpaths within a raster
- * @return A vector of tool path data
+ * @param tool_path_segment A vector of Eigen::Isometry objects describing a segment of tool path poses
+ * @return A structure of vtk objects describing a segment of tool path poses
*/
-ToolPathsData toToolPathsData(const ToolPaths& paths);
+ToolPathSegmentData toToolPathSegmentData(const ToolPathSegment& tool_path_segment);
+
+/**
+ * @brief conversion function. Not well tested yet
+ * @param tool_path A vector of ToolPathSegment objects
+ * @return A vector of ToolPathSegementData obects
+ */
+ToolPathData toToolPathData(const ToolPath& tool_path);
+
+/**
+ * @brief conversion function. Not well tested yet
+ * @param tool_paths A vector of ToolPath objects
+ * @return A vector of ToolPathData objects
+ */
+ToolPathsData toToolPathsData(const ToolPaths& tool_paths);
/**
* @details creates a rotation matrix from the column vectors; it can then be assigned to a Isometry3d pose
@@ -118,6 +136,59 @@ static std::string getClassName()
return (status == 0) ? res.get() : mangled_name;
}
+ToolPaths splitSegments(const ToolPaths& tool_paths, double max_segment_length);
+
+// duplicates first and last segment of each toolpath in tool_paths offset by the offset distance
+ToolPaths addExtraPaths(const ToolPaths& tool_paths, double offset_distance);
+
+// fills in waypoints to ensure double coverage when adjacent rasters are different lengths
+ToolPaths addExtraWaypoints(const ToolPaths& tool_paths, double raster_spacing, double point_spacing);
+
+// reverse direction of every other raster, flip orientation or not depending on raster style
+ToolPaths reverseOddRasters(const ToolPaths& tool_paths, RasterStyle raster_style);
+
+double computeOffsetSign(const ToolPathSegment& adjusted_segment, const ToolPathSegment& away_from_segment);
+
+ToolPath splitByAxes(const ToolPathSegment& tool_path_segment);
+ToolPath splitByAxes(const ToolPathSegment& tool_path_segment,
+ const Eigen::Vector3f& axis_1,
+ const Eigen::Vector3f& axis_2);
+ToolPaths splitByAxes(const ToolPaths& tool_paths);
+ToolPaths splitByAxes(const ToolPaths& tool_paths, const Eigen::Vector3f& axis_1, const Eigen::Vector3f& axis_2);
+
+ToolPathSegment checkForPeninsulaOrInlet(const ToolPathSegment& tool_path,
+ const double& min_allowed_width);
+
+/**
+ * @brief Appends an interleaved set tool_paths to the provided tool_paths
+ * @param tool_paths The input trajectory to interleave
+ * @param raster_spacing
+ */
+void InterleavePoseTraj(noether_msgs::ToolPaths& tool_paths, double raster_spacing);
+
+pcl::PointCloud computeMLSMeshNormals(const pcl::PointCloud::Ptr& mesh_cloud,
+ double normal_search_radius);
+
+bool applyEqualDistance(const std::vector& pnt_indices,
+ const pcl::PointCloud& mesh_cloud,
+ pcl::PointCloud& path_cloud,
+ double dist);
+
+bool insertPointNormals(const pcl::PointCloud::Ptr& mesh_cloud,
+ pcl::PointCloud& path_cloud);
+
+void shapeMsgToPclPointXYZ(const shape_msgs::Mesh& mesh, pcl::PointCloud& mesh_cloud);
+
+void computeFaceNormals(const shape_msgs::Mesh& mesh, std::vector& face_normals);
+
+void computeMeshNormals(const shape_msgs::Mesh& mesh,
+ std::vector& face_normals,
+ std::vector& vertex_normals);
+void computePCLMeshNormals(pcl::PolygonMesh::ConstPtr& mesh,
+ std::vector& face_normals,
+ std::vector& vertex_normals);
+
+bool alignToVertexNormals(pcl::PointCloud& mesh_cloud, std::vector& vertex_normals);
} // namespace tool_path_planner
#endif /* INCLUDE_TOOL_PATH_PLANNER_UTILITIES_H_ */
diff --git a/tool_path_planner/package.xml b/tool_path_planner/package.xml
index 310f39ef..2816c231 100644
--- a/tool_path_planner/package.xml
+++ b/tool_path_planner/package.xml
@@ -10,6 +10,7 @@
cmake_modules
roslib
+ roscpp
eigen_conversions
eigen_stl_containers
noether_conversions
diff --git a/tool_path_planner/src/eigen_value_edge_generator.cpp b/tool_path_planner/src/eigen_value_edge_generator.cpp
index d411dde7..3c493933 100644
--- a/tool_path_planner/src/eigen_value_edge_generator.cpp
+++ b/tool_path_planner/src/eigen_value_edge_generator.cpp
@@ -330,6 +330,14 @@ boost::optional EigenValueEdgeGenerator::generate()
edge_paths.push_back({ edge_segment });
}
}
+
+ if (config_.split_by_axes)
+ {
+ edge_paths = tool_path_planner::splitByAxes(edge_paths);
+ }
+
+ CONSOLE_BRIDGE_logInform("Found %lu valid edge segments", edge_paths.size());
+
return edge_paths;
}
diff --git a/tool_path_planner/src/halfedge_edge_generator.cpp b/tool_path_planner/src/halfedge_edge_generator.cpp
index beeb58b5..ab6979ea 100644
--- a/tool_path_planner/src/halfedge_edge_generator.cpp
+++ b/tool_path_planner/src/halfedge_edge_generator.cpp
@@ -155,9 +155,9 @@ bool applyMinPointDistance(const pcl::PointCloud& in,
* @param dist The required point spacing distance
* @return True on success, False otherwise
*/
-bool applyEqualDistance(const pcl::PointCloud& in,
- pcl::PointCloud& out,
- double dist)
+static bool applyEqualDistancePathCloud(const pcl::PointCloud& in,
+ pcl::PointCloud& out,
+ double dist)
{
using namespace pcl;
using namespace Eigen;
@@ -215,7 +215,7 @@ bool applyEqualDistance(const pcl::PointCloud& in,
}
// add last point if not already there
- if ((out.back().getVector3fMap() - in.back().getNormalVector3fMap()).norm() > MIN_POINT_DIST_ALLOWED)
+ if ((out.back().getVector3fMap() - in.back().getVector3fMap()).norm() > MIN_POINT_DIST_ALLOWED)
{
out.push_back(in.back());
}
@@ -500,9 +500,9 @@ boost::optional HalfedgeEdgeGenerator::generate()
break;
case PointSpacingMethod::EQUAL_SPACING:
- if (!applyEqualDistance(bound_segment_points, decimated_points, config_.point_dist))
+ if (!applyEqualDistancePathCloud(bound_segment_points, decimated_points, config_.point_dist))
{
- CONSOLE_BRIDGE_logError("applyEqualDistance point spacing method failed");
+ CONSOLE_BRIDGE_logError("applyEqualDistancePathCloud point spacing method failed");
return boost::none;
}
break;
@@ -556,8 +556,11 @@ boost::optional HalfedgeEdgeGenerator::generate()
if (!createToolPathSegment(bound_segment_points, {}, edge_segment))
return boost::none;
- edge_paths.push_back({ edge_segment });
- CONSOLE_BRIDGE_logInform("Added boundary with %lu points", edge_paths.back()[0].size());
+ ToolPathSegment cleaned_segment =
+ checkForPeninsulaOrInlet(edge_segment, config_.max_bridge_distance);
+
+ edge_paths.push_back({ cleaned_segment });
+ CONSOLE_BRIDGE_logDebug("Added boundary with %lu points", edge_paths.back()[0].size());
}
// sorting
@@ -580,7 +583,13 @@ boost::optional HalfedgeEdgeGenerator::generate()
return boost::none;
}
+ if (config_.split_by_axes)
+ {
+ edge_paths = tool_path_planner::splitByAxes(edge_paths);
+ }
+
CONSOLE_BRIDGE_logInform("Found %lu valid edge segments", edge_paths.size());
+
return edge_paths;
}
diff --git a/tool_path_planner/src/plane_slicer_raster_generator.cpp b/tool_path_planner/src/plane_slicer_raster_generator.cpp
index 8bbbefdb..dcb39e3d 100644
--- a/tool_path_planner/src/plane_slicer_raster_generator.cpp
+++ b/tool_path_planner/src/plane_slicer_raster_generator.cpp
@@ -19,48 +19,122 @@
* limitations under the License.
*/
-#include
+#include
+
+#include
+
+#include
+#include
+#include
+#include
#include
+#include
#include
#include
#include
#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
#include
#include
#include
#include
-#include
-#include
+#include
+#include
#include
#include
-#include
-#include
-#include
-#include
#include
-#include
-#include
-#include
-#include
-#include
-#include
-#include
#include
-#include
#include
+
#include
#include
-#include
static const double EPSILON = 1e-6;
-using PolyDataPtr = vtkSmartPointer;
-struct RasterConstructData
+static Eigen::Vector3d getSegDir(vtkSmartPointer seg)
{
- std::vector raster_segments;
- std::vector segment_lengths;
-};
+ if (seg->GetPoints()->GetNumberOfPoints() < 1)
+ {
+ CONSOLE_BRIDGE_logError("can't get direction from a segment with fewer than 2 points");
+ Eigen::Vector3d v;
+ v.x() = 1.0;
+ v.y() = 0.0;
+ v.z() = 0.0;
+ return (v);
+ }
+ Eigen::Vector3d seg_start, seg_end;
+ seg->GetPoint(0, seg_start.data());
+ seg->GetPoint(seg->GetPoints()->GetNumberOfPoints() - 1, seg_end.data());
+ return (seg_end - seg_start);
+}
+
+static bool compare_ds_pair(std::pair& first, std::pair& second)
+{
+ return (first.first < second.first);
+}
+
+// @brief this function accepts and returns a rasterConstruct where every segment progresses in the same direction and
+// in the right order
+static tool_path_planner::PlaneSlicerRasterGenerator::RasterConstructData
+alignRasterCD(tool_path_planner::PlaneSlicerRasterGenerator::RasterConstructData& rcd,
+ Eigen::Vector3d& raster_direction)
+{
+ if (rcd.raster_segments[0]->GetPoints()->GetNumberOfPoints() <= 1)
+ {
+ CONSOLE_BRIDGE_logError("first raster segment has 0 or 1 points, unable to alignRasterCD()");
+ return (rcd);
+ }
+ Eigen::Vector3d raster_start;
+ rcd.raster_segments[0]->GetPoint(0, raster_start.data());
+
+ // determine location and direction of each successive segement, reverse any mis-directed segments
+ tool_path_planner::PlaneSlicerRasterGenerator::RasterConstructData temp_rcd;
+ std::list> seg_order; // once sorted this list will be the order of the segments
+ for (size_t i = 0; i < rcd.raster_segments.size(); i++)
+ {
+ // determine i'th segments direction
+ Eigen::Vector3d seg_dir = getSegDir(rcd.raster_segments[i]);
+ Eigen::Vector3d seg_start;
+ rcd.raster_segments[i]->GetPoint(0, seg_start.data());
+ // if segment direction is opposite raster direction, reverse the segment
+ if (seg_dir.dot(raster_direction) < 0.0)
+ {
+ vtkSmartPointer old_points = rcd.raster_segments[i]->GetPoints();
+ vtkSmartPointer new_points = vtkSmartPointer::New();
+ for (long int j = static_cast(old_points->GetNumberOfPoints() - 1); j >= 0; j--)
+ {
+ new_points->InsertNextPoint(old_points->GetPoint(j));
+ }
+ rcd.raster_segments[i]->SetPoints(new_points);
+ rcd.raster_segments[i]->GetPoint(0, seg_start.data());
+ }
+
+ // determine location of this segment in raster
+ double seg_loc = (seg_start - raster_start).dot(raster_direction);
+ std::pair p(seg_loc, i);
+ seg_order.push_back(p);
+ }
+ // sort the segments by location
+ if (seg_order.size() >= 1)
+ {
+ seg_order.sort(compare_ds_pair);
+ }
+ tool_path_planner::PlaneSlicerRasterGenerator::RasterConstructData new_rcd;
+ for (std::pair p : seg_order)
+ {
+ size_t seg_index = std::get<1>(p);
+ new_rcd.raster_segments.push_back(rcd.raster_segments[seg_index]);
+ new_rcd.segment_lengths.push_back(rcd.segment_lengths[seg_index]);
+ }
+ return (new_rcd);
+}
static Eigen::Matrix3d computeRotation(const Eigen::Vector3d& vx, const Eigen::Vector3d& vy, const Eigen::Vector3d& vz)
{
@@ -141,7 +215,9 @@ static vtkSmartPointer applyParametricSpline(const vtkSmartPointerEvaluate(u.data(), pt.data(), du);
// check distance
- if ((pt - pt_prev).norm() >= point_spacing)
+ if (1)
+ // TODO Figure out why this reduces the number of points by half and makes them twice the point spacing
+ // if ((pt - pt_prev).norm() >= point_spacing)
{
new_points->InsertNextPoint(pt.data());
pt_prev = pt;
@@ -160,7 +236,7 @@ static vtkSmartPointer applyParametricSpline(const vtkSmartPointer >& points_lists)
+static void removeRedundant(std::vector>& points_lists)
{
using IdList = std::vector;
if (points_lists.size() < 2)
@@ -168,7 +244,7 @@ static void removeRedundant(std::vector >& points_lists)
return;
}
- std::vector > new_points_lists;
+ std::vector> new_points_lists;
new_points_lists.push_back(points_lists.front());
for (std::size_t i = 1; i < points_lists.size(); i++)
{
@@ -204,7 +280,7 @@ static void removeRedundant(std::vector >& points_lists)
static void mergeRasterSegments(const vtkSmartPointer& points,
double merge_dist,
- std::vector >& points_lists)
+ std::vector>& points_lists)
{
using namespace Eigen;
using IdList = std::vector;
@@ -302,7 +378,7 @@ static void mergeRasterSegments(const vtkSmartPointer& points,
static void rectifyDirection(const vtkSmartPointer& points,
const Eigen::Vector3d& ref_point,
- std::vector >& points_lists)
+ std::vector>& points_lists)
{
using namespace Eigen;
Vector3d p0, pf;
@@ -318,7 +394,7 @@ static void rectifyDirection(const vtkSmartPointer& points,
bool reverse = (ref_point - p0).norm() > (ref_point - pf).norm();
if (reverse)
{
- for (auto& s : points_lists)
+ for (auto& s : points_lists) // reverse points in segments
{
std::reverse(s.begin(), s.end());
}
@@ -326,60 +402,6 @@ static void rectifyDirection(const vtkSmartPointer& points,
}
}
-static tool_path_planner::ToolPaths convertToPoses(const std::vector& rasters_data)
-{
- using namespace Eigen;
- tool_path_planner::ToolPaths rasters_array;
- bool reverse = true;
- for (const RasterConstructData& rd : rasters_data)
- {
- reverse = !reverse;
- tool_path_planner::ToolPath raster_path;
- std::vector raster_segments;
- raster_segments.assign(rd.raster_segments.begin(), rd.raster_segments.end());
- if (reverse)
- {
- std::reverse(raster_segments.begin(), raster_segments.end());
- }
-
- for (const PolyDataPtr& polydata : raster_segments)
- {
- tool_path_planner::ToolPathSegment raster_path_segment;
- std::size_t num_points = polydata->GetNumberOfPoints();
- Vector3d p, p_next, vx, vy, vz;
- Isometry3d pose;
- std::vector indices(num_points);
- std::iota(indices.begin(), indices.end(), 0);
- if (reverse)
- {
- std::reverse(indices.begin(), indices.end());
- }
- for (std::size_t i = 0; i < indices.size() - 1; i++)
- {
- int idx = indices[i];
- int idx_next = indices[i + 1];
- polydata->GetPoint(idx, p.data());
- polydata->GetPoint(idx_next, p_next.data());
- polydata->GetPointData()->GetNormals()->GetTuple(idx, vz.data());
- vx = (p_next - p).normalized();
- vy = vz.cross(vx).normalized();
- vz = vx.cross(vy).normalized();
- pose = Translation3d(p) * AngleAxisd(computeRotation(vx, vy, vz));
- raster_path_segment.push_back(pose);
- }
-
- // adding last pose
- pose.translation() = p_next; // orientation stays the same as previous
- raster_path_segment.push_back(pose);
-
- raster_path.push_back(raster_path_segment);
- }
- rasters_array.push_back(raster_path);
- }
-
- return rasters_array;
-}
-
namespace tool_path_planner
{
void PlaneSlicerRasterGenerator::setConfiguration(const PlaneSlicerRasterGenerator::Config& config)
@@ -387,15 +409,6 @@ void PlaneSlicerRasterGenerator::setConfiguration(const PlaneSlicerRasterGenerat
config_ = config;
}
-void PlaneSlicerRasterGenerator::setInput(pcl::PolygonMesh::ConstPtr mesh)
-{
- auto mesh_data = vtkSmartPointer::New();
- pcl::VTKUtils::mesh2vtk(*mesh, mesh_data);
- mesh_data->BuildLinks();
- mesh_data->BuildCells();
- setInput(mesh_data);
-}
-
void PlaneSlicerRasterGenerator::setInput(vtkSmartPointer mesh)
{
if (!mesh_data_)
@@ -409,7 +422,6 @@ void PlaneSlicerRasterGenerator::setInput(vtkSmartPointer mesh)
}
else
{
- CONSOLE_BRIDGE_logWarn("%s generating normal data", getName().c_str());
vtkSmartPointer normal_generator = vtkSmartPointer::New();
normal_generator->SetInputData(mesh_data_);
normal_generator->ComputePointNormalsOn();
@@ -434,6 +446,31 @@ void PlaneSlicerRasterGenerator::setInput(vtkSmartPointer mesh)
}
}
+void PlaneSlicerRasterGenerator::setInput(pcl::PolygonMesh::ConstPtr mesh)
+{
+ auto mesh_data = vtkSmartPointer::New();
+ pcl::VTKUtils::mesh2vtk(*mesh, mesh_data);
+ mesh_data->BuildLinks();
+ mesh_data->BuildCells();
+
+ // compute face and vertex using polygon info
+ tool_path_planner::computePCLMeshNormals(mesh, face_normals_, vertex_normals_);
+
+ // compute vertex normals using Moving Least Squares
+ pcl::PointCloud::Ptr mesh_cloud_ptr(new pcl::PointCloud());
+ pcl::fromPCLPointCloud2(mesh->cloud, *mesh_cloud_ptr);
+ mls_mesh_normals_ptr_ = boost::make_shared>(
+ tool_path_planner::computeMLSMeshNormals(mesh_cloud_ptr, config_.search_radius));
+
+ // align mls_vertex_normals to vertex_normals
+ if (!tool_path_planner::alignToVertexNormals(*mls_mesh_normals_ptr_, vertex_normals_))
+ {
+ CONSOLE_BRIDGE_logError("alignToVertexNormals failed");
+ }
+
+ setInput(mesh_data);
+}
+
void PlaneSlicerRasterGenerator::setInput(const shape_msgs::Mesh& mesh)
{
pcl::PolygonMesh::Ptr pcl_mesh = boost::make_shared();
@@ -442,21 +479,155 @@ void PlaneSlicerRasterGenerator::setInput(const shape_msgs::Mesh& mesh)
}
vtkSmartPointer PlaneSlicerRasterGenerator::getInput() { return mesh_data_; }
+void PlaneSlicerRasterGenerator::computePoseData(const vtkSmartPointer& polydata,
+ int idx,
+ Eigen::Vector3d& p,
+ Eigen::Vector3d& vx,
+ Eigen::Vector3d& vy,
+ Eigen::Vector3d& vz)
+{
+ Eigen::Vector3d p_next;
+ Eigen::Vector3d p_start;
+
+ // polydata->GetPoint(idx, p.data()); // use this and next point to determine x direction
+ // polydata->GetPoint(idx + 1, p_next.data());
+ polydata->GetPoint(0, p_start.data()); // use the first and last point to determine x direction
+ polydata->GetPoint(idx, p.data());
+ polydata->GetPoint(polydata->GetNumberOfPoints() - 1, p_next.data());
+ polydata->GetPointData()->GetNormals()->GetTuple(idx, vz.data());
+ vz = vz.normalized();
+ vx = p_next - p_start;
+ vx = (vx - vx.dot(vz) * vz).normalized();
+ vy = vz.cross(vx).normalized();
+}
+
+tool_path_planner::ToolPaths PlaneSlicerRasterGenerator::convertToPoses(
+ const std::vector& rasters_data)
+{
+ using namespace Eigen;
+ tool_path_planner::ToolPaths rasters_array;
+ for (const tool_path_planner::PlaneSlicerRasterGenerator::RasterConstructData& rd : rasters_data) // for every raster
+ {
+ tool_path_planner::ToolPath raster_path;
+ std::vector> raster_segments;
+ raster_segments.assign(rd.raster_segments.begin(), rd.raster_segments.end());
+ for (const vtkSmartPointer& polydata : raster_segments) // for every segment
+ {
+ tool_path_planner::ToolPathSegment raster_path_segment;
+ std::size_t num_points = polydata->GetNumberOfPoints();
+ Vector3d p, next_p;
+ Vector3d prev_vx, prev_vy, prev_vz;
+ Vector3d vx, vy, vz;
+ Vector3d next_vx, next_vy, next_vz;
+ Isometry3d pose;
+ std::vector indices(num_points);
+ std::iota(indices.begin(), indices.end(), 0);
+ // for every waypoint MAKE A POSE such that
+ // its normal uses the mesh normal
+ // its x axis points along path
+ // its y is z cross x via right hand rule
+ // average 3 x-vectors to smooth out chatter caused by triangles
+ computePoseData(polydata, 0, p, vx, vy, vz);
+ prev_vx = vx;
+ prev_vy = vy;
+ prev_vz = vz;
+ int q = 0;
+ for (std::size_t i = 0; i < indices.size() - 2; i++)
+ {
+ computePoseData(polydata, i + 1, next_p, next_vx, next_vy, next_vz);
+ vx = (prev_vx + vx + next_vx).normalized();
+ vy = vz.cross(vx).normalized();
+ pose = Translation3d(p) * AngleAxisd(computeRotation(vx, vy, vz));
+ raster_path_segment.push_back(pose);
+ q++;
+
+ prev_vx = vx;
+ prev_vy = vy;
+ prev_vz = vz;
+ vx = next_vx;
+ vy = next_vy;
+ vz = next_vz;
+ p = next_p;
+ } // end for every waypoint
+
+ if (indices.size() >= 2) // this throws away short segments
+ {
+ // adding next to last pose
+ computePoseData(polydata, indices.size() - 2, p, vx, vy, vz);
+ vx = (prev_vx + vx).normalized();
+ pose = Translation3d(p) * AngleAxisd(computeRotation(vx, vy, vz));
+ raster_path_segment.push_back(pose);
+ q++;
+
+ // adding last pose
+ polydata->GetPoint(indices.size() - 1, p.data());
+ pose = Translation3d(p) * AngleAxisd(computeRotation(vx, vy, vz));
+ raster_path_segment.push_back(pose);
+ q++;
+ }
+ raster_path.push_back(raster_path_segment);
+ } // end for every segment
+ rasters_array.push_back(raster_path);
+ } // end for every raster
+
+ return rasters_array;
+}
boost::optional PlaneSlicerRasterGenerator::generate()
{
using namespace Eigen;
using IDVec = std::vector;
- boost::optional rasters = boost::none;
+ console_bridge::setLogLevel(console_bridge::LogLevel::CONSOLE_BRIDGE_LOG_ERROR);
+
if (!mesh_data_)
{
CONSOLE_BRIDGE_logDebug("%s No mesh data has been provided", getName().c_str());
}
- // computing mayor axis using oob
- vtkSmartPointer oob = vtkSmartPointer::New();
- Vector3d corner, x_dir, y_dir, z_dir, sizes; // x is longest, y is mid and z is smallest
- oob->ComputeOBB(mesh_data_, corner.data(), x_dir.data(), y_dir.data(), z_dir.data(), sizes.data());
+ // Assign the longest axis of the bounding box to x, middle to y, and shortest to z.
+ Vector3d corner, x_dir, y_dir, z_dir, sizes;
+
+ if (config_.raster_wrt_global_axes)
+ {
+ // Determine extent of mesh along axes of current coordinate frame
+ VectorXd bounds(6);
+ std::vector extents;
+ mesh_data_->GetBounds(bounds.data());
+ extents.push_back(Vector3d::UnitX() * (bounds[1] - bounds[0])); // Extent in x-direction of supplied mesh
+ // coordinate frame
+ extents.push_back(Vector3d::UnitY() * (bounds[3] - bounds[2])); // Extent in y-direction of supplied mesh
+ // coordinate frame
+ extents.push_back(Vector3d::UnitZ() * (bounds[5] - bounds[4])); // Extent in z-direction of supplied mesh
+ // coordinate frame
+
+ // find min and max magnitude.
+ int max = 0;
+ int min = 0;
+ for (std::size_t i = 1; i < extents.size(); ++i)
+ {
+ if (extents[max].squaredNorm() < extents[i].squaredNorm())
+ max = i;
+ else if (extents[min].squaredNorm() > extents[i].squaredNorm())
+ min = i;
+ }
+
+ // Order the axes. Computing y saves comparisons and guarantees right-handedness.
+ Eigen::Matrix3d rotation_transform;
+ rotation_transform.col(0) = extents[max].normalized();
+ rotation_transform.col(2) = extents[min].normalized();
+ rotation_transform.col(1) = rotation_transform.col(2).cross(rotation_transform.col(0)).normalized();
+
+ // Extract our axes transfored to align to the major axes of our aabb
+ x_dir = rotation_transform.row(0).normalized();
+ y_dir = rotation_transform.row(1).normalized();
+ z_dir = rotation_transform.row(2).normalized();
+ }
+ else
+ {
+ // computing major axes using oob and assign to x_dir, y_dir, z_dir
+ vtkSmartPointer oob = vtkSmartPointer::New();
+ oob->ComputeOBB(mesh_data_, corner.data(), x_dir.data(), y_dir.data(), z_dir.data(), sizes.data());
+ }
// Compute the center of mass
Vector3d origin;
@@ -490,12 +661,29 @@ boost::optional PlaneSlicerRasterGenerator::generate()
sizes.z() = bounds[5] - bounds[4];
half_ext = sizes / 2.0;
- center = Eigen::Vector3d(bounds[0], bounds[2], bounds[3]) + half_ext;
+ center = Eigen::Vector3d(bounds[0], bounds[2], bounds[4]) + half_ext;
- // now cutting the mesh with planes along the y axis
- // @todo This should be calculated instead of being fixed along the y-axis
+ // Apply the rotation offset about the short direction (new Z axis) of the bounding box
Isometry3d rotation_offset = Isometry3d::Identity() * AngleAxisd(config_.raster_rot_offset, Vector3d::UnitZ());
- Vector3d raster_dir = (rotation_offset * Vector3d::UnitY()).normalized();
+
+ // Calculate direction of raster strokes, rotated by the above-specified amount
+ Vector3d raster_dir;
+ if (config_.raster_direction.isApprox(Eigen::Vector3d::Zero()))
+ {
+ CONSOLE_BRIDGE_logError("APPROX ZERO");
+ // If no direction was specified, use the middle dimension of the bounding box
+ raster_dir = Eigen::Vector3d::UnitY();
+ raster_dir = (rotation_offset * raster_dir).normalized();
+ }
+ else
+ {
+ // If a direction was specified, transform it into the frame of the bounding box
+ raster_dir = (rotation_offset * // Rotation about short axis of bounding box
+ AngleAxisd(computeRotation(x_dir, y_dir, z_dir)) * // Rotation part of 't' (recalculated because
+ // Eigen makes it hard to access)
+ config_.raster_direction) // Raster direction specified by user
+ .normalized();
+ }
// Calculate all 8 corners projected onto the raster direction vector
Eigen::VectorXd dist(8);
@@ -516,7 +704,6 @@ boost::optional PlaneSlicerRasterGenerator::generate()
// Calculate the start location
Vector3d start_loc = center + (min_coeff * raster_dir);
-
vtkSmartPointer raster_data = vtkSmartPointer::New();
for (std::size_t i = 0; i < num_planes + 1; i++)
{
@@ -549,7 +736,6 @@ boost::optional PlaneSlicerRasterGenerator::generate()
raster_data->AddInputData(stripper->GetOutput(r));
}
}
-
// build cell locator and kd_tree to recover normals later on
kd_tree_ = vtkSmartPointer::New();
kd_tree_->SetDataSet(mesh_data_);
@@ -562,12 +748,12 @@ boost::optional PlaneSlicerRasterGenerator::generate()
// collect rasters and set direction
raster_data->Update();
vtkIdType num_slices = raster_data->GetTotalNumberOfInputConnections();
- std::vector rasters_data_vec;
+ std::vector rasters_data_vec;
std::vector raster_ids;
boost::optional ref_dir;
for (std::size_t i = 0; i < num_slices; i++)
{
- RasterConstructData r;
+ tool_path_planner::PlaneSlicerRasterGenerator::RasterConstructData r;
// collecting raster segments based on min hole size
vtkSmartPointer raster_lines = raster_data->GetInput(i);
@@ -626,7 +812,7 @@ boost::optional PlaneSlicerRasterGenerator::generate()
mergeRasterSegments(raster_lines->GetPoints(), config_.min_hole_size, raster_ids);
// rectifying
- if (!rasters_data_vec.empty())
+ if (!rasters_data_vec.empty() && !rasters_data_vec.back().raster_segments.empty())
{
Vector3d ref_point;
rasters_data_vec.back().raster_segments.front()->GetPoint(0, ref_point.data()); // first point in previous raster
@@ -651,7 +837,7 @@ boost::optional PlaneSlicerRasterGenerator::generate()
decltype(points) new_points = applyParametricSpline(points, line_length, config_.point_spacing);
// add points to segment now
- PolyDataPtr segment_data = PolyDataPtr::New();
+ vtkSmartPointer segment_data = vtkSmartPointer::New();
segment_data->SetPoints(new_points);
// transforming to original coordinate system
@@ -672,16 +858,64 @@ boost::optional PlaneSlicerRasterGenerator::generate()
}
// saving into raster
- r.raster_segments.push_back(segment_data);
- r.segment_lengths.push_back(line_length);
+ if (segment_data->GetPoints()->GetNumberOfPoints() > 0)
+ {
+ r.raster_segments.push_back(segment_data);
+ r.segment_lengths.push_back(line_length);
+ }
}
}
- rasters_data_vec.push_back(r);
+ if (r.raster_segments.size() > 0)
+ rasters_data_vec.push_back(r);
+ }
+ if (rasters_data_vec.size() == 0)
+ {
+ CONSOLE_BRIDGE_logError("no rasters found");
+ ToolPaths rasters;
+ return (rasters);
+ }
+ // make sure every raster has its segments ordered and aligned correctly
+ Eigen::Vector3d raster_direction = getSegDir(rasters_data_vec[0].raster_segments[0]);
+ for (tool_path_planner::PlaneSlicerRasterGenerator::RasterConstructData rcd : rasters_data_vec)
+ {
+ rcd = alignRasterCD(rcd, raster_direction);
}
- // converting to poses msg now
- rasters = convertToPoses(rasters_data_vec);
+ if (config_.interleave_rasters)
+ {
+ std::vector tmp_rasters_data_vec;
+ // evens
+ for (size_t i = 0; i < rasters_data_vec.size(); i += 2)
+ {
+ tmp_rasters_data_vec.push_back(rasters_data_vec[i]);
+ }
+ // odds
+ for (size_t i = 1; i < rasters_data_vec.size(); i += 2)
+ {
+ tmp_rasters_data_vec.push_back(rasters_data_vec[i]);
+ }
+ // clear and copy new order
+ rasters_data_vec.clear();
+ for (size_t i = 0; i < tmp_rasters_data_vec.size(); i++)
+ {
+ rasters_data_vec.push_back(tmp_rasters_data_vec[i]);
+ }
+ }
+
+ // converting to poses msg
+ ToolPaths rasters = convertToPoses(rasters_data_vec);
+
+ if (config_.generate_extra_rasters)
+ {
+ rasters = addExtraWaypoints(rasters, config_.raster_spacing, config_.point_spacing);
+ }
+
+ if ((config_.raster_style != PROCESS_FORWARD_DIRECTION_ONLY))
+ {
+ // switch directions of every other raster, using either flipping orientation or not depending on style selected
+ rasters = reverseOddRasters(rasters, config_.raster_style);
+ }
return rasters;
}
@@ -708,46 +942,31 @@ bool PlaneSlicerRasterGenerator::insertNormals(const double search_radius, vtkSm
Eigen::Vector3d query_point;
vtkSmartPointer id_list = vtkSmartPointer::New();
data->GetPoints()->GetPoint(i, query_point.data());
- kd_tree_->FindPointsWithinRadius(search_radius, query_point.data(), id_list);
+ kd_tree_->FindClosestNPoints(1, query_point.data(), id_list);
if (id_list->GetNumberOfIds() < 1)
{
- CONSOLE_BRIDGE_logWarn("%s FindPointsWithinRadius found no points for normal averaging, using closests",
- getName().c_str());
- kd_tree_->FindClosestNPoints(1, query_point.data(), id_list);
-
- if (id_list->GetNumberOfIds() < 1)
- {
- CONSOLE_BRIDGE_logError("%s failed to find closest for normal computation", getName().c_str());
- return false;
- }
+ CONSOLE_BRIDGE_logError("%s failed to find closest for normal computation", getName().c_str());
+ return false;
}
// compute normal average
normal_vect = Eigen::Vector3d::Zero();
- std::size_t num_normals = 0;
- for (auto p = 0; p < id_list->GetNumberOfIds(); p++)
+ vtkIdType p_id = id_list->GetId(0);
+ if (p_id < 0)
{
- Eigen::Vector3d temp_normal, query_point, closest_point;
- vtkIdType p_id = id_list->GetId(p);
-
- if (p_id < 0)
- {
- CONSOLE_BRIDGE_logError("%s point id is invalid", getName().c_str());
- continue;
- }
-
- // get normal and add it to average
- normal_data->GetTuple(p_id, temp_normal.data());
- normal_vect += temp_normal.normalized();
- num_normals++;
+ CONSOLE_BRIDGE_logError("%s point id is invalid", getName().c_str());
+ continue;
}
- normal_vect /= num_normals;
+ normal_vect.x() = mls_mesh_normals_ptr_->points[p_id].normal_x;
+ normal_vect.y() = mls_mesh_normals_ptr_->points[p_id].normal_y;
+ normal_vect.z() = mls_mesh_normals_ptr_->points[p_id].normal_z;
normal_vect.normalize();
// save normal
new_normals->SetTuple3(i, normal_vect(0), normal_vect(1), normal_vect(2));
- }
+ } // end for every point
+
data->GetPointData()->SetNormals(new_normals);
return true;
}
diff --git a/tool_path_planner/src/surface_walk_raster_generator.cpp b/tool_path_planner/src/surface_walk_raster_generator.cpp
index c6a3b50f..1b1d9fc8 100644
--- a/tool_path_planner/src/surface_walk_raster_generator.cpp
+++ b/tool_path_planner/src/surface_walk_raster_generator.cpp
@@ -496,7 +496,11 @@ int findClosestPoint(const std::vector& pt, const std::vector
#include
#include
+#include // std::sort
+#include
#include
#include
#include
@@ -36,9 +38,16 @@
#include
#include
#include
+#include