feat: port p-rec-annot ortho batch update lifecycle

This commit is contained in:
codex
2026-08-07 13:27:52 +03:00
parent b381f83bbd
commit 461cc8f71b
3 changed files with 150 additions and 0 deletions
@@ -52,6 +52,7 @@ public sealed class EntryPoint : IExtensionApplication
[CommandMethod("PXP-COV")] public void CheckOrthoViewportCommand() => OrthoMaintenanceCommands.CheckViewport(); [CommandMethod("PXP-COV")] public void CheckOrthoViewportCommand() => OrthoMaintenanceCommands.CheckViewport();
[CommandMethod("PXP-UOB")] public void UpdateOrthoViewBlocksCommand() => PRecAnnot.Plant3D2026.UpdateOrthoViewBlocksCommand.Run(); [CommandMethod("PXP-UOB")] public void UpdateOrthoViewBlocksCommand() => PRecAnnot.Plant3D2026.UpdateOrthoViewBlocksCommand.Run();
[CommandMethod("PXP-AC", CommandFlags.UsePickSet)] public void SwapAttributesCommand() => PRecAnnot.Plant3D2026.SwapAttributesCommand.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)] [CommandMethod("PHP_REC_AUTOMATION_START", CommandFlags.Session)]
public void AutomationStartCommand() => FirstLimitedApplyAutomationBootstrap.Arm(); public void AutomationStartCommand() => FirstLimitedApplyAutomationBootstrap.Arm();
@@ -20,5 +20,6 @@
<Reference Include="PnP3dOrthoDrawingsUI"><HintPath>$(Plant3D2026ManagedDir)\PnP3dOrthoDrawingsUI.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference> <Reference Include="PnP3dOrthoDrawingsUI"><HintPath>$(Plant3D2026ManagedDir)\PnP3dOrthoDrawingsUI.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference>
<Reference Include="PnPDwg2dDef"><HintPath>$(Plant3D2026ManagedDir)\PnPDwg2dDef.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference> <Reference Include="PnPDwg2dDef"><HintPath>$(Plant3D2026ManagedDir)\PnPDwg2dDef.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference>
<Reference Include="PnPDwg2dUtil"><HintPath>$(Plant3D2026ManagedDir)\PnPDwg2dUtil.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference> <Reference Include="PnPDwg2dUtil"><HintPath>$(Plant3D2026ManagedDir)\PnPDwg2dUtil.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference>
<Reference Include="PnPDwg2dGen"><HintPath>$(Plant3D2026ManagedDir)\PnPDwg2dGen.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference>
</ItemGroup> </ItemGroup>
</Project> </Project>
@@ -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<string, object?> { ["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<string, object?> { ["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<OrthoBatchDrawing> 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<OrthoBatchSelection> PromptSelections(Editor editor, IReadOnlyList<OrthoBatchDrawing> drawings)
{
var result = new List<OrthoBatchSelection>();
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<OrthoBatchSelection>();
if (string.Equals(answer.StringResult, "Yes", StringComparison.OrdinalIgnoreCase)) result.Add(new(drawing.RelativePath, drawing.Views));
}
return result;
}
private static IReadOnlyList<string> Apply(PlantProject project, OrthoProject ortho, OrthoBatchPlan plan, string fixtureRoot)
{
var projectRoot = Path.GetDirectoryName(project.FileName)!;
var failures = new List<string>();
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<Document>().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;
}
}