/// KiCad 原理图文件导入器 /// /// 解析 KiCad .sch 和 .kicad_sch 文件,映射到核心数据模型 /// /// @version 0.1.0 /// @date 2026-03-07 import 'dart:convert'; import 'dart:typed_data'; // ============================================================================ // KiCad S-表达式解析器 // ============================================================================ /// KiCad S-表达式词法单元 enum SExprToken { leftParen, rightParen, string, number, symbol, eof, } /// S-表达式解析器 class SExprParser { final String input; int position = 0; SExprParser(this.input); /// 解析整个 S-表达式 dynamic parse() { _skipWhitespace(); if (position >= input.length) { return null; } return _parseExpr(); } dynamic _parseExpr() { _skipWhitespace(); if (position >= input.length) { return null; } final char = input[position]; if (char == '(') { return _parseList(); } else if (char == '"' || char == '\'') { return _parseString(); } else if (char == '-' || (char >= '0' && char <= '9')) { return _parseNumber(); } else { return _parseSymbol(); } } List _parseList() { position++; // skip '(' final list = []; while (true) { _skipWhitespace(); if (position >= input.length) { throw FormatException('Unexpected end of input in list'); } if (input[position] == ')') { position++; // skip ')' break; } list.add(_parseExpr()); } return list; } String _parseString() { final quote = input[position]; position++; // skip opening quote final buffer = StringBuffer(); while (position < input.length) { final char = input[position]; if (char == quote) { position++; // skip closing quote break; } if (char == '\\' && position + 1 < input.length) { position++; final escaped = input[position]; switch (escaped) { case 'n': buffer.write('\n'); break; case 't': buffer.write('\t'); break; case 'r': buffer.write('\r'); break; case '\\': buffer.write('\\'); break; case '"': buffer.write('"'); break; default: buffer.write(escaped); } } else { buffer.write(char); } position++; } return buffer.toString(); } num _parseNumber() { final start = position; if (input[position] == '-') { position++; } while (position < input.length && (input[position] >= '0' && input[position] <= '9')) { position++; } if (position < input.length && input[position] == '.') { position++; while (position < input.length && (input[position] >= '0' && input[position] <= '9')) { position++; } return double.parse(input.substring(start, position)); } return int.parse(input.substring(start, position)); } String _parseSymbol() { final start = position; while (position < input.length && !_isWhitespace(input[position]) && input[position] != '(' && input[position] != ')' && input[position] != '"' && input[position] != '\'') { position++; } return input.substring(start, position); } void _skipWhitespace() { while (position < input.length && _isWhitespace(input[position])) { position++; } } bool _isWhitespace(String char) { return char == ' ' || char == '\t' || char == '\n' || char == '\r'; } } // ============================================================================ // KiCad 数据模型 // ============================================================================ /// KiCad 元件 class KicadComponent { final String reference; final String value; final String footprint; final String? libraryLink; final double x; final double y; final int rotation; final List pins; final Map properties; KicadComponent({ required this.reference, required this.value, required this.footprint, this.libraryLink, required this.x, required this.y, this.rotation = 0, this.pins = const [], this.properties = const {}, }); factory KicadComponent.fromSExpr(List expr) { String reference = ''; String value = ''; String footprint = ''; String? libraryLink; double x = 0; double y = 0; int rotation = 0; final pins = []; final properties = {}; for (int i = 1; i < expr.length; i++) { final item = expr[i]; if (item is! List) continue; if (item.isNotEmpty && item[0] == 'property') { final propName = item.length > 1 ? item[1].toString() : ''; final propValue = item.length > 2 ? item[2].toString() : ''; if (propName == 'Reference') { reference = propValue; } else if (propName == 'Value') { value = propValue; } else { properties[propName] = propValue; } } else if (item.isNotEmpty && item[0] == 'symbol') { libraryLink = item.length > 1 ? item[1].toString() : null; } else if (item.isNotEmpty && item[0] == 'footprint') { footprint = item.length > 1 ? item[1].toString() : ''; } else if (item.isNotEmpty && item[0] == 'at') { if (item.length >= 3) { x = _parseNumber(item[1]); y = _parseNumber(item[2]); if (item.length >= 4) { rotation = _parseNumber(item[3]).toInt(); } } } else if (item.isNotEmpty && item[0] == 'pin') { pins.add(KicadPin.fromSExpr(item)); } } return KicadComponent( reference: reference, value: value, footprint: footprint, libraryLink: libraryLink, x: x, y: y, rotation: rotation, pins: pins, properties: properties, ); } static num _parseNumber(dynamic value) { if (value is num) return value; if (value is String) { return double.tryParse(value) ?? 0; } return 0; } } /// KiCad 引脚 class KicadPin { final String number; final String name; final double x; final double y; final int rotation; final String type; KicadPin({ required this.number, required this.name, required this.x, required this.y, this.rotation = 0, this.type = 'passive', }); factory KicadPin.fromSExpr(List expr) { String number = ''; String name = ''; double x = 0; double y = 0; int rotation = 0; String type = 'passive'; for (int i = 1; i < expr.length; i++) { final item = expr[i]; if (item is List) { if (item.isNotEmpty && item[0] == 'at') { if (item.length >= 3) { x = _parseNumber(item[1]); y = _parseNumber(item[2]); if (item.length >= 4) { rotation = _parseNumber(item[3]).toInt(); } } } else if (item.isNotEmpty && item[0] == 'property') { if (item.length >= 3) { final propName = item[1].toString(); final propValue = item[2].toString(); if (propName == 'Name') { name = propValue; } else if (propName == 'Number') { number = propValue; } } } } else if (item is String) { if (item == 'input' || item == 'output' || item == 'bidirectional' || item == 'power_in' || item == 'power_out' || item == 'passive') { type = item; } } } return KicadPin( number: number, name: name, x: x, y: y, rotation: rotation, type: type, ); } static num _parseNumber(dynamic value) { if (value is num) return value; if (value is String) { return double.tryParse(value) ?? 0; } return 0; } } /// KiCad 网络 class KicadNet { final String name; final List connections; KicadNet({ required this.name, this.connections = const [], }); factory KicadNet.fromSExpr(List expr) { String name = ''; final connections = []; for (int i = 1; i < expr.length; i++) { final item = expr[i]; if (item is List) { if (item.isNotEmpty && item[0] == 'node') { connections.add(KicadNetConnection.fromSExpr(item)); } } else if (item is String && i == 1) { name = item; } } return KicadNet( name: name, connections: connections, ); } } /// KiCad 网络连接 class KicadNetConnection { final String? pinNumber; final String? componentRef; final double? x; final double? y; KicadNetConnection({ this.pinNumber, this.componentRef, this.x, this.y, }); factory KicadNetConnection.fromSExpr(List expr) { String? pinNumber; String? componentRef; double? x, y; for (int i = 1; i < expr.length; i++) { final item = expr[i]; if (item is List) { if (item.isNotEmpty && item[0] == 'pin') { if (item.length >= 2) { pinNumber = item[1].toString(); } // 查找引脚所属的元件引用 for (int j = 2; j < item.length; j++) { if (item[j] is List && item[j][0] == 'lib_pin') { // 提取元件引用信息 } } } } } return KicadNetConnection( pinNumber: pinNumber, componentRef: componentRef, x: x, y: y, ); } } /// KiCad 原理图 class KicadSchematic { final String version; final List components; final List nets; KicadSchematic({ required this.version, this.components = const [], this.nets = const [], }); factory KicadSchematic.fromSExpr(List expr) { String version = ''; final components = []; final nets = []; for (int i = 1; i < expr.length; i++) { final item = expr[i]; if (item is! List) continue; if (item.isNotEmpty && item[0] == 'version') { version = item.length > 1 ? item[1].toString() : ''; } else if (item.isNotEmpty && item[0] == 'component') { components.add(KicadComponent.fromSExpr(item)); } else if (item.isNotEmpty && item[0] == 'net') { nets.add(KicadNet.fromSExpr(item)); } } return KicadSchematic( version: version, components: components, nets: nets, ); } } // ============================================================================ // KiCad 导入器 // ============================================================================ /// KiCad 原理图导入器 class KicadImporter { /// 导入 KiCad .kicad_sch 文件内容 KicadSchematic import(String content) { final parser = SExprParser(content); final sExpr = parser.parse(); if (sExpr is! List) { throw FormatException('Invalid KiCad schematic format'); } return KicadSchematic.fromSExpr(sExpr); } /// 导入 KiCad .sch 文件 (旧格式) KicadSchematic importLegacySch(String content) { // 旧格式解析逻辑 (简化实现) final components = []; final nets = []; final lines = content.split('\n'); KicadComponent? currentComponent; for (final line in lines) { final trimmed = line.trim(); if (trimmed.startsWith('C ')) { // 元件定义行 if (currentComponent != null) { components.add(currentComponent); } currentComponent = _parseLegacyComponent(line); } else if (trimmed.startsWith('N ')) { // 网络定义 final net = _parseLegacyNet(line); if (net != null) { nets.add(net); } } } if (currentComponent != null) { components.add(currentComponent); } return KicadSchematic( version: 'legacy', components: components, nets: nets, ); } KicadComponent? _parseLegacyComponent(String line) { // 简化实现,实际需要完整的旧格式解析 final parts = line.split(RegExp(r'\s+')); if (parts.length < 4) return null; return KicadComponent( reference: parts[1], value: parts.length > 2 ? parts[2] : '', footprint: '', x: double.tryParse(parts[3]) ?? 0, y: double.tryParse(parts.length > 4 ? parts[4] : '0') ?? 0, ); } KicadNet? _parseLegacyNet(String line) { // 简化实现 return null; } } // ============================================================================ // 转换为 EDA 核心数据模型 // ============================================================================ /// 将 KiCad 原理图转换为 EDA 核心 Design 模型 Map kicadToDesign(KicadSchematic kicadSch) { final design = { 'id': _generateId('design'), 'name': 'Imported from KiCad', 'version': '1.0.0', 'parameters': { 'boardSize': {'width': 100000000, 'height': 100000000}, // 100mm x 100mm 'origin': {'x': 0, 'y': 0}, 'units': 'nm', 'grid': {'x': 25400, 'y': 25400}, // 10mil grid }, 'tables': { 'components': >[], 'nets': >[], 'layers': >[], 'footprints': >[], 'traces': >[], 'vias': >[], 'polygons': >[], }, 'stackup': { 'layers': ['top', 'bottom'], 'totalThickness': 1600000, // 1.6mm }, 'designRules': { 'traceWidth': {'min': 152400, 'max': 2000000, 'preferred': 254000}, 'traceSpacing': {'min': 152400, 'preferred': 203200}, 'viaHoleSize': {'min': 200000, 'max': 800000}, 'viaDiameter': {'min': 400000, 'max': 1000000}, 'clearance': {'componentToComponent': 500000, 'componentToEdge': 1000000}, }, 'metadata': { 'createdAt': DateTime.now().millisecondsSinceEpoch, 'updatedAt': DateTime.now().millisecondsSinceEpoch, 'isDirty': false, }, }; // 添加默认层 (design['tables']['layers'] as List).addAll([ _createLayer('top', 'Top', 'signal', 0), _createLayer('bottom', 'Bottom', 'signal', 1), _createLayer('silkscreen', 'Silkscreen', 'silkscreen', 2), ]); // 转换元件 final componentMap = >{}; for (final kicadComp in kicadSch.components) { final component = _convertComponent(kicadComp); componentMap[kicadComp.reference] = component; (design['tables']['components'] as List).add(component); } // 转换网络 for (final kicadNet in kicadSch.nets) { final net = _convertNet(kicadNet, componentMap); (design['tables']['nets'] as List).add(net); } return design; } Map _convertComponent(KicadComponent kicadComp) { // 转换 KiCad 单位 (mm) 到纳米 final xNm = (kicadComp.x * 1000000).toInt(); final yNm = (kicadComp.y * 1000000).toInt(); // 转换引脚 final pins = >[]; for (final pin in kicadComp.pins) { pins.add({ 'pinId': _generateId('pin'), 'name': pin.name, 'x': (pin.x * 1000000).toInt(), 'y': (pin.y * 1000000).toInt(), 'rotation': pin.rotation, 'electricalType': _convertPinType(pin.type), }); } // 确定元件类型 final type = _determineComponentType(kicadComp.value); return { 'id': _generateId('comp'), 'name': kicadComp.reference, 'type': type, 'value': kicadComp.value, 'footprint': { 'id': _generateId('footprint'), 'name': kicadComp.footprint, 'pads': [], }, 'position': { 'x': xNm, 'y': yNm, 'layer': 'top', 'rotation': kicadComp.rotation, 'mirror': 'none', }, 'pins': pins, 'attributes': { 'manufacturer': kicadComp.properties['Manufacturer'] ?? '', 'partNumber': kicadComp.properties['PartNumber'] ?? '', 'description': kicadComp.properties['Description'] ?? '', }, 'metadata': { 'createdAt': DateTime.now().millisecondsSinceEpoch, 'updatedAt': DateTime.now().millisecondsSinceEpoch, }, }; } Map _convertNet(KicadNet kicadNet, Map> componentMap) { final connections = >[]; for (final conn in kicadNet.connections) { if (conn.componentRef != null && componentMap.containsKey(conn.componentRef)) { final component = componentMap[conn.componentRef]!; connections.add({ 'id': _generateId('conn'), 'type': 'pin', 'componentId': component['id'], 'pinId': conn.pinNumber, }); } } return { 'id': _generateId('net'), 'name': kicadNet.name, 'type': 'signal', 'connections': connections, 'properties': {}, 'metadata': { 'createdAt': DateTime.now().millisecondsSinceEpoch, 'updatedAt': DateTime.now().millisecondsSinceEpoch, }, }; } Map _createLayer(String id, String name, String type, int order) { return { 'id': id, 'name': name, 'type': type, 'stackupOrder': order, 'properties': { 'isVisible': true, 'isEnabled': true, 'color': type == 'signal' ? '#00ff00' : '#ffffff', }, 'objects': {}, 'metadata': { 'createdAt': DateTime.now().millisecondsSinceEpoch, 'updatedAt': DateTime.now().millisecondsSinceEpoch, }, }; } String _determineComponentType(String value) { final lowerValue = value.toLowerCase(); if (lowerValue.contains('r') || lowerValue.contains('resistor')) { return 'resistor'; } else if (lowerValue.contains('c') || lowerValue.contains('capacitor')) { return 'capacitor'; } else if (lowerValue.contains('l') || lowerValue.contains('inductor')) { return 'inductor'; } else if (lowerValue.contains('d') || lowerValue.contains('diode')) { return 'diode'; } else if (lowerValue.contains('q') || lowerValue.contains('transistor')) { return 'transistor'; } else if (lowerValue.contains('u') || lowerValue.contains('ic')) { return 'ic'; } return 'custom'; } String _convertPinType(String kicadType) { switch (kicadType) { case 'input': return 'input'; case 'output': return 'output'; case 'bidirectional': return 'bidirectional'; case 'power_in': return 'power'; case 'power_out': return 'power'; default: return 'passive'; } } String _generateId(String prefix) { return '$prefix-${DateTime.now().millisecondsSinceEpoch}-${(DateTime.now().microsecondsSinceEpoch % 10000).toString().padLeft(4, '0')}'; } // ============================================================================ // 公共 API // ============================================================================ /// 从文件内容导入 KiCad 原理图 Map importKicadSchematic(String content, {String? format}) { final importer = KicadImporter(); KicadSchematic kicadSch; if (format == 'legacy') { kicadSch = importer.importLegacySch(content); } else { kicadSch = importer.import(content); } return kicadToDesign(kicadSch); } /// 从文件路径导入 KiCad 原理图 (需要文件系统访问) Future> importKicadFile(String filePath) async { // 实际实现需要文件读取 throw UnimplementedError('File system access not implemented'); }