nova.actions

 1from nova.actions.base import Action
 2from nova.actions.container import CombinedActions, MovementController, MovementControllerContext
 3from nova.actions.io import io_write
 4from nova.actions.motions import cir, collision_free, jnt, lin, ptp
 5
 6__all__ = [
 7    "Action",
 8    "lin",
 9    "ptp",
10    "cir",
11    "jnt",
12    "collision_free",
13    "io_write",
14    "MovementController",
15    "CombinedActions",
16    "MovementControllerContext",
17]
class Action(pydantic.main.BaseModel, abc.ABC):
 7class Action(pydantic.BaseModel, ABC):
 8    @abstractmethod
 9    @pydantic.model_serializer
10    def serialize_model(self):
11        """Serialize the model to a dictionary"""
12
13    @abstractmethod
14    def is_motion(self) -> bool:
15        """Return whether the action is a motion"""

Usage docs: https://docs.pydantic.dev/2.10/concepts/models/

A base class for creating Pydantic models.

Attributes: __class_vars__: The names of the class variables defined on the model. __private_attributes__: Metadata about the private attributes of the model. __signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.

__pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
__pydantic_core_schema__: The core schema of the model.
__pydantic_custom_init__: Whether the model has a custom `__init__` function.
__pydantic_decorators__: Metadata containing the decorators defined on the model.
    This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
__pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
    __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
__pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
__pydantic_post_init__: The name of the post-init method for the model, if defined.
__pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
__pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
__pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.

__pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
__pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.

__pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
    is set to `'allow'`.
__pydantic_fields_set__: The names of fields explicitly set during instantiation.
__pydantic_private__: Values of private attributes set on the model instance.
@abstractmethod
@pydantic.model_serializer
def serialize_model(self):
 8    @abstractmethod
 9    @pydantic.model_serializer
10    def serialize_model(self):
11        """Serialize the model to a dictionary"""

Serialize the model to a dictionary

@abstractmethod
def is_motion(self) -> bool:
13    @abstractmethod
14    def is_motion(self) -> bool:
15        """Return whether the action is a motion"""

Return whether the action is a motion

model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

def lin( target: nova.types.Pose | tuple[float, float, float, float, float, float] | tuple[float, float, float], settings: nova.MotionSettings = MotionSettings(min_blending_velocity=None, position_zone_radius=None, joint_velocity_limits=None, joint_acceleration_limits=None, tcp_velocity_limit=None, tcp_acceleration_limit=None, tcp_orientation_velocity_limit=None, tcp_orientation_acceleration_limit=None), collision_scene: wandelbots_api_client.models.collision_scene.CollisionScene | None = None) -> nova.actions.motions.Linear:
123def lin(
124    target: PoseOrVectorTuple,
125    settings: MotionSettings = MotionSettings(),
126    collision_scene: wb.models.CollisionScene | None = None,
127) -> Linear:
128    """Convenience function to create a linear motion
129
130    Args:
131        target: the target pose or vector. If the target is a vector, the orientation is set to (0, 0, 0).
132        settings: the motion settings
133
134    Returns: the linear motion
135
136    Examples:
137    >>> ms = MotionSettings(tcp_velocity_limit=10)
138    >>> assert lin((1, 2, 3, 4, 5, 6), settings=ms) == Linear(target=Pose((1, 2, 3, 4, 5, 6)), settings=ms)
139    >>> assert lin((1, 2, 3)) == lin((1, 2, 3, 0, 0, 0))
140    >>> assert lin(Pose((1, 2, 3, 4, 5, 6)), settings=ms) == lin((1, 2, 3, 4, 5, 6), settings=ms)
141
142    """
143    if not isinstance(target, Pose):
144        t = (*target, 0.0, 0.0, 0.0) if len(target) == 3 else target
145        target = Pose(t)
146
147    return Linear(target=target, settings=settings, collision_scene=collision_scene)

Convenience function to create a linear motion

Args: target: the target pose or vector. If the target is a vector, the orientation is set to (0, 0, 0). settings: the motion settings

Returns: the linear motion

Examples:

>>> ms = MotionSettings(tcp_velocity_limit=10)
>>> assert lin((1, 2, 3, 4, 5, 6), settings=ms) == Linear(target=Pose((1, 2, 3, 4, 5, 6)), settings=ms)
>>> assert lin((1, 2, 3)) == lin((1, 2, 3, 0, 0, 0))
>>> assert lin(Pose((1, 2, 3, 4, 5, 6)), settings=ms) == lin((1, 2, 3, 4, 5, 6), settings=ms)
def ptp( target: nova.types.Pose | tuple[float, float, float, float, float, float] | tuple[float, float, float], settings: nova.MotionSettings = MotionSettings(min_blending_velocity=None, position_zone_radius=None, joint_velocity_limits=None, joint_acceleration_limits=None, tcp_velocity_limit=None, tcp_acceleration_limit=None, tcp_orientation_velocity_limit=None, tcp_orientation_acceleration_limit=None), collision_scene: wandelbots_api_client.models.collision_scene.CollisionScene | None = None) -> nova.actions.motions.PTP:
180def ptp(
181    target: PoseOrVectorTuple,
182    settings: MotionSettings = MotionSettings(),
183    collision_scene: wb.models.CollisionScene | None = None,
184) -> PTP:
185    """Convenience function to create a point-to-point motion
186
187    Args:
188        target: the target pose or vector. If the target is a vector, the orientation is set to (0, 0, 0).
189        settings: the motion settings
190
191    Returns: the point-to-point motion
192
193    Examples:
194    >>> ms = MotionSettings(tcp_acceleration_limit=10)
195    >>> assert ptp((1, 2, 3, 4, 5, 6), settings=ms) == PTP(target=Pose((1, 2, 3, 4, 5, 6)), settings=ms)
196    >>> assert ptp((1, 2, 3)) == ptp((1, 2, 3, 0, 0, 0))
197    >>> assert ptp(Pose((1, 2, 3, 4, 5, 6)), settings=ms) == ptp((1, 2, 3, 4, 5, 6), settings=ms)
198
199    """
200    if not isinstance(target, Pose):
201        t = (*target, 0.0, 0.0, 0.0) if len(target) == 3 else target
202        target = Pose(t)
203
204    return PTP(target=target, settings=settings, collision_scene=collision_scene)

Convenience function to create a point-to-point motion

Args: target: the target pose or vector. If the target is a vector, the orientation is set to (0, 0, 0). settings: the motion settings

Returns: the point-to-point motion

Examples:

>>> ms = MotionSettings(tcp_acceleration_limit=10)
>>> assert ptp((1, 2, 3, 4, 5, 6), settings=ms) == PTP(target=Pose((1, 2, 3, 4, 5, 6)), settings=ms)
>>> assert ptp((1, 2, 3)) == ptp((1, 2, 3, 0, 0, 0))
>>> assert ptp(Pose((1, 2, 3, 4, 5, 6)), settings=ms) == ptp((1, 2, 3, 4, 5, 6), settings=ms)
def cir( target: nova.types.Pose | tuple[float, float, float, float, float, float] | tuple[float, float, float], intermediate: nova.types.Pose | tuple[float, float, float, float, float, float] | tuple[float, float, float], settings: nova.MotionSettings = MotionSettings(min_blending_velocity=None, position_zone_radius=None, joint_velocity_limits=None, joint_acceleration_limits=None, tcp_velocity_limit=None, tcp_acceleration_limit=None, tcp_orientation_velocity_limit=None, tcp_orientation_acceleration_limit=None), collision_scene: wandelbots_api_client.models.collision_scene.CollisionScene | None = None) -> nova.actions.motions.Circular:
240def cir(
241    target: PoseOrVectorTuple,
242    intermediate: PoseOrVectorTuple,
243    settings: MotionSettings = MotionSettings(),
244    collision_scene: wb.models.CollisionScene | None = None,
245) -> Circular:
246    """Convenience function to create a circular motion
247
248    Args:
249        target: the target pose or vector. If the target is a vector, the orientation is set to (0, 0, 0).
250        intermediate: the intermediate pose or vector. If the intermediate is a vector, the orientation is set to
251            (0, 0, 0).
252        settings: the motion settings
253
254    Returns: the circular motion
255
256    Examples:
257    >>> ms = MotionSettings(tcp_acceleration_limit=10)
258    >>> assert cir((1, 2, 3, 4, 5, 6), (7, 8, 9, 10, 11, 12), settings=ms) == Circular(target=Pose((1, 2, 3, 4, 5, 6)), intermediate=Pose((7, 8, 9, 10, 11, 12)), settings=ms)
259    >>> assert cir((1, 2, 3), (4, 5, 6)) == cir((1, 2, 3, 0, 0, 0), (4, 5, 6, 0, 0, 0))
260
261    """
262    if not isinstance(target, Pose):
263        t = (*target, 0.0, 0.0, 0.0) if len(target) == 3 else target
264        target = Pose(t)
265
266    if not isinstance(intermediate, Pose):
267        i = (*intermediate, 0.0, 0.0, 0.0) if len(intermediate) == 3 else intermediate
268        intermediate = Pose(i)
269
270    return Circular(
271        target=target, intermediate=intermediate, settings=settings, collision_scene=collision_scene
272    )

Convenience function to create a circular motion

Args: target: the target pose or vector. If the target is a vector, the orientation is set to (0, 0, 0). intermediate: the intermediate pose or vector. If the intermediate is a vector, the orientation is set to (0, 0, 0). settings: the motion settings

Returns: the circular motion

Examples:

>>> ms = MotionSettings(tcp_acceleration_limit=10)
>>> assert cir((1, 2, 3, 4, 5, 6), (7, 8, 9, 10, 11, 12), settings=ms) == Circular(target=Pose((1, 2, 3, 4, 5, 6)), intermediate=Pose((7, 8, 9, 10, 11, 12)), settings=ms)
>>> assert cir((1, 2, 3), (4, 5, 6)) == cir((1, 2, 3, 0, 0, 0), (4, 5, 6, 0, 0, 0))
def jnt( target: tuple[float, ...], settings: nova.MotionSettings = MotionSettings(min_blending_velocity=None, position_zone_radius=None, joint_velocity_limits=None, joint_acceleration_limits=None, tcp_velocity_limit=None, tcp_acceleration_limit=None, tcp_orientation_velocity_limit=None, tcp_orientation_acceleration_limit=None), collision_scene: wandelbots_api_client.models.collision_scene.CollisionScene | None = None) -> nova.actions.motions.JointPTP:
304def jnt(
305    target: tuple[float, ...],
306    settings: MotionSettings = MotionSettings(),
307    collision_scene: wb.models.CollisionScene | None = None,
308) -> JointPTP:
309    """Convenience function to create a joint PTP motion
310
311    Args:
312        target: the target joint configuration
313        settings: the motion settings
314
315    Returns: the joint PTP motion
316
317    Examples:
318    >>> ms = MotionSettings(tcp_acceleration_limit=10)
319    >>> assert jnt((1, 2, 3, 4, 5, 6), settings=ms) == JointPTP(target=(1, 2, 3, 4, 5, 6), settings=ms)
320
321    """
322    return JointPTP(target=target, settings=settings, collision_scene=collision_scene)

Convenience function to create a joint PTP motion

Args: target: the target joint configuration settings: the motion settings

Returns: the joint PTP motion

Examples:

>>> ms = MotionSettings(tcp_acceleration_limit=10)
>>> assert jnt((1, 2, 3, 4, 5, 6), settings=ms) == JointPTP(target=(1, 2, 3, 4, 5, 6), settings=ms)
def collision_free( target: nova.types.Pose | tuple[float, ...], settings: nova.MotionSettings = MotionSettings(min_blending_velocity=None, position_zone_radius=None, joint_velocity_limits=None, joint_acceleration_limits=None, tcp_velocity_limit=None, tcp_acceleration_limit=None, tcp_orientation_velocity_limit=None, tcp_orientation_acceleration_limit=None), collision_scene: wandelbots_api_client.models.collision_scene.CollisionScene | None = None) -> nova.actions.motions.CollisionFreeMotion:
41def collision_free(
42    target: Pose | tuple[float, ...],
43    settings: MotionSettings = MotionSettings(),
44    collision_scene: wb.models.CollisionScene | None = None,
45) -> CollisionFreeMotion:
46    return CollisionFreeMotion(target=target, settings=settings, collision_scene=collision_scene)
def io_write( key: str, value: Any, device_id: str | None = None) -> nova.actions.io.WriteAction:
24def io_write(key: str, value: Any, device_id: str | None = None) -> WriteAction:
25    """Create a WriteAction
26
27    Args:
28        key: The key to write
29        value: The value to write
30        device_id: The device id
31
32    Returns:
33        The WriteAction
34
35    """
36    return WriteAction(key=key, value=value, device_id=device_id)

Create a WriteAction

Args: key: The key to write value: The value to write device_id: The device id

Returns: The WriteAction

MovementController = typing.Callable[[MovementControllerContext], typing.Callable[[typing.AsyncIterator[wandelbots_api_client.models.execute_trajectory_response.ExecuteTrajectoryResponse]], typing.AsyncIterator[wandelbots_api_client.models.execute_trajectory_request.ExecuteTrajectoryRequest]]]
class CombinedActions(pydantic.main.BaseModel):
 25class CombinedActions(pydantic.BaseModel):
 26    """A trajectory of motions and actions"""
 27
 28    # See: https://docs.pydantic.dev/latest/concepts/serialization/#serialize_as_any-runtime-setting
 29    items: tuple[
 30        Annotated[
 31            pydantic.SerializeAsAny[ActionContainerItem], pydantic.Field(discriminator="type")
 32        ],
 33        ...,
 34    ] = ()
 35
 36    def __len__(self):
 37        return len(self.items)
 38
 39    def __getitem__(self, item):
 40        return self.items[item]
 41
 42    def __setattr__(self, key, value):
 43        if key == "items":
 44            raise TypeError("Cannot set items directly")
 45        super().__setattr__(key, value)
 46
 47    def __iter__(self):
 48        return iter(self.items)
 49
 50    def append(self, item: ActionContainerItem):
 51        super().__setattr__("items", self.items + (item,))
 52
 53    def _generate_trajectory(
 54        self,
 55    ) -> tuple[list[Motion | CollisionFreeMotion], list[ActionLocation]]:
 56        """Generate two lists: one of Motion objects and another of ActionContainer objects,
 57        where each ActionContainer wraps a non-Motion action with its path parameter.
 58
 59        The path parameter is the index of the last Motion object in the list of Motion objects.
 60        S - M - M - A - A - M - M - A - M - M
 61        0 - 1 - 2 - 3 - 3 - 3 - 4 - 5 - 5 - 6
 62
 63        Returns:
 64            tuple: A tuple containing:
 65                - list of Motion objects from self.items.
 66                - list of ActionContainer objects with indexed path parameters.
 67        """
 68        motions = []
 69        actions = []
 70        last_motion_index = 0
 71
 72        for item in self.items:
 73            if isinstance(item, Motion) or isinstance(item, CollisionFreeMotion):
 74                motions.append(item)
 75                last_motion_index += 1  # Increment the motion index for each new Motion
 76            else:
 77                # Assign the current value of last_motion_index as path_parameter for actions
 78                actions.append(ActionLocation(path_parameter=last_motion_index, action=item))
 79
 80        return motions, actions
 81
 82    @property
 83    def motions(self) -> list[Motion | CollisionFreeMotion]:
 84        motions, _ = self._generate_trajectory()
 85        return motions
 86
 87    @property
 88    def actions(self) -> list[ActionLocation]:
 89        _, actions = self._generate_trajectory()
 90        return actions
 91
 92    @property
 93    def start(self) -> ActionContainerItem | None:
 94        return self.motions[0] if self.motions else None
 95
 96    @property
 97    def end(self) -> ActionContainerItem | None:
 98        return self.motions[-1] if self.motions else None
 99
100    def poses(self) -> list[Pose]:
101        """Returns the positions of all motions. If a motion is not a cartesian motion, the position is ignored
102
103        Returns: the positions
104
105        """
106        motions, _ = self._generate_trajectory()
107        return [
108            Pose(position=motion.target.position, orientation=motion.target.orientation)
109            for motion in motions
110            if isinstance(motion.target, Pose)
111        ]
112
113    def positions(self):
114        """Returns the positions of all motions. If a motion is not a cartesian motion, the position is ignored
115
116        Returns: the positions
117
118        """
119        return [pose.position for pose in self.poses()]
120
121    def orientations(self):
122        """Returns the orientations of all motions. If a motion is not a cartesian motion, the orientation is ignored
123
124        Returns: the orientations
125
126        """
127        return [pose.orientation for pose in self.poses()]
128
129    def __add__(self, other: CombinedActions) -> CombinedActions:
130        return CombinedActions(items=self.items + other.items)
131
132    def to_motion_command(self) -> list[api.models.MotionCommand]:
133        motion_commands = []
134        for motion in self.motions:
135            path = api.models.MotionCommandPath.from_dict(motion.model_dump())
136            blending = (
137                motion.settings.as_blending_setting()
138                if motion.settings.has_blending_settings()
139                else None
140            )
141            limits_override = (
142                motion.settings.as_limits_settings()
143                if motion.settings.has_limits_override()
144                else None
145            )
146            motion_commands.append(
147                api.models.MotionCommand(
148                    path=path, blending=blending, limits_override=limits_override
149                )
150            )
151        return motion_commands
152
153    def to_set_io(self) -> list[api.models.SetIO]:
154        return [
155            api.models.SetIO(
156                io=api.models.IOValue(**action.action.model_dump(exclude_unset=True)),
157                location=action.path_parameter,
158            )
159            for action in self.actions
160        ]

A trajectory of motions and actions

items: tuple[typing.Annotated[nova.actions.motions.Motion | nova.actions.io.WriteAction | nova.actions.motions.CollisionFreeMotion, SerializeAsAny(), FieldInfo(annotation=NoneType, required=True, discriminator='type')], ...]
def append( self, item: nova.actions.motions.Motion | nova.actions.io.WriteAction | nova.actions.motions.CollisionFreeMotion):
50    def append(self, item: ActionContainerItem):
51        super().__setattr__("items", self.items + (item,))
motions: list[nova.actions.motions.Motion | nova.actions.motions.CollisionFreeMotion]
82    @property
83    def motions(self) -> list[Motion | CollisionFreeMotion]:
84        motions, _ = self._generate_trajectory()
85        return motions
actions: list[nova.actions.container.ActionLocation]
87    @property
88    def actions(self) -> list[ActionLocation]:
89        _, actions = self._generate_trajectory()
90        return actions
start: nova.actions.motions.Motion | nova.actions.io.WriteAction | nova.actions.motions.CollisionFreeMotion | None
92    @property
93    def start(self) -> ActionContainerItem | None:
94        return self.motions[0] if self.motions else None
end: nova.actions.motions.Motion | nova.actions.io.WriteAction | nova.actions.motions.CollisionFreeMotion | None
96    @property
97    def end(self) -> ActionContainerItem | None:
98        return self.motions[-1] if self.motions else None
def poses(self) -> list[nova.types.Pose]:
100    def poses(self) -> list[Pose]:
101        """Returns the positions of all motions. If a motion is not a cartesian motion, the position is ignored
102
103        Returns: the positions
104
105        """
106        motions, _ = self._generate_trajectory()
107        return [
108            Pose(position=motion.target.position, orientation=motion.target.orientation)
109            for motion in motions
110            if isinstance(motion.target, Pose)
111        ]

Returns the positions of all motions. If a motion is not a cartesian motion, the position is ignored

Returns: the positions

def positions(self):
113    def positions(self):
114        """Returns the positions of all motions. If a motion is not a cartesian motion, the position is ignored
115
116        Returns: the positions
117
118        """
119        return [pose.position for pose in self.poses()]

Returns the positions of all motions. If a motion is not a cartesian motion, the position is ignored

Returns: the positions

def orientations(self):
121    def orientations(self):
122        """Returns the orientations of all motions. If a motion is not a cartesian motion, the orientation is ignored
123
124        Returns: the orientations
125
126        """
127        return [pose.orientation for pose in self.poses()]

Returns the orientations of all motions. If a motion is not a cartesian motion, the orientation is ignored

Returns: the orientations

def to_motion_command(self) -> list[wandelbots_api_client.models.motion_command.MotionCommand]:
132    def to_motion_command(self) -> list[api.models.MotionCommand]:
133        motion_commands = []
134        for motion in self.motions:
135            path = api.models.MotionCommandPath.from_dict(motion.model_dump())
136            blending = (
137                motion.settings.as_blending_setting()
138                if motion.settings.has_blending_settings()
139                else None
140            )
141            limits_override = (
142                motion.settings.as_limits_settings()
143                if motion.settings.has_limits_override()
144                else None
145            )
146            motion_commands.append(
147                api.models.MotionCommand(
148                    path=path, blending=blending, limits_override=limits_override
149                )
150            )
151        return motion_commands
def to_set_io(self) -> list[wandelbots_api_client.models.set_io.SetIO]:
153    def to_set_io(self) -> list[api.models.SetIO]:
154        return [
155            api.models.SetIO(
156                io=api.models.IOValue(**action.action.model_dump(exclude_unset=True)),
157                location=action.path_parameter,
158            )
159            for action in self.actions
160        ]
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].

class MovementControllerContext(pydantic.main.BaseModel):
164class MovementControllerContext(pydantic.BaseModel):
165    combined_actions: CombinedActions
166    motion_id: str

Usage docs: https://docs.pydantic.dev/2.10/concepts/models/

A base class for creating Pydantic models.

Attributes: __class_vars__: The names of the class variables defined on the model. __private_attributes__: Metadata about the private attributes of the model. __signature__: The synthesized __init__ [Signature][inspect.Signature] of the model.

__pydantic_complete__: Whether model building is completed, or if there are still undefined fields.
__pydantic_core_schema__: The core schema of the model.
__pydantic_custom_init__: Whether the model has a custom `__init__` function.
__pydantic_decorators__: Metadata containing the decorators defined on the model.
    This replaces `Model.__validators__` and `Model.__root_validators__` from Pydantic V1.
__pydantic_generic_metadata__: Metadata for generic models; contains data used for a similar purpose to
    __args__, __origin__, __parameters__ in typing-module generics. May eventually be replaced by these.
__pydantic_parent_namespace__: Parent namespace of the model, used for automatic rebuilding of models.
__pydantic_post_init__: The name of the post-init method for the model, if defined.
__pydantic_root_model__: Whether the model is a [`RootModel`][pydantic.root_model.RootModel].
__pydantic_serializer__: The `pydantic-core` `SchemaSerializer` used to dump instances of the model.
__pydantic_validator__: The `pydantic-core` `SchemaValidator` used to validate instances of the model.

__pydantic_fields__: A dictionary of field names and their corresponding [`FieldInfo`][pydantic.fields.FieldInfo] objects.
__pydantic_computed_fields__: A dictionary of computed field names and their corresponding [`ComputedFieldInfo`][pydantic.fields.ComputedFieldInfo] objects.

__pydantic_extra__: A dictionary containing extra values, if [`extra`][pydantic.config.ConfigDict.extra]
    is set to `'allow'`.
__pydantic_fields_set__: The names of fields explicitly set during instantiation.
__pydantic_private__: Values of private attributes set on the model instance.
combined_actions: CombinedActions
motion_id: str
model_config: ClassVar[pydantic.config.ConfigDict] = {}

Configuration for the model, should be a dictionary conforming to [ConfigDict][pydantic.config.ConfigDict].