feat: restore p-rec-annot manual annotation palette

This commit is contained in:
codex
2026-08-07 13:36:17 +03:00
parent 461cc8f71b
commit fcdec4d227
4 changed files with 156 additions and 0 deletions
@@ -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.")
};
}
@@ -19,6 +19,7 @@ public sealed class EntryPoint : IExtensionApplication
RuntimeDiagnostics.Write("CompositeModulesValidated", resultStatus: "Validated", details: new Dictionary<string, object?> { ["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();
@@ -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<WinForms.RadioButton, ManualAnnotationKind> 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.");
}
}
@@ -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));
}
}