实现项目关闭和IFC导出功能,添加相关API支持

This commit is contained in:
sladro 2026-03-01 08:04:22 +08:00
parent 8f5bcc0c98
commit 6aa9aa8456
11 changed files with 531 additions and 28 deletions

View File

@ -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\":*)"
]
}
}

View File

@ -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 不支持取消");
}
}
}

View File

@ -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 不支持取消");
}
}
}

View File

@ -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<string> values, out DbType[] types, out string error)
{
private static bool TryParseDbTypes(IEnumerable<string> values, out DbType[] types, out string error)
{
var list = new List<DbType>();
var invalid = new List<string>();
@ -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
}
}
}
}
}

View File

@ -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; }
}
}

View File

@ -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; }
}
}

View File

@ -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<CloseProjectRequest>(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<ExportIfcRequest>(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();
}
}
}
}

View File

@ -53,6 +53,9 @@
<Reference Include="Aveva.ApplicationFramework">
<HintPath>..\..\..\..\..\AVEVA\Plant\PDMS12.1.SP4\Aveva.ApplicationFramework.dll</HintPath>
</Reference>
<Reference Include="Aveva.ApplicationFramework.Presentation">
<HintPath>..\..\..\..\..\AVEVA\Plant\PDMS12.1.SP4\Aveva.ApplicationFramework.Presentation.dll</HintPath>
</Reference>
<Reference Include="Aveva.ApplicationFramework.Presentation.Customize">
<HintPath>..\..\..\..\..\AVEVA\Plant\PDMS12.1.SP4\Aveva.ApplicationFramework.Presentation.Customize.dll</HintPath>
</Reference>
@ -74,8 +77,10 @@
<Reference Include="System.Xml" />
<Reference Include="System.Web.Extensions" />
</ItemGroup>
<ItemGroup>
<Compile Include="Class1.cs" />
<ItemGroup>
<Compile Include="Class1.cs" />
<Compile Include="Commands\CloseProjectCommand.cs" />
<Compile Include="Commands\ExportIfcCommand.cs" />
<Compile Include="Commands\OpenMdbCommand.cs" />
<Compile Include="Commands\OpenProjectCommand.cs" />
<Compile Include="Commands\ShrinkwrapModelCommand.cs" />
@ -85,7 +90,9 @@
<Compile Include="Core\MainThreadInvoker.cs" />
<Compile Include="Core\PdmsManager.cs" />
<Compile Include="Core\SafeQueue.cs" />
<Compile Include="Models\ModelStatusResponse.cs" />
<Compile Include="Models\ExportIfcRequest.cs" />
<Compile Include="Models\CloseProjectRequest.cs" />
<Compile Include="Models\ModelStatusResponse.cs" />
<Compile Include="Models\OpenMdbRequest.cs" />
<Compile Include="Models\OpenProjectRequest.cs" />
<Compile Include="Models\ShrinkwrapModelRequest.cs" />
@ -94,4 +101,4 @@
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project>
</Project>

23
inspect.ps1 Normal file
View File

@ -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

2
inspect_result.txt Normal file
View File

@ -0,0 +1,2 @@
=== Command subclasses with Macro in name ===
=== All direct Command subclasses ===

22
命令.md Normal file
View File

@ -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
添加这一行:
<Addin id="TellmePdmsPluging"
assembly="TellmePdmsPluging.dll"
type="TellmePdmsPluging.TellmePdmsAddin" />
3. 确保 C:\temp\ 目录存在
插件会往 C:\temp\pdms_plugin_log.txt 写日志,需要提前创建:
mkdir C:\temp
4. 启动PDMS
PDMS启动时会自动加载插件成功后会弹出提示框显示HTTP服务已在9001端口启动。