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.
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/PKG-INFO +1 -1
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/cloud_item.py +22 -20
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/mesh_item.py +49 -17
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/PKG-INFO +1 -1
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/setup.py +1 -1
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/README.md +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/Qt/__init__.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/__init__.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/base_glwidget.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/base_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/__init__.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/axis_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/cloud_io_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/frame_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/gaussian_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/grid_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/image_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/line_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/static_mesh_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/text3d_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/custom_items/text_item.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/glwidget.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/__init__.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/cloud_viewer.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/example_viewer.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/film_maker.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/gaussian_viewer.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/lidar_calib.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/lidar_cam_calib.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/tools/ros_viewer.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/__init__.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/cloud_io.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/convert_ros_msg.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/gl_helper.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/helpers.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/maths.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/utils/range_slider.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer/viewer.py +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/SOURCES.txt +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/dependency_links.txt +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/entry_points.txt +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/requires.txt +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/q3dviewer.egg-info/top_level.txt +0 -0
- {q3dviewer-1.2.4 → q3dviewer-1.2.5}/setup.cfg +0 -0
|
@@ -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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
138
|
+
if (index == self.MODE_TABLE['FLAT']): # flat color
|
|
137
139
|
self.edit_rgb.show()
|
|
138
|
-
elif (index == self.
|
|
140
|
+
elif (index == self.MODE_TABLE['I']): # intensity
|
|
139
141
|
self.slider_v.show()
|
|
140
|
-
elif (index == self.
|
|
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.
|
|
150
|
+
self.combo_color.setCurrentIndex(self.MODE_TABLE[color_mode])
|
|
149
151
|
except:
|
|
150
|
-
self.color_mode = self.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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 ==
|
|
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[
|
|
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] =
|
|
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
|
-
|
|
416
|
-
|
|
417
|
-
self.faces[:
|
|
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
|
-
|
|
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)"""
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|