feat: add p-rec-annot line-number marker writer
This commit is contained in:
@@ -59,7 +59,8 @@ public sealed record LineNumberCandidate(string StableId, long RowId, Annotation
|
|||||||
public sealed record ExistingLineNumberAnnotation(string StableId, string SourceStableId, string Text);
|
public sealed record ExistingLineNumberAnnotation(string StableId, string SourceStableId, string Text);
|
||||||
public sealed record LineNumberCommandOperation(string StableId, AnnotationOperationKind Kind, string? Text, AnnotationPoint Position, double Rotation, string Layer, string TextStyle, IReadOnlyList<string> Reasons);
|
public sealed record LineNumberCommandOperation(string StableId, AnnotationOperationKind Kind, string? Text, AnnotationPoint Position, double Rotation, string Layer, string TextStyle, IReadOnlyList<string> Reasons);
|
||||||
public sealed record LineNumberCommandPlan(IReadOnlyList<LineNumberCommandOperation> Operations);
|
public sealed record LineNumberCommandPlan(IReadOnlyList<LineNumberCommandOperation> Operations);
|
||||||
public sealed record LineNumberApplyPreflight(string StableId, string Text, AnnotationPoint Position, double Rotation, string Layer, string TextStyle);
|
public sealed record LineNumberApplyPreflight(string StableId, int MarkerNumber, string Text, AnnotationPoint Position, double Rotation, string Layer, string TextStyle);
|
||||||
|
public sealed record LineNumberApplyResult(int Created, int Updated, int Unchanged);
|
||||||
public sealed record LineNumberTextAsset(string Layer, string TextStyle, double Size, string TextValue, Point2Dto Position);
|
public sealed record LineNumberTextAsset(string Layer, string TextStyle, double Size, string TextValue, Point2Dto Position);
|
||||||
public sealed record LineNumberLineAsset(string Layer, Point2Dto Start, Point2Dto End);
|
public sealed record LineNumberLineAsset(string Layer, Point2Dto Start, Point2Dto End);
|
||||||
public sealed record LineNumberMarkerAsset(string Layer, double MaskScaleFactor, IReadOnlyList<LineNumberTextAsset> Texts, IReadOnlyList<LineNumberLineAsset> Lines);
|
public sealed record LineNumberMarkerAsset(string Layer, double MaskScaleFactor, IReadOnlyList<LineNumberTextAsset> Texts, IReadOnlyList<LineNumberLineAsset> Lines);
|
||||||
|
|||||||
@@ -21,5 +21,5 @@ public sealed class LineNumberCommandPlanner(ILineNumberAnnotationFormatter form
|
|||||||
public static IReadOnlyList<LineNumberApplyPreflight> PrepareApply(LineNumberCommandPlan plan) => plan.Operations
|
public static IReadOnlyList<LineNumberApplyPreflight> PrepareApply(LineNumberCommandPlan plan) => plan.Operations
|
||||||
.Where(x => x.Kind is AnnotationOperationKind.Create or AnnotationOperationKind.Update)
|
.Where(x => x.Kind is AnnotationOperationKind.Create or AnnotationOperationKind.Update)
|
||||||
.OrderBy(x => x.StableId, StringComparer.Ordinal)
|
.OrderBy(x => x.StableId, StringComparer.Ordinal)
|
||||||
.Select(x => new LineNumberApplyPreflight(x.StableId, x.Text!, x.Position, x.Rotation, x.Layer, x.TextStyle)).ToArray();
|
.Select((x, index) => new LineNumberApplyPreflight(x.StableId, index + 1, x.Text!, x.Position, x.Rotation, x.Layer, x.TextStyle)).ToArray();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,172 @@
|
|||||||
|
using Autodesk.AutoCAD.ApplicationServices;
|
||||||
|
using Autodesk.AutoCAD.DatabaseServices;
|
||||||
|
using Autodesk.AutoCAD.Geometry;
|
||||||
|
using PRecAnnot.Abstractions;
|
||||||
|
|
||||||
|
namespace PRecAnnot.Plant3D2026;
|
||||||
|
|
||||||
|
internal sealed class LineNumberAnnotationWriter
|
||||||
|
{
|
||||||
|
private const string ApplicationName = "PXP-LN";
|
||||||
|
private const string MarkerBlockName = "LN_MARKER";
|
||||||
|
private const string MarkerAttributeTag = "NUMBER";
|
||||||
|
|
||||||
|
public LineNumberApplyResult Apply(Document document, IReadOnlyList<LineNumberApplyPreflight> plan, AnnotateLineNumbersAssets assets)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(document);
|
||||||
|
ArgumentNullException.ThrowIfNull(plan);
|
||||||
|
ArgumentNullException.ThrowIfNull(assets);
|
||||||
|
if (plan.Select(item => item.StableId).Distinct(StringComparer.Ordinal).Count() != plan.Count)
|
||||||
|
throw new InvalidOperationException("LINE_NUMBER_APPLY_DUPLICATE_STABLE_ID");
|
||||||
|
|
||||||
|
var created = 0;
|
||||||
|
var updated = 0;
|
||||||
|
var unchanged = 0;
|
||||||
|
var database = document.Database;
|
||||||
|
using var documentLock = document.LockDocument();
|
||||||
|
using var transaction = database.TransactionManager.StartTransaction();
|
||||||
|
var blocks = (BlockTable)transaction.GetObject(database.BlockTableId, OpenMode.ForRead);
|
||||||
|
ValidateAssets(transaction, database, assets.Marker);
|
||||||
|
EnsureRegApp(transaction, database);
|
||||||
|
var markerDefinition = EnsureMarkerDefinition(transaction, database, blocks, assets.Marker);
|
||||||
|
var paper = (BlockTableRecord)transaction.GetObject(blocks[BlockTableRecord.PaperSpace], OpenMode.ForWrite);
|
||||||
|
|
||||||
|
foreach (var item in plan.OrderBy(value => value.MarkerNumber))
|
||||||
|
{
|
||||||
|
var existing = FindOwnedMarker(transaction, paper, item.StableId);
|
||||||
|
if (existing is null)
|
||||||
|
{
|
||||||
|
CreateMarker(transaction, paper, markerDefinition, item, assets.Marker);
|
||||||
|
created++;
|
||||||
|
}
|
||||||
|
else if (UpdateMarker(transaction, existing, item, assets.Marker.Layer)) updated++;
|
||||||
|
else unchanged++;
|
||||||
|
}
|
||||||
|
|
||||||
|
transaction.Commit();
|
||||||
|
return new(created, updated, unchanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateAssets(Transaction transaction, Database database, LineNumberMarkerAsset marker)
|
||||||
|
{
|
||||||
|
var layers = (LayerTable)transaction.GetObject(database.LayerTableId, OpenMode.ForRead);
|
||||||
|
var styles = (TextStyleTable)transaction.GetObject(database.TextStyleTableId, OpenMode.ForRead);
|
||||||
|
foreach (var layer in marker.Texts.Select(value => value.Layer).Append(marker.Layer).Concat(marker.Lines.Select(value => value.Layer)).Where(value => !string.IsNullOrWhiteSpace(value)).Distinct(StringComparer.OrdinalIgnoreCase))
|
||||||
|
if (!layers.Has(layer)) throw new InvalidOperationException($"LINE_NUMBER_LAYER_MISSING:{layer}");
|
||||||
|
foreach (var style in marker.Texts.Select(value => value.TextStyle).Where(value => !string.IsNullOrWhiteSpace(value)).Distinct(StringComparer.OrdinalIgnoreCase))
|
||||||
|
if (!styles.Has(style)) throw new InvalidOperationException($"LINE_NUMBER_TEXT_STYLE_MISSING:{style}");
|
||||||
|
if (marker.Texts.Count == 0) throw new InvalidOperationException("LINE_NUMBER_MARKER_TEXT_ASSET_MISSING");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ObjectId EnsureMarkerDefinition(Transaction transaction, Database database, BlockTable blocks, LineNumberMarkerAsset marker)
|
||||||
|
{
|
||||||
|
if (blocks.Has(MarkerBlockName)) return blocks[MarkerBlockName];
|
||||||
|
blocks.UpgradeOpen();
|
||||||
|
using var definition = new BlockTableRecord { Name = MarkerBlockName, Origin = Point3d.Origin };
|
||||||
|
var definitionId = blocks.Add(definition);
|
||||||
|
transaction.AddNewlyCreatedDBObject(definition, true);
|
||||||
|
var layers = (LayerTable)transaction.GetObject(database.LayerTableId, OpenMode.ForRead);
|
||||||
|
var styles = (TextStyleTable)transaction.GetObject(database.TextStyleTableId, OpenMode.ForRead);
|
||||||
|
|
||||||
|
foreach (var text in marker.Texts)
|
||||||
|
{
|
||||||
|
using var attribute = new AttributeDefinition(
|
||||||
|
new Point3d(text.Position.X, text.Position.Y, 0), text.TextValue, MarkerAttributeTag, "Номер маркера:", styles[text.TextStyle])
|
||||||
|
{
|
||||||
|
Height = text.Size,
|
||||||
|
IsMTextAttributeDefinition = true,
|
||||||
|
LayerId = layers[text.Layer]
|
||||||
|
};
|
||||||
|
definition.AppendEntity(attribute);
|
||||||
|
transaction.AddNewlyCreatedDBObject(attribute, true);
|
||||||
|
}
|
||||||
|
foreach (var source in marker.Lines)
|
||||||
|
{
|
||||||
|
using var line = new Line(new Point3d(source.Start.X, source.Start.Y, 0), new Point3d(source.End.X, source.End.Y, 0)) { LayerId = layers[source.Layer] };
|
||||||
|
definition.AppendEntity(line);
|
||||||
|
transaction.AddNewlyCreatedDBObject(line, true);
|
||||||
|
}
|
||||||
|
return definitionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void CreateMarker(Transaction transaction, BlockTableRecord paper, ObjectId definitionId, LineNumberApplyPreflight item, LineNumberMarkerAsset marker)
|
||||||
|
{
|
||||||
|
using var reference = new BlockReference(new Point3d(item.Position.X, item.Position.Y, item.Position.Z), definitionId)
|
||||||
|
{
|
||||||
|
Rotation = item.Rotation,
|
||||||
|
Layer = marker.Layer
|
||||||
|
};
|
||||||
|
paper.AppendEntity(reference);
|
||||||
|
transaction.AddNewlyCreatedDBObject(reference, true);
|
||||||
|
reference.XData = Ownership(item);
|
||||||
|
|
||||||
|
var definition = (BlockTableRecord)transaction.GetObject(definitionId, OpenMode.ForRead);
|
||||||
|
foreach (ObjectId id in definition)
|
||||||
|
{
|
||||||
|
if (transaction.GetObject(id, OpenMode.ForRead) is not AttributeDefinition source || source.Constant) continue;
|
||||||
|
using var attribute = new AttributeReference();
|
||||||
|
attribute.SetAttributeFromBlock(source, reference.BlockTransform);
|
||||||
|
attribute.TextString = item.MarkerNumber.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
attribute.XData = new ResultBuffer(
|
||||||
|
new TypedValue((int)DxfCode.ExtendedDataRegAppName, ApplicationName),
|
||||||
|
new TypedValue((int)DxfCode.ExtendedDataAsciiString, "MARKER"),
|
||||||
|
new TypedValue((int)DxfCode.ExtendedDataInteger32, item.MarkerNumber));
|
||||||
|
reference.AttributeCollection.AppendAttribute(attribute);
|
||||||
|
transaction.AddNewlyCreatedDBObject(attribute, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static BlockReference? FindOwnedMarker(Transaction transaction, BlockTableRecord paper, string stableId)
|
||||||
|
{
|
||||||
|
foreach (ObjectId id in paper)
|
||||||
|
{
|
||||||
|
if (transaction.GetObject(id, OpenMode.ForRead) is not BlockReference reference) continue;
|
||||||
|
using var data = reference.GetXDataForApplication(ApplicationName);
|
||||||
|
var values = data?.AsArray();
|
||||||
|
if (values?.Any(value => value.TypeCode == (int)DxfCode.ExtendedDataAsciiString && string.Equals(value.Value as string, stableId, StringComparison.Ordinal)) == true)
|
||||||
|
return reference;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool UpdateMarker(Transaction transaction, BlockReference marker, LineNumberApplyPreflight item, string layer)
|
||||||
|
{
|
||||||
|
var position = new Point3d(item.Position.X, item.Position.Y, item.Position.Z);
|
||||||
|
using var existingOwnership = marker.GetXDataForApplication(ApplicationName);
|
||||||
|
var ownership = existingOwnership?.AsArray() ?? [];
|
||||||
|
var changed = marker.Position != position || marker.Rotation != item.Rotation ||
|
||||||
|
!string.Equals(marker.Layer, layer, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
!ownership.Any(value => value.TypeCode == (int)DxfCode.ExtendedDataAsciiString && string.Equals(value.Value as string, item.Text, StringComparison.Ordinal));
|
||||||
|
marker.UpgradeOpen();
|
||||||
|
marker.Position = position;
|
||||||
|
marker.Rotation = item.Rotation;
|
||||||
|
marker.Layer = layer;
|
||||||
|
marker.XData = Ownership(item);
|
||||||
|
foreach (ObjectId id in marker.AttributeCollection)
|
||||||
|
{
|
||||||
|
var attribute = (AttributeReference)transaction.GetObject(id, OpenMode.ForWrite);
|
||||||
|
if (!string.Equals(attribute.Tag, MarkerAttributeTag, StringComparison.OrdinalIgnoreCase)) continue;
|
||||||
|
var value = item.MarkerNumber.ToString(System.Globalization.CultureInfo.InvariantCulture);
|
||||||
|
changed |= !string.Equals(attribute.TextString, value, StringComparison.Ordinal);
|
||||||
|
attribute.TextString = value;
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ResultBuffer Ownership(LineNumberApplyPreflight item) => new(
|
||||||
|
new TypedValue((int)DxfCode.ExtendedDataRegAppName, ApplicationName),
|
||||||
|
new TypedValue((int)DxfCode.ExtendedDataAsciiString, item.StableId),
|
||||||
|
new TypedValue((int)DxfCode.ExtendedDataAsciiString, "MARKER"),
|
||||||
|
new TypedValue((int)DxfCode.ExtendedDataInteger32, item.MarkerNumber),
|
||||||
|
new TypedValue((int)DxfCode.ExtendedDataAsciiString, item.Text));
|
||||||
|
|
||||||
|
private static void EnsureRegApp(Transaction transaction, Database database)
|
||||||
|
{
|
||||||
|
var table = (RegAppTable)transaction.GetObject(database.RegAppTableId, OpenMode.ForRead);
|
||||||
|
if (table.Has(ApplicationName)) return;
|
||||||
|
table.UpgradeOpen();
|
||||||
|
using var record = new RegAppTableRecord { Name = ApplicationName };
|
||||||
|
table.Add(record);
|
||||||
|
transaction.AddNewlyCreatedDBObject(record, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@ public sealed class LineNumberCommandPlannerTests
|
|||||||
var c = new LineNumberCandidate("b", 1, new(0,0), 0, "PXP", "Standard", new Dictionary<string,string?> { ["EngineeringItems.LineNumber"]="L-2" });
|
var c = new LineNumberCandidate("b", 1, new(0,0), 0, "PXP", "Standard", new Dictionary<string,string?> { ["EngineeringItems.LineNumber"]="L-2" });
|
||||||
var plan = new LineNumberCommandPlanner(new LineNumberAnnotationFormatter()).Build(def, [c, c, c with { StableId="a" }], [new("x", "b", "old")]);
|
var plan = new LineNumberCommandPlanner(new LineNumberAnnotationFormatter()).Build(def, [c, c, c with { StableId="a" }], [new("x", "b", "old")]);
|
||||||
Assert.Collection(plan.Operations, x => Assert.Equal(AnnotationOperationKind.Create,x.Kind), x => Assert.Equal(AnnotationOperationKind.Update,x.Kind), x => Assert.Equal(AnnotationOperationKind.Conflict,x.Kind));
|
Assert.Collection(plan.Operations, x => Assert.Equal(AnnotationOperationKind.Create,x.Kind), x => Assert.Equal(AnnotationOperationKind.Update,x.Kind), x => Assert.Equal(AnnotationOperationKind.Conflict,x.Kind));
|
||||||
Assert.Equal(new[] { "a", "b" }, LineNumberCommandPlanner.PrepareApply(plan).Select(x => x.StableId));
|
var apply = LineNumberCommandPlanner.PrepareApply(plan);
|
||||||
|
Assert.Equal(new[] { "a", "b" }, apply.Select(x => x.StableId));
|
||||||
|
Assert.Equal(new[] { 1, 2 }, apply.Select(x => x.MarkerNumber));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user