boardforge 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- boardforge/__init__.py +7 -0
- boardforge/analysis/__init__.py +259 -0
- boardforge/cli.py +280 -0
- boardforge/config.py +218 -0
- boardforge/datasheets.py +604 -0
- boardforge/drc.py +71 -0
- boardforge/fabs/__init__.py +18 -0
- boardforge/fabs/base.py +108 -0
- boardforge/fabs/jlcpcb/__init__.py +60 -0
- boardforge/fabs/jlcpcb/profile.toml +56 -0
- boardforge/kicad.py +189 -0
- boardforge/netlist.py +118 -0
- boardforge/pcb/__init__.py +8 -0
- boardforge/pcb/astar.py +543 -0
- boardforge/pcb/build.py +158 -0
- boardforge/pcb/common.py +112 -0
- boardforge/pcb/congestion.py +116 -0
- boardforge/pcb/fanout.py +278 -0
- boardforge/pcb/fiducials.py +104 -0
- boardforge/pcb/finish.py +215 -0
- boardforge/pcb/measure.py +76 -0
- boardforge/pcb/pinswap.py +143 -0
- boardforge/pcb/place.py +556 -0
- boardforge/pcb/specctra.py +24 -0
- boardforge/pcbfile.py +41 -0
- boardforge/physical.py +811 -0
- boardforge/project.py +116 -0
- boardforge/render.py +66 -0
- boardforge/review/__init__.py +0 -0
- boardforge/review/bom_check.py +280 -0
- boardforge/review/budget.py +92 -0
- boardforge/review/constraints.py +61 -0
- boardforge/review/datasheet_check.py +172 -0
- boardforge/review/gate.py +80 -0
- boardforge/review/package_check.py +246 -0
- boardforge/review/spice_path.py +351 -0
- boardforge/routers/__init__.py +17 -0
- boardforge/routers/astar.py +87 -0
- boardforge/routers/base.py +77 -0
- boardforge/routers/freerouting.py +58 -0
- boardforge/routers/specctra.py +84 -0
- boardforge/skidl_runner.py +144 -0
- boardforge/state.py +54 -0
- boardforge/units.py +33 -0
- boardforge/vendor/README.md +5 -0
- boardforge/vendor/kicad_happy/LICENSE +21 -0
- boardforge/vendor/kicad_happy/VENDORED.md +25 -0
- boardforge/vendor/kicad_happy/how-it-works.md +254 -0
- boardforge/vendor/kicad_happy/skills/bom/SKILL.md +378 -0
- boardforge/vendor/kicad_happy/skills/bom/references/kicad-fields.md +68 -0
- boardforge/vendor/kicad_happy/skills/bom/references/ordering-and-fabrication.md +150 -0
- boardforge/vendor/kicad_happy/skills/bom/references/part-number-conventions.md +409 -0
- boardforge/vendor/kicad_happy/skills/bom/scripts/bom_manager.py +1298 -0
- boardforge/vendor/kicad_happy/skills/bom/scripts/edit_properties.py +438 -0
- boardforge/vendor/kicad_happy/skills/bom/scripts/kicad_sexp.py +85 -0
- boardforge/vendor/kicad_happy/skills/bom/scripts/sync_datasheet_urls.py +393 -0
- boardforge/vendor/kicad_happy/skills/bom/scripts/translate_bom_pnp.py +731 -0
- boardforge/vendor/kicad_happy/skills/datasheets/SKILL.md +119 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/__init__.py +80 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/__pycache__/__init__.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/__pycache__/base_block.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/__pycache__/codec.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/__pycache__/extraction.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/__pycache__/pinout.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/__pycache__/regulator.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/__pycache__/spec_value.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/__pycache__/trust_gating.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/base_block.py +132 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/codec.py +174 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/extraction.py +177 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/pinout.py +117 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/regulator.py +95 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/spec_value.py +51 -0
- boardforge/vendor/kicad_happy/skills/datasheets/datasheet_types/trust_gating.py +125 -0
- boardforge/vendor/kicad_happy/skills/datasheets/examples/abm8g-106-12.000mhz-t.json +372 -0
- boardforge/vendor/kicad_happy/skills/datasheets/examples/irlml6344.json +616 -0
- boardforge/vendor/kicad_happy/skills/datasheets/examples/lm2596-adj.json +507 -0
- boardforge/vendor/kicad_happy/skills/datasheets/examples/lm358.json +598 -0
- boardforge/vendor/kicad_happy/skills/datasheets/examples/mbrs540t3g.json +498 -0
- boardforge/vendor/kicad_happy/skills/datasheets/examples/stm32f103c8t6.json +2090 -0
- boardforge/vendor/kicad_happy/skills/datasheets/prompts/base.md +56 -0
- boardforge/vendor/kicad_happy/skills/datasheets/prompts/crystal.md +127 -0
- boardforge/vendor/kicad_happy/skills/datasheets/prompts/diode.md +89 -0
- boardforge/vendor/kicad_happy/skills/datasheets/prompts/mcu.md +163 -0
- boardforge/vendor/kicad_happy/skills/datasheets/prompts/opamp.md +164 -0
- boardforge/vendor/kicad_happy/skills/datasheets/prompts/pinout.md +54 -0
- boardforge/vendor/kicad_happy/skills/datasheets/prompts/regulator.md +56 -0
- boardforge/vendor/kicad_happy/skills/datasheets/prompts/scout.md +68 -0
- boardforge/vendor/kicad_happy/skills/datasheets/prompts/transistor.md +145 -0
- boardforge/vendor/kicad_happy/skills/datasheets/references/cache-layout.md +183 -0
- boardforge/vendor/kicad_happy/skills/datasheets/references/consumer-api.md +240 -0
- boardforge/vendor/kicad_happy/skills/datasheets/references/dispatch-claude-code.md +179 -0
- boardforge/vendor/kicad_happy/skills/datasheets/references/dispatcher-contract.md +112 -0
- boardforge/vendor/kicad_happy/skills/datasheets/references/extraction-pipeline.md +24 -0
- boardforge/vendor/kicad_happy/skills/datasheets/references/extraction-schema.md +387 -0
- boardforge/vendor/kicad_happy/skills/datasheets/references/field-extraction-guide.md +262 -0
- boardforge/vendor/kicad_happy/skills/datasheets/references/quality-scoring.md +205 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/CHANGELOG.md +180 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/base.schema.json +181 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/crystal.schema.json +106 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/diode.schema.json +148 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/extraction.schema.json +91 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/fixtures/lm2596-adj.example.json +507 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/fixtures/manifest.example.json +31 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/fixtures/minimal.example.json +95 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/manifest.schema.json +69 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/mcu.schema.json +252 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/opamp.schema.json +167 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/pinout.schema.json +130 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/plan.schema.json +100 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/regulator.schema.json +156 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/scout.schema.json +68 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/spec_value.schema.json +62 -0
- boardforge/vendor/kicad_happy/skills/datasheets/schemas/transistor.schema.json +221 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/__pycache__/datasheet_extract_cache.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/__pycache__/datasheet_features.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/__pycache__/datasheet_lookup.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/__pycache__/datasheet_score.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/__pycache__/datasheet_verify.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/_smoke_v14_roundtrip.py +211 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/datasheet_extract_cache.py +541 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/datasheet_features.py +423 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/datasheet_lookup.py +243 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/datasheet_page_selector.py +500 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/datasheet_score.py +459 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/datasheet_verify.py +1070 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/merge_results.py +318 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/plan_extraction.py +198 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/validate_extraction_result.py +142 -0
- boardforge/vendor/kicad_happy/skills/datasheets/scripts/validate_sanity_vector.py +157 -0
- boardforge/vendor/kicad_happy/skills/digikey/SKILL.md +377 -0
- boardforge/vendor/kicad_happy/skills/digikey/scripts/fetch_datasheet_digikey.py +547 -0
- boardforge/vendor/kicad_happy/skills/digikey/scripts/sync_datasheets_digikey.py +908 -0
- boardforge/vendor/kicad_happy/skills/element14/SKILL.md +273 -0
- boardforge/vendor/kicad_happy/skills/element14/scripts/fetch_datasheet_element14.py +516 -0
- boardforge/vendor/kicad_happy/skills/element14/scripts/sync_datasheets_element14.py +775 -0
- boardforge/vendor/kicad_happy/skills/emc/SKILL.md +206 -0
- boardforge/vendor/kicad_happy/skills/emc/references/emc-methodology.md +142 -0
- boardforge/vendor/kicad_happy/skills/emc/references/emc-standards.md +110 -0
- boardforge/vendor/kicad_happy/skills/emc/references/pcb-emc-rules.md +381 -0
- boardforge/vendor/kicad_happy/skills/emc/scripts/__pycache__/emc_envelope.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/emc/scripts/__pycache__/emc_formulas.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/emc/scripts/__pycache__/emc_rules.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/emc/scripts/analyze_emc.py +682 -0
- boardforge/vendor/kicad_happy/skills/emc/scripts/emc_envelope.py +291 -0
- boardforge/vendor/kicad_happy/skills/emc/scripts/emc_formulas.py +1392 -0
- boardforge/vendor/kicad_happy/skills/emc/scripts/emc_rules.py +4552 -0
- boardforge/vendor/kicad_happy/skills/emc/scripts/emc_spice.py +708 -0
- boardforge/vendor/kicad_happy/skills/jlcpcb/SKILL.md +233 -0
- boardforge/vendor/kicad_happy/skills/kicad/SKILL.md +850 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/config-reference.md +356 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/datasheet-verification.md +322 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/deep-review.md +162 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/design-intent.md +123 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/diff-analysis.md +355 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/file-formats.md +379 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/gerber-parsing.md +729 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/manual-gerber-parsing.md +621 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/manual-pcb-parsing.md +467 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/manual-schematic-parsing.md +289 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/net-tracing.md +169 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/oshwa-certification.md +89 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/output-schema.md +317 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/pcb-layout-analysis.md +447 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/pdf-schematic-extraction.md +315 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/report-generation.md +696 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/schematic-analysis.md +1165 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/standards-compliance.md +638 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/supplementary-data-sources.md +288 -0
- boardforge/vendor/kicad_happy/skills/kicad/references/what-if.md +500 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/README.md +46 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/fixtures/deep_review.example.json +39 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/fixtures/design_context.example.json +9 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/fixtures/review_annotations.example.json +22 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/prompts/design_context.md +100 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/schemas/deep_review.schema.json +72 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/schemas/design_context.schema.json +59 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/schemas/review_annotations.schema.json +49 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/scripts/_mini_jsonschema.py +173 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/scripts/build_review_plan.py +65 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/scripts/deep_review_gate.py +347 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/scripts/merge_annotations.py +175 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/scripts/run_phase4_exercise.py +281 -0
- boardforge/vendor/kicad_happy/skills/kicad/review/scripts/validate_review.py +72 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/README.md +389 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/analyzer_envelope.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/bus_resolver.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/capability_mode.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/detection_schema.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/detector_helpers.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/domain_detectors.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/finding_schema.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/inputs_builder.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/kicad_types.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/kicad_utils.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/lookup_detectors.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/lookup_helpers.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/netlist_queries.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/output_filters.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/pcb_connectivity.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/project_config.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/run_id.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/schema_codec.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/sexp_parser.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/signal_detectors.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/__pycache__/validation_detectors.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/analysis_cache.py +604 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/analyze_gerbers.py +1670 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/analyze_pcb.py +7544 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/analyze_schematic.py +10421 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/analyze_thermal.py +1222 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/analyzer_envelope.py +295 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/bus_resolver.py +289 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/capability_mode.py +218 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/check_report_sections.py +201 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/cross_analysis.py +1201 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/cross_verify.py +657 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/detection_schema.py +654 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/detector_helpers.py +76 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/diff_analysis.py +1596 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/domain_detectors.py +6161 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/__init__.py +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/__pycache__/__init__.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/__pycache__/gerber.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/__pycache__/pcb.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/__pycache__/schematic.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/__pycache__/thermal.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/cross_analysis.py +132 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/gerber.py +355 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/pcb.py +395 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/schematic.py +450 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/envelopes/thermal.py +144 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/export_issues.py +547 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/fab_release_gate.py +604 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/finding_schema.py +715 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/gen_output_schema_md.py +230 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/inputs_builder.py +107 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/kicad_types.py +117 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/kicad_utils.py +2278 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/lifecycle_audit.py +1137 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/lookup_detectors.py +701 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/lookup_helpers.py +60 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/methodology_gerbers.md +447 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/methodology_pcb.md +473 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/methodology_schematic.md +892 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/netlist_queries.py +360 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/output_filters.py +468 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/pcb_connectivity.py +516 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/project_config.py +956 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/run_id.py +47 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/schema_codec.py +145 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/sexp_parser.py +233 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/signal_detectors.py +4699 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/summarize_findings.py +421 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/validation_detectors.py +1440 -0
- boardforge/vendor/kicad_happy/skills/kicad/scripts/what_if.py +1554 -0
- boardforge/vendor/kicad_happy/skills/lcsc/SKILL.md +241 -0
- boardforge/vendor/kicad_happy/skills/lcsc/scripts/fetch_datasheet_lcsc.py +591 -0
- boardforge/vendor/kicad_happy/skills/lcsc/scripts/search_lcsc.py +360 -0
- boardforge/vendor/kicad_happy/skills/lcsc/scripts/sync_datasheets_lcsc.py +755 -0
- boardforge/vendor/kicad_happy/skills/lcsc/scripts/test_search_lcsc.py +358 -0
- boardforge/vendor/kicad_happy/skills/mouser/SKILL.md +265 -0
- boardforge/vendor/kicad_happy/skills/mouser/scripts/fetch_datasheet_mouser.py +600 -0
- boardforge/vendor/kicad_happy/skills/mouser/scripts/sync_datasheets_mouser.py +805 -0
- boardforge/vendor/kicad_happy/skills/pcbway/SKILL.md +179 -0
- boardforge/vendor/kicad_happy/skills/spice/SKILL.md +333 -0
- boardforge/vendor/kicad_happy/skills/spice/references/simulation-models.md +427 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/__pycache__/spice_model_generator.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/__pycache__/spice_models.cpython-314.pyc +0 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/extract_parasitics.py +319 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/simulate_subcircuits.py +689 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/spice_model_cache.py +293 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/spice_model_generator.py +231 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/spice_models.py +196 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/spice_part_library.py +447 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/spice_results.py +1260 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/spice_simulator.py +475 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/spice_spec_fetcher.py +650 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/spice_templates.py +1740 -0
- boardforge/vendor/kicad_happy/skills/spice/scripts/spice_tolerance.py +311 -0
- boardforge-0.1.0.dist-info/METADATA +258 -0
- boardforge-0.1.0.dist-info/RECORD +286 -0
- boardforge-0.1.0.dist-info/WHEEL +5 -0
- boardforge-0.1.0.dist-info/entry_points.txt +2 -0
- boardforge-0.1.0.dist-info/licenses/LICENSE +21 -0
- boardforge-0.1.0.dist-info/top_level.txt +1 -0
boardforge/__init__.py
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"""boardforge: SKiDL circuit -> routed, manufacturable KiCad board.
|
|
2
|
+
|
|
3
|
+
Keep this module free of imports: the scripts in boardforge.pcb run under KiCad's own
|
|
4
|
+
Python, which imports this package but has none of boardforge's dependencies.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
"""Analysis-step registry (design doc §4.7).
|
|
2
|
+
|
|
3
|
+
Each step (schematic, pcb, thermal, emc, cross_verify, gerbers, release_gate) has one or
|
|
4
|
+
more implementations. The default is the vendored kicad-happy script, run as a subprocess.
|
|
5
|
+
Steps talk through JSON files with kicad-happy's schema
|
|
6
|
+
(``skills/kicad/references/output-schema.md``), so a replacement only has to write the same
|
|
7
|
+
JSON. Choose an implementation per step in boardforge.toml:
|
|
8
|
+
|
|
9
|
+
[checks.steps]
|
|
10
|
+
thermal = "boardforge"
|
|
11
|
+
|
|
12
|
+
Register a replacement with ``@register("thermal", "boardforge")`` on a function
|
|
13
|
+
``(ctx) -> output path``.
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import subprocess
|
|
19
|
+
import sys
|
|
20
|
+
|
|
21
|
+
VENDOR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "vendor", "kicad_happy", "skills")
|
|
22
|
+
REGISTRY = {}
|
|
23
|
+
|
|
24
|
+
# Steps in dependency order; each lists the outputs it reads.
|
|
25
|
+
ORDER = ["schematic", "pcb", "thermal", "emc", "cross_verify", "gerbers", "release_gate"]
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def vendor_script(rel):
|
|
29
|
+
return os.path.join(VENDOR, rel)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def register(step, impl):
|
|
33
|
+
def deco(fn):
|
|
34
|
+
REGISTRY.setdefault(step, {})[impl] = fn
|
|
35
|
+
return fn
|
|
36
|
+
return deco
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class Context:
|
|
40
|
+
"""What a step needs: input files and where outputs go. ``outputs`` fills as steps run."""
|
|
41
|
+
|
|
42
|
+
def __init__(self, sch, pcb, outdir, gerber_dir=None, rail_currents=None, drc_report=None,
|
|
43
|
+
fab_limits=None):
|
|
44
|
+
self.sch, self.pcb, self.outdir, self.gerber_dir = sch, pcb, outdir, gerber_dir
|
|
45
|
+
# {"name": "jlcpcb/multilayer", min_track, min_clearance, min_drill, annular_ring}:
|
|
46
|
+
# the fab tier the board was set up for; DFM findings are re-judged against it.
|
|
47
|
+
self.fab_limits = fab_limits
|
|
48
|
+
self.drc_report = drc_report # kicad-cli DRC JSON: the authority on unrouted connections
|
|
49
|
+
self.rail_currents = dict(rail_currents or {}) # {KiCad net: A}
|
|
50
|
+
self.outputs = {}
|
|
51
|
+
self.loads = [] # [(regulator ref, rail, A)] patched by schematic_with_loads()
|
|
52
|
+
os.makedirs(outdir, exist_ok=True)
|
|
53
|
+
|
|
54
|
+
def out(self, step):
|
|
55
|
+
return os.path.join(self.outdir, f"{step}.json")
|
|
56
|
+
|
|
57
|
+
def schematic_with_loads(self):
|
|
58
|
+
"""The schematic analysis with declared rail currents in place of kicad-happy's load
|
|
59
|
+
guesses (LDO dissipation, switcher rail loads). Same file if nothing is declared."""
|
|
60
|
+
src = self.outputs["schematic"]
|
|
61
|
+
if not self.rail_currents:
|
|
62
|
+
return src
|
|
63
|
+
with open(src) as f:
|
|
64
|
+
sch = json.load(f)
|
|
65
|
+
self.loads = apply_rail_currents(sch, self.rail_currents)
|
|
66
|
+
path = self.out("schematic_loads")
|
|
67
|
+
with open(path, "w") as f:
|
|
68
|
+
json.dump(sch, f)
|
|
69
|
+
return path
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def apply_rail_currents(sch, currents):
|
|
73
|
+
"""Patch declared load currents into a kicad-happy schematic analysis, in place.
|
|
74
|
+
Returns [(regulator ref, rail, amps)] for the regulators changed."""
|
|
75
|
+
patched = []
|
|
76
|
+
for reg in sch.get("findings", []):
|
|
77
|
+
if reg.get("detector") != "detect_power_regulators":
|
|
78
|
+
continue
|
|
79
|
+
rail = reg.get("output_rail")
|
|
80
|
+
if rail not in currents:
|
|
81
|
+
continue
|
|
82
|
+
amps = float(currents[rail])
|
|
83
|
+
pd = reg.get("power_dissipation")
|
|
84
|
+
if isinstance(pd, dict) and "dropout_V" in pd: # LDO
|
|
85
|
+
pd["estimated_iout_A"] = round(amps, 4)
|
|
86
|
+
pd["estimated_pdiss_W"] = round(pd["dropout_V"] * amps, 4)
|
|
87
|
+
pd["_iout_provenance"] = {"source": "boardforge_config", "confidence": "declared",
|
|
88
|
+
"basis": "checks.rail_currents"}
|
|
89
|
+
elif isinstance(pd, dict) and pd.get("efficiency_assumed") and pd.get("vout_V"): # switcher
|
|
90
|
+
eta = pd["efficiency_assumed"]
|
|
91
|
+
pd["estimated_iout_A"] = round(amps, 4)
|
|
92
|
+
pd["estimated_pdiss_W"] = round(pd["vout_V"] * amps * (1 / eta - 1), 4)
|
|
93
|
+
pd["_iout_provenance"] = {"source": "boardforge_config", "confidence": "declared",
|
|
94
|
+
"basis": "checks.rail_currents"}
|
|
95
|
+
reg["estimated_iout_A"] = round(amps, 4)
|
|
96
|
+
patched.append((reg.get("ref"), rail, amps))
|
|
97
|
+
rails = sch.get("power_budget", {}).get("rails", {})
|
|
98
|
+
for rail, amps in currents.items():
|
|
99
|
+
if isinstance(rails.get(rail), dict):
|
|
100
|
+
rails[rail]["estimated_load_mA"] = round(float(amps) * 1000, 1)
|
|
101
|
+
return patched
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def _vendor(script, *args, timeout=900):
|
|
105
|
+
r = subprocess.run([sys.executable, vendor_script(script), *map(str, args)],
|
|
106
|
+
capture_output=True, text=True, timeout=timeout)
|
|
107
|
+
if r.returncode not in (0, 1): # some analyzers exit 1 when they report errors
|
|
108
|
+
raise RuntimeError(f"{os.path.basename(script)} exit {r.returncode}: {r.stderr.strip()[-400:]}")
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@register("schematic", "kicad_happy")
|
|
112
|
+
def _schematic(ctx):
|
|
113
|
+
_vendor("kicad/scripts/analyze_schematic.py", ctx.sch, "-o", ctx.out("schematic"))
|
|
114
|
+
return ctx.out("schematic")
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
@register("pcb", "kicad_happy")
|
|
118
|
+
def _pcb(ctx):
|
|
119
|
+
_vendor("kicad/scripts/analyze_pcb.py", ctx.pcb, "--full", "-o", ctx.out("pcb"))
|
|
120
|
+
return ctx.out("pcb")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@register("thermal", "kicad_happy")
|
|
124
|
+
def _thermal(ctx):
|
|
125
|
+
_vendor("kicad/scripts/analyze_thermal.py", "-s", ctx.schematic_with_loads(), "-p",
|
|
126
|
+
ctx.outputs["pcb"], "-o", ctx.out("thermal"))
|
|
127
|
+
return ctx.out("thermal")
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
@register("emc", "kicad_happy")
|
|
131
|
+
def _emc(ctx):
|
|
132
|
+
_vendor("emc/scripts/analyze_emc.py", "-s", ctx.outputs["schematic"], "-p", ctx.outputs["pcb"],
|
|
133
|
+
"-o", ctx.out("emc"))
|
|
134
|
+
return ctx.out("emc")
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@register("cross_verify", "kicad_happy")
|
|
138
|
+
def _cross(ctx):
|
|
139
|
+
args = ["-s", ctx.schematic_with_loads(), "-p", ctx.outputs["pcb"], "-o", ctx.out("cross_verify")]
|
|
140
|
+
if "thermal" in ctx.outputs:
|
|
141
|
+
args += ["-t", ctx.outputs["thermal"]]
|
|
142
|
+
_vendor("kicad/scripts/cross_verify.py", *args)
|
|
143
|
+
return ctx.out("cross_verify")
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
@register("gerbers", "kicad_happy")
|
|
147
|
+
def _gerbers(ctx):
|
|
148
|
+
_vendor("kicad/scripts/analyze_gerbers.py", ctx.gerber_dir, "-o", ctx.out("gerbers"))
|
|
149
|
+
return ctx.out("gerbers")
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# kicad-happy DFM-001 parameter -> the fab-tier minimum it is held to
|
|
153
|
+
DFM_LIMITS = {"track_spacing": "min_clearance", "track_width": "min_track", "via_drill": "min_drill",
|
|
154
|
+
"pad_drill": "min_drill", "annular_ring": "annular_ring"}
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def dfm_for_fab(pcb, limits):
|
|
158
|
+
"""Re-judge kicad-happy's DFM tier against the board's own fab tier. Its 'standard' limits
|
|
159
|
+
are JLCPCB's 1-2 layer ones (0.127 mm spacing), so a 4-layer board at JLCPCB's 0.09 mm
|
|
160
|
+
multilayer minimum was reported as needing the 'advanced' process. Findings within the
|
|
161
|
+
tier are dropped; the tier becomes 'standard' (meaning: this fab tier) when none are left.
|
|
162
|
+
Returns the parameters dropped."""
|
|
163
|
+
if not limits:
|
|
164
|
+
return []
|
|
165
|
+
dropped, keep = [], []
|
|
166
|
+
for f in pcb.get("findings", []):
|
|
167
|
+
par = f.get("parameter") if f.get("rule_id") == "DFM-001" else None
|
|
168
|
+
lim = limits.get(DFM_LIMITS.get(par, ""))
|
|
169
|
+
if lim is not None and isinstance(f.get("actual_mm"), (int, float)) and f["actual_mm"] >= lim - 1e-6:
|
|
170
|
+
dropped.append(par)
|
|
171
|
+
continue
|
|
172
|
+
keep.append(f)
|
|
173
|
+
pcb["findings"] = keep
|
|
174
|
+
left = [f for f in keep if f.get("rule_id") == "DFM-001" and f.get("parameter") in DFM_LIMITS]
|
|
175
|
+
dfm = pcb.setdefault("dfm_summary", {})
|
|
176
|
+
if dropped and not left:
|
|
177
|
+
dfm["dfm_tier"] = "standard"
|
|
178
|
+
dfm["judged_against"] = limits.get("name")
|
|
179
|
+
return dropped
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def routing_from_drc(pcb, drc_report):
|
|
183
|
+
"""Replace kicad-happy's routing status with KiCad's: kicad-happy counts a net as routed
|
|
184
|
+
once it has any copper, so a partly routed net passes. Nets come from the DRC report's
|
|
185
|
+
unconnected items (``Pad 1 [NET] of U1``)."""
|
|
186
|
+
nets = sorted({i["description"].split("[", 1)[1].split("]", 1)[0]
|
|
187
|
+
for u in drc_report.get("unconnected_items", []) for i in u.get("items", [])
|
|
188
|
+
if "[" in i.get("description", "")})
|
|
189
|
+
conn = pcb.setdefault("connectivity", {})
|
|
190
|
+
conn["unrouted_count"] = len(nets)
|
|
191
|
+
conn["routing_complete"] = not nets
|
|
192
|
+
conn["unrouted"] = [{"net_name": n, "source": "kicad-cli DRC"} for n in nets]
|
|
193
|
+
return nets
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
@register("release_gate", "kicad_happy")
|
|
197
|
+
def _gate(ctx):
|
|
198
|
+
# The gate compares schematic parts with *all* PCB footprints; board-only ones
|
|
199
|
+
# (fiducials, mounting holes) have no schematic symbol, and the schematic count leaves
|
|
200
|
+
# out DNP parts, so both are left out of the count.
|
|
201
|
+
# Its DFM tier is re-judged against the board's fab tier (dfm_for_fab).
|
|
202
|
+
with open(ctx.outputs["pcb"]) as f:
|
|
203
|
+
pcb = json.load(f)
|
|
204
|
+
fps = pcb.get("footprints", [])
|
|
205
|
+
pcb.setdefault("statistics", {})["footprint_count"] = sum(
|
|
206
|
+
1 for fp in fps if not fp.get("board_only") and not fp.get("dnp"))
|
|
207
|
+
if ctx.drc_report and os.path.exists(ctx.drc_report):
|
|
208
|
+
with open(ctx.drc_report) as f:
|
|
209
|
+
routing_from_drc(pcb, json.load(f))
|
|
210
|
+
dfm_for_fab(pcb, ctx.fab_limits)
|
|
211
|
+
pcb_gate = ctx.out("pcb_for_gate")
|
|
212
|
+
with open(pcb_gate, "w") as f:
|
|
213
|
+
json.dump(pcb, f)
|
|
214
|
+
args = ["-s", ctx.outputs["schematic"], "-p", pcb_gate, "-o", ctx.out("release_gate")]
|
|
215
|
+
for step, flag in (("gerbers", "-g"), ("thermal", "-t"), ("emc", "-e")):
|
|
216
|
+
if step in ctx.outputs:
|
|
217
|
+
args += [flag, ctx.outputs[step]]
|
|
218
|
+
_vendor("kicad/scripts/fab_release_gate.py", *args)
|
|
219
|
+
return ctx.out("release_gate")
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def summarize(path, top=5):
|
|
223
|
+
"""Counts by severity plus the first few errors, from kicad-happy's common envelope."""
|
|
224
|
+
with open(path) as f:
|
|
225
|
+
d = json.load(f)
|
|
226
|
+
s = d.get("summary", {}) if isinstance(d, dict) else {}
|
|
227
|
+
out = {"by_severity": s.get("by_severity")}
|
|
228
|
+
findings = [x for x in d.get("findings", []) if not x.get("suppressed")]
|
|
229
|
+
errs = [x for x in findings if x.get("severity") == "error"]
|
|
230
|
+
out["errors"] = [f"{x.get('rule_id', '?')}: {(x.get('summary') or x.get('title') or '')[:90]}"
|
|
231
|
+
for x in errs[:top]]
|
|
232
|
+
if "overall_status" in d: # release gate
|
|
233
|
+
out = {"status": d["overall_status"], **{k: d.get("summary", {}).get(k)
|
|
234
|
+
for k in ("pass", "warn", "fail", "skip")}}
|
|
235
|
+
out["not_passed"] = [f"{c.get('status')}: {c.get('check_id')}: {c.get('message', '')[:80]}"
|
|
236
|
+
for c in d.get("checks", []) if c.get("status") in ("warn", "fail")][:top]
|
|
237
|
+
return out
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
def run_steps(ctx, steps, choice=None):
|
|
241
|
+
"""Run the named steps in dependency order. A failure is recorded and later steps that
|
|
242
|
+
need its output are skipped. Returns {step: summary}."""
|
|
243
|
+
choice = choice or {}
|
|
244
|
+
results = {}
|
|
245
|
+
for step in [s for s in ORDER if s in steps]:
|
|
246
|
+
impl = choice.get(step, "kicad_happy")
|
|
247
|
+
fn = REGISTRY.get(step, {}).get(impl)
|
|
248
|
+
if fn is None:
|
|
249
|
+
results[step] = {"error": f"no implementation {impl!r}"}
|
|
250
|
+
continue
|
|
251
|
+
try:
|
|
252
|
+
path = fn(ctx)
|
|
253
|
+
ctx.outputs[step] = path
|
|
254
|
+
results[step] = summarize(path)
|
|
255
|
+
except KeyError as e:
|
|
256
|
+
results[step] = {"skipped": f"needs {e.args[0]} output"}
|
|
257
|
+
except Exception as e: # noqa: BLE001 - recorded, run continues
|
|
258
|
+
results[step] = {"error": f"{type(e).__name__}: {str(e)[:300]}"}
|
|
259
|
+
return results
|
boardforge/cli.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
"""boardforge command line.
|
|
2
|
+
|
|
3
|
+
boardforge run [-C DIR] [--from c3] [--to c6] physical pipeline C1-C6 (or a range)
|
|
4
|
+
boardforge sch|board|place|route|check|fab one stage (C1 ... C6)
|
|
5
|
+
boardforge review B4 gate: constraints.json against the design
|
|
6
|
+
boardforge datasheets status|sync|add|check|show|publish|paths
|
|
7
|
+
part-fact cache (datasheet PDFs, extractions)
|
|
8
|
+
boardforge render [--side top|bottom|both] pictures of the current board (to check placement)
|
|
9
|
+
boardforge status stage results from pipeline_state.json
|
|
10
|
+
boardforge doctor check KiCad, Freerouting, config
|
|
11
|
+
|
|
12
|
+
Output is a short summary per stage; details are in <project>/build/reports/.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import argparse
|
|
16
|
+
import json
|
|
17
|
+
import os
|
|
18
|
+
import sys
|
|
19
|
+
|
|
20
|
+
from boardforge import __version__, fabs, routers
|
|
21
|
+
from boardforge.config import Project
|
|
22
|
+
from boardforge.state import STAGES, TITLES, State
|
|
23
|
+
|
|
24
|
+
ALIASES = {"sch": "c1", "board": "c2", "place": "c3", "route": "c4", "check": "c5", "fab": "c6"}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def _print_stage(res, verbose):
|
|
28
|
+
s = res["summary"]
|
|
29
|
+
print(f"[{res['stage']}] {TITLES[res['stage']]}: {res['status']} ({res['seconds']} s)")
|
|
30
|
+
text = json.dumps(s, indent=1 if verbose else None, default=str)
|
|
31
|
+
print(" " + (text if verbose else text[:600] + ("..." if len(text) > 600 else "")))
|
|
32
|
+
for w in res["warnings"][:12]:
|
|
33
|
+
print(f" ! {w}")
|
|
34
|
+
if len(res["warnings"]) > 12:
|
|
35
|
+
print(f" ! ... {len(res['warnings']) - 12} more (pipeline_state.json)")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def cmd_run(args, stages):
|
|
39
|
+
from boardforge.physical import Pipeline
|
|
40
|
+
|
|
41
|
+
from boardforge.physical import StageError
|
|
42
|
+
|
|
43
|
+
pipe = Pipeline(Project.load(args.dir))
|
|
44
|
+
for w in pipe.k.warnings:
|
|
45
|
+
print(f"! {w}")
|
|
46
|
+
failed = False
|
|
47
|
+
try:
|
|
48
|
+
results = pipe.run(stages)
|
|
49
|
+
except StageError as e:
|
|
50
|
+
print(f"! {e}")
|
|
51
|
+
return 1
|
|
52
|
+
for res in results:
|
|
53
|
+
_print_stage(res, args.verbose)
|
|
54
|
+
failed |= res["status"] == "failed"
|
|
55
|
+
return 1 if failed else 0
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def cmd_review(args):
|
|
59
|
+
from boardforge.review.gate import review
|
|
60
|
+
|
|
61
|
+
rep = review(Project.load(args.dir))
|
|
62
|
+
print(f"[review] {rep['status']}: {rep['counts']} (reports/review.json)")
|
|
63
|
+
for r in rep["results"]:
|
|
64
|
+
detail = (f"{r['gain_db']} dB at {r['at_hz']:g} Hz, limit {r['limit']}" if "gain_db" in r
|
|
65
|
+
else r["why"] if r["status"] == "NOT CHECKED" and r.get("why")
|
|
66
|
+
else ",".join(r.get("refs", [])) or r.get("why", ""))
|
|
67
|
+
print(f" {r['status']:11} {r['tag']:7} {r['id']}: {detail}")
|
|
68
|
+
for f in r.get("findings", []):
|
|
69
|
+
if args.verbose or f["severity"] != "info":
|
|
70
|
+
print(f" {f['severity']}: {f['detail']}")
|
|
71
|
+
if args.verbose:
|
|
72
|
+
for n in r.get("notes", []):
|
|
73
|
+
print(f" {n}")
|
|
74
|
+
for p in rep["problems"]:
|
|
75
|
+
print(f" ! {p}")
|
|
76
|
+
return 1 if rep["status"] == "FAIL" else 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def cmd_datasheets(args):
|
|
80
|
+
import os
|
|
81
|
+
|
|
82
|
+
from boardforge import datasheets as ds
|
|
83
|
+
|
|
84
|
+
proj = Project.load(args.dir)
|
|
85
|
+
try:
|
|
86
|
+
if args.action == "status":
|
|
87
|
+
rows = ds.status(proj)
|
|
88
|
+
shown = [r for r in rows if r["wanted"] or args.all]
|
|
89
|
+
print(f"{'MPN':28} {'refs':14} pdf extracted quality cache")
|
|
90
|
+
for r in shown:
|
|
91
|
+
refs = ",".join(r["refs"][:3]) + ("..." if len(r["refs"]) > 3 else "")
|
|
92
|
+
ext = ("stale" if r["stale"] else "yes") if r["extracted"] else "-"
|
|
93
|
+
print(f"{r['mpn'][:28]:28} {refs[:14]:14} {'yes' if r['pdf'] else '-':3} "
|
|
94
|
+
f"{ext:9} {r['quality'] if r['quality'] is not None else '-':7} "
|
|
95
|
+
f"{'yes' if r['in_cache'] else '-'}")
|
|
96
|
+
no_pdf = sum(not r["pdf"] for r in shown)
|
|
97
|
+
todo = sum(r["pdf"] and (not r["extracted"] or bool(r["stale"])) for r in shown)
|
|
98
|
+
print(f"{len(shown)} parts ({len(rows) - len(shown)} passives etc. hidden; --all shows them): "
|
|
99
|
+
f"{no_pdf} without a PDF, {todo} to extract")
|
|
100
|
+
return 0
|
|
101
|
+
if args.action == "paths":
|
|
102
|
+
project, shared = ds.stores(proj)
|
|
103
|
+
print(f"project: {project.root}\nshared cache: {shared.root if shared else '(none)'}\n"
|
|
104
|
+
f"prompts: {os.path.join(ds.SKILL, 'prompts')}\nschemas: {ds.SCHEMAS}\n"
|
|
105
|
+
f"references: {os.path.join(ds.SKILL, 'references')}\n"
|
|
106
|
+
f"examples: {os.path.join(ds.SKILL, 'examples')}")
|
|
107
|
+
return 0
|
|
108
|
+
if args.action in ("add", "check", "show") and not args.mpn:
|
|
109
|
+
raise ValueError(f"datasheets {args.action} needs MPNs")
|
|
110
|
+
if args.action == "add":
|
|
111
|
+
if len(args.mpn) != 2:
|
|
112
|
+
raise ValueError("datasheets add MPN FILE_OR_URL")
|
|
113
|
+
print(f" added {ds.add(proj, args.mpn[0], args.mpn[1], force=args.force)}")
|
|
114
|
+
return 0
|
|
115
|
+
if args.action == "check":
|
|
116
|
+
bad = 0
|
|
117
|
+
for r in ds.check(proj, args.mpn):
|
|
118
|
+
ok = not r["problems"]
|
|
119
|
+
bad += not ok
|
|
120
|
+
print(f"{r['mpn']}: {'ok' if ok else 'INVALID'}, quality {r['quality']}")
|
|
121
|
+
for x in r["problems"]:
|
|
122
|
+
print(f" ! schema {x}")
|
|
123
|
+
for x in r["issues"]:
|
|
124
|
+
print(f" ? {x}")
|
|
125
|
+
return 1 if bad else 0
|
|
126
|
+
if args.action == "show":
|
|
127
|
+
project, shared = ds.stores(proj)
|
|
128
|
+
for mpn in args.mpn:
|
|
129
|
+
ex = project.extraction(mpn) or (shared.extraction(mpn) if shared else None)
|
|
130
|
+
print(ds.summary(ex) if ex else f"! {mpn}: no extraction (run the librarian)")
|
|
131
|
+
return 0
|
|
132
|
+
if args.action == "sync":
|
|
133
|
+
res = ds.sync(proj, fresh=args.fresh, download=not args.offline)
|
|
134
|
+
else:
|
|
135
|
+
res = ds.publish(proj, force=args.force)
|
|
136
|
+
except (FileNotFoundError, ValueError) as e:
|
|
137
|
+
print(f"! {e}")
|
|
138
|
+
return 1
|
|
139
|
+
for k, v in res.items():
|
|
140
|
+
print(f" {k}: {len(v)}{' ' + ', '.join(v) if v else ''}")
|
|
141
|
+
return 0
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def cmd_render(args):
|
|
145
|
+
from boardforge.kicad import Kicad
|
|
146
|
+
from boardforge.render import render
|
|
147
|
+
|
|
148
|
+
proj = Project.load(args.dir)
|
|
149
|
+
sides = ("top", "bottom") if args.side == "both" else (args.side,)
|
|
150
|
+
try:
|
|
151
|
+
for f in render(proj, Kicad.find(proj.cfg["kicad"]), sides, pours=args.pours):
|
|
152
|
+
print(f" {os.path.relpath(f, proj.root)}")
|
|
153
|
+
except FileNotFoundError as e:
|
|
154
|
+
print(f"! {e}")
|
|
155
|
+
return 1
|
|
156
|
+
return 0
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def cmd_status(args):
|
|
160
|
+
proj = Project.load(args.dir)
|
|
161
|
+
st = State(proj.state_file)
|
|
162
|
+
for sid in STAGES:
|
|
163
|
+
s = st.stage(sid)
|
|
164
|
+
if s:
|
|
165
|
+
print(f"[{sid}] {TITLES[sid]}: {s['status']} ({s.get('seconds')} s, {s.get('finished')}), "
|
|
166
|
+
f"{len(s.get('warnings', []))} warnings")
|
|
167
|
+
else:
|
|
168
|
+
print(f"[{sid}] {TITLES[sid]}: not run")
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def cmd_doctor(args):
|
|
173
|
+
from boardforge.kicad import Kicad, KicadError
|
|
174
|
+
|
|
175
|
+
ok = True
|
|
176
|
+
try:
|
|
177
|
+
proj = Project.load(args.dir)
|
|
178
|
+
cfg = proj.cfg
|
|
179
|
+
print(f"config: {proj.root}/boardforge.toml (design {cfg['design']['file']})")
|
|
180
|
+
except (ValueError, FileNotFoundError) as e:
|
|
181
|
+
print(f"config: {e} (checking machine settings only)")
|
|
182
|
+
from boardforge.config import DEFAULTS
|
|
183
|
+
|
|
184
|
+
cfg = DEFAULTS
|
|
185
|
+
try:
|
|
186
|
+
k = Kicad.find(cfg["kicad"])
|
|
187
|
+
print(f"kicad-cli: {k.cli} ({k.version})")
|
|
188
|
+
print(f"KiCad python: {k.python}")
|
|
189
|
+
print(f"symbols: {k.symbol_dir}\nfootprints: {k.footprint_dir}")
|
|
190
|
+
for w in k.warnings:
|
|
191
|
+
print(f"! {w}")
|
|
192
|
+
ok = False
|
|
193
|
+
except KicadError as e:
|
|
194
|
+
print(f"! {e}")
|
|
195
|
+
ok = False
|
|
196
|
+
for name in cfg["routing"]["chain"]:
|
|
197
|
+
good, msg = routers.get(name).available(cfg)
|
|
198
|
+
print(f"router {name}: {'ok' if good else 'MISSING'} {msg}")
|
|
199
|
+
ok &= good
|
|
200
|
+
from boardforge.review.spice_path import find_ngspice
|
|
201
|
+
|
|
202
|
+
ng = find_ngspice(cfg.get("review", {}).get("ngspice", ""))
|
|
203
|
+
print(f"ngspice: {ng or 'MISSING (the review gate reports SPICE checks as not checked)'}")
|
|
204
|
+
try:
|
|
205
|
+
f = fabs.get(cfg["fab"]["name"], cfg["fab"].get("tier"))
|
|
206
|
+
print(f"fab: {f.name} tier {f.tier_name}")
|
|
207
|
+
except ValueError as e:
|
|
208
|
+
print(f"! {e}")
|
|
209
|
+
ok = False
|
|
210
|
+
# Look the packages up without importing them: importing SKiDL creates .log/.erc files in
|
|
211
|
+
# the current directory.
|
|
212
|
+
from importlib import metadata, util
|
|
213
|
+
|
|
214
|
+
for pkg in ("skidl", "schematizer"):
|
|
215
|
+
if util.find_spec(pkg) is None:
|
|
216
|
+
print(f"! {pkg} is not installed")
|
|
217
|
+
ok = False
|
|
218
|
+
continue
|
|
219
|
+
try:
|
|
220
|
+
print(f"{pkg} {metadata.version(pkg)}")
|
|
221
|
+
except metadata.PackageNotFoundError:
|
|
222
|
+
print(f"{pkg} (version unknown)")
|
|
223
|
+
import os
|
|
224
|
+
import shutil
|
|
225
|
+
|
|
226
|
+
cache = cfg.get("datasheets", {}).get("cache", "")
|
|
227
|
+
print(f"datasheet cache: {os.path.expanduser(cache) if cache else '(none)'}")
|
|
228
|
+
print(f"pdftotext: {shutil.which('pdftotext') or 'MISSING (downloaded PDFs are not identity-checked)'}")
|
|
229
|
+
if util.find_spec("jsonschema") is None:
|
|
230
|
+
print("jsonschema: MISSING (datasheets publish can't validate extractions; "
|
|
231
|
+
"pip install 'boardforge[analysis]')")
|
|
232
|
+
return 0 if ok else 1
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def main(argv=None):
|
|
236
|
+
ap = argparse.ArgumentParser(prog="boardforge", description=__doc__,
|
|
237
|
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
238
|
+
ap.add_argument("--version", action="version", version=__version__)
|
|
239
|
+
ap.add_argument("-C", "--dir", default=".", help="project directory (has boardforge.toml)")
|
|
240
|
+
ap.add_argument("-v", "--verbose", action="store_true", help="full stage summaries")
|
|
241
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
242
|
+
r = sub.add_parser("run", help="run the physical pipeline")
|
|
243
|
+
r.add_argument("--from", dest="start", default="c1", choices=STAGES)
|
|
244
|
+
r.add_argument("--to", dest="end", default="c6", choices=STAGES)
|
|
245
|
+
for alias, sid in ALIASES.items():
|
|
246
|
+
sub.add_parser(alias, help=f"stage {sid}: {TITLES[sid]}")
|
|
247
|
+
sub.add_parser("review", help="B4 gate: check constraints.json")
|
|
248
|
+
d = sub.add_parser("datasheets", help="part-fact cache: status, sync, add, check, show, publish, paths")
|
|
249
|
+
d.add_argument("action", choices=("status", "sync", "add", "check", "show", "publish", "paths"))
|
|
250
|
+
d.add_argument("mpn", nargs="*", help="add: MPN FILE_OR_URL; check/show: MPNs")
|
|
251
|
+
d.add_argument("--all", action="store_true", help="status: include passives")
|
|
252
|
+
d.add_argument("--fresh", action="store_true", help="sync: don't copy from the shared cache")
|
|
253
|
+
d.add_argument("--offline", action="store_true", help="sync: don't download PDFs")
|
|
254
|
+
d.add_argument("--force", action="store_true",
|
|
255
|
+
help="publish: replace shared entries; add: skip the part-name check")
|
|
256
|
+
r2 = sub.add_parser("render", help="SVG/PNG of the current board (placed or routed)")
|
|
257
|
+
r2.add_argument("--side", choices=("top", "bottom", "both"), default="top")
|
|
258
|
+
r2.add_argument("--pours", action="store_true", help="keep copper pours (hide tracks under them)")
|
|
259
|
+
sub.add_parser("status", help="show stage results")
|
|
260
|
+
sub.add_parser("doctor", help="check the environment")
|
|
261
|
+
args = ap.parse_args(argv)
|
|
262
|
+
|
|
263
|
+
if args.cmd == "run":
|
|
264
|
+
i, j = STAGES.index(args.start), STAGES.index(args.end)
|
|
265
|
+
return cmd_run(args, STAGES[i:j + 1])
|
|
266
|
+
if args.cmd in ALIASES:
|
|
267
|
+
return cmd_run(args, (ALIASES[args.cmd],))
|
|
268
|
+
if args.cmd == "status":
|
|
269
|
+
return cmd_status(args)
|
|
270
|
+
if args.cmd == "render":
|
|
271
|
+
return cmd_render(args)
|
|
272
|
+
if args.cmd == "review":
|
|
273
|
+
return cmd_review(args)
|
|
274
|
+
if args.cmd == "datasheets":
|
|
275
|
+
return cmd_datasheets(args)
|
|
276
|
+
return cmd_doctor(args)
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
if __name__ == "__main__":
|
|
280
|
+
sys.exit(main())
|