feat(ui): render DXF text annotations in CAD converter preview
Extract MText and TextEntity from the CadDocument during DXF import and render them in the EntityView. Handles text alignment (left/center/ right via InsertPoint vs AlignmentPoint) and replaces AutoCAD control codes (%%p → ±, %%d → °, %%c → ⌀). MText formatting codes are stripped before display. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using ACadSharp;
|
||||||
using OpenNest.Bending;
|
using OpenNest.Bending;
|
||||||
using OpenNest.Geometry;
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
@@ -38,5 +39,11 @@ namespace OpenNest.IO
|
|||||||
/// Default drawing name (filename without extension, unless overridden).
|
/// Default drawing name (filename without extension, unless overridden).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Name { get; set; }
|
public string Name { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The raw CAD document from the source file. Available for callers
|
||||||
|
/// that need access to non-geometry entities (e.g., text annotations).
|
||||||
|
/// </summary>
|
||||||
|
public CadDocument Document { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace OpenNest.IO
|
|||||||
Bounds = dxf.Entities.GetBoundingBox(),
|
Bounds = dxf.Entities.GetBoundingBox(),
|
||||||
SourcePath = path,
|
SourcePath = path,
|
||||||
Name = options.Name ?? Path.GetFileNameWithoutExtension(path),
|
Name = options.Name ?? Path.GetFileNameWithoutExtension(path),
|
||||||
|
Document = dxf.Document,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
using System.Drawing;
|
||||||
|
using OpenNest.Geometry;
|
||||||
|
|
||||||
|
namespace OpenNest.Controls
|
||||||
|
{
|
||||||
|
public class CadText
|
||||||
|
{
|
||||||
|
public Vector Position { get; set; }
|
||||||
|
public string Value { get; set; }
|
||||||
|
public double Height { get; set; }
|
||||||
|
public double Rotation { get; set; }
|
||||||
|
public string LayerName { get; set; }
|
||||||
|
public StringAlignment HAlign { get; set; }
|
||||||
|
public StringAlignment VAlign { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,12 +29,14 @@ namespace OpenNest.Controls
|
|||||||
public List<Entity> SimplifierToleranceRight { get; set; }
|
public List<Entity> SimplifierToleranceRight { get; set; }
|
||||||
public List<Entity> OriginalEntities { get; set; }
|
public List<Entity> OriginalEntities { get; set; }
|
||||||
public bool ShowEntityLabels { get; set; }
|
public bool ShowEntityLabels { get; set; }
|
||||||
|
public List<CadText> Texts { get; set; } = new List<CadText>();
|
||||||
|
|
||||||
private readonly Pen gridPen = new Pen(Color.FromArgb(70, 70, 70));
|
private readonly Pen gridPen = new Pen(Color.FromArgb(70, 70, 70));
|
||||||
private readonly Dictionary<int, Pen> penCache = new Dictionary<int, Pen>();
|
private readonly Dictionary<int, Pen> penCache = new Dictionary<int, Pen>();
|
||||||
private readonly Font labelFont = new Font("Segoe UI", 7f);
|
private readonly Font labelFont = new Font("Segoe UI", 7f);
|
||||||
private readonly SolidBrush labelBrush = new SolidBrush(Color.FromArgb(220, 255, 255, 200));
|
private readonly SolidBrush labelBrush = new SolidBrush(Color.FromArgb(220, 255, 255, 200));
|
||||||
private readonly SolidBrush labelBackBrush = new SolidBrush(Color.FromArgb(33, 40, 48));
|
private readonly SolidBrush labelBackBrush = new SolidBrush(Color.FromArgb(33, 40, 48));
|
||||||
|
private readonly SolidBrush textBrush = new SolidBrush(Color.FromArgb(180, 200, 200, 200));
|
||||||
|
|
||||||
public event EventHandler<Line> LinePicked;
|
public event EventHandler<Line> LinePicked;
|
||||||
public event EventHandler PickCancelled;
|
public event EventHandler PickCancelled;
|
||||||
@@ -116,6 +118,8 @@ namespace OpenNest.Controls
|
|||||||
DrawEntity(e.Graphics, entity, pen);
|
DrawEntity(e.Graphics, entity, pen);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DrawTexts(e.Graphics);
|
||||||
|
|
||||||
if (ShowEntityLabels)
|
if (ShowEntityLabels)
|
||||||
DrawEntityLabels(e.Graphics);
|
DrawEntityLabels(e.Graphics);
|
||||||
|
|
||||||
@@ -408,6 +412,7 @@ namespace OpenNest.Controls
|
|||||||
labelFont.Dispose();
|
labelFont.Dispose();
|
||||||
labelBrush.Dispose();
|
labelBrush.Dispose();
|
||||||
labelBackBrush.Dispose();
|
labelBackBrush.Dispose();
|
||||||
|
textBrush.Dispose();
|
||||||
}
|
}
|
||||||
base.Dispose(disposing);
|
base.Dispose(disposing);
|
||||||
}
|
}
|
||||||
@@ -474,6 +479,34 @@ namespace OpenNest.Controls
|
|||||||
diameter);
|
diameter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DrawTexts(Graphics g)
|
||||||
|
{
|
||||||
|
if (Texts == null || Texts.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
using var sf = new StringFormat();
|
||||||
|
|
||||||
|
foreach (var text in Texts)
|
||||||
|
{
|
||||||
|
var pos = PointWorldToGraph(text.Position);
|
||||||
|
var fontSize = LengthWorldToGui(text.Height);
|
||||||
|
if (fontSize < 2f) continue;
|
||||||
|
|
||||||
|
var state = g.Save();
|
||||||
|
g.TranslateTransform(pos.X, pos.Y);
|
||||||
|
|
||||||
|
if (text.Rotation != 0)
|
||||||
|
g.RotateTransform((float)OpenNest.Math.Angle.ToDegrees(text.Rotation));
|
||||||
|
|
||||||
|
sf.Alignment = text.HAlign;
|
||||||
|
sf.LineAlignment = text.VAlign;
|
||||||
|
|
||||||
|
using var font = new Font("Segoe UI", fontSize, GraphicsUnit.Pixel);
|
||||||
|
g.DrawString(text.Value, font, textBrush, 0, 0, sf);
|
||||||
|
g.Restore(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void DrawPoint(Graphics g, Vector pt, Pen pen)
|
private void DrawPoint(Graphics g, Vector pt, Pen pen)
|
||||||
{
|
{
|
||||||
var pt1 = PointWorldToGraph(pt);
|
var pt1 = PointWorldToGraph(pt);
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ namespace OpenNest.Controls
|
|||||||
public HashSet<Guid> SuppressedEntityIds { get; set; }
|
public HashSet<Guid> SuppressedEntityIds { get; set; }
|
||||||
public Box Bounds { get; set; }
|
public Box Bounds { get; set; }
|
||||||
public int EntityCount { get; set; }
|
public int EntityCount { get; set; }
|
||||||
|
public List<CadText> Texts { get; set; } = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
public class FileListControl : Control
|
public class FileListControl : Control
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ namespace OpenNest.Forms
|
|||||||
Customer = string.Empty,
|
Customer = string.Empty,
|
||||||
Bends = result.Bends,
|
Bends = result.Bends,
|
||||||
Bounds = result.Bounds,
|
Bounds = result.Bounds,
|
||||||
EntityCount = result.Entities.Count
|
EntityCount = result.Entities.Count,
|
||||||
|
Texts = ExtractTexts(result.Document),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (InvokeRequired)
|
if (InvokeRequired)
|
||||||
@@ -152,6 +153,7 @@ namespace OpenNest.Forms
|
|||||||
entityView1.Entities.Clear();
|
entityView1.Entities.Clear();
|
||||||
entityView1.Entities.AddRange(item.Entities);
|
entityView1.Entities.AddRange(item.Entities);
|
||||||
entityView1.Bends = item.Bends ?? new List<Bend>();
|
entityView1.Bends = item.Bends ?? new List<Bend>();
|
||||||
|
entityView1.Texts = item.Texts ?? new List<CadText>();
|
||||||
|
|
||||||
item.Entities.ForEach(e => e.IsVisible = true);
|
item.Entities.ForEach(e => e.IsVisible = true);
|
||||||
if (item.Entities.Any(e => e.Layer != null))
|
if (item.Entities.Any(e => e.Layer != null))
|
||||||
@@ -804,6 +806,102 @@ namespace OpenNest.Forms
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private static List<CadText> ExtractTexts(ACadSharp.CadDocument doc)
|
||||||
|
{
|
||||||
|
var texts = new List<CadText>();
|
||||||
|
if (doc == null) return texts;
|
||||||
|
|
||||||
|
foreach (var entity in doc.Entities)
|
||||||
|
{
|
||||||
|
switch (entity)
|
||||||
|
{
|
||||||
|
case ACadSharp.Entities.MText mtext:
|
||||||
|
var (mh, mv) = MapAttachmentPoint(mtext.AttachmentPoint);
|
||||||
|
texts.Add(new CadText
|
||||||
|
{
|
||||||
|
Position = new Vector(mtext.InsertPoint.X, mtext.InsertPoint.Y),
|
||||||
|
Value = ReplaceControlCodes(StripMTextFormatting(mtext.Value)),
|
||||||
|
Height = mtext.Height,
|
||||||
|
Rotation = mtext.Rotation,
|
||||||
|
LayerName = mtext.Layer?.Name,
|
||||||
|
HAlign = mh,
|
||||||
|
VAlign = mv,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
|
||||||
|
case ACadSharp.Entities.TextEntity text:
|
||||||
|
var useAlignment = text.HorizontalAlignment != 0
|
||||||
|
|| text.VerticalAlignment != 0;
|
||||||
|
var pt = useAlignment ? text.AlignmentPoint : text.InsertPoint;
|
||||||
|
var ha = text.HorizontalAlignment switch
|
||||||
|
{
|
||||||
|
ACadSharp.Entities.TextHorizontalAlignment.Center => System.Drawing.StringAlignment.Center,
|
||||||
|
ACadSharp.Entities.TextHorizontalAlignment.Right => System.Drawing.StringAlignment.Far,
|
||||||
|
_ => System.Drawing.StringAlignment.Near,
|
||||||
|
};
|
||||||
|
texts.Add(new CadText
|
||||||
|
{
|
||||||
|
Position = new Vector(pt.X, pt.Y),
|
||||||
|
Value = ReplaceControlCodes(text.Value),
|
||||||
|
Height = text.Height,
|
||||||
|
Rotation = text.Rotation,
|
||||||
|
LayerName = text.Layer?.Name,
|
||||||
|
HAlign = ha,
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return texts;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (System.Drawing.StringAlignment h, System.Drawing.StringAlignment v) MapAttachmentPoint(
|
||||||
|
ACadSharp.Entities.AttachmentPointType apt)
|
||||||
|
{
|
||||||
|
var h = apt switch
|
||||||
|
{
|
||||||
|
ACadSharp.Entities.AttachmentPointType.TopCenter
|
||||||
|
or ACadSharp.Entities.AttachmentPointType.MiddleCenter
|
||||||
|
or ACadSharp.Entities.AttachmentPointType.BottomCenter => System.Drawing.StringAlignment.Center,
|
||||||
|
ACadSharp.Entities.AttachmentPointType.TopRight
|
||||||
|
or ACadSharp.Entities.AttachmentPointType.MiddleRight
|
||||||
|
or ACadSharp.Entities.AttachmentPointType.BottomRight => System.Drawing.StringAlignment.Far,
|
||||||
|
_ => System.Drawing.StringAlignment.Near,
|
||||||
|
};
|
||||||
|
var v = apt switch
|
||||||
|
{
|
||||||
|
ACadSharp.Entities.AttachmentPointType.MiddleLeft
|
||||||
|
or ACadSharp.Entities.AttachmentPointType.MiddleCenter
|
||||||
|
or ACadSharp.Entities.AttachmentPointType.MiddleRight => System.Drawing.StringAlignment.Center,
|
||||||
|
ACadSharp.Entities.AttachmentPointType.BottomLeft
|
||||||
|
or ACadSharp.Entities.AttachmentPointType.BottomCenter
|
||||||
|
or ACadSharp.Entities.AttachmentPointType.BottomRight => System.Drawing.StringAlignment.Far,
|
||||||
|
_ => System.Drawing.StringAlignment.Near,
|
||||||
|
};
|
||||||
|
return (h, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string StripMTextFormatting(string text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text)) return text;
|
||||||
|
var result = System.Text.RegularExpressions.Regex.Replace(text, @"\\[A-Za-z][^;]*;", "");
|
||||||
|
result = result.Replace("{", "").Replace("}", "");
|
||||||
|
return result.Trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string ReplaceControlCodes(string text)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(text)) return text;
|
||||||
|
return text
|
||||||
|
.Replace("%%p", "±")
|
||||||
|
.Replace("%%P", "±")
|
||||||
|
.Replace("%%d", "°")
|
||||||
|
.Replace("%%D", "°")
|
||||||
|
.Replace("%%c", "⌀")
|
||||||
|
.Replace("%%C", "⌀")
|
||||||
|
.Replace("%%%", "%");
|
||||||
|
}
|
||||||
|
|
||||||
private void filterPanel_Paint(object sender, PaintEventArgs e)
|
private void filterPanel_Paint(object sender, PaintEventArgs e)
|
||||||
{
|
{
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user