q3dviewer 1.1.7__py3-none-any.whl → 1.1.9__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/Qt/__init__.py +1 -0
- q3dviewer/base_glwidget.py +83 -1
- q3dviewer/custom_items/__init__.py +1 -0
- q3dviewer/custom_items/cloud_io_item.py +16 -1
- q3dviewer/custom_items/cloud_item.py +31 -20
- q3dviewer/custom_items/text3d_item.py +152 -0
- q3dviewer/custom_items/text_item.py +19 -4
- q3dviewer/glwidget.py +23 -3
- q3dviewer/shaders/cloud_frag.glsl +1 -1
- q3dviewer/shaders/cloud_vert.glsl +4 -4
- q3dviewer/shaders/gau_frag.glsl +1 -1
- q3dviewer/shaders/gau_prep.glsl +1 -1
- q3dviewer/shaders/gau_vert.glsl +1 -1
- q3dviewer/shaders/sort_by_key.glsl +1 -1
- q3dviewer/tools/cloud_viewer.py +79 -3
- q3dviewer/tools/example_viewer.py +8 -28
- q3dviewer/tools/film_maker.py +1 -1
- q3dviewer/tools/lidar_cam_calib.py +8 -8
- q3dviewer/utils/convert_ros_msg.py +49 -6
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.9.dist-info}/METADATA +12 -9
- q3dviewer-1.1.9.dist-info/RECORD +44 -0
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.9.dist-info}/WHEEL +1 -1
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.9.dist-info}/entry_points.txt +1 -0
- q3dviewer/basic_window.py +0 -228
- q3dviewer/cloud_viewer.py +0 -74
- q3dviewer/custom_items/camera_frame_item.py +0 -173
- q3dviewer/custom_items/trajectory_item.py +0 -79
- q3dviewer/gau_io.py +0 -168
- q3dviewer/utils.py +0 -71
- q3dviewer-1.1.7.dist-info/RECORD +0 -49
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.9.dist-info}/LICENSE +0 -0
- {q3dviewer-1.1.7.dist-info → q3dviewer-1.1.9.dist-info}/top_level.txt +0 -0
q3dviewer/tools/cloud_viewer.py
CHANGED
|
@@ -8,7 +8,9 @@ Distributed under MIT license. See LICENSE for more information.
|
|
|
8
8
|
import numpy as np
|
|
9
9
|
import q3dviewer as q3d
|
|
10
10
|
from q3dviewer.Qt.QtWidgets import QVBoxLayout, QProgressBar, QDialog, QLabel
|
|
11
|
-
from q3dviewer.Qt.QtCore import QThread, Signal
|
|
11
|
+
from q3dviewer.Qt.QtCore import QThread, Signal, Qt
|
|
12
|
+
from q3dviewer.Qt.QtGui import QKeyEvent
|
|
13
|
+
from q3dviewer import GLWidget
|
|
12
14
|
|
|
13
15
|
|
|
14
16
|
class ProgressDialog(QDialog):
|
|
@@ -57,9 +59,57 @@ class FileLoaderThread(QThread):
|
|
|
57
59
|
self.finished.emit()
|
|
58
60
|
|
|
59
61
|
|
|
62
|
+
class CustomGLWidget(GLWidget):
|
|
63
|
+
def __init__(self, viewer):
|
|
64
|
+
super().__init__()
|
|
65
|
+
self.viewer = viewer
|
|
66
|
+
self.selected_points = []
|
|
67
|
+
|
|
68
|
+
def mousePressEvent(self, event):
|
|
69
|
+
# ctrl + left click to add select point
|
|
70
|
+
# ctrl + right click to remove a point from selected points
|
|
71
|
+
if event.button() == Qt.LeftButton and event.modifiers() & Qt.ControlModifier:
|
|
72
|
+
x, y = event.x(), event.y()
|
|
73
|
+
p = self.get_point(x, y)
|
|
74
|
+
if p is not None:
|
|
75
|
+
self.selected_points.append(p)
|
|
76
|
+
self.update_marker()
|
|
77
|
+
elif event.button() == Qt.RightButton and event.modifiers() & Qt.ControlModifier:
|
|
78
|
+
if len(self.selected_points) > 0:
|
|
79
|
+
self.selected_points.pop()
|
|
80
|
+
self.update_marker()
|
|
81
|
+
super().mouseReleaseEvent(event)
|
|
82
|
+
|
|
83
|
+
def update_marker(self):
|
|
84
|
+
marks = []
|
|
85
|
+
for p in self.selected_points:
|
|
86
|
+
m = {'text': '',
|
|
87
|
+
'position': p,
|
|
88
|
+
'color': (0.0, 1.0, 0.0, 1.0),
|
|
89
|
+
'font_size': 16,
|
|
90
|
+
'point_size': 5.0,
|
|
91
|
+
'line_width': 1.0}
|
|
92
|
+
marks.append(m)
|
|
93
|
+
|
|
94
|
+
# calculate distances between consecutive points
|
|
95
|
+
self.viewer['marker'].set_data(data=marks, append=False)
|
|
96
|
+
if len(self.selected_points) >= 2:
|
|
97
|
+
total_dists = 0.0
|
|
98
|
+
for i in range(1, len(self.selected_points)):
|
|
99
|
+
p1 = self.selected_points[i - 1]
|
|
100
|
+
p2 = self.selected_points[i]
|
|
101
|
+
dist = np.linalg.norm(np.array(p2) - np.array(p1))
|
|
102
|
+
total_dists += dist
|
|
103
|
+
self.viewer['text'].set_data(text=f'Total Distance: {total_dists:.2f} meter')
|
|
104
|
+
else:
|
|
105
|
+
self.viewer['text'].set_data(text='')
|
|
106
|
+
|
|
107
|
+
|
|
60
108
|
class CloudViewer(q3d.Viewer):
|
|
61
109
|
def __init__(self, **kwargs):
|
|
62
|
-
|
|
110
|
+
gl_widget_class=lambda: CustomGLWidget(self)
|
|
111
|
+
super(CloudViewer, self).__init__(
|
|
112
|
+
**kwargs, gl_widget_class=gl_widget_class)
|
|
63
113
|
self.setAcceptDrops(True)
|
|
64
114
|
|
|
65
115
|
def dragEnterEvent(self, event):
|
|
@@ -96,8 +146,28 @@ class CloudViewer(q3d.Viewer):
|
|
|
96
146
|
center = np.nanmean(cloud['xyz'].astype(np.float64), axis=0)
|
|
97
147
|
self.glwidget.set_cam_position(center=center)
|
|
98
148
|
|
|
149
|
+
# print a quick help message
|
|
150
|
+
def print_help():
|
|
151
|
+
# ANSI color codes
|
|
152
|
+
GREEN = '\033[92m'
|
|
153
|
+
BLUE = '\033[94m'
|
|
154
|
+
BOLD = '\033[1m'
|
|
155
|
+
END = '\033[0m'
|
|
156
|
+
|
|
157
|
+
help_msg = f"""
|
|
158
|
+
{BOLD}Cloud Viewer Help:{END}
|
|
159
|
+
{GREEN}• Drag and drop cloud files into the viewer to load them.{END}
|
|
160
|
+
{GREEN}• Measure distance between points:{END}
|
|
161
|
+
{BLUE}- Hold Ctrl and left-click to select points on the cloud.{END}
|
|
162
|
+
{BLUE}- Hold Ctrl and right-click to remove the last selected point.{END}
|
|
163
|
+
{BLUE}- The total distance between selected points will be displayed.{END}
|
|
164
|
+
{GREEN}• Press 'M' to open the settings window.{END}
|
|
165
|
+
{BLUE}- Use the settings window to adjust item properties.{END}
|
|
166
|
+
"""
|
|
167
|
+
print(help_msg)
|
|
99
168
|
|
|
100
169
|
def main():
|
|
170
|
+
print_help()
|
|
101
171
|
import argparse
|
|
102
172
|
parser = argparse.ArgumentParser()
|
|
103
173
|
parser.add_argument("--path", help="the cloud file path")
|
|
@@ -107,9 +177,15 @@ def main():
|
|
|
107
177
|
cloud_item = q3d.CloudIOItem(size=1, alpha=0.1)
|
|
108
178
|
axis_item = q3d.AxisItem(size=0.5, width=5)
|
|
109
179
|
grid_item = q3d.GridItem(size=1000, spacing=20)
|
|
180
|
+
marker_item = q3d.Text3DItem() # Changed from CloudItem to Text3DItem
|
|
181
|
+
text_item = q3d.Text2DItem(pos=(20, 40), text="", color='lime', size=16)
|
|
110
182
|
|
|
111
183
|
viewer.add_items(
|
|
112
|
-
{'
|
|
184
|
+
{'marker': marker_item,
|
|
185
|
+
'cloud': cloud_item,
|
|
186
|
+
'grid': grid_item,
|
|
187
|
+
'axis': axis_item,
|
|
188
|
+
'text': text_item})
|
|
113
189
|
|
|
114
190
|
if args.path:
|
|
115
191
|
pcd_fn = args.path
|
|
@@ -1,47 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import q3dviewer as q3d
|
|
5
|
-
from q3dviewer.Qt.QtCore import QTimer
|
|
6
|
-
|
|
7
|
-
i = 0.0
|
|
8
|
-
|
|
9
|
-
def update(viewer):
|
|
10
|
-
global i
|
|
11
|
-
i += 0.05
|
|
12
|
-
new = np.array([np.sin(i) * i, np.cos(i) * i, i])
|
|
13
|
-
viewer['traj'].set_data(new, append=True)
|
|
14
|
-
viewer.update()
|
|
15
|
-
return i
|
|
16
|
-
|
|
3
|
+
import q3dviewer as q3d # Import q3dviewer
|
|
17
4
|
|
|
18
5
|
def main():
|
|
19
|
-
#
|
|
6
|
+
# Create a Qt application
|
|
20
7
|
app = q3d.QApplication([])
|
|
21
8
|
|
|
22
|
-
#
|
|
9
|
+
# Create various 3D items
|
|
23
10
|
axis_item = q3d.AxisItem(size=0.5, width=5)
|
|
24
11
|
grid_item = q3d.GridItem(size=10, spacing=1)
|
|
25
|
-
traj_item = q3d.LineItem(width=2)
|
|
26
12
|
|
|
27
|
-
#
|
|
13
|
+
# Create a viewer
|
|
28
14
|
viewer = q3d.Viewer(name='example')
|
|
29
|
-
|
|
30
|
-
#
|
|
15
|
+
|
|
16
|
+
# Add items to the viewer
|
|
31
17
|
viewer.add_items({
|
|
32
18
|
'grid': grid_item,
|
|
33
19
|
'axis': axis_item,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
# dynamic update by timer
|
|
37
|
-
timer = QTimer()
|
|
38
|
-
timer.timeout.connect(lambda: update(viewer))
|
|
39
|
-
timer.start(100)
|
|
20
|
+
})
|
|
40
21
|
|
|
41
|
-
#
|
|
22
|
+
# Show the viewer & run the Qt application
|
|
42
23
|
viewer.show()
|
|
43
24
|
app.exec()
|
|
44
25
|
|
|
45
|
-
|
|
46
26
|
if __name__ == '__main__':
|
|
47
27
|
main()
|
q3dviewer/tools/film_maker.py
CHANGED
|
@@ -417,7 +417,7 @@ def main():
|
|
|
417
417
|
args = parser.parse_args()
|
|
418
418
|
app = q3d.QApplication(['Film Maker'])
|
|
419
419
|
viewer = CMMViewer(name='Film Maker', update_interval=30)
|
|
420
|
-
cloud_item = q3d.CloudIOItem(size=
|
|
420
|
+
cloud_item = q3d.CloudIOItem(size=1, point_type='SPHERE', alpha=0.5, depth_test=True)
|
|
421
421
|
grid_item = q3d.GridItem(size=1000, spacing=20)
|
|
422
422
|
|
|
423
423
|
viewer.add_items(
|
|
@@ -187,11 +187,11 @@ class LidarCamViewer(q3d.Viewer):
|
|
|
187
187
|
def camera_info_cb(data):
|
|
188
188
|
global remap_info, K
|
|
189
189
|
if remap_info is None: # Initialize only once
|
|
190
|
-
K = np.array(
|
|
191
|
-
D = np.array(
|
|
190
|
+
K = np.array([2.4480877131144061e+02, 0., 4.7808179466584482e+02, 0., 2.4540886113128877e+02, 2.8080868163348435e+02, 0., 0., 1.]).reshape(3, 3)
|
|
191
|
+
D = np.array([0,0,0,0,0])
|
|
192
192
|
rospy.loginfo("Camera intrinsic parameters set")
|
|
193
|
-
height =
|
|
194
|
-
width =
|
|
193
|
+
height = 540
|
|
194
|
+
width = 960
|
|
195
195
|
mapx, mapy = cv2.initUndistortRectifyMap(
|
|
196
196
|
K, D, None, K, (width, height), cv2.CV_32FC1)
|
|
197
197
|
remap_info = [mapx, mapy]
|
|
@@ -287,14 +287,14 @@ def main():
|
|
|
287
287
|
scan_item = q3d.CloudItem(size=2, alpha=1, color_mode='I')
|
|
288
288
|
img_item = q3d.ImageItem(pos=np.array([0, 0]), size=np.array([800, 600]))
|
|
289
289
|
viewer.add_items({'scan': scan_item, 'grid': grid_item, 'img': img_item})
|
|
290
|
+
camera_info_cb(None)
|
|
290
291
|
|
|
291
292
|
rospy.init_node('lidar_cam_calib', anonymous=True)
|
|
292
293
|
|
|
293
294
|
# Use topic names from arguments
|
|
294
|
-
rospy.Subscriber(
|
|
295
|
-
rospy.Subscriber(
|
|
296
|
-
rospy.Subscriber(args.camera_info, CameraInfo,
|
|
297
|
-
camera_info_cb, queue_size=1)
|
|
295
|
+
rospy.Subscriber("cloud", PointCloud2, scan_cb, queue_size=1)
|
|
296
|
+
rospy.Subscriber("/usb_cam/image_raw", Image, image_cb, queue_size=1)
|
|
297
|
+
# rospy.Subscriber(args.camera_info, CameraInfo, camera_info_cb, queue_size=1)
|
|
298
298
|
|
|
299
299
|
viewer.show()
|
|
300
300
|
app.exec()
|
|
@@ -4,18 +4,59 @@ Distributed under MIT license. See LICENSE for more information.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import numpy as np
|
|
7
|
-
from pypcd4 import PointCloud
|
|
8
7
|
from q3dviewer.utils.maths import make_transform
|
|
9
8
|
|
|
9
|
+
def get_dtype(msg):
|
|
10
|
+
dtype_map = {
|
|
11
|
+
1: 'i1', # INT8
|
|
12
|
+
2: 'u1', # UINT8
|
|
13
|
+
3: 'i2', # INT16
|
|
14
|
+
4: 'u2', # UINT16
|
|
15
|
+
5: 'i4', # INT32
|
|
16
|
+
6: 'u4', # UINT32
|
|
17
|
+
7: 'f4', # FLOAT32
|
|
18
|
+
8: 'f8' # FLOAT64
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
point_step = msg.point_step
|
|
22
|
+
dtype_list = []
|
|
23
|
+
for field in msg.fields:
|
|
24
|
+
dtype_str = dtype_map.get(field.datatype, 'f4')
|
|
25
|
+
dtype_list.append((field.name, dtype_str, field.offset))
|
|
26
|
+
|
|
27
|
+
dtype_list.sort(key=lambda x: x[2])
|
|
28
|
+
|
|
29
|
+
structured_dtype = []
|
|
30
|
+
last_offset = 0
|
|
31
|
+
|
|
32
|
+
for field_name, field_dtype, offset in dtype_list:
|
|
33
|
+
if offset > last_offset:
|
|
34
|
+
padding_size = offset - last_offset
|
|
35
|
+
if padding_size > 0:
|
|
36
|
+
structured_dtype.append((f'pad{last_offset}', f'u1', padding_size))
|
|
37
|
+
|
|
38
|
+
structured_dtype.append((field_name, field_dtype))
|
|
39
|
+
last_offset = offset + np.dtype(field_dtype).itemsize
|
|
40
|
+
|
|
41
|
+
if point_step > last_offset:
|
|
42
|
+
final_padding = point_step - last_offset
|
|
43
|
+
structured_dtype.append((f'pad{last_offset}', f'u1', final_padding))
|
|
44
|
+
return structured_dtype
|
|
45
|
+
|
|
10
46
|
|
|
11
47
|
def convert_pointcloud2_msg(msg):
|
|
12
|
-
|
|
48
|
+
# Build dtype with proper offsets for PointCloud2 message
|
|
49
|
+
structured_dtype = get_dtype(msg)
|
|
50
|
+
pc = np.frombuffer(msg.data, dtype=structured_dtype)
|
|
51
|
+
# pc = PointCloud.from_msg(msg).pc_data
|
|
13
52
|
data_type = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
|
|
14
53
|
rgb = np.zeros([pc.shape[0]], dtype=np.uint32)
|
|
15
54
|
intensity = np.zeros([pc.shape[0]], dtype=np.uint32)
|
|
16
55
|
fields = ['xyz']
|
|
17
56
|
if 'intensity' in pc.dtype.names:
|
|
18
|
-
intensity = pc['intensity']
|
|
57
|
+
intensity = pc['intensity']
|
|
58
|
+
intensity = np.clip(intensity, 0, 255)
|
|
59
|
+
intensity = intensity.astype(np.uint32)
|
|
19
60
|
fields.append('intensity')
|
|
20
61
|
if 'rgb' in pc.dtype.names:
|
|
21
62
|
rgb = pc['rgb'].view(np.uint32)
|
|
@@ -42,10 +83,12 @@ def convert_odometry_msg(msg):
|
|
|
42
83
|
return transform, stamp
|
|
43
84
|
|
|
44
85
|
|
|
45
|
-
def convert_image_msg(msg):
|
|
86
|
+
def convert_image_msg(msg, bgr=False):
|
|
46
87
|
image = np.frombuffer(msg.data, dtype=np.uint8).reshape(
|
|
47
88
|
msg.height, msg.width, -1)
|
|
48
|
-
if
|
|
49
|
-
|
|
89
|
+
# if bgr is True return BGR image, else return RGB image
|
|
90
|
+
if (bgr and msg.encoding == 'rgb8') or (not bgr and msg.encoding == 'bgr8'):
|
|
91
|
+
image = image[:, :, ::-1]
|
|
92
|
+
|
|
50
93
|
stamp = msg.header.stamp.to_sec()
|
|
51
94
|
return image, stamp
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: q3dviewer
|
|
3
|
-
Version: 1.1.
|
|
3
|
+
Version: 1.1.9
|
|
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
|
|
7
|
+
License: UNKNOWN
|
|
8
|
+
Platform: UNKNOWN
|
|
7
9
|
Classifier: Programming Language :: Python :: 3
|
|
8
10
|
Classifier: License :: OSI Approved :: MIT License
|
|
9
11
|
Classifier: Operating System :: OS Independent
|
|
10
12
|
Description-Content-Type: text/markdown
|
|
11
|
-
License-File: LICENSE
|
|
12
|
-
Requires-Dist: numpy
|
|
13
|
-
Requires-Dist: pyside6
|
|
14
13
|
Requires-Dist: PyOpenGL
|
|
15
|
-
Requires-Dist: meshio
|
|
16
|
-
Requires-Dist: pypcd4
|
|
17
|
-
Requires-Dist: pye57
|
|
18
|
-
Requires-Dist: laspy
|
|
19
14
|
Requires-Dist: imageio
|
|
20
15
|
Requires-Dist: imageio[ffmpeg]
|
|
16
|
+
Requires-Dist: laspy
|
|
21
17
|
Requires-Dist: matplotlib
|
|
18
|
+
Requires-Dist: meshio
|
|
19
|
+
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: pye57
|
|
21
|
+
Requires-Dist: pypcd4
|
|
22
|
+
Requires-Dist: pyside6
|
|
22
23
|
|
|
23
24
|
|
|
24
25
|

|
|
@@ -171,7 +172,7 @@ def main():
|
|
|
171
172
|
|
|
172
173
|
# Create a viewer
|
|
173
174
|
viewer = q3d.Viewer(name='example')
|
|
174
|
-
|
|
175
|
+
|
|
175
176
|
# Add items to the viewer
|
|
176
177
|
viewer.add_items({
|
|
177
178
|
'grid': grid_item,
|
|
@@ -195,6 +196,7 @@ if __name__ == '__main__':
|
|
|
195
196
|
- **GridItem**: Displays grids.
|
|
196
197
|
- **ImageItem**: Displays 2D images.
|
|
197
198
|
- **Text2DItem**: Displays 2D text.
|
|
199
|
+
- **Text3DItem**: Displays 3D test and mark.
|
|
198
200
|
- **LineItem**: Displays lines or trajectories.
|
|
199
201
|
|
|
200
202
|
### Developing Custom Items
|
|
@@ -233,3 +235,4 @@ class YourItem(q3d.BaseItem):
|
|
|
233
235
|
```
|
|
234
236
|
|
|
235
237
|
Enjoy using `q3dviewer`!
|
|
238
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
q3dviewer/__init__.py,sha256=cjyfUE5zK6xohDGDQIWfb0DKkWChVznBd7CrVLg7whQ,376
|
|
2
|
+
q3dviewer/base_glwidget.py,sha256=iBvYqoFAqh09TDzugqDfu99_JqHL6IrvWOjm1XJJpsk,15395
|
|
3
|
+
q3dviewer/base_item.py,sha256=63MarHyoWszPL40ox-vPoOAQ1N4ypekOjoRARdPik-E,1755
|
|
4
|
+
q3dviewer/glwidget.py,sha256=EmrxPtVQ8RdPK5INKMlpKpVfX0KCfjSKRdGf4cSB1f0,5405
|
|
5
|
+
q3dviewer/viewer.py,sha256=Vq3ucDlBcBBoiVVGmqG1sRjhLePl50heblx6wJpsc1A,2603
|
|
6
|
+
q3dviewer/Qt/__init__.py,sha256=VJj7Ge6N_81__T9eHFl_YQpa1HyQrlLhMqC_9pUOYtc,2233
|
|
7
|
+
q3dviewer/custom_items/__init__.py,sha256=cwcNn40MzYXVO9eSzpRknJdzZNKPf2kaEMZy5DCNYcE,567
|
|
8
|
+
q3dviewer/custom_items/axis_item.py,sha256=-WM2urosqV847zpTpOtxdLjb7y9NJqFCH13qqodcCTg,2572
|
|
9
|
+
q3dviewer/custom_items/cloud_io_item.py,sha256=Haz-SOUUCPDSHgmKyyyFfP7LXBSEiN4r8xmchQwCm-k,4721
|
|
10
|
+
q3dviewer/custom_items/cloud_item.py,sha256=NkZ5rFM_hVxBPMZYml3oX2CI5d0ApJY1wet8I1eIssE,14024
|
|
11
|
+
q3dviewer/custom_items/frame_item.py,sha256=bUzww3tSDah0JZeqtU6_cYHhhTVWzXhJVMcAa5pCXHI,7458
|
|
12
|
+
q3dviewer/custom_items/gaussian_item.py,sha256=JMubpahkTPh0E8ShL3FLTahv0e35ODzjgK5K1i0YXSU,9884
|
|
13
|
+
q3dviewer/custom_items/grid_item.py,sha256=LDB_MYACoxld-xvz01_MfAf12vLcRkH7R_WtGHHdSgk,4945
|
|
14
|
+
q3dviewer/custom_items/image_item.py,sha256=k7HNTqdL2ckTbxMx7A7eKaP4aksZ85-pBjNdbpm6PXM,5355
|
|
15
|
+
q3dviewer/custom_items/line_item.py,sha256=rel-lx8AgjDY7qyIecHxHQZzaswRn2ZTiOIjB_0Mrqo,4444
|
|
16
|
+
q3dviewer/custom_items/text3d_item.py,sha256=DYBPXnCmMEzWDE1y523YsWSl91taXAdu0kdnhUcwE4A,5524
|
|
17
|
+
q3dviewer/custom_items/text_item.py,sha256=toeGjBu7RtT8CMUuaDWnmXPnA1UKHhnCzUNeonGczSo,2703
|
|
18
|
+
q3dviewer/shaders/cloud_frag.glsl,sha256=psKVt9qI6BW0bCqOk4lcKqUd6XgYGtdFigyN9OdYSNI,609
|
|
19
|
+
q3dviewer/shaders/cloud_vert.glsl,sha256=gKI6EJrzX5ga2W2yjU6x7Wjz7Cu2Y-wrPl4g10RfTLM,2376
|
|
20
|
+
q3dviewer/shaders/gau_frag.glsl,sha256=vWt5I3Ojrc2PCxRlBJGyJhujbveSicMA54T01Fk293A,975
|
|
21
|
+
q3dviewer/shaders/gau_prep.glsl,sha256=0BiWhYCQGeX2iN-e7m3dy1xWXqWrErErRAzHlcmWHF0,7218
|
|
22
|
+
q3dviewer/shaders/gau_vert.glsl,sha256=_rkm51zaWgPDJ-otJL-WX12fDvnPBOTooVfqo21Rexs,1666
|
|
23
|
+
q3dviewer/shaders/sort_by_key.glsl,sha256=M5RK6uRDp40vVH6XtBIrdJTcYatqXyZwd6kCzEa2DZg,1097
|
|
24
|
+
q3dviewer/tools/__init__.py,sha256=01wG7BGM6VX0QyFBKsqPmyf2e-vrmV_N3-mo-VQ1VBg,20
|
|
25
|
+
q3dviewer/tools/cloud_viewer.py,sha256=UsKALGiFR2Ck___LSGNPIZ3PPUlECO20jVkcP5g6yyc,6807
|
|
26
|
+
q3dviewer/tools/example_viewer.py,sha256=C867mLnCBjawS6LGgRsJ_c6-6wztfL9vOBQt85KbbdU,572
|
|
27
|
+
q3dviewer/tools/film_maker.py,sha256=xLFgRhFWoMQ37qlvcu1lXWaTWXMNRYlRcZFfHW5JtmQ,16676
|
|
28
|
+
q3dviewer/tools/gaussian_viewer.py,sha256=vIwWmiFhjNmknrEkBLzt2yiegeH7LP3OeNjnGM6GzaI,1633
|
|
29
|
+
q3dviewer/tools/lidar_calib.py,sha256=hHnsSaQh_Pkdh8tPntt0MgEW26nQyAdC_HQHq4I3sw8,10562
|
|
30
|
+
q3dviewer/tools/lidar_cam_calib.py,sha256=4CDcZZiFZDeKo2Y2_lXF9tfbiF9dPsz0OjppQdxQsU4,11430
|
|
31
|
+
q3dviewer/tools/ros_viewer.py,sha256=ARB3I5wohY3maP8dCu0O0hxObd6JFKuK2y7AApVgMWA,2551
|
|
32
|
+
q3dviewer/utils/__init__.py,sha256=dwTNAAebTiKY4ygv2G1O-w6-TbJnmnNVO2UfJXvJhaQ,107
|
|
33
|
+
q3dviewer/utils/cloud_io.py,sha256=xarfYakY0zgKwvZkgKSPO6b4DEo42hsq3mcvCbK64yg,12134
|
|
34
|
+
q3dviewer/utils/convert_ros_msg.py,sha256=lNbLIawJfwp3VzygdW3dUXkfSG8atg_CoZbQFmt8H70,3142
|
|
35
|
+
q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
|
|
36
|
+
q3dviewer/utils/helpers.py,sha256=SqR4YTQZi13FKbkVUYgodXce1JJ_YmrHEIRkUmnIUas,3085
|
|
37
|
+
q3dviewer/utils/maths.py,sha256=zHaPtvVZIuo8xepIXCMeSL9tpx8FahUrq0l4K1oXrBk,8834
|
|
38
|
+
q3dviewer/utils/range_slider.py,sha256=9djTxuzmzH54DgSwAljRpLGjsrIJ0hTxhaxFjPxsk8g,4007
|
|
39
|
+
q3dviewer-1.1.9.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
|
|
40
|
+
q3dviewer-1.1.9.dist-info/METADATA,sha256=k7pCVA3O73KUcjQEWcO08VM0Vl50gcBopuYVlyu1nU4,8024
|
|
41
|
+
q3dviewer-1.1.9.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
|
|
42
|
+
q3dviewer-1.1.9.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
|
|
43
|
+
q3dviewer-1.1.9.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
|
|
44
|
+
q3dviewer-1.1.9.dist-info/RECORD,,
|
q3dviewer/basic_window.py
DELETED
|
@@ -1,228 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
|
|
3
|
-
import numpy as np
|
|
4
|
-
import pyqtgraph.opengl as gl
|
|
5
|
-
from pyqtgraph.Qt import QtCore
|
|
6
|
-
from PyQt5.QtWidgets import QWidget, QComboBox, QVBoxLayout, QHBoxLayout, QSizePolicy,\
|
|
7
|
-
QSpacerItem, QMainWindow
|
|
8
|
-
from OpenGL.GL import *
|
|
9
|
-
from PyQt5.QtGui import QKeyEvent, QVector3D
|
|
10
|
-
from PyQt5.QtWidgets import QApplication, QWidget
|
|
11
|
-
import numpy as np
|
|
12
|
-
import signal
|
|
13
|
-
import sys
|
|
14
|
-
from PyQt5.QtWidgets import QLabel, QLineEdit, QDoubleSpinBox, QSpinBox
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
class SettingWindow(QWidget):
|
|
18
|
-
def __init__(self):
|
|
19
|
-
super().__init__()
|
|
20
|
-
self.combo_items = QComboBox()
|
|
21
|
-
self.combo_items.currentIndexChanged.connect(self.onComboboxSelection)
|
|
22
|
-
main_layout = QVBoxLayout()
|
|
23
|
-
self.stretch = QSpacerItem(
|
|
24
|
-
10, 10, QSizePolicy.Minimum, QSizePolicy.Expanding)
|
|
25
|
-
main_layout.addWidget(self.combo_items)
|
|
26
|
-
self.layout = QVBoxLayout()
|
|
27
|
-
self.layout.addItem(self.stretch)
|
|
28
|
-
main_layout.addLayout(self.layout)
|
|
29
|
-
self.setLayout(main_layout)
|
|
30
|
-
self.setWindowTitle("Setting Window")
|
|
31
|
-
self.setGeometry(200, 200, 300, 200)
|
|
32
|
-
self.items = {}
|
|
33
|
-
|
|
34
|
-
def addSetting(self, name, item):
|
|
35
|
-
self.items.update({name: item})
|
|
36
|
-
self.combo_items.addItem("%s(%s)" % (name, item.__class__.__name__))
|
|
37
|
-
|
|
38
|
-
def clearSetting(self):
|
|
39
|
-
while self.layout.count():
|
|
40
|
-
child = self.layout.takeAt(0)
|
|
41
|
-
if child.widget():
|
|
42
|
-
child.widget().deleteLater()
|
|
43
|
-
|
|
44
|
-
def onComboboxSelection(self, index):
|
|
45
|
-
self.layout.removeItem(self.stretch)
|
|
46
|
-
# remove all setting of previous widget
|
|
47
|
-
self.clearSetting()
|
|
48
|
-
|
|
49
|
-
key = list(self.items.keys())
|
|
50
|
-
try:
|
|
51
|
-
item = self.items[key[index]]
|
|
52
|
-
item.addSetting(self.layout)
|
|
53
|
-
self.layout.addItem(self.stretch)
|
|
54
|
-
except AttributeError:
|
|
55
|
-
print("%s: No setting." % (item.__class__.__name__))
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
class ViewWidget(gl.GLViewWidget):
|
|
59
|
-
def __init__(self):
|
|
60
|
-
self.followed_name = 'none'
|
|
61
|
-
self.named_items = {}
|
|
62
|
-
self.color = '#000000'
|
|
63
|
-
self.followable_item_name = ['none']
|
|
64
|
-
self.setting_window = SettingWindow()
|
|
65
|
-
super(ViewWidget, self).__init__()
|
|
66
|
-
|
|
67
|
-
def onFollowableSelection(self, index):
|
|
68
|
-
self.followed_name = self.followable_item_name[index]
|
|
69
|
-
|
|
70
|
-
def update(self):
|
|
71
|
-
if self.followed_name != 'none':
|
|
72
|
-
pos = self.named_items[self.followed_name].T[:3, 3]
|
|
73
|
-
self.opts['center'] = QVector3D(pos[0], pos[1], pos[2])
|
|
74
|
-
super().update()
|
|
75
|
-
|
|
76
|
-
def addSetting(self, layout):
|
|
77
|
-
label1 = QLabel("Set background color:")
|
|
78
|
-
label1.setToolTip("using '#xxxxxx', i.e. #FF4500")
|
|
79
|
-
box1 = QLineEdit()
|
|
80
|
-
box1.setToolTip("'using '#xxxxxx', i.e. #FF4500")
|
|
81
|
-
box1.setText(str(self.color))
|
|
82
|
-
box1.textChanged.connect(self.setBKColor)
|
|
83
|
-
layout.addWidget(label1)
|
|
84
|
-
layout.addWidget(box1)
|
|
85
|
-
label2 = QLabel("Set Focus:")
|
|
86
|
-
combo2 = QComboBox()
|
|
87
|
-
for name in self.followable_item_name:
|
|
88
|
-
combo2.addItem(name)
|
|
89
|
-
combo2.currentIndexChanged.connect(self.onFollowableSelection)
|
|
90
|
-
layout.addWidget(label2)
|
|
91
|
-
layout.addWidget(combo2)
|
|
92
|
-
|
|
93
|
-
def setBKColor(self, color):
|
|
94
|
-
if (type(color) != str):
|
|
95
|
-
return
|
|
96
|
-
if color.startswith("#"):
|
|
97
|
-
try:
|
|
98
|
-
self.setBackgroundColor(color)
|
|
99
|
-
self.color = color
|
|
100
|
-
except ValueError:
|
|
101
|
-
return
|
|
102
|
-
|
|
103
|
-
def addItem(self, name, item):
|
|
104
|
-
self.named_items.update({name: item})
|
|
105
|
-
if (item.__class__.__name__ == 'GLAxisItem'):
|
|
106
|
-
self.followable_item_name.append(name)
|
|
107
|
-
self.setting_window.addSetting(name, item)
|
|
108
|
-
super().addItem(item)
|
|
109
|
-
|
|
110
|
-
def mouseReleaseEvent(self, ev):
|
|
111
|
-
if hasattr(self, 'mousePos'):
|
|
112
|
-
delattr(self, 'mousePos')
|
|
113
|
-
|
|
114
|
-
def mouseMoveEvent(self, ev):
|
|
115
|
-
lpos = ev.localPos()
|
|
116
|
-
if not hasattr(self, 'mousePos'):
|
|
117
|
-
self.mousePos = lpos
|
|
118
|
-
diff = lpos - self.mousePos
|
|
119
|
-
self.mousePos = lpos
|
|
120
|
-
if ev.buttons() == QtCore.Qt.MouseButton.RightButton:
|
|
121
|
-
self.orbit(-diff.x(), diff.y())
|
|
122
|
-
elif ev.buttons() == QtCore.Qt.MouseButton.LeftButton:
|
|
123
|
-
pitch_abs = np.abs(self.opts['elevation'])
|
|
124
|
-
camera_mode = 'view-upright'
|
|
125
|
-
if(pitch_abs <= 45.0 or pitch_abs == 90):
|
|
126
|
-
camera_mode = 'view'
|
|
127
|
-
self.pan(diff.x(), diff.y(), 0, relative=camera_mode)
|
|
128
|
-
|
|
129
|
-
def keyPressEvent(self, ev: QKeyEvent):
|
|
130
|
-
step = 10
|
|
131
|
-
zoom_delta = 20
|
|
132
|
-
speed = 2
|
|
133
|
-
self.projectionMatrix().data()
|
|
134
|
-
|
|
135
|
-
pitch_abs = np.abs(self.opts['elevation'])
|
|
136
|
-
camera_mode = 'view-upright'
|
|
137
|
-
if(pitch_abs <= 45.0 or pitch_abs == 90):
|
|
138
|
-
camera_mode = 'view'
|
|
139
|
-
|
|
140
|
-
if ev.key() == QtCore.Qt.Key_M: # setting meun
|
|
141
|
-
print("Open setting windows")
|
|
142
|
-
self.openSettingWindow()
|
|
143
|
-
elif ev.key() == QtCore.Qt.Key_R:
|
|
144
|
-
print("Clear viewer")
|
|
145
|
-
for item in self.named_items.values():
|
|
146
|
-
try:
|
|
147
|
-
item.clear()
|
|
148
|
-
except:
|
|
149
|
-
pass
|
|
150
|
-
elif ev.key() == QtCore.Qt.Key_Up:
|
|
151
|
-
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
152
|
-
self.pan(0, +step, 0, relative=camera_mode)
|
|
153
|
-
else:
|
|
154
|
-
self.orbit(azim=0, elev=-speed)
|
|
155
|
-
elif ev.key() == QtCore.Qt.Key_Down:
|
|
156
|
-
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
157
|
-
self.pan(0, -step, 0, relative=camera_mode)
|
|
158
|
-
else:
|
|
159
|
-
self.orbit(azim=0, elev=speed)
|
|
160
|
-
elif ev.key() == QtCore.Qt.Key_Left:
|
|
161
|
-
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
162
|
-
self.pan(+step, 0, 0, relative=camera_mode)
|
|
163
|
-
else:
|
|
164
|
-
self.orbit(azim=speed, elev=0)
|
|
165
|
-
|
|
166
|
-
elif ev.key() == QtCore.Qt.Key_Right:
|
|
167
|
-
if ev.modifiers() & QtCore.Qt.KeyboardModifier.ControlModifier:
|
|
168
|
-
self.pan(-step, 0, 0, relative=camera_mode)
|
|
169
|
-
else:
|
|
170
|
-
self.orbit(azim=-speed, elev=0)
|
|
171
|
-
|
|
172
|
-
elif ev.key() == QtCore.Qt.Key_Z:
|
|
173
|
-
self.opts['distance'] *= 0.999**(+zoom_delta)
|
|
174
|
-
elif ev.key() == QtCore.Qt.Key_X:
|
|
175
|
-
self.opts['distance'] *= 0.999**(-zoom_delta)
|
|
176
|
-
else:
|
|
177
|
-
super().keyPressEvent(ev)
|
|
178
|
-
|
|
179
|
-
def openSettingWindow(self):
|
|
180
|
-
if self.setting_window.isVisible():
|
|
181
|
-
self.setting_window.raise_()
|
|
182
|
-
|
|
183
|
-
else:
|
|
184
|
-
self.setting_window.show()
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
class Viewer(QMainWindow):
|
|
188
|
-
def __init__(self, name='Viewer', win_size=[1920, 1080], vw=ViewWidget):
|
|
189
|
-
signal.signal(signal.SIGINT, signal.SIG_DFL)
|
|
190
|
-
super(Viewer, self).__init__()
|
|
191
|
-
self.vw = vw
|
|
192
|
-
self.setGeometry(0, 0, win_size[0], win_size[1])
|
|
193
|
-
self.initUI()
|
|
194
|
-
self.setWindowTitle(name)
|
|
195
|
-
|
|
196
|
-
def initUI(self):
|
|
197
|
-
centerWidget = QWidget()
|
|
198
|
-
self.setCentralWidget(centerWidget)
|
|
199
|
-
layout = QVBoxLayout()
|
|
200
|
-
centerWidget.setLayout(layout)
|
|
201
|
-
self.viewerWidget = self.vw()
|
|
202
|
-
layout.addWidget(self.viewerWidget, 1)
|
|
203
|
-
timer = QtCore.QTimer(self)
|
|
204
|
-
timer.setInterval(20) # period, in milliseconds
|
|
205
|
-
timer.timeout.connect(self.update)
|
|
206
|
-
self.viewerWidget.setCameraPosition(distance=40)
|
|
207
|
-
timer.start()
|
|
208
|
-
|
|
209
|
-
def addItems(self, named_items: dict):
|
|
210
|
-
for name, item in named_items.items():
|
|
211
|
-
self.viewerWidget.addItem(name, item)
|
|
212
|
-
|
|
213
|
-
def __getitem__(self, name: str):
|
|
214
|
-
if name in self.viewerWidget.named_items:
|
|
215
|
-
return self.viewerWidget.named_items[name]
|
|
216
|
-
else:
|
|
217
|
-
return None
|
|
218
|
-
|
|
219
|
-
def update(self):
|
|
220
|
-
# force update by timer
|
|
221
|
-
self.viewerWidget.update()
|
|
222
|
-
|
|
223
|
-
def closeEvent(self, _):
|
|
224
|
-
sys.exit(0)
|
|
225
|
-
|
|
226
|
-
def show(self):
|
|
227
|
-
self.viewerWidget.setting_window.addSetting("main win", self.viewerWidget)
|
|
228
|
-
super().show()
|