-
Notifications
You must be signed in to change notification settings - Fork 3
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
base: release/2.0.0
Are you sure you want to change the base?
Changes from all commits
d5ed24e
f43e197
4809f30
3486fa4
ad8e895
4d5c71b
256fd88
3971d40
13c4759
78231f7
d967ea8
9aadeff
188dc81
56a80d5
0d98ad0
00ceefa
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): | ||
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:
|
||
df_cloud = df_cvt_bindings.cvt_rhcloud_2_dfcloud(cloud) | ||
if df_cloud is None: | ||
return None, None | ||
df_cloud.estimate_normals(True, 12) | ||
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. 12 is the k-nearest-neighbour? Does it make sense to expose this? or rather require the input pcd to have normals computed and if not, the user has to use the DFNormalEstimator? Or it doesnt affect much the performance of the PCA? |
||
|
||
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 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you don't use this anymore here so could be deleted :)