Skip to content

Commit f205939

Browse files
eleniv3d9and3
andauthored
Enhancements error viz (#115)
* ADD units in legend and decimals according to document units * ADD x axis to histogram * FIX metadata o_legend in DF_visualization * FIX remove o_success form cvs exporter * ADD Allow Viz component to show graphs per item * FIX distances output of CloudMeshDistance to Tree * FIX add Rhino import to resolve error when updating to 8.11.24254.15001 * FIX undo changes ro DF_Tester component * FIX test pre-commits * FIX cloudmeshdistance works for Faces, input can be tree or list * ADD meshcloud distance calculation now handles non valid point clouds * FIX: quick cleaning --------- Co-authored-by: Andrea Settimi <[email protected]> Co-authored-by: Andrea Settimi <[email protected]>
1 parent 1bd5727 commit f205939

File tree

13 files changed

+11650
-3628
lines changed

13 files changed

+11650
-3628
lines changed
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
#! python3
22

3-
import System
4-
53
import Rhino
4+
import Grasshopper
65
from ghpythonlib.componentbase import executingcomponent as component
76
from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML
87

@@ -12,38 +11,52 @@
1211

1312

1413
class DFCloudMeshDistance(component):
14+
1515
def RunScript(self,
16-
i_cloud_source: System.Collections.Generic.List[Rhino.Geometry.PointCloud],
16+
i_cloud_source: Grasshopper.DataTree[Rhino.Geometry.PointCloud],
1717
i_assembly,
1818
i_signed_flag: bool,
1919
i_swap: bool,
2020
i_analysis_resolution: float):
2121

22+
if i_cloud_source is None or i_assembly is None:
23+
return None, None, None, None, None, None
24+
2225
if i_analysis_resolution is None:
2326
scalef = diffCheck.df_util.get_doc_2_meters_unitf()
2427
i_analysis_resolution = 0.1 / scalef
2528

29+
# if the input is Gh tree, flatten it
30+
flat_list = []
31+
for branch in i_cloud_source.Branches:
32+
flat_list.extend(list(branch))
33+
i_cloud_list = flat_list
34+
2635
# Based on cloud source input + beam size, decide whether to calculate joints or entire assembly and output respective message
27-
if len(i_assembly.beams) == len(i_cloud_source):
36+
if len(i_assembly.beams) == len(i_cloud_list):
2837
ghenv.Component.Message = "Per Beam" # noqa: F821
2938
rh_mesh_target_list = [beam.to_mesh(i_analysis_resolution) for beam in i_assembly.beams]
30-
elif len(i_assembly.all_joints) == len(i_cloud_source):
39+
elif len(i_assembly.all_joints) == len(i_cloud_list):
3140
ghenv.Component.Message = "Per Joint" # noqa: F821
3241
rh_mesh_target_list = [joint.to_mesh(i_analysis_resolution) for joint in i_assembly._all_joints]
42+
elif len(i_assembly.all_joint_faces) == len(i_cloud_list):
43+
ghenv.Component.Message = "Per Joint Face" # noqa: F821
44+
rh_mesh_target_list = [joint_face.to_mesh() for joint_face in i_assembly.all_joint_faces]
3345
else:
3446
ghenv.Component.AddRuntimeMessage(RML.Warning, "The input number of objects to compare matches neither the number of beams nor the number of joints") # noqa: F821
3547
return None, None, None, None, None, None
3648

3749
# conversion
38-
siffed_df_cloud_source_list = []
39-
siffed_rh_mesh_target_list = []
40-
for i in range(len(i_cloud_source)):
41-
if i_cloud_source[i] is not None:
42-
siffed_df_cloud_source_list.append(df_cvt_bindings.cvt_rhcloud_2_dfcloud(i_cloud_source[i]))
43-
siffed_rh_mesh_target_list.append(rh_mesh_target_list[i])
44-
50+
df_cloud_source_list = [df_cvt_bindings.cvt_rhcloud_2_dfcloud(i_cl_s) for i_cl_s in i_cloud_list]
4551

4652
# calculate distances
4753
o_result = df_error_estimation.df_cloud_2_rh_mesh_comparison(siffed_df_cloud_source_list, siffed_rh_mesh_target_list, i_signed_flag, i_swap)
4854

49-
return o_result.distances, o_result.distances_rmse, o_result.distances_max_deviation, o_result.distances_min_deviation, o_result.distances_sd_deviation, o_result
55+
# distances to tree
56+
distances_tree = Grasshopper.DataTree[object]()
57+
for i, sublist in enumerate(o_result.distances):
58+
for j, item in enumerate(sublist):
59+
path = Grasshopper.Kernel.Data.GH_Path(i)
60+
distances_tree.Add(item, path)
61+
62+
return distances_tree, o_result.distances_rmse, o_result.distances_max_deviation, o_result.distances_min_deviation, o_result.distances_sd_deviation, o_result

src/gh/components/DF_cloud_mesh_distance/metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"subcategory": "Analysis",
66
"description": "Computes the distance between a point cloud and a mesh",
77
"exposure": 4,
8-
"instanceGuid": "eda01e34-b89b-4e3c-a9b6-0178a05ae81b",
8+
"instanceGuid": "99422b2f-c143-43d8-a4b7-0a5fc803bffe",
99
"ghpython": {
1010
"hideOutput": true,
1111
"hideInput": true,

src/gh/components/DF_csv_exporter/code.py

+15-8
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,12 @@
1010

1111
class DFCsvExporter(component):
1212
def RunScript(self,
13-
i_dump: bool,
14-
i_export_dir: str,
15-
i_file_name: str,
16-
i_export_seperate_files: bool,
17-
i_result: DFVizResults):
13+
i_dump: bool,
14+
i_export_dir: str,
15+
i_file_name: str,
16+
i_export_seperate_files: bool,
17+
i_result: DFVizResults):
18+
1819
if i_dump:
1920
# Ensure the export directory exists
2021
os.makedirs(i_export_dir, exist_ok=True)
@@ -35,6 +36,12 @@ def RunScript(self,
3536
for list_of_values in i_result.distances:
3637
writer.writerow([list_of_values])
3738

38-
o_success = "Successfully exported the values"
39-
40-
return o_success
39+
# if __name__ == "__main__":
40+
# com = DFCsvExporter()
41+
# o_viz_settings = com.RunScript(
42+
# i_dump,
43+
# i_export_dir,
44+
# i_file_name,
45+
# i_export_seperate_files,
46+
# i_result
47+
# )

src/gh/components/DF_csv_exporter/metadata.json

+1-10
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,6 @@
7474
"typeHintID": "ghdoc"
7575
}
7676
],
77-
"outputParameters": [
78-
{
79-
"name": "o_success",
80-
"nickname": "o_success",
81-
"description": "Whether the values exported successfully",
82-
"optional": false,
83-
"sourceCount": 0,
84-
"graft": false
85-
}
86-
]
77+
"outputParameters": []
8778
}
8879
}

src/gh/components/DF_tester/metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"subcategory": "Utility",
66
"description": "This component is testing diffCheck imports and bindings.",
77
"exposure": 4,
8-
"instanceGuid": "7a600924-29bb-4682-8b95-a37752cefdbc",
8+
"instanceGuid": "d0dcb2e4-a801-4c2d-9bd4-f2b03f3feb50",
99
"ghpython": {
1010
"hideOutput": true,
1111
"hideInput": true,

src/gh/components/DF_visualization/code.py

+16-4
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
#! python3
22

3-
43
import Rhino.Geometry as rg
54
from ghpythonlib.componentbase import executingcomponent as component
65

7-
86
from diffCheck import df_cvt_bindings
97
from diffCheck import df_visualization
108
from diffCheck.df_visualization import DFVizSettings
119
from diffCheck.df_error_estimation import DFVizResults
1210
from diffCheck import diffcheck_bindings
1311

12+
1413
class DFVisualization(component):
1514
def RunScript(self,
1615
i_result: DFVizResults,
1716
i_viz_settings: DFVizSettings):
17+
18+
if i_result is None or i_viz_settings is None:
19+
return None, None, None
20+
1821
values, min_value, max_value = i_result.filter_values_based_on_valuetype(i_viz_settings)
1922

2023
# check if i_result.source is a list of pointclouds or a mesh
@@ -41,12 +44,21 @@ def RunScript(self,
4144
width=i_viz_settings.legend_width,
4245
total_height=i_viz_settings.legend_height)
4346

47+
# add option to create a histogram for each item
48+
49+
if len(i_result.source) > 1 and i_viz_settings.one_histogram_per_item:
50+
multiple_curves = True
51+
else:
52+
multiple_curves = False
53+
4454
o_histogram = df_visualization.create_histogram(values,
4555
min_value,
4656
max_value,
47-
steps=100,
57+
res=100,
58+
steps=10,
4859
plane=i_viz_settings.legend_plane,
4960
total_height=i_viz_settings.legend_height,
50-
scaling_factor=i_viz_settings.histogram_scale_factor)
61+
scaling_factor=i_viz_settings.histogram_scale_factor,
62+
multiple_curves = multiple_curves)
5163

5264
return o_colored_geo, o_legend, o_histogram

src/gh/components/DF_visualization/metadata.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
},
5050
{
5151
"name": "o_legend",
52-
"nickname": "_legend",
52+
"nickname": "o_legend",
5353
"description": "The legend of the visualization",
5454
"optional": false,
5555
"sourceCount": 0,

src/gh/components/DF_visualization_settings/code.py

+35-4
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,7 @@
22

33
import System
44
import typing
5-
65
import Rhino
7-
86
from ghpythonlib.componentbase import executingcomponent as component
97
import Grasshopper as gh
108
from Grasshopper import Instances
@@ -182,7 +180,23 @@ def RunScript(self,
182180
i_legend_height: float,
183181
i_legend_width: float,
184182
i_legend_plane: Rhino.Geometry.Plane,
185-
i_histogram_scale_factor: float):
183+
i_histogram_scale_factor: float,
184+
i_one_histogram_per_item: bool):
185+
186+
"""
187+
Compiles all the visualization settings to feed to the visualization component
188+
189+
:param i_value_type: selected type indicates Which values to display. Possible values: "dist", "RMSE", "MAX", "MIN", "STD"
190+
:param i_palette: Select a color palette to map the values to. Possible values: "Jet", "Rainbow", "RdPu", "Viridis"
191+
:param i_upper_threshold: Thresholds the values with a maximum value
192+
:param i_lower_threshold: Thresholds the values with a minimum value
193+
:param i_legend_height: the total height of the legend
194+
:param i_legend_width: the total width of the legend
195+
:param i_legend_plane: the construction plane of the legend
196+
:param i_histogram_scale_factor: Scales the height of the histogram with a factor
197+
198+
:returns o_viz_settings: the results of the comparison all in one object
199+
"""
186200
# set default values
187201
if i_value_type is not None:
188202
if i_value_type not in self.poss_value_types:
@@ -204,6 +218,8 @@ def RunScript(self,
204218
i_legend_plane = Rhino.Geometry.Plane.WorldXY
205219
if i_histogram_scale_factor is None:
206220
i_histogram_scale_factor = 0.01
221+
if i_one_histogram_per_item is None:
222+
i_one_histogram_per_item = False
207223

208224
# pack settings
209225
o_viz_settings = df_visualization.DFVizSettings(i_value_type,
@@ -213,6 +229,21 @@ def RunScript(self,
213229
i_legend_height,
214230
i_legend_width,
215231
i_legend_plane,
216-
i_histogram_scale_factor)
232+
i_histogram_scale_factor,
233+
i_one_histogram_per_item)
217234

218235
return o_viz_settings
236+
237+
# if __name__ == "__main__":
238+
# com = DFVisualizationSettings()
239+
# o_viz_settings = com.RunScript(
240+
# i_value_type,
241+
# i_palette,
242+
# i_upper_threshold,
243+
# i_lower_threshold,
244+
# i_legend_height,
245+
# i_legend_width,
246+
# i_legend_plane,
247+
# i_histogram_scale_factor,
248+
# i_one_histogram_per_item
249+
# )

src/gh/components/DF_visualization_settings/metadata.json

+12
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,18 @@
108108
"wireDisplay": "default",
109109
"sourceCount": 0,
110110
"typeHintID": "float"
111+
},
112+
{
113+
"name": "i_one_histogram_per_item",
114+
"nickname": "i_one_histogram_per_item",
115+
"description": "make one curve per item",
116+
"optional": false,
117+
"allowTreeAccess": false,
118+
"showTypeHints": true,
119+
"scriptParamAccess": "item",
120+
"wireDisplay": "default",
121+
"sourceCount": 0,
122+
"typeHintID": "float"
111123
}
112124
],
113125
"outputParameters": [
+15-14
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
Metadata-Version: 2.1
22
Name: diffCheck
3-
Version: 0.0.36
3+
Version: 0.0.37
44
Summary: DiffCheck is a package to check the differences between two timber structures
55
Home-page: https://github.com/diffCheckOrg/diffCheck
66
Author: Andrea Settimi, Damien Gilliard, Eleni Skevaki, Marirena Kladeftira, Julien Gamerro, Stefana Parascho, and Yves Weinand
77
Author-email: [email protected]
8-
License: UNKNOWN
9-
Description: # DiffCheck: CAD-Scan comparison
10-
11-
diffCheck(DF) allows to identify discrepancies across point clouds and 3D models of both individually machined timber pieces featuring various joints as well as fully assembled timber structures. It can help you quantify the differences between the CAD and scanned fabricated structure, providing a comprehensive report that highlights the discrepancies.
12-
13-
The software is designed to be user-friendly and can be used either via a Grasshopper plug-in or its Python API.
14-
15-
Visit the [DiffCheck website](https://diffcheckorg.github.io/diffCheck/) for more information and documentation.
16-
17-
![alt text](demo.png)
18-
19-
The software is developed by the [Laboratory of Timber Construction (IBOIS)](https://www.epfl.ch/labs/ibois/) and the [Laboratory for Creative Computation (CRCL)](https://www.epfl.ch/labs/crcl/) at [Polytechnique Fédérale de Lausanne (EPFL)](https://www.epfl.ch/en/).
20-
Platform: UNKNOWN
218
Classifier: License :: OSI Approved :: MIT License
229
Classifier: Programming Language :: Python :: 3
2310
Classifier: Programming Language :: Python :: 3.9
2411
Description-Content-Type: text/markdown
12+
Requires-Dist: numpy
13+
Requires-Dist: pybind11>=2.5.0
14+
15+
# DiffCheck: CAD-Scan comparison
16+
17+
diffCheck(DF) allows to identify discrepancies across point clouds and 3D models of both individually machined timber pieces featuring various joints as well as fully assembled timber structures. It can help you quantify the differences between the CAD and scanned fabricated structure, providing a comprehensive report that highlights the discrepancies.
18+
19+
The software is designed to be user-friendly and can be used either via a Grasshopper plug-in or its Python API.
20+
21+
Visit the [DiffCheck website](https://diffcheckorg.github.io/diffCheck/) for more information and documentation.
22+
23+
![alt text](demo.png)
24+
25+
The software is developed by the [Laboratory of Timber Construction (IBOIS)](https://www.epfl.ch/labs/ibois/) and the [Laboratory for Creative Computation (CRCL)](https://www.epfl.ch/labs/crcl/) at [Polytechnique Fédérale de Lausanne (EPFL)](https://www.epfl.ch/en/).

src/gh/diffCheck/diffCheck/df_error_estimation.py

+25-12
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ def df_cloud_2_rh_mesh_comparison(cloud_source_list, rhino_mesh_target_list, sig
2828
results = DFVizResults()
2929

3030
for source, target in zip(cloud_source_list, rhino_mesh_target_list):
31-
if swap:
32-
# this mean we want to visualize the result on the target mesh
33-
distances = rh_mesh_2_df_cloud_distance(target, source, signed_flag)
31+
32+
if len(source.points) == 0:
33+
distances = np.empty(0)
3434
else:
35-
# this means we want to visualize the result on the source pcd
36-
distances = df_cloud_2_rh_mesh_distance(source, target, signed_flag)
35+
if swap:
36+
# this mean we want to visualize the result on the target mesh
37+
distances = rh_mesh_2_df_cloud_distance(target, source, signed_flag)
38+
else:
39+
# this means we want to visualize the result on the source pcd
40+
distances = df_cloud_2_rh_mesh_distance(source, target, signed_flag)
3741

3842
if swap:
3943
results.add(target, source, distances)
@@ -135,18 +139,27 @@ def add(self, source, target, distances):
135139
self.source.append(source)
136140
self.target.append(target)
137141

138-
self.distances_rmse.append(np.sqrt(np.mean(distances ** 2)))
139-
self.distances_max_deviation.append(np.max(distances))
140-
self.distances_min_deviation.append(np.min(distances))
141-
self.distances_sd_deviation.append(np.std(distances))
142-
self.distances.append(distances.tolist())
142+
if distances.size == 0:
143+
self.distances_rmse.append(None)
144+
self.distances_max_deviation.append(None)
145+
self.distances_min_deviation.append(None)
146+
self.distances_sd_deviation.append(None)
147+
self.distances.append(np.empty(0))
148+
else:
149+
self.distances_rmse.append(np.sqrt(np.mean(distances ** 2)))
150+
self.distances_max_deviation.append(np.max(distances))
151+
self.distances_min_deviation.append(np.min(distances))
152+
self.distances_sd_deviation.append(np.std(distances))
153+
self.distances.append(distances.tolist())
143154

144155
def filter_values_based_on_valuetype(self, settings):
145156

146157
if settings.valueType == "Dist":
147158

148-
min_value = min(min(sublist) for sublist in self.distances)
149-
max_value = max(max(sublist) for sublist in self.distances)
159+
valid_sublists = [sublist for sublist in self.distances if len(sublist) > 0]
160+
161+
min_value = min(min(sublist) for sublist in valid_sublists)
162+
max_value = max(max(sublist) for sublist in valid_sublists)
150163
values = self.distances
151164

152165
elif settings.valueType == "RMSE":

0 commit comments

Comments
 (0)