diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..66fb2ab --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,14 @@ +{ + "permissions": { + "allow": [ + "WebSearch", + "Bash(7z l:*)", + "Bash(hh:*)", + "Bash(powershell:*)", + "Bash(grep:*)", + "WebFetch(domain:)", + "Bash(\"%SystemRoot%\\\\SysWOW64\\\\WindowsPowerShell\\\\v1.0\\\\powershell.exe\":*)", + "Bash(\"/c/Windows/SysWOW64/WindowsPowerShell/v1.0/powershell.exe\":*)" + ] + } +} diff --git a/Commands/CloseProjectCommand.cs b/Commands/CloseProjectCommand.cs new file mode 100644 index 0000000..426733d --- /dev/null +++ b/Commands/CloseProjectCommand.cs @@ -0,0 +1,40 @@ +using System; +using TellmePdmsPluging.Core; +using TellmePdmsPluging.Models; + +namespace TellmePdmsPluging.Commands +{ + public class CloseProjectCommand : ICommand + { + public CloseProjectCommand(CloseProjectRequest request) + { + Request = request ?? new CloseProjectRequest(); + CommandId = Guid.NewGuid().ToString("N"); + } + + public string CommandId { get; } + + public string CommandType + { + get { return "CloseProject"; } + } + + public bool CanCancel + { + get { return false; } + } + + public CloseProjectRequest Request { get; } + + public object Execute() + { + Request.ApplyDefaults(); + return PdmsManager.Instance.CloseProject(Request); + } + + public void Cancel() + { + throw new NotSupportedException("CloseProjectCommand 不支持取消"); + } + } +} diff --git a/Commands/ExportIfcCommand.cs b/Commands/ExportIfcCommand.cs new file mode 100644 index 0000000..fef51fd --- /dev/null +++ b/Commands/ExportIfcCommand.cs @@ -0,0 +1,39 @@ +using TellmePdmsPluging.Core; +using TellmePdmsPluging.Models; + +namespace TellmePdmsPluging.Commands +{ + public class ExportIfcCommand : ICommand + { + public ExportIfcCommand(ExportIfcRequest request) + { + Request = request ?? new ExportIfcRequest(); + CommandId = System.Guid.NewGuid().ToString("N"); + } + + public string CommandId { get; } + + public string CommandType + { + get { return "ExportIfc"; } + } + + public bool CanCancel + { + get { return false; } + } + + public ExportIfcRequest Request { get; } + + public object Execute() + { + Request.ApplyDefaults(); + return PdmsManager.Instance.ExportIfc(Request); + } + + public void Cancel() + { + throw new System.NotSupportedException("ExportIfcCommand 不支持取消"); + } + } +} diff --git a/Core/PdmsManager.cs b/Core/PdmsManager.cs index 2438160..6db0d19 100644 --- a/Core/PdmsManager.cs +++ b/Core/PdmsManager.cs @@ -3,8 +3,8 @@ using System.Collections.Generic; using TellmePdmsPluging.Models; using Aveva.ApplicationFramework; using Aveva.Pdms.Database; +using Aveva.ApplicationFramework.Presentation; using System.Linq; -using System; namespace TellmePdmsPluging.Core { @@ -242,8 +242,8 @@ namespace TellmePdmsPluging.Core } } - public OpenProjectResult OpenProject(OpenProjectRequest request) - { + public OpenProjectResult OpenProject(OpenProjectRequest request) + { var effectiveRequest = request ?? new OpenProjectRequest(); effectiveRequest.ApplyDefaults(); @@ -307,6 +307,179 @@ namespace TellmePdmsPluging.Core result.CompletedAt = DateTime.Now; return result; } + } + + public CloseProjectResult CloseProject(CloseProjectRequest request) + { + var effectiveRequest = request ?? new CloseProjectRequest(); + effectiveRequest.ApplyDefaults(); + + var result = new CloseProjectResult + { + CompletedAt = DateTime.Now + }; + + try + { + var currentProject = Project.CurrentProject; + if (currentProject == null) + { + result.Success = false; + result.Message = "Project.CurrentProject 为空,无法关闭项目"; + return result; + } + + bool isOpen = false; + try + { + isOpen = currentProject.IsOpen(); + } + catch + { + isOpen = false; + } + + result.WasOpen = isOpen; + result.ProjectName = GetProjectNameSafe(currentProject); + + if (!isOpen) + { + result.Success = true; + result.Message = "当前没有已打开项目"; + result.CompletedAt = DateTime.Now; + return result; + } + + var projectType = currentProject.GetType(); + + var closeWithForce = projectType.GetMethod("Close", new[] { typeof(bool) }); + if (closeWithForce != null) + { + closeWithForce.Invoke(currentProject, new object[] { effectiveRequest.Force }); + } + else + { + var closeNoArg = projectType.GetMethod("Close", Type.EmptyTypes); + if (closeNoArg == null) + { + result.Success = false; + result.Message = "未找到可用的 Project.Close 方法"; + result.CompletedAt = DateTime.Now; + return result; + } + + closeNoArg.Invoke(currentProject, null); + } + + result.Success = true; + result.Message = "项目关闭成功"; + result.CompletedAt = DateTime.Now; + return result; + } + catch (Exception ex) + { + result.Success = false; + result.Message = "关闭项目异常: " + ex.Message; + result.CompletedAt = DateTime.Now; + return result; + } + } + + public ExportIfcResult ExportIfc(ExportIfcRequest request) + { + var effectiveRequest = request ?? new ExportIfcRequest(); + effectiveRequest.ApplyDefaults(); + + var result = new ExportIfcResult + { + StartedAt = DateTime.Now, + ExportPath = effectiveRequest.ExportPath, + FileName = effectiveRequest.FileName, + FullPath = effectiveRequest.GetFullPath() + }; + + try + { + if (!IsPdmsConnected()) + { + result.Success = false; + result.Message = "PDMS 未连接"; + result.CompletedAt = DateTime.Now; + result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; + return result; + } + + var currentMdb = MDB.CurrentMDB; + var designDb = currentMdb?.GetFirstDB(DbType.Design); + if (designDb == null || designDb.World == null) + { + result.Success = false; + result.Message = "未找到有效的设计数据库"; + result.CompletedAt = DateTime.Now; + result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; + return result; + } + + // 确保导出目录存在 + if (!System.IO.Directory.Exists(effectiveRequest.ExportPath)) + { + System.IO.Directory.CreateDirectory(effectiveRequest.ExportPath); + } + + // 构建PML导出命令 + string pmlCommand = BuildIfcExportCommand(effectiveRequest); + + // 执行PML命令 - 通过MacroCommand类型反射构造并执行 + var macroType = CommandManager.Instance.MacroCommand; + if (macroType == null) + throw new InvalidOperationException("MacroCommand类型未注册"); + var ctors = macroType.GetConstructors( + System.Reflection.BindingFlags.Public | + System.Reflection.BindingFlags.NonPublic | + System.Reflection.BindingFlags.Instance); + var ctorInfo = new System.Text.StringBuilder(); + foreach (var c in ctors) + { + ctorInfo.Append(c.ToString()).Append(" | "); + } + throw new InvalidOperationException("MacroCommand类型: " + macroType.FullName + " 构造函数: " + ctorInfo); + + // 检查文件是否生成 + if (System.IO.File.Exists(result.FullPath)) + { + var fileInfo = new System.IO.FileInfo(result.FullPath); + result.FileSizeBytes = fileInfo.Length; + result.Success = true; + result.Message = "IFC导出成功"; + } + else + { + result.Success = false; + result.Message = "IFC导出失败,未生成文件"; + } + + result.CompletedAt = DateTime.Now; + result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; + return result; + } + catch (Exception ex) + { + result.Success = false; + result.Message = "IFC导出异常: " + ex.Message; + result.CompletedAt = DateTime.Now; + result.DurationSeconds = (result.CompletedAt - result.StartedAt).TotalSeconds; + return result; + } + } + + private string BuildIfcExportCommand(ExportIfcRequest request) + { + // 构建PDMS IFC导出的PML命令 + // 根据PDMS版本和配置,命令可能有所不同 + string fullPath = request.GetFullPath().Replace("\\", "/"); + + // 基本的IFC导出命令格式 + return string.Format("EXPORT IFC FILE '{0}'", fullPath); } public OpenMdbResult OpenMdb(OpenMdbRequest request) @@ -359,11 +532,6 @@ namespace TellmePdmsPluging.Core setup.ReadOnly = effectiveRequest.ReadOnly; - if (!string.IsNullOrEmpty(effectiveRequest.Stamp)) - { - setup.Stamp = effectiveRequest.Stamp; - } - if (effectiveRequest.Subtype.HasValue) { setup.Subtype = effectiveRequest.Subtype.Value; @@ -432,8 +600,8 @@ namespace TellmePdmsPluging.Core } } - private static bool TryParseDbTypes(IEnumerable values, out DbType[] types, out string error) - { + private static bool TryParseDbTypes(IEnumerable values, out DbType[] types, out string error) + { var list = new List(); var invalid = new List(); @@ -470,8 +638,8 @@ namespace TellmePdmsPluging.Core return true; } - private static bool TryParseDbType(string value, out DbType dbType) - { + private static bool TryParseDbType(string value, out DbType dbType) + { dbType = DbType.Design; if (string.IsNullOrEmpty(value)) { @@ -487,7 +655,34 @@ namespace TellmePdmsPluging.Core { return false; } - } + } + + private static string GetProjectNameSafe(Project project) + { + if (project == null) + { + return null; + } + + try + { + var nameProperty = project.GetType().GetProperty("Name"); + if (nameProperty != null) + { + var value = nameProperty.GetValue(project, null) as string; + if (!string.IsNullOrEmpty(value)) + { + return value; + } + } + } + catch + { + // ignore and fallback + } + + return null; + } private bool IsPdmsConnected() { @@ -1166,4 +1361,4 @@ namespace TellmePdmsPluging.Core } } } -} \ No newline at end of file +} diff --git a/Models/CloseProjectRequest.cs b/Models/CloseProjectRequest.cs new file mode 100644 index 0000000..b977fa6 --- /dev/null +++ b/Models/CloseProjectRequest.cs @@ -0,0 +1,23 @@ +using System; + +namespace TellmePdmsPluging.Models +{ + public class CloseProjectRequest + { + public bool Force { get; set; } + + public void ApplyDefaults() + { + // reserved for future defaults + } + } + + public class CloseProjectResult + { + public bool Success { get; set; } + public string Message { get; set; } + public string ProjectName { get; set; } + public bool WasOpen { get; set; } + public DateTime CompletedAt { get; set; } + } +} diff --git a/Models/ExportIfcRequest.cs b/Models/ExportIfcRequest.cs new file mode 100644 index 0000000..f2aedfd --- /dev/null +++ b/Models/ExportIfcRequest.cs @@ -0,0 +1,41 @@ +using System; + +namespace TellmePdmsPluging.Models +{ + public class ExportIfcRequest + { + public string ExportPath { get; set; } + public string FileName { get; set; } + + public void ApplyDefaults() + { + if (string.IsNullOrEmpty(ExportPath)) + { + ExportPath = @"C:\temp"; + } + + if (string.IsNullOrEmpty(FileName)) + { + FileName = string.Format("pdms_export_{0:yyyyMMdd_HHmmss}.ifc", DateTime.Now); + } + } + + public string GetFullPath() + { + return System.IO.Path.Combine(ExportPath, FileName); + } + } + + public class ExportIfcResult + { + public bool Success { get; set; } + public string Message { get; set; } + public string ExportPath { get; set; } + public string FileName { get; set; } + public string FullPath { get; set; } + public long FileSizeBytes { get; set; } + public DateTime StartedAt { get; set; } + public DateTime CompletedAt { get; set; } + public double DurationSeconds { get; set; } + } +} diff --git a/Network/HttpServer.cs b/Network/HttpServer.cs index ab43382..65c74b8 100644 --- a/Network/HttpServer.cs +++ b/Network/HttpServer.cs @@ -123,18 +123,24 @@ namespace TellmePdmsPluging.Network case "/api/status/model": responseJson = HandleModelStatus(); break; - case "/api/project/open": - responseJson = HandleProjectOpen(request); - break; - case "/api/mdb/open": - responseJson = HandleMdbOpen(request); - break; + case "/api/project/open": + responseJson = HandleProjectOpen(request); + break; + case "/api/project/close": + responseJson = HandleProjectClose(request); + break; + case "/api/mdb/open": + responseJson = HandleMdbOpen(request); + break; case "/api/model/simplify": responseJson = HandleModelSimplify(request); break; case "/api/model/shrinkwrap": responseJson = HandleModelShrinkwrap(request); break; + case "/api/export/ifc": + responseJson = HandleExportIfc(request); + break; default: response.StatusCode = 404; responseJson = CreateErrorResponse(404, "接口不存在"); @@ -302,8 +308,8 @@ namespace TellmePdmsPluging.Network } } - private string HandleProjectOpen(HttpListenerRequest request) - { + private string HandleProjectOpen(HttpListenerRequest request) + { try { if (!string.Equals(request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase)) @@ -347,7 +353,51 @@ namespace TellmePdmsPluging.Network { return CreateErrorResponse(500, $"打开项目失败: {ex.Message}"); } - } + } + + private string HandleProjectClose(HttpListenerRequest request) + { + try + { + if (!string.Equals(request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase)) + { + return CreateErrorResponse(405, "仅支持POST"); + } + + var payload = ReadRequestBody(request); + var serializer = new JavaScriptSerializer(); + var closeRequest = string.IsNullOrEmpty(payload) + ? new CloseProjectRequest() + : (serializer.Deserialize(payload) ?? new CloseProjectRequest()); + + var command = new CloseProjectCommand(closeRequest); + var invokeResult = MainThreadInvoker.Invoke(command, 600000); + + if (!invokeResult.Success) + { + var msg = string.IsNullOrEmpty(invokeResult.Message) ? "关闭项目失败" : invokeResult.Message; + return CreateErrorResponse(500, msg); + } + + var result = invokeResult.Result as CloseProjectResult; + if (result == null) + { + return CreateErrorResponse(500, "关闭项目结果为空"); + } + + if (!result.Success) + { + var message = string.IsNullOrEmpty(result.Message) ? "关闭项目失败" : result.Message; + return CreateErrorResponse(500, message); + } + + return CreateSuccessResponse(result); + } + catch (Exception ex) + { + return CreateErrorResponse(500, "关闭项目失败: " + ex.Message); + } + } private string HandleMdbOpen(HttpListenerRequest request) { @@ -396,6 +446,53 @@ namespace TellmePdmsPluging.Network } } + private string HandleExportIfc(HttpListenerRequest request) + { + try + { + if (!string.Equals(request.HttpMethod, "POST", StringComparison.OrdinalIgnoreCase)) + { + return CreateErrorResponse(405, "仅支持POST"); + } + + var payload = ReadRequestBody(request); + if (string.IsNullOrEmpty(payload)) + { + return CreateErrorResponse(400, "请求体不能为空"); + } + + var serializer = new JavaScriptSerializer(); + var exportRequest = serializer.Deserialize(payload) ?? new ExportIfcRequest(); + + var command = new ExportIfcCommand(exportRequest); + var invokeResult = MainThreadInvoker.Invoke(command, 600000); + + if (!invokeResult.Success) + { + var msg = string.IsNullOrEmpty(invokeResult.Message) ? "IFC导出失败" : invokeResult.Message; + return CreateErrorResponse(500, msg); + } + + var result = invokeResult.Result as ExportIfcResult; + if (result == null) + { + return CreateErrorResponse(500, "IFC导出结果为空"); + } + + if (!result.Success) + { + var message = string.IsNullOrEmpty(result.Message) ? "IFC导出失败" : result.Message; + return CreateErrorResponse(500, message); + } + + return CreateSuccessResponse(result); + } + catch (Exception ex) + { + return CreateErrorResponse(500, $"IFC导出失败: {ex.Message}"); + } + } + private string ReadRequestBody(HttpListenerRequest request) { if (request == null || request.InputStream == null) @@ -552,4 +649,4 @@ namespace TellmePdmsPluging.Network _listener?.Close(); } } -} \ No newline at end of file +} diff --git a/TellmePdmsPluging.csproj b/TellmePdmsPluging.csproj index f9e7ec6..880bd3c 100644 --- a/TellmePdmsPluging.csproj +++ b/TellmePdmsPluging.csproj @@ -53,6 +53,9 @@ ..\..\..\..\..\AVEVA\Plant\PDMS12.1.SP4\Aveva.ApplicationFramework.dll + + ..\..\..\..\..\AVEVA\Plant\PDMS12.1.SP4\Aveva.ApplicationFramework.Presentation.dll + ..\..\..\..\..\AVEVA\Plant\PDMS12.1.SP4\Aveva.ApplicationFramework.Presentation.Customize.dll @@ -74,8 +77,10 @@ - - + + + + @@ -85,7 +90,9 @@ - + + + @@ -94,4 +101,4 @@ - \ No newline at end of file + diff --git a/inspect.ps1 b/inspect.ps1 new file mode 100644 index 0000000..331a3bb --- /dev/null +++ b/inspect.ps1 @@ -0,0 +1,23 @@ +[System.Reflection.Assembly]::LoadFrom('C:/Users/sladr/source/repos/TellmePdmsPluging/bin/Debug/Aveva.ApplicationFramework.Presentation.dll') | Out-Null +[System.Reflection.Assembly]::LoadFrom('C:/Users/sladr/source/repos/TellmePdmsPluging/bin/Debug/Aveva.ApplicationFramework.dll') | Out-Null + +$assemblies = [System.AppDomain]::CurrentDomain.GetAssemblies() +$allTypes = $assemblies | ForEach-Object { try { $_.GetTypes() } catch { @() } } + +# 找所有 Command 子类 +$cmdType = $allTypes | Where-Object { $_.Name -eq 'Command' -and $_.Namespace -like '*Aveva*' } | Select-Object -First 1 +$subTypes = $allTypes | Where-Object { $_.BaseType -eq $cmdType -and $_.Name -like '*Macro*' } + +$output = @() +$output += "=== Command subclasses with Macro in name ===" +foreach ($t in $subTypes) { + $output += $t.FullName + $output += " Constructors: " + ($t.GetConstructors([System.Reflection.BindingFlags]::Public -bor [System.Reflection.BindingFlags]::NonPublic -bor [System.Reflection.BindingFlags]::Instance) | ForEach-Object { $_.ToString() }) +} + +# 也找所有 Command 子类 +$allSubs = $allTypes | Where-Object { $_.BaseType -eq $cmdType } +$output += "=== All direct Command subclasses ===" +$output += ($allSubs | ForEach-Object { $_.FullName }) + +$output | Out-File 'C:/Users/sladr/source/repos/TellmePdmsPluging/inspect_result.txt' -Encoding UTF8 diff --git a/inspect_result.txt b/inspect_result.txt new file mode 100644 index 0000000..c428a85 --- /dev/null +++ b/inspect_result.txt @@ -0,0 +1,2 @@ +=== Command subclasses with Macro in name === +=== All direct Command subclasses === diff --git a/命令.md b/命令.md new file mode 100644 index 0000000..80fe33c --- /dev/null +++ b/命令.md @@ -0,0 +1,22 @@ +## 集成步骤: + + 1. 复制DLL到PDMS目录 + bin\Debug\TellmePdmsPluging.dll → C:\AVEVA\Plant\PDMS12.1.SP4\ + + 2. 修改 DesignAddin.xml + + 文件位置:C:\AVEVA\Plant\PDMS12.1.SP4\DesignAddin.xml + + 添加这一行: + + + 3. 确保 C:\temp\ 目录存在 + + 插件会往 C:\temp\pdms_plugin_log.txt 写日志,需要提前创建: + mkdir C:\temp + + 4. 启动PDMS + + PDMS启动时会自动加载插件,成功后会弹出提示框,显示HTTP服务已在9001端口启动。 \ No newline at end of file