feat: port p-rec-annot line connection boundary rules
This commit is contained in:
@@ -70,6 +70,18 @@ public sealed record ElevationAnnotationOperation(string StableId, AnnotationOpe
|
|||||||
public sealed record ElevationAnnotationPlan(IReadOnlyList<ElevationAnnotationOperation> Operations);
|
public sealed record ElevationAnnotationPlan(IReadOnlyList<ElevationAnnotationOperation> Operations);
|
||||||
public sealed record LineConnectionCandidate(string StableId, string LineNumber, Point3Dto FirstEndpoint, Point3Dto SecondEndpoint, Bounds3Dto Bounds, AnnotationPoint Position, double Rotation);
|
public sealed record LineConnectionCandidate(string StableId, string LineNumber, Point3Dto FirstEndpoint, Point3Dto SecondEndpoint, Bounds3Dto Bounds, AnnotationPoint Position, double Rotation);
|
||||||
public sealed record LineConnectionGroup(string LineNumber, IReadOnlyList<LineConnectionCandidate> Members, AnnotationPoint Placement, double Rotation);
|
public sealed record LineConnectionGroup(string LineNumber, IReadOnlyList<LineConnectionCandidate> Members, AnnotationPoint Placement, double Rotation);
|
||||||
|
// A legacy PXP-ALC annotation belongs to a connector between two *different*
|
||||||
|
// line groups; it is not a duplicate marker for one line group.
|
||||||
|
public sealed record LineConnectionBoundaryCandidate(
|
||||||
|
string StableId,
|
||||||
|
string FirstLineNumber,
|
||||||
|
string SecondLineNumber,
|
||||||
|
Point3Dto FirstEndpoint,
|
||||||
|
Point3Dto SecondEndpoint,
|
||||||
|
Bounds3Dto Bounds,
|
||||||
|
AnnotationPoint Position,
|
||||||
|
double Rotation);
|
||||||
|
public sealed record LineConnectionBoundaryPlan(string StableId, string Text, AnnotationPoint Position, double Rotation, IReadOnlyList<string> Reasons);
|
||||||
public sealed record LineNumberOwnedRow(string SourceStableId, int MarkerNumber, string Text, AnnotationPoint MarkerAttributePosition);
|
public sealed record LineNumberOwnedRow(string SourceStableId, int MarkerNumber, string Text, AnnotationPoint MarkerAttributePosition);
|
||||||
public sealed record LineNumberRowMutation(string SourceStableId, int PreviousMarkerNumber, int MarkerNumber, string Text, AnnotationPoint? MarkerAttributePosition);
|
public sealed record LineNumberRowMutation(string SourceStableId, int PreviousMarkerNumber, int MarkerNumber, string Text, AnnotationPoint? MarkerAttributePosition);
|
||||||
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);
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
using PRecAnnot.Abstractions;
|
||||||
|
|
||||||
|
namespace PRecAnnot.Core;
|
||||||
|
|
||||||
|
// Source-backed PXP-ALC decision logic: only a connector whose related parts
|
||||||
|
// resolve to different line groups produces an annotation. The connector is
|
||||||
|
// the stable ownership identity, preserving repeat/update behaviour.
|
||||||
|
public sealed class LineConnectionBoundaryPlanner
|
||||||
|
{
|
||||||
|
public IReadOnlyList<LineConnectionBoundaryPlan> Build(IEnumerable<LineConnectionBoundaryCandidate> candidates)
|
||||||
|
{
|
||||||
|
var seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||||
|
var result = new List<LineConnectionBoundaryPlan>();
|
||||||
|
foreach (var candidate in candidates.OrderBy(value => value.StableId, StringComparer.Ordinal))
|
||||||
|
{
|
||||||
|
if (!seen.Add(candidate.StableId))
|
||||||
|
{
|
||||||
|
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["DUPLICATE_CONNECTOR_CANDIDATE"]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var first = candidate.FirstLineNumber?.Trim() ?? string.Empty;
|
||||||
|
var second = candidate.SecondLineNumber?.Trim() ?? string.Empty;
|
||||||
|
if (first.Length == 0 || second.Length == 0)
|
||||||
|
{
|
||||||
|
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["LINE_GROUP_NOT_RESOLVED"]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (string.Equals(first, second, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["SAME_LINE_GROUP"]));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var ordered = new[] { first, second }.OrderBy(value => value, StringComparer.OrdinalIgnoreCase).ToArray();
|
||||||
|
result.Add(new(candidate.StableId, string.Join(" / ", ordered), candidate.Position, candidate.Rotation, Array.Empty<string>()));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
+28
@@ -0,0 +1,28 @@
|
|||||||
|
using PRecAnnot.Abstractions;
|
||||||
|
using PRecAnnot.Core;
|
||||||
|
|
||||||
|
namespace PRecAnnot.Core.Tests;
|
||||||
|
|
||||||
|
public sealed class LineConnectionBoundaryPlannerTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
public void EmitsSortedTextOnlyForDifferentLineGroups()
|
||||||
|
{
|
||||||
|
var result = new LineConnectionBoundaryPlanner().Build([
|
||||||
|
Candidate("b", "L-200", "L-100"), Candidate("a", "L-100", "L-100")]);
|
||||||
|
Assert.Equal("a", result[0].StableId);
|
||||||
|
Assert.Contains("SAME_LINE_GROUP", result[0].Reasons);
|
||||||
|
Assert.Equal("L-100 / L-200", result[1].Text);
|
||||||
|
Assert.Empty(result[1].Reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RejectsDuplicateAndMissingLineGroupsDeterministically()
|
||||||
|
{
|
||||||
|
var result = new LineConnectionBoundaryPlanner().Build([Candidate("a", "", "L-2"), Candidate("a", "L-1", "L-2")]);
|
||||||
|
Assert.Contains("LINE_GROUP_NOT_RESOLVED", result[0].Reasons);
|
||||||
|
Assert.Contains("DUPLICATE_CONNECTOR_CANDIDATE", result[1].Reasons);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LineConnectionBoundaryCandidate Candidate(string id, string first, string second) => new(id, first, second, new(), new(1, 1, 1), new(new(), new(1, 1, 1)), new(2, 3), 0);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user