@@ -48,43 +48,48 @@ def __init__(self):
4848 base_screen_height = 400 # Match our display
4949 )
5050
51- # Initialize Plaipin application and controller
52- self .app = Application (self .screen_width , self .screen_height , config )
51+ # Get package share directory path
52+ from ament_index_python .packages import get_package_share_directory
53+ package_share_dir = get_package_share_directory ('coffee_expressions' )
54+
55+ # Get absolute path to expressions.json in the package share directory
56+ expressions_path = os .path .join (package_share_dir , 'plaipin' , 'display_pi' , 'src' , 'expressions.json' )
57+
58+ # Initialize Plaipin application and controller with expressions file path
59+ self .app = Application (self .screen_width , self .screen_height , config , expressions_file = expressions_path )
5360 self .eye_controller = VizEyeController (self .app )
5461
5562 # Initialize state
56- self .current_expression = "neutral"
57- self .last_update = self .get_clock ().now ()
63+ self .current_expression = "base_blob" # Default neutral expression
64+ self .eye_controller .set_expression (self .current_expression )
65+ self .running = True
5866
5967 # Create subscription
6068 self .subscription = self .create_subscription (
6169 AffectiveState ,
6270 'affective_state' ,
6371 self .affective_state_callback ,
6472 10 )
65-
66- # Create timer for animation updates
67- self .create_timer (0.016 , self .update_animation ) # ~60 FPS
68-
73+
6974 def affective_state_callback (self , msg : AffectiveState ):
7075 """Handle incoming affective state messages."""
71- # Map ROS expression to plaipin expression
76+ # Map ROS2 expressions to plaipin expressions
7277 expression = msg .expression .lower ()
7378 if expression == "happy" :
7479 plaipin_expression = "joy1"
75- elif expression == "curious" :
76- plaipin_expression = "curious"
7780 elif expression == "angry" :
7881 plaipin_expression = "ang"
7982 elif expression == "sad" :
80- plaipin_expression = "sad_tired "
83+ plaipin_expression = "sad_1 "
8184 elif expression == "loving" :
82- plaipin_expression = "leftheart "
85+ plaipin_expression = "base_waitingforyoutobuy "
8386 else :
84- plaipin_expression = "neutral"
85-
86- # Set the expression
87- self .eye_controller .set_expression (plaipin_expression )
87+ plaipin_expression = "base_blob"
88+
89+ # Only update if expression changed
90+ if plaipin_expression != self .current_expression :
91+ self .current_expression = plaipin_expression
92+ self .eye_controller .set_expression (plaipin_expression )
8893
8994 # Update gaze target if not idle
9095 if not msg .is_idle :
@@ -97,58 +102,64 @@ def affective_state_callback(self, msg: AffectiveState):
97102 # Return to center when idle
98103 self .eye_controller .set_eye_positions ((0.0 , 0.0 ))
99104
100- def update_animation (self ):
101- """Update the animation state"""
102- # Handle events
103- for event in pygame .event .get ():
104- if event .type == pygame .QUIT :
105- self .destroy_node ()
106- return
107- elif event .type == pygame .KEYDOWN :
108- if event .key == pygame .K_ESCAPE :
109- self .destroy_node ()
110- return
105+ def run (self ):
106+ """Main animation loop"""
107+ clock = pygame .time .Clock ()
108+
109+ while self .running and rclpy .ok ():
110+ # Handle events
111+ for event in pygame .event .get ():
112+ if event .type == pygame .QUIT :
113+ self .running = False
114+ break
115+ elif event .type == pygame .KEYDOWN :
116+ if event .key == pygame .K_ESCAPE :
117+ self .running = False
118+ break
119+
120+ # Let the application handle other events
121+ self .app .input_handler .handle_keyboard (event )
122+
123+ # Handle UI events
124+ ui_result = self .app .ui_manager .handle_event (event )
125+ if ui_result :
126+ if ui_result == "control_points_changed" :
127+ self .app .animated_eyes .update_control_points ()
128+ elif ui_result == "config_changed" :
129+ self .app .animated_eyes .update_eye_positions ()
111130
112- # Let the application handle other events
113- self .app . input_handler . handle_keyboard ( event )
131+ # Update the eye controller
132+ self .eye_controller . update ( )
114133
115- # Handle UI events
116- ui_result = self .app .ui_manager .handle_event (event )
117- if ui_result :
118- if ui_result == "control_points_changed" :
119- self .app .animated_eyes .update_control_points ()
120- elif ui_result == "config_changed" :
121- self .app .animated_eyes .update_eye_positions ()
122-
123- # Update the eye controller
124- self .eye_controller .update ()
125-
126- # Update animated eyes
127- self .app .animated_eyes .update ()
128-
129- # Drawing
130- self .app .screen .fill (self .app .config .background_color )
131- self .app .ui_manager .draw_grid ()
132- self .app .animated_eyes .draw ()
133-
134- # Update display
135- pygame .display .flip ()
134+ # Update animated eyes
135+ self .app .animated_eyes .update ()
136+
137+ # Drawing
138+ self .app .screen .fill (self .app .config .background_color )
139+ self .app .ui_manager .draw_grid ()
140+ self .app .animated_eyes .draw ()
141+
142+ # Update display
143+ pygame .display .flip ()
144+
145+ # Maintain frame rate and process ROS callbacks
146+ clock .tick (60 )
147+ rclpy .spin_once (self , timeout_sec = 0 )
136148
137149 def destroy_node (self ):
138150 """Clean up resources."""
151+ self .running = False
139152 pygame .quit ()
140153 super ().destroy_node ()
141154
142155def main (args = None ):
143156 rclpy .init (args = args )
157+
144158 node = PlaipinExpressiveEyes ()
145- try :
146- rclpy .spin (node )
147- except KeyboardInterrupt :
148- pass
149- finally :
150- node .destroy_node ()
151- rclpy .shutdown ()
159+ node .run ()
160+
161+ node .destroy_node ()
162+ rclpy .shutdown ()
152163
153164if __name__ == '__main__' :
154165 main ()
0 commit comments