feat: validate p-rec-annot connector view eligibility

This commit is contained in:
codex
2026-08-07 09:48:22 +03:00
parent acfa6723c9
commit 01a0b711d0
3 changed files with 67 additions and 0 deletions
@@ -122,6 +122,7 @@ public interface IPlantConnectorPortReader
{ {
PlantConnectorPortReadResult Read(PlantConnectorRelationshipSnapshot connector); PlantConnectorPortReadResult Read(PlantConnectorRelationshipSnapshot connector);
} }
public sealed record ConnectorViewportEligibility(bool Eligible, Point3Dto? PlacementPoint, string? FailureCode = null);
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,34 @@
using PRecAnnot.Abstractions;
namespace PRecAnnot.Core;
// Port of AutoLinenumberConnections.IsOKPrepareAnnotation's geometry gate.
// The caller performs model-to-paper transformation only after the connector
// is eligible, preserving legacy model-space containment semantics.
public sealed class ConnectorViewportEligibilityEvaluator
{
public ConnectorViewportEligibility Evaluate(PlantConnectorPortSnapshot ports, Point3Dto viewDirection, Bounds3Dto viewExtents, double parallelTolerance = 0.1)
{
if (parallelTolerance < 0) throw new ArgumentOutOfRangeException(nameof(parallelTolerance));
if (Parallel(ports.FirstDirection, viewDirection, parallelTolerance) || Parallel(ports.SecondDirection, viewDirection, parallelTolerance))
return new(false, null, "CONNECTOR_PORT_PARALLEL_TO_VIEW");
if (!Inside(ports.FirstPosition, viewExtents) || !Inside(ports.SecondPosition, viewExtents))
return new(false, null, "CONNECTOR_OUTSIDE_VIEW_EXTENTS");
// Legacy stores ports[1] as the insertion/model anchor.
return new(true, ports.SecondPosition);
}
private static bool Inside(Point3Dto point, Bounds3Dto bounds) =>
point.X >= bounds.Minimum.X && point.X <= bounds.Maximum.X &&
point.Y >= bounds.Minimum.Y && point.Y <= bounds.Maximum.Y &&
point.Z >= bounds.Minimum.Z && point.Z <= bounds.Maximum.Z;
private static bool Parallel(Point3Dto first, Point3Dto second, double tolerance)
{
var firstLength = Math.Sqrt(first.X * first.X + first.Y * first.Y + first.Z * first.Z);
var secondLength = Math.Sqrt(second.X * second.X + second.Y * second.Y + second.Z * second.Z);
if (firstLength == 0 || secondLength == 0) return true;
var cosine = Math.Abs((first.X * second.X + first.Y * second.Y + first.Z * second.Z) / (firstLength * secondLength));
return 1d - cosine <= tolerance;
}
}
@@ -0,0 +1,32 @@
using PRecAnnot.Abstractions;
using PRecAnnot.Core;
namespace PRecAnnot.Core.Tests;
public sealed class ConnectorViewportEligibilityEvaluatorTests
{
[Fact]
public void UsesSecondPortAsLegacyPlacementAnchorWhenBothPortsAreVisible()
{
var result = new ConnectorViewportEligibilityEvaluator().Evaluate(Ports(new(1, 2, 3), new(4, 5, 6), new(1, 0, 0), new(0, 1, 0)), new(0, 0, 1), Bounds());
Assert.True(result.Eligible);
Assert.Equal(new Point3Dto(4, 5, 6), result.PlacementPoint);
}
[Fact]
public void RejectsPortParallelToViewDirection()
{
var result = new ConnectorViewportEligibilityEvaluator().Evaluate(Ports(new(), new(), new(0, 0, 1), new(1, 0, 0)), new(0, 0, 1), Bounds());
Assert.Equal("CONNECTOR_PORT_PARALLEL_TO_VIEW", result.FailureCode);
}
[Fact]
public void RejectsConnectorWhenAnyPortLiesOutsideModelViewExtents()
{
var result = new ConnectorViewportEligibilityEvaluator().Evaluate(Ports(new(0, 0, 0), new(11, 0, 0), new(1, 0, 0), new(0, 1, 0)), new(0, 0, 1), Bounds());
Assert.Equal("CONNECTOR_OUTSIDE_VIEW_EXTENTS", result.FailureCode);
}
private static Bounds3Dto Bounds() => new(new(-10, -10, -10), new(10, 10, 10));
private static PlantConnectorPortSnapshot Ports(Point3Dto firstPosition, Point3Dto secondPosition, Point3Dto firstDirection, Point3Dto secondDirection) => new("c", firstPosition, secondPosition, firstDirection, secondDirection);
}