@@ -1152,7 +1152,7 @@ def plot(
11521152 """
11531153 Plot 3D camera icon in world view (base method)
11541154
1155- :param pose: camera pose
1155+ :param pose: camera pose, required when called unbound (see below)
11561156 :type pose: :class:`~spatialmath.pose3d.SE3`
11571157 :param scale: scale factor, defaults to 1
11581158 :type scale: float
@@ -1170,15 +1170,28 @@ def plot(
11701170 :type projection: str, optional
11711171 :param ax: axes to draw in, defaults to current 3D axes
11721172 :type ax: :class:`~matplotlib.Axes3D`, optional
1173+ :raises ValueError: called unbound (eg. ``CentralCamera.plot(...)``)
1174+ without a ``pose``
11731175 :return: axes drawn into
11741176 :rtype: :class:`~matplotlib.Axes3D`
11751177
11761178 Plot a 3D icon representing the pose of a camera into a 3D Matplotlib
11771179 plot. Two icons are supported: the traditional frustum, and a
11781180 simplistic camera comprising a box and cylinder.
11791181
1180- .. note:: If ``pose`` is not given it defaults to the pose of the
1181- instance.
1182+ This method can be called two ways:
1183+
1184+ - **Bound**, ``camera.plot(...)`` -- draws an icon for ``camera``.
1185+ ``pose`` defaults to the instance's own pose if not given.
1186+ - **Unbound**, ``CentralCamera.plot(pose=..., ...)`` -- called on
1187+ the class itself, with no camera instance involved (``self`` is
1188+ ``None``). Useful for drawing icons at a set of poses that don't
1189+ correspond to any single camera instance -- e.g. plotting the
1190+ calibration frames returned by :meth:`images2C`. ``pose`` is
1191+ required in this form.
1192+
1193+ .. note:: If ``pose`` is not given, it defaults to the pose of
1194+ ``self`` -- this only applies to the bound form.
11821195 """
11831196
11841197 # if (fig is None) and (ax is None):
@@ -1195,6 +1208,11 @@ def plot(
11951208 ax = smb .axes_logic (ax , 3 , projection = projection )
11961209
11971210 if pose is None :
1211+ if self is None :
1212+ raise ValueError (
1213+ "pose is required when plot() is called unbound, eg. "
1214+ "CentralCamera.plot(pose=...)"
1215+ )
11981216 pose = self .pose
11991217
12001218 # Matplotlib >=3.10 can raise while autoscaling ragged 3D line
@@ -1216,26 +1234,27 @@ def _add_collection3d_compat(collection, *args, **kwargs):
12161234 try :
12171235 # draw camera-like object:
12181236 if shape == "frustum" :
1219- self ._plot_frustum (ax , pose , scale , alpha )
1237+ CameraBase ._plot_frustum (ax , pose , scale , alpha )
12201238
12211239 elif shape == "camera" :
1222- self ._plot_camera_icon (ax , pose , scale , solid , color , alpha , label )
1240+ CameraBase ._plot_camera_icon (ax , pose , scale , solid , color , alpha , label )
12231241
12241242 if frame is True :
1225- self . pose .plot (
1243+ pose .plot (
12261244 length = scale * 1.5 ,
12271245 style = "line" ,
12281246 color = color ,
12291247 flo = (0.07 , 0 , - 0.01 ),
12301248 )
12311249 elif frame is not False :
1232- self . pose .plot (** frame )
1250+ pose .plot (** frame )
12331251 finally :
12341252 ax .add_collection3d = _orig_add_collection3d
12351253
12361254 return ax
12371255
1238- def _plot_frustum (self , ax : Axes , pose : SE3 , scale : float , alpha : float ) -> None :
1256+ @staticmethod
1257+ def _plot_frustum (ax : Axes , pose : SE3 , scale : float , alpha : float ) -> None :
12391258 """Draw the ``shape="frustum"`` camera icon into ``ax``."""
12401259 # TODO make this kwargs or optional inputs
12411260 # side colors:
@@ -1279,8 +1298,8 @@ def _plot_frustum(self, ax: Axes, pose: SE3, scale: float, alpha: float) -> None
12791298 poly = Poly3DCollection (points , facecolors = ["r" , "g" , "r" , "y" ], alpha = alpha )
12801299 ax .add_collection3d (poly )
12811300
1301+ @staticmethod
12821302 def _plot_camera_icon (
1283- self ,
12841303 ax : Axes ,
12851304 pose : SE3 ,
12861305 scale : float ,
0 commit comments