[Added] Basic support reading and writing v7 schematics
This commit is contained in:
parent
13d4be5ff3
commit
0b748e1d9d
|
|
@ -1,6 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2020-2022 Salvador E. Tropea
|
||||
# Copyright (c) 2020-2022 Instituto Nacional de Tecnología Industrial
|
||||
# Copyright (c) 2020-2023 Salvador E. Tropea
|
||||
# Copyright (c) 2020-2023 Instituto Nacional de Tecnología Industrial
|
||||
# Copyright (c) 2018 John Beard
|
||||
# License: GPL-3.0
|
||||
# Project: KiBot (formerly KiPlot)
|
||||
|
|
@ -83,7 +83,6 @@ from . import __version__, __copyright__, __license__
|
|||
from . import log
|
||||
log.set_domain('kibot')
|
||||
logger = log.init()
|
||||
from . import dep_downloader
|
||||
from .docopt import docopt
|
||||
# GS will import pcbnew, so we must solve the nightly setup first
|
||||
# Check if we have to run the nightly KiCad build
|
||||
|
|
@ -99,6 +98,7 @@ if os.environ.get('KIAUS_USE_NIGHTLY'): # pragma: no cover (nightly)
|
|||
os.environ['PYTHONPATH'] = pcbnew_path
|
||||
nightly = True
|
||||
from .gs import GS
|
||||
from . import dep_downloader
|
||||
from .misc import EXIT_BAD_ARGS, W_VARCFG, NO_PCBNEW_MODULE, W_NOKIVER, hide_stderr, TRY_INSTALL_CHECK, W_ONWIN
|
||||
from .pre_base import BasePreFlight
|
||||
from .error import KiPlotConfigurationError, config_error
|
||||
|
|
|
|||
|
|
@ -1,12 +1,12 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
# Copyright (c) 2021-2022 Salvador E. Tropea
|
||||
# Copyright (c) 2021-2022 Instituto Nacional de Tecnología Industrial
|
||||
# Copyright (c) 2021-2023 Salvador E. Tropea
|
||||
# Copyright (c) 2021-2023 Instituto Nacional de Tecnología Industrial
|
||||
# License: GPL-3.0
|
||||
# Project: KiBot (formerly KiPlot)
|
||||
"""
|
||||
KiCad v6 Schematic format.
|
||||
KiCad v6/7 Schematic format.
|
||||
A basic implementation of the .kicad_sch file format.
|
||||
Currently oriented to collect the components for the BoM.
|
||||
Currently oriented to collect the components for the BoM and create a variant of the SCH
|
||||
Documentation: https://dev-docs.kicad.org/en/file-formats/sexpr-schematic/
|
||||
"""
|
||||
# Encapsulate file/line
|
||||
|
|
@ -22,6 +22,10 @@ from .sexpdata import load, SExpData, Symbol, dumps, Sep
|
|||
logger = log.get_logger()
|
||||
CROSSED_LIB = 'kibot_crossed'
|
||||
NO_YES = ['no', 'yes']
|
||||
version = None
|
||||
KICAD_7_VER = 20230121
|
||||
SHEET_FILE = {'Sheet file', 'Sheetfile'}
|
||||
SHEET_NAME = {'Sheet name', 'Sheetname'}
|
||||
|
||||
|
||||
def _check_is_symbol_list(e, allow_orphan_symbol=()):
|
||||
|
|
@ -372,7 +376,9 @@ class Stroke(object):
|
|||
def write(self):
|
||||
data = [_symbol('width', [self.width])]
|
||||
data.append(_symbol('type', [Symbol(self.type)]))
|
||||
data.append(self.color.write())
|
||||
c = self.color
|
||||
if version < KICAD_7_VER or c.r+c.g+c.b+c.a != 0:
|
||||
data.append(c.write())
|
||||
return _symbol('stroke', data)
|
||||
|
||||
|
||||
|
|
@ -723,24 +729,37 @@ class SchematicFieldV6(object):
|
|||
self.hide = False
|
||||
|
||||
@staticmethod
|
||||
def parse(items):
|
||||
if len(items) != 6:
|
||||
_check_len_total(items, 5, 'property')
|
||||
def parse(items, number):
|
||||
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
|
||||
name = 'field'
|
||||
field.name = _check_str(items, 1, name+' name')
|
||||
field.value = _check_str(items, 2, name+' value')
|
||||
# Default values
|
||||
field.number = number
|
||||
field.effects = None
|
||||
found_at = False
|
||||
for c, i in enumerate(items[3:]):
|
||||
i_type = _check_is_symbol_list(i)
|
||||
if i_type == 'at':
|
||||
field.x, field.y, field.ang = _get_at(items, c+3, name)
|
||||
found_at = True
|
||||
elif i_type == 'effects':
|
||||
field.effects = FontEffects.parse(i)
|
||||
elif i_type == 'id':
|
||||
field.number = _check_integer(i, 1, name+' id')
|
||||
else:
|
||||
raise SchError('Unknown property attribute `{}`'.format(i))
|
||||
if not found_at:
|
||||
raise SchError('Missing position for property `{}`'.format(field.name))
|
||||
return field
|
||||
|
||||
def write(self):
|
||||
if self.number < 0:
|
||||
return None
|
||||
data = [self.name, self.value, _symbol('id', [self.number])]
|
||||
data = [self.name, self.value]
|
||||
if version < KICAD_7_VER:
|
||||
# Removed in KiCad 7
|
||||
data.append(_symbol('id', [self.number]))
|
||||
data.append(_symbol('at', [self.x, self.y, self.ang]))
|
||||
if self.effects:
|
||||
data.extend([Sep(), self.effects.write(), Sep()])
|
||||
|
|
@ -811,6 +830,7 @@ class LibComponent(object):
|
|||
comp.pins = []
|
||||
comp.all_pins = []
|
||||
comp.unit_count = 1
|
||||
field_id = 0
|
||||
# Variable list
|
||||
for i in c[2:]:
|
||||
i_type = _check_is_symbol_list(i)
|
||||
|
|
@ -838,7 +858,8 @@ class LibComponent(object):
|
|||
comp.is_power = True
|
||||
# SYMBOL_PROPERTIES...
|
||||
elif i_type == 'property':
|
||||
field = SchematicFieldV6.parse(i)
|
||||
field = SchematicFieldV6.parse(i, field_id)
|
||||
field_id += 1
|
||||
comp.fields.append(field)
|
||||
comp.dfields[field.name.lower()] = field
|
||||
# GRAPHIC_ITEMS...
|
||||
|
|
@ -970,6 +991,42 @@ class LibComponent(object):
|
|||
return _symbol('symbol', sdata)
|
||||
|
||||
|
||||
class SymbolInstance(object):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Doesn't exist on v7
|
||||
self.value = None
|
||||
self.footprint = None
|
||||
|
||||
@staticmethod
|
||||
def parse(items):
|
||||
name = 'symbol instance'
|
||||
instances = []
|
||||
for c, _ 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')
|
||||
if len(v) > 4:
|
||||
# KiCad 6
|
||||
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])]
|
||||
if self.value is not None:
|
||||
data.append(_symbol('value', [self.value]))
|
||||
if self.footprint is not None:
|
||||
data.append(_symbol('footprint', [self.footprint]))
|
||||
data.append(Sep())
|
||||
return _symbol('path', data)
|
||||
|
||||
|
||||
class SchematicComponentV6(SchematicComponent):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
|
@ -982,6 +1039,12 @@ class SchematicComponentV6(SchematicComponent):
|
|||
self.mirror = None
|
||||
self.convert = None
|
||||
self.pin_alternates = {}
|
||||
# KiCad v7:
|
||||
self.kicad_dnp = None
|
||||
# Instances classified by project (v7)
|
||||
self.projects = None
|
||||
# All instances, by path (v7)
|
||||
self.all_instances = {}
|
||||
|
||||
def set_ref(self, ref):
|
||||
self.ref = ref
|
||||
|
|
@ -1028,6 +1091,21 @@ class SchematicComponentV6(SchematicComponent):
|
|||
# Not documented
|
||||
self.pin_alternates[pin_name] = _check_symbol_str(i, 3, name, 'alternate')
|
||||
|
||||
def load_project(self, prj):
|
||||
name = _check_str(prj, 1, 'instance project')
|
||||
instances = SymbolInstance().parse(prj[1:])
|
||||
for i in instances:
|
||||
self.all_instances[i.path] = i
|
||||
return name, instances
|
||||
|
||||
def load_instances(self, i):
|
||||
self.projects = []
|
||||
for prj in i[1:]:
|
||||
i_type = _check_is_symbol_list(prj)
|
||||
if i_type != 'project':
|
||||
raise SchError('Found `{}` instead of `project` in symbol instance'.format(i_type))
|
||||
self.projects.append(self.load_project(prj))
|
||||
|
||||
@staticmethod
|
||||
def load(c, project, parent):
|
||||
if not isinstance(c, list):
|
||||
|
|
@ -1044,6 +1122,7 @@ class SchematicComponentV6(SchematicComponent):
|
|||
name = 'component'
|
||||
lib_id_found = False
|
||||
at_found = False
|
||||
field_id = 0
|
||||
|
||||
# Variable list
|
||||
for i in c[1:]:
|
||||
|
|
@ -1077,6 +1156,8 @@ class SchematicComponentV6(SchematicComponent):
|
|||
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 == 'dnp':
|
||||
comp.kicad_dnp = _get_yes_no(i, 1, i_type)
|
||||
elif i_type == 'fields_autoplaced':
|
||||
# Not documented
|
||||
comp.fields_autoplaced = True
|
||||
|
|
@ -1084,7 +1165,8 @@ class SchematicComponentV6(SchematicComponent):
|
|||
comp.uuid = _check_symbol(i, 1, name + ' uuid')
|
||||
# SYMBOL_PROPERTIES...
|
||||
elif i_type == 'property':
|
||||
field = SchematicFieldV6.parse(i)
|
||||
field = SchematicFieldV6.parse(i, field_id)
|
||||
field_id += 1
|
||||
name_lc = field.name.lower()
|
||||
# Add to the global collection
|
||||
if name_lc not in parent.fields_lc:
|
||||
|
|
@ -1099,6 +1181,20 @@ class SchematicComponentV6(SchematicComponent):
|
|||
# PINS...
|
||||
elif i_type == 'pin':
|
||||
comp.load_pin(i, name)
|
||||
# KiCad v7 instances
|
||||
elif i_type == 'instances':
|
||||
comp.load_instances(i)
|
||||
# We should get an instance for us
|
||||
ins = comp.all_instances.get(parent.get_full_path())
|
||||
if ins is None:
|
||||
raise SchError('Missing {} symbol instance for `{}`'.format(comp.name, parent.get_full_path()))
|
||||
# Translate the instance to the v6 format (remove UUID for / and add the component UUID)
|
||||
v6_ins = SymbolInstance()
|
||||
v6_ins.path = os.path.join('/', '/'.join(ins.path.split('/')[2:]), comp.uuid)
|
||||
v6_ins.reference = ins.reference
|
||||
v6_ins.unit = ins.unit
|
||||
# Add to the root symbol_instances, so we reconstruct it
|
||||
parent.symbol_instances.append(v6_ins)
|
||||
else:
|
||||
raise SchError('Unknown component attribute `{}`'.format(i))
|
||||
if not lib_id_found or not at_found:
|
||||
|
|
@ -1129,6 +1225,8 @@ class SchematicComponentV6(SchematicComponent):
|
|||
data.append(Sep())
|
||||
data.append(_symbol('in_bom', [Symbol(NO_YES[self.in_bom])]))
|
||||
data.append(_symbol('on_board', [Symbol(NO_YES[self.on_board])]))
|
||||
if self.kicad_dnp is not None:
|
||||
data.append(_symbol('dnp', [Symbol(NO_YES[self.kicad_dnp])]))
|
||||
if self.fields_autoplaced:
|
||||
data.append(_symbol('fields_autoplaced'))
|
||||
data.append(Sep())
|
||||
|
|
@ -1143,6 +1241,14 @@ class SchematicComponentV6(SchematicComponent):
|
|||
if alternate:
|
||||
pin_data.append(_symbol('alternate', [alternate]))
|
||||
data.extend([_symbol('pin', pin_data), Sep()])
|
||||
if self.projects is not None:
|
||||
prj_data = [Sep()]
|
||||
for prj, ins in self.projects:
|
||||
ins_data = [prj, Sep()]
|
||||
for i in ins:
|
||||
ins_data.extend([i.write(), Sep()])
|
||||
prj_data.extend([_symbol('project', ins_data), Sep()])
|
||||
data.extend([_symbol('instances', prj_data), Sep()])
|
||||
return _symbol('symbol', data)
|
||||
|
||||
|
||||
|
|
@ -1267,11 +1373,11 @@ class SchematicBitmapV6(object):
|
|||
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
|
||||
|
|
@ -1294,61 +1400,57 @@ class GlobalLabel(object):
|
|||
self.effects = None
|
||||
self.uuid = None
|
||||
self.properties = []
|
||||
self.name = 'global_label'
|
||||
|
||||
@staticmethod
|
||||
def parse(items):
|
||||
label = GlobalLabel()
|
||||
label.text = _check_str(items, 1, 'global_label')
|
||||
@classmethod
|
||||
def parse(cls, items):
|
||||
label = cls()
|
||||
label.text = _check_str(items, 1, label.name)
|
||||
field_id = 0
|
||||
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)
|
||||
label.shape = _check_symbol(i, 1, label.name+' '+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')
|
||||
label.pos_x, label.pos_y, label.ang = _get_at(items, c+2, label.name)
|
||||
elif i_type == 'effects':
|
||||
label.effects = FontEffects.parse(i)
|
||||
elif i_type == 'uuid':
|
||||
label.uuid = _get_uuid(items, c+2, 'global_label')
|
||||
label.uuid = _get_uuid(items, c+2, label.name)
|
||||
elif i_type == 'property':
|
||||
label.properties.append(SchematicFieldV6.parse(i))
|
||||
label.properties.append(SchematicFieldV6.parse(i, field_id))
|
||||
field_id += 1
|
||||
else:
|
||||
raise SchError('Unknown label attribute `{}`'.format(i))
|
||||
raise SchError('Unknown {} attribute `{}`'.format(label.name, i))
|
||||
return label
|
||||
|
||||
def write(self):
|
||||
data = [self.text,
|
||||
_symbol('shape', [Symbol(self.shape)]),
|
||||
_symbol('at', [self.pos_x, self.pos_y, self.ang])]
|
||||
data = [self.text]
|
||||
if self.shape is not None:
|
||||
data.append(_symbol('shape', [Symbol(self.shape)]))
|
||||
data.append(_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()])
|
||||
if self.effects is not None:
|
||||
data.extend([Sep(), self.effects.write()])
|
||||
data.extend([Sep(), _symbol('uuid', [Symbol(self.uuid)]), Sep()])
|
||||
for p in self.properties:
|
||||
data.extend([p.write(), Sep()])
|
||||
return _symbol('global_label', data)
|
||||
return _symbol(self.name, 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
|
||||
class Label(GlobalLabel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = '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 HierarchicalLabel(GlobalLabel):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.name = 'hierarchical_label'
|
||||
|
||||
|
||||
class HSPin(object):
|
||||
|
|
@ -1375,82 +1477,6 @@ class HSPin(object):
|
|||
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):
|
||||
|
|
@ -1468,29 +1494,121 @@ class SheetInstance(object):
|
|||
return _symbol('path', [self.path, _symbol('page', [self.page])])
|
||||
|
||||
|
||||
class SymbolInstance(object):
|
||||
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
|
||||
# KiCad v7:
|
||||
# Instances classified by project
|
||||
self.projects = None
|
||||
# All instances, by path (page look-up)
|
||||
self.all_instances = {}
|
||||
|
||||
def load_project(self, prj):
|
||||
name = _check_str(prj, 1, 'instance project')
|
||||
instances = SheetInstance().parse(prj[1:])
|
||||
for i in instances:
|
||||
self.all_instances[i.path] = i.page
|
||||
return name, instances
|
||||
|
||||
def load_instances(self, i):
|
||||
self.projects = []
|
||||
for prj in i[1:]:
|
||||
i_type = _check_is_symbol_list(prj)
|
||||
if i_type != 'project':
|
||||
raise SchError('Found `{}` instead of `project` in sheet instance'.format(i_type))
|
||||
self.projects.append(self.load_project(prj))
|
||||
|
||||
@staticmethod
|
||||
def parse(items):
|
||||
name = 'symbol instance'
|
||||
instances = []
|
||||
for c, _ 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
|
||||
sheet = Sheet()
|
||||
field_id = 0
|
||||
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, field_id)
|
||||
field_id += 1
|
||||
sheet.properties.append(field)
|
||||
if field.name in SHEET_NAME:
|
||||
sheet.name = field.value
|
||||
elif field.name in 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))
|
||||
elif i_type == 'instances':
|
||||
sheet.load_instances(i)
|
||||
else:
|
||||
raise SchError('Unknown sheet attribute `{}`'.format(i))
|
||||
return sheet
|
||||
|
||||
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 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)
|
||||
# self.sheet_paths
|
||||
if self.projects is not None:
|
||||
# KiCad v7 sheet pages are here
|
||||
page = self.all_instances.get(parent_obj.get_full_path())
|
||||
if page is None:
|
||||
raise SchError('Missing sheet instance for `{}`'.format(parent_obj.get_full_path()))
|
||||
else:
|
||||
sheet.sheet = page
|
||||
parent_obj.all_sheets.append(sheet)
|
||||
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 in 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()])
|
||||
if self.projects is not None:
|
||||
prj_data = [Sep()]
|
||||
for prj, ins in self.projects:
|
||||
ins_data = [prj, Sep()]
|
||||
for i in ins:
|
||||
ins_data.extend([i.write(), Sep()])
|
||||
prj_data.extend([_symbol('project', ins_data), Sep()])
|
||||
data.extend([_symbol('instances', prj_data), Sep()])
|
||||
return _symbol('sheet', data)
|
||||
|
||||
|
||||
# Here because we have al s-expr tools here
|
||||
|
|
@ -1680,6 +1798,14 @@ class SchematicV6(Schematic):
|
|||
if base_sheet is None:
|
||||
# We are the base sheet
|
||||
base_sheet = self
|
||||
# Copy potentially modified data from components
|
||||
for s in self.symbol_instances:
|
||||
comp = s.component
|
||||
s.reference = comp.ref
|
||||
if s.value is not None:
|
||||
s.value = comp.value
|
||||
if s.footprint is not None:
|
||||
s.footprint = comp.footprint_lib+':'+comp.footprint if comp.footprint_lib else comp.footprint
|
||||
if saved is None:
|
||||
# Start memorizing saved files
|
||||
saved = set()
|
||||
|
|
@ -1739,14 +1865,8 @@ class SchematicV6(Schematic):
|
|||
# Sheet instances
|
||||
_add_items_list('sheet_instances', self.sheet_instances, sch)
|
||||
# Symbol instances
|
||||
# Copy potentially modified data from components
|
||||
if base_sheet == self:
|
||||
for s in self.symbol_instances:
|
||||
comp = s.component
|
||||
s.reference = comp.ref
|
||||
s.value = comp.value
|
||||
s.footprint = comp.footprint_lib+':'+comp.footprint if comp.footprint_lib else comp.footprint
|
||||
_add_items_list('symbol_instances', self.symbol_instances, sch)
|
||||
if version < KICAD_7_VER:
|
||||
_add_items_list('symbol_instances', self.symbol_instances, sch)
|
||||
logger.debug('Saving schematic: `{}`'.format(fname))
|
||||
# Keep a back-up of existing files
|
||||
if os.path.isfile(fname):
|
||||
|
|
@ -1785,6 +1905,14 @@ class SchematicV6(Schematic):
|
|||
self.title_ori = title
|
||||
return old_title
|
||||
|
||||
def get_full_path(self):
|
||||
""" Path using the UUID of the root. Used by v7 """
|
||||
path = '/'+self.root_sheet.uuid
|
||||
# Avoid returning /UUID/
|
||||
if self.sheet_path != '/':
|
||||
path += self.sheet_path
|
||||
return path
|
||||
|
||||
def load(self, fname, project, parent=None): # noqa: C901
|
||||
""" Load a v6.x KiCad Schematic.
|
||||
The caller must be sure the file exists.
|
||||
|
|
@ -1798,13 +1926,19 @@ class SchematicV6(Schematic):
|
|||
self.sheet_path = '/'
|
||||
self.sheet_path_h = '/'
|
||||
self.sheet_names = {}
|
||||
self.all_sheets = []
|
||||
self.root_sheet = self
|
||||
self.symbol_instances = []
|
||||
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_path/_h is set by sch.load_sheet
|
||||
self.sheet_names = parent.sheet_names
|
||||
# self.sheet_path is set by sch.load_sheet
|
||||
self.all_sheets = parent.all_sheets
|
||||
self.root_sheet = parent.root_sheet
|
||||
self.symbol_instances = parent.symbol_instances
|
||||
self.parent = parent
|
||||
self.fname = fname
|
||||
self.project = project
|
||||
|
|
@ -1822,7 +1956,6 @@ class SchematicV6(Schematic):
|
|||
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.
|
||||
|
|
@ -1830,6 +1963,8 @@ class SchematicV6(Schematic):
|
|||
# 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 = {}
|
||||
if not os.path.isfile(fname):
|
||||
raise SchError('Missing subsheet: '+fname)
|
||||
with open(fname, 'rt') as fh:
|
||||
error = None
|
||||
try:
|
||||
|
|
@ -1845,6 +1980,8 @@ class SchematicV6(Schematic):
|
|||
obj = None
|
||||
if e_type == 'version':
|
||||
self.version = _check_integer(e, 1, e_type)
|
||||
global version
|
||||
version = self.version
|
||||
elif e_type == 'generator':
|
||||
self.generator = _check_symbol(e, 1, e_type)
|
||||
elif e_type == 'uuid':
|
||||
|
|
@ -1879,7 +2016,10 @@ class SchematicV6(Schematic):
|
|||
elif e_type == 'text':
|
||||
self.texts.append(Text.parse(e, e_type))
|
||||
elif e_type == 'label':
|
||||
self.labels.append(Text.parse(e, e_type))
|
||||
if self.version < KICAD_7_VER:
|
||||
self.labels.append(Text.parse(e, e_type))
|
||||
else:
|
||||
self.labels.append(Label.parse(e))
|
||||
elif e_type == 'global_label':
|
||||
self.glabels.append(GlobalLabel.parse(e))
|
||||
elif e_type == 'hierarchical_label':
|
||||
|
|
@ -1908,7 +2048,10 @@ class SchematicV6(Schematic):
|
|||
# Here we finished for sub-sheets
|
||||
return
|
||||
# On the main sheet analyze the sheet and symbol instances
|
||||
self.all_sheets = []
|
||||
# Solve the sheet pages: assign the page numbers.
|
||||
# KiCad 6: for all pages
|
||||
# KiCad 7: only for /, the rest are already assigned
|
||||
# We use the page number for the netlist generation
|
||||
for i in self.sheet_instances:
|
||||
sheet = self.sheet_paths.get(i.path)
|
||||
if sheet:
|
||||
|
|
@ -1925,8 +2068,11 @@ class SchematicV6(Schematic):
|
|||
# Transfer the instance data
|
||||
comp.set_ref(s.reference)
|
||||
comp.unit = s.unit
|
||||
comp.set_value(s.value)
|
||||
comp.set_footprint(s.footprint)
|
||||
# Value and footprint were available in v6, but they were just copies, not really used
|
||||
if s.value is not None:
|
||||
comp.set_value(s.value)
|
||||
if s.footprint is not None:
|
||||
comp.set_footprint(s.footprint)
|
||||
comp.sheet_path = path
|
||||
comp.sheet_path_h = self.path_to_human(path)
|
||||
comp.id = comp_uuid
|
||||
|
|
|
|||
|
|
@ -1628,9 +1628,15 @@
|
|||
(effects (font (size 1.27 1.27)) (justify left bottom))
|
||||
(uuid f144a97d-c3f0-423f-b0a9-3f7dbc42478b)
|
||||
)
|
||||
(label "B{AELEM}" (at 165.1 88.9 0) (fields_autoplaced)
|
||||
(label "B{AELEM}" (at 165.1 88.9 0)
|
||||
(effects (font (size 1.27 1.27)) (justify left bottom))
|
||||
(uuid fc80fa5b-8c07-4dda-8002-331dcafd556b)
|
||||
(property "Netclass" "a net class" (at 165.1 90.17 0)
|
||||
(effects (font (size 1.27 1.27) italic) (justify left))
|
||||
)
|
||||
(property "Something" "pp2" (at 165.1 92.71 0)
|
||||
(effects (font (size 1.27 1.27) italic) (justify left))
|
||||
)
|
||||
)
|
||||
|
||||
(global_label "L2B" (shape output) (at 39.37 55.88 90) (fields_autoplaced)
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ kicad_minor = int(m.group(2))
|
|||
kicad_patch = int(m.group(3))
|
||||
kicad_version = kicad_major*1000000+kicad_minor*1000+kicad_patch
|
||||
if kicad_version >= KICAD_VERSION_5_99:
|
||||
BOARDS_DIR = '../board_samples/kicad_6'
|
||||
BOARDS_DIR = '../board_samples/kicad_'+str(kicad_major)
|
||||
REF_DIR = 'tests/reference/6_0_8'
|
||||
KICAD_SCH_EXT = '.kicad_sch'
|
||||
# Now these layers can be renamed.
|
||||
|
|
|
|||
Loading…
Reference in New Issue