Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions frame_editor/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ add_service_files(
GetFrame.srv
GetFrameNames.srv
RemoveFrame.srv
RenameFrame.srv
SetFrame.srv
SetParentFrame.srv
CopyFrame.srv
Expand Down
13 changes: 11 additions & 2 deletions frame_editor/src/frame_editor/FrameEditorGUI.ui
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,17 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_rename">
<property name="text">
<string>Rename</string>
</property>
<property name="icon">
<iconset theme="text-editor">
<normaloff>.</normaloff>.</iconset>
</property>
</widget>
</item>
</layout>
</item>
</layout>
Expand Down Expand Up @@ -174,9 +185,7 @@
</item>
<item>
<widget class="QLineEdit" name="txt_name">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
Expand Down
54 changes: 54 additions & 0 deletions frame_editor/src/frame_editor/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,61 @@ def undo(self):
self.element.hidden = True
self.editor.add_undo_level(1, [self.element])

class Command_RenameElement(QUndoCommand):
'''Copys a source frame's transformation and sets a new parent
'''

def __init__(self, editor, element, new_name):
QUndoCommand.__init__(self, "Rename")
self.editor = editor
self.element = element

if editor.active_frame is element:
self.was_active = True
else:
self.was_active = False
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if editor.active_frame is element:
self.was_active = True
else:
self.was_active = False
self.was_active = editor.active_frame is element

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Implemented the suggestion


self.new_name = new_name
self.old_name = element.name

self.element.name = self.new_name

def redo(self):
if self.was_active:
self.editor.active_frame = self.element
self.editor.add_undo_level(2)

# Remove old frame
if self.editor.frames.get(self.old_name):
del self.editor.frames[self.old_name]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if self.editor.frames.get(self.old_name):
del self.editor.frames[self.old_name]
self.editor.frames.pop(self.old_name, None):

or

Suggested change
if self.editor.frames.get(self.old_name):
del self.editor.frames[self.old_name]
if self.old_name in self.editor.frames:
del self.editor.frames[self.old_name]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Changed it to self.editor.frames.pop(self.old_name, None)


# Add new frame
self.element.name = self.new_name
self.editor.frames[self.new_name] = self.element
self.editor.add_undo_level(1, [self.element])

# Update parent names
for frame in self.editor.frames.values():
if frame.parent == self.old_name:
frame.parent = self.new_name
self.editor.add_undo_level(4, [frame])

def undo(self):
if self.was_active:
self.editor.active_frame = self.element
self.editor.add_undo_level(2)

if self.editor.frames.get(self.new_name):
del self.editor.frames[self.new_name]
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see above

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Implemented suggestion


self.element.name = self.old_name
self.editor.frames[self.old_name] = self.element
self.editor.add_undo_level(1, [self.element])

for frame in self.editor.frames.values():
if frame.parent == self.new_name:
frame.parent = self.old_name
self.editor.add_undo_level(4, [frame])

class Command_RebaseElement(QUndoCommand):
'''Copys a source frame's transformation and sets a new parent
Expand Down
32 changes: 32 additions & 0 deletions frame_editor/src/frame_editor/interface_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ def __init__(self, frame_editor):
rospy.Service("~set_frame", SetFrame, self.callback_set_frame)
rospy.Service("~set_parent", SetParentFrame, self.callback_set_parent_frame)
rospy.Service("~copy_frame", CopyFrame, self.callback_copy_frame)
rospy.Service("~rename_frame", RenameFrame, self.callback_rename_frame)

rospy.Service("~load_yaml", LoadYaml, self.callback_load_yaml)
rospy.Service("~save_yaml", SaveYaml, self.callback_save_yaml)
Expand Down Expand Up @@ -276,5 +277,36 @@ def callback_copy_frame(self, request):
response.error_code = 9

return response

def callback_rename_frame(self, request):
rospy.loginfo("> Request to rename frame '{}' with new name '{}'".format(request.source_name, request.new_name))

response = RenameFrameResponse()
response.error_code = 0

if request.new_name == "":
rospy.logerr(" Error: No name given")
response.error_code = 1

elif request.source_name == "":
rospy.logerr(" Error: No source name given")
response.error_code = 3

elif request.source_name not in self.editor.frames:
rospy.logerr(f" Error: Frame not found: {request.source_name}")
response.error_code = 2

elif request.new_name in self.editor.frames:
rospy.logerr(f" Error: New frame name already exists: {request.new_name}")
response.error_code = 4

else:
try:
self.editor.command(Command_RenameElement(self.editor, self.editor.frames[request.source_name], request.new_name))
except Exception as e:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you specify the exception a bit more? I think here it should be key and attribute errors

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. I added the AttributeError exception when the dictionary entry cannot be found. The general exception is still there in case I missed something.

rospy.logerr("Error: unhandled exception {}".format(e))
response.error_code = 9

return response

# eof
45 changes: 44 additions & 1 deletion frame_editor/src/frame_editor/rqt_editor.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ def create_main_widget(self):
##
widget.btn_add.clicked.connect(self.btn_add_clicked)
widget.btn_delete.clicked.connect(self.btn_delete_clicked)
widget.btn_rename.clicked.connect(self.btn_rename_clicked)
widget.btn_duplicate.clicked.connect(self.btn_duplicate_clicked)
widget.list_frames.currentItemChanged.connect(self.selected_frame_changed)
widget.list_tf.currentItemChanged.connect(self.update_measurement)
Expand Down Expand Up @@ -151,6 +152,7 @@ def create_main_widget(self):
widget.txt_c.editingFinished.connect(self.c_valueChanged)

widget.txt_group.editingFinished.connect(self.group_valueChanged)
widget.txt_name.editingFinished.connect(self.frameName_valueChanged)

widget.btn_rad.toggled.connect(self.update_fields)

Expand Down Expand Up @@ -559,6 +561,24 @@ def btn_duplicate_clicked(self, checked):

self.editor.command(Command_CopyElement(self.editor, name, source_name, parent_name))
self.signal_update_tf.emit(False, False)

@Slot(bool)
def btn_rename_clicked(self, checked):
item = self.widget.list_frames.currentItem()
if not item:
return

# Check if the item is a frame. Early returns if selected item is not a frame (e.g. a group)
source_name = item.text(0)
if self.editor.frames.get(source_name) is None:
return

new_name = self.get_valid_frame_name("Rename Frame", default_name=source_name)
if not new_name:
return

self.editor.command(Command_RenameElement(self.editor, self.editor.frames[source_name], new_name))
self.signal_update_tf.emit(True, True)

def get_sleep_time(self):
return max(5.0 / self.editor.hz, 0.1)
Expand All @@ -585,7 +605,13 @@ def btn_delete_clicked(self, checked):
item = self.widget.list_frames.currentItem()
if not item:
return
self.editor.command(Command_RemoveElement(self.editor, self.editor.frames[item.text(0)]))

# Check if the item is a frame. Early returns if selected item is not a frame (e.g. a group)
source_name = item.text(0)
if self.editor.frames.get(source_name) is None:
return

self.editor.command(Command_RemoveElement(self.editor, self.editor.frames[source_name]))
self.signal_update_tf.emit(True, True)

## PARENTING ##
Expand Down Expand Up @@ -714,7 +740,24 @@ def group_valueChanged(self):
if self.editor.active_frame.group != value:
self.editor.command(Command_SetGroup(self.editor, self.editor.active_frame, value))

@Slot()
def frameName_valueChanged(self):
item = self.widget.list_frames.currentItem()
if not item:
return
source_name = item.text(0)

new_name = self.widget.txt_name.text()
existing_tf_frames = set(self.editor.all_frame_ids())
existing_editor_frames = set(self.editor.all_editor_frame_ids())
Comment on lines +751 to +752
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont think it is required to make them into a set if you just check if an element is in the list

Copy link

@cellios-fbr cellios-fbr Jan 19, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still Open.
That is true. I've tried to stick as closely as possible to the source code and was inspired by the function get_valid_frame_name(). I would leave it as is for now, unless you see it differently


# allow recreating if frame was published by frameditor node originally
if new_name in existing_editor_frames or (new_name in existing_tf_frames and not Frame.was_published_by_frameeditor(new_name)):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have an error popup here i think

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. Impleneted an error pop-up. Had to insert another check source_name != new_name. Otherwise, an error will pop up every time someone clicks on the box without actually inserting something.

self.widget.txt_name.setText(source_name)
return None

self.editor.command(Command_RenameElement(self.editor, self.editor.frames[source_name], new_name))
self.signal_update_tf.emit(True, True)

## FRAME STYLE ##
##
Expand Down
4 changes: 4 additions & 0 deletions frame_editor/srv/RenameFrame.srv
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
string source_name
string new_name
---
int32 error_code