q3dviewer 1.1.1__py3-none-any.whl → 1.1.3__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -25,7 +25,7 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
25
25
  self.active_keys = set()
26
26
  self.show_center = False
27
27
  self.enable_show_center = True
28
- self.view_need_update = True
28
+ self.need_recalc_view = True
29
29
  self.view_matrix = self.get_view_matrix()
30
30
  self.projection_matrix = self.get_projection_matrix()
31
31
 
@@ -98,7 +98,7 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
98
98
 
99
99
  def set_view_matrix(self, view_matrix):
100
100
  self.view_matrix = view_matrix
101
- self.view_need_update = False
101
+ self.need_recalc_view = False
102
102
 
103
103
  def mouseReleaseEvent(self, ev):
104
104
  if hasattr(self, 'mousePos'):
@@ -106,13 +106,13 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
106
106
 
107
107
  def set_dist(self, dist):
108
108
  self.dist = dist
109
- self.view_need_update = True
109
+ self.need_recalc_view = True
110
110
 
111
111
  def update_dist(self, delta):
112
112
  self.dist += delta
113
113
  if self.dist < 0.1:
114
114
  self.dist = 0.1
115
- self.view_need_update = True
115
+ self.need_recalc_view = True
116
116
 
117
117
  def wheelEvent(self, ev):
118
118
  delta = ev.angleDelta().x()
@@ -121,6 +121,24 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
121
121
  self.update_dist(-delta * self.dist * 0.001)
122
122
  self.show_center = True
123
123
 
124
+
125
+ def rotate_keep_cam_pos(self, rx=0, ry=0, rz=0):
126
+ """
127
+ Rotate the camera while keeping the current camera position.
128
+ This updates both the Euler angles and the center point.
129
+ """
130
+ new_euler = self.euler + np.array([rx, ry, rz])
131
+ new_euler = (new_euler + np.pi) % (2 * np.pi) - np.pi
132
+
133
+ Rwc_old = euler_to_matrix(self.euler)
134
+ tco = np.array([0, 0, self.dist])
135
+ twc = self.center + Rwc_old @ tco
136
+
137
+ Rwc_new = euler_to_matrix(new_euler)
138
+ self.center = twc - Rwc_new @ tco
139
+ self.euler = new_euler
140
+ self.need_recalc_view = True
141
+
124
142
  def mouseMoveEvent(self, ev):
125
143
  lpos = ev.localPos()
126
144
  if not hasattr(self, 'mousePos'):
@@ -131,24 +149,26 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
131
149
  rot_speed = 0.2
132
150
  dyaw = radians(-diff.x() * rot_speed)
133
151
  droll = radians(-diff.y() * rot_speed)
134
- self.rotate(droll, 0, dyaw)
152
+ if ev.modifiers() & QtCore.Qt.ShiftModifier:
153
+ self.rotate_keep_cam_pos(droll, 0, dyaw)
154
+ else:
155
+ self.rotate(droll, 0, dyaw)
135
156
  elif ev.buttons() == QtCore.Qt.MouseButton.LeftButton:
136
157
  Rwc = euler_to_matrix(self.euler)
137
158
  Kinv = np.linalg.inv(self.get_K())
138
159
  dist = max(self.dist, 0.5)
139
- self.center += Rwc @ Kinv @ np.array([-diff.x(), diff.y(), 0]) * dist
160
+ self.translate(Rwc @ Kinv @ np.array([-diff.x(), diff.y(), 0]) * dist)
140
161
  self.show_center = True
141
- self.view_need_update = True
142
162
 
143
163
  def set_center(self, center):
144
164
  self.center = center
145
- self.view_need_update = True
165
+ self.need_recalc_view = True
146
166
 
147
167
  def paintGL(self):
148
168
  # if the camera is moved, update the model view matrix.
149
- if self.view_need_update:
169
+ if self.need_recalc_view:
150
170
  self.view_matrix = self.get_view_matrix()
151
- self.view_need_update = False
171
+ self.need_recalc_view = False
152
172
  self.update_model_view()
153
173
 
154
174
  # set the background color
@@ -192,45 +212,48 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
192
212
  return
193
213
  rot_speed = 0.5
194
214
  trans_speed = max(self.dist * 0.005, 0.1)
215
+ shift_pressed = QtCore.Qt.Key_Shift in self.active_keys
195
216
  # Handle rotation keys
196
217
  if QtCore.Qt.Key_Up in self.active_keys:
197
- self.rotate(radians(rot_speed), 0, 0)
198
- self.view_need_update = True
218
+ if shift_pressed:
219
+ self.rotate_keep_cam_pos(radians(rot_speed), 0, 0)
220
+ else:
221
+ self.rotate(radians(rot_speed), 0, 0)
199
222
  if QtCore.Qt.Key_Down in self.active_keys:
200
- self.rotate(radians(-rot_speed), 0, 0)
201
- self.view_need_update = True
223
+ if shift_pressed:
224
+ self.rotate_keep_cam_pos(radians(-rot_speed), 0, 0)
225
+ else:
226
+ self.rotate(radians(-rot_speed), 0, 0)
202
227
  if QtCore.Qt.Key_Left in self.active_keys:
203
- self.rotate(0, 0, radians(rot_speed))
204
- self.view_need_update = True
228
+ if shift_pressed:
229
+ self.rotate_keep_cam_pos(0, 0, radians(rot_speed))
230
+ else:
231
+ self.rotate(0, 0, radians(rot_speed))
205
232
  if QtCore.Qt.Key_Right in self.active_keys:
206
- self.rotate(0, 0, radians(-rot_speed))
207
- self.view_need_update = True
233
+ if shift_pressed:
234
+ self.rotate_keep_cam_pos(0, 0, radians(-rot_speed))
235
+ else:
236
+ self.rotate(0, 0, radians(-rot_speed))
208
237
  # Handle zoom keys
209
238
  xz_keys = {QtCore.Qt.Key_Z, QtCore.Qt.Key_X}
210
239
  if self.active_keys & xz_keys:
211
240
  Rwc = euler_to_matrix(self.euler)
212
241
  if QtCore.Qt.Key_Z in self.active_keys:
213
- self.center += Rwc @ np.array([0, 0, -trans_speed])
214
- self.view_need_update = True
242
+ self.translate(Rwc @ np.array([0, 0, -trans_speed]))
215
243
  if QtCore.Qt.Key_X in self.active_keys:
216
- self.center += Rwc @ np.array([0, 0, trans_speed])
217
- self.view_need_update = True
244
+ self.translate(Rwc @ np.array([0, 0, trans_speed]))
218
245
  # Handle translation keys on the z plane
219
246
  dir_keys = {QtCore.Qt.Key_W, QtCore.Qt.Key_S, QtCore.Qt.Key_A, QtCore.Qt.Key_D}
220
247
  if self.active_keys & dir_keys:
221
248
  Rz = euler_to_matrix([0, 0, self.euler[2]])
222
249
  if QtCore.Qt.Key_W in self.active_keys:
223
- self.center += Rz @ np.array([0, trans_speed, 0])
224
- self.view_need_update = True
250
+ self.translate(Rz @ np.array([0, trans_speed, 0]))
225
251
  if QtCore.Qt.Key_S in self.active_keys:
226
- self.center += Rz @ np.array([0, -trans_speed, 0])
227
- self.view_need_update = True
252
+ self.translate(Rz @ np.array([0, -trans_speed, 0]))
228
253
  if QtCore.Qt.Key_A in self.active_keys:
229
- self.center += Rz @ np.array([-trans_speed, 0, 0])
230
- self.view_need_update = True
254
+ self.translate(Rz @ np.array([-trans_speed, 0, 0]))
231
255
  if QtCore.Qt.Key_D in self.active_keys:
232
- self.center += Rz @ np.array([trans_speed, 0, 0])
233
- self.view_need_update = True
256
+ self.translate(Rz @ np.array([trans_speed, 0, 0]))
234
257
 
235
258
  def update_model_view(self):
236
259
  glMatrixMode(GL_MODELVIEW)
@@ -259,7 +282,7 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
259
282
 
260
283
  def set_euler(self, euler):
261
284
  self.euler = euler
262
- self.view_need_update = True
285
+ self.need_recalc_view = True
263
286
 
264
287
  def set_color(self, color):
265
288
  self.color = color
@@ -303,6 +326,11 @@ class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
303
326
  self.euler[2] = (self.euler[2] + np.pi) % (2 * np.pi) - np.pi
304
327
  self.euler[1] = (self.euler[1] + np.pi) % (2 * np.pi) - np.pi
305
328
  self.euler[0] = np.clip(self.euler[0], 0, np.pi)
329
+ self.need_recalc_view = True
330
+
331
+ def translate(self, trans):
332
+ self.center += trans
333
+ self.need_recalc_view = True
306
334
 
307
335
  def change_show_center(self, state):
308
336
  self.enable_show_center = state
@@ -7,7 +7,6 @@ from q3dviewer.base_item import BaseItem
7
7
  from OpenGL.GL import *
8
8
  import numpy as np
9
9
  from OpenGL.GL import shaders
10
- from PIL import Image as PIL_Image
11
10
  from PySide6.QtWidgets import QLabel, QSpinBox, QCheckBox
12
11
 
13
12
 
@@ -108,12 +107,8 @@ class ImageItem(BaseItem):
108
107
  glBindVertexArray(0)
109
108
 
110
109
  def set_data(self, data):
111
- if isinstance(data, np.ndarray):
112
- pass
113
- elif isinstance(data, PIL_Image.Image):
114
- data = np.array(data)
115
- else:
116
- print("not support image type")
110
+ if not isinstance(data, np.ndarray):
111
+ print("The image type is not supported.")
117
112
  raise NotImplementedError
118
113
 
119
114
  if data.ndim == 2: # Grayscale image
@@ -107,7 +107,7 @@ class CMMViewer(q3d.Viewer):
107
107
  label_codec = QLabel("Codec:")
108
108
  codec_layout.addWidget(label_codec)
109
109
  self.codec_combo = QComboBox()
110
- self.codec_combo.addItems(["mjpeg", "mpeg4", "libx264", "libx265"])
110
+ self.codec_combo.addItems(["libx264", "mjpeg", "mpeg4", "libx265"])
111
111
  codec_layout.addWidget(self.codec_combo)
112
112
  setting_layout.addLayout(codec_layout)
113
113
 
@@ -143,6 +143,8 @@ class CMMViewer(q3d.Viewer):
143
143
  dock_widget = QDockWidget("Settings", self)
144
144
  dock_widget.setWidget(QWidget())
145
145
  dock_widget.widget().setLayout(setting_layout)
146
+ # Hide close and undock buttons, I don't want the user to close the dock
147
+ dock_widget.setFeatures(QDockWidget.DockWidgetMovable)
146
148
  self.addDockWidget(QtCore.Qt.RightDockWidgetArea, dock_widget)
147
149
 
148
150
  # Add the dock widget to the main layout
@@ -308,14 +310,16 @@ class CMMViewer(q3d.Viewer):
308
310
 
309
311
  def start_recording(self):
310
312
  self.is_recording = True
311
- self.frames_to_record = []
313
+ self.prv_frame_shape = None
312
314
  video_path = self.video_path_edit.text()
313
315
  codec = self.codec_combo.currentText()
314
316
  self.play_button.setStyleSheet("background-color: red")
315
317
  self.play_button.setText("Recording")
316
318
  self.writer = imageio.get_writer(video_path,
317
319
  fps=self.update_interval,
318
- codec=codec) # quality=10
320
+ codec=codec,
321
+ quality=10,
322
+ pixelformat='yuvj420p')
319
323
  # disable the all the frame_item while recording
320
324
  for frame in self.key_frames:
321
325
  frame.item.hide()
@@ -323,6 +327,7 @@ class CMMViewer(q3d.Viewer):
323
327
 
324
328
  def stop_recording(self, save_movie=True):
325
329
  self.is_recording = False
330
+ self.prv_frame_shape = None
326
331
  self.record_checkbox.setChecked(False)
327
332
  # enable the all the frame_item after recording
328
333
  for frame in self.key_frames:
@@ -342,17 +347,27 @@ class CMMViewer(q3d.Viewer):
342
347
 
343
348
  def record_frame(self):
344
349
  frame = self.glwidget.capture_frame()
345
- # make sure the frame size is multiple of 16
350
+
351
+ # restart recording if the window size changes
352
+ if self.prv_frame_shape is not None and frame.shape != self.prv_frame_shape:
353
+ self.writer.close()
354
+ self.start_recording()
355
+ return
356
+ self.prv_frame_shape = frame.shape
357
+
346
358
  height, width, _ = frame.shape
347
- if height % 16 != 0 or width % 16 != 0:
348
- frame = frame[:-(height % 16), :-(width % 16), :]
349
- frame = np.ascontiguousarray(frame)
350
- self.frames_to_record.append(frame)
359
+ # Adjust frame dimensions to be multiples of 16
360
+ new_height = height - (height % 16)
361
+ new_width = width - (width % 16)
362
+ frame = frame[:new_height, :new_width, :]
363
+ frame = np.ascontiguousarray(frame)
351
364
  try:
352
365
  self.writer.append_data(frame)
353
366
  except Exception as e:
354
- print("Don't change the window size during recording.")
355
- self.stop_recording(False) # Stop recording without saving
367
+ # unexpected error, stop recording without saving
368
+ print("Error while recording:")
369
+ print(e)
370
+ self.stop_recording(False)
356
371
  self.stop_playback()
357
372
 
358
373
  def eventFilter(self, obj, event):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: q3dviewer
3
- Version: 1.1.1
3
+ Version: 1.1.3
4
4
  Summary: A library designed for quickly deploying a 3D viewer.
5
5
  Home-page: https://github.com/scomup/q3dviewer
6
6
  Author: Liu Yang
@@ -16,7 +16,6 @@ Requires-Dist: imageio[ffmpeg]
16
16
  Requires-Dist: laspy
17
17
  Requires-Dist: meshio
18
18
  Requires-Dist: numpy
19
- Requires-Dist: pillow
20
19
  Requires-Dist: pye57
21
20
  Requires-Dist: pypcd4
22
21
  Requires-Dist: pyside6
@@ -1,5 +1,5 @@
1
1
  q3dviewer/__init__.py,sha256=rP5XX_x8g7hxIMqNHlU89BN4dt5MSvoYYwip68fCmhc,173
2
- q3dviewer/base_glwidget.py,sha256=tk8icQsRZ3OrSLkHvywmrxElk6jpMooLDZdbZ3sRLnk,11313
2
+ q3dviewer/base_glwidget.py,sha256=yQb4ckj4ldmiFoKL_TdZAqZNeBnMeSRDhLbMaruM3nk,12298
3
3
  q3dviewer/base_item.py,sha256=lzb04oRaS4rRJrAP6C1Bu4ugK237FgupMTB97zjNVFw,1768
4
4
  q3dviewer/basic_window.py,sha256=CFErOPRMysFcCqq3vhDsQ-xZzLArO3m1yABCTIhq5do,7946
5
5
  q3dviewer/cloud_viewer.py,sha256=IxxrB6Sl6aPCr9P9QzKHGkMcDP_DjsBWbkmIbsAoIM4,2358
@@ -15,7 +15,7 @@ q3dviewer/custom_items/cloud_item.py,sha256=s46TxNDw7E6DW3V-ek_PF7egB3kRbnqpOBNU
15
15
  q3dviewer/custom_items/frame_item.py,sha256=6BOM3MXi-Akv6KUXDC3QYEAXKxwk0Eo1KQn-F7jkqrQ,7559
16
16
  q3dviewer/custom_items/gaussian_item.py,sha256=CZoXMmj2JPFfMqu7v4q4dLUI2cg_WfE1DHWGXjEYn6M,9869
17
17
  q3dviewer/custom_items/grid_item.py,sha256=20n4TGm5YEaudhnEOCOk-HtsKwxVxaPr8YV36kO04yU,4802
18
- q3dviewer/custom_items/image_item.py,sha256=ctNR81fVgxkdl2n3U_TPFL6yn086UhNI_A9fFHgkc-4,5491
18
+ q3dviewer/custom_items/image_item.py,sha256=3FYAVlNLEILKZplkt2wbL8y16ke124GmwxcSmWmJY8Q,5357
19
19
  q3dviewer/custom_items/line_item.py,sha256=u0oFN2iHzsRHtnbvyvC_iglEkCwEU2NnTuE536sKUAE,4348
20
20
  q3dviewer/custom_items/text_item.py,sha256=nuHMVMQrwy50lNk9hxB94criFxbJJK-SYiK2fSXWUMQ,2158
21
21
  q3dviewer/custom_items/trajectory_item.py,sha256=uoKQSrTs_m_m1M8iNAm3peiXnZ9uVPsYQLYas3Gksjg,2754
@@ -28,7 +28,7 @@ q3dviewer/shaders/sort_by_key.glsl,sha256=CA2zOcbyDGYAJSJEUvgjUqNshg9NAehf8ipL3J
28
28
  q3dviewer/tools/__init__.py,sha256=01wG7BGM6VX0QyFBKsqPmyf2e-vrmV_N3-mo-VQ1VBg,20
29
29
  q3dviewer/tools/cloud_viewer.py,sha256=10f2LSWpmsXzxrGobXw188doVjJbgBfPoqZPUi35EtI,3867
30
30
  q3dviewer/tools/example_viewer.py,sha256=yeVXT0k4-h1vTLKnGzWADZD3our6XUaYUTy0p5daTkE,959
31
- q3dviewer/tools/film_maker.py,sha256=PkkMW6__ml7850U2UV2G1Pn4Ahk4EWa7edcvI_5-Z4Y,16028
31
+ q3dviewer/tools/film_maker.py,sha256=KXl0H03vnS7eAFw7nSRvTG3J0Y7gYt6YFUYaoxJf3xM,16600
32
32
  q3dviewer/tools/gaussian_viewer.py,sha256=vIwWmiFhjNmknrEkBLzt2yiegeH7LP3OeNjnGM6GzaI,1633
33
33
  q3dviewer/tools/lidar_calib.py,sha256=M01bGg2mT8LwVcYybolr4UW_UUaR-f-BFciEHtjeK-w,10488
34
34
  q3dviewer/tools/lidar_cam_calib.py,sha256=SYNLDvi15MX7Q3aGn771fvu1cES9xeXgP0_WmDq33w4,11200
@@ -39,9 +39,9 @@ q3dviewer/utils/convert_ros_msg.py,sha256=sAoQfy3qLQKsIArBAVm8H--wlQXOcmkKK3-Ox9
39
39
  q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
40
40
  q3dviewer/utils/maths.py,sha256=5TmjWUX1K3UjygXxrUsydjbo7tPzu0gD-yy7qtQUGBU,10588
41
41
  q3dviewer/utils/range_slider.py,sha256=jZJQL-uQgnpgLvtYSWpKTrJlLkt3aqNpaRQAePEpNd0,3174
42
- q3dviewer-1.1.1.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
43
- q3dviewer-1.1.1.dist-info/METADATA,sha256=wSqlVSSanm719PxnxIE7VOStyogcvsVwK1QobHCOLQI,7032
44
- q3dviewer-1.1.1.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
45
- q3dviewer-1.1.1.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
46
- q3dviewer-1.1.1.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
47
- q3dviewer-1.1.1.dist-info/RECORD,,
42
+ q3dviewer-1.1.3.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
43
+ q3dviewer-1.1.3.dist-info/METADATA,sha256=AXXroAQRcP2jMPxZCzpiR0tu-Ykj2lHUg28rQQZvM6I,7010
44
+ q3dviewer-1.1.3.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
45
+ q3dviewer-1.1.3.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
46
+ q3dviewer-1.1.3.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
47
+ q3dviewer-1.1.3.dist-info/RECORD,,