GH NET7 Visual Studio Template: Bitmap could not be found

I’m setting up a new Grasshopper project with the VS templates for Rhino8 (from here). I’m not ticking WPF or WinForm as ideally I want it to be cross platform.

However opening the template straight out of box wont allow me to compile. The Grasshopper component is refering to System.Drawing.Bitmap which is not part of .NET7.

Looks like the grasshopper template component is dependent on .NET Framework 4.8, which I don’t fully understand if we’re all switching to .NET7 ?

3 Likes

Looks like the core (pun intended) of the problem is that NuGet only refers to Framework48 versions of Grasshopper and RhinoCommon.

1 Like

Semi solved by adding System.Drawing.Common to my project, which allows to compile the .net7 in windows.

In order to remove the Framework48 warnings i ended up pointing my

Grasshopper dll to C:\Program Files\Rhino 8\Plug-ins\Grasshopper\Grasshopper.dll
RhinoCommon to C:\Program Files\Rhino 8\System\netcore\RhinoCommon.dll

which I’m very sure is NOT the intended way to do it. However I’m unsure how to get .NET 7 versions of RhinoCommon.dll and Grasshopper.dll

2 Likes

System.Drawing is a Nuget package in Core. I think you only need to satisfy the compiler/linker, but you don’t need to ship it. I think Eto/Rhino can handle it both for Mac and Windows. Still its not good that a template does not work out of the box.

Agreed,

I see two problems though:

  1. Templates not working out of the box
  2. NuGet versions of Grasshopper and RhinoCommon are not .NET 7 Compatible

Regarding 2. This is what I would have also done. There was a time before VS Templates. I think its worth to dig into the old Grasshopper SDK. Its not to hard to follow and gives a decent idea on what actually happens.

Agreed and thanks for reminding. Heard mcneel peeps say on forum that people should use the nuget packages though.

I have tested with template and started from scratch and always ending up getting fails about assemblies…

  1. Created Class library - C# for .net 7
  2. Project Settings
   <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
	  <Version>1.0</Version>
	  <Title>UserPrincipleTest2</Title>
	  <Description>Description of UserPrincipleTest2</Description>
	  <TargetExt>.gha</TargetExt>    
  </PropertyGroup>
  1. Added two classes into project (same as creating with template)
  • UserPrincipleTestComponent2.cs
  • UserPrincipleTest2Info.cs
  1. Added references
  <ItemGroup>
    <Reference Include="GH_IO">
      <HintPath>..\..\..\..\Program Files\Rhino 8\Plug-ins\Grasshopper\GH_IO.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Grasshopper">
      <HintPath>..\..\..\..\Program Files\Rhino 8\Plug-ins\Grasshopper\Grasshopper.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RhinoCommon">
      <HintPath>..\..\..\..\Program Files\Rhino 8\System\RhinoCommon.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>
  1. Added two nuget packages
  <ItemGroup>
    <PackageReference Include="System.DirectoryServices.AccountManagement" Version="8.0.0" />
    <PackageReference Include="System.Drawing.Common" Version="8.0.7" />
  </ItemGroup>
  1. Modify SolveInstance and input
        protected override void SolveInstance(IGH_DataAccess DA)
        {

            bool run = false;
            if (!DA.GetData(0, ref run)) { return; }

            if(!run) { return; }
            //Checking if User Principle works ok
            string userName = Environment.UserName;
            string userFullName = UserPrincipal.Current.DisplayName;


        }
        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddBooleanParameter("Run", "Run", "True = the tool is running and waits for changes in \"Trigger Component\" input", GH_ParamAccess.item);

        }

  1. Compile has 2 warnings and 0 errors.

  2. Rhino GrasshopperDeveloperSettings has correct folder for automatic loading.

  3. When adding and connecting boolean trigger into component → Solution exception:Could not load file or assembly ‘System.DirectoryServices.AccountManagement, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’. The system cannot find the file specified.

  4. FIX Round 1 Modify PropertyGroup

   <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
	  <Version>1.0</Version>
	  <Title>UserPrincipleTest2</Title>
	  <Description>Description of UserPrincipleTest2</Description>
	  <TargetExt>.gha</TargetExt
Addedd line--><CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
  </PropertyGroup>
  1. Clean + Compile with no errors.

  2. Now we have another fail when running component in Grasshopper. Solution exception:System.DirectoryServices.AccountManagement is not supported on this platform.

  3. Fix Round 2 Remove AccountManagement nuget package and add .netframework version as reference.

    <Reference Include="System.DirectoryServices.AccountManagement">
      <HintPath>..\..\..\..\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.8.1\System.DirectoryServices.AccountManagement.dll</HintPath>
      <Private>True</Private>
    </Reference>
  1. Comment <!--<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>-->
  2. Clean + Compile with no errors.
  3. Failing 1. Solution exception:Could not load file or assembly ‘System.DirectoryServices.AccountManagement, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’. Reference assemblies cannot be loaded for execution. (0x80131058)
  4. File list from folder after fail
  System.DirectoryServices.AccountManagement.dll 
  System.DirectoryServices.AccountManagement.xml
  System.DirectoryServices.dll
  System.DirectoryServices.Protocols.dll
  System.DirectoryServices.Protocols.xml
  System.DirectoryServices.x ml
  UserPrincipleTest2.deps.json 
  UserPrincipleTest2.gha
  UserPrincipleTest2.pdb

Ay ideas how to fix the issue? Can anyone get UserPrinciple working in Visual Studio - Grasshopper .net 7 component?

As I see it, you have to set copylocal to true on all dlls that are not part of the rhino package.

So your user thingies dll needs to be copied.

And then reference the system/dotnet/rhinocommon.dll instead of the system/rhinocommon.dll if building for core (.net7).

Also check if the right gha is referenced to gh. You might have one in repo/bin folder and another in gh/libraries. In that case make sure your dlls are in both.

<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>

This copies all references which are not part of rhino installation.

  • Updated grasshopper to default settings, Rhino 8 using NetCore
  • Checked that grasshopper loads correct gha file
  • My project settings
<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <TargetFramework>net7.0</TargetFramework>
	  <Version>1.0</Version>
	  <Title>UserPrincipleTest2</Title>
	  <Description>Description of UserPrincipleTest2</Description>
	  <TargetExt>.gha</TargetExt>      
	  <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="System.DirectoryServices.AccountManagement" Version="8.0.0" />
    <PackageReference Include="System.Drawing.Common" Version="8.0.7" />
  </ItemGroup>

  <ItemGroup>
    <Reference Include="GH_IO">
      <HintPath>..\..\..\..\Program Files\Rhino 8\Plug-ins\Grasshopper\GH_IO.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Grasshopper">
      <HintPath>..\..\..\..\Program Files\Rhino 8\Plug-ins\Grasshopper\Grasshopper.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RhinoCommon">
      <HintPath>..\..\..\..\Program Files\Rhino 8\System\netcore\RhinoCommon.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>

</Project>

Compile ok. Getting error from component: Solution exception:System.DirectoryServices.AccountManagement is not supported on this platform.

UPDATED FILE LIST

        26ÿ784 Microsoft.Win32.SystemEvents.dll
        <DIR>  runtimes
       442ÿ640 System.Configuration.ConfigurationManager.dll
        51ÿ872 System.Diagnostics.EventLog.dll
        68ÿ376 System.DirectoryServices.AccountManagement.dll
       129ÿ800 System.DirectoryServices.dll
        71ÿ856 System.DirectoryServices.Protocols.dll
       595ÿ632 System.Drawing.Common.dll
        37ÿ136 System.Security.Cryptography.ProtectedData.dll
         8ÿ103 UserPrincipleTest2.deps.json
         6ÿ144 UserPrincipleTest2.gha
        11ÿ716 UserPrincipleTest2.pdb

Looks like you are referring a .net framework dll into .net core. Did you check the compatibility of your reference (you can see that in nuget)

tor. 11. jul. 2024 15.07 skrev -JHS- via McNeel Forum <notifications@mcneel.discoursemail.com>:

None of the outputs are in .net framework.

image

Referenced libraies are supporting .Net 7

I don’t know if it helps. But System.DirectoryServices.AccountManagement is the dll which gives you access to the ActiveDirectory/LDAP. Since this is a Windows feature it will only work if you set your target framework in the project file to “net7.0-windows”. Also note that this means your plugin only runs under Windows. Obviously.

I tried that one too… with same results…

Returns this when trying to read UserPrinciples when CopyLocalLockFileAssemblies is set to false

  • Solution exception:Could not load file or assembly ‘System.DirectoryServices.AccountManagement, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089’. The system cannot find the file specified.

Returns this when loading components SolveInstance when CopyLocalLockFileAssemblies is set to true

  • Solution exception:System.DirectoryServices.AccountManagement is not supported on this platform.

Settings in project.cs file

  <PropertyGroup>    
	  <TargetFrameworks>net7.0-windows</TargetFrameworks>
	  <Version>1.0</Version>
	  <Title>UserPrincipleTest2</Title>
	  <Description>Description of UserPrincipleTest2</Description>
	  <TargetExt>.gha</TargetExt>	  
	  <UseWindowsForms>true</UseWindowsForms>
	  <UseWPF>true</UseWPF>
	  <CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
  </PropertyGroup>

File list when CopyLocalLockFileAssemblies is set to true
image

Can anyone test this if they can actually get this working? Very simple component…

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>    
	  <TargetFrameworks>net7.0-windows</TargetFrameworks>
	  <Version>1.0</Version>
	  <Title>UserPrincipleTest2</Title>
	  <Description>Description of UserPrincipleTest2</Description>
	  <TargetExt>.gha</TargetExt>	  
	  <UseWindowsForms>true</UseWindowsForms>
	  <UseWPF>true</UseWPF>
	  <CopyLocalLockFileAssemblies>false</CopyLocalLockFileAssemblies>
  </PropertyGroup>

 <ItemGroup>
    <PackageReference Include="System.DirectoryServices.AccountManagement" Version="8.0.0" />
    <PackageReference Include="System.Drawing.Common" Version="8.0.7" />    
  </ItemGroup>

  <ItemGroup>
    <Reference Include="GH_IO">
      <HintPath>..\..\..\..\Program Files\Rhino 8\Plug-ins\Grasshopper\GH_IO.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="Grasshopper">
      <HintPath>..\..\..\..\Program Files\Rhino 8\Plug-ins\Grasshopper\Grasshopper.dll</HintPath>
      <Private>False</Private>
    </Reference>
    <Reference Include="RhinoCommon">
      <HintPath>..\..\..\..\Program Files\Rhino 8\System\netcore\RhinoCommon.dll</HintPath>
      <Private>False</Private>
    </Reference>
  </ItemGroup>

</Project>
        protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
        {
            pManager.AddBooleanParameter("Run", "Run", "True = the tool is running and waits for changes in \"Trigger Component\" input", GH_ParamAccess.item);

        }

        protected override void SolveInstance(IGH_DataAccess DA)
        {

            bool run = false;
            if (!DA.GetData(0, ref run)) { return; }

            if(!run) { return; }
            //Checking if User Principle works ok
            string userName = Environment.UserName;
            string userFullName = UserPrincipal.Current.DisplayName;


        }