附注参数已经可以填写,下一步要开始生图了,过去这一步就没有难度了

This commit is contained in:
sladro 2025-12-17 13:59:00 +08:00
parent 0a4a003a11
commit c1d18663bb
13 changed files with 1541 additions and 59 deletions

View File

@ -5,6 +5,7 @@ using System.Text.RegularExpressions;
using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Runtime;
using CadParamPluging.Common;
using CadParamPluging.Domain.Models;
namespace CadParamPluging.Cad
@ -180,6 +181,359 @@ namespace CadParamPluging.Cad
return s;
}
public sealed class NoteApplyResult
{
public bool Applied { get; set; }
public string RenderedText { get; set; }
public int PlaceholderCountInDwg { get; set; }
public string TargetKind { get; set; }
public string Message { get; set; }
}
private sealed class NoteCandidate
{
public int Score;
public string Kind;
public string PlainText;
public Action<string> Apply;
}
public static NoteApplyResult ApplyNoteTemplate(CadContext ctx, string layoutName, bool scanModelSpace, TemplateSchemaDefinition schema, ParamBag bag)
{
if (ctx == null)
{
throw new ArgumentNullException(nameof(ctx));
}
if (schema == null)
{
return new NoteApplyResult { Applied = false, Message = "Schema is null" };
}
if (bag == null)
{
return new NoteApplyResult { Applied = false, Message = "ParamBag is null" };
}
if (schema.NoteBindings == null || schema.NoteBindings.Count == 0)
{
return new NoteApplyResult { Applied = false, Message = "未配置附注绑定,跳过。" };
}
var tr = ctx.Transaction;
var db = ctx.Database;
var space = GetTargetSpace(tr, db, layoutName, scanModelSpace);
if (space == null)
{
return new NoteApplyResult { Applied = false, Message = "未找到目标空间,跳过附注替换。" };
}
var candidates = new List<NoteCandidate>();
var visitedBlocks = new HashSet<ObjectId>();
foreach (ObjectId id in space)
{
var ent = tr.GetObject(id, OpenMode.ForRead, false) as Entity;
if (ent != null)
{
CollectNoteCandidates(tr, ent, visitedBlocks, candidates);
}
}
if (candidates.Count == 0)
{
return new NoteApplyResult { Applied = false, Message = "未找到附注文本目标(包含‘附注’且包含占位符*),跳过。" };
}
var best = candidates.OrderByDescending(c => c.Score).FirstOrDefault();
if (best == null || best.Apply == null)
{
return new NoteApplyResult { Applied = false, Message = "未找到可替换的附注文本目标,跳过。" };
}
var templateText = best.PlainText ?? string.Empty;
var placeholderCount = NoteTemplateEngine.CountPlaceholders(templateText);
if (placeholderCount <= 0)
{
return new NoteApplyResult
{
Applied = false,
TargetKind = best.Kind,
PlaceholderCountInDwg = placeholderCount,
Message = "附注目标中未检测到有效占位符(*,忽略****),跳过。"
};
}
var effective = NoteTemplateEngine.BuildEffectiveValueKeyBindings(schema.NoteBindings);
var rendered = NoteTemplateEngine.Render(templateText, effective, bag.GetString);
best.Apply(rendered);
return new NoteApplyResult
{
Applied = true,
TargetKind = best.Kind,
PlaceholderCountInDwg = placeholderCount,
RenderedText = rendered,
Message = "附注已替换。"
};
}
private static BlockTableRecord GetTargetSpace(Transaction tr, Database db, string layoutName, bool scanModelSpace)
{
if (tr == null || db == null)
{
return null;
}
if (scanModelSpace)
{
var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead);
return (BlockTableRecord)tr.GetObject(bt[BlockTableRecord.ModelSpace], OpenMode.ForRead);
}
if (string.IsNullOrWhiteSpace(layoutName))
{
return null;
}
var layoutDict = (DBDictionary)tr.GetObject(db.LayoutDictionaryId, OpenMode.ForRead);
ObjectId layoutId = ObjectId.Null;
foreach (DBDictionaryEntry entry in layoutDict)
{
if (string.Equals(entry.Key, layoutName, StringComparison.OrdinalIgnoreCase))
{
layoutId = entry.Value;
break;
}
}
if (layoutId.IsNull)
{
return null;
}
var layout = (Layout)tr.GetObject(layoutId, OpenMode.ForRead);
return (BlockTableRecord)tr.GetObject(layout.BlockTableRecordId, OpenMode.ForRead);
}
private static void CollectNoteCandidates(Transaction tr, Entity ent, HashSet<ObjectId> visitedBlocks, List<NoteCandidate> candidates)
{
if (tr == null || ent == null || candidates == null)
{
return;
}
if (ent is DBText t)
{
AddNoteCandidateIfMatch(t.TextString, "DBText", t.ObjectId, tr, candidates);
return;
}
if (ent is MText mt)
{
var plain = SimplifyMText(mt.Contents);
AddNoteCandidateIfMatch(plain, "MText", mt.ObjectId, tr, candidates);
return;
}
if (ent is BlockReference br)
{
foreach (ObjectId attId in br.AttributeCollection)
{
var att = tr.GetObject(attId, OpenMode.ForRead, false) as AttributeReference;
if (att == null)
{
continue;
}
AddNoteCandidateIfMatch(att.TextString, "Attribute", attId, tr, candidates);
}
var blockId = br.BlockTableRecord;
if (blockId.IsNull)
{
return;
}
if (visitedBlocks == null)
{
visitedBlocks = new HashSet<ObjectId>();
}
if (visitedBlocks.Contains(blockId))
{
return;
}
visitedBlocks.Add(blockId);
var btr = tr.GetObject(blockId, OpenMode.ForRead, false) as BlockTableRecord;
if (btr == null)
{
return;
}
foreach (ObjectId childId in btr)
{
var child = tr.GetObject(childId, OpenMode.ForRead, false) as Entity;
if (child != null)
{
CollectNoteCandidates(tr, child, visitedBlocks, candidates);
}
}
}
}
private static void AddNoteCandidateIfMatch(string plainText, string kind, ObjectId id, Transaction tr, List<NoteCandidate> candidates)
{
if (!IsLikelyNoteText(plainText))
{
return;
}
var score = ComputeNoteScore(plainText);
if (score <= 0)
{
return;
}
candidates.Add(new NoteCandidate
{
Score = score,
Kind = kind,
PlainText = plainText,
Apply = rendered => ApplyNoteTextToObject(tr, id, kind, rendered)
});
}
private static bool IsLikelyNoteText(string plain)
{
if (string.IsNullOrWhiteSpace(plain))
{
return false;
}
if (!plain.Contains("*"))
{
return false;
}
if (plain.Contains("附注"))
{
return true;
}
// fallback: numbered note line
return plain.Contains("1*") || plain.Contains("1 *");
}
private static int ComputeNoteScore(string plain)
{
if (string.IsNullOrWhiteSpace(plain))
{
return 0;
}
var score = 0;
var lines = SplitLines(plain).ToList();
if (plain.Contains("附注"))
{
score += 10;
}
if (lines.Count > 0 && string.Equals(lines[0].Trim(), "附注", StringComparison.OrdinalIgnoreCase))
{
score += 10;
}
if (plain.Contains("*"))
{
score += 5;
}
if (plain.Contains("1*"))
{
score += 3;
}
return score;
}
private static IEnumerable<string> SplitLines(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return Enumerable.Empty<string>();
}
return (text ?? string.Empty)
.Replace("\r\n", "\n")
.Replace("\r", "\n")
.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => (s ?? string.Empty).Trim());
}
private static void ApplyNoteTextToObject(Transaction tr, ObjectId id, string kind, string rendered)
{
if (tr == null || id.IsNull)
{
return;
}
rendered = rendered ?? string.Empty;
// We try multiple types regardless of recorded kind to be safe.
var obj = tr.GetObject(id, OpenMode.ForWrite, false);
if (obj is MText mt)
{
mt.Contents = ToMTextContents(rendered);
return;
}
if (obj is DBText t)
{
t.TextString = CollapseToSingleLine(rendered);
return;
}
if (obj is AttributeReference att)
{
att.TextString = CollapseToSingleLine(rendered);
}
}
private static string ToMTextContents(string plainText)
{
if (string.IsNullOrEmpty(plainText))
{
return string.Empty;
}
var s = plainText
.Replace("\r\n", "\n")
.Replace("\r", "\n")
.Replace("\n", "\\P");
return s;
}
private static string CollapseToSingleLine(string text)
{
if (string.IsNullOrWhiteSpace(text))
{
return string.Empty;
}
var parts = (text ?? string.Empty)
.Replace("\r\n", "\n")
.Replace("\r", "\n")
.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries)
.Select(s => (s ?? string.Empty).Trim())
.Where(s => s.Length > 0)
.ToArray();
return string.Join(" ", parts);
}
public static void KeepOnlyLayout(Document doc, string layoutName)
{
if (doc == null)

View File

@ -65,9 +65,12 @@
<Compile Include="Common\ParamCatalogStore.cs" />
<Compile Include="Common\ParamDefinition.cs" />
<Compile Include="Common\ParamValueType.cs" />
<Compile Include="Common\NotePlaceholderBinding.cs" />
<Compile Include="Common\NoteTemplateEngine.cs" />
<Compile Include="Common\TemplateKeyBuilder.cs" />
<Compile Include="Common\TemplateSchemaDefinition.cs" />
<Compile Include="Common\TemplateSchemas.cs" />
<Compile Include="Common\TemplateSchemaDefaults.cs" />
<Compile Include="Common\TemplateSchemaStore.cs" />
<Compile Include="Common\DropdownOptions.cs" />
<Compile Include="Common\DropdownOptionsStore.cs" />

View File

@ -0,0 +1,11 @@
using System;
namespace CadParamPluging.Common
{
[Serializable]
public class NotePlaceholderBinding
{
public int Index { get; set; }
public string ParamKey { get; set; }
}
}

View File

@ -0,0 +1,293 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace CadParamPluging.Common
{
public static class NoteTemplateEngine
{
public sealed class PlaceholderOccurrence
{
public int Index { get; set; }
public int Start { get; set; }
public int LineNumber { get; set; }
public int ColumnInLine { get; set; }
public string LineText { get; set; }
}
public static int CountPlaceholders(string text)
{
if (string.IsNullOrEmpty(text))
{
return 0;
}
var count = 0;
for (var i = 0; i < text.Length;)
{
if (text[i] != '*')
{
i++;
continue;
}
var j = i;
while (j < text.Length && text[j] == '*')
{
j++;
}
var len = j - i;
if (len != 4)
{
count += len;
}
i = j;
}
return count;
}
public static List<PlaceholderOccurrence> ParseOccurrences(string text)
{
var result = new List<PlaceholderOccurrence>();
if (string.IsNullOrEmpty(text))
{
return result;
}
var s = NormalizeNewLines(text);
// Keep empty lines for correct line number mapping.
var lines = s.Split(new[] { '\n' }, StringSplitOptions.None);
// Build line start offsets.
var lineStartOffsets = new int[lines.Length];
var offset = 0;
for (var i = 0; i < lines.Length; i++)
{
lineStartOffsets[i] = offset;
offset += (lines[i] ?? string.Empty).Length;
if (i < lines.Length - 1)
{
offset += 1; // '\n'
}
}
var placeholderIndex = 0;
var lineIdx = 0;
var lineStart = lines.Length > 0 ? lineStartOffsets[0] : 0;
for (var i = 0; i < s.Length;)
{
// Move line index if needed.
while (lineIdx + 1 < lineStartOffsets.Length && i >= lineStartOffsets[lineIdx] + (lines[lineIdx] ?? string.Empty).Length + 1)
{
lineIdx++;
lineStart = lineStartOffsets[lineIdx];
}
if (s[i] != '*')
{
i++;
continue;
}
var j = i;
while (j < s.Length && s[j] == '*')
{
j++;
}
var runLen = j - i;
if (runLen == 4)
{
i = j;
continue;
}
var lineText = (lineIdx >= 0 && lineIdx < lines.Length) ? (lines[lineIdx] ?? string.Empty) : string.Empty;
for (var k = 0; k < runLen; k++)
{
placeholderIndex++;
var pos = i + k;
var col = pos - lineStart;
if (col < 0) col = 0;
result.Add(new PlaceholderOccurrence
{
Index = placeholderIndex,
Start = pos,
LineNumber = lineIdx + 1,
ColumnInLine = col,
LineText = lineText
});
}
i = j;
}
return result;
}
public static string BuildNumberedPreviewText(string templateText)
{
templateText = templateText ?? string.Empty;
var s = NormalizeNewLines(templateText);
var sb = new StringBuilder(s.Length + 64);
var placeholderIndex = 0;
for (var i = 0; i < s.Length;)
{
if (s[i] != '*')
{
sb.Append(s[i]);
i++;
continue;
}
var j = i;
while (j < s.Length && s[j] == '*')
{
j++;
}
var runLen = j - i;
if (runLen == 4)
{
sb.Append("****");
i = j;
continue;
}
for (var k = 0; k < runLen; k++)
{
placeholderIndex++;
sb.Append("【#");
sb.Append(placeholderIndex);
sb.Append("】");
}
i = j;
}
return sb.ToString();
}
public static string Render(string templateText, IEnumerable<NotePlaceholderBinding> bindings, Func<string, string> getValue)
{
templateText = templateText ?? string.Empty;
var s = NormalizeNewLines(templateText);
var map = new Dictionary<int, string>();
foreach (var b in bindings ?? Enumerable.Empty<NotePlaceholderBinding>())
{
if (b == null)
{
continue;
}
if (b.Index <= 0)
{
continue;
}
var key = (b.ParamKey ?? string.Empty).Trim();
if (key.Length == 0)
{
continue;
}
if (!map.ContainsKey(b.Index))
{
map[b.Index] = key;
}
}
var sb = new StringBuilder(s.Length + 64);
var placeholderIndex = 0;
for (var i = 0; i < s.Length;)
{
if (s[i] != '*')
{
sb.Append(s[i]);
i++;
continue;
}
var j = i;
while (j < s.Length && s[j] == '*')
{
j++;
}
var runLen = j - i;
if (runLen == 4)
{
sb.Append("****");
i = j;
continue;
}
for (var k = 0; k < runLen; k++)
{
placeholderIndex++;
if (map.TryGetValue(placeholderIndex, out var key) && getValue != null)
{
var v = getValue(key);
if (!string.IsNullOrWhiteSpace(v))
{
sb.Append(v);
continue;
}
}
sb.Append('*');
}
i = j;
}
return sb.ToString();
}
/// <summary>
/// When the same ParamKey is bound by multiple placeholders, each placeholder should have its own value key,
/// otherwise values would overwrite each other.
/// This method returns an equivalent bindings list where ParamKey is replaced by an effective value key.
/// </summary>
public static List<NotePlaceholderBinding> BuildEffectiveValueKeyBindings(IEnumerable<NotePlaceholderBinding> bindings)
{
var list = (bindings ?? Enumerable.Empty<NotePlaceholderBinding>())
.Where(b => b != null && b.Index > 0 && !string.IsNullOrWhiteSpace(b.ParamKey))
.Select(b => new NotePlaceholderBinding { Index = b.Index, ParamKey = b.ParamKey.Trim() })
.ToList();
var dup = list
.GroupBy(b => b.ParamKey, StringComparer.OrdinalIgnoreCase)
.ToDictionary(g => g.Key, g => g.Count(), StringComparer.OrdinalIgnoreCase);
foreach (var b in list)
{
if (dup.TryGetValue(b.ParamKey, out var cnt) && cnt > 1)
{
b.ParamKey = string.Format("{0}@{1}", b.ParamKey, b.Index);
}
}
return list;
}
private static string NormalizeNewLines(string text)
{
return (text ?? string.Empty)
.Replace("\r\n", "\n")
.Replace("\r", "\n");
}
}
}

View File

@ -69,7 +69,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "数据库下拉列表(支持数据扩充),点选映射",
Order = 100,
Group = "材料"
Group = "备注参数"
},
new ParamDefinition
{
@ -81,7 +81,7 @@ namespace CadParamPluging.Common
EnumOptions = new List<string> { "一般件", "关键件", "重要件" },
Hint = "下拉列表(一般件/关键件/重要件),点选;关键件/重要件需加方框标注",
Order = 110,
Group = "材料"
Group = "备注参数"
},
new ParamDefinition
{
@ -92,7 +92,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "多选下拉列表(支持数据扩充),选项用\"或\"连接",
Order = 120,
Group = "材料"
Group = "备注参数"
},
new ParamDefinition
{
@ -103,7 +103,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "多选下拉列表(支持数据扩充),选项用\"或\"连接",
Order = 130,
Group = "材料"
Group = "备注参数"
},
// --- Inspection / heat treatment ---
@ -117,7 +117,7 @@ namespace CadParamPluging.Common
EnumOptions = new List<string> { "Ⅱ", "Ⅱa", "Ⅱ大", "Ⅲ", "Ⅳ" },
Hint = "单选下拉列表",
Order = 200,
Group = "检验/热处理"
Group = "备注参数"
},
new ParamDefinition
{
@ -129,7 +129,7 @@ namespace CadParamPluging.Common
EnumOptions = new List<string> { "Ⅱ", "Ⅲ", "Ⅳ" },
Hint = "单选下拉列表",
Order = 210,
Group = "检验/热处理"
Group = "备注参数"
},
new ParamDefinition
{
@ -141,7 +141,7 @@ namespace CadParamPluging.Common
EnumOptions = new List<string> { "预备热处理", "热处理" },
Hint = "单选下拉列表",
Order = 220,
Group = "检验/热处理"
Group = "备注参数"
},
new ParamDefinition
{
@ -152,7 +152,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "数据库下拉列表(支持数据扩充),点选",
Order = 230,
Group = "检验/热处理"
Group = "备注参数"
},
new ParamDefinition
{
@ -163,7 +163,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "组合选择:硬度符号+连接符+数值,可空",
Order = 240,
Group = "检验/热处理"
Group = "备注参数"
},
new ParamDefinition
{
@ -174,7 +174,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "手动输入,可空",
Order = 250,
Group = "检验/热处理"
Group = "备注参数"
},
// --- Marking / notes ---
@ -187,7 +187,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "多选下拉列表:零件尾号/零件号、材料牌号、熔炼炉号、锭节号、热处理炉次号、热处理炉批号、批次号",
Order = 300,
Group = "标注/说明"
Group = "备注参数"
},
new ParamDefinition
{
@ -198,7 +198,7 @@ namespace CadParamPluging.Common
DefaultValue = "1",
Hint = "手动编辑,默认为\"1\"",
Order = 310,
Group = "标注/说明"
Group = "备注参数"
},
new ParamDefinition
{
@ -210,7 +210,7 @@ namespace CadParamPluging.Common
EnumOptions = new List<string> { "空", "0.8", "1.6", "3.2" },
Hint = "单选下拉列表,默认为空",
Order = 320,
Group = "标注/说明"
Group = "备注参数"
},
new ParamDefinition
{
@ -222,7 +222,7 @@ namespace CadParamPluging.Common
EnumOptions = new List<string> { "空", "A", "B", "C", "D", "E", "F" },
Hint = "下拉列表A-F可手动编辑支持空选项",
Order = 330,
Group = "标注/说明"
Group = "备注参数"
},
new ParamDefinition
{
@ -233,7 +233,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "下拉列表模板\"X个A零件和X个B零件或X个C零件\",点选后可编辑",
Order = 340,
Group = "标注/说明"
Group = "备注参数"
},
new ParamDefinition
{
@ -244,7 +244,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "下拉列表模板\"每批/每*件多投*件\",点选后可编辑",
Order = 350,
Group = "标注/说明"
Group = "备注参数"
},
new ParamDefinition
{
@ -255,7 +255,7 @@ namespace CadParamPluging.Common
DefaultValue = "",
Hint = "多选下拉列表传递抗拉强度实测值、机加夹头标注、使用温度300℃以上、每批投产数量限制点选后可编辑",
Order = 360,
Group = "标注/说明"
Group = "备注参数"
},
// --- Template key fields (also exist in panel dropdowns) ---
@ -457,15 +457,37 @@ namespace CadParamPluging.Common
},
new ParamDefinition
{
Key = "PartDimensionsPrime",
Label = "零件尺寸(Φ'1/Φ'2/H'1等",
Type = ParamValueType.String,
Key = "OuterDiameter1Prime",
Label = "外径Φ'1",
Type = ParamValueType.Double,
Required = false,
DefaultValue = "",
Hint = "手动编辑",
Order = 630,
Group = "尺寸-环形"
},
new ParamDefinition
{
Key = "InnerDiameter2Prime",
Label = "内径Φ'2",
Type = ParamValueType.Double,
Required = false,
DefaultValue = "",
Hint = "手动编辑",
Order = 640,
Group = "尺寸-环形"
},
new ParamDefinition
{
Key = "Height1Prime",
Label = "高度H'1",
Type = ParamValueType.Double,
Required = false,
DefaultValue = "",
Hint = "手动编辑",
Order = 650,
Group = "尺寸-环形"
},
// --- Dimensions: shaft/rod (diameter/length) ---
new ParamDefinition

View File

@ -1,5 +1,8 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Xml.Serialization;
namespace CadParamPluging.Common
@ -65,11 +68,51 @@ namespace CadParamPluging.Common
}
}
// Migration: move remark-like fields into "备注参数" group if they are still using older default groups.
var remarkKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
{
"MaterialGrade",
"FeatureCategory",
"MaterialTechnicalCondition",
"ForgingTechnicalCondition",
"InspectionCategory",
"ForgingCategory",
"HeatTreatmentState",
"HeatTreatmentProcess",
"Hardness",
"UltrasonicRequirement",
"MarkingContent",
"PartsPerForging",
"SurfaceRoughness",
"DesignRevision",
"SerialInfo",
"MachiningSpecimenRequirement",
"AdditionalNotes"
};
foreach (var key in remarkKeys)
{
var existing = obj.FindByKey(key);
if (existing == null)
{
continue;
}
var g = existing.Group ?? string.Empty;
if (string.IsNullOrWhiteSpace(g)
|| string.Equals(g, "材料", StringComparison.OrdinalIgnoreCase)
|| string.Equals(g, "检验/热处理", StringComparison.OrdinalIgnoreCase)
|| string.Equals(g, "标注/说明", StringComparison.OrdinalIgnoreCase))
{
existing.Group = "备注参数";
}
}
// Migration: remove legacy demo parameters if they exist.
obj.Items = (obj.Items ?? new System.Collections.Generic.List<ParamDefinition>())
.Where(x => x != null
&& !string.Equals(x.Key, "Width", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(x.Key, "Height", StringComparison.OrdinalIgnoreCase))
&& !string.Equals(x.Key, "Height", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(x.Key, "PartDimensionsPrime", StringComparison.OrdinalIgnoreCase))
.ToList();
obj.Normalize();

View File

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
namespace CadParamPluging.Common
{
public static class TemplateSchemaDefaults
{
public static TemplateSchemas CreateDefault()
{
var schemas = new TemplateSchemas();
// 交付状态=车加工, 工艺方法=轧制, 结构特征=环形, 特殊条件=无
// TemplateKey: "车加工|轧制|环形|" ("无" 会被归一化为空)
schemas.Items.Add(new TemplateSchemaDefinition
{
ProjectType = "车加工",
DrawingType = "轧制",
SheetSize = "环形",
Scale = "无",
DisplayName = "环形(车加工/轧制)",
SelectedParamKeys = new List<string>
{
"OuterDiameter1",
"OuterDiameter1TolPlus",
"OuterDiameter1TolMinus",
"InnerDiameter2",
"InnerDiameter2TolPlus",
"InnerDiameter2TolMinus",
"Height1",
"Height1TolPlus",
"Height1TolMinus",
"MinWallThickness",
"OuterDiameter1Prime",
"InnerDiameter2Prime",
"Height1Prime"
}
});
schemas.Normalize();
return schemas;
}
}
}

View File

@ -18,9 +18,14 @@ namespace CadParamPluging.Common
public List<string> SelectedParamKeys { get; set; }
public string NoteTemplateText { get; set; }
public List<NotePlaceholderBinding> NoteBindings { get; set; }
public TemplateSchemaDefinition()
{
SelectedParamKeys = new List<string>();
NoteTemplateText = string.Empty;
NoteBindings = new List<NotePlaceholderBinding>();
}
public void Normalize()
@ -31,6 +36,8 @@ namespace CadParamPluging.Common
Scale = (Scale ?? string.Empty).Trim();
DisplayName = (DisplayName ?? string.Empty).Trim();
NoteTemplateText = NoteTemplateText ?? string.Empty;
TemplateKey = TemplateKeyBuilder.Build(ProjectType, DrawingType, SheetSize, Scale);
if (SelectedParamKeys == null)
@ -43,6 +50,18 @@ namespace CadParamPluging.Common
.Select(s => s.Trim())
.Distinct(StringComparer.OrdinalIgnoreCase)
.ToList();
if (NoteBindings == null)
{
NoteBindings = new List<NotePlaceholderBinding>();
}
NoteBindings = NoteBindings
.Where(b => b != null && b.Index > 0 && !string.IsNullOrWhiteSpace(b.ParamKey))
.GroupBy(b => b.Index)
.Select(g => g.First())
.OrderBy(b => b.Index)
.ToList();
}
}
}

View File

@ -22,10 +22,13 @@ namespace CadParamPluging.Common
{
try
{
var defaults = TemplateSchemaDefaults.CreateDefault();
defaults.Normalize();
var path = GetFilePath();
if (!File.Exists(path))
{
return new TemplateSchemas();
return defaults;
}
using (var stream = File.OpenRead(path))
@ -34,7 +37,25 @@ namespace CadParamPluging.Common
var obj = serializer.Deserialize(stream) as TemplateSchemas;
if (obj == null)
{
return new TemplateSchemas();
return defaults;
}
obj.Normalize();
// Merge: keep local modifications, but add any missing built-in defaults.
foreach (var def in defaults.Items ?? Enumerable.Empty<TemplateSchemaDefinition>())
{
if (def == null || string.IsNullOrWhiteSpace(def.TemplateKey))
{
continue;
}
var exists = (obj.Items ?? Enumerable.Empty<TemplateSchemaDefinition>())
.Any(x => x != null && string.Equals(x.TemplateKey, def.TemplateKey, StringComparison.OrdinalIgnoreCase));
if (!exists)
{
obj.Items.Add(def);
}
}
obj.Normalize();
@ -51,7 +72,7 @@ namespace CadParamPluging.Common
}
catch
{
return new TemplateSchemas();
return TemplateSchemaDefaults.CreateDefault();
}
}

View File

@ -1,4 +1,5 @@
using System;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
@ -12,10 +13,16 @@ namespace CadParamPluging.UI
{
private readonly TableLayoutPanel _grid;
private readonly Dictionary<string, Control> _controlsByKey;
private readonly Dictionary<string, ParamDefinition> _defsByControlKey;
private readonly ParamCatalog _catalog;
private readonly TemplateSchemaDefinition _schema;
private readonly ToolTip _toolTip;
private readonly TabControl _notePreviewTabs;
private readonly RichTextBox _rtbNoteNumbered;
private readonly RichTextBox _rtbNoteRendered;
private Dictionary<int, NoteTemplateEngine.PlaceholderOccurrence> _occByIndex;
public ParamBag Result { get; private set; }
public DrawingParamsForm(ParamCatalog catalog, TemplateSchemaDefinition schema)
@ -25,14 +32,16 @@ namespace CadParamPluging.UI
_schema = schema;
Text = "填写参数";
Size = new Size(560, 620);
Size = new Size(980, 680);
MinimumSize = new Size(900, 620);
StartPosition = FormStartPosition.CenterParent;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
FormBorderStyle = FormBorderStyle.Sizable;
MaximizeBox = true;
MinimizeBox = true;
ShowInTaskbar = false;
_controlsByKey = new Dictionary<string, Control>(StringComparer.OrdinalIgnoreCase);
_defsByControlKey = new Dictionary<string, ParamDefinition>(StringComparer.OrdinalIgnoreCase);
_toolTip = new ToolTip();
var root = new TableLayoutPanel
@ -59,6 +68,17 @@ namespace CadParamPluging.UI
AutoScroll = true
};
_notePreviewTabs = new TabControl { Dock = DockStyle.Fill };
_rtbNoteNumbered = new RichTextBox { Dock = DockStyle.Fill, ReadOnly = true, HideSelection = false };
_rtbNoteRendered = new RichTextBox { Dock = DockStyle.Fill, ReadOnly = true, HideSelection = false };
var tabNumbered = new TabPage { Text = "附注编号预览" };
tabNumbered.Controls.Add(_rtbNoteNumbered);
var tabRendered = new TabPage { Text = "填充值预览" };
tabRendered.Controls.Add(_rtbNoteRendered);
_notePreviewTabs.TabPages.Add(tabNumbered);
_notePreviewTabs.TabPages.Add(tabRendered);
_grid = new TableLayoutPanel
{
Dock = DockStyle.Top,
@ -74,6 +94,16 @@ namespace CadParamPluging.UI
scrollPanel.Controls.Add(_grid);
var split = new SplitContainer
{
Dock = DockStyle.Fill,
Orientation = Orientation.Vertical
};
split.Panel1.Controls.Add(scrollPanel);
split.Panel2.Controls.Add(_notePreviewTabs);
split.SizeChanged += (_, __) => EnsureValidSplitterDistance(split);
var buttons = new FlowLayoutPanel
{
Dock = DockStyle.Fill,
@ -87,12 +117,66 @@ namespace CadParamPluging.UI
buttons.Controls.Add(btnOk);
root.Controls.Add(header, 0, 0);
root.Controls.Add(scrollPanel, 0, 1);
root.Controls.Add(split, 0, 1);
root.Controls.Add(buttons, 0, 2);
Controls.Add(root);
AcceptButton = btnOk;
CancelButton = btnCancel;
Shown += (_, __) =>
{
split.Panel1MinSize = 360;
split.Panel2MinSize = 280;
EnsureValidSplitterDistance(split);
UpdateNotePreviews();
};
}
private static void EnsureValidSplitterDistance(SplitContainer split)
{
if (split == null)
{
return;
}
// SplitterDistance must be within [Panel1MinSize, Width - Panel2MinSize - SplitterWidth]
var width = split.Width;
if (width <= 0)
{
return;
}
// If container is too narrow, collapse preview panel to avoid throwing.
var required = split.Panel1MinSize + split.Panel2MinSize + split.SplitterWidth + 8;
if (width < required)
{
split.Panel2Collapsed = true;
return;
}
split.Panel2Collapsed = false;
var min = split.Panel1MinSize;
var max = width - split.Panel2MinSize - split.SplitterWidth;
if (max < min)
{
// Too narrow: keep it at minimum to avoid exception.
max = min;
}
var desired = (int)(width * 0.6);
if (desired < min) desired = min;
if (desired > max) desired = max;
try
{
split.SplitterDistance = desired;
}
catch
{
// ignore
}
}
private static string BuildHeaderText(TemplateSchemaDefinition schema)
@ -108,8 +192,48 @@ namespace CadParamPluging.UI
private void BuildFields()
{
var keys = (_schema?.SelectedParamKeys ?? new List<string>()).ToList();
if (keys.Count == 0)
_occByIndex = NoteTemplateEngine.ParseOccurrences(_schema?.NoteTemplateText ?? string.Empty)
.Where(o => o != null)
.GroupBy(o => o.Index)
.Select(g => g.First())
.ToDictionary(o => o.Index, o => o);
var mainDefs = new List<ParamDefinition>();
var seenMain = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
foreach (var key in (_schema?.SelectedParamKeys ?? new List<string>()))
{
if (string.IsNullOrWhiteSpace(key))
{
continue;
}
var def = _catalog.FindByKey(key);
if (def == null)
{
continue;
}
if (string.Equals(def.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase))
{
// 备注参数只在附注里填写
continue;
}
if (!seenMain.Add(def.Key))
{
continue;
}
mainDefs.Add(def);
}
var noteBindings = (_schema?.NoteBindings ?? new List<NotePlaceholderBinding>())
.Where(b => b != null && b.Index > 0 && !string.IsNullOrWhiteSpace(b.ParamKey))
.OrderBy(b => b.Index)
.ToList();
if (mainDefs.Count == 0 && noteBindings.Count == 0)
{
var rowIdx = _grid.RowCount++;
_grid.RowStyles.Add(new RowStyle(SizeType.AutoSize));
@ -118,24 +242,99 @@ namespace CadParamPluging.UI
return;
}
foreach (var key in keys)
if (mainDefs.Count > 0)
{
var def = _catalog.FindByKey(key);
AddSectionHeader("模板参数");
foreach (var def in mainDefs)
{
AddField(def, def.Key, def.Label, null);
}
}
if (noteBindings.Count > 0)
{
AddSectionHeader("备注参数(附注)");
foreach (var b in noteBindings)
{
var baseKey = (b.ParamKey ?? string.Empty).Trim();
var def = _catalog.FindByKey(baseKey);
if (def == null)
{
continue;
}
AddField(def);
// If same ParamKey is used by multiple placeholders, give each placeholder its own value key.
var valueKey = baseKey;
var dupCount = noteBindings.Count(x => string.Equals((x.ParamKey ?? string.Empty).Trim(), baseKey, StringComparison.OrdinalIgnoreCase));
if (dupCount > 1)
{
valueKey = string.Format("{0}@{1}", baseKey, b.Index);
}
var labelText = string.Format("{0} (占位符: #{1})", def.Label, b.Index);
var tip = BuildOccurrenceTip(b.Index);
AddField(def, valueKey, labelText, tip);
}
}
}
private void AddField(ParamDefinition def)
private string BuildOccurrenceTip(int placeholderIndex)
{
if (_occByIndex == null)
{
return null;
}
if (!_occByIndex.TryGetValue(placeholderIndex, out var o) || o == null)
{
return null;
}
var line = o.LineText ?? string.Empty;
var col = o.ColumnInLine;
if (col < 0) col = 0;
if (col >= line.Length)
{
return string.Format("第{0}行: 【#{1}】", o.LineNumber, placeholderIndex);
}
const int ctx = 18;
var start = Math.Max(0, col - ctx);
var end = Math.Min(line.Length, col + 1 + ctx);
var prefix = start > 0 ? "…" : string.Empty;
var suffix = end < line.Length ? "…" : string.Empty;
var left = line.Substring(start, col - start);
var right = line.Substring(col + 1, end - (col + 1));
var snippet = prefix + left + string.Format("【#{0}】", placeholderIndex) + right + suffix;
return string.Format("第{0}行: {1}", o.LineNumber, snippet);
}
private void AddSectionHeader(string text)
{
var rowIdx = _grid.RowCount++;
_grid.RowStyles.Add(new RowStyle(SizeType.AutoSize));
var lbl = new Label
{
AutoSize = true,
ForeColor = Color.DarkBlue,
Text = text,
Padding = new Padding(0, 10, 0, 4)
};
_grid.Controls.Add(lbl, 0, rowIdx);
_grid.SetColumnSpan(lbl, 2);
}
private void AddField(ParamDefinition def, string controlKey, string labelText, string extraToolTip)
{
var rowIdx = _grid.RowCount++;
_grid.RowStyles.Add(new RowStyle(SizeType.AutoSize));
var labelText = def.Label;
if (string.IsNullOrWhiteSpace(labelText))
{
labelText = def.Label;
}
if (def.Required)
{
labelText += " *";
@ -151,9 +350,96 @@ namespace CadParamPluging.UI
_toolTip.SetToolTip(ctrl, def.Hint);
}
if (!string.IsNullOrWhiteSpace(extraToolTip))
{
_toolTip.SetToolTip(lbl, extraToolTip);
_toolTip.SetToolTip(ctrl, extraToolTip);
}
_grid.Controls.Add(lbl, 0, rowIdx);
_grid.Controls.Add(ctrl, 1, rowIdx);
_controlsByKey[def.Key] = ctrl;
_controlsByKey[controlKey] = ctrl;
_defsByControlKey[controlKey] = def;
AttachPreviewUpdate(ctrl);
}
private void AttachPreviewUpdate(Control ctrl)
{
if (ctrl == null)
{
return;
}
if (ctrl is TextBox tb)
{
tb.TextChanged += (_, __) => UpdateNotePreviews();
return;
}
if (ctrl is ComboBox cb)
{
cb.SelectedIndexChanged += (_, __) => UpdateNotePreviews();
cb.TextChanged += (_, __) => UpdateNotePreviews();
return;
}
if (ctrl is NumericUpDown num)
{
num.ValueChanged += (_, __) => UpdateNotePreviews();
return;
}
if (ctrl is CheckBox chk)
{
chk.CheckedChanged += (_, __) => UpdateNotePreviews();
}
}
private void UpdateNotePreviews()
{
if (_schema == null)
{
_rtbNoteNumbered.Text = "(模板参数未配置)";
_rtbNoteRendered.Text = string.Empty;
return;
}
var noteTemplate = _schema.NoteTemplateText ?? string.Empty;
if (string.IsNullOrWhiteSpace(noteTemplate))
{
_rtbNoteNumbered.Text = "(未配置附注模板文本:请到【设置→模板参数绑定→附注绑定】粘贴附注模板)";
_rtbNoteRendered.Text = string.Empty;
return;
}
_rtbNoteNumbered.Text = NoteTemplateEngine.BuildNumberedPreviewText(noteTemplate);
var tempBag = CollectBagFromUi();
var effective = NoteTemplateEngine.BuildEffectiveValueKeyBindings(_schema.NoteBindings);
_rtbNoteRendered.Text = NoteTemplateEngine.Render(noteTemplate, effective, tempBag.GetString);
}
private ParamBag CollectBagFromUi()
{
var bag = new ParamBag();
foreach (var key in _controlsByKey.Keys.ToList())
{
if (!_controlsByKey.TryGetValue(key, out var ctrl) || ctrl == null)
{
continue;
}
if (!_defsByControlKey.TryGetValue(key, out var def) || def == null)
{
continue;
}
var value = ReadValue(ctrl, def);
bag.Set(key, value);
}
return bag;
}
private Control CreateControl(ParamDefinition def)
@ -256,8 +542,7 @@ namespace CadParamPluging.UI
continue;
}
var def = _catalog.FindByKey(key);
if (def == null)
if (!_defsByControlKey.TryGetValue(key, out var def) || def == null)
{
continue;
}
@ -270,7 +555,7 @@ namespace CadParamPluging.UI
return;
}
bag.Set(def.Key, value);
bag.Set(key, value);
}
Result = bag;

View File

@ -589,6 +589,24 @@ namespace CadParamPluging.UI
AppendLog($"已移除模板匹配参数标注文本: {removed} 处");
}
var noteResult = TemplateDrawingService.ApplyNoteTemplate(
ctx,
_selectedTemplate.LayoutName,
_selectedModelWindow.HasValue,
schema,
bag);
if (noteResult != null)
{
if (noteResult.Applied)
{
AppendLog($"附注替换成功(目标={noteResult.TargetKind}, 占位符={noteResult.PlaceholderCountInDwg}");
}
else if (!string.IsNullOrWhiteSpace(noteResult.Message))
{
AppendLog($"附注替换跳过: {noteResult.Message}");
}
}
var cadService = new CadDrawingService(ctx);
DomainFacade.DrawByParams(_selectedTemplate, drawingParams, cadService);
ctx.Commit();

View File

@ -142,7 +142,7 @@ namespace CadParamPluging.UI
}
catch (Exception ex)
{
MessageBox.Show(this, $"打开模板参数绑定失败: {ex.Message}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
MessageBox.Show(this, $"打开模板参数绑定失败:\n\n{ex}", "错误", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
}

View File

@ -18,9 +18,22 @@ namespace CadParamPluging.UI
private readonly ListBox _lbSchemas;
private readonly ListView _lvParams;
private readonly TextBox _txtNoteTemplate;
private readonly Label _lblNotePlaceholderCount;
private readonly DataGridView _gvNoteBindings;
private readonly RichTextBox _rtbNotePreview;
private readonly List<ParamOption> _remarkParamOptions;
private readonly ParamCatalog _catalog;
private TemplateSchemas _schemas;
private sealed class ParamOption
{
public string Key { get; set; }
public string Display { get; set; }
}
public TemplateSchemaBindingForm(
System.Collections.Generic.IEnumerable<string> projectTypes,
System.Collections.Generic.IEnumerable<string> drawingTypes,
@ -30,16 +43,23 @@ namespace CadParamPluging.UI
{
Text = "模板参数绑定";
Size = new Size(920, 620);
MinimumSize = new Size(920, 620);
StartPosition = FormStartPosition.CenterParent;
FormBorderStyle = FormBorderStyle.FixedDialog;
MaximizeBox = false;
MinimizeBox = false;
FormBorderStyle = FormBorderStyle.Sizable;
MaximizeBox = true;
MinimizeBox = true;
ShowInTaskbar = false;
_catalog = (catalog ?? ParamCatalog.CreateDefault()).Clone();
_catalog.Normalize();
_schemas = TemplateSchemaStore.Load();
_remarkParamOptions = (_catalog.Items ?? Enumerable.Empty<ParamDefinition>())
.Where(p => p != null && string.Equals(p.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase))
.OrderBy(p => p.Order)
.Select(p => new ParamOption { Key = p.Key, Display = string.Format("{0} ({1})", p.Label, p.Key) })
.ToList();
_schemas = TemplateSchemaStore.Load() ?? new TemplateSchemas();
_schemas.Normalize();
var root = new TableLayoutPanel
@ -55,8 +75,6 @@ namespace CadParamPluging.UI
root.RowStyles.Add(new RowStyle(SizeType.AutoSize));
_lbSchemas = new ListBox { Dock = DockStyle.Fill };
_lbSchemas.SelectedIndexChanged += (_, __) => LoadSelectedSchemaToUi();
ReloadSchemaList();
var leftButtons = new FlowLayoutPanel
{
@ -155,6 +173,80 @@ namespace CadParamPluging.UI
paramsButtonPanel.Controls.Add(btnSelectAll);
paramsButtonPanel.Controls.Add(btnUnselectAll);
_txtNoteTemplate = new TextBox
{
Dock = DockStyle.Fill,
Multiline = true,
ScrollBars = ScrollBars.Vertical,
Height = 120
};
_lblNotePlaceholderCount = new Label
{
AutoSize = true,
ForeColor = Color.DarkBlue,
Text = "占位符: 0"
};
_gvNoteBindings = new DataGridView
{
Dock = DockStyle.Fill,
AutoGenerateColumns = false,
AllowUserToAddRows = false,
AllowUserToDeleteRows = false,
RowHeadersVisible = false,
SelectionMode = DataGridViewSelectionMode.FullRowSelect
};
var colIdx = new DataGridViewTextBoxColumn
{
HeaderText = "序号",
Width = 60,
ReadOnly = true
};
var colLine = new DataGridViewTextBoxColumn
{
HeaderText = "所在行",
Width = 80,
ReadOnly = true
};
var colSnippet = new DataGridViewTextBoxColumn
{
HeaderText = "文本定位",
AutoSizeMode = DataGridViewAutoSizeColumnMode.Fill,
ReadOnly = true
};
var colParam = new DataGridViewComboBoxColumn
{
HeaderText = "绑定参数(备注参数)",
Width = 320,
DisplayStyle = DataGridViewComboBoxDisplayStyle.DropDownButton,
DataSource = _remarkParamOptions,
DisplayMember = "Display",
ValueMember = "Key"
};
_gvNoteBindings.Columns.Add(colIdx);
_gvNoteBindings.Columns.Add(colLine);
_gvNoteBindings.Columns.Add(colSnippet);
_gvNoteBindings.Columns.Add(colParam);
_gvNoteBindings.DataError += (_, __) => { };
_gvNoteBindings.SelectionChanged += (_, __) => HighlightSelectedPlaceholderInPreview();
_rtbNotePreview = new RichTextBox
{
Dock = DockStyle.Fill,
ReadOnly = true,
HideSelection = false
};
var btnParseNote = new Button { Text = "解析占位符", AutoSize = true };
btnParseNote.Click += (_, __) => ReloadNoteBindingsFromTemplateText();
var rightPanel = new TableLayoutPanel
{
Dock = DockStyle.Fill,
@ -179,7 +271,45 @@ namespace CadParamPluging.UI
paramArea.Controls.Add(paramsButtonPanel, 0, 0);
paramArea.Controls.Add(_lvParams, 0, 1);
rightPanel.Controls.Add(paramArea, 0, 2);
var noteArea = new TableLayoutPanel
{
Dock = DockStyle.Fill,
ColumnCount = 1,
RowCount = 6,
Padding = new Padding(0)
};
noteArea.RowStyles.Add(new RowStyle(SizeType.AutoSize));
noteArea.RowStyles.Add(new RowStyle(SizeType.AutoSize));
noteArea.RowStyles.Add(new RowStyle(SizeType.AutoSize));
noteArea.RowStyles.Add(new RowStyle(SizeType.Percent, 60f));
noteArea.RowStyles.Add(new RowStyle(SizeType.AutoSize));
noteArea.RowStyles.Add(new RowStyle(SizeType.Percent, 40f));
noteArea.Controls.Add(new Label { Text = "附注模板(多行文本,使用 * 作为占位符;**** 会忽略)", AutoSize = true }, 0, 0);
noteArea.Controls.Add(_txtNoteTemplate, 0, 1);
var noteToolbar = new FlowLayoutPanel
{
Dock = DockStyle.Fill,
FlowDirection = FlowDirection.LeftToRight,
AutoSize = true
};
noteToolbar.Controls.Add(btnParseNote);
noteToolbar.Controls.Add(_lblNotePlaceholderCount);
noteArea.Controls.Add(noteToolbar, 0, 2);
noteArea.Controls.Add(_gvNoteBindings, 0, 3);
noteArea.Controls.Add(new Label { Text = "编号预览(用于对照绑定)", AutoSize = true }, 0, 4);
noteArea.Controls.Add(_rtbNotePreview, 0, 5);
var tabs = new TabControl { Dock = DockStyle.Fill };
var tabParams = new TabPage { Text = "参数绑定" };
tabParams.Controls.Add(paramArea);
var tabNotes = new TabPage { Text = "附注绑定" };
tabNotes.Controls.Add(noteArea);
tabs.TabPages.Add(tabParams);
tabs.TabPages.Add(tabNotes);
rightPanel.Controls.Add(tabs, 0, 2);
var bottomButtons = new FlowLayoutPanel
{
@ -203,10 +333,9 @@ namespace CadParamPluging.UI
BuildParamListItems();
UpdateTemplateKeyLabel();
if (_lbSchemas.Items.Count > 0)
{
_lbSchemas.SelectedIndex = 0;
}
_lbSchemas.SelectedIndexChanged += (_, __) => LoadSelectedSchemaToUi();
ReloadSchemaList();
}
private static ComboBox CreateCombo(System.Collections.Generic.IEnumerable<string> items)
@ -231,7 +360,10 @@ namespace CadParamPluging.UI
try
{
_lvParams.Items.Clear();
foreach (var p in (_catalog.Items ?? Enumerable.Empty<ParamDefinition>()).OrderBy(x => x.Order))
foreach (var p in (_catalog.Items ?? Enumerable.Empty<ParamDefinition>())
.Where(x => x != null)
.Where(x => !string.Equals(x.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase))
.OrderBy(x => x.Order))
{
var it = new ListViewItem(new[] { p.Label, p.Key })
{
@ -295,6 +427,7 @@ namespace CadParamPluging.UI
_lbSchemas.Items.Clear();
foreach (var s in (_schemas.Items ?? Enumerable.Empty<TemplateSchemaDefinition>())
.Where(x => x != null)
.OrderBy(x => x.ProjectType)
.ThenBy(x => x.DrawingType)
.ThenBy(x => x.SheetSize)
@ -329,6 +462,7 @@ namespace CadParamPluging.UI
}
var ordered = (_schemas.Items ?? Enumerable.Empty<TemplateSchemaDefinition>())
.Where(x => x != null)
.OrderBy(x => x.ProjectType)
.ThenBy(x => x.DrawingType)
.ThenBy(x => x.SheetSize)
@ -341,7 +475,9 @@ namespace CadParamPluging.UI
}
var key = ordered[idx].TemplateKey;
return (_schemas.Items ?? new List<TemplateSchemaDefinition>()).FirstOrDefault(x => string.Equals(x.TemplateKey, key, StringComparison.OrdinalIgnoreCase));
return (_schemas.Items ?? new List<TemplateSchemaDefinition>())
.Where(x => x != null)
.FirstOrDefault(x => string.Equals(x.TemplateKey, key, StringComparison.OrdinalIgnoreCase));
}
private void LoadSelectedSchemaToUi()
@ -359,6 +495,7 @@ namespace CadParamPluging.UI
_txtDisplayName.Text = schema.DisplayName ?? string.Empty;
UpdateTemplateKeyLabel();
ApplySchemaToParamList(schema);
LoadNoteBindingsToUi(schema);
}
private static void SelectComboByText(ComboBox cb, string value)
@ -403,7 +540,9 @@ namespace CadParamPluging.UI
};
candidate.Normalize();
if ((_schemas.Items ?? new List<TemplateSchemaDefinition>()).Any(x => string.Equals(x.TemplateKey, candidate.TemplateKey, StringComparison.OrdinalIgnoreCase)))
if ((_schemas.Items ?? new List<TemplateSchemaDefinition>())
.Where(x => x != null)
.Any(x => string.Equals(x.TemplateKey, candidate.TemplateKey, StringComparison.OrdinalIgnoreCase)))
{
MessageBox.Show(this, "该模板定义已存在(四参数组合重复)。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
@ -434,7 +573,7 @@ namespace CadParamPluging.UI
}
_schemas.Items = (_schemas.Items ?? Enumerable.Empty<TemplateSchemaDefinition>())
.Where(x => !string.Equals(x.TemplateKey, schema.TemplateKey, StringComparison.OrdinalIgnoreCase))
.Where(x => x != null && !string.Equals(x.TemplateKey, schema.TemplateKey, StringComparison.OrdinalIgnoreCase))
.ToList();
_schemas.Normalize();
ReloadSchemaList();
@ -485,6 +624,185 @@ namespace CadParamPluging.UI
}
}
private void LoadNoteBindingsToUi(TemplateSchemaDefinition schema)
{
if (schema == null)
{
_txtNoteTemplate.Text = string.Empty;
ReloadNoteBindingsGrid(string.Empty, null);
return;
}
_txtNoteTemplate.Text = schema.NoteTemplateText ?? string.Empty;
ReloadNoteBindingsGrid(_txtNoteTemplate.Text, schema.NoteBindings);
}
private void ReloadNoteBindingsFromTemplateText()
{
var map = ReadNoteBindingMapFromGrid();
var bindings = map
.Select(kv => new NotePlaceholderBinding { Index = kv.Key, ParamKey = kv.Value })
.ToList();
ReloadNoteBindingsGrid(_txtNoteTemplate.Text ?? string.Empty, bindings);
}
private void ReloadNoteBindingsGrid(string templateText, IEnumerable<NotePlaceholderBinding> bindings)
{
var occ = NoteTemplateEngine.ParseOccurrences(templateText ?? string.Empty);
var count = occ.Count;
_lblNotePlaceholderCount.Text = string.Format("占位符: {0}", count);
_rtbNotePreview.Text = NoteTemplateEngine.BuildNumberedPreviewText(templateText ?? string.Empty);
var map = new Dictionary<int, string>();
foreach (var b in bindings ?? Enumerable.Empty<NotePlaceholderBinding>())
{
if (b == null)
{
continue;
}
if (b.Index <= 0)
{
continue;
}
var key = (b.ParamKey ?? string.Empty).Trim();
if (key.Length == 0)
{
continue;
}
if (!map.ContainsKey(b.Index))
{
map[b.Index] = key;
}
}
_gvNoteBindings.SuspendLayout();
try
{
_gvNoteBindings.Rows.Clear();
foreach (var o in occ)
{
var idx = _gvNoteBindings.Rows.Add();
var row = _gvNoteBindings.Rows[idx];
row.Cells[0].Value = o.Index;
row.Cells[1].Value = string.Format("第{0}行", o.LineNumber);
row.Cells[2].Value = BuildOccurrenceSnippet(o);
row.Cells[3].Value = map.ContainsKey(o.Index) ? map[o.Index] : null;
}
}
finally
{
_gvNoteBindings.ResumeLayout();
}
HighlightSelectedPlaceholderInPreview();
}
private Dictionary<int, string> ReadNoteBindingMapFromGrid()
{
var map = new Dictionary<int, string>();
foreach (DataGridViewRow row in _gvNoteBindings.Rows)
{
if (row == null)
{
continue;
}
var idxObj = row.Cells[0].Value;
var keyObj = row.Cells[3].Value;
int idx;
if (idxObj == null || !int.TryParse(idxObj.ToString(), out idx) || idx <= 0)
{
continue;
}
var key = (keyObj ?? string.Empty).ToString().Trim();
if (key.Length == 0)
{
continue;
}
if (!map.ContainsKey(idx))
{
map[idx] = key;
}
}
return map;
}
private static string BuildOccurrenceSnippet(NoteTemplateEngine.PlaceholderOccurrence o)
{
if (o == null)
{
return string.Empty;
}
var line = o.LineText ?? string.Empty;
var token = string.Format("【#{0}】", o.Index);
var col = o.ColumnInLine;
if (col < 0) col = 0;
if (col >= line.Length)
{
return token;
}
const int ctx = 16;
var start = Math.Max(0, col - ctx);
var end = Math.Min(line.Length, col + 1 + ctx);
var prefix = start > 0 ? "…" : string.Empty;
var suffix = end < line.Length ? "…" : string.Empty;
var left = line.Substring(start, col - start);
var right = line.Substring(col + 1, end - (col + 1));
return prefix + left + token + right + suffix;
}
private void HighlightSelectedPlaceholderInPreview()
{
if (_rtbNotePreview == null || string.IsNullOrEmpty(_rtbNotePreview.Text))
{
return;
}
if (_gvNoteBindings.SelectedRows.Count == 0)
{
return;
}
var row = _gvNoteBindings.SelectedRows[0];
if (row == null)
{
return;
}
var idxObj = row.Cells[0].Value;
int idx;
if (idxObj == null || !int.TryParse(idxObj.ToString(), out idx) || idx <= 0)
{
return;
}
var token = string.Format("【#{0}】", idx);
var pos = _rtbNotePreview.Text.IndexOf(token, StringComparison.Ordinal);
if (pos < 0)
{
return;
}
_rtbNotePreview.Select(pos, token.Length);
_rtbNotePreview.ScrollToCaret();
}
private void OnSave()
{
var schema = GetSelectedSchema();
@ -499,14 +817,66 @@ namespace CadParamPluging.UI
schema.SheetSize = _cbSheetSize.Text;
schema.Scale = _cbScale.Text;
schema.DisplayName = _txtDisplayName.Text;
schema.SelectedParamKeys = _lvParams.Items
var selectedParamKeys = _lvParams.Items
.Cast<ListViewItem>()
.Where(i => i.Checked)
.Select(i => (string)i.Tag)
.Where(k => !string.IsNullOrWhiteSpace(k))
.ToList();
// “备注参数”仅在附注绑定中配置,不出现在模板参数绑定里。
selectedParamKeys = selectedParamKeys
.Where(k =>
{
var def = _catalog.FindByKey(k);
if (def == null)
{
return true;
}
return !string.Equals(def.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase);
})
.ToList();
schema.SelectedParamKeys = selectedParamKeys;
schema.NoteTemplateText = _txtNoteTemplate.Text ?? string.Empty;
var placeholderCount = NoteTemplateEngine.CountPlaceholders(schema.NoteTemplateText);
if (_gvNoteBindings.Rows.Count != placeholderCount)
{
ReloadNoteBindingsFromTemplateText();
MessageBox.Show(this, "附注模板已变化,已重新解析占位符。请确认附注绑定后再保存。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Information);
return;
}
var noteBindings = new List<NotePlaceholderBinding>();
if (placeholderCount > 0)
{
var map = ReadNoteBindingMapFromGrid();
for (var i = 1; i <= placeholderCount; i++)
{
if (!map.TryGetValue(i, out var key) || string.IsNullOrWhiteSpace(key))
{
MessageBox.Show(this, string.Format("请绑定第 {0} 个占位符(*)对应的参数。", i), "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
var def = _catalog.FindByKey(key);
if (def == null || !string.Equals(def.Group ?? string.Empty, "备注参数", StringComparison.OrdinalIgnoreCase))
{
MessageBox.Show(this, string.Format("占位符 #{0} 绑定的参数不在【备注参数】分组:{1}", i, key), "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);
return;
}
noteBindings.Add(new NotePlaceholderBinding { Index = i, ParamKey = key.Trim() });
}
}
schema.NoteBindings = noteBindings;
schema.Normalize();
if ((_schemas.Items ?? new List<TemplateSchemaDefinition>())
.Where(x => x != null)
.Any(x => !ReferenceEquals(x, schema) && string.Equals(x.TemplateKey, schema.TemplateKey, StringComparison.OrdinalIgnoreCase)))
{
MessageBox.Show(this, "保存失败:存在重复的四参数组合。", "提示", MessageBoxButtons.OK, MessageBoxIcon.Warning);