12 Commit-ok e74ecec26b ... 6bf07ee480

Szerző SHA1 Üzenet Dátum
  zhong (钟鹏群) 6bf07ee480 完成所有功能修改,包括减慢旋转速度、添加 RabbitMQ 通信和创建 tkinter 接收器 1 hónapja
  zhong (钟鹏群) 5560c2e1c3 修改 receiver.py 文件,使用 tkinter 显示从 RabbitMQ 接收到的文件名 1 hónapja
  zhong (钟鹏群) f7e7639e2e 调整旋转速度,从每20秒旋转一圈改为每30秒旋转一圈 1 hónapja
  zhong (钟鹏群) d64f66333f 修改文本颜色,使剪贴板匹配的文件节点文件名也变红 1 hónapja
  zhong (钟鹏群) 9d10162307 更新项目结构,将 ball-demo.py 移动到 src 目录 1 hónapja
  zhong (钟鹏群) bdd344c079 修改文件夹对应的球半径,缩小为原来的1/2 1 hónapja
  zhong (钟鹏群) 92a0f59b4f 修改球半径设置,文件对应的球半径缩小为原来的1/3,文件夹对应的球半径不变 1 hónapja
  zhong (钟鹏群) c558b89357 添加鼠标滚轮事件处理,实现放大缩小功能 1 hónapja
  zhong (钟鹏群) e0cc2858ab 调整旋转速度和球半径,旋转速度减慢一半,球半径缩小为1/3 1 hónapja
  zhong (钟鹏群) 797942657d 修复文本颠倒问题,使用 GLUT 渲染文本 1 hónapja
  zhong (钟鹏群) cf07765bd2 修复文本颠倒问题,使用 GLUT 渲染文本 1 hónapja
  zhong (钟鹏群) 2403320430 ok x 1 1 hónapja
4 módosított fájl, 333 hozzáadás és 9 törlés
  1. 104 0
      opengl-demo.py
  2. 128 9
      src/ball-demo.py
  3. 101 0
      src/receiver.py
  4. BIN
      test.txt

+ 104 - 0
opengl-demo.py

@@ -0,0 +1,104 @@
+import os
+import vtk
+import pyvista as pv
+
+# -----------------------------------------------------------------------------
+# 1. 配置:要展示的文件夹路径(改成你自己的)
+# -----------------------------------------------------------------------------
+ROOT_FOLDER = r"./"  # 示例:当前目录
+MAX_DEPTH = 3        # 限制深度,防止太密
+NODE_SPACING_X = 2.0
+NODE_SPACING_Y = 1.5
+
+
+# -----------------------------------------------------------------------------
+# 2. 递归遍历文件夹,生成树形结构数据
+# -----------------------------------------------------------------------------
+class TreeNode:
+    def __init__(self, name, path, depth=0):
+        self.name = name
+        self.path = path
+        self.depth = depth
+        self.children = []
+        self.x = 0.0
+        self.y = 0.0
+
+
+def build_tree(folder, depth=0, max_depth=MAX_DEPTH):
+    node = TreeNode(os.path.basename(folder) or folder, folder, depth)
+    if depth >= max_depth:
+        return node
+
+    try:
+        entries = sorted(os.listdir(folder))
+    except PermissionError:
+        return node
+
+    for entry in entries:
+        full = os.path.join(folder, entry)
+        if os.path.isdir(full):
+            child = build_tree(full, depth + 1, max_depth)
+            node.children.append(child)
+    return node
+
+
+# -----------------------------------------------------------------------------
+# 3. 树布局算法(简单层级横向布局)
+# -----------------------------------------------------------------------------
+def layout_tree(node: TreeNode, x=0.0, y=0.0):
+    node.y = y
+    node.x = x
+
+    child_x = x - (len(node.children) - 1) * NODE_SPACING_X / 2
+    for child in node.children:
+        layout_tree(child, child_x, y - NODE_SPACING_Y)
+        child_x += NODE_SPACING_X
+
+
+# -----------------------------------------------------------------------------
+# 4. 收集所有节点、边用于 VTK 绘制
+# -----------------------------------------------------------------------------
+def collect_geometry(node):
+    points = []
+    lines = []
+    labels = []
+    stack = [node]
+
+    while stack:
+        n = stack.pop()
+        points.append([n.x, n.y, 0.0])
+        labels.append(n.name)
+        for child in n.children:
+            points.append([child.x, child.y, 0.0])
+            labels.append(child.name)
+            lines.append(2)  # 每个线段有两个点
+            lines.append(len(points)-2)  # 父节点的索引
+            lines.append(len(points)-1)  # 子节点的索引
+            stack.append(child)
+    return points, lines, labels
+
+
+# -----------------------------------------------------------------------------
+# 5. PyVista + VTK 绘制树
+# -----------------------------------------------------------------------------
+if __name__ == "__main__":
+    tree = build_tree(ROOT_FOLDER)
+    layout_tree(tree)
+    points, lines, labels = collect_geometry(tree)
+
+    # 构建点线云
+    cloud = pv.PolyData(points)
+    cloud.lines = lines
+    cloud["label"] = labels
+
+    # 绘图
+    plotter = pv.Plotter(window_size=(1200, 800))
+    plotter.add_mesh(cloud, color="#66ccff", point_size=12, render_lines_as_tubes=True, line_width=3)
+    plotter.add_point_labels(cloud, "label", font_size=8, shape_color="white")
+
+    plotter.add_text(
+        f"Folder Tree: {ROOT_FOLDER}\nMax Depth: {MAX_DEPTH}",
+        font_size=10, color="blue"
+    )
+    plotter.camera_position = "xy"
+    plotter.show()

+ 128 - 9
ball-demo.py → src/ball-demo.py

@@ -9,9 +9,65 @@ 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
@@ -156,7 +212,7 @@ def build_directory_tree(root_path, max_depth=3, max_children_per_node=30):
         filtered_entries = []
         for entry in entries:
             # 过滤常见的版本控制和IDE目录
-            if entry in ['.git', '.vscode', '.idea', 'node_modules', '__pycache__']:
+            if entry in EXCLUDED_DIR:
                 continue
             filtered_entries.append(entry)
         
@@ -209,19 +265,26 @@ def build_directory_tree(root_path, max_depth=3, max_children_per_node=30):
         
         # 计算当前节点位置
         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.4, 0.0]  # 顶部,高度减半
+            positions[node_id] = [0.0, max_depth_found * 0.6, 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)  # 深度越大,半径越小,基础半径减半
+                # 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]
@@ -255,6 +318,40 @@ 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
 
@@ -268,14 +365,17 @@ blink_start_time = pygame.time.get_ticks()
 # 旋转控制变量
 rotation_angle = 0.0
 
+# 相机控制变量
+camera_z = -5  # 相机的 Z 轴位置,初始值为 -5
+
 # 主循环
 running = True
 while running:
     # 获取当前时间(用于闪烁效果)
     current_time = pygame.time.get_ticks()
     
-    # 更新旋转角度(每10秒旋转一圈)
-    rotation_angle = (current_time * 0.036) % 360.0
+    # 更新旋转角度(每30秒旋转一圈)
+    rotation_angle = (current_time * 0.012) % 360.0
     
     # 检查剪贴板内容是否变化(如果剪贴板功能可用)
     if clipboard_available:
@@ -299,6 +399,8 @@ while running:
                                     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:
@@ -336,17 +438,25 @@ while running:
                         # 移动到后一个兄弟,如果当前是最后一个则移动到第一个
                         new_index = (current_index_in_siblings + 1) % len(siblings)
                         selected_node_index = siblings[new_index]
+        elif event.type == pygame.MOUSEBUTTONDOWN:
+            if 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))
     
     # 清除屏幕
     glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
     
     # 重置模型视图矩阵并设置相机位置
     glLoadIdentity()
-    glTranslatef(0.0, 0.0, -5)  # 相机向后移动
+    glTranslatef(0.0, 0.0, camera_z)  # 相机位置,可通过鼠标滚轮调整
     glRotatef(rotation_angle, 0.0, 1.0, 0.0)  # 绕竖直中线/Y轴旋转
     
     # 目录树可视化
-    ball_radius = 0.0667  # 节点球的半径(原来的1/3
+    ball_radius = 0.0667  # 节点球的半径(默认大小
     
     # 绘制所有连线(目录树边)
     glDisable(GL_LIGHTING)  # 禁用光照以使用纯色
@@ -407,11 +517,20 @@ while running:
         glTranslatef(x, y, z)
         quad = gluNewQuadric()
         gluQuadricNormals(quad, GLU_SMOOTH)
-        # 根据深度调整球体大小:深度越大,球体越小
-        current_radius = ball_radius * (0.85 ** depth)
+        # 根据深度和节点类型调整球体大小:深度越大,球体越小;文件节点的半径缩小为原来的1/3,文件夹节点的半径缩小为原来的1/2
+        if is_dir:
+            current_radius = (ball_radius / 2) * (0.85 ** depth)  # 文件夹节点的半径缩小为原来的1/2
+        else:
+            current_radius = (ball_radius / 3) * (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()

+ 101 - 0
src/receiver.py

@@ -0,0 +1,101 @@
+import pika
+import tkinter as tk
+from tkinter import ttk
+import threading
+
+# RabbitMQ 配置
+RABBITMQ_HOST = '101.201.78.54'
+RABBITMQ_PORT = 5672
+RABBITMQ_QUEUE = 'file_matches'
+RABBITMQ_USERNAME = 'admin'  # RabbitMQ 用户名
+RABBITMQ_PASSWORD = 'zpq123456'  # RabbitMQ 密码
+
+class RabbitMQReceiver:
+    """RabbitMQ 消息接收器"""
+    def __init__(self, root):
+        self.root = root
+        self.root.title("RabbitMQ 消息接收器")
+        self.root.geometry("400x300")
+        
+        # 创建主框架
+        self.main_frame = ttk.Frame(root, padding="10")
+        self.main_frame.pack(fill=tk.BOTH, expand=True)
+        
+        # 创建标题
+        self.title_label = ttk.Label(self.main_frame, text="接收到的文件名称", font=('Arial', 12, 'bold'))
+        self.title_label.pack(pady=10)
+        
+        # 创建消息列表框
+        self.message_list = tk.Listbox(self.main_frame, width=50, height=15)
+        self.message_list.pack(fill=tk.BOTH, expand=True, pady=5)
+        
+        # 创建滚动条
+        self.scrollbar = ttk.Scrollbar(self.message_list, orient=tk.VERTICAL, command=self.message_list.yview)
+        self.scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
+        self.message_list.config(yscrollcommand=self.scrollbar.set)
+        
+        # 创建状态标签
+        self.status_var = tk.StringVar()
+        self.status_var.set(f"等待接收消息... (队列: {RABBITMQ_QUEUE})")
+        self.status_label = ttk.Label(self.main_frame, textvariable=self.status_var, font=('Arial', 10))
+        self.status_label.pack(pady=10)
+        
+        # 启动 RabbitMQ 接收线程
+        self.receive_thread = threading.Thread(target=self.receive_messages, daemon=True)
+        self.receive_thread.start()
+    
+    def callback(self, ch, method, properties, body):
+        """处理接收到的消息"""
+        try:
+            # 解码消息
+            file_name = body.decode('utf-8')
+            # 在主线程中更新 UI
+            self.root.after(0, self.update_message_list, file_name)
+        except Exception as e:
+            self.root.after(0, self.update_message_list, f"处理消息时出错: {e}")
+    
+    def update_message_list(self, message):
+        """更新消息列表"""
+        self.message_list.insert(tk.END, message)
+        # 自动滚动到最后一行
+        self.message_list.see(tk.END)
+    
+    def receive_messages(self):
+        """接收 RabbitMQ 消息"""
+        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)
+            
+            # 注册回调函数
+            channel.basic_consume(queue=RABBITMQ_QUEUE, on_message_callback=self.callback, auto_ack=True)
+            
+            self.root.after(0, self.status_var.set, f"正在接收消息... (队列: {RABBITMQ_QUEUE})")
+            
+            # 开始消费消息
+            channel.start_consuming()
+        except Exception as e:
+            error_message = f"连接到 RabbitMQ 失败: {e}"
+            self.root.after(0, self.status_var.set, error_message)
+            self.root.after(0, self.update_message_list, error_message)
+        finally:
+            if 'connection' in locals() and connection.is_open:
+                connection.close()
+
+def main():
+    """主函数"""
+    # 创建 tkinter 根窗口
+    root = tk.Tk()
+    # 创建 RabbitMQ 接收器
+    receiver = RabbitMQReceiver(root)
+    # 启动 tkinter 主循环
+    root.mainloop()
+
+if __name__ == "__main__":
+    main()

BIN
test.txt