# === planner_interface ===
class Pose:
    """Represents a 3D pose with position and orientation"""
    p: NDArray[np.float64]  # Position [x, y, z]
    q: NDArray[np.float64]  # Quaternion [w, x, y, z]
    def __init__(self, p: Optional[NDArray[np.float64]] = None, q: Optional[NDArray[np.float64]] = None) -> None
    def __mul__(self, other: Pose) -> Pose
    def inv(self) -> Pose
    def distance(self, other: Pose) -> float

class GoalType:
    """Enum for goal constraint types"""
    JOINTS: int
    POSE: int

class GoalConstraint:
    """Goal constraint for motion planning"""
    def __init__(self, type: int, data: List) -> None

class Trajectory:
    """Represents a robot trajectory"""
    positions: List[List[float]]
    velocities: List[List[float]]
    accelerations: List[List[float]]
    times: List[float]

class PlannerInterface:
    """Main interface for motion planning with IMS"""
    def __init__(self) -> None
        # Constructs a default PlannerInterface
    def add_articulation( self, name: str, end_effector: str, urdf_path: str, srdf_path: str = "", link_names: List[str] = [], joint_names: List[str] = [], gravity: NDArray[np.float64] = np.array([0, 0, 0]), planned: bool = True ) -> None
        # Adds an articulation to the planning world
    def add_robot( self, robot: str, name: Optional[str] = None, srdf_path: Optional[str] = None, end_effector: Optional[str] = None, planned: bool = True, gravity: Optional[NDArray[np.float64]] = None, link_names: Optional[List[str]] = None, joint_names: Optional[List[str]] = None, ) -> None
        # Add a robot from the registry or by file path.
    def remove_articulation(self, name: str) -> None
        # Removes an articulation from the planning world
    def set_base_pose(self, name: str, pose: Pose) -> None
        # Sets the base pose of the articulation
    def read_sim( self, sim: Any, sim_type: str, articulations: Optional[List[Any]] = None ) -> None
        # Read the simulation world objects from sim of type sim_type
    def add_mesh( self, name: str, mesh_path: str = "", scale: Union[NDArray[np.float64], List[float]] = ..., pose: Pose = ..., convex: bool = False, *, vertices: None = None, triangles: None = None ) -> None
        # Adds a mesh to the planning world from a file
    def add_box( self, name: str, size: Union[NDArray[np.float64], List[float]], pose: Pose = Pose() ) -> None
        # Adds a box to the planning world
    def add_sphere( self, name: str, radius: float, pose: Pose = Pose() ) -> None
        # Adds a sphere to the planning world
    def add_cylinder( self, name: str, radius: float, height: float, pose: Pose = Pose() ) -> None
        # Adds a cylinder to the planning world
    def add_point_cloud( self, name: str, vertices: NDArray[np.float64], resolution: float = 0.01 ) -> None
        # Adds a point cloud to the planning world
    def remove_object(self, name: str) -> None
        # Removes an object from the planning world
    def get_articulation_names(self) -> List[str]
        # Get names of all articulations in the planning world
    def get_object_names(self) -> List[str]
        # Get names of all objects in the planning world
    def get_active_joint_names(self, articulation_name: str) -> List[str]
        # Get names of active joints for an articulation
    def get_link_index(self, articulation_name: str, link_name: str) -> int
        # Get link index from link name for an articulation
    def get_link_names(self, articulation_name: str) -> List[str]
        # Get link names for an articulation
    def has_articulation(self, name: str) -> bool
        # Check if articulation exists
    def has_object(self, name: str) -> bool
        # Check if object exists
    def is_articulation_planned(self, name: str) -> bool
        # Check if articulation is being planned
    def set_articulation_planned(self, name: str, planned: bool) -> None
        # Set whether articulation is being planned
    def is_object_attached(self, name: str) -> bool
        # Check if object is attached to a robot
    def attach_object( self, name: str, art_name: str, link_id: int, touch_links: Optional[List[str]] = None ) -> None
        # Attach object to robot link
    def detach_object(self, name: str, also_remove: bool = False) -> bool
        # Detach object from robot
    def detach_all_objects(self, also_remove: bool = False) -> bool
        # Detach all attached objects
    def is_state_colliding(self, articulation_name: str = "") -> bool
        # Check if current state is in collision
    def is_robot_colliding_with_objects(self, art_name: str) -> bool
        # Check if robot is colliding with objects
    def distance_to_self_collision(self) -> float
        # Get minimum distance to self-collision
    def distance_to_robot_collision(self) -> float
        # Get minimum distance to environment collision
    def distance_to_collision(self) -> float
        # Get minimum distance to any collision
    def set_allowed_collision(self, name1: str, name2: str, allowed: bool) -> None
        # Set allowed collision between two objects
    def set_qpos(self, name: str, qpos: NDArray[np.float64]) -> None
        # Set joint positions for articulation
    def set_qpos_all(self, state: NDArray[np.float64]) -> None
        # Set joint positions for all planned articulations
    def update_attached_bodies_pose(self) -> None
        # Update poses of all attached bodies based on current robot state
    def compute_fk(self, articulation_name: str, qpos: NDArray[np.float64]) -> Pose
        # Compute forward kinematics for an articulation
    def compute_ik( self, articulation_name: str, ee_pose: List[float], init_state_val: List[float] ) -> Tuple[bool, List[float]]
        # Compute inverse kinematics using CLIK with joint limits
    def get_link_pose(self, articulation_name: str, link_name: str) -> Pose
        # Get link pose for an articulation
    def make_planner( self, articulation_names: List[str], planner_context: Dict[str, str] ) -> None
        # Makes a planner with given articulation names and configuration
    def plan(self, start: List[float], goal_constraint: GoalConstraint) -> Trajectory
        # Plan a motion for a single agent
    def plan_multi( self, start_states: Dict[str, List[float]], goal_constraints: Dict[str, GoalConstraint] ) -> Dict[str, Trajectory]
        # Plan motions for multiple agents simultaneously
    def reset(self, reset_robots: bool = True) -> None
        # Reset the planner interface
    def print_available_planners(self) -> None
        # Print list of available planners

# === viser_visualizer ===
class ViserPlannerInterface(PlannerInterface):
    """PlannerInterface with interactive Viser 3D visualization."""
    def __init__(self, port: int = 8080, share: bool = False) -> None
    def visualize(self, open_browser: bool = True, add_grid: bool = True) -> None
        # Start the Viser server and visualize the current scene.
    def animate_trajectory( self, trajectories: Union[Dict[str, Trajectory], Trajectory], dt: float = 0.05, robot_name: Optional[str] = None, ) -> None
        # Animate trajectory by updating robot configurations in the 3D viewer.
    def add_robot_controls(self, robot_name: str) -> None
        # Add interactive GUI controls for a robot's joints in the sidebar.
    def add_gui_controls(self) -> None
        # Add interactive GUI controls for adding/manipulating obstacles.
    def stop(self) -> None
        # Stop the Viser server.
    def url(self) -> str
        # Get the Viser server URL.


# === Robot Registry ===
import srmp.robots as robots
robots.list_available()  # returns {'remote': [...], 'local': [...], 'custom': [...]}
robots.download(name)    # download a robot by name
robots.get(name)         # returns RobotInfo with .urdf_path, .srdf_path, .end_effector
