Skip to content

Commit

Permalink
Footprint search now uses the global fp-lib-table file to choose what…
Browse files Browse the repository at this point in the history
… footprints to search.

* Fixed problem with escape sequences in regex strings.
  • Loading branch information
Dave Vandenbout committed Sep 27, 2019
1 parent 7881a8c commit 84f5693
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 45 deletions.
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
"skidl_part_search = skidl.skidl_part_search:main",
],
"gui_scripts": [
"SKiDL_Part_FP_Search = skidl.search_gui.skidl_part_footprint_search:main"
"skidl_part_fp_search = skidl.search_gui.skidl_part_footprint_search:main",
],
},
package_dir={"skidl": "skidl"},
Expand Down
104 changes: 72 additions & 32 deletions skidl/part_query.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,77 @@ def reset(self):
self.clear() # Clear out cache.
self.valid = False # Cache is empty, hence invalid.

def load(self, fp_tbl_filename):
"""Load cache with footprints from libraries in fp-lib-table file."""

# Read contents of footprint library file into a single string.
try:
with open(fp_tbl_filename) as fp:
tbl = fp.read()
except FileNotFoundError:
return

# Get individual "(lib ...)" entries from the string.
libs = re.findall(
r"\(\s*lib\s* .*? \)\)", tbl, flags=re.IGNORECASE | re.VERBOSE | re.DOTALL
)

# Add the footprint modules found in each enabled KiCad libray.
for lib in libs:

# Skip disabled libraries.
disabled = re.findall(
r"\(\s*disabled\s*\)", lib, flags=re.IGNORECASE | re.VERBOSE
)
if disabled:
continue

# Skip non-KiCad libraries (primarily git repos).
type_ = re.findall(
r'(?:\(\s*type\s*) ("[^"]*?"|[^)]*?) (?:\s*\))',
lib,
flags=re.IGNORECASE | re.VERBOSE,
)[0]
if type_.lower() != "kicad":
continue

# Get the library directory and nickname.
uri = re.findall(
r'(?:\(\s*uri\s*) ("[^"]*?"|[^)]*?) (?:\s*\))',
lib,
flags=re.IGNORECASE | re.VERBOSE,
)[0]
nickname = re.findall(
r'(?:\(\s*name\s*) ("[^"]*?"|[^)]*?) (?:\s*\))',
lib,
flags=re.IGNORECASE | re.VERBOSE,
)[0]

# Remove any quotes around the URI or nickname.
uri = rmv_quotes(uri)
nickname = rmv_quotes(nickname)

# Expand variables and ~ in the URI.
uri = os.path.expandvars(os.path.expanduser(uri))

# Get a list of all the footprint module files in the top-level of the library URI.
filenames = [
fn
for fn in os.listdir(uri)
if os.path.isfile(fn) and fn.lower().endswith(".kicad_mod")
]

# Create an entry in the cache for this nickname. (This will overwrite
# any previous nickname entry, so make sure to scan fp-lib-tables in order of
# increasing priority.) Each entry contains the path to the directory containing
# the footprint module and a dictionary of the modules keyed by the module name
# with an associated value containing the module file contents (which starts off
# as None).
self[nickname] = {
"path": uri,
"modules": {os.path.splitext(fn)[0]: None for fn in filenames},
}


# Cache for storing footprints read from .kicad_mod files.
footprint_cache = FootprintCache()
Expand All @@ -192,8 +263,6 @@ def reset(self):
def search_footprints_iter(terms, tool=None):
"""Return a list of (lib, footprint) sequences that match a regex term."""

global footprint_cache

import skidl

if tool is None:
Expand All @@ -206,36 +275,7 @@ def search_footprints_iter(terms, tool=None):
if not footprint_cache.valid:
footprint_cache.clear()
for path in skidl.footprint_search_paths[tool]:
for dir, subdirs, file_names in os.walk(path):

# Don't visit hidden directories like .git.
if os.path.basename(dir).startswith("."):
del subdirs[:] # Don't visit any subdirs either.
continue

# Skip directories without .pretty extension.
if not dir.lower().endswith(".pretty"):
continue

# Get name of library by stripping .pretty extension.
lib_name = os.path.basename(dir)

# Skip libraries having the same name that were already
# handled in previous search path directories.
if lib_name in footprint_cache:
continue

# Create a dict containing the path to this library
# and another dict for storing the contents of each
# footprint file in the library.
footprint_cache[lib_name] = {"path": dir, "modules": {}}

# Create dict entries for the modules in the footprint lib directory.
modules = footprint_cache[lib_name]["modules"]
for file_name in file_names:
if file_name.lower().endswith(".kicad_mod"):
module_name = os.path.splitext(file_name)[0]
modules[module_name] = None # Don't read file, yet.
footprint_cache.load(os.path.join(path, "fp-lib-table"))

# Get the number of footprint libraries to be searched..
num_fp_libs = len(footprint_cache)
Expand Down
2 changes: 1 addition & 1 deletion skidl/search_gui/skidl_footprint_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ def OnSearchPath(self, event):
self,
title="Set Footprint Search Path",
caption="Footprint Search Path",
tip="Enter {sep}-separated list of directories in which to search for footprints.".format(
tip="Enter {sep}-separated list of directories in which to search for fp-lib-table file.".format(
sep=os.pathsep
),
)
Expand Down
4 changes: 2 additions & 2 deletions skidl/search_gui/skidl_part_footprint_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,7 @@ def OnFootprintSearchPath(self, event):
self,
title="Set Footprint Search Path",
caption="Footprint Search Path",
tip="Enter {sep}-separated list of directories in which to search for footprints.".format(
tip="Enter {sep}-separated list of directories in which to search for fp-lib-table file.".format(
sep=os.pathsep
),
)
Expand Down Expand Up @@ -181,7 +181,7 @@ def ShowHelp(self, e):
General:
* Drag sashes to resize individual panels.
* Double-click column headers to sort table contents.
*
* Ctrl-click to select/deselect table cells.
""",
"Help",
)
Expand Down
9 changes: 6 additions & 3 deletions skidl/search_gui/skidl_part_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -462,9 +462,12 @@ def OnFootprint(self, evt):

# Remove the end parenthesis of the non-footprint part instance
# and append the footprint selected in the footprint panel.
part_inst = "{part_inst}, {footprint})".format(
part_inst=self.part_inst[:-1], footprint=evt.footprint
)
try:
part_inst = "{part_inst}, {footprint})".format(
part_inst=self.part_inst[:-1], footprint=evt.footprint
)
except AttributeError:
return # No part was copied.

# Make a data object to hold the SKiDL part instantiation.
dataObj = wx.TextDataObject()
Expand Down
20 changes: 18 additions & 2 deletions skidl/skidl.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ def store(self, dir="."):
with open(path, "w") as cfg_fp:
json.dump(self, cfg_fp, indent=4)

def get_kicad_lib_tbl_dir():
"""Get the path to where the global fp-lib-table file is found."""

paths = (
"$HOME/.config/kicad",
"~/.config/kicad",
"%APPDATA%/kicad",
"$HOME/Library/Preferences/kicad",
"~/Library/Preferences/kicad",
)
for path in paths:
path = os.path.normpath(os.path.expanduser(os.path.expandvars(path)))
if os.path.lexists(path):
return path
return ""


###############################################################################
# Globals that are used by everything else.
Expand Down Expand Up @@ -107,8 +123,8 @@ def store(self, dir="."):

# If no configuration files were found, set some default footprint search paths.
if "footprint_search_paths" not in skidl_cfg:
skidl_cfg["footprint_search_paths"] = {KICAD: ["."], SKIDL: ["."], SPICE: ["."]}
skidl_cfg["footprint_search_paths"][KICAD].append(os.environ["KISYSMOD"])
dir = get_kicad_lib_tbl_dir()
skidl_cfg["footprint_search_paths"] = {KICAD: [dir], SKIDL: [dir], SPICE: [dir]}

# Cause the footprint cache to be invalidated if the footprint search path changes.
def invalidate_footprint_cache(self, k, v):
Expand Down
8 changes: 4 additions & 4 deletions tests/test_index_slicing.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@

def test_index_slicing_1():
mcu = Part("GameteSnapEDA", "STM32F767ZGT6")
assert len(mcu["\(FMC_D[0:15]\)"]) == 16
assert len(mcu["\(FMC_D[15:0]\)"]) == 16
assert len(mcu["FMC_D[0:15]\)"]) == 16
assert len(mcu["FMC_D[15:0]\)"]) == 16
assert len(mcu[r"\(FMC_D[0:15]\)"]) == 16
assert len(mcu[r"\(FMC_D[15:0]\)"]) == 16
assert len(mcu[r"FMC_D[0:15]\)"]) == 16
assert len(mcu[r"FMC_D[15:0]\)"]) == 16
assert len(mcu["FMC_D[0:15]"]) == 16
assert len(mcu["FMC_D[15:0]"]) == 16

Expand Down

0 comments on commit 84f5693

Please sign in to comment.