PDF Export, ForgeTypeId and Multi-Target Add-In

This is blog post number 1900, just fyi, cf. The Building Coder index and table of contents.

Revit 2022 has been released and the time has come to migrate to the new version.

Updates for RevitLookup, the Visual Studio Revit add-in wizards and The Building Coder samples are in the works and not done yet... one-man-band lagging... all Revit 2021 add-ins should work just fine in Revit 2022 as well, though.

Two important features are the parameter API enhancements and built-in PDF export functionality. Initial issues with these two have already been discussed:

Replace Deprecated ParameterType with ForgeTypeId

David Becroft of Autodesk and Maxim Stepannikov of BIM Planet, aka Максим Степанников or architect.bim, helped address the Revit API discussion forum question on Revit 2022 ParameterType.Text to ForgeTypeId:

Question: Could somebody please help me out with this conversion from the deprecated ParameterType to ForgeTypeId for the Revit 2022 API?

The 'old' code has a line like this:

  if( parameter.Definition.ParameterType 
    == ParameterType.Text )  ...

What would be the 2022 equivalent for it?

It seems that the left side may be:

  if( parameter.Definition.GetDataType() == ????)  ....

But, for some reason, I cannot find what I have to use on right side of the operation... there must be something I am overlooking.

Answer: To perform this check you need to create instance of ForgeTypeId class. Use one of the SpecTypeId's properties to get value to compare with. In your case (for text parameter) you need Number property:

>>> element.Parameter[BuiltInParameter.ALL_MODEL_MARK].Definition.GetSpecTypeId() == SpecTypeId.Number
True

Also take a look at the conversation on how to use ForgeTypeId.

Response: Oh dear! This is super confusing. I've just checked and parameter definitions that store TEXT have a SpecTypeId = SpecTypeId.Number.

Now how do you distinguish between actual Numbers and Text?

It looks like the problem is this:

The Autodesk.Revit.DB.InternalDefinition class has:

Now with ParameterType becoming obsolete, we have to use Parameter.GetSpecTypeId that SEEMINGLY corresponds to the UnitType member above, and for Text parameters like Comment, it has a value of SpecTypeId.Number!

The question is: How can I know if a Parameter is Text or not in Revit 2022 – without using Definition.ParameterType ?

Same would apply to YesNo type parameters... SpecTypeId cannot be used to determine if a ParameterDefinition is for a YesNo type parameter...

And even more: The ONLY place where I can see if a parameter is a YesNo parameter is in the Parameter.Definition.ParameterType !! If ParameterType is obsolete... how to determine if a parameter is YesNo or something else?

Answer: It is a well-known fact that unit type of text is number. Actually, I don't know why   :-)   But you definitely should use the Number property. Each Parameter object also has a StorageType property. In case of a Yes/No parameter, its value is Integer. In case of Text parameter, String. I hope this solves your problem.

Response: Well, sorry for my ignorance but seemingly there is still one 'minor' issue left for me to be answered:

  var option = new ExternalDefinitionCreationOptions( 
    "ExampleParamForge", SpecTypeId.XXX ???);

  var definition = definitionGroup.Definitions.Create( 
    option );

Using SpecTypeId.Number creates a Parameter that has the "Type of Parameter" set to Number (obviously!, right?)

How do I create a simple TEXT Parameter definition using the Revit 2022 API?

There must be something here that I am missing.

Answer: To create a text parameter, please use SpecTypeId.String.Text.

For context, the ForgeTypeId properties directly in the SpecTypeId class identify the measurable data types, like SpecTypeId.Length or SpecTypeId.Mass. The non-measurable data types are organized into nested classes within SpecTypeId, like SpecTypeId.String.Text, SpecTypeId.Boolean.YesNo, SpecTypeId.Int.Integer, or SpecTypeId.Reference.Material.

Regarding text parameters that report their type as "Number", here's the history:

Many thanks to Maxim and David for their clarification!

Multi-Target 2021 and 2022 Using MSBuild

Josiah Offord very kindly shared his solution to implement a multi-target add-in for several releases of Revit using the Microsoft Build Engine MSBuild in both a comment on multi-targeting Revit Versions using TargetFrameworks and in a dedicated Revit API discussion forum thread on multi-targeting 2021 and 2022 using MSBuild:

For those interested, you can configure a .csproj to multi-target both 2021 and 2022 on .NET Framework 4.8 using MSBuild. I posted this on a comment in The Building Coder blog and figured it'd be useful here too.

The first task is to choose what .NET 4.8 add-in you want to actively program against. There can only be one active add-in per .NET framework as far as I know. In this example, I'm setting my default to Revit 2022 using the custom RevitVersion property if it hasn't been configured yet.

<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop" InitialTargets="Test">
  ...
  <PropertyGroup>
    <TargetFrameworks>net461;net47;net472;net48</TargetFrameworks>
    <Configurations>Debug;Release</Configurations>
    <OutputPath>bin\$(Configuration)\</OutputPath>
    <UseWindowsForms>true</UseWindowsForms>
    <RevitVersion Condition=" '$(RevitVersion)' == '' ">2022</RevitVersion>
    ...
  </PropertyGroup>

Next, define configurations changes per each version.

<PropertyGroup Condition=" '$(TargetFramework)' == 'net461' ">
  <PlatformTarget>x64</PlatformTarget>
  <DefineConstants>DEBUG;REVIT2018</DefineConstants>
  <OutputPath>bin\$(Configuration)\2018\</OutputPath>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net47' ">
  <PlatformTarget>x64</PlatformTarget>
  <DefineConstants>$(DefineConstants);REVIT2019</DefineConstants>
  <OutputPath>bin\$(Configuration)\2019</OutputPath>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net472' ">
  <PlatformTarget>x64</PlatformTarget>
  <DefineConstants>$(DefineConstants);REVIT2020</DefineConstants>
  <OutputPath>bin\$(Configuration)\2020\</OutputPath>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net48' And '$(RevitVersion)' == '2021' ">
  <PlatformTarget>x64</PlatformTarget>
  <DefineConstants>$(DefineConstants);REVIT2021</DefineConstants>
  <OutputPath>bin\$(Configuration)\2021</OutputPath>
</PropertyGroup>

<PropertyGroup Condition=" '$(TargetFramework)' == 'net48' And '$(RevitVersion)' == '2022' ">
  <PlatformTarget>x64</PlatformTarget>
  <DefineConstants>$(DefineConstants);REVIT2022</DefineConstants>
  <OutputPath>bin\$(Configuration)\2022</OutputPath>
</PropertyGroup>

Next, load the proper dll references for each version.

<ItemGroup Condition=" '$(TargetFramework)' == 'net461' ">
  <Reference Include="AdWindows">
    <HintPath>C:\Program Files\Autodesk\Revit 2018\AdWindows.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPI">
    <HintPath>C:\Program Files\Autodesk\Revit 2018\RevitAPI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPIUI">
    <HintPath>C:\Program Files\Autodesk\Revit 2018\RevitAPIUI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net47' ">
  <Reference Include="AdWindows">
    <HintPath>C:\Program Files\Autodesk\Revit 2019\AdWindows.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPI">
    <HintPath>C:\Program Files\Autodesk\Revit 2019\RevitAPI.dll</HintPath>
    <Private>False</Private>
    <EmbedInteropTypes>false</EmbedInteropTypes>
  </Reference>
  <Reference Include="RevitAPIUI">
    <HintPath>C:\Program Files\Autodesk\Revit 2019\RevitAPIUI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net472' ">
  <Reference Include="AdWindows">
    <HintPath>C:\Program Files\Autodesk\Revit 2020\AdWindows.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPI">
    <HintPath>C:\Program Files\Autodesk\Revit 2020\RevitAPI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPIUI">
    <HintPath>C:\Program Files\Autodesk\Revit 2020\RevitAPIUI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net48' And '$(RevitVersion)' == '2021' ">
  <Reference Include="AdWindows">
    <HintPath>C:\Program Files\Autodesk\Revit 2021\AdWindows.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPI">
    <HintPath>C:\Program Files\Autodesk\Revit 2021\RevitAPI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPIUI">
    <HintPath>C:\Program Files\Autodesk\Revit 2021\RevitAPIUI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net48' And '$(RevitVersion)' == '2022' ">
  <Reference Include="AdWindows">
    <HintPath>C:\Program Files\Autodesk\Revit 2022\AdWindows.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPI">
    <HintPath>C:\Program Files\Autodesk\Revit 2022\RevitAPI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
  <Reference Include="RevitAPIUI">
    <HintPath>C:\Program Files\Autodesk\Revit 2022\RevitAPIUI.dll</HintPath>
    <EmbedInteropTypes>false</EmbedInteropTypes>
    <Private>false</Private>
  </Reference>
</ItemGroup>

At the end, you need to configure an additional build for whatever .NET 4.8 Revit add-in you didn't set as the default above. This is nice because it will catch build errors even though the active .NET 4.8 version is something else.

<Target Name="Test">
  <Message Importance="high" Text="-- Building $(MSBuildProjectFile), TF = $(TargetFramework), Config = $(Configuration), Revit Version = $(RevitVersion) --" />
</Target>
<Target Name="Build2021" BeforeTargets="DispatchToInnerBuilds">
  <Message Importance="high" Text="*** running pre-dispatch builds ***" />
  <MSBuild Projects="myProject.csproj" Properties="Configuration=$(Configuration);TargetFramework=net48;RevitVersion=2021"></MSBuild>
</Target>

Side note: Using a multi-target solution means you need to keep references to all the old versions of Revit. Be sure to copy out the needed dlls before removing that Revit version from your machine.

Hope this helps.

Thank you very much, Josiah, for this important and timely advice!

PDF Export Default Paper Format Can Fail

The new built-in PDF export is a certainly a very useful feature in Revit 2022 and has gathered a lot of interest.

Unfortunately, it also caused some misunderstandings, and a first problem was discovered and described in the thread on Revit 2022 PDF export fails with paper format set as default with other parameters.

The title says it all, and the development team explain:

By design, if PaperFormat is default, then PaperPlacement should always be Center. However, there is no restriction ensuring this on the API side. We should either silently set PaperPlacement to Center during export, or throw an exception notifying the add-in about this.

Currently, in this case, nothing happens and no warning or error is raised.

PDF Export Output File Naming

Another thread question why 2022 PDF exporter can't use the "sheet number" parameter.

Apparently, you have to be sure that the Sheet Number parameter from the "Parameter Type" drop down menu is selected from the Sheet type fields.

Also, if the sheet has no revision the filename, the words 'Current revision' may be inserted into the filename instead.

The development team confirm this fallback behaviour; if the parameter you selected is empty, it will fill the parameter name (Sample Value) in the filename.

The sheet number in the parameter set is confusing due to the parameter type. The designed scenario is: the customer will use this parameter only in either sheet or view, not in both of them. If you select a mixed type of both view and sheet with this parameter, one parameter will fallback to its name due to its absence in the view type.

Five Beginner Mistakes

Taking a quick look beyond Revit and .NET development for the desktop, the article on 5 mistakes beginner web developers make – and how to fix them addresses topics that are of use in a non-web environment as well, and that I actually adhere to pretty strictly myself on all platforms – possibly excepting the last – I am still practicing that:

Taking a break and reading in the Swiss winter sun