2323logger = logging .getLogger (__name__ )
2424
2525
26+ # ============================================================================
27+ # Geometric Detection Constants
28+ # ============================================================================
29+
30+ # Symmetry detection thresholds for gripper finger detection
31+ SYMMETRY_POSITION_TOLERANCE = 0.01 # Max deviation for symmetric positions (m)
32+ MIN_POSITION_OFFSET = 0.005 # Min offset to consider as valid finger position (m)
33+ Z_SIMILARITY_TOLERANCE = 0.02 # Max Z-axis difference for symmetric fingers (m)
34+
35+ # Search depth for finding end-effector links
36+ MAX_SEARCH_DEPTH = 5
37+
38+ # Mesh analysis thresholds
39+ POSITION_EPSILON = 1e-6 # Threshold for near-zero position values
40+ FINGERTIP_RATIO = 0.1 # Top percentage of vertices considered as fingertip
41+ ASYMMETRY_RATIO = 2.0 # Ratio to determine asymmetric mesh extension
42+
43+ # Y-coordinate thresholds for left/right limb detection (assumes T-pose)
44+ Y_THRESHOLD_WEAK = 0.05 # Weak threshold for Y-coordinate detection (m)
45+ Y_THRESHOLD_STRONG = 0.1 # Strong threshold for Y-coordinate detection (m)
46+
47+ # Joint count thresholds for limb classification
48+ MIN_ARM_JOINTS = 5 # Minimum joints to classify as arm
49+ MIN_LEG_JOINTS = 2 # Minimum joints to classify as leg
50+ MIN_HEAD_JOINTS = 4 # Minimum joints to classify as head (with torso)
51+
52+
2653# ============================================================================
2754# Pattern Configuration
2855# ============================================================================
@@ -384,12 +411,12 @@ def _find_symmetric_gripper_midpoint(descendants, link_map):
384411 for i , (child1 , pos1 ) in enumerate (local_positions ):
385412 for child2 , pos2 in local_positions [i + 1 :]:
386413 # Check if symmetric in x or y (signs opposite, similar magnitude)
387- x_symmetric = (abs (pos1 [0 ] + pos2 [0 ]) < 0.01
388- and abs (pos1 [0 ]) > 0.005 )
389- y_symmetric = (abs (pos1 [1 ] + pos2 [1 ]) < 0.01
390- and abs (pos1 [1 ]) > 0.005 )
414+ x_symmetric = (abs (pos1 [0 ] + pos2 [0 ]) < SYMMETRY_POSITION_TOLERANCE
415+ and abs (pos1 [0 ]) > MIN_POSITION_OFFSET )
416+ y_symmetric = (abs (pos1 [1 ] + pos2 [1 ]) < SYMMETRY_POSITION_TOLERANCE
417+ and abs (pos1 [1 ]) > MIN_POSITION_OFFSET )
391418 # z should be similar
392- z_similar = abs (pos1 [2 ] - pos2 [2 ]) < 0.02
419+ z_similar = abs (pos1 [2 ] - pos2 [2 ]) < Z_SIMILARITY_TOLERANCE
393420
394421 if (x_symmetric or y_symmetric ) and z_similar :
395422 # Found symmetric pair - use link origins midpoint
@@ -409,7 +436,7 @@ def _find_symmetric_gripper_midpoint(descendants, link_map):
409436
410437 # x-axis: forward direction (toward TCP from origin)
411438 # Use the direction from parent origin to midpoint
412- if np .linalg .norm (midpoint_local ) > 1e-6 :
439+ if np .linalg .norm (midpoint_local ) > POSITION_EPSILON :
413440 x_axis_local = midpoint_local / np .linalg .norm (
414441 midpoint_local )
415442 else :
@@ -420,7 +447,7 @@ def _find_symmetric_gripper_midpoint(descendants, link_map):
420447 tip1_local = parent_rot_inv .dot (tip1 - parent_pos )
421448 tip2_local = parent_rot_inv .dot (tip2 - parent_pos )
422449 tip_midpoint = (tip1_local + tip2_local ) / 2
423- if np .linalg .norm (tip_midpoint ) > 1e-6 :
450+ if np .linalg .norm (tip_midpoint ) > POSITION_EPSILON :
424451 x_axis_local = tip_midpoint / np .linalg .norm (
425452 tip_midpoint )
426453 else :
@@ -431,7 +458,7 @@ def _find_symmetric_gripper_midpoint(descendants, link_map):
431458 x_axis_local = x_axis_local - np .dot (
432459 x_axis_local , y_axis_local ) * y_axis_local
433460 x_norm = np .linalg .norm (x_axis_local )
434- if x_norm > 1e-6 :
461+ if x_norm > POSITION_EPSILON :
435462 x_axis_local = x_axis_local / x_norm
436463 else :
437464 # x and y are parallel, use perpendicular
@@ -514,17 +541,15 @@ def _find_best_end_coords_parent(G, link_map, tip_link_name, group_type,
514541 'rot' : None ,
515542 }
516543
517- max_depth = 5
518-
519544 # First: Search DESCENDANTS for tool frame or hand link
520545 # (e.g., panda_link7 -> panda_hand)
521546 try :
522547 descendants = nx .descendants (G , tip_link_name )
523548 except nx .NetworkXError :
524549 descendants = set ()
525550
526- # Check descendants up to max_depth
527- for depth in range (1 , max_depth + 1 ):
551+ # Check descendants up to MAX_SEARCH_DEPTH
552+ for depth in range (1 , MAX_SEARCH_DEPTH + 1 ):
528553 for desc in descendants :
529554 try :
530555 path_len = nx .shortest_path_length (G , tip_link_name , desc )
@@ -557,7 +582,7 @@ def _find_best_end_coords_parent(G, link_map, tip_link_name, group_type,
557582 visited = set ()
558583 current = tip_link_name
559584
560- for _ in range (max_depth ):
585+ for _ in range (MAX_SEARCH_DEPTH ):
561586 visited .add (current )
562587 neighbors = list (undirected .neighbors (current ))
563588 parent_candidates = [n for n in neighbors if n not in visited
@@ -685,7 +710,7 @@ def _get_fingertip_position(link):
685710
686711 # Get the centroid of vertices at the tip (max along primary axis)
687712 max_val = np .max (vertices [:, primary_axis ])
688- threshold = max_val - extents [primary_axis ] * 0.1 # Top 10%
713+ threshold = max_val - extents [primary_axis ] * FINGERTIP_RATIO
689714 tip_vertices = vertices [vertices [:, primary_axis ] > threshold ]
690715 tip_local = np .mean (tip_vertices , axis = 0 )
691716
@@ -792,14 +817,14 @@ def _estimate_tcp_from_mesh(link):
792817
793818 # Check if mesh extends significantly more in one direction
794819 # (asymmetric = likely end effector direction)
795- (bounds [0 ] + bounds [1 ]) / 2
796820 min_dist = abs (bounds [0 ][primary_axis ])
797821 max_dist = abs (bounds [1 ][primary_axis ])
798822
799823 # If asymmetric (one side extends more), use that as TCP direction
800- asymmetry_ratio = max (min_dist , max_dist ) / (min (min_dist , max_dist ) + 1e-6 )
824+ asymmetry_ratio = (max (min_dist , max_dist )
825+ / (min (min_dist , max_dist ) + POSITION_EPSILON ))
801826
802- if asymmetry_ratio > 2.0 : # Significant asymmetry
827+ if asymmetry_ratio > ASYMMETRY_RATIO : # Significant asymmetry
803828 # TCP is at the extended end
804829 if min_dist > max_dist :
805830 # Extends in negative direction
@@ -975,7 +1000,7 @@ def _detect_limb_type(link_names, tip_link=None, tip_y_coord=None,
9751000
9761001 # Geometric detection: long kinematic chain without left/right pattern
9771002 # is likely a single arm (serial manipulator)
978- if movable_joint_count >= 5 :
1003+ if movable_joint_count >= MIN_ARM_JOINTS :
9791004 has_lr_pattern = (right_arm_count >= 2 or left_arm_count >= 2
9801005 or right_leg_count >= 2 or left_leg_count >= 2 )
9811006 if not has_lr_pattern :
@@ -996,23 +1021,20 @@ def _detect_limb_type(link_names, tip_link=None, tip_y_coord=None,
9961021 # Use Y coordinate for geometric left/right detection
9971022 # WARNING: This assumes the robot is in T-pose or similar standard pose.
9981023 # For robots with non-standard initial poses, this detection may fail.
999- y_threshold = 0.05
10001024 if tip_y_coord is not None :
10011025 has_arm_pattern = (right_arm_count >= 1 or left_arm_count >= 1
10021026 or single_arm_count >= 2 )
10031027 has_leg_pattern = right_leg_count >= 1 or left_leg_count >= 1
10041028
1005- strong_y_threshold = 0.1
1006-
10071029 if has_arm_pattern or has_leg_pattern :
1008- if tip_y_coord > y_threshold :
1009- boost = 4 if tip_y_coord > strong_y_threshold else 2
1030+ if tip_y_coord > Y_THRESHOLD_WEAK :
1031+ boost = 4 if tip_y_coord > Y_THRESHOLD_STRONG else 2
10101032 if has_arm_pattern :
10111033 counts ['left_arm' ] += boost
10121034 if has_leg_pattern :
10131035 counts ['left_leg' ] += boost
1014- elif tip_y_coord < - y_threshold :
1015- boost = 4 if tip_y_coord < - strong_y_threshold else 2
1036+ elif tip_y_coord < - Y_THRESHOLD_WEAK :
1037+ boost = 4 if tip_y_coord < - Y_THRESHOLD_STRONG else 2
10161038 if has_arm_pattern :
10171039 counts ['right_arm' ] += boost
10181040 if has_leg_pattern :
0 commit comments