Add plugin GUID, fix template import and dialog improvements

- Add [assembly: Guid(...)] for proper Rhino plugin registration
- Fix Import() to add new config to _configs after importing XML
- Simplify XmlImportDialog: remove section labels, add conflict confirmation
- Move plugin data folder to standard Plug-ins path with GUID suffix
- Add ContractXml as third embedded template
- Update README with correct import flow and build requirements
- Fix csproj duplicate PropertyGroup issue
This commit is contained in:
2026-05-02 11:34:51 +08:00
parent c54bdebe59
commit 37888b5f7e
7 changed files with 184 additions and 86 deletions

View File

@@ -1,6 +1,6 @@
# UserStringProperty
Rhino 插件,用于根据用户自定义 XML 配置,给 Block 实例附加构件属性UserString
Rhino 插件,根据用户自定义 XML 配置,给 Block 实例附加属性UserString
## 功能
@@ -9,7 +9,7 @@ Rhino 插件,用于根据用户自定义 XML 配置,给 Block 实例附加
- 自动保存到 Rhino Block 的 UserString
- 支持多个 XML 模板同时加载、合并
- 模板自动持久化Rhino 重启后自动恢复
- 导入时提供四种合并模式(追加、替代、合并更新、智能合并)
- 导入时检测属性集冲突,覆盖或新增
## 安装
@@ -32,28 +32,25 @@ dotnet build
1. 在 Rhino 中输入命令:`UserstringProperty`
2. 选择要编辑的 Block 实例
3. 默认模板首次启动自动加载;点 **加载XML** 导入自定义模板
4.下拉框中选择属性集,编辑各字段值
5. 点击 **保存**
4.弹出的对话框中选择 cmig 和属性集,若同名已存在则需确认覆盖
5. 在下拉框中选择属性集,编辑各字段值
6. 点击 **写入属性**
## 模板导入模式
## 模板导入
加载 XML 时,如果已有模板,会弹出对话框让选择导入模式
加载 XML 时,会弹出对话框让选择 cmig 和属性集,检测同名冲突
| 模式 | 说明 |
|------|------|
| 添加为新属性集 | 追加到现有列表末尾 |
| 完全替代现有模板 | 清空所有现有属性集 |
| 合并更新 | 同名属性集覆盖,其他保留 |
| 智能合并 | 同名覆盖,同时追加新属性集 |
- **同名属性集不存在** → 直接保存为新模板
- **同名属性集已存在** → 需确认是否覆盖
## XML 配置格式
插件根据 XML 动态生成 UI。示例参见 `Component_Property_base.xml``Component_Property_carbon.xml`
插件根据 XML 动态生成 UI。示例 XML 格式如下:
```xml
<?xml version="1.0" encoding="utf-8"?>
<cmig name="构件编码">
<propertyset name="构件属性-基" exclusive="false">
<cmig name="构件属性">
<propertyset name="构件属性-基" exclusive="false">
<!-- string 类型 → TextBox -->
<property name="0-00-02-年代" type="string" default="PRC"/>
@@ -95,7 +92,7 @@ dotnet build
```
UserStringProperty/
├── CLAUDE.md # agent 指示(不要当作文档)
├── CLAUDE.md # agent 指示
├── PLAN.md # 待办事项与开发计划
├── UserStringProperty.csproj
├── UserStringProperty.cs # 插件主类
@@ -111,11 +108,15 @@ UserStringProperty/
└── EmbeddedTemplates.cs # 内嵌默认 XML 模板
```
## 环境要求
## 编译环境
- **.NET SDK 8.0**
- **Rhino 8**(包含 RhinoCommon.dll SDK 引用)
- Windows 10/11
## 运行时要求
- Rhino 8
- .NET Framework 4.8
- .NET SDK 8.0(编译用)
## License

View File

@@ -38,7 +38,8 @@ namespace UserStringProperty
{
_pluginFolder = Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData),
"McNeel", "Rhinoceros", "8.0", "plugins", "UserStringProperty"
"McNeel", "Rhinoceros", "8.0", "Plug-ins",
"UserStringProperty (C286E052-06F6-475F-A882-1C1AA31744CF)"
);
if (!Directory.Exists(_pluginFolder))
Directory.CreateDirectory(_pluginFolder);
@@ -61,6 +62,8 @@ namespace UserStringProperty
// 执行导入
public void Import(string filePath, string cmigName, string propertysetName)
{
RebuildIndexIfMissing();
var config = XmlPropertyConfig.Load(filePath);
// 检查文件是否已存在
@@ -82,6 +85,13 @@ namespace UserStringProperty
{
File.WriteAllText(destPath, xmlContent);
_loadedPaths.Add(destPath);
// 将新导入的 config 加入内存列表
var newConfig = XmlPropertyConfig.Load(destPath);
if (newConfig.PropertySets.Count > 0)
{
_configs.Add(newConfig);
}
}
SaveIndex();
@@ -192,6 +202,30 @@ namespace UserStringProperty
File.WriteAllLines(GetIndexFilePath(), _loadedPaths);
}
private void RebuildIndexIfMissing()
{
var indexPath = GetIndexFilePath();
if (!File.Exists(indexPath))
{
var files = GetLocalTemplateFiles();
_loadedPaths.Clear();
foreach (var file in files)
{
try
{
var config = XmlPropertyConfig.Load(file);
if (config.PropertySets.Count > 0)
{
_configs.Add(config);
_loadedPaths.Add(file);
}
}
catch { }
}
SaveIndex();
}
}
private void SaveTemplatesToPluginFolder()
{
// 按 [cmig name]-[propertyset name].xml 格式保存

View File

@@ -7,7 +7,7 @@ namespace UserStringProperty.Resources
public const string BaseXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<cmig name=""构件属性"">
<propertyset name=""构件属性-基"" exclusive=""false"" picture=""基础-任意实体.png"">
<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>
@@ -118,13 +118,107 @@ namespace UserStringProperty.Resources
<property name=""4-02-06-构件装配施工单位工人数量"" type=""string"" default=""1"" />
</propertyset>
</cmig>";
public const string ContractXml = @"<?xml version=""1.0"" encoding=""utf-8""?>
<!--
构件合同属性 XML
插件化设计 - 可与基础属性组合使用
前缀: 1-03-xx, 2-03-xx, 3-03-xx, 4-03-xx
-->
<cmig name=""构件合同属性"">
<propertyset name=""构件合同属性"" exclusive=""false"" picture=""合同.png"">
<!-- 材料制备阶段合同属性 -->
<property name=""1-03-00-材料制备合同属性"" type=""string"" default=""--------------------"" />
<property name=""1-03-01-构件材料制备合同编码"" type=""string"" default="""" />
<property name=""1-03-02-构件材料制备合作单位"" type=""string"" default=""筑晓龙商贸"" />
<property name=""1-03-03-构件材料制备合同合作形式"" type=""list"" default=""采购"" >
<option>赞助</option><option>采购</option><option>租赁</option><option>技术研发分包</option><option>劳务</option>
</property>
<property name=""1-03-04-构件材料制备质量"" type=""list"" default=""合格"">
<option>合格</option><option>不合格</option>
</property>
<property name=""1-03-05-构件材料制备造价因子"" type=""string"" default=""数据库自动生成后导入"" />
<property name=""1-03-06-构件材料制备造价"" type=""string"" default=""0.00"" />
<property name=""1-03-07-构件材料制备造价单位"" type=""string"" default=""元"" />
<property name=""1-03-08-构件材料制备存在高空作业"" type=""string"" default=""否"" />
<property name=""1-03-09-构件材料制备高空作业率"" type=""string"" default=""0"" />
<property name=""1-03-10-构件材料制备状态"" type=""list"" default=""未制备"">
<option>未制备</option><option>制备中</option><option>制备完成</option><option>验收完成</option>
</property>
<property name=""1-03-11-构件材料制备进度%"" type=""string"" default=""0"" />
<!-- 生产制造阶段合同属性 -->
<property name=""2-03-00-生产制造合同属性"" type=""string"" default=""--------------------"" />
<property name=""2-03-01-构件生产制造合同编码"" type=""string"" default=""0"" />
<property name=""2-03-02-构件生产制造合作单位"" type=""string"" default=""筑新技术集团"" />
<property name=""2-03-03-构件生产制造合同合作形式"" type=""list"" default=""技术研发分包"" >
<option>赞助</option><option>采购</option><option>租赁</option><option>技术研发分包</option><option>劳务</option>
</property>
<property name=""2-03-04-构件生产制造质量"" type=""list"" default=""合格"">
<option>合格</option><option>不合格</option>
</property>
<property name=""2-03-05-构件生产制造造价因子"" type=""string"" default=""数据库自动生成后导入"" />
<property name=""2-03-06-构件生产制造造价"" type=""string"" default=""0.00"" />
<property name=""2-03-07-构件生产制造造价单位"" type=""string"" default=""元"" />
<property name=""2-03-08-构件生产制造存在高空作业"" type=""string"" default=""是"" />
<property name=""2-03-09-构件生产制造高空作业率"" type=""string"" default=""0"" />
<property name=""2-03-10-构件生产制造状态"" type=""list"" default=""未生产"">
<option>未生产</option><option>生产中</option><option>生产完成</option><option>验收完成</option>
</property>
<property name=""2-03-11-构件生产制造进度%"" type=""string"" default=""0"" />
<!-- 物流运输阶段合同属性 -->
<property name=""3-03-00-物流运输合同属性"" type=""string"" default=""--------------------""/>
<property name=""3-03-01-构件物流运输合同编码"" type=""string"" default=""0""/>
<property name=""3-03-02-构件物流运输合作单位"" type=""string"" default=""南京瑞新供应链""/>
<property name=""3-03-03-构件物流运输合同合作形式"" type=""list"" default=""采购"" >
<option>赞助</option><option>采购</option><option>租赁</option><option>技术研发分包</option><option>劳务</option>
</property>
<property name=""3-03-04-构件物流运输质量"" type=""list"" default=""合格"">
<option>合格</option><option>不合格</option>
</property>
<property name=""3-03-05-构件物流运输造价因子"" type=""string"" default=""数据库自动生成后导入""/>
<property name=""3-03-06-构件物流运输造价"" type=""string"" default=""0.00""/>
<property name=""3-03-07-构件物流运输造价单位"" type=""string"" default=""元""/>
<property name=""3-03-08-构件物流运输存在高空作业"" type=""string"" default=""否"" />
<property name=""3-03-09-构件物流运输高空作业率"" type=""string"" default=""0"" />
<property name=""3-03-10-构件物流运输状态"" type=""list"" default=""未运输"">
<option>未运输</option><option>装车中</option><option>运输中</option><option>运输完成</option><option>已堆场</option>
</property>
<property name=""3-03-11-构件物流运输进度%"" type=""string"" default=""0"" />
<!-- 装配施工阶段合同属性 -->
<property name=""4-03-00-装配施工合同属性"" type=""string"" default=""--------------------""/>
<property name=""4-03-01-构件装配施工合同编码"" type=""string"" default=""0""/>
<property name=""4-03-02-构件装配施工合作单位"" type=""string"" default=""内蒙古大元建筑""/>
<property name=""4-03-03-构件装配施工合同合作形式"" type=""list"" default=""技术研发分包"" >
<option>赞助</option><option>采购</option><option>租赁</option><option>技术研发分包</option><option>劳务</option>
</property>
<property name=""4-03-04-构件装配施工质量"" type=""list"" default=""合格"">
<option>合格</option><option>不合格</option>
</property>
<property name=""4-03-05-构件装配施工造价因子"" type=""string"" default=""数据库自动生成后导入""/>
<property name=""4-03-06-构件装配施工造价"" type=""string"" default=""0.00""/>
<property name=""4-03-07-构件装配施工造价单位"" type=""string"" default=""元""/>
<property name=""4-03-08-构件装配施工存在高空作业"" type=""string"" default=""是"" />
<property name=""4-03-09-构件装配施工高空作业率"" type=""string"" default=""0"" />
<property name=""4-03-10-构件装配施工状态"" type=""list"" default=""未装配施工"">
<option>未装配施工</option><option>装配施工中</option><option>装配施工完成</option><option>验收完成</option>
</property>
<property name=""4-03-11-构件装配施工进度%"" type=""string"" default=""0"" />
</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"), BaseXml);
File.WriteAllText(Path.Combine(folder, "构件碳排放属性-构件碳排放属性.xml"), CarbonXml);
File.WriteAllText(Path.Combine(folder, "构件合同属性-构件合同属性.xml"), ContractXml);
}
}
}

View File

@@ -40,7 +40,7 @@ namespace UserStringProperty.UI
private void InitializeComponent()
{
this.Text = "构件属性编辑";
this.Text = "属性编辑";
this.Size = new Size(550, 580);
this.StartPosition = FormStartPosition.CenterParent;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
@@ -68,7 +68,7 @@ namespace UserStringProperty.UI
// PropertySet 选择
var lblPropertySet = new Label
{
Text = "属性模板:",
Text = "属性:",
Location = new Point(285, 10),
Size = new Size(80, 20)
};
@@ -91,19 +91,6 @@ namespace UserStringProperty.UI
};
_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 = "",
@@ -135,7 +122,7 @@ namespace UserStringProperty.UI
_btnCancel.Click += (s, e) => this.Close();
this.Controls.AddRange(new Control[] {
_btnLoadXml, btnResetTemplate, _lblTemplateInfo,
_btnLoadXml, _lblTemplateInfo,
lblCmig, _cmbCmig, lblPropertySet, _cmbPropertySet,
lblFields, _panelFields, _btnSave, _btnCancel
});

View File

@@ -41,7 +41,7 @@ namespace UserStringProperty.UI
private void InitializeComponent()
{
this.Text = "导入属性集";
this.Size = new Size(550, 350);
this.Size = new Size(550, 250);
this.StartPosition = FormStartPosition.CenterParent;
this.FormBorderStyle = FormBorderStyle.FixedDialog;
this.MaximizeBox = false;
@@ -49,22 +49,10 @@ namespace UserStringProperty.UI
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):",
Text = "模板类型 (cmig):",
Location = new Point(15, y),
Size = new Size(120, 20)
};
@@ -108,30 +96,6 @@ namespace UserStringProperty.UI
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
{
@@ -150,8 +114,7 @@ namespace UserStringProperty.UI
{
Text = "",
Location = new Point(15, y),
Size = new Size(500, 30),
ForeColor = Color.DarkOrange
Size = new Size(500, 20)
};
this.Controls.Add(_lblConflictWarning);
@@ -166,8 +129,24 @@ namespace UserStringProperty.UI
};
_btnOK.Click += (s, e) =>
{
SelectedCmigName = _cmbCmig.SelectedItem?.ToString();
SelectedPropertySetName = _cmbPropertySet.SelectedItem?.ToString();
var cmigName = _cmbCmig.SelectedItem?.ToString() ?? "";
var psName = _cmbPropertySet.SelectedItem?.ToString() ?? "";
var fileName = $"{cmigName}-{psName}.xml";
var existingPath = Path.Combine(_pluginFolder, fileName);
if (File.Exists(existingPath))
{
var result = MessageBox.Show(
$"文件 {fileName} 已存在,是否覆盖?",
"确认覆盖",
MessageBoxButtons.YesNo,
MessageBoxIcon.Warning);
if (result != DialogResult.Yes)
return;
}
SelectedCmigName = cmigName;
SelectedPropertySetName = psName;
this.DialogResult = DialogResult.OK;
this.Close();
};
@@ -203,16 +182,16 @@ namespace UserStringProperty.UI
var existingPath = Path.Combine(_pluginFolder, fileName);
var exists = File.Exists(existingPath);
_lblIncomingInfo.Text = $"待导入: {fileName}";
_lblIncomingInfo.Text = $"待导入: {cmigName}-{psName}";
if (exists)
{
_lblConflictWarning.Text = $"⚠ 文件已存在: {fileName}\n是否替换现有文件";
_lblConflictWarning.ForeColor = Color.Red;
_lblConflictWarning.Text = $"⚠ 同名属性集已存在,将覆盖现有文件";
_lblConflictWarning.ForeColor = Color.DarkOrange;
}
else
{
_lblConflictWarning.Text = $"✓ 文件不存在,将作为新文件保存";
_lblConflictWarning.Text = $"✓ 同名属性集不存在,将作为新文件保存";
_lblConflictWarning.ForeColor = Color.DarkGreen;
}
}

View File

@@ -1,7 +1,11 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Rhino;
using Rhino.PlugIns;
[assembly: Guid("C286E052-06F6-475F-A882-1C1AA31744CF")]
namespace UserStringProperty
{
public class UserStringPropertyPlugin : PlugIn

View File

@@ -18,8 +18,7 @@
<Version>1.0</Version>
<Company>YourCompany</Company>
<Description>User String Property Plugin</Description>
</PropertyGroup>
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
</PropertyGroup>
<ItemGroup>