feat: port p-rec-annot ortho viewport maintenance foundation
This commit is contained in:
@@ -85,7 +85,7 @@ public sealed record LineConnectionBoundaryCandidate(
|
|||||||
double Rotation,
|
double Rotation,
|
||||||
string? FirstLineGroupStableId = null,
|
string? FirstLineGroupStableId = null,
|
||||||
string? SecondLineGroupStableId = null);
|
string? SecondLineGroupStableId = null);
|
||||||
public sealed record LineConnectionBoundaryPlan(string StableId, string Text, AnnotationPoint Position, double Rotation, IReadOnlyList<string> Reasons, string? FirstText = null, string? SecondText = null);
|
public sealed record LineConnectionBoundaryPlan(string StableId, string Text, AnnotationPoint Position, double Rotation, IReadOnlyList<string> Reasons, string? FirstText = null, string? SecondText = null, Point3Dto? ConnectorPosition = null);
|
||||||
public sealed record LineConnectionAssets(string PolylineLayer, string TextLayer, string TextStyle, string BlockName = "BLOCK_LINENUMBER_CONNECTION");
|
public sealed record LineConnectionAssets(string PolylineLayer, string TextLayer, string TextStyle, string BlockName = "BLOCK_LINENUMBER_CONNECTION");
|
||||||
public sealed record LineConnectionApplyResult(int Deleted, int Created);
|
public sealed record LineConnectionApplyResult(int Deleted, int Created);
|
||||||
// The geometry adapter supplies the paper-space connector point and orientation.
|
// The geometry adapter supplies the paper-space connector point and orientation.
|
||||||
@@ -154,6 +154,7 @@ public sealed record ComponentGeometryDefinition(
|
|||||||
Bounds3Dto Bounds,
|
Bounds3Dto Bounds,
|
||||||
bool ImperialDimensions);
|
bool ImperialDimensions);
|
||||||
public sealed record ComponentGeometryReadResult(bool Success, ComponentGeometryDefinition? Geometry, string? FailureCode = null);
|
public sealed record ComponentGeometryReadResult(bool Success, ComponentGeometryDefinition? Geometry, string? FailureCode = null);
|
||||||
|
public sealed record ViewportLayerPlan(bool RequiresChange, string TargetLayer, string? FailureCode = null);
|
||||||
public interface IPlantComponentGeometryReader
|
public interface IPlantComponentGeometryReader
|
||||||
{
|
{
|
||||||
ComponentGeometryReadResult Read(string stableEntityId, bool convertToMetric);
|
ComponentGeometryReadResult Read(string stableEntityId, bool convertToMetric);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public sealed class LineConnectionBoundaryPlanner
|
|||||||
{
|
{
|
||||||
if (!seen.Add(candidate.StableId))
|
if (!seen.Add(candidate.StableId))
|
||||||
{
|
{
|
||||||
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["DUPLICATE_CONNECTOR_CANDIDATE"]));
|
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["DUPLICATE_CONNECTOR_CANDIDATE"], ConnectorPosition: candidate.FirstEndpoint));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var first = candidate.FirstLineNumber?.Trim() ?? string.Empty;
|
var first = candidate.FirstLineNumber?.Trim() ?? string.Empty;
|
||||||
@@ -27,21 +27,21 @@ public sealed class LineConnectionBoundaryPlanner
|
|||||||
if (!string.IsNullOrWhiteSpace(candidate.FirstLineGroupStableId) &&
|
if (!string.IsNullOrWhiteSpace(candidate.FirstLineGroupStableId) &&
|
||||||
string.Equals(candidate.FirstLineGroupStableId, candidate.SecondLineGroupStableId, StringComparison.OrdinalIgnoreCase))
|
string.Equals(candidate.FirstLineGroupStableId, candidate.SecondLineGroupStableId, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["SAME_LINE_GROUP"]));
|
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["SAME_LINE_GROUP"], ConnectorPosition: candidate.FirstEndpoint));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (first.Length == 0 || second.Length == 0)
|
if (first.Length == 0 || second.Length == 0)
|
||||||
{
|
{
|
||||||
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["LINE_GROUP_NOT_RESOLVED"]));
|
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["LINE_GROUP_NOT_RESOLVED"], ConnectorPosition: candidate.FirstEndpoint));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (string.Equals(first, second, StringComparison.OrdinalIgnoreCase))
|
if (string.Equals(first, second, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["SAME_LINE_GROUP"]));
|
result.Add(new(candidate.StableId, string.Empty, candidate.Position, candidate.Rotation, ["SAME_LINE_GROUP"], ConnectorPosition: candidate.FirstEndpoint));
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var ordered = new[] { first, second }.OrderBy(value => value, StringComparer.OrdinalIgnoreCase).ToArray();
|
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>(), first, second));
|
result.Add(new(candidate.StableId, string.Join(" / ", ordered), candidate.Position, candidate.Rotation, Array.Empty<string>(), first, second, candidate.FirstEndpoint));
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
using PRecAnnot.Abstractions;
|
||||||
|
|
||||||
|
namespace PRecAnnot.Core;
|
||||||
|
|
||||||
|
public sealed class ViewportLayerPlanner
|
||||||
|
{
|
||||||
|
public ViewportLayerPlan Build(string? currentLayer, bool targetLayerExists, string targetLayer = "Defpoints")
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(targetLayer)) return new(false, string.Empty, "VIEWPORT_TARGET_LAYER_INVALID");
|
||||||
|
if (!targetLayerExists) return new(false, targetLayer, "VIEWPORT_TARGET_LAYER_MISSING");
|
||||||
|
return new(!string.Equals(currentLayer, targetLayer, StringComparison.OrdinalIgnoreCase), targetLayer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -47,6 +47,7 @@ public sealed class EntryPoint : IExtensionApplication
|
|||||||
[CommandMethod("PXP-ALC")] public void AutoLineNumberConnectionsCommand() => LineConnectionCommand.Run();
|
[CommandMethod("PXP-ALC")] public void AutoLineNumberConnectionsCommand() => LineConnectionCommand.Run();
|
||||||
[CommandMethod("PXP-AE")] public void AutoElevationCommand() => ElevationCommands.RunAutomatic();
|
[CommandMethod("PXP-AE")] public void AutoElevationCommand() => ElevationCommands.RunAutomatic();
|
||||||
[CommandMethod("PXP-IE")] public void InsertElevationCommand() => ElevationCommands.RunManual();
|
[CommandMethod("PXP-IE")] public void InsertElevationCommand() => ElevationCommands.RunManual();
|
||||||
|
[CommandMethod("PXP-COV")] public void CheckOrthoViewportCommand() => OrthoMaintenanceCommands.CheckViewport();
|
||||||
|
|
||||||
[CommandMethod("PHP_REC_AUTOMATION_START", CommandFlags.Session)]
|
[CommandMethod("PHP_REC_AUTOMATION_START", CommandFlags.Session)]
|
||||||
public void AutomationStartCommand() => FirstLimitedApplyAutomationBootstrap.Arm();
|
public void AutomationStartCommand() => FirstLimitedApplyAutomationBootstrap.Arm();
|
||||||
|
|||||||
@@ -49,6 +49,16 @@ internal sealed class LineConnectionAnnotationWriter
|
|||||||
attribute.LayerId = layers[assets.TextLayer]; attribute.TextStyleId = styles[assets.TextStyle];
|
attribute.LayerId = layers[assets.TextLayer]; attribute.TextStyleId = styles[assets.TextStyle];
|
||||||
attribute.UpdateMTextAttribute(); reference.AttributeCollection.AppendAttribute(attribute); transaction.AddNewlyCreatedDBObject(attribute, true);
|
attribute.UpdateMTextAttribute(); reference.AttributeCollection.AppendAttribute(attribute); transaction.AddNewlyCreatedDBObject(attribute, true);
|
||||||
}
|
}
|
||||||
|
if (plan.ConnectorPosition is { } connector)
|
||||||
|
{
|
||||||
|
using var leader = new Polyline(3) { LayerId = layers[assets.PolylineLayer], XData = Ownership(viewportStableId, plan) };
|
||||||
|
leader.AddVertexAt(0, new Point2d(connector.X, connector.Y), 0, 0, 0);
|
||||||
|
// Legacy PXP-ALC uses an orthogonal two-segment leader. Its
|
||||||
|
// elbow shares the annotation Y coordinate for a stable route.
|
||||||
|
leader.AddVertexAt(1, new Point2d(connector.X, plan.Position.Y), 0, 0, 0);
|
||||||
|
leader.AddVertexAt(2, new Point2d(plan.Position.X, plan.Position.Y), 0, 0, 0);
|
||||||
|
paper.AppendEntity(leader); transaction.AddNewlyCreatedDBObject(leader, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
transaction.Commit();
|
transaction.Commit();
|
||||||
return new(deleted, insertable.Length);
|
return new(deleted, insertable.Length);
|
||||||
|
|||||||
@@ -0,0 +1,67 @@
|
|||||||
|
using Autodesk.AutoCAD.ApplicationServices;
|
||||||
|
using Autodesk.AutoCAD.DatabaseServices;
|
||||||
|
using Autodesk.AutoCAD.EditorInput;
|
||||||
|
using Autodesk.ProcessPower.PlantInstance;
|
||||||
|
using Autodesk.ProcessPower.P3dProjectParts;
|
||||||
|
using PRecAnnot.Abstractions;
|
||||||
|
using PRecAnnot.Core;
|
||||||
|
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Core.Application;
|
||||||
|
|
||||||
|
namespace PRecAnnot.Plant3D2026;
|
||||||
|
|
||||||
|
internal static class OrthoMaintenanceCommands
|
||||||
|
{
|
||||||
|
public static void CheckViewport()
|
||||||
|
{
|
||||||
|
var document = AcadApplication.DocumentManager.MdiActiveDocument;
|
||||||
|
if (document is null) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var options = new PromptEntityOptions("\nВыберите видовой экран для проверки: ");
|
||||||
|
options.SetRejectMessage("\nДолжен быть видовой экран."); options.AddAllowedClass(typeof(Viewport), true);
|
||||||
|
var selected = document.Editor.GetEntity(options); if (selected.Status != PromptStatus.OK) return;
|
||||||
|
OrthoViewportInfo view;
|
||||||
|
ViewportLayerPlan layerPlan;
|
||||||
|
using (var transaction = document.Database.TransactionManager.StartOpenCloseTransaction())
|
||||||
|
{
|
||||||
|
var viewport = (Viewport)transaction.GetObject(selected.ObjectId, OpenMode.ForRead);
|
||||||
|
var layers = (LayerTable)transaction.GetObject(document.Database.LayerTableId, OpenMode.ForRead);
|
||||||
|
layerPlan = new ViewportLayerPlanner().Build(viewport.Layer, layers.Has("Defpoints"));
|
||||||
|
view = Resolve(document, viewport);
|
||||||
|
}
|
||||||
|
if (layerPlan.FailureCode is not null) throw new InvalidOperationException(layerPlan.FailureCode);
|
||||||
|
var objects = new PlantObjectReader().ReadObjects(view, CancellationToken.None);
|
||||||
|
var resolved = objects.Count(x => x.MembershipStatus == ViewMembershipStatus.Resolved);
|
||||||
|
var outside = objects.Count(x => x.MembershipStatus == ViewMembershipStatus.ObjectOutsideView);
|
||||||
|
var unresolved = objects.Count - resolved - outside;
|
||||||
|
document.Editor.WriteMessage($"\nPXP-COV preview: resolved={resolved}; outside={outside}; unresolved={unresolved}; viewport-layer-change={layerPlan.RequiresChange}. DWG не изменён.");
|
||||||
|
RuntimeDiagnostics.Write("OrthoViewportCheckCompleted", commandName: "PXP-COV", resultStatus: "PreviewCompleted", document: document, details: new Dictionary<string, object?> { ["Resolved"] = resolved, ["Outside"] = outside, ["Unresolved"] = unresolved, ["LayerChange"] = layerPlan.RequiresChange });
|
||||||
|
if (!layerPlan.RequiresChange || !string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_RUNTIME_MODE"), "Apply", StringComparison.OrdinalIgnoreCase)) return;
|
||||||
|
ValidateFixture(document);
|
||||||
|
using var documentLock = document.LockDocument();
|
||||||
|
using var write = document.Database.TransactionManager.StartTransaction();
|
||||||
|
var target = (Viewport)write.GetObject(selected.ObjectId, OpenMode.ForWrite);
|
||||||
|
var layerTable = (LayerTable)write.GetObject(document.Database.LayerTableId, OpenMode.ForRead);
|
||||||
|
target.LayerId = layerTable[layerPlan.TargetLayer];
|
||||||
|
write.Commit();
|
||||||
|
document.Editor.WriteMessage("\nPXP-COV Apply: viewport moved to Defpoints; AutoCAD UNDO is available.");
|
||||||
|
}
|
||||||
|
catch (Exception exception) { RuntimeDiagnostics.Write("OrthoViewportCheckFailed", "ERROR", commandName: "PXP-COV", resultStatus: exception.Message, exception: exception, document: document); document.Editor.WriteMessage("\nPXP-COV: " + exception.Message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
private static OrthoViewportInfo Resolve(Document document, Viewport selected)
|
||||||
|
{
|
||||||
|
var project = PlantApplication.ProjectPart("Ortho") as OrthoProject ?? throw new InvalidOperationException("ORTHO_PROJECT_UNAVAILABLE");
|
||||||
|
var matches = new PlantOrthoViewportReader(project, document.Name).ReadViewports(CancellationToken.None)
|
||||||
|
.Where(x => Math.Abs(x.CenterX - selected.CenterPoint.X) < .001 && Math.Abs(x.CenterY - selected.CenterPoint.Y) < .001 && Math.Abs(x.Width - selected.Width) < .001 && Math.Abs(x.Height - selected.Height) < .001).ToArray();
|
||||||
|
return matches.Length == 1 ? matches[0] : throw new InvalidOperationException(matches.Length == 0 ? "ORTHO_VIEW_NOT_FOUND" : "ORTHO_VIEW_AMBIGUOUS");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ValidateFixture(Document document)
|
||||||
|
{
|
||||||
|
var marker = Environment.GetEnvironmentVariable("P_REC_ANNOT_ORTHO_MAINTENANCE_FIXTURE_MARKER");
|
||||||
|
if (string.IsNullOrWhiteSpace(marker) || !Path.IsPathFullyQualified(marker) || !File.Exists(marker) || !string.Equals(File.ReadAllText(marker).Trim(), "P_REC_ANNOT_TEST_FIXTURE=ORTHO_MAINTENANCE_APPLY", StringComparison.Ordinal)) throw new InvalidOperationException("ORTHO_MAINTENANCE_TEST_FIXTURE_REQUIRED");
|
||||||
|
var root = Path.GetDirectoryName(Path.GetFullPath(marker))! + Path.DirectorySeparatorChar;
|
||||||
|
if (!Path.GetFullPath(document.Name).StartsWith(root, StringComparison.OrdinalIgnoreCase)) throw new InvalidOperationException("ORTHO_MAINTENANCE_TEST_FIXTURE_REQUIRED");
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
@@ -13,6 +13,7 @@ public sealed class LineConnectionBoundaryPlannerTests
|
|||||||
Assert.Equal("a", result[0].StableId);
|
Assert.Equal("a", result[0].StableId);
|
||||||
Assert.Contains("SAME_LINE_GROUP", result[0].Reasons);
|
Assert.Contains("SAME_LINE_GROUP", result[0].Reasons);
|
||||||
Assert.Equal("L-100 / L-200", result[1].Text);
|
Assert.Equal("L-100 / L-200", result[1].Text);
|
||||||
|
Assert.Equal(new Point3Dto(), result[1].ConnectorPosition);
|
||||||
Assert.Empty(result[1].Reasons);
|
Assert.Empty(result[1].Reasons);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
using PRecAnnot.Core;
|
||||||
|
|
||||||
|
namespace PRecAnnot.Core.Tests;
|
||||||
|
|
||||||
|
public sealed class ViewportLayerPlannerTests
|
||||||
|
{
|
||||||
|
[Theory]
|
||||||
|
[InlineData("0", true)]
|
||||||
|
[InlineData("VIEWPORTS", true)]
|
||||||
|
public void PlansLegacyDefpointsAssignment(string current, bool exists) => Assert.True(new ViewportLayerPlanner().Build(current, exists).RequiresChange);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void PreservesAlreadyAssignedViewport() => Assert.False(new ViewportLayerPlanner().Build("defpoints", true).RequiresChange);
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void RejectsMissingTargetLayer() => Assert.Equal("VIEWPORT_TARGET_LAYER_MISSING", new ViewportLayerPlanner().Build("0", false).FailureCode);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user