关于错误 11 未能将“obj\Debug\Finally2.exe”复制到“bin\Debug\Finally2.exe

博客围绕调试中的问题展开,但具体内容缺失。推测会涉及调试过程里遇到的各类状况及解决思路等信息技术相关内容。
using System; using System.Windows.Forms; using System.IO; using System.Diagnostics; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Win32; namespace 安装包打包工具 { public partial class Form1 : Form { private string innoSetupPath = “”; private string tempAppFolder = “”; public Form1() { InitializeComponent(); } private void Form1_Load(object sender, EventArgs e) { // 设置默认版本号 txtVersion.Text = "v1.0"; // 设置默认产品名称 if (string.IsNullOrEmpty(txtProductName.Text)) { txtProductName.Text = "忠利模架CAD插件"; } // 设置默认安装路径为Autodesk的子目录 string appName = txtProductName.Text; txtInstallPath.Text = $@"C:\Program Files (x86)\Autodesk\{appName}"; // 初始化文件夹树形视图 InitializeFolderTree(); // 检查Inno Setup是否安装 CheckInnoSetupInstallation(); // 创建临时目录 tempAppFolder = Path.Combine(Path.GetTempPath(), $"app_temp_{Guid.NewGuid():N}"); } private void InitializeFolderTree() { treeViewFolder.Nodes.Clear(); treeViewFolder.CheckBoxes = true; treeViewFolder.AfterCheck += TreeViewFolder_AfterCheck; } private void CheckInnoSetupInstallation() { string innoPath = FindInnoSetupCompiler(); if (string.IsNullOrEmpty(innoPath)) { lblInnoStatus.Text = "Inno Setup: 未安装"; lblInnoStatus.ForeColor = System.Drawing.Color.Red; btnGenerate.Enabled = false; // 提供安装指引 MessageBox.Show( "未检测到 Inno Setup 安装。\n\n" + "请先安装 Inno Setup 5 或 6 版本:\n" + "1. 访问 http://www.jrsoftware.org/isdl.php\n" + "2. 下载并安装 Inno Setup\n" + "3. 重新启动本程序", "Inno Setup 未安装", MessageBoxButtons.OK, MessageBoxIcon.Warning); } else { innoSetupPath = Path.GetDirectoryName(innoPath); lblInnoStatus.Text = $"Inno Setup: 已安装 ({Path.GetFileName(innoSetupPath)})"; lblInnoStatus.ForeColor = System.Drawing.Color.Green; btnGenerate.Enabled = true; } } // ============ 安装包打包功能 ============ private void TreeViewFolder_AfterCheck(object sender, TreeViewEventArgs e) { if (e.Node.Nodes.Count > 0) { SetChildNodesCheckedState(e.Node, e.Node.Checked); } } private void SetChildNodesCheckedState(TreeNode parentNode, bool isChecked) { foreach (TreeNode childNode in parentNode.Nodes) { childNode.Checked = isChecked; SetChildNodesCheckedState(childNode, isChecked); } } // 选择主程序(单个文件) private void btnBrowseMain_Click(object sender, EventArgs e) { using (OpenFileDialog openFileDialog = new OpenFileDialog()) { openFileDialog.Filter = "可执行文件 (*.exe)|*.exe|所有文件 (*.*)|*.*"; openFileDialog.Title = "选择主程序文件"; if (openFileDialog.ShowDialog() == DialogResult.OK) { txtMainProgram.Text = openFileDialog.FileName; AddFileToTreeView(openFileDialog.FileName); // 自动获取程序信息 try { FileVersionInfo fileInfo = FileVersionInfo.GetVersionInfo(openFileDialog.FileName); if (!string.IsNullOrEmpty(fileInfo.FileVersion)) txtVersion.Text = fileInfo.FileVersion; if (!string.IsNullOrEmpty(fileInfo.ProductName)) txtProductName.Text = fileInfo.ProductName; } catch (Exception ex) { MessageBox.Show($"无法读取文件信息: {ex.Message}", "警告", MessageBoxButtons.OK, MessageBoxIcon.Warning); } } } } // 选择包含exe的文件夹 private void btnBrowseFolder_Click(object sender, EventArgs e) { using (FolderBrowserDialog folderDialog = new FolderBrowserDialog()) { folderDialog.Description = "选择包含应用程序文件的文件夹"; folderDialog.ShowNewFolderButton = false; if (folderDialog.ShowDialog() == DialogResult.OK) { string selectedFolder = folderDialog.SelectedPath; txtSelectedFolder.Text = selectedFolder; // 扫描文件夹中的exe文件 ScanFolderForExecutables(selectedFolder); } } } // 扫描文件夹中的可执行文件 private void ScanFolderForExecutables(string folderPath) { try { treeViewFolder.Nodes.Clear(); // 添加根节点 TreeNode rootNode = new TreeNode(Path.GetFileName(folderPath)); rootNode.Tag = folderPath; rootNode.Checked = true; treeViewFolder.Nodes.Add(rootNode); // 递归扫描文件夹 ScanDirectory(folderPath, rootNode); // 展开第一层 rootNode.Expand(); // 自动选择第一个exe文件作为主程序 AutoSelectMainExecutable(folderPath); } catch (Exception ex) { MessageBox.Show($"扫描文件夹时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } // 递归扫描目录 private void ScanDirectory(string directoryPath, TreeNode parentNode) { try { // 添加文件 foreach (string file in Directory.GetFiles(directoryPath)) { string extension = Path.GetExtension(file).ToLower(); if (extension == ".exe" || extension == ".dll" || extension == ".config" || extension == ".lsp" || extension == ".xml" || extension == ".json" || extension == ".txt" || extension == ".bmp" || extension == ".png" || extension == ".ico" || extension == ".dwg" || extension == ".dxf") { TreeNode fileNode = new TreeNode(Path.GetFileName(file)); fileNode.Tag = file; fileNode.Checked = true; parentNode.Nodes.Add(fileNode); } } // 递归添加子目录 foreach (string subDirectory in Directory.GetDirectories(directoryPath)) { if (ShouldSkipDirectory(subDirectory)) continue; TreeNode dirNode = new TreeNode(Path.GetFileName(subDirectory)); dirNode.Tag = subDirectory; dirNode.Checked = true; parentNode.Nodes.Add(dirNode); ScanDirectory(subDirectory, dirNode); } } catch (UnauthorizedAccessException) { // 忽略无访问权限的目录 } catch (Exception ex) { Debug.WriteLine($"扫描目录 {directoryPath} 时出错: {ex.Message}"); } } // 判断是否应该跳过目录 private bool ShouldSkipDirectory(string directoryPath) { string dirName = Path.GetFileName(directoryPath).ToLower(); string[] skipDirs = { "bin", "obj", "debug", "release", ".git", ".vs", "packages" }; return Array.Exists(skipDirs, dir => dir == dirName); } // 自动选择主可执行文件 private void AutoSelectMainExecutable(string folderPath) { try { string[] exeFiles = Directory.GetFiles(folderPath, "*.exe"); if (exeFiles.Length > 0) { string mainExe = FindMainExecutable(exeFiles); if (mainExe != null) { txtMainProgram.Text = mainExe; try { FileVersionInfo fileInfo = FileVersionInfo.GetVersionInfo(mainExe); if (!string.IsNullOrEmpty(fileInfo.FileVersion)) txtVersion.Text = fileInfo.FileVersion; if (!string.IsNullOrEmpty(fileInfo.ProductName)) txtProductName.Text = fileInfo.ProductName; } catch (Exception ex) { Debug.WriteLine($"无法读取文件信息: {ex.Message}"); } } } } catch (Exception ex) { Debug.WriteLine($"自动选择主程序时出错: {ex.Message}"); } } // 查找最可能的主程序 private string FindMainExecutable(string[] exeFiles) { foreach (string exe in exeFiles) { string fileName = Path.GetFileName(exe).ToLower(); if (!fileName.Contains("vshost") && !fileName.Contains("test") && !fileName.Contains("setup")) { return exe; } } return exeFiles.Length > 0 ? exeFiles[0] : null; } // 添加单个文件到树形视图 private void AddFileToTreeView(string filePath) { if (treeViewFolder.Nodes.Count == 0) { TreeNode rootNode = new TreeNode("选择的文件"); rootNode.Checked = true; treeViewFolder.Nodes.Add(rootNode); } TreeNode fileNode = new TreeNode(Path.GetFileName(filePath)); fileNode.Tag = filePath; fileNode.Checked = true; treeViewFolder.Nodes[0].Nodes.Add(fileNode); treeViewFolder.Nodes[0].Expand(); } // 获取所有选中的文件 private List<string> GetSelectedFiles() { List<string> selectedFiles = new List<string>(); if (treeViewFolder.Nodes.Count > 0) { CollectSelectedFiles(treeViewFolder.Nodes[0], selectedFiles); } // 添加附加文件 foreach (var item in listBoxAdditionalFiles.Items) { string filePath = item.ToString(); if (File.Exists(filePath)) { selectedFiles.Add(filePath); } } return selectedFiles; } // 递归收集选中的文件 private void CollectSelectedFiles(TreeNode node, List<string> selectedFiles) { if (node.Checked && node.Tag is string filePath && File.Exists(filePath)) { selectedFiles.Add(filePath); } foreach (TreeNode childNode in node.Nodes) { CollectSelectedFiles(childNode, selectedFiles); } } // 检查是否包含CAD插件文件夹 - 增强版本 private bool ContainsCADPluginFolder(List<string> files) { foreach (string file in files) { if (file.Contains("Zhongli Mojia Software.bundle")) { Debug.WriteLine($"找到CAD插件文件夹: {file}"); return true; } // 同时检查可能的不同命名方式 if (file.ToLower().Contains("zhongli") || file.ToLower().Contains("mojia") || file.Contains(".bundle")) { Debug.WriteLine($"找到可能的CAD插件文件: {file}"); } } // 额外检查:在临时目录中查找 if (Directory.Exists(tempAppFolder)) { string[] bundleDirs = Directory.GetDirectories(tempAppFolder, "*bundle*", SearchOption.AllDirectories); foreach (string bundleDir in bundleDirs) { Debug.WriteLine($"在临时目录中找到bundle文件夹: {bundleDir}"); if (bundleDir.Contains("Zhongli") || bundleDir.Contains("Mojia")) { Debug.WriteLine($"确认找到CAD插件文件夹: {bundleDir}"); return true; } } } return false; } // 选择图标 private void btnBrowseIcon_Click(object sender, EventArgs e) { using (OpenFileDialog openFileDialog = new OpenFileDialog()) { openFileDialog.Filter = "图标文件 (*.ico)|*.ico|所有文件 (*.*)|*.*"; openFileDialog.Title = "选择应用程序图标"; if (openFileDialog.ShowDialog() == DialogResult.OK) { txtIconPath.Text = openFileDialog.FileName; try { pictureBoxIcon.Image = System.Drawing.Icon.ExtractAssociatedIcon(openFileDialog.FileName).ToBitmap(); } catch { pictureBoxIcon.Image = null; } } } } // 选择安装路径 private void btnBrowseInstallPath_Click(object sender, EventArgs e) { using (FolderBrowserDialog folderDialog = new FolderBrowserDialog()) { folderDialog.Description = "选择默认安装目录"; folderDialog.SelectedPath = txtInstallPath.Text; if (folderDialog.ShowDialog() == DialogResult.OK) { txtInstallPath.Text = folderDialog.SelectedPath; } } } // 刷新文件夹视图 private void btnRefreshFolder_Click(object sender, EventArgs e) { if (!string.IsNullOrEmpty(txtSelectedFolder.Text) && Directory.Exists(txtSelectedFolder.Text)) { ScanFolderForExecutables(txtSelectedFolder.Text); } } // 生成安装包 - 修复版本 private void btnGenerate_Click(object sender, EventArgs e) { // 移除主程序文件检查,允许没有主程序文件 if (string.IsNullOrEmpty(txtProductName.Text)) { MessageBox.Show("请输入产品名称", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } List<string> selectedFiles = GetSelectedFiles(); if (selectedFiles.Count == 0) { MessageBox.Show("请选择要打包的文件", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return; } using (SaveFileDialog saveDialog = new SaveFileDialog()) { // 优化文件名生成:确保版本号格式正确 string productName = txtProductName.Text.Trim(); string version = txtVersion.Text.Trim(); // 构建文件名:产品名称 + 空格 + 版本号 string fileName = productName; if (!string.IsNullOrEmpty(version)) { // 确保版本号格式正确,移除可能的多余字符 version = version.Replace("_", "."); // 将下划线替换为点 fileName += $" {version}"; } saveDialog.Filter = "安装包文件 (*.exe)|*.exe"; saveDialog.FileName = fileName; saveDialog.Title = "保存安装包"; if (saveDialog.ShowDialog() == DialogResult.OK) { try { progressBar.Value = 0; btnGenerate.Enabled = false; // 使用Inno Setup生成安装包 bool success = GenerateInnoSetupScript(saveDialog.FileName, selectedFiles); if (success) { progressBar.Value = 100; MessageBox.Show($"安装包生成成功!\n保存位置: {saveDialog.FileName}", "成功", MessageBoxButtons.OK, MessageBoxIcon.Information); // 打开输出目录 string outputDir = Path.GetDirectoryName(saveDialog.FileName); if (Directory.Exists(outputDir)) { Process.Start("explorer.exe", outputDir); } } else { MessageBox.Show("安装包生成失败,请检查Inno Setup安装和配置。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } } catch (Exception ex) { MessageBox.Show($"生成安装包时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); } finally { progressBar.Value = 0; btnGenerate.Enabled = true; // 清理临时目录 CleanupTempFolder(); } } } } // 修复的Inno Setup脚本生成方法 private bool GenerateInnoSetupScript(string outputPath, List<string> files) { try { // 准备临时文件目录 PrepareTempAppFolder(files); string scriptContent = CreateInnoScriptContent(outputPath, files); string scriptPath = Path.Combine(Path.GetTempPath(), $"setup_script_{Guid.NewGuid():N}.iss"); File.WriteAllText(scriptPath, scriptContent, Encoding.UTF8); // 调试:显示生成的脚本内容 Debug.WriteLine("=== 生成的Inno Setup脚本 ==="); Debug.WriteLine(scriptContent); Debug.WriteLine("=== 脚本结束 ==="); // 编译Inno Setup脚本 bool result = CompileInnoSetupScript(scriptPath, outputPath); // 清理临时文件 try { if (File.Exists(scriptPath)) File.Delete(scriptPath); } catch { } return result; } catch (Exception ex) { MessageBox.Show($"生成脚本时出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } } // 准备临时应用程序文件夹 private void PrepareTempAppFolder(List<string> files) { // 清理临时目录 CleanupTempFolder(); // 创建临时目录 Directory.CreateDirectory(tempAppFolder); Debug.WriteLine($"=== 开始准备临时应用程序文件夹 ==="); Debug.WriteLine($"临时目录: {tempAppFolder}"); Debug.WriteLine($"文件总数: {files.Count}"); // 复制所有文件到临时目录,保持目录结构 foreach (string file in files) { try { string relativePath = GetRelativePath(file, files); string destPath = Path.Combine(tempAppFolder, relativePath); // 确保目标目录存在 string destDir = Path.GetDirectoryName(destPath); if (!Directory.Exists(destDir)) { Directory.CreateDirectory(destDir); Debug.WriteLine($"创建目录: {destDir}"); } File.Copy(file, destPath, true); Debug.WriteLine($"复制文件: {file} -> {destPath}"); // 特别记录CAD插件文件 if (file.Contains("Zhongli Mojia Software.bundle")) { Debug.WriteLine($"!!! CAD插件文件已复制: {file} -> {destPath}"); } } catch (Exception ex) { Debug.WriteLine($"!!! 复制文件 {file} 失败: {ex.Message}"); } } // 详细检查临时目录结构 Debug.WriteLine("=== 临时目录完整结构 ==="); DisplayDirectoryStructure(tempAppFolder, 0); Debug.WriteLine("=== 结构显示结束 ==="); } // 辅助方法:显示目录结构 private void DisplayDirectoryStructure(string directory, int indentLevel) { string indent = new string(' ', indentLevel * 2); try { // 显示文件 foreach (string file in Directory.GetFiles(directory)) { Debug.WriteLine($"{indent}📄 {Path.GetFileName(file)}"); } // 递归显示子目录 foreach (string subDir in Directory.GetDirectories(directory)) { string dirName = Path.GetFileName(subDir); Debug.WriteLine($"{indent}📁 {dirName}/"); DisplayDirectoryStructure(subDir, indentLevel + 1); } } catch (Exception ex) { Debug.WriteLine($"{indent}❌ 访问目录失败: {ex.Message}"); } } // 获取文件的相对路径 - 改进版本 private string GetRelativePath(string filePath, List<string> allFiles) { // 如果文件在CAD插件文件夹中,保持完整路径结构 if (filePath.Contains("Zhongli Mojia Software.bundle")) { // 找到bundle文件夹的起始位置 int bundleIndex = filePath.IndexOf("Zhongli Mojia Software.bundle"); if (bundleIndex >= 0) { string relativePath = filePath.Substring(bundleIndex); Debug.WriteLine($"CAD插件文件相对路径: {relativePath}"); return relativePath; } } // 找出共同的基础目录 string commonBase = FindCommonBaseDirectory(allFiles); if (!string.IsNullOrEmpty(commonBase) && filePath.StartsWith(commonBase)) { string relativePath = filePath.Substring(commonBase.Length).TrimStart(Path.DirectorySeparatorChar); Debug.WriteLine($"普通文件相对路径: {relativePath} (基础目录: {commonBase})"); return relativePath; } string fileNameOnly = Path.GetFileName(filePath); Debug.WriteLine($"仅文件名: {fileNameOnly}"); return fileNameOnly; } // 找出所有文件的共同基础目录 private string FindCommonBaseDirectory(List<string> files) { if (files == null || files.Count == 0) return null; string firstFile = files[0]; string baseDir = Path.GetDirectoryName(firstFile); foreach (string file in files.Skip(1)) { string fileDir = Path.GetDirectoryName(file); while (!string.IsNullOrEmpty(baseDir) && !fileDir.StartsWith(baseDir) && baseDir.Length > 0) { baseDir = Path.GetDirectoryName(baseDir); if (baseDir == null) break; } } return baseDir; } // 清理临时文件夹 private void CleanupTempFolder() { try { if (Directory.Exists(tempAppFolder)) { Directory.Delete(tempAppFolder, true); Debug.WriteLine("临时目录已清理"); } } catch (Exception ex) { Debug.WriteLine($"清理临时文件夹失败: {ex.Message}"); } } private string CreateInnoScriptContent(string outputPath, List<string> files) { string appName = txtProductName.Text; string appVersion = txtVersion.Text; string company = string.IsNullOrEmpty(txtCompanyName.Text) ? "我的公司" : txtCompanyName.Text; // 使用应用程序名称作为子目录 string installDir = $@"{{autopf}}\{appName}"; string outputDir = Path.GetDirectoryName(outputPath); string outputBaseName = Path.GetFileNameWithoutExtension(outputPath); StringBuilder sb = new StringBuilder(); // ==================== [Setup] 部分 ==================== sb.AppendLine(@"[Setup]"); sb.AppendLine($"AppName={appName}"); sb.AppendLine($"AppVersion={appVersion}"); sb.AppendLine($"AppVerName={appName} {appVersion}"); sb.AppendLine($"AppPublisher={company}"); sb.AppendLine($"DefaultDirName={installDir}"); sb.AppendLine("AppendDefaultDirName=no"); sb.AppendLine("UsePreviousAppDir=no"); sb.AppendLine("DisableDirPage=no"); sb.AppendLine("DirExistsWarning=no"); sb.AppendLine("DisableProgramGroupPage=yes"); sb.AppendLine($"OutputDir={outputDir}"); sb.AppendLine($"OutputBaseFilename={outputBaseName}"); sb.AppendLine("Compression=lzma"); sb.AppendLine("SolidCompression=yes"); sb.AppendLine("SetupLogging=yes"); sb.AppendLine("PrivilegesRequired=admin"); // 添加管理员权限 sb.AppendLine("AppId=ZhongliMojiaCADPlugin"); sb.AppendLine("DisableFinishedPage=no"); sb.AppendLine("DisableWelcomePage=no"); sb.AppendLine("ShowComponentSizes=no"); sb.AppendLine("UsePreviousTasks=no"); sb.AppendLine("CreateUninstallRegKey=yes"); sb.AppendLine("Uninstallable=yes"); // 添加卸载文件锁定处理 sb.AppendLine("UninstallFilesDir={app}"); // 确保卸载文件在应用目录 if (!string.IsNullOrEmpty(txtIconPath.Text) && File.Exists(txtIconPath.Text)) { sb.AppendLine($"SetupIconFile={txtIconPath.Text}"); } // ==================== [Languages] 部分 ==================== sb.AppendLine(@"[Languages]"); string chineseLangPath = Path.Combine(innoSetupPath, "Languages", "ChineseSimplified.isl"); if (File.Exists(chineseLangPath)) { sb.AppendLine(@"Name: ""chinesesimplified""; MessagesFile: ""compiler:Languages\ChineseSimplified.isl"""); } else { sb.AppendLine(@"Name: ""english""; MessagesFile: ""compiler:Default.isl"""); } // ==================== [Files] 部分 ==================== sb.AppendLine(@"[Files]"); bool hasCADPlugin = ContainsCADPluginFolder(files); foreach (string file in files) { string relativePath = GetRelativePath(file, files); string sourcePath = Path.Combine(tempAppFolder, relativePath); if (hasCADPlugin && relativePath.Contains("Zhongli Mojia Software.bundle")) { string cadRelativePath = relativePath.Substring(relativePath.IndexOf("Zhongli Mojia Software.bundle")); string destDir = @"{commonappdata}\Autodesk\ApplicationPlugins\" + Path.GetDirectoryName(cadRelativePath); sb.AppendLine($@"Source: ""{sourcePath}""; DestDir: ""{destDir}""; Flags: ignoreversion nocompression"); } else { string destDir = "{app}"; if (!string.IsNullOrEmpty(Path.GetDirectoryName(relativePath))) { destDir += "\\" + Path.GetDirectoryName(relativePath); } sb.AppendLine($@"Source: ""{sourcePath}""; DestDir: ""{destDir}""; Flags: ignoreversion"); } } // ==================== [Icons] 部分 ==================== if (!string.IsNullOrEmpty(txtMainProgram.Text) && File.Exists(txtMainProgram.Text)) { sb.AppendLine(@"[Icons]"); string mainExe = Path.GetFileName(txtMainProgram.Text); sb.AppendLine($@"Name: ""{{autodesktop}}\{appName}""; Filename: ""{{app}}\{mainExe}"""); sb.AppendLine($@"Name: ""{{autoprograms}}\{appName}""; Filename: ""{{app}}\{mainExe}"""); } // ==================== [Run] 部分 ==================== if (!string.IsNullOrEmpty(txtMainProgram.Text) && File.Exists(txtMainProgram.Text)) { string mainExe = Path.GetFileName(txtMainProgram.Text); bool mainExeIncluded = files.Any(f => string.Equals(Path.GetFileName(f), mainExe, StringComparison.OrdinalIgnoreCase)); if (mainExeIncluded) { sb.AppendLine(@"[Run]"); sb.AppendLine($@"Filename: ""{{app}}\{mainExe}""; Description: ""运行 {appName}""; Flags: nowait postinstall skipifsilent"); } } // ==================== [UninstallRun] 部分 ==================== sb.AppendLine(@"[UninstallRun]"); sb.AppendLine(@"Filename: ""{app}\unins000.exe""; Flags: runhidden"); // ==================== [UninstallDelete] 部分 ==================== sb.AppendLine(@"[UninstallDelete]"); sb.AppendLine(@"Type: filesandordirs; Name: ""{app}"""); sb.AppendLine(@"Type: filesandordirs; Name: ""{commonappdata}\Autodesk\ApplicationPlugins\Zhongli Mojia Software.bundle"""); // ==================== [Code] 部分 ==================== sb.AppendLine(@"[Code]"); // 声明Windows API函数 sb.AppendLine(@"function FindWindow(lpClassName, lpWindowName: PAnsiChar): HWND;"); sb.AppendLine(@"external 'FindWindowA@user32.dll stdcall';"); sb.AppendLine(@""); sb.AppendLine(@"function PostMessage(hWnd: HWND; Msg: UINT; wParam, lParam: Longint): BOOL;"); sb.AppendLine(@"external 'PostMessageA@user32.dll stdcall';"); sb.AppendLine(@""); sb.AppendLine(@"const"); sb.AppendLine(@" WM_CLOSE = 16;"); sb.AppendLine(@" PROCESS_TERMINATE = $0001;"); sb.AppendLine(@""); sb.AppendLine(@"function TerminateProcessByID(ProcessID: DWORD): Boolean;"); sb.AppendLine(@"external 'TerminateProcess@kernel32.dll stdcall';"); sb.AppendLine(@""); sb.AppendLine(@"function OpenProcess(dwDesiredAccess: DWORD; bInheritHandle: BOOL; dwProcessId: DWORD): THandle;"); sb.AppendLine(@"external 'OpenProcess@kernel32.dll stdcall';"); sb.AppendLine(@""); sb.AppendLine(@"function CloseHandle(hObject: THandle): BOOL;"); sb.AppendLine(@"external 'CloseHandle@kernel32.dll stdcall';"); sb.AppendLine(@""); // 查找并关闭主程序 sb.AppendLine(@"procedure CloseMainApplication();"); sb.AppendLine(@"var"); sb.AppendLine(@" hWnd: HWND;"); sb.AppendLine(@" processID: DWORD;"); sb.AppendLine(@" hProcess: THandle;"); sb.AppendLine(@"begin"); sb.AppendLine(@" hWnd := FindWindow(nil, '忠利模架CAD插件');"); sb.AppendLine(@" if hWnd <> 0 then"); sb.AppendLine(@" begin"); sb.AppendLine(@" PostMessage(hWnd, WM_CLOSE, 0, 0);"); sb.AppendLine(@" Sleep(1000); // 等待进程退出"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" // 强制终止同名进程"); sb.AppendLine(@" if Exec('taskkill', '/F /IM ""忠利模架CAD插件.exe""', '', SW_HIDE, ewWaitUntilTerminated, processID) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" Log('成功终止主进程');"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 安全删除文件(带重试机制) sb.AppendLine(@"function SafeDeleteFile(const FileName: string): Boolean;"); sb.AppendLine(@"var"); sb.AppendLine(@" Tries: Integer;"); sb.AppendLine(@"begin"); sb.AppendLine(@" Tries := 0;"); sb.AppendLine(@" Result := False;"); sb.AppendLine(@" "); sb.AppendLine(@" while Tries < 5 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" if DeleteFile(FileName) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" Result := True;"); sb.AppendLine(@" Break;"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" Sleep(1000); // 等待1秒后重试"); sb.AppendLine(@" Tries := Tries + 1;"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" if not Result then"); sb.AppendLine(@" Log('无法删除文件: ' + FileName + ' | 错误代码: ' + IntToStr(DLLGetLastError));"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 安全删除目录 sb.AppendLine(@"function SafeDeleteDirectory(const DirName: string): Boolean;"); sb.AppendLine(@"var"); sb.AppendLine(@" Tries: Integer;"); sb.AppendLine(@"begin"); sb.AppendLine(@" Tries := 0;"); sb.AppendLine(@" Result := False;"); sb.AppendLine(@" "); sb.AppendLine(@" while Tries < 5 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" if DelTree(DirName, True, True, True) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" Result := True;"); sb.AppendLine(@" Break;"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" Sleep(2000); // 等待2秒后重试"); sb.AppendLine(@" Tries := Tries + 1;"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 注册重启后删除 sb.AppendLine(@"procedure RegisterRestartDelete(const FileName: string);"); sb.AppendLine(@"begin"); sb.AppendLine(@" RegWriteStringValue(HKEY_LOCAL_MACHINE, "); sb.AppendLine(@" 'SYSTEM\CurrentControlSet\Control\Session Manager\Environment', "); sb.AppendLine(@" 'PendingFileRenameOperations', "); sb.AppendLine(@" '\??\' + FileName + #0);"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 在常见路径中查找AutoCAD - 必须先定义 sb.AppendLine(@"procedure FindAutoCADInCommonPath(basePath: string; var paths: TArrayOfString);"); sb.AppendLine(@"var"); sb.AppendLine(@" subDirs: TArrayOfString;"); sb.AppendLine(@" i: Integer;"); sb.AppendLine(@" acadExePath: string;"); sb.AppendLine(@"begin"); sb.AppendLine(@" if FindSubDirectories(basePath, subDirs) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" for i := 0 to GetArrayLength(subDirs) - 1 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" acadExePath := subDirs[i] + '\\acad.exe';"); sb.AppendLine(@" if FileExists(acadExePath) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" SetArrayLength(paths, GetArrayLength(paths) + 1);"); sb.AppendLine(@" paths[GetArrayLength(paths) - 1] := subDirs[i];"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 查找AutoCAD安装路径 - 简化版本 sb.AppendLine(@"function FindAutoCADInstallations(): TArrayOfString;"); sb.AppendLine(@"var"); sb.AppendLine(@" paths: TArrayOfString;"); sb.AppendLine(@" versions, acadPaths: TArrayOfString;"); sb.AppendLine(@" i, j: Integer;"); sb.AppendLine(@" location: string;"); sb.AppendLine(@" acadExePath: string;"); sb.AppendLine(@"begin"); sb.AppendLine(@" SetArrayLength(paths, 0);"); sb.AppendLine(@" "); sb.AppendLine(@" // 方法1: 通过注册表查找AutoCAD安装路径"); sb.AppendLine(@" if RegGetSubkeyNames(HKEY_LOCAL_MACHINE, 'SOFTWARE\\Autodesk\\AutoCAD', versions) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" for i := 0 to GetArrayLength(versions) - 1 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" if RegGetSubkeyNames(HKEY_LOCAL_MACHINE, 'SOFTWARE\\Autodesk\\AutoCAD\\' + versions[i], acadPaths) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" for j := 0 to GetArrayLength(acadPaths) - 1 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\\Autodesk\\AutoCAD\\' + versions[i] + '\\' + acadPaths[j], 'Location', location) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" if (location <> '') and DirExists(location) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 验证是否真的是AutoCAD目录(检查acad.exe是否存在)"); sb.AppendLine(@" acadExePath := location + '\\acad.exe';"); sb.AppendLine(@" if FileExists(acadExePath) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" SetArrayLength(paths, GetArrayLength(paths) + 1);"); sb.AppendLine(@" paths[GetArrayLength(paths) - 1] := location;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" // 方法2: 检查常见安装路径"); sb.AppendLine(@" if GetArrayLength(paths) = 0 then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 检查Program Files目录"); sb.AppendLine(@" if DirExists('C:\\Program Files\\Autodesk') then"); sb.AppendLine(@" begin"); sb.AppendLine(@" FindAutoCADInCommonPath('C:\\Program Files\\Autodesk', paths);"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" // 检查Program Files (x86)目录"); sb.AppendLine(@" if DirExists('C:\\Program Files (x86)\\Autodesk') then"); sb.AppendLine(@" begin"); sb.AppendLine(@" FindAutoCADInCommonPath('C:\\Program Files (x86)\\Autodesk', paths);"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" Result := paths;"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 检查天正路径 - 必须先定义 sb.AppendLine(@"procedure CheckTArchPath(path: string; var paths: TArrayOfString);"); sb.AppendLine(@"var"); sb.AppendLine(@" subDirs: TArrayOfString;"); sb.AppendLine(@" i: Integer;"); sb.AppendLine(@" sysPath: string;"); sb.AppendLine(@"begin"); sb.AppendLine(@" if DirExists(path) and FindSubDirectories(path, subDirs) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" for i := 0 to GetArrayLength(subDirs) - 1 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" sysPath := subDirs[i] + '\\sys23x64';"); sb.AppendLine(@" if DirExists(sysPath) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" SetArrayLength(paths, GetArrayLength(paths) + 1);"); sb.AppendLine(@" paths[GetArrayLength(paths) - 1] := subDirs[i];"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 查找天正安装路径 - 简化版本 sb.AppendLine(@"function FindTArchInstallations(): TArrayOfString;"); sb.AppendLine(@"var"); sb.AppendLine(@" paths: TArrayOfString;"); sb.AppendLine(@" versions: TArrayOfString;"); sb.AppendLine(@" i: Integer;"); sb.AppendLine(@" installPath: string;"); sb.AppendLine(@" sysPath: string;"); sb.AppendLine(@"begin"); sb.AppendLine(@" SetArrayLength(paths, 0);"); sb.AppendLine(@" "); sb.AppendLine(@" // 方法1: 通过注册表查找天正安装路径"); sb.AppendLine(@" if RegGetSubkeyNames(HKEY_LOCAL_MACHINE, 'SOFTWARE\\Tangent', versions) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" for i := 0 to GetArrayLength(versions) - 1 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" if RegQueryStringValue(HKEY_LOCAL_MACHINE, 'SOFTWARE\\Tangent\\' + versions[i], 'InstallPath', installPath) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" if (installPath <> '') and DirExists(installPath) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 验证是否存在sys23x64目录"); sb.AppendLine(@" sysPath := installPath + '\\sys23x64';"); sb.AppendLine(@" if DirExists(sysPath) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" SetArrayLength(paths, GetArrayLength(paths) + 1);"); sb.AppendLine(@" paths[GetArrayLength(paths) - 1] := installPath;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" // 方法2: 检查常见安装路径"); sb.AppendLine(@" if GetArrayLength(paths) = 0 then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 检查常见天正安装路径"); sb.AppendLine(@" CheckTArchPath('C:\\Program Files\\Tangent', paths);"); sb.AppendLine(@" CheckTArchPath('C:\\Tangent', paths);"); sb.AppendLine(@" CheckTArchPath('D:\\Program Files\\Tangent', paths);"); sb.AppendLine(@" CheckTArchPath('D:\\Tangent', paths);"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" Result := paths;"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 移除acaddoc.lsp文件 sb.AppendLine(@"procedure RemoveAcaddocFromAutoCAD();"); sb.AppendLine(@"var"); sb.AppendLine(@" acadPaths: TArrayOfString;"); sb.AppendLine(@" tarchPaths: TArrayOfString;"); sb.AppendLine(@" i: Integer;"); sb.AppendLine(@" filePath: string;"); sb.AppendLine(@"begin"); sb.AppendLine(@" // 从AutoCAD安装目录删除"); sb.AppendLine(@" acadPaths := FindAutoCADInstallations();"); sb.AppendLine(@" for i := 0 to GetArrayLength(acadPaths) - 1 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" if acadPaths[i] <> '' then"); sb.AppendLine(@" begin"); sb.AppendLine(@" filePath := acadPaths[i] + '\\acaddoc.lsp';"); sb.AppendLine(@" if FileExists(filePath) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" DeleteFile(filePath);"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" // 从天正安装目录删除"); sb.AppendLine(@" tarchPaths := FindTArchInstallations();"); sb.AppendLine(@" for i := 0 to GetArrayLength(tarchPaths) - 1 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" if tarchPaths[i] <> '' then"); sb.AppendLine(@" begin"); sb.AppendLine(@" filePath := tarchPaths[i] + '\\sys23x64\\acaddoc.lsp';"); sb.AppendLine(@" if FileExists(filePath) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" DeleteFile(filePath);"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 复制acaddoc.lsp文件 sb.AppendLine(@"procedure CopyAcaddocToAutoCAD();"); sb.AppendLine(@"var"); sb.AppendLine(@" acadPaths: TArrayOfString;"); sb.AppendLine(@" tarchPaths: TArrayOfString;"); sb.AppendLine(@" i: Integer;"); sb.AppendLine(@" sourceFile, destFile: string;"); sb.AppendLine(@" sourceFound: Boolean;"); sb.AppendLine(@"begin"); sb.AppendLine(@" sourceFound := False;"); sb.AppendLine(@" "); sb.AppendLine(@" // 尝试多个可能的源文件路径"); sb.AppendLine(@" sourceFile := ExpandConstant('{commonappdata}') + '\\Autodesk\\ApplicationPlugins\\Zhongli Mojia Software.bundle\\Zhongli Mojia Software\\net48\\acaddoc.lsp';"); sb.AppendLine(@" if FileExists(sourceFile) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" sourceFound := True;"); sb.AppendLine(@" end"); sb.AppendLine(@" else"); sb.AppendLine(@" begin"); sb.AppendLine(@" sourceFile := ExpandConstant('{commonappdata}') + '\\Autodesk\\ApplicationPlugins\\Zhongli Mojia Software.bundle\\Contents\\acaddoc.lsp';"); sb.AppendLine(@" if FileExists(sourceFile) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" sourceFound := True;"); sb.AppendLine(@" end"); sb.AppendLine(@" else"); sb.AppendLine(@" begin"); sb.AppendLine(@" sourceFile := ExpandConstant('{commonappdata}') + '\\Autodesk\\ApplicationPlugins\\Zhongli Mojia Software.bundle\\acaddoc.lsp';"); sb.AppendLine(@" if FileExists(sourceFile) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" sourceFound := True;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" if sourceFound then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 复制到AutoCAD安装目录"); sb.AppendLine(@" acadPaths := FindAutoCADInstallations();"); sb.AppendLine(@" for i := 0 to GetArrayLength(acadPaths) - 1 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" if acadPaths[i] <> '' then"); sb.AppendLine(@" begin"); sb.AppendLine(@" destFile := acadPaths[i] + '\\acaddoc.lsp';"); sb.AppendLine(@" try"); sb.AppendLine(@" if FileCopy(sourceFile, destFile, False) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 复制成功"); sb.AppendLine(@" end;"); sb.AppendLine(@" except"); sb.AppendLine(@" // 忽略复制错误"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" // 复制到天正安装目录"); sb.AppendLine(@" tarchPaths := FindTArchInstallations();"); sb.AppendLine(@" for i := 0 to GetArrayLength(tarchPaths) - 1 do"); sb.AppendLine(@" begin"); sb.AppendLine(@" if tarchPaths[i] <> '' then"); sb.AppendLine(@" begin"); sb.AppendLine(@" destFile := tarchPaths[i] + '\\sys23x64\\acaddoc.lsp';"); sb.AppendLine(@" ForceDirectories(ExtractFilePath(destFile));"); sb.AppendLine(@" try"); sb.AppendLine(@" if FileCopy(sourceFile, destFile, False) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 复制成功"); sb.AppendLine(@" end;"); sb.AppendLine(@" except"); sb.AppendLine(@" // 忽略复制错误"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 卸载步骤处理 sb.AppendLine(@"procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);"); sb.AppendLine(@"var"); sb.AppendLine(@" ResultCode: Integer;"); sb.AppendLine(@"begin"); sb.AppendLine(@" if CurUninstallStep = usUninstall then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 关闭可能占用文件的进程"); sb.AppendLine(@" Exec('taskkill', '/f /im ""权利档案CAD插件*""', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);"); sb.AppendLine(@" Exec('taskkill', '/f /im ""Zhongli*""', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);"); sb.AppendLine(@" Exec('taskkill', '/f /im ""acad.exe""', '', SW_HIDE, ewWaitUntilTerminated, ResultCode);"); sb.AppendLine(@" "); sb.AppendLine(@" // 等待进程完全关闭"); sb.AppendLine(@" Sleep(2000);"); sb.AppendLine(@" "); sb.AppendLine(@" // 清理AutoCAD相关文件"); sb.AppendLine(@" RemoveAcaddocFromAutoCAD();"); sb.AppendLine(@" "); sb.AppendLine(@" // 删除CAD插件目录"); sb.AppendLine(@" if DirExists(ExpandConstant('{commonappdata}') + '\\Autodesk\\ApplicationPlugins\\Zhongli Mojia Software.bundle') then"); sb.AppendLine(@" begin"); sb.AppendLine(@" DelTree(ExpandConstant('{commonappdata}') + '\\Autodesk\\ApplicationPlugins\\Zhongli Mojia Software.bundle', True, True, True);"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); sb.AppendLine(@""); // 安装步骤处理 sb.AppendLine(@"procedure CurStepChanged(CurStep: TSetupStep);"); sb.AppendLine(@"begin"); sb.AppendLine(@" if CurStep = ssPostInstall then"); sb.AppendLine(@" begin"); sb.AppendLine(@" CopyAcaddocToAutoCAD();"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); // 修改后的卸载步骤 sb.AppendLine(@"procedure CurUninstallStepChanged(CurUninstallStep: TUninstallStep);"); sb.AppendLine(@"var"); sb.AppendLine(@" appDir, datFile: string;"); sb.AppendLine(@"begin"); sb.AppendLine(@" if CurUninstallStep = usUninstall then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 1. 关闭主应用程序"); sb.AppendLine(@" CloseMainApplication();"); sb.AppendLine(@" "); sb.AppendLine(@" // 2. 删除AutoCAD相关文件"); sb.AppendLine(@" RemoveAcaddocFromAutoCAD();"); sb.AppendLine(@" "); sb.AppendLine(@" // 3. 准备删除应用目录"); sb.AppendLine(@" appDir := ExpandConstant('{app}');"); sb.AppendLine(@" datFile := appDir + '\unins000.dat';"); sb.AppendLine(@" "); sb.AppendLine(@" // 4. 尝试安全删除"); sb.AppendLine(@" if not SafeDeleteFile(datFile) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" // 5. 注册重启删除"); sb.AppendLine(@" RegisterRestartDelete(datFile);"); sb.AppendLine(@" MsgBox('文件正在使用中,将在系统重启后删除', mbInformation, MB_OK);"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" // 6. 删除整个应用程序目录"); sb.AppendLine(@" if not SafeDeleteDirectory(appDir) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" RegisterRestartDelete(appDir);"); sb.AppendLine(@" MsgBox('目录正在使用中,将在系统重启后删除', mbInformation, MB_OK);"); sb.AppendLine(@" end;"); sb.AppendLine(@" "); sb.AppendLine(@" // 7. 删除CAD插件"); sb.AppendLine(@" if not SafeDeleteDirectory(ExpandConstant('{commonappdata}\Autodesk\ApplicationPlugins\Zhongli Mojia Software.bundle')) then"); sb.AppendLine(@" begin"); sb.AppendLine(@" RegisterRestartDelete(ExpandConstant('{commonappdata}\Autodesk\ApplicationPlugins\Zhongli Mojia Software.bundle'));"); sb.AppendLine(@" end;"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); // 安装完成后的步骤 sb.AppendLine(@"procedure CurStepChanged(CurStep: TSetupStep);"); sb.AppendLine(@"begin"); sb.AppendLine(@" if CurStep = ssPostInstall then"); sb.AppendLine(@" begin"); sb.AppendLine(@" CopyAcaddocToAutoCAD();"); sb.AppendLine(@" end;"); sb.AppendLine(@"end;"); return sb.ToString(); } // 修复的编译方法 private bool CompileInnoSetupScript(string scriptPath, string outputPath) { string innoCompiler = FindInnoSetupCompiler(); if (string.IsNullOrEmpty(innoCompiler)) { MessageBox.Show("未找到Inno Setup编译器。请先安装Inno Setup 5或6版本。", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } try { ProcessStartInfo psi = new ProcessStartInfo { FileName = innoCompiler, Arguments = $"/Q \"{scriptPath}\"", UseShellExecute = false, RedirectStandardOutput = true, RedirectStandardError = true, CreateNoWindow = true, WorkingDirectory = Path.GetTempPath() }; using (Process process = new Process()) { process.StartInfo = psi; StringBuilder output = new StringBuilder(); StringBuilder error = new StringBuilder(); process.OutputDataReceived += (s, e) => { if (!string.IsNullOrEmpty(e.Data)) output.AppendLine(e.Data); }; process.ErrorDataReceived += (s, e) => { if (!string.IsNullOrEmpty(e.Data)) error.AppendLine(e.Data); }; process.Start(); process.BeginOutputReadLine(); process.BeginErrorReadLine(); bool exited = process.WaitForExit(60000); // 60秒超时 if (!exited) { process.Kill(); MessageBox.Show("Inno Setup编译超时,请检查系统资源或重试。", "编译超时", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } if (process.ExitCode == 0) { return true; } else { string errorMsg = $"Inno Setup编译失败 (退出代码: {process.ExitCode})"; if (output.Length > 0) errorMsg += $"\n输出: {output}"; if (error.Length > 0) errorMsg += $"\n错误: {error}"; MessageBox.Show(errorMsg, "编译错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } } } catch (Exception ex) { MessageBox.Show($"编译过程中出错: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error); return false; } } // 增强的Inno Setup查找方法 private string FindInnoSetupCompiler() { string[] possiblePaths = { @"C:\Program Files (x86)\Inno Setup 6\ISCC.exe", @"C:\Program Files\Inno Setup 6\ISCC.exe", @"C:\Program Files (x86)\Inno Setup 5\ISCC.exe", @"C:\Program Files\Inno Setup 5\ISCC.exe", @"D:\Program Files (x86)\Inno Setup 6\ISCC.exe", @"D:\Program Files\Inno Setup 6\ISCC.exe" }; foreach (string path in possiblePaths) { if (File.Exists(path)) { return path; } } // 尝试从注册表查找 try { string[] registryPaths = { @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1", @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 5_is1", @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1", @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 5_is1" }; foreach (string registryPath in registryPaths) { using (RegistryKey key = Registry.LocalMachine.OpenSubKey(registryPath)) { if (key != null) { string installPath = key.GetValue("InstallLocation")?.ToString(); if (!string.IsNullOrEmpty(installPath)) { string compilerPath = Path.Combine(installPath, "ISCC.exe"); if (File.Exists(compilerPath)) return compilerPath; } } } } // 尝试从32位注册表查找 using (RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32) .OpenSubKey(@"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Inno Setup 6_is1")) { if (key != null) { string installPath = key.GetValue("InstallLocation")?.ToString(); if (!string.IsNullOrEmpty(installPath)) { string compilerPath = Path.Combine(installPath, "ISCC.exe"); if (File.Exists(compilerPath)) return compilerPath; } } } } catch (Exception ex) { Debug.WriteLine($"注册表查找失败: {ex.Message}"); } return null; } // 添加附加文件 private void btnAddFile_Click(object sender, EventArgs e) { using (OpenFileDialog openFileDialog = new OpenFileDialog()) { openFileDialog.Multiselect = true; openFileDialog.Filter = "所有文件 (*.*)|*.*"; openFileDialog.Title = "选择附加文件"; if (openFileDialog.ShowDialog() == DialogResult.OK) { foreach (string fileName in openFileDialog.FileNames) { listBoxAdditionalFiles.Items.Add(fileName); } } } } // 移除选中的附加文件 private void btnRemoveFile_Click(object sender, EventArgs e) { if (listBoxAdditionalFiles.SelectedIndex != -1) { listBoxAdditionalFiles.Items.RemoveAt(listBoxAdditionalFiles.SelectedIndex); } } // 清空所有设置 private void btnClear_Click(object sender, EventArgs e) { if (MessageBox.Show("确定要清空所有设置吗?", "确认", MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes) { txtMainProgram.Clear(); txtSelectedFolder.Clear(); txtProductName.Text = "忠利模架CAD插件"; txtVersion.Text = "v1.0"; txtIconPath.Clear(); // 重置为默认子目录路径 string appName = txtProductName.Text; txtInstallPath.Text = $@"C:\Program Files (x86)\Autodesk\{appName}"; txtCompanyName.Clear(); treeViewFolder.Nodes.Clear(); pictureBoxIcon.Image = null; listBoxAdditionalFiles.Items.Clear(); progressBar.Value = 0; // 清理临时目录 CleanupTempFolder(); } } // 显示关于信息 private void btnAbout_Click(object sender, EventArgs e) { MessageBox.Show( "安装包打包工具 v4.0\n\n" + "功能特性:\n" + "• 创建专业的 Windows 安装程序 (.exe)\n" + "• 使用 Inno Setup 引擎\n" + "• 支持自定义安装路径\n" + "• 自动设置图标和版本信息\n" + "• 支持附加文件打包\n" + "• 包含卸载功能\n" + "• 自动检测已安装版本\n" + "• 支持卸载旧版本后安装\n" + "• 支持覆盖安装\n" + "• 自动安装CAD插件到AutoCAD\n" + "• 全盘搜索AutoCAD和天正安装目录\n" + "• 自动复制acaddoc.lsp到所有AutoCAD和天正目录\n" + "• 卸载时自动清理所有相关文件\n\n" + "注意: 需要安装 Inno Setup 5 或 6\n" + "版权所有 © 2024", "关于", MessageBoxButtons.OK, MessageBoxIcon.Information); } // 窗体关闭时清理资源 protected override void OnFormClosed(FormClosedEventArgs e) { base.OnFormClosed(e); CleanupTempFolder(); } } } 生成安装包提示错误 Inno Setup编译失败(退出代码:2错误:Error on line 63 in C:\Users\椿 \AppData\Local\Temp\setup script 524aa389b3b14c57b99bc6a2c38753 36.iss: Column 39: Type mismatch. Compile aborted.
10-14
# -*- mode: python ; coding: utf-8 -*- a = Analysis( ['wanzheng.py'], pathex=[], binaries=[], datas=[], hiddenimports=[], hookspath=[], hooksconfig={}, runtime_hooks=[], excludes=[], noarchive=False, optimize=0, ) pyz = PYZ(a.pure) exe = EXE( pyz, a.scripts, [], exclude_binaries=True, name='wanzheng', debug=False, bootloader_ignore_signals=False, strip=False, upx=True, console=True, disable_windowed_traceback=False, argv_emulation=False, target_arch=None, codesign_identity=None, entitlements_file=None, ) coll = COLLECT( exe, a.binaries, a.datas, strip=False, upx=True, upx_exclude=[], name='wanzheng', ) 这是我wanzheng.py的spec的打包代码下面是我wanzheng.py的完整代码,要求根据我的wanzheng.py的完整代码,填充spec的必要依赖 # -*- coding: utf-8 -*- import sys import os import cv2 import numpy as np import time from PyQt5.QtWidgets import ( QApplication, QMainWindow, QPushButton, QWidget, QVBoxLayout, QHBoxLayout, QMessageBox, QLabel, QFileDialog, QToolBar, QComboBox, QStatusBar, QGroupBox, QSlider, QDockWidget, QProgressDialog, QLineEdit, QRadioButton, QGridLayout, QSpinBox ) from PyQt5.QtCore import QRect, Qt, QSettings, QThread, pyqtSignal from CamOperation_class import CameraOperation #sys.path.append("D:\\海康\\MVS\\Development\\Samples\\Python\\wanzheng.py") import ctypes from ctypes import cast, POINTER from datetime import datetime import logging import socket import serial import skimage import platform from CameraConstants import * import threading import time class ManagedThread(threading.Thread): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self._stop_event = threading.Event() # 设置为非守护线程 self.daemon = False def stop(self): """安全停止线程""" self._stop_event.set() def should_stop(self): """检查是否应该停止""" return self._stop_event.is_set() def worker(): """线程工作函数""" try: while not threading.current_thread().should_stop(): # 模拟工作 time.sleep(1) # 安全输出 sys.stdout.write("Working...\n") except Exception as e: # 避免在关闭时使用stderr pass def main(): # 创建并启动线程 threads = [] for _ in range(3): t = ManagedThread(target=worker) t.start() threads.append(t) try: # 主程序逻辑 time.sleep(5) finally: # 安全停止所有线程 for t in threads: t.stop() for t in threads: t.join(timeout=2.0) # 设置超时避免无限等待 # 确保所有输出完成 sys.stdout.flush() sys.stderr.flush() # 在导入部分添加 from CameraParams_header import ( MV_GIGE_DEVICE, MV_USB_DEVICE, MV_GENTL_CAMERALINK_DEVICE, MV_GENTL_CXP_DEVICE, MV_GENTL_XOF_DEVICE ) # 获取当前文件所在目录 current_dir = os.path.dirname(os.path.abspath(__file__)) # ===== 路径修复 ===== sdk_path = os.path.join(current_dir, "MvImport") if sdk_path not in sys.path: sys.path.append(sdk_path) def fix_sdk_path(): """修复海康SDK的加载路径""" if getattr(sys, 'frozen', False): # 打包模式 base_path = sys._MEIPASS # 添加DLL目录到系统路径 dll_path = os.path.join(base_path, "dlls") os.environ['PATH'] = dll_path + os.pathsep + os.environ['PATH'] try: # 直接加载DLL ctypes.WinDLL(os.path.join(dll_path, "MvCamCtrldll.dll")) ctypes.WinDLL(os.path.join(dll_path, "MvCameraControl.dll")) except OSError as e: logging.error(f"核心DLL加载失败: {e}") sys.exit(1) else: # 开发模式 # 确保SDK路径存在 if sdk_path not in sys.path: sys.path.append(sdk_path) # 添加DLL到系统路径 dll_dir = r"D:\海康\MVS\Runtime\Win64" if dll_dir not in os.environ['PATH']: os.environ['PATH'] = dll_dir + os.pathsep + os.environ['PATH'] # 立即执行路径修复 fix_sdk_path() # ===== 正确导入SDK模块 ===== try: from MvImport.MvCameraControl_class import MvCamera print("成功导入MvCamera类") from CameraParams_header import * from MvErrorDefine_const import * except ImportError as e: print(f"SDK导入失败: {e}") sys.exit(1) # 配置日志系统 logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', handlers=[ logging.FileHandler("cloth_inspection_debug.log"), logging.StreamHandler() ] ) logging.info("布料印花检测系统启动") # 全局变量 current_sample_path = "" detection_history = [] isGrabbing = False isOpen = False obj_cam_operation = None frame_monitor_thread = None sensor_monitor_thread = None sensor_controller = None MV_OK = 0 MV_E_CALLORDER = -2147483647 # ==================== 传感器通讯模块 ==================== class SensorController: def __init__(self): self.connected = False self.running = False self.connection = None def connect(self, config): try: if config['type'] == 'serial': self.connection = serial.Serial( port=config['port'], baudrate=config['baudrate'], timeout=config.get('timeout', 1.0) ) else: self.connection = socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.connection.connect((config['ip'], config['port'])) self.connection.settimeout(config.get('timeout', 1.0)) self.connected = True self.running = True logging.info(f"传感器连接成功: {config}") return True except Exception as e: logging.error(f"传感器连接失败: {str(e)}") return False def disconnect(self): if self.connection: try: self.connection.close() except: pass self.connection = None self.connected = False self.running = False logging.info("传感器已断开") def read_data(self): if not self.connected: return None return { 'tension': np.random.uniform(10.0, 20.0), 'speed': np.random.uniform(1.0, 5.0), 'temperature': np.random.uniform(20.0, 30.0), 'humidity': np.random.uniform(40.0, 60.0) } def wait_for_material(self, delay_seconds=0): if not self.connected: logging.warning("未连接传感器,跳过等待") return False logging.info(f"等待布料到达,延迟 {delay_seconds} 秒") start_time = time.time() while time.time() - start_time < delay_seconds: QThread.msleep(100) if not self.running: return False logging.info("布料已到位,准备拍摄") return True class SensorMonitorThread(QThread): data_updated = pyqtSignal(dict) def __init__(self, sensor_controller): super().__init__() self.sensor_controller = sensor_controller self.running = True def run(self): while self.running: if self.sensor_controller and self.sensor_controller.connected: try: data = self.sensor_controller.read_data() if data: self.data_updated.emit(data) except Exception as e: logging.error(f"传感器数据读取错误: {str(e)}") QThread.msleep(500) def stop(self): self.running = False self.wait(2000) def wait_for_material(self, delay_seconds): return self.sensor_controller.wait_for_material(delay_seconds) # ==================== 相机帧监控线程 ==================== class FrameMonitorThread(QThread): frame_status = pyqtSignal(str) # 用于发送状态消息的信号 def __init__(self, cam_operation): super().__init__() self.cam_operation = cam_operation self.running = True self.frame_count = 0 self.last_time = time.time() def run(self): """监控相机帧状态的主循环""" while self.running: try: if self.cam_operation and self.cam_operation.is_grabbing: # 获取帧统计信息 frame_info = self.get_frame_info() if frame_info: fps = frame_info.get('fps', 0) dropped = frame_info.get('dropped', 0) status = f"FPS: {fps:.1f} | 丢帧: {dropped}" self.frame_status.emit(status) else: self.frame_status.emit("取流中...") else: self.frame_status.emit("相机未取流") except Exception as e: self.frame_status.emit(f"监控错误: {str(e)}") # 每500ms检查一次 QThread.msleep(500) def stop(self): """停止监控线程""" self.running = False self.wait(1000) # 等待线程结束 def calculate_fps(self): """计算当前帧率""" current_time = time.time() elapsed = current_time - self.last_time if elapsed > 0: fps = self.frame_count / elapsed self.frame_count = 0 self.last_time = current_time return fps return 0 def get_frame_info(self): """获取帧信息""" try: # 更新帧计数 self.frame_count += 1 # 返回帧信息 return { 'fps': self.calculate_fps(), 'dropped': 0 # 实际应用中需要从相机获取真实丢帧数 } except Exception as e: logging.error(f"获取帧信息失败: {str(e)}") return None # ==================== 优化后的检测算法 ==================== def enhanced_check_print_quality(sample_image_path, test_image, threshold=0.05, sensor_data=None): if sensor_data: speed_factor = min(1.0 + sensor_data['speed'] * 0.1, 1.5) env_factor = 1.0 + abs(sensor_data['temperature'] - 25) * 0.01 + abs(sensor_data['humidity'] - 50) * 0.005 adjusted_threshold = threshold * speed_factor * env_factor logging.info(f"根据传感器数据调整阈值: 原始={threshold:.4f}, 调整后={adjusted_threshold:.4f}") else: adjusted_threshold = threshold try: sample_img_data = np.fromfile(sample_image_path, dtype=np.uint8) sample_image = cv2.imdecode(sample_img_data, cv2.IMREAD_GRAYSCALE) if sample_image is None: logging.error(f"无法解码样本图像: {sample_image_path}") return None, None, None except Exception as e: logging.exception(f"样本图像读取异常: {str(e)}") return None, None, None if len(test_image.shape) == 3: test_image_gray = cv2.cvtColor(test_image, cv2.COLOR_BGR2GRAY) else: test_image_gray = test_image.copy() sample_image = cv2.GaussianBlur(sample_image, (5, 5), 0) test_image_gray = cv2.GaussianBlur(test_image_gray, (5, 5), 0) try: orb = cv2.ORB_create(nfeatures=200) keypoints1, descriptors1 = orb.detectAndCompute(sample_image, None) keypoints2, descriptors2 = orb.detectAndCompute(test_image_gray, None) if descriptors1 is None or descriptors2 is None: logging.warning("无法提取特征描述符,跳过配准") aligned_sample = sample_image else: bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True) matches = bf.match(descriptors1, descriptors2) matches = sorted(matches, key=lambda x: x.distance) if len(matches) > 10: src_pts = np.float32([keypoints1[m.queryIdx].pt for m in matches]).reshape(-1, 1, 2) dst_pts = np.float32([keypoints2[m.trainIdx].pt for m in matches]).reshape(-1, 1, 2) H, mask = cv2.findHomography(src_pts, dst_pts, cv2.RANSAC, 5.0) if H is not None: aligned_sample = cv2.warpPerspective( sample_image, H, (test_image_gray.shape[1], test_image_gray.shape[0]) ) logging.info("图像配准成功,使用配准后样本") else: aligned_sample = sample_image logging.warning("无法计算单应性矩阵,使用原始样本") else: aligned_sample = sample_image logging.warning("特征点匹配不足,跳过图像配准") except Exception as e: logging.error(f"图像配准失败: {str(e)}") aligned_sample = sample_image try: if aligned_sample.shape != test_image_gray.shape: test_image_gray = cv2.resize(test_image_gray, (aligned_sample.shape[1], aligned_sample.shape[0])) except Exception as e: logging.error(f"图像调整大小失败: {str(e)}") return None, None, None try: from skimage.metrics import structural_similarity as compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True, data_range=255 ) except ImportError: from skimage.measure import compare_ssim ssim_score, ssim_diff = compare_ssim( aligned_sample, test_image_gray, full=True, gaussian_weights=True ) except Exception as e: logging.error(f"SSIM计算失败: {str(e)}") abs_diff = cv2.absdiff(aligned_sample, test_image_gray) ssim_diff = abs_diff.astype(np.float32) / 255.0 ssim_score = 1.0 - np.mean(ssim_diff) ssim_diff = (1 - ssim_diff) * 255 abs_diff = cv2.absdiff(aligned_sample, test_image_gray) combined_diff = cv2.addWeighted(ssim_diff.astype(np.uint8), 0.7, abs_diff, 0.3, 0) _, thresholded = cv2.threshold(combined_diff, 30, 255, cv2.THRESH_BINARY) kernel = np.ones((3, 3), np.uint8) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_OPEN, kernel) thresholded = cv2.morphologyEx(thresholded, cv2.MORPH_CLOSE, kernel) diff_pixels = np.count_nonzero(thresholded) total_pixels = aligned_sample.size diff_ratio = diff_pixels / total_pixels is_qualified = diff_ratio <= adjusted_threshold marked_image = cv2.cvtColor(test_image_gray, cv2.COLOR_GRAY2BGR) marked_image[thresholded == 255] = [0, 0, 255] labels = skimage.measure.label(thresholded) properties = skimage.measure.regionprops(labels) for prop in properties: if prop.area > 50: y, x = prop.centroid cv2.putText(marked_image, f"Defect", (int(x), int(y)), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0, 255, 255), 1) return is_qualified, diff_ratio, marked_image # ==================== 传感器控制的质量检测流程 ==================== def sensor_controlled_check(): global isGrabbing, obj_cam_operation, current_sample_path, detection_history, sensor_controller logging.info("质量检测启动") sensor_data = None if sensor_controller and sensor_controller.connected: sensor_data = sensor_controller.read_data() if not sensor_data: QMessageBox.warning(mainWindow, "传感器警告", "无法读取传感器数据,将使用默认参数", QMessageBox.Ok) else: logging.info("未连接传感器,使用默认参数检测") check_print_with_sensor(sensor_data) def check_print_with_sensor(sensor_data=None): global isGrabbing, obj_cam_operation, current_sample_path, detection_history logging.info("检测印花质量按钮按下") if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return if not obj_cam_operation: QMessageBox.warning(mainWindow, "错误", "相机未正确初始化!", QMessageBox.Ok) return if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return progress = QProgressDialog("正在检测...", "取消", 0, 100, mainWindow) progress.setWindowModality(Qt.WindowModal) progress.setValue(10) try: test_image = obj_cam_operation.get_current_frame() progress.setValue(30) if test_image is None: QMessageBox.warning(mainWindow, "错误", "无法获取当前帧图像!", QMessageBox.Ok) return diff_threshold = mainWindow.sliderDiffThreshold.value() / 100.0 logging.info(f"使用差异度阈值: {diff_threshold}") progress.setValue(50) is_qualified, diff_ratio, marked_image = enhanced_check_print_quality( current_sample_path, test_image, threshold=diff_threshold, sensor_data=sensor_data ) progress.setValue(70) if is_qualified is None: QMessageBox.critical(mainWindow, "检测错误", "检测失败,请检查日志", QMessageBox.Ok) return logging.info(f"检测结果: 合格={is_qualified}, 差异={diff_ratio}") progress.setValue(90) update_diff_display(diff_ratio, is_qualified) result_text = f"印花是否合格: {'合格' if is_qualified else '不合格'}\n差异占比: {diff_ratio*100:.2f}%\n阈值: {diff_threshold*100:.2f}%" QMessageBox.information(mainWindow, "检测结果", result_text, QMessageBox.Ok) if marked_image is not None: cv2.imshow("缺陷标记结果", marked_image) cv2.waitKey(0) cv2.destroyAllWindows() detection_result = { 'timestamp': datetime.now(), 'qualified': is_qualified, 'diff_ratio': diff_ratio, 'threshold': diff_threshold, 'sensor_data': sensor_data if sensor_data else {} } detection_history.append(detection_result) update_history_display() progress.setValue(100) except Exception as e: logging.exception("印花检测失败") QMessageBox.critical(mainWindow, "检测错误", f"检测过程中发生错误: {str(e)}", QMessageBox.Ok) finally: progress.close() def update_diff_display(diff_ratio, is_qualified): mainWindow.lblCurrentDiff.setText(f"当前差异度: {diff_ratio*100:.2f}%") if is_qualified: mainWindow.lblDiffStatus.setText("状态: 合格") mainWindow.lblDiffStatus.setStyleSheet("color: green; font-size: 12px;") else: mainWindow.lblDiffStatus.setText("状态: 不合格") mainWindow.lblDiffStatus.setStyleSheet("color: red; font-size: 12px;") def update_diff_threshold(value): mainWindow.lblDiffValue.setText(f"{value}%") def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 检查是否有可用帧 if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() format_mapping = {".bmp": "bmp", ".png": "png", ".jpg": "jpg", ".jpeg": "jpg"} save_format = format_mapping.get(file_extension) if not save_format: QMessageBox.warning(mainWindow, "错误", "不支持的文件格式!", QMessageBox.Ok) return directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录创建错误", f"无法创建目录 {directory}: {str(e)}", QMessageBox.Ok) return try: ret = obj_cam_operation.save_image(file_path, save_format) if ret != MV_OK: strError = f"保存样本图像失败: {hex(ret)}" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: QMessageBox.information(mainWindow, "成功", f"标准样本已保存至:\n{file_path}", QMessageBox.Ok) current_sample_path = file_path update_sample_display() settings.setValue("last_save_dir", os.path.dirname(file_path)) except Exception as e: QMessageBox.critical(mainWindow, "异常错误", f"保存图像时发生错误: {str(e)}", QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: img_data = np.fromfile(current_sample_path, dtype=np.uint8) sample_img = cv2.imdecode(img_data, cv2.IMREAD_COLOR) if sample_img is None: raise Exception("无法加载图像") cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) def update_sample_display(): global current_sample_path if current_sample_path: mainWindow.lblSamplePath.setText(f"当前样本: {os.path.basename(current_sample_path)}") mainWindow.lblSamplePath.setToolTip(current_sample_path) mainWindow.bnPreviewSample.setEnabled(True) else: mainWindow.lblSamplePath.setText("当前样本: 未设置样本") mainWindow.bnPreviewSample.setEnabled(False) def update_history_display(): global detection_history mainWindow.cbHistory.clear() for i, result in enumerate(detection_history[-10:]): timestamp = result['timestamp'].strftime("%H:%M:%S") status = "合格" if result['qualified'] else "不合格" ratio = f"{result['diff_ratio']*100:.2f}%" mainWindow.cbHistory.addItem(f"[极客{timestamp}] {status} - 差异: {ratio}") def TxtWrapBy(start_str, end, all): start = all.find(start_str) if start >= 0: start += len(start_str) end = all.find(end, start) if end >= 0: return all[start:end].strip() def ToHexStr(num): if not isinstance(num, int): try: num = int(num) except: return f"<非整数:{type(num)}>" chaDic = {10: 'a', 11: 'b', 12: 'c', 13: 'd', 14: 'e', 15: 'f'} hexStr = "" if num < 0: num = num + 2 ** 32 while num >= 16: digit = num % 16 hexStr = chaDic.get(digit, str(digit)) + hexStr num //= 16 hexStr = chaDic.get(num, str(num)) + hexStr return "0x" + hexStr def decoding_char(c_ubyte_value): c_char_p_value = ctypes.cast(c_ubyte_value, ctypes.c_char_p) try: decode_str = c_char_p_value.value.decode('gbk') except UnicodeDecodeError: decode_str = str(c_char_p_value.value) return decode_str def enum_devices(): global deviceList, obj_cam_operation n_layer_type = ( MV_GIGE_DEVICE | MV_USB_DEVICE | MV_GENTL_CAMERALINK_DEVICE | MV_GENTL_CXP_DEVICE | MV_GENTL_XOF_DEVICE ) # 创建设备列表 deviceList = MV_CC_DEVICE_INFO_LIST() # 枚举设备 ret = MvCamera.MV_CC_EnumDevices(n_layer_type, deviceList) if ret != MV_OK: error_msg = f"枚举设备失败! 错误码: 0x{ret:x}" logging.error(error_msg) QMessageBox.warning(mainWindow, "错误", error_msg, QMessageBox.Ok) return ret if deviceList.nDeviceNum == 0: QMessageBox.warning(mainWindow, "提示", "未找到任何设备", QMessageBox.Ok) return MV_OK logging.info(f"找到 {deviceList.nDeviceNum} 个设备") # 处理设备信息 devList = [] for i in range(deviceList.nDeviceNum): # 获取设备信息 mvcc_dev_info = ctypes.cast( deviceList.pDeviceInfo[i], ctypes.POINTER(MV_CC_DEVICE_INFO) ).contents # 根据设备类型提取信息 if mvcc_dev_info.nTLayerType == MV_GIGE_DEVICE: st_gige_info = mvcc_dev_info.SpecialInfo.stGigEInfo ip_addr = ( f"{(st_gige_info.nCurrentIp >> 24) & 0xFF}." f"{(st_gige_info.nCurrentIp >> 16) & 0xFF}." f"{(st_gige_info.nCurrentIp >> 8) & 0xFF}." f"{st_gige_info.nCurrentIp & 0xFF}" ) # 修复:将c_ubyte_Array_16转换为字节串再解码 user_defined_bytes = bytes(st_gige_info.chUserDefinedName) dev_name = f"GigE: {user_defined_bytes.decode('gbk', 'ignore')}" devList.append(f"[{i}] {dev_name} ({ip_addr})") elif mvcc_dev_info.nTLayerType == MV_USB_DEVICE: st_usb_info = mvcc_dev_info.SpecialInfo.stUsb3VInfo serial = bytes(st_usb_info.chSerialNumber).decode('ascii', 'ignore').rstrip('\x00') # 修复:同样处理用户自定义名称 user_defined_bytes = bytes(st_usb_info.chUserDefinedName) dev_name = f"USB: {user_defined_bytes.decode('gbk', 'ignore')}" devList.append(f"[{i}] {dev_name} (SN: {serial})") else: devList.append(f"[{i}] 未知设备类型: {mvcc_dev_info.nTLayerType}") # 更新UI mainWindow.ComboDevices.clear() mainWindow.ComboDevices.addItems(devList) if devList: mainWindow.ComboDevices.setCurrentIndex(0) mainWindow.statusBar().showMessage(f"找到 {deviceList.nDeviceNum} 个设备", 3000) return MV_OK # ===== 关键改进:相机操作函数 ===== def open_device(): global deviceList, nSelCamIndex, obj_cam_operation, isOpen, frame_monitor_thread, mainWindow if isOpen: QMessageBox.warning(mainWindow, "Error", '相机已打开!', QMessageBox.Ok) return MV_E_CALLORDER nSelCamIndex = mainWindow.ComboDevices.currentIndex() if nSelCamIndex < 0: QMessageBox.warning(mainWindow, "Error", '请选择相机!', QMessageBox.Ok) return MV_E_CALLORDER # 创建相机控制对象 cam = MvCamera() # 初始化相机操作对象 - 确保传入有效的相机对象 obj_cam_operation = CameraOperation(cam, deviceList, nSelCamIndex) ret = obj_cam_operation.open_device() if 0 != ret: strError = "打开设备失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) isOpen = False else: set_continue_mode() get_param() isOpen = True enable_controls() # 创建并启动帧监控线程 frame_monitor_thread = FrameMonitorThread(obj_cam_operation) frame_monitor_thread.frame_status.connect(mainWindow.statusBar().showMessage) frame_monitor_thread.start() def start_grabbing(): global obj_cam_operation, isGrabbing # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, "Error", "相机对象未正确初始化", QMessageBox.Ok) return ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = "开始取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "警告", "开始取流后未接收到帧,请检查相机连接!", QMessageBox.Ok) def stop_grabbing(): global obj_cam_operation, isGrabbing # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: QMessageBox.warning(mainWindow, "Error", "相机对象未正确初始化", QMessageBox.Ok) return # 关键改进:添加连接状态检查 if not hasattr(obj_cam_operation, 'connected') or not obj_cam_operation.connected: QMessageBox.warning(mainWindow, "Error", "相机未连接", QMessageBox.Ok) return ret = obj_cam_operation.Stop_grabbing() if ret != 0: strError = "停止取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = False enable_controls() def close_device(): global isOpen, isGrabbing, obj_cam_operation, frame_monitor_thread if frame_monitor_thread and frame_monitor_thread.isRunning(): frame_monitor_thread.stop() frame_monitor_thread.wait(2000) if isOpen and obj_cam_operation: # 关键改进:确保相机对象存在 if hasattr(obj_cam_operation, 'cam') and obj_cam_operation.cam: obj_cam_operation.close_device() isOpen = False isGrabbing = False enable_controls() def set_continue_mode(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(False) if ret != 0: strError = "设置连续模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(True) mainWindow.radioTriggerMode.setChecked(False) mainWindow.bnSoftwareTrigger.setEnabled(False) def set_software_trigger_mode(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.set_trigger_mode(True) if ret != 0: strError = "设置触发模式失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: mainWindow.radioContinueMode.setChecked(False) mainWindow.radioTriggerMode.setChecked(True) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing) def trigger_once(): # 关键改进:添加相机状态检查 if not obj_cam_operation or not hasattr(obj_cam_operation, 'cam') or not obj_cam_operation.cam: return ret = obj_cam_operation.trigger_once() if ret != 0: strError = "软触发失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) def save_sample_image(): global isGrabbing, obj_cam_operation, current_sample_path if not isGrabbing: QMessageBox.warning(mainWindow, "错误", "请先开始取流并捕获图像!", QMessageBox.Ok) return # 尝试捕获当前帧 frame = obj_cam_operation.capture_frame() if frame is None: QMessageBox.warning(mainWindow, "无有效图像", "未捕获到有效图像,请检查相机状态!", QMessageBox.Ok) return # 确保图像有效 if frame.size == 0 or frame.shape[0] == 0 or frame.shape[1] == 0: QMessageBox.warning(mainWindow, "无效图像", "捕获的图像无效,请检查相机设置!", QMessageBox.Ok) return settings = QSettings("ClothInspection", "CameraApp") last_dir = settings.value("last_save_dir", os.path.join(os.getcwd(), "captures")) timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") default_filename = f"sample_{timestamp}" file_path, selected_filter = QFileDialog.getSaveFileName( mainWindow, "保存标准样本图像", os.path.join(last_dir, default_filename), "BMP Files (*.bmp);;PNG Files (*.png);;JPEG Files (*.jpg);;所有文件 (*)", options=QFileDialog.DontUseNativeDialog ) if not file_path: return # 确保文件扩展名正确 file_extension = os.path.splitext(file_path)[1].lower() if not file_extension: if "BMP" in selected_filter: file_path += ".bmp" elif "PNG" in selected_filter: file_path += ".png" elif "JPEG" in selected_filter or "JPG" in selected_filter: file_path += ".jpg" else: file_path += ".bmp" file_extension = os.path.splitext(file_path)[1].lower() # 创建目录(如果不存在) directory = os.path.dirname(file_path) if directory and not os.path.exists(directory): try: os.makedirs(directory, exist_ok=True) except OSError as e: QMessageBox.critical(mainWindow, "目录创建错误", f"无法创建目录 {directory}: {str(e)}", QMessageBox.Ok) return # 保存图像 try: # 使用OpenCV保存图像 if not cv2.imwrite(file_path, frame): raise Exception("OpenCV保存失败") # 更新状态 current_sample_path = file_path update_sample_display() settings.setValue("last_save_dir", os.path.dirname(file_path)) # 显示成功消息 QMessageBox.information(mainWindow, "成功", f"标准样本已保存至:\n{file_path}", QMessageBox.Ok) # 可选:自动预览样本 preview_sample() except Exception as e: logging.error(f"保存图像失败: {str(e)}") QMessageBox.critical(mainWindow, "保存错误", f"保存图像时发生错误:\n{str(e)}", QMessageBox.Ok) def preview_sample(): global current_sample_path if not current_sample_path or not os.path.exists(current_sample_path): QMessageBox.warning(mainWindow, "错误", "请先设置有效的标准样本图像!", QMessageBox.Ok) return try: # 直接使用OpenCV加载图像 sample_img = cv2.imread(current_sample_path) if sample_img is None: raise Exception("无法加载图像") # 显示图像 cv2.imshow("标准样本预览", sample_img) cv2.waitKey(0) cv2.destroyAllWindows() except Exception as e: QMessageBox.warning(mainWindow, "错误", f"预览样本失败: {str(e)}", QMessageBox.Ok) def start_grabbing(): global obj_cam_operation, isGrabbing ret = obj_cam_operation.start_grabbing(mainWindow.widgetDisplay.winId()) if ret != 0: strError = "开始取流失败 ret:" + ToHexStr(ret) QMessageBox.warning(mainWindow, "Error", strError, QMessageBox.Ok) else: isGrabbing = True enable_controls() # 等待第一帧到达 QThread.msleep(500) if not obj_cam_operation.is_frame_available(): QMessageBox.warning(mainWindow, "警告", "开始取流后未接收到帧,请检查相机连接!", QMessageBox.Ok) def is_float(str): try: float(str) return True except ValueError: return False def get_param(): try: ret = obj_cam_operation.get_parameters() if ret != MV_OK: strError = "获取参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) else: mainWindow.edtExposureTime.setText("{0:.2f}".format(obj_cam_operation.exposure_time)) mainWindow.edtGain.setText("{0:.2f}".format(obj_cam_operation.gain)) mainWindow.edtFrameRate.setText("{0:.2f}".format(obj_cam_operation.frame_rate)) except Exception as e: error_msg = f"获取参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def set_param(): frame_rate = mainWindow.edtFrameRate.text() exposure = mainWindow.edtExposureTime.text() gain = mainWindow.edtGain.text() if not (is_float(frame_rate) and is_float(exposure) and is_float(gain)): strError = "设置参数失败: 参数必须是有效的浮点数" QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) return MV_E_PARAMETER try: ret = obj_cam_operation.set_param( frame_rate=float(frame_rate), exposure_time=float(exposure), gain=float(gain) ) if ret != MV_OK: strError = "设置参数失败,错误码: " + ToHexStr(ret) QMessageBox.warning(mainWindow, "错误", strError, QMessageBox.Ok) except Exception as e: error_msg = f"设置参数时发生错误: {str(e)}" QMessageBox.critical(mainWindow, "严重错误", error_msg, QMessageBox.Ok) def enable_controls(): global isGrabbing, isOpen mainWindow.groupGrab.setEnabled(isOpen) mainWindow.paramgroup.setEnabled(isOpen) mainWindow.bnOpen.setEnabled(not isOpen) mainWindow.bnClose.setEnabled(isOpen) mainWindow.bnStart.setEnabled(isOpen and (not isGrabbing)) mainWindow.bnStop.setEnabled(isOpen and isGrabbing) mainWindow.bnSoftwareTrigger.setEnabled(isGrabbing and mainWindow.radioTriggerMode.isChecked()) mainWindow.bnSaveImage.setEnabled(isOpen and isGrabbing) mainWindow.bnCheckPrint.setEnabled(isOpen and isGrabbing) mainWindow.bnSaveSample.setEnabled(isOpen and isGrabbing) mainWindow.bnPreviewSample.setEnabled(bool(current_sample_path)) def update_sensor_display(data): if not data: return text = (f"张力: {data['tension']:.2f}N | " f"速度: {data['speed']:.2f}m/s | " f"温度: {data['temperature']:.1f}°C | " f"湿度: {data['humidity']:.1f}%") mainWindow.lblSensorData.setText(text) def connect_sensor(): global sensor_monitor_thread, sensor_controller sensor_type = mainWindow.cbSensorType.currentText() if sensor_controller is None: sensor_controller = SensorController() if sensor_type == "串口": config = { 'type': 'serial', 'port': mainWindow.cbComPort.currentText(), 'baudrate': int(mainWindow.cbBaudrate.currentText()), 'timeout': 1.0 } else: config = { 'type': 'ethernet', 'ip': mainWindow.edtIP.text(), 'port': int(mainWindow.edtPort.text()), 'timeout': 1.0 } if sensor_controller.connect(config): mainWindow.bnConnectSensor.setEnabled(False) mainWindow.bnDisconnectSensor.setEnabled(True) sensor_monitor_thread = SensorMonitorThread(sensor_controller) sensor_monitor_thread.data_updated.connect(update_sensor_display) sensor_monitor_thread.start() def disconnect_sensor(): global sensor_monitor_thread if sensor_controller: sensor_controller.disconnect() mainWindow.bnConnectSensor.setEnabled(True) mainWindow.bnDisconnectSensor.setEnabled(False) if sensor_monitor_thread and sensor_monitor_thread.isRunning(): sensor_monitor_thread.stop() sensor_monitor_thread.wait(2000) sensor_monitor_thread = None mainWindow.lblSensorData.setText("传感器数据: 未连接") def update_sensor_ui(index): mainWindow.serialGroup.setVisible(index == 0) mainWindow.ethernetGroup.setVisible(index == 1) class MainWindow(QMainWindow): def __init__(self): super().__init__() self.setWindowTitle("布料印花检测系统") self.resize(1200, 800) central_widget = QWidget() self.setCentralWidget(central_widget) main_layout = QVBoxLayout(central_widget) # 设备枚举区域 device_layout = QHBoxLayout() self.ComboDevices = QComboBox() self.bnEnum = QPushButton("枚举设备") self.bnOpen = QPushButton("打开设备") self.bnClose = QPushButton("关闭设备") device_layout.addWidget(self.ComboDevices) device_layout.addWidget(self.bnEnum) device_layout.addWidget(self.bnOpen) device_layout.addWidget(self.bnClose) main_layout.addLayout(device_layout) # 取流控制组 self.groupGrab = QGroupBox("取流控制") grab_layout = QHBoxLayout(self.groupGrab) self.bnStart = QPushButton("开始取流") self.bnStop = QPushButton("停止取流") self.radioContinueMode = QRadioButton("连续模式") self.radioTriggerMode = QRadioButton("触发模式") self.bnSoftwareTrigger = QPushButton("软触发") grab_layout.addWidget(self.bnStart) grab_layout.addWidget(self.bnStop) grab_layout.addWidget(self.radioContinueMode) grab_layout.addWidget(self.radioTriggerMode) grab_layout.addWidget(self.bnSoftwareTrigger) main_layout.addWidget(self.groupGrab) # 参数设置组 self.paramgroup = QGroupBox("相机参数") param_layout = QGridLayout(self.paramgroup) self.edtExposureTime = QLineEdit() self.edtGain = QLineEdit() self.edtFrameRate = QLineEdit() self.bnGetParam = QPushButton("获取参数") self.bnSetParam = QPushButton("设置参数") self.bnSaveImage = QPushButton("保存图像") param_layout.addWidget(QLabel("曝光时间:"), 0, 0) param_layout.addWidget(self.edtExposureTime, 0, 1) param_layout.addWidget(self.bnGetParam, 0, 2) param_layout.addWidget(QLabel("增益:"), 1, 0) param_layout.addWidget(self.edtGain, 1, 1) param_layout.addWidget(self.bnSetParam, 1, 2) param_layout.addWidget(QLabel("帧率:"), 2, 0) param_layout.addWidget(self.edtFrameRate, 2, 1) param_layout.addWidget(self.bnSaveImage, 2, 2) main_layout.addWidget(self.paramgroup) # 图像显示区域 self.widgetDisplay = QLabel() self.widgetDisplay.setMinimumSize(640, 480) self.widgetDisplay.setStyleSheet("background-color: black;") self.widgetDisplay.setAlignment(Qt.AlignCenter) self.widgetDisplay.setText("相机预览区域") main_layout.addWidget(self.widgetDisplay, 1) # 状态栏 #self.statusBar = QStatusBar() #self.setStatusBar(self.statusBar) # 创建自定义UI组件 self.setup_custom_ui() def setup_custom_ui(self): # 工具栏 toolbar = self.addToolBar("检测工具") self.bnCheckPrint = QPushButton("检测印花质量") self.bnSaveSample = QPushButton("保存标准样本") self.bnPreviewSample = QPushButton("预览样本") self.cbHistory = QComboBox() self.cbHistory.setMinimumWidth(300) toolbar.addWidget(self.bnCheckPrint) toolbar.addWidget(self.bnSaveSample) toolbar.addWidget(self.bnPreviewSample) toolbar.addWidget(QLabel("历史记录:")) toolbar.addWidget(self.cbHistory) # 状态栏样本路径 self.lblSamplePath = QLabel("当前样本: 未设置样本") self.statusBar().addPermanentWidget(self.lblSamplePath) # 右侧面板 right_panel = QWidget() right_layout = QVBoxLayout(right_panel) right_layout.setContentsMargins(10, 10, 10, 10) # 差异度调整组 diff_group = QGroupBox("差异度调整") diff_layout = QVBoxLayout(diff_group) self.lblDiffThreshold = QLabel("差异度阈值 (0-100%):") self.sliderDiffThreshold = QSlider(Qt.Horizontal) self.sliderDiffThreshold.setRange(0, 100) self.sliderDiffThreshold.setValue(5) self.lblDiffValue = QLabel("5%") self.lblCurrentDiff = QLabel("当前差异度: -") self.lblCurrentDiff.setStyleSheet("font-size: 14px; font-weight: bold;") self.lblDiffStatus = QLabel("状态: 未检测") self.lblDiffStatus.setStyleSheet("font-size: 12px;") diff_layout.addWidget(self.lblDiffThreshold) diff_layout.addWidget(self.sliderDiffThreshold) diff_layout.addWidget(self.lblDiffValue) diff_layout.addWidget(self.lblCurrentDiff) diff_layout.addWidget(self.lblDiffStatus) right_layout.addWidget(diff_group) # 传感器控制面板 sensor_panel = QGroupBox("传感器控制") sensor_layout = QVBoxLayout(sensor_panel) sensor_type_layout = QHBoxLayout() self.lblSensorType = QLabel("传感器类型:") self.cbSensorType = QComboBox() self.cbSensorType.addItems(["串口", "以太网"]) sensor_type_layout.addWidget(self.lblSensorType) sensor_type_layout.addWidget(self.cbSensorType) sensor_layout.addLayout(sensor_type_layout) # 串口参数 self.serialGroup = QGroupBox("串口参数") serial_layout = QVBoxLayout(self.serialGroup) self.lblComPort = QLabel("端口:") self.cbComPort = QComboBox() if platform.system() == 'Windows': ports = [f"COM{i}" for i in range(1, 21)] else: ports = [f"/dev/ttyS{i}" for i in range(0, 4)] + [f"/dev/ttyUSB{i}" for i in range(0, 4)] self.cbComPort.addItems(ports) self.lblBaudrate = QLabel("波特率:") self.cbBaudrate = QComboBox() self.cbBaudrate.addItems(["96000", "19200", "38400", "57600", "115200"]) self.cbBaudrate.setCurrentText("115200") serial_layout.addWidget(self.lblComPort) serial_layout.addWidget(self.cbComPort) serial_layout.addWidget(self.lblBaudrate) serial_layout.addWidget(self.cbBaudrate) sensor_layout.addWidget(self.serialGroup) # 以太网参数 self.ethernetGroup = QGroupBox("以太网参数") ethernet_layout = QVBoxLayout(self.ethernetGroup) self.lblIP = QLabel("IP地址:") self.edtIP = QLineEdit("192.168.1.100") self.lblPort = QLabel("端口:") self.edtPort = QLineEdit("502") ethernet_layout.addWidget(self.lblIP) ethernet_layout.addWidget(self.edtIP) ethernet_layout.addWidget(self.lblPort) ethernet_layout.addWidget(self.edtPort) sensor_layout.addWidget(self.ethernetGroup) # 连接按钮 self.bnConnectSensor = QPushButton("连接传感器") self.bnDisconnectSensor = QPushButton("断开传感器") self.bnDisconnectSensor.setEnabled(False) sensor_layout.addWidget(self.bnConnectSensor) sensor_layout.addWidget(self.bnDisconnectSensor) # 延迟设置 delay_layout = QHBoxLayout() self.lblDelay = QLabel("触发延迟(秒):") self.spinDelay = QSpinBox() self.spinDelay.setRange(0, 60) self.spinDelay.setValue(0) self.spinDelay.setToolTip("传感器检测到布料后延迟拍摄的时间") delay_layout.addWidget(self.lblDelay) delay_layout.addWidget(self.spinDelay) sensor_layout.addLayout(delay_layout) # 传感器数据 self.lblSensorData = QLabel("传感器数据: 未连接") self.lblSensorData.setStyleSheet("font-size: 10pt;") sensor_layout.addWidget(self.lblSensorData) right_layout.addWidget(sensor_panel) right_layout.addStretch(1) # 停靠窗口 dock = QDockWidget("检测控制面板", self) dock.setWidget(right_panel) dock.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable) self.addDockWidget(Qt.RightDockWidgetArea, dock) def closeEvent(self, event): logging.info("主窗口关闭,执行清理...") close_device() disconnect_sensor() event.accept() if __name__ == "__main__": app = QApplication(sys.argv) mainWindow = MainWindow() # 信号连接 mainWindow.cbSensorType.currentIndexChanged.connect(update_sensor_ui) update_sensor_ui(0) mainWindow.bnConnectSensor.clicked.connect(connect_sensor) mainWindow.bnDisconnectSensor.clicked.connect(disconnect_sensor) mainWindow.sliderDiffThreshold.valueChanged.connect(update_diff_threshold) mainWindow.bnCheckPrint.clicked.connect(sensor_controlled_check) mainWindow.bnSaveSample.clicked.connect(save_sample_image) mainWindow.bnPreviewSample.clicked.connect(preview_sample) mainWindow.bnEnum.clicked.connect(enum_devices) mainWindow.bnOpen.clicked.connect(open_device) mainWindow.bnClose.clicked.connect(close_device) mainWindow.bnStart.clicked.connect(start_grabbing) mainWindow.bnStop.clicked.connect(stop_grabbing) mainWindow.bnSoftwareTrigger.clicked.connect(trigger_once) mainWindow.radioTriggerMode.clicked.connect(set_software_trigger_mode) mainWindow.radioContinueMode.clicked.connect(set_continue_mode) mainWindow.bnGetParam.clicked.connect(get_param) mainWindow.bnSetParam.clicked.connect(set_param) mainWindow.bnSaveImage.clicked.connect(save_sample_image) main() mainWindow.show() app.exec_() close_device() disconnect_sensor() sys.exit()
07-12
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Computer Camera Cuboid Detection System - Traditional Algorithm Approach Features: 1. 支持红、蓝、绿三种颜色物块识别 2. 优化红色识别,减少橙色干扰 3. 增大了最大轮廓识别面积 4. 实时桌面预览和性能统计 """ import cv2 import time import numpy as np import sys import logging # Configure logging system logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s', handlers=[ logging.StreamHandler(sys.stdout), logging.FileHandler('cuboid_detection.log') ] ) logger = logging.getLogger(__name__) class CuboidDetectionSystem: def __init__(self, camera_index=0): # Camera configuration self.camera_index = camera_index self.cap = None # Detection parameters self.min_area = 1000 # Minimum contour area to consider self.max_area = 150000 # Increased maximum contour area (was 50000) self.aspect_ratio_min = 0.3 # Minimum aspect ratio for rectangles self.aspect_ratio_max = 3.0 # Maximum aspect ratio for rectangles self.circularity_thresh = 0.6 # Circularity threshold for cylinders # Color detection parameters (in HSV space) # Red - optimized to reduce orange interference self.red_lower1 = np.array([0, 150, 100]) # Increased saturation and value self.red_upper1 = np.array([10, 255, 255]) self.red_lower2 = np.array([170, 150, 100]) # Increased saturation and value self.red_upper2 = np.array([180, 255, 255]) # Blue self.blue_lower = np.array([100, 120, 70]) self.blue_upper = np.array([130, 255, 255]) # Green - added for green objects self.green_lower = np.array([35, 80, 60]) # Green range self.green_upper = np.array([85, 255, 255]) # Performance tracking self.fps_history = [] self.detection_history = [] self.last_detection_time = 0 # Initialize camera self.open_camera() logger.info("Cuboid Detection System (Traditional Algorithm) initialized successfully") def open_camera(self): """Open computer's external camera""" try: self.cap = cv2.VideoCapture(self.camera_index) if not self.cap.isOpened(): # Try common alternative indices for idx in [2, 1, 0]: self.cap = cv2.VideoCapture(idx) if self.cap.isOpened(): self.camera_index = idx break if not self.cap.isOpened(): logger.error("Unable to open any camera!") return False # Set camera resolution self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 1280) self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 720) logger.info(f"Camera opened successfully at index {self.camera_index}") return True except Exception as e: logger.error(f"Camera initialization failed: {str(e)}") return False def preprocess_frame(self, frame): """Preprocess frame for contour detection""" # Convert to HSV color space hsv = cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) # Blur to reduce noise hsv = cv2.GaussianBlur(hsv, (5, 5), 0) # Create masks for red, blue and green red_mask1 = cv2.inRange(hsv, self.red_lower1, self.red_upper1) red_mask2 = cv2.inRange(hsv, self.red_lower2, self.red_upper2) red_mask = cv2.bitwise_or(red_mask1, red_mask2) blue_mask = cv2.inRange(hsv, self.blue_lower, self.blue_upper) green_mask = cv2.inRange(hsv, self.green_lower, self.green_upper) # Combine masks color_mask = cv2.bitwise_or(red_mask, blue_mask) color_mask = cv2.bitwise_or(color_mask, green_mask) # Apply morphological operations to clean up the mask kernel = np.ones((7, 7), np.uint8) color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_OPEN, kernel) color_mask = cv2.morphologyEx(color_mask, cv2.MORPH_CLOSE, kernel) # Additional dilation to fill gaps color_mask = cv2.dilate(color_mask, kernel, iterations=1) return color_mask, red_mask, blue_mask, green_mask def detect_shapes(self, mask, frame): """Detect rectangular and circular shapes in the mask""" contours, _ = cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) detected_objects = [] for contour in contours: # Filter by area - max_area increased to 150000 area = cv2.contourArea(contour) if area < self.min_area or area > self.max_area: continue # Approximate the contour to a polygon peri = cv2.arcLength(contour, True) approx = cv2.approxPolyDP(contour, 0.04 * peri, True) # Calculate shape properties x, y, w, h = cv2.boundingRect(approx) aspect_ratio = float(w) / h circularity = 4 * np.pi * area / (peri * peri) if peri > 0 else 0 # Detect rectangles (books, boxes) if len(approx) == 4 and self.aspect_ratio_min < aspect_ratio < self.aspect_ratio_max: # Calculate the angles between consecutive edges vectors = [] for i in range(4): pt1 = approx[i][0] pt2 = approx[(i + 1) % 4][0] vectors.append(np.array(pt2) - np.array(pt1)) # Calculate angles between consecutive vectors angles = [] for i in range(4): v1 = vectors[i] v2 = vectors[(i + 1) % 4] cos_angle = np.dot(v1, v2) / (np.linalg.norm(v1) * np.linalg.norm(v2) + 1e-5) angle = np.arccos(np.clip(cos_angle, -1, 1)) * 180 / np.pi angles.append(angle) # Check if angles are close to 90 degrees (rectangle) if all(70 < angle < 110 for angle in angles): detected_objects.append({ 'type': 'rectangle', 'contour': contour, 'approx': approx, 'center': (x + w // 2, y + h // 2), 'box': (x, y, w, h), 'area': area }) # Detect circles/cylinders elif circularity > self.circularity_thresh: detected_objects.append({ 'type': 'cylinder', 'contour': contour, 'approx': approx, 'center': (x + w // 2, y + h // 2), 'box': (x, y, w, h), 'area': area }) return detected_objects def detect_colors(self, frame, detected_objects, red_mask, blue_mask, green_mask): """Determine the color of detected objects""" results = [] for obj in detected_objects: x, y, w, h = obj['box'] # Create mask for the object region obj_mask = np.zeros(frame.shape[:2], dtype=np.uint8) cv2.drawContours(obj_mask, [obj['contour']], -1, 255, -1) # Extract the object region from color masks obj_red = cv2.bitwise_and(red_mask, red_mask, mask=obj_mask) obj_blue = cv2.bitwise_and(blue_mask, blue_mask, mask=obj_mask) obj_green = cv2.bitwise_and(green_mask, green_mask, mask=obj_mask) # Count red, blue and green pixels in the object region red_pixels = cv2.countNonZero(obj_red) blue_pixels = cv2.countNonZero(obj_blue) green_pixels = cv2.countNonZero(obj_green) total_pixels = cv2.countNonZero(obj_mask) # Determine dominant color color = "unknown" if total_pixels > 0: red_ratio = red_pixels / total_pixels blue_ratio = blue_pixels / total_pixels green_ratio = green_pixels / total_pixels # Require at least 40% dominance for color classification if red_ratio > 0.4 and red_ratio > blue_ratio and red_ratio > green_ratio: color = "red" elif blue_ratio > 0.4 and blue_ratio > red_ratio and blue_ratio > green_ratio: color = "blue" elif green_ratio > 0.4 and green_ratio > red_ratio and green_ratio > blue_ratio: color = "green" # Add to results results.append({ 'type': obj['type'], 'color': color, 'center': obj['center'], 'box': obj['box'], 'contour': obj['contour'], 'timestamp': time.time() }) return results def detect_cuboids(self, frame): """Detect cuboid objects using traditional computer vision techniques""" # Step 1: Preprocess frame to create color mask color_mask, red_mask, blue_mask, green_mask = self.preprocess_frame(frame) # Step 2: Detect shapes detected_objects = self.detect_shapes(color_mask, frame) # Step 3: Detect colors of the shapes results = self.detect_colors(frame, detected_objects, red_mask, blue_mask, green_mask) return results, color_mask def run(self): """Main loop for desktop preview and detection""" logger.info("Starting main detection loop") window_name = "Cuboid Detection (Traditional Algorithm)" cv2.namedWindow(window_name, cv2.WINDOW_NORMAL) cv2.resizeWindow(window_name, 1200, 800) # Create mask window cv2.namedWindow("Color Mask", cv2.WINDOW_NORMAL) cv2.resizeWindow("Color Mask", 600, 400) frame_count = 0 start_time = time.time() try: while True: # Start timer for FPS calculation frame_start = time.time() # Get frame from camera ret, frame = self.cap.read() if not ret: logger.warning("Failed to capture frame") time.sleep(0.1) continue frame_count += 1 # Detect cuboids results, color_mask = self.detect_cuboids(frame) # Draw results on frame for result in results: color_type = result['color'] obj_type = result['type'] x, y, w, h = result['box'] center_x, center_y = result['center'] # Determine drawing color based on detected color if color_type == "red": color = (0, 0, 255) # Red in BGR elif color_type == "blue": color = (255, 0, 0) # Blue in BGR elif color_type == "green": color = (0, 255, 0) # Green in BGR else: color = (0, 255, 255) # Yellow for unknown # Draw bounding box cv2.rectangle(frame, (x, y), (x + w, y + h), color, 2) # Draw contour cv2.drawContours(frame, [result['contour']], -1, color, 2) # Draw center point cv2.circle(frame, (center_x, center_y), 5, color, -1) # Draw label label = f"{color_type} {obj_type}" cv2.putText(frame, label, (x, y - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.7, color, 2) # Calculate FPS current_time = time.time() elapsed_time = current_time - start_time fps = frame_count / elapsed_time if elapsed_time > 0 else 0 # Update FPS history (keep last 30 values) self.fps_history.append(fps) if len(self.fps_history) > 30: self.fps_history.pop(0) avg_fps = sum(self.fps_history) / len(self.fps_history) if self.fps_history else 0 # Update detection history detection_status = 1 if results else 0 self.detection_history.append(detection_status) if len(self.detection_history) > 30: self.detection_history.pop(0) detection_rate = sum(self.detection_history) / len(self.detection_history) * 100 # Display performance stats stats_y = 30 cv2.putText(frame, f"FPS: {avg_fps:.1f}", (10, stats_y), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 0), 2) cv2.putText(frame, f"Detection Rate: {detection_rate:.1f}%", (10, stats_y + 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 255, 255), 2) cv2.putText(frame, f"Objects: {len(results)}", (10, stats_y + 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 200, 255), 2) # Display algorithm info cv2.putText(frame, "Algorithm: Traditional CV (Contour + Color Analysis)", (10, frame.shape[0] - 10), cv2.FONT_HERSHEY_SIMPLEX, 0.5, (200, 200, 200), 1) # Show frames cv2.imshow(window_name, frame) cv2.imshow("Color Mask", color_mask) # Process keyboard input key = cv2.waitKey(1) & 0xFF if key == ord('q') or key == 27: # 'q' or ESC break elif key == ord('c'): # Capture screenshot timestamp = time.strftime("%Y%m%d-%H%M%S") filename = f"capture_{timestamp}.png" cv2.imwrite(filename, frame) logger.info(f"Screenshot saved as {filename}") elif key == ord('r'): # Reset detection history self.detection_history = [] self.fps_history = [] start_time = time.time() frame_count = 0 logger.info("Reset performance counters") elif key == ord('d'): # Toggle debug mode cv2.imshow("Red Mask", self.red_mask) cv2.imshow("Green Mask", self.green_mask) cv2.imshow("Blue Mask", self.blue_mask) logger.info("Debug masks displayed") # Calculate processing time frame_time = time.time() - frame_start target_frame_time = 1 / 30 # Target 30 FPS if frame_time < target_frame_time: time.sleep(target_frame_time - frame_time) except Exception as e: logger.error(f"Main loop exception: {str(e)}", exc_info=True) finally: self.cleanup() def cleanup(self): """Clean up resources""" # Release camera if self.cap and self.cap.isOpened(): self.cap.release() logger.info("Camera resources released") # Close windows cv2.destroyAllWindows() logger.info("Program exited safely") def print_controls(): """Print program controls""" print("=" * 70) print("Computer Camera Cuboid Detection System - Traditional Algorithm") print("=" * 70) print("Detects cuboid objects (books, boxes, etc.) using computer vision techniques") print("Supports RED, BLUE and GREEN objects") print("Desktop window shows real-time preview with bounding boxes") print("Second window shows the color mask used for detection") print("=" * 70) print("Controls:") print(" q or ESC - Quit program") print(" c - Capture screenshot") print(" r - Reset performance counters") print(" d - Show debug masks (red, green, blue)") print("=" * 70) print("Detection parameters:") print(" - Red, blue and green color detection in HSV space") print(" - Rectangle detection based on polygon approximation and angle analysis") print(" - Cylinder detection based on circularity metric") print(f" - Max contour area: 150000 (was 50000)") print("=" * 70) if __name__ == "__main__": print_controls() # Default camera index (0 is usually the built-in or first external camera) camera_index = 0 # Allow specifying camera index from command line if len(sys.argv) > 1: try: camera_index = int(sys.argv[1]) print(f"Using camera index: {camera_index}") except ValueError: print(f"Invalid camera index: {sys.argv[1]}, using default (0)") try: detector = CuboidDetectionSystem(camera_index=camera_index) detector.run() except Exception as e: logger.error(f"System startup failed: {str(e)}", exc_info=True) print(f"System startup failed: {str(e)}") 现在就只是简单的将这个桌面运行的代码,完整的转化为在vnc上运行的代码就可以了,因为这个代码的效果是很不错的,我现在只想看看它在vnc上运行时机器狗的识别效果如何!!!! 给出完整的转换代码!!!
08-09
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值