using System; using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using System.Windows.Forms; using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.DatabaseServices; using Autodesk.AutoCAD.Geometry; using CadParamPluging.Cad; using CadParamPluging.Common; using CadParamPluging.Domain; using CadParamPluging.Domain.Models; using AcadApp = Autodesk.AutoCAD.ApplicationServices.Application; namespace CadParamPluging.UI { public class ParamDrawingPanel : UserControl { private readonly ComboBox _cbProjectType; private readonly ComboBox _cbDrawingType; private readonly ComboBox _cbSheetSize; private readonly ComboBox _cbScale; private DropdownOptions _dropdownOptions; private TextBox _txtLog; public ParamDrawingPanel() { Dock = DockStyle.Fill; _dropdownOptions = DropdownOptionsStore.Load(); var layout = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 1, RowCount = 4, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink, Padding = new Padding(8) }; _cbProjectType = CreateComboBox(_dropdownOptions.DeliveryStatuses, _dropdownOptions, DropdownOptionCategory.DeliveryStatus); _cbDrawingType = CreateComboBox(_dropdownOptions.ProcessMethods, _dropdownOptions, DropdownOptionCategory.ProcessMethod); _cbSheetSize = CreateComboBox(_dropdownOptions.StructuralFeatures, _dropdownOptions, DropdownOptionCategory.StructuralFeature); _cbScale = CreateComboBox(_dropdownOptions.SpecialConditions, _dropdownOptions, DropdownOptionCategory.SpecialCondition); // Initialize template controls here in constructor to follow readonly/non-readonly best practices, // though they are no longer readonly. var templateGroup = BuildTemplateGroup(); var drawingGroup = BuildDrawingGroup(); var actionPanel = BuildActionPanel(); var logGroup = BuildLogGroup(); layout.Controls.Add(templateGroup, 0, 0); layout.Controls.Add(drawingGroup, 0, 1); layout.Controls.Add(actionPanel, 0, 2); layout.Controls.Add(logGroup, 0, 3); Controls.Add(layout); } private GroupBox BuildTemplateGroup() { var group = new GroupBox { Text = "模板参数", Dock = DockStyle.Top, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink }; var grid = new TableLayoutPanel { Dock = DockStyle.Fill, ColumnCount = 2, RowCount = 4, AutoSize = true }; grid.Controls.Add(new Label { Text = "交付状态", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, 0); grid.Controls.Add(_cbProjectType, 1, 0); grid.Controls.Add(new Label { Text = "工艺方法", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, 1); grid.Controls.Add(_cbDrawingType, 1, 1); grid.Controls.Add(new Label { Text = "结构特征", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, 2); grid.Controls.Add(_cbSheetSize, 1, 2); grid.Controls.Add(new Label { Text = "特殊条件", AutoSize = true, TextAlign = ContentAlignment.MiddleLeft }, 0, 3); grid.Controls.Add(_cbScale, 1, 3); group.Controls.Add(grid); return group; } private GroupBox BuildDrawingGroup() { var group = new GroupBox { Text = "出图参数", Dock = DockStyle.Top, AutoSize = true, AutoSizeMode = AutoSizeMode.GrowAndShrink }; var label = new Label { AutoSize = true, Padding = new Padding(8), Text = "出图参数较多,请点击【生成图纸】后在弹窗中填写。" }; group.Controls.Add(label); return group; } private FlowLayoutPanel BuildActionPanel() { var panel = new FlowLayoutPanel { Dock = DockStyle.Top, AutoSize = true }; var btnGenerate = new Button { Text = "生成图纸", AutoSize = true }; btnGenerate.Click += (_, __) => OnGenerateDrawing(); var btnSaveAs = new Button { Text = "保存当前图纸", AutoSize = true }; btnSaveAs.Click += (_, __) => OnSaveDrawing(); var btnSettings = new Button { Text = "设置", AutoSize = true }; btnSettings.Click += (_, __) => OnOpenSettings(); var btnTestDraw = new Button { Text = "测试绘制", AutoSize = true }; btnTestDraw.Click += (_, __) => OnTestDraw(); panel.Controls.Add(btnGenerate); panel.Controls.Add(btnSaveAs); var btnLoadParams = new Button { Text = "读取参数", AutoSize = true }; btnLoadParams.Click += (_, __) => OnLoadParams(); panel.Controls.Add(btnLoadParams); panel.Controls.Add(btnSettings); // panel.Controls.Add(btnTestDraw); // 暂时屏蔽测试绘制按钮 return panel; } private GroupBox BuildLogGroup() { var group = new GroupBox { Text = "日志", Dock = DockStyle.Fill }; _txtLog = new TextBox { Multiline = true, ScrollBars = ScrollBars.Vertical, Dock = DockStyle.Fill, Height = 140 }; group.Controls.Add(_txtLog); return group; } private static double ParseScaleFactor(string raw) { if (string.IsNullOrWhiteSpace(raw)) { throw new BusinessException("请填写:出图比例 (DrawingScale)"); } var s = (raw ?? string.Empty).Trim(); s = s.Replace(" ", "").Replace("\u3000", "").Replace(":", ":"); var parts = s.Split(':'); if (parts.Length != 2) { throw new BusinessException("出图比例格式错误,应为 M:N(例如 1:5 或 2:1)"); } if (!TryParseDouble(parts[0], out var num) || num < 1e-9 || !TryParseDouble(parts[1], out var denom) || denom < 1e-9) { throw new BusinessException("出图比例数值错误,M和N必须为大于0的数字"); } return num / denom; } private static bool TryParseDouble(string s, out double value) { value = 0; if (string.IsNullOrWhiteSpace(s)) { return false; } return double.TryParse(s, NumberStyles.Float, CultureInfo.InvariantCulture, out value) || double.TryParse(s, NumberStyles.Float, CultureInfo.CurrentCulture, out value); } private void OnGenerateDrawing() { try { var tplParams = CollectTemplateParams(); var templateKey = TemplateKeyBuilder.Build(tplParams); if (string.IsNullOrWhiteSpace(templateKey)) { AppendLog("模板参数不完整,无法生成 TemplateKey。"); return; } var schemas = TemplateSchemaStore.Load(); var schema = (schemas?.Items ?? Enumerable.Empty()) .FirstOrDefault(s => string.Equals(s.TemplateKey, templateKey, StringComparison.OrdinalIgnoreCase)); if (schema == null) { AppendLog($"未找到该模板的参数绑定配置(TemplateKey={templateKey})。请到【设置→模板参数绑定】中新增/配置。"); return; } if (!TemplateDrawingRegistry.TryResolve(templateKey, out var generator)) { AppendLog($"未找到该模板的图纸生成逻辑(TemplateKey={templateKey})。请先实现对应模板生成器。"); return; } // 调试日志:显示找到的生成器类型 AppendLog($"[调试] 已找到生成器: {generator.GetType().Name}, TemplateKey={templateKey}"); var catalog = ParamCatalogStore.Load(); CadParamPluging.Common.ParamBag bag; // 若用户点击了【读取参数】,则将读取到的参数作为本次弹窗的默认值回填 using (var f = new DrawingParamsForm(catalog, schema, _lastLoadedBag, _dropdownOptions)) { var r = f.ShowDialog(this); if (r != DialogResult.OK) { AppendLog("用户取消填写参数。"); return; } bag = f.Result; } // Current demo generator uses a rectangle. Size defaults are handled inside DomainFacade.DrawByParams. // Real templates will use their own generator/parameters. var drawingParams = new DrawingParams(); DomainFacade.ValidateParameters(tplParams, drawingParams); var doc = AcadApp.DocumentManager.MdiActiveDocument; if (doc == null) { AppendLog("未检测到活动图纸,请先打开 CAD 图纸。"); return; } string layoutName = LayoutManager.Current.CurrentLayout; // 判断是否模型空间。注意:LayoutName为"Model"通常表示模型空间 bool isModelSpace = string.Equals(layoutName, "Model", StringComparison.OrdinalIgnoreCase); AppendLog($"正在当前图纸生成: {doc.Name} ({layoutName})"); using (doc.LockDocument()) using (var ctx = new CadContext(doc)) { // 用户要求保留模板配置文字,不执行移除操作 // var removed = TemplateDrawingService.RemoveMatchParameterAnnotations( // ctx, // layoutName, // isModelSpace); // if (removed > 0) // { // AppendLog($"已移除模板匹配参数标注文本: {removed} 处"); // } var featureCategoryCount = TemplateDrawingService.ApplyFeatureCategory( ctx, layoutName, isModelSpace, bag, msg => AppendLog(msg)); // 传递日志回调 if (featureCategoryCount > 0) { AppendLog($"已更新特性分类(******): {featureCategoryCount} 处"); } var noteResult = TemplateDrawingService.ApplyNoteTemplate( ctx, layoutName, isModelSpace, schema, bag); if (noteResult != null) { if (noteResult.Applied) { AppendLog($"附注替换成功(目标={noteResult.TargetKind}, 占位符={noteResult.PlaceholderCountInDwg})"); } else if (!string.IsNullOrWhiteSpace(noteResult.Message)) { AppendLog($"附注替换跳过: {noteResult.Message}"); } } // 删除模板中原有的图纸图形(CAXA图层和尺寸标注,保留右上角) var removeResult = TemplateDrawingService.RemoveTemplateOriginalDrawing(ctx); AppendLog($"已删除原有图形: 红色外框={removeResult.OuterFrameErased}, CAXA图层={removeResult.CaxaLayerErased}, 标注={removeResult.DimensionLayerErased}, 保留右上角={removeResult.DimensionLayerKept}"); // 删除白色线框内“上半部分”的所有内容(保留右上角与下半部分附注/表格) var upperRemoved = TemplateDrawingService.RemoveWhiteFrameUpperContent( ctx, keepBottomRatio: 0.50, topRightThreshold: 0.70); if (upperRemoved > 0) { AppendLog($"已清理白框上半部内容: {upperRemoved} 处"); } var scaleFactor = ParseScaleFactor(bag.GetString("DrawingScale")); if (removeResult.WhiteFrameExtents.HasValue && removeResult.CaxaLayerErased == 0) { AppendLog($"未检测到CAXA层,已切换至白框定位: {removeResult.OriginalCenter}"); } else { AppendLog($"原图中心点: {removeResult.OriginalCenter}"); } // --------------------------------------------------------- // 智能比例建议 (Pre-calculation) // --------------------------------------------------------- if (removeResult.WhiteFrameExtents.HasValue) { var frame = removeResult.WhiteFrameExtents.Value; var frameWidth = frame.MaxPoint.X - frame.MinPoint.X; var frameHeight = frame.MaxPoint.Y - frame.MinPoint.Y; // 确保 bag 中包含交付状态和工艺方法,以便 CalculateExpectedSize 正确判断示意图模式 // tplParams.ProjectType = 交付状态(如"车加工"),tplParams.DrawingType = 工艺方法(如"轧制") if (string.IsNullOrEmpty(bag.GetString("DeliveryStatus"))) { bag.Set("DeliveryStatus", tplParams.ProjectType ?? string.Empty); } if (string.IsNullOrEmpty(bag.GetString("ProcessMethod"))) { bag.Set("ProcessMethod", tplParams.DrawingType ?? string.Empty); } // 1. 获取预估图形尺寸 (不含标注的纯几何尺寸 + 预留边距) var expectedSize = generator.CalculateExpectedSize(bag, tplParams); // 调试:输出关键参数以排查比例建议错误 var debugDelivery = bag.GetString("DeliveryStatus"); var debugProcess = bag.GetString("ProcessMethod"); var debugMinWall = bag.GetDoubleOrNull("MinWallThickness"); var debugHeight = bag.GetDoubleOrNull("Height1"); var debugOuterDia = bag.GetDoubleOrNull("OuterDiameter1"); AppendLog($"[调试] 交付状态={debugDelivery}, 工艺方法={debugProcess}"); AppendLog($"[调试] 外径D1={debugOuterDia}, 高度H1={debugHeight}, 最小壁厚={debugMinWall}, 结构={tplParams.SheetSize}"); if (expectedSize.IsValid) { var currentDrawW = expectedSize.Width * scaleFactor; var currentDrawH = expectedSize.Height * scaleFactor; bool isOverflow = currentDrawW > frameWidth || currentDrawH > frameHeight; bool isTooSmall = currentDrawW < frameWidth * 0.3 && currentDrawH < frameHeight * 0.3; // 调试日志 AppendLog($"[尺寸检查] 白框尺寸: {frameWidth:F0}x{frameHeight:F0}, 预期图形(原始): {expectedSize.Width:F0}x{expectedSize.Height:F0}, 当前比例: 1:{(1/scaleFactor):F1}"); AppendLog($"[尺寸检查] 预期图形(缩放后): {currentDrawW:F0}x{currentDrawH:F0}, 超出={isOverflow}, 过小={isTooSmall}"); AppendLog($"[尺寸检查] 判断阈值: 超出条件(W>{frameWidth:F0} || H>{frameHeight:F0}), 过小条件(W<{frameWidth*0.3:F0} && H<{frameHeight*0.3:F0})"); if (isOverflow || isTooSmall) { // 计算建议比例 (目标填充率 65%) var ratioW = frameWidth / expectedSize.Width; var ratioH = frameHeight / expectedSize.Height; var targetScale = Math.Min(ratioW, ratioH) * 0.65; // 格式化建议比例 string suggestedScaleText; double newOneToN = 1.0 / targetScale; // 简单的取整逻辑,优先凑整 1:1, 1:2, 1:5, 1:10 等 if (targetScale >= 0.95) { suggestedScaleText = "1:1"; targetScale = 1.0; } else { // 凑整到最近的 0.5 或 整数 if (newOneToN < 10) { newOneToN = Math.Round(newOneToN * 2) / 2.0; // 凑整 0.5 } else { newOneToN = Math.Round(newOneToN); // 凑整 1 } suggestedScaleText = $"1:{newOneToN}"; targetScale = 1.0 / newOneToN; } var msg = isOverflow ? $"当前比例 (1:{(1/scaleFactor):F1}) 会导致图形超出图纸边框。\n建议调整为 {suggestedScaleText}。\n\n是否应用建议比例?" : $"当前比例 (1:{(1/scaleFactor):F1}) 会导致图形过小(占比<30%)。\n建议调整为 {suggestedScaleText}。\n\n是否应用建议比例?"; var dialogResult = MessageBox.Show(this, msg, "自动比例建议", MessageBoxButtons.YesNo, MessageBoxIcon.Question); if (dialogResult == DialogResult.Yes) { scaleFactor = targetScale; bag.Set("DrawingScale", suggestedScaleText); // 更新参数包中的比例字符串 AppendLog($"[自动调整] 已应用建议比例: {suggestedScaleText}"); } else { AppendLog($"[自动调整] 用户忽略了比例建议,保持原比例。"); } } } } AppendLog($"绘图参数: Type={tplParams.ProjectType}, Size={tplParams.SheetSize}, Scale={bag.GetString("DrawingScale")}, Method={tplParams.DrawingType}"); // Dubug: Dump bag keys var bagKeys = bag.GetKeys(); // Assuming GetKeys exists? If not, skip or use reflection? // ParamBag implementation is simple dictionary usually. // Let's just log specific keys we suspect. AppendLog($"Bag Keys check: OD1={bag.GetDoubleOrNull("OuterDiameter1")}, H1={bag.GetDoubleOrNull("Height1")}"); // 方轴参数检查 AppendLog($"[方轴参数] Size1={bag.GetDoubleOrNull("SquareShaftSize1")}, Size2={bag.GetDoubleOrNull("SquareShaftSize2")}, Size3={bag.GetDoubleOrNull("SquareShaftSize3")}"); // 根据模板参数绘制半剖视图,居中放置于原图纸位置 // 使用特征驱动模式:根据参数存在性动态绘制对应特征 AppendLog($"[绘图] 准备调用 generator.Draw, scaleFactor={scaleFactor}"); generator.Draw( ctx, bag, tplParams, removeResult.OriginalCenter, scaleFactor); AppendLog($"[绘图] generator.Draw 调用完成"); // Force Zoom Extents to ensure visibility try { doc.SendStringToExecute("._ZOOM _E ", true, false, false); } catch { } // 更新标题栏中的比例属性 var scaleUpdated = TemplateDrawingService.UpdateScaleAttribute( ctx, layoutName, isModelSpace, scaleFactor ); if (scaleUpdated) { AppendLog($"已更新标题栏比例: {(scaleFactor >= 0.9999 ? "1:1" : $"1:{(1/scaleFactor):F1}")}"); } else { AppendLog("未找到标题栏中的'比例'文字,无法更新比例"); } // 更新标题栏中的检验类别 var inspectionCategory = bag.GetString("InspectionCategory"); if (!string.IsNullOrWhiteSpace(inspectionCategory)) { var categoryUpdated = TemplateDrawingService.UpdateInspectionCategoryAttribute( ctx, layoutName, isModelSpace, inspectionCategory ); if (categoryUpdated) { AppendLog($"已更新标题栏检验类别: {inspectionCategory}"); } else { AppendLog("未找到标题栏中的'检验类别'文字,无法更新检验类别"); } } // 添加左上角"内部资料,控制范围"标记 var labelAdded = TemplateDrawingService.AddInternalMaterialLabel( ctx, layoutName, isModelSpace); if (labelAdded) { AppendLog("已添加内部资料标记"); } ctx.Commit(); try { doc.Editor.Regen(); } catch { } // 保存参数到图纸内部 (Extension Dictionary / NOD) try { var bagToSave = bag; // 保存面板选项 bagToSave.Set("ProjectType", tplParams.ProjectType); bagToSave.Set("DrawingType", tplParams.DrawingType); bagToSave.Set("SheetSize", tplParams.SheetSize); bagToSave.Set("SpecialCondition", tplParams.Scale); // tplParams.Scale 对应 _cbScale (特殊条件) DictionaryHelper.SaveParams(doc.Database, bagToSave); AppendLog("参数已保存到图纸内部数据中。"); } catch (Exception ex) { AppendLog($"保存内部参数失败: {ex.Message}"); Logger.Error("SaveParamsInternal", ex); } } // --------- 新增:提示用户保存参数文档 --------- using (var saveDialog = new SaveFileDialog()) { saveDialog.Filter = "文本文件 (*.txt)|*.txt"; string docName = System.IO.Path.GetFileNameWithoutExtension(doc.Name); saveDialog.FileName = $"{docName}_生成参数.txt"; if (saveDialog.ShowDialog() == DialogResult.OK) { try { var sb = new System.Text.StringBuilder(); sb.AppendLine("========== CAD图纸生成参数 =========="); sb.AppendLine($"生成时间: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); sb.AppendLine($"关联图纸: {docName}"); sb.AppendLine("------------------------------------"); // 从 catalog 中获取中文名称映射 var catalogForSave = ParamCatalogStore.Load(); // 这里的 bag 已经在上面收集完成 foreach (var item in bag.Items) { if (!string.IsNullOrWhiteSpace(item.Value)) { var def = catalogForSave?.FindByKey(item.Key); string label = def != null ? def.Label : item.Key; sb.AppendLine($"{label}: {item.Value}"); } } sb.AppendLine("===================================="); System.IO.File.WriteAllText(saveDialog.FileName, sb.ToString(), System.Text.Encoding.UTF8); AppendLog($"参数文档已保存至: {saveDialog.FileName}"); } catch (Exception ex) { AppendLog($"保存参数文档失败: {ex.Message}"); } } } // --------------------------------------------- AppendLog("图纸生成完成。"); } catch (BusinessException bex) { AppendLog($"参数错误: {bex.Message}"); } catch (Exception ex) { AppendLog($"生成失败: {ex.Message}"); Logger.Error("GenerateDrawing", ex); } } private void OnSaveDrawing() { try { var doc = AcadApp.DocumentManager.MdiActiveDocument; if (doc == null) { AppendLog("未找到活动图纸,无法保存。"); return; } using (var dialog = new SaveFileDialog { Filter = "AutoCAD Drawing (*.dwg)|*.dwg", FileName = "新图纸.dwg" }) { if (dialog.ShowDialog() == DialogResult.OK) { doc.Database.SaveAs(dialog.FileName, true, DwgVersion.Current, doc.Database.SecurityParameters); AppendLog($"图纸已保存到: {dialog.FileName}"); } } } catch (Exception ex) { AppendLog($"保存失败: {ex.Message}"); Logger.Error("SaveDrawing", ex); } } private void OnOpenSettings() { try { var current = DropdownOptionsStore.Load(); using (var form = new SettingsForm(current)) { if (form.ShowDialog() == DialogResult.OK) { DropdownOptionsStore.Save(form.Result); _dropdownOptions = form.Result.Clone(); ReloadDropdownOptions(form.Result); AppendLog("设置已保存"); } } } catch (Exception ex) { AppendLog($"打开设置失败: {ex.Message}"); Logger.Error("OpenSettings", ex); } } private void OnLoadParams() { try { var doc = AcadApp.DocumentManager.MdiActiveDocument; if (doc == null) { AppendLog("未找到活动图纸,无法读取参数。"); return; } ParamBag bag; using (doc.LockDocument()) { bag = DictionaryHelper.ReadParams(doc.Database); } if (bag == null || bag.Items == null || bag.Items.Count == 0) { AppendLog("当前图纸未找到存储的参数(或非本插件生成)。"); return; } // 2. 恢复下拉框选项 // ProjectType TrySelectRequest(bag, "ProjectType", _cbProjectType); // DrawingType TrySelectRequest(bag, "DrawingType", _cbDrawingType); // SheetSize TrySelectRequest(bag, "SheetSize", _cbSheetSize); // Scale (DrawingScale) - 注意我们下拉框叫 _cbScale,参数里可能是 DrawingScale var scaleVal = bag.GetString("DrawingScale"); // 特殊条件/Scale 其实是一个 ComboBox,通常用于 SpecialConditions // 但 DrawingScale 是在生成时 Parse 出来的。 // 这里的 _cbScale 绑定的是 options.SpecialConditions (e.g. "中心冲孔") 还是 "1:50"? // 回看代码 reset _cbScale 绑定的是 options.SpecialConditions (ResetComboBoxItems(_cbScale, options.SpecialConditions)) // 而 "scale" 是指比例。 // 观察 GenerateDrawing 逻辑: // var tplParams = CollectTemplateParams(); -> Scale = _cbScale.Text // 这个 _cbScale 存的是“特殊条件”。 // 真正的比例是在 Bag.GetString("DrawingScale"),这是在 DrawParamsForm 里填写的。 // // 等等!看 Readme 和 UI 逻辑,主面板只有 4 个下拉框: Project/Drawing/SheetSize/Scale // 其中 _cbScale 有时候被用作 Scale,但 DropdownOptions 绑定的是 SpecialConditions? // Let's check ReloadDropdownOptions: // ResetComboBoxItems(_cbScale, options.SpecialConditions); -> 所以 _cbScale 是“特殊条件”! // 那比例去哪了? // Readme 说 "ComboBox / TextBox:比例(Scale)" // 让我们确认 CollectTemplateParams: Scale = _cbScale.Text (specialCondition) // 看来目前主面板并没有“比例”输入框?比例是在 Form 里填写的? // 检查 OnGenerateDrawing -> new DrawingParamsForm(catalog, schema) -> 这个 Form 里填写的具体参数。 // // 所以,OnLoadParams 只能恢复 面板上的 4 个下拉框。 // 而 Form 里的参数(如 OuterDiameter, DrawingScale)需要在点“生成”后,弹出 Form 时自动填入。 // 这需要修改 DrawingParamsForm 的构造函数或者逻辑,让它支持“默认值”从 Bag 中覆盖。 TrySelectRequest(bag, "SpecialCondition", _cbScale); // 假设存的时候 key 是 SpecialCondition? // CollectTemplateParams 里没有把 template params 放入 bag。 // 但是!DomainFacade.ValidateParameters 可能会用到。 // 关键点:TemplateParams 只是用来匹配 TemplateKey。 // 实际生成图纸时,OnGenerateDrawing 收集了 tplParams,然后 Build TemplateKey,然后 Load Schema,然后... // 用 Form 填写了 Bag。 // 所以“项目类型/图纸类型”等并没有被存入 Bag (除非 Form 里也有对应字段)。 // 这是一个问题:Bag 主要是 Form 的结果。面板上的选择决定了 TemplateKey。 // 我们在 SaveParamsToExtensionDictionary 时,只存了 Bag。 // 如果面板上的选项(项目类型等)没在 Bag 里,我们就无法恢复面板状态! // // 补救措施:必须在 SaveParams 时把 tplParams 的内容也塞进 Bag。 // 我刚刚加了 _TemplatePath/_LayoutName,但漏了 ProjectType 等。 // // 既然现在代码还没运行,我可以再加几个 Set。 AppendLog("参数读取完成。请点击【生成图纸】继续修改详细参数。"); // 将读取到的 Bag 暂存,以便 DrawingParamsForm 打开时使用它作为默认值 _lastLoadedBag = bag; } catch (Exception ex) { AppendLog($"读取参数失败: {ex.Message}"); Logger.Error("LoadParams", ex); } } private ParamBag _lastLoadedBag; // 暂存读取到的参数 private void TrySelectRequest(ParamBag bag, string key, ComboBox cb) { var val = bag.GetString(key); if (!string.IsNullOrEmpty(val)) { // 尝试选中 for (int i = 0; i < cb.Items.Count; i++) { if (cb.Items[i] is DropdownItem item && string.Equals(item.Value, val, StringComparison.OrdinalIgnoreCase)) { cb.SelectedIndex = i; return; } } } } private void ReloadDropdownOptions(DropdownOptions options) { if (options == null) { return; } ResetComboBoxItems(_cbProjectType, options.DeliveryStatuses, options, DropdownOptionCategory.DeliveryStatus); ResetComboBoxItems(_cbDrawingType, options.ProcessMethods, options, DropdownOptionCategory.ProcessMethod); ResetComboBoxItems(_cbSheetSize, options.StructuralFeatures, options, DropdownOptionCategory.StructuralFeature); ResetComboBoxItems(_cbScale, options.SpecialConditions, options, DropdownOptionCategory.SpecialCondition); } private TemplateParams CollectTemplateParams() { var specialCondition = GetComboBoxValue(_cbScale); if (string.Equals(specialCondition, "无", StringComparison.OrdinalIgnoreCase)) { specialCondition = null; } return new TemplateParams { ProjectType = GetComboBoxValue(_cbProjectType), DrawingType = GetComboBoxValue(_cbDrawingType), SheetSize = GetComboBoxValue(_cbSheetSize), Scale = specialCondition }; } private class DropdownItem { public string Display { get; set; } public string Value { get; set; } public override string ToString() => Display; } private static string GetComboBoxValue(ComboBox cb) { if (cb.SelectedItem is DropdownItem item) { return item.Value; } return cb.Text?.Trim(); } private static ComboBox CreateComboBox(System.Collections.Generic.IEnumerable items, DropdownOptions options, string category) { var cb = new ComboBox { DropDownStyle = ComboBoxStyle.DropDownList, Width = 160, DrawMode = DrawMode.OwnerDrawFixed }; var toolTip = new ToolTip(); cb.DrawItem += (s, e) => { if (e.Index < 0) return; e.DrawBackground(); var text = cb.GetItemText(cb.Items[e.Index]); TextRenderer.DrawText(e.Graphics, text, e.Font, e.Bounds, e.ForeColor, TextFormatFlags.Left | TextFormatFlags.VerticalCenter); e.DrawFocusRectangle(); if (cb.DroppedDown && (e.State & DrawItemState.Selected) == DrawItemState.Selected) { int lastHovered = cb.Tag is int i ? i : -1; if (lastHovered != e.Index) { cb.Tag = e.Index; var point = cb.PointToClient(Cursor.Position); point.Y += 20; toolTip.Show(text, cb, point.X, point.Y); } } }; cb.DropDownClosed += (s, e) => { toolTip.Hide(cb); cb.Tag = -1; }; ResetComboBoxItems(cb, items, options, category); return cb; } private static void ResetComboBoxItems(ComboBox cb, System.Collections.Generic.IEnumerable items, DropdownOptions options, string category) { if (cb == null) { return; } var previousValue = GetComboBoxValue(cb); var arr = (items ?? Enumerable.Empty()).Where(s => !string.IsNullOrWhiteSpace(s)).Select(s => s.Trim()).ToArray(); cb.BeginUpdate(); try { cb.Items.Clear(); if (arr.Length > 0) { var dropDownItems = new System.Collections.Generic.List(); foreach (var item in arr) { var display = options != null ? options.GetDisplayName(category, item) : item; dropDownItems.Add(new DropdownItem { Display = display, Value = item }); } cb.Items.AddRange(dropDownItems.ToArray()); var idx = dropDownItems.FindIndex(v => string.Equals(v.Value, previousValue, StringComparison.OrdinalIgnoreCase)); cb.SelectedIndex = idx >= 0 ? idx : 0; } } finally { cb.EndUpdate(); } } private void OnTestDraw() { try { // ========================================== // 测试参数构造 - 演示特征驱动模式 // ========================================== var bag = new ParamBag(); // 锻件参数 (调整为细长型,符合参考图比例) bag.Set("OuterDiameter1", "160"); bag.Set("OuterDiameter1TolPlus", "2"); bag.Set("OuterDiameter1TolMinus", "-2"); bag.Set("InnerDiameter2", "100"); bag.Set("InnerDiameter2TolPlus", "3"); bag.Set("InnerDiameter2TolMinus", "-3"); bag.Set("Height1", "400"); bag.Set("Height1TolPlus", "5"); bag.Set("Height1TolMinus", "-5"); // 零件参数 (车加工) bag.Set("OuterDiameter1Prime", "150"); bag.Set("InnerDiameter2Prime", "110"); bag.Set("Height1Prime", "380"); // 可选特征参数 - 根据模板绑定动态显示 bag.Set("UnspecifiedFilletRadiusMax", "6"); // 未注圆角半径R≤6 bag.Set("MinWallThickness", "25"); // 最小壁厚T // bag.Set("InnerRadiusMax", "8"); // 内径半径R≤8(中心冲孔时用) var doc = AcadApp.DocumentManager.MdiActiveDocument; if (doc == null) { AppendLog("未找到活动图纸。"); return; } using (doc.LockDocument()) using (var ctx = new CadContext(doc)) { // 调用特征驱动绘图引擎 // 会根据bag中存在的参数自动绘制对应特征 HalfSectionDrawer.Draw( ctx, bag, "车加工", // 交付状态 "环形", // 结构特征 null // 特殊条件(可选:中心冲孔/有圆头等) ); ctx.Commit(); AppendLog($"测试绘制完成 (特征驱动模式)"); AppendLog($"已绘制特征: 外轮廓、内孔(虚线)、公差标注、未注圆角、壁厚、零件(填充)"); } // Zoom Extents doc.SendStringToExecute("._ZOOM _E ", true, false, false); } catch (Exception ex) { AppendLog($"测试绘制失败: {ex.Message}"); Logger.Error("TestDraw", ex); } } private void AppendLog(string message) { var time = DateTime.Now.ToString("HH:mm:ss"); _txtLog.AppendText($"[{time}] {message}{Environment.NewLine}"); } } }