diff --git a/CMakeLists.txt b/CMakeLists.txt index 2f7fa708..79f25866 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.16.) -project(diffCheck VERSION 1.3.0 LANGUAGES CXX C) +project(diffCheck VERSION 1.3.1 LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 17) list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake) diff --git a/deps/eigen b/deps/eigen index 81044ec1..954e2115 160000 --- a/deps/eigen +++ b/deps/eigen @@ -1 +1 @@ -Subproject commit 81044ec13df7608d0d9d86aff2ef9805fc69bed1 +Subproject commit 954e21152e204b1960aca802eb9f16d054d70fd9 diff --git a/deps/pybind11 b/deps/pybind11 index 03d8f487..23c59b6e 160000 --- a/deps/pybind11 +++ b/deps/pybind11 @@ -1 +1 @@ -Subproject commit 03d8f48750ba4486a2c9aeff82e9702109db5cb3 +Subproject commit 23c59b6e3d3535949bcb30b24de4fefdcded44d9 diff --git a/manifest.yml b/manifest.yml index 6b3e4f27..629efda5 100644 --- a/manifest.yml +++ b/manifest.yml @@ -1,6 +1,6 @@ --- name: diffCheck -version: 1.3.0 +version: 1.3.1 authors: - Andrea Settimi - Damien Gilliard diff --git a/src/gh/components/DF_pose_comparison/code.py b/src/gh/components/DF_pose_comparison/code.py new file mode 100644 index 00000000..5576303a --- /dev/null +++ b/src/gh/components/DF_pose_comparison/code.py @@ -0,0 +1,73 @@ +"""Compares CAD poses with measured poses to compute errors.""" +#! python3 + +import Rhino +import Grasshopper +from ghpythonlib.componentbase import executingcomponent as component +import ghpythonlib.treehelpers as th + +import diffCheck.df_geometries +import numpy + +def compute_comparison(measured_pose, cad_pose): + cad_origin = cad_pose.Origin + measured_origin = measured_pose.Origin + distance = cad_origin.DistanceTo(measured_origin) + + # Compare the orientations using the formula: $$ \theta = \arccos\left(\frac{\text{trace}(R_{\text{pred}}^T R_{\text{meas}}) - 1}{2}\right) $$ + transform_o_to_cad = Rhino.Geometry.Transform.PlaneToPlane(Rhino.Geometry.Plane.WorldXY, cad_pose) + transform_o_to_measured = Rhino.Geometry.Transform.PlaneToPlane(Rhino.Geometry.Plane.WorldXY, measured_pose) + np_transform_o_to_cad = numpy.array(transform_o_to_cad.ToDoubleArray(rowDominant=True)).reshape((4, 4)) + np_transform_o_to_measured = numpy.array(transform_o_to_measured.ToDoubleArray(rowDominant=True)).reshape((4, 4)) + + R_cad = np_transform_o_to_cad[:3, :3] + R_measured = np_transform_o_to_measured[:3, :3] + R_rel = numpy.dot(R_cad.T, R_measured) + theta = numpy.arccos(numpy.clip((numpy.trace(R_rel) - 1) / 2, -1.0, 1.0)) + + # Compute the transformation matrix between the CAD pose and the measured pose + transform_cad_to_measured = Rhino.Geometry.Transform.PlaneToPlane(cad_pose, measured_pose) + return distance, theta, transform_cad_to_measured + +class DFPoseComparison(component): + def RunScript(self, i_assembly: diffCheck.df_geometries.DFAssembly, i_measured_planes: Grasshopper.DataTree[object]): + + CAD_poses = [beam.plane for beam in i_assembly.beams] + + o_distances = [] + o_angles = [] + o_transforms_cad_to_measured = [] + # Compare the origins + # measure the distance between the origins of the CAD pose and the measured pose and output this in the component + + bc = i_measured_planes.BranchCount + if bc > 1: + poses_per_beam = i_measured_planes.Branches + for beam_id, poses in enumerate(poses_per_beam): + o_distances.append(bc * []) + o_angles.append(bc * []) + o_transforms_cad_to_measured.append(bc * []) + for pose in poses: + if not pose: + o_distances[beam_id].append(None) + o_angles[beam_id].append(None) + o_transforms_cad_to_measured[beam_id].append(None) + else: + dist, angle, transform_cad_to_measured = compute_comparison(pose, CAD_poses[beam_id]) + o_distances[beam_id].append(dist) + o_angles[beam_id].append(angle) + o_transforms_cad_to_measured[beam_id].append(transform_cad_to_measured) + else: + i_measured_planes.Flatten() + measured_plane_list = th.tree_to_list(i_measured_planes) + print(measured_plane_list) + for i, plane in enumerate(measured_plane_list): + dist, angle, transform_cad_to_measured = compute_comparison(plane, CAD_poses[i]) + o_distances.append(dist) + o_angles.append(angle) + o_transforms_cad_to_measured.append(transform_cad_to_measured) + + if bc == 1: + return o_distances, o_angles, o_transforms_cad_to_measured + else: + return th.list_to_tree(o_distances), th.list_to_tree(o_angles), th.list_to_tree(o_transforms_cad_to_measured) diff --git a/src/gh/components/DF_pose_comparison/icon.png b/src/gh/components/DF_pose_comparison/icon.png new file mode 100644 index 00000000..31191432 Binary files /dev/null and b/src/gh/components/DF_pose_comparison/icon.png differ diff --git a/src/gh/components/DF_pose_comparison/metadata.json b/src/gh/components/DF_pose_comparison/metadata.json new file mode 100644 index 00000000..c9ad3a8e --- /dev/null +++ b/src/gh/components/DF_pose_comparison/metadata.json @@ -0,0 +1,67 @@ +{ + "name": "DFPoseComparison", + "nickname": "DFPoseComparison", + "category": "diffCheck", + "subcategory": "Analysis", + "description": "Compares CAD poses with measured poses to compute errors.", + "exposure": 4, + "instanceGuid": "13d76641-6f4f-4e78-a7dd-e64e176ffb2a", + "ghpython": { + "hideOutput": true, + "hideInput": true, + "isAdvancedMode": true, + "marshalOutGuids": true, + "iconDisplay": 2, + "inputParameters": [ + { + "name": "i_assembly", + "nickname": "i_assembly", + "description": "The DFAssembly of the structure.", + "optional": false, + "allowTreeAccess": true, + "showTypeHints": true, + "scriptParamAccess": "item", + "wireDisplay": "default", + "sourceCount": 0, + "typeHintID": "ghdoc" + }, + { + "name": "i_measured_planes", + "nickname": "i_measured_planes", + "description": "The measured planes (aka poses) to compare against the CAD planes.", + "optional": false, + "allowTreeAccess": true, + "showTypeHints": true, + "scriptParamAccess": "tree", + "wireDisplay": "default", + "sourceCount": 0 + } + ], + "outputParameters": [ + { + "name": "o_distances", + "nickname": "o_distances", + "description": "The distances between the CAD pose origins and measured pose origins.", + "optional": false, + "sourceCount": 0, + "graft": false + }, + { + "name": "o_angles", + "nickname": "o_angles", + "description": "The angles between the CAD pose orientations and measured pose orientations.", + "optional": false, + "sourceCount": 0, + "graft": false + }, + { + "name": "o_transforms_cad_to_measured", + "nickname": "o_transforms_cad_to_measured", + "description": "The transformation matrices from CAD poses to measured poses.", + "optional": false, + "sourceCount": 0, + "graft": false + } + ] + } +} \ No newline at end of file diff --git a/src/gh/components/DF_pose_estimation/metadata.json b/src/gh/components/DF_pose_estimation/metadata.json index 60d1f363..c638dc7c 100644 --- a/src/gh/components/DF_pose_estimation/metadata.json +++ b/src/gh/components/DF_pose_estimation/metadata.json @@ -64,8 +64,8 @@ ], "outputParameters": [ { - "name": "o_planes", - "nickname": "o_planes", + "name": "o_measured_planes", + "nickname": "o_measured_planes", "description": "The resulting planes of the pose estimation in the last iteration.", "optional": false, "sourceCount": 0, diff --git a/src/gh/diffCheck/diffCheck/__init__.py b/src/gh/diffCheck/diffCheck/__init__.py index dcf45d61..261820b2 100644 --- a/src/gh/diffCheck/diffCheck/__init__.py +++ b/src/gh/diffCheck/diffCheck/__init__.py @@ -1,6 +1,6 @@ import os -__version__ = "1.3.0" +__version__ = "1.3.1" # make the dlls available to the python interpreter PATH_TO_DLL = "dlls" diff --git a/src/gh/diffCheck/diffCheck/df_geometries.py b/src/gh/diffCheck/diffCheck/df_geometries.py index 541efcd5..d97146d0 100644 --- a/src/gh/diffCheck/diffCheck/df_geometries.py +++ b/src/gh/diffCheck/diffCheck/df_geometries.py @@ -382,7 +382,7 @@ def __post_init__(self): self._center: rg.Point3d = None self._axis: rg.Line = self.compute_axis() - self.plane: rg.Plane = self.compute_plane() + self._plane: rg.Plane = self.compute_plane() self._length: float = self._axis.Length self.__uuid = uuid.uuid4().int @@ -516,25 +516,17 @@ def compute_axis(self, is_unitized: bool = True) -> rg.Line: def compute_plane(self) -> rg.Plane: """ - This function computes the plane of the beam based on its axis and the first joint's center. - The plane is oriented along the beam's axis. + This is an utility function that computes the plane of the beam. + The plane is calculated using the beam's axis and the world Z axis. :return plane: The plane of the beam """ - if not self.joints: - raise ValueError("The beam has no joints to compute a plane") + beam_direction = self.axis.Direction + df_faces = [face for face in self.faces] + sorted_df_faces = sorted(df_faces, key=lambda face: Rhino.Geometry.AreaMassProperties.Compute(face._rh_brepface).Area if face._rh_brepface else 0, reverse=True) + largest_side_face_normal = sorted_df_faces[0].normal - #main axis as defined above - main_direction = self.compute_axis().Direction - - #secondary axis as normal to the largest face of the beam - largest_face = max(self.faces, key=lambda f: f.area) - secondary_axis = largest_face.normal - secondary_vector = rg.Vector3d(secondary_axis[0], secondary_axis[1], secondary_axis[2]) - first_vector = rg.Vector3d.CrossProduct(main_direction, secondary_vector) - origin = self.center - - return rg.Plane(origin, first_vector, secondary_vector) + return rg.Plane(self.center, beam_direction, rg.Vector3d(largest_side_face_normal[0], largest_side_face_normal[1], largest_side_face_normal[2])) def compute_joint_distances_to_midpoint(self) -> typing.List[float]: """ @@ -696,6 +688,11 @@ def axis(self): self._axis = self.compute_axis() return self._axis + @property + def plane(self): + self._plane = self.compute_plane() + return self._plane + @property def length(self): self._length = self._axis.Length diff --git a/src/gh/diffCheck/setup.py b/src/gh/diffCheck/setup.py index bbb48856..f862e56a 100644 --- a/src/gh/diffCheck/setup.py +++ b/src/gh/diffCheck/setup.py @@ -4,7 +4,7 @@ setup( name="diffCheck", - version="1.3.0", + version="1.3.1", packages=find_packages(), install_requires=[ "numpy",