q3dviewer 1.2.2__py3-none-any.whl → 1.2.3__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.
@@ -10,148 +10,75 @@ import numpy as np
10
10
  from q3dviewer.base_item import BaseItem
11
11
  from OpenGL.GL import *
12
12
  from OpenGL.GL import shaders
13
- from q3dviewer.Qt.QtWidgets import QLabel, QCheckBox, QDoubleSpinBox, QSlider, QHBoxLayout, QLineEdit, QComboBox
14
- import matplotlib.colors as mcolors
13
+ from q3dviewer.Qt.QtWidgets import QLabel, QCheckBox, QDoubleSpinBox, QSlider, QHBoxLayout, QLineEdit
14
+
15
15
  import os
16
16
  from q3dviewer.utils import set_uniform, text_to_rgba
17
+ import time
17
18
 
18
19
 
19
20
 
20
21
  class MeshItem(BaseItem):
21
22
  """
22
- An OpenGL mesh item for rendering triangulated 3D surfaces.
23
-
23
+ A OpenGL mesh item for rendering 3D triangular meshes.
24
24
  Attributes:
25
- color (str or tuple): The flat color to use when `color_mode` is 'FLAT'.
26
- Accepts any valid matplotlib color (e.g., 'lightblue', 'red', '#FF4500', (1.0, 0.5, 0.0)).
27
- wireframe (bool): If True, render the mesh in wireframe mode (edges only).
28
- If False, render filled triangles. Default is False.
29
- enable_lighting (bool): Whether to enable Phong lighting for the mesh.
30
- If True, the mesh will be shaded based on light direction and material properties.
31
- If False, the mesh will use flat shading with object colors only. Default is True.
32
- color_mode (str): The coloring mode for mesh vertices.
33
- - 'FLAT': Single flat color for all vertices (uses the `color` attribute).
34
- - 'I': Color by intensity channel from per-vertex colors (rainbow gradient).
35
- - 'RGB': Per-vertex RGB color from per-vertex color data.
36
- alpha (float): The transparency of the mesh, in the range [0, 1],
37
- where 0 is fully transparent and 1 is fully opaque. Default is 1.0.
38
- line_width (float): The width of lines when rendering in wireframe mode.
39
- Range is typically 0.5 to 5.0. Default is 1.0.
40
-
41
- Material Properties (Phong Lighting):
42
- ambient_strength (float): Ambient light contribution [0.0-1.0]. Default is 0.1.
43
- diffuse_strength (float): Diffuse light contribution [0.0-2.0]. Default is 1.2.
44
- specular_strength (float): Specular highlight contribution [0.0-2.0]. Default is 0.1.
45
- shininess (float): Specular shininess exponent [1-256]. Higher values = smaller highlights. Default is 32.0.
46
-
47
- Methods:
48
- set_data(verts, faces, colors=None): Set mesh geometry and optional per-vertex colors.
49
- - verts: np.ndarray of shape (N, 3) - vertex positions
50
- - faces: np.ndarray of shape (M, 3) with uint32 indices - triangle indices
51
- - colors: np.ndarray of shape (N,) with uint32 IRGB format (optional)
52
- uint32 format: I (bits 24-31), R (bits 16-23), G (bits 8-15), B (bits 0-7)
53
-
54
- Example:
55
- # Create a simple triangle mesh with per-vertex colors
56
- verts = np.array([[0,0,0], [1,0,0], [0,1,0]], dtype=np.float32)
57
- faces = np.array([[0,1,2]], dtype=np.uint32)
58
- colors = np.array([
59
- (255 << 24) | (255 << 16) | (0 << 8) | 0, # Red, intensity=255
60
- (200 << 24) | (0 << 16) | (255 << 8) | 0, # Green, intensity=200
61
- (150 << 24) | (0 << 16) | (0 << 8) | 255 # Blue, intensity=150
62
- ], dtype=np.uint32)
63
-
64
- mesh = q3d.MeshItem(color='lightblue', color_mode='RGB', enable_lighting=True)
65
- mesh.set_data(verts, faces, colors)
25
+ color (str or tuple): Accepts any valid matplotlib color (e.g., 'red', '#FF4500', (1.0, 0.5, 0.0)).
26
+ wireframe (bool): If True, renders the mesh in wireframe mode.
66
27
  """
67
- def __init__(self, color='lightblue', wireframe=False, enable_lighting=True, color_mode='FLAT'):
28
+ def __init__(self, color='lightblue', wireframe=False):
68
29
  super(MeshItem, self).__init__()
30
+ self.wireframe = wireframe
69
31
  self.color = color
70
32
  self.flat_rgb = text_to_rgba(color, flat=True)
71
- self.wireframe = wireframe
72
- self.enable_lighting = enable_lighting
73
33
 
74
- # Mesh data
75
- self.triangles = None
76
- self.normals = None
77
- self.vertex_colors = None # Per-vertex colors (uint32 IRGB format)
34
+ # Incremental buffer management
35
+ self.FACE_CAPACITY = 1000000 # Initial capacity for faces
36
+
37
+ # Faces buffer: N x 13 numpy array
38
+ # 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]
39
+ self.faces = np.zeros((self.FACE_CAPACITY, 13), dtype=np.float32)
40
+
41
+ # valid_f_top: pointer to end of valid faces
42
+ self.valid_f_top = 0
78
43
 
79
- self.mode_table = {'FLAT': 0, 'I': 1, 'RGB': 2}
80
- self.color_mode = self.mode_table[color_mode]
81
- self.vmin = 0
82
- self.vmax = 255
44
+ # key2index: mapping from face_key to face buffer index
45
+ self.key2index = {} # {face_key: face_index}
83
46
 
84
47
  # OpenGL objects
85
48
  self.vao = None
86
- self.vbo_vertices = None
87
- self.vbo_normals = None
88
- self.vbo_colors = None
49
+ self.vbo = None
89
50
  self.program = None
51
+ self._gpu_face_capacity = 0 # Track GPU buffer capacity
90
52
 
91
- # Rendering parameters
53
+ # Fixed rendering parameters (not adjustable via UI)
54
+ self.enable_lighting = True
92
55
  self.line_width = 1.0
93
56
  self.light_pos = [1.0, 1.0, 1.0]
94
57
  self.light_color = [1.0, 1.0, 1.0]
95
-
96
- # Phong lighting material properties
97
58
  self.ambient_strength = 0.1
98
59
  self.diffuse_strength = 1.2
99
60
  self.specular_strength = 0.1
100
61
  self.shininess = 32.0
101
- # Alpha (opacity)
102
62
  self.alpha = 1.0
103
63
 
104
- # Buffer initialization flag
105
- self.need_update_buffer = True
64
+ # Settings flag
106
65
  self.need_update_setting = True
107
66
  self.path = os.path.dirname(__file__)
108
67
 
109
68
 
110
69
  def add_setting(self, layout):
111
70
  """Add UI controls for mesh visualization"""
112
- # Wireframe toggle
71
+ # Only keep wireframe toggle - all other parameters are fixed
113
72
  self.wireframe_box = QCheckBox("Wireframe Mode")
114
73
  self.wireframe_box.setChecked(self.wireframe)
115
74
  self.wireframe_box.toggled.connect(self.update_wireframe)
116
75
  layout.addWidget(self.wireframe_box)
117
-
76
+
118
77
  # Enable lighting toggle
119
78
  self.lighting_box = QCheckBox("Enable Lighting")
120
79
  self.lighting_box.setChecked(self.enable_lighting)
121
80
  self.lighting_box.toggled.connect(self.update_enable_lighting)
122
81
  layout.addWidget(self.lighting_box)
123
-
124
- # Line width control
125
- line_width_label = QLabel("Line Width:")
126
- layout.addWidget(line_width_label)
127
- self.line_width_box = QDoubleSpinBox()
128
- self.line_width_box.setRange(0.5, 5.0)
129
- self.line_width_box.setSingleStep(0.5)
130
- self.line_width_box.setValue(self.line_width)
131
- self.line_width_box.valueChanged.connect(self.update_line_width)
132
- layout.addWidget(self.line_width_box)
133
-
134
- # Alpha control
135
- alpha_label = QLabel("Alpha:")
136
- layout.addWidget(alpha_label)
137
- alpha_box = QDoubleSpinBox()
138
- alpha_box.setRange(0.0, 1.0)
139
- alpha_box.setSingleStep(0.05)
140
- alpha_box.setValue(self.alpha)
141
- alpha_box.valueChanged.connect(self.update_alpha)
142
- layout.addWidget(alpha_box)
143
-
144
-
145
- # Color mode selection
146
- label_color = QLabel("Color Mode:")
147
- layout.addWidget(label_color)
148
- self.combo_color = QComboBox()
149
- self.combo_color.addItem("flat color")
150
- self.combo_color.addItem("intensity")
151
- self.combo_color.addItem("RGB")
152
- self.combo_color.setCurrentIndex(self.color_mode)
153
- self.combo_color.currentIndexChanged.connect(self._on_color_mode)
154
- layout.addWidget(self.combo_color)
155
82
 
156
83
  label_rgb = QLabel("Color:")
157
84
  label_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
@@ -162,7 +89,6 @@ class MeshItem(BaseItem):
162
89
  self.edit_rgb.textChanged.connect(self._on_color)
163
90
  layout.addWidget(self.edit_rgb)
164
91
 
165
-
166
92
  # Material property controls for Phong lighting
167
93
  if self.enable_lighting:
168
94
  # Ambient strength control (slider 0-100 mapped to 0.0-1.0)
@@ -221,12 +147,6 @@ class MeshItem(BaseItem):
221
147
  except ValueError:
222
148
  pass
223
149
 
224
- def _on_color_mode(self, index):
225
- print(f"Color mode1 : {index}")
226
- self.color_mode = index
227
- self.edit_rgb.setVisible(index == self.mode_table['FLAT'])
228
- self.need_update_setting = True
229
-
230
150
  def update_wireframe(self, value):
231
151
  self.wireframe = value
232
152
 
@@ -258,135 +178,233 @@ class MeshItem(BaseItem):
258
178
  """Update mesh alpha (opacity)"""
259
179
  self.alpha = float(value)
260
180
  self.need_update_setting = True
261
-
262
- def set_data(self, verts, faces, colors=None):
181
+
182
+ def set_data(self, data):
183
+ """
184
+ Set complete mesh data at once.
185
+
186
+ Args:
187
+ data: is Nx3 numpy array (N must be divisible by 3) or dict
188
+ if is dict, uses the dict format:
189
+ [face_key: (v0.x, v0.y, v0.z, ..., v3.z, good)]
190
+ """
191
+ self.clear_mesh()
192
+
193
+ if isinstance(data, dict):
194
+ # Use dict format directly (from get_mesh_data/get_incremental_mesh_data)
195
+ self.set_incremental_data(data)
196
+ return
197
+
198
+ # Check if Nx3 array
199
+
200
+ if not isinstance(data, np.ndarray):
201
+ raise ValueError("Invalid data type")
202
+
203
+ good_format = False
204
+ if data.ndim == 2 and \
205
+ data.shape[1] == 3 and \
206
+ data.shape[0] % 3 == 0:
207
+ good_format = True
208
+
209
+ if not good_format:
210
+ raise ValueError("Invalid data shape")
211
+
212
+ # Convert to Nx13 numpy array
213
+ N = data.shape[0] // 3
214
+ faces = np.zeros((N, 13), dtype=np.float32)
215
+ tmp = data.reshape(N, 9)
216
+ faces[:, 0:3] = tmp[:, 0:3] # copy v0
217
+ faces[:, 3:6] = tmp[:, 3:6] # copy v1
218
+ faces[:, 6:9] = tmp[:, 6:9] # copy v2
219
+ faces[:, 9:12] = tmp[:, 6:9] # copy v3 from v2 (degenerate quad)
220
+ faces[:, 12] = 1.0 # set good=1.0
221
+ self.faces = faces
222
+ self.valid_f_top = N
223
+
224
+
225
+ def set_incremental_data(self, fs):
263
226
  """
264
- verts: np.ndarray of shape (N, 3)
265
- faces: np.ndarray of shape (M, 3) with uint32 indices
266
- colors: np.ndarray of shape (N,) with uint32 IRGB format (optional)
267
- uint32 contains: I (bits 24-31), R (bits 16-23), G (bits 8-15), B (bits 0-7)
227
+ Incrementally update mesh with new face data.
228
+ Args:
229
+ fs: Dict {face_key: (v0.x, v0.y, v0.z, v1.x, v1.y, v1.z,
230
+ v2.x, v2.y, v2.z, v3.x, v3.y, v3.z, good), ...}
231
+ 13-tuple with vertex positions and good flag (0.0 or 1.0)
232
+ If good==1:
233
+ Triangle 1: (v0, v1, v3)
234
+ Triangle 2: (v0, v3, v2)
235
+ Updates:
236
+ - faces: updates existing faces or appends new ones
237
+ - key2index: tracks face_key -> face_index mapping
268
238
  """
269
- verts = np.asarray(verts, dtype=np.float32)
270
- faces = np.asarray(faces, dtype=np.uint32)
271
- triangles = verts[faces.flatten()]
272
-
273
- if colors is not None:
274
- colors = np.asarray(colors, dtype=np.uint32)
275
- if len(colors) == len(verts):
276
- # Expand per-vertex colors to per-triangle-vertex
277
- self.vertex_colors = colors[faces.flatten()]
239
+ if not fs:
240
+ return
241
+
242
+ # Ensure enough capacity in faces buffer
243
+ # wasted cases are better than frequent expansions
244
+ while self.valid_f_top + len(fs) > len(self.faces):
245
+ self._expand_face_buffer()
246
+
247
+ # Optimization: Separate updates from new insertions to avoid
248
+ # dictionary lookup performance degradation during growth
249
+ update_idxs = [] # [idx, ...]
250
+ update_data = [] # [face_data, ...]
251
+ new_keys = [] # [key, ...]
252
+ new_data = [] # [face_data, ...]
253
+
254
+ for face_key, face_data in fs.items():
255
+ face_idx = self.key2index.get(face_key)
256
+ if face_idx is not None:
257
+ update_idxs.append(face_idx)
258
+ update_data.append(face_data)
278
259
  else:
279
- self.vertex_colors = None
280
- else:
281
- self.vertex_colors = None
282
-
283
- self.triangles = np.asarray(triangles, dtype=np.float32)
284
- self.normals = self.calculate_normals()
285
- self.need_update_buffer = True
286
-
287
- def calculate_normals(self):
288
- if self.triangles is None or len(self.triangles) == 0:
289
- return None
290
-
291
- # Ensure we have complete triangles
292
- num_vertices = len(self.triangles)
293
- num_triangles = num_vertices // 3
294
- if num_triangles == 0:
295
- return None
296
-
297
- # Reshape vertices into triangles (N, 3, 3) where N is number of triangles
298
- vertices_reshaped = self.triangles[:num_triangles * 3].reshape(-1, 3, 3)
299
-
300
- v0 = vertices_reshaped[:, 0, :]
301
- v1 = vertices_reshaped[:, 1, :]
302
- v2 = vertices_reshaped[:, 2, :]
303
-
304
- # Calculate edges for all triangles at once
305
- edge1 = v1 - v0
306
- edge2 = v2 - v0
307
-
308
- face_normals = np.cross(edge1, edge2)
309
-
310
- norms = np.linalg.norm(face_normals, axis=1, keepdims=True)
311
- norms[norms < 1e-6] = 1.0
312
- face_normals = face_normals / norms
313
-
314
- normals_per_vertex = np.repeat(face_normals[:, np.newaxis, :], 3, axis=1)
315
- normals = normals_per_vertex.reshape(-1, 3)
316
- return normals.astype(np.float32)
260
+ new_keys.append(face_key)
261
+ new_data.append(face_data)
262
+
263
+ # Batch update existing faces
264
+ if update_data:
265
+ indices = np.array(update_idxs, dtype=np.int32)
266
+ data = np.array(update_data, dtype=np.float32)
267
+ self.faces[indices] = data
268
+
269
+ # Batch insert new faces
270
+ if new_data:
271
+ n_new = len(new_data)
272
+ data = np.array(new_data, dtype=np.float32)
273
+ self.faces[self.valid_f_top: self.valid_f_top + n_new] = data
274
+
275
+ # Update key2index mapping for new faces
276
+ for i, face_key in enumerate(new_keys):
277
+ self.key2index[face_key] = self.valid_f_top + i
317
278
 
279
+ self.valid_f_top += n_new
280
+
281
+ def _expand_face_buffer(self):
282
+ """Expand the faces buffer when capacity is reached"""
283
+ new_capacity = len(self.faces) + self.FACE_CAPACITY
284
+ new_buffer = np.zeros((new_capacity, 13), dtype=np.float32)
285
+ new_buffer[:len(self.faces)] = self.faces
286
+ self.faces = new_buffer
287
+
288
+ def clear_mesh(self):
289
+ """Clear all mesh data and reset buffers"""
290
+ self.valid_f_top = 0
291
+ self.key2index.clear()
292
+ if hasattr(self, 'indices_array'):
293
+ self.indices_array = np.array([], dtype=np.uint32)
294
+
318
295
  def initialize_gl(self):
319
296
  """OpenGL initialization"""
320
- vertex_shader = open(self.path + '/../shaders/mesh_vert.glsl', 'r').read()
321
- fragment_shader = open(self.path + '/../shaders/mesh_frag.glsl', 'r').read()
322
-
323
- program = shaders.compileProgram(
324
- shaders.compileShader(vertex_shader, GL_VERTEX_SHADER),
325
- shaders.compileShader(fragment_shader, GL_FRAGMENT_SHADER),
326
- )
327
- self.program = program
297
+ # Use instanced mesh shaders with geometry shader for GPU-side triangle generation
298
+ vert_shader = open(self.path + '/../shaders/mesh_vert.glsl', 'r').read()
299
+ geom_shader = open(self.path + '/../shaders/mesh_geom.glsl', 'r').read()
300
+ frag_shader = open(self.path + '/../shaders/mesh_frag.glsl', 'r').read()
301
+ try:
302
+ program = shaders.compileProgram(
303
+ shaders.compileShader(vert_shader, GL_VERTEX_SHADER),
304
+ shaders.compileShader(geom_shader, GL_GEOMETRY_SHADER),
305
+ shaders.compileShader(frag_shader, GL_FRAGMENT_SHADER),
306
+ )
307
+ self.program = program
308
+ except Exception as e:
309
+ raise
328
310
 
329
311
  def update_render_buffer(self):
330
- """Initialize OpenGL buffers"""
331
- if not self.need_update_buffer:
312
+ """
313
+ Update GPU buffer with face data (no separate vertex buffer).
314
+ Each face contains embedded vertex positions (13 floats).
315
+ Geometry shader generates triangles on GPU from face vertices.
316
+ Dynamically resizes GPU buffer when Python buffer expands.
317
+ """
318
+ if self.valid_f_top == 0:
332
319
  return
333
-
334
- # Generate VAO and VBOs
320
+
321
+ # Initialize buffers on first call
335
322
  if self.vao is None:
336
323
  self.vao = glGenVertexArrays(1)
337
- self.vbo_vertices = glGenBuffers(1)
338
- self.vbo_normals = glGenBuffers(1)
339
- self.vbo_colors = glGenBuffers(1)
340
-
341
- glBindVertexArray(self.vao)
342
-
343
- # Vertex buffer
344
- glBindBuffer(GL_ARRAY_BUFFER, self.vbo_vertices)
345
- glBufferData(GL_ARRAY_BUFFER, self.triangles.nbytes, self.triangles, GL_STATIC_DRAW)
346
- glEnableVertexAttribArray(0)
347
- glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, None)
348
-
349
- # Normal buffer
350
- if self.normals is not None:
351
- glBindBuffer(GL_ARRAY_BUFFER, self.vbo_normals)
352
- glBufferData(GL_ARRAY_BUFFER, self.normals.nbytes, self.normals, GL_STATIC_DRAW)
324
+ self.vbo = glGenBuffers(1)
325
+ self._gpu_face_capacity = 0
326
+
327
+ # Check if we need to reallocate VBO for faces
328
+ if self._gpu_face_capacity < len(self.faces):
329
+ glBindVertexArray(self.vao)
330
+ glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
331
+ glBufferData(GL_ARRAY_BUFFER,
332
+ self.faces.nbytes,
333
+ None,
334
+ GL_DYNAMIC_DRAW)
335
+
336
+ # Setup face attributes (per-instance)
337
+ # Face data: [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]
338
+ # 13 floats = 52 bytes stride
339
+
340
+ # v0 (location 1) - vec3
353
341
  glEnableVertexAttribArray(1)
354
- glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 0, None)
355
-
356
- # Color buffer (uint32 IRGB format)
357
- if self.vertex_colors is not None:
358
- glBindBuffer(GL_ARRAY_BUFFER, self.vbo_colors)
359
- glBufferData(GL_ARRAY_BUFFER, self.vertex_colors.nbytes, self.vertex_colors, GL_STATIC_DRAW)
342
+ glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 52, ctypes.c_void_p(0))
343
+ glVertexAttribDivisor(1, 1)
344
+
345
+ # v1 (location 2) - vec3
360
346
  glEnableVertexAttribArray(2)
361
- glVertexAttribIPointer(2, 1, GL_UNSIGNED_INT, 0, None)
362
-
363
- glBindVertexArray(0)
364
- self.need_update_buffer = False
347
+ glVertexAttribPointer(2, 3, GL_FLOAT, GL_FALSE, 52, ctypes.c_void_p(12))
348
+ glVertexAttribDivisor(2, 1)
349
+
350
+ # v2 (location 3) - vec3
351
+ glEnableVertexAttribArray(3)
352
+ glVertexAttribPointer(3, 3, GL_FLOAT, GL_FALSE, 52, ctypes.c_void_p(24))
353
+ glVertexAttribDivisor(3, 1)
354
+
355
+ # v3 (location 4) - vec3
356
+ glEnableVertexAttribArray(4)
357
+ glVertexAttribPointer(4, 3, GL_FLOAT, GL_FALSE, 52, ctypes.c_void_p(36))
358
+ glVertexAttribDivisor(4, 1)
359
+
360
+ # good flag (location 5) - float
361
+ glEnableVertexAttribArray(5)
362
+ glVertexAttribPointer(5, 1, GL_FLOAT, GL_FALSE, 52, ctypes.c_void_p(48))
363
+ glVertexAttribDivisor(5, 1)
364
+
365
+ glBindVertexArray(0)
366
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
367
+ self._gpu_face_capacity = len(self.faces)
368
+
369
+ # Upload faces to VBO
370
+ glBindBuffer(GL_ARRAY_BUFFER, self.vbo)
371
+ glBufferSubData(GL_ARRAY_BUFFER,
372
+ 0,
373
+ self.valid_f_top * 13 * 4, # 13 floats * 4 bytes per face
374
+ self.faces[:self.valid_f_top])
375
+ glBindBuffer(GL_ARRAY_BUFFER, 0)
365
376
 
366
377
  def update_setting(self):
367
- if (self.need_update_setting is False):
378
+ """Set fixed rendering parameters (called once during initialization)"""
379
+ if not self.need_update_setting:
368
380
  return
381
+ # Set fixed uniforms for instanced shaders
369
382
  set_uniform(self.program, int(self.enable_lighting), 'if_light')
370
383
  set_uniform(self.program, 1, 'two_sided')
371
- set_uniform(self.program, np.array(self.light_color), 'light_color')
384
+
385
+ set_uniform(self.program, np.array(self.light_color, dtype=np.float32), 'light_color')
372
386
  set_uniform(self.program, float(self.ambient_strength), 'ambient_strength')
373
387
  set_uniform(self.program, float(self.diffuse_strength), 'diffuse_strength')
374
388
  set_uniform(self.program, float(self.specular_strength), 'specular_strength')
375
389
  set_uniform(self.program, float(self.shininess), 'shininess')
376
390
  set_uniform(self.program, float(self.alpha), 'alpha')
377
391
  set_uniform(self.program, int(self.flat_rgb), 'flat_rgb')
378
- set_uniform(self.program, int(self.color_mode), 'color_mode')
379
- set_uniform(self.program, float(self.vmin), 'vmin')
380
- set_uniform(self.program, float(self.vmax), 'vmax')
381
392
  self.need_update_setting = False
382
393
 
383
394
  def paint(self):
384
- """Render the mesh using modern OpenGL with shaders"""
385
- if self.triangles is None or len(self.triangles) == 0:
395
+ """
396
+ Render the mesh using instanced rendering with geometry shader.
397
+ Each face instance is rendered as a point, geometry shader generates 2 triangles.
398
+ GPU filters faces based on good flag.
399
+ """
400
+ if self.valid_f_top == 0:
386
401
  return
402
+
387
403
  glUseProgram(self.program)
404
+
388
405
  self.update_render_buffer()
389
406
  self.update_setting()
407
+
390
408
  view_matrix = self.glwidget().view_matrix
391
409
  set_uniform(self.program, view_matrix, 'view')
392
410
  project_matrix = self.glwidget().projection_matrix
@@ -394,30 +412,29 @@ class MeshItem(BaseItem):
394
412
  view_pos = self.glwidget().center
395
413
  set_uniform(self.program, np.array(view_pos), 'view_pos')
396
414
 
397
-
398
415
  # Enable blending and depth testing
399
416
  glEnable(GL_BLEND)
400
417
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
401
418
  glEnable(GL_DEPTH_TEST)
402
419
  glDisable(GL_CULL_FACE) # two-sided rendering
403
-
420
+
404
421
  # Set line width
405
422
  glLineWidth(self.line_width)
406
423
 
407
- # Bind VAO and render
424
+ # Bind VAO (vertex positions are now in VBO attributes)
408
425
  glBindVertexArray(self.vao)
409
-
410
- if len(self.triangles) > 0:
411
- # Render faces
412
- if self.wireframe:
413
- glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
414
- else:
415
- glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
416
426
 
417
- # Draw triangles
418
- glDrawArrays(GL_TRIANGLES, 0, len(self.triangles))
427
+ if self.wireframe:
428
+ glPolygonMode(GL_FRONT_AND_BACK, GL_LINE)
429
+ else:
419
430
  glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
420
-
431
+
432
+ # Draw using instanced rendering
433
+ # Input: POINTS (one per face instance)
434
+ # Geometry shader generates 2 triangles (6 vertices) per point
435
+ glDrawArraysInstanced(GL_POINTS, 0, 1, self.valid_f_top)
436
+
437
+ glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
421
438
  glBindVertexArray(0)
422
439
  glDisable(GL_DEPTH_TEST)
423
440
  glDisable(GL_BLEND)
q3dviewer/glwidget.py CHANGED
@@ -55,6 +55,7 @@ class GLWidget(BaseGLWidget):
55
55
  self.followable_item_name = None
56
56
  self.setting_window = SettingWindow()
57
57
  self.enable_show_center = True
58
+ self.old_center = None
58
59
  super(GLWidget, self).__init__()
59
60
 
60
61
  def keyPressEvent(self, ev: QKeyEvent):
@@ -63,14 +64,38 @@ class GLWidget(BaseGLWidget):
63
64
  self.open_setting_window()
64
65
  else:
65
66
  super().keyPressEvent(ev)
67
+ if ev.key() == QtCore.Qt.Key_F: # reset follow
68
+ if self.followable_item_name is None:
69
+ self.initial_followable()
70
+
71
+ if self.followed_name != 'none':
72
+ self.followed_name = 'none'
73
+ print("Reset follow.")
74
+ elif len(self.followable_item_name) > 1:
75
+ self.followed_name = self.followable_item_name[1]
76
+ print("Set follow to ", self.followed_name)
77
+ else:
78
+ pass # do nothing
66
79
 
67
80
  def on_followable_selection(self, index):
68
81
  self.followed_name = self.followable_item_name[index]
69
82
 
83
+ def mouseDoubleClickEvent(self, event):
84
+ """Double click to set center."""
85
+ p = self.get_point(event.x(), event.y())
86
+ if p is not None:
87
+ self.set_center(p)
88
+ super().mouseDoubleClickEvent(event)
89
+
70
90
  def update(self):
71
91
  if self.followed_name != 'none':
72
92
  new_center = self.named_items[self.followed_name].T[:3, 3]
73
- self.set_center(new_center)
93
+ if self.old_center is None:
94
+ self.old_center = self.center
95
+ return
96
+ delta = new_center - self.old_center
97
+ self.set_center(self.center + delta)
98
+ self.old_center = new_center
74
99
  super().update()
75
100
 
76
101
  def add_setting(self, layout):
@@ -66,7 +66,7 @@ void main()
66
66
  {
67
67
  uint intensity = value >> 24;
68
68
  float range = vmax - vmin;
69
- float value = 1.0 - (float(intensity) - vmin) / range;
69
+ float value = (float(intensity) - vmin) / range;
70
70
  c.z = value;
71
71
  c.y = value;
72
72
  c.x = value;
@@ -0,0 +1,83 @@
1
+ #version 430 core
2
+
3
+ /*
4
+ Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
5
+ Distributed under MIT license. See LICENSE for more information.
6
+ */
7
+
8
+ layout(points) in;
9
+ layout(triangle_strip, max_vertices = 6) out;
10
+
11
+ // Input from vertex shader
12
+ in VS_OUT {
13
+ vec3 v0, v1, v2, v3;
14
+ float good;
15
+ } gs_in[];
16
+
17
+ // Uniforms
18
+ uniform mat4 view;
19
+ uniform mat4 projection;
20
+
21
+ uniform int flat_rgb;
22
+
23
+
24
+ // Output to fragment shader
25
+ out vec3 FragPos;
26
+ out vec3 Normal;
27
+ out vec3 objectColor;
28
+
29
+ // Calculate normal from three vertices
30
+ vec3 calculateNormal(vec3 a, vec3 b, vec3 c) {
31
+ vec3 edge1 = b - a;
32
+ vec3 edge2 = c - a;
33
+ return normalize(cross(edge1, edge2));
34
+ }
35
+
36
+ void emitVertex(vec3 pos, vec3 normal, vec3 color) {
37
+ FragPos = pos;
38
+ Normal = normal;
39
+ objectColor = color;
40
+ gl_Position = projection * view * vec4(pos, 1.0);
41
+ EmitVertex();
42
+ }
43
+
44
+ void main()
45
+ {
46
+ // Discard if face is not good
47
+ if (gs_in[0].good != 1.0) {
48
+ return;
49
+ }
50
+
51
+ vec3 v0 = gs_in[0].v0;
52
+ vec3 v1 = gs_in[0].v1;
53
+ vec3 v2 = gs_in[0].v2;
54
+ vec3 v3 = gs_in[0].v3;
55
+
56
+ float eps = 0.0001;
57
+
58
+ // Use default light blue color
59
+ vec3 color = vec3(
60
+ float((uint(flat_rgb) & uint(0x00FF0000)) >> 16)/255.,
61
+ float((uint(flat_rgb) & uint(0x0000FF00)) >> 8)/255.,
62
+ float( uint(flat_rgb) & uint(0x000000FF))/255.
63
+ );
64
+ // Triangle 1: (v0, v1, v2)
65
+ vec3 normal1 = calculateNormal(v0, v1, v2);
66
+ // Skip degenerate triangles
67
+ if (length(normal1) > eps) {
68
+ emitVertex(v0, normal1, color);
69
+ emitVertex(v1, normal1, color);
70
+ emitVertex(v2, normal1, color);
71
+ EndPrimitive();
72
+ }
73
+
74
+ // Triangle 2: (v0, v1, v3)
75
+ vec3 normal2 = calculateNormal(v0, v1, v3);
76
+ // Skip degenerate triangles
77
+ if (length(normal2) > eps) {
78
+ emitVertex(v0, normal2, color);
79
+ emitVertex(v1, normal2, color);
80
+ emitVertex(v3, normal2, color);
81
+ EndPrimitive();
82
+ }
83
+ }
@@ -1,65 +1,37 @@
1
- #version 330 core
2
- layout (location = 0) in vec3 aPos;
3
- layout (location = 1) in vec3 aNormal;
4
- layout (location = 2) in uint aColor;
1
+ #version 430 core
5
2
 
6
- out vec3 FragPos;
7
- out vec3 Normal;
8
- out vec3 objectColor;
3
+ /*
4
+ Copyright 2024 Panasonic Advanced Technology Development Co.,Ltd. (Liu Yang)
5
+ Distributed under MIT license. See LICENSE for more information.
6
+ */
9
7
 
8
+ // Face attributes (per-instance) - vertex positions embedded in each face
9
+ layout(location = 1) in vec3 v0;
10
+ layout(location = 2) in vec3 v1;
11
+ layout(location = 3) in vec3 v2;
12
+ layout(location = 4) in vec3 v3;
13
+ layout(location = 5) in float good;
14
+
15
+ // Uniforms
10
16
  uniform mat4 view;
11
17
  uniform mat4 projection;
12
- uniform int flat_rgb;
13
- uniform int color_mode; // 0: FLAT, 1: Intensity, 2: RGB
14
- uniform float vmin;
15
- uniform float vmax;
16
-
17
- vec3 getRainbowColor(uint value_raw) {
18
- float range = vmax - vmin;
19
- float value = 1.0 - (float(value_raw) - vmin) / range;
20
- value = clamp(value, 0.0, 1.0);
21
- float hue = value * 5.0 + 1.0;
22
- int i = int(floor(hue));
23
- float f = hue - float(i);
24
- if (mod(i, 2) == 0) f = 1.0 - f;
25
- float n = 1.0 - f;
26
18
 
27
- vec3 color;
28
- if (i <= 1) color = vec3(n, 0.0, 1.0);
29
- else if (i == 2) color = vec3(0.0, n, 1.0);
30
- else if (i == 3) color = vec3(0.0, 1.0, n);
31
- else if (i == 4) color = vec3(n, 1.0, 0.0);
32
- else color = vec3(1.0, n, 0.0);
33
- return color;
34
- }
19
+ // Outputs to fragment shader (via geometry shader)
20
+ out VS_OUT {
21
+ vec3 v0, v1, v2, v3;
22
+ float good;
23
+ } vs_out;
35
24
 
36
25
  void main()
37
26
  {
38
- FragPos = aPos;
39
- Normal = aNormal;
40
-
41
- vec3 c = vec3(1.0, 1.0, 1.0);
27
+ // Pass vertex positions directly (no SSBO lookup needed)
28
+ vs_out.v0 = v0;
29
+ vs_out.v1 = v1;
30
+ vs_out.v2 = v2;
31
+ vs_out.v3 = v3;
32
+ vs_out.good = good;
42
33
 
43
- if (color_mode == 0) {
44
- // FLAT: use uniform flat color
45
- c.z = float( uint(flat_rgb) & uint(0x000000FF))/255.;
46
- c.y = float((uint(flat_rgb) & uint(0x0000FF00)) >> 8)/255.;
47
- c.x = float((uint(flat_rgb) & uint(0x00FF0000)) >> 16)/255.;
48
- }
49
- else if (color_mode == 1) {
50
- // Intensity: use intensity channel (bits 24-31) for rainbow color
51
- uint intensity = aColor >> 24;
52
- c = getRainbowColor(intensity);
53
- }
54
- else if (color_mode == 2) {
55
- // RGB: use RGB channels (bits 0-23)
56
- c.z = float(aColor & uint(0x000000FF))/255.;
57
- c.y = float((aColor & uint(0x0000FF00)) >> 8)/255.;
58
- c.x = float((aColor & uint(0x00FF0000)) >> 16)/255.;
59
- }
60
-
61
- objectColor = c;
62
-
63
- // Final vertex position (apply view/projection)
64
- gl_Position = projection * view * vec4(aPos, 1.0);
65
- }
34
+ // Output dummy point (geometry shader will generate triangles)
35
+ // Note: This position is not used; geometry shader generates actual triangles
36
+ gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
37
+ }
@@ -7,39 +7,29 @@ Distributed under MIT license. See LICENSE for more information.
7
7
 
8
8
  import numpy as np
9
9
  import q3dviewer as q3d
10
- from q3dviewer.Qt.QtWidgets import QVBoxLayout, QProgressBar, QDialog, QLabel
10
+ from q3dviewer.Qt.QtWidgets import QVBoxLayout, QDialog, QLabel
11
11
  from q3dviewer.Qt.QtCore import QThread, Signal, Qt
12
- from q3dviewer.Qt.QtGui import QKeyEvent
13
12
  from q3dviewer import GLWidget
14
13
 
15
-
16
- class ProgressDialog(QDialog):
14
+ class ProgressWindow(QDialog):
17
15
  def __init__(self, parent=None):
18
16
  super().__init__(parent)
19
- self.setWindowTitle("Loading Cloud File")
17
+ self.setWindowTitle("Loading")
20
18
  self.setModal(True)
21
- self.progress_bar = QProgressBar(self)
22
- self.file_label = QLabel(self)
19
+ self.setMinimumWidth(400)
20
+ self.label = QLabel(self)
21
+ self.label.setAlignment(Qt.AlignCenter)
23
22
  layout = QVBoxLayout()
24
- layout.addWidget(self.file_label)
25
- layout.addWidget(self.progress_bar)
23
+ layout.addWidget(self.label)
26
24
  self.setLayout(layout)
27
25
 
28
- def set_value(self, value):
29
- self.progress_bar.setValue(value)
30
-
31
- def set_file_name(self, file_name):
32
- self.file_label.setText(f"Loading: {file_name}")
33
-
34
- def closeEvent(self, event):
35
- if self.parent().progress_thread and self.parent().progress_thread.isRunning():
36
- event.ignore()
37
- else:
38
- event.accept()
26
+ def update_progress(self, current, total, file_name):
27
+ text = f"[{current}/{total}] loading file: {file_name}"
28
+ self.label.setText(text)
39
29
 
40
30
 
41
31
  class FileLoaderThread(QThread):
42
- progress = Signal(int)
32
+ progress = Signal(int, int, str) # current, total, filename
43
33
  finished = Signal()
44
34
 
45
35
  def __init__(self, viewer, files):
@@ -50,25 +40,22 @@ class FileLoaderThread(QThread):
50
40
  def run(self):
51
41
  cloud_item = self.viewer['cloud']
52
42
  mesh_item = self.viewer['mesh']
43
+ total = len(self.files)
53
44
  for i, url in enumerate(self.files):
54
45
  # if the file is a mesh file, use mesh_item to load
55
46
  file_path = url.toLocalFile()
56
- file_path = url.toLocalFile()
57
- self.viewer.progress_dialog.set_file_name(file_path)
47
+ import os
48
+ file_name = os.path.basename(file_path)
49
+ self.progress.emit(i + 1, total, file_name)
50
+
58
51
  if url.toLocalFile().lower().endswith(('.stl')):
59
52
  from q3dviewer.utils.cloud_io import load_stl
60
- verts, faces = load_stl(file_path)
61
- # create colors, N, array
62
- color = np.zeros((verts.shape[0],), dtype=np.uint32)
63
- # random uint32 colors in IRGB format
64
- color = np.random.randint(0, 0xFFFFFFFF, size=(verts.shape[0],), dtype=np.uint32)
65
- mesh_item.set_data(verts=verts, faces=faces, colors=color)
66
- break
53
+ mesh = load_stl(file_path)
54
+ mesh_item.set_data(mesh)
67
55
  else:
68
56
  cloud = cloud_item.load(file_path, append=(i > 0))
69
57
  center = np.nanmean(cloud['xyz'].astype(np.float64), axis=0)
70
58
  self.viewer.glwidget.set_cam_position(center=center)
71
- self.progress.emit(int((i + 1) / len(self.files) * 100))
72
59
  self.finished.emit()
73
60
 
74
61
 
@@ -135,20 +122,19 @@ class CloudViewer(q3d.Viewer):
135
122
  """
136
123
  Overwrite the drop event to open the cloud file.
137
124
  """
138
- self.progress_dialog = ProgressDialog(self)
139
- self.progress_dialog.show()
125
+ self.progress_window = ProgressWindow(self)
126
+ self.progress_window.show()
140
127
  files = event.mimeData().urls()
141
128
  self.progress_thread = FileLoaderThread(self, files)
142
- self['cloud'].load(files[0].toLocalFile(), append=False)
143
129
  self.progress_thread.progress.connect(self.file_loading_progress)
144
130
  self.progress_thread.finished.connect(self.file_loading_finished)
145
131
  self.progress_thread.start()
146
132
 
147
- def file_loading_progress(self, value):
148
- self.progress_dialog.set_value(value)
133
+ def file_loading_progress(self, current, total, file_name):
134
+ self.progress_window.update_progress(current, total, file_name)
149
135
 
150
136
  def file_loading_finished(self):
151
- self.progress_dialog.close()
137
+ self.progress_window.close()
152
138
 
153
139
  def open_cloud_file(self, file, append=False):
154
140
  cloud_item = self['cloud']
@@ -159,27 +145,50 @@ class CloudViewer(q3d.Viewer):
159
145
  center = np.nanmean(cloud['xyz'].astype(np.float64), axis=0)
160
146
  self.glwidget.set_cam_position(center=center)
161
147
 
162
- # print a quick help message
148
+ # print a quick help message using rich
163
149
  def print_help():
164
- # ANSI color codes
165
- GREEN = '\033[92m'
166
- BLUE = '\033[94m'
167
- BOLD = '\033[1m'
168
- END = '\033[0m'
150
+ from rich.console import Console
151
+ from rich.panel import Panel
152
+ from rich.table import Table
153
+ from rich.text import Text
154
+
155
+ console = Console()
156
+
157
+ # Create a table for better organization
158
+ table = Table(show_header=False, box=None, padding=(0, 2))
159
+ table.add_column(style="bold cyan", width=30)
160
+ table.add_column(style="white")
161
+
162
+ # File loading section
163
+ table.add_row("📁 Load Files","Drag and drop files into the viewer")
164
+ table.add_row("","[dim]• Point clouds: .pcd, .ply, .las, .e57[/dim]")
165
+ table.add_row("","[dim]• Mesh files: .stl[/dim]")
166
+ table.add_row("", "")
167
+
168
+ # Measurement section
169
+ table.add_row("📏 Measure Distance", "Interactive point measurement")
170
+ table.add_row("","[dim]• Ctrl + Left Click: Add measurement point[/dim]")
171
+ table.add_row("","[dim]• Ctrl + Right Click: Remove last point[/dim]")
172
+ table.add_row("","[dim]• Total distance displayed automatically[/dim]")
173
+ table.add_row("", "")
174
+
175
+ # Camera controls
176
+ table.add_row("🎥 Camera Controls","Navigate the 3D scene")
177
+ table.add_row("","[dim]• Double Click: Set camera center to point[/dim]")
178
+ table.add_row("","[dim]• Right Drag: Rotate view[/dim]")
179
+ table.add_row("","[dim]• Left Drag: Pan view[/dim]")
180
+ table.add_row("","[dim]• Mouse Wheel: Zoom in/out[/dim]")
181
+ table.add_row("", "")
182
+
183
+ # Settings section
184
+ table.add_row("⚙️ Settings","Press [bold green]'M'[/bold green] to open settings window")
185
+ table.add_row("","[dim]Adjust visualization properties[/dim]")
169
186
 
170
- help_msg = f"""
171
- {BOLD}Cloud Viewer Help:{END}
172
- {GREEN}• Drag and drop cloud files into the viewer to load them.{END}
173
- {BLUE}- support .pcd, .ply, .las, .e57, for point clouds.{END}
174
- {BLUE}- support .stl for mesh files.{END}
175
- {GREEN}• Measure distance between points:{END}
176
- {BLUE}- Hold Ctrl and left-click to select points on the cloud.{END}
177
- {BLUE}- Hold Ctrl and right-click to remove the last selected point.{END}
178
- {BLUE}- The total distance between selected points will be displayed.{END}
179
- {GREEN}• Press 'M' to open the settings window.{END}
180
- {BLUE}- Use the settings window to adjust item properties.{END}
181
- """
182
- print(help_msg)
187
+ # Print title and table without border
188
+ console.print()
189
+ console.print("[bold magenta]☁️ Cloud Viewer Help[/bold magenta]\n")
190
+ console.print(table)
191
+ console.print()
183
192
 
184
193
  def main():
185
194
  print_help()
@@ -7,25 +7,52 @@ import numpy as np
7
7
 
8
8
 
9
9
  def load_stl(file_path):
10
- from stl import mesh as stlmesh
11
- m = stlmesh.Mesh.from_file(file_path)
12
- verts = m.vectors.reshape(-1, 3).astype(np.float32)
13
- faces = np.arange(len(verts), dtype=np.uint32).reshape(-1, 3)
14
- return verts, faces
15
-
16
-
17
- def save_stl(verts, faces, save_path):
18
- """Save the generated mesh as an STL file."""
19
- from stl import mesh as stlmesh
20
- from stl import Mode
21
- verts = np.asarray(verts, dtype=np.float32)
22
- faces = np.asarray(faces, dtype=np.uint32)
23
- # Create the mesh
24
- m = stlmesh.Mesh(np.zeros(faces.shape[0], dtype=stlmesh.Mesh.dtype))
25
- m.vectors[:] = verts[faces].astype(np.float32)
26
- # Save to file
27
- m.save(save_path, mode=Mode.BINARY)
28
-
10
+ import meshio
11
+ mesh = meshio.read(file_path)
12
+ # meshio returns cells as a list of (cell_type, cell_data) tuples
13
+ # For STL, we expect 'triangle' cells
14
+ vertices = mesh.points.astype(np.float32)
15
+
16
+ # Find triangle cells
17
+ triangles = None
18
+ for cell_block in mesh.cells:
19
+ if cell_block.type == 'triangle':
20
+ triangles = cell_block.data
21
+ break
22
+
23
+ if triangles is None:
24
+ raise ValueError(f"No triangle cells found in STL file: {file_path}")
25
+
26
+ # Convert indexed triangles to flat vertex array (N*3, 3)
27
+ faces = vertices[triangles.flatten()].astype(np.float32)
28
+ return faces
29
+
30
+
31
+ def save_stl(faces, save_path, binary=True):
32
+ import meshio
33
+ faces = np.asarray(faces, dtype=np.float32)
34
+ if faces.shape[0] % 3 != 0:
35
+ raise ValueError(f"Invalid faces shape: {faces.shape}, must be (N*3, 3)")
36
+
37
+ # Reshape to (num_triangles, 3, 3)
38
+ num_triangles = faces.shape[0] // 3
39
+ triangles = faces.reshape(num_triangles, 3, 3)
40
+
41
+ # Get unique vertices and create index array
42
+ vertices, indices = np.unique(triangles.reshape(-1, 3), axis=0, return_inverse=True)
43
+ triangle_indices = indices.reshape(num_triangles, 3)
44
+
45
+ # Create meshio mesh object
46
+ mesh = meshio.Mesh(
47
+ points=vertices,
48
+ cells=[("triangle", triangle_indices)]
49
+ )
50
+ # meshio automatically saves STL as binary by default
51
+ # Use file_format="stl-ascii" for ASCII format
52
+ if binary:
53
+ mesh.write(save_path, binary=True)
54
+ else:
55
+ mesh.write(save_path, binary=False)
29
56
 
30
57
  def save_ply(cloud, save_path):
31
58
  import meshio
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: q3dviewer
3
- Version: 1.2.2
3
+ Version: 1.2.3
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
@@ -17,7 +17,6 @@ Requires-Dist: laspy
17
17
  Requires-Dist: matplotlib
18
18
  Requires-Dist: meshio
19
19
  Requires-Dist: numpy
20
- Requires-Dist: numpy-stl
21
20
  Requires-Dist: pye57
22
21
  Requires-Dist: pypcd4
23
22
  Requires-Dist: pyside6
@@ -1,7 +1,7 @@
1
1
  q3dviewer/__init__.py,sha256=cjyfUE5zK6xohDGDQIWfb0DKkWChVznBd7CrVLg7whQ,376
2
2
  q3dviewer/base_glwidget.py,sha256=QxAuZzQSBbzTwpHHqYpiM-Jqv41E4YJmFG4KRF-HruY,15274
3
3
  q3dviewer/base_item.py,sha256=63MarHyoWszPL40ox-vPoOAQ1N4ypekOjoRARdPik-E,1755
4
- q3dviewer/glwidget.py,sha256=EmrxPtVQ8RdPK5INKMlpKpVfX0KCfjSKRdGf4cSB1f0,5405
4
+ q3dviewer/glwidget.py,sha256=taewDTQUqmKOrDYz_ygghu00dlCnWzFRpDnEaTG25pA,6390
5
5
  q3dviewer/viewer.py,sha256=Vq3ucDlBcBBoiVVGmqG1sRjhLePl50heblx6wJpsc1A,2603
6
6
  q3dviewer/Qt/__init__.py,sha256=VJj7Ge6N_81__T9eHFl_YQpa1HyQrlLhMqC_9pUOYtc,2233
7
7
  q3dviewer/custom_items/__init__.py,sha256=kaaf84wOObfybJ8a12FqPMeg8ImTJWggA6g5nvpY2YY,621
@@ -13,19 +13,20 @@ q3dviewer/custom_items/gaussian_item.py,sha256=JMubpahkTPh0E8ShL3FLTahv0e35ODzjg
13
13
  q3dviewer/custom_items/grid_item.py,sha256=LDB_MYACoxld-xvz01_MfAf12vLcRkH7R_WtGHHdSgk,4945
14
14
  q3dviewer/custom_items/image_item.py,sha256=k7HNTqdL2ckTbxMx7A7eKaP4aksZ85-pBjNdbpm6PXM,5355
15
15
  q3dviewer/custom_items/line_item.py,sha256=rel-lx8AgjDY7qyIecHxHQZzaswRn2ZTiOIjB_0Mrqo,4444
16
- q3dviewer/custom_items/mesh_item.py,sha256=Ds3GyYzvFF5VpjG-_LU9SNZiBoLmztCZrkcalEMjub8,17890
16
+ q3dviewer/custom_items/mesh_item.py,sha256=L6GzCcxiAkeYMLBUbbTvQgY0s-maCYQZqZqeMDtxuFE,17568
17
17
  q3dviewer/custom_items/text3d_item.py,sha256=DYBPXnCmMEzWDE1y523YsWSl91taXAdu0kdnhUcwE4A,5524
18
18
  q3dviewer/custom_items/text_item.py,sha256=toeGjBu7RtT8CMUuaDWnmXPnA1UKHhnCzUNeonGczSo,2703
19
19
  q3dviewer/shaders/cloud_frag.glsl,sha256=psKVt9qI6BW0bCqOk4lcKqUd6XgYGtdFigyN9OdYSNI,609
20
- q3dviewer/shaders/cloud_vert.glsl,sha256=gKI6EJrzX5ga2W2yjU6x7Wjz7Cu2Y-wrPl4g10RfTLM,2376
20
+ q3dviewer/shaders/cloud_vert.glsl,sha256=7tNkdx0iuoq1sG3YZ1UuF2JOhEohcodZ363WUV2B2Q4,2370
21
21
  q3dviewer/shaders/gau_frag.glsl,sha256=vWt5I3Ojrc2PCxRlBJGyJhujbveSicMA54T01Fk293A,975
22
22
  q3dviewer/shaders/gau_prep.glsl,sha256=0BiWhYCQGeX2iN-e7m3dy1xWXqWrErErRAzHlcmWHF0,7218
23
23
  q3dviewer/shaders/gau_vert.glsl,sha256=_rkm51zaWgPDJ-otJL-WX12fDvnPBOTooVfqo21Rexs,1666
24
24
  q3dviewer/shaders/mesh_frag.glsl,sha256=i9ljnO2kjLNGaR1TPQIK4-4iJ-JppJ5bCsOHg1730gQ,1997
25
- q3dviewer/shaders/mesh_vert.glsl,sha256=tj9pbaWUYoKn1CtYahahPVzXNDTEtaQanq3S3VphmPg,1896
25
+ q3dviewer/shaders/mesh_geom.glsl,sha256=HwNEZy7UAQm-PKEP-LnqHTlsSqZ5eqm49CYZSVylzXk,1964
26
+ q3dviewer/shaders/mesh_vert.glsl,sha256=bFg7HXesdVg7UaGDuZ4g7IpRdSKcfc6jzTcxLjNXkt8,968
26
27
  q3dviewer/shaders/sort_by_key.glsl,sha256=M5RK6uRDp40vVH6XtBIrdJTcYatqXyZwd6kCzEa2DZg,1097
27
28
  q3dviewer/tools/__init__.py,sha256=01wG7BGM6VX0QyFBKsqPmyf2e-vrmV_N3-mo-VQ1VBg,20
28
- q3dviewer/tools/cloud_viewer.py,sha256=sZ16uOt5J_3E7H0ZW3NHq0mTkWuoBkRRtwEQgcEv-Io,7794
29
+ q3dviewer/tools/cloud_viewer.py,sha256=wjgQNrn9IzHXd8V2Y45ANm12_YZRaV3uL7F8HkylrN0,8185
29
30
  q3dviewer/tools/example_viewer.py,sha256=C867mLnCBjawS6LGgRsJ_c6-6wztfL9vOBQt85KbbdU,572
30
31
  q3dviewer/tools/film_maker.py,sha256=xLFgRhFWoMQ37qlvcu1lXWaTWXMNRYlRcZFfHW5JtmQ,16676
31
32
  q3dviewer/tools/gaussian_viewer.py,sha256=vIwWmiFhjNmknrEkBLzt2yiegeH7LP3OeNjnGM6GzaI,1633
@@ -33,15 +34,15 @@ q3dviewer/tools/lidar_calib.py,sha256=hHnsSaQh_Pkdh8tPntt0MgEW26nQyAdC_HQHq4I3sw
33
34
  q3dviewer/tools/lidar_cam_calib.py,sha256=4CDcZZiFZDeKo2Y2_lXF9tfbiF9dPsz0OjppQdxQsU4,11430
34
35
  q3dviewer/tools/ros_viewer.py,sha256=ARB3I5wohY3maP8dCu0O0hxObd6JFKuK2y7AApVgMWA,2551
35
36
  q3dviewer/utils/__init__.py,sha256=dwTNAAebTiKY4ygv2G1O-w6-TbJnmnNVO2UfJXvJhaQ,107
36
- q3dviewer/utils/cloud_io.py,sha256=vU03lT4Y-4oPPTNV137HBw-tfwwCwpcaoz3ENWfIAN4,12837
37
+ q3dviewer/utils/cloud_io.py,sha256=OLmVQWbrnGrlZHPz3zdoVn79r50JhM6V0zV-KwogEU8,13732
37
38
  q3dviewer/utils/convert_ros_msg.py,sha256=lNbLIawJfwp3VzygdW3dUXkfSG8atg_CoZbQFmt8H70,3142
38
39
  q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
39
40
  q3dviewer/utils/helpers.py,sha256=SqR4YTQZi13FKbkVUYgodXce1JJ_YmrHEIRkUmnIUas,3085
40
41
  q3dviewer/utils/maths.py,sha256=zHaPtvVZIuo8xepIXCMeSL9tpx8FahUrq0l4K1oXrBk,8834
41
42
  q3dviewer/utils/range_slider.py,sha256=Cs_xrwt6FCDVxGxan7r-ARd5ySwQ50xnCzcmz0dB_X0,4215
42
- q3dviewer-1.2.2.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
43
- q3dviewer-1.2.2.dist-info/METADATA,sha256=pZQwcgig77nSIonNFabIUAptK75BfyLqrydzgc7FwNg,8049
44
- q3dviewer-1.2.2.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
45
- q3dviewer-1.2.2.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
46
- q3dviewer-1.2.2.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
47
- q3dviewer-1.2.2.dist-info/RECORD,,
43
+ q3dviewer-1.2.3.dist-info/LICENSE,sha256=81cMOyNfw8KLb1JnPYngGHJ5W83gSbZEBU9MEP3tl-E,1124
44
+ q3dviewer-1.2.3.dist-info/METADATA,sha256=JjSE7g9HjmxRfl18Z79_EA90drP_EKhBVWuiMYk7yNM,8024
45
+ q3dviewer-1.2.3.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
46
+ q3dviewer-1.2.3.dist-info/entry_points.txt,sha256=EOjker7XYaBk70ffvNB_knPcfA33Bnlg21ZjEeM1EyI,362
47
+ q3dviewer-1.2.3.dist-info/top_level.txt,sha256=HFFDCbGu28txcGe2HPc46A7EPaguBa_b5oH7bufmxHM,10
48
+ q3dviewer-1.2.3.dist-info/RECORD,,