401 lines
28 KiB
C#
401 lines
28 KiB
C#
using System.Diagnostics;
|
|
using Autodesk.AutoCAD.ApplicationServices;
|
|
using Autodesk.AutoCAD.EditorInput;
|
|
using Autodesk.AutoCAD.Runtime;
|
|
using Autodesk.Windows;
|
|
using Autodesk.ProcessPower.PlantInstance;
|
|
using PRecAnnot.Abstractions;
|
|
using PRecAnnot.Core;
|
|
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
|
|
|
|
namespace PRecAnnot.Plant3D2026;
|
|
|
|
public sealed class EntryPoint : IExtensionApplication
|
|
{
|
|
public void Initialize()
|
|
{
|
|
RuntimeDiagnostics.Write("AssemblyInitializeStarted");
|
|
var manifest = CompositeModuleManifest.Validate();
|
|
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");
|
|
}
|
|
|
|
public void Terminate()
|
|
{
|
|
RuntimeDiagnostics.Write("AssemblyTerminateStarted");
|
|
RibbonIntegration.Terminate();
|
|
SwapAttributesContextMenu.Detach();
|
|
ManualAnnotationPalette.Terminate();
|
|
RuntimeDiagnostics.Write("AssemblyTerminateCompleted");
|
|
}
|
|
|
|
[CommandMethod("PHP_REC_ANNOT")]
|
|
public void PrimaryCommand() => RunDiagnostic("PHP_REC_ANNOT");
|
|
|
|
[CommandMethod("PXP-AAV")]
|
|
public void AutoAnnotateViewCommand() => PRecAnnot.Plant3D2026.AutoAnnotateViewCommand.Run();
|
|
|
|
[CommandMethod("PXP-OA")]
|
|
public void OrthoAnnotateCommand() => RunDiagnostic("PXP-OA");
|
|
|
|
[CommandMethod("PXP-LN")] public void AnnotateLineNumbersCommand() => RunLineNumberPreview();
|
|
[CommandMethod("PXP-ULN")] public void UpdateLineNumbersCommand() => LineNumberRefreshCommand.Run();
|
|
[CommandMethod("PXP-ESN")] public void EditLineNumberStartCommand() => LineNumberEditCommands.Renumber();
|
|
[CommandMethod("PXP-ELN")] public void EditLineNumbersCommand() => LineNumberEditCommands.EditText();
|
|
[CommandMethod("PXP-RMP")] public void ResetMarkerPositionCommand() => LineNumberEditCommands.ResetMarkerPosition();
|
|
[CommandMethod("PXP-ALC")] public void AutoLineNumberConnectionsCommand() => LineConnectionCommand.Run();
|
|
[CommandMethod("PXP-AE")] public void AutoElevationCommand() => ElevationCommands.RunAutomatic();
|
|
[CommandMethod("PXP-IE")] public void InsertElevationCommand() => ElevationCommands.RunManual();
|
|
[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("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();
|
|
|
|
// Test-only command-queue barrier. It deliberately performs no Plant or
|
|
// drawing work; its CommandEnded event proves the activated document is
|
|
// ready before the coordinator calls the Plant APIs.
|
|
[CommandMethod("PHP_REC_AUTOMATION_READY")]
|
|
public void AutomationReadyCommand() { }
|
|
|
|
[CommandMethod("PHP_REC_AUTOMATION_PREPARE", CommandFlags.Session)]
|
|
public void AutomationPrepareCommand() => ScriptDrivenFirstApplyAutomation.Prepare();
|
|
|
|
[CommandMethod("PHP_REC_AUTOMATION_BEFORE_PXP_1", CommandFlags.Session)]
|
|
public void AutomationBeforePxp1Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("BeforePxp1");
|
|
|
|
[CommandMethod("PHP_REC_AUTOMATION_AFTER_PXP_1", CommandFlags.Session)]
|
|
public void AutomationAfterPxp1Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("AfterPxp1");
|
|
|
|
[CommandMethod("PHP_REC_AUTOMATION_BEFORE_PXP_2", CommandFlags.Session)]
|
|
public void AutomationBeforePxp2Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("BeforePxp2");
|
|
|
|
[CommandMethod("PHP_REC_AUTOMATION_AFTER_PXP_2", CommandFlags.Session)]
|
|
public void AutomationAfterPxp2Command() => ScriptDrivenFirstApplyAutomation.RecordSentinel("AfterPxp2");
|
|
|
|
[CommandMethod("PHP_REC_AUTOMATION_FINALIZE", CommandFlags.Session)]
|
|
public void AutomationFinalizeCommand() => ScriptDrivenFirstApplyAutomation.FinalizeAutomation();
|
|
|
|
private static void RunLineNumberPreview()
|
|
{
|
|
var document = AcadApplication.DocumentManager.MdiActiveDocument;
|
|
if (document is null) return;
|
|
var editor = document.Editor;
|
|
try
|
|
{
|
|
var configuration = LineNumberRuntimeConfigurationReader.Read();
|
|
var profileDefault = configuration.Annotations.Annotations.OrderBy(value => value.Nickname, StringComparer.Ordinal).First();
|
|
var profilePrompt = new PromptStringOptions($"\nПрофиль номера линии <{profileDefault.Nickname}>: ") { AllowSpaces = false, DefaultValue = profileDefault.Nickname, UseDefaultValue = true };
|
|
var profileInput = editor.GetString(profilePrompt);
|
|
if (profileInput.Status != PromptStatus.OK) return;
|
|
var profile = configuration.Annotations.Annotations.SingleOrDefault(value => string.Equals(value.Nickname, profileInput.StringResult, StringComparison.OrdinalIgnoreCase));
|
|
if (profile is null) throw new InvalidOperationException("LINE_NUMBER_PROFILE_NOT_FOUND");
|
|
|
|
var selection = editor.GetEntity("\nВыберите компонент Plant 3D для аннотации номера линии: ");
|
|
if (selection.Status != PromptStatus.OK) return;
|
|
var piping = PlantApplication.ProjectPart("Piping")?.DataLinksManager ?? throw new InvalidOperationException("LINE_NUMBER_PIPING_DATALINKS_UNAVAILABLE");
|
|
var candidate = new PlantLineNumberCandidateReader(piping).Read(selection.ObjectId, configuration.Assets.Marker.Layer, configuration.Assets.Marker.Texts[0].TextStyle);
|
|
if (!candidate.Success) throw new InvalidOperationException(candidate.FailureCode);
|
|
var preview = new LineNumberCommandPreviewService(
|
|
new LineNumberCommandPlanner(new LineNumberAnnotationFormatter()),
|
|
new LineNumberFieldResolver(new PlantDataLinksPropertyReader(piping)))
|
|
.Preview(profile, [candidate.Candidate!], Array.Empty<ExistingLineNumberAnnotation>());
|
|
if (!preview.Success) throw new InvalidOperationException(preview.FailureCode);
|
|
var operations = string.Join(", ", preview.Plan!.Operations.Select(value => $"{value.Kind}:{value.Text}"));
|
|
editor.WriteMessage($"\nPXP-LN preview: {operations}. DWG не изменён.");
|
|
RuntimeDiagnostics.Write("LineNumberPreviewCompleted", commandName: "PXP-LN", resultStatus: "PreviewCompleted", document: document, details: new Dictionary<string, object?> { ["OperationCount"] = preview.Apply.Count });
|
|
if (ParseMode(RuntimeDiagnostics.RuntimeMode) != PRecAnnotExecutionMode.Apply) return;
|
|
|
|
var operation = preview.Apply.SingleOrDefault() ?? throw new InvalidOperationException("LINE_NUMBER_APPLY_REQUIRES_EXACTLY_ONE_OPERATION");
|
|
var placement = editor.GetPoint("\nУкажите положение маркера номера линии: ");
|
|
if (placement.Status != PromptStatus.OK) return;
|
|
operation = operation with { Position = new AnnotationPoint(placement.Value.X, placement.Value.Y, placement.Value.Z) };
|
|
var permit = LineNumberApplyPermitReader.Read();
|
|
LineNumberApplyPermitReader.Validate(document, permit, operation);
|
|
// AutoCAD wraps a command's committed transaction in its native
|
|
// UNDO record. No compensating-delete path is used here.
|
|
var result = new LineNumberAnnotationWriter().Apply(document, [operation], configuration.Assets);
|
|
RuntimeDiagnostics.Write("LineNumberApplyCompleted", commandName: "PXP-LN", resultStatus: result.Created > 0 ? "Created" : result.Updated > 0 ? "Updated" : "AlreadyCurrent", document: document, details: new Dictionary<string, object?> { ["Created"] = result.Created, ["Updated"] = result.Updated, ["AlreadyCurrent"] = result.Unchanged });
|
|
editor.WriteMessage($"\nPXP-LN Apply: created={result.Created}; updated={result.Updated}; already-current={result.Unchanged}.");
|
|
}
|
|
catch (System.Exception exception)
|
|
{
|
|
RuntimeDiagnostics.Write("LineNumberPreviewFailed", "ERROR", commandName: "PXP-LN", resultStatus: exception.Message, exception: exception, document: document);
|
|
WriteEditor(document, $"\nPXP-LN: {exception.Message}");
|
|
}
|
|
}
|
|
|
|
private static void RunDiagnostic(string commandName)
|
|
{
|
|
var operationId = Guid.NewGuid().ToString("N");
|
|
var stopwatch = Stopwatch.StartNew();
|
|
Document? document = null;
|
|
RuntimeDiagnostics.Write("CommandStarted", operationId: operationId, commandName: commandName);
|
|
try
|
|
{
|
|
document = AcadApplication.DocumentManager.MdiActiveDocument;
|
|
var mode = ParseMode(RuntimeDiagnostics.RuntimeMode);
|
|
|
|
// Script-driven commands can run while Plant's implicit current
|
|
// document lags behind MDI activation. Bind the planner to the
|
|
// command document captured above, which is also the observer's
|
|
// fixture identity.
|
|
var context = ScriptDrivenFirstApplyAutomation.TryGetPreparedContext(document, out var scriptContext)
|
|
? scriptContext
|
|
: new PlantRuntimeContextReader().ReadCurrentContext(document);
|
|
var details = new Dictionary<string, object?>
|
|
{
|
|
["ExecutionMode"] = mode.ToString(),
|
|
["IsPlantProjectDetected"] = context.HasPlantProject,
|
|
["IsPlantProjectOpen"] = context.HasPlantProject,
|
|
["IsOrthoDrawing"] = context.IsOrthoDrawing,
|
|
["ViewportCount"] = context.Viewports.Count,
|
|
["DataLinksAvailable"] = DataLinksAvailable(context.HasPlantProject)
|
|
};
|
|
|
|
if (document is null || !context.HasPlantProject)
|
|
{
|
|
RuntimeDiagnostics.Write("CommandPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "PreflightRejected", document: document, details: details);
|
|
WriteEditor(document, "\nP_Rec_Annot: Активный Plant 3D-проект не найден; DWG не изменён.");
|
|
WriteUsage(operationId, commandName, "PreflightRejected", document, details, stopwatch.ElapsedMilliseconds);
|
|
RuntimeDiagnostics.Write("CommandCompleted", operationId: operationId, commandName: commandName, resultStatus: "PreflightRejected", document: document, details: details);
|
|
return;
|
|
}
|
|
|
|
if (mode == PRecAnnotExecutionMode.Smoke || !context.IsOrthoDrawing)
|
|
{
|
|
var status = context.IsOrthoDrawing ? "PreflightAccepted" : "NotOrtho";
|
|
RuntimeDiagnostics.Write("CommandPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: status, document: document, details: details);
|
|
WriteEditor(document, $"\nP_Rec_Annot {commandName}: проект '{context.ProjectDisplayName}' найден; тип чертежа: {(context.IsOrthoDrawing ? "Ortho" : "NotOrtho")}; DWG не изменён.");
|
|
WriteUsage(operationId, commandName, status, document, details, stopwatch.ElapsedMilliseconds);
|
|
RuntimeDiagnostics.Write("CommandCompleted", operationId: operationId, commandName: commandName, resultStatus: status, document: document, details: details);
|
|
return;
|
|
}
|
|
|
|
if (mode == PRecAnnotExecutionMode.Apply && !string.Equals(commandName, "PXP-OA", StringComparison.Ordinal))
|
|
throw new InvalidOperationException("APPLY_RULE_NOT_SUPPORTED");
|
|
var hasLimitedApplyPermit = !string.IsNullOrWhiteSpace(Environment.GetEnvironmentVariable("P_REC_ANNOT_APPLY_PERMIT_PATH"));
|
|
var applyPermit = (mode == PRecAnnotExecutionMode.Apply || hasLimitedApplyPermit) && document is not null
|
|
? FirstLimitedApplyPermitReader.ReadFor(document)
|
|
: null;
|
|
var automationTarget = ScriptDrivenFirstApplyAutomation.SelectedTargetSnapshotId
|
|
?? FirstLimitedApplyAutomationCoordinator.SelectedTargetSnapshotId;
|
|
var limitedTarget = applyPermit?.TargetSnapshotId ?? automationTarget;
|
|
if (mode == PRecAnnotExecutionMode.Apply && context.Viewports.Count != 1)
|
|
throw new InvalidOperationException("APPLY_REQUIRES_EXACTLY_ONE_VIEWPORT");
|
|
|
|
var builder = new AnnotationPlanBuilder();
|
|
var plans = new List<AnnotationPlan>();
|
|
var objectsByViewport = new List<IReadOnlyList<PlantObjectSnapshot>>();
|
|
foreach (var viewport in context.Viewports)
|
|
{
|
|
var objects = new PlantObjectReader().ReadObjects(viewport, CancellationToken.None);
|
|
objectsByViewport.Add(objects);
|
|
plans.Add(builder.Build(new AnnotationPlanningRequest
|
|
{
|
|
CommandName = commandName,
|
|
Viewport = viewport,
|
|
Objects = objects,
|
|
LimitedApplyTargetSnapshotId = limitedTarget,
|
|
LimitedApplyRuleId = limitedTarget is null ? null : FirstLimitedApplyPlanner.RuleId
|
|
}));
|
|
}
|
|
|
|
if (mode == PRecAnnotExecutionMode.DryRun &&
|
|
string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_DIAGNOSTICS"), "FirstLimitedApply", StringComparison.Ordinal))
|
|
{
|
|
var candidates = objectsByViewport.SelectMany(x => x)
|
|
.Where(x => string.Equals(x.ObjectClass, "Pipe", StringComparison.OrdinalIgnoreCase) && x.MembershipStatus == ViewMembershipStatus.Resolved && x.Geometry.PaperPosition is not null)
|
|
.Select(x => x.SnapshotId)
|
|
.OrderBy(x => x, StringComparer.Ordinal)
|
|
.ToArray();
|
|
WriteEditor(document, $"\nP_Rec_Annot Apply preview candidates: {string.Join(",", candidates)}");
|
|
details["ApplyPreviewCandidateCount"] = candidates.Length;
|
|
}
|
|
|
|
var summary = new
|
|
{
|
|
ObjectsExamined = plans.Sum(x => x.Summary.ObjectsExamined),
|
|
CreateCount = plans.Sum(x => x.Summary.CreateCount),
|
|
UpdateCount = plans.Sum(x => x.Summary.UpdateCount),
|
|
SkipCount = plans.Sum(x => x.Summary.SkipCount),
|
|
ConflictCount = plans.Sum(x => x.Summary.ConflictCount),
|
|
UnsupportedCount = plans.Sum(x => x.Summary.UnsupportedCount),
|
|
WarningCount = plans.Sum(x => x.Summary.WarningCount),
|
|
ErrorCount = plans.Sum(x => x.Summary.ErrorCount)
|
|
};
|
|
var membershipResolved = objectsByViewport.SelectMany(x => x).Count(x => x.MembershipStatus == ViewMembershipStatus.Resolved);
|
|
var membershipPartial = objectsByViewport.SelectMany(x => x).Count(x => x.MembershipStatus == ViewMembershipStatus.PartiallyResolved);
|
|
var membershipUnresolved = objectsByViewport.SelectMany(x => x).Count(x => x.MembershipStatus == ViewMembershipStatus.NotResolved);
|
|
var membershipOutside = objectsByViewport.SelectMany(x => x).Count(x => x.MembershipStatus == ViewMembershipStatus.ObjectOutsideView);
|
|
var orthoClassifications = objectsByViewport.SelectMany(x => x).OrderBy(x => x.SnapshotId, StringComparer.Ordinal)
|
|
.Select(x => new OrthoAnnotationClassifier().Classify(x, new(true, true, true, true, true))).ToArray();
|
|
var planId = plans.Count == 1 ? plans[0].PlanId : null;
|
|
details["ObjectsExamined"] = summary.ObjectsExamined;
|
|
details["PlanSchemaVersion"] = AnnotationPlanBuilder.CurrentPlanSchemaVersion;
|
|
details["PlanId"] = planId;
|
|
details["MembershipResolved"] = membershipResolved;
|
|
details["MembershipPartial"] = membershipPartial;
|
|
details["MembershipUnresolved"] = membershipUnresolved;
|
|
details["MembershipOutsideView"] = membershipOutside;
|
|
details["MultiLeaderCandidates"] = orthoClassifications.Count(x => x.Representation == OrthoAnnotationRepresentation.MultiLeader);
|
|
details["BubbleCandidates"] = orthoClassifications.Count(x => x.Representation == OrthoAnnotationRepresentation.BubbleBlock);
|
|
details["PlanCreateCount"] = summary.CreateCount;
|
|
details["PlanUpdateCount"] = summary.UpdateCount;
|
|
details["PlanSkipCount"] = summary.SkipCount;
|
|
details["PlanConflictCount"] = summary.ConflictCount;
|
|
details["PlanUnsupportedCount"] = summary.UnsupportedCount;
|
|
details["ErrorCount"] = summary.ErrorCount;
|
|
if (string.Equals(commandName, "PXP-OA", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
var candidates = objectsByViewport.SelectMany(x => x)
|
|
.Where(x => string.Equals(x.ObjectClass, "Pipe", StringComparison.OrdinalIgnoreCase) && x.MembershipStatus == ViewMembershipStatus.Resolved && x.Geometry.PaperPosition is not null)
|
|
.Select(x => x.SnapshotId).OrderBy(x => x, StringComparer.Ordinal).ToArray();
|
|
ScriptDrivenFirstApplyAutomation.RecordPxpOa(planId, candidates, context.Viewports.SingleOrDefault()?.Id, summary.CreateCount, summary.SkipCount, mode == PRecAnnotExecutionMode.Apply ? applyPermit is not null : null, summary.ObjectsExamined, membershipResolved);
|
|
FirstLimitedApplyAutomationCoordinator.RecordPxpOa(planId, candidates, context.Viewports.SingleOrDefault()?.Id, summary.CreateCount, summary.SkipCount, mode == PRecAnnotExecutionMode.Apply ? applyPermit is not null : null);
|
|
}
|
|
if (mode == PRecAnnotExecutionMode.Apply)
|
|
{
|
|
var corePermit = new FirstLimitedApplyPermit
|
|
{
|
|
FixtureMarker = FirstLimitedApplyPlanner.FixtureMarker,
|
|
AllowApply = applyPermit!.AllowApply,
|
|
TargetSnapshotId = applyPermit.TargetSnapshotId,
|
|
ExpectedPlanId = applyPermit.ExpectedPlanId
|
|
};
|
|
var preflight = new FirstLimitedApplyPlanner().Prepare(mode, plans.Single(), context.Viewports.Single(), objectsByViewport.Single(), corePermit);
|
|
new RuntimeWriteGuard(mode).EnsureWriteAllowed(preflight.Operation.OperationId);
|
|
RuntimeDiagnostics.Write("ApplyPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "Accepted", document: document);
|
|
RuntimeDiagnostics.Write("AnnotationTransactionStarted", operationId: operationId, commandName: commandName, document: document);
|
|
var outcome = new FirstLimitedPipeAnnotationWriter().Write(document!, preflight.Operation, applyPermit.VisibilityState, applyPermit.AnnotationTemplateDwgPath);
|
|
ScriptDrivenFirstApplyAutomation.RecordApplyOutcome(outcome);
|
|
FirstLimitedApplyAutomationCoordinator.RecordApplyOutcome(outcome);
|
|
details["ApplyOutcome"] = outcome.ToString();
|
|
details["AnnotationsCreated"] = outcome == PipeAnnotationWriteOutcome.Created ? 1 : 0;
|
|
details["AnnotationsSkipped"] = outcome == PipeAnnotationWriteOutcome.AlreadyExists ? 1 : 0;
|
|
RuntimeDiagnostics.Write(outcome == PipeAnnotationWriteOutcome.Created ? "AnnotationCreated" : "AnnotationDuplicateDetected", operationId: operationId, commandName: commandName, resultStatus: outcome.ToString(), document: document);
|
|
RuntimeDiagnostics.Write("AnnotationTransactionCommitted", operationId: operationId, commandName: commandName, resultStatus: outcome.ToString(), document: document);
|
|
}
|
|
RuntimeDiagnostics.Write("CommandPreflightCompleted", operationId: operationId, commandName: commandName, resultStatus: "DryRunCompleted", document: document, details: details);
|
|
WriteEditor(document, $"\nP_Rec_Annot — диагностический план\nПроект: {context.ProjectDisplayName}\nТип чертежа: Ortho\nViewport: {context.Viewports.Count}\nОбъектов найдено: {summary.ObjectsExamined}\nБудет создано: {summary.CreateCount}\nБудет обновлено: {summary.UpdateCount}\nБудет пропущено: {summary.SkipCount}\nКонфликтов: {summary.ConflictCount}\nОшибок: {summary.ErrorCount}\nDWG не изменён.");
|
|
WriteEditor(document, $"\nPlan schema: {AnnotationPlanBuilder.CurrentPlanSchemaVersion}; PlanId: {planId ?? "multiple"}; membership resolved/partial/outside/unresolved: {membershipResolved}/{membershipPartial}/{membershipOutside}/{membershipUnresolved}.");
|
|
var completedStatus = mode == PRecAnnotExecutionMode.Apply ? "ApplyCompleted" : "DryRunCompleted";
|
|
WriteUsage(operationId, commandName, completedStatus, document, details, stopwatch.ElapsedMilliseconds);
|
|
RuntimeDiagnostics.Write("CommandCompleted", operationId: operationId, commandName: commandName, resultStatus: completedStatus, document: document, details: details);
|
|
}
|
|
catch (System.Exception exception)
|
|
{
|
|
if (string.Equals(commandName, "PXP-OA", StringComparison.OrdinalIgnoreCase) && string.Equals(Environment.GetEnvironmentVariable("P_REC_ANNOT_RUNTIME_MODE"), "Apply", StringComparison.OrdinalIgnoreCase))
|
|
ScriptDrivenFirstApplyAutomation.RecordApplyFailure(exception);
|
|
RuntimeDiagnostics.Write("CommandFailed", "ERROR", operationId, commandName, exception.Message, exception, document);
|
|
WriteUsage(operationId, commandName, exception.Message, document, null, stopwatch.ElapsedMilliseconds, exception);
|
|
WriteEditor(document, $"\nP_Rec_Annot: безопасная диагностика завершилась ошибкой: {exception.Message}");
|
|
}
|
|
}
|
|
|
|
private static PRecAnnotExecutionMode ParseMode(string value) =>
|
|
value.Equals("Smoke", StringComparison.OrdinalIgnoreCase) ? PRecAnnotExecutionMode.Smoke :
|
|
value.Equals("Apply", StringComparison.OrdinalIgnoreCase) ? PRecAnnotExecutionMode.Apply : PRecAnnotExecutionMode.DryRun;
|
|
|
|
private static bool DataLinksAvailable(bool hasPlantProject)
|
|
{
|
|
try { return hasPlantProject && Autodesk.ProcessPower.PlantInstance.PlantApplication.ProjectPart("Piping")?.DataLinksManager is not null; }
|
|
catch { return false; }
|
|
}
|
|
|
|
private static void WriteUsage(string operationId, string commandName, string status, Document? document, IReadOnlyDictionary<string, object?>? details, long duration, System.Exception? exception = null)
|
|
{
|
|
var eventDetails = new Dictionary<string, object?>
|
|
{
|
|
["ObjectsExamined"] = 0, ["AnnotationsCreated"] = 0, ["AnnotationsUpdated"] = 0,
|
|
["AnnotationsDeleted"] = 0, ["AnnotationsSkipped"] = 0, ["WarningCount"] = 0,
|
|
["ErrorCount"] = exception is null ? 0 : 1, ["DurationMilliseconds"] = duration
|
|
};
|
|
if (details is not null) foreach (var item in details) eventDetails[item.Key] = item.Value;
|
|
RuntimeDiagnostics.Write("CommandUsageEvent", exception is null ? "INFO" : "ERROR", operationId, commandName, status, exception, document, eventDetails);
|
|
}
|
|
|
|
private static void WriteEditor(Document? document, string message)
|
|
{
|
|
try { document?.Editor.WriteMessage(message); } catch { }
|
|
}
|
|
}
|
|
|
|
internal static class RibbonIntegration
|
|
{
|
|
private const string TabId = "PRecAnnot.Plant3D2026";
|
|
private const string PanelId = "PRecAnnot.Plant3D2026.Annotations";
|
|
private const string ButtonId = "PRecAnnot.Plant3D2026.PHP_REC_ANNOT";
|
|
private static bool deferred;
|
|
|
|
public static void Register()
|
|
{
|
|
RuntimeDiagnostics.Write("RibbonInitializationStarted");
|
|
try
|
|
{
|
|
if (ComponentManager.Ribbon is null)
|
|
{
|
|
if (!deferred) { ComponentManager.ItemInitialized += OnItemInitialized; deferred = true; }
|
|
RuntimeDiagnostics.Write("RibbonInitializationCompleted", resultStatus: "Deferred");
|
|
return;
|
|
}
|
|
RegisterNow();
|
|
}
|
|
catch (System.Exception exception) { RuntimeDiagnostics.Write("RibbonInitializationCompleted", "ERROR", exception: exception, resultStatus: "Failed"); }
|
|
}
|
|
|
|
private static void OnItemInitialized(object? sender, EventArgs args)
|
|
{
|
|
if (ComponentManager.Ribbon is null) return;
|
|
ComponentManager.ItemInitialized -= OnItemInitialized;
|
|
deferred = false;
|
|
Register();
|
|
}
|
|
|
|
private static void RegisterNow()
|
|
{
|
|
var ribbon = ComponentManager.Ribbon!;
|
|
var tab = ribbon.Tabs.FirstOrDefault(x => string.Equals(x.Id, TabId, StringComparison.OrdinalIgnoreCase));
|
|
if (tab is null) { tab = new RibbonTab { Id = TabId, Title = "Промхимпроект" }; ribbon.Tabs.Add(tab); }
|
|
var panel = tab.Panels.FirstOrDefault(x => string.Equals(x.Source?.Id, PanelId, StringComparison.OrdinalIgnoreCase));
|
|
if (panel is not null) { RuntimeDiagnostics.Write("RibbonAlreadyExists", resultStatus: "AlreadyExists"); RuntimeDiagnostics.Write("RibbonInitializationCompleted", resultStatus: "AlreadyExists"); return; }
|
|
// Keep the existing legacy-compatible PHP_REC_ANNOT action and expose the
|
|
// production command surface from the same composite panel.
|
|
var source = new RibbonPanelSource { Id = PanelId, Title = "Аннотации" };
|
|
source.Items.Add(new RibbonButton { Id = ButtonId, Text = "Аннотировать", ShowText = true, CommandParameter = "PHP_REC_ANNOT " });
|
|
AddButton(source, "LineNumber", "Line number", "PXP-LN");
|
|
AddButton(source, "LineConnections", "Line connections", "PXP-ALC");
|
|
AddButton(source, "AutoElevation", "Auto elevation", "PXP-AE");
|
|
AddButton(source, "InsertElevation", "Insert elevation", "PXP-IE");
|
|
AddButton(source, "OrthoAnnotate", "Ortho annotate", "PXP-OA");
|
|
AddButton(source, "AnnotateView", "Annotate view", "PXP-AAV");
|
|
AddButton(source, "ManualPalette", "Manual annotations", "mto-ma");
|
|
tab.Panels.Add(new RibbonPanel { Source = source });
|
|
RuntimeDiagnostics.Write("RibbonInitializationCompleted", resultStatus: "Created");
|
|
}
|
|
|
|
private static void AddButton(RibbonPanelSource? source, string suffix, string text, string command)
|
|
{
|
|
if (source is null) return;
|
|
source.Items.Add(new RibbonButton { Id = $"{PanelId}.{suffix}", Text = text, ShowText = true, CommandParameter = command + " " });
|
|
}
|
|
|
|
public static void Terminate()
|
|
{
|
|
if (!deferred) return;
|
|
ComponentManager.ItemInitialized -= OnItemInitialized;
|
|
deferred = false;
|
|
}
|
|
}
|