diff --git a/Plant3D2026/P_Rec_Annot/bundle/P_Rec_Annot.bundle/PackageContents.xml b/Plant3D2026/P_Rec_Annot/bundle/P_Rec_Annot.bundle/PackageContents.xml index 92fa492..34ee213 100644 --- a/Plant3D2026/P_Rec_Annot/bundle/P_Rec_Annot.bundle/PackageContents.xml +++ b/Plant3D2026/P_Rec_Annot/bundle/P_Rec_Annot.bundle/PackageContents.xml @@ -1,5 +1,5 @@ - + diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs index 36f5e5c..80c746f 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs @@ -55,6 +55,7 @@ public sealed class EntryPoint : IExtensionApplication [CommandMethod("PXP-UOB")] public void UpdateOrthoViewBlocksCommand() => PRecAnnot.Plant3D2026.UpdateOrthoViewBlocksCommand.Run(); [CommandMethod("PXP-RA", CommandFlags.Session)] public void RemoveOrthoAnnotationsCommand() => OrthoAnnotationRemovalCommand.Run(); [CommandMethod("PXP-OSI", CommandFlags.Session)] public void ShowOrthoSourceInModelCommand() => OrthoSourceNavigationCommand.Run(); + [CommandMethod("PXP-DJ")] public void DimensionOrthoAnnotationsCommand() => OrthoDimensionCommand.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(); diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoDimensionCommand.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoDimensionCommand.cs new file mode 100644 index 0000000..88af278 --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/OrthoDimensionCommand.cs @@ -0,0 +1,111 @@ +using Autodesk.AutoCAD.DatabaseServices; +using Autodesk.AutoCAD.EditorInput; +using Autodesk.AutoCAD.Geometry; +using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application; + +namespace PRecAnnot.Plant3D2026; + +// Source-backed PXP-DJ mutation boundary. The legacy command dimensions the +// selected PXP-OA annotation blocks, rejects mixed viewport scales and keeps +// all dimension creation in one transaction so native UNDO restores the prior +// state. The Plant-specific candidate discovery remains deliberately typed +// through the PXP-OA ownership contract. +internal static class OrthoDimensionCommand +{ + private const string AnnotationApp = "PXP-OA"; + private const string DimensionApp = "PXP-DJ"; + + public static void Run() + { + var document = AcadApplication.DocumentManager.MdiActiveDocument; + if (document is null) return; + var selection = document.Editor.GetSelection(new SelectionFilter(new[] { new TypedValue((int)DxfCode.ExtendedDataRegAppName, AnnotationApp) })); + if (selection.Status != PromptStatus.OK || selection.Value.Count < 2) + { + document.Editor.WriteMessage("\nSelect at least two PXP-OA annotations."); + return; + } + + using var read = document.Database.TransactionManager.StartOpenCloseTransaction(); + var points = new List<(ObjectId Id, Point3d Position, string Viewport)>(); + foreach (var id in selection.Value.GetObjectIds()) + { + if (read.GetObject(id, OpenMode.ForRead) is not BlockReference reference) continue; + if (!TryReadViewport(reference, out var viewport)) continue; + points.Add((id, reference.Position, viewport)); + } + read.Commit(); + if (points.Count < 2 || points.Select(point => point.Viewport).Distinct(StringComparer.Ordinal).Count() != 1) + { + document.Editor.WriteMessage("\nSelected annotations must belong to one Ortho viewport."); + return; + } + + points = points.OrderBy(point => point.Position.X).ThenBy(point => point.Position.Y).ThenBy(point => point.Id.Handle.Value).ToList(); + var defaultAngle = Math.Abs(points[^1].Position.X - points[0].Position.X) >= Math.Abs(points[^1].Position.Y - points[0].Position.Y) ? 0d : Math.PI / 2d; + var angle = document.Editor.GetAngle(new PromptAngleOptions("\nDimension rotation : ") { AllowNone = true }); + if (angle.Status is not (PromptStatus.OK or PromptStatus.None)) return; + var rotation = angle.Status == PromptStatus.OK ? angle.Value : defaultAngle; + var offset = document.Editor.GetPoint("\nDimension line location: "); + if (offset.Status != PromptStatus.OK) return; + + using var documentLock = document.LockDocument(); + using var transaction = document.Database.TransactionManager.StartTransaction(); + var table = (BlockTable)transaction.GetObject(document.Database.BlockTableId, OpenMode.ForRead); + var paper = (BlockTableRecord)transaction.GetObject(table[BlockTableRecord.PaperSpace], OpenMode.ForWrite); + EnsureRegApp(transaction, document.Database); + EraseOwnedDimensions(transaction, paper, points[0].Viewport, points.Select(point => point.Id.Handle.ToString()).ToHashSet(StringComparer.OrdinalIgnoreCase)); + for (var index = 1; index < points.Count; index++) + { + var first = points[index - 1].Position; + var second = points[index].Position; + var dimension = new RotatedDimension(rotation, first, second, offset.Value, string.Empty, document.Database.Dimstyle); + dimension.LayerId = document.Database.Clayer; + dimension.XData = Ownership(points[0].Viewport, points.Select(point => point.Id.Handle.ToString())); + paper.AppendEntity(dimension); + transaction.AddNewlyCreatedDBObject(dimension, true); + } + transaction.Commit(); + document.Editor.WriteMessage($"\nCreated {points.Count - 1} Ortho dimensions; native UNDO is available."); + } + + private static bool TryReadViewport(BlockReference reference, out string viewport) + { + viewport = string.Empty; + using var data = reference.GetXDataForApplication(AnnotationApp); + var values = data?.AsArray().Where(value => value.TypeCode == (int)DxfCode.ExtendedDataAsciiString).Select(value => value.Value?.ToString() ?? string.Empty).ToArray(); + if (values is null) return false; + for (var index = 0; index + 1 < values.Length; index++) if (string.Equals(values[index], "VIEWPORT", StringComparison.Ordinal)) { viewport = values[index + 1]; break; } + return !string.IsNullOrWhiteSpace(viewport); + } + + private static void EraseOwnedDimensions(Transaction transaction, BlockTableRecord paper, string viewport, HashSet handles) + { + foreach (ObjectId id in paper) + { + if (transaction.GetObject(id, OpenMode.ForRead) is not Entity entity) continue; + using var data = entity.GetXDataForApplication(DimensionApp); + var values = data?.AsArray().Where(value => value.TypeCode == (int)DxfCode.ExtendedDataAsciiString).Select(value => value.Value?.ToString() ?? string.Empty).ToArray(); + if (values is null || !values.Contains(viewport, StringComparer.Ordinal)) continue; + if (!handles.Any(handle => values.Contains(handle, StringComparer.OrdinalIgnoreCase))) continue; + entity.UpgradeOpen(); + entity.Erase(); + } + } + + private static ResultBuffer Ownership(string viewport, IEnumerable handles) => new( + new TypedValue((int)DxfCode.ExtendedDataRegAppName, DimensionApp), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, viewport), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, "SOURCES"), + new TypedValue((int)DxfCode.ExtendedDataAsciiString, string.Join(",", handles))); + + private static void EnsureRegApp(Transaction transaction, Database database) + { + var table = (RegAppTable)transaction.GetObject(database.RegAppTableId, OpenMode.ForRead); + if (table.Has(DimensionApp)) return; + table.UpgradeOpen(); + using var record = new RegAppTableRecord { Name = DimensionApp }; + table.Add(record); + transaction.AddNewlyCreatedDBObject(record, true); + } +} diff --git a/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 b/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 index c54f25a..7f593ec 100644 --- a/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 +++ b/Plant3D2026/P_Rec_Annot/tools/Build-Bundle.ps1 @@ -16,6 +16,6 @@ $moduleManifestPath=Join-Path $bundleRoot 'Contents\module-manifest.json' $files=Get-ChildItem $bundleRoot -Recurse -File | Where-Object { $_.FullName -ne $moduleManifestPath } | Sort-Object FullName | ForEach-Object { [ordered]@{ relativePath=$_.FullName.Substring($bundleRoot.Length + 1).Replace('\','/'); sha256=(Get-FileHash $_.FullName -Algorithm SHA256).Hash.ToLowerInvariant(); required=$true } } -$manifest=[ordered]@{ schemaVersion=2; package='P_Rec_Annot'; version='0.2.6'; modules=$modules; assemblies=$assemblies; files=$files } +$manifest=[ordered]@{ schemaVersion=2; package='P_Rec_Annot'; version='0.2.7'; modules=$modules; assemblies=$assemblies; files=$files } $manifest | ConvertTo-Json -Depth 5 | Set-Content (Join-Path $bundleRoot 'Contents\module-manifest.json') -Encoding utf8 Write-Host 'Bundle assembly build completed; deployment is intentionally not performed.'