q3dviewer 1.2.4__tar.gz → 1.2.5__tar.gz

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.
Files changed (44) hide show
  1. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/PKG-INFO +1 -1
  2. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/cloud_item.py +22 -20
  3. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/mesh_item.py +49 -17
  4. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/PKG-INFO +1 -1
  5. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/setup.py +1 -1
  6. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/README.md +0 -0
  7. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/Qt/__init__.py +0 -0
  8. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/__init__.py +0 -0
  9. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/base_glwidget.py +0 -0
  10. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/base_item.py +0 -0
  11. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/__init__.py +0 -0
  12. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/axis_item.py +0 -0
  13. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/cloud_io_item.py +0 -0
  14. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/frame_item.py +0 -0
  15. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/gaussian_item.py +0 -0
  16. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/grid_item.py +0 -0
  17. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/image_item.py +0 -0
  18. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/line_item.py +0 -0
  19. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/static_mesh_item.py +0 -0
  20. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/text3d_item.py +0 -0
  21. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/text_item.py +0 -0
  22. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/glwidget.py +0 -0
  23. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/__init__.py +0 -0
  24. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/cloud_viewer.py +0 -0
  25. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/example_viewer.py +0 -0
  26. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/film_maker.py +0 -0
  27. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/gaussian_viewer.py +0 -0
  28. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/lidar_calib.py +0 -0
  29. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/lidar_cam_calib.py +0 -0
  30. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/ros_viewer.py +0 -0
  31. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/__init__.py +0 -0
  32. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/cloud_io.py +0 -0
  33. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/convert_ros_msg.py +0 -0
  34. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/gl_helper.py +0 -0
  35. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/helpers.py +0 -0
  36. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/maths.py +0 -0
  37. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/range_slider.py +0 -0
  38. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/viewer.py +0 -0
  39. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/SOURCES.txt +0 -0
  40. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/dependency_links.txt +0 -0
  41. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/entry_points.txt +0 -0
  42. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/requires.txt +0 -0
  43. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/top_level.txt +0 -0
  44. {q3dviewer-1.2.4 → q3dviewer-1.2.5}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: q3dviewer
3
- Version: 1.2.4
3
+ Version: 1.2.5
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
@@ -39,32 +39,34 @@ class CloudItem(BaseItem):
39
39
  - 'SPHERE': Draw each point as a sphere in 3D space.
40
40
  depth_test (bool): Whether to enable depth testing. If True, points closer to the camera will appear in front of farther ones.
41
41
  """
42
+ # Class-level constants
43
+ STRIDE = 16 # Stride of cloud array in bytes
44
+ CAPACITY = 10000000 # Initial buffer capacity (10M points)
45
+ DATA_TYPE = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
46
+ MODE_TABLE = {'FLAT': 0, 'I': 1, 'RGB': 2, 'GRAY': 3}
47
+ POINT_TYPE_TABLE = {'PIXEL': 0, 'SQUARE': 1, 'SPHERE': 2}
48
+
42
49
  def __init__(self, size, alpha,
43
50
  color_mode='I',
44
51
  color='white',
45
52
  point_type='PIXEL'):
46
53
  super().__init__()
47
- self.STRIDE = 16 # stride of cloud array
48
54
  self.valid_buff_top = 0
49
55
  self.add_buff_loc = 0
50
56
  self.alpha = alpha
51
57
  self.size = size
52
58
  self.point_type = point_type
53
59
  self.mutex = threading.Lock()
54
- self.data_type = [('xyz', '<f4', (3,)), ('irgb', '<u4')]
55
60
  self.color = color
56
61
  try:
57
62
  self.flat_rgb = text_to_rgba(color, flat=True)
58
63
  except ValueError:
59
64
  print(f"Invalid color: {color}, please use matplotlib color format")
60
65
  exit(1)
61
- self.mode_table = {'FLAT': 0, 'I': 1, 'RGB': 2, 'GRAY': 3}
62
- self.point_type_table = {'PIXEL': 0, 'SQUARE': 1, 'SPHERE': 2}
63
- self.color_mode = self.mode_table[color_mode]
64
- self.CAPACITY = 10000000 # 10MB * 3 (x,y,z, color) * 4
66
+ self.color_mode = self.MODE_TABLE[color_mode]
65
67
  self.vmin = 0
66
68
  self.vmax = 255
67
- self.buff = np.empty((0), self.data_type)
69
+ self.buff = np.empty((0), self.DATA_TYPE)
68
70
  self.wait_add_data = None
69
71
  self.need_update_setting = True
70
72
  self.max_cloud_size = 300000000
@@ -78,7 +80,7 @@ class CloudItem(BaseItem):
78
80
  combo_ptype.addItem("pixels")
79
81
  combo_ptype.addItem("flat squares")
80
82
  combo_ptype.addItem("spheres")
81
- combo_ptype.setCurrentIndex(self.point_type_table[self.point_type])
83
+ combo_ptype.setCurrentIndex(self.POINT_TYPE_TABLE[self.point_type])
82
84
  combo_ptype.currentIndexChanged.connect(self._on_point_type_selection)
83
85
  layout.addWidget(combo_ptype)
84
86
 
@@ -88,7 +90,7 @@ class CloudItem(BaseItem):
88
90
  self.box_size.setValue(int(self.size))
89
91
  self.box_size.setRange(0, 100)
90
92
  self.box_size.valueChanged.connect(self.set_size)
91
- self._on_point_type_selection(self.point_type_table[self.point_type])
93
+ self._on_point_type_selection(self.POINT_TYPE_TABLE[self.point_type])
92
94
  layout.addWidget(self.box_size)
93
95
 
94
96
  box_alpha = QDoubleSpinBox()
@@ -133,11 +135,11 @@ class CloudItem(BaseItem):
133
135
  self.color_mode = index
134
136
  self.edit_rgb.hide()
135
137
  self.slider_v.hide()
136
- if (index == self.mode_table['FLAT']): # flat color
138
+ if (index == self.MODE_TABLE['FLAT']): # flat color
137
139
  self.edit_rgb.show()
138
- elif (index == self.mode_table['I']): # flat color
140
+ elif (index == self.MODE_TABLE['I']): # intensity
139
141
  self.slider_v.show()
140
- elif (index == self.mode_table['GRAY']): # flat color
142
+ elif (index == self.MODE_TABLE['GRAY']): # grayscale
141
143
  self.slider_v.show()
142
144
 
143
145
  self.need_update_setting = True
@@ -145,15 +147,15 @@ class CloudItem(BaseItem):
145
147
  def set_color_mode(self, color_mode):
146
148
  if color_mode in {'FLAT', 'RGB', 'I', 'GRAY'}:
147
149
  try:
148
- self.combo_color.setCurrentIndex(self.mode_table[color_mode])
150
+ self.combo_color.setCurrentIndex(self.MODE_TABLE[color_mode])
149
151
  except:
150
- self.color_mode = self.mode_table[color_mode]
152
+ self.color_mode = self.MODE_TABLE[color_mode]
151
153
  self.need_update_setting = True
152
154
  else:
153
155
  print(f"Invalid color mode: {color_mode}")
154
156
 
155
157
  def _on_point_type_selection(self, index):
156
- self.point_type = list(self.point_type_table.keys())[index]
158
+ self.point_type = list(self.POINT_TYPE_TABLE.keys())[index]
157
159
  if self.point_type == 'PIXEL':
158
160
  self.box_size.setPrefix("Set size (pixel): ")
159
161
  else:
@@ -184,7 +186,7 @@ class CloudItem(BaseItem):
184
186
  self.need_update_setting = True
185
187
 
186
188
  def clear(self):
187
- data = np.empty((0), self.data_type)
189
+ data = np.empty((0), self.DATA_TYPE)
188
190
  self.set_data(data)
189
191
 
190
192
  def set_data(self, data, append=False):
@@ -193,7 +195,7 @@ class CloudItem(BaseItem):
193
195
 
194
196
  if data.dtype in {np.dtype('float32'), np.dtype('float64')}:
195
197
  if data.size == 0:
196
- data = np.empty((0), self.data_type)
198
+ data = np.empty((0), self.DATA_TYPE)
197
199
  elif data.ndim == 2 and data.shape[1] >= 3:
198
200
  xyz = data[:, :3]
199
201
  if data.shape[1] >= 4:
@@ -201,7 +203,7 @@ class CloudItem(BaseItem):
201
203
  else:
202
204
  color = np.zeros(data.shape[0], dtype=np.uint32)
203
205
  data = np.rec.fromarrays(
204
- [xyz, color[:data.shape[0]]], dtype=self.data_type)
206
+ [xyz, color[:data.shape[0]]], dtype=self.DATA_TYPE)
205
207
 
206
208
  with self.mutex:
207
209
  if append:
@@ -226,7 +228,7 @@ class CloudItem(BaseItem):
226
228
  set_uniform(self.program, float(self.vmin), 'vmin')
227
229
  set_uniform(self.program, float(self.alpha), 'alpha')
228
230
  set_uniform(self.program, int(self.size), 'point_size')
229
- set_uniform(self.program, int(self.point_type_table[self.point_type]), 'point_type')
231
+ set_uniform(self.program, int(self.POINT_TYPE_TABLE[self.point_type]), 'point_type')
230
232
  glUseProgram(0)
231
233
  self.need_update_setting = False
232
234
 
@@ -245,7 +247,7 @@ class CloudItem(BaseItem):
245
247
  buff_capacity += self.CAPACITY
246
248
  if Q3D_DEBUG is not None:
247
249
  print("[Cloud Item] Update capacity to %d" % buff_capacity)
248
- new_buff = np.empty((buff_capacity), self.data_type)
250
+ new_buff = np.empty((buff_capacity), self.DATA_TYPE)
249
251
  new_buff[:self.add_buff_loc] = self.buff[:self.add_buff_loc]
250
252
  new_buff[self.add_buff_loc:new_buff_top] = self.wait_add_data
251
253
  self.buff = new_buff
@@ -24,15 +24,21 @@ class MeshItem(BaseItem):
24
24
  color (str or tuple): Accepts any valid matplotlib color (e.g., 'red', '#FF4500', (1.0, 0.5, 0.0)).
25
25
  wireframe (bool): If True, renders the mesh in wireframe mode.
26
26
  """
27
+ # Class-level constants
28
+ FACE_CAPACITY = 1000000 # Initial capacity for faces
29
+ BIG_INT = 2**31 - 1 # Sentinel value for dirty region tracking
30
+ FACE_INPUT_DTYPE = np.dtype([
31
+ ('key', np.int64),
32
+ ('vertices', np.float32, (12,)),
33
+ ('good', np.uint8)
34
+ ])
35
+
27
36
  def __init__(self, color='lightblue', wireframe=False):
28
37
  super(MeshItem, self).__init__()
29
38
  self.wireframe = wireframe
30
39
  self.color = color
31
40
  self.flat_rgb = text_to_rgba(color, flat=True)
32
41
 
33
- # Incremental buffer management
34
- self.FACE_CAPACITY = 1000000 # Initial capacity for faces
35
-
36
42
  # Faces buffer: N x 13 numpy array
37
43
  # Each row: [v0.x, v0.y, v0.z, v1.x, v1.y, v1.z, v2.x, v2.y, v2.z, v3.x, v3.y, v3.z, good]
38
44
  self.faces = np.zeros((self.FACE_CAPACITY, 13), dtype=np.float32)
@@ -40,6 +46,12 @@ class MeshItem(BaseItem):
40
46
  # valid_f_top: pointer to end of valid faces
41
47
  self.valid_f_top = 0
42
48
 
49
+ # Dirty region tracking for efficient GPU updates
50
+ # dirty_min: start index of modified region (inclusive)
51
+ # dirty_max: end index of modified region (exclusive)
52
+ self.dirty_min = self.BIG_INT
53
+ self.dirty_max = 0
54
+
43
55
  # key2index: mapping from face_key to face buffer index
44
56
  self.key2index = {} # {face_key: face_index}
45
57
 
@@ -190,14 +202,8 @@ class MeshItem(BaseItem):
190
202
  if not isinstance(data, np.ndarray):
191
203
  raise ValueError("Data must be a numpy array")
192
204
 
193
- want_dtype = np.dtype([
194
- ('key', np.int64),
195
- ('vertices', np.float32, (12,)),
196
- ('good', np.uint32)
197
- ])
198
-
199
205
  # Structured array format -> use incremental path (has keys for updates)
200
- if data.dtype == want_dtype:
206
+ if data.dtype == self.FACE_INPUT_DTYPE:
201
207
  self.set_incremental_data(data)
202
208
  return
203
209
 
@@ -256,6 +262,10 @@ class MeshItem(BaseItem):
256
262
  self.clear_mesh()
257
263
  self.faces = faces
258
264
  self.valid_f_top = num_faces
265
+
266
+ # Mark entire buffer as dirty
267
+ self.dirty_min = 0
268
+ self.dirty_max = self.valid_f_top
259
269
  self.need_update_buffer = True
260
270
 
261
271
 
@@ -304,6 +314,11 @@ class MeshItem(BaseItem):
304
314
  update_keys = keys[update_mask]
305
315
  update_indices = np.array([self.key2index[key] for key in update_keys], dtype=np.int32)
306
316
  self.faces[update_indices] = face_data[update_mask]
317
+
318
+ # Update dirty region for modified faces
319
+ self.dirty_min = min(self.dirty_min, int(np.min(update_indices)))
320
+ self.dirty_max = max(self.dirty_max, int(np.max(update_indices) + 1))
321
+ self.need_update_buffer = True
307
322
 
308
323
  # Batch insert new faces
309
324
  if np.any(new_mask):
@@ -311,13 +326,17 @@ class MeshItem(BaseItem):
311
326
  new_face_data = face_data[new_mask]
312
327
  n_new = len(new_keys)
313
328
 
329
+ # Update dirty region for new faces
330
+ start_index = self.valid_f_top
314
331
  # Insert data
315
- self.faces[self.valid_f_top: self.valid_f_top + n_new] = new_face_data
332
+ self.faces[start_index: start_index + n_new] = new_face_data
316
333
 
317
334
  # Update key2index mapping for new faces
318
335
  for i, face_key in enumerate(new_keys):
319
- self.key2index[face_key] = self.valid_f_top + i
336
+ self.key2index[face_key] = start_index + i
320
337
  self.valid_f_top += n_new
338
+ self.dirty_min = min(self.dirty_min, start_index)
339
+ self.dirty_max = max(self.dirty_max, start_index + n_new)
321
340
  self.need_update_buffer = True
322
341
 
323
342
  def _expand_face_buffer(self):
@@ -330,6 +349,8 @@ class MeshItem(BaseItem):
330
349
  def clear_mesh(self):
331
350
  """Clear all mesh data and reset buffers"""
332
351
  self.valid_f_top = 0
352
+ self.dirty_min = self.BIG_INT
353
+ self.dirty_max = 0
333
354
  self.key2index.clear()
334
355
  if hasattr(self, 'indices_array'):
335
356
  self.indices_array = np.array([], dtype=np.uint32)
@@ -408,16 +429,27 @@ class MeshItem(BaseItem):
408
429
  glBindBuffer(GL_ARRAY_BUFFER, 0)
409
430
  self._gpu_face_capacity = len(self.faces)
410
431
 
411
- # Upload faces to VBO
432
+ # Upload faces to VBO (only dirty region)
412
433
  if self.need_update_buffer:
413
434
  glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
435
+
436
+ # Calculate the range to upload [dirty_min, dirty_max)
437
+ start_index = int(self.dirty_min)
438
+ end_index = int(self.dirty_max)
439
+ count = end_index - start_index
440
+
441
+ # Upload only the modified region
414
442
  glBufferSubData(GL_ARRAY_BUFFER,
415
- 0,
416
- self.valid_f_top * 13 * 4, # 13 floats * 4 bytes per face
417
- self.faces[:self.valid_f_top])
443
+ start_index * 13 * 4, # offset in bytes
444
+ count * 13 * 4, # size in bytes
445
+ self.faces[start_index:end_index])
446
+
418
447
  glBindBuffer(GL_ARRAY_BUFFER, 0)
419
448
  self.need_update_buffer = False
420
- glBindBuffer(GL_ARRAY_BUFFER, 0)
449
+
450
+ # Reset dirty region
451
+ self.dirty_min = self.BIG_INT
452
+ self.dirty_max = 0
421
453
 
422
454
  def update_setting(self):
423
455
  """Set fixed rendering parameters (called once during initialization)"""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: q3dviewer
3
- Version: 1.2.4
3
+ Version: 1.2.5
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
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
2
2
 
3
3
  setup(
4
4
  name='q3dviewer',
5
- version='1.2.4',
5
+ version='1.2.5',
6
6
  author="Liu Yang",
7
7
  author_email="liu.yang@jp.panasonic.com",
8
8
  license="MIT",
File without changes
File without changes
File without changes