q3dviewer 1.1.2__py3-none-any.whl → 1.1.4__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
@@ -61,18 +61,28 @@ class CloudIOItem(CloudItem):
61
61
  def load(self, file, append=False):
62
62
  # print("Try to load %s ..." % file)
63
63
  if file.endswith(".pcd"):
64
- cloud, color_mode = load_pcd(file)
64
+ cloud = load_pcd(file)
65
65
  elif file.endswith(".ply"):
66
- cloud, color_mode = load_ply(file)
66
+ cloud = load_ply(file)
67
67
  elif file.endswith(".e57"):
68
- cloud, color_mode = load_e57(file)
68
+ cloud = load_e57(file)
69
69
  elif file.endswith(".las"):
70
- cloud, color_mode = load_las(file)
70
+ cloud = load_las(file)
71
71
  else:
72
72
  print("Not supported file type.")
73
73
  return
74
74
  self.set_data(data=cloud, append=append)
75
- self.set_color_mode(color_mode)
75
+
76
+ has_intensity = cloud['irgb'][0] & 0xff000000 > 0
77
+ has_rgb = cloud['irgb'][0] & 0x00ffffff > 0
78
+
79
+ if has_rgb:
80
+ self.set_color_mode('RGB')
81
+ elif has_intensity:
82
+ self.set_color_mode('I')
83
+ else:
84
+ self.set_color_mode('FLAT')
85
+
76
86
  return cloud
77
87
 
78
88
  def set_path(self, path):
@@ -14,15 +14,13 @@ from PySide6.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox, \
14
14
  from OpenGL.GL import shaders
15
15
  from q3dviewer.utils import *
16
16
  from q3dviewer.utils.range_slider import RangeSlider
17
- from PySide6.QtCore import QRegularExpression
18
- from PySide6.QtGui import QRegularExpressionValidator
19
17
 
20
18
 
21
19
  # draw points with color (x, y, z, color)
22
20
  class CloudItem(BaseItem):
23
21
  def __init__(self, size, alpha,
24
22
  color_mode='I',
25
- color='#ffffff',
23
+ color='white',
26
24
  point_type='PIXEL',
27
25
  depth_test=False):
28
26
  super().__init__()
@@ -34,7 +32,12 @@ class CloudItem(BaseItem):
34
32
  self.point_type = point_type
35
33
  self.mutex = threading.Lock()
36
34
  self.data_type = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
37
- self.flat_rgb = int(color[1:], 16)
35
+ self.color = color
36
+ try:
37
+ self.flat_rgb = text_to_rgba(color, flat=True)
38
+ except ValueError:
39
+ print(f"Invalid color: {color}, please use matplotlib color format")
40
+ exit(1)
38
41
  self.mode_table = {'FLAT': 0, 'I': 1, 'RGB': 2}
39
42
  self.point_type_table = {'PIXEL': 0, 'SQUARE': 1, 'SPHERE': 2}
40
43
  self.color_mode = self.mode_table[color_mode]
@@ -89,14 +92,12 @@ class CloudItem(BaseItem):
89
92
  layout.addWidget(self.combo_color)
90
93
 
91
94
  label_rgb = QLabel("Color:")
95
+ label_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
92
96
  layout.addWidget(label_rgb)
93
97
  self.edit_rgb = QLineEdit()
94
- self.edit_rgb.setToolTip("Hex number, i.e. #FF4500")
95
- self.edit_rgb.setText(f"#{self.flat_rgb:06x}")
98
+ self.edit_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
99
+ self.edit_rgb.setText(self.color)
96
100
  self.edit_rgb.textChanged.connect(self._on_color)
97
- regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
98
- validator = QRegularExpressionValidator(regex)
99
- self.edit_rgb.setValidator(validator)
100
101
  layout.addWidget(self.edit_rgb)
101
102
 
102
103
  self.slider_v = RangeSlider()
@@ -108,6 +109,7 @@ class CloudItem(BaseItem):
108
109
  "Show front points first (Depth Test)")
109
110
  self.checkbox_depth_test.setChecked(self.depth_test)
110
111
  self.checkbox_depth_test.stateChanged.connect(self.set_depthtest)
112
+ self._on_color_mode(self.color_mode)
111
113
  layout.addWidget(self.checkbox_depth_test)
112
114
 
113
115
  def _on_range(self, lower, upper):
@@ -164,11 +166,10 @@ class CloudItem(BaseItem):
164
166
 
165
167
  def _on_color(self, color):
166
168
  try:
167
- flat_rgb = int(color[1:], 16)
168
- self.flat_rgb = flat_rgb
169
+ self.flat_rgb = text_to_rgba(color, flat=True)
169
170
  self.need_update_setting = True
170
171
  except ValueError:
171
- pass
172
+ print(f"Invalid color: {color}, please use matplotlib color format")
172
173
 
173
174
  def set_size(self, size):
174
175
  self.size = size
@@ -162,13 +162,9 @@ class FrameItem(BaseItem):
162
162
  self.need_updating = False
163
163
 
164
164
  def set_color(self, color):
165
- if isinstance(color, str):
166
- self.rgba = hex_to_rgba(color)
167
- elif isinstance(color, list):
168
- self.rgba = color
169
- elif isinstance(color, tuple):
170
- self.rgba = list(color)
171
- else:
165
+ try:
166
+ self.rgba = text_to_rgba(color)
167
+ except ValueError:
172
168
  raise ValueError("Invalid color format")
173
169
 
174
170
  def set_line_width(self, width):
@@ -6,10 +6,8 @@ Distributed under MIT license. See LICENSE for more information.
6
6
  from q3dviewer.base_item import BaseItem
7
7
  from OpenGL.GL import *
8
8
  from PySide6.QtWidgets import QLabel, QDoubleSpinBox, QLineEdit
9
- from PySide6.QtCore import QRegularExpression
10
- from PySide6.QtGui import QRegularExpressionValidator
11
9
  import numpy as np
12
- from q3dviewer.utils.maths import hex_to_rgba
10
+ from q3dviewer.utils.maths import text_to_rgba
13
11
 
14
12
 
15
13
  class GridItem(BaseItem):
@@ -24,9 +22,9 @@ class GridItem(BaseItem):
24
22
 
25
23
  def set_color(self, color):
26
24
  try:
27
- self.rgba = hex_to_rgba(color)
25
+ self.rgba = text_to_rgba(color)
28
26
  except ValueError:
29
- pass
27
+ raise ValueError("Invalid color format. Use hex format like '#RRGGBB' or '#RRGGBBAA'.")
30
28
 
31
29
  def generate_grid_vertices(self):
32
30
  vertices = []
@@ -41,16 +39,13 @@ class GridItem(BaseItem):
41
39
  def initialize_gl(self):
42
40
  self.vao = glGenVertexArrays(1)
43
41
  vbo = glGenBuffers(1)
44
-
45
42
  glBindVertexArray(self.vao)
46
-
47
43
  glBindBuffer(GL_ARRAY_BUFFER, vbo)
48
44
  glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)
49
-
50
45
  glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, ctypes.c_void_p(0))
51
46
  glEnableVertexAttribArray(0)
52
-
53
47
  glBindVertexArray(0)
48
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
54
49
 
55
50
  def add_setting(self, layout):
56
51
  spinbox_size = QDoubleSpinBox()
@@ -73,6 +68,7 @@ class GridItem(BaseItem):
73
68
  spinbox_offset_x.setPrefix("Offset X: ")
74
69
  spinbox_offset_x.setSingleStep(0.1)
75
70
  spinbox_offset_x.setValue(self.offset[0])
71
+ spinbox_offset_x.setRange(-1000, 1000)
76
72
  spinbox_offset_x.valueChanged.connect(self._on_offset_x)
77
73
  layout.addWidget(spinbox_offset_x)
78
74
 
@@ -80,6 +76,7 @@ class GridItem(BaseItem):
80
76
  spinbox_offset_y.setPrefix("Offset Y: ")
81
77
  spinbox_offset_y.setSingleStep(0.1)
82
78
  spinbox_offset_y.setValue(self.offset[1])
79
+ spinbox_offset_y.setRange(-1000, 1000)
83
80
  spinbox_offset_y.valueChanged.connect(self._on_offset_y)
84
81
  layout.addWidget(spinbox_offset_y)
85
82
 
@@ -87,6 +84,7 @@ class GridItem(BaseItem):
87
84
  spinbox_offset_z.setPrefix("Offset Z: ")
88
85
  spinbox_offset_z.setSingleStep(0.1)
89
86
  spinbox_offset_z.setValue(self.offset[2])
87
+ spinbox_offset_z.setRange(-1000, 1000)
90
88
  spinbox_offset_z.valueChanged.connect(self._on_offset_z)
91
89
  layout.addWidget(spinbox_offset_z)
92
90
 
@@ -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
@@ -8,13 +8,16 @@ from OpenGL.GL import *
8
8
  import numpy as np
9
9
  import threading
10
10
  from PySide6.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox
11
- from PySide6.QtCore import QRegularExpression
12
- from PySide6.QtGui import QRegularExpressionValidator
13
- from q3dviewer.utils.maths import hex_to_rgba
11
+ from q3dviewer.utils.maths import text_to_rgba
14
12
 
15
13
 
16
14
  class LineItem(BaseItem):
17
15
  def __init__(self, width=1, color='#00ff00', line_type='LINE_STRIP'):
16
+ """
17
+ line_type: 'LINE_STRIP' or 'LINES'
18
+ LINE_STRIP: draw a connected line strip
19
+ LINES: draw a series of unconnected lines
20
+ """
18
21
  super(LineItem, self).__init__()
19
22
  self.width = width
20
23
  self.buff = np.empty((0, 3), np.float32)
@@ -23,19 +26,20 @@ class LineItem(BaseItem):
23
26
  self.capacity = 100000
24
27
  self.valid_buff_top = 0
25
28
  self.color = color
26
- self.rgb = hex_to_rgba(color)
29
+ try:
30
+ self.rgb = text_to_rgba(color)
31
+ except ValueError:
32
+ raise ValueError("Invalid color format. Use mathplotlib color format.")
33
+
27
34
  self.line_type = GL_LINE_STRIP if line_type == 'LINE_STRIP' else GL_LINES
28
35
 
29
36
  def add_setting(self, layout):
30
37
  label_color = QLabel("Color:")
31
38
  layout.addWidget(label_color)
32
39
  self.color_edit = QLineEdit()
33
- self.color_edit.setToolTip("Hex number, i.e. #FF4500")
40
+ self.color_edit.setToolTip("mathplotlib color format")
34
41
  self.color_edit.setText(self.color)
35
42
  self.color_edit.textChanged.connect(self._on_color)
36
- regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
37
- validator = QRegularExpressionValidator(regex)
38
- self.color_edit.setValidator(validator)
39
43
  layout.addWidget(self.color_edit)
40
44
 
41
45
  spinbox_width = QDoubleSpinBox()
@@ -48,10 +52,10 @@ class LineItem(BaseItem):
48
52
 
49
53
  def _on_color(self, color):
50
54
  try:
51
- self.rgb = hex_to_rgba(color)
55
+ self.rgb = text_to_rgba(color)
52
56
  self.color = color
53
57
  except ValueError:
54
- pass
58
+ print("Invalid color format. Use mathplotlib color format.")
55
59
 
56
60
  def set_color(self, color):
57
61
  self.color_edit.setText(color)
@@ -6,7 +6,7 @@ Distributed under MIT license. See LICENSE for more information.
6
6
  from PySide6 import QtCore, QtGui
7
7
  from q3dviewer.base_item import BaseItem
8
8
  from OpenGL.GL import *
9
- from q3dviewer.utils.maths import hex_to_rgba
9
+ from q3dviewer.utils.maths import text_to_rgba
10
10
 
11
11
 
12
12
  class Text2DItem(BaseItem):
@@ -16,8 +16,10 @@ class Text2DItem(BaseItem):
16
16
  """All keyword arguments are passed to set_data()"""
17
17
  BaseItem.__init__(self)
18
18
  self.pos = (20, 50)
19
- self.color = '#ffffff'
20
- self.rgb = hex_to_rgba(self.color)
19
+ try:
20
+ self.rgb = text_to_rgba(self.color)
21
+ except ValueError:
22
+ raise ValueError("Invalid color format. Use mathplotlib color format.")
21
23
  self.text = ''
22
24
  self.font = QtGui.QFont('Helvetica', 16)
23
25
  self.set_data(**kwds)
@@ -44,10 +46,10 @@ class Text2DItem(BaseItem):
44
46
 
45
47
  def set_color(self, color):
46
48
  try:
47
- self.rgb = hex_to_rgba(color)
49
+ self.rgb = text_to_rgba(color)
48
50
  self.color = color
49
51
  except ValueError:
50
- pass
52
+ print("Invalid color format. Use mathplotlib color format.")
51
53
 
52
54
  def paint(self):
53
55
  if len(self.text) < 1:
q3dviewer/glwidget.py CHANGED
@@ -5,11 +5,11 @@ Distributed under MIT license. See LICENSE for more information.
5
5
 
6
6
  from PySide6 import QtCore
7
7
  from PySide6.QtWidgets import QWidget, QComboBox, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QCheckBox, QGroupBox
8
- from PySide6.QtGui import QKeyEvent, QVector3D, QRegularExpressionValidator
9
- from PySide6.QtCore import QRegularExpression
8
+ from PySide6.QtGui import QKeyEvent, QVector3D
10
9
  from OpenGL.GL import *
11
10
  import numpy as np
12
11
  from q3dviewer.base_glwidget import BaseGLWidget
12
+ from q3dviewer.utils.maths import text_to_rgba
13
13
 
14
14
  class SettingWindow(QWidget):
15
15
  def __init__(self):
@@ -52,7 +52,7 @@ class GLWidget(BaseGLWidget):
52
52
  def __init__(self):
53
53
  self.followed_name = 'none'
54
54
  self.named_items = {}
55
- self.color_str = '#000000'
55
+ self.color_str = 'black'
56
56
  self.followable_item_name = None
57
57
  self.setting_window = SettingWindow()
58
58
  self.enable_show_center = True
@@ -80,9 +80,6 @@ class GLWidget(BaseGLWidget):
80
80
  color_edit.setToolTip("'using hex color, i.e. #FF4500")
81
81
  color_edit.setText(self.color_str)
82
82
  color_edit.textChanged.connect(self.set_bg_color)
83
- regex = QRegularExpression(r"^#[0-9A-Fa-f]{6}$")
84
- validator = QRegularExpressionValidator(regex)
85
- color_edit.setValidator(validator)
86
83
  layout.addWidget(color_edit)
87
84
 
88
85
  label_focus = QLabel("Set Focus:")
@@ -108,16 +105,13 @@ class GLWidget(BaseGLWidget):
108
105
  if item.__class__.__name__ == 'AxisItem' and not item._disable_setting:
109
106
  self.followable_item_name.append(name)
110
107
 
111
- def set_bg_color(self, color_str):
108
+ def set_bg_color(self, color):
112
109
  try:
113
- color_flat = int(color_str[1:], 16)
114
- red = (color_flat >> 16) & 0xFF
115
- green = (color_flat >> 8) & 0xFF
116
- blue = color_flat & 0xFF
117
- self.color_str = color_str
118
- self.set_color([red, green, blue, 0])
110
+ self.color_str = color
111
+ red, green, blue, alpha = text_to_rgba(color)
112
+ self.set_color([red, green, blue, alpha])
119
113
  except ValueError:
120
- return
114
+ print("Invalid color format. Use mathplotlib color format.")
121
115
 
122
116
  def add_item_with_name(self, name, item):
123
117
  self.named_items.update({name: item})
@@ -151,7 +151,6 @@ class CMMViewer(q3d.Viewer):
151
151
  main_layout.addWidget(dock_widget)
152
152
  self.dock = dock_widget
153
153
 
154
-
155
154
  def update_video_path(self, path):
156
155
  self.video_path = path
157
156
 
@@ -235,7 +234,6 @@ class CMMViewer(q3d.Viewer):
235
234
  self.glwidget.set_cam_position(center=center,
236
235
  euler=euler)
237
236
 
238
-
239
237
  def create_frames(self):
240
238
  """
241
239
  Create the frames for playback by interpolating between key frames.
@@ -24,17 +24,14 @@ def load_ply(file):
24
24
  xyz = mesh.points
25
25
  rgb = np.zeros([xyz.shape[0]], dtype=np.uint32)
26
26
  intensity = np.zeros([xyz.shape[0]], dtype=np.uint32)
27
- color_mode = 'FLAT'
28
27
  if "intensity" in mesh.point_data:
29
28
  intensity = mesh.point_data["intensity"]
30
- color_mode = 'I'
31
29
  if "rgb" in mesh.point_data:
32
30
  rgb = mesh.point_data["rgb"]
33
- color_mode = 'RGB'
34
31
  irgb = (intensity << 24) | rgb
35
32
  dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
36
33
  cloud = np.rec.fromarrays([xyz, irgb], dtype=dtype)
37
- return cloud, color_mode
34
+ return cloud
38
35
 
39
36
 
40
37
  def save_pcd(cloud, save_path):
@@ -51,8 +48,35 @@ def save_pcd(cloud, save_path):
51
48
  i = (cloud['irgb'] & 0xFF000000) >> 24
52
49
  rgb = cloud['irgb'] & 0x00FFFFFF
53
50
 
54
- dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4'), ('rgb', '<u4')]
55
- tmp = np.rec.fromarrays([cloud['xyz'], i, rgb], dtype=dtype)
51
+ # check no rgb value
52
+ if np.max(rgb) == 0:
53
+ fields = ('x', 'y', 'z', 'intensity')
54
+ metadata = MetaData.model_validate(
55
+ {
56
+ "fields": fields,
57
+ "size": [4, 4, 4, 4],
58
+ "type": ['F', 'F', 'F', 'U'],
59
+ "count": [1, 1, 1, 1],
60
+ "width": cloud.shape[0],
61
+ "points": cloud.shape[0],
62
+ })
63
+
64
+ dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4')]
65
+ tmp = np.rec.fromarrays([cloud['xyz'], i], dtype=dtype)
66
+ PointCloud(metadata, tmp).save(save_path)
67
+ else:
68
+ fields = ('x', 'y', 'z', 'intensity', 'rgb')
69
+ metadata = MetaData.model_validate(
70
+ {
71
+ "fields": fields,
72
+ "size": [4, 4, 4, 4, 4],
73
+ "type": ['F', 'F', 'F', 'U', 'U'],
74
+ "count": [1, 1, 1, 1, 1],
75
+ "width": cloud.shape[0],
76
+ "points": cloud.shape[0],
77
+ })
78
+ dtype = [('xyz', '<f4', (3,)), ('intensity', '<u4'), ('rgb', '<u4')]
79
+ tmp = np.rec.fromarrays([cloud['xyz'], i, rgb], dtype=dtype)
56
80
 
57
81
  PointCloud(metadata, tmp).save(save_path)
58
82
 
@@ -62,17 +86,14 @@ def load_pcd(file):
62
86
  pc = PointCloud.from_path(file).pc_data
63
87
  rgb = np.zeros([pc.shape[0]], dtype=np.uint32)
64
88
  intensity = np.zeros([pc.shape[0]], dtype=np.uint32)
65
- color_mode = 'FLAT'
66
89
  if 'intensity' in pc.dtype.names:
67
90
  intensity = pc['intensity'].astype(np.uint32)
68
- color_mode = 'I'
69
91
  if 'rgb' in pc.dtype.names:
70
92
  rgb = pc['rgb'].astype(np.uint32)
71
- color_mode = 'RGB'
72
93
  irgb = (intensity << 24) | rgb
73
94
  xyz = np.stack([pc['x'], pc['y'], pc['z']], axis=1)
74
95
  cloud = np.rec.fromarrays([xyz, irgb], dtype=dtype)
75
- return cloud, color_mode
96
+ return cloud
76
97
 
77
98
 
78
99
  def save_e57(cloud, save_path):
@@ -100,23 +121,20 @@ def load_e57(file_path):
100
121
  z = scans["cartesianZ"]
101
122
  rgb = np.zeros([x.shape[0]], dtype=np.uint32)
102
123
  intensity = np.zeros([x.shape[0]], dtype=np.uint32)
103
- color_mode = 'FLAT'
104
124
  if "intensity" in scans:
105
125
  intensity = scans["intensity"].astype(np.uint32)
106
- color_mode = 'I'
107
126
  if all([x in scans for x in ["colorRed", "colorGreen", "colorBlue"]]):
108
127
  r = scans["colorRed"].astype(np.uint32)
109
128
  g = scans["colorGreen"].astype(np.uint32)
110
129
  b = scans["colorBlue"].astype(np.uint32)
111
130
  rgb = (r << 16) | (g << 8) | b
112
- color_mode = 'RGB'
113
131
  irgb = (intensity << 24) | rgb
114
132
  dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
115
133
  cloud = np.rec.fromarrays(
116
134
  [np.stack([x, y, z], axis=1), irgb],
117
135
  dtype=dtype)
118
136
  e57.close()
119
- return cloud, color_mode
137
+ return cloud
120
138
 
121
139
 
122
140
  def load_las(file):
@@ -124,27 +142,24 @@ def load_las(file):
124
142
  las = f.read()
125
143
  xyz = np.vstack((las.x, las.y, las.z)).transpose()
126
144
  dimensions = list(las.point_format.dimension_names)
127
- color_mode = 'FLAT'
128
145
  rgb = np.zeros([las.x.shape[0]], dtype=np.uint32)
129
146
  intensity = np.zeros([las.x.shape[0]], dtype=np.uint32)
130
147
  if 'intensity' in dimensions:
131
148
  intensity = las.intensity.astype(np.uint32)
132
- color_mode = 'I'
133
149
  if 'red' in dimensions and 'green' in dimensions and 'blue' in dimensions:
134
150
  red = las.red
135
151
  green = las.green
136
152
  blue = las.blue
137
153
  max_val = np.max([red, green, blue])
138
154
  if red.dtype == np.dtype('uint16') and max_val > 255:
139
- red = (red / 65535.0 * 255).astype(np.uint32)
140
- green = (green / 65535.0 * 255).astype(np.uint32)
141
- blue = (blue / 65535.0 * 255).astype(np.uint32)
155
+ red = (red / 255).astype(np.uint32)
156
+ green = (green / 255).astype(np.uint32)
157
+ blue = (blue / 255).astype(np.uint32)
142
158
  rgb = (red << 16) | (green << 8) | blue
143
- color_mode = 'RGB'
144
- color = (intensity << 24) | rgb
159
+ color = ((intensity / 255)<< 24) | rgb
145
160
  dtype = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
146
161
  cloud = np.rec.fromarrays([xyz, color], dtype=dtype)
147
- return cloud, color_mode
162
+ return cloud
148
163
 
149
164
  def save_las(cloud, save_path):
150
165
  header = laspy.LasHeader(point_format=3, version="1.2")
@@ -152,10 +167,10 @@ def save_las(cloud, save_path):
152
167
  las.x = cloud['xyz'][:, 0]
153
168
  las.y = cloud['xyz'][:, 1]
154
169
  las.z = cloud['xyz'][:, 2]
155
- las.red = (cloud['irgb'] >> 16) & 0xFF
156
- las.green = (cloud['irgb'] >> 8) & 0xFF
157
- las.blue = cloud['irgb'] & 0xFF
158
- las.intensity = cloud['irgb'] >> 24
170
+ las.red = ((cloud['irgb'] >> 16) & 0xFF) * 255
171
+ las.green = ((cloud['irgb'] >> 8) & 0xFF) * 255
172
+ las.blue = (cloud['irgb'] & 0xFF)
173
+ las.intensity = (cloud['irgb'] >> 24) * 255
159
174
  las.write(save_path)
160
175
 
161
176
 
q3dviewer/utils/maths.py CHANGED
@@ -10,6 +10,7 @@ https://github.com/scomup/MathematicalRobotics.git
10
10
 
11
11
 
12
12
  import numpy as np
13
+ from matplotlib.colors import to_rgba
13
14
 
14
15
 
15
16
  _epsilon_ = 1e-5
@@ -292,26 +293,27 @@ def makeRt(T):
292
293
  t = T[0:3, 3]
293
294
  return R, t
294
295
 
295
- def hex_to_rgba(hex_color):
296
- if not hex_color.startswith("#"):
297
- print("Invalid hex color string.")
298
- return (1.0, 1.0, 1.0, 1.0)
299
- if len(hex_color) == 7:
300
- color_flat = int(hex_color[1:], 16)
301
- red = (color_flat >> 16) & 0xFF
302
- green = (color_flat >> 8) & 0xFF
303
- blue = color_flat & 0xFF
304
- return (red / 255.0, green / 255.0, blue / 255.0, 1.0)
305
- elif len(hex_color) == 9:
306
- color_flat = int(hex_color[1:], 16)
307
- red = (color_flat >> 24) & 0xFF
308
- green = (color_flat >> 16) & 0xFF
309
- blue = (color_flat >> 8) & 0xFF
310
- alpha = color_flat & 0xFF
311
- return (red / 255.0, green / 255.0, blue / 255.0, alpha / 255.0)
296
+
297
+ def text_to_rgba(color_text, flat=False):
298
+ """
299
+ Convert a color text to an RGBA tuple.
300
+
301
+ :param color_text: e.g. '#FF0000', '#FF0000FF', 'red', 'green', 'blue', 'yellow',
302
+ 'black', 'white', 'magenta', 'cyan', 'r', 'g', 'b', 'y', 'k', 'w', 'm', 'c'
303
+ :return: RGBA tuple, e.g. (1.0, 0.0, 0.0, 1.0)
304
+ """
305
+ rgba = to_rgba(color_text)
306
+ if flat:
307
+ r, g, b, _ = (np.array(rgba)*255).astype(np.uint32)
308
+ falt_rgb = ((r << 16) & 0xFF0000) | \
309
+ ((g << 8) & 0x00FF00) | \
310
+ ((b << 0) & 0x0000FF)
311
+ return falt_rgb
312
312
  else:
313
- print("Invalid hex color string.")
314
- return (1.0, 1.0, 1.0, 1.0)
313
+ return rgba
314
+ # except ValueError as e:
315
+ # raise ValueError(f"Invalid color text '{color_text}': {e}")
316
+
315
317
 
316
318
  # euler = np.array([1, 0.1, 0.1])
317
319
  # euler_angles = matrix_to_euler(euler_to_matrix(euler))
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: q3dviewer
3
- Version: 1.1.2
3
+ Version: 1.1.4
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
@@ -14,17 +14,25 @@ Requires-Dist: PyOpenGL
14
14
  Requires-Dist: imageio
15
15
  Requires-Dist: imageio[ffmpeg]
16
16
  Requires-Dist: laspy
17
+ Requires-Dist: matplotlib
17
18
  Requires-Dist: meshio
18
19
  Requires-Dist: numpy
19
- Requires-Dist: pillow
20
20
  Requires-Dist: pye57
21
21
  Requires-Dist: pypcd4
22
22
  Requires-Dist: pyside6
23
23
 
24
- ## q3dviewer
24
+
25
+ ![q3dviewer Logo](imgs/logo.png)
26
+
27
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
28
+ [![PyPI version](https://badge.fury.io/py/q3dviewer.svg)](https://badge.fury.io/py/q3dviewer)
25
29
 
26
30
  `q3dviewer` is a library designed for quickly deploying a 3D viewer. It is based on Qt (PySide6) and provides efficient OpenGL items for displaying 3D objects (e.g., point clouds, cameras, and 3D Gaussians). You can use it to visualize your 3D data or set up an efficient viewer application. It is inspired by PyQtGraph but focuses more on efficient 3D rendering.
27
31
 
32
+
33
+ To show how to use `q3dviewer` as a library, we also provide some [very useful tools](#tools).
34
+
35
+
28
36
  ## Installation
29
37
 
30
38
  To install `q3dviewer`, execute the following command in your terminal on either Linux or Windows:
@@ -53,10 +61,10 @@ Once installed, you can directly use the following tools:
53
61
 
54
62
  ### 1. Cloud Viewer
55
63
 
56
- A tool for visualizing point cloud files. Launch it by executing the following command in your terminal:
64
+ A tool for visualizing point cloud files (LAS, PCD, PLY, E57). Launch it by executing the following command in your terminal:
57
65
 
58
66
  ```sh
59
- cloud_viewer # The viewer will be displayed
67
+ cloud_viewer
60
68
  ```
61
69
 
62
70
  *Alternatively*, if the path is not set (though it's not recommended):
@@ -65,7 +73,13 @@ cloud_viewer # The viewer will be displayed
65
73
  python3 -m q3dviewer.tools.cloud_viewer
66
74
  ```
67
75
 
68
- After the viewer launches, you can drag and drop files onto the window to display the point clouds. Multiple files can be dropped simultaneously to view them together. Supported formats include LAS, PCD, PLY, and E57.
76
+ **Basic Operations**
77
+ * Load files: Drag and drop point cloud files onto the window (multiple files are OK).
78
+ * `M` key: Display the visualization settings screen for point clouds, background color, etc.
79
+ * `Left mouse button` & `W, A, S, D` keys: Move the viewpoint on the horizontal plane.
80
+ * `Z, X` keys: Move in the direction the screen is facing.
81
+ * `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the screen center unchanged.
82
+ * `Shift` + `Right mouse button` & `Arrow` keys: Rotate the viewpoint while keeping the camera position unchanged.
69
83
 
70
84
  For example, you can download and view point clouds of Tokyo in LAS format from the following link:
71
85
 
@@ -91,15 +105,21 @@ ros_viewer
91
105
  Would you like to create a video from point cloud data? With Film Maker, you can easily create videos with simple operations. Just edit keyframes using the user-friendly GUI, and the software will automatically interpolate the keyframes to generate the video.
92
106
 
93
107
  ```sh
94
- film_maker # drag and drop your cloud file to the window
108
+ film_maker
95
109
  ```
96
110
 
111
+ **Basic Operations**
112
+ * File loading & viewpoint movement: Same as Cloud_Viewer
97
113
  * Space key to add a keyframe.
98
114
  * Delete key to remove a keyframe.
115
+ * Play button: Automatically play the video (pressing again will stop playback)
116
+ * Record checkbox: When checked, actions will be automatically recorded during playback
99
117
 
100
118
  Film Maker GUI:
101
119
 
102
- ![Screenshot from 2025-02-02 18-20-51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/149168/a1a6ad63-237c-482e-439d-e760223c59ca.png)
120
+ ![film_maker_demo.gif](imgs/film_maker_demo.gif)
121
+
122
+ The demo video demonstrating how to use Film Maker utilizes the [cloud data of Kyobashi Station Area](https://www.geospatial.jp/ckan/dataset/kyoubasiekisyuuhen_las) located in Osaka, Japan.
103
123
 
104
124
  ### 4. Gaussian Viewer
105
125
 
@@ -1,23 +1,23 @@
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
6
6
  q3dviewer/gau_io.py,sha256=S6NmqL5vSMgHVxKR-eu4CWCqgZeWBJKYRoOMAwr8Xbo,5890
7
- q3dviewer/glwidget.py,sha256=im8hjVYEL0Zl7fOIHTQMJdWu7WNOHlvTdIDYjebz9WA,4940
7
+ q3dviewer/glwidget.py,sha256=2scii9IeY3ion12lFgWtGXO9JrlUD3tfp3zXt7gnFoQ,4689
8
8
  q3dviewer/utils.py,sha256=evF0d-v17hbTmquC24fmMIp9CsXpUnSQZr4MVy2sfao,2426
9
9
  q3dviewer/viewer.py,sha256=LH1INLFhi6pRjzazzQJ0AWT4hgyXI6GnmqoJFUwUZVE,2517
10
10
  q3dviewer/custom_items/__init__.py,sha256=gOiAxdjDaAnFL8YbqSEWWWOwUrJfvzP9JLR34sCB9-4,434
11
11
  q3dviewer/custom_items/axis_item.py,sha256=PTBSf5DmQI8ieSinYjY_aC7P8q1nzE-2Vc2GNd1O3Os,2568
12
12
  q3dviewer/custom_items/camera_frame_item.py,sha256=VBsr3Avly_YWXViIh4DJkGc_HJt227GeOYLpGtbYTOw,5605
13
- q3dviewer/custom_items/cloud_io_item.py,sha256=gjK3n9WKB7JwxC93ijkweEHA5EezpgNJ8KO-PBaDKCs,2835
14
- q3dviewer/custom_items/cloud_item.py,sha256=s46TxNDw7E6DW3V-ek_PF7egB3kRbnqpOBNUKdaC8IY,12620
15
- q3dviewer/custom_items/frame_item.py,sha256=6BOM3MXi-Akv6KUXDC3QYEAXKxwk0Eo1KQn-F7jkqrQ,7559
13
+ q3dviewer/custom_items/cloud_io_item.py,sha256=l7FGKb1s3kEFw1GT8Bro5oJfu4reuvunHJkM1pmpxz4,3038
14
+ q3dviewer/custom_items/cloud_item.py,sha256=8VLjOr2hzq9dWW_rArM8TftShqLBAVOUlD8iNpCK8es,12739
15
+ q3dviewer/custom_items/frame_item.py,sha256=9g4SxIGZCWFWaX5Xfd9G-P63VlonysxEC239lk7dRRg,7408
16
16
  q3dviewer/custom_items/gaussian_item.py,sha256=CZoXMmj2JPFfMqu7v4q4dLUI2cg_WfE1DHWGXjEYn6M,9869
17
- q3dviewer/custom_items/grid_item.py,sha256=20n4TGm5YEaudhnEOCOk-HtsKwxVxaPr8YV36kO04yU,4802
18
- q3dviewer/custom_items/image_item.py,sha256=ctNR81fVgxkdl2n3U_TPFL6yn086UhNI_A9fFHgkc-4,5491
19
- q3dviewer/custom_items/line_item.py,sha256=u0oFN2iHzsRHtnbvyvC_iglEkCwEU2NnTuE536sKUAE,4348
20
- q3dviewer/custom_items/text_item.py,sha256=nuHMVMQrwy50lNk9hxB94criFxbJJK-SYiK2fSXWUMQ,2158
17
+ q3dviewer/custom_items/grid_item.py,sha256=58QUKli_9NEtYchulZt5Xhqk6k2DaL5RlEMaYyt7MSE,4965
18
+ q3dviewer/custom_items/image_item.py,sha256=3FYAVlNLEILKZplkt2wbL8y16ke124GmwxcSmWmJY8Q,5357
19
+ q3dviewer/custom_items/line_item.py,sha256=np8sORS5kZFWptOUa_Vlq1p7b9rMK3CpGsBd77Qpo2M,4445
20
+ q3dviewer/custom_items/text_item.py,sha256=G3PPpN_0yjUMWJKvNG5QFgqxmE6LUEW5VIUDdi3kwuk,2314
21
21
  q3dviewer/custom_items/trajectory_item.py,sha256=uoKQSrTs_m_m1M8iNAm3peiXnZ9uVPsYQLYas3Gksjg,2754
22
22
  q3dviewer/shaders/cloud_frag.glsl,sha256=tbCsDUp9YlPe0hRWlFS724SH6TtMeLO-GVYROzEElZg,609
23
23
  q3dviewer/shaders/cloud_vert.glsl,sha256=Vxgw-Zrr0knAK0z4qMXKML6IC4EbffKMwYN2TMXROoI,2117
@@ -28,20 +28,20 @@ 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=KXl0H03vnS7eAFw7nSRvTG3J0Y7gYt6YFUYaoxJf3xM,16600
31
+ q3dviewer/tools/film_maker.py,sha256=w97LMMmSpjTOMdG2j0ucK2kDoU3k__746jRVh2fJfvc,16598
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
35
35
  q3dviewer/tools/ros_viewer.py,sha256=ARB3I5wohY3maP8dCu0O0hxObd6JFKuK2y7AApVgMWA,2551
36
36
  q3dviewer/utils/__init__.py,sha256=irm8Z_bT8l9kzhoMlds2Dal8g4iw4vjmqNPZSs4W6e0,157
37
- q3dviewer/utils/cloud_io.py,sha256=ttD8FJExdDhXB1Z0Ej9S939i8gcq4JfyqwLXVh8CEFw,11094
37
+ q3dviewer/utils/cloud_io.py,sha256=xnsT3VEKS435BE1DwhciRHicwzuC-8-JASkImT8pKNk,11681
38
38
  q3dviewer/utils/convert_ros_msg.py,sha256=sAoQfy3qLQKsIArBAVm8H--wlQXOcmkKK3-Ox9UCcrc,1686
39
39
  q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
40
- q3dviewer/utils/maths.py,sha256=5TmjWUX1K3UjygXxrUsydjbo7tPzu0gD-yy7qtQUGBU,10588
40
+ q3dviewer/utils/maths.py,sha256=tHx2q_qAFPQQoFJbnwB0Ts-xtEibeGAhJrMHOH6aCPk,10575
41
41
  q3dviewer/utils/range_slider.py,sha256=jZJQL-uQgnpgLvtYSWpKTrJlLkt3aqNpaRQAePEpNd0,3174
42
- q3dviewer-1.1.2.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
43
- q3dviewer-1.1.2.dist-info/METADATA,sha256=c8ONBUQAOF1v0UYF_6_w_vZ25fagwH-bL-k0Mj6x71I,7032
44
- q3dviewer-1.1.2.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
45
- q3dviewer-1.1.2.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
46
- q3dviewer-1.1.2.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
47
- q3dviewer-1.1.2.dist-info/RECORD,,
42
+ q3dviewer-1.1.4.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
43
+ q3dviewer-1.1.4.dist-info/METADATA,sha256=yU-owFX8Qufnd9zdyjhp4v3LNKz5vaUHZ8O9e539m74,7984
44
+ q3dviewer-1.1.4.dist-info/WHEEL,sha256=g4nMs7d-Xl9-xC9XovUrsDHGXt-FT0E17Yqo92DEfvY,92
45
+ q3dviewer-1.1.4.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
46
+ q3dviewer-1.1.4.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
47
+ q3dviewer-1.1.4.dist-info/RECORD,,