Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
20960ae91f | ||
|
|
ca0f49ee5a |
@@ -0,0 +1,13 @@
|
|||||||
|
<Project>
|
||||||
|
<PropertyGroup>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<LangVersion>latest</LangVersion>
|
||||||
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
<DebugType>portable</DebugType>
|
||||||
|
<AutoCAD2026ManagedDir Condition="'$(AutoCAD2026ManagedDir)' == '' and '$(Plant3D2026InstallDir)' != ''">$(Plant3D2026InstallDir)</AutoCAD2026ManagedDir>
|
||||||
|
<Plant3D2026ManagedDir Condition="'$(Plant3D2026ManagedDir)' == '' and '$(Plant3D2026InstallDir)' != ''">$(Plant3D2026InstallDir)\PLNT3D</Plant3D2026ManagedDir>
|
||||||
|
<Plant3D2026InstallDir Condition="'$(Plant3D2026InstallDir)' == '' and '$(AutoCAD2026ManagedDir)' != ''">$(AutoCAD2026ManagedDir)</Plant3D2026InstallDir>
|
||||||
|
</PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.0.31903.59
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PRecUpdate.Core", "src\PRecUpdate.Core\PRecUpdate.Core.csproj", "{20000000-0000-0000-0000-000000000001}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PRecUpdate.Plant3D2026", "src\PRecUpdate.Plant3D2026\PRecUpdate.Plant3D2026.csproj", "{20000000-0000-0000-0000-000000000002}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PRecUpdate.Core.Tests", "tests\PRecUpdate.Core.Tests\PRecUpdate.Core.Tests.csproj", "{20000000-0000-0000-0000-000000000003}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{20000000-0000-0000-0000-000000000001}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000001}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000001}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000001}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000002}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000002}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000002}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000002}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000003}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000003}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{20000000-0000-0000-0000-000000000003}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
# P_Rec_Update capability contract
|
||||||
|
|
||||||
|
Selected legacy provenance is `\\phpsrv03\NBIM\code\CS\P_Rec_Update\P_Rec_Update\P_Rec_Update.csproj`
|
||||||
|
(SHA-256 `1945B572CEE3A308320C164549D38C95C4ED40183347CE341ACD481ED3895BFD`). It is the
|
||||||
|
expanded successor to the 2023 `_v2.1` line: it exposes `P-R-UPD`, initializes an
|
||||||
|
Update ribbon panel, and presents four selectable operations in `Main_Form`.
|
||||||
|
|
||||||
|
| Source-backed operation | Legacy effects | 2026 status |
|
||||||
|
|---|---|---|
|
||||||
|
| Project NKR | reads project XML and `Settings_NKR.xml`; updates NKR-related project data | DryRun plan and preconditions implemented; Apply blocked |
|
||||||
|
| Flanges | reads Piping DataLinks and geometry; may update support/flange-related data | DryRun plan and preconditions implemented; Apply blocked |
|
||||||
|
| Fasteners | reads EngineeringItems and may update ItemCode values | DryRun plan and preconditions implemented; Apply blocked |
|
||||||
|
| Project Spec | reads spec/project database and may update `LinearWeight` | DryRun plan and preconditions implemented; Apply blocked |
|
||||||
|
|
||||||
|
The 2026 entry assembly preserves `P-R-UPD` and adds `P-R-UPD-STATUS` for a
|
||||||
|
non-mutating controlled registration check. It is deterministic and refuses an
|
||||||
|
Apply-capable plan without an explicit controlled-fixture permit.
|
||||||
|
|
||||||
|
## Exact dependency blocker for domain Apply
|
||||||
|
|
||||||
|
The legacy code directly binds Plant 2019 DataLinks/SQLite schemas, the external
|
||||||
|
`GetComponentGeometryDescription.dll`, package-local `Settings_Flanges.xml` /
|
||||||
|
`Settings_NKR.xml`, and a hard-coded legacy template path. No controlled Plant 2026
|
||||||
|
fixture containing the required Piping rows, EngineeringItems/spec records and
|
||||||
|
expected post-state has been supplied. Therefore domain mutation is intentionally
|
||||||
|
not attempted. This is `EXACT_DEPENDENCY_BLOCKER`, not a claim of completed Apply
|
||||||
|
parity. Assembly load and command registration remain independently verifiable.
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup><TargetFramework>net8.0</TargetFramework></PropertyGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
namespace PRecUpdate.Core;
|
||||||
|
|
||||||
|
public enum UpdateOperation
|
||||||
|
{
|
||||||
|
ProjectNkr,
|
||||||
|
Flanges,
|
||||||
|
Fasteners,
|
||||||
|
ProjectSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum UpdateExecutionMode
|
||||||
|
{
|
||||||
|
DryRun,
|
||||||
|
Apply
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed record UpdateRequest(
|
||||||
|
bool HasActiveDocument,
|
||||||
|
bool HasPlantProject,
|
||||||
|
bool HasPipingProjectPart,
|
||||||
|
IReadOnlySet<UpdateOperation> SelectedOperations,
|
||||||
|
UpdateExecutionMode Mode,
|
||||||
|
bool ControlledFixturePermit);
|
||||||
|
|
||||||
|
public sealed record UpdatePlan(
|
||||||
|
bool Accepted,
|
||||||
|
IReadOnlyList<UpdateOperation> Operations,
|
||||||
|
IReadOnlyList<string> Diagnostics,
|
||||||
|
bool MayWriteDrawing,
|
||||||
|
bool MayWriteProjectData);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Source-backed planner for the four operations exposed by legacy P_Rec_Update.
|
||||||
|
/// It intentionally contains no Autodesk calls or file writes, which makes the
|
||||||
|
/// safety decision deterministic before a Plant transaction can be opened.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class UpdatePlanBuilder
|
||||||
|
{
|
||||||
|
private static readonly UpdateOperation[] CanonicalOrder =
|
||||||
|
[
|
||||||
|
UpdateOperation.ProjectNkr,
|
||||||
|
UpdateOperation.Flanges,
|
||||||
|
UpdateOperation.Fasteners,
|
||||||
|
UpdateOperation.ProjectSpec
|
||||||
|
];
|
||||||
|
|
||||||
|
public UpdatePlan Build(UpdateRequest request)
|
||||||
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(request);
|
||||||
|
var diagnostics = new List<string>();
|
||||||
|
if (!request.HasActiveDocument) diagnostics.Add("ACTIVE_DOCUMENT_REQUIRED");
|
||||||
|
if (!request.HasPlantProject) diagnostics.Add("PLANT_PROJECT_REQUIRED");
|
||||||
|
if (!request.HasPipingProjectPart) diagnostics.Add("PIPING_PROJECT_PART_REQUIRED");
|
||||||
|
if (request.SelectedOperations.Count == 0) diagnostics.Add("UPDATE_OPERATION_REQUIRED");
|
||||||
|
if (request.Mode == UpdateExecutionMode.Apply && !request.ControlledFixturePermit)
|
||||||
|
diagnostics.Add("CONTROLLED_FIXTURE_PERMIT_REQUIRED");
|
||||||
|
|
||||||
|
var ordered = CanonicalOrder.Where(request.SelectedOperations.Contains).ToArray();
|
||||||
|
var accepted = diagnostics.Count == 0;
|
||||||
|
var apply = accepted && request.Mode == UpdateExecutionMode.Apply;
|
||||||
|
return new UpdatePlan(accepted, ordered, diagnostics, apply, apply);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
using Autodesk.AutoCAD.Runtime;
|
||||||
|
|
||||||
|
[assembly: CommandClass(typeof(PRecUpdate.Plant3D2026.EntryPoint))]
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using Autodesk.AutoCAD.ApplicationServices;
|
||||||
|
using Autodesk.AutoCAD.Runtime;
|
||||||
|
using PRecUpdate.Core;
|
||||||
|
using AcadApplication = Autodesk.AutoCAD.ApplicationServices.Application;
|
||||||
|
|
||||||
|
namespace PRecUpdate.Plant3D2026;
|
||||||
|
|
||||||
|
public sealed class EntryPoint : IExtensionApplication
|
||||||
|
{
|
||||||
|
public void Initialize()
|
||||||
|
{
|
||||||
|
RuntimeMarker.Write("AssemblyInitializeStarted");
|
||||||
|
RuntimeMarker.Write("AssemblyInitializeCompleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Terminate() => RuntimeMarker.Write("AssemblyTerminateCompleted");
|
||||||
|
|
||||||
|
// Preserves the legacy command name. Apply remains denied unless a future
|
||||||
|
// controlled fixture adapter proves the legacy Plant/DataLinks semantics.
|
||||||
|
[CommandMethod("P-R-UPD", CommandFlags.Session)]
|
||||||
|
public void UpdateCommand() => ReportPlan("P-R-UPD");
|
||||||
|
|
||||||
|
[CommandMethod("P-R-UPD-STATUS", CommandFlags.Session)]
|
||||||
|
public void StatusCommand() => ReportPlan("P-R-UPD-STATUS");
|
||||||
|
|
||||||
|
private static void ReportPlan(string command)
|
||||||
|
{
|
||||||
|
var document = AcadApplication.DocumentManager.MdiActiveDocument;
|
||||||
|
var plan = new UpdatePlanBuilder().Build(new UpdateRequest(
|
||||||
|
document is not null,
|
||||||
|
HasPlantProject(),
|
||||||
|
HasPlantProject(),
|
||||||
|
new HashSet<UpdateOperation>
|
||||||
|
{
|
||||||
|
UpdateOperation.ProjectNkr, UpdateOperation.Flanges,
|
||||||
|
UpdateOperation.Fasteners, UpdateOperation.ProjectSpec
|
||||||
|
},
|
||||||
|
UpdateExecutionMode.DryRun,
|
||||||
|
ControlledFixturePermit: false));
|
||||||
|
document?.Editor.WriteMessage($"\nP_Rec_Update {command}: {(plan.Accepted ? "DryRun planned; no project data changed." : string.Join(", ", plan.Diagnostics))}");
|
||||||
|
RuntimeMarker.Write(plan.Accepted ? "CommandRegistrationVerified" : "CommandPreflightRejected");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool HasPlantProject()
|
||||||
|
{
|
||||||
|
try { return Autodesk.ProcessPower.PlantInstance.PlantApplication.CurrentProject is not null; }
|
||||||
|
catch { return false; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net8.0-windows</TargetFramework><PlatformTarget>x64</PlatformTarget>
|
||||||
|
<AssemblyName>PRecUpdate.Plant3D2026</AssemblyName>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup><ProjectReference Include="..\PRecUpdate.Core\PRecUpdate.Core.csproj" /></ItemGroup>
|
||||||
|
<ItemGroup Condition="Exists('$(AutoCAD2026ManagedDir)')">
|
||||||
|
<Reference Include="AcMgd"><HintPath>$(AutoCAD2026ManagedDir)\AcMgd.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference>
|
||||||
|
<Reference Include="AcDbMgd"><HintPath>$(AutoCAD2026ManagedDir)\AcDbMgd.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference>
|
||||||
|
<Reference Include="AcCoreMgd"><HintPath>$(AutoCAD2026ManagedDir)\AcCoreMgd.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference>
|
||||||
|
<Reference Include="PnPProjectManagerMgd" Condition="Exists('$(Plant3D2026ManagedDir)\PnPProjectManagerMgd.dll')"><HintPath>$(Plant3D2026ManagedDir)\PnPProjectManagerMgd.dll</HintPath><Private>false</Private><CopyLocal>false</CopyLocal></Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
using System.Security.Cryptography;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace PRecUpdate.Plant3D2026;
|
||||||
|
|
||||||
|
internal static class RuntimeMarker
|
||||||
|
{
|
||||||
|
internal static void Write(string state)
|
||||||
|
{
|
||||||
|
var root = Environment.GetEnvironmentVariable("P_REC_UPDATE_MARKER_ROOT");
|
||||||
|
if (string.IsNullOrWhiteSpace(root)) return;
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(root);
|
||||||
|
var payload = $"{DateTimeOffset.UtcNow:O}|{state}|{typeof(EntryPoint).Assembly.GetName().Name}";
|
||||||
|
File.WriteAllText(Path.Combine(root, state + ".marker"), payload, new UTF8Encoding(false));
|
||||||
|
}
|
||||||
|
catch { /* Evidence cannot make Plant initialization fail. */ }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static string AssemblySha256()
|
||||||
|
{
|
||||||
|
var location = typeof(EntryPoint).Assembly.Location;
|
||||||
|
using var stream = File.OpenRead(location);
|
||||||
|
return Convert.ToHexString(SHA256.HashData(stream));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<PropertyGroup><TargetFramework>net8.0</TargetFramework><IsPackable>false</IsPackable><IsTestProject>true</IsTestProject></PropertyGroup>
|
||||||
|
<ItemGroup><ProjectReference Include="..\..\src\PRecUpdate.Core\PRecUpdate.Core.csproj" /></ItemGroup>
|
||||||
|
<ItemGroup><PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" /><PackageReference Include="xunit" Version="2.5.3" /><PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" /></ItemGroup>
|
||||||
|
<ItemGroup><Using Include="Xunit" /></ItemGroup>
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
using PRecUpdate.Core;
|
||||||
|
|
||||||
|
namespace PRecUpdate.Core.Tests;
|
||||||
|
|
||||||
|
public sealed class UpdatePlanBuilderTests
|
||||||
|
{
|
||||||
|
private readonly UpdatePlanBuilder builder = new();
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void DryRun_orders_selected_operations_without_writes()
|
||||||
|
{
|
||||||
|
var plan = builder.Build(new UpdateRequest(true, true, true,
|
||||||
|
new HashSet<UpdateOperation> { UpdateOperation.Fasteners, UpdateOperation.ProjectNkr }, UpdateExecutionMode.DryRun, false));
|
||||||
|
Assert.True(plan.Accepted);
|
||||||
|
Assert.Equal(new[] { UpdateOperation.ProjectNkr, UpdateOperation.Fasteners }, plan.Operations);
|
||||||
|
Assert.False(plan.MayWriteDrawing);
|
||||||
|
Assert.False(plan.MayWriteProjectData);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Rejects_missing_project_and_empty_selection()
|
||||||
|
{
|
||||||
|
var plan = builder.Build(new UpdateRequest(false, false, false, new HashSet<UpdateOperation>(), UpdateExecutionMode.DryRun, false));
|
||||||
|
Assert.False(plan.Accepted);
|
||||||
|
Assert.Contains("ACTIVE_DOCUMENT_REQUIRED", plan.Diagnostics);
|
||||||
|
Assert.Contains("PLANT_PROJECT_REQUIRED", plan.Diagnostics);
|
||||||
|
Assert.Contains("PIPING_PROJECT_PART_REQUIRED", plan.Diagnostics);
|
||||||
|
Assert.Contains("UPDATE_OPERATION_REQUIRED", plan.Diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Apply_requires_explicit_controlled_fixture_permit()
|
||||||
|
{
|
||||||
|
var plan = builder.Build(new UpdateRequest(true, true, true,
|
||||||
|
new HashSet<UpdateOperation> { UpdateOperation.ProjectSpec }, UpdateExecutionMode.Apply, false));
|
||||||
|
Assert.False(plan.Accepted);
|
||||||
|
Assert.Contains("CONTROLLED_FIXTURE_PERMIT_REQUIRED", plan.Diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Permitted_apply_is_the_only_write_capable_plan()
|
||||||
|
{
|
||||||
|
var plan = builder.Build(new UpdateRequest(true, true, true,
|
||||||
|
new HashSet<UpdateOperation> { UpdateOperation.ProjectSpec }, UpdateExecutionMode.Apply, true));
|
||||||
|
Assert.True(plan.Accepted);
|
||||||
|
Assert.True(plan.MayWriteDrawing);
|
||||||
|
Assert.True(plan.MayWriteProjectData);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user