kicad-sch-api 0.3.2__tar.gz → 0.3.5__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.
- {kicad_sch_api-0.3.2/kicad_sch_api.egg-info → kicad_sch_api-0.3.5}/PKG-INFO +1 -1
- kicad_sch_api-0.3.5/examples/parser_demo.py +93 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/__init__.py +2 -2
- kicad_sch_api-0.3.5/kicad_sch_api/collections/__init__.py +21 -0
- kicad_sch_api-0.3.5/kicad_sch_api/collections/base.py +296 -0
- kicad_sch_api-0.3.5/kicad_sch_api/collections/components.py +422 -0
- kicad_sch_api-0.3.5/kicad_sch_api/collections/junctions.py +378 -0
- kicad_sch_api-0.3.5/kicad_sch_api/collections/labels.py +412 -0
- kicad_sch_api-0.3.5/kicad_sch_api/collections/wires.py +407 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/formatter.py +31 -0
- kicad_sch_api-0.3.5/kicad_sch_api/core/labels.py +348 -0
- kicad_sch_api-0.3.5/kicad_sch_api/core/nets.py +310 -0
- kicad_sch_api-0.3.5/kicad_sch_api/core/no_connects.py +274 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/parser.py +72 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/schematic.py +185 -9
- kicad_sch_api-0.3.5/kicad_sch_api/core/texts.py +343 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/types.py +26 -0
- kicad_sch_api-0.3.5/kicad_sch_api/geometry/__init__.py +26 -0
- kicad_sch_api-0.3.5/kicad_sch_api/geometry/font_metrics.py +20 -0
- kicad_sch_api-0.3.5/kicad_sch_api/geometry/symbol_bbox.py +589 -0
- kicad_sch_api-0.3.5/kicad_sch_api/interfaces/__init__.py +17 -0
- kicad_sch_api-0.3.5/kicad_sch_api/interfaces/parser.py +76 -0
- kicad_sch_api-0.3.5/kicad_sch_api/interfaces/repository.py +70 -0
- kicad_sch_api-0.3.5/kicad_sch_api/interfaces/resolver.py +117 -0
- kicad_sch_api-0.3.5/kicad_sch_api/parsers/__init__.py +14 -0
- kicad_sch_api-0.3.5/kicad_sch_api/parsers/base.py +148 -0
- kicad_sch_api-0.3.5/kicad_sch_api/parsers/label_parser.py +254 -0
- kicad_sch_api-0.3.5/kicad_sch_api/parsers/registry.py +153 -0
- kicad_sch_api-0.3.5/kicad_sch_api/parsers/symbol_parser.py +227 -0
- kicad_sch_api-0.3.5/kicad_sch_api/parsers/wire_parser.py +99 -0
- kicad_sch_api-0.3.5/kicad_sch_api/symbols/__init__.py +18 -0
- kicad_sch_api-0.3.5/kicad_sch_api/symbols/cache.py +470 -0
- kicad_sch_api-0.3.5/kicad_sch_api/symbols/resolver.py +367 -0
- kicad_sch_api-0.3.5/kicad_sch_api/symbols/validators.py +453 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5/kicad_sch_api.egg-info}/PKG-INFO +1 -1
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/SOURCES.txt +31 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/pyproject.toml +1 -1
- kicad_sch_api-0.3.5/tests/test_geometry.py +566 -0
- kicad_sch_api-0.3.5/tests/test_image_support.py +119 -0
- kicad_sch_api-0.3.5/tests/test_issue_13_public_properties.py +412 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/dead-code-analysis.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/publish-pypi.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/review-implementation.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/run-tests.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/update-and-commit.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/dev/update-memory-bank.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/.claude/commands/test/run-reference-tests.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/CHANGELOG.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/LICENSE +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/MANIFEST.in +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/README.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/advanced_usage.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/basic_usage.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/mcp_basic_example.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/mcp_integration.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/pin_to_pin_wiring_demo.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/simple_circuit_with_pin_wiring.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/examples/simple_two_resistor_routing.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/cli.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/__init__.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/component_bounds.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/components.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/config.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/geometry.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/ic_manager.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/junctions.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/manhattan_routing.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/pin_utils.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/simple_manhattan.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/wire_routing.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/core/wires.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/discovery/__init__.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/discovery/search_index.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/library/__init__.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/library/cache.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/py.typed +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/utils/__init__.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api/utils/validation.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/dependency_links.txt +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/entry_points.txt +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/requires.txt +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/kicad_sch_api.egg-info/top_level.txt +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/setup.cfg +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/README.md +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/blank_schematic/blank_schematic.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/multi_unit_7400/multi_unit_7400.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/power_symbols/power_symbols.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/resistor_divider/resistor_divider.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/sch_title/sch_title.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_extended_component/single_extended_component.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/single_hierarchical_sheet.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_hierarchical_sheet/subcircuit1.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_label/single_label.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_label_hierarchical/single_label_hierarchical.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_resistor/single_resistor.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_text/single_text.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_text_box/single_text_box.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/single_wire/single_wire.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_pro +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/reference_tests/reference_kicad_projects/two_resistors/two_resistors.kicad_sch +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_bounding_box_rectangles.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_component_removal.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_element_removal.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_grid_snapping.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_kicad_validation.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_manhattan_routing.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_parse_reference_rectangles.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_pin_positioning.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_pin_to_pin_wiring.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_rectangle.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_rectangle_roundtrip.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_removal_against_references.py +0 -0
- {kicad_sch_api-0.3.2 → kicad_sch_api-0.3.5}/tests/test_wire_operations.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: kicad-sch-api
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.5
|
|
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()
|
|
@@ -42,7 +42,7 @@ Advanced Usage:
|
|
|
42
42
|
print(f"Found {len(issues)} validation issues")
|
|
43
43
|
"""
|
|
44
44
|
|
|
45
|
-
__version__ = "0.3.
|
|
45
|
+
__version__ = "0.3.3"
|
|
46
46
|
__author__ = "Circuit-Synth"
|
|
47
47
|
__email__ = "info@circuit-synth.com"
|
|
48
48
|
|
|
@@ -55,7 +55,7 @@ from .library.cache import SymbolLibraryCache, get_symbol_cache
|
|
|
55
55
|
from .utils.validation import ValidationError, ValidationIssue
|
|
56
56
|
|
|
57
57
|
# Version info
|
|
58
|
-
VERSION_INFO = (0, 3,
|
|
58
|
+
VERSION_INFO = (0, 3, 3)
|
|
59
59
|
|
|
60
60
|
# Public API
|
|
61
61
|
__all__ = [
|
|
@@ -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 .wires import WireCollection
|
|
12
|
+
from .junctions import JunctionCollection
|
|
13
|
+
from .labels import LabelCollection
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"IndexedCollection",
|
|
17
|
+
"ComponentCollection",
|
|
18
|
+
"WireCollection",
|
|
19
|
+
"JunctionCollection",
|
|
20
|
+
"LabelCollection",
|
|
21
|
+
]
|
|
@@ -0,0 +1,296 @@
|
|
|
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
|
+
def matches_criteria(item: T) -> bool:
|
|
187
|
+
for attr, value in criteria.items():
|
|
188
|
+
if not hasattr(item, attr) or getattr(item, attr) != value:
|
|
189
|
+
return False
|
|
190
|
+
return True
|
|
191
|
+
|
|
192
|
+
return self.find(matches_criteria)
|
|
193
|
+
|
|
194
|
+
def clear(self) -> None:
|
|
195
|
+
"""Clear all items from the collection."""
|
|
196
|
+
self._items.clear()
|
|
197
|
+
self._uuid_index.clear()
|
|
198
|
+
self._mark_modified()
|
|
199
|
+
logger.debug(f"Cleared all items from {self.__class__.__name__}")
|
|
200
|
+
|
|
201
|
+
# Collection interface methods
|
|
202
|
+
def __len__(self) -> int:
|
|
203
|
+
"""Number of items in collection."""
|
|
204
|
+
return len(self._items)
|
|
205
|
+
|
|
206
|
+
def __iter__(self) -> Iterator[T]:
|
|
207
|
+
"""Iterate over items in collection."""
|
|
208
|
+
return iter(self._items)
|
|
209
|
+
|
|
210
|
+
def __contains__(self, item: Union[str, T]) -> bool:
|
|
211
|
+
"""Check if item or UUID is in collection."""
|
|
212
|
+
if isinstance(item, str):
|
|
213
|
+
# Check by UUID
|
|
214
|
+
self._ensure_indexes_current()
|
|
215
|
+
return item in self._uuid_index
|
|
216
|
+
else:
|
|
217
|
+
# Check by item instance
|
|
218
|
+
uuid_str = self._get_item_uuid(item)
|
|
219
|
+
self._ensure_indexes_current()
|
|
220
|
+
return uuid_str in self._uuid_index
|
|
221
|
+
|
|
222
|
+
def __getitem__(self, index: int) -> T:
|
|
223
|
+
"""Get item by index."""
|
|
224
|
+
return self._items[index]
|
|
225
|
+
|
|
226
|
+
# Internal methods
|
|
227
|
+
def _add_item_to_collection(self, item: T) -> T:
|
|
228
|
+
"""
|
|
229
|
+
Internal method to add item to collection.
|
|
230
|
+
|
|
231
|
+
Args:
|
|
232
|
+
item: Item to add
|
|
233
|
+
|
|
234
|
+
Returns:
|
|
235
|
+
The added item
|
|
236
|
+
"""
|
|
237
|
+
self._items.append(item)
|
|
238
|
+
self._mark_modified()
|
|
239
|
+
self._mark_indexes_dirty()
|
|
240
|
+
|
|
241
|
+
logger.debug(f"Added item with UUID {self._get_item_uuid(item)}")
|
|
242
|
+
return item
|
|
243
|
+
|
|
244
|
+
def _mark_modified(self) -> None:
|
|
245
|
+
"""Mark collection as modified."""
|
|
246
|
+
self._modified = True
|
|
247
|
+
|
|
248
|
+
def _mark_indexes_dirty(self) -> None:
|
|
249
|
+
"""Mark indexes as needing rebuild."""
|
|
250
|
+
self._dirty_indexes = True
|
|
251
|
+
|
|
252
|
+
def _ensure_indexes_current(self) -> None:
|
|
253
|
+
"""Ensure all indexes are current."""
|
|
254
|
+
if self._dirty_indexes:
|
|
255
|
+
self._rebuild_indexes()
|
|
256
|
+
|
|
257
|
+
def _rebuild_indexes(self) -> None:
|
|
258
|
+
"""Rebuild all indexes."""
|
|
259
|
+
# Rebuild UUID index
|
|
260
|
+
self._uuid_index = {
|
|
261
|
+
self._get_item_uuid(item): i
|
|
262
|
+
for i, item in enumerate(self._items)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
# Let subclasses rebuild their additional indexes
|
|
266
|
+
self._build_additional_indexes()
|
|
267
|
+
|
|
268
|
+
self._dirty_indexes = False
|
|
269
|
+
logger.debug(f"Rebuilt indexes for {self.__class__.__name__}")
|
|
270
|
+
|
|
271
|
+
# Collection statistics and debugging
|
|
272
|
+
def get_statistics(self) -> Dict[str, Any]:
|
|
273
|
+
"""
|
|
274
|
+
Get collection statistics for debugging and monitoring.
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
Dictionary with collection statistics
|
|
278
|
+
"""
|
|
279
|
+
self._ensure_indexes_current()
|
|
280
|
+
return {
|
|
281
|
+
"item_count": len(self._items),
|
|
282
|
+
"uuid_index_size": len(self._uuid_index),
|
|
283
|
+
"modified": self._modified,
|
|
284
|
+
"indexes_dirty": self._dirty_indexes,
|
|
285
|
+
"collection_type": self.__class__.__name__
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
@property
|
|
289
|
+
def is_modified(self) -> bool:
|
|
290
|
+
"""Whether collection has been modified."""
|
|
291
|
+
return self._modified
|
|
292
|
+
|
|
293
|
+
def mark_clean(self) -> None:
|
|
294
|
+
"""Mark collection as clean (not modified)."""
|
|
295
|
+
self._modified = False
|
|
296
|
+
logger.debug(f"Marked {self.__class__.__name__} as clean")
|