Skip to content

Commit 3838e2d

Browse files
Merge branch 'main' into arrows
2 parents fea73f4 + 1a61360 commit 3838e2d

8 files changed

Lines changed: 213 additions & 100 deletions

File tree

.github/workflows/ci.yml

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ jobs:
1111
name: Find Trailing Whitespace
1212
runs-on: ubuntu-20.04
1313
steps:
14-
- uses: actions/checkout@v2
14+
- uses: actions/checkout@v4
1515
- name: Find Trailing Whitespace
1616
run: |
1717
set +e
@@ -28,8 +28,7 @@ jobs:
2828
name: Generate python files from ui
2929
runs-on: ubuntu-20.04
3030
steps:
31-
- uses: actions/checkout@v2.4.0
32-
31+
- uses: actions/checkout@v4
3332
- name: Remove broken apt repos [Ubuntu]
3433
run: |
3534
for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done
@@ -52,14 +51,14 @@ jobs:
5251
needs: [build]
5352
if: github.ref == 'refs/heads/main'
5453
steps:
55-
- uses: actions/checkout@v2.4.0
54+
- uses: actions/checkout@v4
5655

5756
- name: Download artifacts
58-
uses: actions/download-artifact@v2
57+
uses: actions/download-artifact@v4
5958
with:
6059
name: autogenerated_files
6160
path: robot_log_visualizer/ui/autogenerated
6261
- name: Deploy
63-
uses: stefanzweifel/git-auto-commit-action@v4
62+
uses: stefanzweifel/git-auto-commit-action@v5
6463
with:
6564
commit_message: ⚙️ Automatic update of the python UI classes

.github/workflows/pypi-ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ jobs:
5454
steps:
5555

5656
- name: Download artifacts
57-
uses: actions/download-artifact@v2
57+
uses: actions/download-artifact@v4
5858
with:
5959
name: dist
6060
path: dist
@@ -74,7 +74,7 @@ jobs:
7474
steps:
7575

7676
- name: Download artifacts
77-
uses: actions/download-artifact@v2
77+
uses: actions/download-artifact@v4
7878
with:
7979
name: dist
8080
path: dist

robot_log_visualizer/file_reader/signal_provider.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,10 @@ def __populate_numerical_data(self, file_object):
130130
continue
131131
if "data" in value.keys():
132132
data[key] = {}
133-
data[key]["data"] = np.squeeze(np.array(value["data"]))
134-
data[key]["timestamps"] = np.squeeze(np.array(value["timestamps"]))
133+
data[key]["data"] = np.atleast_1d(np.squeeze(np.array(value["data"])))
134+
data[key]["timestamps"] = np.atleast_1d(
135+
np.squeeze(np.array(value["timestamps"]))
136+
)
135137

136138
# if the initial or end time has been updated we can also update the entire timestamps dataset
137139
if data[key]["timestamps"][0] < self.initial_time:

robot_log_visualizer/plotter/matplotlib_viewer_canvas.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,13 +201,23 @@ def update_plots(self, paths, legends):
201201

202202
timestamps = data["timestamps"] - self.signal_provider.initial_time
203203

204-
(self.active_paths[path_string],) = self.axes.plot(
205-
timestamps,
206-
datapoints,
207-
label=legend_string,
208-
picker=True,
209-
color=next(self.color_palette),
210-
)
204+
if timestamps.size > 1:
205+
(self.active_paths[path_string],) = self.axes.plot(
206+
timestamps,
207+
datapoints,
208+
label=legend_string,
209+
picker=True,
210+
color=next(self.color_palette),
211+
)
212+
else:
213+
(self.active_paths[path_string],) = self.axes.plot(
214+
timestamps,
215+
datapoints,
216+
label=legend_string,
217+
picker=True,
218+
color=next(self.color_palette),
219+
marker="o",
220+
)
211221

212222
paths_to_be_canceled = []
213223
for active_path in self.active_paths.keys():

robot_log_visualizer/robot_visualizer/meshcat_provider.py

Lines changed: 66 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,16 @@ def __init__(self, signal_provider, period):
3131
self._is_model_loaded = False
3232
self._signal_provider = signal_provider
3333

34-
self.custom_model_path = ""
34+
self.model_path = ""
3535
self.custom_package_dir = ""
36+
self.base_frame = ""
3637
self.env_list = ["GAZEBO_MODEL_PATH", "ROS_PACKAGE_PATH", "AMENT_PREFIX_PATH"]
3738
self._registered_3d_points = set()
3839
self._registered_3d_trajectories = dict()
3940
self._register_3d_arrow = set()
4041

42+
self.frame_T_base = np.eye(4)
43+
4144
@property
4245
def state(self):
4346
locker = QMutexLocker(self.state_lock)
@@ -84,7 +87,7 @@ def unregister_3d_arrow(self, arrow_path):
8487
self._register_3d_arrow.remove(arrow_path)
8588
self._meshcat_visualizer.delete(shape_name=arrow_path)
8689

87-
def load_model(self, considered_joints, model_name):
90+
def load_model(self, considered_joints, model_name, base_frame=None):
8891
def get_model_path_from_envs(env_list):
8992
return [
9093
Path(f) if (env != "AMENT_PREFIX_PATH") else Path(f) / "share"
@@ -115,23 +118,21 @@ def find_model_joints(model_name, considered_joints):
115118

116119
return model_joints_index
117120

118-
self._is_model_loaded = False
119-
120121
# Load the model
121122
model_loader = idyn.ModelLoader()
122123

123124
self.model_joints_index = []
124125
# In this case the user specify the model path
125-
if self.custom_model_path:
126+
if self.custom_package_dir:
126127
self.model_joints_index = find_model_joints(
127-
self.custom_model_path, considered_joints
128+
self.model_path, considered_joints
128129
)
129130
considered_model_joints = [
130131
considered_joints[i] for i in self.model_joints_index
131132
]
132133

133134
model_loader.loadReducedModelFromFile(
134-
self.custom_model_path,
135+
self.model_path,
135136
considered_model_joints,
136137
"urdf",
137138
[self.custom_package_dir],
@@ -152,34 +153,79 @@ def find_model_joints(model_name, considered_joints):
152153

153154
if model_filenames:
154155
model_found_in_env_folders = True
155-
self.custom_model_path = str(model_filenames[0])
156+
self.model_path = str(model_filenames[0])
156157
break
157158

158159
# If the model is not found we exit
159160
if not model_found_in_env_folders:
160161
return False
161162

162163
self.model_joints_index = find_model_joints(
163-
self.custom_model_path, considered_joints
164+
self.model_path, considered_joints
164165
)
165166
considered_model_joints = [
166167
considered_joints[i] for i in self.model_joints_index
167168
]
168169
model_loader.loadReducedModelFromFile(
169-
self.custom_model_path, considered_model_joints
170+
self.model_path, considered_model_joints
170171
)
171172

172173
if not model_loader.isValid():
173174
return False
174175

176+
self.meshcat_visualizer_mutex.lock()
177+
178+
# if the model is already loaded we remove it
179+
if self._is_model_loaded:
180+
self._meshcat_visualizer.delete(shape_name="robot")
181+
self._is_model_loaded = False
182+
183+
model = model_loader.model()
184+
if base_frame is None:
185+
self.frame_T_base = np.eye(4)
186+
self.base_frame = model.getFrameName(model.getDefaultBaseLink())
187+
link_frame = None
188+
else:
189+
base_frame_index = model.getFrameIndex(base_frame)
190+
if base_frame_index == -1: # idyn.FRAME_INVALID_INDEX
191+
raise ValueError(
192+
"MeshcatProvider: Unable to find the base frame "
193+
+ base_frame
194+
+ " in the model."
195+
)
196+
197+
frame_T_base = model.getFrameTransform(base_frame_index)
198+
frame_T_base = frame_T_base.inverse().asHomogeneousTransform()
199+
self.frame_T_base = frame_T_base.toNumPy()
200+
201+
link_frame = model.getLinkName(model.getFrameLink(base_frame_index))
202+
203+
self.base_frame = base_frame
204+
175205
self._meshcat_visualizer.load_model(
176-
model_loader.model(), model_name="robot", color=0.8
206+
model, model_name="robot", color=0.8, base_link=link_frame
177207
)
178208

179209
self._is_model_loaded = True
180210

211+
self.meshcat_visualizer_mutex.unlock()
212+
181213
return True
182214

215+
def robot_frames(self):
216+
frames = []
217+
if not self._is_model_loaded:
218+
return frames
219+
220+
model = self._meshcat_visualizer.model["robot"]
221+
frames_number = model.getNrOfFrames()
222+
223+
for i in range(frames_number):
224+
frame = model.getFrameName(i)
225+
frames.append(frame)
226+
227+
return frames
228+
183229
def run(self):
184230
identity = np.eye(3)
185231

@@ -191,9 +237,16 @@ def run(self):
191237
robot_state = self._signal_provider.get_robot_state_at_index(index)
192238
self.meshcat_visualizer_mutex.lock()
193239
# These are the robot measured joint positions in radians
240+
# we need to compute the base transform as
241+
# I_T_B = I_T_F * frame_T_Base
242+
# I_R_B = I_R_F * frame_R_Base
243+
# I_p_B = I_p_F + I_R_F * frame_p_Base
194244
self._meshcat_visualizer.set_multibody_system_state(
195-
base_position=robot_state["base_position"],
196-
base_rotation=robot_state["base_orientation"],
245+
base_position=robot_state["base_orientation"]
246+
@ self.frame_T_base[:3, 3]
247+
+ robot_state["base_position"],
248+
base_rotation=robot_state["base_orientation"]
249+
@ self.frame_T_base[:3, :3],
197250
joint_value=robot_state["joints_position"][self.model_joints_index],
198251
model_name="robot",
199252
)

robot_log_visualizer/ui/autogenerated/set_robot_model.py

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -13,52 +13,65 @@
1313
class Ui_setRobotModelDialog(object):
1414
def setupUi(self, setRobotModelDialog):
1515
setRobotModelDialog.setObjectName("setRobotModelDialog")
16-
setRobotModelDialog.resize(365, 217)
17-
self.gridLayout = QtWidgets.QGridLayout(setRobotModelDialog)
18-
self.gridLayout.setObjectName("gridLayout")
19-
self.buttonBox = QtWidgets.QDialogButtonBox(setRobotModelDialog)
20-
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
21-
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Abort|QtWidgets.QDialogButtonBox.Save)
22-
self.buttonBox.setObjectName("buttonBox")
23-
self.gridLayout.addWidget(self.buttonBox, 3, 0, 1, 1)
16+
setRobotModelDialog.resize(711, 363)
17+
self.formLayout = QtWidgets.QFormLayout(setRobotModelDialog)
18+
self.formLayout.setObjectName("formLayout")
2419
self.frame = QtWidgets.QFrame(setRobotModelDialog)
2520
self.frame.setFrameShape(QtWidgets.QFrame.StyledPanel)
2621
self.frame.setObjectName("frame")
2722
self.gridLayout_2 = QtWidgets.QGridLayout(self.frame)
2823
self.gridLayout_2.setObjectName("gridLayout_2")
29-
self.robotModelLineEdit = QtWidgets.QLineEdit(self.frame)
30-
font = QtGui.QFont()
31-
font.setFamily("Ubuntu Mono")
32-
self.robotModelLineEdit.setFont(font)
33-
self.robotModelLineEdit.setObjectName("robotModelLineEdit")
34-
self.gridLayout_2.addWidget(self.robotModelLineEdit, 1, 0, 1, 1)
3524
self.label = QtWidgets.QLabel(self.frame)
3625
self.label.setObjectName("label")
3726
self.gridLayout_2.addWidget(self.label, 0, 0, 1, 1)
3827
self.robotModelToolButton = QtWidgets.QToolButton(self.frame)
3928
self.robotModelToolButton.setObjectName("robotModelToolButton")
4029
self.gridLayout_2.addWidget(self.robotModelToolButton, 1, 1, 1, 1)
41-
self.gridLayout.addWidget(self.frame, 0, 0, 1, 1)
30+
self.robotModelLineEdit = QtWidgets.QLineEdit(self.frame)
31+
font = QtGui.QFont()
32+
font.setFamily("Ubuntu Mono")
33+
self.robotModelLineEdit.setFont(font)
34+
self.robotModelLineEdit.setObjectName("robotModelLineEdit")
35+
self.gridLayout_2.addWidget(self.robotModelLineEdit, 1, 0, 1, 1)
36+
self.formLayout.setWidget(0, QtWidgets.QFormLayout.SpanningRole, self.frame)
4237
self.frame_2 = QtWidgets.QFrame(setRobotModelDialog)
4338
self.frame_2.setFrameShape(QtWidgets.QFrame.StyledPanel)
4439
self.frame_2.setObjectName("frame_2")
4540
self.gridLayout_3 = QtWidgets.QGridLayout(self.frame_2)
4641
self.gridLayout_3.setObjectName("gridLayout_3")
47-
self.label_3 = QtWidgets.QLabel(self.frame_2)
48-
self.label_3.setObjectName("label_3")
49-
self.gridLayout_3.addWidget(self.label_3, 0, 0, 1, 1)
50-
self.packageDirToolButton = QtWidgets.QToolButton(self.frame_2)
51-
self.packageDirToolButton.setObjectName("packageDirToolButton")
52-
self.gridLayout_3.addWidget(self.packageDirToolButton, 1, 1, 1, 1)
5342
self.packageDirLineEdit = QtWidgets.QLineEdit(self.frame_2)
5443
font = QtGui.QFont()
5544
font.setFamily("Ubuntu Mono")
5645
self.packageDirLineEdit.setFont(font)
5746
self.packageDirLineEdit.setObjectName("packageDirLineEdit")
5847
self.gridLayout_3.addWidget(self.packageDirLineEdit, 1, 0, 1, 1)
59-
self.gridLayout.addWidget(self.frame_2, 1, 0, 1, 1)
48+
self.packageDirToolButton = QtWidgets.QToolButton(self.frame_2)
49+
self.packageDirToolButton.setObjectName("packageDirToolButton")
50+
self.gridLayout_3.addWidget(self.packageDirToolButton, 1, 1, 1, 1)
51+
self.label_3 = QtWidgets.QLabel(self.frame_2)
52+
self.label_3.setObjectName("label_3")
53+
self.gridLayout_3.addWidget(self.label_3, 0, 0, 1, 1)
54+
self.formLayout.setWidget(1, QtWidgets.QFormLayout.SpanningRole, self.frame_2)
55+
self.frame_3 = QtWidgets.QFrame(setRobotModelDialog)
56+
self.frame_3.setFrameShape(QtWidgets.QFrame.StyledPanel)
57+
self.frame_3.setObjectName("frame_3")
58+
self.formLayout_2 = QtWidgets.QFormLayout(self.frame_3)
59+
self.formLayout_2.setObjectName("formLayout_2")
60+
self.label_5 = QtWidgets.QLabel(self.frame_3)
61+
self.label_5.setObjectName("label_5")
62+
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_5)
63+
self.frameNameComboBox = QtWidgets.QComboBox(self.frame_3)
64+
self.frameNameComboBox.setMaxVisibleItems(5)
65+
self.frameNameComboBox.setObjectName("frameNameComboBox")
66+
self.formLayout_2.setWidget(0, QtWidgets.QFormLayout.FieldRole, self.frameNameComboBox)
67+
self.formLayout.setWidget(2, QtWidgets.QFormLayout.SpanningRole, self.frame_3)
68+
self.buttonBox = QtWidgets.QDialogButtonBox(setRobotModelDialog)
69+
self.buttonBox.setOrientation(QtCore.Qt.Horizontal)
70+
self.buttonBox.setStandardButtons(QtWidgets.QDialogButtonBox.Abort|QtWidgets.QDialogButtonBox.Save)
71+
self.buttonBox.setObjectName("buttonBox")
72+
self.formLayout.setWidget(4, QtWidgets.QFormLayout.SpanningRole, self.buttonBox)
6073
spacerItem = QtWidgets.QSpacerItem(20, 40, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding)
61-
self.gridLayout.addItem(spacerItem, 2, 0, 1, 1)
74+
self.formLayout.setItem(3, QtWidgets.QFormLayout.FieldRole, spacerItem)
6275

6376
self.retranslateUi(setRobotModelDialog)
6477
self.buttonBox.accepted.connect(setRobotModelDialog.accept)
@@ -70,5 +83,6 @@ def retranslateUi(self, setRobotModelDialog):
7083
setRobotModelDialog.setWindowTitle(_translate("setRobotModelDialog", "Dialog"))
7184
self.label.setText(_translate("setRobotModelDialog", "Robot Model"))
7285
self.robotModelToolButton.setText(_translate("setRobotModelDialog", "..."))
73-
self.label_3.setText(_translate("setRobotModelDialog", "Package Directory"))
7486
self.packageDirToolButton.setText(_translate("setRobotModelDialog", "..."))
87+
self.label_3.setText(_translate("setRobotModelDialog", "Package Directory"))
88+
self.label_5.setText(_translate("setRobotModelDialog", "Base Frame"))

0 commit comments

Comments
 (0)