From fcdec4d2273b08dd999b6945a1d82102bc269266 Mon Sep 17 00:00:00 2001 From: codex Date: Fri, 7 Aug 2026 13:36:17 +0300 Subject: [PATCH] feat: restore p-rec-annot manual annotation palette --- .../ManualAnnotationCommandRouter.cs | 27 +++++ .../src/PRecAnnot.Plant3D2026/EntryPoint.cs | 6 ++ .../ManualAnnotationPalette.cs | 102 ++++++++++++++++++ .../ManualAnnotationCommandRouterTests.cs | 21 ++++ 4 files changed, 156 insertions(+) create mode 100644 Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/ManualAnnotationCommandRouter.cs create mode 100644 Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/ManualAnnotationPalette.cs create mode 100644 Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/ManualAnnotationCommandRouterTests.cs diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/ManualAnnotationCommandRouter.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/ManualAnnotationCommandRouter.cs new file mode 100644 index 0000000..a446ac0 --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/ManualAnnotationCommandRouter.cs @@ -0,0 +1,27 @@ +namespace PRecAnnot.Core; + +public enum ManualAnnotationKind +{ + LineNumber, + ValveOrNozzle, + FlowDirection, + InstrumentPosition, + SectionElevation, + InstrumentConnection, + AutoCadBlock, + PlatformElevation, + OtherElevation +} + +public static class ManualAnnotationCommandRouter +{ + public static string Resolve(ManualAnnotationKind kind) => kind switch + { + ManualAnnotationKind.LineNumber => "PXP-LN", + ManualAnnotationKind.FlowDirection => "PXP-OA", + ManualAnnotationKind.SectionElevation or ManualAnnotationKind.PlatformElevation or ManualAnnotationKind.OtherElevation => "PXP-IE", + ManualAnnotationKind.AutoCadBlock => "PXP-MAB", + ManualAnnotationKind.ValveOrNozzle or ManualAnnotationKind.InstrumentPosition or ManualAnnotationKind.InstrumentConnection => "PXP-AAV", + _ => throw new ArgumentOutOfRangeException(nameof(kind), kind, "Unknown manual annotation kind.") + }; +} diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs index cf769b6..2a819ed 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs @@ -19,6 +19,7 @@ public sealed class EntryPoint : IExtensionApplication RuntimeDiagnostics.Write("CompositeModulesValidated", resultStatus: "Validated", details: new Dictionary { ["PackageVersion"] = manifest.Version, ["ModuleCount"] = manifest.Modules.Count }); RibbonIntegration.Register(); SwapAttributesContextMenu.Attach(); + ManualAnnotationPalette.Initialize(); // Test automation is driven only by the explicitly generated /b script. // Initialize remains production-equivalent and must not orchestrate commands. RuntimeDiagnostics.Write("AssemblyInitializeCompleted"); @@ -29,6 +30,7 @@ public sealed class EntryPoint : IExtensionApplication RuntimeDiagnostics.Write("AssemblyTerminateStarted"); RibbonIntegration.Terminate(); SwapAttributesContextMenu.Detach(); + ManualAnnotationPalette.Terminate(); RuntimeDiagnostics.Write("AssemblyTerminateCompleted"); } @@ -53,6 +55,10 @@ public sealed class EntryPoint : IExtensionApplication [CommandMethod("PXP-UOB")] public void UpdateOrthoViewBlocksCommand() => PRecAnnot.Plant3D2026.UpdateOrthoViewBlocksCommand.Run(); [CommandMethod("PXP-AC", CommandFlags.UsePickSet)] public void SwapAttributesCommand() => PRecAnnot.Plant3D2026.SwapAttributesCommand.Run(); [CommandMethod("PXP-IOD", CommandFlags.Session)] public void UpdateOrthoDrawingsCommand() => PRecAnnot.Plant3D2026.UpdateOrthoDrawingsCommand.Run(); + [CommandMethod("mto-ma")] public void ShowManualAnnotationPaletteCommand() => ManualAnnotationPalette.Show(); + [CommandMethod("PXP-MA")] public void ShowManualAnnotationPaletteAliasCommand() => ManualAnnotationPalette.Show(); + [CommandMethod("CAC1")] public void PlaceManualAnnotationCommand() => ManualAnnotationPalette.PlaceSelected(); + [CommandMethod("PXP-MAB")] public void InsertManualBlockCommand() => ManualBlockInsertionCommand.Run(); [CommandMethod("PHP_REC_AUTOMATION_START", CommandFlags.Session)] public void AutomationStartCommand() => FirstLimitedApplyAutomationBootstrap.Arm(); diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/ManualAnnotationPalette.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/ManualAnnotationPalette.cs new file mode 100644 index 0000000..d596ca8 --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/ManualAnnotationPalette.cs @@ -0,0 +1,102 @@ +using Autodesk.AutoCAD.DatabaseServices; +using Autodesk.AutoCAD.EditorInput; +using Autodesk.AutoCAD.Geometry; +using Autodesk.AutoCAD.Windows; +using Autodesk.ProcessPower.PlantInstance; +using PRecAnnot.Core; +using WinForms = System.Windows.Forms; +using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application; + +namespace PRecAnnot.Plant3D2026; + +internal static class ManualAnnotationPalette +{ + private static readonly Guid PaletteId = new("dc74a715-60cc-6f6b-8dc7-ccf6a3e52d60"); + private static PaletteSet? palette; + private static ManualAnnotationControl? control; + + public static void Initialize() + { + AcadApplication.DocumentManager.DocumentActivated -= DocumentActivated; + AcadApplication.DocumentManager.DocumentActivated += DocumentActivated; + } + public static void Terminate() + { + AcadApplication.DocumentManager.DocumentActivated -= DocumentActivated; + if (palette is not null) palette.Visible = false; + } + public static void Show() + { + if (palette is null) + { + control = new ManualAnnotationControl(); + palette = new PaletteSet("Ortho drawing annotations", "P_REC_ANNOT_MANUAL", PaletteId) + { Style = PaletteSetStyles.NameEditable | PaletteSetStyles.ShowPropertiesMenu | PaletteSetStyles.ShowAutoHideButton | PaletteSetStyles.ShowCloseButton }; + palette.Add("Annotations", control); + } + control!.RefreshAvailability(); palette.Visible = true; + RuntimeDiagnostics.Write("ManualAnnotationPaletteShown", resultStatus: "Visible"); + } + public static void PlaceSelected() => control?.Place(); + private static void DocumentActivated(object sender, Autodesk.AutoCAD.ApplicationServices.DocumentCollectionEventArgs args) => control?.RefreshAvailability(); +} + +internal sealed class ManualAnnotationControl : WinForms.UserControl +{ + private readonly WinForms.FlowLayoutPanel choices = new() { Dock = WinForms.DockStyle.Top, AutoSize = true, FlowDirection = WinForms.FlowDirection.TopDown, WrapContents = false }; + private readonly WinForms.Button place = new() { Text = "Place", AutoSize = true }; + private readonly WinForms.Button reconnect = new() { Text = "Reconnect / refresh", AutoSize = true }; + private readonly WinForms.Button help = new() { Text = "Help", AutoSize = true }; + private readonly Dictionary kinds = new(); + public ManualAnnotationControl() + { + Dock = WinForms.DockStyle.Fill; AutoScroll = true; + Add("Line number", ManualAnnotationKind.LineNumber, true); Add("Valve or nozzle", ManualAnnotationKind.ValveOrNozzle); Add("Flow direction", ManualAnnotationKind.FlowDirection); + Add("Instrument position", ManualAnnotationKind.InstrumentPosition); Add("Section elevation", ManualAnnotationKind.SectionElevation); Add("Instrument connection point", ManualAnnotationKind.InstrumentConnection); + Add("AutoCAD block", ManualAnnotationKind.AutoCadBlock); Add("Platform elevation", ManualAnnotationKind.PlatformElevation); Add("Other elevation", ManualAnnotationKind.OtherElevation); + place.Click += (_, _) => Place(); reconnect.Click += (_, _) => Reconnect(); help.Click += (_, _) => OpenHelp(); + var actions = new WinForms.FlowLayoutPanel { Dock = WinForms.DockStyle.Top, AutoSize = true }; actions.Controls.Add(place); actions.Controls.Add(reconnect); actions.Controls.Add(help); + Controls.Add(actions); Controls.Add(choices); RefreshTheme(); + } + private void Add(string text, ManualAnnotationKind kind, bool selected = false) { var button = new WinForms.RadioButton { Text = text, AutoSize = true, Checked = selected }; kinds.Add(button, kind); choices.Controls.Add(button); } + public void RefreshAvailability() { var available = AcadApplication.DocumentManager.MdiActiveDocument is not null && PlantApplication.CurrentProject is not null; place.Enabled = available; reconnect.Enabled = available; RefreshTheme(); } + public void Place() + { + var document = AcadApplication.DocumentManager.MdiActiveDocument; if (document is null) return; + var kind = kinds.First(value => value.Key.Checked).Value; + var command = ManualAnnotationCommandRouter.Resolve(kind); + document.SendStringToExecute(command + " ", true, false, false); + RuntimeDiagnostics.Write("ManualAnnotationActionInvoked", commandName: command, resultStatus: kind.ToString(), document: document); + } + private static void Reconnect() => AcadApplication.DocumentManager.MdiActiveDocument?.SendStringToExecute("PXP-ULN ", true, false, false); + private static void OpenHelp() + { + var path = Path.Combine(Path.GetDirectoryName(typeof(ManualAnnotationControl).Assembly.Location)!, "ManualAnnotationHelp.pdf"); + if (File.Exists(path)) System.Diagnostics.Process.Start(new System.Diagnostics.ProcessStartInfo(path) { UseShellExecute = true }); + } + private void RefreshTheme() + { + var dark = Convert.ToInt16(AcadApplication.GetSystemVariable("COLORTHEME")) == 0; + BackColor = dark ? System.Drawing.Color.FromArgb(92, 92, 92) : System.Drawing.Color.FromArgb(245, 245, 245); ForeColor = dark ? System.Drawing.Color.White : System.Drawing.Color.Black; + foreach (WinForms.Control child in choices.Controls) { child.BackColor = BackColor; child.ForeColor = ForeColor; } + } +} + +internal static class ManualBlockInsertionCommand +{ + public static void Run() + { + var document = AcadApplication.DocumentManager.MdiActiveDocument; if (document is null) return; + var name = document.Editor.GetString(new PromptStringOptions("\nBlock name: ") { AllowSpaces = true }); if (name.Status != PromptStatus.OK || string.IsNullOrWhiteSpace(name.StringResult)) return; + var point = document.Editor.GetPoint("\nInsertion point: "); if (point.Status != PromptStatus.OK) return; + var angle = document.Editor.GetAngle(new PromptAngleOptions("\nRotation <0>: ") { AllowNone = true, BasePoint = point.Value, UseBasePoint = true }); if (angle.Status is not (PromptStatus.OK or PromptStatus.None)) return; + using var documentLock = document.LockDocument(); using var transaction = document.Database.TransactionManager.StartTransaction(); + var blocks = (BlockTable)transaction.GetObject(document.Database.BlockTableId, OpenMode.ForRead); if (!blocks.Has(name.StringResult)) { document.Editor.WriteMessage("\nBlock definition not found."); return; } + var space = (BlockTableRecord)transaction.GetObject(document.Database.CurrentSpaceId, OpenMode.ForWrite); var definition = (BlockTableRecord)transaction.GetObject(blocks[name.StringResult], OpenMode.ForRead); + using var reference = new BlockReference(point.Value, definition.ObjectId) { Rotation = angle.Status == PromptStatus.OK ? angle.Value : 0 }; space.AppendEntity(reference); transaction.AddNewlyCreatedDBObject(reference, true); + foreach (ObjectId id in definition) + if (transaction.GetObject(id, OpenMode.ForRead) is AttributeDefinition source && !source.Constant) + { var value = document.Editor.GetString(new PromptStringOptions($"\n{source.Tag} <{source.TextString}>: ") { AllowSpaces = true }); using var attribute = new AttributeReference(); attribute.SetAttributeFromBlock(source, reference.BlockTransform); attribute.TextString = value.Status == PromptStatus.OK ? value.StringResult : source.TextString; reference.AttributeCollection.AppendAttribute(attribute); transaction.AddNewlyCreatedDBObject(attribute, true); } + transaction.Commit(); document.Editor.WriteMessage("\nManual block placed; AutoCAD UNDO is available."); + } +} diff --git a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/ManualAnnotationCommandRouterTests.cs b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/ManualAnnotationCommandRouterTests.cs new file mode 100644 index 0000000..bf19c40 --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/ManualAnnotationCommandRouterTests.cs @@ -0,0 +1,21 @@ +using PRecAnnot.Core; + +namespace PRecAnnot.Core.Tests; + +public sealed class ManualAnnotationCommandRouterTests +{ + [Theory] + [InlineData(ManualAnnotationKind.LineNumber, "PXP-LN")] + [InlineData(ManualAnnotationKind.ValveOrNozzle, "PXP-AAV")] + [InlineData(ManualAnnotationKind.FlowDirection, "PXP-OA")] + [InlineData(ManualAnnotationKind.InstrumentPosition, "PXP-AAV")] + [InlineData(ManualAnnotationKind.SectionElevation, "PXP-IE")] + [InlineData(ManualAnnotationKind.InstrumentConnection, "PXP-AAV")] + [InlineData(ManualAnnotationKind.AutoCadBlock, "PXP-MAB")] + [InlineData(ManualAnnotationKind.PlatformElevation, "PXP-IE")] + [InlineData(ManualAnnotationKind.OtherElevation, "PXP-IE")] + public void Resolve_routes_every_legacy_palette_choice(ManualAnnotationKind kind, string expected) + { + Assert.Equal(expected, ManualAnnotationCommandRouter.Resolve(kind)); + } +}