-
Notifications
You must be signed in to change notification settings - Fork 42
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support bounded and unbounded arrays with rqt_plot (This should NOT be merged to crystal) #53
Closed
mlautman
wants to merge
7
commits into
ros-visualization:crystal-devel
from
PickNikRobotics:eloquent-port
Closed
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
5541bbc
nicer look
c858a45
Revert to ros1 and re-implement _parse_type, _get_topic_type and get_…
686b482
updating usage of rqt_py_common to reflect newest changes
9584ff0
remove bytes todo. No reason to implement this
0273887
update rqt_py_common usage
47c5b8e
updating to newest version of rqt_py_common
dec1822
fixing fixed array bug
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -33,110 +33,140 @@ | |
import os | ||
import time | ||
|
||
from typing import Tuple, List, ClassVar | ||
|
||
from ament_index_python.resources import get_resource | ||
from python_qt_binding import loadUi | ||
from python_qt_binding.QtCore import Qt, QTimer, qWarning, Slot | ||
from python_qt_binding.QtGui import QIcon | ||
from python_qt_binding.QtWidgets import QAction, QMenu, QWidget | ||
|
||
from rqt_py_common.topic_completer import TopicCompleter | ||
from rqt_py_common import topic_helpers, message_helpers | ||
from rqt_py_common import message_helpers, message_field_type_helpers, topic_helpers | ||
|
||
from rqt_plot.rosplot import ROSData, RosPlotException, get_topic_type | ||
|
||
class MsgSpecException(Exception): | ||
pass | ||
|
||
def _parse_type(topic_type_str): # -> Tuple[str, bool, int]: | ||
""" | ||
Parses a msg type string and returns a tuple with information the type | ||
|
||
:returns: a Tuple with the base type of the slot as a str, a bool indicating | ||
if the slot is an array and an integer if it has a static or bound size | ||
or if it is unbounded, then the third value is None | ||
|
||
Strips out any array information from the topic_type_str | ||
|
||
from rqt_plot.rosplot import ROSData, RosPlotException | ||
eg: | ||
sequence<int8, 3> -> int8, true, 3 | ||
sequence<int8> -> int8, true, None | ||
int8[3] -> int8, true, 3 | ||
|
||
:rtype: str, bool, int | ||
""" | ||
if not topic_type_str: | ||
raise MsgSpecException("Invalid empty type") | ||
|
||
def _parse_type(topic_type_str): | ||
slot_type = topic_type_str | ||
is_array = False | ||
array_size = None | ||
|
||
array_idx = topic_type_str.find('[') | ||
if array_idx < 0: | ||
return slot_type, False, None | ||
topic_type_info = message_field_type_helpers.MessageFieldTypeInfo(topic_type_str) | ||
slot_type = topic_type_info.base_type_str | ||
is_array = topic_type_info.is_array | ||
|
||
end_array_idx = topic_type_str.find(']', array_idx + 1) | ||
if end_array_idx < 0: | ||
return None, False, None | ||
if topic_type_info.is_static_array: | ||
array_size = topic_type_info.static_array_size | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it makes sense to create a helper function (in message_helpers.py?) that calculates array size with this information. |
||
|
||
slot_type = topic_type_str[:array_idx] | ||
array_size_str = topic_type_str[array_idx + 1 : end_array_idx] | ||
try: | ||
array_size = int(array_size_str) | ||
return slot_type, True, array_size | ||
except ValueError as e: | ||
return slot_type, True, None | ||
elif topic_type_info.is_bounded_array: | ||
array_size = topic_type_info.bounded_array_size | ||
|
||
elif topic_type_info.is_unbounded_array: | ||
array_size = None | ||
|
||
return slot_type, is_array, array_size | ||
|
||
def get_plot_fields(node, topic_name): | ||
topics = node.get_topic_names_and_types() | ||
real_topic = None | ||
for name, topic_types in topics: | ||
if name == topic_name[:len(name)]: | ||
real_topic = name | ||
topic_type_str = topic_types[0] if topic_types else None | ||
break | ||
if real_topic is None: | ||
topic_type, real_topic, _ = get_topic_type(node, topic_name) | ||
if topic_type is None: | ||
message = "topic %s does not exist" % (topic_name) | ||
return [], message | ||
|
||
if topic_type_str is None: | ||
message = "no topic types found for topic %s " % (topic_name) | ||
return [], message | ||
|
||
if len(topic_name) < len(real_topic) + 1: | ||
message = 'no field specified in topic name "{}"'.format(topic_name) | ||
return [], message | ||
|
||
field_name = topic_name[len(real_topic) + 1:] | ||
|
||
message_class = message_helpers.get_message_class(topic_type_str) | ||
if message_class is None: | ||
message = 'message class "{}" is invalid'.format(topic_type_str) | ||
return [], message | ||
is_array = False | ||
array_size = None | ||
slot_type = topic_type | ||
|
||
slot_type, is_array, array_size = _parse_type(topic_type_str) | ||
field_class = message_helpers.get_message_class(slot_type) | ||
if field_class is None: | ||
message = "type of topic %s is unknown" % (topic_name) | ||
return [], message | ||
|
||
field_index = None | ||
# Go through the fields until you reach the last msg field | ||
fields = [f for f in field_name.split('/') if f] | ||
|
||
for field in fields: | ||
# parse the field name for an array index | ||
field, _, field_index = _parse_type(field) | ||
if field is None: | ||
try: | ||
field, _, field_index = \ | ||
topic_helpers.separate_field_from_array_information(field) | ||
except MsgSpecException: | ||
message = "invalid field %s in topic %s" % (field, real_topic) | ||
return [], message | ||
|
||
field_names_and_types = field_class.get_fields_and_field_types() | ||
if field not in field_names_and_types: | ||
# If it is not a generated msg type | ||
if not hasattr(field_class, "get_fields_and_field_types"): | ||
msg = "Invalid field path %s in topic %s" % (field_name, real_topic) | ||
return [], msg | ||
|
||
# Find the field in the fields of field_class | ||
fields_and_field_types = field_class.get_fields_and_field_types() | ||
if field not in fields_and_field_types.keys() : | ||
message = "no field %s in topic %s" % (field_name, real_topic) | ||
return [], message | ||
slot_type = field_names_and_types[field] | ||
|
||
slot_type = fields_and_field_types[field] | ||
slot_type, slot_is_array, array_size = _parse_type(slot_type) | ||
is_array = slot_is_array and field_index is None | ||
is_array = slot_is_array | ||
|
||
if topic_helpers.is_primitive_type(slot_type): | ||
field_class = topic_helpers.get_type_class(slot_type) | ||
else: | ||
field_class = message_helpers.get_message_class(slot_type) | ||
field_class = message_field_type_helpers.get_field_python_class( | ||
slot_type) | ||
|
||
if field_class in (int, float, bool): | ||
topic_kind = 'boolean' if field_class == bool else 'numeric' | ||
if is_array: | ||
if array_size is not None: | ||
message = "topic %s is fixed-size %s array" % (topic_name, topic_kind) | ||
return ["%s[%d]" % (topic_name, i) for i in range(array_size)], message | ||
if field_index is None: | ||
msg = "topic %s is a fixed-size %s array" % (topic_name, topic_kind) | ||
return ["%s[%d]" % (topic_name, i) for i in range(array_size)], msg | ||
else: | ||
msg = "topic %s is a fixed-size %s array with ix: %s" % ( | ||
topic_name, topic_kind, field_index | ||
) | ||
return [topic_name], msg | ||
else: | ||
message = "topic %s is variable-size %s array" % (topic_name, topic_kind) | ||
return [], message | ||
if field_index is not None: | ||
msg = "topic %s is variable-size %s array with ix %d" % ( | ||
topic_name, topic_kind, field_index | ||
) | ||
return [topic_name], msg | ||
else: | ||
msg = "topic %s is variable-size %s array" % (topic_name, topic_kind) | ||
return [], msg | ||
else: | ||
message = "topic %s is %s" % (topic_name, topic_kind) | ||
return [topic_name], message | ||
msg = "topic %s is %s" % (topic_name, topic_kind) | ||
return [topic_name], msg | ||
else: | ||
if not topic_helpers.is_primitive_type(slot_type): | ||
if not message_field_type_helpers.is_primitive_type(slot_type): | ||
numeric_fields = [] | ||
for slot, slot_type in field_class.get_fields_and_field_types().items(): | ||
fields_and_field_types = field_class.get_fields_and_field_types() | ||
for i, slot in enumerate(fields_and_field_types.keys()): | ||
slot_type = fields_and_field_types[slot] | ||
slot_type, is_array, array_size = _parse_type(slot_type) | ||
slot_class = topic_helpers.get_type_class(slot_type) | ||
slot_class = \ | ||
message_field_type_helpers.get_field_python_class(slot_type) | ||
if slot_class in (int, float) and not is_array: | ||
numeric_fields.append(slot) | ||
message = "" | ||
|
@@ -321,9 +351,14 @@ def make_remove_topic_function(x): | |
|
||
self.remove_topic_button.setMenu(self._remove_topic_menu) | ||
|
||
def add_topic(self, topic_name): | ||
def add_topic(self, field_path): | ||
topics_changed = False | ||
for topic_name in get_plot_fields(self._node, topic_name)[0]: | ||
topics, msg = get_plot_fields(self._node, field_path) | ||
if len(topics) == 0: | ||
qWarning("get_plot_fields failed with msg: %s" % msg) | ||
return | ||
|
||
for topic_name in topics: | ||
if topic_name in self._rosdata: | ||
qWarning('PlotWidget.add_topic(): topic already subscribed: %s' % topic_name) | ||
continue | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two variables are not used except as a return, you might as well just put them in the return directly.