diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/ManualAnnotationPersistence.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/ManualAnnotationPersistence.cs new file mode 100644 index 0000000..d1713a4 --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Core/ManualAnnotationPersistence.cs @@ -0,0 +1,15 @@ +namespace PRecAnnot.Core; + +public sealed record ManualAnnotationState(string Kind, ManualDatumMode DatumMode, double DatumOffset) +{ + public string Serialize() => string.Join("|", Kind, DatumMode, DatumOffset.ToString(System.Globalization.CultureInfo.InvariantCulture)); + + public static ManualAnnotationState? Deserialize(string? value) + { + if (string.IsNullOrWhiteSpace(value)) return null; + var fields = value.Split('|'); + if (fields.Length != 3 || !Enum.TryParse(fields[1], true, out var mode) || + !double.TryParse(fields[2], System.Globalization.NumberStyles.Float, System.Globalization.CultureInfo.InvariantCulture, out var offset)) return null; + return new ManualAnnotationState(fields[0], mode, offset); + } +} diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs index 406e2da..460e67a 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs @@ -58,7 +58,7 @@ public sealed class EntryPoint : IExtensionApplication [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("PXP-MAB")] public void InsertManualBlockCommand() => ManualBlockInsertionCommand.Run(ManualAnnotationPalette.CurrentState()); [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 index ae7909a..0b876f1 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/ManualAnnotationPalette.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/ManualAnnotationPalette.cs @@ -38,6 +38,7 @@ internal static class ManualAnnotationPalette RuntimeDiagnostics.Write("ManualAnnotationPaletteShown", resultStatus: "Visible"); } public static void PlaceSelected() => control?.Place(); + public static ManualAnnotationState CurrentState() => control?.CurrentState() ?? new ManualAnnotationState("AutoCadBlock", ManualDatumMode.Absolute, 0); private static void DocumentActivated(object sender, Autodesk.AutoCAD.ApplicationServices.DocumentCollectionEventArgs args) => control?.RefreshAvailability(); } @@ -74,6 +75,7 @@ internal sealed class ManualAnnotationControl : WinForms.UserControl document.SendStringToExecute(command + " ", true, false, false); RuntimeDiagnostics.Write("ManualAnnotationActionInvoked", commandName: command, resultStatus: kind.ToString(), document: document); } + public ManualAnnotationState CurrentState() => new(kinds.First(value => value.Key.Checked).Value.ToString(), relativeDatum.Checked ? ManualDatumMode.Relative : ManualDatumMode.Absolute, (double)datumOffset.Value); private static void Reconnect() => AcadApplication.DocumentManager.MdiActiveDocument?.SendStringToExecute("PXP-ULN ", true, false, false); private static void OpenHelp() { @@ -90,7 +92,7 @@ internal sealed class ManualAnnotationControl : WinForms.UserControl internal static class ManualBlockInsertionCommand { - public static void Run() + public static void Run(ManualAnnotationState state) { 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; @@ -99,7 +101,12 @@ internal static class ManualBlockInsertionCommand 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); + using var reference = new BlockReference(point.Value, definition.ObjectId) { Rotation = angle.Status == PromptStatus.OK ? angle.Value : 0 }; + space.AppendEntity(reference); transaction.AddNewlyCreatedDBObject(reference, true); + var regApps = (RegAppTable)transaction.GetObject(document.Database.RegAppTableId, OpenMode.ForRead); + const string appName = "P_REC_ANNOT_MANUAL"; + if (!regApps.Has(appName)) { regApps.UpgradeOpen(); using var app = new RegAppTableRecord { Name = appName }; regApps.Add(app); transaction.AddNewlyCreatedDBObject(app, true); } + reference.XData = new ResultBuffer(new TypedValue((int)DxfCode.ExtendedDataRegAppName, appName), new TypedValue((int)DxfCode.ExtendedDataAsciiString, state.Serialize())); 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); } diff --git a/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/ManualAnnotationPersistenceTests.cs b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/ManualAnnotationPersistenceTests.cs new file mode 100644 index 0000000..089e68a --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/tests/PRecAnnot.Core.Tests/ManualAnnotationPersistenceTests.cs @@ -0,0 +1,20 @@ +using PRecAnnot.Core; + +namespace PRecAnnot.Core.Tests; + +public sealed class ManualAnnotationPersistenceTests +{ + [Theory] + [InlineData("AutoCadBlock", ManualDatumMode.Absolute, 0)] + [InlineData("SectionElevation", ManualDatumMode.Relative, -12.5)] + [InlineData("InstrumentPosition", ManualDatumMode.Relative, 4.25)] + public void State_round_trips_without_losing_legacy_datum_values(string kind, ManualDatumMode mode, double offset) + { + var state = new ManualAnnotationState(kind, mode, offset); + var restored = ManualAnnotationState.Deserialize(state.Serialize()); + Assert.Equal(state, restored); + } + + [Fact] + public void Invalid_state_is_not_accepted() => Assert.Null(ManualAnnotationState.Deserialize("bad|payload")); +}