Tips on creating plugins that use Cloud Zoo (developer)

Rhino - Creating Plugins that use Cloud Zoo is a pretty good guide, for the most part.

But I wasted quite a bit of time, so I’m creating this topic for fellow plugin developers, to share my experience in case it can help you if you’re stuck.

Step 1

Register as an Issuer in Cloud Zoo. was straightforward and worked well for me, I’m sure the fantastic support team will help you if you’re stuck.

Step 2

Add products to Cloud Zoo. worked, same comment as step 1 really.

Step 3

Implement the required HTTPS callbacks. seemed to work for me, but it seemed completely useless in my case (more on that later). And once it’s done, you’ll need to send the urls for your 3 endpoints (get_license add_license remove_license) to the same email address (support@mcneel.com) as step 1. I was saved by How is the Cloud Zoo Server told of Issuer Server URLs? - #4 by nicolaasburgers so credit goes there, but like him I’d love to see this information in the guide.

Step 4

Modify Plug-In licensing code to support Cloud Zoo. is not completely bad but this is where I’ve struggled the most.

The example in github SampleCsWithLicensePlugIn.cs is not for Cloud Zoo. There are 2 methods for GetLicense and you are not told which one to use, with which parameters.

Here is some sample code you can use:

    public static bool IsLicenseValid = false; // use this in your plugin to remove functionality if it is not licensed
    private static LicenseCapabilities Capabilities => LicenseCapabilities.SupportsRhinoAccounts;
    private static string TextMask = "\\C\\AD-AAAA-AAAA-AAAA-AAAA-AAAA"; // different text mask from step 2, replace XXXX with AAAA, and escape some characters, you may need some trial and error
    

    protected override LoadReturnCode OnLoad(ref string errorMessage)
    {
      IsLicenseValid = false;
      if (!GetLicense(Capabilities, TextMask, OnValidateProductKey, OnLeaseChanged)) return LoadReturnCode.ErrorNoDialog;
      IsLicenseValid = true;
      return LoadReturnCode.Success;
    }
    // Only support Cloud Zoo licensing (LicenseCapabilities.SupportsRhinoAccounts)
    private static ValidateResult OnValidateProductKey(string licenseKey, out LicenseData licenseData)
    {
      // will not be called but must nonetheless be a valid delegate method
      licenseData = null;
      return ValidateResult.ErrorShowMessage;
    }
    private static void OnLeaseChanged(LicenseLeaseChangedEventArgs args, out System.Drawing.Icon icon)
    {
      System.Resources.ResourceManager rm = new System.Resources.ResourceManager("MyPlugin.Properties.Resources", typeof(MyPlugin.Properties.Resources).Assembly);
      object obj = rm.GetObject("logo", CultureInfo.CurrentCulture);
      icon = ((System.Drawing.Icon)(obj));
      // This sample does not support Rhino accounts.

      if (null == args.Lease)
      {
        IsLicenseValid = false;
        return;
        // Lease has been voided; this product should behave as if it has no
        // license. It is up to the plug-in to determine what that looks like.
      }
      // Verify that args.Lease.ProductId is correct
      if (args.Lease.ProductId != "product-guid-from-step-2")
      {
        IsLicenseValid = false;
        return;
      }
      /* Following checks not needed
      // Verify that args.Lease.ProductEdition is correct
      if (args.Lease.ProductEdition != "Commercial")
      {
        IsLicenseValid = false;
        return;
      }
      // Verify that args.Lease.ProductVersion is correct
      if (args.Lease.ProductVersion != PluginConstant.PluginVersion.ToString())
      {
        IsLicenseValid = false;
        return;
      }*/

      // Verify that args.Lease.IsExpired() is false
      if (args.Lease.Expiration < DateTime.Now)
      {
        IsLicenseValid = false;
        return;
      }
      IsLicenseValid = true;
    }

Step 5 (mandatory)

(Optional???) Take advantage of Cloud Zoo endpoints for querying and modifying licenses in Cloud Zoo. In my case, step 3 seemed optional and Step 5 mandatory.

I’ve managed to add a license to a team, with a PUT/https://cloudzoo.rhino3d.com/v1/license

{
    "entityId": "theGroupsEntityIdSeeUrlWhenYouManageYourGroup-|-Group",
    "licenseCluster": {
        "licenses": [{
        "id": "somethingMeaningfulToYou",
        "key": "CAD-ABCD-1234-EFGH-5678-IJKL",
        "aud": "product-guid-from-step-2",
        "iss": "your_issuer_from_step_1",
        "exp": null,
        "numberOfSeats": 20,
        "editions": {
                "en": "Commercial",
                "es": "Comercial"
        },
        "metadata": null
        }]
    }
}

Interestingly, it didn’t call my endoint addLicense nor getLicense, and when checking the license key from my user account, the license key was somethingMeaningfulToYou (which is the id, I expected the key CAD-ABCD-1234-EFGH-5678-IJKL).

I wasn’t sure if that was a problem, so I deleted that license (DELETE/https://cloudzoo.rhino3d.com/v1/license) and recreated one with id=key (PUT/…).

I am now using the license along with the other users in that group, with no issue. My endpoints add_license remove_license get_license have not once been called by Rhino / McNeel.

If I need to add or remove users, I will repeat step 5.

Happy developing!