Skip to content

Commit fa7498c

Browse files
committed
WIP - ADD alternative method for cloud2mesh with Rhino.Geometry
1 parent 0bc5a3f commit fa7498c

File tree

6 files changed

+281
-19
lines changed

6 files changed

+281
-19
lines changed

src/gh/components/DF_cloud_to_mesh_distance/code.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,18 +38,19 @@ def RunScript(self,
3838

3939
# conversion
4040
df_cloud_source = df_cvt_bindings.cvt_rhcloud_2_dfcloud(i_cloud_source)
41-
i_mesh_target = i_beam.to_mesh()
42-
#df_mesh_target = df_cvt_bindings.cvt_rhmesh_2_dfmesh(i_mesh_target)
41+
rhino_mesh_target = i_beam.to_mesh()
42+
df_mesh_target = df_cvt_bindings.cvt_rhmesh_2_dfmesh(rhino_mesh_target)
4343

4444
# calculate distances
45-
#o_distances = df_error_estimation.cloud_2_mesh_distance(df_cloud_source, df_mesh_target, i_signed_flag)
46-
#o_mse = df_error_estimation.compute_mse(o_distances)
47-
#o_max_deviation = df_error_estimation.compute_max_deviation(o_distances)
48-
#o_min_deviation = df_error_estimation.compute_min_deviation(o_distances)
49-
50-
o_mesh = i_mesh_target
51-
#return o_distances.tolist(), o_mse, o_max_deviation, o_min_deviation, o_mesh
52-
return 0, 0, 0, 0, o_mesh
45+
o_distances = df_error_estimation.cloud_2_mesh_distance(df_cloud_source, df_mesh_target, i_signed_flag)
46+
o_mse = df_error_estimation.compute_mse(o_distances)
47+
o_max_deviation = df_error_estimation.compute_max_deviation(o_distances)
48+
o_min_deviation = df_error_estimation.compute_min_deviation(o_distances)
49+
50+
o_mesh = rhino_mesh_target
51+
52+
return o_distances.tolist(), o_mse, o_max_deviation, o_min_deviation, o_mesh
53+
5354

5455

5556
if __name__ == "__main__":
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#! python3
2+
3+
import System
4+
import typing
5+
6+
import Rhino
7+
import Rhino.Geometry as rg
8+
from ghpythonlib.componentbase import executingcomponent as component
9+
10+
import Grasshopper as gh
11+
from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML
12+
13+
import diffCheck
14+
from diffCheck import diffcheck_bindings
15+
from diffCheck import df_cvt_bindings
16+
from diffCheck import df_error_estimation
17+
18+
19+
class CloudToMeshDistance(component):
20+
def RunScript(self,
21+
i_cloud_source: rg.PointCloud,
22+
i_beam,
23+
i_signed_flag: bool):
24+
"""
25+
The cloud-to-cloud component computes the distance between each point in the source point cloud and its nearest neighbour in thr target point cloud.
26+
27+
:param i_cloud_source: source point cloud
28+
:param i_mesh_target: target point cloud to align to
29+
30+
:return o_distances : list of calculated distances for each point
31+
:return o_mse: the average squared difference between corresponding points of source and target
32+
:return o_max_deviation: the max deviation between source and target (Hausdorff Distance)
33+
:return o_min_deviation: the min deviation between source and target
34+
"""
35+
if i_cloud_source is None or i_beam is None:
36+
ghenv.Component.AddRuntimeMessage(RML.Warning, "Please provide an object of type point cloud and an object of type mesh to compare")
37+
return None
38+
39+
# conversion
40+
df_cloud_source = df_cvt_bindings.cvt_rhcloud_2_dfcloud(i_cloud_source)
41+
rhino_mesh_target = i_beam.to_mesh()
42+
43+
# calculate distances
44+
o_distances = df_error_estimation.cloud_2_rhino_mesh_distance(df_cloud_source, rhino_mesh_target, i_signed_flag)
45+
o_mse = df_error_estimation.compute_mse(o_distances)
46+
o_max_deviation = df_error_estimation.compute_max_deviation(o_distances)
47+
o_min_deviation = df_error_estimation.compute_min_deviation(o_distances)
48+
49+
o_mesh = rhino_mesh_target
50+
51+
return o_distances.tolist(), o_mse, o_max_deviation, o_min_deviation, o_mesh
52+
53+
54+
55+
if __name__ == "__main__":
56+
com = CloudToMeshDistance()
57+
o_distances, o_mse, o_max_deviation, o_min_deviation, o_mesh = com.RunScript(
58+
i_cloud_source,
59+
i_mesh_target,
60+
i_signed_flag
61+
)
9.56 KB
Loading
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
{
2+
"name": "RANSACGlobalRegistration",
3+
"nickname": "RANSACGReg",
4+
"category": "diffCheck",
5+
"subcategory": "Registrations",
6+
"description": "Register two point clouds together with a feature matching based on RANSAC.",
7+
"exposure": 4,
8+
"instanceGuid": "805911b9-7ea9-4bbf-be34-00f9f225b9b3",
9+
"ghpython": {
10+
"hideOutput": true,
11+
"hideInput": true,
12+
"isAdvancedMode": true,
13+
"marshalOutGuids": true,
14+
"iconDisplay": 2,
15+
"inputParameters": [
16+
{
17+
"name": "i_recompute",
18+
"nickname": "i_recompute",
19+
"description": "Connect a button to recompute the registration.",
20+
"optional": true,
21+
"allowTreeAccess": true,
22+
"showTypeHints": true,
23+
"scriptParamAccess": "item",
24+
"wireDisplay": "default",
25+
"sourceCount": 0,
26+
"typeHintID": "bool"
27+
},
28+
{
29+
"name": "i_cloud_source",
30+
"nickname": "i_cloud_source",
31+
"description": "The source point cloud.",
32+
"optional": true,
33+
"allowTreeAccess": true,
34+
"showTypeHints": true,
35+
"scriptParamAccess": "item",
36+
"wireDisplay": "default",
37+
"sourceCount": 0,
38+
"typeHintID": "pointcloud"
39+
},
40+
{
41+
"name": "i_cloud_target",
42+
"nickname": "i_cloud_target",
43+
"description": "The target cloud.",
44+
"optional": false,
45+
"allowTreeAccess": true,
46+
"showTypeHints": true,
47+
"scriptParamAccess": "item",
48+
"wireDisplay": "default",
49+
"sourceCount": 0,
50+
"typeHintID": "pointcloud"
51+
},
52+
{
53+
"name": "i_radius_kd_search",
54+
"nickname": "i_radius_kd_search",
55+
"description": "The radius used to search for neighbors in the KDTree.it is expressed relative to the point cloud size (0.01 means radiusKDTreeSearch = 1% of maxSize(pointCloud). It is used for the calculation of FPFHFeatures.",
56+
"optional": false,
57+
"allowTreeAccess": true,
58+
"showTypeHints": true,
59+
"scriptParamAccess": "item",
60+
"wireDisplay": "default",
61+
"sourceCount": 0,
62+
"typeHintID": "float"
63+
},
64+
{
65+
"name": "i_neighbours_kd_search",
66+
"nickname": "i_neighbours_kd_search",
67+
"description": "The maximum number of neighbors to search for in the KDTree. It is used for the calculation of FPFHFeatures. A higher value will result in heavier computation but potentially more precise.",
68+
"optional": false,
69+
"allowTreeAccess": true,
70+
"showTypeHints": true,
71+
"scriptParamAccess": "item",
72+
"wireDisplay": "default",
73+
"sourceCount": 0,
74+
"typeHintID": "int"
75+
},
76+
{
77+
"name": "i_max_corrspondence_dist",
78+
"nickname": "i_max_corrspondence_dist",
79+
"description": "The maximum distance between correspondences. A higher value will result in more correspondences, but potentially include wrong ones.",
80+
"optional": false,
81+
"allowTreeAccess": true,
82+
"showTypeHints": true,
83+
"scriptParamAccess": "item",
84+
"wireDisplay": "default",
85+
"sourceCount": 0,
86+
"typeHintID": "float"
87+
},
88+
{
89+
"name": "is_t_estimate_pt2pt",
90+
"nickname": "is_t_estimate_pt2pt",
91+
"description": "If true it deforms the cloud to match. The transformation estimation method to use. By default, it uses a point to point transformation estimation. If true it will scale and deform the cloud.",
92+
"optional": false,
93+
"allowTreeAccess": true,
94+
"showTypeHints": true,
95+
"scriptParamAccess": "item",
96+
"wireDisplay": "default",
97+
"sourceCount": 0,
98+
"typeHintID": "bool"
99+
},
100+
{
101+
"name": "i_ransac_n",
102+
"nickname": "i_ransac_n",
103+
"description": "The number of points to sample in the source point cloud. A higher value can result in a more precise transformation, but will take more time to compute.",
104+
"optional": false,
105+
"allowTreeAccess": true,
106+
"showTypeHints": true,
107+
"scriptParamAccess": "item",
108+
"wireDisplay": "default",
109+
"sourceCount": 0,
110+
"typeHintID": "int"
111+
},
112+
{
113+
"name": "i_checker_dist",
114+
"nickname": "i_checker_dist",
115+
"description": "The maximum distance between correspondances in the FPFH space before testing a RanSaC model. It is exprimed in relative values (it is scaled by the size of the bounding box of the poinnt cloud).",
116+
"optional": false,
117+
"allowTreeAccess": true,
118+
"showTypeHints": true,
119+
"scriptParamAccess": "item",
120+
"wireDisplay": "default",
121+
"sourceCount": 0,
122+
"typeHintID": "float"
123+
},
124+
{
125+
"name": "i_similarity_threshold",
126+
"nickname": "i_similarity_threshold",
127+
"description": "The threshold for the ransac check based on edge length to consider a model as inlier. A higher value will be stricter, discarding more ransac models.",
128+
"optional": false,
129+
"allowTreeAccess": true,
130+
"showTypeHints": true,
131+
"scriptParamAccess": "item",
132+
"wireDisplay": "default",
133+
"sourceCount": 0,
134+
"typeHintID": "float"
135+
},
136+
{
137+
"name": "i_max_iterations",
138+
"nickname": "i_max_iterations",
139+
"description": "The maximum number of iterations to run the Ransac algorithm. A higher value will take more time to compute but increases the chances of finding a good transformation.",
140+
"optional": false,
141+
"allowTreeAccess": true,
142+
"showTypeHints": true,
143+
"scriptParamAccess": "item",
144+
"wireDisplay": "default",
145+
"sourceCount": 0,
146+
"typeHintID": "int"
147+
},
148+
{
149+
"name": "i_confidence_threshold",
150+
"nickname": "i_confidence_threshold",
151+
"description": "The threshold for the convergence criteria of the ransac models. A higher value will be stricter, discarding more ransac models.",
152+
"optional": false,
153+
"allowTreeAccess": true,
154+
"showTypeHints": true,
155+
"scriptParamAccess": "item",
156+
"wireDisplay": "default",
157+
"sourceCount": 0,
158+
"typeHintID": "float"
159+
}
160+
],
161+
"outputParameters": [
162+
{
163+
"name": "o_x_form",
164+
"nickname": "o_x_form",
165+
"description": "The computed transformation.",
166+
"optional": false,
167+
"sourceCount": 0,
168+
"graft": false
169+
}
170+
]
171+
}
172+
}

src/gh/diffCheck/diffCheck/df_error_estimation.py

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import numpy as np
77
import open3d as o3d
88
from diffCheck import diffcheck_bindings
9+
import Rhino.Geometry as rg
910

1011
def cloud_2_cloud_distance(source, target, signed=False):
1112
"""
@@ -18,7 +19,7 @@ def cloud_2_cloud_distance(source, target, signed=False):
1819

1920
def cloud_2_mesh_distance(source, target, signed=False):
2021
"""
21-
Calculate the distance between every point of a source pcd to its closest point on a target beam
22+
Calculate the distance between every point of a source pcd to its closest point on a target DFMesh
2223
"""
2324

2425
# for every point on the PCD compute the point_2_mesh_distance
@@ -29,6 +30,35 @@ def cloud_2_mesh_distance(source, target, signed=False):
2930

3031
return distances
3132

33+
def cloud_2_rhino_mesh_distance(source, target, signed=False):
34+
"""
35+
Calculate the distance between every point of a source pcd to its closest point on a target Rhino Mesh
36+
"""
37+
38+
#for every point on the point cloud find distance to mesh
39+
distances = []
40+
41+
for p in source.points:
42+
43+
rhp = rg.Point3d(p[0], p[1], p[2])
44+
closest_meshPoint = target.ClosestMeshPoint(rhp, 1000)
45+
closest_point = closest_meshPoint.Point
46+
face_Index = closest_meshPoint.FaceIndex
47+
distance = rhp.DistanceTo(closest_point)
48+
49+
if signed:
50+
# Calculate the direction from target to source
51+
direction = rhp - closest_point
52+
# Calculate the signed distance
53+
normal = target.NormalAt(closest_meshPoint)
54+
dot_product = direction * normal
55+
if dot_product < 0:
56+
distance = -distance
57+
58+
distances.append(distance)
59+
60+
return np.asarray(distances)
61+
3262

3363
def compute_mse(distances):
3464
"""

src/gh/diffCheck/diffCheck/df_geometries.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -243,21 +243,19 @@ def to_mesh(self):
243243
Convert the beam to a Rhino Mesh object
244244
"""
245245
rhino_brep_faces = [f.to_brep_face() for f in self.faces]
246-
# mesh = rg.Mesh()
247-
#o_side_faces = [f.to_brep_face() for f in self.side_faces]
248-
#o_joint_faces = [f.to_brep_face() for f in self.joint_faces]
249-
#rhino_brep_faces = o_side_faces + o_joint_faces
250-
251-
new_faces = []
252246
mesh = rg.Mesh()
253247

254248
new_faces = [f.DuplicateFace(True) for f in rhino_brep_faces] # .DuplicateFace bypasses the problem of untrimmed faces that appear in f.to_brep_face
255249

256250
for f in new_faces:
257-
mesh_part = rg.Mesh.CreateFromBrep(f, rg.MeshingParameters.Coarse)[0] #returns a list of meshes with one element
251+
252+
param = rg.MeshingParameters()
253+
scalef = diffCheck.df_util.get_doc_2_meters_unitf()
254+
param.MaximumEdgeLength = 0.01 / scalef
255+
mesh_part = rg.Mesh.CreateFromBrep(f, param)[0] #returns a list of meshes with one element
258256
mesh.Append(mesh_part)
259-
mesh.Compact()
260257

258+
mesh.Compact()
261259
return mesh
262260

263261
def __repr__(self):

0 commit comments

Comments
 (0)