diff --git a/README.md b/README.md index 49590fad5..2aba3f94a 100644 --- a/README.md +++ b/README.md @@ -223,7 +223,7 @@ import roboticstoolbox as rtb robot = rtb.models.Panda() print(robot) - ERobot: panda (by Franka Emika), 7 joints (RRRRRRR), 1 gripper, geometry, collision + URDFRobot.Panda: panda (by Franka Emika), 7 joints (RRRRRRR), 1 gripper, geometry, collision ┌─────┬──────────────┬───────┬─────────────┬────────────────────────────────────────────────┐ │link │ link │ joint │ parent │ ETS: parent to link │ ├─────┼──────────────┼───────┼─────────────┼────────────────────────────────────────────────┤ diff --git a/src/roboticstoolbox/robot/BaseRobot.py b/src/roboticstoolbox/robot/BaseRobot.py index 355454567..ecc1cd523 100644 --- a/src/roboticstoolbox/robot/BaseRobot.py +++ b/src/roboticstoolbox/robot/BaseRobot.py @@ -73,6 +73,10 @@ def _is_symbolic(value: Any) -> bool: return np.asarray(value).dtype == object +# the modules of the robot classes themselves, as distinct from models built from them +_FRAMEWORK_MODULES = ("roboticstoolbox.robot.", "roboticstoolbox.models.URDF.URDFRobot") + + class BaseRobot(SceneNode, DynamicsMixin, RobotPlottingMPLMixin, ABC, Generic[LinkType]): # set when the links are processed in the constructor _base_link: LinkType @@ -531,9 +535,7 @@ def __str__(self) -> str: f"{s}", ) - classname = "ERobot" - - s = f"{classname}: {self.name}" + s = f"{self._class_label()}: {self.name}" if self.manufacturer is not None and len(self.manufacturer) > 0: s += f" (by {self.manufacturer})" s += f", {self.n} joints ({self.structure})" @@ -559,6 +561,20 @@ def __str__(self) -> str: def __repr__(self) -> str: return str(self) + def _class_label(self) -> str: + """ + The class of this robot, for the first line of its display + + ``Robot``, ``Robot2``, ``DHRobot`` or ``URDFRobot`` for an instance of the class + itself, and ``X.Y`` for an instance of a subclass ``Y`` of one of them, for + example ``DHRobot.Puma560`` or ``URDFRobot.Panda``: the kind of robot, then the + model. + """ + cls = type(self) + if cls.__module__.startswith(_FRAMEWORK_MODULES): + return cls.__name__ + return f"{cls.__mro__[1].__name__}.{cls.__name__}" + # --------------------------------------------------------------------- # # --------- Properties ------------------------------------------------ # # --------------------------------------------------------------------- # diff --git a/src/roboticstoolbox/robot/DHRobot.py b/src/roboticstoolbox/robot/DHRobot.py index e57e2c039..4ec9317bd 100644 --- a/src/roboticstoolbox/robot/DHRobot.py +++ b/src/roboticstoolbox/robot/DHRobot.py @@ -303,7 +303,7 @@ def __str__(self) -> str: unicode = rtb_get_param("unicode") border = "thin" if unicode else "ascii" - s = f"DHRobot: {self.name}" + s = f"{self._class_label()}: {self.name}" if self.manufacturer is not None and len(self.manufacturer) > 0: s += f" (by {self.manufacturer})" diff --git a/tests/test_robot_label.py b/tests/test_robot_label.py new file mode 100644 index 000000000..48b522437 --- /dev/null +++ b/tests/test_robot_label.py @@ -0,0 +1,38 @@ +"""The first line of a robot's display names its class: the kind of robot, then the +model, as ``DHRobot.Puma560``.""" + +import roboticstoolbox as rtb + + +def _first_line(robot): + return str(robot).splitlines()[0] + + +def test_ets_model(): + assert _first_line(rtb.models.ETS.Planar_Y()).startswith("Robot.Planar_Y: ") + + +def test_urdf_model(): + assert _first_line(rtb.models.URDF.Panda()).startswith("URDFRobot.Panda: ") + + +def test_dh_model(): + assert _first_line(rtb.models.DH.Puma560()).startswith("DHRobot.Puma560: ") + + +def test_the_classes_themselves_have_no_model(): + robot = rtb.Robot(rtb.ET.Rz() * rtb.ET.tx(1), name="arm") + dh = rtb.DHRobot([rtb.RevoluteDH(a=1.0)], name="dh") + robot2 = rtb.Robot2(rtb.ET2.R() * rtb.ET2.tx(1), name="flat") + + assert _first_line(robot).startswith("Robot: arm") + assert _first_line(dh).startswith("DHRobot: dh") + assert _first_line(robot2).startswith("Robot2: flat") + + +def test_a_users_own_subclass(): + class Mine(rtb.Robot): + def __init__(self): + super().__init__(rtb.ET.Rz() * rtb.ET.tx(1), name="mine") + + assert _first_line(Mine()).startswith("Robot.Mine: mine")