mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-28 20:39:35 +00:00
Address PR review: rename to eda_visibility, merge migrations, API versioning
Changes based on jbtronics' review of PR #1241: - Rename kicad_export -> eda_visibility (entities, forms, templates, translations, tests) with nullable bool for system default support - Merge two database migrations into one (Version20260211000000) - Rename createCachedJsonResponse -> createCacheableJsonResponse - Change bool $apiV2 -> int $apiVersion with version validation - EDA visibility field only shown for part parameters, not other entities - PopulateKicadCommand: check alternative names of footprints/categories - PopulateKicadCommand: support external JSON mapping file (--mapping-file) - Ship default mappings JSON at contrib/kicad-populate/default_mappings.json - Add system-wide defaultEdaVisibility setting in KiCadEDASettings - Add KiCad HTTP Library v2 spec link in controller docs
This commit is contained in:
parent
06c6542438
commit
ae7e31f0bd
17 changed files with 532 additions and 177 deletions
195
contrib/kicad-populate/default_mappings.json
Normal file
195
contrib/kicad-populate/default_mappings.json
Normal file
|
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"_comment": "Default KiCad footprint/symbol mappings for partdb:kicad:populate command. Based on KiCad 9.x standard libraries. Use --mapping-file to override or extend these mappings.",
|
||||
"footprints": {
|
||||
"SOT-23": "Package_TO_SOT_SMD:SOT-23",
|
||||
"SOT-23-3": "Package_TO_SOT_SMD:SOT-23",
|
||||
"SOT-23-5": "Package_TO_SOT_SMD:SOT-23-5",
|
||||
"SOT-23-6": "Package_TO_SOT_SMD:SOT-23-6",
|
||||
"SOT-223": "Package_TO_SOT_SMD:SOT-223-3_TabPin2",
|
||||
"SOT-223-3": "Package_TO_SOT_SMD:SOT-223-3_TabPin2",
|
||||
"SOT-89": "Package_TO_SOT_SMD:SOT-89-3",
|
||||
"SOT-89-3": "Package_TO_SOT_SMD:SOT-89-3",
|
||||
"SOT-323": "Package_TO_SOT_SMD:SOT-323_SC-70",
|
||||
"SOT-363": "Package_TO_SOT_SMD:SOT-363_SC-70-6",
|
||||
"TSOT-25": "Package_TO_SOT_SMD:SOT-23-5",
|
||||
"SC-70-5": "Package_TO_SOT_SMD:SOT-353_SC-70-5",
|
||||
"SC-70-6": "Package_TO_SOT_SMD:SOT-363_SC-70-6",
|
||||
"TO-220": "Package_TO_SOT_THT:TO-220-3_Vertical",
|
||||
"TO-220AB": "Package_TO_SOT_THT:TO-220-3_Vertical",
|
||||
"TO-220AB-3": "Package_TO_SOT_THT:TO-220-3_Vertical",
|
||||
"TO-220FP": "Package_TO_SOT_THT:TO-220F-3_Vertical",
|
||||
"TO-247-3": "Package_TO_SOT_THT:TO-247-3_Vertical",
|
||||
"TO-92": "Package_TO_SOT_THT:TO-92_Inline",
|
||||
"TO-92-3": "Package_TO_SOT_THT:TO-92_Inline",
|
||||
"TO-252": "Package_TO_SOT_SMD:TO-252-2",
|
||||
"TO-252-2L": "Package_TO_SOT_SMD:TO-252-2",
|
||||
"TO-252-3L": "Package_TO_SOT_SMD:TO-252-3",
|
||||
"TO-263": "Package_TO_SOT_SMD:TO-263-2",
|
||||
"TO-263-2": "Package_TO_SOT_SMD:TO-263-2",
|
||||
"D2PAK": "Package_TO_SOT_SMD:TO-252-2",
|
||||
"DPAK": "Package_TO_SOT_SMD:TO-252-2",
|
||||
"SOIC-8": "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
|
||||
"ESOP-8": "Package_SO:SOIC-8_3.9x4.9mm_P1.27mm",
|
||||
"SOIC-14": "Package_SO:SOIC-14_3.9x8.7mm_P1.27mm",
|
||||
"SOIC-16": "Package_SO:SOIC-16_3.9x9.9mm_P1.27mm",
|
||||
"TSSOP-8": "Package_SO:TSSOP-8_3x3mm_P0.65mm",
|
||||
"TSSOP-14": "Package_SO:TSSOP-14_4.4x5mm_P0.65mm",
|
||||
"TSSOP-16": "Package_SO:TSSOP-16_4.4x5mm_P0.65mm",
|
||||
"TSSOP-16L": "Package_SO:TSSOP-16_4.4x5mm_P0.65mm",
|
||||
"TSSOP-20": "Package_SO:TSSOP-20_4.4x6.5mm_P0.65mm",
|
||||
"MSOP-8": "Package_SO:MSOP-8_3x3mm_P0.65mm",
|
||||
"MSOP-10": "Package_SO:MSOP-10_3x3mm_P0.5mm",
|
||||
"MSOP-16": "Package_SO:MSOP-16_3x4mm_P0.5mm",
|
||||
"SO-5": "Package_TO_SOT_SMD:SOT-23-5",
|
||||
"DIP-4": "Package_DIP:DIP-4_W7.62mm",
|
||||
"DIP-6": "Package_DIP:DIP-6_W7.62mm",
|
||||
"DIP-8": "Package_DIP:DIP-8_W7.62mm",
|
||||
"DIP-14": "Package_DIP:DIP-14_W7.62mm",
|
||||
"DIP-16": "Package_DIP:DIP-16_W7.62mm",
|
||||
"DIP-18": "Package_DIP:DIP-18_W7.62mm",
|
||||
"DIP-20": "Package_DIP:DIP-20_W7.62mm",
|
||||
"DIP-24": "Package_DIP:DIP-24_W7.62mm",
|
||||
"DIP-28": "Package_DIP:DIP-28_W7.62mm",
|
||||
"DIP-40": "Package_DIP:DIP-40_W15.24mm",
|
||||
"QFN-8": "Package_DFN_QFN:QFN-8-1EP_3x3mm_P0.65mm_EP1.55x1.55mm",
|
||||
"QFN-12(3x3)": "Package_DFN_QFN:QFN-12-1EP_3x3mm_P0.5mm_EP1.65x1.65mm",
|
||||
"QFN-16": "Package_DFN_QFN:QFN-16-1EP_3x3mm_P0.5mm_EP1.45x1.45mm",
|
||||
"QFN-20": "Package_DFN_QFN:QFN-20-1EP_4x4mm_P0.5mm_EP2.5x2.5mm",
|
||||
"QFN-24": "Package_DFN_QFN:QFN-24-1EP_4x4mm_P0.5mm_EP2.45x2.45mm",
|
||||
"QFN-32": "Package_DFN_QFN:QFN-32-1EP_5x5mm_P0.5mm_EP3.45x3.45mm",
|
||||
"QFN-48": "Package_DFN_QFN:QFN-48-1EP_7x7mm_P0.5mm_EP5.3x5.3mm",
|
||||
"TQFP-32": "Package_QFP:TQFP-32_7x7mm_P0.8mm",
|
||||
"TQFP-44": "Package_QFP:TQFP-44_10x10mm_P0.8mm",
|
||||
"TQFP-48": "Package_QFP:TQFP-48_7x7mm_P0.5mm",
|
||||
"TQFP-48(7x7)": "Package_QFP:TQFP-48_7x7mm_P0.5mm",
|
||||
"TQFP-64": "Package_QFP:TQFP-64_10x10mm_P0.5mm",
|
||||
"TQFP-100": "Package_QFP:TQFP-100_14x14mm_P0.5mm",
|
||||
"LQFP-32": "Package_QFP:LQFP-32_7x7mm_P0.8mm",
|
||||
"LQFP-48": "Package_QFP:LQFP-48_7x7mm_P0.5mm",
|
||||
"LQFP-64": "Package_QFP:LQFP-64_10x10mm_P0.5mm",
|
||||
"LQFP-100": "Package_QFP:LQFP-100_14x14mm_P0.5mm",
|
||||
"SOD-123": "Diode_SMD:D_SOD-123",
|
||||
"SOD-123F": "Diode_SMD:D_SOD-123F",
|
||||
"SOD-123FL": "Diode_SMD:D_SOD-123F",
|
||||
"SOD-323": "Diode_SMD:D_SOD-323",
|
||||
"SOD-523": "Diode_SMD:D_SOD-523",
|
||||
"SOD-882": "Diode_SMD:D_SOD-882",
|
||||
"SOD-882D": "Diode_SMD:D_SOD-882",
|
||||
"SMA(DO-214AC)": "Diode_SMD:D_SMA",
|
||||
"SMA": "Diode_SMD:D_SMA",
|
||||
"SMB": "Diode_SMD:D_SMB",
|
||||
"SMC": "Diode_SMD:D_SMC",
|
||||
"DO-35": "Diode_THT:D_DO-35_SOD27_P7.62mm_Horizontal",
|
||||
"DO-35(DO-204AH)": "Diode_THT:D_DO-35_SOD27_P7.62mm_Horizontal",
|
||||
"DO-41": "Diode_THT:D_DO-41_SOD81_P10.16mm_Horizontal",
|
||||
"DO-201": "Diode_THT:D_DO-201_P15.24mm_Horizontal",
|
||||
"DFN-2(0.6x1)": "Package_DFN_QFN:DFN-2-1EP_0.6x1.0mm_P0.65mm_EP0.2x0.55mm",
|
||||
"DFN1006-2": "Package_DFN_QFN:DFN-2_1.0x0.6mm",
|
||||
"DFN-6": "Package_DFN_QFN:DFN-6-1EP_2x2mm_P0.65mm_EP1x1.6mm",
|
||||
"DFN-8": "Package_DFN_QFN:DFN-8-1EP_3x2mm_P0.5mm_EP1.3x1.5mm",
|
||||
"0201": "Resistor_SMD:R_0201_0603Metric",
|
||||
"0402": "Resistor_SMD:R_0402_1005Metric",
|
||||
"0603": "Resistor_SMD:R_0603_1608Metric",
|
||||
"0805": "Resistor_SMD:R_0805_2012Metric",
|
||||
"1206": "Resistor_SMD:R_1206_3216Metric",
|
||||
"1210": "Resistor_SMD:R_1210_3225Metric",
|
||||
"1812": "Resistor_SMD:R_1812_4532Metric",
|
||||
"2010": "Resistor_SMD:R_2010_5025Metric",
|
||||
"2512": "Resistor_SMD:R_2512_6332Metric",
|
||||
"2917": "Resistor_SMD:R_2917_7343Metric",
|
||||
"2920": "Resistor_SMD:R_2920_7350Metric",
|
||||
"CASE-A-3216-18(mm)": "Capacitor_Tantalum_SMD:CP_EIA-3216-18_Kemet-A",
|
||||
"CASE-B-3528-21(mm)": "Capacitor_Tantalum_SMD:CP_EIA-3528-21_Kemet-B",
|
||||
"CASE-C-6032-28(mm)": "Capacitor_Tantalum_SMD:CP_EIA-6032-28_Kemet-C",
|
||||
"CASE-D-7343-31(mm)": "Capacitor_Tantalum_SMD:CP_EIA-7343-31_Kemet-D",
|
||||
"CASE-E-7343-43(mm)": "Capacitor_Tantalum_SMD:CP_EIA-7343-43_Kemet-E",
|
||||
"SMD,D4xL5.4mm": "Capacitor_SMD:CP_Elec_4x5.4",
|
||||
"SMD,D5xL5.4mm": "Capacitor_SMD:CP_Elec_5x5.4",
|
||||
"SMD,D6.3xL5.4mm": "Capacitor_SMD:CP_Elec_6.3x5.4",
|
||||
"SMD,D6.3xL7.7mm": "Capacitor_SMD:CP_Elec_6.3x7.7",
|
||||
"SMD,D8xL6.5mm": "Capacitor_SMD:CP_Elec_8x6.5",
|
||||
"SMD,D8xL10mm": "Capacitor_SMD:CP_Elec_8x10",
|
||||
"SMD,D10xL10mm": "Capacitor_SMD:CP_Elec_10x10",
|
||||
"SMD,D10xL10.5mm": "Capacitor_SMD:CP_Elec_10x10.5",
|
||||
"Through Hole,D5xL11mm": "Capacitor_THT:CP_Radial_D5.0mm_P2.00mm",
|
||||
"Through Hole,D6.3xL11mm": "Capacitor_THT:CP_Radial_D6.3mm_P2.50mm",
|
||||
"Through Hole,D8xL11mm": "Capacitor_THT:CP_Radial_D8.0mm_P3.50mm",
|
||||
"Through Hole,D10xL16mm": "Capacitor_THT:CP_Radial_D10.0mm_P5.00mm",
|
||||
"Through Hole,D10xL20mm": "Capacitor_THT:CP_Radial_D10.0mm_P5.00mm",
|
||||
"Through Hole,D12.5xL20mm": "Capacitor_THT:CP_Radial_D12.5mm_P5.00mm",
|
||||
"LED 3mm": "LED_THT:LED_D3.0mm",
|
||||
"LED 5mm": "LED_THT:LED_D5.0mm",
|
||||
"LED 0603": "LED_SMD:LED_0603_1608Metric",
|
||||
"LED 0805": "LED_SMD:LED_0805_2012Metric",
|
||||
"SMD5050-4P": "LED_SMD:LED_WS2812B_PLCC4_5.0x5.0mm_P3.2mm",
|
||||
"SMD5050-6P": "LED_SMD:LED_WS2812B_PLCC4_5.0x5.0mm_P3.2mm",
|
||||
"HC-49": "Crystal:Crystal_HC49-4H_Vertical",
|
||||
"HC-49/U": "Crystal:Crystal_HC49-4H_Vertical",
|
||||
"HC-49/S": "Crystal:Crystal_HC49-U_Vertical",
|
||||
"HC-49/US": "Crystal:Crystal_HC49-U_Vertical",
|
||||
"USB-A": "Connector_USB:USB_A_Stewart_SS-52100-001_Horizontal",
|
||||
"USB-B": "Connector_USB:USB_B_OST_USB-B1HSxx_Horizontal",
|
||||
"USB-Mini-B": "Connector_USB:USB_Mini-B_Lumberg_2486_01_Horizontal",
|
||||
"USB-Micro-B": "Connector_USB:USB_Micro-B_Molex-105017-0001",
|
||||
"USB-C": "Connector_USB:USB_C_Receptacle_GCT_USB4085",
|
||||
"1x2 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x02_P2.54mm_Vertical",
|
||||
"1x3 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x03_P2.54mm_Vertical",
|
||||
"1x4 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x04_P2.54mm_Vertical",
|
||||
"1x5 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x05_P2.54mm_Vertical",
|
||||
"1x6 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x06_P2.54mm_Vertical",
|
||||
"1x8 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x08_P2.54mm_Vertical",
|
||||
"1x10 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_1x10_P2.54mm_Vertical",
|
||||
"2x2 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x02_P2.54mm_Vertical",
|
||||
"2x3 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x03_P2.54mm_Vertical",
|
||||
"2x4 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x04_P2.54mm_Vertical",
|
||||
"2x5 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x05_P2.54mm_Vertical",
|
||||
"2x10 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x10_P2.54mm_Vertical",
|
||||
"2x20 P2.54mm": "Connector_PinHeader_2.54mm:PinHeader_2x20_P2.54mm_Vertical",
|
||||
"SIP-3-2.54mm": "Package_SIP:SIP-3_P2.54mm",
|
||||
"SIP-4-2.54mm": "Package_SIP:SIP-4_P2.54mm",
|
||||
"SIP-5-2.54mm": "Package_SIP:SIP-5_P2.54mm"
|
||||
},
|
||||
"categories": {
|
||||
"Electrolytic": "Device:C_Polarized",
|
||||
"Polarized": "Device:C_Polarized",
|
||||
"Tantalum": "Device:C_Polarized",
|
||||
"Zener": "Device:D_Zener",
|
||||
"Schottky": "Device:D_Schottky",
|
||||
"TVS": "Device:D_TVS",
|
||||
"LED": "Device:LED",
|
||||
"NPN": "Device:Q_NPN_BCE",
|
||||
"PNP": "Device:Q_PNP_BCE",
|
||||
"N-MOSFET": "Device:Q_NMOS_GDS",
|
||||
"NMOS": "Device:Q_NMOS_GDS",
|
||||
"N-MOS": "Device:Q_NMOS_GDS",
|
||||
"P-MOSFET": "Device:Q_PMOS_GDS",
|
||||
"PMOS": "Device:Q_PMOS_GDS",
|
||||
"P-MOS": "Device:Q_PMOS_GDS",
|
||||
"MOSFET": "Device:Q_NMOS_GDS",
|
||||
"JFET": "Device:Q_NJFET_DSG",
|
||||
"Ferrite": "Device:Ferrite_Bead",
|
||||
"Crystal": "Device:Crystal",
|
||||
"Oscillator": "Oscillator:Oscillator_Crystal",
|
||||
"Fuse": "Device:Fuse",
|
||||
"Transformer": "Device:Transformer_1P_1S",
|
||||
"Resistor": "Device:R",
|
||||
"Capacitor": "Device:C",
|
||||
"Inductor": "Device:L",
|
||||
"Diode": "Device:D",
|
||||
"Transistor": "Device:Q_NPN_BCE",
|
||||
"Voltage Regulator": "Regulator_Linear:LM317_TO-220",
|
||||
"LDO": "Regulator_Linear:AMS1117-3.3",
|
||||
"Op-Amp": "Amplifier_Operational:LM358",
|
||||
"Comparator": "Comparator:LM393",
|
||||
"Optocoupler": "Isolator:PC817",
|
||||
"Relay": "Relay:Relay_DPDT",
|
||||
"Connector": "Connector:Conn_01x02",
|
||||
"Switch": "Switch:SW_Push",
|
||||
"Button": "Switch:SW_Push",
|
||||
"Potentiometer": "Device:R_POT",
|
||||
"Trimpot": "Device:R_POT",
|
||||
"Thermistor": "Device:Thermistor",
|
||||
"Varistor": "Device:Varistor",
|
||||
"Photo": "Device:LED"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
final class Version20260208190000 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add kicad_export boolean column to parameters table';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters ADD kicad_export TINYINT(1) NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters DROP COLUMN kicad_export');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters ADD COLUMN kicad_export BOOLEAN NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
// SQLite does not support DROP COLUMN in older versions; recreate table if needed
|
||||
$this->addSql('ALTER TABLE parameters DROP COLUMN kicad_export');
|
||||
}
|
||||
|
||||
public function postgreSQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters ADD kicad_export BOOLEAN NOT NULL DEFAULT FALSE');
|
||||
}
|
||||
|
||||
public function postgreSQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters DROP COLUMN kicad_export');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,46 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
final class Version20260210120000 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add kicad_export boolean column to orderdetails table';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE `orderdetails` ADD kicad_export TINYINT(1) NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE `orderdetails` DROP COLUMN kicad_export');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE orderdetails ADD COLUMN kicad_export BOOLEAN NOT NULL DEFAULT 0');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE orderdetails DROP COLUMN kicad_export');
|
||||
}
|
||||
|
||||
public function postgreSQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE orderdetails ADD kicad_export BOOLEAN NOT NULL DEFAULT FALSE');
|
||||
}
|
||||
|
||||
public function postgreSQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE orderdetails DROP COLUMN kicad_export');
|
||||
}
|
||||
}
|
||||
52
migrations/Version20260211000000.php
Normal file
52
migrations/Version20260211000000.php
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use App\Migration\AbstractMultiPlatformMigration;
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
|
||||
final class Version20260211000000 extends AbstractMultiPlatformMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return 'Add eda_visibility nullable boolean column to parameters and orderdetails tables';
|
||||
}
|
||||
|
||||
public function mySQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters ADD eda_visibility TINYINT(1) DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE `orderdetails` ADD eda_visibility TINYINT(1) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function mySQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters DROP COLUMN eda_visibility');
|
||||
$this->addSql('ALTER TABLE `orderdetails` DROP COLUMN eda_visibility');
|
||||
}
|
||||
|
||||
public function sqLiteUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters ADD COLUMN eda_visibility BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE orderdetails ADD COLUMN eda_visibility BOOLEAN DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function sqLiteDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters DROP COLUMN eda_visibility');
|
||||
$this->addSql('ALTER TABLE orderdetails DROP COLUMN eda_visibility');
|
||||
}
|
||||
|
||||
public function postgreSQLUp(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters ADD eda_visibility BOOLEAN DEFAULT NULL');
|
||||
$this->addSql('ALTER TABLE orderdetails ADD eda_visibility BOOLEAN DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function postgreSQLDown(Schema $schema): void
|
||||
{
|
||||
$this->addSql('ALTER TABLE parameters DROP COLUMN eda_visibility');
|
||||
$this->addSql('ALTER TABLE orderdetails DROP COLUMN eda_visibility');
|
||||
}
|
||||
}
|
||||
|
|
@ -32,6 +32,7 @@ class PopulateKicadCommand extends Command
|
|||
->addOption('categories', null, InputOption::VALUE_NONE, 'Only update category entities')
|
||||
->addOption('force', null, InputOption::VALUE_NONE, 'Overwrite existing values (by default, only empty values are updated)')
|
||||
->addOption('list', null, InputOption::VALUE_NONE, 'List all footprints and categories with their current KiCad values')
|
||||
->addOption('mapping-file', null, InputOption::VALUE_REQUIRED, 'Path to a JSON file with custom mappings (merges with built-in defaults)')
|
||||
;
|
||||
}
|
||||
|
||||
|
|
@ -43,6 +44,7 @@ class PopulateKicadCommand extends Command
|
|||
$categoriesOnly = $input->getOption('categories');
|
||||
$force = $input->getOption('force');
|
||||
$list = $input->getOption('list');
|
||||
$mappingFile = $input->getOption('mapping-file');
|
||||
|
||||
// If neither specified, do both
|
||||
$doFootprints = !$categoriesOnly || $footprintsOnly;
|
||||
|
|
@ -53,6 +55,26 @@ class PopulateKicadCommand extends Command
|
|||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
// Load mappings: start with built-in defaults, then merge user-supplied file
|
||||
$footprintMappings = $this->getFootprintMappings();
|
||||
$categoryMappings = $this->getCategoryMappings();
|
||||
|
||||
if ($mappingFile !== null) {
|
||||
$customMappings = $this->loadMappingFile($mappingFile, $io);
|
||||
if ($customMappings === null) {
|
||||
return Command::FAILURE;
|
||||
}
|
||||
if (isset($customMappings['footprints']) && is_array($customMappings['footprints'])) {
|
||||
// User mappings take priority (overwrite defaults)
|
||||
$footprintMappings = array_merge($footprintMappings, $customMappings['footprints']);
|
||||
$io->text(sprintf('Loaded %d custom footprint mappings from %s', count($customMappings['footprints']), $mappingFile));
|
||||
}
|
||||
if (isset($customMappings['categories']) && is_array($customMappings['categories'])) {
|
||||
$categoryMappings = array_merge($categoryMappings, $customMappings['categories']);
|
||||
$io->text(sprintf('Loaded %d custom category mappings from %s', count($customMappings['categories']), $mappingFile));
|
||||
}
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
$io->note('DRY RUN MODE - No changes will be made');
|
||||
}
|
||||
|
|
@ -60,12 +82,12 @@ class PopulateKicadCommand extends Command
|
|||
$totalUpdated = 0;
|
||||
|
||||
if ($doFootprints) {
|
||||
$updated = $this->updateFootprints($io, $dryRun, $force);
|
||||
$updated = $this->updateFootprints($io, $dryRun, $force, $footprintMappings);
|
||||
$totalUpdated += $updated;
|
||||
}
|
||||
|
||||
if ($doCategories) {
|
||||
$updated = $this->updateCategories($io, $dryRun, $force);
|
||||
$updated = $this->updateCategories($io, $dryRun, $force, $categoryMappings);
|
||||
$totalUpdated += $updated;
|
||||
}
|
||||
|
||||
|
|
@ -120,12 +142,10 @@ class PopulateKicadCommand extends Command
|
|||
$io->table(['ID', 'Name', 'KiCad Symbol'], $rows);
|
||||
}
|
||||
|
||||
private function updateFootprints(SymfonyStyle $io, bool $dryRun, bool $force): int
|
||||
private function updateFootprints(SymfonyStyle $io, bool $dryRun, bool $force, array $mappings): int
|
||||
{
|
||||
$io->section('Updating Footprint Entities');
|
||||
|
||||
$mappings = $this->getFootprintMappings();
|
||||
|
||||
$footprintRepo = $this->entityManager->getRepository(Footprint::class);
|
||||
/** @var Footprint[] $footprints */
|
||||
$footprints = $footprintRepo->findAll();
|
||||
|
|
@ -142,13 +162,14 @@ class PopulateKicadCommand extends Command
|
|||
continue;
|
||||
}
|
||||
|
||||
// Check for exact match first
|
||||
if (isset($mappings[$name])) {
|
||||
$newValue = $mappings[$name];
|
||||
$io->text(sprintf(' %s: %s -> %s', $name, $currentValue ?? '(empty)', $newValue));
|
||||
// Check for exact match on name first, then try alternative names
|
||||
$matchedValue = $this->findFootprintMapping($mappings, $name, $footprint->getAlternativeNames());
|
||||
|
||||
if ($matchedValue !== null) {
|
||||
$io->text(sprintf(' %s: %s -> %s', $name, $currentValue ?? '(empty)', $matchedValue));
|
||||
|
||||
if (!$dryRun) {
|
||||
$footprint->getEdaInfo()->setKicadFootprint($newValue);
|
||||
$footprint->getEdaInfo()->setKicadFootprint($matchedValue);
|
||||
}
|
||||
$updated++;
|
||||
} else {
|
||||
|
|
@ -170,12 +191,10 @@ class PopulateKicadCommand extends Command
|
|||
return $updated;
|
||||
}
|
||||
|
||||
private function updateCategories(SymfonyStyle $io, bool $dryRun, bool $force): int
|
||||
private function updateCategories(SymfonyStyle $io, bool $dryRun, bool $force, array $mappings): int
|
||||
{
|
||||
$io->section('Updating Category Entities');
|
||||
|
||||
$mappings = $this->getCategoryMappings();
|
||||
|
||||
$categoryRepo = $this->entityManager->getRepository(Category::class);
|
||||
/** @var Category[] $categories */
|
||||
$categories = $categoryRepo->findAll();
|
||||
|
|
@ -192,22 +211,17 @@ class PopulateKicadCommand extends Command
|
|||
continue;
|
||||
}
|
||||
|
||||
// Check for matches using the pattern-based mappings
|
||||
$matched = false;
|
||||
foreach ($mappings as $pattern => $kicadSymbol) {
|
||||
if ($this->matchesPattern($name, $pattern)) {
|
||||
$io->text(sprintf(' %s: %s -> %s', $name, $currentValue ?? '(empty)', $kicadSymbol));
|
||||
// Check for matches using the pattern-based mappings (also check alternative names)
|
||||
$matchedValue = $this->findCategoryMapping($mappings, $name, $category->getAlternativeNames());
|
||||
|
||||
if (!$dryRun) {
|
||||
$category->getEdaInfo()->setKicadSymbol($kicadSymbol);
|
||||
}
|
||||
$updated++;
|
||||
$matched = true;
|
||||
break;
|
||||
if ($matchedValue !== null) {
|
||||
$io->text(sprintf(' %s: %s -> %s', $name, $currentValue ?? '(empty)', $matchedValue));
|
||||
|
||||
if (!$dryRun) {
|
||||
$category->getEdaInfo()->setKicadSymbol($matchedValue);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$matched) {
|
||||
$updated++;
|
||||
} else {
|
||||
$skipped[] = $name;
|
||||
}
|
||||
}
|
||||
|
|
@ -225,6 +239,34 @@ class PopulateKicadCommand extends Command
|
|||
return $updated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a JSON mapping file and returns the parsed data.
|
||||
* Expected format: {"footprints": {"Name": "KiCad:Path"}, "categories": {"Pattern": "KiCad:Path"}}
|
||||
*
|
||||
* @return array|null The parsed mappings, or null on error
|
||||
*/
|
||||
private function loadMappingFile(string $path, SymfonyStyle $io): ?array
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
$io->error(sprintf('Mapping file not found: %s', $path));
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = file_get_contents($path);
|
||||
if ($content === false) {
|
||||
$io->error(sprintf('Could not read mapping file: %s', $path));
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($content, true);
|
||||
if (!is_array($data)) {
|
||||
$io->error(sprintf('Invalid JSON in mapping file: %s', $path));
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function matchesPattern(string $name, string $pattern): bool
|
||||
{
|
||||
// Check for exact match
|
||||
|
|
@ -240,6 +282,71 @@ class PopulateKicadCommand extends Command
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a footprint mapping by checking the entity name and its alternative names.
|
||||
* Footprints use exact matching.
|
||||
*
|
||||
* @param array<string, string> $mappings
|
||||
* @param string $name The primary name of the footprint
|
||||
* @param string|null $alternativeNames Comma-separated alternative names
|
||||
* @return string|null The matched KiCad path, or null if no match found
|
||||
*/
|
||||
private function findFootprintMapping(array $mappings, string $name, ?string $alternativeNames): ?string
|
||||
{
|
||||
// Check primary name
|
||||
if (isset($mappings[$name])) {
|
||||
return $mappings[$name];
|
||||
}
|
||||
|
||||
// Check alternative names
|
||||
if ($alternativeNames !== null && $alternativeNames !== '') {
|
||||
foreach (explode(',', $alternativeNames) as $altName) {
|
||||
$altName = trim($altName);
|
||||
if ($altName !== '' && isset($mappings[$altName])) {
|
||||
return $mappings[$altName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds a category mapping by checking the entity name and its alternative names.
|
||||
* Categories use pattern-based matching (case-insensitive contains).
|
||||
*
|
||||
* @param array<string, string> $mappings
|
||||
* @param string $name The primary name of the category
|
||||
* @param string|null $alternativeNames Comma-separated alternative names
|
||||
* @return string|null The matched KiCad symbol path, or null if no match found
|
||||
*/
|
||||
private function findCategoryMapping(array $mappings, string $name, ?string $alternativeNames): ?string
|
||||
{
|
||||
// Check primary name against all patterns
|
||||
foreach ($mappings as $pattern => $kicadSymbol) {
|
||||
if ($this->matchesPattern($name, $pattern)) {
|
||||
return $kicadSymbol;
|
||||
}
|
||||
}
|
||||
|
||||
// Check alternative names against all patterns
|
||||
if ($alternativeNames !== null && $alternativeNames !== '') {
|
||||
foreach (explode(',', $alternativeNames) as $altName) {
|
||||
$altName = trim($altName);
|
||||
if ($altName === '') {
|
||||
continue;
|
||||
}
|
||||
foreach ($mappings as $pattern => $kicadSymbol) {
|
||||
if ($this->matchesPattern($altName, $pattern)) {
|
||||
return $kicadSymbol;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns footprint name to KiCad footprint path mappings.
|
||||
* These are based on KiCad 9.x standard library paths.
|
||||
|
|
@ -496,4 +603,37 @@ class PopulateKicadCommand extends Command
|
|||
'Photo' => 'Device:LED', // Photodiode/phototransistor
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Load a custom mapping file (JSON format).
|
||||
*
|
||||
* Expected format:
|
||||
* {
|
||||
* "footprints": { "SOT-23": "Package_TO_SOT_SMD:SOT-23", ... },
|
||||
* "categories": { "Resistor": "Device:R", ... }
|
||||
* }
|
||||
*
|
||||
* @return array|null The parsed mappings, or null on error
|
||||
*/
|
||||
private function loadMappingFile(string $path, SymfonyStyle $io): ?array
|
||||
{
|
||||
if (!file_exists($path)) {
|
||||
$io->error(sprintf('Mapping file not found: %s', $path));
|
||||
return null;
|
||||
}
|
||||
|
||||
$content = file_get_contents($path);
|
||||
if ($content === false) {
|
||||
$io->error(sprintf('Could not read mapping file: %s', $path));
|
||||
return null;
|
||||
}
|
||||
|
||||
$data = json_decode($content, true);
|
||||
if (!is_array($data)) {
|
||||
$io->error(sprintf('Invalid JSON in mapping file: %s', json_last_error_msg()));
|
||||
return null;
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class KiCadApiController extends AbstractController
|
|||
$this->denyAccessUnlessGranted('@categories.read');
|
||||
|
||||
$data = $this->kiCADHelper->getCategories();
|
||||
return $this->createCachedJsonResponse($request, $data, 300);
|
||||
return $this->createCacheableJsonResponse($request, $data, 300);
|
||||
}
|
||||
|
||||
#[Route('/parts/category/{category}.json', name: 'kicad_api_category')]
|
||||
|
|
@ -77,7 +77,7 @@ class KiCadApiController extends AbstractController
|
|||
|
||||
$minimal = $request->query->getBoolean('minimal', false);
|
||||
$data = $this->kiCADHelper->getCategoryParts($category, $minimal);
|
||||
return $this->createCachedJsonResponse($request, $data, 300);
|
||||
return $this->createCacheableJsonResponse($request, $data, 300);
|
||||
}
|
||||
|
||||
#[Route('/parts/{part}.json', name: 'kicad_api_part')]
|
||||
|
|
@ -86,14 +86,14 @@ class KiCadApiController extends AbstractController
|
|||
$this->denyAccessUnlessGranted('read', $part);
|
||||
|
||||
$data = $this->kiCADHelper->getKiCADPart($part);
|
||||
return $this->createCachedJsonResponse($request, $data, 60);
|
||||
return $this->createCacheableJsonResponse($request, $data, 60);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a JSON response with HTTP cache headers (ETag and Cache-Control).
|
||||
* Returns 304 Not Modified if the client's ETag matches.
|
||||
*/
|
||||
private function createCachedJsonResponse(Request $request, array $data, int $maxAge): Response
|
||||
private function createCacheableJsonResponse(Request $request, array $data, int $maxAge): Response
|
||||
{
|
||||
$response = new JsonResponse($data);
|
||||
$response->setEtag(md5(json_encode($data)));
|
||||
|
|
|
|||
|
|
@ -34,6 +34,9 @@ use Symfony\Component\Routing\Attribute\Route;
|
|||
/**
|
||||
* KiCad HTTP Library API v2 controller.
|
||||
*
|
||||
* v1 spec: https://dev-docs.kicad.org/en/apis-and-binding/http-libraries/index.html
|
||||
* v2 spec (draft): https://gitlab.com/RosyDev/kicad-dev-docs/-/blob/http-lib-v2/content/apis-and-binding/http-libraries/http-lib-v2-00.adoc
|
||||
*
|
||||
* Differences from v1:
|
||||
* - Volatile fields: Stock and Storage Location are marked volatile (shown in KiCad but NOT saved to schematic)
|
||||
* - Category descriptions: Uses actual category comments instead of URLs
|
||||
|
|
@ -64,7 +67,7 @@ class KiCadApiV2Controller extends AbstractController
|
|||
$this->denyAccessUnlessGranted('@categories.read');
|
||||
|
||||
$data = $this->kiCADHelper->getCategories();
|
||||
return $this->createCachedJsonResponse($request, $data, 300);
|
||||
return $this->createCacheableJsonResponse($request, $data, 300);
|
||||
}
|
||||
|
||||
#[Route('/parts/category/{category}.json', name: 'kicad_api_v2_category')]
|
||||
|
|
@ -79,7 +82,7 @@ class KiCadApiV2Controller extends AbstractController
|
|||
|
||||
$minimal = $request->query->getBoolean('minimal', false);
|
||||
$data = $this->kiCADHelper->getCategoryParts($category, $minimal);
|
||||
return $this->createCachedJsonResponse($request, $data, 300);
|
||||
return $this->createCacheableJsonResponse($request, $data, 300);
|
||||
}
|
||||
|
||||
#[Route('/parts/{part}.json', name: 'kicad_api_v2_part')]
|
||||
|
|
@ -88,11 +91,11 @@ class KiCadApiV2Controller extends AbstractController
|
|||
$this->denyAccessUnlessGranted('read', $part);
|
||||
|
||||
// Use API v2 format with volatile fields
|
||||
$data = $this->kiCADHelper->getKiCADPart($part, true);
|
||||
return $this->createCachedJsonResponse($request, $data, 60);
|
||||
$data = $this->kiCADHelper->getKiCADPart($part, 2);
|
||||
return $this->createCacheableJsonResponse($request, $data, 60);
|
||||
}
|
||||
|
||||
private function createCachedJsonResponse(Request $request, array $data, int $maxAge): Response
|
||||
private function createCacheableJsonResponse(Request $request, array $data, int $maxAge): Response
|
||||
{
|
||||
$response = new JsonResponse($data);
|
||||
$response->setEtag(md5(json_encode($data)));
|
||||
|
|
|
|||
|
|
@ -173,11 +173,11 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
|
|||
protected string $group = '';
|
||||
|
||||
/**
|
||||
* @var bool Whether this parameter should be exported as a KiCad field in the EDA HTTP library API
|
||||
* @var bool|null Whether this parameter should be exported as a field in the EDA HTTP library API. Null means use system default.
|
||||
*/
|
||||
#[Groups(['full', 'parameter:read', 'parameter:write', 'import'])]
|
||||
#[ORM\Column(type: Types::BOOLEAN)]
|
||||
protected bool $kicad_export = false;
|
||||
#[ORM\Column(type: Types::BOOLEAN, nullable: true, options: ['default' => null])]
|
||||
protected ?bool $eda_visibility = null;
|
||||
|
||||
/**
|
||||
* Mapping is done in subclasses.
|
||||
|
|
@ -478,17 +478,17 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
|
|||
return static::ALLOWED_ELEMENT_CLASS;
|
||||
}
|
||||
|
||||
public function isKicadExport(): bool
|
||||
public function isEdaVisibility(): ?bool
|
||||
{
|
||||
return $this->kicad_export;
|
||||
return $this->eda_visibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setKicadExport(bool $kicad_export): self
|
||||
public function setEdaVisibility(?bool $eda_visibility): self
|
||||
{
|
||||
$this->kicad_export = $kicad_export;
|
||||
$this->eda_visibility = $eda_visibility;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -123,11 +123,11 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N
|
|||
protected bool $obsolete = false;
|
||||
|
||||
/**
|
||||
* @var bool Whether this orderdetail's supplier part number should be exported as a KiCad field
|
||||
* @var bool|null Whether this orderdetail's supplier part number should be exported as an EDA field. Null means use system default.
|
||||
*/
|
||||
#[Groups(['full', 'import', 'orderdetail:read', 'orderdetail:write'])]
|
||||
#[ORM\Column(type: Types::BOOLEAN, options: ['default' => false])]
|
||||
protected bool $kicad_export = false;
|
||||
#[ORM\Column(type: Types::BOOLEAN, nullable: true, options: ['default' => null])]
|
||||
protected ?bool $eda_visibility = null;
|
||||
|
||||
/**
|
||||
* @var string The URL to the product on the supplier's website
|
||||
|
|
@ -425,17 +425,17 @@ class Orderdetail extends AbstractDBElement implements TimeStampableInterface, N
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function isKicadExport(): bool
|
||||
public function isEdaVisibility(): ?bool
|
||||
{
|
||||
return $this->kicad_export;
|
||||
return $this->eda_visibility;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return $this
|
||||
*/
|
||||
public function setKicadExport(bool $kicad_export): self
|
||||
public function setEdaVisibility(?bool $eda_visibility): self
|
||||
{
|
||||
$this->kicad_export = $kicad_export;
|
||||
$this->eda_visibility = $eda_visibility;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -149,10 +149,13 @@ class ParameterType extends AbstractType
|
|||
],
|
||||
]);
|
||||
|
||||
$builder->add('kicad_export', CheckboxType::class, [
|
||||
'label' => false,
|
||||
'required' => false,
|
||||
]);
|
||||
// Only show the EDA visibility field for part parameters, as it has no function for other entities
|
||||
if ($options['data_class'] === PartParameter::class) {
|
||||
$builder->add('eda_visibility', CheckboxType::class, [
|
||||
'label' => false,
|
||||
'required' => false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function finishView(FormView $view, FormInterface $form, array $options): void
|
||||
|
|
|
|||
|
|
@ -79,9 +79,9 @@ class OrderdetailType extends AbstractType
|
|||
'label' => 'orderdetails.edit.prices_includes_vat',
|
||||
]);
|
||||
|
||||
$builder->add('kicad_export', CheckboxType::class, [
|
||||
$builder->add('eda_visibility', CheckboxType::class, [
|
||||
'required' => false,
|
||||
'label' => 'orderdetails.edit.kicad_export',
|
||||
'label' => 'orderdetails.edit.eda_visibility',
|
||||
]);
|
||||
|
||||
//Add pricedetails after we know the data, so we can set the default currency
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ class KiCadHelper
|
|||
/** @var bool Whether to resolve actual datasheet PDF URLs (true) or use Part-DB page links (false) */
|
||||
private readonly bool $datasheetAsPdf;
|
||||
|
||||
/** @var bool The system-wide default for EDA visibility when not explicitly set on an element */
|
||||
private readonly bool $defaultEdaVisibility;
|
||||
|
||||
public function __construct(
|
||||
private readonly NodesListBuilder $nodesListBuilder,
|
||||
private readonly TagAwareCacheInterface $kicadCache,
|
||||
|
|
@ -59,6 +62,7 @@ class KiCadHelper
|
|||
) {
|
||||
$this->category_depth = $kiCadEDASettings->categoryDepth;
|
||||
$this->datasheetAsPdf = $kiCadEDASettings->datasheetAsPdf ?? true;
|
||||
$this->defaultEdaVisibility = $kiCadEDASettings->defaultEdaVisibility;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -194,10 +198,14 @@ class KiCadHelper
|
|||
}
|
||||
|
||||
/**
|
||||
* @param bool $apiV2 If true, use API v2 format with volatile field support
|
||||
* @param int $apiVersion The API version to use (1 or 2). Version 2 adds volatile field support.
|
||||
*/
|
||||
public function getKiCADPart(Part $part, bool $apiV2 = false): array
|
||||
public function getKiCADPart(Part $part, int $apiVersion = 1): array
|
||||
{
|
||||
if ($apiVersion < 1 || $apiVersion > 2) {
|
||||
throw new \InvalidArgumentException(sprintf('Unsupported API version %d. Supported versions: 1, 2.', $apiVersion));
|
||||
}
|
||||
|
||||
$result = [
|
||||
'id' => (string)$part->getId(),
|
||||
'name' => $part->getName(),
|
||||
|
|
@ -277,13 +285,14 @@ class KiCadHelper
|
|||
}
|
||||
|
||||
// Add supplier information from orderdetails (include obsolete orderdetails)
|
||||
// If any orderdetail has kicad_export=true, only export those; otherwise export all (backward compat)
|
||||
// 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) {
|
||||
$hasKicadExportFlag = false;
|
||||
$hasExplicitEdaVisibility = false;
|
||||
foreach ($allOrderdetails as $od) {
|
||||
if ($od->isKicadExport()) {
|
||||
$hasKicadExportFlag = true;
|
||||
if ($od->isEdaVisibility() !== null) {
|
||||
$hasExplicitEdaVisibility = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -291,8 +300,9 @@ class KiCadHelper
|
|||
$supplierCounts = [];
|
||||
foreach ($allOrderdetails as $orderdetail) {
|
||||
if ($orderdetail->getSupplier() !== null && $orderdetail->getSupplierPartNr() !== '') {
|
||||
// Skip orderdetails not marked for export when the flag is used
|
||||
if ($hasKicadExportFlag && !$orderdetail->isKicadExport()) {
|
||||
// When explicit flags exist, filter by resolved visibility
|
||||
$resolvedVisibility = $orderdetail->isEdaVisibility() ?? $this->defaultEdaVisibility;
|
||||
if ($hasExplicitEdaVisibility && !$resolvedVisibility) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -330,14 +340,15 @@ class KiCadHelper
|
|||
}
|
||||
}
|
||||
// In API v2, stock and location are volatile (shown but not saved to schematic)
|
||||
$result['fields']['Stock'] = $this->createField($totalStock, false, $apiV2);
|
||||
$result['fields']['Stock'] = $this->createField($totalStock, false, $apiVersion >= 2);
|
||||
if ($locations !== []) {
|
||||
$result['fields']['Storage Location'] = $this->createField(implode(', ', array_unique($locations)), false, $apiV2);
|
||||
$result['fields']['Storage Location'] = $this->createField(implode(', ', array_unique($locations)), false, $apiVersion >= 2);
|
||||
}
|
||||
|
||||
//Add parameters marked for KiCad export
|
||||
//Add parameters marked for EDA export (explicit true, or system default when null)
|
||||
foreach ($part->getParameters() as $parameter) {
|
||||
if ($parameter->isKicadExport() && $parameter->getName() !== '') {
|
||||
$paramVisibility = $parameter->isEdaVisibility() ?? $this->defaultEdaVisibility;
|
||||
if ($paramVisibility && $parameter->getName() !== '') {
|
||||
$fieldName = $parameter->getName();
|
||||
//Don't overwrite hardcoded fields
|
||||
if (!isset($result['fields'][$fieldName])) {
|
||||
|
|
|
|||
|
|
@ -48,4 +48,9 @@ class KiCadEDASettings
|
|||
description: new TM("settings.misc.kicad_eda.datasheet_link.help"),
|
||||
envVar: "bool:EDA_KICAD_DATASHEET_AS_PDF", envVarMode: EnvVarMode::OVERWRITE)]
|
||||
public ?bool $datasheetAsPdf = true;
|
||||
|
||||
#[SettingsParameter(label: new TM("settings.misc.kicad_eda.default_eda_visibility"),
|
||||
description: new TM("settings.misc.kicad_eda.default_eda_visibility.help"),
|
||||
envVar: "bool:EDA_KICAD_DEFAULT_VISIBILITY", envVarMode: EnvVarMode::OVERWRITE)]
|
||||
public bool $defaultEdaVisibility = false;
|
||||
}
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
<th>{% trans %}specifications.unit{% endtrans %}</th>
|
||||
<th>{% trans %}specifications.text{% endtrans %}</th>
|
||||
<th>{% trans %}specifications.group{% endtrans %}</th>
|
||||
<th title="{% trans %}specifications.kicad_export.help{% endtrans %}"><i class="fas fa-bolt fa-fw"></i></th>
|
||||
<th title="{% trans %}specifications.eda_visibility.help{% endtrans %}"><i class="fas fa-bolt fa-fw"></i></th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@
|
|||
{{ form_row(form.supplier_product_url, {'attr': {'class': 'form-control-sm'}}) }}
|
||||
{{ form_widget(form.obsolete) }}
|
||||
{{ form_widget(form.pricesIncludesVAT) }}
|
||||
{{ form_widget(form.kicad_export) }}
|
||||
{{ form_widget(form.eda_visibility) }}
|
||||
</td>
|
||||
<td>
|
||||
<div {{ collection.controller(form.pricedetails, 'pricedetails.edit.delete.confirm') }}>
|
||||
|
|
@ -80,7 +80,9 @@
|
|||
<td {{ stimulus_controller('pages/latex_preview', {"unit": true}) }}>{{ form_widget(form.unit, {"attr": {"data-pages--parameters-autocomplete-target": "unit", "data-pages--latex-preview-target": "input"}}) }}{{ form_errors(form.unit) }}<span {{ stimulus_target('pages/latex_preview', 'preview') }}></span></td>
|
||||
<td>{{ form_widget(form.value_text) }}{{ form_errors(form.value_text) }}</td>
|
||||
<td>{{ form_widget(form.group) }}{{ form_errors(form.group) }}</td>
|
||||
<td class="text-center">{{ form_widget(form.kicad_export) }}</td>
|
||||
{% if form.eda_visibility is defined %}
|
||||
<td class="text-center">{{ form_widget(form.eda_visibility) }}</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
<button type="button" class="btn btn-danger btn-sm order_btn_delete position-relative {% if form.parent.vars.allow_delete is defined and not form.parent.vars.allow_delete %}disabled{% endif %}"
|
||||
{{ collection.delete_btn() }} title="{% trans %}orderdetail.delete{% endtrans %}">
|
||||
|
|
|
|||
|
|
@ -362,9 +362,9 @@ final class KiCadHelperTest extends KernelTestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* Test that a parameter with kicad_export=true appears in the KiCad fields.
|
||||
* Test that a parameter with eda_visibility=true appears in the KiCad fields.
|
||||
*/
|
||||
public function testParameterWithKicadExportAppearsInFields(): void
|
||||
public function testParameterWithEdaVisibilityAppearsInFields(): void
|
||||
{
|
||||
$category = $this->em->find(Category::class, 1);
|
||||
|
||||
|
|
@ -376,7 +376,7 @@ final class KiCadHelperTest extends KernelTestCase
|
|||
$param->setName('Voltage Rating');
|
||||
$param->setValueTypical(3.3);
|
||||
$param->setUnit('V');
|
||||
$param->setKicadExport(true);
|
||||
$param->setEdaVisibility(true);
|
||||
$part->addParameter($param);
|
||||
|
||||
$this->em->persist($part);
|
||||
|
|
@ -389,9 +389,9 @@ final class KiCadHelperTest extends KernelTestCase
|
|||
}
|
||||
|
||||
/**
|
||||
* Test that a parameter with kicad_export=false does NOT appear in the KiCad fields.
|
||||
* Test that a parameter with eda_visibility=false does NOT appear in the KiCad fields.
|
||||
*/
|
||||
public function testParameterWithoutKicadExportDoesNotAppear(): void
|
||||
public function testParameterWithoutEdaVisibilityDoesNotAppear(): void
|
||||
{
|
||||
$category = $this->em->find(Category::class, 1);
|
||||
|
||||
|
|
@ -402,7 +402,7 @@ final class KiCadHelperTest extends KernelTestCase
|
|||
$param = new PartParameter();
|
||||
$param->setName('Internal Note');
|
||||
$param->setValueText('for testing only');
|
||||
$param->setKicadExport(false);
|
||||
$param->setEdaVisibility(false);
|
||||
$part->addParameter($param);
|
||||
|
||||
$this->em->persist($part);
|
||||
|
|
@ -413,6 +413,31 @@ final class KiCadHelperTest extends KernelTestCase
|
|||
self::assertArrayNotHasKey('Internal Note', $result['fields']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that a parameter with eda_visibility=null (system default) does NOT appear in the KiCad fields.
|
||||
*/
|
||||
public function testParameterWithNullEdaVisibilityDoesNotAppear(): void
|
||||
{
|
||||
$category = $this->em->find(Category::class, 1);
|
||||
|
||||
$part = new Part();
|
||||
$part->setName('Part with Default Parameter');
|
||||
$part->setCategory($category);
|
||||
|
||||
$param = new PartParameter();
|
||||
$param->setName('Default Param');
|
||||
$param->setValueText('some value');
|
||||
// eda_visibility is null by default
|
||||
$part->addParameter($param);
|
||||
|
||||
$this->em->persist($part);
|
||||
$this->em->flush();
|
||||
|
||||
$result = $this->helper->getKiCADPart($part);
|
||||
|
||||
self::assertArrayNotHasKey('Default Param', $result['fields']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that an exported parameter named "description" does NOT overwrite the hardcoded description field.
|
||||
*/
|
||||
|
|
@ -428,7 +453,7 @@ final class KiCadHelperTest extends KernelTestCase
|
|||
$param = new PartParameter();
|
||||
$param->setName('description');
|
||||
$param->setValueText('should not overwrite');
|
||||
$param->setKicadExport(true);
|
||||
$param->setEdaVisibility(true);
|
||||
$part->addParameter($param);
|
||||
|
||||
$this->em->persist($part);
|
||||
|
|
|
|||
|
|
@ -642,10 +642,10 @@ Sub elements will be moved upwards.</target>
|
|||
<target>Group</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="kicadExportHelp" name="specifications.kicad_export.help">
|
||||
<unit id="edaVisibilityHelp" name="specifications.eda_visibility.help">
|
||||
<segment state="translated">
|
||||
<source>specifications.kicad_export.help</source>
|
||||
<target>Export this parameter as a KiCad field</target>
|
||||
<source>specifications.eda_visibility.help</source>
|
||||
<target>Export this parameter as an EDA field</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="XclPxI9" name="specification.create">
|
||||
|
|
@ -3308,10 +3308,10 @@ If you have done this incorrectly or if a computer is no longer trusted, you can
|
|||
<target>No longer available</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="kicad_od_export" name="orderdetails.edit.kicad_export">
|
||||
<unit id="eda_od_visibility" name="orderdetails.edit.eda_visibility">
|
||||
<segment state="translated">
|
||||
<source>orderdetails.edit.kicad_export</source>
|
||||
<target>Export to KiCad</target>
|
||||
<source>orderdetails.edit.eda_visibility</source>
|
||||
<target>EDA visibility</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="ZsO5AKM" name="orderdetails.edit.supplierpartnr.placeholder">
|
||||
|
|
@ -10005,6 +10005,18 @@ Please note, that you can not impersonate a disabled user. If you try you will g
|
|||
<target>When enabled, the datasheet field in KiCad will link to the actual PDF file (if found). When disabled, it will link to the Part-DB page instead. The Part-DB page link is always available as a separate "Part-DB URL" field.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="eda_default_vis" name="settings.misc.kicad_eda.default_eda_visibility">
|
||||
<segment state="translated">
|
||||
<source>settings.misc.kicad_eda.default_eda_visibility</source>
|
||||
<target>Default EDA visibility</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="eda_default_vis_help" name="settings.misc.kicad_eda.default_eda_visibility.help">
|
||||
<segment state="translated">
|
||||
<source>settings.misc.kicad_eda.default_eda_visibility.help</source>
|
||||
<target>Default EDA visibility for parameters and orderdetails that have no explicit value set. When enabled, all parameters and supplier part numbers will be exported to EDA by default.</target>
|
||||
</segment>
|
||||
</unit>
|
||||
<unit id="VwvmcWE" name="settings.behavior.sidebar">
|
||||
<segment state="translated">
|
||||
<source>settings.behavior.sidebar</source>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue