From 94e2f343ae3df47e13644ef1f73a932af589ccfd Mon Sep 17 00:00:00 2001 From: sladro Date: Mon, 12 Jan 2026 16:39:48 +0800 Subject: [PATCH] =?UTF-8?q?=E5=9C=A8=E5=BD=93=E5=89=8D=E6=89=93=E5=BC=80?= =?UTF-8?q?=E7=9A=84=E6=96=87=E4=BB=B6=E4=B8=AD=E7=94=9F=E6=88=90=E5=9B=BE?= =?UTF-8?q?=E7=BA=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cad/DrawingStyleManager.cs | 2 +- Cad/FeatureDrivenDrawer.cs | 42 +++++- Cad/TemplateDrawingService.cs | 237 +++++++++++++++++++++++++++++++++- UI/ParamDrawingPanel.cs | 3 +- 4 files changed, 278 insertions(+), 6 deletions(-) diff --git a/Cad/DrawingStyleManager.cs b/Cad/DrawingStyleManager.cs index 8ed402f..28f3aa3 100644 --- a/Cad/DrawingStyleManager.cs +++ b/Cad/DrawingStyleManager.cs @@ -65,7 +65,7 @@ namespace CadParamPluging.Cad case Role.Hatch: return new LayerSpec { LayerName = "剖面线", DefaultLinetypeName = "Continuous", DefaultLineWeight = LineWeight.LineWeight015, DefaultColorIndex = 4 }; case Role.Dimension: - return new LayerSpec { LayerName = "尺寸标注", DefaultLinetypeName = "Continuous", DefaultLineWeight = LineWeight.LineWeight018, DefaultColorIndex = 7 }; + return new LayerSpec { LayerName = "尺寸标注", DefaultLinetypeName = "Continuous", DefaultLineWeight = LineWeight.LineWeight018, DefaultColorIndex = 3 }; case Role.Text: return new LayerSpec { LayerName = "文字", DefaultLinetypeName = "Continuous", DefaultLineWeight = LineWeight.LineWeight018, DefaultColorIndex = 7 }; case Role.OutlineThin: diff --git a/Cad/FeatureDrivenDrawer.cs b/Cad/FeatureDrivenDrawer.cs index c915a65..eaa3f3f 100644 --- a/Cad/FeatureDrivenDrawer.cs +++ b/Cad/FeatureDrivenDrawer.cs @@ -1732,7 +1732,7 @@ namespace CadParamPluging.Cad { // 按需求修改半边标注样式: // 尺寸线 1和2开 (默认即为开) - // 界线1关 (Dimse1 = true) + // 界线1关 (Dimse1 = true) - 对称轴处无尺寸界线 // 界线2开 (Dimse2 = false) TrySetDimProp(dim, "Dimse1", true); TrySetDimProp(dim, "Dimse2", false); @@ -1744,8 +1744,12 @@ namespace CadParamPluging.Cad // 需要设置 Dimsah = true 才能分别设置箭头 TrySetDimProp(dim, "Dimsah", true); - // 尝试设置箭头1为 _NONE + // [Modified] Ensure _NONE block exists, then use it. + // Restore line display (remove Dimsd1=true) so it aligns with left side logic. + EnsureNoneBlock(ctx.Db, ctx.Tr); + var bt = (BlockTable)ctx.Tr.GetObject(ctx.Db.BlockTableId, OpenMode.ForRead); + // Now we are confident _NONE exists or defaults to something safe if (bt.Has("_NONE")) { dim.Dimblk1 = bt["_NONE"]; @@ -1754,6 +1758,28 @@ namespace CadParamPluging.Cad catch { } } + private static void EnsureNoneBlock(Database db, Transaction tr) + { + try + { + var bt = (BlockTable)tr.GetObject(db.BlockTableId, OpenMode.ForRead); + if (!bt.Has("_NONE")) + { + // Create empty block named _NONE + using (var btr = new BlockTableRecord()) + { + btr.Name = "_NONE"; + btr.Origin = Point3d.Origin; + + bt.UpgradeOpen(); + bt.Add(btr); + tr.AddNewlyCreatedDBObject(btr, true); + } + } + } + catch { } + } + private static void AddLinearDim(DrawingContext ctx, Point3d pt1, Point3d pt2, Point3d dimLinePt, double rotationDeg, string textOverride, Action customizer = null) { @@ -1769,6 +1795,9 @@ namespace CadParamPluging.Cad var dim = new RotatedDimension(rotRad, pt1, pt2, dimLinePt, textOverride, ctx.Db.Dimstyle); try { dim.SetDatabaseDefaults(); } catch { } + // [Modified] Explicitly set ColorIndex to 3 (Green) as requested by user + dim.ColorIndex = 3; + // Re-apply text override because SetDatabaseDefaults might have cleared it if (!string.IsNullOrEmpty(textOverride)) { @@ -1782,11 +1811,13 @@ namespace CadParamPluging.Cad customizer?.Invoke(dim, ctx); ctx.Style?.Apply(dim, DrawingStyleManager.Role.Dimension); + // Re-force color after style application just in case + dim.ColorIndex = 3; + ctx.Btr.AppendEntity(dim); ctx.Tr.AddNewlyCreatedDBObject(dim, true); // Ensure dimension graphics (including text) are computed and persisted in the DWG. - // Some CAD viewers may otherwise show '*' placeholders. try { dim.RecomputeDimensionBlock(true); @@ -1826,6 +1857,11 @@ namespace CadParamPluging.Cad TrySetDimProp(dim, "Dimtofl", true); // Draw dim line between ext lines TrySetDimProp(dim, "Dimatfit", 3); // Text or Arrows (Best fit) TrySetDimProp(dim, "Dimtad", 1); // Text Vertical: Above + + // [Modified] Change dimension color to Green (3) as requested + TrySetDimProp(dim, "Dimclrd", 3); // Dimension Line Color + TrySetDimProp(dim, "Dimclre", 3); // Extension Line Color + TrySetDimProp(dim, "Dimclrt", 3); // Text Color } private static void TryApplyDimLayoutOverrides(Dimension dim, Point3d pt1, Point3d pt2, Point3d dimLinePt, double rotRad) diff --git a/Cad/TemplateDrawingService.cs b/Cad/TemplateDrawingService.cs index a61b441..007bae5 100644 --- a/Cad/TemplateDrawingService.cs +++ b/Cad/TemplateDrawingService.cs @@ -1080,7 +1080,185 @@ namespace CadParamPluging.Cad return string.Join(" ", parts); } - public static int ApplyFeatureCategory(CadContext ctx, string layoutName, bool scanModelSpace, ParamBag bag) + /// + /// 确保特性分类文字周围有框体,如果没有则创建 + /// + private static void EnsureFeatureCategoryBox(Transaction tr, Database db, BlockTableRecord space, List spaceEntList, ObjectId textEntityId, Action logAction) + { + // 获取文字实体的最新范围 + Extents3d textExt; + try + { + var textEnt = tr.GetObject(textEntityId, OpenMode.ForRead, false) as Entity; + if (textEnt == null || textEnt.IsErased) return; + + // 针对 MText,使用实际文字范围而非控制框范围 + if (textEnt is MText mt) + { + // MText 的 Location 是插入点,根据 Attachment 不同位置不同 + // 使用 ActualWidth 和 ActualHeight 获取实际文字尺寸 + var loc = mt.Location; + var actualW = mt.ActualWidth; + var actualH = mt.ActualHeight; + + // 根据 Attachment 计算实际边界 + double minX, maxX, minY, maxY; + switch (mt.Attachment) + { + case AttachmentPoint.TopLeft: + minX = loc.X; maxX = loc.X + actualW; + minY = loc.Y - actualH; maxY = loc.Y; + break; + case AttachmentPoint.TopCenter: + minX = loc.X - actualW / 2; maxX = loc.X + actualW / 2; + minY = loc.Y - actualH; maxY = loc.Y; + break; + case AttachmentPoint.TopRight: + minX = loc.X - actualW; maxX = loc.X; + minY = loc.Y - actualH; maxY = loc.Y; + break; + case AttachmentPoint.MiddleLeft: + minX = loc.X; maxX = loc.X + actualW; + minY = loc.Y - actualH / 2; maxY = loc.Y + actualH / 2; + break; + case AttachmentPoint.MiddleCenter: + minX = loc.X - actualW / 2; maxX = loc.X + actualW / 2; + minY = loc.Y - actualH / 2; maxY = loc.Y + actualH / 2; + break; + case AttachmentPoint.MiddleRight: + minX = loc.X - actualW; maxX = loc.X; + minY = loc.Y - actualH / 2; maxY = loc.Y + actualH / 2; + break; + case AttachmentPoint.BottomLeft: + minX = loc.X; maxX = loc.X + actualW; + minY = loc.Y; maxY = loc.Y + actualH; + break; + case AttachmentPoint.BottomCenter: + minX = loc.X - actualW / 2; maxX = loc.X + actualW / 2; + minY = loc.Y; maxY = loc.Y + actualH; + break; + case AttachmentPoint.BottomRight: + minX = loc.X - actualW; maxX = loc.X; + minY = loc.Y; maxY = loc.Y + actualH; + break; + default: + // 兜底使用 GeometricExtents + textExt = textEnt.GeometricExtents; + goto afterExtents; + } + textExt = new Extents3d(new Point3d(minX, minY, 0), new Point3d(maxX, maxY, 0)); + + logAction?.Invoke($"[DEBUG] MText: Loc=({loc.X:F1},{loc.Y:F1}), W={actualW:F1}, H={actualH:F1}, Attach={mt.Attachment}"); + logAction?.Invoke($"[DEBUG] 计算范围: ({minX:F1},{minY:F1})->({maxX:F1},{maxY:F1})"); + } + else + { + textExt = textEnt.GeometricExtents; + } + afterExtents:; + + logAction?.Invoke($"[DEBUG] 最终范围: Min=({textExt.MinPoint.X:F1},{textExt.MinPoint.Y:F1}), Max=({textExt.MaxPoint.X:F1},{textExt.MaxPoint.Y:F1})"); + } + catch (System.Exception ex) + { + logAction?.Invoke($"[DEBUG] 获取范围失败: {ex.Message}"); + return; + } + + // 检查是否已经有包围该文字的闭合 Polyline(大小匹配) + bool hasBox = false; + var textWidth = textExt.MaxPoint.X - textExt.MinPoint.X; + var textHeight = textExt.MaxPoint.Y - textExt.MinPoint.Y; + // 框体最大允许尺寸 = 文字尺寸 + 最大 padding (5mm) + var maxBoxWidth = textWidth + 10.0; // 两边各5mm + var maxBoxHeight = textHeight + 10.0; // 上下各5mm + + foreach (var ent in spaceEntList) + { + if (ent is Polyline pl && pl.Closed && !pl.IsErased) + { + try + { + var ex = pl.GeometricExtents; + var boxWidth = ex.MaxPoint.X - ex.MinPoint.X; + var boxHeight = ex.MaxPoint.Y - ex.MinPoint.Y; + + // 检查 Polyline 是否包围文字(有 0.1 的容差) + // 并且大小接近文字大小(不能太大,避免误判表格边框) + if (ex.MinPoint.X <= textExt.MinPoint.X - 0.1 + && ex.MaxPoint.X >= textExt.MaxPoint.X + 0.1 + && ex.MinPoint.Y <= textExt.MinPoint.Y - 0.1 + && ex.MaxPoint.Y >= textExt.MaxPoint.Y + 0.1 + && boxWidth <= maxBoxWidth + && boxHeight <= maxBoxHeight) + { + hasBox = true; + logAction?.Invoke($"[DEBUG] 已找到匹配框体: 框体尺寸({boxWidth:F1}x{boxHeight:F1}), 文字尺寸({textWidth:F1}x{textHeight:F1}), 图层={pl.Layer}"); + + // 将框体移动到安全图层,防止被 RemoveTemplateOriginalDrawing 删除 + try + { + var plWrite = (Polyline)tr.GetObject(pl.ObjectId, OpenMode.ForWrite, false); + var layerTbl = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); + string safeLayer = "0"; + if (layerTbl.Has("TEXT")) safeLayer = "TEXT"; + else if (layerTbl.Has("文字")) safeLayer = "文字"; + plWrite.Layer = safeLayer; + logAction?.Invoke($"[DEBUG] 已将框体移动到安全图层: {safeLayer}"); + + // 标记此框体,便于后续识别 + var groupId = Guid.NewGuid().ToString("N"); + SetFeatureCategoryData(tr, pl.ObjectId, string.Empty, groupId, "FallbackBox"); + } + catch { } + + break; + } + } + catch { } + } + } + + // 如果没有框体,则创建一个 + if (!hasBox) + { + double paddingH = 1.5; + double paddingV = 1.5; + + var boxMinX = textExt.MinPoint.X - paddingH; + var boxMaxX = textExt.MaxPoint.X + paddingH; + var boxMinY = textExt.MinPoint.Y - paddingV; + var boxMaxY = textExt.MaxPoint.Y + paddingV; + + logAction?.Invoke($"[DEBUG] 创建框体: ({boxMinX:F1},{boxMinY:F1})->({boxMaxX:F1},{boxMaxY:F1})"); + + var poly = new Polyline(); + poly.AddVertexAt(0, new Point2d(boxMinX, boxMinY), 0, 0, 0); + poly.AddVertexAt(1, new Point2d(boxMaxX, boxMinY), 0, 0, 0); + poly.AddVertexAt(2, new Point2d(boxMaxX, boxMaxY), 0, 0, 0); + poly.AddVertexAt(3, new Point2d(boxMinX, boxMaxY), 0, 0, 0); + poly.Closed = true; + poly.ColorIndex = 7; // White + + try + { + var layerTbl = (LayerTable)tr.GetObject(db.LayerTableId, OpenMode.ForRead); + string targetLayer = "0"; + if (layerTbl.Has("TEXT")) targetLayer = "TEXT"; + else if (layerTbl.Has("文字")) targetLayer = "文字"; + poly.Layer = targetLayer; + } + catch { } + + space.AppendEntity(poly); + tr.AddNewlyCreatedDBObject(poly, true); + + var groupId = Guid.NewGuid().ToString("N"); + try { SetFeatureCategoryData(tr, poly.ObjectId, string.Empty, groupId, "FallbackBox"); } catch { } + } + } + + public static int ApplyFeatureCategory(CadContext ctx, string layoutName, bool scanModelSpace, ParamBag bag, Action logAction = null) { if (ctx == null || bag == null) { @@ -1098,6 +1276,8 @@ namespace CadParamPluging.Cad textToShow = string.Empty; // Show blank } + logAction?.Invoke($"[DEBUG] ApplyFeatureCategory: category='{category}', textToShow='{textToShow}'"); + var db = ctx.Database; var tr = ctx.Transaction; var space = GetTargetSpace(tr, db, layoutName, scanModelSpace); @@ -1105,6 +1285,7 @@ namespace CadParamPluging.Cad if (space == null) return 0; var ids = space.Cast().ToArray(); + logAction?.Invoke($"[DEBUG] Space实体数量: {ids.Length}"); int count = 0; @@ -1163,6 +1344,9 @@ namespace CadParamPluging.Cad } // 1) Update previously replaced placeholder entities using stored original template. + // 收集被替换的占位符文字实体ID,以便后续检查框体 + var replacedPlaceholderIds = new List(); + foreach (var id in ids) { var ent = tr.GetObject(id, OpenMode.ForWrite, false) as Entity; @@ -1178,6 +1362,7 @@ namespace CadParamPluging.Cad var original = fcData.OriginalTemplate ?? string.Empty; t.TextString = original.Contains("******") ? original.Replace("******", textToShow) : textToShow; count++; + replacedPlaceholderIds.Add(id); continue; } @@ -1187,6 +1372,7 @@ namespace CadParamPluging.Cad t.TextString = original.Replace("******", textToShow); try { SetFeatureCategoryData(tr, id, original, null, "Placeholder"); } catch { } count++; + replacedPlaceholderIds.Add(id); } } else if (ent is MText mt) @@ -1199,6 +1385,7 @@ namespace CadParamPluging.Cad var original = fcData.OriginalTemplate ?? string.Empty; mt.Contents = original.Contains("******") ? original.Replace("******", textToShow) : textToShow; count++; + replacedPlaceholderIds.Add(id); continue; } @@ -1208,6 +1395,7 @@ namespace CadParamPluging.Cad mt.Contents = original.Replace("******", textToShow); try { SetFeatureCategoryData(tr, id, original, null, "Placeholder"); } catch { } count++; + replacedPlaceholderIds.Add(id); } } else if (ent is BlockReference br) @@ -1225,6 +1413,7 @@ namespace CadParamPluging.Cad var original = fcData.OriginalTemplate ?? string.Empty; att.TextString = original.Contains("******") ? original.Replace("******", textToShow) : textToShow; count++; + replacedPlaceholderIds.Add(attId); continue; } @@ -1234,11 +1423,36 @@ namespace CadParamPluging.Cad att.TextString = original.Replace("******", textToShow); try { SetFeatureCategoryData(tr, attId, original, null, "Placeholder"); } catch { } count++; + replacedPlaceholderIds.Add(attId); } } } } + logAction?.Invoke($"[DEBUG] 替换占位符数量: {replacedPlaceholderIds.Count}, count: {count}"); + + // 2) 如果有被替换的占位符且内容不为空(非"一般件"),检查并确保每个占位符都有框体 + if (replacedPlaceholderIds.Count > 0 && !string.IsNullOrWhiteSpace(textToShow)) + { + logAction?.Invoke($"[DEBUG] 进入框体检查逻辑,textToShow='{textToShow}'"); + + // 获取实体列表用于框体检查 + var spaceEntListForBox = ids + .Select(x => + { + try { return tr.GetObject(x, OpenMode.ForRead, false) as Entity; } + catch { return null; } + }) + .Where(e => e != null && !e.IsErased) + .ToList(); + + foreach (var placeholderId in replacedPlaceholderIds) + { + logAction?.Invoke($"[DEBUG] 调用 EnsureFeatureCategoryBox, placeholderId={placeholderId}"); + EnsureFeatureCategoryBox(tr, db, space, spaceEntListForBox, placeholderId, logAction); + } + } + // If the category is blank ("一般件"), also try to remove previously inserted bottom-right category texts/boxes (legacy, unmarked). if (string.IsNullOrWhiteSpace(textToShow)) { @@ -1455,6 +1669,9 @@ namespace CadParamPluging.Cad { dt.TextString = textToShow; SetFeatureCategoryData(tr, keepId, "******", null, "Placeholder"); + + // 检查是否有包围该文字的框体,如果没有则创建 + EnsureFeatureCategoryBox(tr, db, space, spaceEntList, keepId, logAction); return 1; } if (e is MText mtt) @@ -1470,6 +1687,9 @@ namespace CadParamPluging.Cad mtt.Contents = mtt.Contents.Replace(plain, textToShow); } SetFeatureCategoryData(tr, keepId, "******", null, "Placeholder"); + + // 检查是否有包围该文字的框体,如果没有则创建 + EnsureFeatureCategoryBox(tr, db, space, spaceEntList, keepId, logAction); return 1; } } @@ -1809,6 +2029,11 @@ namespace CadParamPluging.Cad // 删除红色外框线(红色的Line/Polyline,且在红色框的边界上) if (IsOuterFrameEntity(ent, redFrameExtents, tr)) { + // [Modified] Skip erasing outer frame to keep the template's original frame and title block + // Explicitly continue to prevent it being caught by subsequent checks (e.g. CAXA layer) + continue; + + /* Original deletion logic: var entForWrite = tr.GetObject(ent.ObjectId, OpenMode.ForWrite) as Entity; if (entForWrite != null) { @@ -1816,6 +2041,7 @@ namespace CadParamPluging.Cad result.OuterFrameErased++; } continue; + */ } if (IsCaxaLayer(layerName)) @@ -2237,6 +2463,9 @@ namespace CadParamPluging.Cad } var cutY = frame.MinPoint.Y + height * keepBottomRatio; + + // [Modified] Compute Red Frame Extents to protect it from deletion + var redFrameExtents = ComputeRedFrameExtents(tr, allEntities); var removed = 0; foreach (ObjectId id in ms) @@ -2253,6 +2482,12 @@ namespace CadParamPluging.Cad continue; } + // [Modified] Keep Red Outer Frame + if (IsOuterFrameEntity(ent, redFrameExtents, tr)) + { + continue; + } + if (!IsWithin(ent, frame)) { continue; diff --git a/UI/ParamDrawingPanel.cs b/UI/ParamDrawingPanel.cs index f1e7482..512cd74 100644 --- a/UI/ParamDrawingPanel.cs +++ b/UI/ParamDrawingPanel.cs @@ -280,7 +280,8 @@ namespace CadParamPluging.UI ctx, layoutName, isModelSpace, - bag); + bag, + msg => AppendLog(msg)); // 传递日志回调 if (featureCategoryCount > 0) { AppendLog($"已更新特性分类(******): {featureCategoryCount} 处");