-
Notifications
You must be signed in to change notification settings - Fork 4
Add axis calculation component for pose estimation #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 16 commits
d5ed24e
f43e197
4809f30
3486fa4
ad8e895
4d5c71b
256fd88
3971d40
13c4759
78231f7
d967ea8
9aadeff
188dc81
56a80d5
0d98ad0
00ceefa
961b28b
3828e8e
2181ad9
cedfcc1
953f5f4
adf1576
f7016b4
de7b580
5745db8
a4e54d2
a637b5d
2513713
a851476
b00e145
6b38623
daff8bb
067903e
1fad289
2995041
f29ec53
22d4fda
4d2f3b1
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| #! python3 | ||
|
|
||
| from diffCheck import diffcheck_bindings | ||
| from diffCheck import df_cvt_bindings | ||
| from diffCheck import df_poses | ||
|
|
||
| import Rhino | ||
|
|
||
| from ghpythonlib.componentbase import executingcomponent as component | ||
|
|
||
| import System | ||
|
|
||
| def compute_dot_product(v1, v2): | ||
| """ | ||
| Compute the dot product of two vectors. | ||
| """ | ||
| return (v1.X * v2.X) + (v1.Y * v2.Y) + (v1.Z * v2.Z) | ||
|
|
||
| class DFMainPCAxes(component): | ||
| def RunScript(self, | ||
| i_clouds: System.Collections.Generic.List[Rhino.Geometry.PointCloud], | ||
| i_reset: bool): | ||
|
|
||
| planes = [] | ||
| all_poses_in_time = df_poses.DFPosesAssembly() | ||
| if i_reset: | ||
| all_poses_in_time.reset() | ||
| return None, None | ||
|
|
||
| previous_poses = all_poses_in_time.get_last_poses() | ||
| all_poses_this_time = [] | ||
| for i, cloud in enumerate(i_clouds): | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It would be better to check that i_clouds is not empty. so add sth like:
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You are right, even better I have set i_clouds as not optional in the metadata, so we get "i_cloud parameter failed to collect data" and the code is simply not run. Let me know if you think this is not a satisfactory solution and if I also should implement it in the code :) |
||
| df_cloud = df_cvt_bindings.cvt_rhcloud_2_dfcloud(cloud) | ||
| if df_cloud is None: | ||
| return None, None | ||
| df_cloud.estimate_normals(True, 12) | ||
|
DamienGilliard marked this conversation as resolved.
Outdated
|
||
|
|
||
| df_points = df_cloud.get_axis_aligned_bounding_box() | ||
| df_point = (df_points[0] + df_points[1]) / 2 | ||
| rh_point = Rhino.Geometry.Point3d(df_point[0], df_point[1], df_point[2]) | ||
| vectors = [] | ||
| # Get the main axes of the point cloud | ||
| previous_pose = previous_poses[i] if previous_poses else None | ||
| if previous_pose: | ||
| rh_previous_xDirection = Rhino.Geometry.Vector3d(previous_pose.xDirection[0], previous_pose.xDirection[1], previous_pose.xDirection[2]) | ||
| rh_previous_yDirection = Rhino.Geometry.Vector3d(previous_pose.yDirection[0], previous_pose.yDirection[1], previous_pose.yDirection[2]) | ||
| n_faces = all_poses_in_time.poses_per_element_dictionary[f"element_{i}"].n_faces | ||
| else: | ||
| rh_previous_xDirection = None | ||
| rh_previous_yDirection = None | ||
| n_faces = len(diffcheck_bindings.dfb_segmentation.DFSegmentation.segment_by_normal(df_cloud, 12, int(len(df_cloud.points)/20), True, int(len(df_cloud.points)/200), 1)) | ||
|
|
||
| axes = df_cloud.get_principal_axes(n_faces) | ||
| for axe in axes: | ||
| vectors.append(Rhino.Geometry.Vector3d(axe[0], axe[1], axe[2])) | ||
|
|
||
| new_xDirection, new_yDirection = df_poses.select_vectors(vectors, rh_previous_xDirection, rh_previous_yDirection) | ||
|
|
||
| pose = df_poses.DFPose( | ||
| origin = [rh_point.X, rh_point.Y, rh_point.Z], | ||
| xDirection = [new_xDirection.X, new_xDirection.Y, new_xDirection.Z], | ||
| yDirection = [new_yDirection.X, new_yDirection.Y, new_yDirection.Z]) | ||
| all_poses_this_time.append(pose) | ||
| plane = Rhino.Geometry.Plane(origin = rh_point, xDirection=new_xDirection, yDirection=new_yDirection) | ||
| planes.append(plane) | ||
|
|
||
| all_poses_in_time.add_step(all_poses_this_time) | ||
|
|
||
| return [planes, all_poses_in_time] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,60 @@ | ||
| { | ||
| "name": "DFPoseEstimation", | ||
| "nickname": "PoseEsimation", | ||
| "category": "diffCheck", | ||
| "subcategory": "PointCloud", | ||
| "description": "This compoment calculates the pose of a list of point clouds.", | ||
| "exposure": 4, | ||
| "instanceGuid": "22b0c6fc-bc16-4ff5-b789-e99776277f65", | ||
| "ghpython": { | ||
| "hideOutput": true, | ||
| "hideInput": true, | ||
| "isAdvancedMode": true, | ||
| "marshalOutGuids": true, | ||
| "iconDisplay": 2, | ||
| "inputParameters": [ | ||
| { | ||
| "name": "i_clouds", | ||
| "nickname": "i_clouds", | ||
| "description": "clouds whose main axes are to be calculated", | ||
| "optional": true, | ||
| "allowTreeAccess": true, | ||
| "showTypeHints": true, | ||
| "scriptParamAccess": "list", | ||
| "wireDisplay": "default", | ||
| "sourceCount": 0, | ||
| "typeHintID": "pointcloud" | ||
| }, | ||
| { | ||
| "name": "i_reset", | ||
| "nickname": "i_reset", | ||
| "description": "reset the history of the pose estimation", | ||
| "optional": true, | ||
| "allowTreeAccess": false, | ||
| "showTypeHints": true, | ||
| "scriptParamAccess": "item", | ||
| "wireDisplay": "default", | ||
| "sourceCount": 0, | ||
| "typeHintID": "bool" | ||
| } | ||
| ], | ||
| "outputParameters": [ | ||
| { | ||
| "name": "o_planes", | ||
| "nickname": "o_planes", | ||
| "description": "The resulting planes of the pose estimation in the last iteration.", | ||
| "optional": false, | ||
| "sourceCount": 0, | ||
| "graft": false | ||
| }, | ||
| { | ||
| "name": "o_history", | ||
| "nickname": "o_history", | ||
| "description": "The history of poses of all the elements.", | ||
| "optional": false, | ||
| "sourceCount": 0, | ||
| "graft": false | ||
| } | ||
| ] | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,118 @@ | ||
| from scriptcontext import sticky as rh_sticky_dict | ||
| import json | ||
| from dataclasses import dataclass, field | ||
|
|
||
| @dataclass | ||
| class DFPose: | ||
| """ | ||
| This class represents the pose of a single element at a given time in the assembly process. | ||
| """ | ||
| origin: list | ||
| xDirection: list | ||
| yDirection: list | ||
|
|
||
| @dataclass | ||
| class DFPosesBeam: | ||
| """ | ||
| This class contains the poses of a single beam, at different times in the assembly process. | ||
| It also contains the number of faces detected for this element, based on which the poses are calculated. | ||
| """ | ||
| poses_dictionnary: dict | ||
| n_faces: int = 3 | ||
|
|
||
| def add_pose(self, pose: DFPose, step_number: int): | ||
| """ | ||
| Add a pose to the dictionary of poses. | ||
| """ | ||
| self.poses_dictionnary[f"pose_{step_number}"] = pose | ||
|
|
||
| def set_n_faces(self, n_faces: int): | ||
| """ | ||
| Set the number of faces detected for this element. | ||
| """ | ||
| self.n_faces = n_faces | ||
|
|
||
| @dataclass | ||
| class DFPosesAssembly: | ||
| n_step: int = 0 | ||
| poses_per_element_dictionary: dict = field(default_factory=lambda: rh_sticky_dict) | ||
|
|
||
| """ | ||
| This class contains the poses of the different elements of the assembly, at different times in the assembly process. | ||
| """ | ||
| def __post_init__(self): | ||
| """ | ||
| Initialize the poses_per_element_dictionary with empty DFPosesBeam objects. | ||
| """ | ||
| lengths = [] | ||
| for element in self.poses_per_element_dictionary: | ||
| lengths.append(len(self.poses_per_element_dictionary[element].poses_dictionnary)) | ||
| self.n_step = max(lengths) if lengths else 0 | ||
|
|
||
| def add_step(self, new_poses: list[DFPose]): | ||
| for i, pose in enumerate(new_poses): | ||
| if f"element_{i}" not in self.poses_per_element_dictionary: | ||
| self.poses_per_element_dictionary[f"element_{i}"] = DFPosesBeam({}, 4) | ||
| for j in range(self.n_step): | ||
| self.poses_per_element_dictionary[f"element_{i}"].add_pose(None, j) | ||
| self.poses_per_element_dictionary[f"element_{i}"].add_pose(pose, self.n_step) | ||
| self.n_step += 1 | ||
|
|
||
| def get_last_poses(self): | ||
| """ | ||
| Get the last poses of each element. | ||
| """ | ||
| if self.n_step == 0: | ||
| return None | ||
| last_poses = [] | ||
| for i in range(len(self.poses_per_element_dictionary)): | ||
| last_poses.append(self.poses_per_element_dictionary[f"element_{i}"].poses_dictionnary[f"pose_{self.n_step-1}"]) | ||
| return last_poses | ||
|
|
||
| def reset(self): | ||
| """ | ||
| Reset the assembly poses to the initial state. | ||
| """ | ||
| self.n_step = 0 | ||
| rh_sticky_dict.clear() | ||
|
|
||
| def save(self, file_path: str): | ||
| """ | ||
| Save the assembly poses to a JSON file. | ||
| """ | ||
| with open(file_path, 'w') as f: | ||
| json.dump(self.poses_per_element_dictionary, f, default=lambda o: o.__dict__, indent=4) | ||
|
|
||
|
|
||
| def compute_dot_product(v1, v2): | ||
| """ | ||
| Compute the dot product of two vectors. | ||
| """ | ||
| return (v1.X * v2.X) + (v1.Y * v2.Y) + (v1.Z * v2.Z) | ||
|
|
||
|
|
||
| def select_vectors(vectors, previous_xDirection, previous_yDirection): | ||
| """ | ||
| Select the vectors that are aligned with the xDirection and yDirection. | ||
| """ | ||
| if previous_xDirection is not None and previous_yDirection is not None: | ||
| sorted_vectors_by_alignment = sorted(vectors, key=lambda v: compute_dot_product(v, previous_xDirection), reverse=True) | ||
| new_xDirection = sorted_vectors_by_alignment[0] | ||
| else: | ||
| new_xDirection = vectors[0] | ||
|
|
||
| condidates_for_yDirection = [] | ||
| for v in vectors: | ||
| if compute_dot_product(v, new_xDirection) ** 2 < 0.5: | ||
| condidates_for_yDirection.append(v) | ||
| if previous_xDirection is not None and previous_yDirection is not None: | ||
| sorted_vectors_by_perpendicularity = sorted(condidates_for_yDirection, key=lambda v: compute_dot_product(v, previous_yDirection), reverse=True) | ||
| new_xDirection = sorted_vectors_by_alignment[0] | ||
| new_yDirection = sorted_vectors_by_perpendicularity[0] - compute_dot_product(sorted_vectors_by_perpendicularity[0], new_xDirection) * new_xDirection | ||
| new_yDirection.Unitize() | ||
| else: | ||
| new_xDirection = vectors[0] | ||
| sorted_vectors = sorted(vectors[1:], key=lambda v: compute_dot_product(v, new_xDirection)**2) | ||
| new_yDirection = sorted_vectors[0] - compute_dot_product(vectors[1], new_xDirection) * new_xDirection | ||
| new_yDirection.Unitize() | ||
| return new_xDirection, new_yDirection |
Uh oh!
There was an error while loading. Please reload this page.