Skip to content

Commit 22d4fda

Browse files
Merge branch 'release/2.0.0' into implement_pca
2 parents f29ec53 + eb3ab9a commit 22d4fda

File tree

37 files changed

+1522
-139
lines changed

37 files changed

+1522
-139
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ repos:
3434
types-requests==2.31.0,
3535
numpy==2.0.1,
3636
pytest==8.3.1,
37+
websockets>=10.4,
3738
types-setuptools>=71.1.0.20240818
3839
]
3940
args: [--config=pyproject.toml]

README.md

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -64,24 +64,59 @@ gantt
6464
title diffCheck - general overview
6565
excludes weekends
6666
67-
section Publication
68-
Abstract edition :active, absed, 2024-03-01, 2024-03-15
69-
Submission abstract ICSA :milestone, icsaabs, 2024-03-15, 0d
70-
Paper edition :paperd, 2024-10-01, 2024-10-30
71-
Submission paper ICSA :milestone, icsapap, 2024-10-30, 0d
72-
73-
section Code development
74-
Backend development :backenddev, after icsaabs, 6w
75-
Rhino/Grasshopper integration :rhghinteg, after backenddev, 6w
76-
Documentation & Interface :docuint, after fabar, 3w
67+
section Workshop
68+
Workshop dryrun :milestone, crit, dryrun, 2025-09-15, 1d
69+
Workshop in Boston :workshop, 2025-11-16, 2d
70+
71+
section Component development
72+
Pose estimation :CD1, 2025-05-15, 1w
73+
Communication w/ hardware :CD2, after CD1, 3w
74+
Pose comparison :CD3, after CD1, 3w
75+
General PC manipulation :CD4, after CD1, 6w
76+
Data analysis component :CD5, after CD3, 3w
77+
78+
section Workshop preparation
79+
Workshop scenario :doc1, 2025-08-01, 1w
80+
New compilation documentation :doc2, after mac, 2w
81+
New components documentation :doc2, 2025-08-01, 4w
82+
Development of special pipeline for data:doc3, after doc1, 3w
83+
84+
section Cross-platform
85+
adaptation of CMake for mac compilation :mac, 2025-07-01, 3w
7786
7887
section Prototype testing
79-
Fabrication of AR Prototype :crit, fabar, 2024-07-01, 2024-08-30
80-
Fabrication of CNC Prototype :crit, fabcnc, 2024-07-01, 2024-08-30
81-
Fabrication of Robot Prototype :crit, fabrob, 2024-07-01, 2024-08-30
82-
Data collection and evaluation :dataeval, after fabrob, 4w
88+
Fabrication of iterative prototype :fab, 2025-08-01, 2w
8389
```
8490

91+
92+
8593
## How to contribute
8694

8795
If you want to contribute to the project, please refer to the [contribution guidelines]([./CONTRIBUTING.md](https://diffcheckorg.github.io/diffCheck/contribute.html)).
96+
97+
## Logic
98+
The logic of the workflow is currently as follows:
99+
100+
```mermaid
101+
stateDiagram-v2
102+
state "[breps to assemble]" as s1
103+
state "scan of latest element placed" as s2
104+
state "get pose of i-th brep" as s3
105+
state "get pose of i-1-th brep" as s4
106+
state "compute pose of i-1-th element from scan" as s5
107+
state "compute pose difference" as s6
108+
state "compute pose correction" as s7
109+
state "assemble i-th-element" as s8
110+
state "i += 1" as s9
111+
[*]-->s2
112+
s1-->s3
113+
s1-->s4
114+
s2-->s5
115+
s5-->s6
116+
s4-->s6
117+
s6-->s7
118+
s3-->s7
119+
s7-->s8
120+
s8-->s9
121+
s9-->[*]
122+
```

deps/eigen

Submodule eigen updated from 11fd34c to 81044ec

deps/pybind11

Submodule pybind11 updated 144 files

environment.yml

48 Bytes
Binary file not shown.

src/diffCheck/geometry/DFPointCloud.cc

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,42 @@ namespace diffCheck::geometry
284284
principalAxes = {obb.R_.col(0), obb.R_.col(1), obb.R_.col(2)};
285285
}
286286
return principalAxes;
287+
void DFPointCloud::Crop(const Eigen::Vector3d &minBound, const Eigen::Vector3d &maxBound)
288+
{
289+
auto O3DPointCloud = this->Cvt2O3DPointCloud();
290+
auto O3DPointCloudCropped = O3DPointCloud->Crop(open3d::geometry::AxisAlignedBoundingBox(minBound, maxBound));
291+
this->Points.clear();
292+
for (auto &point : O3DPointCloudCropped->points_)
293+
this->Points.push_back(point);
294+
this->Colors.clear();
295+
for (auto &color : O3DPointCloudCropped->colors_)
296+
this->Colors.push_back(color);
297+
this->Normals.clear();
298+
for (auto &normal : O3DPointCloudCropped->normals_)
299+
this->Normals.push_back(normal);
300+
}
301+
302+
void DFPointCloud::Crop(const std::vector<Eigen::Vector3d> &corners)
303+
{
304+
if (corners.size() != 8)
305+
throw std::invalid_argument("The corners vector must contain exactly 8 points.");
306+
open3d::geometry::OrientedBoundingBox obb = open3d::geometry::OrientedBoundingBox::CreateFromPoints(corners);
307+
auto O3DPointCloud = this->Cvt2O3DPointCloud();
308+
auto O3DPointCloudCropped = O3DPointCloud->Crop(obb);
309+
this->Points.clear();
310+
for (auto &point : O3DPointCloudCropped->points_)
311+
this->Points.push_back(point);
312+
this->Colors.clear();
313+
for (auto &color : O3DPointCloudCropped->colors_)
314+
this->Colors.push_back(color);
315+
this->Normals.clear();
316+
for (auto &normal : O3DPointCloudCropped->normals_)
317+
this->Normals.push_back(normal);
318+
}
319+
320+
DFPointCloud DFPointCloud::Duplicate() const
321+
{
322+
return DFPointCloud(this->Points, this->Colors, this->Normals);
287323
}
288324

289325
void DFPointCloud::UniformDownsample(int everyKPoints)
@@ -328,6 +364,86 @@ namespace diffCheck::geometry
328364
return bboxPts;
329365
}
330366

367+
void DFPointCloud::SubtractPoints(const DFPointCloud &pointCloud, double distanceThreshold)
368+
{
369+
if (this->Points.size() == 0 || pointCloud.Points.size() == 0)
370+
throw std::invalid_argument("One of the point clouds is empty.");
371+
372+
auto O3DSourcePointCloud = this->Cvt2O3DPointCloud();
373+
auto O3DTargetPointCloud = std::make_shared<DFPointCloud>(pointCloud)->Cvt2O3DPointCloud();
374+
auto O3DResultPointCloud = std::make_shared<open3d::geometry::PointCloud>();
375+
376+
open3d::geometry::KDTreeFlann threeDTree;
377+
threeDTree.SetGeometry(*O3DTargetPointCloud);
378+
std::vector<int> indices;
379+
std::vector<double> distances;
380+
for (const auto &point : O3DSourcePointCloud->points_)
381+
{
382+
threeDTree.SearchRadius(point, distanceThreshold, indices, distances);
383+
if (indices.empty())
384+
{
385+
O3DResultPointCloud->points_.push_back(point);
386+
if (O3DSourcePointCloud->HasColors())
387+
{
388+
O3DResultPointCloud->colors_.push_back(O3DSourcePointCloud->colors_[&point - &O3DSourcePointCloud->points_[0]]);
389+
}
390+
if (O3DSourcePointCloud->HasNormals())
391+
{
392+
O3DResultPointCloud->normals_.push_back(O3DSourcePointCloud->normals_[&point - &O3DSourcePointCloud->points_[0]]);
393+
}
394+
}
395+
}
396+
this->Points.clear();
397+
for (auto &point : O3DResultPointCloud->points_)
398+
this->Points.push_back(point);
399+
if (O3DResultPointCloud->HasColors())
400+
{
401+
this->Colors.clear();
402+
for (auto &color : O3DResultPointCloud->colors_){this->Colors.push_back(color);};
403+
}
404+
if (O3DResultPointCloud->HasNormals())
405+
{
406+
this->Normals.clear();
407+
for (auto &normal : O3DResultPointCloud->normals_){this->Normals.push_back(normal);};
408+
}
409+
}
410+
411+
diffCheck::geometry::DFPointCloud DFPointCloud::Intersect(const DFPointCloud &pointCloud, double distanceThreshold)
412+
{
413+
if (this->Points.size() == 0 || pointCloud.Points.size() == 0)
414+
throw std::invalid_argument("One of the point clouds is empty.");
415+
416+
auto O3DSourcePointCloud = this->Cvt2O3DPointCloud();
417+
auto O3DTargetPointCloud = std::make_shared<DFPointCloud>(pointCloud)->Cvt2O3DPointCloud();
418+
auto O3DResultPointCloud = std::make_shared<open3d::geometry::PointCloud>();
419+
420+
open3d::geometry::KDTreeFlann threeDTree;
421+
threeDTree.SetGeometry(*O3DTargetPointCloud);
422+
std::vector<int> indices;
423+
std::vector<double> distances;
424+
for (const auto &point : O3DSourcePointCloud->points_)
425+
{
426+
threeDTree.SearchRadius(point, distanceThreshold, indices, distances);
427+
if (!indices.empty())
428+
{
429+
O3DResultPointCloud->points_.push_back(point);
430+
if (O3DSourcePointCloud->HasColors())
431+
{
432+
O3DResultPointCloud->colors_.push_back(O3DSourcePointCloud->colors_[&point - &O3DSourcePointCloud->points_[0]]);
433+
}
434+
if (O3DSourcePointCloud->HasNormals())
435+
{
436+
O3DResultPointCloud->normals_.push_back(O3DSourcePointCloud->normals_[&point - &O3DSourcePointCloud->points_[0]]);
437+
}
438+
}
439+
}
440+
diffCheck::geometry::DFPointCloud result;
441+
result.Points = O3DResultPointCloud->points_;
442+
result.Colors = O3DResultPointCloud->colors_;
443+
result.Normals = O3DResultPointCloud->normals_;
444+
return result;
445+
}
446+
331447
void DFPointCloud::ApplyTransformation(const diffCheck::transformation::DFTransformation &transformation)
332448
{
333449
auto O3DPointCloud = this->Cvt2O3DPointCloud();

src/diffCheck/geometry/DFPointCloud.hh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,25 @@ namespace diffCheck::geometry
9898
* @return std::vector<Eigen::Vector3d> the principal axes of the point cloud ordered by number of normals
9999
*/
100100
std::vector<Eigen::Vector3d> GetPrincipalAxes(int nComponents = 6);
101+
* @brief Crop the point cloud to a bounding box defined by the min and max bounds
102+
*
103+
* @param minBound the minimum bound of the bounding box as an Eigen::Vector3d
104+
* @param maxBound the maximum bound of the bounding box as an Eigen::Vector3d
105+
*/
106+
void Crop(const Eigen::Vector3d &minBound, const Eigen::Vector3d &maxBound);
107+
108+
/**
109+
* @brief Crop the point cloud to a bounding box defined by the 8 corners of the box
110+
* @param corners the 8 corners of the bounding box as a vector of Eigen::Vector3d
111+
*/
112+
void Crop(const std::vector<Eigen::Vector3d> &corners);
113+
114+
/**
115+
* @brief Get the duplicate of the point cloud. This is mainly used in the python bindings
116+
*
117+
* @return DFPointCloud a copy of the point cloud
118+
*/
119+
diffCheck::geometry::DFPointCloud Duplicate() const;
101120

102121
public: ///< Downsamplers
103122
/**
@@ -146,6 +165,24 @@ namespace diffCheck::geometry
146165
* ///
147166
*/
148167
std::vector<Eigen::Vector3d> GetTightBoundingBox();
168+
169+
public: ///< Point cloud subtraction and intersection
170+
/**
171+
* @brief Subtract the points, colors and normals from another point cloud when they are too close to the points of another point cloud.
172+
*
173+
* @param pointCloud the other point cloud to subtract from this one
174+
* @param distanceThreshold the distance threshold to consider a point as too close. Default is 0.01.
175+
*/
176+
void SubtractPoints(const DFPointCloud &pointCloud, double distanceThreshold = 0.01);
177+
178+
/**
179+
* @brief Intersect the points, colors and normals from another point cloud when they are close enough to the points of another point cloud. Is the point cloud interpretation of a boolean intersection.
180+
*
181+
* @param pointCloud the other point cloud to intersect with this one
182+
* @param distanceThreshold the distance threshold to consider a point as too close. Default is 0.01.
183+
* @return diffCheck::geometry::DFPointCloud the intersected point cloud
184+
*/
185+
diffCheck::geometry::DFPointCloud Intersect(const DFPointCloud &pointCloud, double distanceThreshold = 0.01);
149186

150187
public: ///< Transformers
151188
/**

src/diffCheckBindings.cc

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ PYBIND11_MODULE(diffcheck_bindings, m) {
4141
.def("downsample_by_size", &diffCheck::geometry::DFPointCloud::DownsampleBySize,
4242
py::arg("target_size"))
4343

44+
.def("subtract_points", &diffCheck::geometry::DFPointCloud::SubtractPoints,
45+
py::arg("point_cloud"), py::arg("distance_threshold"))
46+
47+
.def("intersect", &diffCheck::geometry::DFPointCloud::Intersect,
48+
py::arg("point_cloud"), py::arg("distance_threshold"))
49+
4450
.def("apply_transformation", &diffCheck::geometry::DFPointCloud::ApplyTransformation,
4551
py::arg("transformation"))
4652

@@ -57,6 +63,17 @@ PYBIND11_MODULE(diffcheck_bindings, m) {
5763

5864
.def("get_principal_axes", &diffCheck::geometry::DFPointCloud::GetPrincipalAxes,
5965
py::arg("n_components") = 6)
66+
.def("crop",
67+
(void (diffCheck::geometry::DFPointCloud::*)(const Eigen::Vector3d&, const Eigen::Vector3d&))
68+
&diffCheck::geometry::DFPointCloud::Crop,
69+
py::arg("min_bound"), py::arg("max_bound"))
70+
71+
.def("crop",
72+
(void (diffCheck::geometry::DFPointCloud::*)(const std::vector<Eigen::Vector3d>&))
73+
&diffCheck::geometry::DFPointCloud::Crop,
74+
py::arg("corners"))
75+
76+
.def("duplicate", &diffCheck::geometry::DFPointCloud::Duplicate)
6077

6178
.def("load_from_PLY", &diffCheck::geometry::DFPointCloud::LoadFromPLY)
6279
.def("save_to_PLY", &diffCheck::geometry::DFPointCloud::SaveToPLY)
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
from diffCheck import df_cvt_bindings as df_cvt
2+
3+
import Rhino
4+
from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML
5+
6+
from ghpythonlib.componentbase import executingcomponent as component
7+
8+
9+
class DFCloudDifference(component):
10+
def __init__(self):
11+
super(DFCloudDifference, self).__init__()
12+
13+
def RunScript(self,
14+
i_cloud_A: Rhino.Geometry.PointCloud,
15+
i_cloud_B: Rhino.Geometry.PointCloud,
16+
i_distance_threshold: float):
17+
df_cloud = df_cvt.cvt_rhcloud_2_dfcloud(i_cloud_A)
18+
df_cloud_substract = df_cvt.cvt_rhcloud_2_dfcloud(i_cloud_B)
19+
if i_distance_threshold is None:
20+
ghenv.Component.AddRuntimeMessage(RML.Warning, "Distance threshold not defined. 0.01 used as default value.")# noqa: F821
21+
i_distance_threshold = 0.01
22+
if i_distance_threshold <= 0:
23+
ghenv.Component.AddRuntimeMessage(RML.Warning, "Distance threshold must be greater than 0. Please provide a valid distance threshold.")# noqa: F821
24+
return None
25+
df_cloud.subtract_points(df_cloud_substract, i_distance_threshold)
26+
rh_cloud = df_cvt.cvt_dfcloud_2_rhcloud(df_cloud)
27+
return [rh_cloud]
15.1 KB
Loading

0 commit comments

Comments
 (0)