kicad-sch-api 0.3.4__tar.gz → 0.4.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of kicad-sch-api might be problematic. Click here for more details.
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/CHANGELOG.md +65 -0
- {kicad_sch_api-0.3.4/kicad_sch_api.egg-info → kicad_sch_api-0.4.0}/PKG-INFO +1 -1
- kicad_sch_api-0.4.0/examples/parser_demo.py +93 -0
- kicad_sch_api-0.4.0/kicad_sch_api/collections/__init__.py +21 -0
- kicad_sch_api-0.4.0/kicad_sch_api/collections/base.py +294 -0
- kicad_sch_api-0.4.0/kicad_sch_api/collections/components.py +434 -0
- kicad_sch_api-0.4.0/kicad_sch_api/collections/junctions.py +366 -0
- kicad_sch_api-0.4.0/kicad_sch_api/collections/labels.py +404 -0
- kicad_sch_api-0.4.0/kicad_sch_api/collections/wires.py +406 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/components.py +5 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/formatter.py +3 -1
- kicad_sch_api-0.4.0/kicad_sch_api/core/labels.py +348 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/managers/__init__.py +26 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/managers/file_io.py +243 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/managers/format_sync.py +501 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/managers/graphics.py +579 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/managers/metadata.py +268 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/managers/sheet.py +454 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/managers/text_elements.py +536 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/managers/validation.py +474 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/managers/wire.py +346 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/nets.py +310 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/no_connects.py +276 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/parser.py +75 -41
- kicad_sch_api-0.4.0/kicad_sch_api/core/schematic.py +1584 -0
- kicad_sch_api-0.4.0/kicad_sch_api/core/texts.py +343 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/types.py +13 -4
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/geometry/font_metrics.py +3 -1
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/geometry/symbol_bbox.py +56 -43
- kicad_sch_api-0.4.0/kicad_sch_api/interfaces/__init__.py +17 -0
- kicad_sch_api-0.4.0/kicad_sch_api/interfaces/parser.py +76 -0
- kicad_sch_api-0.4.0/kicad_sch_api/interfaces/repository.py +70 -0
- kicad_sch_api-0.4.0/kicad_sch_api/interfaces/resolver.py +117 -0
- kicad_sch_api-0.4.0/kicad_sch_api/parsers/__init__.py +14 -0
- kicad_sch_api-0.4.0/kicad_sch_api/parsers/base.py +145 -0
- kicad_sch_api-0.4.0/kicad_sch_api/parsers/label_parser.py +254 -0
- kicad_sch_api-0.4.0/kicad_sch_api/parsers/registry.py +155 -0
- kicad_sch_api-0.4.0/kicad_sch_api/parsers/symbol_parser.py +222 -0
- kicad_sch_api-0.4.0/kicad_sch_api/parsers/wire_parser.py +99 -0
- kicad_sch_api-0.4.0/kicad_sch_api/symbols/__init__.py +18 -0
- kicad_sch_api-0.4.0/kicad_sch_api/symbols/cache.py +467 -0
- kicad_sch_api-0.4.0/kicad_sch_api/symbols/resolver.py +361 -0
- kicad_sch_api-0.4.0/kicad_sch_api/symbols/validators.py +504 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0/kicad_sch_api.egg-info}/PKG-INFO +1 -1
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api.egg-info/SOURCES.txt +35 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/pyproject.toml +1 -1
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_sch +6 -1
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_geometry.py +5 -4
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_image_support.py +15 -30
- kicad_sch_api-0.4.0/tests/test_issue_13_public_properties.py +406 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_kicad_validation.py +4 -3
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_parse_reference_rectangles.py +5 -3
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_rectangle.py +6 -9
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_rectangle_roundtrip.py +8 -6
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_wire_operations.py +9 -3
- kicad_sch_api-0.3.4/kicad_sch_api/core/schematic.py +0 -1754
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/.claude/commands/dev/dead-code-analysis.md +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/.claude/commands/dev/publish-pypi.md +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/.claude/commands/dev/review-implementation.md +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/.claude/commands/dev/run-tests.md +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/.claude/commands/dev/update-and-commit.md +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/.claude/commands/dev/update-memory-bank.md +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/.claude/commands/test/run-reference-tests.md +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/LICENSE +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/MANIFEST.in +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/README.md +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/examples/advanced_usage.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/examples/basic_usage.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/examples/mcp_basic_example.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/examples/mcp_integration.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/examples/pin_to_pin_wiring_demo.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/examples/simple_circuit_with_pin_wiring.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/examples/simple_two_resistor_routing.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/__init__.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/cli.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/__init__.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/component_bounds.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/config.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/geometry.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/ic_manager.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/junctions.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/manhattan_routing.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/pin_utils.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/simple_manhattan.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/wire_routing.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/core/wires.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/discovery/__init__.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/discovery/search_index.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/geometry/__init__.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/library/__init__.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/library/cache.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/py.typed +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/utils/__init__.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api/utils/validation.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api.egg-info/dependency_links.txt +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api.egg-info/entry_points.txt +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api.egg-info/requires.txt +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/kicad_sch_api.egg-info/top_level.txt +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/setup.cfg +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/README.md +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/subcircuit1.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_pro +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_sch +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_bounding_box_rectangles.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_component_removal.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_element_removal.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_grid_snapping.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_manhattan_routing.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_pin_positioning.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_pin_to_pin_wiring.py +0 -0
- {kicad_sch_api-0.3.4 → kicad_sch_api-0.4.0}/tests/test_removal_against_references.py +0 -0
|
@@ -5,6 +5,71 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.4.0] - 2025-10-26
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
- **Phase 4 Manager Architecture**: Complete refactoring of schematic data management
|
|
12
|
+
- Introduced `ComponentManager` for component operations and lib_symbols synchronization
|
|
13
|
+
- Introduced `WireManager` for wire and bus operations
|
|
14
|
+
- Introduced `JunctionManager` for junction operations
|
|
15
|
+
- Introduced `SheetManager` for hierarchical sheet management
|
|
16
|
+
- Introduced `TextElementManager` for label and text operations
|
|
17
|
+
- Introduced `GraphicsManager` for graphical elements (rectangles, circles, arcs, polylines)
|
|
18
|
+
- Composition-based architecture replacing monolithic schematic class
|
|
19
|
+
|
|
20
|
+
- **Text Box Support**: Full text box functionality with complete KiCAD compatibility
|
|
21
|
+
- `add_text_box()` method with rotation, font size, margins, and justification
|
|
22
|
+
- Support for stroke styling and fill options
|
|
23
|
+
- Proper serialization matching KiCAD format exactly
|
|
24
|
+
|
|
25
|
+
- **Enhanced Hierarchical Sheet Support**: Complete sheet management functionality
|
|
26
|
+
- Sheet creation with proper position, size, and styling
|
|
27
|
+
- Sheet pin management with multiple pin types (input, output, bidirectional, tri_state, passive)
|
|
28
|
+
- Sheet hierarchy validation and traversal
|
|
29
|
+
- Proper data structure matching KiCAD parser expectations
|
|
30
|
+
|
|
31
|
+
- **Rectangle Color Support**: Full color support for graphical rectangles
|
|
32
|
+
- Stroke color customization (RGBA)
|
|
33
|
+
- Fill color customization (RGBA)
|
|
34
|
+
- Proper color serialization in S-expression output
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
- **Hierarchical Sheet Data Structure**: Fixed sheet serialization format
|
|
38
|
+
- Changed storage key from "sheet" (singular) to "sheets" (plural)
|
|
39
|
+
- Fixed position format from lists to dictionaries: `{"x": x, "y": y}`
|
|
40
|
+
- Fixed size format from lists to dictionaries: `{"width": w, "height": h}`
|
|
41
|
+
- Fixed fill_color default from white (255,255,255,0.0) to transparent black (0,0,0,0.0)
|
|
42
|
+
- Fixed sheet pin structure: "pin" → "pins", "shape" → "pin_type"
|
|
43
|
+
|
|
44
|
+
- **Rectangle Color Flow**: Fixed color parameter propagation through managers and parser
|
|
45
|
+
- GraphicsManager now extracts and stores stroke_color and fill_color from stroke/fill dicts
|
|
46
|
+
- Parser now serializes colors in stroke and fill S-expression sections
|
|
47
|
+
- Complete color support for bounding box visualization
|
|
48
|
+
|
|
49
|
+
- **Text Box Data Structure**: Fixed text box format to match parser expectations
|
|
50
|
+
- Changed storage key from "text_box" to "text_boxes" (plural)
|
|
51
|
+
- Implemented complete parameter set (rotation, font_size, margins, stroke, fill, justification)
|
|
52
|
+
- Fixed position/size format to use dictionaries instead of lists
|
|
53
|
+
|
|
54
|
+
### Changed
|
|
55
|
+
- **Schematic Class Refactoring**: Delegated operations to specialized managers
|
|
56
|
+
- Schematic class now composes managers instead of implementing all operations
|
|
57
|
+
- Cleaner separation of concerns and improved maintainability
|
|
58
|
+
- All existing APIs maintained for backward compatibility
|
|
59
|
+
|
|
60
|
+
- **Test Suite**: All 302 tests passing (295 run, 7 skipped)
|
|
61
|
+
- Added tests for hierarchical sheets with proper serialization
|
|
62
|
+
- Added tests for rectangle colors with stroke and fill
|
|
63
|
+
- Added tests for text boxes with full parameter support
|
|
64
|
+
- Format preservation tests validate exact KiCAD compatibility
|
|
65
|
+
|
|
66
|
+
### Technical Notes
|
|
67
|
+
- Manager architecture enables easier feature additions and maintenance
|
|
68
|
+
- All data structures now properly synchronized between managers and parser
|
|
69
|
+
- Singular/plural key consistency enforced throughout codebase
|
|
70
|
+
- Position and size formats standardized to dictionary format
|
|
71
|
+
- 100% backward compatibility maintained - no breaking changes
|
|
72
|
+
|
|
8
73
|
## [0.3.0] - 2025-10-12
|
|
9
74
|
|
|
10
75
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kicad-sch-api
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.0
|
|
4
4
|
Summary: Professional KiCAD schematic manipulation library with exact format preservation
|
|
5
5
|
Author-email: Circuit-Synth <shane@circuit-synth.com>
|
|
6
6
|
Maintainer-email: Circuit-Synth <shane@circuit-synth.com>
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Demonstration of the new modular parser system.
|
|
4
|
+
|
|
5
|
+
This example shows how to use the new ElementParserRegistry
|
|
6
|
+
and specialized parsers for different S-expression elements.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import logging
|
|
10
|
+
import sexpdata
|
|
11
|
+
|
|
12
|
+
from kicad_sch_api.parsers.registry import ElementParserRegistry
|
|
13
|
+
from kicad_sch_api.parsers.wire_parser import WireParser
|
|
14
|
+
from kicad_sch_api.parsers.symbol_parser import SymbolParser
|
|
15
|
+
from kicad_sch_api.parsers.label_parser import LabelParser, HierarchicalLabelParser
|
|
16
|
+
|
|
17
|
+
# Enable debug logging to see what's happening
|
|
18
|
+
logging.basicConfig(level=logging.WARNING) # Reduce noise
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def main():
|
|
22
|
+
"""Demonstrate the new parser system."""
|
|
23
|
+
print("🔧 KiCAD Schematic API - New Parser System Demo")
|
|
24
|
+
print("=" * 50)
|
|
25
|
+
|
|
26
|
+
# Create registry and register specialized parsers
|
|
27
|
+
registry = ElementParserRegistry()
|
|
28
|
+
registry.register("wire", WireParser())
|
|
29
|
+
registry.register("symbol", SymbolParser())
|
|
30
|
+
registry.register("label", LabelParser())
|
|
31
|
+
registry.register("hierarchical_label", HierarchicalLabelParser())
|
|
32
|
+
|
|
33
|
+
print(f"📋 Registered parsers: {registry.get_registered_types()}")
|
|
34
|
+
print()
|
|
35
|
+
|
|
36
|
+
# Example S-expression elements (using sexpdata.Symbol for type names)
|
|
37
|
+
test_elements = [
|
|
38
|
+
# Wire element
|
|
39
|
+
[sexpdata.Symbol("wire"),
|
|
40
|
+
[sexpdata.Symbol("pts"), [sexpdata.Symbol("xy"), 100.0, 100.0], [sexpdata.Symbol("xy"), 150.0, 100.0]],
|
|
41
|
+
[sexpdata.Symbol("stroke"), [sexpdata.Symbol("width"), 0.0], [sexpdata.Symbol("type"), sexpdata.Symbol("default")]],
|
|
42
|
+
[sexpdata.Symbol("uuid"), "12345678-1234-5678-9abc-123456789abc"]],
|
|
43
|
+
|
|
44
|
+
# Label element
|
|
45
|
+
[sexpdata.Symbol("label"), "VCC",
|
|
46
|
+
[sexpdata.Symbol("at"), 125.0, 95.0, 0],
|
|
47
|
+
[sexpdata.Symbol("effects"), [sexpdata.Symbol("font"), [sexpdata.Symbol("size"), 1.27, 1.27]]],
|
|
48
|
+
[sexpdata.Symbol("uuid"), "87654321-4321-8765-cba9-876543210987"]],
|
|
49
|
+
|
|
50
|
+
# Unknown element (will be skipped)
|
|
51
|
+
[sexpdata.Symbol("unknown_element"), "some_data"]
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
# Parse elements using the registry
|
|
55
|
+
print("🔍 Parsing S-expression elements:")
|
|
56
|
+
print()
|
|
57
|
+
|
|
58
|
+
for i, element in enumerate(test_elements, 1):
|
|
59
|
+
element_type = element[0] if element else "unknown"
|
|
60
|
+
print(f"Element {i}: {element_type}")
|
|
61
|
+
|
|
62
|
+
result = registry.parse_element(element)
|
|
63
|
+
if result:
|
|
64
|
+
print(f" ✅ Successfully parsed")
|
|
65
|
+
print(f" 📊 Result keys: {list(result.keys())}")
|
|
66
|
+
if "points" in result:
|
|
67
|
+
print(f" 📍 Points: {len(result['points'])} points")
|
|
68
|
+
if "text" in result:
|
|
69
|
+
print(f" 📝 Text: '{result['text']}'")
|
|
70
|
+
else:
|
|
71
|
+
print(f" ❌ No parser available")
|
|
72
|
+
print()
|
|
73
|
+
|
|
74
|
+
# Demonstrate batch parsing
|
|
75
|
+
print("📦 Batch parsing multiple elements:")
|
|
76
|
+
results = registry.parse_elements(test_elements)
|
|
77
|
+
print(f" ✅ Successfully parsed {len(results)} of {len(test_elements)} elements")
|
|
78
|
+
|
|
79
|
+
# Show parsing statistics
|
|
80
|
+
successful_types = [r.get("type", "unknown") for r in results if "type" in r]
|
|
81
|
+
print(f" 📊 Parsed types: {set(successful_types)}")
|
|
82
|
+
|
|
83
|
+
print()
|
|
84
|
+
print("✨ New parser system benefits:")
|
|
85
|
+
print(" • Modular and testable parsers")
|
|
86
|
+
print(" • Easy to add new element types")
|
|
87
|
+
print(" • Clear separation of concerns")
|
|
88
|
+
print(" • Comprehensive error handling")
|
|
89
|
+
print(" • Extensible with fallback parsers")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
if __name__ == "__main__":
|
|
93
|
+
main()
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Modern collection architecture for KiCAD schematic elements.
|
|
3
|
+
|
|
4
|
+
This module provides a unified collection framework that eliminates code duplication
|
|
5
|
+
across component, wire, and junction collections while providing consistent
|
|
6
|
+
indexing, performance optimization, and management capabilities.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from .base import IndexedCollection
|
|
10
|
+
from .components import ComponentCollection
|
|
11
|
+
from .junctions import JunctionCollection
|
|
12
|
+
from .labels import LabelCollection
|
|
13
|
+
from .wires import WireCollection
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"IndexedCollection",
|
|
17
|
+
"ComponentCollection",
|
|
18
|
+
"WireCollection",
|
|
19
|
+
"JunctionCollection",
|
|
20
|
+
"LabelCollection",
|
|
21
|
+
]
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base collection class for unified schematic element management.
|
|
3
|
+
|
|
4
|
+
Provides common functionality for indexing, searching, and managing
|
|
5
|
+
collections of schematic elements with performance optimization.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import logging
|
|
9
|
+
from abc import ABC, abstractmethod
|
|
10
|
+
from typing import Any, Callable, Dict, Generic, Iterator, List, Optional, TypeVar, Union
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
T = TypeVar("T") # Type variable for collection items
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class IndexedCollection(Generic[T], ABC):
|
|
18
|
+
"""
|
|
19
|
+
Base class for all schematic element collections with automatic indexing.
|
|
20
|
+
|
|
21
|
+
Provides unified functionality for:
|
|
22
|
+
- UUID-based fast lookups
|
|
23
|
+
- Automatic index management
|
|
24
|
+
- Modification tracking
|
|
25
|
+
- Consistent iteration interface
|
|
26
|
+
- Performance optimization for large collections
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
def __init__(self, items: Optional[List[T]] = None):
|
|
30
|
+
"""
|
|
31
|
+
Initialize indexed collection.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
items: Initial list of items to add to collection
|
|
35
|
+
"""
|
|
36
|
+
self._items: List[T] = []
|
|
37
|
+
self._uuid_index: Dict[str, int] = {}
|
|
38
|
+
self._modified = False
|
|
39
|
+
self._dirty_indexes = False
|
|
40
|
+
|
|
41
|
+
# Add initial items if provided
|
|
42
|
+
if items:
|
|
43
|
+
for item in items:
|
|
44
|
+
self._add_item_to_collection(item)
|
|
45
|
+
|
|
46
|
+
logger.debug(f"{self.__class__.__name__} initialized with {len(self._items)} items")
|
|
47
|
+
|
|
48
|
+
# Abstract methods for subclasses to implement
|
|
49
|
+
@abstractmethod
|
|
50
|
+
def _get_item_uuid(self, item: T) -> str:
|
|
51
|
+
"""
|
|
52
|
+
Extract UUID from an item.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
item: Item to extract UUID from
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
UUID string for the item
|
|
59
|
+
"""
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
@abstractmethod
|
|
63
|
+
def _create_item(self, **kwargs) -> T:
|
|
64
|
+
"""
|
|
65
|
+
Create a new item with given parameters.
|
|
66
|
+
|
|
67
|
+
Args:
|
|
68
|
+
**kwargs: Parameters for item creation
|
|
69
|
+
|
|
70
|
+
Returns:
|
|
71
|
+
Newly created item
|
|
72
|
+
"""
|
|
73
|
+
pass
|
|
74
|
+
|
|
75
|
+
@abstractmethod
|
|
76
|
+
def _build_additional_indexes(self) -> None:
|
|
77
|
+
"""
|
|
78
|
+
Build any additional indexes specific to the collection type.
|
|
79
|
+
|
|
80
|
+
Called after UUID index is rebuilt. Subclasses should implement
|
|
81
|
+
this to maintain their own specialized indexes.
|
|
82
|
+
"""
|
|
83
|
+
pass
|
|
84
|
+
|
|
85
|
+
# Core collection operations
|
|
86
|
+
def add(self, item: T) -> T:
|
|
87
|
+
"""
|
|
88
|
+
Add an item to the collection.
|
|
89
|
+
|
|
90
|
+
Args:
|
|
91
|
+
item: Item to add
|
|
92
|
+
|
|
93
|
+
Returns:
|
|
94
|
+
The added item
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
ValueError: If item with same UUID already exists
|
|
98
|
+
"""
|
|
99
|
+
uuid_str = self._get_item_uuid(item)
|
|
100
|
+
|
|
101
|
+
# Ensure indexes are current before checking for duplicates
|
|
102
|
+
self._ensure_indexes_current()
|
|
103
|
+
|
|
104
|
+
# Check for duplicate UUID
|
|
105
|
+
if uuid_str in self._uuid_index:
|
|
106
|
+
raise ValueError(f"Item with UUID {uuid_str} already exists")
|
|
107
|
+
|
|
108
|
+
return self._add_item_to_collection(item)
|
|
109
|
+
|
|
110
|
+
def remove(self, identifier: Union[str, T]) -> bool:
|
|
111
|
+
"""
|
|
112
|
+
Remove an item from the collection.
|
|
113
|
+
|
|
114
|
+
Args:
|
|
115
|
+
identifier: UUID string or item instance to remove
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
True if item was removed, False if not found
|
|
119
|
+
"""
|
|
120
|
+
self._ensure_indexes_current()
|
|
121
|
+
|
|
122
|
+
if isinstance(identifier, str):
|
|
123
|
+
# Remove by UUID
|
|
124
|
+
if identifier not in self._uuid_index:
|
|
125
|
+
return False
|
|
126
|
+
|
|
127
|
+
index = self._uuid_index[identifier]
|
|
128
|
+
item = self._items[index]
|
|
129
|
+
else:
|
|
130
|
+
# Remove by item instance
|
|
131
|
+
item = identifier
|
|
132
|
+
uuid_str = self._get_item_uuid(item)
|
|
133
|
+
if uuid_str not in self._uuid_index:
|
|
134
|
+
return False
|
|
135
|
+
|
|
136
|
+
index = self._uuid_index[uuid_str]
|
|
137
|
+
|
|
138
|
+
# Remove from main list
|
|
139
|
+
self._items.pop(index)
|
|
140
|
+
self._mark_modified()
|
|
141
|
+
self._mark_indexes_dirty()
|
|
142
|
+
|
|
143
|
+
logger.debug(f"Removed item with UUID {self._get_item_uuid(item)}")
|
|
144
|
+
return True
|
|
145
|
+
|
|
146
|
+
def get(self, uuid: str) -> Optional[T]:
|
|
147
|
+
"""
|
|
148
|
+
Get an item by UUID.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
uuid: UUID to search for
|
|
152
|
+
|
|
153
|
+
Returns:
|
|
154
|
+
Item if found, None otherwise
|
|
155
|
+
"""
|
|
156
|
+
self._ensure_indexes_current()
|
|
157
|
+
|
|
158
|
+
if uuid in self._uuid_index:
|
|
159
|
+
index = self._uuid_index[uuid]
|
|
160
|
+
return self._items[index]
|
|
161
|
+
|
|
162
|
+
return None
|
|
163
|
+
|
|
164
|
+
def find(self, predicate: Callable[[T], bool]) -> List[T]:
|
|
165
|
+
"""
|
|
166
|
+
Find all items matching a predicate.
|
|
167
|
+
|
|
168
|
+
Args:
|
|
169
|
+
predicate: Function that returns True for matching items
|
|
170
|
+
|
|
171
|
+
Returns:
|
|
172
|
+
List of matching items
|
|
173
|
+
"""
|
|
174
|
+
return [item for item in self._items if predicate(item)]
|
|
175
|
+
|
|
176
|
+
def filter(self, **criteria) -> List[T]:
|
|
177
|
+
"""
|
|
178
|
+
Filter items by attribute criteria.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
**criteria: Attribute name/value pairs to match
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
List of matching items
|
|
185
|
+
"""
|
|
186
|
+
|
|
187
|
+
def matches_criteria(item: T) -> bool:
|
|
188
|
+
for attr, value in criteria.items():
|
|
189
|
+
if not hasattr(item, attr) or getattr(item, attr) != value:
|
|
190
|
+
return False
|
|
191
|
+
return True
|
|
192
|
+
|
|
193
|
+
return self.find(matches_criteria)
|
|
194
|
+
|
|
195
|
+
def clear(self) -> None:
|
|
196
|
+
"""Clear all items from the collection."""
|
|
197
|
+
self._items.clear()
|
|
198
|
+
self._uuid_index.clear()
|
|
199
|
+
self._mark_modified()
|
|
200
|
+
logger.debug(f"Cleared all items from {self.__class__.__name__}")
|
|
201
|
+
|
|
202
|
+
# Collection interface methods
|
|
203
|
+
def __len__(self) -> int:
|
|
204
|
+
"""Number of items in collection."""
|
|
205
|
+
return len(self._items)
|
|
206
|
+
|
|
207
|
+
def __iter__(self) -> Iterator[T]:
|
|
208
|
+
"""Iterate over items in collection."""
|
|
209
|
+
return iter(self._items)
|
|
210
|
+
|
|
211
|
+
def __contains__(self, item: Union[str, T]) -> bool:
|
|
212
|
+
"""Check if item or UUID is in collection."""
|
|
213
|
+
if isinstance(item, str):
|
|
214
|
+
# Check by UUID
|
|
215
|
+
self._ensure_indexes_current()
|
|
216
|
+
return item in self._uuid_index
|
|
217
|
+
else:
|
|
218
|
+
# Check by item instance
|
|
219
|
+
uuid_str = self._get_item_uuid(item)
|
|
220
|
+
self._ensure_indexes_current()
|
|
221
|
+
return uuid_str in self._uuid_index
|
|
222
|
+
|
|
223
|
+
def __getitem__(self, index: int) -> T:
|
|
224
|
+
"""Get item by index."""
|
|
225
|
+
return self._items[index]
|
|
226
|
+
|
|
227
|
+
# Internal methods
|
|
228
|
+
def _add_item_to_collection(self, item: T) -> T:
|
|
229
|
+
"""
|
|
230
|
+
Internal method to add item to collection.
|
|
231
|
+
|
|
232
|
+
Args:
|
|
233
|
+
item: Item to add
|
|
234
|
+
|
|
235
|
+
Returns:
|
|
236
|
+
The added item
|
|
237
|
+
"""
|
|
238
|
+
self._items.append(item)
|
|
239
|
+
self._mark_modified()
|
|
240
|
+
self._mark_indexes_dirty()
|
|
241
|
+
|
|
242
|
+
logger.debug(f"Added item with UUID {self._get_item_uuid(item)}")
|
|
243
|
+
return item
|
|
244
|
+
|
|
245
|
+
def _mark_modified(self) -> None:
|
|
246
|
+
"""Mark collection as modified."""
|
|
247
|
+
self._modified = True
|
|
248
|
+
|
|
249
|
+
def _mark_indexes_dirty(self) -> None:
|
|
250
|
+
"""Mark indexes as needing rebuild."""
|
|
251
|
+
self._dirty_indexes = True
|
|
252
|
+
|
|
253
|
+
def _ensure_indexes_current(self) -> None:
|
|
254
|
+
"""Ensure all indexes are current."""
|
|
255
|
+
if self._dirty_indexes:
|
|
256
|
+
self._rebuild_indexes()
|
|
257
|
+
|
|
258
|
+
def _rebuild_indexes(self) -> None:
|
|
259
|
+
"""Rebuild all indexes."""
|
|
260
|
+
# Rebuild UUID index
|
|
261
|
+
self._uuid_index = {self._get_item_uuid(item): i for i, item in enumerate(self._items)}
|
|
262
|
+
|
|
263
|
+
# Let subclasses rebuild their additional indexes
|
|
264
|
+
self._build_additional_indexes()
|
|
265
|
+
|
|
266
|
+
self._dirty_indexes = False
|
|
267
|
+
logger.debug(f"Rebuilt indexes for {self.__class__.__name__}")
|
|
268
|
+
|
|
269
|
+
# Collection statistics and debugging
|
|
270
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
271
|
+
"""
|
|
272
|
+
Get collection statistics for debugging and monitoring.
|
|
273
|
+
|
|
274
|
+
Returns:
|
|
275
|
+
Dictionary with collection statistics
|
|
276
|
+
"""
|
|
277
|
+
self._ensure_indexes_current()
|
|
278
|
+
return {
|
|
279
|
+
"item_count": len(self._items),
|
|
280
|
+
"uuid_index_size": len(self._uuid_index),
|
|
281
|
+
"modified": self._modified,
|
|
282
|
+
"indexes_dirty": self._dirty_indexes,
|
|
283
|
+
"collection_type": self.__class__.__name__,
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
@property
|
|
287
|
+
def is_modified(self) -> bool:
|
|
288
|
+
"""Whether collection has been modified."""
|
|
289
|
+
return self._modified
|
|
290
|
+
|
|
291
|
+
def mark_clean(self) -> None:
|
|
292
|
+
"""Mark collection as clean (not modified)."""
|
|
293
|
+
self._modified = False
|
|
294
|
+
logger.debug(f"Marked {self.__class__.__name__} as clean")
|