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