Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions docs/usage/eda_integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,30 @@ If you set this value to -1, all parts are shown inside a single category in KiC

You can view the "real" category path of a part in the part details dialog in KiCad.

### Exported symbol fields

Besides the fields KiCad needs (symbol, footprint, reference, value, datasheet, description) Part-DB exports additional
information as symbol fields: manufacturer and MPN, the Part-DB ID and URL, stock and storage locations, supplier part
numbers, KiCost compatible fields (`manf`, `manf#`, `<supplier>#`), part info (category, manufacturing status, mass, IPN, ...)
and the tags as symbol keywords.

KiCad compares the fields of a placed symbol with the library. Every difference is reported as a "library symbol mismatch"
by the ERC. If you use the stock or supplier fields, every stock booking or supplier edit in Part-DB therefore triggers
ERC warnings in all schematics that use the part.

To avoid this, you can disable groups of fields that you do not need in your schematics in the server settings under
"KiCAD integration", or via the following env options (a value of `0` disables the group):

| Option | Fields |
|---------------------------------------|--------------------------------------------------------------------------------------------------|
| `EDA_KICAD_EXPORT_STOCK_FIELDS` | `Stock`, `Storage Location` |
| `EDA_KICAD_EXPORT_SUPPLIER_FIELDS` | `<Supplier> SPN` fields |
| `EDA_KICAD_EXPORT_KICOST_FIELDS` | `manf`, `manf#`, `<supplier>#` |
| `EDA_KICAD_EXPORT_PART_INFO_FIELDS` | `Category`, `Manufacturing Status`, `Mass`, `Part-DB IPN`, `Part-DB Footprint`, `Part-DB Unit`, `Part-DB Custom state` |
| `EDA_KICAD_EXPORT_TAGS_AS_KEYWORDS` | symbol keywords (from the part tags) |

All groups are enabled by default, so existing installations keep exporting the same fields as before.

### Kicad:populate command

Part-DB also provides a command that attempts to automatically populate the KiCad symbol and footprint fields based on the part's category and footprint names.
Expand Down
85 changes: 58 additions & 27 deletions src/Services/EDA/KiCadHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,10 @@ public function getCategories(): array
*/
public function getCategoryParts(?Category $category): array
{
$cacheKey = 'kicad_category_parts_'.($category?->getID() ?? 0) . '_' . $this->category_depth;
// Settings fingerprint keeps cached responses from surviving settings changes
// (tag-based invalidation only fires on part/category/footprint edits)
$cacheKey = 'kicad_category_parts_'.($category?->getID() ?? 0) . '_' . $this->category_depth
. '_' . $this->edaSettingsFingerprint();
return $this->kicadCache->get($cacheKey,
function (ItemInterface $item) use ($category) {
$item->tag([
Expand Down Expand Up @@ -213,7 +216,9 @@ public function getKiCADPart(Part $part): array
$result["fields"]["footprint"] = $this->createField($part->getEdaInfo()->getKicadFootprint() ?? $part->getFootprint()?->getEdaInfo()->getKicadFootprint() ?? "");
$result["fields"]["reference"] = $this->createField($part->getEdaInfo()->getReferencePrefix() ?? $part->getCategory()?->getEdaInfo()->getReferencePrefix() ?? 'U', true);
$result["fields"]["value"] = $this->createField($part->getEdaInfo()->getValue() ?? $part->getName(), true);
$result["fields"]["keywords"] = $this->createField($part->getTags());
if ($this->kiCadEDASettings->exportTagsAsKeywords) {
$result["fields"]["keywords"] = $this->createField($part->getTags());
}

//Use the part info page as Part-DB link. It must be an absolute URL.
$partUrl = $this->urlGenerator->generate(
Expand All @@ -233,7 +238,7 @@ public function getKiCADPart(Part $part): array

//Add basic fields
$result["fields"]["description"] = $this->createField($part->getDescription());
if ($part->getCategory() !== null) {
if ($this->kiCadEDASettings->exportPartInfoFields && $part->getCategory() !== null) {
$result["fields"]["Category"] = $this->createField($part->getCategory()->getFullPath('/'));
}
if ($part->getManufacturer() !== null) {
Expand All @@ -242,47 +247,49 @@ public function getKiCADPart(Part $part): array
if ($part->getManufacturerProductNumber() !== "") {
$result['fields']["MPN"] = $this->createField($part->getManufacturerProductNumber());
}
if ($part->getManufacturingStatus() !== null) {
if ($this->kiCadEDASettings->exportPartInfoFields && $part->getManufacturingStatus() !== null) {
$result["fields"]["Manufacturing Status"] = $this->createField(
//Always use the english translation
$this->translator->trans($part->getManufacturingStatus()->toTranslationKey(), locale: 'en')
);
}
if ($part->getFootprint() !== null) {
if ($this->kiCadEDASettings->exportPartInfoFields && $part->getFootprint() !== null) {
$result["fields"]["Part-DB Footprint"] = $this->createField($part->getFootprint()->getName());
}
if ($part->getPartUnit() !== null) {
if ($this->kiCadEDASettings->exportPartInfoFields && $part->getPartUnit() !== null) {
$unit = $part->getPartUnit()->getName();
if ($part->getPartUnit()->getUnit() !== "") {
$unit .= ' ('.$part->getPartUnit()->getUnit().')';
}
$result["fields"]["Part-DB Unit"] = $this->createField($unit);
}
if ($part->getPartCustomState() !== null) {
if ($this->kiCadEDASettings->exportPartInfoFields && $part->getPartCustomState() !== null) {
$customState = $part->getPartCustomState()->getName();
$result["fields"]["Part-DB Custom state"] = $this->createField($customState);
}
if ($part->getMass()) {
if ($this->kiCadEDASettings->exportPartInfoFields && $part->getMass()) {
$result["fields"]["Mass"] = $this->createField($part->getMass() . ' g');
}
$result["fields"]["Part-DB ID"] = $this->createField($part->getId());
if ($part->getIpn() !== null && $part->getIpn() !== '' && $part->getIpn() !== '0') {
if ($this->kiCadEDASettings->exportPartInfoFields
&& $part->getIpn() !== null && $part->getIpn() !== '' && $part->getIpn() !== '0') {
$result["fields"]["Part-DB IPN"] = $this->createField($part->getIpn());
}

//Add KiCost manufacturer fields (always present, independent of orderdetails)
if ($part->getManufacturer() !== null) {
if ($this->kiCadEDASettings->exportKicostFields && $part->getManufacturer() !== null) {
$result["fields"]["manf"] = $this->createField($part->getManufacturer()->getName());
}
if ($part->getManufacturerProductNumber() !== "") {
if ($this->kiCadEDASettings->exportKicostFields && $part->getManufacturerProductNumber() !== "") {
$result['fields']['manf#'] = $this->createField($part->getManufacturerProductNumber());
}

// Add supplier information from orderdetails (include obsolete orderdetails)
// If any orderdetail has eda_visibility explicitly set to true, only export those;
// otherwise export all (backward compat when no flags are set)
$allOrderdetails = $part->getOrderdetails(false);
if ($allOrderdetails->count() > 0) {
if (($this->kiCadEDASettings->exportSupplierFields || $this->kiCadEDASettings->exportKicostFields)
&& $allOrderdetails->count() > 0) {
$hasExplicitEdaVisibility = false;
foreach ($allOrderdetails as $od) {
if ($od->isEdaVisibility() !== null) {
Expand Down Expand Up @@ -312,30 +319,36 @@ public function getKiCADPart(Part $part): array
? $supplierName . ' ' . $supplierCounts[$supplierName]
: $supplierName;

$result["fields"][$fieldName] = $this->createField($orderdetail->getSupplierPartNr());
if ($this->kiCadEDASettings->exportSupplierFields) {
$result["fields"][$fieldName] = $this->createField($orderdetail->getSupplierPartNr());
}

//Also add a KiCost-compatible field (supplier_name# = SPN)
$kicostFieldName = mb_strtolower($orderdetail->getSupplier()->getName()) . '#';
$result["fields"][$kicostFieldName] = $this->createField($orderdetail->getSupplierPartNr());
if ($this->kiCadEDASettings->exportKicostFields) {
$kicostFieldName = mb_strtolower($orderdetail->getSupplier()->getName()) . '#';
$result["fields"][$kicostFieldName] = $this->createField($orderdetail->getSupplierPartNr());
}
}
}
}

//Add stock quantity and storage locations (only count non-expired lots with known quantity)
$totalStock = 0;
$locations = [];
foreach ($part->getPartLots() as $lot) {
$isAvailable = !$lot->isInstockUnknown() && $lot->isExpired() !== true;
if ($isAvailable) {
$totalStock += $lot->getAmount();
if ($lot->getAmount() > 0 && $lot->getStorageLocation() !== null) {
$locations[] = $lot->getStorageLocation()->getName();
if ($this->kiCadEDASettings->exportStockFields) {
$totalStock = 0;
$locations = [];
foreach ($part->getPartLots() as $lot) {
$isAvailable = !$lot->isInstockUnknown() && $lot->isExpired() !== true;
if ($isAvailable) {
$totalStock += $lot->getAmount();
if ($lot->getAmount() > 0 && $lot->getStorageLocation() !== null) {
$locations[] = $lot->getStorageLocation()->getName();
}
}
}
}
$result['fields']['Stock'] = $this->createField($totalStock);
if ($locations !== []) {
$result['fields']['Storage Location'] = $this->createField(implode(', ', array_unique($locations)));
$result['fields']['Stock'] = $this->createField($totalStock);
if ($locations !== []) {
$result['fields']['Storage Location'] = $this->createField(implode(', ', array_unique($locations)));
}
}

//Add parameters marked for EDA export (explicit true, or system default when null)
Expand All @@ -355,6 +368,24 @@ public function getKiCADPart(Part $part): array
return $result;
}

/**
* Fingerprint of every setting that changes the content of a serialized part.
*/
private function edaSettingsFingerprint(): string
{
return md5(json_encode([
$this->datasheetAsPdf,
$this->kiCadEDASettings->defaultOrderdetailsVisibility,
$this->kiCadEDASettings->defaultParameterVisibility,
$this->kiCadEDASettings->defaultParameterSymbolVisibility,
$this->kiCadEDASettings->exportStockFields,
$this->kiCadEDASettings->exportSupplierFields,
$this->kiCadEDASettings->exportKicostFields,
$this->kiCadEDASettings->exportPartInfoFields,
$this->kiCadEDASettings->exportTagsAsKeywords,
], JSON_THROW_ON_ERROR));
}

/**
* Determine if the given part should be visible for the EDA.
* @param Category $category
Expand Down
35 changes: 35 additions & 0 deletions src/Settings/MiscSettings/KiCadEDASettings.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,39 @@ class KiCadEDASettings
description: new TM("settings.misc.kicad_eda.use_custom_list.help"),
)]
public bool $useCustomList = false;

#[SettingsParameter(
label: new TM("settings.misc.kicad_eda.export_stock_fields"),
description: new TM("settings.misc.kicad_eda.export_stock_fields.help"),
envVar: "bool:EDA_KICAD_EXPORT_STOCK_FIELDS", envVarMode: EnvVarMode::OVERWRITE,
)]
public bool $exportStockFields = true;

#[SettingsParameter(
label: new TM("settings.misc.kicad_eda.export_supplier_fields"),
description: new TM("settings.misc.kicad_eda.export_supplier_fields.help"),
envVar: "bool:EDA_KICAD_EXPORT_SUPPLIER_FIELDS", envVarMode: EnvVarMode::OVERWRITE,
)]
public bool $exportSupplierFields = true;

#[SettingsParameter(
label: new TM("settings.misc.kicad_eda.export_kicost_fields"),
description: new TM("settings.misc.kicad_eda.export_kicost_fields.help"),
envVar: "bool:EDA_KICAD_EXPORT_KICOST_FIELDS", envVarMode: EnvVarMode::OVERWRITE,
)]
public bool $exportKicostFields = true;

#[SettingsParameter(
label: new TM("settings.misc.kicad_eda.export_part_info_fields"),
description: new TM("settings.misc.kicad_eda.export_part_info_fields.help"),
envVar: "bool:EDA_KICAD_EXPORT_PART_INFO_FIELDS", envVarMode: EnvVarMode::OVERWRITE,
)]
public bool $exportPartInfoFields = true;

#[SettingsParameter(
label: new TM("settings.misc.kicad_eda.export_tags_as_keywords"),
description: new TM("settings.misc.kicad_eda.export_tags_as_keywords.help"),
envVar: "bool:EDA_KICAD_EXPORT_TAGS_AS_KEYWORDS", envVarMode: EnvVarMode::OVERWRITE,
)]
public bool $exportTagsAsKeywords = true;
}
Loading