Initial commit: UserStringProperty Rhino plugin
- Rhino 8 C# plugin for managing Block UserString properties - XML template import with merge modes - WinForms UI for property editing - .NET Framework 4.8
This commit is contained in:
25
.gitignore
vendored
Normal file
25
.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Build results
|
||||||
|
|
||||||
|
bin/
|
||||||
|
obj/
|
||||||
|
|
||||||
|
# Visual Studio
|
||||||
|
.vs/
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# Rider
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# VS Code
|
||||||
|
.code/
|
||||||
|
|
||||||
|
# Rhino plugin output (should be .rhp, not tracked)
|
||||||
|
*.rhp
|
||||||
|
*.dll
|
||||||
|
|
||||||
|
# OS
|
||||||
|
Thumbs.db
|
||||||
|
.DS_Store
|
||||||
27
CHANGELOG.md
Normal file
27
CHANGELOG.md
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# 更新日志
|
||||||
|
|
||||||
|
所有重要版本更新会记录在此文件。
|
||||||
|
|
||||||
|
## [0.1.0] - 2026-05-01
|
||||||
|
|
||||||
|
### 新增
|
||||||
|
- 项目骨架创建(插件主类、命令入口)
|
||||||
|
- XML 配置文件解析器
|
||||||
|
- 动态表单 UI:根据 XML 生成 TextBox / ComboBox 控件
|
||||||
|
- 支持 string 类型字段(TextBox)
|
||||||
|
- 支持 list 类型字段(ComboBox 下拉选择)
|
||||||
|
- 多 XML 模板支持:可同时加载多个 XML 文件
|
||||||
|
- 四种导入合并模式:追加、替代、合并更新、智能合并
|
||||||
|
- `XmlImportDialog`:导入时弹出模式选择对话框
|
||||||
|
- 模板持久化:Rhino 重启后自动恢复已加载的模板
|
||||||
|
- `TemplateManager`:管理模板生命周期和 index 文件
|
||||||
|
- 默认 XML 模板内嵌在 DLL 中,首次启动自动提取
|
||||||
|
- 命令名:`UserstringProperty`
|
||||||
|
|
||||||
|
### 功能
|
||||||
|
- 选择 Block 后弹出属性编辑窗口
|
||||||
|
- 属性保存到 Rhino Block 的 UserString(键值对存储)
|
||||||
|
- 分隔标题自动识别(属性名含 `---` 时显示为分组标题)
|
||||||
|
|
||||||
|
### 文档
|
||||||
|
- README、LICENSE、.gitignore、CONTRIBUTING、CHANGELOG
|
||||||
17
CLAUDE.md
Normal file
17
CLAUDE.md
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
> 本文件仅保留高优先级、易混淆、跨会话必须一致的约束。
|
||||||
|
> 本文件必须极精简,占用大模型注意力视为失效。
|
||||||
|
|
||||||
|
## 项目一句话
|
||||||
|
|
||||||
|
Rhino 8 C# 插件(.rhp),管理 Block 的 UserString 属性,支持 XML 模板导入。
|
||||||
|
|
||||||
|
## 自动化约定
|
||||||
|
|
||||||
|
- 禁止自行 commit / push git,用户下达指令再执行
|
||||||
|
- 完成任务后清理临时文件
|
||||||
|
|
||||||
|
## 文档入口
|
||||||
|
|
||||||
|
- PLAN.md:待办事项
|
||||||
|
- CHANGELOG.md:版本记录
|
||||||
|
- CLAUDE.md:仅包含本文件(不要混入项目说明)
|
||||||
31
CONTRIBUTING.md
Normal file
31
CONTRIBUTING.md
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
# 贡献指南
|
||||||
|
|
||||||
|
欢迎提交 Issue 和 Pull Request!
|
||||||
|
|
||||||
|
## 如何贡献
|
||||||
|
|
||||||
|
### 报告问题
|
||||||
|
|
||||||
|
发现 Bug 或有新功能想法?请先搜索 [Issues](https://gitea.com/user/project/issues) 确保不是重复提交,然后按以下格式描述:
|
||||||
|
|
||||||
|
- **问题描述**:清晰说明问题或建议
|
||||||
|
- **复现步骤**:如果是 Bug,列出具体步骤
|
||||||
|
- **环境信息**:Rhino 版本、操作系统、.NET SDK 版本
|
||||||
|
|
||||||
|
### 提交代码
|
||||||
|
|
||||||
|
1. Fork 本项目
|
||||||
|
2. 创建分支 (`git checkout -b feature/your-feature`)
|
||||||
|
3. 确保代码符合项目风格,编译通过
|
||||||
|
4. 提交时请写清楚改动内容和原因
|
||||||
|
5. Push 到你的 Fork,发起 Pull Request
|
||||||
|
|
||||||
|
### 代码规范
|
||||||
|
|
||||||
|
- C# 代码遵循 [C# Coding Conventions](https://learn.microsoft.com/en-us/dotnet/csharp/fundamentals/coding-style/coding-conventions)
|
||||||
|
- 公共方法需有 XML 注释
|
||||||
|
- 新功能请附带测试或更新文档
|
||||||
|
|
||||||
|
## 问题解答
|
||||||
|
|
||||||
|
如有问题可以在 [Discussions](https://gitea.com/user/project/discussions) 中讨论。
|
||||||
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
14
PLAN.md
Normal file
14
PLAN.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# 待办事项
|
||||||
|
|
||||||
|
## 插件开发
|
||||||
|
|
||||||
|
- [ ] 为插件生成真实的 GUID 并配置到项目中
|
||||||
|
- 生成唯一的 GUID(不含全零值)
|
||||||
|
- 将 GUID 配置到 AssemblyInfo 或 csproj 中
|
||||||
|
- 确保插件在 Rhino 插件管理器中正确注册
|
||||||
|
|
||||||
|
- [ ] 修复"重置模板"按钮的 bug
|
||||||
|
- 当前实现有 bug,需要排查并修复
|
||||||
|
|
||||||
|
- [ ] 简化"导入 XML"的判断逻辑
|
||||||
|
- 当前分两段判断过于繁琐,合并为一段
|
||||||
122
README.md
Normal file
122
README.md
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
# UserStringProperty
|
||||||
|
|
||||||
|
Rhino 插件,用于根据用户自定义 XML 配置,给 Block 实例附加构件属性(UserString)。
|
||||||
|
|
||||||
|
## 功能
|
||||||
|
|
||||||
|
- 根据 XML 配置文件动态生成属性编辑界面
|
||||||
|
- 支持 `string` 类型字段(TextBox)和 `list` 类型字段(ComboBox)
|
||||||
|
- 自动保存到 Rhino Block 的 UserString
|
||||||
|
- 支持多个 XML 模板同时加载、合并
|
||||||
|
- 模板自动持久化,Rhino 重启后自动恢复
|
||||||
|
- 导入时提供四种合并模式(追加、替代、合并更新、智能合并)
|
||||||
|
|
||||||
|
## 安装
|
||||||
|
|
||||||
|
### 编译
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd UserStringProperty
|
||||||
|
dotnet build
|
||||||
|
```
|
||||||
|
|
||||||
|
输出:`UserStringProperty/bin/UserStringProperty.dll`
|
||||||
|
|
||||||
|
### 加载到 Rhino
|
||||||
|
|
||||||
|
1. 将 `bin/UserStringProperty.dll` 重命名为 `UserStringProperty.rhp`
|
||||||
|
2. Rhino → `Options` → `Plug-ins` → `Install` → 选择 `.rhp` 文件
|
||||||
|
|
||||||
|
### 使用
|
||||||
|
|
||||||
|
1. 在 Rhino 中输入命令:`UserstringProperty`
|
||||||
|
2. 选择要编辑的 Block 实例
|
||||||
|
3. 默认模板首次启动自动加载;点 **加载XML** 导入自定义模板
|
||||||
|
4. 在下拉框中选择属性集,编辑各字段值
|
||||||
|
5. 点击 **保存**
|
||||||
|
|
||||||
|
## 模板导入模式
|
||||||
|
|
||||||
|
加载 XML 时,如果已有模板,会弹出对话框让选择导入模式:
|
||||||
|
|
||||||
|
| 模式 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| 添加为新属性集 | 追加到现有列表末尾 |
|
||||||
|
| 完全替代现有模板 | 清空所有现有属性集 |
|
||||||
|
| 合并更新 | 同名属性集覆盖,其他保留 |
|
||||||
|
| 智能合并 | 同名覆盖,同时追加新属性集 |
|
||||||
|
|
||||||
|
## XML 配置格式
|
||||||
|
|
||||||
|
插件根据 XML 动态生成 UI。示例参见 `Component_Property_base.xml` 和 `Component_Property_carbon.xml`。
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<cmig name="构件编码">
|
||||||
|
<propertyset name="构件属性-基础" exclusive="false">
|
||||||
|
<!-- string 类型 → TextBox -->
|
||||||
|
<property name="0-00-02-年代" type="string" default="PRC"/>
|
||||||
|
|
||||||
|
<!-- list 类型 → ComboBox -->
|
||||||
|
<property name="0-00-06-构件类型" type="list" default="DS-JG">
|
||||||
|
<option>DS-JG</option>
|
||||||
|
<option>DS-WH</option>
|
||||||
|
<option>DS-SB</option>
|
||||||
|
</property>
|
||||||
|
|
||||||
|
<!-- 分隔标题(default 值为一串 dash 且 type="string") -->
|
||||||
|
<property name="0-01-00-构件基本属性" type="string" default="--------------------" />
|
||||||
|
</propertyset>
|
||||||
|
</cmig>
|
||||||
|
```
|
||||||
|
|
||||||
|
### 字段类型
|
||||||
|
|
||||||
|
| type | 控件 | 说明 |
|
||||||
|
|------|------|------|
|
||||||
|
| `string` | TextBox | 自由文本输入 |
|
||||||
|
| `list` | ComboBox | 下拉选项(通过 `<option>` 定义) |
|
||||||
|
|
||||||
|
### 分隔标题
|
||||||
|
|
||||||
|
属性名的 `default` 值以 `---` 开头时(即 `default="--------------------"`),该属性显示为分组标题栏。
|
||||||
|
|
||||||
|
## 模板存储
|
||||||
|
|
||||||
|
已加载的 XML 模板会复制一份保存到插件目录,重启 Rhino 后自动恢复:
|
||||||
|
|
||||||
|
```
|
||||||
|
%APPDATA%\McNeel\Rhinoceros\8.0\plugins\UserStringProperty\
|
||||||
|
├── template_index.txt # 记录已加载的 XML 路径
|
||||||
|
└── *.xml # 模板副本
|
||||||
|
```
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
|
||||||
|
```
|
||||||
|
UserStringProperty/
|
||||||
|
├── CLAUDE.md # agent 指示(不要当作文档)
|
||||||
|
├── PLAN.md # 待办事项与开发计划
|
||||||
|
├── UserStringProperty.csproj
|
||||||
|
├── UserStringProperty.cs # 插件主类
|
||||||
|
├── Core/
|
||||||
|
│ ├── XmlPropertyConfig.cs # XML 解析器
|
||||||
|
│ └── TemplateManager.cs # 模板管理与持久化
|
||||||
|
├── Commands/
|
||||||
|
│ └── CmdUserstringProperty.cs # 命令入口
|
||||||
|
├── UI/
|
||||||
|
│ ├── MainDialog.cs # 主窗口
|
||||||
|
│ └── XmlImportDialog.cs # 导入模式选择对话框
|
||||||
|
└── Resources/
|
||||||
|
└── EmbeddedTemplates.cs # 内嵌默认 XML 模板
|
||||||
|
```
|
||||||
|
|
||||||
|
## 环境要求
|
||||||
|
|
||||||
|
- Rhino 8
|
||||||
|
- .NET Framework 4.8
|
||||||
|
- .NET SDK 8.0(编译用)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
43
UserStringProperty/Commands/CmdUserstringProperty.cs
Normal file
43
UserStringProperty/Commands/CmdUserstringProperty.cs
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
using Rhino;
|
||||||
|
using Rhino.Commands;
|
||||||
|
using Rhino.DocObjects;
|
||||||
|
using Rhino.Input;
|
||||||
|
|
||||||
|
namespace UserStringProperty.Commands
|
||||||
|
{
|
||||||
|
public class CmdUserstringProperty : Command
|
||||||
|
{
|
||||||
|
public CmdUserstringProperty()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CmdUserstringProperty Instance { get; private set; }
|
||||||
|
|
||||||
|
public override string EnglishName => "UserstringProperty";
|
||||||
|
|
||||||
|
protected override Result RunCommand(RhinoDoc doc, RunMode mode)
|
||||||
|
{
|
||||||
|
var result = RhinoGet.GetOneObject(
|
||||||
|
"选择 Block 实例",
|
||||||
|
false,
|
||||||
|
ObjectType.InstanceReference,
|
||||||
|
out var objref);
|
||||||
|
|
||||||
|
if (result != Result.Success)
|
||||||
|
return result;
|
||||||
|
|
||||||
|
var instanceObject = objref.Object() as InstanceObject;
|
||||||
|
if (instanceObject == null)
|
||||||
|
{
|
||||||
|
RhinoApp.WriteLine("所选对象不是 Block 实例");
|
||||||
|
return Result.Failure;
|
||||||
|
}
|
||||||
|
|
||||||
|
var dialog = new UI.MainDialog(instanceObject);
|
||||||
|
dialog.ShowDialog();
|
||||||
|
|
||||||
|
return Result.Success;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
296
UserStringProperty/Core/TemplateManager.cs
Normal file
296
UserStringProperty/Core/TemplateManager.cs
Normal file
@@ -0,0 +1,296 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
using Rhino;
|
||||||
|
using UserStringProperty.Core;
|
||||||
|
using UserStringProperty.Resources;
|
||||||
|
using UserStringProperty.UI;
|
||||||
|
|
||||||
|
namespace UserStringProperty
|
||||||
|
{
|
||||||
|
public class TemplateManager
|
||||||
|
{
|
||||||
|
private static TemplateManager _instance;
|
||||||
|
|
||||||
|
// 当前加载的所有模板(合并后)
|
||||||
|
private List<XmlPropertyConfig> _configs = new List<XmlPropertyConfig>();
|
||||||
|
private List<string> _loadedPaths = new List<string>();
|
||||||
|
|
||||||
|
// 插件目录(存储模板副本)
|
||||||
|
private string _pluginFolder;
|
||||||
|
|
||||||
|
public static TemplateManager Instance
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
if (_instance == null)
|
||||||
|
_instance = new TemplateManager();
|
||||||
|
return _instance;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<string> LoadedPaths => _loadedPaths;
|
||||||
|
public bool IsLoaded => _configs.Count > 0 && _configs.Any(c => c.PropertySets.Count > 0);
|
||||||
|
|
||||||
|
public TemplateManager()
|
||||||
|
{
|
||||||
|
_pluginFolder = Path.Combine(
|
||||||
|
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
|
||||||
|
"McNeel", "Rhinoceros", "8.0", "plugins", "UserStringProperty"
|
||||||
|
);
|
||||||
|
if (!Directory.Exists(_pluginFolder))
|
||||||
|
Directory.CreateDirectory(_pluginFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取插件目录下的模板副本文件列表
|
||||||
|
public List<string> GetLocalTemplateFiles()
|
||||||
|
{
|
||||||
|
return Directory.GetFiles(_pluginFolder, "*.xml")
|
||||||
|
.OrderBy(f => File.GetCreationTime(f))
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用户选择 XML 文件后的预处理——返回导入对话框让用户选择模式
|
||||||
|
public void PrepareImport(string filePath, List<PropertySet> existingSets, out XmlPropertyConfig incomingConfig)
|
||||||
|
{
|
||||||
|
incomingConfig = XmlPropertyConfig.Load(filePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行导入
|
||||||
|
public void Import(string filePath, string cmigName, string propertysetName)
|
||||||
|
{
|
||||||
|
var config = XmlPropertyConfig.Load(filePath);
|
||||||
|
|
||||||
|
// 检查文件是否已存在
|
||||||
|
var fileName = config.GetStorageFileName(config.PropertySets.FirstOrDefault(p => p.Name == propertysetName) ?? config.PropertySets.First());
|
||||||
|
var destPath = Path.Combine(_pluginFolder, fileName);
|
||||||
|
|
||||||
|
if (File.Exists(destPath))
|
||||||
|
{
|
||||||
|
// 文件已存在,询问是否替换(对话框已在外面处理,这里直接覆盖)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提取指定的 propertset 并保存
|
||||||
|
var ps = config.PropertySets.FirstOrDefault(p => p.Name == propertysetName);
|
||||||
|
if (ps == null) return;
|
||||||
|
|
||||||
|
// 保存文件
|
||||||
|
var xmlContent = ExtractPropertySetXml(filePath, propertysetName);
|
||||||
|
if (!string.IsNullOrEmpty(xmlContent))
|
||||||
|
{
|
||||||
|
File.WriteAllText(destPath, xmlContent);
|
||||||
|
_loadedPaths.Add(destPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
SaveIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadFromFile(string filePath)
|
||||||
|
{
|
||||||
|
if (!File.Exists(filePath))
|
||||||
|
throw new FileNotFoundException($"XML 文件不存在: {filePath}");
|
||||||
|
|
||||||
|
if (_loadedPaths.Contains(filePath))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var config = XmlPropertyConfig.Load(filePath);
|
||||||
|
_configs.Add(config);
|
||||||
|
_loadedPaths.Add(filePath);
|
||||||
|
SaveIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void LoadDefault()
|
||||||
|
{
|
||||||
|
_configs.Clear();
|
||||||
|
_loadedPaths.Clear();
|
||||||
|
|
||||||
|
// 确保插件目录存在
|
||||||
|
if (!Directory.Exists(_pluginFolder))
|
||||||
|
Directory.CreateDirectory(_pluginFolder);
|
||||||
|
|
||||||
|
// 如果 index 不存在,先提取默认模板
|
||||||
|
var indexPath = GetIndexFilePath();
|
||||||
|
if (!File.Exists(indexPath))
|
||||||
|
{
|
||||||
|
EmbeddedTemplates.ExtractToFolder(_pluginFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载 index 中记录的路径(这些路径指向插件目录副本)
|
||||||
|
if (File.Exists(indexPath))
|
||||||
|
{
|
||||||
|
var lines = File.ReadAllLines(indexPath);
|
||||||
|
foreach (var line in lines)
|
||||||
|
{
|
||||||
|
var trimmed = line.Trim();
|
||||||
|
if (!string.IsNullOrEmpty(trimmed) && File.Exists(trimmed))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = XmlPropertyConfig.Load(trimmed);
|
||||||
|
if (config.PropertySets.Count > 0)
|
||||||
|
{
|
||||||
|
_configs.Add(config);
|
||||||
|
_loadedPaths.Add(trimmed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扫描插件目录下所有 XML,检查是否有未加载的文件(防止有模板被复制进去但不在 index)
|
||||||
|
var existingFiles = Directory.GetFiles(_pluginFolder, "*.xml");
|
||||||
|
foreach (var file in existingFiles)
|
||||||
|
{
|
||||||
|
if (_loadedPaths.Contains(file))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = XmlPropertyConfig.Load(file);
|
||||||
|
if (config.PropertySets.Count > 0)
|
||||||
|
{
|
||||||
|
_configs.Add(config);
|
||||||
|
_loadedPaths.Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果没有任何模板,重新提取并重试
|
||||||
|
if (_configs.Count == 0)
|
||||||
|
{
|
||||||
|
EmbeddedTemplates.ExtractToFolder(_pluginFolder);
|
||||||
|
foreach (var file in Directory.GetFiles(_pluginFolder, "*.xml"))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var config = XmlPropertyConfig.Load(file);
|
||||||
|
if (config.PropertySets.Count > 0)
|
||||||
|
{
|
||||||
|
_configs.Add(config);
|
||||||
|
_loadedPaths.Add(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存 index(确保 index 与实际加载状态一致)
|
||||||
|
SaveIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetIndexFilePath()
|
||||||
|
{
|
||||||
|
return Path.Combine(_pluginFolder, "template_index.txt");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveIndex()
|
||||||
|
{
|
||||||
|
File.WriteAllLines(GetIndexFilePath(), _loadedPaths);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SaveTemplatesToPluginFolder()
|
||||||
|
{
|
||||||
|
// 按 [cmig name]-[propertyset name].xml 格式保存
|
||||||
|
foreach (var config in _configs)
|
||||||
|
{
|
||||||
|
foreach (var ps in config.PropertySets)
|
||||||
|
{
|
||||||
|
var fileName = config.GetStorageFileName(ps);
|
||||||
|
var destPath = Path.Combine(_pluginFolder, fileName);
|
||||||
|
|
||||||
|
// 如果 config 有原始内容,使用原始内容;否则从 config 重新生成 XML
|
||||||
|
if (!string.IsNullOrEmpty(config.SourcePath) && File.Exists(config.SourcePath))
|
||||||
|
{
|
||||||
|
// 从原始文件提取对应的 propertset 部分来保存
|
||||||
|
var xmlContent = ExtractPropertySetXml(config.SourcePath, ps.Name);
|
||||||
|
if (!string.IsNullOrEmpty(xmlContent))
|
||||||
|
File.WriteAllText(destPath, xmlContent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从原始 XML 中提取指定 propertset 的完整 XML
|
||||||
|
private string ExtractPropertySetXml(string sourcePath, string propertysetName)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var doc = XDocument.Load(sourcePath);
|
||||||
|
var cmig = doc.Root;
|
||||||
|
if (cmig == null) return null;
|
||||||
|
|
||||||
|
var psElem = cmig.Elements("propertyset")
|
||||||
|
.FirstOrDefault(p => p.Attribute("name")?.Value == propertysetName);
|
||||||
|
|
||||||
|
if (psElem == null) return null;
|
||||||
|
|
||||||
|
// 重新构建只包含这个 propertset 的 XML
|
||||||
|
var newDoc = new XDocument(new XDeclaration("1.0", "utf-8", null));
|
||||||
|
var newCmig = new XElement(cmig.Name, cmig.Attributes());
|
||||||
|
newCmig.Add(new XElement(psElem));
|
||||||
|
newDoc.Add(newCmig);
|
||||||
|
|
||||||
|
return newDoc.ToString();
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_configs.Clear();
|
||||||
|
_loadedPaths.Clear();
|
||||||
|
var indexPath = GetIndexFilePath();
|
||||||
|
if (File.Exists(indexPath))
|
||||||
|
File.Delete(indexPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<PropertySet> GetAllPropertySets()
|
||||||
|
{
|
||||||
|
var result = new List<PropertySet>();
|
||||||
|
foreach (var config in _configs)
|
||||||
|
{
|
||||||
|
foreach (var ps in config.PropertySets)
|
||||||
|
{
|
||||||
|
result.Add(ps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetCmigNames()
|
||||||
|
{
|
||||||
|
return _configs.Select(c => c.CmigName).Distinct().ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetPropertySetNames(string cmigName)
|
||||||
|
{
|
||||||
|
var config = _configs.FirstOrDefault(c => c.CmigName == cmigName);
|
||||||
|
if (config == null) return new List<string>();
|
||||||
|
return config.PropertySets.Select(ps => ps.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public PropertySet GetPropertySet(string cmigName, string psName)
|
||||||
|
{
|
||||||
|
var config = _configs.FirstOrDefault(c => c.CmigName == cmigName);
|
||||||
|
if (config == null) return null;
|
||||||
|
return config.PropertySets.FirstOrDefault(ps => ps.Name == psName);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<string> GetPropertySetNames()
|
||||||
|
{
|
||||||
|
return GetAllPropertySets().Select(ps => ps.Name).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
public string GetPluginFolderPath()
|
||||||
|
{
|
||||||
|
return _pluginFolder;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
124
UserStringProperty/Core/XmlPropertyConfig.cs
Normal file
124
UserStringProperty/Core/XmlPropertyConfig.cs
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Xml.Linq;
|
||||||
|
|
||||||
|
namespace UserStringProperty.Core
|
||||||
|
{
|
||||||
|
public class PropertyDefinition
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Type { get; set; } // "string" or "list"
|
||||||
|
public string DefaultValue { get; set; }
|
||||||
|
public List<string> Options { get; set; } = new List<string>();
|
||||||
|
public bool IsSeparator => DefaultValue != null && DefaultValue.Length >= 10 && DefaultValue.All(c => c == '-') && Type == "string";
|
||||||
|
|
||||||
|
public PropertyDefinition Clone()
|
||||||
|
{
|
||||||
|
return new PropertyDefinition
|
||||||
|
{
|
||||||
|
Name = this.Name,
|
||||||
|
Type = this.Type,
|
||||||
|
DefaultValue = this.DefaultValue,
|
||||||
|
Options = new List<string>(this.Options)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class PropertySet
|
||||||
|
{
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string Picture { get; set; }
|
||||||
|
public bool Exclusive { get; set; }
|
||||||
|
public List<PropertyDefinition> Properties { get; set; } = new List<PropertyDefinition>();
|
||||||
|
|
||||||
|
public PropertySet Clone()
|
||||||
|
{
|
||||||
|
var clone = new PropertySet
|
||||||
|
{
|
||||||
|
Name = this.Name,
|
||||||
|
Picture = this.Picture,
|
||||||
|
Exclusive = this.Exclusive,
|
||||||
|
Properties = new List<PropertyDefinition>()
|
||||||
|
};
|
||||||
|
foreach (var p in this.Properties)
|
||||||
|
clone.Properties.Add(p.Clone());
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class XmlPropertyConfig
|
||||||
|
{
|
||||||
|
public string CmigName { get; set; } // cmig 的 name,即容器名称
|
||||||
|
public string Name { get; set; }
|
||||||
|
public string SourcePath { get; set; }
|
||||||
|
public List<PropertySet> PropertySets { get; set; } = new List<PropertySet>();
|
||||||
|
|
||||||
|
// 获取存储文件名:[cmig name]-[propertyset name].xml
|
||||||
|
public string GetStorageFileName(PropertySet ps)
|
||||||
|
{
|
||||||
|
return $"{CmigName}-{ps.Name}.xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
public static XmlPropertyConfig Load(string filePath)
|
||||||
|
{
|
||||||
|
var config = new XmlPropertyConfig();
|
||||||
|
|
||||||
|
if (!System.IO.File.Exists(filePath))
|
||||||
|
return config;
|
||||||
|
|
||||||
|
var doc = XDocument.Load(filePath);
|
||||||
|
var cmig = doc.Root;
|
||||||
|
if (cmig == null) return config;
|
||||||
|
|
||||||
|
config.CmigName = cmig.Attribute("name")?.Value ?? "未命名";
|
||||||
|
config.Name = config.CmigName;
|
||||||
|
config.SourcePath = filePath;
|
||||||
|
|
||||||
|
foreach (var psElem in cmig.Elements("propertyset"))
|
||||||
|
{
|
||||||
|
var ps = new PropertySet
|
||||||
|
{
|
||||||
|
Name = psElem.Attribute("name")?.Value ?? "",
|
||||||
|
Picture = psElem.Attribute("picture")?.Value,
|
||||||
|
Exclusive = psElem.Attribute("exclusive")?.Value == "true"
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var propElem in psElem.Elements("property"))
|
||||||
|
{
|
||||||
|
var prop = new PropertyDefinition
|
||||||
|
{
|
||||||
|
Name = propElem.Attribute("name")?.Value ?? "",
|
||||||
|
Type = propElem.Attribute("type")?.Value ?? "string",
|
||||||
|
DefaultValue = propElem.Attribute("default")?.Value
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var opt in propElem.Elements("option"))
|
||||||
|
{
|
||||||
|
prop.Options.Add(opt.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
ps.Properties.Add(prop);
|
||||||
|
}
|
||||||
|
|
||||||
|
config.PropertySets.Add(ps);
|
||||||
|
}
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public XmlPropertyConfig Clone()
|
||||||
|
{
|
||||||
|
var clone = new XmlPropertyConfig
|
||||||
|
{
|
||||||
|
CmigName = this.CmigName,
|
||||||
|
Name = this.Name,
|
||||||
|
SourcePath = this.SourcePath,
|
||||||
|
PropertySets = new List<PropertySet>()
|
||||||
|
};
|
||||||
|
foreach (var ps in this.PropertySets)
|
||||||
|
clone.PropertySets.Add(ps.Clone());
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
130
UserStringProperty/Resources/EmbeddedTemplates.cs
Normal file
130
UserStringProperty/Resources/EmbeddedTemplates.cs
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
using System.IO;
|
||||||
|
|
||||||
|
namespace UserStringProperty.Resources
|
||||||
|
{
|
||||||
|
public static class EmbeddedTemplates
|
||||||
|
{
|
||||||
|
public const string BaseXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||||
|
<cmig name=""构件属性"">
|
||||||
|
|
||||||
|
<propertyset name=""构件属性-基础"" exclusive=""false"" picture=""基础-任意实体.png"">
|
||||||
|
<property name=""0-00-00-构件编码属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""0-00-01-星球"" type=""list"" default=""E"">
|
||||||
|
<option>E</option><option>M</option><option>V</option><option>J</option><option>S</option><option>U</option><option>N</option><option>L</option>
|
||||||
|
</property>
|
||||||
|
<property name=""0-00-02-年代"" type=""string"" default=""PRC""/>
|
||||||
|
<property name=""0-00-03-国家"" type=""string"" default=""156"" />
|
||||||
|
<property name=""0-00-04-地区"" type=""string"" default=""130723"" />
|
||||||
|
<property name=""0-00-05-项目楼栋"" type=""list"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""0-00-06-构件类型"" type=""list"" default=""DS-JG"">
|
||||||
|
<option>DS-JG</option><option>DS-WH</option><option>DS-SB</option><option>DS-NZ</option><option>DS-HJ</option>
|
||||||
|
<option>DX-FH</option><option>DX-JG</option><option>DX-WH</option><option>DX-SB</option><option>DX-NZ</option>
|
||||||
|
</property>
|
||||||
|
<property name=""0-00-07-楼层号"" type=""string"" default=""1""/>
|
||||||
|
<property name=""0-00-08-标高信息"" type=""string"" default=""0.000""/>
|
||||||
|
<property name=""0-00-09-轴号"" type=""string"" default=""1/A""/>
|
||||||
|
<property name=""0-00-10-位置"" type=""string"" default=""H0V0""/>
|
||||||
|
<property name=""0-00-11-坐标"" type=""string"" default=""0,0,0""/>
|
||||||
|
<property name=""0-01-00-构件基本属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""0-01-01-构件id"" type=""string"" default=""数据库自动生成后导入""/>
|
||||||
|
<property name=""0-01-02-构件名称"" type=""string"" default=""标准拱""/>
|
||||||
|
<property name=""0-01-03-所属项目名称"" type=""string"" default=""SFC""/>
|
||||||
|
<property name=""0-01-04-所属楼栋名称"" type=""string"" default=""SolarArk5.0""/>
|
||||||
|
<property name=""0-01-05-孪生属性"" type=""list"" default=""模拟"">
|
||||||
|
<option>模拟</option><option>实际</option>
|
||||||
|
</property>
|
||||||
|
<property name=""0-01-06-总体积"" type=""string"" default=""0.00""/>
|
||||||
|
<property name=""0-01-07-总体积单位"" type=""string"" default=""m³""/>
|
||||||
|
<property name=""0-01-08-材料(用,分隔)"" type=""string"" default=""钢,木""/>
|
||||||
|
<property name=""0-01-09-各材料体积"" type=""string"" default=""0,0""/>
|
||||||
|
<property name=""0-01-10-密度"" type=""string"" default=""0.00""/>
|
||||||
|
<property name=""0-01-11-密度单位"" type=""string"" default=""kg/m³""/>
|
||||||
|
<property name=""0-01-12-重量"" type=""string"" default=""0.00""/>
|
||||||
|
<property name=""0-01-13-重量单位"" type=""string"" default=""kg""/>
|
||||||
|
<property name=""0-01-14-表面积"" type=""string"" default=""0.00""/>
|
||||||
|
<property name=""0-01-15-结构层体积"" type=""string"" default=""0.00""/>
|
||||||
|
<property name=""0-02-00-性能属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""0-02-01-热阻"" type=""string"" default=""0.00""/>
|
||||||
|
<property name=""0-02-02-热阻单位"" type=""string"" default=""m²·K/W""/>
|
||||||
|
<property name=""0-02-03-热容"" type=""string"" default=""0.00""/>
|
||||||
|
<property name=""0-02-04-热容单位"" type=""string"" default=""J/(kg·K)""/>
|
||||||
|
<property name=""1-00-00-材料制备阶段构件属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""1-01-01-所属材料制备工序"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""1-01-02-材料制备序号"" type=""string"" default=""1"" />
|
||||||
|
<property name=""1-01-03-材料制备类型"" type=""string"" default=""钢材加工"" />
|
||||||
|
<property name=""1-01-04-材料制备用时(天)"" type=""string"" default=""15"" />
|
||||||
|
<property name=""1-01-05-材料制备开始日期"" type=""string"" default=""20260515"" />
|
||||||
|
<property name=""1-01-06-材料制备结束日期"" type=""string"" default=""20260530"" />
|
||||||
|
<property name=""2-00-00-生产制造阶段构件属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""2-01-01-所属生产工序"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""2-01-02-生产序号"" type=""string"" default=""1"" />
|
||||||
|
<property name=""2-01-03-并行工序组"" type=""string"" default=""1"" />
|
||||||
|
<property name=""2-01-04-生产类型"" type=""string"" default=""焊接"" />
|
||||||
|
<property name=""2-01-05-生产用时(天)"" type=""string"" default=""30"" />
|
||||||
|
<property name=""2-01-06-生产开始日期"" type=""string"" default=""20260601"" />
|
||||||
|
<property name=""2-01-07-生产结束日期"" type=""string"" default=""20260701"" />
|
||||||
|
<property name=""3-00-00-物流运输阶段构件属性"" type=""string"" default=""--------------------""/>
|
||||||
|
<property name=""3-01-01-所属运输工序"" type=""string"" default=""数据库自动生成后导入""/>
|
||||||
|
<property name=""3-01-02-所属运输装备"" type=""string"" default=""苏A00000""/>
|
||||||
|
<property name=""3-01-03-运输方式"" type=""list"" default=""轻型汽油货车运输"">
|
||||||
|
<option>轻型汽油货车运输</option><option>中型汽油货车运输</option><option>重型汽油货车运输</option>
|
||||||
|
</property>
|
||||||
|
<property name=""3-01-04-运输用时(天)"" type=""string"" default=""3"" />
|
||||||
|
<property name=""3-01-05-运输开始日期"" type=""string"" default=""20260701"" />
|
||||||
|
<property name=""3-01-06-运输结束日期"" type=""string"" default=""20260705"" />
|
||||||
|
<property name=""4-00-00-装配施工阶段构件属性"" type=""string"" default=""--------------------""/>
|
||||||
|
<property name=""4-01-01-所属装配工序"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""4-01-02-装配序号"" type=""string"" default=""1"" />
|
||||||
|
<property name=""4-01-03-并行工序组"" type=""string"" default=""1"" />
|
||||||
|
<property name=""4-01-04-装配类型"" type=""string"" default=""吊装"" />
|
||||||
|
<property name=""4-01-05-装配用时"" type=""string"" default=""0:00"" />
|
||||||
|
<property name=""4-01-06-装配日期"" type=""string"" default=""20250710"" />
|
||||||
|
<property name=""5-00-00-运营维护阶段构件属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""5-01-01-所属维护工序"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""6-00-00-改造再利用阶段构件属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""6-01-01-所属改造工序"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""7-00-00-拆除再利用阶段构件属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""7-01-01-所属拆除工序"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
</propertyset>
|
||||||
|
|
||||||
|
</cmig>";
|
||||||
|
|
||||||
|
public const string CarbonXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
|
||||||
|
<cmig name=""构件碳排放属性"">
|
||||||
|
|
||||||
|
<propertyset name=""构件碳排放属性"" exclusive=""false"" picture=""碳排放.png"">
|
||||||
|
<property name=""1-02-00-材料制备碳排放属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""1-02-01-构件材料制备碳排放因子"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""1-02-02-构件材料制备碳排放值"" type=""string"" default=""0.00"" />
|
||||||
|
<property name=""1-02-03-构件材料制备碳排放值单位"" type=""string"" default=""kgCO2e"" />
|
||||||
|
<property name=""2-02-00-生产制造碳排放属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""2-02-01-构件生产制造碳排放因子"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""2-02-02-构件生产制造碳排放值"" type=""string"" default=""0.00"" />
|
||||||
|
<property name=""2-02-03-构件生产制造碳排放值单位"" type=""string"" default=""kgCO2e"" />
|
||||||
|
<property name=""2-02-04-生产制造产生废料比例"" type=""string"" default=""10%"" />
|
||||||
|
<property name=""2-02-05-生产制造废料碳排放因子"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""2-02-06-生产制造废料碳排放值"" type=""string"" default=""0.00"" />
|
||||||
|
<property name=""3-02-00-物流运输碳排放属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""3-02-01-构件物流运输碳排放因子"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""3-02-02-构件物流运输碳排放值"" type=""string"" default=""0.00""/>
|
||||||
|
<property name=""3-02-03-构件物流运输碳排放值单位"" type=""string"" default=""kgCO2e"" />
|
||||||
|
<property name=""3-02-04-构件物流运输距离(km)"" type=""string"" default=""500""/>
|
||||||
|
<property name=""4-02-00-装配施工碳排放属性"" type=""string"" default=""--------------------"" />
|
||||||
|
<property name=""4-02-01-构件装配施工碳排放因子"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""4-02-02-构件装配施工碳排放值"" type=""string"" default=""0.00"" />
|
||||||
|
<property name=""4-02-03-构件装配施工碳排放值单位"" type=""string"" default=""kgCO2e"" />
|
||||||
|
<property name=""4-02-04-构件装配施工产生废料比例"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""4-02-05-构件装配施工废料碳排放因子"" type=""string"" default=""数据库自动生成后导入"" />
|
||||||
|
<property name=""4-02-06-构件装配施工单位工人数量"" type=""string"" default=""1"" />
|
||||||
|
</propertyset>
|
||||||
|
|
||||||
|
</cmig>";
|
||||||
|
|
||||||
|
public static void ExtractToFolder(string folder)
|
||||||
|
{
|
||||||
|
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
|
||||||
|
File.WriteAllText(Path.Combine(folder, "构件属性-构件属性-基础.xml"), BaseXml);
|
||||||
|
File.WriteAllText(Path.Combine(folder, "构件碳排放属性-构件碳排放属性.xml"), CarbonXml);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
517
UserStringProperty/UI/MainDialog.cs
Normal file
517
UserStringProperty/UI/MainDialog.cs
Normal file
@@ -0,0 +1,517 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using Rhino.DocObjects;
|
||||||
|
using UserStringProperty.Core;
|
||||||
|
|
||||||
|
namespace UserStringProperty.UI
|
||||||
|
{
|
||||||
|
public enum ApplyMode
|
||||||
|
{
|
||||||
|
Replace, // 替代:清空现有属性后应用新模板
|
||||||
|
Append // 附加:合并,重复 key 时询问
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum ConflictResolution
|
||||||
|
{
|
||||||
|
ReplaceDuplicates, // 替代重复的 key
|
||||||
|
SkipDuplicates // 跳过重复的 key
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class MainDialog : Form
|
||||||
|
{
|
||||||
|
private InstanceObject _instanceObject;
|
||||||
|
private ComboBox _cmbCmig;
|
||||||
|
private ComboBox _cmbPropertySet;
|
||||||
|
private Panel _panelFields;
|
||||||
|
private Dictionary<string, Control> _fieldControls = new Dictionary<string, Control>();
|
||||||
|
private Button _btnSave;
|
||||||
|
private Button _btnCancel;
|
||||||
|
private Button _btnLoadXml;
|
||||||
|
private Label _lblTemplateInfo;
|
||||||
|
|
||||||
|
public MainDialog(InstanceObject instanceObject)
|
||||||
|
{
|
||||||
|
_instanceObject = instanceObject;
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
this.Text = "构件属性编辑";
|
||||||
|
this.Size = new Size(550, 580);
|
||||||
|
this.StartPosition = FormStartPosition.CenterParent;
|
||||||
|
this.FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
|
||||||
|
// Cmig 选择
|
||||||
|
var lblCmig = new Label
|
||||||
|
{
|
||||||
|
Text = "模板类型:",
|
||||||
|
Location = new Point(10, 10),
|
||||||
|
Size = new Size(80, 20)
|
||||||
|
};
|
||||||
|
|
||||||
|
_cmbCmig = new ComboBox
|
||||||
|
{
|
||||||
|
Location = new Point(95, 8),
|
||||||
|
Size = new Size(180, 20),
|
||||||
|
DropDownStyle = ComboBoxStyle.DropDownList
|
||||||
|
};
|
||||||
|
_cmbCmig.SelectedIndexChanged += CmbCmig_SelectedIndexChanged;
|
||||||
|
this.Controls.Add(lblCmig);
|
||||||
|
this.Controls.Add(_cmbCmig);
|
||||||
|
|
||||||
|
// PropertySet 选择
|
||||||
|
var lblPropertySet = new Label
|
||||||
|
{
|
||||||
|
Text = "属性模板:",
|
||||||
|
Location = new Point(285, 10),
|
||||||
|
Size = new Size(80, 20)
|
||||||
|
};
|
||||||
|
|
||||||
|
_cmbPropertySet = new ComboBox
|
||||||
|
{
|
||||||
|
Location = new Point(370, 8),
|
||||||
|
Size = new Size(160, 20),
|
||||||
|
DropDownStyle = ComboBoxStyle.DropDownList
|
||||||
|
};
|
||||||
|
_cmbPropertySet.SelectedIndexChanged += CmbPropertySet_SelectedIndexChanged;
|
||||||
|
this.Controls.Add(lblPropertySet);
|
||||||
|
this.Controls.Add(_cmbPropertySet);
|
||||||
|
|
||||||
|
_btnLoadXml = new Button
|
||||||
|
{
|
||||||
|
Text = "加载XML",
|
||||||
|
Location = new Point(10, 38),
|
||||||
|
Size = new Size(90, 25)
|
||||||
|
};
|
||||||
|
_btnLoadXml.Click += BtnLoadXml_Click;
|
||||||
|
|
||||||
|
var btnResetTemplate = new Button
|
||||||
|
{
|
||||||
|
Text = "重置模板",
|
||||||
|
Location = new Point(105, 38),
|
||||||
|
Size = new Size(90, 25)
|
||||||
|
};
|
||||||
|
btnResetTemplate.Click += (s, e) =>
|
||||||
|
{
|
||||||
|
TemplateManager.Instance.Clear();
|
||||||
|
RefreshCmigCombo();
|
||||||
|
UpdateTemplateInfo();
|
||||||
|
};
|
||||||
|
|
||||||
|
_lblTemplateInfo = new Label
|
||||||
|
{
|
||||||
|
Text = "",
|
||||||
|
Location = new Point(200, 41),
|
||||||
|
Size = new Size(330, 15),
|
||||||
|
ForeColor = Color.Gray,
|
||||||
|
Font = new System.Drawing.Font(Font.FontFamily, 8)
|
||||||
|
};
|
||||||
|
|
||||||
|
var lblFields = new Label
|
||||||
|
{
|
||||||
|
Text = "字段:",
|
||||||
|
Location = new Point(10, 75),
|
||||||
|
Size = new Size(60, 20)
|
||||||
|
};
|
||||||
|
|
||||||
|
_panelFields = new Panel
|
||||||
|
{
|
||||||
|
Location = new Point(10, 95),
|
||||||
|
Size = new Size(510, 370),
|
||||||
|
AutoScroll = true,
|
||||||
|
BorderStyle = BorderStyle.FixedSingle
|
||||||
|
};
|
||||||
|
|
||||||
|
_btnSave = new Button { Text = "写入属性", Location = new Point(370, 480), Size = new Size(90, 30) };
|
||||||
|
_btnSave.Click += BtnSave_Click;
|
||||||
|
|
||||||
|
_btnCancel = new Button { Text = "取消", Location = new Point(270, 480), Size = new Size(90, 30) };
|
||||||
|
_btnCancel.Click += (s, e) => this.Close();
|
||||||
|
|
||||||
|
this.Controls.AddRange(new Control[] {
|
||||||
|
_btnLoadXml, btnResetTemplate, _lblTemplateInfo,
|
||||||
|
lblCmig, _cmbCmig, lblPropertySet, _cmbPropertySet,
|
||||||
|
lblFields, _panelFields, _btnSave, _btnCancel
|
||||||
|
});
|
||||||
|
|
||||||
|
RefreshCmigCombo();
|
||||||
|
UpdateTemplateInfo();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTemplateInfo()
|
||||||
|
{
|
||||||
|
var tm = TemplateManager.Instance;
|
||||||
|
if (!tm.IsLoaded)
|
||||||
|
{
|
||||||
|
_lblTemplateInfo.Text = "未加载模板";
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var paths = tm.LoadedPaths;
|
||||||
|
if (paths.Count == 1)
|
||||||
|
_lblTemplateInfo.Text = $"已加载: {System.IO.Path.GetFileName(paths[0])}";
|
||||||
|
else
|
||||||
|
_lblTemplateInfo.Text = $"已加载 {paths.Count} 个模板文件";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshCmigCombo()
|
||||||
|
{
|
||||||
|
_cmbCmig.Items.Clear();
|
||||||
|
_cmbPropertySet.Items.Clear();
|
||||||
|
|
||||||
|
var cmigNames = TemplateManager.Instance.GetCmigNames();
|
||||||
|
if (cmigNames.Count == 0)
|
||||||
|
{
|
||||||
|
_cmbCmig.Items.Add("(请先加载XML模板)");
|
||||||
|
_cmbCmig.SelectedIndex = 0;
|
||||||
|
_cmbCmig.Enabled = false;
|
||||||
|
_cmbPropertySet.Enabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cmbCmig.Items.AddRange(cmigNames.ToArray());
|
||||||
|
_cmbCmig.Enabled = true;
|
||||||
|
if (_cmbCmig.Items.Count > 0)
|
||||||
|
_cmbCmig.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void RefreshPropertySetCombo(string cmigName)
|
||||||
|
{
|
||||||
|
_cmbPropertySet.Items.Clear();
|
||||||
|
|
||||||
|
var psNames = TemplateManager.Instance.GetPropertySetNames(cmigName);
|
||||||
|
if (psNames.Count == 0)
|
||||||
|
{
|
||||||
|
_cmbPropertySet.Items.Add("(无属性集)");
|
||||||
|
_cmbPropertySet.SelectedIndex = 0;
|
||||||
|
_cmbPropertySet.Enabled = false;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_cmbPropertySet.Items.AddRange(psNames.ToArray());
|
||||||
|
_cmbPropertySet.Enabled = true;
|
||||||
|
if (_cmbPropertySet.Items.Count > 0)
|
||||||
|
_cmbPropertySet.SelectedIndex = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CmbCmig_SelectedIndexChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var cmigName = _cmbCmig.SelectedItem?.ToString();
|
||||||
|
if (!string.IsNullOrEmpty(cmigName))
|
||||||
|
{
|
||||||
|
RefreshPropertySetCombo(cmigName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CmbPropertySet_SelectedIndexChanged(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var cmigName = _cmbCmig.SelectedItem?.ToString();
|
||||||
|
var psName = _cmbPropertySet.SelectedItem?.ToString();
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(cmigName) || string.IsNullOrEmpty(psName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 直接加载字段显示,让用户先看到/编辑现有内容
|
||||||
|
// 替代/附加逻辑在保存时才处理
|
||||||
|
BuildFieldsForPropertySet(cmigName, psName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ApplyMode AskApplyMode()
|
||||||
|
{
|
||||||
|
var result = MessageBox.Show(
|
||||||
|
"Block 已有属性。\n\n选择「是」替代现有属性(清空后应用新模板)\n选择「否」附加到现有属性(合并)",
|
||||||
|
"应用模式",
|
||||||
|
MessageBoxButtons.YesNoCancel,
|
||||||
|
MessageBoxIcon.Question);
|
||||||
|
|
||||||
|
if (result == DialogResult.Yes)
|
||||||
|
return ApplyMode.Replace;
|
||||||
|
else if (result == DialogResult.No)
|
||||||
|
return ApplyMode.Append;
|
||||||
|
else
|
||||||
|
return ApplyMode.Replace; // 默认替代
|
||||||
|
}
|
||||||
|
|
||||||
|
private ConflictResolution AskConflictResolution(List<string> duplicateKeys)
|
||||||
|
{
|
||||||
|
var suffix = duplicateKeys.Count > 5 ? "..." : "";
|
||||||
|
var result = MessageBox.Show(
|
||||||
|
$"发现 {duplicateKeys.Count} 个重复的属性 key:\n{string.Join(", ", duplicateKeys.Take(5))}{suffix}\n\n选择「是」替代这些 key 的值\n选择「否」跳过这些 key",
|
||||||
|
"属性冲突",
|
||||||
|
MessageBoxButtons.YesNoCancel,
|
||||||
|
MessageBoxIcon.Warning);
|
||||||
|
|
||||||
|
if (result == DialogResult.Yes)
|
||||||
|
return ConflictResolution.ReplaceDuplicates;
|
||||||
|
else
|
||||||
|
return ConflictResolution.SkipDuplicates;
|
||||||
|
}
|
||||||
|
|
||||||
|
private string GetExistingPropertyValue(string key)
|
||||||
|
{
|
||||||
|
var userStrings = _instanceObject.Attributes?.GetUserStrings();
|
||||||
|
if (userStrings != null && userStrings[key] != null)
|
||||||
|
{
|
||||||
|
return userStrings[key];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetFieldValue(string key, string value)
|
||||||
|
{
|
||||||
|
if (_fieldControls.ContainsKey(key))
|
||||||
|
{
|
||||||
|
var ctrl = _fieldControls[key];
|
||||||
|
if (ctrl is ComboBox cmb)
|
||||||
|
{
|
||||||
|
if (cmb.Items.Contains(value))
|
||||||
|
cmb.SelectedItem = value;
|
||||||
|
}
|
||||||
|
else if (ctrl is TextBox txt)
|
||||||
|
{
|
||||||
|
txt.Text = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<string> GetExistingPropertyKeys()
|
||||||
|
{
|
||||||
|
var keys = new List<string>();
|
||||||
|
var userStrings = _instanceObject.Attributes?.GetUserStrings();
|
||||||
|
if (userStrings != null)
|
||||||
|
{
|
||||||
|
keys.AddRange(userStrings.AllKeys);
|
||||||
|
}
|
||||||
|
return keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnLoadXml_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
using (var dlg = new OpenFileDialog())
|
||||||
|
{
|
||||||
|
dlg.Filter = "XML文件|*.xml";
|
||||||
|
dlg.Title = "选择属性配置文件";
|
||||||
|
if (dlg.ShowDialog() == DialogResult.OK)
|
||||||
|
{
|
||||||
|
var filePath = dlg.FileName;
|
||||||
|
|
||||||
|
XmlPropertyConfig incomingConfig;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
incomingConfig = XmlPropertyConfig.Load(filePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
MessageBox.Show($"XML 解析失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (incomingConfig.PropertySets.Count == 0)
|
||||||
|
{
|
||||||
|
MessageBox.Show("XML 中没有找到属性集", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var pluginFolder = TemplateManager.Instance.GetPluginFolderPath();
|
||||||
|
using (var importDlg = new XmlImportDialog(incomingConfig, pluginFolder))
|
||||||
|
{
|
||||||
|
if (importDlg.ShowDialog() == DialogResult.OK)
|
||||||
|
{
|
||||||
|
TemplateManager.Instance.Import(
|
||||||
|
filePath,
|
||||||
|
importDlg.SelectedCmigName,
|
||||||
|
importDlg.SelectedPropertySetName
|
||||||
|
);
|
||||||
|
RefreshCmigCombo();
|
||||||
|
UpdateTemplateInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BuildFieldsForPropertySet(string cmigName, string psName)
|
||||||
|
{
|
||||||
|
_panelFields.Controls.Clear();
|
||||||
|
_fieldControls.Clear();
|
||||||
|
|
||||||
|
var ps = TemplateManager.Instance.GetPropertySet(cmigName, psName);
|
||||||
|
if (ps == null) return;
|
||||||
|
|
||||||
|
int y = 10;
|
||||||
|
|
||||||
|
foreach (var prop in ps.Properties)
|
||||||
|
{
|
||||||
|
if (prop.IsSeparator)
|
||||||
|
{
|
||||||
|
var sep = new Label
|
||||||
|
{
|
||||||
|
Text = prop.Name,
|
||||||
|
Location = new Point(10, y),
|
||||||
|
Size = new Size(480, 20),
|
||||||
|
Font = new System.Drawing.Font(Font.FontFamily, 9, FontStyle.Bold),
|
||||||
|
ForeColor = Color.DarkBlue,
|
||||||
|
BackColor = Color.AliceBlue
|
||||||
|
};
|
||||||
|
_panelFields.Controls.Add(sep);
|
||||||
|
y += 25;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 只使用模板的默认值,不读取 block 现有属性
|
||||||
|
AddFieldControl(prop, y, prop.DefaultValue ?? "");
|
||||||
|
y += 28;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddFieldControl(PropertyDefinition prop, int y, string value)
|
||||||
|
{
|
||||||
|
var lbl = new Label
|
||||||
|
{
|
||||||
|
Text = prop.Name,
|
||||||
|
Location = new Point(10, y),
|
||||||
|
Size = new Size(200, 20)
|
||||||
|
};
|
||||||
|
_panelFields.Controls.Add(lbl);
|
||||||
|
|
||||||
|
Control ctrl;
|
||||||
|
if (prop.Type == "list" && prop.Options.Count > 0)
|
||||||
|
{
|
||||||
|
var cmb = new ComboBox
|
||||||
|
{
|
||||||
|
Location = new Point(220, y),
|
||||||
|
Size = new Size(260, 20),
|
||||||
|
DropDownStyle = ComboBoxStyle.DropDownList
|
||||||
|
};
|
||||||
|
cmb.Items.AddRange(prop.Options.ToArray());
|
||||||
|
cmb.SelectedItem = prop.Options.Contains(value) ? value : prop.DefaultValue;
|
||||||
|
ctrl = cmb;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var txt = new TextBox
|
||||||
|
{
|
||||||
|
Location = new Point(220, y),
|
||||||
|
Size = new Size(260, 20),
|
||||||
|
Text = value
|
||||||
|
};
|
||||||
|
ctrl = txt;
|
||||||
|
}
|
||||||
|
|
||||||
|
_panelFields.Controls.Add(ctrl);
|
||||||
|
_fieldControls[prop.Name] = ctrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnSave_Click(object sender, EventArgs e)
|
||||||
|
{
|
||||||
|
var existingKeys = GetExistingPropertyKeys();
|
||||||
|
|
||||||
|
if (existingKeys.Count > 0)
|
||||||
|
{
|
||||||
|
// block 已有属性,询问 Replace 或 Append
|
||||||
|
var result = MessageBox.Show(
|
||||||
|
"Block 已有属性。\n\n选择「是」替代现有属性(清空后应用新模板)\n选择「否」附加到现有属性(合并)",
|
||||||
|
"应用模式",
|
||||||
|
MessageBoxButtons.YesNoCancel,
|
||||||
|
MessageBoxIcon.Question);
|
||||||
|
|
||||||
|
if (result == DialogResult.Cancel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (result == DialogResult.Yes)
|
||||||
|
{
|
||||||
|
// 替代模式:清空所有现有属性
|
||||||
|
ClearAllProperties();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 附加模式:检查重复 key
|
||||||
|
var cmigName = _cmbCmig.SelectedItem?.ToString();
|
||||||
|
var psName = _cmbPropertySet.SelectedItem?.ToString();
|
||||||
|
if (!string.IsNullOrEmpty(cmigName) && !string.IsNullOrEmpty(psName))
|
||||||
|
{
|
||||||
|
var ps = TemplateManager.Instance.GetPropertySet(cmigName, psName);
|
||||||
|
if (ps != null)
|
||||||
|
{
|
||||||
|
var duplicateKeys = ps.Properties
|
||||||
|
.Where(p => existingKeys.Contains(p.Name) && !p.IsSeparator)
|
||||||
|
.Select(p => p.Name)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
if (duplicateKeys.Count > 0)
|
||||||
|
{
|
||||||
|
var suffix = duplicateKeys.Count > 5 ? "..." : "";
|
||||||
|
var res = MessageBox.Show(
|
||||||
|
$"发现 {duplicateKeys.Count} 个重复的属性 key:\n{string.Join(", ", duplicateKeys.Take(5))}{suffix}\n\n选择「是」替代这些 key 的值\n选择「否」跳过这些 key",
|
||||||
|
"属性冲突",
|
||||||
|
MessageBoxButtons.YesNoCancel,
|
||||||
|
MessageBoxIcon.Warning);
|
||||||
|
|
||||||
|
if (res == DialogResult.Cancel)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (res == DialogResult.Yes)
|
||||||
|
{
|
||||||
|
// 替代重复的 key - 不做特殊处理,直接保存会用模板值覆盖
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 跳过重复的 key - 需要恢复原值后再保存
|
||||||
|
foreach (var key in duplicateKeys)
|
||||||
|
{
|
||||||
|
var existingValue = GetExistingPropertyValue(key);
|
||||||
|
if (!string.IsNullOrEmpty(existingValue))
|
||||||
|
{
|
||||||
|
SetFieldValue(key, existingValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存字段
|
||||||
|
var attrs = _instanceObject.Attributes;
|
||||||
|
|
||||||
|
foreach (var kvp in _fieldControls)
|
||||||
|
{
|
||||||
|
string value = "";
|
||||||
|
if (kvp.Value is ComboBox cmb)
|
||||||
|
value = cmb.SelectedItem?.ToString() ?? "";
|
||||||
|
else if (kvp.Value is TextBox txt)
|
||||||
|
value = txt.Text;
|
||||||
|
|
||||||
|
attrs.SetUserString(kvp.Key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
Rhino.RhinoApp.WriteLine($"已保存 {_fieldControls.Count} 个属性到 Block");
|
||||||
|
this.Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ClearAllProperties()
|
||||||
|
{
|
||||||
|
var attrs = _instanceObject.Attributes;
|
||||||
|
if (attrs == null) return;
|
||||||
|
|
||||||
|
var userStrings = attrs.GetUserStrings();
|
||||||
|
if (userStrings == null) return;
|
||||||
|
|
||||||
|
// 清除所有现有属性
|
||||||
|
foreach (var key in userStrings.AllKeys)
|
||||||
|
{
|
||||||
|
attrs.SetUserString(key, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
227
UserStringProperty/UI/XmlImportDialog.cs
Normal file
227
UserStringProperty/UI/XmlImportDialog.cs
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Drawing;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Windows.Forms;
|
||||||
|
using UserStringProperty.Core;
|
||||||
|
|
||||||
|
namespace UserStringProperty.UI
|
||||||
|
{
|
||||||
|
public enum ImportMode
|
||||||
|
{
|
||||||
|
Append, // 添加新的(如果同名已存在则提示替换)
|
||||||
|
Replace // 完全替代现有同名属性集
|
||||||
|
}
|
||||||
|
|
||||||
|
public class XmlImportDialog : Form
|
||||||
|
{
|
||||||
|
private XmlPropertyConfig _incomingConfig;
|
||||||
|
private ComboBox _cmbCmig;
|
||||||
|
private ComboBox _cmbPropertySet;
|
||||||
|
private Label _lblIncomingInfo;
|
||||||
|
private Label _lblConflictWarning;
|
||||||
|
private Button _btnOK;
|
||||||
|
private Button _btnCancel;
|
||||||
|
private string _pluginFolder;
|
||||||
|
|
||||||
|
public ImportMode SelectedMode { get; private set; }
|
||||||
|
public string SelectedCmigName { get; private set; }
|
||||||
|
public string SelectedPropertySetName { get; private set; }
|
||||||
|
public bool ShouldReplace { get; private set; }
|
||||||
|
|
||||||
|
public XmlImportDialog(XmlPropertyConfig incoming, string pluginFolder)
|
||||||
|
{
|
||||||
|
_incomingConfig = incoming;
|
||||||
|
_pluginFolder = pluginFolder;
|
||||||
|
InitializeComponent();
|
||||||
|
UpdateConflictWarning();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeComponent()
|
||||||
|
{
|
||||||
|
this.Text = "导入属性集";
|
||||||
|
this.Size = new Size(550, 350);
|
||||||
|
this.StartPosition = FormStartPosition.CenterParent;
|
||||||
|
this.FormBorderStyle = FormBorderStyle.FixedDialog;
|
||||||
|
this.MaximizeBox = false;
|
||||||
|
this.MinimizeBox = false;
|
||||||
|
|
||||||
|
int y = 15;
|
||||||
|
|
||||||
|
// 第一段:选择属性集
|
||||||
|
var lblSection1 = new Label
|
||||||
|
{
|
||||||
|
Text = "第一段:选择属性集",
|
||||||
|
Location = new Point(15, y),
|
||||||
|
Size = new Size(200, 20),
|
||||||
|
Font = new Font(Font.FontFamily, 10, FontStyle.Bold)
|
||||||
|
};
|
||||||
|
this.Controls.Add(lblSection1);
|
||||||
|
|
||||||
|
y += 30;
|
||||||
|
|
||||||
|
// 选择 Cmig
|
||||||
|
var lblCmig = new Label
|
||||||
|
{
|
||||||
|
Text = "属性容器 (cmig):",
|
||||||
|
Location = new Point(15, y),
|
||||||
|
Size = new Size(120, 20)
|
||||||
|
};
|
||||||
|
this.Controls.Add(lblCmig);
|
||||||
|
|
||||||
|
_cmbCmig = new ComboBox
|
||||||
|
{
|
||||||
|
Location = new Point(140, y),
|
||||||
|
Size = new Size(380, 20),
|
||||||
|
DropDownStyle = ComboBoxStyle.DropDownList
|
||||||
|
};
|
||||||
|
_cmbCmig.Items.Add(_incomingConfig.CmigName);
|
||||||
|
_cmbCmig.SelectedIndex = 0;
|
||||||
|
this.Controls.Add(_cmbCmig);
|
||||||
|
|
||||||
|
y += 30;
|
||||||
|
|
||||||
|
// 选择 PropertySet
|
||||||
|
var lblPropertySet = new Label
|
||||||
|
{
|
||||||
|
Text = "属性集 (propertyset):",
|
||||||
|
Location = new Point(15, y),
|
||||||
|
Size = new Size(120, 20)
|
||||||
|
};
|
||||||
|
this.Controls.Add(lblPropertySet);
|
||||||
|
|
||||||
|
_cmbPropertySet = new ComboBox
|
||||||
|
{
|
||||||
|
Location = new Point(140, y),
|
||||||
|
Size = new Size(380, 20),
|
||||||
|
DropDownStyle = ComboBoxStyle.DropDownList
|
||||||
|
};
|
||||||
|
foreach (var ps in _incomingConfig.PropertySets)
|
||||||
|
{
|
||||||
|
_cmbPropertySet.Items.Add(ps.Name);
|
||||||
|
}
|
||||||
|
if (_cmbPropertySet.Items.Count > 0)
|
||||||
|
_cmbPropertySet.SelectedIndex = 0;
|
||||||
|
_cmbPropertySet.SelectedIndexChanged += (s, e) => UpdateConflictWarning();
|
||||||
|
this.Controls.Add(_cmbPropertySet);
|
||||||
|
|
||||||
|
y += 35;
|
||||||
|
|
||||||
|
// 分隔线
|
||||||
|
var separator = new Label
|
||||||
|
{
|
||||||
|
Text = "────────────────────────────────────────────────────",
|
||||||
|
Location = new Point(15, y),
|
||||||
|
Size = new Size(500, 15),
|
||||||
|
ForeColor = Color.Gray
|
||||||
|
};
|
||||||
|
this.Controls.Add(separator);
|
||||||
|
|
||||||
|
y += 25;
|
||||||
|
|
||||||
|
// 第二段:导入模式
|
||||||
|
var lblSection2 = new Label
|
||||||
|
{
|
||||||
|
Text = "第二段:检测冲突",
|
||||||
|
Location = new Point(15, y),
|
||||||
|
Size = new Size(200, 20),
|
||||||
|
Font = new Font(Font.FontFamily, 10, FontStyle.Bold)
|
||||||
|
};
|
||||||
|
this.Controls.Add(lblSection2);
|
||||||
|
|
||||||
|
y += 30;
|
||||||
|
|
||||||
|
// 即将导入的信息
|
||||||
|
_lblIncomingInfo = new Label
|
||||||
|
{
|
||||||
|
Text = "",
|
||||||
|
Location = new Point(15, y),
|
||||||
|
Size = new Size(500, 20),
|
||||||
|
ForeColor = Color.DarkGreen,
|
||||||
|
Font = new Font(Font.FontFamily, 9, FontStyle.Bold)
|
||||||
|
};
|
||||||
|
this.Controls.Add(_lblIncomingInfo);
|
||||||
|
|
||||||
|
y += 25;
|
||||||
|
|
||||||
|
// 冲突警告
|
||||||
|
_lblConflictWarning = new Label
|
||||||
|
{
|
||||||
|
Text = "",
|
||||||
|
Location = new Point(15, y),
|
||||||
|
Size = new Size(500, 30),
|
||||||
|
ForeColor = Color.DarkOrange
|
||||||
|
};
|
||||||
|
this.Controls.Add(_lblConflictWarning);
|
||||||
|
|
||||||
|
y += 40;
|
||||||
|
|
||||||
|
// 按钮
|
||||||
|
_btnOK = new Button
|
||||||
|
{
|
||||||
|
Text = "确定导入",
|
||||||
|
Location = new Point(350, y),
|
||||||
|
Size = new Size(90, 30)
|
||||||
|
};
|
||||||
|
_btnOK.Click += (s, e) =>
|
||||||
|
{
|
||||||
|
SelectedCmigName = _cmbCmig.SelectedItem?.ToString();
|
||||||
|
SelectedPropertySetName = _cmbPropertySet.SelectedItem?.ToString();
|
||||||
|
this.DialogResult = DialogResult.OK;
|
||||||
|
this.Close();
|
||||||
|
};
|
||||||
|
this.Controls.Add(_btnOK);
|
||||||
|
|
||||||
|
_btnCancel = new Button
|
||||||
|
{
|
||||||
|
Text = "取消",
|
||||||
|
Location = new Point(245, y),
|
||||||
|
Size = new Size(90, 30)
|
||||||
|
};
|
||||||
|
_btnCancel.Click += (s, e) =>
|
||||||
|
{
|
||||||
|
this.DialogResult = DialogResult.Cancel;
|
||||||
|
this.Close();
|
||||||
|
};
|
||||||
|
this.Controls.Add(_btnCancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateConflictWarning()
|
||||||
|
{
|
||||||
|
var cmigName = _cmbCmig.SelectedItem?.ToString() ?? "";
|
||||||
|
var psName = _cmbPropertySet.SelectedItem?.ToString() ?? "";
|
||||||
|
|
||||||
|
if (string.IsNullOrEmpty(cmigName) || string.IsNullOrEmpty(psName))
|
||||||
|
{
|
||||||
|
_lblIncomingInfo.Text = "";
|
||||||
|
_lblConflictWarning.Text = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var fileName = $"{cmigName}-{psName}.xml";
|
||||||
|
var existingPath = Path.Combine(_pluginFolder, fileName);
|
||||||
|
var exists = File.Exists(existingPath);
|
||||||
|
|
||||||
|
_lblIncomingInfo.Text = $"待导入: {fileName}";
|
||||||
|
|
||||||
|
if (exists)
|
||||||
|
{
|
||||||
|
_lblConflictWarning.Text = $"⚠ 文件已存在: {fileName}\n是否替换现有文件?";
|
||||||
|
_lblConflictWarning.ForeColor = Color.Red;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_lblConflictWarning.Text = $"✓ 文件不存在,将作为新文件保存";
|
||||||
|
_lblConflictWarning.ForeColor = Color.DarkGreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool CheckReplace()
|
||||||
|
{
|
||||||
|
var fileName = $"{SelectedCmigName}-{SelectedPropertySetName}.xml";
|
||||||
|
var existingPath = Path.Combine(_pluginFolder, fileName);
|
||||||
|
return File.Exists(existingPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
32
UserStringProperty/UserStringProperty.cs
Normal file
32
UserStringProperty/UserStringProperty.cs
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
using System;
|
||||||
|
using Rhino;
|
||||||
|
using Rhino.PlugIns;
|
||||||
|
|
||||||
|
namespace UserStringProperty
|
||||||
|
{
|
||||||
|
public class UserStringPropertyPlugin : PlugIn
|
||||||
|
{
|
||||||
|
public UserStringPropertyPlugin()
|
||||||
|
{
|
||||||
|
Instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static UserStringPropertyPlugin Instance { get; private set; }
|
||||||
|
|
||||||
|
protected override LoadReturnCode OnLoad(ref string errorMessage)
|
||||||
|
{
|
||||||
|
// 首次启动时,从内嵌资源提取默认 XML 到插件目录
|
||||||
|
Resources.EmbeddedTemplates.ExtractToFolder(
|
||||||
|
TemplateManager.Instance.GetPluginFolderPath()
|
||||||
|
);
|
||||||
|
|
||||||
|
// 启动时自动加载已保存的模板
|
||||||
|
TemplateManager.Instance.LoadDefault();
|
||||||
|
return LoadReturnCode.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnShutdown()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
52
UserStringProperty/UserStringProperty.csproj
Normal file
52
UserStringProperty/UserStringProperty.csproj
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net48</TargetFramework>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<UseWindowsForms>true</UseWindowsForms>
|
||||||
|
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
|
||||||
|
<OutputPath>bin\</OutputPath>
|
||||||
|
<AssemblyName>UserStringProperty</AssemblyName>
|
||||||
|
<RootNamespace>UserStringProperty</RootNamespace>
|
||||||
|
<EnableDefaultCompileItems>false</EnableDefaultCompileItems>
|
||||||
|
<StartupObject />
|
||||||
|
<Nullable>disable</Nullable>
|
||||||
|
<ImplicitUsings>disable</ImplicitUsings>
|
||||||
|
<!-- Rhino 插件配置 -->
|
||||||
|
<AssemblyName>UserStringProperty</AssemblyName>
|
||||||
|
<Version>1.0</Version>
|
||||||
|
<Company>YourCompany</Company>
|
||||||
|
<Description>User String Property Plugin</Description>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="RhinoCommon">
|
||||||
|
<HintPath>C:\Program Files\Rhino 8\System\RhinoCommon.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Rhino.UI">
|
||||||
|
<HintPath>C:\Program Files\Rhino 8\System\Rhino.UI.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Drawing" />
|
||||||
|
<Reference Include="System.Windows.Forms" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="UserStringProperty.cs" />
|
||||||
|
<Compile Include="Commands\CmdUserstringProperty.cs" />
|
||||||
|
<Compile Include="UI\MainDialog.cs" />
|
||||||
|
<Compile Include="UI\XmlImportDialog.cs" />
|
||||||
|
<Compile Include="Core\XmlPropertyConfig.cs" />
|
||||||
|
<Compile Include="Core\TemplateManager.cs" />
|
||||||
|
<Compile Include="Resources\EmbeddedTemplates.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
Reference in New Issue
Block a user