Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2181ad9
feat: add plane property to DFBeam
DamienGilliard Aug 11, 2025
cedfcc1
feat: add max id to CAD segmentation to allow segmentation during fab…
DamienGilliard Aug 11, 2025
953f5f4
feat: remove the i_stop_after_id parameter because it is moved to a d…
DamienGilliard Sep 2, 2025
adf1576
feat: create new truncate_assembly component
DamienGilliard Sep 2, 2025
f7016b4
fix: main axis of DFBeam computed such that the main axes are also th…
DamienGilliard Sep 2, 2025
de7b580
fix: add ghpythonlib. to the overrides of mypy
DamienGilliard Sep 2, 2025
5745db8
feat: update DFMainPCAxes to save the new poses only when user trigge…
DamienGilliard Sep 2, 2025
a4e54d2
fix: name of class in truncate_assembly component
DamienGilliard Sep 9, 2025
a637b5d
fix: remove unused function parameter in DF_CAD_segmentator component
DamienGilliard Sep 9, 2025
2513713
fix: change index in TruncateAssembly component
DamienGilliard Oct 2, 2025
a851476
fix: change sign of index change in TruncateAssembly component
DamienGilliard Oct 2, 2025
b00e145
fix: output the history as ghtree
DamienGilliard Oct 15, 2025
6b38623
feat: add fallback to obb when knn on normals gives insufficiently di…
DamienGilliard Oct 15, 2025
daff8bb
ADD check i_assembly has enough beams and if there is an error on a c…
eleniv3d Oct 20, 2025
067903e
FIX rename files assossiated with Pose Estimation component to have t…
eleniv3d Oct 20, 2025
1fad289
FIX typo and use not all sticky but a single namespaced key df_poses
eleniv3d Oct 20, 2025
2995041
FIX remove re-assignying new_xDirection and fix projection to use tth…
eleniv3d Oct 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ module = [
"GH_IO.*",
"clr.*",
"diffcheck_bindings",
"diffCheck.diffcheck_bindings"
"diffCheck.diffcheck_bindings",
"ghpythonlib.*"
]
ignore_missing_imports = true

Expand Down
27 changes: 26 additions & 1 deletion src/diffCheck/geometry/DFPointCloud.cc
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,32 @@ namespace diffCheck::geometry

for(size_t i = 0; i < nComponents; ++i)
{
principalAxes.push_back(sortedClustersBySize[i].second);
if(principalAxes.size() == 0)
{
principalAxes.push_back(sortedClustersBySize[i].second);
}
else
{
bool isAlreadyPresent = false;
for (const auto& axis : principalAxes)
{
double dotProduct = std::abs(axis.dot(sortedClustersBySize[i].second));
if (std::abs(dotProduct) > 0.7) // Threshold to consider as similar direction
{
isAlreadyPresent = true;
break;
}
}
if (!isAlreadyPresent)
{
principalAxes.push_back(sortedClustersBySize[i].second);
}
}
}
if (principalAxes.size() < 2) // Fallback to OBB if k-means fails to provide enough distinct axes
{
open3d::geometry::OrientedBoundingBox obb = this->Cvt2O3DPointCloud()->GetOrientedBoundingBox();
principalAxes = {obb.R_.col(0), obb.R_.col(1), obb.R_.col(2)};
}
return principalAxes;
}
Expand Down
65 changes: 0 additions & 65 deletions src/gh/components/DF_main_pc_axes/code.py

This file was deleted.

68 changes: 68 additions & 0 deletions src/gh/components/DF_pose_estimation/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#! python3

from diffCheck import df_cvt_bindings
from diffCheck import df_poses

import Rhino
from Grasshopper.Kernel import GH_RuntimeMessageLevel as RML

from ghpythonlib.componentbase import executingcomponent as component
import System


class DFPoseEstimation(component):
def RunScript(self,
i_clouds: System.Collections.Generic.List[Rhino.Geometry.PointCloud],
i_assembly,
i_save: bool,
i_reset: bool):

# ensure assembly has enough beams
if len(i_assembly.beams) < len(i_clouds):
ghenv.Component.AddRuntimeMessage(RML.Warning, "Assembly has fewer beams than input clouds") # noqa: F821
return None, None

planes = []
all_poses_in_time = df_poses.DFPosesAssembly()
if i_reset:
all_poses_in_time.reset()
return None, None

all_poses_this_time = []
for i, cloud in enumerate(i_clouds):
try:
df_cloud = df_cvt_bindings.cvt_rhcloud_2_dfcloud(cloud)
if df_cloud is None:
return None, None
if not df_cloud.has_normals():
ghenv.Component.AddRuntimeMessage(RML.Error, f"Point cloud {i} has no normals. Please compute the normals.") # noqa: F821

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])

axes = df_cloud.get_principal_axes(3)
vectors = []
for axe in axes:
vectors.append(Rhino.Geometry.Vector3d(axe[0], axe[1], axe[2]))

new_xDirection, new_yDirection = df_poses.select_vectors(vectors, i_assembly.beams[i].plane.XAxis, i_assembly.beams[i].plane.YAxis)

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)
except Exception as e:
# Any unexpected error on this cloud, skip it and keep going
ghenv.Component.AddRuntimeMessage(RML.Error, f"Cloud {i}: processing failed ({e}); skipping.") # noqa: F821
planes.append(None)
all_poses_this_time.append(None)
continue

if i_save:
all_poses_in_time.add_step(all_poses_this_time)

return [planes, all_poses_in_time.to_gh_tree()]
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"subcategory": "PointCloud",
"description": "This compoment calculates the pose of a list of point clouds.",
"exposure": 4,
"instanceGuid": "22b0c6fc-bc16-4ff5-b789-e99776277f65",
"instanceGuid": "a13c4414-f5df-46e6-beae-7054bb9c3e72",
"ghpython": {
"hideOutput": true,
"hideInput": true,
Expand All @@ -16,7 +16,7 @@
{
"name": "i_clouds",
"nickname": "i_clouds",
"description": "clouds whose main axes are to be calculated",
"description": "clouds whose pose is to be calculated",
"optional": false,
"allowTreeAccess": true,
"showTypeHints": true,
Expand All @@ -25,6 +25,18 @@
"sourceCount": 0,
"typeHintID": "pointcloud"
},
{
"name": "i_assembly",
"nickname": "i_assembly",
"description": "The DFAssembly corresponding to the list of clouds.",
"optional": false,
"allowTreeAccess": true,
"showTypeHints": true,
"scriptParamAccess": "item",
"wireDisplay": "default",
"sourceCount": 0,
"typeHintID": "ghdoc"
},
{
"name": "i_reset",
"nickname": "i_reset",
Expand All @@ -36,6 +48,18 @@
"wireDisplay": "default",
"sourceCount": 0,
"typeHintID": "bool"
},
{
"name": "i_save",
"nickname": "i_save",
"description": "save the poses computed at this iteration",
"optional": true,
"allowTreeAccess": false,
"showTypeHints": true,
"scriptParamAccess": "item",
"wireDisplay": "default",
"sourceCount": 0,
"typeHintID": "bool"
}
],
"outputParameters": [
Expand All @@ -50,7 +74,7 @@
{
"name": "o_history",
"nickname": "o_history",
"description": "The history of poses of all the elements.",
"description": "The history of poses per elements.",
"optional": false,
"sourceCount": 0,
"graft": false
Expand Down
15 changes: 15 additions & 0 deletions src/gh/components/DF_truncate_assembly/code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from ghpythonlib.componentbase import executingcomponent as component

import diffCheck
import diffCheck.df_geometries

class DFTruncateAssembly(component):
def RunScript(self,
i_assembly,
i_truncate_index: int):
beams = i_assembly.beams[:i_truncate_index]
name = i_assembly.name

o_assembly = diffCheck.df_geometries.DFAssembly(name=name, beams=beams)
ghenv.Component.Message = f"number of beams: {len(o_assembly.beams)}" # noqa: F821
return o_assembly
Binary file added src/gh/components/DF_truncate_assembly/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions src/gh/components/DF_truncate_assembly/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"name": "DFTruncateAssembly",
"nickname": "TruncateAssembly",
"category": "diffCheck",
"subcategory": "Structure",
"description": "This component truncates an assembly.",
"exposure": 4,
"instanceGuid": "cf8af97f-dd84-40b6-af44-bf6aca7b941b",
"ghpython": {
"hideOutput": true,
"hideInput": true,
"isAdvancedMode": true,
"marshalOutGuids": true,
"iconDisplay": 2,
"inputParameters": [
{
"name": "i_assembly",
"nickname": "i_assembly",
"description": "The assembly to be truncated.",
"optional": false,
"allowTreeAccess": true,
"showTypeHints": true,
"scriptParamAccess": "item",
"wireDisplay": "default",
"sourceCount": 0,
"typeHintID": "ghdoc"
},
{
"name": "i_truncate_index",
"nickname": "i_truncate_index",
"description": "The index at which to truncate the assembly.",
"optional": false,
"allowTreeAccess": false,
"showTypeHints": true,
"scriptParamAccess": "item",
"wireDisplay": "default",
"sourceCount": 0,
"typeHintID": "int"
}
],
"outputParameters": [
{
"name": "o_assembly",
"nickname": "o_assembly",
"description": "The resulting assembly after truncation.",
"optional": false,
"sourceCount": 0,
"graft": false
}
]
}
}
30 changes: 30 additions & 0 deletions src/gh/diffCheck/diffCheck/df_geometries.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ def __post_init__(self):
self._center: DFVertex = None
# the normal of the face
self._normal: typing.List[float] = None
self._area: float = None

def __getstate__(self):
state = self.__dict__.copy()
Expand Down Expand Up @@ -261,6 +262,12 @@ def normal(self):
self._normal = [normal_rg.X, normal_rg.Y, normal_rg.Z]
return self._normal

@property
def area(self):
if self._area is None:
self._area = self.to_brep_face().ToBrep().GetArea()
return self._area

@dataclass
class DFJoint:
"""
Expand Down Expand Up @@ -375,6 +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._length: float = self._axis.Length

self.__uuid = uuid.uuid4().int
Expand Down Expand Up @@ -506,6 +514,28 @@ def compute_axis(self, is_unitized: bool = True) -> rg.Line:

return axis_ln

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.

:return plane: The plane of the beam
"""
if not self.joints:
raise ValueError("The beam has no joints to compute a plane")

#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)

def compute_joint_distances_to_midpoint(self) -> typing.List[float]:
"""
This function computes the distances from the center of the beam to each joint.
Expand Down
Loading
Loading