mobile-eda/lib/data/import/kicad_importer.dart

755 lines
20 KiB
Dart

/// 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<dynamic> _parseList() {
position++; // skip '('
final list = <dynamic>[];
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<KicadPin> pins;
final Map<String, String> 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<dynamic> expr) {
String reference = '';
String value = '';
String footprint = '';
String? libraryLink;
double x = 0;
double y = 0;
int rotation = 0;
final pins = <KicadPin>[];
final properties = <String, String>{};
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<dynamic> 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<KicadNetConnection> connections;
KicadNet({
required this.name,
this.connections = const [],
});
factory KicadNet.fromSExpr(List<dynamic> expr) {
String name = '';
final connections = <KicadNetConnection>[];
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<dynamic> 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<KicadComponent> components;
final List<KicadNet> nets;
KicadSchematic({
required this.version,
this.components = const [],
this.nets = const [],
});
factory KicadSchematic.fromSExpr(List<dynamic> expr) {
String version = '';
final components = <KicadComponent>[];
final nets = <KicadNet>[];
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 = <KicadComponent>[];
final nets = <KicadNet>[];
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<String, dynamic> kicadToDesign(KicadSchematic kicadSch) {
final design = <String, dynamic>{
'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': <Map<String, dynamic>>[],
'nets': <Map<String, dynamic>>[],
'layers': <Map<String, dynamic>>[],
'footprints': <Map<String, dynamic>>[],
'traces': <Map<String, dynamic>>[],
'vias': <Map<String, dynamic>>[],
'polygons': <Map<String, dynamic>>[],
},
'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 = <String, Map<String, dynamic>>{};
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<String, dynamic> _convertComponent(KicadComponent kicadComp) {
// 转换 KiCad 单位 (mm) 到纳米
final xNm = (kicadComp.x * 1000000).toInt();
final yNm = (kicadComp.y * 1000000).toInt();
// 转换引脚
final pins = <Map<String, dynamic>>[];
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<String, dynamic> _convertNet(KicadNet kicadNet, Map<String, Map<String, dynamic>> componentMap) {
final connections = <Map<String, dynamic>>[];
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<String, dynamic> _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<String, dynamic> 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<Map<String, dynamic>> importKicadFile(String filePath) async {
// 实际实现需要文件读取
throw UnimplementedError('File system access not implemented');
}