Skip to content
Open
Show file tree
Hide file tree
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
3 changes: 1 addition & 2 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,8 @@ jobs:
steps:
- uses: actions/checkout@v6
- name: Changelog check
uses: Zomzog/changelog-checker@v1.1.0
uses: Zomzog/changelog-checker@v1.3.0
with:
fileName: CHANGELOG.md
checkNotification: Simple
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,13 @@ Requires `compas_robots >= 1.1`.

### Added

* Made `Waypoints` (`FrameWaypoints` and `PointAxisWaypoints`) behave like a list.
* The `Tool From Mesh` Grasshopper component gained a `base_plane` input: where the robot's flange takes hold of the geometry, expressed in the coordinates the mesh was modelled in. Its Z axis points away from the robot, so a tool drawn reaching along world Z needs none, and a tool drawn along another axis is mounted by wiring a plane instead of redrawing the geometry. Backed by the new `base_frame` argument of `compas_robots.ToolModel`; nothing is baked into the mesh, so the plane can be re-wired at any time. The component also surfaces a remark when the TCP does not sit roughly on the tool's +Z, since that means the tool will point sideways once attached — the direction from the mount to the TCP is only a hint (it says nothing about roll), so it is reported rather than applied.

### Fixed

* Fix `get_link_names` for groups with a single link.

### Changed

* The tools in `ToolLibrary` now mount along the +Z axis of their base frame instead of +X. Every planning group in `RobotCellLibrary` ends at a link whose +Z points away from the arm (`tool0` for the industrial robots, `panda_hand_tcp` for the Panda), so with this the same tool attaches to any of them with an identity attachment frame — previously each cell carried a rotation to bridge the two conventions, and a tool authored for one robot did not necessarily fit another. Their TCF states the tool's working direction with its own Z axis too, so a `TargetMode.TOOL` target aligns the tool along the target's Z — previously the TCF's X axis ran along the tool, which put every tool-mode target 90 degrees out. The tools are still modelled along +X internally and re-framed on the way out via `ToolModel.reframe_base`. The beams held by the gripper cells are authored in TCF coordinates and were re-authored to match, so they stay put. Poses are unchanged: the attached tools and workpieces of every cell land exactly where they did, only the tool's base frame is now the end effector link's frame rather than a rotated version of it. Requires the `reframe_base` support of `compas_robots >= 1.1`.
Expand Down
4 changes: 4 additions & 0 deletions src/compas_fab/robots/robot_cell.py
Original file line number Diff line number Diff line change
Expand Up @@ -449,6 +449,10 @@ def get_link_names(self, group: Optional[str] = None) -> list[str]:
group = group or self.main_group_name
base_link_name = self.get_base_link_name(group)
end_effector_link_name = self.get_end_effector_link_name(group)

if base_link_name == end_effector_link_name:
return [base_link_name]

link_names = []
for link in self.robot_model.iter_link_chain(base_link_name, end_effector_link_name):
link_names.append(link.name)
Expand Down
33 changes: 33 additions & 0 deletions src/compas_fab/robots/targets.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,39 @@ class Waypoints(Target):
def __init__(self, target_mode: TargetMode = None, native_scale: float = 1.0, name: str = "Generic Waypoints"):
super(Waypoints, self).__init__(target_mode=target_mode, native_scale=native_scale, name=name)

@property
def waypoints(self):
if hasattr(self, "target_frames"):
return self.target_frames
elif hasattr(self, "target_points_and_axes"):
return self.target_points_and_axes
else:
raise NotImplementedError

def __len__(self):
return len(self.waypoints)

def __getitem__(self, item):
return self.waypoints[item]

def __setitem__(self, key, value):
self.waypoints[key] = value

def __delitem__(self, key):
del self.waypoints[key]

def __iter__(self):
return iter(self.waypoints)

def append(self, item):
self.waypoints.append(item)

def extend(self, items):
self.waypoints.extend(items)

def insert(self, i, item):
self.waypoints.insert(i, item)


class FrameWaypoints(Waypoints):
"""Represents a sequence of fully constrained pose target for the robot's end-effector using a [`Frame`][compas.geometry.Frame].
Expand Down
21 changes: 21 additions & 0 deletions tests/robots/test_waypoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import pytest
from compas.geometry import Frame, Point, Vector
from compas_fab.robots import FrameWaypoints, PointAxisWaypoints, TargetMode

def test_frame_waypoints_list_behavior():
fw = FrameWaypoints([Frame.worldXY()], TargetMode.ROBOT)
assert len(fw) == 1
fw.append(Frame.worldZX())
assert len(fw) == 2

# Test iteration
frames = [f for f in fw]
assert len(frames) == 2
assert frames[0] == Frame.worldXY()
assert frames[1] == Frame.worldZX()

def test_point_axis_waypoints_list_behavior():
pw = PointAxisWaypoints([(Point(0,0,0), Vector(1,0,0))], TargetMode.ROBOT)
assert len(pw) == 1
pw.append((Point(1,1,1), Vector(0,1,0)))
assert len(pw) == 2
Loading