Multi-Targeting Revit Versions, CAD Terms, Textures

Here comes another solution for efficiently compiling add-ins for multiple Revit version targets from one single code base, a note on resources for CAD term databases for consistent terminology translation, and a discussion on accessing custom texture maps in Forge and Revit:

Change target

Multi-Targeting Revit Versions Using TargetFrameworks

Just recently, we pointed out a suggestion for compiling add-ins for multiple Revit versions.

Here are some other, previous, related discussions:

Today, Olivier 'Vilo' Bastide of BBS Slama, éditeur de logiciels de calculs thermiques, thermal calculation software editor, suggests a different and simpler approach making use of the TargetFrameworks functionality, and a further enhancement to that using the Import tag.

In his own words:

I'd like to give you some new stuff I've recently found, that can vastly simplify Revit add-in deployment.

This is about add-in multi-targeting.

Targeting all Revit versions can become a nightmare in terms of VS projects.

I'm personally developing 6 projects for Revit, and I have to compile each one for all Revit versions (2016 to 2019), resulting in 24 projects to maintain.

Source code is (thankfully) unique using conditional coding, but having all these projects is not elegant at all.

You recently posted a suggestion for handling this, but after some tuning I couldn't manage to make it work as I wanted.

So, I dug into new csproj format (introduced with .NET Core) and managed to find a pretty simple solution to target all Revit versions with only one csproj and source code per project.

Here is the csproj code skeleton; it works with the latest VS Community 2017, at least:

  <Project Sdk="Microsoft.NET.Sdk">
    <PropertyGroup>
        <TargetFrameworks>net452;net46;net47</TargetFrameworks>
        <Configurations>Debug;Release</Configurations>
        <Platforms>x64</Platforms>
        <OutputPath>bin\$(Configuration)\</OutputPath>
    </PropertyGroup>
    
    <PropertyGroup Condition="'$(Configuration)'=='Debug'">
        <PlatformTarget>x64</PlatformTarget>
        <DefineConstants>DEBUG</DefineConstants>
        <Optimize>false</Optimize>
        <DebugType>full</DebugType>
        <DebugSymbols>true</DebugSymbols>
    </PropertyGroup>

    <PropertyGroup Condition="'$(Configuration)'=='Release'">
        <PlatformTarget>x64</PlatformTarget>
        <DebugType>none</DebugType>
        <DebugSymbols>false</DebugSymbols>
    </PropertyGroup>

    <PropertyGroup Condition=" '$(TargetFramework)' == 'net452' ">
        <DefineConstants>$(DefineConstants);REVIT2017</DefineConstants>
        <AssemblyName>SomeProject_2017</AssemblyName>
    </PropertyGroup>

    <PropertyGroup Condition=" '$(TargetFramework)' == 'net46' ">
        <DefineConstants>$(DefineConstants);REVIT2018</DefineConstants>
        <AssemblyName>SomeProject_2018</AssemblyName>
    </PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'net47' ">
    <DefineConstants>$(DefineConstants);REVIT2019</DefineConstants>
    <AssemblyName>SomeProject_2019</AssemblyName>
  </PropertyGroup>

  <ItemGroup Condition=" '$(TargetFramework)' == 'net452' ">
        <Reference Include="AdWindows, Version=2015.11.1.0, Culture=neutral, PublicKeyToken=null">
            <HintPath>......\2017\AdWindows.dll</HintPath>
            <EmbedInteropTypes>false</EmbedInteropTypes>
            <Private>false</Private>
        </Reference>
        <Reference Include="RevitAPI">
            <HintPath>......\2017\RevitAPI.dll</HintPath>
            <EmbedInteropTypes>false</EmbedInteropTypes>
            <Private>false</Private>
        </Reference>
        <Reference Include="RevitAPIUI">
            <HintPath>......\2017\RevitAPIUI.dll</HintPath>
            <EmbedInteropTypes>false</EmbedInteropTypes>
            <Private>false</Private>
        </Reference>
    </ItemGroup>

    <ItemGroup Condition=" '$(TargetFramework)' == 'net46' ">
        <Reference Include="AdWindows">
            <HintPath>......\2018\AdWindows.dll</HintPath>
            <EmbedInteropTypes>false</EmbedInteropTypes>
            <Private>false</Private>
        </Reference>
        <Reference Include="RevitAPI">
            <HintPath>......\2018\RevitAPI.dll</HintPath>
            <EmbedInteropTypes>false</EmbedInteropTypes>
            <Private>false</Private>
        </Reference>
        <Reference Include="RevitAPIUI">
            <HintPath>......\2018\RevitAPIUI.dll</HintPath>
            <EmbedInteropTypes>false</EmbedInteropTypes>
            <Private>false</Private>
        </Reference>
    </ItemGroup>

  <ItemGroup Condition=" '$(TargetFramework)' == 'net47' ">
    <Reference Include="AdWindows">
      <HintPath>......\2019\AdWindows.dll</HintPath>
      <EmbedInteropTypes>false</EmbedInteropTypes>
      <Private>false</Private>
    </Reference>
    <Reference Include="RevitAPI">
      <HintPath>......\2019\RevitAPI.dll</HintPath>
      <EmbedInteropTypes>false</EmbedInteropTypes>
      <Private>false</Private>
    </Reference>
    <Reference Include="RevitAPIUI">
      <HintPath>......\2019\RevitAPIUI.dll</HintPath>
      <EmbedInteropTypes>false</EmbedInteropTypes>
      <Private>false</Private>
    </Reference>
  </ItemGroup>

  <ItemGroup>
    <Compile Include="SomeFile.cs">
    </Compile>
  </ItemGroup>

Obviously, you will have to customize the file paths appropriately...

By using the new TargetFrameworks mechanism, it is possible to configure separate settings for each version.

Actually, the trick is possible because each Revit version is targeting a different .NET framework...

I've included 'non-mandatory' things in the above example.

For example, the AssemblyName tag is optional, but I've included it to show how to change the output DLL filename according to the Revit version.

When a new Revit version is released, the only thing to do is to declare a new target in the TargetFrameworks tag and configure the matching ItemGroup to tell VS where to find new SDK's DLLs.

If the new version does not target a different .NET framework, it is possible to keep the thing working by targeting a sub-version (like 4.7.1).

Finally, to make your source code version agnostic, you can use conditional definitions such as REVIT2017, REVIT2018, REVIT2019, etc., as usual.

Hope this will help.

Further Enhancement Using the CSPROJ Import Tag

MultiTarget addins : season 01 episode 02...

Here is an addendum to my preceding suggestion with a further enhancement.

In the eternal fight to reduce efforts involved to obtain the same achievements, I've made some new progress.

The csproj format (both old and new) supports a kind of sequential inheritance through the tag <Import>.

Using that, you can tailor a 'root' .csproj file that contains all the basic logic about Revit versions, and then inherits from it in each of the 'real' projects, overriding only the specific varying tags.

Here is a sample root csproj, named C:\ROOT.CSPROJ for this sample to work:

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup Condition="'$(TargetFrameworks)'==''">
    <TargetFrameworks>net45;net452;net46;net47</TargetFrameworks>
  </PropertyGroup>
  
  <PropertyGroup>
    <Configurations>Debug;Release</Configurations>
    <Platforms>x64</Platforms>
    <EnableDefaultCompileItems>false</EnableDefaultCompileItems>
    <EnableDefaultItems>false</EnableDefaultItems>
  </PropertyGroup>
  
  <PropertyGroup Condition=" '$(TargetFramework)' == 'net45' ">
    <RevitVersion>2016</RevitVersion>
  </PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'net452' ">
    <RevitVersion>2017</RevitVersion>
  </PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'net46' ">
    <RevitVersion>2018</RevitVersion>
  </PropertyGroup>

  <PropertyGroup Condition=" '$(TargetFramework)' == 'net47' ">
    <RevitVersion>2019</RevitVersion>
  </PropertyGroup>
  
  <PropertyGroup>
    <DefineConstants>$(DefineConstants);REVIT$(RevitVersion)</DefineConstants> <!-- defines conditional directive REVIT2016, REVIT2017 ... -->
    <AssemblyName>$(PrefixAssembly)_$(RevitVersion)</AssemblyName>
  </PropertyGroup>
  
  <PropertyGroup>
    <Path_DLL_API>... FQPN to folder with API dlls, separated into subfolders named 2016, 2017, 2018, 2019, etc ...</Path_DLL_API>
  </PropertyGroup>
  
  <ItemGroup>
  <Reference Include="AdWindows">
    <HintPath>$(Path_DLL_API)\$(RevitVersion)\AdWindows.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPI">
    <HintPath>$(Path_DLL_API)\$(RevitVersion)\RevitAPI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPIUI">
    <HintPath>$(Path_DLL_API)\$(RevitVersion)\RevitAPIUI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  </ItemGroup>
</Project>

This one is suitable to compile for Revit versions 2016 to 2019 (by targeting 4 different frameworks).

And here is a sample child project using it:

<Project>
    <!-- this group must stay before <Import> !! -->
  <PropertyGroup>
<!-- this tag allows generic naming, for example this will lead to assemblies named "SomeAssembly_2016", "SomeAssembly_2017", ... -->
    <PrefixAssembly>SomeAssembly</PrefixAssembly> 

<!-- by setting this tag here, you can override default behaviour that is to compile for versions 2016 to 2019
Delete it to restore default behaviour and compile for 2016 to 2019 -->
    <TargetFrameworks>net452</TargetFrameworks>
  </PropertyGroup>

<!-- import root csproj -->
  <Import Project="C:\ROOT.CSPROJ" /> <!-- FQPN to file defined above -->

<!-- all other stuff that is project specific -->
    ...
</Project>

Of course, these 2 samples are just a proof of concept, and tailored for my own needs.

Feel free to make what you want with it ;-)

Many thanks to Olivier 'Vilo' for researching and sharing this very sensible solution!

Vilo responds: Cool     ;-)     Hope it will help some people simplify their DevEnv.

Jeremy answers: if people take the trouble to read and the time to improve, it will for sure     :-)     all too often, however, one does not take the time to improve things, even though it would save ten times the time it costs to make the improvement...

CAD Terminology Resources for Consistent Translation

People occasionally ask for help translating CAD terms, and I already mentioned a couple of useful resources for this in the past.

This question came up again in the Revit API discussion forum thread on a dictionary for Revit and AutoCAD terms for localisation:

Question: I want to use the correct Autodesk terms in our localized add-in user documentation.

I am searching for a table of translations of Revit and AutoCAD terms into multiple languages which can be used for our automated translation dictionary. I just need a mapping English term → Localized term. The supported languages are currently German, French, Spanish (Mexico), Chinese (Simplified), Portuguese (Brazil), Japanese and Italian.

I found a good Glossary of German AutoCAD Terms which can be displayed in different languages.

I see that, e.g., LAYER in English is the same in German, but SOLID is VOLUMENKÖRPER.

I would prefer a table instead of an HTML page for more automation.

Answer: I provided an answer to a similar question on CAD terminology translation back in 2014.

It mentions a number of useful resources back then.

Here is an expanded and updated list for today:

The latter resource is product agnostic, so it will hopefully include all you need for Revit, AEC and BIM.

Showing a Custom Revit Texture Map in the Forge Viewer

To help address the recurring question of accessing texture map data in a Revit model, my colleague Eason Kang researched and published how to show Revit custom texture map in the Forge Viewer.

Many thanks to Eason for sharing this important information!