diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs index 9297ce2..cf769b6 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/EntryPoint.cs @@ -52,6 +52,7 @@ public sealed class EntryPoint : IExtensionApplication [CommandMethod("PXP-COV")] public void CheckOrthoViewportCommand() => OrthoMaintenanceCommands.CheckViewport(); [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("PHP_REC_AUTOMATION_START", CommandFlags.Session)] public void AutomationStartCommand() => FirstLimitedApplyAutomationBootstrap.Arm(); diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/PRecAnnot.Plant3D2026.csproj b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/PRecAnnot.Plant3D2026.csproj index 3c844df..ba967fd 100644 --- a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/PRecAnnot.Plant3D2026.csproj +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/PRecAnnot.Plant3D2026.csproj @@ -20,5 +20,6 @@ $(Plant3D2026ManagedDir)\PnP3dOrthoDrawingsUI.dllfalsefalse $(Plant3D2026ManagedDir)\PnPDwg2dDef.dllfalsefalse $(Plant3D2026ManagedDir)\PnPDwg2dUtil.dllfalsefalse + $(Plant3D2026ManagedDir)\PnPDwg2dGen.dllfalsefalse diff --git a/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/UpdateOrthoDrawingsCommand.cs b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/UpdateOrthoDrawingsCommand.cs new file mode 100644 index 0000000..0f12d53 --- /dev/null +++ b/Plant3D2026/P_Rec_Annot/src/PRecAnnot.Plant3D2026/UpdateOrthoDrawingsCommand.cs @@ -0,0 +1,148 @@ +using System.Collections; +using System.Collections.Specialized; +using Autodesk.AutoCAD.ApplicationServices; +using Autodesk.AutoCAD.DatabaseServices; +using Autodesk.AutoCAD.EditorInput; +using Autodesk.AutoCAD.Geometry; +using Autodesk.ProcessPower.Drawings2d; +using Autodesk.ProcessPower.PlantInstance; +using Autodesk.ProcessPower.P3dProjectParts; +using Autodesk.ProcessPower.PnP3dOrthoDrawingsUI; +using Autodesk.ProcessPower.ProjectManager; +using PRecAnnot.Abstractions; +using PRecAnnot.Core; +using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application; + +namespace PRecAnnot.Plant3D2026; + +internal static class UpdateOrthoDrawingsCommand +{ + public static void Run() + { + var owner = AcadApplication.DocumentManager.MdiActiveDocument; + if (owner is null) return; + try + { + var project = PlantApplication.CurrentProject ?? throw new InvalidOperationException("ORTHO_BATCH_PROJECT_REQUIRED"); + var ortho = PlantApplication.ProjectPart("Ortho") as OrthoProject ?? throw new InvalidOperationException("ORTHO_BATCH_PROJECT_PART_REQUIRED"); + var drawings = ReadDrawings(project, ortho); + var selections = PromptSelections(owner.Editor, drawings); + if (selections.Count == 0) return; + var plan = new OrthoBatchPlanner().Build(drawings, selections); + foreach (var diagnostic in plan.Diagnostics) owner.Editor.WriteMessage("\nPXP-IOD: " + diagnostic); + owner.Editor.WriteMessage($"\nPXP-IOD preview: drawings={plan.Operations.Select(value => value.RelativePath).Distinct(StringComparer.OrdinalIgnoreCase).Count()}; views={plan.Operations.Count}; no drawings modified."); + RuntimeDiagnostics.Write("UpdateOrthoDrawingsPreviewCompleted", commandName: "PXP-IOD", resultStatus: "PreviewCompleted", document: owner, details: new Dictionary { ["DrawingCount"] = plan.Operations.Select(value => value.RelativePath).Distinct(StringComparer.OrdinalIgnoreCase).Count(), ["ViewCount"] = plan.Operations.Count }); + if (!string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_RUNTIME_MODE"), "Apply", StringComparison.OrdinalIgnoreCase)) return; + var fixtureRoot = ValidateFixture(); + var failures = Apply(project, ortho, plan, fixtureRoot); + owner.Editor.WriteMessage($"\nPXP-IOD Apply completed: views={plan.Operations.Count}; failures={failures.Count}."); + RuntimeDiagnostics.Write("UpdateOrthoDrawingsApplyCompleted", commandName: "PXP-IOD", resultStatus: failures.Count == 0 ? "Applied" : "PartiallyApplied", document: owner, details: new Dictionary { ["FailureCount"] = failures.Count, ["Failures"] = failures.ToArray() }); + } + catch (Exception exception) + { + RuntimeDiagnostics.Write("UpdateOrthoDrawingsFailed", "ERROR", commandName: "PXP-IOD", resultStatus: exception.Message, exception: exception, document: owner); + owner.Editor.WriteMessage("\nPXP-IOD: " + exception.Message); + } + } + + private static IReadOnlyList ReadDrawings(PlantProject project, OrthoProject ortho) + { + var root = Path.GetDirectoryName(project.FileName) ?? throw new InvalidOperationException("ORTHO_BATCH_PROJECT_ROOT_UNAVAILABLE"); + return ortho.DwgSheetDefinitions.AsArray().Select(sheet => + { + var path = UIUtils.GetDwgSheetDefResolvedPath(sheet); + var relative = Path.GetRelativePath(root, path); + return new OrthoBatchDrawing(sheet.DwgGuid.ToString(), Path.GetFileName(path), relative, sheet.DwgViewDefs.AsArray().Select(view => view.ViewName).ToArray()); + }).OrderBy(value => value.RelativePath, StringComparer.OrdinalIgnoreCase).ToArray(); + } + + private static IReadOnlyList PromptSelections(Editor editor, IReadOnlyList drawings) + { + var result = new List(); + foreach (var drawing in drawings) + { + var prompt = new PromptKeywordOptions($"\nUpdate Ortho drawing {drawing.RelativePath}?") { AllowNone = true }; + prompt.Keywords.Add("Yes"); prompt.Keywords.Add("No"); prompt.Keywords.Default = "No"; + var answer = editor.GetKeywords(prompt); + if (answer.Status == PromptStatus.Cancel) return Array.Empty(); + if (string.Equals(answer.StringResult, "Yes", StringComparison.OrdinalIgnoreCase)) result.Add(new(drawing.RelativePath, drawing.Views)); + } + return result; + } + + private static IReadOnlyList Apply(PlantProject project, OrthoProject ortho, OrthoBatchPlan plan, string fixtureRoot) + { + var projectRoot = Path.GetDirectoryName(project.FileName)!; + var failures = new List(); + foreach (var group in plan.Operations.GroupBy(value => value.RelativePath, StringComparer.OrdinalIgnoreCase)) + { + var path = Path.GetFullPath(Path.Combine(projectRoot, group.Key)); + if (!path.StartsWith(fixtureRoot, StringComparison.OrdinalIgnoreCase)) { failures.Add(group.Key + ":OUTSIDE_FIXTURE"); continue; } + Document? document = AcadApplication.DocumentManager.Cast().FirstOrDefault(value => string.Equals(Path.GetFullPath(value.Name), path, StringComparison.OrdinalIgnoreCase)); + var wasOpen = document is not null; + try + { + if (document is null) { UIUtils.OpenDocument(path, out document, false); document.Database.CloseInput(true); } + AcadApplication.DocumentManager.MdiActiveDocument = document; + using (document.LockDocument()) foreach (var operation in group) UpdateView(document, ortho, operation.ViewName); + if (wasOpen) document.Database.SaveAs(document.Name, true, DwgVersion.Current, document.Database.SecurityParameters); else document.CloseAndSave(path); + } + catch (Exception exception) + { + failures.Add(group.Key + ":" + exception.GetType().Name); + if (!wasOpen && document is not null) try { document.CloseAndDiscard(); } catch { } + } + } + return failures; + } + + private static void UpdateView(Document document, OrthoProject ortho, string viewName) + { + if (!UIUtils.GetDwgSheetFromOrthoDrawingName(document.Database.Filename, out IDwgSheetDef sheet) || !UIUtils.GetViewDefByViewName(viewName, sheet, out IDwgViewDef definition)) + throw new InvalidOperationException("ORTHO_BATCH_VIEW_NOT_FOUND:" + viewName); + var viewportId = FindViewport(document.Database, definition); + var frozen = ReadFrozenLayers(document.Database, viewportId); var off = new StringCollection(); + if (!UIUtils.IsValidViewpport(viewportId, out double width, out double height, out double scale, out Point3d center) || !definition.SetLayout(width, height, ref center)) + throw new InvalidOperationException("ORTHO_BATCH_VIEW_LAYOUT_INVALID:" + viewName); + definition.ViewScale = scale; + var resolved = new StringCollection(); resolved.AddRange(UIUtils.GetResolvedRefModels(definition, false).Select(value => value.ModelFileName).ToArray()); + var models = new ArrayList(); UIUtils.UpdateFileCollectionWithXrefs(resolved, ref models, out Extents3d extents); + definition.DwgQuery.ModelFiles = models.ToArray(typeof(ModelFile)) as ModelFile[]; + definition.DwgQuery.Execute(frozen, off); + UIUtils.CreateOrUpdateOrthoView(null!, sheet, viewName, ortho, false, true, Dwg2dGen.NewDwgSetClipper(definition, 0), 0, frozen, off, new Vector3d(), 0, new Vector3d(), null!); + definition.ViewCreationTime = DateTime.UtcNow.ToString("O"); + } + + private static ObjectId FindViewport(Database database, IDwgViewDef definition) + { + using var transaction = database.TransactionManager.StartOpenCloseTransaction(); + var layouts = (DBDictionary)transaction.GetObject(database.LayoutDictionaryId, OpenMode.ForRead); + foreach (DBDictionaryEntry entry in layouts) + { + var layout = (Layout)transaction.GetObject(entry.Value, OpenMode.ForRead); + if (layout.ModelType) continue; + var paper = (BlockTableRecord)transaction.GetObject(layout.BlockTableRecordId, OpenMode.ForRead); + foreach (ObjectId id in paper) + if (transaction.GetObject(id, OpenMode.ForRead) is Viewport viewport && viewport.Number > 1 && + Math.Abs(viewport.CenterPoint.X - definition.CenterPoint.X) < .001 && Math.Abs(viewport.CenterPoint.Y - definition.CenterPoint.Y) < .001 && + Math.Abs(viewport.Width - definition.ViewWidth) < .001 && Math.Abs(viewport.Height - definition.ViewHeight) < .001) return id; + } + throw new InvalidOperationException("ORTHO_BATCH_VIEWPORT_NOT_FOUND:" + definition.ViewName); + } + + private static StringCollection ReadFrozenLayers(Database database, ObjectId viewportId) + { + var result = new StringCollection(); + using var transaction = database.TransactionManager.StartOpenCloseTransaction(); + var viewport = (Viewport)transaction.GetObject(viewportId, OpenMode.ForRead); + foreach (ObjectId id in viewport.GetFrozenLayers()) if (transaction.GetObject(id, OpenMode.ForRead) is LayerTableRecord layer) result.Add(layer.Name); + return result; + } + + private static string ValidateFixture() + { + var marker = Environment.GetEnvironmentVariable("P_REC_ANNOT_ORTHO_BATCH_FIXTURE_MARKER"); + if (string.IsNullOrWhiteSpace(marker) || !File.Exists(marker) || !string.Equals(File.ReadAllText(marker).Trim(), "P_REC_ANNOT_TEST_FIXTURE=ORTHO_BATCH_APPLY", StringComparison.Ordinal)) throw new InvalidOperationException("ORTHO_BATCH_TEST_FIXTURE_REQUIRED"); + return Path.GetDirectoryName(Path.GetFullPath(marker))! + Path.DirectorySeparatorChar; + } +}