diff --git a/utils/aio_print_api.py b/utils/aio_print_api.py index 1da53f2..5957adc 100644 --- a/utils/aio_print_api.py +++ b/utils/aio_print_api.py @@ -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), diff --git a/utils/gcode_viewer.py b/utils/gcode_viewer.py index 5832007..3f2d9c0 100644 --- a/utils/gcode_viewer.py +++ b/utils/gcode_viewer.py @@ -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]