q3dviewer 1.0.3__py3-none-any.whl → 1.0.5__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.
- q3dviewer/__init__.py +5 -0
- q3dviewer/base_glwidget.py +256 -0
- q3dviewer/base_item.py +57 -0
- q3dviewer/custom_items/__init__.py +9 -0
- q3dviewer/custom_items/axis_item.py +148 -0
- q3dviewer/custom_items/cloud_io_item.py +79 -0
- q3dviewer/custom_items/cloud_item.py +314 -0
- q3dviewer/custom_items/frame_item.py +194 -0
- q3dviewer/custom_items/gaussian_item.py +254 -0
- q3dviewer/custom_items/grid_item.py +88 -0
- q3dviewer/custom_items/image_item.py +172 -0
- q3dviewer/custom_items/line_item.py +120 -0
- q3dviewer/custom_items/text_item.py +63 -0
- q3dviewer/gau_io.py +0 -0
- q3dviewer/glwidget.py +131 -0
- q3dviewer/shaders/cloud_frag.glsl +28 -0
- q3dviewer/shaders/cloud_vert.glsl +72 -0
- q3dviewer/shaders/gau_frag.glsl +42 -0
- q3dviewer/shaders/gau_prep.glsl +249 -0
- q3dviewer/shaders/gau_vert.glsl +77 -0
- q3dviewer/shaders/sort_by_key.glsl +56 -0
- q3dviewer/tools/__init__.py +1 -0
- q3dviewer/tools/cloud_viewer.py +123 -0
- q3dviewer/tools/example_viewer.py +47 -0
- q3dviewer/tools/gaussian_viewer.py +60 -0
- q3dviewer/tools/lidar_calib.py +294 -0
- q3dviewer/tools/lidar_cam_calib.py +314 -0
- q3dviewer/tools/ros_viewer.py +85 -0
- q3dviewer/utils/__init__.py +4 -0
- q3dviewer/utils/cloud_io.py +323 -0
- q3dviewer/utils/convert_ros_msg.py +49 -0
- q3dviewer/utils/gl_helper.py +40 -0
- q3dviewer/utils/maths.py +168 -0
- q3dviewer/utils/range_slider.py +86 -0
- q3dviewer/viewer.py +58 -0
- q3dviewer-1.0.5.dist-info/LICENSE +21 -0
- {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/METADATA +7 -4
- q3dviewer-1.0.5.dist-info/RECORD +41 -0
- q3dviewer-1.0.5.dist-info/top_level.txt +1 -0
- q3dviewer-1.0.3.dist-info/RECORD +0 -5
- q3dviewer-1.0.3.dist-info/top_level.txt +0 -1
- {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/WHEEL +0 -0
- {q3dviewer-1.0.3.dist-info → q3dviewer-1.0.5.dist-info}/entry_points.txt +0 -0
q3dviewer/__init__.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
3
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from OpenGL.GL import *
|
|
7
|
+
from math import radians, tan
|
|
8
|
+
import numpy as np
|
|
9
|
+
from PySide6 import QtCore, QtGui, QtOpenGLWidgets
|
|
10
|
+
from q3dviewer.utils.maths import frustum, euler_to_matrix, makeT
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseGLWidget(QtOpenGLWidgets.QOpenGLWidget):
|
|
14
|
+
def __init__(self, parent=None):
|
|
15
|
+
QtOpenGLWidgets.QOpenGLWidget.__init__(self, parent)
|
|
16
|
+
self.setFocusPolicy(QtCore.Qt.FocusPolicy.ClickFocus)
|
|
17
|
+
self.reset()
|
|
18
|
+
self._fov = 60
|
|
19
|
+
self.items = []
|
|
20
|
+
self.keyTimer = QtCore.QTimer()
|
|
21
|
+
self.color = np.array([0, 0, 0, 0])
|
|
22
|
+
self.dist = 40
|
|
23
|
+
self.euler = np.array([np.pi/3, 0, np.pi/4])
|
|
24
|
+
self.center = np.array([0, 0, 0.])
|
|
25
|
+
self.active_keys = set()
|
|
26
|
+
self.show_center = False
|
|
27
|
+
self.enable_show_center = True
|
|
28
|
+
|
|
29
|
+
def keyPressEvent(self, ev: QtGui.QKeyEvent):
|
|
30
|
+
if ev.key() == QtCore.Qt.Key_Up or \
|
|
31
|
+
ev.key() == QtCore.Qt.Key_Down or \
|
|
32
|
+
ev.key() == QtCore.Qt.Key_Left or \
|
|
33
|
+
ev.key() == QtCore.Qt.Key_Right or \
|
|
34
|
+
ev.key() == QtCore.Qt.Key_Z or \
|
|
35
|
+
ev.key() == QtCore.Qt.Key_X or \
|
|
36
|
+
ev.key() == QtCore.Qt.Key_A or \
|
|
37
|
+
ev.key() == QtCore.Qt.Key_D or \
|
|
38
|
+
ev.key() == QtCore.Qt.Key_W or \
|
|
39
|
+
ev.key() == QtCore.Qt.Key_S:
|
|
40
|
+
self.active_keys.add(ev.key())
|
|
41
|
+
self.active_keys.add(ev.key())
|
|
42
|
+
|
|
43
|
+
def keyReleaseEvent(self, ev: QtGui.QKeyEvent):
|
|
44
|
+
self.active_keys.discard(ev.key())
|
|
45
|
+
|
|
46
|
+
def current_width(self):
|
|
47
|
+
"""
|
|
48
|
+
Return the current width of the widget.
|
|
49
|
+
"""
|
|
50
|
+
return int(self.width() * self.devicePixelRatioF())
|
|
51
|
+
|
|
52
|
+
def current_height(self):
|
|
53
|
+
"""
|
|
54
|
+
Return the current height of the widget.
|
|
55
|
+
"""
|
|
56
|
+
return int(self.height() * self.devicePixelRatioF())
|
|
57
|
+
|
|
58
|
+
def reset(self):
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
def add_item(self, item):
|
|
62
|
+
"""
|
|
63
|
+
Add the item to the glwidget.
|
|
64
|
+
"""
|
|
65
|
+
self.items.append(item)
|
|
66
|
+
item.set_glwidget(self)
|
|
67
|
+
|
|
68
|
+
def remove_item(self, item):
|
|
69
|
+
"""
|
|
70
|
+
Remove the item from the glwidget.
|
|
71
|
+
"""
|
|
72
|
+
self.items.remove(item)
|
|
73
|
+
item.set_glwidget(None)
|
|
74
|
+
|
|
75
|
+
def clear(self):
|
|
76
|
+
"""
|
|
77
|
+
Remove all items from the glwidget.
|
|
78
|
+
"""
|
|
79
|
+
for item in self.items:
|
|
80
|
+
item.set_glwidget(None)
|
|
81
|
+
self.items = []
|
|
82
|
+
|
|
83
|
+
def initializeGL(self):
|
|
84
|
+
"""
|
|
85
|
+
the method is herted from QOpenGLWidget,
|
|
86
|
+
and it is called when the widget is first shown.
|
|
87
|
+
"""
|
|
88
|
+
for item in self.items:
|
|
89
|
+
item.initialize()
|
|
90
|
+
|
|
91
|
+
def mouseReleaseEvent(self, ev):
|
|
92
|
+
if hasattr(self, 'mousePos'):
|
|
93
|
+
delattr(self, 'mousePos')
|
|
94
|
+
|
|
95
|
+
def update_dist(self, delta):
|
|
96
|
+
self.dist += delta
|
|
97
|
+
if self.dist < 0.1:
|
|
98
|
+
self.dist = 0.1
|
|
99
|
+
|
|
100
|
+
def wheelEvent(self, ev):
|
|
101
|
+
delta = ev.angleDelta().x()
|
|
102
|
+
if delta == 0:
|
|
103
|
+
delta = ev.angleDelta().y()
|
|
104
|
+
self.update_dist(-delta * self.dist * 0.001)
|
|
105
|
+
self.show_center = True
|
|
106
|
+
|
|
107
|
+
def mouseMoveEvent(self, ev):
|
|
108
|
+
lpos = ev.localPos()
|
|
109
|
+
if not hasattr(self, 'mousePos'):
|
|
110
|
+
self.mousePos = lpos
|
|
111
|
+
diff = lpos - self.mousePos
|
|
112
|
+
self.mousePos = lpos
|
|
113
|
+
if ev.buttons() == QtCore.Qt.MouseButton.RightButton:
|
|
114
|
+
rot_speed = 0.2
|
|
115
|
+
dyaw = radians(-diff.x() * rot_speed)
|
|
116
|
+
droll = radians(-diff.y() * rot_speed)
|
|
117
|
+
self.rotate(droll, 0, dyaw)
|
|
118
|
+
elif ev.buttons() == QtCore.Qt.MouseButton.LeftButton:
|
|
119
|
+
Rwc = euler_to_matrix(self.euler)
|
|
120
|
+
Kinv = np.linalg.inv(self.get_K())
|
|
121
|
+
dist = max(self.dist, 0.5)
|
|
122
|
+
self.center += Rwc @ Kinv @ np.array([-diff.x(), diff.y(), 0]) * dist
|
|
123
|
+
self.show_center = True
|
|
124
|
+
|
|
125
|
+
def paintGL(self):
|
|
126
|
+
self.update_model_projection()
|
|
127
|
+
self.update_model_view()
|
|
128
|
+
bgcolor = self.color
|
|
129
|
+
glClearColor(*bgcolor)
|
|
130
|
+
glClear(GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT)
|
|
131
|
+
for item in self.items:
|
|
132
|
+
if not item.visible():
|
|
133
|
+
continue
|
|
134
|
+
glMatrixMode(GL_MODELVIEW)
|
|
135
|
+
glPushMatrix()
|
|
136
|
+
glPushAttrib(GL_ALL_ATTRIB_BITS)
|
|
137
|
+
item.paint()
|
|
138
|
+
glPopAttrib()
|
|
139
|
+
glMatrixMode(GL_MODELVIEW)
|
|
140
|
+
glPopMatrix()
|
|
141
|
+
|
|
142
|
+
# Show center as a point if updated by mouse move event
|
|
143
|
+
if self.enable_show_center and self.show_center:
|
|
144
|
+
point_size = np.clip((self.get_K()[0, 0] / self.dist), 10, 100)
|
|
145
|
+
glPointSize(point_size)
|
|
146
|
+
glBegin(GL_POINTS)
|
|
147
|
+
glColor3f(1.0, 0.0, 0.0) # Red color for the center point
|
|
148
|
+
glVertex3f(*self.center)
|
|
149
|
+
glEnd()
|
|
150
|
+
self.show_center = False
|
|
151
|
+
|
|
152
|
+
def update_movement(self):
|
|
153
|
+
"""
|
|
154
|
+
Update the movement of the camera based on the active keys.
|
|
155
|
+
"""
|
|
156
|
+
if not self.active_keys:
|
|
157
|
+
return
|
|
158
|
+
rot_speed = 0.5
|
|
159
|
+
trans_speed = max(self.dist * 0.005, 0.1)
|
|
160
|
+
# Handle rotation keys
|
|
161
|
+
if QtCore.Qt.Key_Up in self.active_keys:
|
|
162
|
+
self.rotate(radians(rot_speed), 0, 0)
|
|
163
|
+
if QtCore.Qt.Key_Down in self.active_keys:
|
|
164
|
+
self.rotate(radians(-rot_speed), 0, 0)
|
|
165
|
+
if QtCore.Qt.Key_Left in self.active_keys:
|
|
166
|
+
self.rotate(0, 0, radians(rot_speed))
|
|
167
|
+
if QtCore.Qt.Key_Right in self.active_keys:
|
|
168
|
+
self.rotate(0, 0, radians(-rot_speed))
|
|
169
|
+
# Handle zoom keys
|
|
170
|
+
xz_keys = {QtCore.Qt.Key_Z, QtCore.Qt.Key_X}
|
|
171
|
+
if self.active_keys & xz_keys:
|
|
172
|
+
Rwc = euler_to_matrix(self.euler)
|
|
173
|
+
if QtCore.Qt.Key_Z in self.active_keys:
|
|
174
|
+
self.center += Rwc @ np.array([0, 0, -trans_speed])
|
|
175
|
+
if QtCore.Qt.Key_X in self.active_keys:
|
|
176
|
+
self.center += Rwc @ np.array([0, 0, trans_speed])
|
|
177
|
+
# Handle translation keys on the z plane
|
|
178
|
+
dir_keys = {QtCore.Qt.Key_W, QtCore.Qt.Key_S, QtCore.Qt.Key_A, QtCore.Qt.Key_D}
|
|
179
|
+
if self.active_keys & dir_keys:
|
|
180
|
+
Rz = euler_to_matrix([0, 0, self.euler[2]])
|
|
181
|
+
if QtCore.Qt.Key_W in self.active_keys:
|
|
182
|
+
self.center += Rz @ np.array([0, trans_speed, 0])
|
|
183
|
+
if QtCore.Qt.Key_S in self.active_keys:
|
|
184
|
+
self.center += Rz @ np.array([0, -trans_speed, 0])
|
|
185
|
+
if QtCore.Qt.Key_A in self.active_keys:
|
|
186
|
+
self.center += Rz @ np.array([-trans_speed, 0, 0])
|
|
187
|
+
if QtCore.Qt.Key_D in self.active_keys:
|
|
188
|
+
self.center += Rz @ np.array([trans_speed, 0, 0])
|
|
189
|
+
|
|
190
|
+
def update_model_view(self):
|
|
191
|
+
m = self.get_view_matrix()
|
|
192
|
+
glMatrixMode(GL_MODELVIEW)
|
|
193
|
+
glLoadMatrixf(m.T)
|
|
194
|
+
|
|
195
|
+
def get_view_matrix(self):
|
|
196
|
+
twc = self.center
|
|
197
|
+
tcw = np.array([0, 0, self.dist])
|
|
198
|
+
Rwc = euler_to_matrix(self.euler)
|
|
199
|
+
twc = twc + Rwc @ tcw
|
|
200
|
+
Twc = makeT(Rwc, twc)
|
|
201
|
+
return np.linalg.inv(Twc)
|
|
202
|
+
|
|
203
|
+
def set_cam_position(self, **kwargs):
|
|
204
|
+
pos = kwargs.get('pos', None)
|
|
205
|
+
distance = kwargs.get('distance', None)
|
|
206
|
+
if pos is not None:
|
|
207
|
+
self.center = pos
|
|
208
|
+
if distance is not None:
|
|
209
|
+
self.dist = distance
|
|
210
|
+
|
|
211
|
+
def set_color(self, color):
|
|
212
|
+
self.color = color
|
|
213
|
+
|
|
214
|
+
def update(self):
|
|
215
|
+
self.update_movement()
|
|
216
|
+
super().update()
|
|
217
|
+
|
|
218
|
+
def update_model_projection(self):
|
|
219
|
+
m = self.get_projection_matrix()
|
|
220
|
+
glMatrixMode(GL_PROJECTION)
|
|
221
|
+
glLoadMatrixf(m.T)
|
|
222
|
+
|
|
223
|
+
def get_projection_matrix(self):
|
|
224
|
+
w, h = self.current_width(), self.current_height()
|
|
225
|
+
dist = self.dist
|
|
226
|
+
near = dist * 0.001
|
|
227
|
+
far = dist * 10000.
|
|
228
|
+
r = near * tan(0.5 * radians(self._fov))
|
|
229
|
+
t = r * h / w
|
|
230
|
+
matrix = frustum(-r, r, -t, t, near, far)
|
|
231
|
+
return matrix
|
|
232
|
+
|
|
233
|
+
def get_K(self):
|
|
234
|
+
project_matrix = self.get_projection_matrix()
|
|
235
|
+
width = self.current_width()
|
|
236
|
+
height = self.current_height()
|
|
237
|
+
fx = project_matrix[0, 0] * width / 2
|
|
238
|
+
fy = project_matrix[1, 1] * height / 2
|
|
239
|
+
cx = width / 2
|
|
240
|
+
cy = height / 2
|
|
241
|
+
K = np.array([
|
|
242
|
+
[fx, 0, cx],
|
|
243
|
+
[0, fy, cy],
|
|
244
|
+
[0, 0, 1]
|
|
245
|
+
])
|
|
246
|
+
return K
|
|
247
|
+
|
|
248
|
+
def rotate(self, rx=0, ry=0, rz=0):
|
|
249
|
+
# update the euler angles
|
|
250
|
+
self.euler += np.array([rx, ry, rz])
|
|
251
|
+
self.euler[2] = (self.euler[2] + np.pi) % (2 * np.pi) - np.pi
|
|
252
|
+
self.euler[1] = (self.euler[1] + np.pi) % (2 * np.pi) - np.pi
|
|
253
|
+
self.euler[0] = np.clip(self.euler[0], 0, np.pi)
|
|
254
|
+
|
|
255
|
+
def change_show_center(self, state):
|
|
256
|
+
self.enable_show_center = state
|
q3dviewer/base_item.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
3
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from PySide6 import QtCore
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
class BaseItem(QtCore.QObject):
|
|
10
|
+
_next_id = 0
|
|
11
|
+
|
|
12
|
+
def __init__(self):
|
|
13
|
+
super().__init__()
|
|
14
|
+
self._id = BaseItem._next_id
|
|
15
|
+
BaseItem._next_id += 1
|
|
16
|
+
self._glwidget = None
|
|
17
|
+
self._visible = True
|
|
18
|
+
self._initialized = False
|
|
19
|
+
|
|
20
|
+
def set_glwidget(self, v):
|
|
21
|
+
self._glwidget = v
|
|
22
|
+
|
|
23
|
+
def glwidget(self):
|
|
24
|
+
return self._glwidget
|
|
25
|
+
|
|
26
|
+
def hide(self):
|
|
27
|
+
self._visible = False
|
|
28
|
+
|
|
29
|
+
def show(self):
|
|
30
|
+
self._visible = True
|
|
31
|
+
|
|
32
|
+
def set_visible(self, vis):
|
|
33
|
+
self._visible = vis
|
|
34
|
+
|
|
35
|
+
def visible(self):
|
|
36
|
+
return self._visible
|
|
37
|
+
|
|
38
|
+
def initialize(self):
|
|
39
|
+
if not self._initialized:
|
|
40
|
+
self.initialize_gl()
|
|
41
|
+
|
|
42
|
+
def initialize_gl(self):
|
|
43
|
+
"""
|
|
44
|
+
Initialize OpenGL resources for the item.
|
|
45
|
+
This method should be overridden by subclasses to set up any necessary OpenGL resources.
|
|
46
|
+
"""
|
|
47
|
+
pass
|
|
48
|
+
|
|
49
|
+
def paint(self):
|
|
50
|
+
"""
|
|
51
|
+
Render the item using OpenGL.
|
|
52
|
+
This method should be overridden by subclasses to perform the actual rendering.
|
|
53
|
+
"""
|
|
54
|
+
pass
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
from q3dviewer.custom_items.axis_item import *
|
|
2
|
+
from q3dviewer.custom_items.cloud_item import *
|
|
3
|
+
from q3dviewer.custom_items.cloud_io_item import *
|
|
4
|
+
from q3dviewer.custom_items.gaussian_item import *
|
|
5
|
+
from q3dviewer.custom_items.frame_item import *
|
|
6
|
+
from q3dviewer.custom_items.grid_item import *
|
|
7
|
+
from q3dviewer.custom_items.text_item import *
|
|
8
|
+
from q3dviewer.custom_items.image_item import *
|
|
9
|
+
from q3dviewer.custom_items.line_item import *
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
3
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from q3dviewer.base_item import BaseItem
|
|
7
|
+
from q3dviewer.utils import set_uniform
|
|
8
|
+
from OpenGL.GL import *
|
|
9
|
+
import numpy as np
|
|
10
|
+
from OpenGL.GL import shaders
|
|
11
|
+
from PySide6.QtWidgets import QLabel, QDoubleSpinBox
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
# Vertex and Fragment shader source code
|
|
15
|
+
vertex_shader_source = """
|
|
16
|
+
#version 330 core
|
|
17
|
+
layout(location = 0) in vec3 position;
|
|
18
|
+
layout(location = 1) in vec3 color;
|
|
19
|
+
|
|
20
|
+
out vec3 ourColor;
|
|
21
|
+
|
|
22
|
+
uniform mat4 view_matrix;
|
|
23
|
+
uniform mat4 project_matrix;
|
|
24
|
+
uniform mat4 model_matrix;
|
|
25
|
+
uniform float size;
|
|
26
|
+
|
|
27
|
+
void main()
|
|
28
|
+
{
|
|
29
|
+
vec3 scaled_position = position * size;
|
|
30
|
+
gl_Position = project_matrix * view_matrix * model_matrix * vec4(scaled_position, 1.0);
|
|
31
|
+
ourColor = color;
|
|
32
|
+
}
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
fragment_shader_source = """
|
|
36
|
+
#version 330 core
|
|
37
|
+
in vec3 ourColor;
|
|
38
|
+
out vec4 color;
|
|
39
|
+
|
|
40
|
+
void main()
|
|
41
|
+
{
|
|
42
|
+
color = vec4(ourColor, 1.0);
|
|
43
|
+
}
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class AxisItem(BaseItem):
|
|
48
|
+
def __init__(self, size=1.0, width=2):
|
|
49
|
+
super().__init__()
|
|
50
|
+
self.size = size
|
|
51
|
+
self.width = width
|
|
52
|
+
self.model_matrix = np.eye(4, dtype=np.float32)
|
|
53
|
+
self.need_update_setting = True
|
|
54
|
+
|
|
55
|
+
def initialize_gl(self):
|
|
56
|
+
# Axis vertices and colors
|
|
57
|
+
self.vertices = np.array([
|
|
58
|
+
# positions # colors
|
|
59
|
+
[0.0, 0.0, 0.0, 1.0, 0.0, 0.0], # X axis (red)
|
|
60
|
+
[1.0, 0.0, 0.0, 1.0, 0.0, 0.0],
|
|
61
|
+
[0.0, 0.0, 0.0, 0.0, 1.0, 0.0], # Y axis (green)
|
|
62
|
+
[0.0, 1.0, 0.0, 0.0, 1.0, 0.0],
|
|
63
|
+
[0.0, 0.0, 0.0, 0.0, 0.0, 1.0], # Z axis (blue)
|
|
64
|
+
[0.0, 0.0, 1.0, 0.0, 0.0, 1.0],
|
|
65
|
+
], dtype=np.float32)
|
|
66
|
+
|
|
67
|
+
self.vao = glGenVertexArrays(1)
|
|
68
|
+
vbo = glGenBuffers(1)
|
|
69
|
+
|
|
70
|
+
glBindVertexArray(self.vao)
|
|
71
|
+
|
|
72
|
+
glBindBuffer(GL_ARRAY_BUFFER, vbo)
|
|
73
|
+
glBufferData(GL_ARRAY_BUFFER, self.vertices.nbytes, self.vertices, GL_STATIC_DRAW)
|
|
74
|
+
|
|
75
|
+
# Vertex positions
|
|
76
|
+
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(0))
|
|
77
|
+
glEnableVertexAttribArray(0)
|
|
78
|
+
|
|
79
|
+
# Vertex colors
|
|
80
|
+
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 24, ctypes.c_void_p(12))
|
|
81
|
+
glEnableVertexAttribArray(1)
|
|
82
|
+
|
|
83
|
+
# Compile shaders and create shader program
|
|
84
|
+
self.program = shaders.compileProgram(
|
|
85
|
+
shaders.compileShader(vertex_shader_source, GL_VERTEX_SHADER),
|
|
86
|
+
shaders.compileShader(fragment_shader_source, GL_FRAGMENT_SHADER),
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
glBindVertexArray(0)
|
|
90
|
+
|
|
91
|
+
def add_setting(self, layout):
|
|
92
|
+
label_size = QLabel("Set size:")
|
|
93
|
+
layout.addWidget(label_size)
|
|
94
|
+
spinbox_size = QDoubleSpinBox()
|
|
95
|
+
spinbox_size.setSingleStep(0.1)
|
|
96
|
+
layout.addWidget(spinbox_size)
|
|
97
|
+
spinbox_size.setValue(self.size)
|
|
98
|
+
spinbox_size.valueChanged.connect(self.set_size)
|
|
99
|
+
spinbox_size.setRange(0.0, 100)
|
|
100
|
+
|
|
101
|
+
label_width = QLabel("Set width:")
|
|
102
|
+
layout.addWidget(label_width)
|
|
103
|
+
spinbox_width = QDoubleSpinBox()
|
|
104
|
+
layout.addWidget(spinbox_width)
|
|
105
|
+
spinbox_width.setSingleStep(0.1)
|
|
106
|
+
spinbox_width.setValue(self.width)
|
|
107
|
+
spinbox_width.valueChanged.connect(self.set_width)
|
|
108
|
+
spinbox_width.setRange(0, 1000)
|
|
109
|
+
|
|
110
|
+
def set_size(self, size):
|
|
111
|
+
self.size = size
|
|
112
|
+
self.need_update_setting = True
|
|
113
|
+
|
|
114
|
+
def update_setting(self):
|
|
115
|
+
if not self.need_update_setting:
|
|
116
|
+
return
|
|
117
|
+
glUseProgram(self.program)
|
|
118
|
+
set_uniform(self.program, float(self.size), 'size')
|
|
119
|
+
set_uniform(self.program, self.model_matrix, 'model_matrix')
|
|
120
|
+
glUseProgram(0)
|
|
121
|
+
self.need_update_setting = False
|
|
122
|
+
|
|
123
|
+
def set_width(self, width):
|
|
124
|
+
self.width = width
|
|
125
|
+
|
|
126
|
+
def set_transform(self, transform):
|
|
127
|
+
"""
|
|
128
|
+
Set the transformation matrix for the axis item.
|
|
129
|
+
"""
|
|
130
|
+
self.model_matrix = transform
|
|
131
|
+
self.need_update_setting = True
|
|
132
|
+
|
|
133
|
+
def paint(self):
|
|
134
|
+
self.update_setting()
|
|
135
|
+
glLineWidth(self.width)
|
|
136
|
+
glUseProgram(self.program)
|
|
137
|
+
glBindVertexArray(self.vao)
|
|
138
|
+
|
|
139
|
+
view_matrix = self.glwidget().get_view_matrix()
|
|
140
|
+
project_matrix = self.glwidget().get_projection_matrix()
|
|
141
|
+
set_uniform(self.program, view_matrix, 'view_matrix')
|
|
142
|
+
set_uniform(self.program, project_matrix, 'project_matrix')
|
|
143
|
+
|
|
144
|
+
glDrawArrays(GL_LINES, 0, 6)
|
|
145
|
+
|
|
146
|
+
glBindVertexArray(0)
|
|
147
|
+
glUseProgram(0)
|
|
148
|
+
glLineWidth(1)
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
|
|
3
|
+
Distributed under MIT license. See LICENSE for more information.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from q3dviewer.custom_items.cloud_item import CloudItem
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import os
|
|
9
|
+
from PySide6.QtWidgets import QPushButton, QLabel, QLineEdit, QMessageBox
|
|
10
|
+
from q3dviewer.utils.cloud_io import save_pcd, save_ply, save_e57, save_las, load_pcd, load_ply, load_e57, load_las
|
|
11
|
+
|
|
12
|
+
class CloudIOItem(CloudItem):
|
|
13
|
+
"""
|
|
14
|
+
add save/load function to CloudItem
|
|
15
|
+
"""
|
|
16
|
+
def __init__(self, **kwargs):
|
|
17
|
+
super().__init__(**kwargs)
|
|
18
|
+
self.save_path = str(Path(os.path.expanduser("~"), "data.pcd"))
|
|
19
|
+
|
|
20
|
+
def add_setting(self, layout):
|
|
21
|
+
super().add_setting(layout)
|
|
22
|
+
|
|
23
|
+
label4 = QLabel("Save Path:")
|
|
24
|
+
layout.addWidget(label4)
|
|
25
|
+
box4 = QLineEdit()
|
|
26
|
+
box4.setText(self.save_path)
|
|
27
|
+
box4.textChanged.connect(self.set_path)
|
|
28
|
+
layout.addWidget(box4)
|
|
29
|
+
save_button = QPushButton("Save Cloud")
|
|
30
|
+
save_button.clicked.connect(self.save)
|
|
31
|
+
layout.addWidget(save_button)
|
|
32
|
+
self.save_msg = QMessageBox()
|
|
33
|
+
self.save_msg.setIcon(QMessageBox.Information)
|
|
34
|
+
self.save_msg.setWindowTitle("save")
|
|
35
|
+
self.save_msg.setStandardButtons(QMessageBox.Ok)
|
|
36
|
+
|
|
37
|
+
def save(self):
|
|
38
|
+
cloud = self.buff[:self.valid_buff_top]
|
|
39
|
+
func = None
|
|
40
|
+
if self.save_path.endswith(".pcd"):
|
|
41
|
+
func = save_pcd
|
|
42
|
+
elif self.save_path.endswith(".ply"):
|
|
43
|
+
func = save_ply
|
|
44
|
+
elif self.save_path.endswith(".e57"):
|
|
45
|
+
func = save_e57
|
|
46
|
+
elif self.save_path.endswith(".las"):
|
|
47
|
+
func = save_las
|
|
48
|
+
elif self.save_path.endswith(".tif") or self.save_path.endswith(".tiff"):
|
|
49
|
+
print("Do not support save as tif type!")
|
|
50
|
+
else:
|
|
51
|
+
print("Unsupported cloud file type!")
|
|
52
|
+
try:
|
|
53
|
+
func(cloud, self.save_path)
|
|
54
|
+
self.save_msg.setText("Save cloud to %s" % self.save_path)
|
|
55
|
+
except Exception as e:
|
|
56
|
+
print(e)
|
|
57
|
+
self.save_msg.setText("Cannot save to %s" % self.save_path)
|
|
58
|
+
self.save_msg.exec()
|
|
59
|
+
self.save_msg.exec()
|
|
60
|
+
|
|
61
|
+
def load(self, file, append=False):
|
|
62
|
+
# print("Try to load %s ..." % file)
|
|
63
|
+
if file.endswith(".pcd"):
|
|
64
|
+
cloud, color_mode = load_pcd(file)
|
|
65
|
+
elif file.endswith(".ply"):
|
|
66
|
+
cloud, color_mode = load_ply(file)
|
|
67
|
+
elif file.endswith(".e57"):
|
|
68
|
+
cloud, color_mode = load_e57(file)
|
|
69
|
+
elif file.endswith(".las"):
|
|
70
|
+
cloud, color_mode = load_las(file)
|
|
71
|
+
else:
|
|
72
|
+
print("Not supported file type.")
|
|
73
|
+
return
|
|
74
|
+
self.set_data(data=cloud, append=append)
|
|
75
|
+
self.set_color_mode(color_mode)
|
|
76
|
+
return cloud
|
|
77
|
+
|
|
78
|
+
def set_path(self, path):
|
|
79
|
+
self.save_path = path
|