Files
autodesk/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/ElevationCommands.cs
T

123 lines
8.6 KiB
C#

using Autodesk.AutoCAD.ApplicationServices;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.EditorInput;
using Autodesk.AutoCAD.Geometry;
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 ElevationCommands
{
public static void RunAutomatic() => Run("PXP-AE", manual: false);
public static void RunManual() => Run("PXP-IE", manual: true);
private static void Run(string command, bool manual)
{
var document = AcadApplication.DocumentManager.MdiActiveDocument;
if (document is null) return;
try
{
var assets = ElevationRuntimeConfiguration.Read();
var datum = PromptDatum(document.Editor);
if (datum is null) return;
if (manual) RunManual(document, assets, datum.Value);
else RunAutomatic(document, assets, datum.Value);
}
catch (Exception exception)
{
RuntimeDiagnostics.Write("ElevationCommandFailed", "ERROR", commandName: command, resultStatus: exception.Message, exception: exception, document: document);
document.Editor.WriteMessage($"\n{command}: {exception.Message}");
}
}
private static void RunAutomatic(Document document, ElevationAssets assets, double datum)
{
var selected = SelectViewport(document.Editor);
if (selected.Status != PromptStatus.OK) return;
OrthoViewportInfo viewport;
using (var transaction = document.Database.TransactionManager.StartOpenCloseTransaction())
viewport = ResolveViewport(document, (Viewport)transaction.GetObject(selected.ObjectId, OpenMode.ForRead));
var candidates = new PlantObjectReader().ReadObjects(viewport, CancellationToken.None)
.Where(value => value.MembershipStatus == ViewMembershipStatus.Resolved && value.Geometry.PaperPosition is not null && value.Geometry.Elevation.HasValue)
.Select(value => new ElevationCandidate(value.SnapshotId, value.Geometry.Elevation!.Value, datum,
new AnnotationPoint(value.Geometry.PaperPosition!.Value.X, value.Geometry.PaperPosition.Value.Y), value.Geometry.Rotation ?? 0, assets.Layer, assets.TextStyle))
.ToArray();
var writer = new ElevationAnnotationWriter();
var plan = new ElevationAnnotationPlanner().Build(candidates, writer.ReadExisting(document));
WritePreview(document, "PXP-AE", plan);
if (!IsApply()) return;
ElevationApplyPermitReader.ReadAndValidate(document);
var result = writer.Apply(document, plan.Operations, assets);
WriteApply(document, "PXP-AE", result);
}
private static void RunManual(Document document, ElevationAssets assets, double datum)
{
while (true)
{
var point = document.Editor.GetPoint("\nУкажите положение высотной отметки: ");
if (point.Status != PromptStatus.OK) return;
Viewport? viewport = null;
Point3d modelPoint = default;
using (var transaction = document.Database.TransactionManager.StartOpenCloseTransaction())
{
var blocks = (BlockTable)transaction.GetObject(document.Database.BlockTableId, OpenMode.ForRead);
var paper = (BlockTableRecord)transaction.GetObject(blocks[BlockTableRecord.PaperSpace], OpenMode.ForRead);
viewport = paper.Cast<ObjectId>().Select(id => transaction.GetObject(id, OpenMode.ForRead)).OfType<Viewport>()
.Where(value => value.Number > 1 && !value.PerspectiveOn && Contains(value, point.Value))
.OrderBy(value => value.Number).FirstOrDefault();
if (viewport is not null) modelPoint = point.Value.TransformBy(ModelToPaper(viewport).Inverse());
}
if (viewport is null) { document.Editor.WriteMessage("\nТочка должна находиться внутри ортографического видового экрана."); continue; }
var stableId = $"manual:{viewport.Handle}:{point.Value.X:R}:{point.Value.Y:R}";
var candidate = new ElevationCandidate(stableId, modelPoint.Z, datum, new AnnotationPoint(point.Value.X, point.Value.Y), 0, assets.Layer, assets.TextStyle);
var plan = new ElevationAnnotationPlanner().Build([candidate], Array.Empty<ExistingElevationAnnotation>());
WritePreview(document, "PXP-IE", plan);
if (!IsApply()) return;
ElevationApplyPermitReader.ReadAndValidate(document);
var result = new ElevationAnnotationWriter().Apply(document, plan.Operations, assets, manual: true);
WriteApply(document, "PXP-IE", result);
// Legacy PXP-IE repeats until Escape; every committed iteration is
// a native AutoCAD transaction and therefore participates in UNDO.
}
}
private static double? PromptDatum(Editor editor)
{
var options = new PromptDoubleOptions("\nСмещение нуля МСК (в мм) <0>: ") { AllowNone = true, DefaultValue = 0, UseDefaultValue = true };
var result = editor.GetDouble(options);
return result.Status == PromptStatus.OK ? result.Value : result.Status == PromptStatus.None ? 0 : null;
}
private static PromptEntityResult SelectViewport(Editor editor)
{
var options = new PromptEntityOptions("\nВыберите видовой экран: "); options.SetRejectMessage("\nДолжен быть видовой экран."); options.AddAllowedClass(typeof(Viewport), true);
return editor.GetEntity(options);
}
private static OrthoViewportInfo ResolveViewport(Document document, Viewport selected)
{
var project = PlantApplication.ProjectPart("Ortho") as OrthoProject ?? throw new InvalidOperationException("ELEVATION_ORTHO_PROJECT_UNAVAILABLE");
var matches = new PlantOrthoViewportReader(project, document.Name).ReadViewports(CancellationToken.None)
.Where(value => Math.Abs(value.CenterX - selected.CenterPoint.X) < 0.001 && Math.Abs(value.CenterY - selected.CenterPoint.Y) < 0.001 && Math.Abs(value.Width - selected.Width) < 0.001 && Math.Abs(value.Height - selected.Height) < 0.001).ToArray();
return matches.Length == 1 ? matches[0] : throw new InvalidOperationException(matches.Length == 0 ? "ELEVATION_ORTHO_VIEW_NOT_FOUND" : "ELEVATION_ORTHO_VIEW_AMBIGUOUS");
}
private static Matrix3d ModelToPaper(Viewport viewport)
{
var center = new Point3d(viewport.ViewCenter.X, viewport.ViewCenter.Y, 0);
return Matrix3d.Displacement(new Vector3d(viewport.CenterPoint.X - center.X, viewport.CenterPoint.Y - center.Y, 0))
* Matrix3d.Scaling(viewport.CustomScale, center)
* Matrix3d.Rotation(viewport.TwistAngle, Vector3d.ZAxis, Point3d.Origin)
* Matrix3d.WorldToPlane(new Plane(viewport.ViewTarget, viewport.ViewDirection));
}
private static bool Contains(Viewport viewport, Point3d point) => viewport.GeometricExtents.MinPoint.X <= point.X && viewport.GeometricExtents.MaxPoint.X >= point.X && viewport.GeometricExtents.MinPoint.Y <= point.Y && viewport.GeometricExtents.MaxPoint.Y >= point.Y;
private static bool IsApply() => string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_RUNTIME_MODE"), "Apply", StringComparison.OrdinalIgnoreCase);
private static void WritePreview(Document document, string command, ElevationAnnotationPlan plan) { document.Editor.WriteMessage($"\n{command} preview: create={plan.Operations.Count(x => x.Kind == AnnotationOperationKind.Create)}; update={plan.Operations.Count(x => x.Kind == AnnotationOperationKind.Update)}; skip={plan.Operations.Count(x => x.Kind == AnnotationOperationKind.Skip)}; conflict={plan.Operations.Count(x => x.Kind == AnnotationOperationKind.Conflict)}. DWG не изменён."); RuntimeDiagnostics.Write("ElevationPreviewCompleted", commandName: command, resultStatus: "PreviewCompleted", document: document, details: new Dictionary<string, object?> { ["OperationCount"] = plan.Operations.Count }); }
private static void WriteApply(Document document, string command, ElevationApplyResult result) { document.Editor.WriteMessage($"\n{command} Apply: created={result.Created}; updated={result.Updated}; already-current={result.Unchanged}."); RuntimeDiagnostics.Write("ElevationApplyCompleted", commandName: command, resultStatus: "Applied", document: document, details: new Dictionary<string, object?> { ["Created"] = result.Created, ["Updated"] = result.Updated, ["AlreadyCurrent"] = result.Unchanged }); }
}