mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-03-01 12:59:36 +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
|
|
@ -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;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue