Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 27 additions & 5 deletions pylabrobot/resources/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ def __init__(
# need full isolation of mutable metadata values.
self.metadata: Dict[str, Any] = dict(metadata) if metadata is not None else {}

self.location: Optional[Coordinate] = None
self._location: Optional[Coordinate] = None
self.parent: Optional[Resource] = None
self.children: List[Resource] = []

Expand Down Expand Up @@ -802,6 +802,23 @@ def find_resource(
)
return results[0] if results else None

@property
def location(self) -> Optional[Coordinate]:
"""Where this resource sits, relative to its parent."""
return self._location

@location.setter
def location(self, location: Optional[Coordinate]) -> None:
"""Record a new position, and notify subscribers.

Silent when the position does not change, and while the resource is outside a tree, where
there is nobody to tell.
"""
changed = location != self._location
self._location = location
if changed and self.parent is not None:
self._state_updated()

def rotate(self, x: float = 0, y: float = 0, z: float = 0):
"""Rotate counter-clockwise by the given number of degrees."""

Expand Down Expand Up @@ -1062,12 +1079,15 @@ def serialize_state(self) -> Dict[str, Any]:
Use :meth:`pylabrobot.resources.resource.Resource.serialize_all_state` to serialize the state of
this resource and all children.

The base implementation includes ``"rotation"`` so that subscribers
(e.g. the Visualizer) are notified of orientation changes through the
The base implementation includes ``"rotation"`` and ``"location"`` so that subscribers
(e.g. the Visualizer) are notified of orientation and position changes through the
standard state channel. Subclasses overriding this method should merge
in ``super().serialize_state()``.
"""
return {"rotation": self.rotation.serialize()}
state: Dict[str, Any] = {"rotation": self.rotation.serialize()}
if self._location is not None:
state["location"] = self._location.serialize()
return state

# Developer note: you probably don't need to override this method. Instead, override
# `serialize_state`.
Expand All @@ -1092,11 +1112,13 @@ def serialize_all_state(self) -> Dict[str, Dict[str, Any]]:
def load_state(self, state: Dict[str, Any]) -> None:
"""Load state for this resource only.

The base implementation reads ``"rotation"`` if present. Subclasses
The base implementation reads ``"rotation"`` and ``"location"`` if present. Subclasses
overriding this method should call ``super().load_state(state)``.
"""
if "rotation" in state:
self.rotation = deserialize(state["rotation"])
if "location" in state:
self.location = deserialize(state["location"])

# Developer note: you probably don't need to override this method. Instead, override `load_state`.
def load_all_state(self, state: Dict[str, Dict[str, Any]]) -> None:
Expand Down
Loading