# -*- coding: utf-8 -*-
# The MIT License (MIT) - Copyright (c) 2016-2021 Dave Vandenbout.
"""
Handles schematic libraries for various ECAD tools.
"""
from __future__ import ( # isort:skip
absolute_import,
division,
print_function,
unicode_literals,
)
from builtins import object, str
from future import standard_library
from .logger import active_logger
from .utilities import *
standard_library.install_aliases()
[docs]class SchLib(object):
"""
A class for storing parts from a schematic component library file.
Attributes:
filename: The name of the file from which the parts were read.
parts: The list of parts (composed of Part objects).
Args:
filename: The name of the library file.
tool: The format of the library file (e.g., KICAD).
Keyword Args:
attribs: Key/value pairs of attributes to add to the library.
"""
# Keep a dict of filenames and their associated SchLib object
# for fast loading of libraries.
_cache = {}
def __init__(self, filename=None, tool=None, lib_section=None, **attribs):
"""
Load the parts from a library file.
"""
import skidl
if tool is None:
tool = skidl.get_default_tool()
# Library starts off empty of parts.
self.parts = []
# Attach attributes to the library.
for k, v in list(attribs.items()):
setattr(self, k, v)
# If no filename, create an empty library.
if not filename:
pass
# Load this SchLib with an existing SchLib object if the file name
# matches one in the cache.
elif filename in self._cache:
self.__dict__.update(self._cache[filename].__dict__)
# Otherwise, load from a schematic library file.
else:
try:
# Use the tool name to find the function for loading the library.
load_func = getattr(self, "_load_sch_lib_{}".format(tool))
except AttributeError:
# OK, that didn't work so well...
active_logger.raise_(
ValueError,
"Unsupported ECAD tool library: {}.".format(tool),
)
else:
load_func(
filename, skidl.lib_search_paths[tool], lib_section=lib_section
)
self.filename = filename
# Cache a reference to the library.
self._cache[filename] = self
[docs] @classmethod
def reset(cls):
"""Clear the cache of processed library files."""
cls._cache = {}
[docs] def add_parts(self, *parts):
"""Add one or more parts to a library."""
from .part import TEMPLATE
for part in flatten(parts):
# Parts with the same name are not allowed in the library.
# Also, do not check the backup library to see if the parts
# are in there because that's probably a different library.
if not self.get_parts(use_backup_lib=False, name=re.escape(part.name)):
self.parts.append(part.copy(dest=TEMPLATE))
# Place a pointer to this library into the added part.
self.parts[-1].lib = self
return self
__iadd__ = add_parts
[docs] def get_parts(self, use_backup_lib=True, **criteria):
"""
Return parts from a library that match *all* the given criteria.
Keyword Args:
criteria: One or more keyword-argument pairs. The keyword specifies
the attribute name while the argument contains the desired value
of the attribute.
Returns:
A single Part or a list of Parts that match all the criteria.
"""
import skidl
parts = list_or_scalar(filter_list(self.parts, **criteria))
if not parts and use_backup_lib and skidl.QUERY_BACKUP_LIB:
try:
backup_lib_ = skidl.load_backup_lib()
parts = backup_lib_.get_parts(use_backup_lib=False, **criteria)
except AttributeError:
pass
return parts
[docs] def get_part_by_name(
self, name, allow_multiples=False, silent=False, get_name_only=False
):
"""
Return a Part with the given name or alias from the part list.
Args:
name: The part name or alias to search for in the library.
allow_multiples: If true, return a list of parts matching the name.
If false, return only the first matching part and issue
a warning if there were more than one.
silent: If true, don't issue errors or warnings.
Returns:
A single Part or a list of Parts that match all the criteria.
"""
# First check to see if there is a part or parts with a matching name.
parts = self.get_parts(name=name)
# No part with that name, so check for an alias that matches.
if not parts:
parts = self.get_parts(aliases=name)
# No part with that alias either, so signal an error.
if not parts:
message = "Unable to find part {} in library {}.".format(
name, getattr(self, "filename", "UNKNOWN")
)
if not silent:
active_logger.error(message)
raise ValueError(message)
# Multiple parts with that name or alias exists, so return the list
# of parts or just the first part on the list.
if isinstance(parts, (list, tuple)):
# Return the entire list if multiples are allowed.
if allow_multiples:
parts = [p.parse(get_name_only) for p in parts]
# Just return the first part from the list if multiples are not
# allowed and issue a warning.
else:
if not silent:
active_logger.warning(
"Found multiple parts matching {}. Selecting {}.".format(
name, parts[0].name
)
)
parts = parts[0]
parts.parse(get_name_only)
# Only a single matching part was found, so return that.
else:
parts.parse(get_name_only)
# Return the library part or parts that were found.
return parts
# Get part by name or alias using []'s.
__getitem__ = get_part_by_name
def __str__(self):
"""Return a list of the part names in this library as a string."""
return "\n".join(["{}: {}".format(p.name, p.description) for p in self.parts])
__repr__ = __str__
[docs] def export(self, libname, file_=None, tool=None):
"""
Export a library into a file.
Args:
libname: A string containing the name of the library.
file_: The file the library will be exported to. It can either
be a file object or a string or None. If None, the file
will be the same as the library name with the library
suffix appended.
tool: The CAD tool library format to be used. Currently, this can
only be SKIDL.
"""
def prettify(s):
"""Breakup and indent library export string."""
s = re.sub(r"(Part\()", r"\n \1", s)
s = re.sub(r"(Pin\()", r"\n \1", s)
return s
import skidl
from .tools import SKIDL
if tool is None:
tool = SKIDL
if not file_:
file_ = libname + skidl.lib_suffixes[tool]
export_str = "from skidl import Pin, Part, Alias, SchLib, SKIDL, TEMPLATE\n\n"
export_str += "SKIDL_lib_version = '0.0.1'\n\n"
part_export_str = ",".join([p.export() for p in self.parts])
export_str += "{} = SchLib(tool=SKIDL).add_parts(*[{}])".format(
cnvt_to_var_name(libname), part_export_str
)
export_str = prettify(export_str)
with opened(file_, "w") as f:
f.write(export_str)
def __len__(self):
"""
Return number of parts in library.
"""
return len(self.parts)