diff --git a/.github/workflows/create-debug-docs.yaml b/.github/workflows/create-debug-docs.yaml index ecabe33..b3108ef 100644 --- a/.github/workflows/create-debug-docs.yaml +++ b/.github/workflows/create-debug-docs.yaml @@ -1,90 +1,11 @@ -name: Create Production Data For Beta Version +name: Create Debug Docs on: workflow_dispatch: push: - branches: - - 'main' - + branches: [main] permissions: contents: write - jobs: - export-ecad: - name: Export ECAD - runs-on: ubuntu-24.04 - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Update the PCBs with the BETA and git hash - run: | - export COMMIT=$(git rev-parse --short HEAD) - echo "COMMIT = ${COMMIT}" - sed -i "s/<>/BETA-${COMMIT}/g" *.kicad_* - - - name: Generate Export Files - uses: INTI-CMNB/KiBot@v2_k9 - with: - config: production.kibot.yaml - dir: output - schema: main.kicad_sch - board: main.kicad_pcb - verbose: 0 - - - name: Fix output directory permissions - run: sudo chmod -R a+w output/ - - - name: Generate Stencil PNG - run: | - pip install -r scripts/requirements.txt - python3 scripts/make_stencil_image.py \ - output/main-stencil_top.svg \ - output/main-stencil_top.png - - - name: Upload Export Files as Artifacts - uses: actions/upload-artifact@v4 - with: - name: production - path: output - if-no-files-found: error - - - collect-and-provide: - runs-on: ubuntu-24.04 - needs: export-ecad - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Setup Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: '3.1' - bundler-cache: true - cache-version: 0 - - - name: Setup Pages - id: pages - uses: actions/configure-pages@v5 - with: - generator_config_file: doc - - - name: Build with Jekyll - run: cd doc && bundle exec jekyll build - env: - JEKYLL_ENV: production - - - uses: actions/download-artifact@v5 - with: - name: production - path: doc/_site - - - name: Deploy πŸš€ - uses: JamesIves/github-pages-deploy-action@v4 - with: - token: ${{ secrets.GITHUB_TOKEN }} - branch: gh-pages - folder: doc/_site - target-folder: ${{ env.DEPLOY_PATH }} - clean: true + build: + uses: OE5XRX/HW-Module-CI/.github/workflows/create-debug-docs.yaml@main + secrets: inherit diff --git a/.github/workflows/create-release-docs.yaml b/.github/workflows/create-release-docs.yaml index bf49da9..7f81061 100644 --- a/.github/workflows/create-release-docs.yaml +++ b/.github/workflows/create-release-docs.yaml @@ -1,75 +1,9 @@ -name: Create Production Data For Release Version +name: Create Release Docs on: workflow_dispatch: release: types: [published] - jobs: - export-ecad: - name: Export ECAD - runs-on: ubuntu-24.04 - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Update the PCBs with the BETA and git hash - run: | - export COMMIT=$(git rev-parse --short HEAD) - echo "COMMIT = ${COMMIT}" - sed -i "s/<>/${GITHUB_REF_NAME}-${COMMIT}/g" *.kicad_* - - # kibot -c production.kibot.yaml -b main.kicad_pcb -e main.kicad_sch -d output - - name: Generate Export Files - uses: INTI-CMNB/KiBot@v2_k9 - with: - config: production.kibot.yaml - dir: output - schema: main.kicad_sch - board: main.kicad_pcb - verbose: 0 - - - name: Fix output directory permissions - run: sudo chmod -R a+w output/ - - - name: Generate Stencil PNG - run: | - pip install -r scripts/requirements.txt - python3 scripts/make_stencil_image.py \ - output/main-stencil_top.svg \ - output/main-stencil_top.png - - - name: copy doc files to output folder - run: sudo cp doc/* output/ - - - name: Upload Export Files as Artifacts - uses: actions/upload-artifact@v4 - with: - name: production - path: output - if-no-files-found: error - - - name: Generate Parts on InvenTree Server - env: - INVENTREE_API_TOKEN: ${{ secrets.INVENTREE_API_TOKEN }} - INVENTREE_API_HOST: ${{ secrets.INVENTREE_API_HOST }} - MOUSER_API_KEY: ${{ secrets.MOUSER_API_KEY }} - run: | - pip install -r scripts/requirements.txt - python3 scripts/bom_export.py \ - --csv_file output/*-bom.csv \ - --name "${{ github.event.repository.name }}" \ - --version "${{ github.ref_name }}" \ - --pcb_image output/*-3D_top-without.png \ - --assembly_image output/*-3D_top-with.png \ - --stencil_image output/*-stencil_top.png - - - name: Deploy πŸš€ - uses: JamesIves/github-pages-deploy-action@v4 - with: - token: ${{ secrets.DEPLOY_GH_TOKEN }} - repository-name: OE5XRX/OE5XRX.github.io - branch: main - folder: output - target-folder: docs/remote-station/hardware/${{ github.event.repository.name }} - clean: true + build: + uses: OE5XRX/HW-Module-CI/.github/workflows/create-release-docs.yaml@main + secrets: inherit diff --git a/.github/workflows/kibot-check.yaml b/.github/workflows/kibot-check.yaml index 0710288..2d43302 100644 --- a/.github/workflows/kibot-check.yaml +++ b/.github/workflows/kibot-check.yaml @@ -1,26 +1,11 @@ -name: Kibot check +name: KiBot Check on: workflow_dispatch: pull_request: - branches: - - 'main' + branches: [main] push: - branches: - - 'main' - + branches: [main] jobs: - kibot_check: - name: Check with Kibot - runs-on: ubuntu-24.04 - - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: check KiCad files - uses: INTI-CMNB/KiBot@v2_k9 - with: - config: test.kibot.yaml - dir: output - schema: main.kicad_sch - verbose: 0 + check: + uses: OE5XRX/HW-Module-CI/.github/workflows/kibot-check.yaml@main + secrets: inherit diff --git a/.gitignore b/.gitignore index 069d389..e3af744 100644 --- a/.gitignore +++ b/.gitignore @@ -41,3 +41,6 @@ Thumbs.db # Python cache (if scripts are used) __pycache__/ *.pyc + +# Generated by HW-Module-CI at runtime (must never be committed) +doc/_data/project.yml diff --git a/5V.kicad_sch b/5V.kicad_sch index 72195f3..4ad863d 100644 --- a/5V.kicad_sch +++ b/5V.kicad_sch @@ -1571,7 +1571,7 @@ (uuid "f8f871e5-dc70-4a78-be98-3b301780e1d0") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "L801") (unit 1) @@ -1680,7 +1680,7 @@ (uuid "5811f004-daec-465d-a097-a41dff3e9fca") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "U801") (unit 1) @@ -1774,7 +1774,7 @@ (uuid "d24f22ca-98bb-4d36-b96b-5378a60d66cf") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "C805") (unit 1) @@ -1870,7 +1870,7 @@ (uuid "8f25bf8c-4014-4202-9fd0-ab5be78f6c5f") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "C808") (unit 1) @@ -1936,7 +1936,7 @@ (uuid "bf610729-1809-4f4b-a5af-0ad307d7c660") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "#PWR0802") (unit 1) @@ -2002,7 +2002,7 @@ (uuid "99663a84-ce9f-4e80-8cc7-cf62d2250dcd") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "#PWR0801") (unit 1) @@ -2068,7 +2068,7 @@ (uuid "ca193669-554b-4d26-921a-11bb0483dc6b") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "#PWR0808") (unit 1) @@ -2164,7 +2164,7 @@ (uuid "344f4674-9ae1-4563-9afc-e2097c1fb79d") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "C807") (unit 1) @@ -2261,7 +2261,7 @@ (uuid "26389217-010a-4d94-9e7c-dc26a361ca32") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "R801") (unit 1) @@ -2357,7 +2357,7 @@ (uuid "06d067db-fbcb-4cc0-a03c-11660457f3bd") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "C804") (unit 1) @@ -2453,7 +2453,7 @@ (uuid "b4b8abfc-cfe4-4a4d-a739-070240a0c622") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "C806") (unit 1) @@ -2549,7 +2549,7 @@ (uuid "ed1af738-0df4-48c0-a8ec-b77290170528") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "C803") (unit 1) @@ -2615,7 +2615,7 @@ (uuid "689a0150-1b48-4c28-8fa0-ac198c0d04c0") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "#PWR0809") (unit 1) @@ -2711,7 +2711,7 @@ (uuid "3e1907f9-851f-4f39-9f93-ec570e99e309") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "C802") (unit 1) @@ -2777,7 +2777,7 @@ (uuid "c8b74a34-b778-4625-9926-0653ade83923") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "#PWR0803") (unit 1) @@ -2843,7 +2843,7 @@ (uuid "daa64f72-dfc4-417d-9d77-6ec6a6dde295") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "#PWR0804") (unit 1) @@ -2939,7 +2939,7 @@ (uuid "09b253ba-c55c-4e77-94df-bb9a8132d7db") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "C104") (unit 1) @@ -3036,7 +3036,7 @@ (uuid "438ffd7d-0efd-49df-a5b8-87698ba988f1") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "R803") (unit 1) @@ -3102,7 +3102,7 @@ (uuid "9d8a4f3d-7016-466b-b8d4-f4864f3503f3") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "#PWR0806") (unit 1) @@ -3199,7 +3199,7 @@ (uuid "b62fd113-9e64-4691-9509-99dc034ca215") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "R802") (unit 1) @@ -3265,7 +3265,7 @@ (uuid "c2e6d74c-9b7b-4936-af60-b0aba628a5e7") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "#PWR0807") (unit 1) @@ -3331,7 +3331,7 @@ (uuid "2be55837-df8b-48cf-8732-03625aca6997") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/ae2b220f-194e-4d1c-9897-31c95a90a28f" (reference "#PWR0805") (unit 1) diff --git a/AF_OUT-ADC-filter.kicad_sch b/AF_OUT-ADC-filter.kicad_sch index dfb25b1..2f7bcf0 100644 --- a/AF_OUT-ADC-filter.kicad_sch +++ b/AF_OUT-ADC-filter.kicad_sch @@ -702,7 +702,7 @@ (uuid "82da7912-375a-47f3-8410-5e2682dc584e") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/78e2bba8-f1d6-407f-8788-be9ef4447fa2" (reference "#PWR0402") (unit 1) @@ -790,7 +790,7 @@ (uuid "89f7653d-00c4-4506-9675-91accb865947") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/78e2bba8-f1d6-407f-8788-be9ef4447fa2" (reference "R401") (unit 1) @@ -856,7 +856,7 @@ (uuid "5b48af77-3259-452d-8bef-e207b5f70439") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/78e2bba8-f1d6-407f-8788-be9ef4447fa2" (reference "#PWR0403") (unit 1) @@ -942,7 +942,7 @@ (uuid "9ece77e4-a50a-49ac-a790-13c9f8c69794") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/78e2bba8-f1d6-407f-8788-be9ef4447fa2" (reference "R403") (unit 1) @@ -1039,7 +1039,7 @@ (uuid "507a8d23-9e08-4800-a87a-6e687da9a776") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/78e2bba8-f1d6-407f-8788-be9ef4447fa2" (reference "C402") (unit 1) @@ -1105,7 +1105,7 @@ (uuid "edf72584-2e4d-450b-84ac-2453a1fd62d8") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/78e2bba8-f1d6-407f-8788-be9ef4447fa2" (reference "#PWR0401") (unit 1) @@ -1193,7 +1193,7 @@ (uuid "edb87206-b3d2-402a-8279-4187b7558c0f") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/78e2bba8-f1d6-407f-8788-be9ef4447fa2" (reference "R402") (unit 1) @@ -1288,7 +1288,7 @@ (uuid "ed2397ea-7c4a-4e1d-b965-a093bee5bb14") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/78e2bba8-f1d6-407f-8788-be9ef4447fa2" (reference "C401") (unit 1) diff --git a/DAC-MIC_IN-filter.kicad_sch b/DAC-MIC_IN-filter.kicad_sch index 20ca983..a0e6425 100644 --- a/DAC-MIC_IN-filter.kicad_sch +++ b/DAC-MIC_IN-filter.kicad_sch @@ -652,7 +652,7 @@ (uuid "a43691c0-b7b2-408f-a5f6-2fb3551c1fab") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "R304") (unit 1) @@ -718,7 +718,7 @@ (uuid "ee2cea53-5c53-4214-a86a-304a321786f3") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "#PWR0302") (unit 1) @@ -813,7 +813,7 @@ (uuid "c82c0fbb-3cbd-4e3c-92f4-ec5835cb22ce") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "R303") (unit 1) @@ -910,7 +910,7 @@ (uuid "15eb9f6a-e96c-455d-9668-49bd8d74884b") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "R301") (unit 1) @@ -1007,7 +1007,7 @@ (uuid "84f69737-a6c7-455d-9d8d-acf1e34e974a") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "C303") (unit 1) @@ -1102,7 +1102,7 @@ (uuid "51da5eb6-5d7f-4e6e-b87f-3f1e09717e38") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "C301") (unit 1) @@ -1168,7 +1168,7 @@ (uuid "a35a8354-f9b3-46ef-b006-07efdd72444b") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "#PWR0303") (unit 1) @@ -1265,7 +1265,7 @@ (uuid "47795439-9424-4e0f-8975-470e01092f0f") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "R302") (unit 1) @@ -1362,7 +1362,7 @@ (uuid "bce97f4e-bcce-4156-80ca-8b4b3e2b3242") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "C302") (unit 1) @@ -1428,7 +1428,7 @@ (uuid "eb838493-9127-46b0-9fe8-b3eee2f82fa1") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/be8a26fb-cb42-4581-b87a-069209548956" (reference "#PWR0301") (unit 1) diff --git a/main.kicad_pcb b/FMTransceiver.kicad_pcb similarity index 99% rename from main.kicad_pcb rename to FMTransceiver.kicad_pcb index 272123f..aa21c6d 100644 --- a/main.kicad_pcb +++ b/FMTransceiver.kicad_pcb @@ -1852,7 +1852,7 @@ (property ki_fp_filters "MountingHole*") (path "/df999db3-7087-40c6-8ce3-d5dd370ce265") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr exclude_from_pos_files exclude_from_bom) (fp_circle (center 0 0) @@ -2877,7 +2877,7 @@ (property ki_fp_filters "*BNC* *SMA* *SMB* *SMC* *Cinch* *LEMO* *UMRF* *MCX* *U.FL*") (path "/cb47309f-4f17-4d44-aeb2-96b6cbad6b4f") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr smd) (fp_line (start -3.21 0) @@ -6054,7 +6054,7 @@ (property ki_fp_filters "C_*") (path "/e75eeb7d-593d-4306-9c4c-d944a66a4f8d") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr smd) (fp_line (start -0.261252 -0.735) @@ -10803,7 +10803,7 @@ (property ki_fp_filters "R_*") (path "/aa40c029-3644-458e-b3cb-532aa386a1fb") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr smd) (fp_line (start -0.227064 0.735) @@ -11907,7 +11907,7 @@ (property ki_fp_filters "D*SOD?123*") (path "/e56e2a97-71b1-4d21-b73a-f7f360f5b1ef") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr smd) (fp_line (start -2.36 -1) @@ -12229,7 +12229,7 @@ (property ki_fp_filters "C_*") (path "/5c7f5efb-e956-43e3-8aa5-4e6b4a7005c6") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr smd) (fp_line (start -0.261252 -0.735) @@ -13457,7 +13457,7 @@ ) (path "/84ab70c1-2e7e-4b2c-8410-1f1129fb1cdc") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr smd) (fp_line (start -17.8 -9.5) @@ -15061,7 +15061,7 @@ (property ki_fp_filters "Filter*Mini?Circuits*FV1206*") (path "/41ba872f-62e1-4497-9c28-d238c56e3486") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr smd) (fp_line (start 0.57 0.91) @@ -16227,7 +16227,7 @@ (property ki_fp_filters "MountingHole*") (path "/232daf58-0468-4f94-bfdb-9142284c844d") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr exclude_from_pos_files exclude_from_bom) (fp_circle (center 0 0) @@ -16900,7 +16900,7 @@ (property ki_fp_filters "C_*") (path "/fc6cd225-5757-46cd-8c67-0beb9cd542fe") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr smd) (fp_line (start -0.261252 -0.735) @@ -17797,7 +17797,7 @@ (property ki_fp_filters "R_*") (path "/1a1b846a-0258-4ade-ba0a-5a86240af8a3") (sheetname "/") - (sheetfile "main.kicad_sch") + (sheetfile "FMTransceiver.kicad_sch") (attr smd) (fp_line (start -0.227064 0.735) diff --git a/main.kicad_pro b/FMTransceiver.kicad_pro similarity index 99% rename from main.kicad_pro rename to FMTransceiver.kicad_pro index 633505e..fe13b48 100644 --- a/main.kicad_pro +++ b/FMTransceiver.kicad_pro @@ -489,7 +489,7 @@ "pinned_symbol_libs": [] }, "meta": { - "filename": "main.kicad_pro", + "filename": "FMTransceiver.kicad_pro", "version": 3 }, "net_settings": { diff --git a/main.kicad_sch b/FMTransceiver.kicad_sch similarity index 98% rename from main.kicad_sch rename to FMTransceiver.kicad_sch index c75533f..0c6ed51 100644 --- a/main.kicad_sch +++ b/FMTransceiver.kicad_sch @@ -2553,7 +2553,7 @@ (uuid "84bbb5a8-9ffe-4339-94b8-ccc481fec90a") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0105") (unit 1) @@ -2619,7 +2619,7 @@ (uuid "9a4d8591-b88b-417f-b36b-605b6a547650") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0120") (unit 1) @@ -2714,7 +2714,7 @@ (uuid "3b747517-3bf5-41d0-86a6-749d21e7464c") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "R101") (unit 1) @@ -2804,7 +2804,7 @@ ) ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "H101") (unit 1) @@ -2870,7 +2870,7 @@ (uuid "c85f4ce8-e8e9-4b4b-ba04-7d166d65cc7f") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0118") (unit 1) @@ -2971,7 +2971,7 @@ (uuid "3b811160-3afd-45c7-942a-574cc5b5b6ed") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "FL101") (unit 1) @@ -3037,7 +3037,7 @@ (uuid "e7d27ecb-98a4-4da1-9461-7741b832bdf4") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#FLG0103") (unit 1) @@ -3134,7 +3134,7 @@ (uuid "27b94d5a-e0f4-493b-a22d-9640fb1750b8") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "C103") (unit 1) @@ -3209,7 +3209,7 @@ (uuid "5b57a963-1d10-4c4c-a7af-353b525ba12e") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0103") (unit 1) @@ -3284,7 +3284,7 @@ (uuid "dce40637-3def-447b-876a-a3939932b8ac") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0101") (unit 1) @@ -3403,7 +3403,7 @@ (uuid "bba62b64-9f6a-465a-9dc4-b69e5d9b654b") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "U101") (unit 1) @@ -3478,7 +3478,7 @@ (uuid "8dbae92e-68b8-4631-a454-7c6de274fdb5") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0104") (unit 1) @@ -3544,7 +3544,7 @@ (uuid "fa00756f-7da9-40cd-93bf-a31b1a31fe0b") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0102") (unit 1) @@ -3610,7 +3610,7 @@ (uuid "9719ef8b-2621-4ebe-8caf-a308324e15df") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0119") (unit 1) @@ -3705,7 +3705,7 @@ (uuid "935c60cc-5e6b-416f-be6c-0e0a43209921") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "R102") (unit 1) @@ -3771,7 +3771,7 @@ (uuid "82016e90-e91e-41b8-bcd5-51c947fb530a") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0116") (unit 1) @@ -3837,7 +3837,7 @@ (uuid "943b8bf5-30ea-433e-b8d2-ec77868593cf") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0115") (unit 1) @@ -3925,7 +3925,7 @@ (uuid "27e40e55-09cd-4652-813a-57253b9024b4") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "J101") (unit 1) @@ -4015,7 +4015,7 @@ ) ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "H102") (unit 1) @@ -4101,7 +4101,7 @@ (uuid "f485b5a0-5a78-4612-9c9b-3b34d23c4952") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "D101") (unit 1) @@ -4189,7 +4189,7 @@ (uuid "66fe1331-a899-4cac-89fa-cdc247dbfb0a") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "C101") (unit 1) @@ -4286,7 +4286,7 @@ (uuid "0eaa388c-f6be-4d6a-8686-bc38978249d3") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "C102") (unit 1) @@ -4352,7 +4352,7 @@ (uuid "6f2bee3e-5c1d-46b2-9427-35cd73f2dd2e") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (reference "#PWR0113") (unit 1) @@ -4415,7 +4415,7 @@ ) ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (page "5") ) @@ -4477,7 +4477,7 @@ ) ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (page "4") ) @@ -4559,7 +4559,7 @@ ) ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (page "7") ) @@ -4621,7 +4621,7 @@ ) ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (page "8") ) @@ -4683,7 +4683,7 @@ ) ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (page "3") ) @@ -4825,7 +4825,7 @@ ) ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639" (page "2") ) diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 2f7fc6b..0000000 --- a/Gemfile +++ /dev/null @@ -1,8 +0,0 @@ -source 'https://rubygems.org' - -gem "jekyll", "4.4.1" - -group :jekyll_plugins do - gem "just-the-docs", "0.10.1" - gem "jekyll-git-hash" -end diff --git a/connector_cpu.kicad_sch b/connector_cpu.kicad_sch index c0cd93a..ceb11a3 100644 --- a/connector_cpu.kicad_sch +++ b/connector_cpu.kicad_sch @@ -6697,7 +6697,7 @@ (uuid "c97783cb-339f-4c53-a751-75b3b16c9359") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0202") (unit 1) @@ -6793,7 +6793,7 @@ (uuid "153e7dad-7b8b-4a5f-8548-82091132b37b") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C213") (unit 1) @@ -6890,7 +6890,7 @@ (uuid "76591c0a-d305-4347-a6ed-554dbe99a0a7") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C216") (unit 1) @@ -6987,7 +6987,7 @@ (uuid "2d9eef76-25eb-431c-861c-44f2b8027f67") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C211") (unit 1) @@ -7053,7 +7053,7 @@ (uuid "2838f2c9-5918-4d44-acd7-8d823d0df84e") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0207") (unit 1) @@ -7119,7 +7119,7 @@ (uuid "8dd05ef4-191c-4d8b-bebc-146fc4f93a66") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0228") (unit 1) @@ -7194,7 +7194,7 @@ (uuid "f6b69d75-cb23-40e2-9eec-9795353de383") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0230") (unit 1) @@ -7291,7 +7291,7 @@ (uuid "c8f9268b-6e1f-4629-9493-58885450d262") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "R202") (unit 1) @@ -7379,7 +7379,7 @@ (uuid "075fed7a-b993-4481-9a20-09397331b0e7") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C208") (unit 1) @@ -7445,7 +7445,7 @@ (uuid "7161855f-b866-4ea9-8ad2-6c3fd49abf0a") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0206") (unit 1) @@ -7520,7 +7520,7 @@ (uuid "692a8717-b447-441b-8dc9-aa87ba05ecd6") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0201") (unit 1) @@ -7586,7 +7586,7 @@ (uuid "a23cbef9-5335-4b98-80b9-26d28788e0b9") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0219") (unit 1) @@ -7652,7 +7652,7 @@ (uuid "9480deb7-271e-403c-afd3-4e549b055655") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0211") (unit 1) @@ -7767,7 +7767,7 @@ (uuid "30b7f6d3-37ba-41d5-83cf-d42f88d14bc0") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "U202") (unit 1) @@ -7833,7 +7833,7 @@ (uuid "ddd6b815-7ad0-4a52-9296-73b8129975e1") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0227") (unit 1) @@ -7899,7 +7899,7 @@ (uuid "cbcd0deb-4792-4065-b384-4e62b6669319") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0234") (unit 1) @@ -7965,7 +7965,7 @@ (uuid "50694311-92ff-4564-b71e-c86339296df6") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0225") (unit 1) @@ -8040,7 +8040,7 @@ (uuid "a53820d9-c082-4e0c-ae6a-63354cc7d112") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0223") (unit 1) @@ -8134,7 +8134,7 @@ (uuid "cb3da6ca-8f6b-4b35-994e-4cef80520c1c") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "J201") (unit 1) @@ -8231,7 +8231,7 @@ (uuid "10c34cb9-74da-41f6-afc5-7322a0a73b75") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C212") (unit 1) @@ -8458,7 +8458,7 @@ (uuid "dfa4ea0c-9bd9-4a80-87ae-b994b475213b") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "U201") (unit 1) @@ -8546,7 +8546,7 @@ (uuid "0f7a3022-ea7f-405e-9f1f-218ab217ed66") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C202") (unit 1) @@ -8621,7 +8621,7 @@ (uuid "eca733a7-3ee5-4b77-a876-88db6ce4afa6") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0203") (unit 1) @@ -8716,7 +8716,7 @@ (uuid "4b02014c-83d0-4ff3-b429-5a00a4d4a972") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C215") (unit 1) @@ -8782,7 +8782,7 @@ (uuid "a965c3a6-6b80-44ba-a5a8-27550734d8c5") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0216") (unit 1) @@ -8848,7 +8848,7 @@ (uuid "8352b88d-966d-43f1-8d81-de0c473c37be") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0209") (unit 1) @@ -8923,7 +8923,7 @@ (uuid "544a9cb4-261d-4eaf-b252-e0234d287a35") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0221") (unit 1) @@ -9000,7 +9000,7 @@ (uuid "2150270a-606a-433c-91bf-ad887e2c424c") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "JP201") (unit 1) @@ -9088,7 +9088,7 @@ (uuid "70b94a8a-f316-48fa-b132-41268247a3d2") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C206") (unit 1) @@ -9176,7 +9176,7 @@ (uuid "1938cbc9-7a04-48fb-9d71-aeb2ae35991b") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "R201") (unit 1) @@ -9264,7 +9264,7 @@ (uuid "be838230-ad5d-44c0-8872-c8b19e602b66") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C201") (unit 1) @@ -9358,7 +9358,7 @@ (uuid "f14b7564-3f84-4507-87f1-b6c33b65d7e7") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "Y201") (unit 1) @@ -9424,7 +9424,7 @@ (uuid "4ce42835-6924-446b-8b5f-583227b94be4") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0237") (unit 1) @@ -9521,7 +9521,7 @@ (uuid "3365ec39-1899-4ea2-9de6-7d7b831a93b2") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C203") (unit 1) @@ -9587,7 +9587,7 @@ (uuid "4830a379-03db-4d3e-926e-d89d78c93cdf") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0214") (unit 1) @@ -9736,7 +9736,7 @@ (uuid "979a2c40-5bdb-4756-a3d2-8917bf9535f7") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "J202") (unit 1) @@ -9802,7 +9802,7 @@ (uuid "5443fbe0-6a9d-4d1c-b611-6bc6b4d9d375") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0239") (unit 1) @@ -9868,7 +9868,7 @@ (uuid "53719941-a15e-453d-a648-3301b1a6d661") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0215") (unit 1) @@ -9965,7 +9965,7 @@ (uuid "84b43af7-d9db-45d2-a51f-7d00ef931aea") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C207") (unit 1) @@ -10053,7 +10053,7 @@ (uuid "3c747ebb-ed3c-4476-8075-9f46418927e6") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C214") (unit 1) @@ -10119,7 +10119,7 @@ (uuid "9d6c50a8-d338-427f-b923-477437d03f12") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#FLG0202") (unit 1) @@ -10185,7 +10185,7 @@ (uuid "b1258751-19a4-42ef-b83a-69958ce8d5e0") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0232") (unit 1) @@ -10290,7 +10290,7 @@ (uuid "6665bd3f-2ce1-4eef-9299-715f606f5a9f") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "U203") (unit 1) @@ -10356,7 +10356,7 @@ (uuid "b405a132-c34f-425f-b92d-7ef3a0a33ca0") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0236") (unit 1) @@ -10422,7 +10422,7 @@ (uuid "f6cb7842-6cc2-459d-b232-6361896a8b5d") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0224") (unit 1) @@ -10487,7 +10487,7 @@ (uuid "2d0cc835-9286-4133-b8aa-007a8e9b94b3") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0240") (unit 1) @@ -10553,7 +10553,7 @@ (uuid "c21544e8-f089-4a30-ba6c-dc3bc832ef43") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0226") (unit 1) @@ -10663,7 +10663,7 @@ (uuid "49f465fd-0c89-448c-b6e6-11d5cb49f1dc") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "U204") (unit 1) @@ -10738,7 +10738,7 @@ (uuid "edd260b0-ea75-4449-96fc-82784d33287e") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0233") (unit 1) @@ -10804,7 +10804,7 @@ (uuid "444a25ed-32d3-4bae-b27a-de34df620eda") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0208") (unit 1) @@ -10901,7 +10901,7 @@ (uuid "ecd89e12-88d5-4d85-9990-dfb589c7df44") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C209") (unit 1) @@ -10998,7 +10998,7 @@ (uuid "243066ad-3721-46a1-aee1-1c80df9b6a8f") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C205") (unit 1) @@ -11086,7 +11086,7 @@ (uuid "59f47b16-3b38-4605-8418-097ee9b12622") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C210") (unit 1) @@ -11152,7 +11152,7 @@ (uuid "a71dd889-32b0-4822-8fe5-00c5476044e5") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0217") (unit 1) @@ -11218,7 +11218,7 @@ (uuid "0bc2edf3-aa01-45ad-a80d-526c41678325") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0220") (unit 1) @@ -11284,7 +11284,7 @@ (uuid "5378f3da-332d-4487-bf1b-128c4131522c") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0231") (unit 1) @@ -11350,7 +11350,7 @@ (uuid "8c9872fc-7456-4929-a4e5-fc778c37efee") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0229") (unit 1) @@ -11416,7 +11416,7 @@ (uuid "f55a20e0-7365-4606-8438-73c4ebc1ab1a") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0218") (unit 1) @@ -11482,7 +11482,7 @@ (uuid "0aa839a5-f953-40cb-96ac-55addabdf358") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0213") (unit 1) @@ -11548,7 +11548,7 @@ (uuid "3085c96b-8eb3-46bb-a264-9f12a27cd0c0") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0204") (unit 1) @@ -11614,7 +11614,7 @@ (uuid "714f828d-8e48-4fac-b54a-5015aa36e4d2") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0210") (unit 1) @@ -11680,7 +11680,7 @@ (uuid "18d7b30a-1c31-4128-ad42-74f8de4eb534") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0205") (unit 1) @@ -11746,7 +11746,7 @@ (uuid "9973bb09-2185-48a2-a89b-d8cf83884a48") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0212") (unit 1) @@ -11812,7 +11812,7 @@ (uuid "4a1aaa3b-aa1f-45b8-a96b-41f68f2df3df") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#FLG0201") (unit 1) @@ -11878,7 +11878,7 @@ (uuid "cd3c64de-cefb-48f7-8f08-9ff035b7552b") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0222") (unit 1) @@ -11943,7 +11943,7 @@ (uuid "5fb1f4e8-bde6-4eab-a986-bd74be24f41f") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0238") (unit 1) @@ -12009,7 +12009,7 @@ (uuid "a34fc511-2c1d-4109-b387-8b741d9dce59") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0241") (unit 1) @@ -12097,7 +12097,7 @@ (uuid "541e780b-efe7-4200-ac7b-be3c85c8bdd0") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "C204") (unit 1) @@ -12163,7 +12163,7 @@ (uuid "985f86ff-44e3-4f7f-810b-bae4c938301c") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#FLG0203") (unit 1) @@ -12229,7 +12229,7 @@ (uuid "75549e36-83bc-4462-a836-7b9cd1a5e001") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/d648e681-e38c-420a-82e8-80657e125ef8" (reference "#PWR0235") (unit 1) diff --git a/doc/Icon.png b/doc/Icon.png deleted file mode 100644 index f0d52f2..0000000 Binary files a/doc/Icon.png and /dev/null differ diff --git a/doc/_config.yml b/doc/_config.yml deleted file mode 100644 index 56c5d99..0000000 --- a/doc/_config.yml +++ /dev/null @@ -1,16 +0,0 @@ -title: OE5XRX -description: Amateurfunkclub fΓΌr Remote Stationen - -theme: just-the-docs -favicon_ico: /favicon.ico -logo: "/Icon.png" - -mermaid: - version: "11.3.0" - -defaults: - - - scope: - path: "" - values: - layout: "default" diff --git a/doc/favicon.ico b/doc/favicon.ico deleted file mode 100644 index ba65c61..0000000 Binary files a/doc/favicon.ico and /dev/null differ diff --git a/doc/index.md b/doc/index.md index d4d6a0e..e4ac573 100644 --- a/doc/index.md +++ b/doc/index.md @@ -9,8 +9,8 @@ parent: Hardware - - + +
TopBottom
topbottomtopbottom
@@ -23,9 +23,9 @@ Das `FM` Modul beherbergt einen FM Chip (SA818x) fΓΌr das 2m oder 70cm Band. ## Daten -- [Schaltplan](main-schematic.pdf) -- [BOM](main-bom.html) -- [iBOM](main-ibom.html) -- [JLCPCB fabrication & stencil](JLCPCB/main-_JLCPCB_compress.zip) -- [JLCPCB Bom](JLCPCB/main_bom_jlc.csv) -- [JLCPCB Pick&Place](JLCPCB/main_cpl_jlc.csv) +- [Schaltplan]({{ site.data.project.name }}-schematic.pdf) +- [BOM]({{ site.data.project.name }}-bom.html) +- [iBOM]({{ site.data.project.name }}-ibom.html) +- [JLCPCB fabrication & stencil](JLCPCB/{{ site.data.project.name }}-_JLCPCB_compress.zip) +- [JLCPCB Bom](JLCPCB/{{ site.data.project.name }}_bom_jlc.csv) +- [JLCPCB Pick&Place](JLCPCB/{{ site.data.project.name }}_cpl_jlc.csv) diff --git a/pin_driver.kicad_sch b/pin_driver.kicad_sch index d62d107..241642d 100644 --- a/pin_driver.kicad_sch +++ b/pin_driver.kicad_sch @@ -684,7 +684,7 @@ (uuid "f56af2b2-698d-4731-873b-eb94426d31e2") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/0ab8e43f-a9b2-43d4-be36-1f1ef25ab18a" (reference "R501") (unit 1) @@ -750,7 +750,7 @@ (uuid "e65dbc7c-b5df-4cef-89f6-aedbfc5730b5") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/0ab8e43f-a9b2-43d4-be36-1f1ef25ab18a" (reference "#PWR0501") (unit 1) @@ -853,7 +853,7 @@ (uuid "271c0986-37d8-4e4e-899c-924c5ebe7232") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/0ab8e43f-a9b2-43d4-be36-1f1ef25ab18a" (reference "Q501") (unit 1) diff --git a/pin_driver_npn.kicad_sch b/pin_driver_npn.kicad_sch index 79d8da9..306e05b 100644 --- a/pin_driver_npn.kicad_sch +++ b/pin_driver_npn.kicad_sch @@ -984,7 +984,7 @@ (uuid "63dfc0dd-5f3d-45ef-b154-5f2626126392") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "R704") (unit 1) @@ -1079,7 +1079,7 @@ (uuid "9dcce799-e0cc-4a14-96b4-d2a9928dd351") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "R701") (unit 1) @@ -1174,7 +1174,7 @@ (uuid "2541bf32-ff5a-458a-86f8-3a84786f0fb8") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "R702") (unit 1) @@ -1240,7 +1240,7 @@ (uuid "b4463f32-d858-41e6-bf94-b8deabf290ba") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "#PWR0701") (unit 1) @@ -1337,7 +1337,7 @@ (uuid "03f64b6d-cfb9-455b-a5f4-6220df340d7d") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "R705") (unit 1) @@ -1403,7 +1403,7 @@ (uuid "451b5c1d-0373-4b44-a5f6-cf5670f02aeb") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "#PWR0706") (unit 1) @@ -1469,7 +1469,7 @@ (uuid "de5e0945-5b2c-41e6-a7b4-02d1267713d6") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "#PWR0702") (unit 1) @@ -1535,7 +1535,7 @@ (uuid "e1828168-f90f-4396-b169-0e8f96c94e03") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "#PWR0704") (unit 1) @@ -1601,7 +1601,7 @@ (uuid "b7c5f500-dd95-453e-afed-f10bbc1024cd") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "#PWR0703") (unit 1) @@ -1698,7 +1698,7 @@ (uuid "9f64fe18-ab63-449f-84a5-a4e9364be53f") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "R703") (unit 1) @@ -1764,7 +1764,7 @@ (uuid "cf3ac44e-0234-42c6-b479-ba72b8b59b24") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "#PWR0705") (unit 1) @@ -1864,7 +1864,7 @@ (uuid "fe1f2e7b-38fb-468c-8c46-6cfc13d1a522") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "Q701") (unit 2) @@ -1964,7 +1964,7 @@ (uuid "fe1f2e7b-38fb-468c-8c46-6cfc13d1a523") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "Q701") (unit 1) @@ -2061,7 +2061,7 @@ (uuid "6790e27f-b539-4563-b1b7-882eb041c2af") ) (instances - (project "main" + (project "FMTransceiver" (path "/f228839c-6576-40b2-ae70-679b7b460639/955bc80c-8e97-4c74-b833-12570cf5294a" (reference "R706") (unit 1) diff --git a/production.kibot.yaml b/production.kibot.yaml deleted file mode 100644 index 797b705..0000000 --- a/production.kibot.yaml +++ /dev/null @@ -1,110 +0,0 @@ -kibot: - version: 1 - -globals: - resources_dir: Fabrication - field_lcsc_part: LCSC - -import: - - file: JLCPCB_stencil - -outputs: - - name: ibom - comment: 'Interactive Bill of Materials' - type: ibom - - - name: bom - comment: 'Bill of Materials' - type: bom - options: - csv: - hide_pcb_info: true - hide_stats_info: true - format: CSV - - - name: html_bom - comment: 'HTML BOM' - type: bom - options: - html: - title: 'Bill of Materials' - lcsc_link: 'LCSC' - mouser_link: 'MOUSER' - logo: 'doc/Icon.png' - - - name: SchematicPDF - comment: "Print schematic PDF" - type: pdf_sch_print - options: - color_theme: dracula - background_color: true - - - name: step_file - comment: 'STEP file generation' - type: step - - - name: KiCad_3D_top - comment: "render 3D top" - type: render_3d - options: - view: 'top' - orthographic: true - ray_tracing: true - auto_crop: true - enable_crop_workaround: true - transparent_background: true - - - name: KiCad_3D_bottom - comment: "render 3D bottom" - type: render_3d - options: - view: 'bottom' - orthographic: true - ray_tracing: true - auto_crop: true - enable_crop_workaround: true - transparent_background: true - - - name: KiCad_3D_top_with - comment: "render 3D top with" - type: render_3d - options: - output: '%f-%i%I%v-with.%x' - view: 'top' - orthographic: true - ray_tracing: true - auto_crop: true - enable_crop_workaround: true - transparent_background: true - show_components: 'all' - zoom: -2 - move_y: -1 - - - name: KiCad_3D_top_without - comment: "render 3D top without" - type: render_3d - options: - output: '%f-%i%I%v-without.%x' - view: 'top' - orthographic: true - ray_tracing: true - auto_crop: true - enable_crop_workaround: true - transparent_background: true - show_components: 'none' - - - name: stencil_top - comment: "SMT stencil top paste layer render (post-processed to PNG)" - type: pcb_print - options: - output: '%f-stencil_top.%x' - format: SVG - plot_sheet_reference: false - pages: - - layers: - - layer: F.Paste - color: '#000000' - - layer: F.Silkscreen - color: '#0055AA' - - layer: Edge.Cuts - color: '#FFB300' diff --git a/scripts/bom_export.py b/scripts/bom_export.py deleted file mode 100644 index 3fd15e3..0000000 --- a/scripts/bom_export.py +++ /dev/null @@ -1,243 +0,0 @@ -#!/usr/bin/env python3 -""" -bom_export.py – Export a KiCad BOM CSV to InvenTree. - -Creates a PCB part and an assembly part in InvenTree, then populates the BOM -with all components from the CSV. Any parts not yet present in InvenTree are -created automatically (via LCSC / Mouser) before the BOM is assembled. - -Required environment variables: - INVENTREE_API_HOST – InvenTree server URL - INVENTREE_API_TOKEN – API token (or use USERNAME + PASSWORD instead) - MOUSER_API_KEY – Mouser API v2 key -""" - -import argparse -import csv -import logging -import sys - -from inventree.api import InvenTreeAPI -from inventree.company import SupplierPart -from inventree.part import BomItem, Part, PartCategory, PartRelated - -from inventree_sync import BomEntry, ensure_parts_exist -from inventree_sync.categories import load_category_map - -logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s") -log = logging.getLogger(__name__) - -# Human-readable category names used for PCB and assembly parts. -PCB_CATEGORY_NAME = "Printed-Circuit Boards" -ASSEMBLY_CATEGORY_NAME = "PCBA" -STENCIL_CATEGORY_NAME = "SMT Stencil" - - -# --------------------------------------------------------------------------- -# Category lookup -# --------------------------------------------------------------------------- - -def get_category_by_name(api: InvenTreeAPI, name: str) -> PartCategory: - """Return the PartCategory with the given name, or abort if not found.""" - matches = [c for c in PartCategory.list(api) if c.name == name] - if not matches: - log.error("InvenTree category %r not found. Create it first.", name) - sys.exit(1) - return matches[0] - - -# --------------------------------------------------------------------------- -# CSV loading -# --------------------------------------------------------------------------- - -def load_bom(csv_path: str) -> list[BomEntry]: - """Parse the KiCad BOM CSV and return a list of BomEntry objects.""" - entries: list[BomEntry] = [] - with open(csv_path, newline="") as fh: - for row in csv.DictReader(fh): - entries.append(BomEntry( - reference=row["References"], - qty=int(row["Quantity Per PCB"]), - kicad_part=row["Part"].strip(), - kicad_value=row["Value"].strip(), - kicad_footprint=row["Footprint"].strip(), - lcsc=row["LCSC"].split(",") if row["LCSC"].strip() else [], - mouser=row["MOUSER"].split(",") if row["MOUSER"].strip() else [], - )) - log.info("Loaded %d BOM entries from %s", len(entries), csv_path) - return entries - - -# --------------------------------------------------------------------------- -# Part matching -# --------------------------------------------------------------------------- - -def match_supplier_parts(api: InvenTreeAPI, entries: list[BomEntry]) -> None: - """ - Match each BomEntry to its InvenTree Part via SupplierPart SKU lookup. - Populates entry.inventree_part for every entry that has a supplier SKU. - """ - all_supplier_parts = SupplierPart.list(api) - sku_to_part: dict[str, Part] = { - sp.SKU: Part(api, pk=sp.part) for sp in all_supplier_parts - } - - for entry in entries: - if entry.inventree_part: - continue # already resolved by ensure_parts_exist - for sku in entry.lcsc + entry.mouser: - if part := sku_to_part.get(sku): - entry.inventree_part.append(part) - break - - missing = [e for e in entries if not e.inventree_part and (e.lcsc or e.mouser)] - if missing: - for entry in missing: - log.error("No InvenTree part found for %s (LCSC=%s, Mouser=%s)", - entry.reference, entry.lcsc, entry.mouser) - sys.exit(1) - - -# --------------------------------------------------------------------------- -# PCB + assembly + stencil creation -# --------------------------------------------------------------------------- - -def create_pcb_part(api: InvenTreeAPI, category: PartCategory, name: str, version: str, image: str) -> Part: - part = Part.create(api, { - "category": category.pk, - "name": f"{name} PCB", - "revision": version, - "component": True, - }) - assert part.uploadImage(image) is not None, f"Image upload failed: {image}" - log.info("Created PCB part '%s PCB' (pk=%s)", name, part.pk) - return part - - -def create_assembly_part(api: InvenTreeAPI, category: PartCategory, name: str, version: str, image: str) -> Part: - part = Part.create(api, { - "category": category.pk, - "name": f"{name} Module", - "revision": version, - "component": True, - "assembly": True, - "trackable": True, - }) - assert part.uploadImage(image) is not None, f"Image upload failed: {image}" - log.info("Created assembly part '%s Module' (pk=%s)", name, part.pk) - return part - - -def create_stencil_part( - api: InvenTreeAPI, - category: PartCategory, - name: str, - version: str, - image: str | None = None, -) -> Part: - part = Part.create(api, { - "category": category.pk, - "name": f"{name} SMT Stencil", - "revision": version, - "component": True, - }) - if image: - assert part.uploadImage(image) is not None, f"Image upload failed: {image}" - log.info("Created stencil part '%s SMT Stencil' (pk=%s)", name, part.pk) - return part - - -# --------------------------------------------------------------------------- -# BOM population -# --------------------------------------------------------------------------- - -def populate_bom( - api: InvenTreeAPI, - assembly: Part, - pcb: Part, - entries: list[BomEntry], -) -> None: - """Create BomItems on *assembly*: one for the PCB, one per BomEntry.""" - BomItem.create(api, { - "part": assembly.pk, - "sub_part": pcb.pk, - "reference": "", - "quantity": 1, - }) - - for entry in entries: - for inv_part in entry.inventree_part: - BomItem.create(api, { - "part": assembly.pk, - "sub_part": inv_part.pk, - "reference": entry.reference, - "quantity": entry.qty, - }) - - log.info("BOM populated with %d unique components", len(entries)) - - -# --------------------------------------------------------------------------- -# Entry point -# --------------------------------------------------------------------------- - -def parse_args() -> argparse.Namespace: - parser = argparse.ArgumentParser( - description="Export a KiCad BOM CSV to an InvenTree assembly BOM." - ) - parser.add_argument("--csv_file", required=True, help="Path to the KiCad BOM CSV") - parser.add_argument("--name", required=True, help="Module name (e.g. HW-Module-FMTransceiver)") - parser.add_argument("--version", required=True, help="Revision string (e.g. 0.99)") - parser.add_argument("--pcb_image", required=True, help="PCB render image") - parser.add_argument("--assembly_image", required=True, help="Assembly render image") - parser.add_argument("--stencil_image", required=False, help="Stencil paste-layer render (optional)") - parser.add_argument( - "--categories", - required=False, - metavar="YAML_FILE", - help=( - "Path to a YAML file mapping KiCad symbol names to InvenTree " - "category hierarchies. Defaults to the built-in " - "default_categories.yaml shipped with the package." - ), - ) - return parser.parse_args() - - -def main() -> None: - args = parse_args() - - # Connection settings are read from environment variables by InvenTreeAPI: - # INVENTREE_API_HOST + INVENTREE_API_TOKEN - # (or INVENTREE_API_USERNAME / INVENTREE_API_PASSWORD) - api = InvenTreeAPI() - - entries = load_bom(args.csv_file) - - # Load category map (custom file or built-in default) - category_map = load_category_map(args.categories) - - # Create any parts that don't exist in InvenTree yet - ensure_parts_exist(api, entries, category_map) - - # Match every BOM entry to its InvenTree part via supplier SKU - match_supplier_parts(api, entries) - - pcb_cat = get_category_by_name(api, PCB_CATEGORY_NAME) - assembly_cat = get_category_by_name(api, ASSEMBLY_CATEGORY_NAME) - stencil_cat = get_category_by_name(api, STENCIL_CATEGORY_NAME) - - pcb = create_pcb_part(api, pcb_cat, args.name, args.version, args.pcb_image) - assembly = create_assembly_part(api, assembly_cat, args.name, args.version, args.assembly_image) - stencil = create_stencil_part(api, stencil_cat, args.name, args.version, args.stencil_image) - - # Link stencil ↔ PCB as related parts (not BOM – the stencil is a - # production tool, not a consumed component of the assembly). - PartRelated.add_related(api, pcb, stencil) - log.info("Linked stencil to PCB as related part") - - populate_bom(api, assembly, pcb, entries) - - -if __name__ == "__main__": - main() diff --git a/scripts/inventree_sync/__init__.py b/scripts/inventree_sync/__init__.py deleted file mode 100644 index cb5780e..0000000 --- a/scripts/inventree_sync/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -inventree_sync – Python package for syncing KiCad BOM data to InvenTree. - -Public API ----------- -ensure_parts_exist – create missing InvenTree parts from a BOM -load_category_map – load a KiCadβ†’InvenTree category map from a YAML file -BomEntry – dataclass for one KiCad BOM row -PartData – dataclass for raw supplier part data -""" - -from .categories import load_category_map -from .models import BomEntry, PartData -from .part_manager import ensure_parts_exist - -__all__ = [ - "BomEntry", - "load_category_map", - "PartData", - "ensure_parts_exist", -] diff --git a/scripts/inventree_sync/categories.py b/scripts/inventree_sync/categories.py deleted file mode 100644 index e4a2ead..0000000 --- a/scripts/inventree_sync/categories.py +++ /dev/null @@ -1,179 +0,0 @@ -""" -categories.py – KiCad β†’ InvenTree category mapping and part-name generation. -""" - -import logging -import re -from pathlib import Path -from typing import Optional - -import yaml -from inventree.api import InvenTreeAPI -from inventree.part import PartCategory - -from .models import PartData - -logger = logging.getLogger(__name__) - -# Path to the built-in category map shipped with the package. -_DEFAULT_CATEGORIES_FILE = Path(__file__).parent / "default_categories.yaml" - -# KiCad symbol names that receive an automatic package-level sub-category. -_PACKAGE_SUBCATEGORY_CAPS = frozenset({"C", "C_Small"}) -_PACKAGE_SUBCATEGORY_RESISTORS = frozenset({"R", "R_Small", "R_Network", "RN"}) - - -# --------------------------------------------------------------------------- -# Category map loading -# --------------------------------------------------------------------------- - -def load_category_map(path: Optional[str] = None) -> dict[str, tuple[str, ...]]: - """Load a KiCad symbol β†’ InvenTree category map from a YAML file. - - Each YAML key is a KiCad symbol name; its value must be a list of strings - that form the InvenTree category hierarchy (top-level β†’ sub-category). - - If *path* is None the built-in ``default_categories.yaml`` is used. - - Example YAML entry:: - - R: [Resistors, Surface Mount] - Crystal: [Crystals and Oscillators, Crystals] - - Raises ``SystemExit`` with a descriptive message when the file cannot be - read or contains an invalid entry. - """ - file_path = Path(path) if path else _DEFAULT_CATEGORIES_FILE - try: - with open(file_path) as fh: - raw = yaml.safe_load(fh) or {} - except FileNotFoundError: - logger.error("Category map file not found: %s", file_path) - raise SystemExit(f"ERROR: category map file not found: {file_path}") - except yaml.YAMLError as exc: - logger.error("Failed to parse category map %s: %s", file_path, exc) - raise SystemExit(f"ERROR: failed to parse YAML in {file_path}: {exc}") - - result: dict[str, tuple[str, ...]] = {} - for key, value in raw.items(): - if not isinstance(value, list) or not all(isinstance(s, str) for s in value): - raise SystemExit( - f"ERROR: invalid entry in {file_path}: key '{key}' must map to " - f"a list of strings, got {type(value).__name__!r}" - ) - result[str(key)] = tuple(value) - return result - - -def extract_package(footprint: str) -> str: - """ - Extract a short package code from a KiCad footprint string. - 'C_0805_2012Metric' β†’ '0805', 'SOT-23' β†’ 'SOT-23', 'SOIC-8_3.9x...' β†’ 'SOIC-8' - """ - m = re.match(r"(?:C|R|L)_(\w+?)_", footprint) - if m: - return m.group(1) - return footprint.split("_")[0] - - -def generate_part_name(kicad_part: str, kicad_value: str, footprint: str) -> str: - """ - Generate a human-readable InvenTree part name from KiCad fields. - - Examples: - R, '10k', 'R_0805_2012Metric' β†’ 'R 10k 0805' - C, '100nF', 'C_0805_2012Metric' β†’ 'C 100nF 0805' - C_Polarized, '100u / 25V', ... β†’ 'CP 100u/25V' - Crystal, '8MHz / 20pF', ... β†’ 'XTAL 8MHz/20pF' - STM32U575CITx, 'STM32U575CITx', ... β†’ 'STM32U575CITx' - """ - # Normalise value: collapse spaces around '/' and consecutive spaces - val = re.sub(r"\s*/\s*", "/", kicad_value.strip()) - val = re.sub(r"\s+", " ", val).strip() - - if kicad_part == "R": - return f"R {val} {extract_package(footprint)}" - elif kicad_part == "C": - return f"C {val} {extract_package(footprint)}" - elif kicad_part == "C_Polarized": - return f"CP {val}" - elif kicad_part in ("L", "L_Iron"): - return f"L {val}" - elif kicad_part == "Crystal": - return f"XTAL {val}" - else: - return val - - -def get_or_create_category(api: InvenTreeAPI, path_tuple: tuple) -> Optional[PartCategory]: - """ - Walk the category hierarchy, creating any levels that don't yet exist. - Returns the leaf PartCategory. - """ - parent = None - category = None - for name in path_tuple: - search_kwargs = {"name": name} - if parent: - search_kwargs["parent"] = parent.pk - try: - cats = PartCategory.list(api, **search_kwargs) - except Exception as exc: - logger.error("Category list failed for '%s': %s", name, exc) - return None - - if cats: - category = cats[0] - else: - data = {"name": name, "description": name} - if parent: - data["parent"] = parent.pk - try: - category = PartCategory.create(api, data) - logger.info("Created category '%s'", name) - except Exception as exc: - logger.error("Category create failed for '%s': %s", name, exc) - return None - parent = category - return category - - -def resolve_part_category( - api: InvenTreeAPI, - kicad_part: str, - part_data: PartData, - footprint: str, - category_map: Optional[dict[str, tuple[str, ...]]] = None, -) -> Optional[PartCategory]: - """Return the InvenTree PartCategory for a part, creating it if necessary. - - *category_map* defaults to the built-in map loaded from - ``default_categories.yaml`` when not provided. - """ - if category_map is None: - category_map = load_category_map() - - path = category_map.get(kicad_part) - if path: - pkg = extract_package(footprint) if footprint else "" - # Ceramic caps and resistors get a package-level sub-category. - if kicad_part in _PACKAGE_SUBCATEGORY_CAPS and pkg: - path = path + (pkg,) - elif kicad_part in _PACKAGE_SUBCATEGORY_RESISTORS and pkg: - path = path + (pkg,) - return get_or_create_category(api, path) - - # Symbol not in the map – warn so the user can extend the YAML file - logger.warning( - "KiCad symbol %r not found in category map; " - "add it to your categories YAML to assign a specific category.", - kicad_part, - ) - - # Supplier-provided category path as a fallback - if part_data and part_data.category_path: - logger.debug("Using supplier-provided category for %r: %s", kicad_part, part_data.category_path) - return get_or_create_category(api, tuple(part_data.category_path)) - - logger.debug("Falling back to 'Miscellaneous' for %r", kicad_part) - return get_or_create_category(api, ("Miscellaneous",)) diff --git a/scripts/inventree_sync/client.py b/scripts/inventree_sync/client.py deleted file mode 100644 index d8e19c3..0000000 --- a/scripts/inventree_sync/client.py +++ /dev/null @@ -1,248 +0,0 @@ -""" -client.py – Low-level InvenTree API helpers for creating and updating parts, -supplier records, manufacturer records, and price breaks. -""" - -import logging -import os -import tempfile -from typing import Optional - -import requests - -from inventree.api import InvenTreeAPI -from inventree.company import Company, ManufacturerPart, SupplierPart, SupplierPriceBreak -from inventree.part import Part, PartCategory - -from .fetchers import _IOS_UA -from .models import PartData - -logger = logging.getLogger(__name__) - - -def get_or_create_supplier(api: InvenTreeAPI, name: str) -> Optional[Company]: - """Return the supplier Company by name, creating it if not found.""" - try: - companies = Company.list(api, name=name, is_supplier=True) - if companies: - return companies[0] - return Company.create(api, {"name": name, "is_supplier": True, "is_manufacturer": False}) - except Exception as exc: - logger.error("get_or_create_supplier(%s) failed: %s", name, exc) - return None - - -def get_or_create_manufacturer(api: InvenTreeAPI, name: str) -> Optional[Company]: - """Return (or create) a manufacturer Company by name (case-insensitive).""" - try: - companies = Company.list(api, is_manufacturer=True) - for c in companies: - if c.name.lower() == name.lower(): - return c - return Company.create(api, {"name": name, "is_manufacturer": True, "is_supplier": False}) - except Exception as exc: - logger.error("get_or_create_manufacturer(%s) failed: %s", name, exc) - return None - - -def upload_image_from_url(part: Part, url: str) -> None: - """Download an image from *url* and attach it to *part*.""" - if not url: - return - try: - resp = requests.get(url, timeout=20, headers={ - "User-Agent": _IOS_UA, - "Referer": "https://www.lcsc.com/", - }) - resp.raise_for_status() - except Exception as exc: - logger.warning("Image download failed (%s): %s", url, exc) - return - - # Guess extension from Content-Type or URL - content_type = resp.headers.get("Content-Type", "") - if "jpeg" in content_type or "jpg" in content_type: - suffix = ".jpg" - elif "png" in content_type: - suffix = ".png" - elif "webp" in content_type: - suffix = ".webp" - else: - suffix = os.path.splitext(url.split("?")[0])[-1] or ".jpg" - - tmp_path = None - try: - with tempfile.NamedTemporaryFile(suffix=suffix, delete=False) as tmp: - tmp.write(resp.content) - tmp_path = tmp.name - part.uploadImage(tmp_path) - logger.info("Uploaded image to part %s", part.pk) - except Exception as exc: - logger.warning("Image upload failed for part %s: %s", part.pk, exc) - finally: - if tmp_path and os.path.exists(tmp_path): - os.unlink(tmp_path) - - -def _add_price_breaks( - api: InvenTreeAPI, - supplier_part: SupplierPart, - price_breaks: dict, - currency: str, -) -> None: - """Create price break records on *supplier_part*.""" - for qty, price in sorted(price_breaks.items()): - try: - SupplierPriceBreak.create(api, { - "part": supplier_part.pk, - "quantity": qty, - "price": str(price), - "price_currency": currency, - }) - except Exception as exc: - logger.warning("Price break creation failed (qty=%s): %s", qty, exc) - - -def create_part_in_inventree( - api: InvenTreeAPI, - name: str, - part_data: PartData, - category: Optional[PartCategory], - lcsc_supplier: Optional[Company], - mouser_supplier: Optional[Company], -) -> Optional[Part]: - """ - Create an InvenTree Part (with manufacturer/supplier parts) from *part_data*. - Returns the created Part, or None on failure. - """ - # 1. Create the base part - part_payload = { - "name": name, - "description": part_data.description or name, - "component": True, - "purchaseable": True, - "active": True, - } - if category: - part_payload["category"] = category.pk - if part_data.datasheet_url: - part_payload["link"] = part_data.datasheet_url - - try: - part = Part.create(api, part_payload) - logger.info("Created part '%s' (pk=%s)", name, part.pk) - except Exception as exc: - logger.error("Part creation failed for '%s': %s", name, exc) - return None - - # 2. Upload image - if part_data.image_url: - upload_image_from_url(part, part_data.image_url) - - # 3. Manufacturer part - if part_data.mpn and part_data.manufacturer: - manufacturer = get_or_create_manufacturer(api, part_data.manufacturer) - if manufacturer: - try: - ManufacturerPart.create(api, { - "part": part.pk, - "manufacturer": manufacturer.pk, - "MPN": part_data.mpn, - }) - logger.info("Created ManufacturerPart %s / %s", part_data.manufacturer, part_data.mpn) - except Exception as exc: - logger.warning("ManufacturerPart creation failed: %s", exc) - - # 4. LCSC supplier part - if part_data.lcsc_sku and lcsc_supplier: - try: - sp = SupplierPart.create(api, { - "part": part.pk, - "supplier": lcsc_supplier.pk, - "SKU": part_data.lcsc_sku, - "manufacturer_part": None, - }) - if part_data.price_breaks: - _add_price_breaks(api, sp, part_data.price_breaks, part_data.currency) - except Exception as exc: - logger.warning("LCSC SupplierPart creation failed (%s): %s", part_data.lcsc_sku, exc) - - # 5. Mouser supplier part - if part_data.mouser_sku and mouser_supplier: - try: - sp = SupplierPart.create(api, { - "part": part.pk, - "supplier": mouser_supplier.pk, - "SKU": part_data.mouser_sku, - }) - # Use Mouser price breaks only if LCSC had none - if part_data.price_breaks and not part_data.lcsc_sku: - _add_price_breaks(api, sp, part_data.price_breaks, part_data.currency) - except Exception as exc: - logger.warning("Mouser SupplierPart creation failed (%s): %s", part_data.mouser_sku, exc) - - return part - - -def find_existing_part( - api: InvenTreeAPI, lcsc_sku: str, mouser_sku: str -) -> Optional[Part]: - """Return the InvenTree Part if a SupplierPart with a matching SKU exists.""" - for sku in filter(None, [lcsc_sku, mouser_sku]): - try: - sp_list = SupplierPart.list(api, SKU=sku) - if sp_list: - return Part(api, pk=sp_list[0].part) - except Exception as exc: - logger.debug("SupplierPart lookup failed for SKU=%s: %s", sku, exc) - return None - - -def find_part_by_name(api: InvenTreeAPI, name: str) -> Optional[Part]: - """Return the InvenTree Part with an exact name match, or None.""" - if not name: - return None - try: - results = Part.list(api, search=name) - for part in results: - if part.name == name: - return part - except Exception as exc: - logger.debug("Part name lookup failed for '%s': %s", name, exc) - return None - - -def ensure_supplier_parts( - api: InvenTreeAPI, - part: Part, - part_data: PartData, - lcsc_supplier: Optional[Company], - mouser_supplier: Optional[Company], -) -> None: - """Add any missing SupplierParts to an already-existing InvenTree Part.""" - try: - existing_skus = {sp.SKU for sp in SupplierPart.list(api, part=part.pk)} - except Exception: - existing_skus = set() - - if part_data.lcsc_sku and lcsc_supplier and part_data.lcsc_sku not in existing_skus: - try: - sp = SupplierPart.create(api, { - "part": part.pk, - "supplier": lcsc_supplier.pk, - "SKU": part_data.lcsc_sku, - }) - if part_data.price_breaks: - _add_price_breaks(api, sp, part_data.price_breaks, part_data.currency) - except Exception as exc: - logger.warning("Could not add LCSC supplier part: %s", exc) - - if part_data.mouser_sku and mouser_supplier and part_data.mouser_sku not in existing_skus: - try: - SupplierPart.create(api, { - "part": part.pk, - "supplier": mouser_supplier.pk, - "SKU": part_data.mouser_sku, - }) - except Exception as exc: - logger.warning("Could not add Mouser supplier part: %s", exc) diff --git a/scripts/inventree_sync/default_categories.yaml b/scripts/inventree_sync/default_categories.yaml deleted file mode 100644 index 852a2f9..0000000 --- a/scripts/inventree_sync/default_categories.yaml +++ /dev/null @@ -1,96 +0,0 @@ -# KiCad symbol name β†’ InvenTree category hierarchy -# -# Each entry maps a KiCad symbol name to a list of category names that form -# a hierarchy in InvenTree (from top-level to sub-category). -# -# You can freely rename, add, or remove entries to match your InvenTree -# category tree. The special keys "package_subcategory_caps" and -# "package_subcategory_resistors" control which KiCad symbols get an -# additional package-level sub-category appended automatically. -# -# Notes: -# - Use a YAML list even for a single-level category: ["Miscellaneous"] -# - Hierarchy depth is unlimited. - -# ── Passives ───────────────────────────────────────────────────────────────── -R: [Resistors, Surface Mount] # + package sub-category appended -R_Small: [Resistors, Surface Mount] -R_Network: [Resistors, Surface Mount] -RN: [Resistors, Surface Mount] -R_Potentiometer: [Resistors, Potentiometers] -R_Thermsistor: [Resistors, NTC] - -C: [Capacitors, Ceramic] # + package sub-category appended -C_Small: [Capacitors, Ceramic] -C_Polarized: [Capacitors, Aluminium] -C_Polarized_Small: [Capacitors, Aluminium] -CP: [Capacitors, Aluminium] -C_Tantalum: [Capacitors, Tantalum] -C_Polymer: [Capacitors, Polymer] -C_SuperCapacitor: [Capacitors, Super Capacitors] - -L: [Inductors, Power] -L_Iron: [Inductors, Power] -L_Small: [Inductors, Power] -Ferrite_Bead: [Inductors, Ferrite Bead] -Ferrite_Bead_Small: [Inductors, Ferrite Bead] - -# ── Diodes ──────────────────────────────────────────────────────────────────── -D: [Diodes, Standard] -D_Schottky: [Diodes, Schottky] -D_Zener: [Diodes, Zener] -D_TVS: [Circuit Protections, TVS] -LED: [Diodes, LED] -LED_RGB: [Diodes, LED] -BAT43W-V: [Diodes, Schottky] -USBLC6-2SC6: [Circuit Protections, TVS] - -# ── Transistors ─────────────────────────────────────────────────────────────── -Q_NPN_BCE: [Transistors, NPN] -Q_PNP_BCE: [Transistors, PNP] -Q_NMOS_GSD: [Transistors, N-Channel FET] -Q_PMOS_GSD: [Transistors, P-Channel FET] -Q_NMOS_GDS: [Transistors, N-Channel FET] -Q_Load_Switch_P: [Transistors, Load Switches] -2N7002: [Transistors, N-Channel FET] -BC847BS: [Transistors, NPN] - -# ── Crystals & oscillators ──────────────────────────────────────────────────── -Crystal: [Crystals and Oscillators, Crystals] -Crystal_GND24: [Crystals and Oscillators, Crystals] -Oscillator: [Crystals and Oscillators, Oscillators] - -# ── Power management ────────────────────────────────────────────────────────── -LMR51430: [Power Management, Buck] -TLV73333PDBV: [Power Management, LDO] -Regulator_Linear: [Power Management, LDO] -Regulator_Switching: [Power Management, Buck] - -# ── Integrated circuits ─────────────────────────────────────────────────────── -STM32U575CITx: [Integrated Circuits, Microcontroller] -CAT24C128: [Integrated Circuits, Memory] -IC_Generic: [Integrated Circuits] - -# ── RF ──────────────────────────────────────────────────────────────────────── -SA818V: [RF, Chipset] -LFCN-160: [RF, Filter] -Antenna: [RF, Antenna] -Antenna_Shield: [RF, Shield] - -# ── Connectors ──────────────────────────────────────────────────────────────── -Conn_Coaxial: [Connectors, Coaxial] -Conn_01x01: [Connectors, Header] -Conn_01x02: [Connectors, Header] -Conn_01x03: [Connectors, Header] -Conn_01x04: [Connectors, Header] -Conn_02x10_Row_Letter_First: [Connectors, Header] -Conn_ARM_JTAG_SWD_10: [Connectors, Header] -USB_C_Receptacle: [Connectors, Interface] -USB_B_Micro: [Connectors, Interface] -Conn_FPC: [Connectors, FPC] -BatteryHolder: [Connectors, Battery] - -# ── Mechanicals ─────────────────────────────────────────────────────────────── -SW_Push: [Mechanicals, Switch] -SW_SPDT: [Mechanicals, Switch] -Mounting_Hole: [Mechanicals] diff --git a/scripts/inventree_sync/fetchers.py b/scripts/inventree_sync/fetchers.py deleted file mode 100644 index ed9dd22..0000000 --- a/scripts/inventree_sync/fetchers.py +++ /dev/null @@ -1,265 +0,0 @@ -""" -fetchers.py – Supplier data fetchers for LCSC and Mouser. -""" - -import logging -import os -import re -from typing import Optional - -import requests - -from .models import PartData - -logger = logging.getLogger(__name__) - -# iOS User-Agent – avoids bot-blocking on LCSC's CDN / wmsc API -_IOS_UA = ( - "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) " - "AppleWebKit/605.1.15 (KHTML, like Gecko) " - "Version/17.0 Mobile/15E148 Safari/604.1" -) - - -class LCSCFetcher: - """Fetches part data from the LCSC wmsc API.""" - - _UA = _IOS_UA - - def __init__(self): - self.session = requests.Session() - self.session.headers.update({ - "User-Agent": self._UA, - "Accept-Language": "en-US,en", - }) - # Initialise session / set currency cookie - try: - self.session.get( - "https://wmsc.lcsc.com/wmsc/home/currency?currencyCode=EUR", - timeout=10, - ) - except Exception as exc: - logger.warning("LCSC currency init failed: %s", exc) - - # ------------------------------------------------------------------ - # Public API - # ------------------------------------------------------------------ - - def fetch_by_sku(self, lcsc_sku: str) -> Optional[PartData]: - """Fetch a single part by its LCSC product code.""" - url = f"https://wmsc.lcsc.com/ftps/wm/product/detail?productCode={lcsc_sku}" - try: - resp = self.session.get(url, timeout=15) - resp.raise_for_status() - body = resp.json() - except Exception as exc: - logger.error("LCSC fetch_by_sku(%s) failed: %s", lcsc_sku, exc) - return None - - result = body.get("result") - if not result: - logger.warning("LCSC fetch_by_sku(%s): empty result", lcsc_sku) - return None - return self._parse(result, lcsc_sku=lcsc_sku) - - def fetch_by_mpn(self, mpn: str) -> Optional[PartData]: - """Search LCSC by MPN; prefer exact match. - - The search endpoint returns minimal product data (no paramVOList), so - after identifying the right product code we always call fetch_by_sku to - get the full detail (parameters, images, price breaks, …). - """ - url = "https://wmsc.lcsc.com/ftps/wm/search/v2/global" - try: - resp = self.session.post(url, json={"keyword": mpn}, timeout=15) - resp.raise_for_status() - body = resp.json() - except Exception as exc: - logger.error("LCSC fetch_by_mpn(%s) failed: %s", mpn, exc) - return None - - result = body.get("result", {}) - if not result: - return None - - # Direct match hint from the API - tip = result.get("tipProductDetailUrlVO") - if tip: - code = tip.get("productCode") - if code: - return self.fetch_by_sku(code) - - # Walk the search result list - product_list = ( - result.get("productSearchResultVO", {}).get("productList") or [] - ) - # Prefer exact MPN match, fall back to first result - best_code = None - for product in product_list: - if product.get("productModel", "").upper() == mpn.upper(): - best_code = product.get("productCode") - break - if best_code is None and product_list: - best_code = product_list[0].get("productCode") - - if best_code: - return self.fetch_by_sku(best_code) - - return None - - # ------------------------------------------------------------------ - # Internal helpers - # ------------------------------------------------------------------ - - @staticmethod - def _fix_datasheet(url: str) -> str: - """Rewrite datasheet CDN URLs to the wmsc mirror.""" - if not url: - return url - return url.replace( - "//datasheet.lcsc.com/", - "//wmsc.lcsc.com/wmsc/upload/file/pdf/v2/", - ) - - def _parse(self, product: dict, lcsc_sku: str = "") -> PartData: - """Convert a raw LCSC product dict into a PartData.""" - # Image: prefer big image, fall back to first in list - image_url = product.get("productImageUrlBig", "") - if not image_url: - images = product.get("productImages") or [] - if images: - image_url = images[0] - - # Datasheet - datasheet = self._fix_datasheet(product.get("pdfUrl", "")) - - # Parameters - params = {} - for p in product.get("paramVOList") or []: - name = p.get("paramNameEn", "").strip() - value = p.get("paramValueEn", "").strip() - if name and value: - params[name] = value - - # Price breaks {ladder_qty: unit_price_eur} - price_breaks = {} - for pb in product.get("productPriceList") or []: - try: - qty = int(pb["ladder"]) - price = float(pb["currencyPrice"]) - price_breaks[qty] = price - except (KeyError, ValueError, TypeError): - pass - - sku = lcsc_sku or product.get("productCode", "") - - return PartData( - mpn=product.get("productModel", ""), - manufacturer=product.get("brandNameEn", ""), - description=product.get("productDescEn", ""), - image_url=image_url, - datasheet_url=datasheet, - lcsc_sku=sku, - package=product.get("encapStandard", ""), - parameters=params, - price_breaks=price_breaks, - currency="EUR", - ) - - -class MouserFetcher: - """Fetches part data from the Mouser API v2. - - Requires the ``MOUSER_API_KEY`` environment variable to be set. - """ - - _URL = "https://api.mouser.com/api/v2/search/partnumber" - - def __init__(self): - self.api_key = os.environ.get("MOUSER_API_KEY") - if not self.api_key: - raise EnvironmentError( - "MOUSER_API_KEY environment variable is not set. " - "Export it before running this script." - ) - - def fetch(self, mouser_sku: str) -> Optional[PartData]: - """Return PartData for a Mouser SKU, or None on failure.""" - payload = { - "SearchByPartRequest": { - "mouserPartNumber": mouser_sku, - "partSearchOptions": "Exact", - } - } - try: - resp = requests.post( - self._URL, - params={"apiKey": self.api_key}, - json=payload, - timeout=15, - ) - resp.raise_for_status() - body = resp.json() - except Exception as exc: - logger.error("Mouser fetch(%s) failed: %s", mouser_sku, exc) - return None - - parts = body.get("SearchResults", {}).get("Parts") or [] - if not parts: - logger.warning("Mouser fetch(%s): no results", mouser_sku) - return None - - p = parts[0] - - # Strip HTML tags from description - description = re.sub(r"<[^>]+>", "", p.get("Description", "")) - - # Category - category_path = [] - cat = p.get("Category", "").strip() - if cat: - category_path = [cat] - - # Price breaks - price_breaks = {} - currency = "EUR" - for pb in p.get("PriceBreaks") or []: - try: - qty = int(pb["Quantity"]) - price = self._parse_price(pb.get("Price", "0")) - price_breaks[qty] = price - if pb.get("Currency"): - currency = pb["Currency"] - except (KeyError, ValueError, TypeError): - pass - - return PartData( - mpn=p.get("ManufacturerPartNumber", ""), - manufacturer=p.get("Manufacturer", ""), - description=description, - image_url=p.get("ImagePath", ""), - datasheet_url=p.get("DataSheetUrl", ""), - mouser_sku=mouser_sku, - category_path=category_path, - price_breaks=price_breaks, - currency=currency, - ) - - @staticmethod - def _parse_price(price_str: str) -> float: - """ - Parse a Mouser price string into a float. - Handles formats like "€ 7,07", "0.1234", "$ 1.23". - """ - cleaned = re.sub(r"[^\d,.]", "", price_str.strip()) - if not cleaned: - return 0.0 - last_comma = cleaned.rfind(",") - last_dot = cleaned.rfind(".") - if last_comma > last_dot: - # European format: 7,07 or 1.234,56 - cleaned = cleaned.replace(".", "").replace(",", ".") - else: - # US format: remove commas used as thousands separator - cleaned = cleaned.replace(",", "") - return float(cleaned) diff --git a/scripts/inventree_sync/models.py b/scripts/inventree_sync/models.py deleted file mode 100644 index 2bcc28e..0000000 --- a/scripts/inventree_sync/models.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -models.py – Shared data-model dataclasses used by the InvenTree sync scripts. -""" - -from dataclasses import dataclass, field -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from inventree.part import Part - - -@dataclass -class PartData: - """Raw supplier data fetched from LCSC / Mouser before creating an InvenTree Part.""" - - mpn: str = "" - manufacturer: str = "" - description: str = "" - image_url: str = "" - datasheet_url: str = "" - supplier_link: str = "" - lcsc_sku: str = "" - mouser_sku: str = "" - category_path: list = field(default_factory=list) - parameters: dict = field(default_factory=dict) - price_breaks: dict = field(default_factory=dict) # {qty: unit_price} - currency: str = "EUR" - package: str = "" - - -@dataclass(slots=True) -class BomEntry: - """One row of the KiCad BOM CSV.""" - - reference: str - qty: int - kicad_part: str - kicad_value: str - kicad_footprint: str - lcsc: list = field(default_factory=list) - mouser: list = field(default_factory=list) - inventree_part: list = field(default_factory=list) # list[Part] diff --git a/scripts/inventree_sync/part_manager.py b/scripts/inventree_sync/part_manager.py deleted file mode 100644 index c6446a4..0000000 --- a/scripts/inventree_sync/part_manager.py +++ /dev/null @@ -1,161 +0,0 @@ -""" -part_manager.py – High-level logic for ensuring BOM parts exist in InvenTree. - -Orchestrates fetching from LCSC/Mouser and creating missing parts. -""" - -import logging -import re -from typing import Optional - -from inventree.api import InvenTreeAPI - -from .categories import generate_part_name, resolve_part_category -from .client import ( - create_part_in_inventree, - ensure_supplier_parts, - find_existing_part, - find_part_by_name, - get_or_create_supplier, -) -from .fetchers import LCSCFetcher, MouserFetcher -from .models import PartData - -logger = logging.getLogger(__name__) - - -def _strip_mouser_prefix(mouser_sku: str) -> str: - """ - Strip the numeric distributor prefix from a Mouser SKU to recover the MPN. - '637-2N7002' β†’ '2N7002', '595-LMR51430XDDCR' β†’ 'LMR51430XDDCR' - Returns the original string when no prefix is found. - """ - m = re.match(r"^\d+-(.+)$", mouser_sku) - return m.group(1) if m else mouser_sku - - -def _fetch_and_merge( - lcsc_fetcher: LCSCFetcher, - mouser_fetcher: MouserFetcher, - lcsc_sku: str, - mouser_sku: str, -) -> Optional[PartData]: - """ - Fetch and merge part data from LCSC and Mouser. - - Strategy: - 1. LCSC by SKU (if available) – best source for parameters. - 2. LCSC by MPN derived from Mouser SKU (if no LCSC SKU). - 3. Mouser (if available) – supplements missing image/price. - LCSC data takes priority; Mouser fills gaps. - """ - lcsc_data: Optional[PartData] = None - mouser_data: Optional[PartData] = None - - if lcsc_sku: - lcsc_data = lcsc_fetcher.fetch_by_sku(lcsc_sku) - if lcsc_data is None and mouser_sku: - mpn = _strip_mouser_prefix(mouser_sku) - lcsc_data = lcsc_fetcher.fetch_by_mpn(mpn) - - if mouser_sku: - mouser_data = mouser_fetcher.fetch(mouser_sku) - - if lcsc_data is None and mouser_data is None: - return None - - # Merge: LCSC is primary, Mouser supplements - if lcsc_data is None: - result = mouser_data - elif mouser_data is None: - result = lcsc_data - else: - result = lcsc_data - if not result.image_url: - result.image_url = mouser_data.image_url - if not result.datasheet_url: - result.datasheet_url = mouser_data.datasheet_url - if not result.price_breaks: - result.price_breaks = mouser_data.price_breaks - result.currency = mouser_data.currency - if not result.description: - result.description = mouser_data.description - - # Stamp both SKUs on the merged result - result.lcsc_sku = lcsc_sku - result.mouser_sku = mouser_sku - return result - - -def ensure_parts_exist( - api: InvenTreeAPI, - parts: list, - category_map: Optional[dict[str, tuple[str, ...]]] = None, -) -> None: - """ - For every BomEntry in *parts* that is missing from InvenTree, fetch data - from LCSC / Mouser and create the part automatically. - - Each item in *parts* must have: reference, qty, lcsc, mouser, - inventree_part, kicad_part, kicad_value, kicad_footprint attributes. - - *category_map* is a dict mapping KiCad symbol names to InvenTree category - path tuples. When None, the built-in ``default_categories.yaml`` is used. - """ - lcsc_fetcher = LCSCFetcher() - mouser_fetcher = MouserFetcher() - - lcsc_supplier = get_or_create_supplier(api, name="LCSC") - mouser_supplier = get_or_create_supplier(api, name="Mouser") - - for entry in parts: - lcsc_skus: list = getattr(entry, "lcsc", []) - mouser_skus: list = getattr(entry, "mouser", []) - kicad_part: str = getattr(entry, "kicad_part", "") - kicad_value: str = getattr(entry, "kicad_value", "") - kicad_footprint: str = getattr(entry, "kicad_footprint", "") - - if not lcsc_skus and not mouser_skus: - logger.debug("Skipping part with no SKUs: %s", entry.reference) - continue - - if getattr(entry, "inventree_part", []): - continue - - lcsc_sku = lcsc_skus[0] if lcsc_skus else "" - mouser_sku = mouser_skus[0] if mouser_skus else "" - - # Check if a matching SupplierPart already exists in InvenTree - existing = find_existing_part(api, lcsc_sku, mouser_sku) - if existing: - entry.inventree_part.append(existing) - logger.info("Found existing part for %s: pk=%s", entry.reference, existing.pk) - continue - - # Fetch data from suppliers - part_data = _fetch_and_merge(lcsc_fetcher, mouser_fetcher, lcsc_sku, mouser_sku) - if part_data is None: - logger.warning( - "No supplier data found for %s (LCSC=%s, Mouser=%s)", - entry.reference, lcsc_sku, mouser_sku, - ) - continue - - # Generate name; reuse if an InvenTree part with that name already exists - name = generate_part_name(kicad_part, kicad_value, kicad_footprint) - existing_by_name = find_part_by_name(api, name) - if existing_by_name: - logger.info( - "Part '%s' already exists (pk=%s); adding missing supplier parts", - name, existing_by_name.pk, - ) - ensure_supplier_parts(api, existing_by_name, part_data, lcsc_supplier, mouser_supplier) - entry.inventree_part.append(existing_by_name) - continue - - category = resolve_part_category(api, kicad_part, part_data, kicad_footprint, category_map) - inv_part = create_part_in_inventree(api, name, part_data, category, lcsc_supplier, mouser_supplier) - if inv_part: - entry.inventree_part.append(inv_part) - else: - logger.error("Failed to create part for %s", entry.reference) diff --git a/scripts/make_stencil_image.py b/scripts/make_stencil_image.py deleted file mode 100644 index 4c766ce..0000000 --- a/scripts/make_stencil_image.py +++ /dev/null @@ -1,190 +0,0 @@ -#!/usr/bin/env python3 -""" -make_stencil_image.py – Convert a KiBot SVG paste-layer export into a -stencil-like PNG suitable for use as an InvenTree part image. - -Works with the pcb_print SVG output (filled shapes, white background). - -Colour mapping applied: - β€’ Background (white/near-white) β†’ steel grey (#B8C0CC) - β€’ F.Paste apertures (any colour) β†’ black (#000000) = holes - β€’ Edge.Cuts outline (yellow) β†’ gold (#D4A017) - -Usage: - python3 scripts/make_stencil_image.py [--dpi DPI] -""" - -import argparse -import subprocess -import sys -import tempfile -from pathlib import Path - -from PIL import Image -import numpy as np - - -# ── colour targets ──────────────────────────────────────────────────────────── -STEEL = np.array([184, 192, 204], dtype=np.uint8) # background (stainless) -BLACK = np.array([ 0, 0, 0], dtype=np.uint8) # paste apertures (holes) -WHITE = np.array([255, 255, 255], dtype=np.uint8) # silkscreen text/outlines -GOLD = np.array([212, 160, 23], dtype=np.uint8) # PCB outline - -# KiBot layer colours as defined in production.kibot.yaml -# F.Paste β†’ #000000 (black) -# F.Silkscreen β†’ #0055AA (blue) -# Edge.Cuts β†’ #FFB300 (amber) -_PASTE_COLOR = ( 0, 0, 0) -_SILK_COLOR = ( 0, 85, 170) -_EDGE_COLOR = (255, 179, 0) - -# Tolerance for colour matching (per-channel) -_TOL = 40 - -# A pixel is "background" when all channels are >= this value -WHITE_THRESHOLD = 220 - - -def _svg_to_png(svg_path: Path, png_path: Path, dpi: int) -> None: - """Rasterise *svg_path* to *png_path*. - - Tries in order: - 1. cairosvg (Python library, no external tool needed) - 2. rsvg-convert - 3. inkscape - """ - # 1. cairosvg - try: - import cairosvg - scale = dpi / 96.0 # SVG default is 96 dpi - cairosvg.svg2png(url=str(svg_path), write_to=str(png_path), scale=scale) - return - except ImportError: - pass - except Exception as exc: - print(f" cairosvg failed ({exc}), trying rsvg-convert …") - - # 2. rsvg-convert / 3. inkscape - for cmd in [ - ["rsvg-convert", "-d", str(dpi), "-p", str(dpi), "-o", str(png_path), str(svg_path)], - ["inkscape", "--export-dpi", str(dpi), f"--export-filename={png_path}", str(svg_path)], - ]: - try: - subprocess.run(cmd, check=True, capture_output=True) - return - except (FileNotFoundError, subprocess.CalledProcessError): - continue - - sys.exit(f"ERROR: no SVG rasteriser found (cairosvg, rsvg-convert, inkscape). " - f"Install one of them or add cairosvg to requirements.txt.") - - -def _composite_on_white(img: Image.Image) -> Image.Image: - """Flatten an RGBA image onto a white background β†’ RGB.""" - bg = Image.new("RGB", img.size, (255, 255, 255)) - if img.mode == "RGBA": - bg.paste(img, mask=img.split()[3]) - else: - bg.paste(img.convert("RGB")) - return bg - - -def _near(arr: np.ndarray, color: tuple, tol: int = _TOL) -> np.ndarray: - """Return boolean mask where pixels are within *tol* of *color* (RGB).""" - r, g, b = color - a = arr - return ( - (np.abs(a[:, :, 0].astype(int) - r) <= tol) & - (np.abs(a[:, :, 1].astype(int) - g) <= tol) & - (np.abs(a[:, :, 2].astype(int) - b) <= tol) - ) - - -def _recolor(img: Image.Image) -> Image.Image: - """Apply the stencil colour mapping to a white-background RGB image.""" - arr = np.array(img, dtype=np.uint8) - r, g, b = arr[:, :, 0], arr[:, :, 1], arr[:, :, 2] - - # Background: all channels bright - bg_mask = (r >= WHITE_THRESHOLD) & (g >= WHITE_THRESHOLD) & (b >= WHITE_THRESHOLD) - edge_mask = _near(arr, _EDGE_COLOR) & ~bg_mask - silk_mask = _near(arr, _SILK_COLOR) & ~bg_mask & ~edge_mask - # Paste: everything else (including pure black from the layer colour) - paste_mask = ~bg_mask & ~edge_mask & ~silk_mask - - out = np.empty_like(arr) - out[bg_mask] = STEEL - out[edge_mask] = GOLD - out[silk_mask] = WHITE - out[paste_mask] = BLACK - - # Diagnostics - total = arr.shape[0] * arr.shape[1] - print(f" bg={bg_mask.sum()} ({100*bg_mask.sum()//total}%) " - f"edge={edge_mask.sum()} ({100*edge_mask.sum()//total}%) " - f"silk={silk_mask.sum()} ({100*silk_mask.sum()//total}%) " - f"paste={paste_mask.sum()} ({100*paste_mask.sum()//total}%)") - - return Image.fromarray(out, "RGB") - - -def _autocrop(img: Image.Image, padding: int = 30) -> Image.Image: - """Crop to the bounding box of non-STEEL pixels + *padding*.""" - arr = np.array(img) - steel = STEEL.tolist() - non_bg = ~( - (arr[:, :, 0] == steel[0]) & - (arr[:, :, 1] == steel[1]) & - (arr[:, :, 2] == steel[2]) - ) - rows = np.any(non_bg, axis=1) - cols = np.any(non_bg, axis=0) - if not rows.any(): - return img - rmin = int(np.argmax(rows)) - rmax = int(len(rows) - 1 - np.argmax(rows[::-1])) - cmin = int(np.argmax(cols)) - cmax = int(len(cols) - 1 - np.argmax(cols[::-1])) - h, w = arr.shape[:2] - return img.crop(( - max(0, cmin - padding), - max(0, rmin - padding), - min(w, cmax + padding), - min(h, rmax + padding), - )) - - -def main() -> None: - parser = argparse.ArgumentParser(description="Convert KiBot SVG to stencil PNG") - parser.add_argument("input", help="Input SVG file (from KiBot pcb_print output)") - parser.add_argument("output", help="Output PNG file") - parser.add_argument("--dpi", type=int, default=300, help="Rasterisation DPI (default: 300)") - args = parser.parse_args() - - svg_path = Path(args.input) - out_path = Path(args.output) - - if not svg_path.exists(): - sys.exit(f"ERROR: input file not found: {svg_path}") - - with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmp: - tmp_png = Path(tmp.name) - - try: - print(f"Rasterising {svg_path} at {args.dpi} DPI …") - _svg_to_png(svg_path, tmp_png, args.dpi) - - print("Applying stencil colour mapping …") - img = _composite_on_white(Image.open(tmp_png)) - img = _recolor(img) - img = _autocrop(img) - - out_path.parent.mkdir(parents=True, exist_ok=True) - img.save(out_path, "PNG", optimize=True) - print(f"Saved stencil image β†’ {out_path} ({img.size[0]}Γ—{img.size[1]} px)") - finally: - tmp_png.unlink(missing_ok=True) - - -if __name__ == "__main__": - main() diff --git a/scripts/requirements.txt b/scripts/requirements.txt deleted file mode 100644 index 285df8f..0000000 --- a/scripts/requirements.txt +++ /dev/null @@ -1,7 +0,0 @@ -inventree -requests -beautifulsoup4 -Pillow -numpy -cairosvg -PyYAML diff --git a/test.kibot.yaml b/test.kibot.yaml deleted file mode 100644 index 0af014f..0000000 --- a/test.kibot.yaml +++ /dev/null @@ -1,16 +0,0 @@ -kibot: - version: 1 - -global: - field_lcsc_part: LCSC - -preflight: - erc: - warnings_as_errors: true - filters: - - filter: 'Ignore library symbol cache drift between local KiCad and CI libs' - error: lib_symbol_mismatch - regex: '.*' - drc: true - update_xml: false - check_zone_fills: true