Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
d5ed24e
feat: add principal axes calculation for pointclouds for pose estimation
DamienGilliard Apr 9, 2025
f43e197
fix: add cilantro KMeans clustering to main header
DamienGilliard Apr 9, 2025
4809f30
feat: add empty component for axis calculation. code must be written
DamienGilliard Apr 9, 2025
3486fa4
feat: add python binding for axis calculation
DamienGilliard Apr 9, 2025
ad8e895
feat: add poses module for pose historic saving
DamienGilliard May 7, 2025
4d5c71b
feat-wip: add code to main axis calculation component. OK but reliabi…
DamienGilliard May 7, 2025
256fd88
feat: add 'save' mathod to DFPoses classes to dump it in a JSON
DamienGilliard May 9, 2025
3971d40
fix: update the metadata of the axis calculation component
DamienGilliard May 9, 2025
13c4759
feat: make pose calculation component leaner
DamienGilliard May 9, 2025
78231f7
feat: slight refactor of df_poses module
DamienGilliard May 9, 2025
d967ea8
fix: simplify k-means clustering implementation
DamienGilliard May 9, 2025
9aadeff
Merge branch 'patch_ws' into implement_pca
DamienGilliard May 9, 2025
188dc81
feat: add unit test to KMeans clustering of normals
DamienGilliard May 10, 2025
e80b266
feat-wip: tentative planning added for DF2 development - TBD
DamienGilliard May 15, 2025
136e3dc
ADD Http Listener
eleniv3d May 15, 2025
56a80d5
fix: marameter name from singular to plural, and corresponding list a…
DamienGilliard May 16, 2025
38aab63
ADD TCP Listener
eleniv3d May 16, 2025
2fd80df
FIX lintering
eleniv3d May 27, 2025
aeecaa2
FIX tcp component + add example file sender
eleniv3d May 27, 2025
5cacef1
Merge branch 'main' into IO
eleniv3d May 28, 2025
4aafbf8
WIP FIX WS Receiver
eleniv3d May 28, 2025
0d98ad0
fix: have new elements pick-up the indexes of the already-existing el…
DamienGilliard May 28, 2025
00ceefa
fix: typo in nickname of i_clouds
DamienGilliard May 28, 2025
3c90378
feat: add a component to crop point clouds
DamienGilliard Jun 17, 2025
a514125
feat: add cropping capability to DFPointCloud
DamienGilliard Jun 17, 2025
e09fb79
FIX websocket component
eleniv3d Jun 17, 2025
f07431f
feat: add binding for cropping capability of point cloud
DamienGilliard Jun 17, 2025
46c6d6f
FIX light refactoring and input reorder
eleniv3d Jun 22, 2025
c546fec
ADD drop btns and panel with standard inputs similar to vizualization…
eleniv3d Jun 22, 2025
a0289fc
fix: simplify cropping component
DamienGilliard Jul 22, 2025
a98b80f
feat: add point cloud subtraction method and implement it in component
DamienGilliard Jul 22, 2025
9964b85
feat-wip: add sequence schema of the df2 workflow as it is now developed
DamienGilliard Jul 22, 2025
8509138
FIX rename python file
eleniv3d Jul 24, 2025
2588656
FIX remove repeated ws from stickys
eleniv3d Jul 25, 2025
683b4e8
ADD warning if trying to load data from tcp/ws listener without start…
eleniv3d Jul 25, 2025
27cad32
ADD sleep to prevent CPU spin
eleniv3d Jul 25, 2025
bd004f7
ADD doc strings
eleniv3d Jul 25, 2025
b383bc1
FIX add docstrings
eleniv3d Jul 25, 2025
7f11249
FIX missing lines from PKG-INFO
eleniv3d Jul 25, 2025
484810f
FIX RML.Warning
eleniv3d Jul 25, 2025
fcc1936
feat: add point cloud duplication for python binding
DamienGilliard Jul 30, 2025
d65fb17
feat: add capacity to crop with any brep, and keep trace of in and ou…
DamienGilliard Jul 30, 2025
10d1943
feat: add point cloud intersection calculation
DamienGilliard Jul 30, 2025
02b4756
feat: add point cloud intersection calculation component
DamienGilliard Jul 30, 2025
bbbee07
feat: add point cloud merge component
DamienGilliard Jul 30, 2025
718a3a6
feat: add point cloud intersection calculation to DFPointCloud
DamienGilliard Jul 30, 2025
4d287f2
FIX cleanup warnings that didnt work
eleniv3d Jul 30, 2025
c65b4ae
Merge pull request #152 from diffCheckOrg/IO
DamienGilliard Jul 30, 2025
f7b1ae6
Update code.py
DamienGilliard Jul 30, 2025
3d33b61
remove unused import
DamienGilliard Jul 30, 2025
f684ca0
Merge pull request #160 from diffCheckOrg/DamienGilliard-patch-3-to-m…
DamienGilliard Jul 30, 2025
e7dad06
fix: rename SubstractCloud to CloudDifference and fix mistakes in met…
DamienGilliard Aug 4, 2025
5308e44
fix: rename MergeCloud to CloudUnion and fix mistakes in metadata
DamienGilliard Aug 4, 2025
164ef82
fix: metadata in CropCloud and IntersectCloud
DamienGilliard Aug 4, 2025
1baf875
fix: small typos again in the metadata
DamienGilliard Aug 4, 2025
8cdd175
feat: add possibility to crop pc with any box
DamienGilliard Aug 4, 2025
3eebaa5
feat: improve cropping component following @eleniv3d 's comments
DamienGilliard Aug 4, 2025
bc42fe2
fix: rename CloudCrop to CloudSplit
DamienGilliard Aug 4, 2025
961b28b
fix: committing insignificant changes to example file because ignorin…
DamienGilliard Aug 10, 2025
3828e8e
fix: improvements to code component and metadata following @eleniv3d …
DamienGilliard Aug 10, 2025
2181ad9
feat: add plane property to DFBeam
DamienGilliard Aug 11, 2025
cedfcc1
feat: add max id to CAD segmentation to allow segmentation during fab…
DamienGilliard Aug 11, 2025
953f5f4
feat: remove the i_stop_after_id parameter because it is moved to a d…
DamienGilliard Sep 2, 2025
adf1576
feat: create new truncate_assembly component
DamienGilliard Sep 2, 2025
f7016b4
fix: main axis of DFBeam computed such that the main axes are also th…
DamienGilliard Sep 2, 2025
de7b580
fix: add ghpythonlib. to the overrides of mypy
DamienGilliard Sep 2, 2025
5745db8
feat: update DFMainPCAxes to save the new poses only when user trigge…
DamienGilliard Sep 2, 2025
a4e54d2
fix: name of class in truncate_assembly component
DamienGilliard Sep 9, 2025
a637b5d
fix: remove unused function parameter in DF_CAD_segmentator component
DamienGilliard Sep 9, 2025
a1d5266
FIX minor cosmetic changes
eleniv3d Oct 2, 2025
eb3ab9a
Merge pull request #155 from diffCheckOrg/feature/point_cloud_utils
eleniv3d Oct 2, 2025
2513713
fix: change index in TruncateAssembly component
DamienGilliard Oct 2, 2025
a851476
fix: change sign of index change in TruncateAssembly component
DamienGilliard Oct 2, 2025
b00e145
fix: output the history as ghtree
DamienGilliard Oct 15, 2025
6b38623
feat: add fallback to obb when knn on normals gives insufficiently di…
DamienGilliard Oct 15, 2025
daff8bb
ADD check i_assembly has enough beams and if there is an error on a c…
eleniv3d Oct 20, 2025
067903e
FIX rename files assossiated with Pose Estimation component to have t…
eleniv3d Oct 20, 2025
1fad289
FIX typo and use not all sticky but a single namespaced key df_poses
eleniv3d Oct 20, 2025
2995041
FIX remove re-assignying new_xDirection and fix projection to use tth…
eleniv3d Oct 20, 2025
f29ec53
Merge pull request #163 from diffCheckOrg/implement_pca_patch/improve…
eleniv3d Oct 20, 2025
22d4fda
Merge branch 'release/2.0.0' into implement_pca
DamienGilliard Oct 20, 2025
4d2f3b1
fix: small merge issue
DamienGilliard Oct 20, 2025
b2af2b8
Merge pull request #149 from diffCheckOrg/implement_pca
DamienGilliard Oct 20, 2025
d5f437c
fix: update insubordinate gh file
DamienGilliard Oct 20, 2025
7d41f20
Merge branch 'release/2.0.0' of https://github.com/diffCheckOrg/diffC…
DamienGilliard Oct 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ repos:
types-requests==2.31.0,
numpy==2.0.1,
pytest==8.3.1,
websockets>=10.4,
types-setuptools>=71.1.0.20240818
]
args: [--config=pyproject.toml]
Expand Down
63 changes: 49 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,24 +64,59 @@ gantt
title diffCheck - general overview
excludes weekends

section Publication
Abstract edition :active, absed, 2024-03-01, 2024-03-15
Submission abstract ICSA :milestone, icsaabs, 2024-03-15, 0d
Paper edition :paperd, 2024-10-01, 2024-10-30
Submission paper ICSA :milestone, icsapap, 2024-10-30, 0d

section Code development
Backend development :backenddev, after icsaabs, 6w
Rhino/Grasshopper integration :rhghinteg, after backenddev, 6w
Documentation & Interface :docuint, after fabar, 3w
section Workshop
Workshop dryrun :milestone, crit, dryrun, 2025-09-15, 1d
Workshop in Boston :workshop, 2025-11-16, 2d

section Component development
Pose estimation :CD1, 2025-05-15, 1w
Communication w/ hardware :CD2, after CD1, 3w
Pose comparison :CD3, after CD1, 3w
General PC manipulation :CD4, after CD1, 6w
Data analysis component :CD5, after CD3, 3w

section Workshop preparation
Workshop scenario :doc1, 2025-08-01, 1w
New compilation documentation :doc2, after mac, 2w
New components documentation :doc2, 2025-08-01, 4w
Development of special pipeline for data:doc3, after doc1, 3w

section Cross-platform
adaptation of CMake for mac compilation :mac, 2025-07-01, 3w

section Prototype testing
Fabrication of AR Prototype :crit, fabar, 2024-07-01, 2024-08-30
Fabrication of CNC Prototype :crit, fabcnc, 2024-07-01, 2024-08-30
Fabrication of Robot Prototype :crit, fabrob, 2024-07-01, 2024-08-30
Data collection and evaluation :dataeval, after fabrob, 4w
Fabrication of iterative prototype :fab, 2025-08-01, 2w
```



## How to contribute

If you want to contribute to the project, please refer to the [contribution guidelines]([./CONTRIBUTING.md](https://diffcheckorg.github.io/diffCheck/contribute.html)).

## Logic
The logic of the workflow is currently as follows:

```mermaid
stateDiagram-v2
state "[breps to assemble]" as s1
state "scan of latest element placed" as s2
state "get pose of i-th brep" as s3
state "get pose of i-1-th brep" as s4
state "compute pose of i-1-th element from scan" as s5
state "compute pose difference" as s6
state "compute pose correction" as s7
state "assemble i-th-element" as s8
state "i += 1" as s9
[*]-->s2
s1-->s3
s1-->s4
s2-->s5
s5-->s6
s4-->s6
s6-->s7
s3-->s7
s7-->s8
s8-->s9
s9-->[*]
```
2 changes: 1 addition & 1 deletion deps/eigen
Submodule eigen updated from 11fd34 to 81044e
2 changes: 1 addition & 1 deletion deps/pybind11
Submodule pybind11 updated 144 files
Binary file modified doc/_static/example_files/subtractive_gh_v1.gh
Binary file not shown.
Binary file modified environment.yml
Binary file not shown.
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ module = [
"GH_IO.*",
"clr.*",
"diffcheck_bindings",
"diffCheck.diffcheck_bindings"
"diffCheck.diffcheck_bindings",
"ghpythonlib.*"
]
ignore_missing_imports = true

Expand Down
3 changes: 3 additions & 0 deletions src/diffCheck.hh
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#include <loguru.hpp>

#include <cilantro/cilantro.hpp>
#include <cilantro/clustering/kmeans.hpp>

#include <Eigen/Dense>

// diffCheck includes
#include "diffCheck/log.hh"
Expand Down
7 changes: 7 additions & 0 deletions src/diffCheck/IOManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -68,4 +68,11 @@ namespace diffCheck::io
std::filesystem::path pathCloud = pathTestData / "test_pc_for_SOR_101pts_with_1_outlier.ply";
return pathCloud.string();
}

std::string GetTwoConnectedPlanesPlyPath()
{
std::filesystem::path pathTestData = GetTestDataDir();
std::filesystem::path pathCloud = pathTestData / "two_connected_planes_with_normals.ply";
return pathCloud.string();
}
} // namespace diffCheck::io
2 changes: 2 additions & 0 deletions src/diffCheck/IOManager.hh
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ namespace diffCheck::io
std::string GetRoofQuarterPlyPath();
/// @brief Get the path to the plane point cloud with one outlier
std::string GetPlanePCWithOneOutliers();
/// @brief Get the path to the two connected planes ply test file
std::string GetTwoConnectedPlanesPlyPath();
} // namespace diffCheck::io
188 changes: 188 additions & 0 deletions src/diffCheck/geometry/DFPointCloud.cc
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,114 @@ namespace diffCheck::geometry
this->Normals.push_back(normal);
}

std::vector<Eigen::Vector3d> DFPointCloud::GetPrincipalAxes(int nComponents)
{
std::vector<Eigen::Vector3d> principalAxes;

if (! this->HasNormals())
{
DIFFCHECK_WARN("The point cloud has no normals. Normals will be estimated with knn = 20.");
this->EstimateNormals(true, 20);
}

// Convert normals to Eigen matrix
Eigen::Matrix<double, 3, Eigen::Dynamic> normalMatrix(3, this->Normals.size());
for (size_t i = 0; i < this->Normals.size(); ++i)
{
normalMatrix.col(i) = this->Normals[i].cast<double>();
}

cilantro::KMeans<double, 3> kmeans(normalMatrix);
kmeans.cluster(nComponents);

const cilantro::VectorSet3d& centroids = kmeans.getClusterCentroids();
const std::vector<size_t>& assignments = kmeans.getPointToClusterIndexMap();
std::vector<int> clusterSizes(nComponents, 0);
for (size_t i = 0; i < assignments.size(); ++i)
{
clusterSizes[assignments[i]]++;
}
// Sort clusters by size
std::vector<std::pair<int, Eigen::Vector3d>> sortedClustersBySize(nComponents);
for (size_t i = 0; i < nComponents; ++i)
{
sortedClustersBySize[i] = {clusterSizes[i], centroids.col(i)};
}
std::sort(sortedClustersBySize.begin(), sortedClustersBySize.end(), [](const auto& a, const auto& b)
{
return a.first > b.first;
});

for(size_t i = 0; i < nComponents; ++i)
{
if(principalAxes.size() == 0)
{
principalAxes.push_back(sortedClustersBySize[i].second);
}
else
{
bool isAlreadyPresent = false;
for (const auto& axis : principalAxes)
{
double dotProduct = std::abs(axis.dot(sortedClustersBySize[i].second));
if (std::abs(dotProduct) > 0.7) // Threshold to consider as similar direction
{
isAlreadyPresent = true;
break;
}
}
if (!isAlreadyPresent)
{
principalAxes.push_back(sortedClustersBySize[i].second);
}
}
}
if (principalAxes.size() < 2) // Fallback to OBB if k-means fails to provide enough distinct axes
{
open3d::geometry::OrientedBoundingBox obb = this->Cvt2O3DPointCloud()->GetOrientedBoundingBox();
principalAxes = {obb.R_.col(0), obb.R_.col(1), obb.R_.col(2)};
}
return principalAxes;
}

void DFPointCloud::Crop(const Eigen::Vector3d &minBound, const Eigen::Vector3d &maxBound)
{
auto O3DPointCloud = this->Cvt2O3DPointCloud();
auto O3DPointCloudCropped = O3DPointCloud->Crop(open3d::geometry::AxisAlignedBoundingBox(minBound, maxBound));
this->Points.clear();
for (auto &point : O3DPointCloudCropped->points_)
this->Points.push_back(point);
this->Colors.clear();
for (auto &color : O3DPointCloudCropped->colors_)
this->Colors.push_back(color);
this->Normals.clear();
for (auto &normal : O3DPointCloudCropped->normals_)
this->Normals.push_back(normal);
}

void DFPointCloud::Crop(const std::vector<Eigen::Vector3d> &corners)
{
if (corners.size() != 8)
throw std::invalid_argument("The corners vector must contain exactly 8 points.");
open3d::geometry::OrientedBoundingBox obb = open3d::geometry::OrientedBoundingBox::CreateFromPoints(corners);
auto O3DPointCloud = this->Cvt2O3DPointCloud();
auto O3DPointCloudCropped = O3DPointCloud->Crop(obb);
this->Points.clear();
for (auto &point : O3DPointCloudCropped->points_)
this->Points.push_back(point);
this->Colors.clear();
for (auto &color : O3DPointCloudCropped->colors_)
this->Colors.push_back(color);
this->Normals.clear();
for (auto &normal : O3DPointCloudCropped->normals_)
this->Normals.push_back(normal);
}

DFPointCloud DFPointCloud::Duplicate() const
{
return DFPointCloud(this->Points, this->Colors, this->Normals);
}

void DFPointCloud::UniformDownsample(int everyKPoints)
{
auto O3DPointCloud = this->Cvt2O3DPointCloud();
Expand Down Expand Up @@ -258,6 +366,86 @@ namespace diffCheck::geometry
return bboxPts;
}

void DFPointCloud::SubtractPoints(const DFPointCloud &pointCloud, double distanceThreshold)
{
if (this->Points.size() == 0 || pointCloud.Points.size() == 0)
throw std::invalid_argument("One of the point clouds is empty.");

auto O3DSourcePointCloud = this->Cvt2O3DPointCloud();
auto O3DTargetPointCloud = std::make_shared<DFPointCloud>(pointCloud)->Cvt2O3DPointCloud();
auto O3DResultPointCloud = std::make_shared<open3d::geometry::PointCloud>();

open3d::geometry::KDTreeFlann threeDTree;
threeDTree.SetGeometry(*O3DTargetPointCloud);
std::vector<int> indices;
std::vector<double> distances;
for (const auto &point : O3DSourcePointCloud->points_)
{
threeDTree.SearchRadius(point, distanceThreshold, indices, distances);
if (indices.empty())
{
O3DResultPointCloud->points_.push_back(point);
if (O3DSourcePointCloud->HasColors())
{
O3DResultPointCloud->colors_.push_back(O3DSourcePointCloud->colors_[&point - &O3DSourcePointCloud->points_[0]]);
}
if (O3DSourcePointCloud->HasNormals())
{
O3DResultPointCloud->normals_.push_back(O3DSourcePointCloud->normals_[&point - &O3DSourcePointCloud->points_[0]]);
}
}
}
this->Points.clear();
for (auto &point : O3DResultPointCloud->points_)
this->Points.push_back(point);
if (O3DResultPointCloud->HasColors())
{
this->Colors.clear();
for (auto &color : O3DResultPointCloud->colors_){this->Colors.push_back(color);};
}
if (O3DResultPointCloud->HasNormals())
{
this->Normals.clear();
for (auto &normal : O3DResultPointCloud->normals_){this->Normals.push_back(normal);};
}
}

diffCheck::geometry::DFPointCloud DFPointCloud::Intersect(const DFPointCloud &pointCloud, double distanceThreshold)
{
if (this->Points.size() == 0 || pointCloud.Points.size() == 0)
throw std::invalid_argument("One of the point clouds is empty.");

auto O3DSourcePointCloud = this->Cvt2O3DPointCloud();
auto O3DTargetPointCloud = std::make_shared<DFPointCloud>(pointCloud)->Cvt2O3DPointCloud();
auto O3DResultPointCloud = std::make_shared<open3d::geometry::PointCloud>();

open3d::geometry::KDTreeFlann threeDTree;
threeDTree.SetGeometry(*O3DTargetPointCloud);
std::vector<int> indices;
std::vector<double> distances;
for (const auto &point : O3DSourcePointCloud->points_)
{
threeDTree.SearchRadius(point, distanceThreshold, indices, distances);
if (!indices.empty())
{
O3DResultPointCloud->points_.push_back(point);
if (O3DSourcePointCloud->HasColors())
{
O3DResultPointCloud->colors_.push_back(O3DSourcePointCloud->colors_[&point - &O3DSourcePointCloud->points_[0]]);
}
if (O3DSourcePointCloud->HasNormals())
{
O3DResultPointCloud->normals_.push_back(O3DSourcePointCloud->normals_[&point - &O3DSourcePointCloud->points_[0]]);
}
}
}
diffCheck::geometry::DFPointCloud result;
result.Points = O3DResultPointCloud->points_;
result.Colors = O3DResultPointCloud->colors_;
result.Normals = O3DResultPointCloud->normals_;
return result;
}

void DFPointCloud::ApplyTransformation(const diffCheck::transformation::DFTransformation &transformation)
{
auto O3DPointCloud = this->Cvt2O3DPointCloud();
Expand Down
Loading
Loading