3838import re as _re
3939
4040import parol6 .protocol .wire as _wire
41+ from waldoctl .commands import CommandKind , command_table
4142from ..protocol .wire import (
4243 HomeCmd ,
4344 SelectToolCmd ,
@@ -78,6 +79,9 @@ def _pascal_to_snake(name: str) -> str:
7879
7980_UPPER_FIELDS : frozenset [str ] = frozenset ({"tool_name" , "tool_key" , "profile" })
8081
82+ _COMMANDS = command_table ()
83+ _AXIS_INDEX : dict [str , int ] = {"X" : 0 , "Y" : 1 , "Z" : 2 , "RX" : 3 , "RY" : 4 , "RZ" : 5 }
84+
8185
8286def build_cmd (name : str , * args : Any , ** kwargs : Any ) -> Any :
8387 """Build a command struct by method name."""
@@ -601,6 +605,52 @@ def servo_j(
601605 return self ._dispatch (build_cmd ("servo_j_pose" , pose , ** kwargs ))
602606 return self ._dispatch (build_cmd ("servo_j" , angles or [], ** kwargs ))
603607
608+ def jog_j (
609+ self ,
610+ joint : int = - 1 ,
611+ speed : float = 0.0 ,
612+ duration : float = 0.1 ,
613+ * ,
614+ joints : list [int ] | None = None ,
615+ speeds : list [float ] | None = None ,
616+ accel : float = 1.0 ,
617+ ) -> DryRunResult | None :
618+ """The live client's signature, so a script's jog previews as written."""
619+ speed_arr = [0.0 ] * 6
620+ if joints is not None and speeds is not None :
621+ for j , s in zip (joints , speeds ):
622+ speed_arr [j ] = s
623+ elif joint >= 0 :
624+ speed_arr [joint ] = speed
625+ else :
626+ raise ValueError ("jog_j requires either joint= or joints=/speeds=" )
627+ return self ._dispatch (
628+ _wire .JogJCmd (speeds = speed_arr , duration = duration , accel = accel )
629+ )
630+
631+ def jog_l (
632+ self ,
633+ frame : str ,
634+ axis : str | None = None ,
635+ speed : float = 0.0 ,
636+ duration : float = 0.1 ,
637+ * ,
638+ axes : list [str ] | None = None ,
639+ speeds_list : list [float ] | None = None ,
640+ accel : float = 1.0 ,
641+ ) -> DryRunResult | None :
642+ vel = [0.0 ] * 6
643+ if axes is not None and speeds_list is not None :
644+ for a , s in zip (axes , speeds_list ):
645+ vel [_AXIS_INDEX [a ]] = s
646+ elif axis is not None :
647+ vel [_AXIS_INDEX [axis ]] = speed
648+ else :
649+ raise ValueError ("jog_l requires either axis= or axes=/speeds_list=" )
650+ return self ._dispatch (
651+ _wire .JogLCmd (frame = frame , velocities = vel , duration = duration , accel = accel )
652+ )
653+
604654 def delay (self , seconds : float = 0.0 ) -> None :
605655 pass
606656
@@ -615,8 +665,19 @@ def __getattr__(self, name: str) -> Any:
615665 if name not in _CMD_STRUCTS :
616666 raise AttributeError (f"'{ type (self ).__name__ } ' has no attribute '{ name } '" )
617667
618- def method (* args : Any , ** kwargs : Any ) -> DryRunResult | None :
619- cmd = build_cmd (name , * args , ** kwargs )
620- return self ._dispatch (cmd )
668+ spec = _COMMANDS .get (name )
669+ applies = spec is not None and spec .kind in (
670+ CommandKind .SYSTEM ,
671+ CommandKind .CONTROL ,
672+ )
673+
674+ def method (* args : Any , ** kwargs : Any ) -> DryRunResult | int | None :
675+ result = self ._dispatch (build_cmd (name , * args , ** kwargs ))
676+ if not applies :
677+ return result
678+ # A system or control command answers as the live client does:
679+ # 1 when it applied, negative when the planner refused it. Its
680+ # planner result carries no path a program could wait on.
681+ return - 1 if result is not None and result .error is not None else 1
621682
622683 return method
0 commit comments