zhong (钟鹏群) 3 недель назад
Родитель
Сommit
2843ff308f
2 измененных файлов с 767 добавлено и 0 удалено
  1. 608 0
      src/ball-demo-2.py
  2. 159 0
      src/yuan.py

+ 608 - 0
src/ball-demo-2.py

@@ -0,0 +1,608 @@
+import pygame
+import math
+import time
+import os
+try:
+    import pyperclip
+    clipboard_available = True
+except ImportError:
+    clipboard_available = False
+    print("警告: pyperclip 模块未安装,剪贴板功能不可用。请运行 'pip install pyperclip' 安装。")
+
+# 尝试导入 pika 库用于 RabbitMQ 通信
+try:
+    import pika
+    rabbitmq_available = True
+except ImportError:
+    rabbitmq_available = False
+    print("警告: pika 模块未安装,RabbitMQ 通信功能不可用。请运行 'pip install pika' 安装。")
+
+from pygame.locals import *
+from OpenGL.GL import *
+from OpenGL.GLU import *
+from OpenGL.GLUT import *
+
+EXCLUDED_DIR = ['.git', '.vscode', '.idea', 'node_modules', '__pycache__']
+
+# 初始化字体
+pygame.font.init()
+# 使用默认字体,增大字体大小
+font = pygame.font.Font(None, 24)
+
+# 初始化 GLUT
+glutInit()
+
+# RabbitMQ 配置
+RABBITMQ_HOST = '101.201.78.54'
+RABBITMQ_PORT = 5672
+RABBITMQ_QUEUE = 'file_matches'
+RABBITMQ_USERNAME = 'admin'  # 默认用户名
+RABBITMQ_PASSWORD = 'zpq123456'  # 默认密码
+
+# 发送消息到 RabbitMQ
+def send_to_rabbitmq(message):
+    """发送消息到 RabbitMQ"""
+    if not rabbitmq_available:
+        return
+    
+    try:
+        # 建立连接
+        credentials = pika.PlainCredentials(RABBITMQ_USERNAME, RABBITMQ_PASSWORD)
+        connection = pika.BlockingConnection(pika.ConnectionParameters(
+            host=RABBITMQ_HOST, port=RABBITMQ_PORT, credentials=credentials
+        ))
+        channel = connection.channel()
+        
+        # 声明队列(使用持久化队列,避免弃用警告)
+        channel.queue_declare(queue=RABBITMQ_QUEUE, durable=True)
+        
+        # 发送消息(不使用 BasicProperties,简化发送方式)
+        channel.basic_publish(
+            exchange='',
+            routing_key=RABBITMQ_QUEUE,
+            body=message.encode('utf-8')
+        )
+        
+        # 关闭连接
+        connection.close()
+        print(f"消息发送成功: {message}")
+    except Exception as e:
+        print(f"发送消息到 RabbitMQ 失败: {e}")
+
+# 模拟模式开关(True: 使用模拟数据,False: 扫描真实目录)
+# USE_MOCK_DATA = True
+USE_MOCK_DATA = False
+
+# 初始化 Pygame
+pygame.init()
+
+# 获取屏幕尺寸
+info = pygame.display.Info()
+screen_width = info.current_w
+screen_height = info.current_h
+
+# 设置窗口大小(固定宽度为1000px,固定高度为1080px)
+width = 1000  # 固定宽度为1000像素
+height = 1800 # 固定高度为1080像素
+display = pygame.display.set_mode((width, height), DOUBLEBUF | OPENGL)
+pygame.display.set_caption('3D Yellow Ball')
+
+# 启用深度测试
+glEnable(GL_DEPTH_TEST)
+
+# 设置背景色
+glClearColor(0.1, 0.1, 0.1, 1.0)
+# 禁用混合,确保完全不透明
+glDisable(GL_BLEND)
+
+# 设置光照
+glEnable(GL_LIGHTING)
+glEnable(GL_LIGHT0)
+
+# 设置光源位置
+light_position = [2.0, 2.0, 2.0, 1.0]  # 点光源
+glLightfv(GL_LIGHT0, GL_POSITION, light_position)
+
+# 设置光源颜色
+light_ambient = [0.3, 0.3, 0.3, 1.0]
+light_diffuse = [1.0, 1.0, 1.0, 1.0]
+light_specular = [1.0, 1.0, 1.0, 1.0]
+glLightfv(GL_LIGHT0, GL_AMBIENT, light_ambient)
+glLightfv(GL_LIGHT0, GL_DIFFUSE, light_diffuse)
+glLightfv(GL_LIGHT0, GL_SPECULAR, light_specular)
+
+# 设置投影矩阵(透视投影)
+glMatrixMode(GL_PROJECTION)
+glLoadIdentity()
+gluPerspective(45, (width/height), 0.1, 50.0)
+
+# 设置模型视图矩阵
+glMatrixMode(GL_MODELVIEW)
+glLoadIdentity()
+# 移动相机(现在在主循环中每帧设置)
+# glTranslatef(0.0, 0.0, -5)
+
+def generate_mock_tree():
+    # 生成模拟目录树数据
+    nodes = [
+        ("root", True, -1, 0, "/mock/root"),
+        ("dir1", True, 0, 1, "/mock/root/dir1"),
+        ("dir2", True, 0, 1, "/mock/root/dir2"),
+        ("file1.txt", False, 0, 1, "/mock/root/file1.txt"),
+        ("file2.py", False, 0, 1, "/mock/root/file2.py"),
+        ("subdir1", True, 1, 2, "/mock/root/dir1/subdir1"),
+        ("file3.js", False, 1, 2, "/mock/root/dir1/file3.js"),
+        ("file4.md", False, 2, 2, "/mock/root/dir2/file4.md"),
+    ]
+    
+    edges = [
+        (0, 1), (0, 2), (0, 3), (0, 4),
+        (1, 5), (1, 6),
+        (2, 7),
+    ]
+    
+    # 构建子节点列表
+    children = [[] for _ in range(len(nodes))]
+    for parent_id, child_id in edges:
+        children[parent_id].append(child_id)
+    
+    # 计算节点位置(使用原有算法)
+    positions = []
+    for i in range(len(nodes)):
+        positions.append([0.0, 0.0, 0.0])
+    
+    # 计算最大深度
+    max_depth_found = 0
+    for node in nodes:
+        max_depth_found = max(max_depth_found, node[3])
+    
+    # 递归计算位置函数
+    def calculate_positions(node_id, start_angle, end_angle, depth):
+        if depth > max_depth_found:
+            return
+        
+        if node_id == 0:  # 根节点
+            positions[node_id] = [0.0, max_depth_found * 0.8, 0.0]
+        else:
+            parent_id = nodes[node_id][2]
+            if parent_id >= 0:
+                child_index = children[parent_id].index(node_id)
+                num_siblings = len(children[parent_id])
+                angle = start_angle + (end_angle - start_angle) * (child_index / max(num_siblings, 1))
+                radius = 0.75 * (0.75 ** depth)
+                px, py, pz = positions[parent_id]
+                x = px + radius * math.cos(angle)
+                y = py - 0.65
+                z = pz + radius * math.sin(angle)
+                positions[node_id] = [x, y, z]
+        
+        node_children = children[node_id]
+        if node_children:
+            # 计算子节点的角度范围
+            angle_range = math.pi * 2.0  # 360度范围
+            angle_start = -angle_range / 2
+            angle_step = angle_range / len(node_children)
+            
+            for i, child_id in enumerate(node_children):
+                child_angle_start = angle_start + i * angle_step
+                child_angle_end = angle_start + (i + 1) * angle_step
+                calculate_positions(child_id, child_angle_start, child_angle_end, depth + 1)
+    
+    calculate_positions(0, -math.pi, math.pi, 0)
+    return nodes, edges, positions, children
+
+def build_directory_tree(root_path, max_depth=3, max_children_per_node=30):
+    # 如果启用模拟模式,返回模拟数据
+    if USE_MOCK_DATA:
+        return generate_mock_tree()
+    
+    # 递归扫描目录树
+    nodes = []  # 每个节点: (name, is_dir, parent_id, depth)
+    edges = []  # (parent_id, child_id)
+    
+    # 递归扫描函数
+    def scan_directory(current_path, parent_id, depth):
+        if depth >= max_depth:
+            return
+        
+        try:
+            entries = os.listdir(current_path)
+        except Exception:
+            return  # 忽略所有访问错误
+        
+        # 过滤掉一些不需要的目录
+        filtered_entries = []
+        for entry in entries:
+            # 过滤常见的版本控制和IDE目录
+            if entry in EXCLUDED_DIR:
+                continue
+            filtered_entries.append(entry)
+        
+        # 限制每个节点的最大子项数量
+        filtered_entries = filtered_entries[:max_children_per_node]
+        
+        # 当前目录的节点ID:根节点为0,其他目录为parent_id
+        current_node_id = 0 if parent_id == -1 else parent_id
+        
+        for entry in filtered_entries:
+            entry_path = os.path.join(current_path, entry)
+            is_dir = os.path.isdir(entry_path)
+            
+            # 添加子节点
+            child_id = len(nodes)
+            nodes.append((entry, is_dir, current_node_id, depth + 1, entry_path))
+            edges.append((current_node_id, child_id))
+            
+            # 如果是目录,递归扫描
+            if is_dir:
+                scan_directory(entry_path, child_id, depth + 1)
+    
+    # 添加根节点
+    root_name = os.path.basename(root_path.rstrip('\\'))
+    nodes.append((root_name, True, -1, 0, root_path))
+    
+    # 从根节点开始递归扫描
+    scan_directory(root_path, -1, 0)
+    
+    # 计算节点位置
+    positions = []
+    for i in range(len(nodes)):
+        positions.append([0.0, 0.0, 0.0])  # 初始位置
+    
+    # 构建子节点列表
+    children = [[] for _ in range(len(nodes))]
+    for edge in edges:
+        parent_id, child_id = edge
+        children[parent_id].append(child_id)
+    
+    # 计算最大深度
+    max_depth_found = 0
+    for node in nodes:
+        max_depth_found = max(max_depth_found, node[3])
+    
+    # 递归计算位置
+    def calculate_positions(node_id, start_angle, end_angle, depth):
+        if depth > max_depth_found:
+            return
+        
+        # 计算当前节点位置
+        if node_id == 0:  # 根节点
+            # positions[node_id] = [0.0, max_depth_found * 0.4, 0.0]  # 顶部,高度减半
+            positions[node_id] = [0.0, max_depth_found * 0.8, 0.0]  # 更靠近顶部
+        else:
+            parent_id = nodes[node_id][2]
+            if parent_id >= 0:
+                # 子节点围绕父节点在圆上分布
+                child_index = children[parent_id].index(node_id)
+                # 用于表示某个节点的兄弟节点数量
+                num_siblings = len(children[parent_id])
+                
+                # 计算角度(在父节点周围的圆上)
+                # 这行代码确保多个子节点在指定的角度范围内均匀分布,避免它们在3D空间中重叠或拥挤。例如,如果起始角度是0度,结束角度是360度,有4个子节点,那么它们的角度会分别是0度、90度、180度和270度,均匀分布在圆周上
+                angle = start_angle + (end_angle - start_angle) * (child_index / max(num_siblings, 1))
+                
+                # 半径随深度减小
+                # radius = 0.75 * (0.75 ** depth)  # 深度越大,半径越小,基础半径减半
+                if depth == 1:
+                    radius = 1.5
+                else:
+                    radius = 0.75 * (0.75 ** depth)  # 深度越大,半径越小,基础半径减半
+                
+                # 计算位置
+                px, py, pz = positions[parent_id]
+                x = px + radius * math.cos(angle)
+                y = py - 0.65  # 每层向下移动,垂直距离减半
+                z = pz + radius * math.sin(angle)
+                
+                positions[node_id] = [x, y, z]
+        
+        # 为子节点递归计算位置
+        node_children = children[node_id]
+        if node_children:
+            # 计算子节点的角度范围
+            angle_range = math.pi * 2.0  # 360度范围
+            angle_start = -angle_range / 2
+            angle_step = angle_range / len(node_children)
+            
+            for i, child_id in enumerate(node_children):
+                child_angle_start = angle_start + i * angle_step
+                child_angle_end = angle_start + (i + 1) * angle_step
+                calculate_positions(child_id, child_angle_start, child_angle_end, depth + 1)
+    
+    # 从根节点开始计算位置
+    calculate_positions(0, -math.pi, math.pi, 0)
+    
+    return nodes, edges, positions, children
+
+# 构建目录树(使用当前目录)
+# root_path = "."
+root_path = "E:\\agricultural_research_platform"
+
+tree_nodes, tree_edges, node_positions, tree_children = build_directory_tree(root_path)
+
+def draw_text(x, y, z, text, color=(1.0, 1.0, 1.0)):
+    """在3D空间中绘制文本"""
+    glDisable(GL_LIGHTING)
+    glColor3f(*color)  # 使用指定的颜色
+    
+    # 保存当前矩阵
+    glPushMatrix()
+    
+    # 移动到指定位置
+    glTranslatef(x, y, z)
+    
+    # 旋转文本,使其面向相机
+    glRotatef(rotation_angle, 0.0, 1.0, 0.0)
+    
+    # 缩小文本,使其适合3D空间
+    scale = 0.001
+    glScalef(scale, scale, scale)
+    
+    # 计算文本宽度,以便居中显示
+    text_width = 0
+    for char in text:
+        text_width += glutBitmapWidth(GLUT_BITMAP_HELVETICA_12, ord(char))
+    
+    # 设置文本位置,使其居中
+    glRasterPos2f(-text_width / 2, 0)
+    
+    # 绘制文本
+    for char in text:
+        glutBitmapCharacter(GLUT_BITMAP_HELVETICA_12, ord(char))
+    
+    # 恢复矩阵
+    glPopMatrix()
+    glEnable(GL_LIGHTING)
+
+# 初始化选中节点(根节点)
+selected_node_index = 0
+
+# 剪贴板相关变量
+clipboard_content = ""
+clipboard_check_time = 0
+blinking_nodes = set()
+file_content_cache = {}
+blink_start_time = pygame.time.get_ticks()
+
+# 旋转控制变量
+rotation_angle = 0.0
+
+# 相机控制变量
+camera_z = -5  # 相机的 Z 轴位置,初始值为 -5
+
+# 鼠标控制旋转变量
+mouse_dragging = False
+last_mouse_x, last_mouse_y = 0, 0
+rotation_x, rotation_y = 0.0, 0.0  # 分别控制上下和左右旋转
+
+# 主循环
+running = True
+while running:
+    # 获取当前时间(用于闪烁效果)
+    current_time = pygame.time.get_ticks()
+    
+    # 更新旋转角度(降低旋转速度,现在大约每60秒旋转一圈)
+    rotation_angle = (current_time * 0.0005) % 360.0
+    
+    # 检查剪贴板内容是否变化(如果剪贴板功能可用)
+    if clipboard_available:
+        if current_time - clipboard_check_time > 1000:  # 每秒检查一次
+            clipboard_check_time = current_time
+            try:
+                new_clipboard_content = pyperclip.paste()
+                if new_clipboard_content != clipboard_content:
+                    clipboard_content = new_clipboard_content
+                    # 剪贴板内容变化,重新检查所有文件节点
+                    blinking_nodes.clear()
+                    if clipboard_content.strip():  # 剪贴板非空
+                        for i, (name, is_dir, parent_id, depth, full_path) in enumerate(tree_nodes):
+                            if not is_dir:  # 文件节点
+                                # 检查文件内容是否包含剪贴板文本
+                                try:
+                                    # 从缓存读取或加载文件内容
+                                    if i not in file_content_cache:
+                                        with open(full_path, 'r', encoding='utf-8', errors='ignore') as f:
+                                            file_content_cache[i] = f.read()
+                                    file_content = file_content_cache[i]
+                                    if clipboard_content in file_content:
+                                        blinking_nodes.add(i)
+                                        # 发送文件名称到 RabbitMQ
+                                        send_to_rabbitmq(name)
+                                except Exception as e:
+                                    pass  # 忽略读取错误
+            except Exception as e:
+                pass  # 忽略剪贴板访问错误
+    
+    # 处理事件
+    for event in pygame.event.get():
+        if event.type == pygame.QUIT:
+            running = False
+        elif event.type == pygame.KEYDOWN:
+            # 获取当前节点信息
+            current_node = tree_nodes[selected_node_index]
+            current_parent_id = current_node[2]
+            current_depth = current_node[3]
+            
+            if event.key == pygame.K_UP:  # 上键:移动到父节点
+                if current_parent_id >= 0:  # 有父节点
+                    selected_node_index = current_parent_id
+            elif event.key == pygame.K_DOWN:  # 下键:移动到第一个子节点
+                if tree_children[selected_node_index]:  # 有子节点
+                    selected_node_index = tree_children[selected_node_index][0]
+            elif event.key == pygame.K_LEFT:  # 左键:移动到前一个兄弟节点
+                if current_parent_id >= 0:  # 有父节点
+                    siblings = tree_children[current_parent_id]
+                    if len(siblings) > 1:
+                        current_index_in_siblings = siblings.index(selected_node_index)
+                        # 移动到前一个兄弟,如果当前是第一个则移动到最后一个
+                        new_index = (current_index_in_siblings - 1) % len(siblings)
+                        selected_node_index = siblings[new_index]
+            elif event.key == pygame.K_RIGHT:  # 右键:移动到后一个兄弟节点
+                if current_parent_id >= 0:  # 有父节点
+                    siblings = tree_children[current_parent_id]
+                    if len(siblings) > 1:
+                        current_index_in_siblings = siblings.index(selected_node_index)
+                        # 移动到后一个兄弟,如果当前是最后一个则移动到第一个
+                        new_index = (current_index_in_siblings + 1) % len(siblings)
+                        selected_node_index = siblings[new_index]
+        elif event.type == pygame.MOUSEBUTTONDOWN:
+            if event.button == 1:  # 左键按下,开始拖拽旋转
+                mouse_dragging = True
+                last_mouse_x, last_mouse_y = pygame.mouse.get_pos()
+            elif event.button == 4:  # 鼠标滚轮向上滚动,放大
+                camera_z += 0.5  # 相机向Z轴正方向移动,放大视图
+            elif event.button == 5:  # 鼠标滚轮向下滚动,缩小
+                camera_z -= 0.5  # 相机向Z轴负方向移动,缩小视图
+            
+            # 限制相机的Z轴范围,防止过度放大或缩小
+            camera_z = max(-10, min(-2, camera_z))
+        elif event.type == pygame.MOUSEBUTTONUP:
+            if event.button == 1:  # 左键释放,停止拖拽
+                mouse_dragging = False
+        elif event.type == pygame.MOUSEMOTION:
+            if mouse_dragging:
+                # 获取鼠标移动距离
+                mouse_dx, mouse_dy = pygame.mouse.get_rel()
+                
+                # 更新旋转角度(水平旋转绕Y轴,垂直旋转绕X轴)
+                rotation_y += mouse_dx * 0.5  # 水平拖拽影响Y轴旋转
+                rotation_x += mouse_dy * 0.5  # 垂直拖拽影响X轴旋转
+                
+                # 限制垂直旋转范围,防止翻转
+                rotation_x = max(-90, min(90, rotation_x))
+    
+    # 清除屏幕
+    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+    
+    # 重置模型视图矩阵并设置相机位置
+    glLoadIdentity()
+    glTranslatef(0.0, 0.0, camera_z)  # 相机位置,可通过鼠标滚轮调整
+    
+    # 应用鼠标控制的旋转(先应用手动旋转,再应用自动旋转)
+    glRotatef(rotation_x, 1.0, 0.0, 0.0)  # 绕X轴旋转(上下视角)
+    glRotatef(rotation_y, 0.0, 1.0, 0.0)  # 绕Y轴旋转(左右视角)
+    glRotatef(rotation_angle, 0.0, 1.0, 0.0)  # 绕竖直中线/Y轴自动旋转
+    
+    # 目录树可视化
+    ball_radius = 0.0667  # 节点球的半径(默认大小)
+    
+    # 绘制所有连线(目录树边)
+    glDisable(GL_LIGHTING)  # 禁用光照以使用纯色
+    glColor3f(1.0, 1.0, 1.0)  # 白色连线
+    glLineWidth(2.0)
+    
+    glBegin(GL_LINES)
+    for edge in tree_edges:
+        node1_idx, node2_idx = edge
+        x1, y1, z1 = node_positions[node1_idx]
+        x2, y2, z2 = node_positions[node2_idx]
+        glVertex3f(x1, y1, z1)
+        glVertex3f(x2, y2, z2)
+    glEnd()
+    
+    # 绘制锥形表面(连接父文件夹与其子节点形成锥形)
+    glDisable(GL_LIGHTING)  # 临时禁用光照以使用纯色
+    glColor4f(0.4, 0.4, 0.6, 0.3)  # 半透明蓝色系,alpha值稍高以增强重叠效果
+    
+    # 启用混合以实现透明效果,使重叠区域变暗
+    glEnable(GL_BLEND)
+    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+    
+    # 遍历所有节点,查找作为父节点的文件夹
+    for i, (name, is_dir, parent_id, depth, full_path) in enumerate(tree_nodes):
+        if is_dir:  # 如果是文件夹节点
+            # 获取该文件夹的所有子节点
+            child_nodes = tree_children[i]
+            if len(child_nodes) > 0:
+                # 获取父节点坐标
+                parent_x, parent_y, parent_z = node_positions[i]
+                
+                # 如果只有一个子节点,无法形成锥形,跳过
+                if len(child_nodes) < 2:
+                    continue
+                
+                # 开始绘制三角形扇形来形成锥形表面
+                glBegin(GL_TRIANGLE_FAN)
+                glVertex3f(parent_x, parent_y, parent_z)  # 锥形顶点(父节点)
+                
+                # 按顺序连接子节点形成锥形侧面
+                for child_id in child_nodes:
+                    child_x, child_y, child_z = node_positions[child_id]
+                    glVertex3f(child_x, child_y, child_z)
+                
+                # 闭合扇形,再次指定第一个子节点
+                if len(child_nodes) > 0:
+                    first_child_x, first_child_y, first_child_z = node_positions[child_nodes[0]]
+                    glVertex3f(first_child_x, first_child_y, first_child_z)
+                
+                glEnd()
+    
+    glDisable(GL_BLEND)  # 禁用混合
+    glEnable(GL_LIGHTING)  # 重新启用光照
+    
+    # 绘制所有节点球(根据类型使用不同颜色)
+    for i, (name, is_dir, parent_id, depth, full_path) in enumerate(tree_nodes):
+        x, y, z = node_positions[i]
+        
+        # 根据节点类型设置材质颜色(选中节点显示为红色)
+        if i in blinking_nodes:  # 闪烁节点
+            # 计算闪烁因子(正弦波,周期约2秒)
+            blink_factor = (math.sin((current_time - blink_start_time) * 0.005) + 1) * 0.5
+            # 基础颜色 #c31c1f
+            base_r, base_g, base_b = 0.7647, 0.1098, 0.1216
+            # 亮度在0.7到1.0之间变化
+            brightness = 0.7 + 0.3 * blink_factor
+            color = [base_r * brightness, base_g * brightness, base_b * brightness, 1.0]
+            ambient = [base_r * 0.5, base_g * 0.5, base_b * 0.5, 1.0]
+            specular = [base_r * 0.8, base_g * 0.8, base_b * 0.8, 1.0]
+        elif i == selected_node_index:  # 选中节点
+            color = [1.0, 0.0, 0.0, 1.0]  # 红色
+            ambient = [0.5, 0.0, 0.0, 1.0]
+            specular = [0.8, 0.3, 0.3, 1.0]
+        elif i == 0:  # 根节点
+            color = [1.0, 1.0, 0.0, 1.0]  # 黄色
+            ambient = [0.5, 0.5, 0.0, 1.0]
+            specular = [0.8, 0.8, 0.4, 1.0]
+        elif is_dir:  # 目录
+            color = [0.2902, 0.5961, 0.3098, 1.0]  # #4a984f
+            ambient = [0.2, 0.4, 0.2, 1.0]
+            specular = [0.4, 0.8, 0.4, 1.0]
+        else:  # 文件
+            color = [0.7059, 0.7176, 0.2549, 1.0]  # #b4b741
+            ambient = [0.4, 0.4, 0.15, 1.0]
+            specular = [0.8, 0.8, 0.4, 1.0]
+        
+        shininess = [30.0]
+        
+        glMaterialfv(GL_FRONT, GL_AMBIENT, ambient)
+        glMaterialfv(GL_FRONT, GL_DIFFUSE, color)
+        glMaterialfv(GL_FRONT, GL_SPECULAR, specular)
+        glMaterialfv(GL_FRONT, GL_SHININESS, shininess)
+        
+        glPushMatrix()
+        glTranslatef(x, y, z)
+        quad = gluNewQuadric()
+        gluQuadricNormals(quad, GLU_SMOOTH)
+        # 根据深度和节点类型调整球体大小:深度越大,球体越小;文件节点的半径缩小为原来的1/3,文件夹节点的半径缩小为原来的1/2
+        # 再次缩小球体大小:文件节点为现在的1/3,文件夹节点为现在的1/2
+        if is_dir:
+            current_radius = (ball_radius / 2) * 0.5 * (0.85 ** depth)  # 文件夹节点的半径再缩小一半
+        else:
+            current_radius = (ball_radius / 3) * 0.33 * (0.85 ** depth)  # 文件节点的半径再缩小为原来的1/3
+        gluSphere(quad, current_radius, 32, 32)
+        gluDeleteQuadric(quad)
+        glPopMatrix()
+        
+        # 绘制文件名
+        if i in blinking_nodes:  # 闪烁节点使用红色文本
+            draw_text(x, y + current_radius + 0.05, z, name, color=(1.0, 0.0, 0.0))  # 红色文本
+        else:
+            draw_text(x, y + current_radius + 0.05, z, name)  # 默认白色文本
+    
+    # 交换缓冲区
+    pygame.display.flip()
+    # 控制帧率 - 增加等待时间使动画更慢
+    pygame.time.wait(20)
+
+# 退出 Pygame
+pygame.quit()

+ 159 - 0
src/yuan.py

@@ -0,0 +1,159 @@
+import pygame
+import math
+from pygame.locals import *
+from OpenGL.GL import *
+from OpenGL.GLU import *
+
+def init_pygame():
+    pygame.init()
+    width, height = 800, 600
+    display = pygame.display.set_mode((width, height), DOUBLEBUF | OPENGL)
+    pygame.display.set_caption('3D Concentric Spheres')
+    return display, width, height
+
+def setup_opengl(width, height):
+    glViewport(0, 0, width, height)
+    glMatrixMode(GL_PROJECTION)
+    glLoadIdentity()
+    gluPerspective(45, (width / height), 0.1, 50.0)
+    glMatrixMode(GL_MODELVIEW)
+    glLoadIdentity()
+    
+    # Enable depth testing
+    glEnable(GL_DEPTH_TEST)
+    
+    # Enable lighting
+    glEnable(GL_LIGHTING)
+    glEnable(GL_LIGHT0)
+    
+    # Set up light properties
+    glLightfv(GL_LIGHT0, GL_POSITION, (5, 5, 5, 1))
+    glLightfv(GL_LIGHT0, GL_AMBIENT, (0.2, 0.2, 0.2, 1))
+    glLightfv(GL_LIGHT0, GL_DIFFUSE, (0.8, 0.8, 0.8, 1))
+    glLightfv(GL_LIGHT0, GL_SPECULAR, (1, 1, 1, 1))
+
+def draw_sphere(radius, color, slices=30, stacks=30):
+    """
+    Draw a sphere with specified radius
+    radius: radius of the sphere
+    """
+    glPushMatrix()
+    
+    # Check if color has alpha channel (4 values) or just RGB (3 values)
+    if len(color) == 4:  # RGBA
+        glEnable(GL_BLEND)
+        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
+        glColor4f(*color)
+    else:  # RGB
+        glColor3f(*color)
+    
+    # Set material properties for better lighting
+    if len(color) == 4:
+        mat_ambient = [color[0]*0.2, color[1]*0.2, color[2]*0.2, color[3]]
+        mat_diffuse = [color[0], color[1], color[2], color[3]]
+    else:
+        mat_ambient = [color[0]*0.2, color[1]*0.2, color[2]*0.2, 1.0]
+        mat_diffuse = [color[0], color[1], color[2], 1.0]
+    
+    mat_specular = [1.0, 1.0, 1.0, 1.0]
+    mat_shininess = [50.0]
+    
+    glMaterialfv(GL_FRONT, GL_AMBIENT, mat_ambient)
+    glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse)
+    glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular)
+    glMaterialfv(GL_FRONT, GL_SHININESS, mat_shininess)
+    
+    # Generate the sphere
+    for i in range(slices):
+        lat0 = math.pi * (-0.5 + float(i) / slices)
+        z0 = radius * math.sin(lat0)
+        zr0 = radius * math.cos(lat0)
+        
+        lat1 = math.pi * (-0.5 + float(i + 1) / slices)
+        z1 = radius * math.sin(lat1)
+        zr1 = radius * math.cos(lat1)
+        
+        glBegin(GL_QUAD_STRIP)
+        for j in range(stacks + 1):
+            lng = 2 * math.pi * float(j) / stacks
+            x = math.cos(lng)
+            y = math.sin(lng)
+            
+            glNormal3f(x * zr1 / radius, y * zr1 / radius, z1 / radius)
+            glVertex3f(x * zr1, y * zr1, z1)
+            glNormal3f(x * zr0 / radius, y * zr0 / radius, z0 / radius)
+            glVertex3f(x * zr0, y * zr0, z0)
+        glEnd()
+    
+    glPopMatrix()
+
+
+def draw_concentric_spheres():
+    """Draw three concentric spheres (big sphere containing smaller spheres)"""
+    # First sphere (smallest, innermost) - Red (drawn first so it's not obscured)
+    draw_sphere(1.0, (1.0, 0.0, 0.0, 0.4))  # Red with semi-transparency
+    
+    # Second sphere (medium) - Green 
+    draw_sphere(2.0, (0.0, 1.0, 0.0, 0.3))  # Green with semi-transparency
+    
+    # Third sphere (largest, outermost) - Blue
+    draw_sphere(3.0, (0.0, 0.0, 1.0, 0.2))  # Blue with higher transparency
+
+def main():
+    display, width, height = init_pygame()
+    setup_opengl(width, height)
+    
+    # Initial camera position
+    camera_distance = -8.0
+    rotation_x = 0
+    rotation_y = 0
+    auto_rotate = True
+    
+    clock = pygame.time.Clock()
+    
+    while True:
+        for event in pygame.event.get():
+            if event.type == pygame.QUIT:
+                pygame.quit()
+                return
+            elif event.type == pygame.KEYDOWN:
+                if event.key == pygame.K_ESCAPE:
+                    pygame.quit()
+                    return
+                elif event.key == pygame.K_SPACE:
+                    auto_rotate = not auto_rotate
+            elif event.type == pygame.MOUSEBUTTONDOWN:
+                if event.button == 4:  # Scroll up
+                    camera_distance += 0.5
+                elif event.button == 5:  # Scroll down
+                    camera_distance += -0.5
+            elif event.type == pygame.MOUSEMOTION:
+                if pygame.mouse.get_pressed()[0]:  # Left button held
+                    rel_x, rel_y = pygame.mouse.get_rel()
+                    rotation_y += rel_x * 0.5
+                    rotation_x += rel_y * 0.5
+                    rotation_x = max(-90, min(90, rotation_x))  # Limit vertical rotation
+        
+        # Clear screen
+        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
+        glLoadIdentity()
+        
+        # Set camera position
+        glTranslatef(0, 0, camera_distance)
+        
+        # Apply rotations
+        glRotatef(rotation_x, 1, 0, 0)  # Vertical rotation
+        glRotatef(rotation_y, 0, 1, 0)  # Horizontal rotation
+        
+        # Auto-rotate slowly if enabled
+        if auto_rotate:
+            glRotatef(pygame.time.get_ticks() * 0.01, 0, 1, 0)
+        
+        # Draw the concentric spheres
+        draw_concentric_spheres()
+        
+        pygame.display.flip()
+        clock.tick(60)
+
+if __name__ == "__main__":
+    main()