diff --git a/.gitignore b/.gitignore index 93d7e05bb..ceffe048d 100644 --- a/.gitignore +++ b/.gitignore @@ -225,3 +225,4 @@ test/SystemInJson # Icon resources /src/DynamoRevitIcons/*.resources +/DynamoRevitDA.bundle diff --git a/src/BUILDSYSTEM_TODO.md b/src/BUILDSYSTEM_TODO.md index 16feb3e37..4c17ab786 100644 --- a/src/BUILDSYSTEM_TODO.md +++ b/src/BUILDSYSTEM_TODO.md @@ -2,7 +2,7 @@ These are known issues and open design questions in the build system that need to be resolved, particularly for the `D4R_DA_2027` branch targeting Revit 2027 / -net10. +net10.0-windows. --- @@ -10,53 +10,53 @@ net10. `CS_SDK.props` defines three platforms: -| Platform | TargetFramework | Notes | -|------------|---------------------|-------| -| `NET80` | `net8.0-windows` | Interactive Revit build (Revit 2026) | -| `NET10_DA` | `net8.0-windows` | **Same TFM as NET80 on the 2026 branch — name implies net10 but it's net8** | -| `NET100` | `net10.0-windows` | Revit 2027 targeting net10 | +| Platform | TargetFramework | Notes | +|------------|----------------------|-------| +| `NET80` | `net8.0-windows` | Interactive Revit 2026 build — kept on this branch, not used for DA | +| `NET10_DA` | `net10.0-windows` | DA build for Revit 2027 — TFM now matches the name | +| `NET100` | `net10.0-windows` | Interactive Revit 2027 build | -For the `D4R_DA_2027` branch (Revit 2027), `NET100` is the primary TFM. -`NET10_DA` is now genuinely net10 on this branch (unlike on 2026), but the naming -is still confusing without context. +On the `D4R_DA_2027` branch `NET10_DA` and `NET100` share the same TFM +(`net10.0-windows`). The only distinction is the `_DA` suffix, which is a +meaningful build profile — not just a TFM alias. Any platform whose name +contains `_DA` triggers these behaviours across multiple projects: -**Important:** `_DA` in the platform name is a meaningful build profile — it is not just a -TFM alias. Any platform whose name contains `_DA` triggers these behaviours across multiple projects: - -| Project | What `_DA` removes | +| Project | What `_DA` activates | |---|---| | `CS_SDK.props` | Defines `DESIGN_AUTOMATION` compile constant | | `DynamoRevit.csproj` | Removes `DynamoRevit.cs`, `DynamoRevitApp.cs`, entire `ViewModel/` (interactive startup + ribbon) | | `RevitNodesUI.csproj` | Removes all WPF controls, selection dialogs, XAML pages | | `RevitServices.csproj` | Removes `Threading/*.cs` (UI thread marshalling, not needed in headless DA) | -So the `_DA` suffix carries real semantic weight. The problem is solely the `NET10` -prefix on the 2026 branch, which implied net10 when the TFM was actually net8. +`NET80` is also still present in `CS_SDK.props` — it is carried over from the 2026 +branch and is not needed for any DA work on 2027. It could be dropped from +`DynamoRevit.DA.sln` and DA-related props without impact. **Questions:** -- Should DA just use `NET100` with a separate property/flag to denote DA vs interactive, - rather than a separate platform name? -- If `NET10_DA` stays, should it be renamed (e.g. `NET100_DA`) to align with the actual TFM name? +- Should `NET10_DA` be renamed `NET100_DA` to make the TFM explicit and consistent + with the `NET100` sibling? +- Should `NET80` be removed from `DynamoRevit.DA.sln` and DA props entirely? +- Should DA just set a property/constant (e.g. `IsDesignAutomation=true`) under + `NET100` instead of maintaining a separate platform name? --- ## 2. Solution file responsibilities are unclear -| Solution | Projects included | Typical build command | -|-----------------------|--------------------------------|-----------------------| -| `DynamoRevit.DA.sln` | DADynamoApp + deps only | `/p:Platform=NET10_DA` | -| `DynamoRevit.All.sln` | Everything incl. tests | `/p:Platform=NET80` or `NET100` | +| Solution | Projects included | Platforms exposed | Typical build command | +|-----------------------|--------------------------------|-----------------------|-----------------------| +| `DynamoRevit.DA.sln` | DADynamoApp + deps only | `NET10_DA` | `/p:Platform=NET10_DA` | +| `DynamoRevit.All.sln` | Everything incl. tests | `NET80`, `NET100` | `/p:Platform=NET100` | -`DADynamoApp` is **not** listed explicitly in `All.sln` — it is pulled in as a -transitive project reference from `RevitSystemTests`. This causes the solution to -build it without a proper platform mapping, which leads to build errors under -`dotnet build` with .NET SDK 10 (see issue 3 below). +`DADynamoApp` is **not** present in `All.sln` at all (not even as a transitive +entry). Building `All.sln` will not produce the DA app bundle. The two solutions +are effectively independent build paths. **Questions:** -- Should `DADynamoApp` be a first-class explicit project in `All.sln`? -- Should `DA.sln` be a proper subset configuration of `All.sln`, or kept fully separate? -- Is there a reason DA uses `NET10_DA` platform instead of `NET100`? If not, unify them - and remove the extra platform. +- Should `DADynamoApp` be added to `All.sln` with a `NET100` → `NET10_DA` platform + mapping, so a single solution build covers everything? +- Should `DA.sln` remain the canonical path for DA-only work, or be retired in + favour of a configuration within `All.sln`? --- @@ -93,11 +93,72 @@ those build scripts to confirm. --- -## 4. Research needed / action items +## 4. Build/Package the AppBundle + +The target bundle layout is: + +``` +DynamoRevitDA.bundle/ + PackageContents.xml + Contents/ + + + Revit/ + DynamoRevit.addin + +``` + +### What is automated (DADynamoApp.csproj `publish_bundle` target) + +Running a build of `DADynamoApp.csproj` with `Platform=NET10_DA` now: + +1. Compiles `DADynamoApp` and writes output to `$(OutputPath)` (`bin\NET10_DA\\Revit\`). +2. **`Copy dll`** — copies `GregRevitAuth.dll` into `$(OutputPath)`. +3. **`CopyDynamoPlayerFiles`** — copies `$(PkgDynamoPlayer)\bin\Release\net8.0\bin\**` into `$(OutputPath)\..` (sibling to the Revit output folder). +4. **`publish_bundle`** — assembles the bundle at `$(SolutionDir)\..\DynamoRevitDA.bundle\`: + - Creates the `Contents\Revit\` directory tree. + - Copies `PackageContents.xml` (template at `src/DADynamoApp/app_bundle_template/`) to the bundle root. + - Copies `DynamoRevit.addin` (same template) into `Contents\Revit\`. + - Copies everything from `$(OutputPath)\..` (build output + DynamoPlayer binaries) into `Contents\Revit\`, preserving subdirectory structure via `GetFullPath` to correctly resolve `%(RecursiveDir)`. + +### What still requires a manual step (TODO) + +**DynamoCore** must be built separately from the [Dynamo](https://github.com/DynamoDS/Dynamo) repo (`DynamoCore.sln`) and its output copied into `Contents\` before the bundle is usable. There is currently no Windows CI pipeline for this — the existing pipeline produces a Linux/DAAS-targeted build only. + +The `publish_bundle` target has a commented-out stub (`TODO: copy DynamoCore contents`) that should be wired up once a reliable source for the Windows DynamoCore binaries exists. + +**Options to resolve:** +- Extend the existing Linux DAAS pipeline to produce an OS-agnostic DynamoCore NuGet/artifact that works for both DA and DAAS. +- Add a separate Windows CI pipeline for `DynamoCore.sln` and surface its output as a versioned artifact consumed here. +- For local development: build `DynamoCore.sln` locally and point `$(Pkgdynamovisualprogramming_servicecoreruntime)` (or an equivalent property) at the output before running `publish_bundle`. + + +--- + +## How to debug Design Automation locally + +There are two options depending on how closely you need to replicate the cloud environment: + +**Option 1 — Full Revit (with UI)** + +Use the [APS local debug tool](https://github.com/autodesk-platform-services/aps-automation-csharp-revit.local.debug.tool) against a standard Revit installation. This is the easiest way to validate general AppBundle functionality. + +**Option 2 — Headless Revit engine (matches cloud)** + +Download the exact Design Automation engine from Artifactory: +`https://art-bobcat.autodesk.com/ui/repos/tree/General/team-designautomation-generic/Revit/Engine` + +This is the same headless executable used on the cloud backend — it starts Revit without loading any UI. Before running it, edit `revitcoreconsole.dll.config` to point to your local Revit installation: + +```xml + +``` + +## 5. Research needed / action items -- [ ] Check `DynamoRevitUtils` Jenkins scripts: which solution, which platform, `dotnet` or VS MSBuild? -- [ ] Confirm whether `NET10_DA` is used in any CI job for this branch -- [ ] Decide on NET10_DA rename/merge with NET100 -- [ ] Decide whether All.sln should be the canonical "build everything" solution -- [ ] Fix `dotnet build` compatibility if CI requires it (MSB3992 + ResGen) -- [ ] Add `DADynamoApp` explicitly to `All.sln` with correct platform mapping +- [ ] Check `DynamoRevitUtils` Jenkins scripts: which solution (`DA.sln` or `All.sln`), which platform, `dotnet` or VS MSBuild? The `Jenkinsfile` delegates entirely to `DynamoRevitUtils/master` — the actual build commands are opaque from this repo. +- [ ] Confirm whether CI builds the DA bundle at all, or whether that is a manual step today. +- [ ] Decide on `NET10_DA` rename to `NET100_DA` (cosmetic but eliminates future confusion). +- [ ] Decide whether `NET80` should be removed from `DynamoRevit.DA.sln` — it is unused for DA on the 2027 branch. +- [ ] Decide whether `DADynamoApp` should be added to `All.sln` with a `NET100` → `NET10_DA` platform mapping. +- [ ] Fix `dotnet build` compatibility if CI requires it (MSB3992 on `DADynamoApp`, ResGen on `DynamoRevitIcons`). diff --git a/src/Config/packages_versions.props b/src/Config/packages_versions.props index abad0f6e6..0baa602f5 100644 --- a/src/Config/packages_versions.props +++ b/src/Config/packages_versions.props @@ -14,7 +14,7 @@ 3.0.2.8780 4.0.9460.41744 - net10.0 + net10.0 1.2.2 diff --git a/src/DADynamoApp/DAApplication.cs b/src/DADynamoApp/DAApplication.cs index 834c3415c..5fd10fee2 100644 --- a/src/DADynamoApp/DAApplication.cs +++ b/src/DADynamoApp/DAApplication.cs @@ -12,7 +12,6 @@ public class DAApplication : IExternalDBApplication { private string ParentPath; private string CurrentDirectory; - private readonly string PythonDllFolder = "pythonDependencies"; private DAEntrypoint daEntryPoint; @@ -57,7 +56,7 @@ private static void CurrentDomain_UnhandledException(object sender, UnhandledExc private Assembly? CurrentDomain_AssemblyResolve(object? sender, ResolveEventArgs args) { - return DynamoRevitAssemblyResolver.ResolveDynamoAssembly(ParentPath, [Path.Combine(CurrentDirectory, PythonDllFolder)], args); + return DynamoRevitAssemblyResolver.ResolveDynamoAssembly(ParentPath, [Path.Combine(CurrentDirectory)], args); } } } \ No newline at end of file diff --git a/src/DADynamoApp/DADynamoApp.csproj b/src/DADynamoApp/DADynamoApp.csproj index c9631efb7..3b8e7b60c 100644 --- a/src/DADynamoApp/DADynamoApp.csproj +++ b/src/DADynamoApp/DADynamoApp.csproj @@ -25,10 +25,10 @@ - + - + runtime @@ -40,19 +40,52 @@ + C:\Users\pinzart\.nuget\packages\dynamoplayer\7.0.3\bin\Release\net10.0\bin\DynamoPlayer.Models.dll $(PkgDynamoPlayer)\bin\Release\net8.0\bin\DynamoPlayer.Models.dll + $(PkgDynamoPlayer)\bin\Release\$(DotnetVerSubFolder)\bin\DynamoPlayer.Models.dll False - $(PkgDynamoPlayer)\bin\Release\net8.0\bin\DynamoPlayer.Server.dll + $(PkgDynamoPlayer)\bin\Release\$(DotnetVerSubFolder)\bin\DynamoPlayer.Server.dll False - $(PkgDynamoPlayer)\bin\Release\net8.0\bin\DynamoPlayer.Workflows.dll + $(PkgDynamoPlayer)\bin\Release\$(DotnetVerSubFolder)\bin\DynamoPlayer.Workflows.dll False + + + + + + + + + $(SolutionDir)\..\DynamoRevitDA.bundle\ + + + + + + + + + + $([System.IO.Path]::GetFullPath('$(OutputPath)..\')) + + + + + + diff --git a/src/DADynamoApp/DAEntrypoint.cs b/src/DADynamoApp/DAEntrypoint.cs index 5478e7086..74fe12161 100644 --- a/src/DADynamoApp/DAEntrypoint.cs +++ b/src/DADynamoApp/DAEntrypoint.cs @@ -1,12 +1,10 @@ using Autodesk.Revit.ApplicationServices; using Autodesk.Revit.DB; using DesignAutomationFramework; -using DSCPython; using Dynamo.Applications; using Dynamo.Graph.Nodes; using Dynamo.Graph.Workspaces; using Dynamo.Models; -using Dynamo.PythonServices; using Dynamo.Scheduler; using DynamoPlayer; using Greg.AuthProviders; @@ -14,10 +12,10 @@ using Newtonsoft.Json.Linq; using RevitServices.Elements; using RevitServices.Persistence; +using System.Diagnostics; using System.Reflection; using System.Text.RegularExpressions; using static Dynamo.Models.DynamoModel; -using DateTime = System.DateTime; namespace DADynamoApp { @@ -288,8 +286,6 @@ public void HandleDesignAutomationReadyEvent(object sender, DesignAutomationRead var loadedLibGVersion = ASMPrealoaderUtils.PreloadAsmFromRevit(asmLocation, DynamoPath); var geometryFactoryPath = ASMPrealoaderUtils.GetGeometryFactoryPath(DynamoPath, loadedLibGVersion); - PreInstallPythonDependencies(); - model = Dynamo.Applications.Models.RevitDynamoModel.Start( new DefaultStartConfiguration { @@ -320,6 +316,7 @@ public void HandleDesignAutomationReadyEvent(object sender, DesignAutomationRead DynamoPlayerLogger.Initialize(playerHost); + // Load dynamo packages from the '${WorkItemFolder}/Packages' folder. workflows.LoadDependencies(new GraphTarget() { DependenciesPath = WorkItemFolder }); var dynHandler = new Handler(playerHost, [new DARunGraphController(controller, model, WorkItemFolder)]); @@ -541,57 +538,5 @@ private void CleanupWorkspaceHandlers( workspaceHandlers.Remove(workspace); } - - private void PreInstallPythonDependencies() - { - try - { - // Preload all python assemblies at the PythonDllFolder. - foreach (var pyDll in Directory.EnumerateFiles(Path.Combine(WorkItemFolder, PythonDllFolder), "*.dll")) - { - Assembly.LoadFrom(pyDll); - } - - var pyIncluded = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(x => x.GetName().Name == "Python.Included"); - if (pyIncluded == null) - { - throw new Exception("Could not find Python.Included assembly"); - } - var type = pyIncluded.GetType("Python.Included.Installer"); - if (type == null) - { - throw new Exception("null Installer type"); - } - var property = type.GetProperty("INSTALL_PATH", BindingFlags.Public | BindingFlags.Static); - if (property == null) - { - throw new Exception("null INSTALL_PATH property"); - } - - // Set the python install location to the DA workfolder (that is the only place we have wrie access) - property.SetValue(null, WorkItemFolder); - - // Dynamo's 'VerifyEngineReferences' wants all the PythonEngine's dependencies to be in the Dynamo folder. - // Temporary until we fix it on the Dynamo side. - if (PythonEngineManager.Instance.AvailableEngines.Count == 0) - { - PropertyInfo instanceProp = typeof(CPythonEvaluator).GetProperty("Instance", BindingFlags.NonPublic | BindingFlags.Static); - if (instanceProp != null) - { - PythonEngine engine = (PythonEngine)instanceProp.GetValue(null); - if (engine == null) - { - throw new Exception($"Could not get a valid PythonEngine instance"); - } - - PythonEngineManager.Instance.AvailableEngines.Add(engine); - } - } - } - catch (Exception ex) - { - Console.WriteLine("Could not setup python " + ex.Message); - } - } } } diff --git a/src/DADynamoApp/app_bundle_template/DynamoRevitDA.bundle/Contents/Revit/DynamoRevit.addin b/src/DADynamoApp/app_bundle_template/DynamoRevitDA.bundle/Contents/Revit/DynamoRevit.addin new file mode 100644 index 000000000..e044ae3c0 --- /dev/null +++ b/src/DADynamoApp/app_bundle_template/DynamoRevitDA.bundle/Contents/Revit/DynamoRevit.addin @@ -0,0 +1,13 @@ + + + + DynamoDesignAutomation + .\DADynamoApp.dll + d7fe1983-8f10-4983-98e2-c3cc332fc978 + DADynamoApp.DAApplication + "Run Dynamo in Design Automation" + Autodesk + + + + diff --git a/src/DADynamoApp/app_bundle_template/DynamoRevitDA.bundle/PackageContents.xml b/src/DADynamoApp/app_bundle_template/DynamoRevitDA.bundle/PackageContents.xml new file mode 100644 index 000000000..be501bcc2 --- /dev/null +++ b/src/DADynamoApp/app_bundle_template/DynamoRevitDA.bundle/PackageContents.xml @@ -0,0 +1,15 @@ + + + + + + +