Some ODB++ jobs have different variants for components. An easy way to find the variants is to check all attributes and list them in an overview dictionary, here is the code for a class who handles variants over strings. You have a list with all variants, it is possible to save the variant names and load them from a file. In the example there are two standard values included.

To find the variants, you have to call CreateVariantList and get the dictionary with all variants.

Here is the solution file as ZIP: Download

using PCBI.Automation;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
 
namespace PCBVariantReport
{
  public class VariantXMLClass
  {
    //list who contains all variant names
    public List<string> VariantenTable = new List<string>() { "VARIANT_INSTANCE", "comp_variant_list" };
 
    /// <summary>
    /// The list is saved as simple xml and its possible to load it with an XmlSerializer.
    /// </summary>
    /// <param name="fullpath">The file path + filename</param>
    /// <returns>ture if new list is loaded</returns>
    internal bool LoadList(string fullpath)
    {
      if (!File.Exists(fullpath))
        return false;
 
      XmlSerializer x = new XmlSerializer(typeof(VariantXMLClass));
      FileStream fs = null;
      try
      {
        using (fs = new FileStream(fullpath, FileMode.Open))
        {
          XmlReader reader = new XmlTextReader(fs);
          VariantXMLClass mainJobEntries = (VariantXMLClass)x.Deserialize(reader);
 
          //add the loaded names to the standards, this can be change to replace if standard values not interesting
          this.VariantenTable.AddRange( mainJobEntries.VariantenTable);
 
          fs.Close();
        }
        return true;
      }
      catch (Exception ex)
      {
        Console.WriteLine("Error wihle loading xml "+ex.ToString());
        if (fs != null) //get sure to close the files stream
          fs.Close();
 
        return false;
      }
    }
    /// <summary>
    /// Save custom setting of list, this is necessary if you have special attributes for variants.
    /// </summary>
    /// <param name="filename"></param>
    internal void SaveFileEntries(string filename)
    {
      try
      {
        XmlSerializer x = new XmlSerializer(typeof(VariantXMLClass));
        TextWriter writer = new StreamWriter(filename);
        x.Serialize(writer, this);
 
        writer.Close();
      }
      catch (Exception ex)
      {
        Console.WriteLine("Error while saving xml file " + ex.ToString());
      }
    }
    /// <summary>
    /// Check the complete step and sort variants in the list.
    /// </summary>
    /// <param name="parent">the current PCBI Window</param>
    /// <returns>a list of variants for both component layers</returns>
    internal Dictionary<string, VariantOverview> CreateVariantList(IPCBIWindow parent)
    {
      Dictionary<string, VariantOverview> PartList = new Dictionary<string, VariantOverview>();
      IStep step = parent.GetCurrentStep(); //can be changed for all steps
      if (step == null) return null;
 
      ICMPLayer topLayer = step.GetCMPLayer(true);
      ICMPLayer botLayer = step.GetCMPLayer(false);
      if (topLayer != null)
      {
        AddCMPsForOneLayer(PartList, topLayer);
      }
      if (botLayer != null)
      {
        AddCMPsForOneLayer(PartList, botLayer);
      }
      return PartList;
    }
    /// <summary>
    /// The actions for one cmp layer.
    /// </summary>
    /// <param name="PartList">List for variants</param>
    /// <param name="relCMPLayer">the relevant component layer</param>
    private void AddCMPsForOneLayer(Dictionary<string, VariantOverview> PartList, ICMPLayer relCMPLayer)
    {
      foreach (ICMPObject cmp in relCMPLayer.GetAllLayerObjects())
      {
        Hashtable attribs = cmp.GetComponentAttributeHashtable(); //get attributes
        bool added = false;
        foreach (string variant in VariantenTable)
        {
          if (attribs.ContainsKey(variant)) //check the variant is included?
          {
            #region variant element
            VariantOverview overview;
            if (!PartList.ContainsKey(variant))
            {
              overview = new VariantOverview();
              overview.Variant = variant;
              PartList.Add(variant, overview);
            }
            else
              overview = PartList[variant];
 
            //add to the correct list
            if (overview.VariantValueAndList.ContainsKey((string)attribs[variant]))
              overview.VariantValueAndList[(string)attribs[variant]].Add(cmp);
            else
              overview.VariantValueAndList.Add((string)attribs[variant], new List<ICMPObject>() { cmp });
 
            added = true; //maybe there is no variant
            #endregion
          }
        }
        if (!added) //all cmps without variant
        {
          #region no variant
          string noVariant = "no variant";
          if (!PartList.ContainsKey(noVariant))
            PartList.Add(noVariant, new VariantOverview() { Variant = noVariant });
 
          if (PartList[noVariant].VariantValueAndList.ContainsKey(noVariant))
            PartList[noVariant].VariantValueAndList[noVariant].Add(cmp);
          else
            PartList[noVariant].VariantValueAndList.Add(noVariant, new List<ICMPObject>() { cmp });
          #endregion
        }
      }
    }
    /// <summary>
    /// we have a simple class to sort the variants with lists
    /// </summary>
    internal class VariantOverview
    {
      public string Variant = "";
      public Dictionary<string, List<ICMPObject>> VariantValueAndList = new Dictionary<string, List<ICMPObject>>();
    }
  }
}