Electronics Bill of Materials Sample

Description

Prints a bill of materials to the Text Commands window: lines that share the same value, footprint, MPN, and manufacturer are merged—Part lists all designators (sorted), Qty is the count. Indented Value, Footprint, MPN, and Manufacturer lines follow. Empty value, MPN, or manufacturer shows (missing). Omits parts without a package, without a linked 3D footprint name, or not populated for the default assembly variant.

Code Samples

/**
 * Electronics Bill of Materials Sample
 * Prints a bill of materials to the Text Commands window: lines that share the same value, footprint, MPN, and manufacturer are merged—Part lists all designators (sorted), Qty is the count. Indented Value, Footprint, MPN, and Manufacturer lines follow. Empty value, MPN, or manufacturer shows (missing). Omits parts without a package, without a linked 3D footprint name, or not populated for the default assembly variant.
 *
 * For this sample script to run, the active Fusion document must have an electronics schematic as the active product.
 */

import { adsk } from '@adsk/fusion';

type BomKey = [string, string, string, string];

function bomKeyId(key: BomKey): string {
  return JSON.stringify(key);
}

function parseBomKey(id: string): BomKey {
  return JSON.parse(id) as BomKey;
}

function orMissing(s: string | null | undefined): string {
  if (s == null) return '(missing)';
  const t = String(s).trim();
  return t ? t : '(missing)';
}

function run(): void {
  const app = adsk.core.Application.get();
  if (!app) throw new Error('No adsk.core.Application.');

  try {
    const product = app.activeProduct;
    if (!product) {
      adsk.log('No active product.');
      return;
    }
    const sch = product as unknown as adsk.electron.Schematic;

    const parts = sch.parts;
    if (!parts) {
      adsk.log('No parts collection.');
      return;
    }

    const groups = new Map<string, string[]>();
    const order: string[] = [];

    for (let i = 0; i < parts.count; i++) {
      const p = parts.item(i);
      if (!p || !p.device || !p.device.package) continue;

      const p3 = p.package3d;
      if (!p3) continue;
      const fp = p3.name;
      if (fp == null || String(fp).trim() === '') continue;

      let popOk = true;
      try {
        const vdefs = sch.variantDefs;
        if (vdefs && vdefs.count > 0) {
          const pv = p.variants;
          if (!pv || pv.count === 0) {
            popOk = true;
          } else {
            const want = '';
            popOk = false;
            for (let j = 0; j < pv.count; j++) {
              const v = pv.item(j);
              if (!v) continue;
              const vd = v.variantDef;
              if (!vd) continue;
              const nm = vd.name;
              const s = nm == null ? '' : String(nm);
              if (s === want) {
                popOk = Boolean(v.populate);
                break;
              }
            }
            if (!popOk && want === '' && pv.count > 0) {
              const v0 = pv.item(0);
              if (v0) popOk = Boolean(v0.populate);
            }
          }
        }
      } catch {
        popOk = true;
      }
      if (!popOk) continue;

      let mpn = '';
      let mfr = '';
      const attrs = p.attributes;
      if (attrs) {
        for (let j = 0; j < attrs.count; j++) {
          const a = attrs.item(j);
          if (!a || a.name == null) continue;
          const up = String(a.name).toUpperCase();
          if (up === 'MPN') {
            mpn = a.value != null ? String(a.value) : '';
          } else if (up === 'MF') {
            mfr = a.value != null ? String(a.value) : '';
          }
        }
      }

      const nm = orMissing(p.name);
      const val = orMissing(p.value);
      const fpS = String(fp).trim();
      const mpnD = orMissing(mpn);
      const mfrD = orMissing(mfr);
      const key = bomKeyId([val, fpS, mpnD, mfrD]);

      if (!groups.has(key)) {
        groups.set(key, []);
        order.push(key);
      }
      groups.get(key)!.push(nm);
    }

    adsk.log('Bill of materials');
    const ind = '    ';
    let first = true;
    for (const keyId of order) {
      const names = [...groups.get(keyId)!].sort();
      const [val, fpS, mpnD, mfrD] = parseBomKey(keyId);
      const qty = String(names.length);
      if (!first) {
        adsk.log(' ');
      }
      first = false;
      adsk.log('Part: ' + names.join(', '));
      adsk.log(ind + 'Value: ' + val);
      adsk.log(ind + 'Qty: ' + qty);
      adsk.log(ind + 'Footprint: ' + fpS);
      adsk.log(ind + 'MPN: ' + mpnD);
      adsk.log(ind + 'Manufacturer: ' + mfrD);
    }
  } catch (err) {
    adsk.log(String(err));
  }
}

run();
#include <Core/CoreAll.h>
#include <Electron/ElectronAll.h>

#include <algorithm>
#include <cctype>
#include <map>
#include <sstream>
#include <string>
#include <vector>

using namespace adsk::core;
using namespace adsk::electron;

namespace
{

std::string trimOrMissing(const std::string& s)
{
    size_t a = 0;
    size_t b = s.size();
    while (a < b && std::isspace(static_cast<unsigned char>(s[a])))
        ++a;
    while (b > a && std::isspace(static_cast<unsigned char>(s[b - 1])))
        --b;
    if (a >= b)
        return "(missing)";
    return s.substr(a, b - a);
}

std::string stripWhitespace(const std::string& s)
{
    size_t a = 0;
    size_t b = s.size();
    while (a < b && std::isspace(static_cast<unsigned char>(s[a])))
        ++a;
    while (b > a && std::isspace(static_cast<unsigned char>(s[b - 1])))
        --b;
    if (a >= b)
        return "";
    return s.substr(a, b - a);
}

std::string bomKey(const std::string& val, const std::string& fp, const std::string& mpn, const std::string& mfr)
{
    return val + "\x1f" + fp + "\x1f" + mpn + "\x1f" + mfr;
}

} // namespace

extern "C" XI_EXPORT bool run(const char* context)
{
    Ptr<Application> app = Application::get();
    if (!app)
        return false;

    Ptr<Product> product = app->activeProduct();
    if (!product)
    {
        app->log("No active product.");
        return false;
    }

    Ptr<Schematic> schematic = product->cast<Schematic>();
    if (!schematic)
    {
        app->log("Active product is not a schematic.");
        return false;
    }

    Ptr<Parts> parts = schematic->parts();
    if (!parts)
    {
        app->log("No parts collection.");
        return false;
    }

    struct Group
    {
        std::vector<std::string> names;
        std::string val;
        std::string fp;
        std::string mpn;
        std::string mfr;
    };
    std::map<std::string, Group> groups;
    std::vector<std::string> keyOrder;

    for (size_t i = 0; i < parts->count(); ++i)
    {
        Ptr<Part> p = parts->item(i);
        if (!p)
            continue;
        Ptr<Device> dev = p->device();
        if (!dev)
            continue;
        Ptr<Package> pkg = dev->package();
        if (!pkg)
            continue;

        Ptr<Package3d> p3 = p->package3d();
        if (!p3)
            continue;
        std::string fp = stripWhitespace(p3->name());
        if (fp.empty())
            continue;

        bool popOk = true;
        Ptr<VariantDefs> vdefs = schematic->variantDefs();
        if (vdefs && vdefs->count() > 0)
        {
            Ptr<Variants> pv = p->variants();
            if (!pv || pv->count() == 0)
            {
                popOk = true;
            }
            else
            {
                const std::string want;
                popOk = false;
                const size_t vn = pv->count();
                for (size_t j = 0; j < vn; ++j)
                {
                    Ptr<Variant> v = pv->item(j);
                    if (!v)
                        continue;
                    Ptr<VariantDef> vdef = v->variantDef();
                    if (!vdef)
                        continue;
                    const std::string nameStr = stripWhitespace(vdef->name());
                    if (nameStr == want)
                    {
                        popOk = v->populate();
                        break;
                    }
                }
                if (!popOk && want.empty() && vn > 0)
                {
                    Ptr<Variant> v0 = pv->item(0);
                    if (v0)
                        popOk = v0->populate();
                }
            }
        }
        if (!popOk)
            continue;

        std::string mpn;
        std::string mfr;
        Ptr<EcadAttributes> attrs = p->attributes();
        if (attrs)
        {
            const size_t ac = attrs->count();
            for (size_t j = 0; j < ac; ++j)
            {
                Ptr<EcadAttribute> a = attrs->item(j);
                if (!a)
                    continue;
                std::string an = stripWhitespace(a->name());
                if (an.empty())
                    continue;
                std::string up = an;
                for (char& c : up)
                    c = static_cast<char>(std::toupper(static_cast<unsigned char>(c)));
                if (up == "MPN")
                    mpn = a->value();
                else if (up == "MF")
                    mfr = a->value();
            }
        }

        std::string nm = trimOrMissing(p->name());
        std::string val = trimOrMissing(p->value());
        std::string mpnDisp = trimOrMissing(mpn);
        std::string mfrDisp = trimOrMissing(mfr);
        const std::string k = bomKey(val, fp, mpnDisp, mfrDisp);
        if (groups.find(k) == groups.end())
        {
            keyOrder.push_back(k);
            Group g;
            g.val = val;
            g.fp = fp;
            g.mpn = mpnDisp;
            g.mfr = mfrDisp;
            g.names.push_back(nm);
            groups[k] = std::move(g);
        }
        else
        {
            groups[k].names.push_back(nm);
        }
    }

    app->log("Bill of materials");

    const char* ind = "    ";
    bool first = true;
    for (const std::string& k : keyOrder)
    {
        Group& g = groups[k];
        std::sort(g.names.begin(), g.names.end());
        std::ostringstream partLine;
        partLine << "Part: ";
        for (size_t i = 0; i < g.names.size(); ++i)
        {
            if (i > 0)
                partLine << ", ";
            partLine << g.names[i];
        }
        std::ostringstream qtyLine;
        qtyLine << ind << "Qty: " << g.names.size();

        if (!first)
            app->log(" ");
        first = false;
        app->log(partLine.str().c_str());
        app->log((std::string(ind) + "Value: " + g.val).c_str());
        app->log(qtyLine.str().c_str());
        app->log((std::string(ind) + "Footprint: " + g.fp).c_str());
        app->log((std::string(ind) + "MPN: " + g.mpn).c_str());
        app->log((std::string(ind) + "Manufacturer: " + g.mfr).c_str());
    }

    return true;
}
# For this sample script to run, the active Fusion document must have an electronics schematic as the active product.
#
# Prints simple bill of material grouped by Value, Footprint, MPN, and Manufacturer: designators listed on Part, Qty is the group size.
# Empty name, value, MPN, or manufacturer is (missing). Skips parts without package, 3D footprint name, or not populated for default variant.

import adsk.core, adsk.electron, traceback

def _or_missing(s):
    if s is None:
        return "(missing)"
    t = str(s).strip()
    return t if t else "(missing)"


def run(context):
    app = adsk.core.Application.get()
    try:
        sch = adsk.electron.Schematic.cast(app.activeProduct)
        if sch is None:
            app.log("Active product is not a schematic.")
            return

        parts = sch.parts
        if parts is None:
            app.log("No parts collection.")
            return

        # key -> list of designator strings, preserve first-seen key order
        groups = {}
        order = []

        for i in range(parts.count):
            p = parts.item(i)
            if p is None or p.device is None or p.device.package is None:
                continue

            p3 = p.package3d
            if p3 is None:
                continue
            fp = p3.name
            if fp is None or str(fp).strip() == "":
                continue

            pop_ok = True
            try:
                vdefs = sch.variantDefs
                if vdefs is not None and vdefs.count > 0:
                    pv = p.variants
                    if pv is None or pv.count == 0:
                        pop_ok = True
                    else:
                        want = ""
                        pop_ok = False
                        for j in range(pv.count):
                            v = pv.item(j)
                            if v is None:
                                continue
                            vd = v.variantDef
                            if vd is None:
                                continue
                            nm = vd.name
                            s = "" if nm is None else str(nm)
                            if s == want:
                                pop_ok = bool(v.populate)
                                break
                        if not pop_ok and want == "" and pv.count > 0:
                            v0 = pv.item(0)
                            if v0 is not None:
                                pop_ok = bool(v0.populate)
            except Exception:
                pop_ok = True
            if not pop_ok:
                continue

            mpn = ""
            mfr = ""
            attrs = p.attributes
            if attrs is not None:
                for j in range(attrs.count):
                    a = attrs.item(j)
                    if a is None or a.name is None:
                        continue
                    up = str(a.name).upper()
                    if up == "MPN":
                        mpn = str(a.value) if a.value is not None else ""
                    elif up == "MF":
                        mfr = str(a.value) if a.value is not None else ""

            nm = _or_missing(p.name)
            val = _or_missing(p.value)
            fp_s = str(fp).strip()
            mpn_d = _or_missing(mpn)
            mfr_d = _or_missing(mfr)
            key = (val, fp_s, mpn_d, mfr_d)
            if key not in groups:
                groups[key] = []
                order.append(key)
            groups[key].append(nm)

        app.log("Bill of materials")
        ind = "    "
        first = True
        for key in order:
            names = sorted(groups[key])
            val, fp_s, mpn_d, mfr_d = key
            qty = str(len(names))
            if not first:
                app.log(" ")
            first = False
            app.log("Part: " + ", ".join(names))
            app.log(ind + "Value: " + val)
            app.log(ind + "Qty: " + qty)
            app.log(ind + "Footprint: " + fp_s)
            app.log(ind + "MPN: " + mpn_d)
            app.log(ind + "Manufacturer: " + mfr_d)

    except Exception:
        app.log(traceback.format_exc() or "Error")