diff --git a/src/diffCheck/geometry/DFPointCloud.cc b/src/diffCheck/geometry/DFPointCloud.cc index 5324f3f6..e7021f70 100644 --- a/src/diffCheck/geometry/DFPointCloud.cc +++ b/src/diffCheck/geometry/DFPointCloud.cc @@ -155,7 +155,14 @@ namespace diffCheck::geometry DIFFCHECK_INFO("Default estimation of normals with knn = 30"); } for (auto &normal : O3DPointCloud->normals_) + { + if(normal.z() < -0.8) + { + normal = -normal; + } this->Normals.push_back(normal); + } + } else { @@ -165,7 +172,13 @@ namespace diffCheck::geometry this->Normals.clear(); for (int i = 0; i < cilantroPointCloud->normals.cols(); i++) + { + if(cilantroPointCloud->normals.col(i).z() < -0.8) + { + cilantroPointCloud->normals.col(i) = -cilantroPointCloud->normals.col(i); + } this->Normals.push_back(cilantroPointCloud->normals.col(i).cast()); + } DIFFCHECK_INFO(("Estimating normals with cilantro evaluator with knn = " + std::to_string(knn.value())).c_str()); } diff --git a/src/diffCheck/segmentation/DFSegmentation.cc b/src/diffCheck/segmentation/DFSegmentation.cc index d84ef254..ca48f2e6 100644 --- a/src/diffCheck/segmentation/DFSegmentation.cc +++ b/src/diffCheck/segmentation/DFSegmentation.cc @@ -1,4 +1,6 @@ #include "DFSegmentation.hh" +#include +#include #include #include @@ -100,7 +102,8 @@ namespace diffCheck::segmentation std::vector> referenceMesh, std::vector> &clusters, double angleThreshold, - double associationThreshold) + double associationThreshold, + double angleAssociationThreshold) { std::vector> faceSegments = std::vector>(); @@ -233,6 +236,12 @@ namespace diffCheck::segmentation } else { + std::string timestamp = std::to_string( + std::chrono::duration_cast( + std::chrono::system_clock::now().time_since_epoch() + ).count() + ); + std::ofstream logFile("C:\\Users\\localuser\\Desktop\\association_log_" + timestamp + ".txt", std::ios::app); for (std::shared_ptr face : referenceMesh) { std::shared_ptr correspondingSegment; @@ -271,10 +280,12 @@ namespace diffCheck::segmentation double currentDistance = (faceCenter - segmentCenter).norm(); double currentAngle = std::abs(sin(acos(faceNormal.dot(faceCenter - segmentCenter)))); // if the distance is smaller than the previous one, update the distance and the corresponding segment - if (std::abs(sin(acos(faceNormal.dot(segmentNormal)))) < angleThreshold && currentDistance < faceDistance && std::abs(1 - currentAngle) < angleThreshold) + if (std::abs(sin(acos(faceNormal.dot(segmentNormal)))) < angleThreshold && currentDistance * (angleAssociationThreshold + std::abs(faceNormal.dot((faceCenter - segmentCenter) / (faceCenter - segmentCenter).norm()))) < faceDistance) { + logFile << std::abs(sin(acos(faceNormal.dot(segmentNormal)))) << " < " << angleThreshold << " and " << currentDistance << " * (" << angleAssociationThreshold << " + " << std::abs(faceNormal.dot((faceCenter - segmentCenter) / (faceCenter - segmentCenter).norm())) << ") < " << faceDistance << std::endl; + logFile << " considered face: Face normal: " << faceNormal.transpose() << ", Segment normal: " << segmentNormal.transpose() << ", Current distance: " << currentDistance << ", Face distance: " << faceDistance << std::endl; correspondingSegment = segment; - faceDistance = currentDistance; + faceDistance = currentDistance * (angleAssociationThreshold + std::abs(faceNormal.dot((faceCenter - segmentCenter) / (faceCenter - segmentCenter).norm()))); } } @@ -323,6 +334,7 @@ namespace diffCheck::segmentation } faceSegments.push_back(facePoints); } + logFile.close(); } return faceSegments; } @@ -333,7 +345,8 @@ namespace diffCheck::segmentation std::vector>> &existingPointCloudSegments, std::vector>> meshes, double angleThreshold, - double associationThreshold) + double associationThreshold, + double angleAssociationThreshold) { if (unassociatedClusters.size() == 0) { @@ -441,7 +454,7 @@ namespace diffCheck::segmentation double currentDistance = (clusterCenter - faceCenter).norm() * std::abs(std::cos(clusterNormalToJunctionLineAngle)) / std::min(std::abs(clusterNormal.dot(faceNormal)), 0.05) ; - if (std::abs(sin(acos(faceNormal.dot(clusterNormal)))) < angleThreshold && currentDistance < distance && std::abs(1 - std::sin(clusterNormalToJunctionLineAngle)) < associationThreshold) + if (std::abs(sin(acos(faceNormal.dot(clusterNormal)))) < angleThreshold && currentDistance * (angleAssociationThreshold + std::abs(faceNormal.dot((faceCenter - clusterCenter) / (faceCenter - clusterCenter).norm()))) < distance) { goodMeshIndex = meshIndex; goodFaceIndex = faceIndex; diff --git a/src/diffCheck/segmentation/DFSegmentation.hh b/src/diffCheck/segmentation/DFSegmentation.hh index 38a0a487..cb453158 100644 --- a/src/diffCheck/segmentation/DFSegmentation.hh +++ b/src/diffCheck/segmentation/DFSegmentation.hh @@ -32,6 +32,7 @@ namespace diffCheck::segmentation * @param clusters the vector of clusters from cilantro to associate with the mesh faces of the reference mesh * @param angleThreshold the threshold to consider the a cluster as potential candidate for association. the value passed is the minimum sine of the angles. A value of 0 requires perfect alignment (angle = 0), while a value of 0.1 allows an angle of 5.7 degrees. * @param associationThreshold the threshold to consider the points of a segment and a mesh face as associable. It is the ratio between the surface of the closest mesh triangle and the sum of the areas of the three triangles that form the rest of the pyramid described by the mesh triangle and the point we want to associate or not. The lower the number, the more strict the association will be and some poinnts on the mesh face might be wrongfully excluded. + * @param angleAssociationThreshold a number to indicate how much distance in the plane of the face should be favored, compared to distance orthogonal to the face normal. If set to 0, any face in the same plane as the face will be considered as having a distance of 0. If set to a high value (e.g. 1000000), no difference will be made between distance in the plane of the face and orthogonal to it. Default is 0.5 * @return std::shared_ptr The unified segments */ static std::vector> DFSegmentation::AssociateClustersToMeshes( @@ -39,15 +40,18 @@ namespace diffCheck::segmentation std::vector> referenceMesh, std::vector> &clusters, double angleThreshold = 0.1, - double associationThreshold = 0.1); + double associationThreshold = 0.1, + double angleAssociationThreshold = 0.5); /** @brief Iterated through clusters and finds the corresponding mesh face. It then associates the points of the cluster that are on the mesh face to the segment already associated with the mesh face. * @param isCylinder a boolean to indicate if the model is a cylinder. If true, the method will use the GetCenterAndAxis method of the mesh to find the center and axis of the mesh. based on that, we only want points that have normals more or less perpendicular to the cylinder axis. * @param unassociatedClusters the clusters from the normal-based segmentatinon that haven't been associated yet. * @param existingPointCloudSegments the already associated segments per mesh face. * @param meshes the mesh faces for all the model. This is used to associate the clusters to the mesh faces. - * * @param angleThreshold the threshold to consider the a cluster as potential candidate for association. the value passed is the minimum sine of the angles. A value of 0 requires perfect alignment (angle = 0), while a value of 0.1 allows an angle of 5.7 degrees. + * @param angleThreshold the threshold to consider the a cluster as potential candidate for association. the value passed is the minimum sine of the angles. A value of 0 requires perfect alignment (angle = 0), while a value of 0.1 allows an angle of 5.7 degrees. * @param associationThreshold the threshold to consider the points of a segment and a mesh face as associable. It is the ratio between the surface of the closest mesh triangle and the sum of the areas of the three triangles that form the rest of the pyramid described by the mesh triangle and the point we want to associate or not. The lower the number, the more strict the association will be and some poinnts on the mesh face might be wrongfully excluded. + * @param angleAssociationThreshold a number to indicate how much distance in the plane of the face should be favored, compared to distance orthogonal to the face normal. If set to 0, any face in the same plane as the face will be considered as having a distance of 0. If set to a high value (e.g. 1000000), no difference will be made between distance in the plane of the face and orthogonal to it. Default is 0.5 + * @return void */ static void DFSegmentation::CleanUnassociatedClusters( bool isCylinder, @@ -55,6 +59,7 @@ namespace diffCheck::segmentation std::vector>> &existingPointCloudSegments, std::vector>> meshes, double angleThreshold = 0.1, - double associationThreshold = 0.1); + double associationThreshold = 0.1, + double angleAssociationThreshold = 0.5); }; } // namespace diffCheck::segmentation \ No newline at end of file diff --git a/src/diffCheckBindings.cc b/src/diffCheckBindings.cc index 25dab20e..e1a7a081 100644 --- a/src/diffCheckBindings.cc +++ b/src/diffCheckBindings.cc @@ -229,7 +229,8 @@ PYBIND11_MODULE(diffcheck_bindings, m) { py::arg("reference_mesh"), py::arg("unassociated_clusters"), py::arg("angle_threshold") = 0.1, - py::arg("association_threshold") = 0.1) + py::arg("association_threshold") = 0.1, + py::arg("angle_association_threshold") = 0.5) .def_static("clean_unassociated_clusters", &diffCheck::segmentation::DFSegmentation::CleanUnassociatedClusters, py::arg("is_roundwood"), @@ -237,5 +238,6 @@ PYBIND11_MODULE(diffcheck_bindings, m) { py::arg("associated_clusters"), py::arg("reference_mesh"), py::arg("angle_threshold") = 0.1, - py::arg("association_threshold") = 0.1); + py::arg("association_threshold") = 0.1, + py::arg("angle_association_threshold") = 0.5); } diff --git a/src/gh/components/DF_CAD_segmentator/code.py b/src/gh/components/DF_CAD_segmentator/code.py index 5a561c33..31809367 100644 --- a/src/gh/components/DF_CAD_segmentator/code.py +++ b/src/gh/components/DF_CAD_segmentator/code.py @@ -20,7 +20,8 @@ def RunScript(self, i_clouds: System.Collections.Generic.IList[Rhino.Geometry.PointCloud], i_assembly, i_angle_threshold: float = 0.1, - i_association_threshold: float = 0.1): + i_association_threshold: float = 0.1, + i_angle_association_threshold: float = 0.5): if i_clouds is None or i_assembly is None: self.AddRuntimeMessage(RML.Warning, "Please provide a cloud and an assembly to segment.") @@ -29,13 +30,15 @@ def RunScript(self, i_angle_threshold = 0.1 if i_association_threshold is None: i_association_threshold = 0.1 - + if i_angle_association_threshold is None: + i_angle_association_threshold = 0.5 o_face_clusters = [] df_clusters = [] # we make a deepcopy of the input clouds df_clouds = [df_cvt_bindings.cvt_rhcloud_2_dfcloud(cloud.Duplicate()) for cloud in i_clouds] df_beams = i_assembly.beams + df_asssociated_cluster_faces_per_beam = [] for df_b in df_beams: o_face_clusters.append([]) @@ -49,22 +52,29 @@ def RunScript(self, reference_mesh=df_b_mesh_faces, unassociated_clusters=df_clouds, angle_threshold=i_angle_threshold, - association_threshold=i_association_threshold + association_threshold=i_association_threshold, + angle_association_threshold=i_angle_association_threshold ) + df_asssociated_cluster_faces_per_beam.append(df_asssociated_cluster_faces) + + for i, df_b in enumerate(df_beams): + rh_b_mesh_faces = [df_b_f.to_mesh() for df_b_f in df_b.side_faces] + df_b_mesh_faces = [df_cvt_bindings.cvt_rhmesh_2_dfmesh(rh_b_mesh_face) for rh_b_mesh_face in rh_b_mesh_faces] dfb_segmentation.DFSegmentation.clean_unassociated_clusters( is_roundwood=df_b.is_roundwood, unassociated_clusters=df_clouds, - associated_clusters=[df_asssociated_cluster_faces], + associated_clusters=[df_asssociated_cluster_faces_per_beam[i]], reference_mesh=[df_b_mesh_faces], angle_threshold=i_angle_threshold, - association_threshold=i_association_threshold + association_threshold=i_association_threshold, + angle_association_threshold=i_angle_association_threshold ) - o_face_clusters[-1] = [df_cvt_bindings.cvt_dfcloud_2_rhcloud(cluster) for cluster in df_asssociated_cluster_faces] + o_face_clusters[-1] = [df_cvt_bindings.cvt_dfcloud_2_rhcloud(cluster) for cluster in df_asssociated_cluster_faces_per_beam[i]] df_asssociated_cluster = dfb_geometry.DFPointCloud() - for df_associated_face in df_asssociated_cluster_faces: + for df_associated_face in df_asssociated_cluster_faces_per_beam[i]: df_asssociated_cluster.add_points(df_associated_face) df_clusters.append(df_asssociated_cluster) diff --git a/src/gh/components/DF_CAD_segmentator/metadata.json b/src/gh/components/DF_CAD_segmentator/metadata.json index 087ade1c..ddfe81bd 100644 --- a/src/gh/components/DF_CAD_segmentator/metadata.json +++ b/src/gh/components/DF_CAD_segmentator/metadata.json @@ -60,6 +60,18 @@ "wireDisplay": "default", "sourceCount": 0, "typeHintID": "float" + }, + { + "name": "i_angle_association_threshold", + "nickname": "i_angle_association_threshold", + "description": "A number to indicate how much distance in the plane of the face should be favored, compared to distance orthogonal to the face normal. Default is 0.5", + "optional": true, + "allowTreeAccess": true, + "showTypeHints": true, + "scriptParamAccess": "item", + "wireDisplay": "default", + "sourceCount": 0, + "typeHintID": "float" } ], "outputParameters": [