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().Select(id => transaction.GetObject(id, OpenMode.ForRead)).OfType() .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()); 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 { ["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 { ["Created"] = result.Created, ["Updated"] = result.Updated, ["AlreadyCurrent"] = result.Unchanged }); } }