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
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
49 changes: 49 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,56 @@ 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

self.was_active = editor.active_frame is element

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
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)

self.editor.frames.pop(self.new_name, None)

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
35 changes: 35 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,39 @@ 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: Source 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.get(request.source_name), request.new_name))
except AttributeError as e:
rospy.logerr("Error: The source frame was not found. {}".format(e))
response.error_code = 9
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
47 changes: 46 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,26 @@ 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 source_name != new_name and (new_name in existing_editor_frames or (new_name in existing_tf_frames and not Frame.was_published_by_frameeditor(new_name))):
self.widget.txt_name.setText(source_name)
QtWidgets.QMessageBox.warning(self.widget, "Invalid Frame Name",
f"The frame name {new_name} already exists. Cannot create a new frame with the same 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