q3dviewer 1.2.0__py3-none-any.whl → 1.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- q3dviewer/custom_items/__init__.py +1 -0
- q3dviewer/custom_items/cloud_item.py +1 -2
- q3dviewer/custom_items/mesh_item.py +425 -0
- q3dviewer/shaders/mesh_frag.glsl +66 -0
- q3dviewer/shaders/mesh_vert.glsl +65 -0
- q3dviewer/tools/cloud_viewer.py +25 -6
- q3dviewer/utils/cloud_io.py +21 -0
- {q3dviewer-1.2.0.dist-info → q3dviewer-1.2.2.dist-info}/METADATA +2 -1
- {q3dviewer-1.2.0.dist-info → q3dviewer-1.2.2.dist-info}/RECORD +13 -10
- {q3dviewer-1.2.0.dist-info → q3dviewer-1.2.2.dist-info}/LICENSE +0 -0
- {q3dviewer-1.2.0.dist-info → q3dviewer-1.2.2.dist-info}/WHEEL +0 -0
- {q3dviewer-1.2.0.dist-info → q3dviewer-1.2.2.dist-info}/entry_points.txt +0 -0
- {q3dviewer-1.2.0.dist-info → q3dviewer-1.2.2.dist-info}/top_level.txt +0 -0
|
@@ -8,3 +8,4 @@ from q3dviewer.custom_items.text_item import Text2DItem
|
|
|
8
8
|
from q3dviewer.custom_items.image_item import ImageItem
|
|
9
9
|
from q3dviewer.custom_items.line_item import LineItem
|
|
10
10
|
from q3dviewer.custom_items.text3d_item import Text3DItem
|
|
11
|
+
from q3dviewer.custom_items.mesh_item import MeshItem
|
|
@@ -42,8 +42,7 @@ class CloudItem(BaseItem):
|
|
|
42
42
|
def __init__(self, size, alpha,
|
|
43
43
|
color_mode='I',
|
|
44
44
|
color='white',
|
|
45
|
-
point_type='PIXEL'
|
|
46
|
-
depth_test=False):
|
|
45
|
+
point_type='PIXEL'):
|
|
47
46
|
super().__init__()
|
|
48
47
|
self.STRIDE = 16 # stride of cloud array
|
|
49
48
|
self.valid_buff_top = 0
|
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
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
|
+
|
|
9
|
+
import numpy as np
|
|
10
|
+
from q3dviewer.base_item import BaseItem
|
|
11
|
+
from OpenGL.GL import *
|
|
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
|
|
15
|
+
import os
|
|
16
|
+
from q3dviewer.utils import set_uniform, text_to_rgba
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MeshItem(BaseItem):
|
|
21
|
+
"""
|
|
22
|
+
An OpenGL mesh item for rendering triangulated 3D surfaces.
|
|
23
|
+
|
|
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)
|
|
66
|
+
"""
|
|
67
|
+
def __init__(self, color='lightblue', wireframe=False, enable_lighting=True, color_mode='FLAT'):
|
|
68
|
+
super(MeshItem, self).__init__()
|
|
69
|
+
self.color = color
|
|
70
|
+
self.flat_rgb = text_to_rgba(color, flat=True)
|
|
71
|
+
self.wireframe = wireframe
|
|
72
|
+
self.enable_lighting = enable_lighting
|
|
73
|
+
|
|
74
|
+
# Mesh data
|
|
75
|
+
self.triangles = None
|
|
76
|
+
self.normals = None
|
|
77
|
+
self.vertex_colors = None # Per-vertex colors (uint32 IRGB format)
|
|
78
|
+
|
|
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
|
|
83
|
+
|
|
84
|
+
# OpenGL objects
|
|
85
|
+
self.vao = None
|
|
86
|
+
self.vbo_vertices = None
|
|
87
|
+
self.vbo_normals = None
|
|
88
|
+
self.vbo_colors = None
|
|
89
|
+
self.program = None
|
|
90
|
+
|
|
91
|
+
# Rendering parameters
|
|
92
|
+
self.line_width = 1.0
|
|
93
|
+
self.light_pos = [1.0, 1.0, 1.0]
|
|
94
|
+
self.light_color = [1.0, 1.0, 1.0]
|
|
95
|
+
|
|
96
|
+
# Phong lighting material properties
|
|
97
|
+
self.ambient_strength = 0.1
|
|
98
|
+
self.diffuse_strength = 1.2
|
|
99
|
+
self.specular_strength = 0.1
|
|
100
|
+
self.shininess = 32.0
|
|
101
|
+
# Alpha (opacity)
|
|
102
|
+
self.alpha = 1.0
|
|
103
|
+
|
|
104
|
+
# Buffer initialization flag
|
|
105
|
+
self.need_update_buffer = True
|
|
106
|
+
self.need_update_setting = True
|
|
107
|
+
self.path = os.path.dirname(__file__)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def add_setting(self, layout):
|
|
111
|
+
"""Add UI controls for mesh visualization"""
|
|
112
|
+
# Wireframe toggle
|
|
113
|
+
self.wireframe_box = QCheckBox("Wireframe Mode")
|
|
114
|
+
self.wireframe_box.setChecked(self.wireframe)
|
|
115
|
+
self.wireframe_box.toggled.connect(self.update_wireframe)
|
|
116
|
+
layout.addWidget(self.wireframe_box)
|
|
117
|
+
|
|
118
|
+
# Enable lighting toggle
|
|
119
|
+
self.lighting_box = QCheckBox("Enable Lighting")
|
|
120
|
+
self.lighting_box.setChecked(self.enable_lighting)
|
|
121
|
+
self.lighting_box.toggled.connect(self.update_enable_lighting)
|
|
122
|
+
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
|
+
|
|
156
|
+
label_rgb = QLabel("Color:")
|
|
157
|
+
label_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
|
|
158
|
+
layout.addWidget(label_rgb)
|
|
159
|
+
self.edit_rgb = QLineEdit()
|
|
160
|
+
self.edit_rgb.setToolTip("Use hex color, i.e. #FF4500, or named color, i.e. 'red'")
|
|
161
|
+
self.edit_rgb.setText(self.color)
|
|
162
|
+
self.edit_rgb.textChanged.connect(self._on_color)
|
|
163
|
+
layout.addWidget(self.edit_rgb)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
# Material property controls for Phong lighting
|
|
167
|
+
if self.enable_lighting:
|
|
168
|
+
# Ambient strength control (slider 0-100 mapped to 0.0-1.0)
|
|
169
|
+
ambient_layout = QHBoxLayout()
|
|
170
|
+
ambient_label = QLabel("Ambient Strength:")
|
|
171
|
+
ambient_layout.addWidget(ambient_label)
|
|
172
|
+
self.ambient_slider = QSlider()
|
|
173
|
+
self.ambient_slider.setOrientation(1) # Qt.Horizontal
|
|
174
|
+
self.ambient_slider.setRange(0, 100)
|
|
175
|
+
self.ambient_slider.setValue(int(self.ambient_strength * 100))
|
|
176
|
+
self.ambient_slider.valueChanged.connect(lambda v: self.update_ambient_strength(v / 100.0))
|
|
177
|
+
ambient_layout.addWidget(self.ambient_slider)
|
|
178
|
+
layout.addLayout(ambient_layout)
|
|
179
|
+
|
|
180
|
+
# Diffuse strength control (slider 0-200 mapped to 0.0-2.0)
|
|
181
|
+
diffuse_layout = QHBoxLayout()
|
|
182
|
+
diffuse_label = QLabel("Diffuse Strength:")
|
|
183
|
+
diffuse_layout.addWidget(diffuse_label)
|
|
184
|
+
self.diffuse_slider = QSlider()
|
|
185
|
+
self.diffuse_slider.setOrientation(1)
|
|
186
|
+
self.diffuse_slider.setRange(0, 200)
|
|
187
|
+
self.diffuse_slider.setValue(int(self.diffuse_strength * 100))
|
|
188
|
+
self.diffuse_slider.valueChanged.connect(lambda v: self.update_diffuse_strength(v / 100.0))
|
|
189
|
+
diffuse_layout.addWidget(self.diffuse_slider)
|
|
190
|
+
layout.addLayout(diffuse_layout)
|
|
191
|
+
|
|
192
|
+
# Specular strength control (slider 0-200 mapped to 0.0-2.0)
|
|
193
|
+
specular_layout = QHBoxLayout()
|
|
194
|
+
specular_label = QLabel("Specular Strength:")
|
|
195
|
+
specular_layout.addWidget(specular_label)
|
|
196
|
+
self.specular_slider = QSlider()
|
|
197
|
+
self.specular_slider.setOrientation(1)
|
|
198
|
+
self.specular_slider.setRange(0, 200)
|
|
199
|
+
self.specular_slider.setValue(int(self.specular_strength * 100))
|
|
200
|
+
self.specular_slider.valueChanged.connect(lambda v: self.update_specular_strength(v / 100.0))
|
|
201
|
+
specular_layout.addWidget(self.specular_slider)
|
|
202
|
+
layout.addLayout(specular_layout)
|
|
203
|
+
|
|
204
|
+
# Shininess control (slider 1-256 mapped to 1-256)
|
|
205
|
+
shininess_layout = QHBoxLayout()
|
|
206
|
+
shininess_label = QLabel("Shininess:")
|
|
207
|
+
shininess_layout.addWidget(shininess_label)
|
|
208
|
+
self.shininess_slider = QSlider()
|
|
209
|
+
self.shininess_slider.setOrientation(1)
|
|
210
|
+
self.shininess_slider.setRange(1, 256)
|
|
211
|
+
self.shininess_slider.setValue(int(self.shininess))
|
|
212
|
+
self.shininess_slider.valueChanged.connect(lambda v: self.update_shininess(float(v)))
|
|
213
|
+
shininess_layout.addWidget(self.shininess_slider)
|
|
214
|
+
layout.addLayout(shininess_layout)
|
|
215
|
+
|
|
216
|
+
def _on_color(self, color):
|
|
217
|
+
try:
|
|
218
|
+
self.color = color
|
|
219
|
+
self.flat_rgb = text_to_rgba(color, flat=True)
|
|
220
|
+
self.need_update_setting = True
|
|
221
|
+
except ValueError:
|
|
222
|
+
pass
|
|
223
|
+
|
|
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
|
+
def update_wireframe(self, value):
|
|
231
|
+
self.wireframe = value
|
|
232
|
+
|
|
233
|
+
def update_enable_lighting(self, value):
|
|
234
|
+
self.enable_lighting = value
|
|
235
|
+
self.need_update_setting = True
|
|
236
|
+
|
|
237
|
+
def update_line_width(self, value):
|
|
238
|
+
self.line_width = value
|
|
239
|
+
self.need_update_setting = True
|
|
240
|
+
|
|
241
|
+
def update_ambient_strength(self, value):
|
|
242
|
+
self.ambient_strength = value
|
|
243
|
+
self.need_update_setting = True
|
|
244
|
+
|
|
245
|
+
def update_diffuse_strength(self, value):
|
|
246
|
+
self.diffuse_strength = value
|
|
247
|
+
self.need_update_setting = True
|
|
248
|
+
|
|
249
|
+
def update_specular_strength(self, value):
|
|
250
|
+
self.specular_strength = value
|
|
251
|
+
self.need_update_setting = True
|
|
252
|
+
|
|
253
|
+
def update_shininess(self, value):
|
|
254
|
+
self.shininess = value
|
|
255
|
+
self.need_update_setting = True
|
|
256
|
+
|
|
257
|
+
def update_alpha(self, value):
|
|
258
|
+
"""Update mesh alpha (opacity)"""
|
|
259
|
+
self.alpha = float(value)
|
|
260
|
+
self.need_update_setting = True
|
|
261
|
+
|
|
262
|
+
def set_data(self, verts, faces, colors=None):
|
|
263
|
+
"""
|
|
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)
|
|
268
|
+
"""
|
|
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()]
|
|
278
|
+
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)
|
|
317
|
+
|
|
318
|
+
def initialize_gl(self):
|
|
319
|
+
"""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
|
|
328
|
+
|
|
329
|
+
def update_render_buffer(self):
|
|
330
|
+
"""Initialize OpenGL buffers"""
|
|
331
|
+
if not self.need_update_buffer:
|
|
332
|
+
return
|
|
333
|
+
|
|
334
|
+
# Generate VAO and VBOs
|
|
335
|
+
if self.vao is None:
|
|
336
|
+
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)
|
|
353
|
+
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)
|
|
360
|
+
glEnableVertexAttribArray(2)
|
|
361
|
+
glVertexAttribIPointer(2, 1, GL_UNSIGNED_INT, 0, None)
|
|
362
|
+
|
|
363
|
+
glBindVertexArray(0)
|
|
364
|
+
self.need_update_buffer = False
|
|
365
|
+
|
|
366
|
+
def update_setting(self):
|
|
367
|
+
if (self.need_update_setting is False):
|
|
368
|
+
return
|
|
369
|
+
set_uniform(self.program, int(self.enable_lighting), 'if_light')
|
|
370
|
+
set_uniform(self.program, 1, 'two_sided')
|
|
371
|
+
set_uniform(self.program, np.array(self.light_color), 'light_color')
|
|
372
|
+
set_uniform(self.program, float(self.ambient_strength), 'ambient_strength')
|
|
373
|
+
set_uniform(self.program, float(self.diffuse_strength), 'diffuse_strength')
|
|
374
|
+
set_uniform(self.program, float(self.specular_strength), 'specular_strength')
|
|
375
|
+
set_uniform(self.program, float(self.shininess), 'shininess')
|
|
376
|
+
set_uniform(self.program, float(self.alpha), 'alpha')
|
|
377
|
+
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
|
+
self.need_update_setting = False
|
|
382
|
+
|
|
383
|
+
def paint(self):
|
|
384
|
+
"""Render the mesh using modern OpenGL with shaders"""
|
|
385
|
+
if self.triangles is None or len(self.triangles) == 0:
|
|
386
|
+
return
|
|
387
|
+
glUseProgram(self.program)
|
|
388
|
+
self.update_render_buffer()
|
|
389
|
+
self.update_setting()
|
|
390
|
+
view_matrix = self.glwidget().view_matrix
|
|
391
|
+
set_uniform(self.program, view_matrix, 'view')
|
|
392
|
+
project_matrix = self.glwidget().projection_matrix
|
|
393
|
+
set_uniform(self.program, project_matrix, 'projection')
|
|
394
|
+
view_pos = self.glwidget().center
|
|
395
|
+
set_uniform(self.program, np.array(view_pos), 'view_pos')
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# Enable blending and depth testing
|
|
399
|
+
glEnable(GL_BLEND)
|
|
400
|
+
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
|
|
401
|
+
glEnable(GL_DEPTH_TEST)
|
|
402
|
+
glDisable(GL_CULL_FACE) # two-sided rendering
|
|
403
|
+
|
|
404
|
+
# Set line width
|
|
405
|
+
glLineWidth(self.line_width)
|
|
406
|
+
|
|
407
|
+
# Bind VAO and render
|
|
408
|
+
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
|
+
|
|
417
|
+
# Draw triangles
|
|
418
|
+
glDrawArrays(GL_TRIANGLES, 0, len(self.triangles))
|
|
419
|
+
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL)
|
|
420
|
+
|
|
421
|
+
glBindVertexArray(0)
|
|
422
|
+
glDisable(GL_DEPTH_TEST)
|
|
423
|
+
glDisable(GL_BLEND)
|
|
424
|
+
glUseProgram(0)
|
|
425
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#version 330 core
|
|
2
|
+
out vec4 FragColor;
|
|
3
|
+
|
|
4
|
+
in vec3 FragPos;
|
|
5
|
+
in vec3 Normal;
|
|
6
|
+
in vec3 objectColor;
|
|
7
|
+
|
|
8
|
+
const vec3 LIGHT_DIR = normalize(vec3(1.0, 1.0, 1.0));
|
|
9
|
+
// Lighting uniforms
|
|
10
|
+
uniform bool if_light;
|
|
11
|
+
uniform bool two_sided;
|
|
12
|
+
// directional light - use a global constant direction (world space)
|
|
13
|
+
uniform vec3 light_color;
|
|
14
|
+
uniform vec3 view_pos;
|
|
15
|
+
|
|
16
|
+
// Material properties
|
|
17
|
+
uniform float ambient_strength;
|
|
18
|
+
uniform float diffuse_strength;
|
|
19
|
+
uniform float specular_strength;
|
|
20
|
+
uniform float shininess;
|
|
21
|
+
// Opacity
|
|
22
|
+
uniform float alpha;
|
|
23
|
+
|
|
24
|
+
void main()
|
|
25
|
+
{
|
|
26
|
+
if (if_light)
|
|
27
|
+
{
|
|
28
|
+
// Ambient lighting
|
|
29
|
+
vec3 ambient = ambient_strength * light_color;
|
|
30
|
+
|
|
31
|
+
// Diffuse lighting with optional two-sided support
|
|
32
|
+
vec3 norm = normalize(Normal);
|
|
33
|
+
vec3 lightDir = LIGHT_DIR; // global directional light
|
|
34
|
+
float diff;
|
|
35
|
+
if (two_sided) {
|
|
36
|
+
// Two-sided lighting - both front and back faces receive light
|
|
37
|
+
diff = max(abs(dot(norm, lightDir)), 0.0);
|
|
38
|
+
} else {
|
|
39
|
+
// Standard one-sided lighting
|
|
40
|
+
diff = max(dot(norm, lightDir), 0.0);
|
|
41
|
+
}
|
|
42
|
+
vec3 diffuse = diffuse_strength * diff * light_color;
|
|
43
|
+
|
|
44
|
+
// Specular lighting (Phong reflection model) with optional two-sided support
|
|
45
|
+
vec3 viewDir = normalize(view_pos - FragPos);
|
|
46
|
+
vec3 reflectDir = reflect(-lightDir, norm);
|
|
47
|
+
float spec;
|
|
48
|
+
if (two_sided) {
|
|
49
|
+
// Two-sided specular
|
|
50
|
+
spec = pow(max(abs(dot(viewDir, reflectDir)), 0.0), shininess);
|
|
51
|
+
} else {
|
|
52
|
+
// Standard one-sided specular
|
|
53
|
+
spec = pow(max(dot(viewDir, reflectDir), 0.0), shininess);
|
|
54
|
+
}
|
|
55
|
+
vec3 specular = specular_strength * spec * light_color;
|
|
56
|
+
|
|
57
|
+
// Combine all lighting components
|
|
58
|
+
vec3 result = (ambient + diffuse + specular) * objectColor;
|
|
59
|
+
FragColor = vec4(result, alpha);
|
|
60
|
+
}
|
|
61
|
+
else
|
|
62
|
+
{
|
|
63
|
+
// No lighting - just object color
|
|
64
|
+
FragColor = vec4(objectColor, alpha);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
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;
|
|
5
|
+
|
|
6
|
+
out vec3 FragPos;
|
|
7
|
+
out vec3 Normal;
|
|
8
|
+
out vec3 objectColor;
|
|
9
|
+
|
|
10
|
+
uniform mat4 view;
|
|
11
|
+
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
|
+
|
|
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
|
+
}
|
|
35
|
+
|
|
36
|
+
void main()
|
|
37
|
+
{
|
|
38
|
+
FragPos = aPos;
|
|
39
|
+
Normal = aNormal;
|
|
40
|
+
|
|
41
|
+
vec3 c = vec3(1.0, 1.0, 1.0);
|
|
42
|
+
|
|
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
|
+
}
|
q3dviewer/tools/cloud_viewer.py
CHANGED
|
@@ -49,13 +49,26 @@ class FileLoaderThread(QThread):
|
|
|
49
49
|
|
|
50
50
|
def run(self):
|
|
51
51
|
cloud_item = self.viewer['cloud']
|
|
52
|
+
mesh_item = self.viewer['mesh']
|
|
52
53
|
for i, url in enumerate(self.files):
|
|
54
|
+
# if the file is a mesh file, use mesh_item to load
|
|
55
|
+
file_path = url.toLocalFile()
|
|
53
56
|
file_path = url.toLocalFile()
|
|
54
57
|
self.viewer.progress_dialog.set_file_name(file_path)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
58
|
+
if url.toLocalFile().lower().endswith(('.stl')):
|
|
59
|
+
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
|
|
67
|
+
else:
|
|
68
|
+
cloud = cloud_item.load(file_path, append=(i > 0))
|
|
69
|
+
center = np.nanmean(cloud['xyz'].astype(np.float64), axis=0)
|
|
70
|
+
self.viewer.glwidget.set_cam_position(center=center)
|
|
71
|
+
self.progress.emit(int((i + 1) / len(self.files) * 100))
|
|
59
72
|
self.finished.emit()
|
|
60
73
|
|
|
61
74
|
|
|
@@ -157,6 +170,8 @@ def print_help():
|
|
|
157
170
|
help_msg = f"""
|
|
158
171
|
{BOLD}Cloud Viewer Help:{END}
|
|
159
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}
|
|
160
175
|
{GREEN}• Measure distance between points:{END}
|
|
161
176
|
{BLUE}- Hold Ctrl and left-click to select points on the cloud.{END}
|
|
162
177
|
{BLUE}- Hold Ctrl and right-click to remove the last selected point.{END}
|
|
@@ -176,16 +191,20 @@ def main():
|
|
|
176
191
|
viewer = CloudViewer(name='Cloud Viewer')
|
|
177
192
|
cloud_item = q3d.CloudIOItem(size=1, alpha=0.1)
|
|
178
193
|
axis_item = q3d.AxisItem(size=0.5, width=5)
|
|
194
|
+
axis_item.disable_setting()
|
|
179
195
|
grid_item = q3d.GridItem(size=1000, spacing=20)
|
|
180
196
|
marker_item = q3d.Text3DItem() # Changed from CloudItem to Text3DItem
|
|
181
197
|
text_item = q3d.Text2DItem(pos=(20, 40), text="", color='lime', size=16)
|
|
198
|
+
text_item.disable_setting()
|
|
199
|
+
mesh_item = q3d.MeshItem() # Added MeshIOItem for mesh support
|
|
182
200
|
|
|
183
201
|
viewer.add_items(
|
|
184
202
|
{'marker': marker_item,
|
|
185
|
-
'cloud': cloud_item,
|
|
203
|
+
'cloud': cloud_item,
|
|
204
|
+
'mesh': mesh_item,
|
|
186
205
|
'grid': grid_item,
|
|
187
206
|
'axis': axis_item,
|
|
188
|
-
'text': text_item})
|
|
207
|
+
'text': text_item,})
|
|
189
208
|
|
|
190
209
|
if args.path:
|
|
191
210
|
pcd_fn = args.path
|
q3dviewer/utils/cloud_io.py
CHANGED
|
@@ -6,6 +6,27 @@ Distributed under MIT license. See LICENSE for more information.
|
|
|
6
6
|
import numpy as np
|
|
7
7
|
|
|
8
8
|
|
|
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
|
+
|
|
29
|
+
|
|
9
30
|
def save_ply(cloud, save_path):
|
|
10
31
|
import meshio
|
|
11
32
|
xyz = cloud['xyz']
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: q3dviewer
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.2
|
|
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,6 +17,7 @@ Requires-Dist: laspy
|
|
|
17
17
|
Requires-Dist: matplotlib
|
|
18
18
|
Requires-Dist: meshio
|
|
19
19
|
Requires-Dist: numpy
|
|
20
|
+
Requires-Dist: numpy-stl
|
|
20
21
|
Requires-Dist: pye57
|
|
21
22
|
Requires-Dist: pypcd4
|
|
22
23
|
Requires-Dist: pyside6
|
|
@@ -4,15 +4,16 @@ q3dviewer/base_item.py,sha256=63MarHyoWszPL40ox-vPoOAQ1N4ypekOjoRARdPik-E,1755
|
|
|
4
4
|
q3dviewer/glwidget.py,sha256=EmrxPtVQ8RdPK5INKMlpKpVfX0KCfjSKRdGf4cSB1f0,5405
|
|
5
5
|
q3dviewer/viewer.py,sha256=Vq3ucDlBcBBoiVVGmqG1sRjhLePl50heblx6wJpsc1A,2603
|
|
6
6
|
q3dviewer/Qt/__init__.py,sha256=VJj7Ge6N_81__T9eHFl_YQpa1HyQrlLhMqC_9pUOYtc,2233
|
|
7
|
-
q3dviewer/custom_items/__init__.py,sha256=
|
|
7
|
+
q3dviewer/custom_items/__init__.py,sha256=kaaf84wOObfybJ8a12FqPMeg8ImTJWggA6g5nvpY2YY,621
|
|
8
8
|
q3dviewer/custom_items/axis_item.py,sha256=-WM2urosqV847zpTpOtxdLjb7y9NJqFCH13qqodcCTg,2572
|
|
9
9
|
q3dviewer/custom_items/cloud_io_item.py,sha256=Haz-SOUUCPDSHgmKyyyFfP7LXBSEiN4r8xmchQwCm-k,4721
|
|
10
|
-
q3dviewer/custom_items/cloud_item.py,sha256=
|
|
10
|
+
q3dviewer/custom_items/cloud_item.py,sha256=UzpkWiMYcX8kEndmRV1ScysFfOvElaKo60KV-uEO2A4,13484
|
|
11
11
|
q3dviewer/custom_items/frame_item.py,sha256=bUzww3tSDah0JZeqtU6_cYHhhTVWzXhJVMcAa5pCXHI,7458
|
|
12
12
|
q3dviewer/custom_items/gaussian_item.py,sha256=JMubpahkTPh0E8ShL3FLTahv0e35ODzjgK5K1i0YXSU,9884
|
|
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
17
|
q3dviewer/custom_items/text3d_item.py,sha256=DYBPXnCmMEzWDE1y523YsWSl91taXAdu0kdnhUcwE4A,5524
|
|
17
18
|
q3dviewer/custom_items/text_item.py,sha256=toeGjBu7RtT8CMUuaDWnmXPnA1UKHhnCzUNeonGczSo,2703
|
|
18
19
|
q3dviewer/shaders/cloud_frag.glsl,sha256=psKVt9qI6BW0bCqOk4lcKqUd6XgYGtdFigyN9OdYSNI,609
|
|
@@ -20,9 +21,11 @@ q3dviewer/shaders/cloud_vert.glsl,sha256=gKI6EJrzX5ga2W2yjU6x7Wjz7Cu2Y-wrPl4g10R
|
|
|
20
21
|
q3dviewer/shaders/gau_frag.glsl,sha256=vWt5I3Ojrc2PCxRlBJGyJhujbveSicMA54T01Fk293A,975
|
|
21
22
|
q3dviewer/shaders/gau_prep.glsl,sha256=0BiWhYCQGeX2iN-e7m3dy1xWXqWrErErRAzHlcmWHF0,7218
|
|
22
23
|
q3dviewer/shaders/gau_vert.glsl,sha256=_rkm51zaWgPDJ-otJL-WX12fDvnPBOTooVfqo21Rexs,1666
|
|
24
|
+
q3dviewer/shaders/mesh_frag.glsl,sha256=i9ljnO2kjLNGaR1TPQIK4-4iJ-JppJ5bCsOHg1730gQ,1997
|
|
25
|
+
q3dviewer/shaders/mesh_vert.glsl,sha256=tj9pbaWUYoKn1CtYahahPVzXNDTEtaQanq3S3VphmPg,1896
|
|
23
26
|
q3dviewer/shaders/sort_by_key.glsl,sha256=M5RK6uRDp40vVH6XtBIrdJTcYatqXyZwd6kCzEa2DZg,1097
|
|
24
27
|
q3dviewer/tools/__init__.py,sha256=01wG7BGM6VX0QyFBKsqPmyf2e-vrmV_N3-mo-VQ1VBg,20
|
|
25
|
-
q3dviewer/tools/cloud_viewer.py,sha256=
|
|
28
|
+
q3dviewer/tools/cloud_viewer.py,sha256=sZ16uOt5J_3E7H0ZW3NHq0mTkWuoBkRRtwEQgcEv-Io,7794
|
|
26
29
|
q3dviewer/tools/example_viewer.py,sha256=C867mLnCBjawS6LGgRsJ_c6-6wztfL9vOBQt85KbbdU,572
|
|
27
30
|
q3dviewer/tools/film_maker.py,sha256=xLFgRhFWoMQ37qlvcu1lXWaTWXMNRYlRcZFfHW5JtmQ,16676
|
|
28
31
|
q3dviewer/tools/gaussian_viewer.py,sha256=vIwWmiFhjNmknrEkBLzt2yiegeH7LP3OeNjnGM6GzaI,1633
|
|
@@ -30,15 +33,15 @@ q3dviewer/tools/lidar_calib.py,sha256=hHnsSaQh_Pkdh8tPntt0MgEW26nQyAdC_HQHq4I3sw
|
|
|
30
33
|
q3dviewer/tools/lidar_cam_calib.py,sha256=4CDcZZiFZDeKo2Y2_lXF9tfbiF9dPsz0OjppQdxQsU4,11430
|
|
31
34
|
q3dviewer/tools/ros_viewer.py,sha256=ARB3I5wohY3maP8dCu0O0hxObd6JFKuK2y7AApVgMWA,2551
|
|
32
35
|
q3dviewer/utils/__init__.py,sha256=dwTNAAebTiKY4ygv2G1O-w6-TbJnmnNVO2UfJXvJhaQ,107
|
|
33
|
-
q3dviewer/utils/cloud_io.py,sha256=
|
|
36
|
+
q3dviewer/utils/cloud_io.py,sha256=vU03lT4Y-4oPPTNV137HBw-tfwwCwpcaoz3ENWfIAN4,12837
|
|
34
37
|
q3dviewer/utils/convert_ros_msg.py,sha256=lNbLIawJfwp3VzygdW3dUXkfSG8atg_CoZbQFmt8H70,3142
|
|
35
38
|
q3dviewer/utils/gl_helper.py,sha256=dRY_kUqyPMr7NTcupUr6_VTvgnj53iE2C0Lk0-oFYsI,1435
|
|
36
39
|
q3dviewer/utils/helpers.py,sha256=SqR4YTQZi13FKbkVUYgodXce1JJ_YmrHEIRkUmnIUas,3085
|
|
37
40
|
q3dviewer/utils/maths.py,sha256=zHaPtvVZIuo8xepIXCMeSL9tpx8FahUrq0l4K1oXrBk,8834
|
|
38
41
|
q3dviewer/utils/range_slider.py,sha256=Cs_xrwt6FCDVxGxan7r-ARd5ySwQ50xnCzcmz0dB_X0,4215
|
|
39
|
-
q3dviewer-1.2.
|
|
40
|
-
q3dviewer-1.2.
|
|
41
|
-
q3dviewer-1.2.
|
|
42
|
-
q3dviewer-1.2.
|
|
43
|
-
q3dviewer-1.2.
|
|
44
|
-
q3dviewer-1.2.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|