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

#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")