Add configurable KiCad field export for part parameters

Add a kicad_export checkbox to parameters, allowing users to control
which specifications appear as fields in the KiCad HTTP library API.
Parameters with kicad_export enabled are included using their formatted
value, without overwriting hardcoded fields like description or Stock.
This commit is contained in:
Sebastian Almberg 2026-02-08 21:46:28 +01:00
parent 44c5d9d727
commit 9178154986
8 changed files with 174 additions and 0 deletions

View file

@ -0,0 +1,47 @@
<?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');
}
}

View file

@ -172,6 +172,13 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
#[Assert\Length(max: 255)]
protected string $group = '';
/**
* @var bool Whether this parameter should be exported as a KiCad field in the EDA HTTP library API
*/
#[Groups(['full', 'parameter:read', 'parameter:write', 'import'])]
#[ORM\Column(type: Types::BOOLEAN)]
protected bool $kicad_export = false;
/**
* Mapping is done in subclasses.
*
@ -471,6 +478,21 @@ abstract class AbstractParameter extends AbstractNamedDBElement implements Uniqu
return static::ALLOWED_ELEMENT_CLASS;
}
public function isKicadExport(): bool
{
return $this->kicad_export;
}
/**
* @return $this
*/
public function setKicadExport(bool $kicad_export): self
{
$this->kicad_export = $kicad_export;
return $this;
}
public function getComparableFields(): array
{
return ['name' => $this->name, 'group' => $this->group, 'element' => $this->element?->getId()];

View file

@ -55,6 +55,7 @@ use App\Entity\Parameters\SupplierParameter;
use App\Entity\Parts\MeasurementUnit;
use App\Form\Type\ExponentialNumberType;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\NumberType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
@ -147,6 +148,11 @@ class ParameterType extends AbstractType
'class' => 'form-control-sm',
],
]);
$builder->add('kicad_export', CheckboxType::class, [
'label' => false,
'required' => false,
]);
}
public function finishView(FormView $view, FormInterface $form, array $options): void

View file

@ -311,6 +311,17 @@ class KiCadHelper
$result['fields']['Storage Location'] = $this->createField(implode(', ', array_unique($locations)));
}
//Add parameters marked for KiCad export
foreach ($part->getParameters() as $parameter) {
if ($parameter->isKicadExport() && $parameter->getName() !== '') {
$fieldName = $parameter->getName();
//Don't overwrite hardcoded fields
if (!isset($result['fields'][$fieldName])) {
$result['fields'][$fieldName] = $this->createField($parameter->getFormattedValue());
}
}
}
return $result;
}

View file

@ -14,6 +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></th>
</tr>
</thead>

View file

@ -79,6 +79,7 @@
<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>
<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 %}">

View file

@ -24,6 +24,7 @@ namespace App\Tests\Services\EDA;
use App\Entity\Attachments\AttachmentType;
use App\Entity\Attachments\PartAttachment;
use App\Entity\Parameters\PartParameter;
use App\Entity\Parts\Category;
use App\Entity\Parts\Part;
use App\Entity\Parts\PartLot;
@ -359,4 +360,83 @@ class KiCadHelperTest extends KernelTestCase
self::assertSame('False', $result['fields']['Stock']['visible']);
}
/**
* Test that a parameter with kicad_export=true appears in the KiCad fields.
*/
public function testParameterWithKicadExportAppearsInFields(): void
{
$category = $this->em->find(Category::class, 1);
$part = new Part();
$part->setName('Part with Exported Parameter');
$part->setCategory($category);
$param = new PartParameter();
$param->setName('Voltage Rating');
$param->setValueTypical(3.3);
$param->setUnit('V');
$param->setKicadExport(true);
$part->addParameter($param);
$this->em->persist($part);
$this->em->flush();
$result = $this->helper->getKiCADPart($part);
self::assertArrayHasKey('Voltage Rating', $result['fields']);
self::assertSame('3.3 V', $result['fields']['Voltage Rating']['value']);
}
/**
* Test that a parameter with kicad_export=false does NOT appear in the KiCad fields.
*/
public function testParameterWithoutKicadExportDoesNotAppear(): void
{
$category = $this->em->find(Category::class, 1);
$part = new Part();
$part->setName('Part with Non-exported Parameter');
$part->setCategory($category);
$param = new PartParameter();
$param->setName('Internal Note');
$param->setValueText('for testing only');
$param->setKicadExport(false);
$part->addParameter($param);
$this->em->persist($part);
$this->em->flush();
$result = $this->helper->getKiCADPart($part);
self::assertArrayNotHasKey('Internal Note', $result['fields']);
}
/**
* Test that an exported parameter named "description" does NOT overwrite the hardcoded description field.
*/
public function testExportedParameterDoesNotOverwriteHardcodedField(): void
{
$category = $this->em->find(Category::class, 1);
$part = new Part();
$part->setName('Part with Conflicting Parameter');
$part->setDescription('The real description');
$part->setCategory($category);
$param = new PartParameter();
$param->setName('description');
$param->setValueText('should not overwrite');
$param->setKicadExport(true);
$part->addParameter($param);
$this->em->persist($part);
$this->em->flush();
$result = $this->helper->getKiCADPart($part);
// The hardcoded description should win
self::assertSame('The real description', $result['fields']['description']['value']);
}
}

View file

@ -642,6 +642,12 @@ Sub elements will be moved upwards.</target>
<target>Group</target>
</segment>
</unit>
<unit id="kicadExportHelp" name="specifications.kicad_export.help">
<segment state="translated">
<source>specifications.kicad_export.help</source>
<target>Export this parameter as a KiCad field</target>
</segment>
</unit>
<unit id="XclPxI9" name="specification.create">
<segment state="translated">
<source>specification.create</source>