Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -225,3 +225,4 @@ test/SystemInJson

# Icon resources
/src/DynamoRevitIcons/*.resources
/DynamoRevitDA.bundle
135 changes: 98 additions & 37 deletions src/BUILDSYSTEM_TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,61 +2,61 @@

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.

---

## 1. Platform naming is confusing and redundant

`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`?

---

Expand Down Expand Up @@ -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/
<DynamoCore files — dlls, nodes, extensions, libg, …>
<DynamoPlayer binaries — copied by CopyDynamoPlayerFiles target>
Revit/
DynamoRevit.addin
<DADynamoApp build output — DADynamoApp.dll, DynamoRevitDS.dll, RevitNodes.dll, …>
```

### 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\<Configuration>\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
<add key="RevitPath" value="C:\Program Files\Autodesk\Revit 2027" />
```

## 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`).
2 changes: 1 addition & 1 deletion src/Config/packages_versions.props
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

<Greg_VERSION>3.0.2.8780</Greg_VERSION>
<GregRevitAuth_VERSION>4.0.9460.41744</GregRevitAuth_VERSION>
<GregRevitAuthLibSubFolder>net10.0</GregRevitAuthLibSubFolder>
<DotnetVerSubFolder>net10.0</DotnetVerSubFolder>

<!-- Keep in sync with DynamoCore & TODO: check if still needed -->
<DynamicLanguageRuntime_VERSION>1.2.2</DynamicLanguageRuntime_VERSION>
Expand Down
3 changes: 1 addition & 2 deletions src/DADynamoApp/DAApplication.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ public class DAApplication : IExternalDBApplication
{
private string ParentPath;
private string CurrentDirectory;
private readonly string PythonDllFolder = "pythonDependencies";

private DAEntrypoint daEntryPoint;

Expand Down Expand Up @@ -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);
}
}
}
41 changes: 37 additions & 4 deletions src/DADynamoApp/DADynamoApp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@
</Reference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Autodesk.Forge.DesignAutomation.Revit" Version="2026.0.0" />
<PackageReference Include="Autodesk.Forge.DesignAutomation.Revit" Version="2027.0.0" />
<PackageReference Include="Autodesk.DataManagement" Version="2.0.0-beta4" />
<PackageReference Include="DynamoVisualProgramming.Core" Version="$(DYNAMOCORE_VERSION)" ExcludeAssets="runtime" GeneratePathProperty="true" />
<PackageReference Include="DynamoPlayer" Version="6.0.2" ExcludeAssets="runtime" GeneratePathProperty="true" />
<PackageReference Include="DynamoPlayer" Version="7.0.3" ExcludeAssets="runtime" GeneratePathProperty="true" />
<PackageReference Include="GregRevitAuth" Version="$(GregRevitAuth_VERSION)" GeneratePathProperty="true">
<ExcludeAssets>runtime</ExcludeAssets>
</PackageReference>
Expand All @@ -40,19 +40,52 @@
</ItemGroup>
<ItemGroup>
<Reference Include="DynamoPlayer.Models">
<HintPath>C:\Users\pinzart\.nuget\packages\dynamoplayer\7.0.3\bin\Release\net10.0\bin\DynamoPlayer.Models.dll</HintPath>
<HintPath>$(PkgDynamoPlayer)\bin\Release\net8.0\bin\DynamoPlayer.Models.dll</HintPath>
<HintPath>$(PkgDynamoPlayer)\bin\Release\$(DotnetVerSubFolder)\bin\DynamoPlayer.Models.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="DynamoPlayer.Server">
<HintPath>$(PkgDynamoPlayer)\bin\Release\net8.0\bin\DynamoPlayer.Server.dll</HintPath>
<HintPath>$(PkgDynamoPlayer)\bin\Release\$(DotnetVerSubFolder)\bin\DynamoPlayer.Server.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="DynamoPlayer.Workflows">
<HintPath>$(PkgDynamoPlayer)\bin\Release\net8.0\bin\DynamoPlayer.Workflows.dll</HintPath>
<HintPath>$(PkgDynamoPlayer)\bin\Release\$(DotnetVerSubFolder)\bin\DynamoPlayer.Workflows.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<Target Name="Copy dll" AfterTargets="Build">
<Copy SourceFiles="$(PkgGregRevitAuth)\lib\$(DotnetVerSubFolder)\GregRevitAuth.dll" DestinationFolder="$(OutputPath)" />
</Target>
<Target Name="CopyDynamoPlayerFiles" AfterTargets="Build">
<ItemGroup>
<DynamoPlayerFiles Include="$(PkgDynamoPlayer)\bin\Release\$(DotnetVerSubFolder)\bin\**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(DynamoPlayerFiles)" DestinationFiles="@(DynamoPlayerFiles->'$(OutputPath)\..\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
<Target Name="publish_bundle" AfterTargets="CopyDynamoPlayerFiles">
<PropertyGroup>
<BundleOutputPath>$(SolutionDir)\..\DynamoRevitDA.bundle\</BundleOutputPath>
</PropertyGroup>
<MakeDir Directories="$(BundleOutputPath)Contents\Revit" />
<!-- Copy static bundle template files -->
<Copy SourceFiles="$(MSBuildProjectDirectory)\app_bundle_template\DynamoRevitDA.bundle\PackageContents.xml" DestinationFolder="$(BundleOutputPath)" />
<Copy SourceFiles="$(MSBuildProjectDirectory)\app_bundle_template\DynamoRevitDA.bundle\Contents\Revit\DynamoRevit.addin" DestinationFolder="$(BundleOutputPath)Contents\Revit\" />
<!-- TODO: copy DynamoCore contents (e.g. from $(Pkgdynamovisualprogramming_servicecoreruntime)\...) into $(BundleOutputPath)Contents\ -->
<!--
<ItemGroup>
<DynamoCoreFiles Include="$(Pkgdynamovisualprogramming_servicecoreruntime)\**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(DynamoCoreFiles)"
DestinationFiles="@(DynamoCoreFiles->'$(BundleOutputPath)Contents\%(RecursiveDir)%(Filename)%(Extension)')" />
-->
<!-- Copy everything from the OutputPath parent (net10.0 build output + DynamoPlayer binaries) into Contents/Revit -->
<PropertyGroup>
<RevitOutputParentPath>$([System.IO.Path]::GetFullPath('$(OutputPath)..\'))</RevitOutputParentPath>
</PropertyGroup>
<ItemGroup>
<RevitBuildFiles Include="$(RevitOutputParentPath)**\*.*" />
</ItemGroup>
<Copy SourceFiles="@(RevitBuildFiles)" DestinationFiles="@(RevitBuildFiles->'$(BundleOutputPath)Contents\%(RecursiveDir)%(Filename)%(Extension)')" />
</Target>
</Project>
59 changes: 2 additions & 57 deletions src/DADynamoApp/DAEntrypoint.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,21 @@
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;
using Newtonsoft.Json;
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
{
Expand Down Expand Up @@ -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
{
Expand Down Expand Up @@ -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)]);
Expand Down Expand Up @@ -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);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<RevitAddIns>
<AddIn Type="DBApplication">
<Name>DynamoDesignAutomation</Name>
<Assembly>.\DADynamoApp.dll</Assembly>
<AddInId>d7fe1983-8f10-4983-98e2-c3cc332fc978</AddInId>
<FullClassName>DADynamoApp.DAApplication</FullClassName>
<Description>"Run Dynamo in Design Automation"</Description>
<VendorId>Autodesk</VendorId>
<VendorDescription>
</VendorDescription>
</AddIn>
</RevitAddIns>
Loading