View Categories

The simplest plugin project that just works

4 min read

Motivation #

To have a AutoCAD/Civil 3D plugin project in Visual Studio setup and running is not easy. Not least because you have to know how to launch AutoCAD/Civil 3D from the Visual Studio to load your plugin . To get it to work, each developers might have to make modifications to their config files due to that the directory of the repos are different for different developers. This makes collaboration and the whole plugin setup process hard, because you might have to exclude an otherwise important configuration files from your git repo.

So here, I would like to provide the simplest plugin project that just works – you can copy and paste everything here into respective locations, and it will just work. You and your fellow developers can check in the code and configs to the git repo, and everyone can modify the code and configs confidently, knowing that once the other parties checkout the code, it will just work, no need to do extra fiddling because now the repo doesn’t depend on the absolute path of your repo.

Machine and AutoCAD setup #

But still, you have to do a bit of environmental setup.

First, you have to define a new environment variable %AutoCADDir% in your Windows machine ( I assume that you are running on Windows, because Civil 3D can only run on Windows). This is essentially just the directory of your AutoCAD program. Usually it’s the C:\Program Files\Autodesk\AutoCAD %Year%

Then, you have to ensure that the directory where your plugin DLL is located is in a Trusted Location of AutoCAD. If not then your plugin won’t run.

The minimum project #

After you have done the setup properly as per the above, then everything is easy; you just have to copy and paste the below code and scripts to their respective place, and compile them in VS 2022. Everything will just work.

The csproject #

Here’s the code for csproject.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net8.0-windows</TargetFramework>
    <UseWPF>true</UseWPF>
    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
    <EnableDefaultItems>true</EnableDefaultItems>
  </PropertyGroup>

  <ItemGroup>
    <None Remove="Properties\launchSettings.template.json" />
    <None Remove="LoadPlugin.scr.template" />
  </ItemGroup>

  <ItemGroup>
    <PackageReference Include="AutoCAD.NET" Version="25.0.1" />
	<PackageReference Include="Civil3D.NET" Version="13.7.1175" /> <!-- civil 3D 2025, you can have your own Civil 3D version -->
  </ItemGroup>

  <ItemGroup>
    <Folder Include="Properties\" />
  </ItemGroup>

  <Target Name="GenerateLaunchSettings" BeforeTargets="Build">  <!-- Generate LaunchSettings.json on the fly based on a template-->
    <PropertyGroup>
      <ProjectDir>$(MSBuildProjectDirectory)</ProjectDir>
      <LaunchTemplate>$(ProjectDir)\Properties\launchSettings.template.json</LaunchTemplate>
      <LaunchOutput>$(ProjectDir)\Properties\launchSettings.json</LaunchOutput>

      <ProjectDirEscaped>$([System.String]::Copy('$(ProjectDir)').Replace('\', '\\'))</ProjectDirEscaped>
    </PropertyGroup>

    <ReadLinesFromFile File="$(LaunchTemplate)">
      <Output TaskParameter="Lines" ItemName="LaunchJsonLines" />
    </ReadLinesFromFile>

    <WriteLinesToFile File="$(LaunchOutput)" Lines="@(LaunchJsonLines-&gt;Replace('{{LaunchDir}}', '$(ProjectDirEscaped)'))" Overwrite="true" />
  </Target>

  <Target Name="GenerateLoadPluginScript" AfterTargets="Build"> <!-- Generate LoadPlugin.scr on the fly based on a template-->
    <PropertyGroup>
      <TemplatePath>$(MSBuildProjectDirectory)\LoadPlugin.scr.template</TemplatePath>
      <OutputScriptPath>$(MSBuildProjectDirectory)\LoadPlugin.scr</OutputScriptPath>
      <DllFullPath>$(TargetPath)</DllFullPath>
    </PropertyGroup>

    <Message Text="Reading template from: $(TemplatePath)" Importance="High" />
    <Message Text="Writing output to: $(OutputScriptPath)" Importance="High" />
    <Message Text="Replacing $DLLPATH$ with: $(DllFullPath)" Importance="High" />

    <ReadLinesFromFile File="$(TemplatePath)">
      <Output TaskParameter="Lines" ItemName="TemplateLine" />
    </ReadLinesFromFile>

    <Message Text="Template content read: @(TemplateLine)" Importance="High" />

    <ItemGroup>
      <ProcessedLines Include="$([System.String]::Copy('%(TemplateLine.Identity)').Replace('$DLLPATH$', '$(DllFullPath)'))" />
    </ItemGroup>

    <WriteLinesToFile File="$(OutputScriptPath)" Lines="@(ProcessedLines)" Overwrite="true" />

    <Message Text="Generated LoadPlugin.scr with contents:" Importance="High" />
    <Message Text="@(ProcessedLines)" Importance="High" />
  </Target>

</Project>

What is important about the csproject is that it generates the launchSettings.json and LoadPlugin.scr files on the fly because these two files must contain the absolute path of your plugin DLL, by taking into the consideration of where the $(ProjectDir) is located. So after your project is compiled, the VS 2022 will create/update the launchSettings.json and LoadPlugin.scr so that they insert in proper absolute path.

The LoadPlugin.scr.template #

NETLOAD "$DLLPATH$"

Note that in the template file, we don’t hardcode the DLLPath, instead we will let the after build event in VS 2022 to generate it when we create the LoadPlugin.scr file

The Properties\launchSettings.template.json #

As per the convention with launchSettings.json, the launchSettings.template.json must also located in the Properties folder. Here’s the content of it

{
  "profiles": {
    "AutoCAD": {
      "commandName": "Executable",
      "executablePath": "%AutoCADDir%\\acad.exe",
      "commandLineArgs": "/ld \"%AutoCADDir%\\AecBase.dbx\" /p \"<<C3D_Metric>>\" /product C3D /b \"{{LaunchDir}}\\LoadPlugin.scr\"",
      "workingDirectory": "{{LaunchDir}}\\bin\\Debug"
    }
  }
}

HookEntry.cs #

In the code I just alert the user if the relevant sections are entered

using System.Windows;
using Autodesk.AutoCAD.Runtime;
using DummDLL;
[assembly: ExtensionApplication(typeof(HookEntry))]
[assembly: CommandClass(typeof(HookEntry))]
namespace DummDLL
{

    public class HookEntry : IExtensionApplication
    {
        public HookEntry()
        {
            MessageBox.Show($"Constructor entered,{this.GetHashCode()}");
        }

        public void Initialize()
        {
            MessageBox.Show($"Initialize method entered,{this.GetHashCode()}");
        }

        public void Terminate()
        {
           
        }


        [CommandMethod(nameof(RunCommand2))]
        public void RunCommand2()
        {
            MessageBox.Show($"{nameof(RunCommand2)},{this.GetHashCode()}");

        }

        [CommandMethod(nameof(RunCommand))]
        public void RunCommand()
        {
            MessageBox.Show($"{nameof(RunCommand)},{this.GetHashCode()}");

        }
    }
}

The .gitignore file #

We want to store our code in the git repository, and we want to make sure that the stage area is clean when we are done, that’s why we must exclude the launchSettings.json and LoadPlugin.scr from the source control. This won’t be a problem for other developers who clone our repo because as mentioned, the After Build Event in Visual Studio will be able to recreate the necessary files.

/Properties/launchSettings.json
LoadPlugin.scr

Recap #

So that’s it. Once you do the machine/AutoCAD setup, and you put the above files into appropriate location, and you compile the csproject, and you press F5 in the Visual Studio, your plugin will automatically be loaded, and you will be greeted with MessageBoxindicating that you have successfully setup your plugin for development and debugging.

Powered by BetterDocs