lcsc2kicad 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- lcsc2kicad-0.1.0/.gitignore +7 -0
- lcsc2kicad-0.1.0/LICENSE +21 -0
- lcsc2kicad-0.1.0/PKG-INFO +83 -0
- lcsc2kicad-0.1.0/README.md +65 -0
- lcsc2kicad-0.1.0/TESTING.md +134 -0
- lcsc2kicad-0.1.0/pyproject.toml +34 -0
- lcsc2kicad-0.1.0/src/lcsc2kicad/__init__.py +1 -0
- lcsc2kicad-0.1.0/src/lcsc2kicad/__main__.py +5 -0
- lcsc2kicad-0.1.0/src/lcsc2kicad/cli.py +297 -0
- lcsc2kicad-0.1.0/src/lcsc2kicad/config.py +199 -0
- lcsc2kicad-0.1.0/src/lcsc2kicad/errors.py +18 -0
- lcsc2kicad-0.1.0/src/lcsc2kicad/importer.py +153 -0
- lcsc2kicad-0.1.0/src/lcsc2kicad/library.py +276 -0
- lcsc2kicad-0.1.0/src/lcsc2kicad/project.py +131 -0
- lcsc2kicad-0.1.0/tests/test_config.py +27 -0
- lcsc2kicad-0.1.0/tests/test_library.py +65 -0
lcsc2kicad-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 [Votre nom]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: lcsc2kicad
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Importe des composants LCSC/EasyEDA dans la librairie KiCad du projet courant (surcouche d'easyeda2kicad).
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Classifier: Environment :: Console
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Operating System :: OS Independent
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
|
|
12
|
+
Requires-Python: >=3.10
|
|
13
|
+
Requires-Dist: easyeda2kicad>=0.8
|
|
14
|
+
Requires-Dist: tomli>=2.0; python_version < '3.11'
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest; extra == 'dev'
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# lcsc2kicad
|
|
20
|
+
|
|
21
|
+
Surcouche de [`easyeda2kicad`](https://github.com/uPesy/easyeda2kicad.py) : importe des
|
|
22
|
+
composants LCSC/EasyEDA (symbole + empreinte + modèle 3D) dans la **librairie KiCad du projet**,
|
|
23
|
+
avec le numéro LCSC conservé dans un champ du symbole.
|
|
24
|
+
|
|
25
|
+
## Installation
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pipx install lcsc2kicad # ou : pip install lcsc2kicad
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Utilisation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
lcsc2kicad C12345 # import complet dans le projet courant
|
|
35
|
+
lcsc2kicad C12345 --no-3d # sans modèle 3D
|
|
36
|
+
lcsc2kicad C123 C456 --project ~/kicad/DesktopHub
|
|
37
|
+
lcsc2kicad --from-file refs.txt -y # sans confirmation
|
|
38
|
+
lcsc2kicad list # composants de la librairie
|
|
39
|
+
lcsc2kicad remove C12345 # supprime symbole (+ empreinte/3D devenus inutiles)
|
|
40
|
+
lcsc2kicad init # facultatif : crée dossiers + .lcsc2kicad.toml
|
|
41
|
+
|
|
42
|
+
lcsc2kicad config list # réglages effectifs et leur origine (défaut/global/projet)
|
|
43
|
+
lcsc2kicad config set no_3d true # modifie .lcsc2kicad.toml du projet
|
|
44
|
+
lcsc2kicad config set --global projects_root ~/kicad # config globale (-g)
|
|
45
|
+
lcsc2kicad config get lcsc_field | unset no_3d | path
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Choix du projet (ordre) : `--project` → sélecteur (si activé) → remontée depuis le dossier courant.
|
|
49
|
+
Le projet retenu est affiché et confirmé avant toute écriture (`-y` pour désactiver ;
|
|
50
|
+
un choix fait dans le sélecteur vaut confirmation).
|
|
51
|
+
|
|
52
|
+
Chaque import affiche le **nom du composant** (nom du symbole) à côté de la référence LCSC.
|
|
53
|
+
La référence est aussi ajoutée aux **mots-clés** du symbole (`ki_keywords`) : taper `C25804`
|
|
54
|
+
dans le sélecteur de symboles de KiCad retrouve le composant (désactivable : `lcsc_keyword = false`).
|
|
55
|
+
|
|
56
|
+
Librairie créée dans `libs/<projet>.kicad_sym`, `libs/<projet>.pretty/`, `libs/3dmodels/`.
|
|
57
|
+
**À faire une seule fois dans KiCad** : Préférences > Gérer les librairies (onglet du projet),
|
|
58
|
+
avec le même nom pour les symboles et les empreintes.
|
|
59
|
+
|
|
60
|
+
## Configuration (TOML)
|
|
61
|
+
|
|
62
|
+
Projet : `<projet>/.lcsc2kicad.toml` — global : `~/.config/lcsc2kicad/config.toml`
|
|
63
|
+
(`%APPDATA%\lcsc2kicad\config.toml` sous Windows). Priorité : flags > projet > global > défauts.
|
|
64
|
+
|
|
65
|
+
```toml
|
|
66
|
+
# global uniquement
|
|
67
|
+
projects_root = "~/kicad"
|
|
68
|
+
use_selector = true
|
|
69
|
+
|
|
70
|
+
# global ou projet
|
|
71
|
+
lib_dir = "libs"
|
|
72
|
+
lib_name = "MaLib" # défaut : nom du projet
|
|
73
|
+
model_dir = "3dmodels" # relatif à lib_dir
|
|
74
|
+
lcsc_field = "LCSC"
|
|
75
|
+
lcsc_keyword = true # réf LCSC dans les mots-clés de recherche
|
|
76
|
+
no_3d = false
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Développement
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pip install -e ".[dev]" && pytest
|
|
83
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# lcsc2kicad
|
|
2
|
+
|
|
3
|
+
Surcouche de [`easyeda2kicad`](https://github.com/uPesy/easyeda2kicad.py) : importe des
|
|
4
|
+
composants LCSC/EasyEDA (symbole + empreinte + modèle 3D) dans la **librairie KiCad du projet**,
|
|
5
|
+
avec le numéro LCSC conservé dans un champ du symbole.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pipx install lcsc2kicad # ou : pip install lcsc2kicad
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Utilisation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
lcsc2kicad C12345 # import complet dans le projet courant
|
|
17
|
+
lcsc2kicad C12345 --no-3d # sans modèle 3D
|
|
18
|
+
lcsc2kicad C123 C456 --project ~/kicad/DesktopHub
|
|
19
|
+
lcsc2kicad --from-file refs.txt -y # sans confirmation
|
|
20
|
+
lcsc2kicad list # composants de la librairie
|
|
21
|
+
lcsc2kicad remove C12345 # supprime symbole (+ empreinte/3D devenus inutiles)
|
|
22
|
+
lcsc2kicad init # facultatif : crée dossiers + .lcsc2kicad.toml
|
|
23
|
+
|
|
24
|
+
lcsc2kicad config list # réglages effectifs et leur origine (défaut/global/projet)
|
|
25
|
+
lcsc2kicad config set no_3d true # modifie .lcsc2kicad.toml du projet
|
|
26
|
+
lcsc2kicad config set --global projects_root ~/kicad # config globale (-g)
|
|
27
|
+
lcsc2kicad config get lcsc_field | unset no_3d | path
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Choix du projet (ordre) : `--project` → sélecteur (si activé) → remontée depuis le dossier courant.
|
|
31
|
+
Le projet retenu est affiché et confirmé avant toute écriture (`-y` pour désactiver ;
|
|
32
|
+
un choix fait dans le sélecteur vaut confirmation).
|
|
33
|
+
|
|
34
|
+
Chaque import affiche le **nom du composant** (nom du symbole) à côté de la référence LCSC.
|
|
35
|
+
La référence est aussi ajoutée aux **mots-clés** du symbole (`ki_keywords`) : taper `C25804`
|
|
36
|
+
dans le sélecteur de symboles de KiCad retrouve le composant (désactivable : `lcsc_keyword = false`).
|
|
37
|
+
|
|
38
|
+
Librairie créée dans `libs/<projet>.kicad_sym`, `libs/<projet>.pretty/`, `libs/3dmodels/`.
|
|
39
|
+
**À faire une seule fois dans KiCad** : Préférences > Gérer les librairies (onglet du projet),
|
|
40
|
+
avec le même nom pour les symboles et les empreintes.
|
|
41
|
+
|
|
42
|
+
## Configuration (TOML)
|
|
43
|
+
|
|
44
|
+
Projet : `<projet>/.lcsc2kicad.toml` — global : `~/.config/lcsc2kicad/config.toml`
|
|
45
|
+
(`%APPDATA%\lcsc2kicad\config.toml` sous Windows). Priorité : flags > projet > global > défauts.
|
|
46
|
+
|
|
47
|
+
```toml
|
|
48
|
+
# global uniquement
|
|
49
|
+
projects_root = "~/kicad"
|
|
50
|
+
use_selector = true
|
|
51
|
+
|
|
52
|
+
# global ou projet
|
|
53
|
+
lib_dir = "libs"
|
|
54
|
+
lib_name = "MaLib" # défaut : nom du projet
|
|
55
|
+
model_dir = "3dmodels" # relatif à lib_dir
|
|
56
|
+
lcsc_field = "LCSC"
|
|
57
|
+
lcsc_keyword = true # réf LCSC dans les mots-clés de recherche
|
|
58
|
+
no_3d = false
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Développement
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
pip install -e ".[dev]" && pytest
|
|
65
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Procédure de test manuel — lcsc2kicad
|
|
2
|
+
|
|
3
|
+
Prérequis : Python ≥ 3.10, accès Internet (API EasyEDA), KiCad ≥ 6 installé.
|
|
4
|
+
Composants de test (courants, avec empreinte et normalement un modèle 3D) :
|
|
5
|
+
`C25804` (10 kΩ 0603), `C14663` (100 nF 0603), `C2040` (RP2040).
|
|
6
|
+
Si une référence est introuvable, prenez-en une autre sur lcsc.com.
|
|
7
|
+
|
|
8
|
+
## 0. Préparation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
python -m venv /tmp/venv && source /tmp/venv/bin/activate # Windows : .venv\Scripts\activate
|
|
12
|
+
pip install -e ".[dev]"
|
|
13
|
+
pytest # tests unitaires hors-ligne : tous verts
|
|
14
|
+
lcsc2kicad --version
|
|
15
|
+
mkdir -p ~/tmp-kicad/TestA ~/tmp-kicad/TestB
|
|
16
|
+
touch ~/tmp-kicad/TestA/TestA.kicad_pro ~/tmp-kicad/TestB/TestB.kicad_pro
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
(Mieux : créez de vrais projets vides depuis KiCad pour les étapes 9 et 10.)
|
|
20
|
+
|
|
21
|
+
## 1. Détection du projet (méthode A)
|
|
22
|
+
|
|
23
|
+
| # | Action | Résultat attendu |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| 1.1 | `cd ~/tmp-kicad/TestA && lcsc2kicad C25804` | Affiche projet `TestA`, demande « Écrire dans ce projet ? » ; `n` → « annulé », rien créé |
|
|
26
|
+
| 1.2 | Même commande, répondre Entrée | `C25804 ajouté (symbole, empreinte, 3D)` ; création de `libs/TestA.kicad_sym`, `libs/TestA.pretty/`, `libs/3dmodels/` ; rappel KiCad affiché |
|
|
27
|
+
| 1.3 | `mkdir sub && cd sub && lcsc2kicad list` | Remonte jusqu'à `TestA` ; liste `C25804` avec empreinte `TestA:<nom>` (pas de confirmation pour `list`) |
|
|
28
|
+
| 1.4 | `cd /tmp && lcsc2kicad list` | Erreur claire listant les 3 moyens de préciser le projet, code retour ≠ 0 |
|
|
29
|
+
|
|
30
|
+
## 2. Import et options
|
|
31
|
+
|
|
32
|
+
| # | Action | Résultat attendu |
|
|
33
|
+
|---|---|---|
|
|
34
|
+
| 2.1 | `lcsc2kicad C25804 -y` | `sauté (déjà présent : symbole)`, aucun appel réseau, rien modifié |
|
|
35
|
+
| 2.2 | `lcsc2kicad C14663 --no-3d -y` | `ajouté (symbole, empreinte)` ; pas de `.wrl`/`.step` pour ce composant ; ligne `(model` absente de son `.kicad_mod` |
|
|
36
|
+
| 2.3 | `lcsc2kicad C2040 --no-symbol --no-footprint -y` | Seul le 3D est ajouté ; `list` ne montre pas C2040 |
|
|
37
|
+
| 2.4 | `lcsc2kicad C2040 --no-3d -y` | Ajoute symbole + empreinte (le 3D de 2.3 reste en place) |
|
|
38
|
+
| 2.5 | `lcsc2kicad C25804 --force -y` | `ajouté` ; un seul symbole C25804 dans le `.kicad_sym` (pas de doublon) |
|
|
39
|
+
| 2.6 | Fichier `refs.txt` : `C25804`, `C14663 # commentaire`, ligne vide, `C2040` puis `lcsc2kicad --from-file refs.txt -y` | 3 lignes de résultat, tout « sauté » |
|
|
40
|
+
| 2.7 | `lcsc2kicad C99999999 C25804x -y` | Deux lignes `erreur` explicites ; code retour 1 ; les autres références du même appel sont quand même traitées |
|
|
41
|
+
| 2.8 | `lcsc2kicad C25804 --force -v -y` | Sortie détaillée d'`easyeda2kicad` affichée |
|
|
42
|
+
|
|
43
|
+
- 2.9 Vérifier que chaque ligne de résultat affiche le **nom du composant** après la référence (ex. `C25804 0603WAF1002T5E5 ajouté (…)`), y compris pour `sauté` et `remove`.
|
|
44
|
+
|
|
45
|
+
## 3. Contenu des fichiers
|
|
46
|
+
|
|
47
|
+
- 3.1 Ouvrir `libs/TestA.kicad_sym` dans un éditeur : chaque symbole contient `(property "LCSC" "C…" …)` ; la propriété `Footprint` vaut `TestA:<empreinte>`.
|
|
48
|
+
- 3.2 Ouvrir un `.kicad_mod` : le chemin du modèle est `${KIPRJMOD}/libs/3dmodels/<nom>.wrl` (aucun chemin absolu ni temporaire).
|
|
49
|
+
- 3.2 bis Chaque symbole contient aussi `(property "ki_keywords" "C…" …)`. Si le symbole avait déjà des mots-clés, la réf LCSC y est ajoutée une seule fois (relancer avec `--force` ne la duplique pas).
|
|
50
|
+
- 3.3 Aucun fichier `.tmp` résiduel dans `libs/`. Les fichiers `sym-lib-table` / `fp-lib-table` du projet n'ont **pas** été modifiés.
|
|
51
|
+
|
|
52
|
+
## 4. Intégration KiCad (point critique)
|
|
53
|
+
|
|
54
|
+
1. Ouvrir `TestA` dans KiCad. Préférences > Gérer les librairies de symboles > onglet *Projet* : ajouter `TestA` avec `${KIPRJMOD}/libs/TestA.kicad_sym`. Idem pour les empreintes avec `${KIPRJMOD}/libs/TestA.pretty` (même nom `TestA`).
|
|
55
|
+
2. Éditeur de schéma > placer un symbole : `TestA` apparaît avec ses composants ; champ `LCSC` visible dans les propriétés (masqué par défaut).
|
|
56
|
+
3. Assigner l'empreinte (elle est déjà liée) et ouvrir l'éditeur PCB : empreinte et modèle 3D (visionneuse 3D) s'affichent.
|
|
57
|
+
4. **Sans fermer KiCad**, lancer `lcsc2kicad C14663 -y` hors-KiCad, puis rouvrir le sélecteur de symboles : le nouveau symbole apparaît sans redémarrage.
|
|
58
|
+
5. Dans le sélecteur de symboles, taper la référence (`C25804`) dans la barre de recherche : le composant ressort. Refaire avec `lcsc_keyword = false` sur un nouveau composant : il ne ressort plus par sa réf (le nom reste cherchable).
|
|
59
|
+
6. Vérifier que KiCad n'affiche aucune erreur de format `.kicad_sym` (note le numéro de version de KiCad testé : points 10 de la spec).
|
|
60
|
+
7. Exporter une BOM contenant le champ `LCSC` (ou tester avec JLCPCB Tools) : la colonne est bien reconnue.
|
|
61
|
+
|
|
62
|
+
## 5. `list` et `remove`
|
|
63
|
+
|
|
64
|
+
| # | Action | Résultat attendu |
|
|
65
|
+
|---|---|---|
|
|
66
|
+
| 5.1 | `lcsc2kicad list` | Tableau LCSC / Symbole / Empreinte, compteur correct |
|
|
67
|
+
| 5.2 | `lcsc2kicad remove C14663 -y` | `supprimé (symbole, empreinte, 3D)` ; `list` ne le montre plus ; fichiers `.kicad_mod`, `.wrl`, `.step` supprimés |
|
|
68
|
+
| 5.3 | Deux composants partageant la même empreinte (ex. deux résistances 0603) : supprimer l'un | L'empreinte et le 3D sont **conservés** ; supprimés au retrait du second |
|
|
69
|
+
| 5.4 | `lcsc2kicad remove C00000 -y` | `erreur : composant introuvable`, code 1 |
|
|
70
|
+
| 5.5 | `lcsc2kicad remove <nom du symbole> -y` | Fonctionne aussi par nom de symbole |
|
|
71
|
+
|
|
72
|
+
## 6. `init` et configuration
|
|
73
|
+
|
|
74
|
+
| # | Action | Résultat attendu |
|
|
75
|
+
|---|---|---|
|
|
76
|
+
| 6.1 | Dans `TestB` : `lcsc2kicad init` | Crée `libs/…`, `.lcsc2kicad.toml` (commenté), affiche les chemins à déclarer ; un `add` suivant n'affiche **pas** le rappel |
|
|
77
|
+
| 6.2 | Dans `TestB/.lcsc2kicad.toml` : `lib_dir = "lib"`, `lib_name = "Perso"`, `lcsc_field = "LCSC Part"`, `no_3d = true` ; `lcsc2kicad C25804 -y` | Fichiers dans `lib/Perso.*` ; champ `LCSC Part` ; pas de 3D |
|
|
78
|
+
| 6.3 | Même config, ajouter `--full` | Le 3D est importé malgré `no_3d = true` |
|
|
79
|
+
| 6.4 | Config globale (`~/.config/lcsc2kicad/config.toml`, ou `%APPDATA%\lcsc2kicad\`) avec `lcsc_field = "MPN_LCSC"` et config projet avec `lcsc_field = "LCSC"` | Le projet l'emporte |
|
|
80
|
+
| 6.5 | Clé inconnue ou mauvais type dans un TOML | Avertissement (clé inconnue) ou erreur claire (mauvais type) |
|
|
81
|
+
|
|
82
|
+
### Commande `config`
|
|
83
|
+
|
|
84
|
+
| # | Action | Résultat attendu |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| 6.6 | `lcsc2kicad config list` (dans un projet) | Tableau clé = valeur `[défaut/global/projet]` ; chemin du fichier affiché |
|
|
87
|
+
| 6.7 | `lcsc2kicad config set no_3d true` (confirmation) | Écrit `no_3d = true` dans `<projet>/.lcsc2kicad.toml` ; `list` → origine `[projet]` ; commentaires du fichier conservés |
|
|
88
|
+
| 6.8 | `lcsc2kicad config get no_3d` / `config path` | Affichent uniquement la valeur / le chemin (utilisable dans un script) |
|
|
89
|
+
| 6.9 | `lcsc2kicad config unset no_3d -y` | Clé retirée ; second `unset` : « n'était pas défini » |
|
|
90
|
+
| 6.10 | `lcsc2kicad config set --global projects_root ~/tmp-kicad` puis `config set -g use_selector true` | Crée/modifie le fichier global (`config path -g` donne son emplacement) ; pas de projet requis, depuis n'importe quel dossier |
|
|
91
|
+
| 6.11 | `config set no_3d peut-être` / `config set projects_root x` (sans `-g`) / `config set foo 1` | Erreurs claires (valeur booléenne invalide, clé globale uniquement, clé inconnue + liste des clés valides) |
|
|
92
|
+
| 6.12 | `config set lib_dir lib` puis `lcsc2kicad C25804 -y` | La librairie est créée dans `lib/` |
|
|
93
|
+
|
|
94
|
+
## 7. Sélecteur (méthode C)
|
|
95
|
+
|
|
96
|
+
Config globale :
|
|
97
|
+
```toml
|
|
98
|
+
projects_root = "~/tmp-kicad"
|
|
99
|
+
use_selector = true
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
| # | Action | Résultat attendu |
|
|
103
|
+
|---|---|---|
|
|
104
|
+
| 7.1 | `cd /tmp && lcsc2kicad C25804` | Liste numérotée `TestA`, `TestB` ; choisir `2` → écrit dans `TestB` (sans seconde confirmation) |
|
|
105
|
+
| 7.2 | Saisir `testa` (nom) | Sélectionne `TestA` |
|
|
106
|
+
| 7.3 | `cd ~/tmp-kicad/TestA && lcsc2kicad C25804` puis Entrée | `TestA` présélectionné (`*`), Entrée valide |
|
|
107
|
+
| 7.4 | `lcsc2kicad C25804 --project ~/tmp-kicad/TestB` | Pas de sélecteur : `--project` gagne ; confirmation demandée |
|
|
108
|
+
| 7.5 | `projects_root` inexistant | Avertissement puis repli sur le dossier courant |
|
|
109
|
+
| 7.6 | `cd ~/tmp-kicad/TestA && lcsc2kicad C25804 -y` | Pas de sélecteur (pas d'interaction avec `-y`) : utilise le projet courant |
|
|
110
|
+
|
|
111
|
+
## 8. Terminal non interactif
|
|
112
|
+
|
|
113
|
+
- 8.1 `lcsc2kicad C25804 < /dev/null` (Windows : `< NUL`) → erreur « terminal non interactif », code 2, rien écrit.
|
|
114
|
+
- 8.2 `lcsc2kicad C25804 -y < /dev/null` → fonctionne.
|
|
115
|
+
|
|
116
|
+
## 9. Cas limites
|
|
117
|
+
|
|
118
|
+
- 9.1 Coupez le réseau : `lcsc2kicad C25804 --force -y` → `erreur` explicite, la librairie existante n'est pas corrompue.
|
|
119
|
+
- 9.2 Projet dont le nom contient espaces/points (`Mon Projet v1.2`) : la librairie s'appelle `Mon_Projet_v1_2` ; import OK.
|
|
120
|
+
- 9.3 Plusieurs `.kicad_pro` dans le même dossier : le premier par ordre alphabétique est utilisé.
|
|
121
|
+
- 9.4 `lcsc2kicad` sans argument : affiche l'aide. `lcsc2kicad --project X` sans référence : erreur « aucune référence ».
|
|
122
|
+
- 9.5 Ctrl+C pendant une confirmation : sortie propre (code 130), aucun fichier modifié.
|
|
123
|
+
|
|
124
|
+
## 10. Windows (PowerShell et cmd)
|
|
125
|
+
|
|
126
|
+
Reprendre 1.1–1.3, 2.2, 4 (KiCad Windows), 7.1 et 8 (`< NUL`). Vérifier : accents lisibles dans la console, chemins `${KIPRJMOD}/…` en `/`, `%APPDATA%\lcsc2kicad\config.toml` bien lu, `pipx install` fonctionnel.
|
|
127
|
+
|
|
128
|
+
## 11. Installation depuis PyPI (après publication)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
pipx install lcsc2kicad && lcsc2kicad --version
|
|
132
|
+
pipx upgrade lcsc2kicad
|
|
133
|
+
```
|
|
134
|
+
Vérifier qu'`easyeda2kicad` est installé comme dépendance (`pipx runpip lcsc2kicad list`).
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "lcsc2kicad"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Importe des composants LCSC/EasyEDA dans la librairie KiCad du projet courant (surcouche d'easyeda2kicad)."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
dependencies = [
|
|
13
|
+
"easyeda2kicad>=0.8",
|
|
14
|
+
"tomli>=2.0; python_version < '3.11'",
|
|
15
|
+
]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Environment :: Console",
|
|
21
|
+
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.optional-dependencies]
|
|
25
|
+
dev = ["pytest"]
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
lcsc2kicad = "lcsc2kicad.cli:main"
|
|
29
|
+
|
|
30
|
+
[tool.hatch.build.targets.wheel]
|
|
31
|
+
packages = ["src/lcsc2kicad"]
|
|
32
|
+
|
|
33
|
+
[tool.pytest.ini_options]
|
|
34
|
+
pythonpath = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
"""Interface en ligne de commande de lcsc2kicad."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import re
|
|
6
|
+
import sys
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
from . import __version__, config, importer
|
|
11
|
+
from . import library as lib
|
|
12
|
+
from . import project as proj
|
|
13
|
+
from .errors import CliError, ImportFailure, Lcsc2KicadError
|
|
14
|
+
|
|
15
|
+
COMMANDS = {"add", "list", "remove", "init", "config"}
|
|
16
|
+
LCSC_RE = re.compile(r"^C\d+$")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Context:
|
|
21
|
+
project: proj.Project
|
|
22
|
+
settings: dict
|
|
23
|
+
paths: lib.LibPaths
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ------------------------------------------------------------------ utilitaires
|
|
27
|
+
def _confirm(question: str) -> bool:
|
|
28
|
+
try:
|
|
29
|
+
answer = input(f"{question} [O/n] ").strip().lower()
|
|
30
|
+
except EOFError:
|
|
31
|
+
return False
|
|
32
|
+
return answer in ("", "o", "oui", "y", "yes")
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _prepare(args, write: bool, quiet: bool = False) -> Context:
|
|
36
|
+
interactive = sys.stdin.isatty()
|
|
37
|
+
if write and not args.yes and not interactive:
|
|
38
|
+
raise CliError("terminal non interactif : confirmation impossible, relancez avec -y")
|
|
39
|
+
|
|
40
|
+
gconf = config.load_global()
|
|
41
|
+
can_prompt = interactive and not (write and args.yes)
|
|
42
|
+
project, via_selector = proj.resolve(args.project, gconf, can_prompt)
|
|
43
|
+
|
|
44
|
+
overrides = {k: getattr(args, k, False) for k in ("no_symbol", "no_footprint", "no_3d", "full")}
|
|
45
|
+
settings = config.load_settings(project.dir, gconf, overrides)
|
|
46
|
+
paths = lib.LibPaths.build(project.dir, project.name, settings)
|
|
47
|
+
|
|
48
|
+
if not quiet:
|
|
49
|
+
print(f"Projet : {project.name}")
|
|
50
|
+
print(f"Dossier : {project.dir}")
|
|
51
|
+
print(f"Librairie : {paths.sym}")
|
|
52
|
+
# Un choix explicite dans le sélecteur vaut confirmation.
|
|
53
|
+
if write and not args.yes and not via_selector:
|
|
54
|
+
if not _confirm("Écrire dans ce projet ?"):
|
|
55
|
+
raise CliError("annulé")
|
|
56
|
+
if not quiet:
|
|
57
|
+
print()
|
|
58
|
+
return Context(project, settings, paths)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _kicad_reminder(paths: lib.LibPaths) -> None:
|
|
62
|
+
print("\nRappel : déclarez la librairie dans KiCad (une seule fois par projet) :")
|
|
63
|
+
print(" Préférences > Gérer les librairies de symboles / d'empreintes > onglet « Projet »")
|
|
64
|
+
print(f" Symboles : nom « {paths.name} » chemin {paths.sym_ref}")
|
|
65
|
+
print(f" Empreintes : nom « {paths.name} » chemin {paths.pretty_ref}")
|
|
66
|
+
print(" Le nom (nickname) doit être identique pour les deux : le symbole le référence.")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _collect_refs(args) -> list[str]:
|
|
70
|
+
refs = list(args.refs)
|
|
71
|
+
if args.from_file:
|
|
72
|
+
try:
|
|
73
|
+
lines = Path(args.from_file).expanduser().read_text(encoding="utf-8-sig").splitlines()
|
|
74
|
+
except OSError as exc:
|
|
75
|
+
raise CliError(f"lecture de {args.from_file} impossible : {exc}") from exc
|
|
76
|
+
for line in lines:
|
|
77
|
+
line = line.split("#", 1)[0].strip()
|
|
78
|
+
if line:
|
|
79
|
+
refs.append(line)
|
|
80
|
+
seen, out = set(), []
|
|
81
|
+
for r in refs:
|
|
82
|
+
r = r.strip().upper()
|
|
83
|
+
if r and r not in seen:
|
|
84
|
+
seen.add(r)
|
|
85
|
+
out.append(r)
|
|
86
|
+
if not out:
|
|
87
|
+
raise CliError("aucune référence LCSC fournie (ex. : lcsc2kicad C12345)")
|
|
88
|
+
return out
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# --------------------------------------------------------------------- commandes
|
|
92
|
+
def cmd_add(args) -> int:
|
|
93
|
+
refs = _collect_refs(args)
|
|
94
|
+
ctx = _prepare(args, write=True)
|
|
95
|
+
created = lib.ensure_library(ctx.paths)
|
|
96
|
+
|
|
97
|
+
added = skipped = errors = 0
|
|
98
|
+
for ref in refs:
|
|
99
|
+
if not LCSC_RE.match(ref):
|
|
100
|
+
print(f"{ref:<11} erreur : référence LCSC invalide (attendu : C + chiffres)")
|
|
101
|
+
errors += 1
|
|
102
|
+
continue
|
|
103
|
+
try:
|
|
104
|
+
res = importer.import_one(ref, ctx.paths, ctx.settings, args.force, args.verbose)
|
|
105
|
+
except (ImportFailure, Lcsc2KicadError, OSError, ValueError) as exc:
|
|
106
|
+
print(f"{ref:<11} erreur : {exc}")
|
|
107
|
+
errors += 1
|
|
108
|
+
continue
|
|
109
|
+
if res.status == "added":
|
|
110
|
+
extra = f" ; conservé : {', '.join(res.kept)}" if res.kept else ""
|
|
111
|
+
print(f"{ref:<11} {res.name} ajouté ({', '.join(res.wrote)}){extra}")
|
|
112
|
+
added += 1
|
|
113
|
+
else:
|
|
114
|
+
print(f"{ref:<11} {res.name} sauté (déjà présent : {', '.join(res.kept)}) — --force pour écraser")
|
|
115
|
+
skipped += 1
|
|
116
|
+
|
|
117
|
+
print(f"\n{added} ajouté(s), {skipped} sauté(s), {errors} erreur(s)")
|
|
118
|
+
if created and added:
|
|
119
|
+
_kicad_reminder(ctx.paths)
|
|
120
|
+
return 1 if errors else 0
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def cmd_list(args) -> int:
|
|
124
|
+
ctx = _prepare(args, write=False)
|
|
125
|
+
if not ctx.paths.sym.exists():
|
|
126
|
+
print("Aucune librairie pour ce projet pour l'instant (lcsc2kicad add C…).")
|
|
127
|
+
return 0
|
|
128
|
+
field = ctx.settings["lcsc_field"]
|
|
129
|
+
syms = lib.parse_symbols(lib.read(ctx.paths.sym))
|
|
130
|
+
rows = sorted(((s.props.get(field, "-"), s.name, s.props.get("Footprint", "-")) for s in syms),
|
|
131
|
+
key=lambda r: r[1].lower())
|
|
132
|
+
if not rows:
|
|
133
|
+
print("La librairie est vide.")
|
|
134
|
+
return 0
|
|
135
|
+
w0 = max(len(field), *(len(r[0]) for r in rows))
|
|
136
|
+
w1 = max(len("Symbole"), *(len(r[1]) for r in rows))
|
|
137
|
+
print(f"{field:<{w0}} {'Symbole':<{w1}} Empreinte")
|
|
138
|
+
for lcsc, name, fp in rows:
|
|
139
|
+
print(f"{lcsc:<{w0}} {name:<{w1}} {fp}")
|
|
140
|
+
print(f"\n{len(rows)} composant(s)")
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def cmd_remove(args) -> int:
|
|
145
|
+
refs = [r.strip() for r in args.refs if r.strip()]
|
|
146
|
+
if not refs:
|
|
147
|
+
raise CliError("indiquez au moins une référence LCSC ou un nom de symbole")
|
|
148
|
+
ctx = _prepare(args, write=True)
|
|
149
|
+
errors = 0
|
|
150
|
+
for ref in refs:
|
|
151
|
+
try:
|
|
152
|
+
name, removed = lib.remove_component(ctx.paths, ctx.settings["lcsc_field"], ref)
|
|
153
|
+
print(f"{ref:<11} {name} supprimé ({', '.join(removed)})")
|
|
154
|
+
except (Lcsc2KicadError, OSError, ValueError) as exc:
|
|
155
|
+
print(f"{ref:<11} erreur : {exc}")
|
|
156
|
+
errors += 1
|
|
157
|
+
return 1 if errors else 0
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def cmd_init(args) -> int:
|
|
161
|
+
ctx = _prepare(args, write=True)
|
|
162
|
+
created = lib.ensure_library(ctx.paths)
|
|
163
|
+
cfg = ctx.project.dir / config.PROJECT_CONFIG_NAME
|
|
164
|
+
if not cfg.exists():
|
|
165
|
+
cfg.write_text(config.PROJECT_CONFIG_TEMPLATE, encoding="utf-8")
|
|
166
|
+
print(f"Créé : {cfg}")
|
|
167
|
+
print(f"{'Créé' if created else 'Déjà présent'} : {ctx.paths.root}")
|
|
168
|
+
_kicad_reminder(ctx.paths)
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def cmd_config(args) -> int:
|
|
173
|
+
action, use_global = args.action, args.use_global
|
|
174
|
+
if action in ("get", "set", "unset") and not args.key:
|
|
175
|
+
raise CliError(f"'config {action}' demande un nom de clé")
|
|
176
|
+
if action == "set" and args.value is None:
|
|
177
|
+
raise CliError("'config set' demande une valeur : lcsc2kicad config set <clé> <valeur>")
|
|
178
|
+
|
|
179
|
+
gconf = config.load_global()
|
|
180
|
+
if use_global:
|
|
181
|
+
path, project_dir = config.global_config_path(), None
|
|
182
|
+
else:
|
|
183
|
+
ctx = _prepare(args, write=action in ("set", "unset"), quiet=action in ("get", "path"))
|
|
184
|
+
path, project_dir = ctx.project.dir / config.PROJECT_CONFIG_NAME, ctx.project.dir
|
|
185
|
+
|
|
186
|
+
if action == "path":
|
|
187
|
+
print(path)
|
|
188
|
+
elif action == "list":
|
|
189
|
+
rows = config.describe(project_dir, gconf)
|
|
190
|
+
w = max(len(r[0]) for r in rows)
|
|
191
|
+
for key, value, origin in rows:
|
|
192
|
+
shown = config.fmt_value(value) or "(non défini)"
|
|
193
|
+
print(f"{key:<{w}} = {shown:<14} [{origin}]")
|
|
194
|
+
print(f"\nFichier : {path}{'' if path.exists() else ' (inexistant)'}")
|
|
195
|
+
elif action == "get":
|
|
196
|
+
config.key_type(args.key, use_global)
|
|
197
|
+
value = next((v for k, v, _ in config.describe(project_dir, gconf) if k == args.key), None)
|
|
198
|
+
if value is None and use_global:
|
|
199
|
+
value = gconf.get(args.key)
|
|
200
|
+
print("" if value is None else config.fmt_value(value).strip('"') if isinstance(value, str)
|
|
201
|
+
else config.fmt_value(value))
|
|
202
|
+
elif action == "set":
|
|
203
|
+
value = config.parse_value(args.key, args.value, use_global)
|
|
204
|
+
config.edit_file(path, args.key, value)
|
|
205
|
+
print(f"{args.key} = {config.fmt_value(value)} ({path})")
|
|
206
|
+
elif action == "unset":
|
|
207
|
+
config.key_type(args.key, use_global)
|
|
208
|
+
if config.edit_file(path, args.key, None):
|
|
209
|
+
print(f"{args.key} supprimé de {path}")
|
|
210
|
+
else:
|
|
211
|
+
print(f"{args.key} n'était pas défini dans {path}")
|
|
212
|
+
return 0
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
# ------------------------------------------------------------------------ parser
|
|
216
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
217
|
+
common = argparse.ArgumentParser(add_help=False)
|
|
218
|
+
common.add_argument("--project", metavar="CHEMIN",
|
|
219
|
+
help="dossier du projet ou fichier .kicad_pro")
|
|
220
|
+
common.add_argument("-y", "--yes", action="store_true",
|
|
221
|
+
help="ne pas demander de confirmation")
|
|
222
|
+
common.add_argument("-v", "--verbose", action="store_true",
|
|
223
|
+
help="afficher la sortie d'easyeda2kicad")
|
|
224
|
+
|
|
225
|
+
parser = argparse.ArgumentParser(
|
|
226
|
+
prog="lcsc2kicad",
|
|
227
|
+
description="Importe des composants LCSC/EasyEDA dans la librairie KiCad du projet.",
|
|
228
|
+
epilog="Sans sous-commande, « add » est utilisé : lcsc2kicad C12345",
|
|
229
|
+
)
|
|
230
|
+
parser.add_argument("--version", action="version", version=f"lcsc2kicad {__version__}")
|
|
231
|
+
sub = parser.add_subparsers(dest="command", metavar="{add,list,remove,init,config}")
|
|
232
|
+
|
|
233
|
+
p = sub.add_parser("add", parents=[common], help="importer un ou plusieurs composants")
|
|
234
|
+
p.add_argument("refs", nargs="*", metavar="LCSC", help="références LCSC (C12345 …)")
|
|
235
|
+
p.add_argument("--from-file", metavar="FICHIER", help="fichier : une référence par ligne")
|
|
236
|
+
p.add_argument("--no-symbol", action="store_true", help="ne pas importer le symbole")
|
|
237
|
+
p.add_argument("--no-footprint", action="store_true", help="ne pas importer l'empreinte")
|
|
238
|
+
p.add_argument("--no-3d", action="store_true", help="ne pas importer le modèle 3D")
|
|
239
|
+
p.add_argument("--full", action="store_true",
|
|
240
|
+
help="tout importer, en ignorant les no_* de la config")
|
|
241
|
+
p.add_argument("--force", action="store_true", help="écraser les éléments déjà présents")
|
|
242
|
+
p.set_defaults(func=cmd_add)
|
|
243
|
+
|
|
244
|
+
p = sub.add_parser("list", parents=[common], help="lister les composants de la librairie")
|
|
245
|
+
p.set_defaults(func=cmd_list)
|
|
246
|
+
|
|
247
|
+
p = sub.add_parser("remove", parents=[common], help="supprimer un composant")
|
|
248
|
+
p.add_argument("refs", nargs="*", metavar="LCSC_OU_NOM")
|
|
249
|
+
p.set_defaults(func=cmd_remove)
|
|
250
|
+
|
|
251
|
+
p = sub.add_parser("init", parents=[common], help="préparer le projet (facultatif)")
|
|
252
|
+
p.set_defaults(func=cmd_init)
|
|
253
|
+
|
|
254
|
+
p = sub.add_parser("config", parents=[common],
|
|
255
|
+
help="afficher / modifier la configuration (list, get, set, unset, path)")
|
|
256
|
+
p.add_argument("action", choices=["list", "get", "set", "unset", "path"])
|
|
257
|
+
p.add_argument("key", nargs="?", help="nom du réglage")
|
|
258
|
+
p.add_argument("value", nargs="?", help="nouvelle valeur (pour set)")
|
|
259
|
+
p.add_argument("-g", "--global", dest="use_global", action="store_true",
|
|
260
|
+
help="agir sur la config globale plutôt que sur celle du projet")
|
|
261
|
+
p.set_defaults(func=cmd_config)
|
|
262
|
+
return parser
|
|
263
|
+
|
|
264
|
+
|
|
265
|
+
def main(argv: list[str] | None = None) -> int:
|
|
266
|
+
for stream in (sys.stdout, sys.stderr):
|
|
267
|
+
try:
|
|
268
|
+
stream.reconfigure(errors="replace")
|
|
269
|
+
except (AttributeError, ValueError):
|
|
270
|
+
pass
|
|
271
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
272
|
+
parser = build_parser()
|
|
273
|
+
if not argv:
|
|
274
|
+
parser.print_help()
|
|
275
|
+
return 0
|
|
276
|
+
if argv[0] not in COMMANDS and argv[0] not in ("-h", "--help", "--version"):
|
|
277
|
+
argv.insert(0, "add")
|
|
278
|
+
# argparse gère mal une option placée entre deux positionnels facultatifs
|
|
279
|
+
# (config set --global clé valeur) : on extrait -g/--global nous-mêmes.
|
|
280
|
+
global_flag = argv[0] == "config" and any(a in ("-g", "--global") for a in argv)
|
|
281
|
+
if global_flag:
|
|
282
|
+
argv = [a for a in argv if a not in ("-g", "--global")]
|
|
283
|
+
args = parser.parse_args(argv)
|
|
284
|
+
if global_flag:
|
|
285
|
+
args.use_global = True
|
|
286
|
+
try:
|
|
287
|
+
return args.func(args)
|
|
288
|
+
except Lcsc2KicadError as exc:
|
|
289
|
+
print(f"Erreur : {exc}", file=sys.stderr)
|
|
290
|
+
return 2
|
|
291
|
+
except KeyboardInterrupt:
|
|
292
|
+
print("\nInterrompu.", file=sys.stderr)
|
|
293
|
+
return 130
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
if __name__ == "__main__":
|
|
297
|
+
sys.exit(main())
|