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.
@@ -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
- super(CloudViewer, self).__init__(**kwargs)
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
- {'cloud': cloud_item, 'grid': grid_item, 'axis': axis_item})
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 numpy as np
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
- # add a qt application
6
+ # Create a Qt application
20
7
  app = q3d.QApplication([])
21
8
 
22
- # create opengl items
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
- # create viewer
13
+ # Create a viewer
28
14
  viewer = q3d.Viewer(name='example')
29
-
30
- # add all items to viewer
15
+
16
+ # Add items to the viewer
31
17
  viewer.add_items({
32
18
  'grid': grid_item,
33
19
  'axis': axis_item,
34
- 'traj': traj_item})
35
-
36
- # dynamic update by timer
37
- timer = QTimer()
38
- timer.timeout.connect(lambda: update(viewer))
39
- timer.start(100)
20
+ })
40
21
 
41
- # show viewer and start application
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()
@@ -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=0.1, point_type='SPHERE', alpha=0.5, depth_test=True)
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(data.K).reshape(3, 3)
191
- D = np.array(data.D)
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 = data.height
194
- width = data.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(args.lidar, PointCloud2, scan_cb, queue_size=1)
295
- rospy.Subscriber(args.camera, Image, image_cb, queue_size=1)
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
- pc = PointCloud.from_msg(msg).pc_data
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'].astype(np.uint32)
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 (msg.encoding == 'bgr8'):
49
- image = image[:, :, ::-1] # convert bgr to rgb
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.7
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
  ![q3dviewer Logo](imgs/logo.png)
@@ -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,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (75.3.2)
2
+ Generator: bdist_wheel (0.45.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -6,3 +6,4 @@ lidar_calib = q3dviewer.tools.lidar_calib:main
6
6
  lidar_cam_calib = q3dviewer.tools.lidar_cam_calib:main
7
7
  mesh_viewer = q3dviewer.tools.mesh_viewer:main
8
8
  ros_viewer = q3dviewer.tools.ros_viewer:main
9
+
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()