优化gcodeviewer.py,现在渲染非常流畅

This commit is contained in:
2026-05-24 16:39:07 +08:00
parent 854642af2c
commit 2a1b1720e5
2 changed files with 148 additions and 79 deletions

View File

@@ -29,6 +29,7 @@ class AIOPrrintSystemAPI:
# },
# 'progress': {
# 'completion': random.uniform(0, 100.0),
# # 'filepos': random.randint(1234,20934490),
# 'filepos': 20934490,
# 'printTime': random.randint(1234,54321),
# 'printTimeLeft': random.randint(1234,54321),

View File

@@ -7,15 +7,17 @@ from PyQt6.QtOpenGL import QOpenGLShaderProgram, QOpenGLShader, QOpenGLBuffer
class GCodeParseWorker(QThread):
finished = pyqtSignal(dict)
def __init__(self, filepath, type_map, default_colors, parent=None):
def __init__(self, filepath, type_map, default_colors, type_id_map, parent=None):
super().__init__(parent)
self.filepath = filepath
self.TYPE_MAP = type_map
self.DEFAULT_COLORS = default_colors
self.TYPE_ID_MAP = type_id_map
def run(self):
points = []
colors = []
filters = []
type_segments = {}
segment_zs = {}
type_visibility = {}
@@ -25,6 +27,7 @@ class GCodeParseWorker(QThread):
feature_type = 'OTHER'
current_segment_type = 'OTHER'
segment_start = 0
segment_count = {}
type_visibility['TRAVEL'] = False
relative_e = False
@@ -126,6 +129,7 @@ class GCodeParseWorker(QThread):
# add_segment(current_segment_type, segment_start, vertex_idx - segment_start, z)
current_segment_type = seg_type
segment_start = vertex_idx
segment_count[seg_type] = segment_count.get(seg_type, 0) + 1
if new_x < min_x: min_x = new_x
if new_x > max_x: max_x = new_x
@@ -150,6 +154,13 @@ class GCodeParseWorker(QThread):
gy = int(new_y / 2.0)
layer_grids.setdefault(grid_z,set()).add((gx, gy))
filt_z = round(new_z, 2) if is_extrusion else round(z, 2)
type_id = self.TYPE_ID_MAP.get(seg_type, 8)
curr_seg_idx = segment_count.get(seg_type, 0)
filters.extend([type_id, 1.0, curr_seg_idx, filt_z,
type_id, 1.0, curr_seg_idx, filt_z])
points.extend([x, y, z, new_x, new_y, new_z])
colors.extend([*c, *c])
vertex_idx += 2
@@ -171,6 +182,8 @@ class GCodeParseWorker(QThread):
add_segment(current_segment_type, segment_start, seg_len, z)
segment_visibility[(segment_start, seg_len)] = True
filters_np = np.array(filters, dtype=np.float32).reshape(-1, 4) if vertex_idx > 0 else np.zeros((0, 4), dtype=np.float32)
# 判断哪些segment在内部
for type_name, segments in type_segments.items():
if type_name in ('WALL-INNER', 'SUPPORT', 'FILL'):
@@ -193,6 +206,7 @@ class GCodeParseWorker(QThread):
if (gx+ox, gy+oy) in gird:
neighbors += 1
if neighbors >= 4:
filters_np[start:start+length, 1] = 0.0
segment_visibility[(start, length)] = False
except Exception as e:
pass
@@ -210,6 +224,7 @@ class GCodeParseWorker(QThread):
result = {
'vertices': np.array(points, dtype=np.float32) if vertex_idx > 0 else np.zeros((0,), dtype=np.float32),
'colors': np.array(colors, dtype=np.float32) if vertex_idx > 0 else np.zeros((0,), dtype=np.float32),
'filters': filters_np,
'vertex_count': vertex_idx,
'center_x': cx,
'center_y': cy,
@@ -260,14 +275,91 @@ class GCodeViewerWidget(QOpenGLWidget):
'TRAVEL': (0.25, 0.31, 0.38), # 0x405060
}
TYPE_ID_MAP = {
'WALL-OUTER': 1,
'WALL-INNER': 2,
'SKIN': 3,
'FILL': 4,
'SUPPORT': 5,
'SUPPORT-INTERFACE': 6,
'SKIRT': 7,
'OTHER': 8,
'TRAVEL': 9,
}
# 顶点着色器GLSL ES 1.00
VERTEX_SHADER = """
attribute vec3 aPos;
attribute vec3 aColor;
attribute vec4 aFilter;
varying vec3 vColor;
uniform mat4 uMVP;
uniform float uZMin;
uniform float uZMax;
uniform float uVis1; // WALL-OUTER
uniform float uVis2; // WALL-INNER
uniform float uVis3; // SKIN
uniform float uVis4; // FILL
uniform float uVis5; // SUPPORT
uniform float uVis6; // SUPPORT-INTERFACE
uniform float uVis7; // SKIRT
uniform float uVis8; // OTHER
uniform float uVis9; // TRAVEL
uniform float uLodFactor;
uniform float uZoomMul;
void main() {
gl_Position = uMVP * vec4(aPos, 1.0);
float doDraw = 1.0;
float type_id = aFilter.x;
float is_vis = aFilter.y;
float seg_idx = aFilter.z;
float seg_z = aFilter.w;
if (is_vis < 0.5) doDraw = 0.0;
int t = int(type_id + 0.1);
if (t == 2 || t == 4 || t == 5) {
if (seg_z < uZMin || seg_z > uZMax) doDraw = 0.0;
}
if (t == 1 && uVis1 < 0.5) doDraw = 0.0;
else if (t == 2 && uVis2 < 0.5) doDraw = 0.0;
else if (t == 3 && uVis3 < 0.5) doDraw = 0.0;
else if (t == 4 && uVis4 < 0.5) doDraw = 0.0;
else if (t == 5 && uVis5 < 0.5) doDraw = 0.0;
else if (t == 6 && uVis6 < 0.5) doDraw = 0.0;
else if (t == 7 && uVis7 < 0.5) doDraw = 0.0;
else if (t == 8 && uVis8 < 0.5) doDraw = 0.0;
else if (t == 9 && uVis9 < 0.5) doDraw = 0.0;
if (uLodFactor > 0.0) {
float simplify_mul = 1.0;
if (t == 4) simplify_mul = 3.0;
else if (t == 5) simplify_mul = 4.0;
else if (t == 9) simplify_mul = 8.0;
float lod_step_f = max(1.0, floor(uLodFactor * simplify_mul * uZoomMul));
if (lod_step_f > 1.0) {
if (mod(seg_idx, lod_step_f) != 0.0) doDraw = 0.0;
}
if (uZoomMul > 1.6 && (t == 4 || t == 5 || t == 2)) {
if (mod(floor(seg_idx / 2.0), 3.0) != 0.0) doDraw = 0.0;
}
if (uZoomMul > 2.8 && (t == 4 || t == 5 || t == 2)) {
if (mod(floor(seg_idx / 2.0), 6.0) != 0.0) doDraw = 0.0;
}
}
if (doDraw < 0.5) {
gl_Position = vec4(0.0, 0.0, 0.0, 0.0);
} else {
gl_Position = uMVP * vec4(aPos, 1.0);
}
vColor = aColor;
}
"""
@@ -295,9 +387,11 @@ class GCodeViewerWidget(QOpenGLWidget):
# 数据缓冲区
self.vertices = None
self.colors = None
self.filters = None
self.vertex_count = 0
self.vbo_vertices = None
self.vbo_colors = None
self.vbo_filters = None
self.vbo_ready = False
# 类型分段
@@ -342,7 +436,7 @@ class GCodeViewerWidget(QOpenGLWidget):
self.uMVP_location = None
# 简化参数
self.enable_lod = True
self.enable_lod = False
self.lod_factor = 0.0
self.visible_top_layers = 0
self.visible_bottom_layers = 0
@@ -353,7 +447,7 @@ class GCodeViewerWidget(QOpenGLWidget):
self._worker.terminate()
self._worker.wait()
self._worker = GCodeParseWorker(filepath, self.TYPE_MAP, self.DEFAULT_COLORS)
self._worker = GCodeParseWorker(filepath, self.TYPE_MAP, self.DEFAULT_COLORS, self.TYPE_ID_MAP)
self._worker.finished.connect(self._on_parse_finished)
self._worker.start()
@@ -363,6 +457,7 @@ class GCodeViewerWidget(QOpenGLWidget):
self.vertices = result['vertices']
self.colors = result['colors']
self.filters = result['filters']
self.vertex_count = result['vertex_count']
self.center_x = result['center_x']
self.center_y = result['center_y']
@@ -419,7 +514,7 @@ class GCodeViewerWidget(QOpenGLWidget):
self.view_rot_x = max(-90.0, min(0.0, rot_x))
self.view_rot_z = rot_z
if zoom is not None:
self.view_zoom = zoom
self.view_zoom = max(-500.0, min(-10.0, zoom))
self.update()
if length == 0:
@@ -442,12 +537,14 @@ class GCodeViewerWidget(QOpenGLWidget):
# 获取属性和 uniform 位置
self.aPos_location = self.shader_program.attributeLocation("aPos")
self.aColor_location = self.shader_program.attributeLocation("aColor")
self.aFilter_location = self.shader_program.attributeLocation("aFilter")
self.uMVP_location = self.shader_program.uniformLocation("uMVP")
self.uDarken_location = self.shader_program.uniformLocation("uDarken")
# 创建缓冲对象
self.vbo_vertices = QOpenGLBuffer(QOpenGLBuffer.Type.VertexBuffer)
self.vbo_colors = QOpenGLBuffer(QOpenGLBuffer.Type.VertexBuffer)
self.vbo_filters = QOpenGLBuffer(QOpenGLBuffer.Type.VertexBuffer)
def resizeGL(self, w, h):
import OpenGL.GL as gl
@@ -500,103 +597,65 @@ class GCodeViewerWidget(QOpenGLWidget):
# 允许 z-fighting 覆盖,用于同一位置多次渲染线条
gl.glDepthFunc(gl.GL_LEQUAL)
count = self.progress_vertices
# Set uniforms for shader logic
visible_toggles = {i: 1.0 for i in range(1, 10)}
for name, viz in self.type_visibility.items():
type_id = self.TYPE_ID_MAP.get(name, 8)
visible_toggles[type_id] = 1.0 if viz else 0.0
for i in range(1, 10):
loc = self.shader_program.uniformLocation(f"uVis{i}")
if loc >= 0:
self.shader_program.setUniformValue(loc, visible_toggles[i])
zoom_mul = max(1.0, abs(self.view_zoom) / 250.0)
lod_loc = self.shader_program.uniformLocation("uLodFactor")
self.shader_program.setUniformValue(lod_loc, float(self.lod_factor) if getattr(self, 'enable_lod', True) else 0.0)
zm_loc = self.shader_program.uniformLocation("uZoomMul")
self.shader_program.setUniformValue(zm_loc, float(zoom_mul))
top_limit = self.max_z - self.visible_top_layers * self.layer_height if getattr(self, 'visible_top_layers', 0) > 0 else 9999.0
bottom_limit = self.min_z + self.visible_bottom_layers * self.layer_height if getattr(self, 'visible_bottom_layers', 0) > 0 else -9999.0
uzmin_loc = self.shader_program.uniformLocation("uZMin")
uzmax_loc = self.shader_program.uniformLocation("uZMax")
self.shader_program.setUniformValue(uzmin_loc, float(bottom_limit))
self.shader_program.setUniformValue(uzmax_loc, float(top_limit))
self.vbo_filters.bind()
self.shader_program.setAttributeBuffer(self.aFilter_location, gl.GL_FLOAT, 0, 4, 0)
self.shader_program.enableAttributeArray(self.aFilter_location)
# 渲染两次:一次绘制加粗加深的边界底线,一次绘制正常宽度的原色骨架线
# 在树莓派等性能有限的平台上使用真实的3D圆柱/方块代替线条会导致顶点数暴增十几倍直接卡顿,
# 因此通过动态加粗像素级线宽来性能无损地模拟出“体积感”)
for pass_idx in range(2):
if pass_idx == 0:
if self.enable_lod:
if getattr(self, 'enable_lod', True):
gl.glLineWidth(3.0) # 底线宽度(加大以模拟体积轮廓)
else:
gl.glLineWidth(6.0) # 底线宽度(加大以模拟体积轮廓)
self.shader_program.setUniformValue(self.uDarken_location, 0.8) # 加深颜色至 40% 亮度
else:
if self.enable_lod:
if getattr(self, 'enable_lod', True):
gl.glLineWidth(1.5) # 主体宽度(加大以模拟线条厚度)
else:
gl.glLineWidth(3.0) # 主体宽度(加大以模拟线条厚度)
self.shader_program.setUniformValue(self.uDarken_location, 1.0) # 保持原色
# 按类型分段绘制
# for type_name, segments in self.type_segments.items():
# if not self.type_visibility.get(type_name, True):
# continue
# for start, length in segments:
# if start >= self.progress_vertices:
# continue
# end = start + length
# visible_start = start
# visible_count = length
# if end > self.progress_vertices:
# visible_count = self.progress_vertices - start
# if visible_count > 0:
# gl.glDrawArrays(gl.GL_LINES, visible_start, visible_count)
# 按类型分段绘制带LOD
for type_name, segments in self.type_segments.items():
if not self.type_visibility.get(type_name, True):
continue
# 不同类型的简化倍率
simplify_mul = 1
if type_name == 'FILL':
simplify_mul = 3
elif type_name == 'SUPPORT':
simplify_mul = 4
elif type_name == 'TRAVEL':
simplify_mul = 8
# 根据缩放动态增强简化
zoom_mul = max(1.0, abs(self.view_zoom) / 250.0)
# 最终步进
lod_step = 1
if self.enable_lod:
lod_step = int(max(1, self.lod_factor * simplify_mul * zoom_mul))
seg_index = 0
for start, length in segments:
if not self.segment_visibility.get((start, length), True):
continue
if type_name in ('WALL-INNER', 'SUPPORT', 'FILL'):
seg_z = self.segment_zs.get((start, length), self.center_z)
top_limit = self.max_z - self.visible_top_layers * self.layer_height if self.visible_top_layers > 0 else self.min_z - 1.0
bottom_limit = self.min_z + self.visible_bottom_layers * self.layer_height if self.visible_bottom_layers > 0 else self.max_z + 1.0
if bottom_limit <= seg_z <= top_limit:
continue
seg_index += 1
# LOD抽样
if lod_step > 1:
if (seg_index % lod_step) != 0:
continue
if start >= self.progress_vertices:
continue
end = start + length
visible_start = start
visible_count = length
if end > self.progress_vertices:
visible_count = self.progress_vertices - start
if visible_count <= 0:
continue
# 超远距离时进一步减少绘制长度
if self.enable_lod:
if type_name in ('FILL','SUPPORT','WALL-INNER'):
if abs(self.view_zoom) > 400:
if(start // 2) % 3 != 0:
continue
if abs(self.view_zoom) > 700:
if(start // 2) % 6 != 0:
continue
# visible_count = visible_count // 2
if visible_count > 0:
gl.glDrawArrays(gl.GL_LINES, visible_start, visible_count)
if count > 0:
gl.glDrawArrays(gl.GL_LINES, 0, count)
# 恢复默认深度测试模式
gl.glDepthFunc(gl.GL_LESS)
self.shader_program.disableAttributeArray(self.aPos_location)
self.shader_program.disableAttributeArray(self.aColor_location)
self.shader_program.disableAttributeArray(self.aFilter_location)
self.vbo_vertices.release()
self.vbo_colors.release()
self.vbo_filters.release()
self.shader_program.release()
def _create_vbos(self):
@@ -604,6 +663,8 @@ class GCodeViewerWidget(QOpenGLWidget):
self.vbo_vertices.destroy()
if self.vbo_colors.isCreated():
self.vbo_colors.destroy()
if self.vbo_filters.isCreated():
self.vbo_filters.destroy()
self.vbo_vertices.create()
self.vbo_vertices.bind()
@@ -614,6 +675,11 @@ class GCodeViewerWidget(QOpenGLWidget):
self.vbo_colors.bind()
self.vbo_colors.allocate(self.colors.tobytes(), self.colors.nbytes)
self.vbo_colors.release()
self.vbo_filters.create()
self.vbo_filters.bind()
self.vbo_filters.allocate(self.filters.tobytes(), self.filters.nbytes)
self.vbo_filters.release()
# ── 触摸/鼠标交互(完全不变) ──
def mousePressEvent(self, event):
@@ -634,6 +700,7 @@ class GCodeViewerWidget(QOpenGLWidget):
def wheelEvent(self, event):
delta = event.angleDelta().y() / 120
self.view_zoom += delta * 10
self.view_zoom = max(-500.0, min(-10.0, self.view_zoom))
self.update()
def event(self, e):
@@ -696,6 +763,7 @@ class GCodeViewerWidget(QOpenGLWidget):
if self._pinch_start_dist > 0:
scale = dist / self._pinch_start_dist
self.view_zoom = self._pinch_start_zoom * (1 / scale)
self.view_zoom = max(-500.0, min(-10.0, self.view_zoom))
# 平移 (双指并行移动)
dcx = center_x - self._pinch_start_center[0]