KiBot/kibot/kicad/v6_sch.py

1737 lines
61 KiB
Python

# -*- coding: utf-8 -*-
# Copyright (c) 2021 Salvador E. Tropea
# Copyright (c) 2021 Instituto Nacional de Tecnología Industrial
# License: GPL-3.0
# Project: KiBot (formerly KiPlot)
"""
KiCad v6 Schematic format.
A basic implementation of the .kicad_sch file format.
Currently oriented to collect the components for the BoM.
Documentation: https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/
"""
# Encapsulate file/line
import os
import re
from collections import OrderedDict
from ..gs import GS
from .. import log
from ..misc import W_NOLIB, W_UNKFLD, W_MISSCMP
from .v5_sch import SchError, SchematicComponent, Schematic
from .sexpdata import load, SExpData, Symbol, dumps, Sep
logger = log.get_logger()
CROSSED_LIB = 'kibot_crossed'
def _check_is_symbol_list(e, allow_orphan_symbol=[]):
# Each entry is a list
if not isinstance(e, list):
if isinstance(e, Symbol):
name = e.value()
if name in allow_orphan_symbol:
return name
raise SchError('Orphan symbol `{}`'.format(e.value()))
else:
raise SchError('Orphan data `{}`'.format(e))
# The first element is a symbol
if not isinstance(e[0], Symbol):
raise SchError('Orphan data `{}`'.format(e[0]))
return e[0].value()
def _check_len(items, pos, name):
if len(items) < pos+1:
raise SchError('Missing argument {} in `{}`'.format(pos, name))
return items[pos]
def _check_len_total(items, num, name):
if len(items) != num:
raise SchError('Wrong number of attributes for {} `{}`'.format(name, items))
def _check_symbol(items, pos, name):
value = _check_len(items, pos, name)
if not isinstance(value, Symbol):
raise SchError('{} is not a Symbol `{}`'.format(name, value))
return value.value()
def _check_hide(items, pos, name):
value = _check_symbol(items, pos, name + ' hide')
if value != 'hide':
raise SchError('Found Symbol `{}` when `hide` expected'.format(value))
return True
def _check_integer(items, pos, name):
value = _check_len(items, pos, name)
if not isinstance(value, int):
raise SchError('{} is not an integer `{}`'.format(name, value))
return value
def _check_float(items, pos, name):
value = _check_len(items, pos, name)
if not isinstance(value, (float, int)):
raise SchError('{} is not a float `{}`'.format(name, value))
return value
def _check_str(items, pos, name):
value = _check_len(items, pos, name)
if not isinstance(value, str):
raise SchError('{} is not a string `{}`'.format(name, value))
return value
def _check_symbol_value(items, pos, name, sym):
value = _check_len(items, pos, name)
if not isinstance(value, list) or not isinstance(value[0], Symbol) or value[0].value() != sym:
raise SchError('Missing `{}` in `{}`'.format(sym, name))
return value
def _check_symbol_float(items, pos, name, sym):
name += ' ' + sym
values = _check_symbol_value(items, pos, name, sym)
return _check_float(values, 1, name)
def _check_symbol_int(items, pos, name, sym):
name += ' ' + sym
values = _check_symbol_value(items, pos, name, sym)
return _check_integer(values, 1, name)
def _check_symbol_str(items, pos, name, sym):
name += ' ' + sym
values = _check_symbol_value(items, pos, name, sym)
return _check_str(values, 1, name)
def _get_offset(items, pos, name):
value = _check_symbol_value(items, pos, name, 'offset')
return _check_float(value, 1, 'offset')
def _get_yes_no(items, pos, name):
sym = _check_symbol(items, pos, name)
return sym == 'yes'
def _get_id(items, pos, name):
value = _check_symbol_value(items, pos, name, 'id')
return _check_integer(value, 1, 'id')
def _get_at(items, pos, name):
value = _check_symbol_value(items, pos, name, 'at')
angle = 0
if len(value) > 3:
angle = _check_float(value, 3, 'at angle')
return _check_float(value, 1, 'at x'), _check_float(value, 2, 'at y'), angle
class Point(object):
def __init__(self, items):
super().__init__()
self.x = _check_float(items, 1, 'x coord')
self.y = _check_float(items, 2, 'y coord')
@staticmethod
def parse(items):
return Point(items)
class PointXY(object):
def __init__(self, x, y):
super().__init__()
self.x = x
self.y = y
class Box(object):
def __init__(self, points=None):
self.x1 = self.y1 = self.x2 = self.y2 = 0
self.set = False
if points:
self.x1 = self.x2 = points[0].x
self.y1 = self.y2 = points[0].y
for p in points[1:]:
self.x1 = min(p.x, self.x1)
self.x2 = max(p.x, self.x2)
self.y1 = min(p.y, self.y1)
self.y2 = max(p.y, self.y2)
self.set = True
def __str__(self):
if not self.set:
return "Box *uninitialized*"
return "Box({},{} to {},{})".format(self.x1, self.y1, self.x2, self.y2)
def diagonal(self, inverse=False):
if inverse:
return [PointXY(self.x1, self.y2), PointXY(self.x2, self.y1)]
return [PointXY(self.x1, self.y1), PointXY(self.x2, self.y2)]
def union(self, b):
if not self.set:
self.x1 = b.x1
self.y1 = b.y1
self.x2 = b.x2
self.y2 = b.y2
self.set = True
elif b.set:
self.x1 = min(self.x1, b.x1)
self.y1 = min(self.y1, b.y1)
self.x2 = max(self.x2, b.x2)
self.y2 = max(self.y2, b.y2)
def _get_xy(items):
if len(items) != 3:
raise SchError('Point definition with wrong args (`{}`)'.format(items))
return Point.parse(items)
def _get_points(items):
points = []
for i in items[1:]:
i_type = _check_is_symbol_list(i)
if i_type == 'xy':
points.append(_get_xy(i))
else:
raise SchError('Unknown points attribute `{}`'.format(i))
return points
class FontEffects(object):
""" Class used to describe text attributes """
def __init__(self):
super().__init__()
self.hide = False
self.w = self.h = 1.27
self.thickness = None
self.bold = self.italic = False
self.hjustify = self.vjustify = 'C'
self.mirror = False
@staticmethod
def parse_font(items):
w = h = 1.27
thickness = None
bold = italic = False
for i in items[1:]:
if isinstance(i, Symbol):
name = i.value()
if name == 'bold':
bold = True
elif name == 'italic':
italic = True
else:
raise SchError('Unknown font effect attribute `{}`'.format(name))
else: # A list
i_type = _check_is_symbol_list(i)
if i_type == 'size':
h = _check_float(i, 1, 'font height')
w = _check_float(i, 2, 'font width')
elif i_type == 'thickness':
thickness = _check_float(i, 1, 'font thickness')
else:
raise SchError('Unknown font effect attribute `{}`'.format(i))
return w, h, thickness, bold, italic
@staticmethod
def parse_justify(items):
h = v = 'C'
mirror = False
for i in items[1:]:
if isinstance(i, Symbol):
name = i.value()
if name == 'left':
h = 'L'
elif name == 'right':
h = 'R'
elif name == 'top':
v = 'T'
elif name == 'bottom':
v = 'B'
elif name == 'mirror':
mirror = True
else:
raise SchError('Unknown font effect attribute `{}`'.format(name))
else: # A list
raise SchError('Unknown font effect attribute `{}`'.format(i))
return h, v, mirror
@staticmethod
def parse(items):
o = FontEffects()
for c, i in enumerate(items[1:]):
if isinstance(i, Symbol):
# Only hide exists
o.hide = _check_hide(items, c+1, 'font effect')
elif isinstance(i, list):
i_type = _check_is_symbol_list(i)
if i_type == 'font':
o.w, o.h, o.thickness, o.bold, o.italic = FontEffects.parse_font(i)
elif i_type == 'justify':
o.hjustify, o.vjustify, o.mirror = FontEffects.parse_justify(i)
else:
raise SchError('Unknown font effect attribute `{}`'.format(i))
return o
def write_font(self):
data = [_symbol('size', [self.h, self.w])]
if self.thickness is not None:
data.append(_symbol('thickness', [self.thickness]))
if self.bold:
data.append(Symbol('bold'))
if self.italic:
data.append(Symbol('italic'))
return _symbol('font', data)
def write_justify(self):
data = []
if self.hjustify == 'L':
data.append(Symbol('left'))
elif self.hjustify == 'R':
data.append(Symbol('right'))
if self.vjustify == 'T':
data.append(Symbol('top'))
elif self.vjustify == 'B':
data.append(Symbol('bottom'))
if self.mirror:
data.append(Symbol('mirror'))
return _symbol('justify', data)
def write(self):
data = [self.write_font()]
if self.hjustify != 'C' or self.vjustify != 'C' or self.mirror:
data.append(self.write_justify())
if self.hide:
data.append(Symbol('hide'))
return _symbol('effects', data)
class Color(object):
def __init__(self, items=None):
super().__init__()
if items:
self.r = _check_integer(items, 1, 'red color')
self.g = _check_integer(items, 2, 'green color')
self.b = _check_integer(items, 3, 'blue color')
# Sheet sheet.fill.color is float ...
self.a = _check_float(items, 4, 'alpha color')
else:
self.r = self.g = self.b = self.a = 0
@staticmethod
def parse(items):
return Color(items)
def write(self):
return _symbol('color', [self.r, self.g, self.b, self.a])
class Stroke(object):
def __init__(self):
super().__init__()
self.width = 0
self.type = 'default'
self.color = Color()
@staticmethod
def parse(items):
stroke = Stroke()
for i in items[1:]:
i_type = _check_is_symbol_list(i)
if i_type == 'width':
stroke.width = _check_float(i, 1, 'stroke width')
elif i_type == 'type':
stroke.type = _check_symbol(i, 1, 'stroke type')
elif i_type == 'color':
stroke.color = Color.parse(i)
else:
raise SchError('Unknown stroke attribute `{}`'.format(i))
return stroke
def write(self):
data = [_symbol('width', [self.width])]
data.append(_symbol('type', [Symbol(self.type)]))
data.append(self.color.write())
return _symbol('stroke', data)
class Fill(object):
def __init__(self):
super().__init__()
self.type = None
self.color = None
@staticmethod
def parse(items):
fill = Fill()
for i in items[1:]:
i_type = _check_is_symbol_list(i)
if i_type == 'type':
fill.type = _check_symbol(i, 1, 'fill type')
elif i_type == 'color':
# Not documented, found in sheet.fill
fill.color = Color.parse(i)
else:
raise SchError('Unknown fill attribute `{}`'.format(i))
return fill
def write(self):
data = []
if self.type is not None:
data.append(_symbol('type', [Symbol(self.type)]))
if self.color is not None:
data.append(self.color.write())
return _symbol('fill', data)
class DrawArcV6(object):
def __init__(self):
super().__init__()
self.start = None
self.mid = None
self.end = None
self.stroke = None
self.fill = None
@staticmethod
def parse(items):
arc = DrawArcV6()
for i in items[1:]:
i_type = _check_is_symbol_list(i)
if i_type == 'start':
arc.start = _get_xy(i)
elif i_type == 'mid':
arc.mid = _get_xy(i)
elif i_type == 'end':
arc.end = _get_xy(i)
elif i_type == 'stroke':
arc.stroke = Stroke.parse(i)
elif i_type == 'fill':
arc.fill = Fill.parse(i)
else:
raise SchError('Unknown arc attribute `{}`'.format(i))
arc.box = Box([arc.start, arc.mid, arc.end])
return arc
def write(self):
data = [_symbol('start', [self.start.x, self.start.y])]
data.append(_symbol('mid', [self.mid.x, self.mid.y]))
data.append(_symbol('end', [self.end.x, self.end.y]))
data.append(Sep())
data.extend([self.stroke.write(), Sep()])
data.extend([self.fill.write(), Sep()])
return _symbol('arc', data)
class DrawCircleV6(object):
def __init__(self):
super().__init__()
self.center = None
self.radius = 0
self.stroke = None
self.fill = None
@staticmethod
def parse(items):
circle = DrawCircleV6()
for i in items[1:]:
i_type = _check_is_symbol_list(i)
if i_type == 'center':
circle.center = _get_xy(i)
elif i_type == 'radius':
circle.radius = _check_float(i, 1, 'circle radius')
elif i_type == 'stroke':
circle.stroke = Stroke.parse(i)
elif i_type == 'fill':
circle.fill = Fill.parse(i)
else:
raise SchError('Unknown circle attribute `{}`'.format(i))
p1 = PointXY(circle.center.x-circle.radius, circle.center.x-circle.radius)
p2 = PointXY(circle.center.x+circle.radius, circle.center.x+circle.radius)
circle.box = Box([p1, p2])
return circle
def write(self):
data = [_symbol('center', [self.center.x, self.center.y])]
data.append(_symbol('radius', [self.radius]))
data.append(Sep())
data.extend([self.stroke.write(), Sep()])
data.extend([self.fill.write(), Sep()])
return _symbol('circle', data)
class DrawRectangleV6(object):
def __init__(self):
super().__init__()
self.start = None
self.end = None
self.stroke = None
self.fill = None
@staticmethod
def parse(items):
rectangle = DrawRectangleV6()
for i in items[1:]:
i_type = _check_is_symbol_list(i)
if i_type == 'start':
rectangle.start = _get_xy(i)
elif i_type == 'end':
rectangle.end = _get_xy(i)
elif i_type == 'stroke':
rectangle.stroke = Stroke.parse(i)
elif i_type == 'fill':
rectangle.fill = Fill.parse(i)
else:
raise SchError('Unknown rectangle attribute `{}`'.format(i))
rectangle.box = Box([rectangle.start, rectangle.end])
return rectangle
def write(self):
data = [_symbol('start', [self.start.x, self.start.y])]
data.append(_symbol('end', [self.end.x, self.end.y]))
data.append(Sep())
data.extend([self.stroke.write(), Sep()])
data.extend([self.fill.write(), Sep()])
return _symbol('rectangle', data)
class DrawCurve(object):
""" Qubic Bezier """
def __init__(self):
super().__init__()
self.points = []
self.stroke = None
self.fill = None
@staticmethod
def parse(items):
curve = DrawCurve()
for i in items[1:]:
i_type = _check_is_symbol_list(i)
if i_type == 'pts':
curve.points = _get_points(i)
elif i_type == 'stroke':
curve.stroke = Stroke.parse(i)
elif i_type == 'fill':
curve.fill = Fill.parse(i)
else:
raise SchError('Unknown curve attribute `{}`'.format(i))
curve.box = Box(curve.points)
return curve
def write(self):
points = [Sep()]
for p in self.points:
points.append(_symbol('xy', [p.x, p.y]))
points.append(Sep())
data = [_symbol('pts', points), Sep()]
data.extend([self.stroke.write(), Sep()])
data.extend([self.fill.write(), Sep()])
return _symbol('gr_curve', data)
class DrawPolyLine(object):
def __init__(self):
super().__init__()
self.points = []
self.stroke = None
self.fill = None
@staticmethod
def parse(items):
line = DrawPolyLine()
for i in items[1:]:
i_type = _check_is_symbol_list(i)
if i_type == 'pts':
line.points = _get_points(i)
elif i_type == 'stroke':
line.stroke = Stroke.parse(i)
elif i_type == 'fill':
line.fill = Fill.parse(i)
else:
raise SchError('Unknown polyline attribute `{}`'.format(i))
line.box = Box(line.points)
return line
def write(self):
points = [Sep()]
for p in self.points:
points.append(_symbol('xy', [p.x, p.y]))
points.append(Sep())
data = [Sep(), _symbol('pts', points), Sep()]
data.extend([self.stroke.write(), Sep()])
data.extend([self.fill.write(), Sep()])
return _symbol('polyline', data)
class DrawTextV6(object):
def __init__(self):
super().__init__()
self.text = None
self.x = self.y = self.ang = 0
self.effects = None
self.box = Box()
@staticmethod
def parse(items):
text = DrawTextV6()
text.text = _check_str(items, 1, 'text')
text.x, text.y, text.ang = _get_at(items, 2, 'text')
text.effects = _get_effects(items, 3, 'text')
return text
def write(self):
data = [self.text, _symbol('at', [self.x, self.y, self.ang]), Sep()]
data.extend([self.effects.write(), Sep()])
return _symbol('text', data)
def _get_effects(items, pos, name):
values = _check_symbol_value(items, pos, name, 'effects')
return FontEffects.parse(values)
class PinV6(object):
def __init__(self):
super().__init__()
self.type = self.gtype = self.name = self.number = ''
self.pos_x = self.pos_y = self.ang = self.len = 0
self.name_effects = self.number_effects = None
self.hide = False
self.box = Box()
@staticmethod
def parse(items):
name = 'pin'
pin = PinV6()
pin.type = _check_symbol(items, 1, name+' type')
pin.gtype = _check_symbol(items, 2, name+' style')
for c, i in enumerate(items[3:]):
i_type = _check_is_symbol_list(i, allow_orphan_symbol=['hide'])
if i_type == 'at':
pin.pos_x, pin.pos_y, pin.ang = _get_at(items, c+3, name)
elif i_type == 'length':
pin.len = _check_float(i, 1, name+' length')
elif i_type == 'hide':
# Not documented yet
pin.hide = True
elif i_type == 'name':
pin.name = _check_str(i, 1, name+' name')
pin.name_effects = _get_effects(i, 2, name+' name')
elif i_type == 'number':
pin.number = _check_str(i, 1, name+' number')
pin.number_effects = _get_effects(i, 2, name+' number')
else:
raise SchError('Unknown pin attribute `{}`'.format(i))
if not pin.hide:
p1 = PointXY(pin.pos_x, pin.pos_y)
ang = pin.ang % 360
if ang == 0:
co = 1
si = 0
elif pin.ang == 90:
co = 0
si = 1
elif pin.ang == 180:
co = -1
si = 0
else: # 270
co = 0
si = -1
p2 = PointXY(pin.pos_x+pin.len*co, pin.pos_y+pin.len*si)
pin.box = Box([p1, p2])
return pin
def write(self):
data = [Symbol(self.type),
Symbol(self.gtype),
_symbol('at', [self.pos_x, self.pos_y, self.ang]),
_symbol('length', [self.len])]
if self.hide:
data.append(Symbol('hide'))
data.extend([Sep(), _symbol('name', [self.name, self.name_effects.write()]), Sep(),
_symbol('number', [self.number, self.number_effects.write()]), Sep()])
return _symbol('pin', data)
class SchematicFieldV6(object):
# Fixed ids:
# 0 Reference
# 1 Value
# 2 Footprint
# 3 Datasheet
# Reserved names: ki_keywords, ki_description, ki_locked, ki_fp_filters
def __init__(self, name='', value='', id=0, x=0, y=0, ang=0):
super().__init__()
self.name = name
self.value = value
self.number = id
self.x = x
self.y = y
self.ang = ang
self.effects = None
self.hide = False
@staticmethod
def parse(items):
if len(items) != 6:
_check_len_total(items, 5, 'property')
field = SchematicFieldV6()
field.name = _check_str(items, 1, 'field name')
field.value = _check_str(items, 2, 'field value')
field.number = _get_id(items, 3, 'field id')
field.x, field.y, field.ang = _get_at(items, 4, 'field')
if len(items) > 5:
field.effects = _get_effects(items, 5, 'field')
else:
field.effects = None
return field
def write(self):
if self.number < 0:
return None
data = [self.name, self.value, _symbol('id', [self.number])]
data.append(_symbol('at', [self.x, self.y, self.ang]))
if self.effects:
data.extend([Sep(), self.effects.write(), Sep()])
return _symbol('property', data)
class LibComponent(object):
unit_regex = re.compile(r'^(.*)_(\d+)_(\d+)$')
cross_color = Color()
cross_stroke = Stroke()
cross_stroke.width = 0.6
cross_stroke.color = cross_color
cross_fill = Fill()
cross_fill.type = 'none'
def __init__(self):
super().__init__()
self.pin_numbers_hide = None
self.pin_names_hide = None
self.pin_names_offset = None
self.in_bom = False
self.on_board = False
self.is_power = False
self.unit = 0
self.draw = []
self.fields = []
self.dfields = {}
self.box = Box()
self.alias = None
self.dcm = None
self.fp_list = None
# This member is used to generate crossed components (DNF).
# When defined means we need to add a cross in this box and then reset the box.
self.cross_box = None
def get_field_value(self, field):
field = field.lower()
if field in self.dfields:
return self.dfields[field].value
return ''
@staticmethod
def load(c, project, parent=None): # noqa: C901
if not isinstance(c, list):
raise SchError('Library component definition is not a list')
if len(c) < 3:
raise SchError('Truncated library component definition (len<3)')
if not isinstance(c[0], Symbol) or c[0].value() != 'symbol':
raise SchError('Library component definition is of wrong type')
comp = LibComponent()
comp.project = project
# First argument is the LIB:NAME
comp.lib_id = comp.name = _check_str(c, 1, 'name')
res = comp.name.split(':')
comp.lib = None
if len(res) == 2:
comp.name = res[1]
comp.lib = res[0]
else:
if parent is None:
logger.warning(W_NOLIB + "Component `{}` doesn't specify its library".format(comp.name))
comp.units = []
comp.pins = []
comp.all_pins = []
comp.unit_count = 1
# Variable list
for i in c[2:]:
i_type = _check_is_symbol_list(i)
vis_obj = None
if i_type == 'pin_numbers':
comp.pin_numbers_hide = _check_hide(i, 1, i_type)
elif i_type == 'pin_names':
value = _check_len(i, 1, i_type)
index = 1
if isinstance(value, list):
comp.pin_names_offset = _get_offset(i, 1, i_type)
index = 2
comp.pin_names_hide = None
try:
comp.pin_names_hide = _check_symbol(i, index, i_type)
except SchError:
# Optional
pass
elif i_type == 'in_bom':
comp.in_bom = _get_yes_no(i, 1, i_type)
elif i_type == 'on_board':
comp.on_board = _get_yes_no(i, 1, i_type)
elif i_type == 'power':
# Not yet documented
comp.is_power = True
# SYMBOL_PROPERTIES...
elif i_type == 'property':
field = SchematicFieldV6.parse(i)
comp.fields.append(field)
comp.dfields[field.name.lower()] = field
# GRAPHIC_ITEMS...
elif i_type == 'arc':
vis_obj = DrawArcV6.parse(i)
comp.draw.append(vis_obj)
elif i_type == 'circle':
vis_obj = DrawCircleV6.parse(i)
comp.draw.append(vis_obj)
elif i_type == 'gr_curve':
vis_obj = DrawCurve.parse(i)
comp.draw.append(vis_obj)
elif i_type == 'polyline':
vis_obj = DrawPolyLine.parse(i)
comp.draw.append(vis_obj)
elif i_type == 'rectangle':
vis_obj = DrawRectangleV6.parse(i)
comp.draw.append(vis_obj)
elif i_type == 'text':
comp.draw.append(DrawTextV6.parse(i))
# PINS...
elif i_type == 'pin':
vis_obj = PinV6.parse(i)
comp.pins.append(vis_obj)
if parent:
parent.all_pins.append(vis_obj)
# UNITS...
elif i_type == 'symbol':
# They use a special naming scheme:
# 1) A symbol without real units:
# - *_0_1 the body
# - *_1_1 the pins
# 2) A symbol with real units:
# - Each unit is *_N_* where N is the unit starting from 1
# - If the unit has alternative drawing they are *_N_1 and *_N_2
# - If the unit doesn't have alternative we have *_N_x x starts from 0
# Pins and drawings can be in _N_0 and/or _N_1
vis_obj = LibComponent.load(i, project, parent=comp if parent is None else parent)
comp.units.append(vis_obj)
m = LibComponent.unit_regex.search(vis_obj.lib_id)
if m is None:
raise SchError('Malformed unit id `{}`'.format(vis_obj.lib_id))
unit = int(m.group(2))
comp.unit_count = max(unit, comp.unit_count)
else:
raise SchError('Unknown symbol attribute `{}`'.format(i))
if vis_obj:
comp.box.union(vis_obj.box)
return comp
def assign_crosses(self):
""" Compute the box for the crossed components """
name0 = self.name+"_0"
# Compute the full box for each unit
for c in range(self.unit_count):
name = self.name+"_"+str(c+1)
box = Box()
unit_with_graphs = None
for unit in self.units:
# Unit 0 is part of unit 1
if unit.name.startswith(name) or (c == 0 and unit.name.startswith(name0)):
box.union(unit.box)
if len(unit.draw):
unit_with_graphs = unit
if unit_with_graphs:
unit_with_graphs.cross_box = box
def write_cross(s, sdata):
""" Add the cross drawing """
if s.cross_box:
# Add a cross
o = DrawPolyLine()
o.stroke = LibComponent.cross_stroke
o.fill = LibComponent.cross_fill
o.points = s.cross_box.diagonal()
sdata.extend([o.write(), Sep()])
o.points = s.cross_box.diagonal(True)
sdata.extend([o.write(), Sep()])
s.cross_box = None
def write(s, cross=False):
lib_id = s.lib_id
if cross:
# Fill the cross_box of our sub/units
s.assign_crosses()
if s.lib:
# Use an alternative name
lib_id = CROSSED_LIB+':'+s.name
sdata = [lib_id]
if s.is_power:
sdata.append(_symbol('power', []))
if s.pin_numbers_hide:
sdata.append(_symbol('pin_numbers', [Symbol('hide')]))
if s.pin_names_hide is not None or s.pin_names_offset is not None:
aux = []
if s.pin_names_offset is not None:
aux.append(_symbol('offset', [s.pin_names_offset]))
if s.pin_names_hide is not None:
aux.append(Symbol('hide'))
sdata.append(_symbol('pin_names', aux))
if s.in_bom:
sdata.append(_symbol('in_bom', [Symbol('yes')]))
if s.on_board:
sdata.append(_symbol('on_board', [Symbol('yes')]))
sdata.append(Sep())
# Properties
for f in s.fields:
fdata = f.write()
if fdata is not None:
sdata.extend([fdata, Sep()])
# Graphics
for g in s.draw:
sdata.extend([g.write(), Sep()])
s.write_cross(sdata)
# Pins
for p in s.pins:
sdata.extend([p.write(), Sep()])
# Units
for u in s.units:
sdata.extend([u.write(cross), Sep()])
return _symbol('symbol', sdata)
class SchematicComponentV6(SchematicComponent):
def __init__(self):
super().__init__()
self.in_bom = False
self.on_board = False
self.pins = OrderedDict()
self.unit = 1
self.unit_specified = False
self.ref = None
def set_ref(self, ref):
self.ref = ref
# Separate the reference in its components
m = SchematicComponent.ref_re.match(ref)
if not m:
raise SchError('Malformed component reference `{}`'.format(ref))
self.ref_prefix, self.ref_suffix = m.groups()
def set_footprint(self, fp):
res = fp.split(':')
cres = len(res)
if cres == 1:
self.footprint = res[0]
self.footprint_lib = None
elif cres == 2:
self.footprint_lib = res[0]
self.footprint = res[1]
else:
raise SchError('Footprint with more than one colon (`{}`)'.format(fp))
@staticmethod
def load(c, project, parent):
if not isinstance(c, list):
raise SchError('Component definition is not a list')
if len(c) < 7:
raise SchError('Truncated component definition (len<7)')
if not isinstance(c[0], Symbol) or c[0].value() != 'symbol':
raise SchError('Component definition is of wrong type')
comp = SchematicComponentV6()
comp.project = project
# The path will be computed by the instance
# comp.sheet_path_h = parent.sheet_path_h
comp.parent_sheet = parent
name = 'component'
# First argument is the LIB:NAME
comp.lib_id = comp.name = _check_symbol_str(c, 1, name, 'lib_id')
res = comp.name.split(':')
comp.lib = None
if len(res) == 2:
comp.name = res[1]
comp.lib = res[0]
else:
logger.warning(W_NOLIB + "Component `{}` doesn't specify its library".format(comp.name))
# 2 The position
comp.x, comp.y, comp.ang = _get_at(c, 2, name)
# Variable list
for i in c[3:]:
i_type = _check_is_symbol_list(i)
if i_type == 'unit':
# This is documented as mandatory, but isn't always there
comp.unit = _check_integer(i, 1, name+' unit')
comp.unit_specified = True
elif i_type == 'in_bom':
comp.in_bom = _get_yes_no(i, 1, i_type)
elif i_type == 'on_board':
comp.on_board = _get_yes_no(i, 1, i_type)
elif i_type == 'uuid':
comp.uuid = _check_symbol(i, 1, name + ' uuid')
# SYMBOL_PROPERTIES...
elif i_type == 'property':
field = SchematicFieldV6.parse(i)
name_lc = field.name.lower()
# Add to the global collection
if name_lc not in parent.fields_lc:
parent.fields.append(field.name)
parent.fields_lc.add(name_lc)
# Add to the component
comp.add_field(field)
if field.number == 3:
# Reference, Value and Footprint are defined by the instance.
# But datasheet must be transferred from this field.
comp.datasheet = field.value
# PINS...
elif i_type == 'pin':
pin_name = _check_str(i, 1, name + 'pin name')
pin_uuid = _get_uuid(i, 2, name)
comp.pins[pin_name] = pin_uuid
# Fake 'Part' field
field = SchematicFieldV6()
field.name = 'part'
field.value = comp.name
field.number = -1
comp.add_field(field)
return comp
def write(self, cross=False):
lib_id = self.lib_id
is_crossed = not(self.fitted or not self.included)
if cross and self.lib and is_crossed:
# Use an alternative name
lib_id = CROSSED_LIB+':'+self.name
data = [_symbol('lib_id', [lib_id]),
_symbol('at', [self.x, self.y, self.ang])]
if self.unit_specified:
data.append(_symbol('unit', [self.unit]))
data.append(Sep())
if self.in_bom or self.on_board:
if self.in_bom:
data.append(_symbol('in_bom', [Symbol('yes')]))
if self.on_board:
data.append(_symbol('on_board', [Symbol('yes')]))
data.append(Sep())
data.extend([_symbol('uuid', [Symbol(self.uuid)]), Sep()])
for f in self.fields:
d = f.write()
if d:
data.extend([d, Sep()])
for k, v in self.pins.items():
data.extend([_symbol('pin', [k, _symbol('uuid', [Symbol(v)])]), Sep()])
return _symbol('symbol', data)
def _get_uuid(items, pos, where):
values = _check_symbol_value(items, pos, where + ' uuid', 'uuid')
return _check_symbol(values, 1, where + ' uuid')
class Junction(object):
@staticmethod
def parse(items):
_check_len_total(items, 5, 'junction')
jun = Junction()
jun.pos_x, jun.pos_y, jun.ang = _get_at(items, 1, 'junction')
jun.diameter = _check_symbol_float(items, 2, 'junction', 'diameter')
jun.color = Color.parse(items[3])
jun.uuid = _get_uuid(items, 4, 'junction')
return jun
def write(self):
data = [_symbol('at', [self.pos_x, self.pos_y]),
_symbol('diameter', [self.diameter]),
self.color.write(), Sep(),
_symbol('uuid', [Symbol(self.uuid)]), Sep()]
return _symbol('junction', data)
class BusAlias(object):
@staticmethod
def parse(items):
_check_len_total(items, 3, 'bus_alias')
alias = BusAlias()
alias.name = _check_str(items, 1, 'bus_alias')
elems = _check_symbol_value(items, 2, 'bus_alias', 'members')
alias.members = elems[1:]
return alias
def write(self):
return _symbol('bus_alias', [self.name, _symbol('members', self.members)])
class NoConnect(object):
@staticmethod
def parse(items):
_check_len_total(items, 3, 'no_connect')
nocon = NoConnect()
nocon.pos_x, nocon.pos_y, nocon.ang = _get_at(items, 1, 'no connect')
nocon.uuid = _get_uuid(items, 2, 'no connect')
return nocon
def write(self):
data = [_symbol('at', [self.pos_x, self.pos_y]),
_symbol('uuid', [Symbol(self.uuid)])]
return _symbol('no_connect', data)
class BusEntry(object):
@staticmethod
def parse(items):
_check_len_total(items, 5, 'bus entry')
buse = BusEntry()
buse.pos_x, buse.pos_y, buse.ang = _get_at(items, 1, 'bus entry')
values = _check_symbol_value(items, 2, 'bus entry size', 'size')
buse.size = _get_xy(values)
buse.stroke = Stroke.parse(items[3])
buse.uuid = _get_uuid(items, 4, 'bus entry')
return buse
def write(self):
data = [_symbol('at', [self.pos_x, self.pos_y]),
_symbol('size', [self.size.x, self.size.y]), Sep(),
self.stroke.write(), Sep(),
_symbol('uuid', [Symbol(self.uuid)]), Sep()]
return _symbol('bus_entry', data)
class SchematicWireV6(object):
@staticmethod
def parse(items, name):
_check_len_total(items, 4, name)
wire = SchematicWireV6()
wire.type = name # wire, bus, polyline
wire.points = _get_points(items[1])
wire.stroke = Stroke.parse(items[2])
wire.uuid = _get_uuid(items, 3, name)
return wire
def write(self):
points = [_symbol('xy', [p.x, p.y]) for p in self.points]
data = [_symbol('pts', points), Sep(), self.stroke.write(), Sep(), _symbol('uuid', [Symbol(self.uuid)]), Sep()]
return _symbol(self.type, data)
class SchematicBitmapV6(object):
@staticmethod
def parse(items):
bmp = SchematicBitmapV6()
if len(items) == 5:
bmp.scale = _check_symbol_float(items, 2, 'image', 'scale')
index = 3
else:
_check_len_total(items, 4, 'image')
bmp.scale = None
index = 2
bmp.pos_x, bmp.pos_y, bmp.ang = _get_at(items, 1, 'image')
bmp.uuid = _get_uuid(items, index, 'image')
values = _check_symbol_value(items, index+1, 'image data', 'data')
bmp.data = [_check_symbol(values, i+1, 'image data') for i, d in enumerate(values[1:])]
return bmp
def write(self):
d = []
for v in self.data:
d.append(Symbol(v))
d.append(Sep())
data = [_symbol('at', [self.pos_x, self.pos_y]), Sep(),
_symbol('uuid', [Symbol(self.uuid)]), Sep(),
_symbol('data', [Sep()] + d), Sep()]
return _symbol('image', data)
class Text(object):
@staticmethod
def parse(items, name):
_check_len_total(items, 5, name)
text = Text()
text.name = name
text.text = _check_str(items, 1, name)
text.pos_x, text.pos_y, text.ang = _get_at(items, 2, name)
text.effects = _get_effects(items, 3, name)
text.uuid = _get_uuid(items, 4, name)
return text
def write(self):
data = [self.text,
_symbol('at', [self.pos_x, self.pos_y, self.ang]), Sep(),
self.effects.write(), Sep(),
_symbol('uuid', [Symbol(self.uuid)]), Sep()]
return _symbol(self.name, data)
class GlobalLabel(object):
def __init__(self):
super().__init__()
self.text = ''
self.shape = None
self.fields_autoplaced = False
self.pos_x = self.pos_y = self.ang = 0
self.effects = None
self.uuid = None
self.properties = []
@staticmethod
def parse(items):
label = GlobalLabel()
label.text = _check_str(items, 1, 'global_label')
for c, i in enumerate(items[2:]):
i_type = _check_is_symbol_list(i)
if i_type == 'shape':
label.shape = _check_symbol(i, 1, i_type)
elif i_type == 'fields_autoplaced':
label.fields_autoplaced = True
elif i_type == 'at':
label.pos_x, label.pos_y, label.ang = _get_at(items, c+2, 'global_label')
elif i_type == 'effects':
label.effects = FontEffects.parse(i)
elif i_type == 'uuid':
label.uuid = _get_uuid(items, c+2, 'global_label')
elif i_type == 'property':
label.properties.append(SchematicFieldV6.parse(i))
else:
raise SchError('Unknown label attribute `{}`'.format(i))
return label
def write(self):
data = [self.text,
_symbol('shape', [Symbol(self.shape)]),
_symbol('at', [self.pos_x, self.pos_y, self.ang])]
if self.fields_autoplaced:
data.append(_symbol('fields_autoplaced', []))
data.extend([Sep(), self.effects.write(), Sep(), _symbol('uuid', [Symbol(self.uuid)]), Sep()])
for p in self.properties:
data.extend([p.write(), Sep()])
return _symbol('global_label', data)
class HierarchicalLabel(object):
@staticmethod
def parse(items):
name = 'hierarchical_label'
_check_len_total(items, 6, name)
label = HierarchicalLabel()
label.text = _check_str(items, 1, name)
label.shape = _check_symbol(items[2], 1, 'shape')
label.pos_x, label.pos_y, label.ang = _get_at(items, 3, name)
label.effects = _get_effects(items, 4, name)
label.uuid = _get_uuid(items, 5, name)
return label
def write(self):
data = [self.text,
_symbol('shape', [Symbol(self.shape)]),
_symbol('at', [self.pos_x, self.pos_y, self.ang]), Sep(),
self.effects.write(), Sep(),
_symbol('uuid', [Symbol(self.uuid)]), Sep()]
return _symbol('hierarchical_label', data)
class HSPin(object):
""" Hierarchical Sheet Pin """
# TODO base class with HierarchicalLabel
@staticmethod
def parse(items):
name = 'hierarchical sheet pin'
_check_len_total(items, 6, name)
pin = HSPin()
pin.name = _check_str(items, 1, name+' name')
pin.type = _check_symbol(items, 2, name+' type')
pin.pos_x, pin.pos_y, pin.ang = _get_at(items, 3, name)
pin.effects = _get_effects(items, 4, name)
pin.uuid = _get_uuid(items, 5, name)
return pin
def write(self):
data = [self.name,
Symbol(self.type),
_symbol('at', [self.pos_x, self.pos_y, self.ang]), Sep(),
self.effects.write(), Sep(),
_symbol('uuid', [Symbol(self.uuid)]), Sep()]
return _symbol('pin', data)
class Sheet(object):
def __init__(self):
super().__init__()
self.pos_x = self.pos_y = self.ang = 0
self.w = self.h = 0
self.fields_autoplaced = False
self.stroke = self.fill = self.uuid = None
self.properties = []
self.name = self.file = ''
self.pins = []
self.sch = None
@staticmethod
def parse(items):
sheet = Sheet()
for c, i in enumerate(items[1:]):
i_type = _check_is_symbol_list(i)
if i_type == 'at':
sheet.pos_x, sheet.pos_y, sheet.ang = _get_at(items, c+1, 'sheet')
elif i_type == 'size':
sheet.w = _check_float(i, 1, 'sheet width')
sheet.h = _check_float(i, 2, 'sheet height')
elif i_type == 'fields_autoplaced':
sheet.fields_autoplaced = True
elif i_type == 'stroke':
sheet.stroke = Stroke.parse(i)
elif i_type == 'fill':
sheet.fill = Fill.parse(i)
elif i_type == 'uuid':
sheet.uuid = _get_uuid(items, c+1, 'sheet')
elif i_type == 'property':
field = SchematicFieldV6.parse(i)
sheet.properties.append(field)
if field.name == 'Sheet name':
sheet.name = field.value
elif field.name == 'Sheet file':
sheet.file = field.value
else:
logger.warning(W_UNKFLD+"Unknown sheet property `{}` ({})".format(field.name, field.value))
elif i_type == 'pin':
sheet.pins.append(HSPin.parse(i))
else:
raise SchError('Unknown sheet attribute `{}`'.format(i))
return sheet
def load_sheet(self, project, parent_file, parent_obj):
assert self.name
sheet = SchematicV6()
self.sheet = sheet
parent_dir = os.path.dirname(parent_file)
sheet.sheet_path = os.path.join(parent_obj.sheet_path, self.uuid)
sheet.sheet_path_h = os.path.join(parent_obj.sheet_path_h, self.name)
parent_obj.sheet_paths[sheet.sheet_path] = sheet
sheet.load(os.path.join(parent_dir, self.file), project, parent_obj)
return sheet
def write(self, cross=False):
data = [_symbol('at', [self.pos_x, self.pos_y]),
_symbol('size', [self.w, self.h])]
if self.fields_autoplaced:
data.append(_symbol('fields_autoplaced', []))
data.extend([Sep(), self.stroke.write(), Sep(),
self.fill.write(), Sep(),
_symbol('uuid', [Symbol(self.uuid)]), Sep()])
for p in self.properties:
change_file = cross and p.name == 'Sheet file'
if change_file:
p.value = self.flat_file
data.extend([p.write(), Sep()])
if change_file:
p.value = self.file
for p in self.pins:
data.extend([p.write(), Sep()])
return _symbol('sheet', data)
class SheetInstance(object):
@staticmethod
def parse(items):
name = 'sheet instance'
instances = []
for c, i in enumerate(items[1:]):
v = _check_symbol_value(items, c+1, name, 'path')
instance = SheetInstance()
instance.path = _check_str(v, 1, name+' path')
instance.page = _check_symbol_str(v, 2, name, 'page')
instances.append(instance)
return instances
def write(self):
return _symbol('path', [self.path, _symbol('page', [self.page])])
class SymbolInstance(object):
@staticmethod
def parse(items):
name = 'symbol instance'
instances = []
for c, i in enumerate(items[1:]):
v = _check_symbol_value(items, c+1, name, 'path')
instance = SymbolInstance()
instance.path = _check_str(v, 1, name+' path')
instance.reference = _check_symbol_str(v, 2, name, 'reference')
instance.unit = _check_symbol_int(v, 3, name, 'unit')
instance.value = _check_symbol_str(v, 4, name, 'value')
instance.footprint = _check_symbol_str(v, 5, name, 'footprint')
instances.append(instance)
return instances
def write(self):
data = [self.path, Sep(),
_symbol('reference', [self.reference]),
_symbol('unit', [self.unit]),
_symbol('value', [self.value]),
_symbol('footprint', [self.footprint]), Sep()]
return _symbol('path', data)
def _symbol(name, content):
return [Symbol(name)] + content
def _add_items(items, sch, sep=False, cross=False, pre_sep=True):
if len(items):
if pre_sep:
sch.append(Sep())
for i in items:
if cross:
sch.append(i.write(cross=True))
else:
sch.append(i.write())
sch.append(Sep())
if sep:
sch.append(Sep())
if sep:
sch.pop()
def _add_items_list(name, items, sch):
if not len(items):
return
data = [Sep()]
for s in items:
data.append(s.write())
data.append(Sep())
sch.extend([Sep(), _symbol(name, data), Sep()])
class SchematicV6(Schematic):
def __init__(self):
super().__init__()
self.annotation_error = False
# The title block is optional
self.date = self.title = self.revision = self.company = ''
self.comment = ['']*9
self.max_comments = 9
self.title_ori = self.date_ori = None
self.netlist_version = 'E'
def _fill_missing_title_block(self):
# Fill in some missing info
self.date = GS.format_date(self.date, self.fname, 'SCH')
if not self.title:
self.title = os.path.splitext(os.path.basename(self.fname))[0]
def _get_title_block(self, items):
if not isinstance(items, list):
raise SchError('The title block is not a list')
for item in items:
if not isinstance(item, list) or len(item) < 2 or not isinstance(item[0], Symbol):
raise SchError('Wrong title block entry ({})'.format(item))
i_type = item[0].value()
if i_type == 'title':
self.title_ori = self.title = _check_str(item, 1, i_type)
elif i_type == 'date':
self.date_ori = self.date = _check_str(item, 1, i_type)
elif i_type == 'rev':
self.revision = _check_str(item, 1, i_type)
elif i_type == 'company':
self.company = _check_str(item, 1, i_type)
elif i_type == 'comment':
index = _check_integer(item, 1, i_type)
if index < 1 or index > 9:
raise SchError('Unsupported comment index {} in title block'.format(index))
value = _check_str(item, 2, i_type)
self.comment[index-1] = value
else:
raise SchError('Unsupported entry in title block ({})'.format(item))
self._fill_missing_title_block()
logger.debug("SCH title: `{}`".format(self.title))
logger.debug("SCH date: `{}`".format(self.date))
logger.debug("SCH revision: `{}`".format(self.revision))
logger.debug("SCH company: `{}`".format(self.company))
def _get_lib_symbols(self, comps):
if not isinstance(comps, list):
raise SchError('The lib symbols is not a list')
for c in comps[1:]:
obj = LibComponent.load(c, self.project)
self.lib_symbols.append(obj)
self.lib_symbol_names[obj.lib_id] = obj
def path_to_human(self, path):
""" Converts a UUID path into something we can read """
if path == '/':
return path
res = self.sheet_names[path]
return res
def write_paper(self):
paper_data = [self.paper]
if self.paper == "User":
paper_data.extend([self.paper_w, self.paper_h])
if self.paper_orientation is not None:
paper_data.append(Symbol(self.paper_orientation))
return [Sep(), Sep(), _symbol('paper', paper_data)]
def write_title_block(self):
data = [Sep()]
data += [_symbol('title', [self.title_ori]), Sep()]
data += [_symbol('date', [self.date_ori]), Sep()]
data += [_symbol('rev', [self.revision]), Sep()]
data += [_symbol('company', [self.company]), Sep()]
for num, val in enumerate(self.comment):
data += [_symbol('comment', [num+1, val]), Sep()]
return [Sep(), Sep(), _symbol('title_block', data)]
def write_lib_symbols(self, cross=False):
data = [Sep()]
for s in self.lib_symbols:
data.extend([s.write(), Sep()])
if cross:
data.extend([s.write(cross), Sep()])
return [Sep(), Sep(), _symbol('lib_symbols', data), Sep()]
def save(self, fname, dest_dir):
cross = True
fname = os.path.join(dest_dir, fname)
sch = [Symbol('kicad_sch')]
sch.append(_symbol('version', [self.version]))
sch.append(_symbol('generator', [Symbol(self.generator)]))
sch.append(Sep())
sch.append(Sep())
sch.append(_symbol('uuid', [Symbol(self.uuid)]))
sch.extend(self.write_paper())
if self.title_ori is not None:
sch.extend(self.write_title_block())
sch.extend(self.write_lib_symbols(cross))
# Bus aliases
_add_items(self.bus_alias, sch)
# Connections (aka Junctions)
_add_items(self.junctions, sch, pre_sep=(len(self.bus_alias) == 0))
# No connect
_add_items(self.no_conn, sch)
# Bus entry
_add_items(self.bus_entry, sch)
# Lines (wire, bus and polyline)
if self.wires:
old_type = 'none'
for e in self.wires:
if e.type != old_type and old_type != 'wire':
sch.append(Sep())
sch.append(e.write())
old_type = e.type
sch.append(Sep())
# Images
_add_items(self.bitmaps, sch)
# Texts
_add_items(self.texts, sch)
# Labels
_add_items(self.labels, sch)
# Global Labels
_add_items(self.glabels, sch)
# Hierarchical Labels
_add_items(self.hlabels, sch)
# Symbols
_add_items(self.symbols, sch, sep=True, cross=cross)
# Sheets
_add_items(self.sheets, sch, sep=True, cross=cross)
# Sheet instances
_add_items_list('sheet_instances', self.sheet_instances, sch)
# Symbol instances
_add_items_list('symbol_instances', self.symbol_instances, sch)
with open(fname, 'wt') as f:
f.write(dumps(sch))
f.write('\n')
for sch in self.sheets:
if sch.sch:
sch.sch.save(sch.flat_file if cross else sch.file, dest_dir)
def save_variant(self, dest_dir):
fname = os.path.basename(self.fname)
self.save(fname, dest_dir)
return fname
def _create_flat_name(self, sch):
""" Create a unique name that doesn't contain subdirs.
Is used to save a variant, where we avoid sharing instance data """
# Store it in the UUID -> name
# Used to create a human readable sheet path
self.sheet_names[os.path.join(self.sheet_path, sch.uuid)] = os.path.join(self.sheet_path_h, sch.name)
# Eliminate subdirs
file = sch.file.replace('/', '_')
fparts = os.path.splitext(file)
sch.flat_file = fparts[0]+'_'+str(len(self.sheet_names))+fparts[1]
def load(self, fname, project, parent=None): # noqa: C901
""" Load a v6.x KiCad Schematic.
The caller must be sure the file exists.
Only the schematics are loaded not the libs. """
logger.debug("Loading sheet from "+fname)
if parent is None:
self.fields = ['part']
self.fields_lc = set(self.fields)
self.sheet_paths = {'/': self}
self.lib_symbol_names = {}
self.sheet_path = '/'
self.sheet_path_h = '/'
self.sheet_names = {}
else:
self.fields = parent.fields
self.fields_lc = parent.fields_lc
self.sheet_paths = parent.sheet_paths
self.lib_symbol_names = parent.lib_symbol_names
self.sheet_names = parent.sheet_names
# self.sheet_path is set by sch.load_sheet
self.parent = parent
self.fname = fname
self.project = project
self.lib_symbols = []
self.symbols = []
self.components = []
self.junctions = [] # Connect
self.no_conn = []
self.bus_entry = []
self.wires = []
self.bitmaps = []
self.texts = []
self.labels = []
self.glabels = []
self.hlabels = []
self.sheets = []
self.sheet_instances = []
self.symbol_instances = []
self.bus_alias = []
self.libs = {} # Just for compatibility with v5 class
# TODO: this assumes we are expanding the schematic to allow variant.
# This is needed to overcome KiCad 6 limitations (symbol instances only differ in Reference)
# If we don't want to expand the schematic this member should be shared with the parent
# TODO: We must fix some UUIDs because now we expanded them.
self.symbol_uuids = {}
with open(fname, 'rt') as fh:
error = None
try:
sch = load(fh)[0]
except SExpData as e:
error = str(e)
if error:
raise SchError(error)
if not isinstance(sch, list) or sch[0].value() != 'kicad_sch':
raise SchError('No kicad_sch signature')
for e in sch[1:]:
e_type = _check_is_symbol_list(e)
obj = None
if e_type == 'version':
self.version = _check_integer(e, 1, e_type)
elif e_type == 'generator':
self.generator = _check_symbol(e, 1, e_type)
elif e_type == 'uuid':
self.id = self.uuid = _check_symbol(e, 1, e_type)
elif e_type == 'paper':
self.paper = _check_str(e, 1, e_type)
index = 2
if self.paper == "User":
self.paper_w = _check_float(e, 2, 'paper width')
self.paper_h = _check_float(e, 3, 'paper height')
index += 2
if len(e) > index:
self.paper_orientation = _check_symbol(e, index, 'paper orientation')
else:
self.paper_orientation = None
elif e_type == 'title_block':
self._get_title_block(e[1:])
elif e_type == 'lib_symbols':
self._get_lib_symbols(e)
elif e_type == 'bus_alias':
self.bus_alias.append(BusAlias.parse(e))
elif e_type == 'junction':
self.junctions.append(Junction.parse(e))
elif e_type == 'no_connect':
self.no_conn.append(NoConnect.parse(e))
elif e_type == 'bus_entry':
self.bus_entry.append(BusEntry.parse(e))
elif e_type == 'bus' or e_type == 'wire' or e_type == 'polyline':
self.wires.append(SchematicWireV6.parse(e, e_type))
elif e_type == 'image':
self.bitmaps.append(SchematicBitmapV6.parse(e))
elif e_type == 'text':
self.texts.append(Text.parse(e, e_type))
elif e_type == 'label':
self.labels.append(Text.parse(e, e_type))
elif e_type == 'global_label':
self.glabels.append(GlobalLabel.parse(e))
elif e_type == 'hierarchical_label':
self.hlabels.append(HierarchicalLabel.parse(e))
elif e_type == 'symbol':
obj = SchematicComponentV6.load(e, self.project, self)
if obj.annotation_error:
self.annotation_error = True
self.symbols.append(obj)
self.symbol_uuids[obj.uuid] = obj
elif e_type == 'sheet':
obj = Sheet.parse(e)
self.sheets.append(obj)
self._create_flat_name(obj)
elif e_type == 'sheet_instances':
self.sheet_instances = SheetInstance.parse(e)
elif e_type == 'symbol_instances':
self.symbol_instances = SymbolInstance.parse(e)
else:
raise SchError('Unknown kicad_sch attribute `{}`'.format(e))
if not self.title:
self._fill_missing_title_block()
# Load sub-sheets
for sch in self.sheets:
sheet = sch.load_sheet(project, fname, self)
if sheet.annotation_error:
self.annotation_error = True
sch.sch = sheet
# Assign the page numbers
if parent is None:
self.all_sheets = []
for i in self.sheet_instances:
sheet = self.sheet_paths.get(i.path)
if sheet:
sheet.sheet = i.page
self.all_sheets.append(sheet)
# Create the components list
for s in self.symbol_instances:
# Get a copy of the original symbol
path = os.path.dirname(s.path)
sheet = self.sheet_paths[path]
comp_uuid = os.path.basename(s.path)
comp = sheet.symbol_uuids[comp_uuid]
# Transfer the instance data
comp.set_ref(s.reference)
comp.unit = s.unit
comp.value = s.value
comp.set_footprint(s.footprint)
comp.sheet_path = path
comp.sheet_path_h = self.path_to_human(path)
comp.id = comp_uuid
# Link with its library symbol
try:
lib_symbol = self.lib_symbol_names[comp.lib_id]
except KeyError:
logger.warning(W_MISSCMP+'Missing component `{}`'.format(comp.lib_id))
lib_symbol = LibComponent()
comp.lib_symbol = lib_symbol
comp.is_power = lib_symbol.is_power
comp.desc = lib_symbol.get_field_value('ki_description')
# Now we have all the data
comp._validate()
# Add it to the list
self.components.append(comp)
self.comps_data = self.lib_symbol_names