0321 from macmini
This commit is contained in:
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/.DS_Store
vendored
Normal file
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/.DS_Store
vendored
Normal file
Binary file not shown.
922
3rdparty/plugins/com_github_Steffen-W_impartGUI/KiCadImport.py
vendored
Normal file
922
3rdparty/plugins/com_github_Steffen-W_impartGUI/KiCadImport.py
vendored
Normal file
@ -0,0 +1,922 @@
|
||||
#!/usr/bin/env python3
|
||||
# coding: utf-8
|
||||
|
||||
# Assembles local KiCad component libraries from downloaded Octopart,
|
||||
# Samacsys, Ultralibrarian and Snapeda zipfiles. Currently assembles just
|
||||
# symbols and footptints. Tested with KiCad 7.0 for Ubuntu.
|
||||
import pathlib
|
||||
from enum import Enum
|
||||
from zipfile import Path
|
||||
from typing import Tuple, Union, Any
|
||||
import re
|
||||
import zipfile
|
||||
from os import stat, remove
|
||||
from os.path import isfile
|
||||
import logging
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from kicad_cli import kicad_cli
|
||||
from s_expression_parse import parse_sexp, search_recursive, extract_properties
|
||||
else:
|
||||
from .kicad_cli import kicad_cli
|
||||
from .s_expression_parse import parse_sexp, search_recursive, extract_properties
|
||||
|
||||
cli = kicad_cli()
|
||||
|
||||
|
||||
class Modification(Enum):
|
||||
MKDIR = 0
|
||||
TOUCH_FILE = 1
|
||||
MODIFIED_FILE = 2
|
||||
EXTRACTED_FILE = 3
|
||||
|
||||
|
||||
class ModifiedObject:
|
||||
def __init__(self):
|
||||
self.dict = {}
|
||||
|
||||
def append(self, obj: pathlib.Path, modification: Modification):
|
||||
self.dict[obj] = modification
|
||||
|
||||
|
||||
# keeps track of which files were modified in case an error occurs we can revert these changes before exiting
|
||||
modified_objects = ModifiedObject()
|
||||
|
||||
|
||||
def check_file(path: pathlib.Path):
|
||||
"""
|
||||
Check if file exists, if not create parent directories and touch file
|
||||
:param path:
|
||||
"""
|
||||
if not path.exists():
|
||||
if not path.parent.is_dir():
|
||||
path.parent.mkdir(parents=True)
|
||||
modified_objects.append(path.parent, Modification.MKDIR)
|
||||
path.touch(mode=0o666)
|
||||
modified_objects.append(path, Modification.TOUCH_FILE)
|
||||
|
||||
|
||||
def unzip(root, suffix):
|
||||
"""
|
||||
return zipfile.Path starting with root ending with suffix else return None
|
||||
"""
|
||||
match = None
|
||||
|
||||
def zipper(parent):
|
||||
if parent.name.endswith(suffix):
|
||||
return parent
|
||||
elif parent.is_dir():
|
||||
for child in parent.iterdir():
|
||||
match = zipper(child)
|
||||
if match:
|
||||
return match
|
||||
|
||||
match = zipper(root)
|
||||
|
||||
return match
|
||||
|
||||
|
||||
class REMOTE_TYPES(Enum):
|
||||
Octopart = 0
|
||||
Samacsys = 1
|
||||
UltraLibrarian = 2
|
||||
Snapeda = 3
|
||||
|
||||
|
||||
class import_lib:
|
||||
def print(self, txt):
|
||||
print("->" + txt)
|
||||
|
||||
def __init__(self):
|
||||
self.KICAD_3RD_PARTY_LINK = "${KICAD_3RD_PARTY}"
|
||||
|
||||
def set_DEST_PATH(self, DEST_PATH_=pathlib.Path.home() / "KiCad"):
|
||||
self.DEST_PATH = pathlib.Path(DEST_PATH_)
|
||||
|
||||
def cleanName(self, name):
|
||||
invalid = '<>:"/\\|?* '
|
||||
name = name.strip()
|
||||
for char in invalid: # remove invalid characters
|
||||
name = name.replace(char, "_")
|
||||
return name
|
||||
|
||||
def get_remote_info(
|
||||
self, zf: zipfile.ZipFile
|
||||
) -> Tuple[Path, Path, Path, Path, REMOTE_TYPES]:
|
||||
"""
|
||||
:param root_path:
|
||||
:type root_path: Path
|
||||
:return: dcm_path, lib_path, footprint_path, model_path, remote_type
|
||||
"""
|
||||
self.footprint_name = None
|
||||
|
||||
root_path = zipfile.Path(zf)
|
||||
self.dcm_path = root_path / "device.dcm"
|
||||
self.lib_path = root_path / "device.lib"
|
||||
self.footprint_path = root_path / "device.pretty"
|
||||
self.model_path = root_path / "device.step"
|
||||
# todo fill in model path for OCTOPART
|
||||
if (
|
||||
self.dcm_path.exists()
|
||||
and self.lib_path.exists()
|
||||
and self.footprint_path.exists()
|
||||
):
|
||||
remote_type = REMOTE_TYPES.Octopart
|
||||
return (
|
||||
self.dcm_path,
|
||||
self.lib_path,
|
||||
self.footprint_path,
|
||||
self.model_path,
|
||||
remote_type,
|
||||
)
|
||||
|
||||
self.lib_path_new = unzip(root_path, ".kicad_sym")
|
||||
|
||||
directory = unzip(root_path, "KiCad")
|
||||
if directory:
|
||||
self.dcm_path = unzip(directory, ".dcm")
|
||||
self.lib_path = unzip(directory, ".lib")
|
||||
self.footprint_path = directory
|
||||
self.model_path = unzip(root_path, ".step")
|
||||
if not self.model_path:
|
||||
self.model_path = unzip(root_path, ".stp")
|
||||
|
||||
assert self.dcm_path and (
|
||||
self.lib_path or self.lib_path_new
|
||||
), "Not in samacsys format"
|
||||
remote_type = REMOTE_TYPES.Samacsys
|
||||
return (
|
||||
self.dcm_path,
|
||||
self.lib_path,
|
||||
self.footprint_path,
|
||||
self.model_path,
|
||||
remote_type,
|
||||
)
|
||||
|
||||
directory = root_path / "KiCAD"
|
||||
if directory.exists():
|
||||
self.dcm_path = unzip(directory, ".dcm")
|
||||
self.lib_path = unzip(directory, ".lib")
|
||||
self.footprint_path = unzip(directory, ".pretty")
|
||||
self.model_path = unzip(root_path, ".step")
|
||||
if not self.model_path:
|
||||
self.model_path = unzip(root_path, ".stp")
|
||||
|
||||
assert (
|
||||
self.lib_path or self.lib_path_new
|
||||
) and self.footprint_path, "Not in ultralibrarian format"
|
||||
remote_type = REMOTE_TYPES.UltraLibrarian
|
||||
return (
|
||||
self.dcm_path,
|
||||
self.lib_path,
|
||||
self.footprint_path,
|
||||
self.model_path,
|
||||
remote_type,
|
||||
)
|
||||
|
||||
footprint = unzip(root_path, ".kicad_mod")
|
||||
self.lib_path = unzip(root_path, ".lib")
|
||||
if self.lib_path or self.lib_path_new:
|
||||
self.dcm_path = unzip(root_path, ".dcm")
|
||||
# self.footprint_path = root_path
|
||||
self.footprint_path = None
|
||||
if footprint:
|
||||
self.footprint_path = footprint.parent
|
||||
self.model_path = unzip(root_path, ".step")
|
||||
remote_type = REMOTE_TYPES.Snapeda
|
||||
|
||||
assert (
|
||||
self.lib_path or self.lib_path_new
|
||||
) and self.footprint_path, "Not in Snapeda format"
|
||||
return (
|
||||
self.dcm_path,
|
||||
self.lib_path,
|
||||
self.footprint_path,
|
||||
self.model_path,
|
||||
remote_type,
|
||||
)
|
||||
|
||||
if footprint or self.lib_path_new:
|
||||
assert False, "Unknown library zipfile"
|
||||
else:
|
||||
assert False, "zipfile is probably not a library to import"
|
||||
|
||||
def import_dcm(
|
||||
self,
|
||||
device: str,
|
||||
remote_type: REMOTE_TYPES,
|
||||
dcm_path: pathlib.Path,
|
||||
overwrite_if_exists=True,
|
||||
file_ending="",
|
||||
) -> Tuple[Union[pathlib.Path, None], Union[pathlib.Path, None]]:
|
||||
"""
|
||||
# .dcm file parsing
|
||||
# Note this reads in the existing dcm file for the particular remote repo, and tries to catch any duplicates
|
||||
# before overwriting or creating duplicates. It reads the existing dcm file line by line and simply copy+paste
|
||||
# each line if nothing will be overwritten or duplicated. If something could be overwritten or duplicated, the
|
||||
# terminal will prompt whether to overwrite or to keep the existing content and ignore the new file contents.
|
||||
:returns: dcm_file_read, dcm_file_write
|
||||
"""
|
||||
|
||||
self.dcm_skipped = False
|
||||
|
||||
# Array of values defining all attributes of .dcm file
|
||||
dcm_attributes = (
|
||||
dcm_path.read_text(encoding="utf-8").splitlines()
|
||||
if dcm_path
|
||||
else ["#", "# " + device, "#", "$CMP " + device, "D", "F", "$ENDCMP"]
|
||||
)
|
||||
|
||||
# Find which lines contain the component information (ignore the rest).
|
||||
index_start = None
|
||||
index_end = None
|
||||
index_header_start = None
|
||||
|
||||
for attribute_idx, attribute in enumerate(dcm_attributes):
|
||||
if index_start is None:
|
||||
if attribute.startswith("#"):
|
||||
if attribute.strip() == "#" and index_header_start is None:
|
||||
index_header_start = attribute_idx # header start
|
||||
elif attribute.startswith("$CMP "):
|
||||
component_name = attribute[5:].strip()
|
||||
if not self.cleanName(component_name) == self.cleanName(device):
|
||||
raise Warning("Unexpected device in " + dcm_path.name)
|
||||
dcm_attributes[attribute_idx] = attribute.replace(
|
||||
component_name, device, 1
|
||||
)
|
||||
index_start = attribute_idx
|
||||
else:
|
||||
index_header_start = None
|
||||
elif index_end is None:
|
||||
if attribute.startswith("$CMP "):
|
||||
raise Warning("Multiple devices in " + dcm_path.name)
|
||||
elif attribute.startswith("$ENDCMP"):
|
||||
index_end = attribute_idx + 1
|
||||
elif attribute.startswith("D"):
|
||||
description = attribute[2:].strip()
|
||||
if description:
|
||||
dcm_attributes[attribute_idx] = "D " + description
|
||||
elif attribute.startswith("F"):
|
||||
datasheet = attribute[2:].strip()
|
||||
if datasheet:
|
||||
dcm_attributes[attribute_idx] = "F " + datasheet
|
||||
if index_end is None:
|
||||
raise Warning(device + "not found in " + dcm_path.name)
|
||||
|
||||
dcm_file_read = self.DEST_PATH / (remote_type.name + file_ending + ".dcm")
|
||||
|
||||
dcm_file_write = self.DEST_PATH / (remote_type.name + ".dcm~")
|
||||
overwrite_existing = overwrote_existing = False
|
||||
|
||||
check_file(dcm_file_read)
|
||||
check_file(dcm_file_write)
|
||||
|
||||
with dcm_file_read.open("rt", encoding="utf-8") as readfile:
|
||||
with dcm_file_write.open("wt", encoding="utf-8") as writefile:
|
||||
if stat(dcm_file_read).st_size == 0:
|
||||
# todo Handle appending to empty file
|
||||
with dcm_file_read.open("wt", encoding="utf-8") as template_file:
|
||||
template = ["EESchema-DOCLIB Version 2.0", "#End Doc Library"]
|
||||
template_file.writelines(line + "\n" for line in template)
|
||||
template_file.close()
|
||||
|
||||
for line in readfile:
|
||||
if re.match("# *end ", line, re.IGNORECASE):
|
||||
if not overwrote_existing:
|
||||
writefile.write(
|
||||
"\n".join(
|
||||
dcm_attributes[
|
||||
(
|
||||
index_start
|
||||
if index_header_start is None
|
||||
else index_header_start
|
||||
) : index_end
|
||||
]
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
writefile.write(line)
|
||||
break
|
||||
elif line.startswith("$CMP "):
|
||||
component_name = line[5:].strip()
|
||||
if component_name.startswith(device):
|
||||
if overwrite_if_exists:
|
||||
overwrite_existing = True
|
||||
self.print("Overwrite existing dcm")
|
||||
else:
|
||||
overwrite_existing = False
|
||||
# self.print("Import of dcm skipped")
|
||||
self.dcm_skipped = True
|
||||
return dcm_file_read, dcm_file_write
|
||||
writefile.write(
|
||||
"\n".join(dcm_attributes[index_start:index_end]) + "\n"
|
||||
)
|
||||
overwrote_existing = True
|
||||
else:
|
||||
writefile.write(line)
|
||||
elif overwrite_existing:
|
||||
if line.startswith("$ENDCMP"):
|
||||
overwrite_existing = False
|
||||
else:
|
||||
writefile.write(line)
|
||||
|
||||
return dcm_file_read, dcm_file_write
|
||||
|
||||
def import_model(
|
||||
self, model_path: pathlib.Path, remote_type: REMOTE_TYPES, overwrite_if_exists
|
||||
) -> Union[pathlib.Path, None]:
|
||||
# --------------------------------------------------------------------------------------------------------
|
||||
# 3D Model file extraction
|
||||
# --------------------------------------------------------------------------------------------------------
|
||||
|
||||
if not model_path:
|
||||
return False
|
||||
|
||||
write_file = self.DEST_PATH / (remote_type.name + ".3dshapes") / model_path.name
|
||||
|
||||
self.model_skipped = False
|
||||
|
||||
if write_file.exists():
|
||||
if overwrite_if_exists:
|
||||
overwrite_existing = True
|
||||
else:
|
||||
self.print("Import of 3d model skipped")
|
||||
self.model_skipped = True
|
||||
return model_path
|
||||
|
||||
if model_path.is_file():
|
||||
check_file(write_file)
|
||||
write_file.write_bytes(model_path.read_bytes())
|
||||
modified_objects.append(write_file, Modification.EXTRACTED_FILE)
|
||||
if overwrite_if_exists:
|
||||
self.print("Overwrite existing 3d model")
|
||||
else:
|
||||
self.print("Import 3d model")
|
||||
|
||||
return model_path
|
||||
|
||||
def import_footprint(
|
||||
self,
|
||||
remote_type: REMOTE_TYPES,
|
||||
footprint_path: pathlib.Path,
|
||||
found_model: pathlib.Path,
|
||||
overwrite_if_exists=True,
|
||||
) -> Tuple[Union[pathlib.Path, None], Union[pathlib.Path, None]]:
|
||||
"""
|
||||
# Footprint file parsing
|
||||
:returns: footprint_file_read, footprint_file_write
|
||||
"""
|
||||
|
||||
footprint_file_read = None
|
||||
footprint_file_write = None
|
||||
self.footprint_skipped = False
|
||||
|
||||
footprint_path_item_tmp = None
|
||||
for (
|
||||
footprint_path_item
|
||||
) in footprint_path.iterdir(): # try to use only newer file
|
||||
if footprint_path_item.name.endswith(".kicad_mod"):
|
||||
footprint_path_item_tmp = footprint_path_item
|
||||
break
|
||||
elif footprint_path_item.name.endswith(".mod"):
|
||||
footprint_path_item_tmp = footprint_path_item
|
||||
|
||||
footprint_path_item = footprint_path_item_tmp
|
||||
if not footprint_path_item:
|
||||
self.print("No footprint found")
|
||||
return footprint_file_read, footprint_file_write
|
||||
|
||||
if footprint_path_item.name.endswith("mod"):
|
||||
footprint = footprint_path_item.read_text(encoding="utf-8")
|
||||
|
||||
footprint_write_path = self.DEST_PATH / (remote_type.name + ".pretty")
|
||||
footprint_file_read = footprint_write_path / footprint_path_item.name
|
||||
footprint_file_write = footprint_write_path / (
|
||||
footprint_path_item.name + "~"
|
||||
)
|
||||
|
||||
if found_model:
|
||||
footprint.splitlines()
|
||||
model_path = f"{self.KICAD_3RD_PARTY_LINK}/{remote_type.name}.3dshapes/{found_model.name}"
|
||||
model = [
|
||||
f' (model "{model_path}"',
|
||||
" (offset (xyz 0 0 0))",
|
||||
" (scale (xyz 1 1 1))",
|
||||
" (rotate (xyz 0 0 0))",
|
||||
" )",
|
||||
]
|
||||
|
||||
overwrite_existing = overwrote_existing = False
|
||||
|
||||
if footprint_file_read.exists():
|
||||
if overwrite_if_exists:
|
||||
overwrite_existing = True
|
||||
self.print("Overwrite existing footprint")
|
||||
else:
|
||||
self.print("Import of footprint skipped")
|
||||
self.footprint_skipped = True
|
||||
return footprint_file_read, footprint_file_write
|
||||
|
||||
check_file(footprint_file_read)
|
||||
with footprint_file_read.open("wt", encoding="utf-8") as wr:
|
||||
wr.write(footprint)
|
||||
overwrote_existing = True
|
||||
|
||||
check_file(footprint_file_write)
|
||||
|
||||
with footprint_file_read.open("rt", encoding="utf-8") as readfile:
|
||||
with footprint_file_write.open("wt", encoding="utf-8") as writefile:
|
||||
if stat(footprint_file_read).st_size == 0:
|
||||
# todo Handle appending to empty file?
|
||||
pass
|
||||
|
||||
lines = readfile.readlines()
|
||||
|
||||
write_3d_into_file = False
|
||||
for line_idx, line in enumerate(lines):
|
||||
if not write_3d_into_file and line_idx == len(lines) - 1:
|
||||
writefile.writelines(
|
||||
model_line + "\n" for model_line in model
|
||||
)
|
||||
writefile.write(line)
|
||||
break
|
||||
elif line.strip().startswith(r"(model"):
|
||||
writefile.write(model[0] + "\n")
|
||||
write_3d_into_file = True
|
||||
else:
|
||||
writefile.write(line)
|
||||
self.print("Import footprint")
|
||||
else:
|
||||
check_file(footprint_file_write)
|
||||
with footprint_file_write.open("wt", encoding="utf-8") as wr:
|
||||
wr.write(footprint)
|
||||
self.print("Import footprint")
|
||||
|
||||
return footprint_file_read, footprint_file_write
|
||||
|
||||
def import_lib(
|
||||
self,
|
||||
remote_type: REMOTE_TYPES,
|
||||
lib_path: pathlib.Path,
|
||||
overwrite_if_exists=True,
|
||||
) -> Tuple[str, Union[pathlib.Path, None], Union[pathlib.Path, None]]:
|
||||
"""
|
||||
.lib file parsing
|
||||
Note this reads in the existing lib file for the particular remote repo, and tries to catch any duplicates
|
||||
before overwriting or creating duplicates. It reads the existing dcm file line by line and simply copy+paste
|
||||
each line if nothing will be overwritten or duplicated. If something could be overwritten or duplicated, the
|
||||
terminal will prompt whether to overwrite or to keep the existing content and ignore the new file contents.
|
||||
:returns: device, lib_file_read, lib_file_write
|
||||
"""
|
||||
|
||||
self.lib_skipped = False
|
||||
|
||||
device = None
|
||||
lib_lines = lib_path.read_text(encoding="utf-8").splitlines()
|
||||
|
||||
# Find which lines contain the component information in file to be imported
|
||||
index_start = None
|
||||
index_end = None
|
||||
index_header_start = None
|
||||
for line_idx, line in enumerate(lib_lines):
|
||||
if index_start is None:
|
||||
if line.startswith("#"):
|
||||
if line.strip() == "#" and index_header_start is None:
|
||||
index_header_start = line_idx # header start
|
||||
elif line.startswith("DEF "):
|
||||
device = line.split()[1]
|
||||
index_start = line_idx
|
||||
else:
|
||||
index_header_start = None
|
||||
elif index_end is None:
|
||||
if line.startswith("F2"):
|
||||
footprint = line.split()[1]
|
||||
footprint = footprint.strip('"')
|
||||
self.footprint_name = self.cleanName(footprint)
|
||||
lib_lines[line_idx] = line.replace(
|
||||
footprint, remote_type.name + ":" + self.footprint_name, 1
|
||||
)
|
||||
elif line.startswith("ENDDEF"):
|
||||
index_end = line_idx + 1
|
||||
elif line.startswith("F1 "):
|
||||
lib_lines[line_idx] = line.replace(device, device, 1)
|
||||
elif line.startswith("DEF "):
|
||||
raise Warning("Multiple devices in " + lib_path.name)
|
||||
if index_end is None:
|
||||
raise Warning(device + " not found in " + lib_path.name)
|
||||
|
||||
lib_file_read = self.DEST_PATH / (remote_type.name + ".lib")
|
||||
lib_file_write = self.DEST_PATH / (remote_type.name + ".lib~")
|
||||
overwrite_existing = overwrote_existing = overwritten = False
|
||||
|
||||
check_file(lib_file_read)
|
||||
check_file(lib_file_write)
|
||||
|
||||
with lib_file_read.open("rt", encoding="utf-8") as readfile:
|
||||
with lib_file_write.open("wt", encoding="utf-8") as writefile:
|
||||
if stat(lib_file_read).st_size == 0:
|
||||
# todo Handle appending to empty file
|
||||
with lib_file_read.open("wt", encoding="utf-8") as template_file:
|
||||
template = [
|
||||
"EESchema-LIBRARY Version 2.4",
|
||||
"#encoding utf-8",
|
||||
"# End Library",
|
||||
]
|
||||
template_file.writelines(line + "\n" for line in template)
|
||||
template_file.close()
|
||||
|
||||
# For each line in the existing lib file (not the file being read from the zip. The lib file you will
|
||||
# add it to.)
|
||||
for line in readfile:
|
||||
# Is this trying to match ENDDRAW, ENDDEF, End Library or any of the above?
|
||||
if re.match("# *end ", line, re.IGNORECASE):
|
||||
# If you already overwrote the new info don't add it to the end
|
||||
if not overwrote_existing:
|
||||
writefile.write(
|
||||
"\n".join(
|
||||
lib_lines[
|
||||
(
|
||||
index_start
|
||||
if index_header_start is None
|
||||
else index_header_start
|
||||
) : index_end
|
||||
]
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
writefile.write(line)
|
||||
break
|
||||
# Catch start of new component definition
|
||||
elif line.startswith("DEF "):
|
||||
component_name = line.split()[1]
|
||||
# Catch if the currently read component matches the name of the component you are planning to
|
||||
# write
|
||||
if component_name.startswith(device):
|
||||
# Ask if you want to overwrite existing component
|
||||
|
||||
if overwrite_if_exists:
|
||||
overwrite_existing = True
|
||||
overwritten = True
|
||||
self.print("Overwrite existing lib")
|
||||
else:
|
||||
self.print("Import of lib skipped")
|
||||
self.lib_skipped = True
|
||||
return device, lib_file_read, lib_file_write
|
||||
writefile.write(
|
||||
"\n".join(lib_lines[index_start:index_end]) + "\n"
|
||||
)
|
||||
overwrote_existing = True
|
||||
else:
|
||||
writefile.write(line)
|
||||
elif overwrite_existing:
|
||||
if line.startswith("ENDDEF"):
|
||||
overwrite_existing = False
|
||||
else:
|
||||
writefile.write(line)
|
||||
if not overwritten:
|
||||
self.print("Import lib")
|
||||
return device, lib_file_read, lib_file_write
|
||||
|
||||
def import_lib_new(
|
||||
self,
|
||||
remote_type: REMOTE_TYPES,
|
||||
lib_path: pathlib.Path,
|
||||
overwrite_if_exists=True,
|
||||
) -> Tuple[str, Union[pathlib.Path, None], Union[pathlib.Path, None]]:
|
||||
symbol_name = None
|
||||
|
||||
def extract_symbol_names(input_text):
|
||||
pattern = r'"(.*?)"' # Searches for text in quotes
|
||||
# Searches for "(symbol" followed by text in quotes
|
||||
pattern = r'\(symbol\s+"(.*?)"'
|
||||
matches = re.findall(pattern, input_text)
|
||||
return matches
|
||||
|
||||
symbol_name = None
|
||||
|
||||
def replace_footprint_name(string, original_name, remote_type_name, new_name):
|
||||
escaped_original_name = re.escape(original_name)
|
||||
pattern = rf'\(property\s+"Footprint"\s+"{escaped_original_name}"'
|
||||
replacement = f'(property "Footprint" "{remote_type_name}:{new_name}"'
|
||||
modified_string = re.sub(pattern, replacement, string, flags=re.MULTILINE)
|
||||
return modified_string
|
||||
|
||||
def extract_symbol_section_new(sexpression: str, symbol_name):
|
||||
pattern = re.compile(
|
||||
r'\(symbol "{}"(.*?)\)\n\)'.format(re.escape(symbol_name)), re.DOTALL
|
||||
)
|
||||
match = pattern.search(sexpression)
|
||||
if match:
|
||||
return f'(symbol "{symbol_name}"{match.group(1)})'
|
||||
return None
|
||||
|
||||
RAW_text = lib_path.read_text(encoding="utf-8") # new
|
||||
sexp_list = parse_sexp(RAW_text) # new
|
||||
|
||||
symbol_name = search_recursive(sexp_list, "symbol") # new
|
||||
symbol_properties = extract_properties(sexp_list) # new
|
||||
|
||||
symbol_section = extract_symbol_section_new(RAW_text, symbol_name)
|
||||
|
||||
Footprint_name_raw = symbol_properties["Footprint"] # new
|
||||
self.footprint_name = self.cleanName(Footprint_name_raw)
|
||||
|
||||
symbol_section = replace_footprint_name(
|
||||
symbol_section, Footprint_name_raw, remote_type.name, self.footprint_name
|
||||
)
|
||||
|
||||
lib_file_read = self.DEST_PATH / (remote_type.name + ".kicad_sym")
|
||||
lib_file_read_old = self.DEST_PATH / (remote_type.name + "_kicad_sym.kicad_sym")
|
||||
lib_file_write = self.DEST_PATH / (remote_type.name + ".kicad_sym~")
|
||||
if isfile(lib_file_read_old) and not isfile(lib_file_read):
|
||||
lib_file_read = lib_file_read_old
|
||||
|
||||
if not lib_file_read.exists(): # library does not yet exist
|
||||
with lib_file_write.open("wt", encoding="utf-8") as writefile:
|
||||
text = RAW_text.strip().split("\n")
|
||||
writefile.write(text[0] + "\n")
|
||||
writefile.write(symbol_section + "\n")
|
||||
writefile.write(text[-1])
|
||||
|
||||
check_file(lib_file_read)
|
||||
self.print("Import kicad_sym")
|
||||
return symbol_name, lib_file_read, lib_file_write
|
||||
|
||||
check_file(lib_file_read)
|
||||
|
||||
lib_file_txt = lib_file_read.read_text(encoding="utf-8")
|
||||
existing_libs = extract_symbol_names(lib_file_txt)
|
||||
|
||||
if symbol_name in existing_libs:
|
||||
if overwrite_if_exists:
|
||||
self.print("Overwrite existing kicad_sym is not implemented") # TODO
|
||||
else:
|
||||
self.print("Import of kicad_sym skipped")
|
||||
|
||||
return symbol_name, lib_file_read, lib_file_write
|
||||
|
||||
closing_bracket = lib_file_txt.rfind(")")
|
||||
|
||||
with lib_file_write.open("wt", encoding="utf-8") as writefile:
|
||||
writefile.write(lib_file_txt[:closing_bracket])
|
||||
writefile.write(symbol_section + "\n")
|
||||
writefile.write(lib_file_txt[closing_bracket:])
|
||||
|
||||
self.print("Import kicad_sym")
|
||||
|
||||
return symbol_name, lib_file_read, lib_file_write
|
||||
|
||||
def import_all(
|
||||
self, zip_file: pathlib.Path, overwrite_if_exists=True, import_old_format=True
|
||||
):
|
||||
"""zip is a pathlib.Path to import the symbol from"""
|
||||
if not zipfile.is_zipfile(zip_file):
|
||||
return None
|
||||
|
||||
self.print(f"Import: {zip_file}")
|
||||
|
||||
with zipfile.ZipFile(zip_file) as zf:
|
||||
(
|
||||
dcm_path,
|
||||
lib_path,
|
||||
footprint_path,
|
||||
model_path,
|
||||
remote_type,
|
||||
) = self.get_remote_info(zf)
|
||||
|
||||
self.print("Identify " + remote_type.name)
|
||||
|
||||
CompatibilityMode = False
|
||||
if lib_path and not self.lib_path_new:
|
||||
CompatibilityMode = True
|
||||
|
||||
temp_path = self.DEST_PATH / "temp.lib"
|
||||
temp_path_new = self.DEST_PATH / "temp.kicad_sym"
|
||||
|
||||
if temp_path.exists():
|
||||
remove(temp_path)
|
||||
if temp_path_new.exists():
|
||||
remove(temp_path_new)
|
||||
|
||||
with temp_path.open("wt", encoding="utf-8") as writefile:
|
||||
text = lib_path.read_text(encoding="utf-8")
|
||||
writefile.write(text)
|
||||
|
||||
if temp_path.exists() and cli.exists():
|
||||
cli.upgrade_sym_lib(temp_path, temp_path_new)
|
||||
self.print("compatibility mode: convert from .lib to .kicad_sym")
|
||||
|
||||
if temp_path_new.exists() and temp_path_new.is_file():
|
||||
self.lib_path_new = temp_path_new
|
||||
else:
|
||||
self.print("error during conversion")
|
||||
|
||||
if self.lib_path_new:
|
||||
device, lib_file_new_read, lib_file_new_write = self.import_lib_new(
|
||||
remote_type, self.lib_path_new, overwrite_if_exists
|
||||
)
|
||||
|
||||
file_ending = ""
|
||||
if "_kicad_sym" in lib_file_new_read.name:
|
||||
file_ending = "_kicad_sym"
|
||||
|
||||
dcm_file_new_read, dcm_file_new_write = self.import_dcm(
|
||||
device,
|
||||
remote_type,
|
||||
dcm_path,
|
||||
overwrite_if_exists,
|
||||
file_ending=file_ending,
|
||||
)
|
||||
if not import_old_format:
|
||||
lib_path = None
|
||||
|
||||
if CompatibilityMode:
|
||||
if temp_path.exists():
|
||||
remove(temp_path)
|
||||
if temp_path_new.exists():
|
||||
remove(temp_path_new)
|
||||
|
||||
if lib_path:
|
||||
device, lib_file_read, lib_file_write = self.import_lib(
|
||||
remote_type, lib_path, overwrite_if_exists
|
||||
)
|
||||
|
||||
dcm_file_read, dcm_file_write = self.import_dcm(
|
||||
device, remote_type, dcm_path, overwrite_if_exists
|
||||
)
|
||||
|
||||
found_model = self.import_model(
|
||||
model_path, remote_type, overwrite_if_exists
|
||||
)
|
||||
|
||||
footprint_file_read, footprint_file_write = self.import_footprint(
|
||||
remote_type, footprint_path, found_model, overwrite_if_exists
|
||||
)
|
||||
|
||||
# replace read files with write files only after all operations succeeded
|
||||
if self.lib_path_new:
|
||||
if lib_file_new_write.exists():
|
||||
lib_file_new_write.replace(lib_file_new_read)
|
||||
|
||||
if dcm_file_new_write.exists() and not self.dcm_skipped:
|
||||
dcm_file_new_write.replace(dcm_file_new_read)
|
||||
elif dcm_file_new_write.exists():
|
||||
remove(dcm_file_new_write)
|
||||
|
||||
if lib_path:
|
||||
if dcm_file_write.exists() and not self.dcm_skipped:
|
||||
dcm_file_write.replace(dcm_file_read)
|
||||
elif dcm_file_write.exists():
|
||||
remove(dcm_file_write)
|
||||
|
||||
if lib_file_write.exists() and not self.lib_skipped:
|
||||
lib_file_write.replace(lib_file_read)
|
||||
elif lib_file_write.exists():
|
||||
remove(lib_file_write)
|
||||
|
||||
if (
|
||||
footprint_file_read
|
||||
and (self.footprint_name != footprint_file_read.stem)
|
||||
and not self.footprint_skipped
|
||||
):
|
||||
self.print(
|
||||
'Warning renaming footprint file "'
|
||||
+ footprint_file_read.stem
|
||||
+ '" to "'
|
||||
+ self.footprint_name
|
||||
+ '"'
|
||||
)
|
||||
|
||||
if footprint_file_read.exists():
|
||||
remove(footprint_file_read)
|
||||
footprint_file_read = footprint_file_read.parent / (
|
||||
self.footprint_name + footprint_file_read.suffix
|
||||
)
|
||||
|
||||
if (
|
||||
footprint_file_write
|
||||
and footprint_file_write.exists()
|
||||
and not self.footprint_skipped
|
||||
):
|
||||
footprint_file_write.replace(footprint_file_read)
|
||||
elif footprint_file_write and footprint_file_write.exists():
|
||||
remove(footprint_file_write)
|
||||
|
||||
return ("OK",)
|
||||
|
||||
|
||||
def main(
|
||||
lib_file, lib_folder, overwrite=False, KICAD_3RD_PARTY_LINK="${KICAD_3RD_PARTY}"
|
||||
):
|
||||
lib_folder = pathlib.Path(lib_folder)
|
||||
lib_file = pathlib.Path(lib_file)
|
||||
|
||||
print("overwrite", overwrite)
|
||||
|
||||
if not lib_folder.is_dir():
|
||||
print(f"Error destination folder {lib_folder} does not exist!")
|
||||
return 0
|
||||
|
||||
if not lib_file.is_file():
|
||||
print(f"Error file {lib_folder} to be imported was not found!")
|
||||
return 0
|
||||
|
||||
impart = import_lib()
|
||||
impart.KICAD_3RD_PARTY_LINK = KICAD_3RD_PARTY_LINK
|
||||
impart.set_DEST_PATH(lib_folder)
|
||||
try:
|
||||
(res,) = impart.import_all(lib_file, overwrite_if_exists=overwrite)
|
||||
print(res)
|
||||
except AssertionError as e:
|
||||
print(e)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import argparse
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
# Example: python plugins/KiCadImport.py --download-folder Demo/libs --lib-folder import_test
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Import KiCad libraries from a file or folder."
|
||||
)
|
||||
|
||||
# Create mutually exclusive arguments for file or folder
|
||||
group = parser.add_mutually_exclusive_group(required=True)
|
||||
group.add_argument(
|
||||
"--download-folder",
|
||||
help="Path to the folder with the zip files to be imported.",
|
||||
)
|
||||
group.add_argument("--download-file", help="Path to the zip file to import.")
|
||||
|
||||
group.add_argument("--easyeda", help="Import easyeda part. example: C2040")
|
||||
|
||||
parser.add_argument(
|
||||
"--lib-folder",
|
||||
required=True,
|
||||
help="Destination folder for the imported KiCad files.",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--overwrite-if-exists",
|
||||
action="store_true",
|
||||
help="Overwrite existing files if they already exist",
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--path-variable",
|
||||
help="Example: if only project-specific '${KIPRJMOD}' standard is '${KICAD_3RD_PARTY}'",
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
lib_folder = pathlib.Path(args.lib_folder)
|
||||
|
||||
if args.path_variable:
|
||||
path_variable = str(args.path_variable).strip()
|
||||
else:
|
||||
path_variable = "${KICAD_3RD_PARTY}"
|
||||
|
||||
if args.download_file:
|
||||
main(
|
||||
lib_file=args.download_file,
|
||||
lib_folder=args.lib_folder,
|
||||
overwrite=args.overwrite_if_exists,
|
||||
KICAD_3RD_PARTY_LINK=path_variable,
|
||||
)
|
||||
elif args.download_folder:
|
||||
download_folder = pathlib.Path(args.download_folder)
|
||||
if not download_folder.is_dir():
|
||||
print(f"Error Source folder {download_folder} does not exist!")
|
||||
elif not lib_folder.is_dir():
|
||||
print(f"Error destination folder {lib_folder} does not exist!")
|
||||
else:
|
||||
for zip_file in download_folder.glob("*.zip"):
|
||||
if (
|
||||
zip_file.is_file() and zip_file.stat().st_size >= 1024
|
||||
): # Check if it's a file and at least 1 KB
|
||||
main(
|
||||
lib_file=zip_file,
|
||||
lib_folder=args.lib_folder,
|
||||
overwrite=args.overwrite_if_exists,
|
||||
KICAD_3RD_PARTY_LINK=path_variable,
|
||||
)
|
||||
elif args.easyeda:
|
||||
if not lib_folder.is_dir():
|
||||
print(f"Error destination folder {lib_folder} does not exist!")
|
||||
else:
|
||||
component_id = str(args.easyeda).strip
|
||||
print("Try to import EeasyEDA / LCSC Part# : ", component_id)
|
||||
from impart_easyeda import easyeda2kicad_wrapper
|
||||
|
||||
easyeda_import = easyeda2kicad_wrapper()
|
||||
|
||||
easyeda_import.full_import(
|
||||
component_id="C2040",
|
||||
base_folder=lib_folder,
|
||||
overwrite=False,
|
||||
lib_var=path_variable,
|
||||
)
|
3
3rdparty/plugins/com_github_Steffen-W_impartGUI/__init__.py
vendored
Normal file
3
3rdparty/plugins/com_github_Steffen-W_impartGUI/__init__.py
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
from .impart_action import ActionImpartPlugin
|
||||
|
||||
ActionImpartPlugin().register()
|
4
3rdparty/plugins/com_github_Steffen-W_impartGUI/config.ini
vendored
Normal file
4
3rdparty/plugins/com_github_Steffen-W_impartGUI/config.ini
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
[config]
|
||||
src_path = /Users/timothy/Downloads
|
||||
dest_path = /Users/timothy/Workbench/embedded/kicad_libs
|
||||
|
490
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_action.py
vendored
Normal file
490
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_action.py
vendored
Normal file
@ -0,0 +1,490 @@
|
||||
import pcbnew
|
||||
import os.path
|
||||
from pathlib import Path
|
||||
import wx
|
||||
from time import sleep
|
||||
from threading import Thread
|
||||
import sys
|
||||
import traceback
|
||||
import subprocess
|
||||
import os
|
||||
import venv
|
||||
|
||||
try:
|
||||
if __name__ == "__main__":
|
||||
from impart_gui import impartGUI
|
||||
from KiCadImport import import_lib
|
||||
from impart_helper_func import filehandler, config_handler, KiCad_Settings
|
||||
from impart_migration import find_old_lib_files, convert_lib_list
|
||||
else:
|
||||
# relative import is required in kicad
|
||||
from .impart_gui import impartGUI
|
||||
from .KiCadImport import import_lib
|
||||
from .impart_helper_func import filehandler, config_handler, KiCad_Settings
|
||||
from .impart_migration import find_old_lib_files, convert_lib_list
|
||||
except Exception as e:
|
||||
print(traceback.format_exc())
|
||||
|
||||
|
||||
def activate_virtualenv(venv_dir):
|
||||
"""Activates a virtual environment, but creates it first if it does not exist."""
|
||||
venv_dir = os.path.abspath(venv_dir)
|
||||
|
||||
if os.name == "nt": # Windows
|
||||
if not os.path.exists(venv_dir):
|
||||
# venv.create(venv_dir, with_pip=True) # dont work
|
||||
kicad_executable = sys.executable
|
||||
kicad_bin_dir = os.path.dirname(kicad_executable)
|
||||
python_executable = os.path.join(kicad_bin_dir, "python.exe")
|
||||
subprocess.run(
|
||||
[python_executable, "-m", "venv", venv_dir],
|
||||
check=True,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW,
|
||||
)
|
||||
print(f"Virtual environment not found. Create new in {venv_dir} ...")
|
||||
|
||||
python_executable = os.path.join(venv_dir, "Scripts", "python.exe")
|
||||
site_packages = os.path.join(venv_dir, "Lib", "site-packages")
|
||||
else: # Linux / macOS
|
||||
if not os.path.exists(venv_dir):
|
||||
venv.create(venv_dir, with_pip=True)
|
||||
print(f"Virtual environment not found. Create new in {venv_dir} ...")
|
||||
python_executable = os.path.join(venv_dir, "bin", "python")
|
||||
site_packages = os.path.join(
|
||||
venv_dir,
|
||||
"lib",
|
||||
f"python{sys.version_info.major}.{sys.version_info.minor}",
|
||||
"site-packages",
|
||||
)
|
||||
|
||||
sys.path.insert(0, site_packages)
|
||||
return python_executable
|
||||
|
||||
|
||||
def ensure_package(package_name, python_executable="python"):
|
||||
try:
|
||||
__import__(package_name)
|
||||
return True
|
||||
except ModuleNotFoundError:
|
||||
try:
|
||||
cmd = [python_executable, "-m", "pip", "install", package_name]
|
||||
print(" ".join(cmd))
|
||||
subprocess.check_call(cmd)
|
||||
__import__(package_name)
|
||||
return True
|
||||
except:
|
||||
return False
|
||||
|
||||
|
||||
EVT_UPDATE_ID = wx.NewIdRef()
|
||||
|
||||
|
||||
def EVT_UPDATE(win, func):
|
||||
win.Connect(-1, -1, EVT_UPDATE_ID, func)
|
||||
|
||||
|
||||
class ResultEvent(wx.PyEvent):
|
||||
def __init__(self, data):
|
||||
wx.PyEvent.__init__(self)
|
||||
self.SetEventType(EVT_UPDATE_ID)
|
||||
self.data = data
|
||||
|
||||
|
||||
class PluginThread(Thread):
|
||||
def __init__(self, wxObject):
|
||||
Thread.__init__(self)
|
||||
self.wxObject = wxObject
|
||||
self.stopThread = False
|
||||
self.start()
|
||||
|
||||
def run(self):
|
||||
lenStr = 0
|
||||
global backend_h
|
||||
while not self.stopThread:
|
||||
if lenStr != len(backend_h.print_buffer):
|
||||
self.report(backend_h.print_buffer)
|
||||
lenStr = len(backend_h.print_buffer)
|
||||
sleep(0.5)
|
||||
|
||||
def report(self, status):
|
||||
wx.PostEvent(self.wxObject, ResultEvent(status))
|
||||
|
||||
|
||||
class impart_backend:
|
||||
|
||||
def __init__(self):
|
||||
path2config = os.path.join(os.path.dirname(__file__), "config.ini")
|
||||
self.config = config_handler(path2config)
|
||||
path_seting = pcbnew.SETTINGS_MANAGER().GetUserSettingsPath()
|
||||
self.KiCad_Settings = KiCad_Settings(path_seting)
|
||||
self.runThread = False
|
||||
self.autoImport = False
|
||||
self.overwriteImport = False
|
||||
self.import_old_format = False
|
||||
self.autoLib = False
|
||||
self.folderhandler = filehandler(".")
|
||||
self.print_buffer = ""
|
||||
self.importer = import_lib()
|
||||
self.importer.print = self.print2buffer
|
||||
|
||||
def version_to_tuple(version_str):
|
||||
try:
|
||||
return tuple(map(int, version_str.split("-")[0].split(".")))
|
||||
except (ValueError, AttributeError) as e:
|
||||
print(f"Version extractions error '{version_str}': {e}")
|
||||
return None
|
||||
|
||||
minVersion = "8.0.4"
|
||||
KiCadVers = version_to_tuple(pcbnew.Version())
|
||||
if not KiCadVers or KiCadVers < version_to_tuple(minVersion):
|
||||
self.print2buffer("KiCad Version: " + str(pcbnew.FullVersion()))
|
||||
self.print2buffer("Minimum required KiCad version is " + minVersion)
|
||||
self.print2buffer("This can limit the functionality of the plugin.")
|
||||
|
||||
if not self.config.config_is_set:
|
||||
self.print2buffer(
|
||||
"Warning: The path where the libraries should be saved has not been adjusted yet."
|
||||
+ " Maybe you use the plugin in this version for the first time.\n"
|
||||
)
|
||||
|
||||
additional_information = (
|
||||
"If this plugin is being used for the first time, settings in KiCad are required. "
|
||||
+ "The settings are checked at the end of the import process. For easy setup, "
|
||||
+ "auto setting can be activated."
|
||||
)
|
||||
self.print2buffer(additional_information)
|
||||
self.print2buffer("\n##############################\n")
|
||||
|
||||
def print2buffer(self, *args):
|
||||
for text in args:
|
||||
self.print_buffer = self.print_buffer + str(text) + "\n"
|
||||
|
||||
def __find_new_file__(self):
|
||||
path = self.config.get_SRC_PATH()
|
||||
|
||||
if not os.path.isdir(path):
|
||||
return 0
|
||||
|
||||
while True:
|
||||
newfilelist = self.folderhandler.GetNewFiles(path)
|
||||
for lib in newfilelist:
|
||||
try:
|
||||
(res,) = self.importer.import_all(
|
||||
lib,
|
||||
overwrite_if_exists=self.overwriteImport,
|
||||
import_old_format=self.import_old_format,
|
||||
)
|
||||
self.print2buffer(res)
|
||||
except AssertionError as e:
|
||||
self.print2buffer(e)
|
||||
except Exception as e:
|
||||
self.print2buffer(e)
|
||||
backend_h.print2buffer(f"Error: {e}")
|
||||
backend_h.print2buffer("Python version " + sys.version)
|
||||
print(traceback.format_exc())
|
||||
self.print2buffer("")
|
||||
|
||||
if not self.runThread:
|
||||
break
|
||||
if not pcbnew.GetBoard():
|
||||
# print("pcbnew close")
|
||||
break
|
||||
sleep(1)
|
||||
|
||||
|
||||
backend_h = impart_backend()
|
||||
|
||||
|
||||
def checkImport(add_if_possible=True):
|
||||
libnames = ["Octopart", "Samacsys", "UltraLibrarian", "Snapeda", "EasyEDA"]
|
||||
setting = backend_h.KiCad_Settings
|
||||
DEST_PATH = backend_h.config.get_DEST_PATH()
|
||||
|
||||
msg = ""
|
||||
msg += setting.check_GlobalVar(DEST_PATH, add_if_possible)
|
||||
|
||||
for name in libnames:
|
||||
# The lines work but old libraries should not be added automatically
|
||||
# libname = os.path.join(DEST_PATH, name + ".lib")
|
||||
# if os.path.isfile(libname):
|
||||
# msg += setting.check_symbollib(name + ".lib", add_if_possible)
|
||||
|
||||
libdir = os.path.join(DEST_PATH, name + ".kicad_sym")
|
||||
libdir_old = os.path.join(DEST_PATH, name + "_kicad_sym.kicad_sym")
|
||||
libdir_convert_lib = os.path.join(DEST_PATH, name + "_old_lib.kicad_sym")
|
||||
if os.path.isfile(libdir):
|
||||
libname = name + ".kicad_sym"
|
||||
msg += setting.check_symbollib(libname, add_if_possible)
|
||||
elif os.path.isfile(libdir_old):
|
||||
libname = name + "_kicad_sym.kicad_sym"
|
||||
msg += setting.check_symbollib(libname, add_if_possible)
|
||||
|
||||
if os.path.isfile(libdir_convert_lib):
|
||||
libname = name + "_old_lib.kicad_sym"
|
||||
msg += setting.check_symbollib(libname, add_if_possible)
|
||||
|
||||
libdir = os.path.join(DEST_PATH, name + ".pretty")
|
||||
if os.path.isdir(libdir):
|
||||
msg += setting.check_footprintlib(name, add_if_possible)
|
||||
return msg
|
||||
|
||||
|
||||
class impart_frontend(impartGUI):
|
||||
global backend_h
|
||||
|
||||
def __init__(self, board, action):
|
||||
super(impart_frontend, self).__init__(None)
|
||||
self.board = board
|
||||
self.action = action
|
||||
|
||||
self.m_dirPicker_sourcepath.SetPath(backend_h.config.get_SRC_PATH())
|
||||
self.m_dirPicker_librarypath.SetPath(backend_h.config.get_DEST_PATH())
|
||||
|
||||
self.m_autoImport.SetValue(backend_h.autoImport)
|
||||
self.m_overwrite.SetValue(backend_h.overwriteImport)
|
||||
self.m_check_autoLib.SetValue(backend_h.autoLib)
|
||||
self.m_check_import_all.SetValue(backend_h.import_old_format)
|
||||
|
||||
if backend_h.runThread:
|
||||
self.m_button.Label = "automatic import / press to stop"
|
||||
else:
|
||||
self.m_button.Label = "Start"
|
||||
|
||||
EVT_UPDATE(self, self.updateDisplay)
|
||||
self.Thread = PluginThread(self) # only for text output
|
||||
|
||||
self.test_migrate_possible()
|
||||
|
||||
def updateDisplay(self, status):
|
||||
self.m_text.SetValue(status.data)
|
||||
self.m_text.SetInsertionPointEnd()
|
||||
|
||||
# def print(self, text):
|
||||
# self.m_text.AppendText(str(text)+"\n")
|
||||
|
||||
def on_close(self, event):
|
||||
if backend_h.runThread:
|
||||
dlg = wx.MessageDialog(
|
||||
None,
|
||||
"The automatic import process continues in the background. "
|
||||
+ "If this is not desired, it must be stopped.\n"
|
||||
+ "As soon as the PCB Editor window is closed, the import process also ends.",
|
||||
"WARNING: impart background process",
|
||||
wx.KILL_OK | wx.ICON_WARNING,
|
||||
)
|
||||
if dlg.ShowModal() != wx.ID_OK:
|
||||
return
|
||||
|
||||
backend_h.autoImport = self.m_autoImport.IsChecked()
|
||||
backend_h.overwriteImport = self.m_overwrite.IsChecked()
|
||||
backend_h.autoLib = self.m_check_autoLib.IsChecked()
|
||||
backend_h.import_old_format = self.m_check_import_all.IsChecked()
|
||||
# backend_h.runThread = False
|
||||
self.Thread.stopThread = True # only for text output
|
||||
event.Skip()
|
||||
|
||||
def BottonClick(self, event):
|
||||
backend_h.importer.set_DEST_PATH(backend_h.config.get_DEST_PATH())
|
||||
|
||||
backend_h.autoImport = self.m_autoImport.IsChecked()
|
||||
|
||||
tmp = self.m_overwrite.IsChecked()
|
||||
if tmp and not tmp == backend_h.overwriteImport:
|
||||
backend_h.folderhandler.filelist = []
|
||||
backend_h.overwriteImport = self.m_overwrite.IsChecked()
|
||||
|
||||
backend_h.autoLib = self.m_check_autoLib.IsChecked()
|
||||
backend_h.import_old_format = self.m_check_import_all.IsChecked()
|
||||
|
||||
if backend_h.runThread:
|
||||
backend_h.runThread = False
|
||||
self.m_button.Label = "Start"
|
||||
return
|
||||
|
||||
backend_h.runThread = False
|
||||
backend_h.__find_new_file__()
|
||||
self.m_button.Label = "Start"
|
||||
|
||||
if backend_h.autoImport:
|
||||
backend_h.runThread = True
|
||||
self.m_button.Label = "automatic import / press to stop"
|
||||
x = Thread(target=backend_h.__find_new_file__, args=[])
|
||||
x.start()
|
||||
|
||||
add_if_possible = self.m_check_autoLib.IsChecked()
|
||||
msg = checkImport(add_if_possible)
|
||||
if msg:
|
||||
msg += "\n\nMore information can be found in the README for the integration into KiCad.\n"
|
||||
msg += "github.com/Steffen-W/Import-LIB-KiCad-Plugin"
|
||||
msg += "\nSome configurations require a KiCad restart to be detected correctly."
|
||||
|
||||
dlg = wx.MessageDialog(None, msg, "WARNING", wx.KILL_OK | wx.ICON_WARNING)
|
||||
|
||||
if dlg.ShowModal() != wx.ID_OK:
|
||||
return
|
||||
|
||||
backend_h.print2buffer("\n##############################\n")
|
||||
backend_h.print2buffer(msg)
|
||||
backend_h.print2buffer("\n##############################\n")
|
||||
event.Skip()
|
||||
|
||||
def DirChange(self, event):
|
||||
backend_h.config.set_SRC_PATH(self.m_dirPicker_sourcepath.GetPath())
|
||||
backend_h.config.set_DEST_PATH(self.m_dirPicker_librarypath.GetPath())
|
||||
backend_h.folderhandler.filelist = []
|
||||
self.test_migrate_possible()
|
||||
event.Skip()
|
||||
|
||||
def ButtomManualImport(self, event):
|
||||
try:
|
||||
from .impart_easyeda import easyeda2kicad_wrapper
|
||||
|
||||
component_id = self.m_textCtrl2.GetValue().strip() # example: "C2040"
|
||||
overwrite = self.m_overwrite.IsChecked()
|
||||
backend_h.print2buffer("")
|
||||
backend_h.print2buffer(
|
||||
"Try to import EeasyEDA / LCSC Part# : " + component_id
|
||||
)
|
||||
base_folder = backend_h.config.get_DEST_PATH()
|
||||
easyeda_import = easyeda2kicad_wrapper()
|
||||
easyeda_import.print = backend_h.print2buffer
|
||||
easyeda_import.full_import(component_id, base_folder, overwrite)
|
||||
event.Skip()
|
||||
except Exception as e:
|
||||
backend_h.print2buffer(f"Error: {e}")
|
||||
backend_h.print2buffer("Python version " + sys.version)
|
||||
print(traceback.format_exc())
|
||||
|
||||
def get_old_libfiles(self):
|
||||
libpath = self.m_dirPicker_librarypath.GetPath()
|
||||
libs = ["Octopart", "Samacsys", "UltraLibrarian", "Snapeda", "EasyEDA"]
|
||||
return find_old_lib_files(folder_path=libpath, libs=libs)
|
||||
|
||||
def test_migrate_possible(self):
|
||||
libs2migrate = self.get_old_libfiles()
|
||||
conv = convert_lib_list(libs2migrate, drymode=True)
|
||||
|
||||
if len(conv):
|
||||
self.m_button_migrate.Show()
|
||||
else:
|
||||
self.m_button_migrate.Hide()
|
||||
|
||||
def migrate_libs(self, event):
|
||||
libs2migrate = self.get_old_libfiles()
|
||||
|
||||
conv = convert_lib_list(libs2migrate, drymode=True)
|
||||
|
||||
def print2GUI(text):
|
||||
backend_h.print2buffer(text)
|
||||
|
||||
if len(conv) <= 0:
|
||||
print2GUI("Error in migrate_libs()")
|
||||
return
|
||||
|
||||
SymbolTable = backend_h.KiCad_Settings.get_sym_table()
|
||||
SymbolLibsUri = {lib["uri"]: lib for lib in SymbolTable}
|
||||
libRename = []
|
||||
|
||||
def lib_entry(lib):
|
||||
return "${KICAD_3RD_PARTY}/" + lib
|
||||
|
||||
msg = ""
|
||||
for line in conv:
|
||||
if line[1].endswith(".blk"):
|
||||
msg += "\n" + line[0] + " rename to " + line[1]
|
||||
else:
|
||||
msg += "\n" + line[0] + " convert to " + line[1]
|
||||
if lib_entry(line[0]) in SymbolLibsUri:
|
||||
entry = SymbolLibsUri[lib_entry(line[0])]
|
||||
tmp = {
|
||||
"oldURI": entry["uri"],
|
||||
"newURI": lib_entry(line[1]),
|
||||
"name": entry["name"],
|
||||
}
|
||||
libRename.append(tmp)
|
||||
|
||||
msg_lib = ""
|
||||
if len(libRename):
|
||||
msg_lib += "The following changes must be made to the list of imported Symbol libs:\n"
|
||||
|
||||
for tmp in libRename:
|
||||
msg_lib += f"\n{tmp['name']} : {tmp['oldURI']} \n-> {tmp['newURI']}"
|
||||
|
||||
msg_lib += "\n\n"
|
||||
msg_lib += "It is necessary to adjust the settings of the imported symbol libraries in KiCad."
|
||||
msg += "\n\n" + msg_lib
|
||||
|
||||
msg += "\n\nBackup files are also created automatically. "
|
||||
msg += "These are named '*.blk'.\nShould the changes be applied?"
|
||||
|
||||
dlg = wx.MessageDialog(
|
||||
None, msg, "WARNING", wx.KILL_OK | wx.ICON_WARNING | wx.CANCEL
|
||||
)
|
||||
if dlg.ShowModal() == wx.ID_OK:
|
||||
print2GUI("Converted libraries:")
|
||||
conv = convert_lib_list(libs2migrate, drymode=False)
|
||||
for line in conv:
|
||||
if line[1].endswith(".blk"):
|
||||
print2GUI(line[0] + " rename to " + line[1])
|
||||
else:
|
||||
print2GUI(line[0] + " convert to " + line[1])
|
||||
else:
|
||||
return
|
||||
|
||||
if not len(msg_lib):
|
||||
return
|
||||
|
||||
msg_dlg = "\nShould the change be made automatically? A restart of KiCad is then necessary to apply all changes."
|
||||
dlg2 = wx.MessageDialog(
|
||||
None, msg_lib + msg_dlg, "WARNING", wx.KILL_OK | wx.ICON_WARNING | wx.CANCEL
|
||||
)
|
||||
if dlg2.ShowModal() == wx.ID_OK:
|
||||
for tmp in libRename:
|
||||
print2GUI(f"\n{tmp['name']} : {tmp['oldURI']} \n-> {tmp['newURI']}")
|
||||
backend_h.KiCad_Settings.sym_table_change_entry(
|
||||
tmp["oldURI"], tmp["newURI"]
|
||||
)
|
||||
print2GUI("\nA restart of KiCad is then necessary to apply all changes.")
|
||||
else:
|
||||
print2GUI(msg_lib)
|
||||
|
||||
self.test_migrate_possible() # When everything has worked, the button disappears
|
||||
event.Skip()
|
||||
|
||||
|
||||
class ActionImpartPlugin(pcbnew.ActionPlugin):
|
||||
def defaults(self):
|
||||
plugin_dir = Path(__file__).resolve().parent
|
||||
self.resources_dir = plugin_dir.parent.parent / "resources" / plugin_dir.name
|
||||
self.plugin_dir = plugin_dir
|
||||
|
||||
self.name = "impartGUI"
|
||||
self.category = "Import library files"
|
||||
self.description = "Import library files from Octopart, Samacsys, Ultralibrarian, Snapeda and EasyEDA"
|
||||
self.show_toolbar_button = True
|
||||
|
||||
self.icon_file_name = str(self.resources_dir / "icon.png")
|
||||
self.dark_icon_file_name = self.icon_file_name
|
||||
|
||||
def Run(self):
|
||||
# Use virtual env
|
||||
# TODO: Does not work completely reliably
|
||||
python_executable = activate_virtualenv(venv_dir=self.plugin_dir / "venv")
|
||||
if not ensure_package("pydantic", python_executable):
|
||||
print("Problems with loading", "pydantic")
|
||||
if not ensure_package("easyeda2kicad", python_executable):
|
||||
print("Problems with loading", "easyeda2kicad")
|
||||
|
||||
# Start GUI
|
||||
board = pcbnew.GetBoard()
|
||||
Impart_h = impart_frontend(board, self)
|
||||
Impart_h.ShowModal()
|
||||
Impart_h.Destroy()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = wx.App()
|
||||
frame = wx.Frame(None, title="KiCad Plugin")
|
||||
Impart_t = impart_frontend(None, None)
|
||||
Impart_t.ShowModal()
|
||||
Impart_t.Destroy()
|
214
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_easyeda.py
vendored
Normal file
214
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_easyeda.py
vendored
Normal file
@ -0,0 +1,214 @@
|
||||
# Created with strong reference to:
|
||||
# https://github.com/uPesy/easyeda2kicad.py/blob/master/easyeda2kicad/__main__.py
|
||||
|
||||
import os
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
|
||||
from easyeda2kicad.easyeda.easyeda_api import EasyedaApi
|
||||
from easyeda2kicad.kicad.parameters_kicad_symbol import KicadVersion
|
||||
from easyeda2kicad.easyeda.parameters_easyeda import EeSymbol
|
||||
|
||||
from easyeda2kicad.helpers import add_component_in_symbol_lib_file
|
||||
from easyeda2kicad.helpers import id_already_in_symbol_lib
|
||||
from easyeda2kicad.helpers import update_component_in_symbol_lib_file
|
||||
|
||||
from easyeda2kicad.easyeda.easyeda_importer import Easyeda3dModelImporter
|
||||
from easyeda2kicad.easyeda.easyeda_importer import EasyedaFootprintImporter
|
||||
from easyeda2kicad.easyeda.easyeda_importer import EasyedaSymbolImporter
|
||||
|
||||
from easyeda2kicad.kicad.export_kicad_3d_model import Exporter3dModelKicad
|
||||
from easyeda2kicad.kicad.export_kicad_footprint import ExporterFootprintKicad
|
||||
from easyeda2kicad.kicad.export_kicad_symbol import ExporterSymbolKicad
|
||||
|
||||
|
||||
class easyeda2kicad_wrapper:
|
||||
|
||||
def print(self, txt):
|
||||
print("easyeda:" + txt)
|
||||
|
||||
def import_Symbol(
|
||||
self,
|
||||
cad_data,
|
||||
output,
|
||||
overwrite=False,
|
||||
kicad_version=KicadVersion.v6,
|
||||
sym_lib_ext="kicad_sym",
|
||||
):
|
||||
importer = EasyedaSymbolImporter(easyeda_cp_cad_data=cad_data)
|
||||
easyeda_symbol: EeSymbol = importer.get_symbol()
|
||||
|
||||
is_id_already_in_symbol_lib = id_already_in_symbol_lib(
|
||||
lib_path=f"{output}.{sym_lib_ext}",
|
||||
component_name=easyeda_symbol.info.name,
|
||||
kicad_version=kicad_version,
|
||||
)
|
||||
|
||||
if not overwrite and is_id_already_in_symbol_lib:
|
||||
self.print("Use overwrite option to update the older symbol")
|
||||
return 1
|
||||
|
||||
exporter = ExporterSymbolKicad(
|
||||
symbol=easyeda_symbol, kicad_version=kicad_version
|
||||
)
|
||||
kicad_symbol_lib = exporter.export(
|
||||
footprint_lib_name=output.split("/")[-1].split(".")[0],
|
||||
)
|
||||
|
||||
if is_id_already_in_symbol_lib:
|
||||
update_component_in_symbol_lib_file(
|
||||
lib_path=f"{output}.{sym_lib_ext}",
|
||||
component_name=easyeda_symbol.info.name,
|
||||
component_content=kicad_symbol_lib,
|
||||
kicad_version=kicad_version,
|
||||
)
|
||||
else:
|
||||
add_component_in_symbol_lib_file(
|
||||
lib_path=f"{output}.{sym_lib_ext}",
|
||||
component_content=kicad_symbol_lib,
|
||||
kicad_version=kicad_version,
|
||||
)
|
||||
|
||||
self.print(f"Created Kicad symbol {easyeda_symbol.info.name}")
|
||||
print(f"Library path : {output}.{sym_lib_ext}")
|
||||
|
||||
def import_Footprint(
|
||||
self, cad_data, output, overwrite=False, lib_name="EASYEDA2KICAD"
|
||||
):
|
||||
importer = EasyedaFootprintImporter(easyeda_cp_cad_data=cad_data)
|
||||
easyeda_footprint = importer.get_footprint()
|
||||
|
||||
is_id_already_in_footprint_lib = os.path.isfile(
|
||||
f"{output}.pretty/{easyeda_footprint.info.name}.kicad_mod"
|
||||
)
|
||||
|
||||
if not overwrite and is_id_already_in_footprint_lib:
|
||||
self.print("Use overwrite option to replace the older footprint")
|
||||
return 1
|
||||
|
||||
ki_footprint = ExporterFootprintKicad(footprint=easyeda_footprint)
|
||||
footprint_filename = f"{easyeda_footprint.info.name}.kicad_mod"
|
||||
footprint_path = f"{output}.pretty"
|
||||
model_3d_path = f"{output}.3dshapes".replace("\\", "/").replace("./", "/")
|
||||
|
||||
model_3d_path = f"${{{lib_name}}}/EasyEDA.3dshapes"
|
||||
|
||||
ki_footprint.export(
|
||||
footprint_full_path=f"{footprint_path}/{footprint_filename}",
|
||||
model_3d_path=model_3d_path,
|
||||
)
|
||||
|
||||
self.print(f"Created Kicad footprint {easyeda_footprint.info.name}")
|
||||
print(f"Footprint path: {os.path.join(footprint_path, footprint_filename)}")
|
||||
|
||||
def import_3D_Model(self, cad_data, output, overwrite=True):
|
||||
model_3d = Easyeda3dModelImporter(
|
||||
easyeda_cp_cad_data=cad_data, download_raw_3d_model=True
|
||||
).output
|
||||
|
||||
if not model_3d:
|
||||
self.print(f"No 3D model available for this component.")
|
||||
return
|
||||
|
||||
exporter = Exporter3dModelKicad(model_3d=model_3d)
|
||||
|
||||
if exporter.output or exporter.output_step:
|
||||
filename_wrl = f"{exporter.output.name}.wrl"
|
||||
filename_step = f"{exporter.output.name}.step"
|
||||
lib_path = f"{output}.3dshapes"
|
||||
|
||||
filepath_wrl = os.path.join(lib_path, filename_wrl)
|
||||
filepath_step = os.path.join(lib_path, filename_step)
|
||||
|
||||
formats = ""
|
||||
if os.path.exists(filepath_wrl) and not overwrite:
|
||||
self.print(
|
||||
f"3D model (wrl) exists:Use overwrite option to replace the 3D model"
|
||||
)
|
||||
return
|
||||
else:
|
||||
formats += "wrl"
|
||||
|
||||
if os.path.exists(filepath_step) and not overwrite:
|
||||
self.print(
|
||||
f"3D model (wrl) exists:Use overwrite option to replace the 3D model"
|
||||
)
|
||||
return
|
||||
else:
|
||||
formats += ",step"
|
||||
|
||||
exporter.export(lib_path=output)
|
||||
self.print(f"Created 3D model {exporter.output.name} ({formats})")
|
||||
if filename_wrl:
|
||||
print("3D model path (wrl): " + filepath_wrl)
|
||||
if filename_step:
|
||||
print("3D model path (step): " + filepath_step)
|
||||
|
||||
def full_import(
|
||||
self,
|
||||
component_id="C2040",
|
||||
base_folder=False,
|
||||
overwrite=False,
|
||||
lib_var="KICAD_3RD_PARTY",
|
||||
):
|
||||
|
||||
base_folder = os.path.expanduser(base_folder)
|
||||
|
||||
if not component_id.startswith("C"):
|
||||
self.print("lcsc_id should start by C.... example: C2040")
|
||||
return False
|
||||
|
||||
if not os.path.isdir(base_folder):
|
||||
os.makedirs(base_folder, exist_ok=True)
|
||||
|
||||
lib_name = "EasyEDA"
|
||||
output = f"{base_folder}/{lib_name}"
|
||||
|
||||
# Create new footprint folder if it does not exist
|
||||
if not os.path.isdir(f"{output}.pretty"):
|
||||
os.mkdir(f"{output}.pretty")
|
||||
self.print(f"Create {lib_name}.pretty footprint folder in {base_folder}")
|
||||
|
||||
# Create new 3d model folder if don't exist
|
||||
if not os.path.isdir(f"{output}.3dshapes"):
|
||||
os.mkdir(f"{output}.3dshapes")
|
||||
self.print(f"Create {lib_name}.3dshapes 3D model folder in {base_folder}")
|
||||
|
||||
lib_extension = "kicad_sym"
|
||||
if not os.path.isfile(f"{output}.{lib_extension}"):
|
||||
with open(
|
||||
file=f"{output}.{lib_extension}", mode="w+", encoding="utf-8"
|
||||
) as my_lib:
|
||||
my_lib.write(
|
||||
"""\
|
||||
(kicad_symbol_lib
|
||||
(version 20211014)
|
||||
(generator https://github.com/uPesy/easyeda2kicad.py)
|
||||
)"""
|
||||
)
|
||||
self.print(f"Create {lib_name}.{lib_extension} symbol lib in {base_folder}")
|
||||
|
||||
# Get CAD data of the component using easyeda API
|
||||
api = EasyedaApi()
|
||||
cad_data = api.get_cad_data_of_component(lcsc_id=component_id)
|
||||
|
||||
# API returned no data
|
||||
if not cad_data:
|
||||
self.print(f"Failed to fetch data from EasyEDA API for part {component_id}")
|
||||
return 1
|
||||
|
||||
# ---------------- SYMBOL ----------------
|
||||
self.import_Symbol(cad_data, output, overwrite=overwrite)
|
||||
# ---------------- FOOTPRINT -------------
|
||||
self.import_Footprint(cad_data, output, overwrite=overwrite, lib_name=lib_var)
|
||||
# ---------------- 3D MODEL --------------
|
||||
self.import_3D_Model(cad_data, output, overwrite=overwrite)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logging.basicConfig(level=logging.ERROR)
|
||||
base_folder = "~/Documents/Kicad/EasyEDA"
|
||||
easyeda_import = easyeda2kicad_wrapper()
|
||||
easyeda_import.full_import(component_id="C2040", base_folder=base_folder)
|
168
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_gui.py
vendored
Normal file
168
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_gui.py
vendored
Normal file
@ -0,0 +1,168 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
###########################################################################
|
||||
## Python code generated with wxFormBuilder (version 4.2.1-0-g80c4cb6)
|
||||
## http://www.wxformbuilder.org/
|
||||
##
|
||||
## PLEASE DO *NOT* EDIT THIS FILE!
|
||||
###########################################################################
|
||||
|
||||
import wx
|
||||
import wx.xrc
|
||||
import wx.adv
|
||||
|
||||
###########################################################################
|
||||
## Class impartGUI
|
||||
###########################################################################
|
||||
|
||||
class impartGUI ( wx.Dialog ):
|
||||
|
||||
def __init__( self, parent ):
|
||||
wx.Dialog.__init__ ( self, parent, id = wx.ID_ANY, title = u"impartGUI", pos = wx.DefaultPosition, size = wx.Size( 650,650 ), style = wx.DEFAULT_DIALOG_STYLE|wx.RESIZE_BORDER|wx.BORDER_DEFAULT )
|
||||
|
||||
self.SetSizeHints( wx.DefaultSize, wx.DefaultSize )
|
||||
self.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) )
|
||||
|
||||
bSizer = wx.BoxSizer( wx.VERTICAL )
|
||||
|
||||
self.m_button_migrate = wx.Button( self, wx.ID_ANY, u"migrate the libraries (highly recommended)", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.m_button_migrate.SetFont( wx.Font( 15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString ) )
|
||||
self.m_button_migrate.Hide()
|
||||
self.m_button_migrate.SetMaxSize( wx.Size( -1,150 ) )
|
||||
|
||||
bSizer.Add( self.m_button_migrate, 1, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.m_button = wx.Button( self, wx.ID_ANY, u"Start", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
bSizer.Add( self.m_button, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.m_text = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_BESTWRAP|wx.TE_MULTILINE )
|
||||
bSizer.Add( self.m_text, 1, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.m_staticline11 = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
self.m_staticline11.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) )
|
||||
self.m_staticline11.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_GRAYTEXT ) )
|
||||
self.m_staticline11.Hide()
|
||||
|
||||
bSizer.Add( self.m_staticline11, 0, wx.EXPAND |wx.ALL, 5 )
|
||||
|
||||
fgSizer2 = wx.FlexGridSizer( 0, 3, 0, 0 )
|
||||
fgSizer2.SetFlexibleDirection( wx.HORIZONTAL )
|
||||
fgSizer2.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_ALL )
|
||||
|
||||
self.m_buttonImportManual = wx.Button( self, wx.ID_ANY, u"Manual Import", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
fgSizer2.Add( self.m_buttonImportManual, 0, wx.ALL, 5 )
|
||||
|
||||
m_choice1Choices = [ u"EeasyEDA / LCSC Part#" ]
|
||||
self.m_choice1 = wx.Choice( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, m_choice1Choices, 0 )
|
||||
self.m_choice1.SetSelection( 0 )
|
||||
fgSizer2.Add( self.m_choice1, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.m_textCtrl2 = wx.TextCtrl( self, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, wx.DefaultSize, wx.TE_PROCESS_ENTER )
|
||||
self.m_textCtrl2.SetMinSize( wx.Size( 220,-1 ) )
|
||||
|
||||
fgSizer2.Add( self.m_textCtrl2, 0, wx.EXPAND|wx.ALL, 5 )
|
||||
|
||||
|
||||
bSizer.Add( fgSizer2, 0, wx.ALIGN_CENTER_HORIZONTAL, 5 )
|
||||
|
||||
self.m_staticline12 = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
self.m_staticline12.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) )
|
||||
self.m_staticline12.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_GRAYTEXT ) )
|
||||
|
||||
bSizer.Add( self.m_staticline12, 0, wx.EXPAND |wx.ALL, 5 )
|
||||
|
||||
fgSizer1 = wx.FlexGridSizer( 0, 4, 0, 0 )
|
||||
fgSizer1.SetFlexibleDirection( wx.BOTH )
|
||||
fgSizer1.SetNonFlexibleGrowMode( wx.FLEX_GROWMODE_SPECIFIED )
|
||||
|
||||
fgSizer1.SetMinSize( wx.Size( -1,0 ) )
|
||||
self.m_autoImport = wx.CheckBox( self, wx.ID_ANY, u"auto background import", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
fgSizer1.Add( self.m_autoImport, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 )
|
||||
|
||||
self.m_overwrite = wx.CheckBox( self, wx.ID_ANY, u"overwrite existing lib", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
fgSizer1.Add( self.m_overwrite, 0, wx.ALL|wx.ALIGN_CENTER_VERTICAL, 5 )
|
||||
|
||||
self.m_check_import_all = wx.CheckBox( self, wx.ID_ANY, u"import old format", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.m_check_import_all.Enable( False )
|
||||
self.m_check_import_all.Hide()
|
||||
|
||||
fgSizer1.Add( self.m_check_import_all, 0, wx.ALL, 5 )
|
||||
|
||||
self.m_check_autoLib = wx.CheckBox( self, wx.ID_ANY, u"auto KiCad setting", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
fgSizer1.Add( self.m_check_autoLib, 0, wx.ALL, 5 )
|
||||
|
||||
|
||||
bSizer.Add( fgSizer1, 0, wx.ALIGN_CENTER, 5 )
|
||||
|
||||
self.m_staticText_sourcepath = wx.StaticText( self, wx.ID_ANY, u"Folder of the library to import:", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.m_staticText_sourcepath.Wrap( -1 )
|
||||
|
||||
bSizer.Add( self.m_staticText_sourcepath, 0, wx.ALL, 5 )
|
||||
|
||||
self.m_dirPicker_sourcepath = wx.DirPickerCtrl( self, wx.ID_ANY, u".", u"Select a folder", wx.DefaultPosition, wx.DefaultSize, wx.DIRP_DEFAULT_STYLE )
|
||||
bSizer.Add( self.m_dirPicker_sourcepath, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.m_staticText_librarypath = wx.StaticText( self, wx.ID_ANY, u"Library save location:", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.m_staticText_librarypath.Wrap( -1 )
|
||||
|
||||
bSizer.Add( self.m_staticText_librarypath, 0, wx.ALL, 5 )
|
||||
|
||||
self.m_dirPicker_librarypath = wx.DirPickerCtrl( self, wx.ID_ANY, u".", u"Select a folder", wx.DefaultPosition, wx.DefaultSize, wx.DIRP_DEFAULT_STYLE )
|
||||
bSizer.Add( self.m_dirPicker_librarypath, 0, wx.ALL|wx.EXPAND, 5 )
|
||||
|
||||
self.m_staticline1 = wx.StaticLine( self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, wx.LI_HORIZONTAL )
|
||||
self.m_staticline1.SetForegroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_WINDOW ) )
|
||||
self.m_staticline1.SetBackgroundColour( wx.SystemSettings.GetColour( wx.SYS_COLOUR_GRAYTEXT ) )
|
||||
self.m_staticline1.Hide()
|
||||
|
||||
bSizer.Add( self.m_staticline1, 0, wx.EXPAND |wx.ALL, 5 )
|
||||
|
||||
self.m_staticText5 = wx.StaticText( self, wx.ID_ANY, u"There is no guarantee for faultless function. Use only at your own risk. Should there be any errors please write an issue.\nNecessary settings for the integration of the libraries can be found in the README:", wx.DefaultPosition, wx.DefaultSize, 0 )
|
||||
self.m_staticText5.Wrap( -1 )
|
||||
|
||||
self.m_staticText5.Hide()
|
||||
self.m_staticText5.SetMinSize( wx.Size( -1,50 ) )
|
||||
|
||||
bSizer.Add( self.m_staticText5, 0, wx.EXPAND|wx.TOP|wx.RIGHT|wx.LEFT, 5 )
|
||||
|
||||
self.m_hyperlink = wx.adv.HyperlinkCtrl( self, wx.ID_ANY, u"github.com/Steffen-W/Import-LIB-KiCad-Plugin", u"https://github.com/Steffen-W/Import-LIB-KiCad-Plugin", wx.DefaultPosition, wx.DefaultSize, wx.adv.HL_DEFAULT_STYLE )
|
||||
bSizer.Add( self.m_hyperlink, 0, wx.BOTTOM|wx.RIGHT|wx.LEFT, 5 )
|
||||
|
||||
|
||||
self.SetSizer( bSizer )
|
||||
self.Layout()
|
||||
|
||||
self.Centre( wx.BOTH )
|
||||
|
||||
# Connect Events
|
||||
self.Bind( wx.EVT_CLOSE, self.on_close )
|
||||
self.m_button_migrate.Bind( wx.EVT_BUTTON, self.migrate_libs )
|
||||
self.m_button.Bind( wx.EVT_BUTTON, self.BottonClick )
|
||||
self.m_buttonImportManual.Bind( wx.EVT_BUTTON, self.ButtomManualImport )
|
||||
self.m_textCtrl2.Bind( wx.EVT_TEXT_ENTER, self.ButtomManualImport )
|
||||
self.m_dirPicker_sourcepath.Bind( wx.EVT_DIRPICKER_CHANGED, self.DirChange )
|
||||
self.m_dirPicker_librarypath.Bind( wx.EVT_DIRPICKER_CHANGED, self.DirChange )
|
||||
|
||||
def __del__( self ):
|
||||
pass
|
||||
|
||||
|
||||
# Virtual event handlers, override them in your derived class
|
||||
def on_close( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def migrate_libs( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def BottonClick( self, event ):
|
||||
event.Skip()
|
||||
|
||||
def ButtomManualImport( self, event ):
|
||||
event.Skip()
|
||||
|
||||
|
||||
def DirChange( self, event ):
|
||||
event.Skip()
|
||||
|
||||
|
||||
|
311
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_helper_func.py
vendored
Normal file
311
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_helper_func.py
vendored
Normal file
@ -0,0 +1,311 @@
|
||||
import os.path
|
||||
import json
|
||||
import configparser
|
||||
from pathlib import Path
|
||||
import re
|
||||
|
||||
from .s_expression_parse import readFile2var, parse_sexp, convert_list_to_dicts
|
||||
|
||||
|
||||
class filehandler:
|
||||
def __init__(self, path):
|
||||
self.path = ""
|
||||
self.filelist = []
|
||||
self.change_path(path)
|
||||
|
||||
def change_path(self, newpath):
|
||||
if not os.path.isdir(newpath):
|
||||
newpath = "."
|
||||
if newpath != self.path:
|
||||
self.filelist = []
|
||||
self.path = newpath
|
||||
|
||||
def GetNewFiles(self, path):
|
||||
if path != self.path:
|
||||
self.change_path(path)
|
||||
|
||||
filelist = os.listdir(self.path)
|
||||
filelist.sort()
|
||||
newFiles = []
|
||||
for i in filelist:
|
||||
if i not in self.filelist and i.endswith(".zip"):
|
||||
pathtemp = os.path.join(self.path, i)
|
||||
# the file is less than 50 MB and larger 1kB
|
||||
if (os.path.getsize(pathtemp) < 1000 * 1000 * 50) and (
|
||||
os.path.getsize(pathtemp) > 1000
|
||||
):
|
||||
newFiles.append(pathtemp)
|
||||
self.filelist.append(i)
|
||||
return newFiles
|
||||
|
||||
|
||||
class config_handler:
|
||||
def __init__(self, config_path):
|
||||
self.config = configparser.ConfigParser()
|
||||
self.config_path = config_path
|
||||
self.config_is_set = False
|
||||
try:
|
||||
self.config.read(self.config_path)
|
||||
self.config["config"]["SRC_PATH"] # only for check
|
||||
self.config["config"]["DEST_PATH"] # only for check
|
||||
self.config_is_set = True
|
||||
except:
|
||||
self.print("An exception occurred during import " + self.config_path)
|
||||
self.config = configparser.ConfigParser()
|
||||
self.config.add_section("config")
|
||||
self.config.set("config", "SRC_PATH", "")
|
||||
self.config.set("config", "DEST_PATH", "")
|
||||
|
||||
if self.config["config"]["SRC_PATH"] == "":
|
||||
self.config["config"]["SRC_PATH"] = str(Path.home() / "Downloads")
|
||||
if self.config["config"]["DEST_PATH"] == "":
|
||||
self.config["config"]["DEST_PATH"] = str(Path.home() / "KiCad")
|
||||
self.config_is_set = False
|
||||
|
||||
def get_SRC_PATH(self):
|
||||
return self.config["config"]["SRC_PATH"]
|
||||
|
||||
def set_SRC_PATH(self, var):
|
||||
self.config["config"]["SRC_PATH"] = var
|
||||
self.save_config()
|
||||
|
||||
def get_DEST_PATH(self):
|
||||
return self.config["config"]["DEST_PATH"]
|
||||
|
||||
def set_DEST_PATH(self, var):
|
||||
self.config["config"]["DEST_PATH"] = var
|
||||
self.save_config()
|
||||
|
||||
def save_config(self):
|
||||
with open(self.config_path, "w") as configfile:
|
||||
self.config.write(configfile)
|
||||
|
||||
def print(self, text):
|
||||
print(text)
|
||||
|
||||
|
||||
class KiCad_Settings:
|
||||
def __init__(self, SettingPath):
|
||||
self.SettingPath = SettingPath
|
||||
|
||||
def get_sym_table(self):
|
||||
path = os.path.join(self.SettingPath, "sym-lib-table")
|
||||
return self.__parse_table__(path)
|
||||
|
||||
def set_sym_table(self, libname: str, libpath: str):
|
||||
path = os.path.join(self.SettingPath, "sym-lib-table")
|
||||
self.__add_entry_sexp__(path, name=libname, uri=libpath)
|
||||
|
||||
def sym_table_change_entry(self, old_uri, new_uri):
|
||||
path = os.path.join(self.SettingPath, "sym-lib-table")
|
||||
self.__update_uri_in_sexp__(path, old_uri=old_uri, new_uri=new_uri)
|
||||
|
||||
def get_lib_table(self):
|
||||
path = os.path.join(self.SettingPath, "fp-lib-table")
|
||||
return self.__parse_table__(path)
|
||||
|
||||
def set_lib_table_entry(self, libname: str):
|
||||
path = os.path.join(self.SettingPath, "fp-lib-table")
|
||||
uri_lib = "${KICAD_3RD_PARTY}/" + libname + ".pretty"
|
||||
self.__add_entry_sexp__(path, name=libname, uri=uri_lib)
|
||||
|
||||
def __parse_table__(self, path):
|
||||
sexp = readFile2var(path)
|
||||
parsed = parse_sexp(sexp)
|
||||
return convert_list_to_dicts(parsed)
|
||||
|
||||
def __update_uri_in_sexp__(
|
||||
self,
|
||||
path,
|
||||
old_uri,
|
||||
new_uri,
|
||||
):
|
||||
with open(path, "r") as file:
|
||||
data = file.readlines()
|
||||
|
||||
entry_pattern = re.compile(
|
||||
r'\s*\(lib \(name "(.*?)"\)\(type "(.*?)"\)\(uri "(.*?)"\)\(options "(.*?)"\)\(descr "(.*?)"\)\)\s*'
|
||||
)
|
||||
uri_found = False
|
||||
|
||||
for index, line in enumerate(data):
|
||||
match = entry_pattern.match(line)
|
||||
if match:
|
||||
name, type_, uri, options, descr = match.groups()
|
||||
if uri == old_uri:
|
||||
# Create a new entry with the new URI
|
||||
new_entry = f' (lib (name "{name}")(type "KiCad")(uri "{new_uri}")(options "{options}")(descr "{descr}"))\n'
|
||||
print("old entry:", data[index], end="")
|
||||
data[index] = new_entry
|
||||
print("new entry:", data[index], end="")
|
||||
uri_found = True
|
||||
break
|
||||
|
||||
if not uri_found:
|
||||
raise ValueError(f"URI '{old_uri}' not found in the file.")
|
||||
|
||||
with open(path, "w") as file:
|
||||
file.writelines(data)
|
||||
|
||||
def __add_entry_sexp__(
|
||||
self,
|
||||
path,
|
||||
name="Snapeda",
|
||||
uri="${KICAD_3RD_PARTY}/Snapeda.pretty",
|
||||
type="KiCad",
|
||||
options="",
|
||||
descr="",
|
||||
):
|
||||
table_entry = self.__parse_table__(path)
|
||||
entries = {lib["name"]: lib for lib in table_entry}
|
||||
if name in entries:
|
||||
raise ValueError(f"Entry with the name '{name}' already exists.")
|
||||
|
||||
new_entry = f' (lib (name "{name}")(type "{type}")(uri "{uri}")(options "{options}")(descr "{descr}"))\n'
|
||||
|
||||
with open(path, "r") as file:
|
||||
data = file.readlines()
|
||||
|
||||
# Insert the new entry before the last bracket character
|
||||
insert_index = len(data) - 1
|
||||
data.insert(insert_index, new_entry)
|
||||
|
||||
with open(path, "w") as file:
|
||||
file.writelines(data)
|
||||
|
||||
def get_kicad_json(self):
|
||||
path = os.path.join(self.SettingPath, "kicad.json")
|
||||
|
||||
with open(path) as json_data:
|
||||
data = json.load(json_data)
|
||||
|
||||
return data
|
||||
|
||||
def set_kicad_json(self, kicad_json):
|
||||
path = os.path.join(self.SettingPath, "kicad.json")
|
||||
|
||||
with open(path, "w") as file:
|
||||
json.dump(kicad_json, file, indent=2)
|
||||
|
||||
def get_kicad_common(self):
|
||||
path = os.path.join(self.SettingPath, "kicad_common.json")
|
||||
|
||||
with open(path) as json_data:
|
||||
data = json.load(json_data)
|
||||
|
||||
return data
|
||||
|
||||
def set_kicad_common(self, kicad_common):
|
||||
path = os.path.join(self.SettingPath, "kicad_common.json")
|
||||
|
||||
with open(path, "w") as file:
|
||||
json.dump(kicad_common, file, indent=2)
|
||||
|
||||
def get_kicad_GlobalVars(self):
|
||||
KiCadjson = self.get_kicad_common()
|
||||
return KiCadjson["environment"]["vars"]
|
||||
|
||||
def check_footprintlib(self, SearchLib, add_if_possible=True):
|
||||
msg = ""
|
||||
FootprintTable = self.get_lib_table()
|
||||
FootprintLibs = {lib["name"]: lib for lib in FootprintTable}
|
||||
|
||||
temp_path = "${KICAD_3RD_PARTY}/" + SearchLib + ".pretty"
|
||||
if SearchLib in FootprintLibs:
|
||||
if not FootprintLibs[SearchLib]["uri"] == temp_path:
|
||||
msg += "\n" + SearchLib
|
||||
msg += " in the Footprint Libraries is not imported correctly."
|
||||
msg += "\nYou have to import the library " + SearchLib
|
||||
msg += "' with the path '" + temp_path + "' in Footprint Libraries."
|
||||
if add_if_possible:
|
||||
msg += "\nThe entry must either be corrected manually or deleted."
|
||||
# self.set_lib_table_entry(SearchLib) # TODO
|
||||
else:
|
||||
msg += "\n" + SearchLib + " is not in the Footprint Libraries."
|
||||
if add_if_possible:
|
||||
self.set_lib_table_entry(SearchLib)
|
||||
msg += "\nThe library " + SearchLib
|
||||
msg += " has been successfully added."
|
||||
msg += "\n##### A restart of KiCad is necessary. #####"
|
||||
else:
|
||||
msg += "\nYou have to import the library " + SearchLib
|
||||
msg += "' with the path '" + temp_path
|
||||
msg += "' in the Footprint Libraries or select the automatic option."
|
||||
|
||||
return msg
|
||||
|
||||
def check_symbollib(self, SearchLib: str, add_if_possible: bool = True):
|
||||
msg = ""
|
||||
SearchLib_name = SearchLib.split(".")[0]
|
||||
SearchLib_name_short = SearchLib_name.split("_")[0]
|
||||
|
||||
SymbolTable = self.get_sym_table()
|
||||
SymbolLibs = {lib["name"]: lib for lib in SymbolTable}
|
||||
SymbolLibsUri = {lib["uri"]: lib for lib in SymbolTable}
|
||||
|
||||
temp_path = "${KICAD_3RD_PARTY}/" + SearchLib
|
||||
|
||||
if not temp_path in SymbolLibsUri:
|
||||
msg += "\n'" + temp_path + "' is not imported into the Symbol Libraries."
|
||||
if add_if_possible:
|
||||
if SearchLib_name_short not in SymbolLibs:
|
||||
self.set_sym_table(SearchLib_name_short, temp_path)
|
||||
msg += "\nThe library " + SearchLib
|
||||
msg += " has been successfully added."
|
||||
msg += "\n##### A restart of KiCad is necessary. #####"
|
||||
elif SearchLib_name not in SymbolLibs:
|
||||
self.set_sym_table(SearchLib_name, temp_path)
|
||||
msg += "\nThe library " + SearchLib
|
||||
msg += " has been successfully added."
|
||||
msg += "\n##### A restart of KiCad is necessary. #####"
|
||||
else:
|
||||
msg += "\nThe entry must either be corrected manually or deleted."
|
||||
# self.set_sym_table(SearchLib_name, temp_path) # TODO
|
||||
else:
|
||||
msg += "\nYou must add them manually or select the automatic option."
|
||||
|
||||
return msg
|
||||
|
||||
def check_GlobalVar(self, LocalLibFolder, add_if_possible=True):
|
||||
msg = ""
|
||||
GlobalVars = self.get_kicad_GlobalVars()
|
||||
|
||||
def setup_kicad_common():
|
||||
kicad_common = self.get_kicad_common()
|
||||
kicad_common["environment"]["vars"]["KICAD_3RD_PARTY"] = LocalLibFolder
|
||||
self.set_kicad_common(kicad_common)
|
||||
|
||||
if GlobalVars and "KICAD_3RD_PARTY" in GlobalVars:
|
||||
if not GlobalVars["KICAD_3RD_PARTY"] == LocalLibFolder:
|
||||
msg += "\nKICAD_3RD_PARTY is defined as '"
|
||||
msg += GlobalVars["KICAD_3RD_PARTY"]
|
||||
msg += "' and not '" + LocalLibFolder + "'."
|
||||
if add_if_possible:
|
||||
setup_kicad_common()
|
||||
msg += "\nThe entry was changed automatically."
|
||||
msg += "\n##### A restart of KiCad is necessary. #####"
|
||||
else:
|
||||
msg += "\nChange the entry or select the automatic option."
|
||||
else:
|
||||
msg += "\nKICAD_3RD_PARTY" + " is not defined in Environment Variables."
|
||||
if add_if_possible:
|
||||
setup_kicad_common()
|
||||
msg += "\nThe entry has been added successfully."
|
||||
else:
|
||||
msg += "\nYou must add them manually or select the automatic option."
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import pcbnew
|
||||
|
||||
Manager = pcbnew.SETTINGS_MANAGER()
|
||||
Setting = KiCad_Settings(Manager.GetUserSettingsPath())
|
||||
|
||||
SearchLib = "Samacsys"
|
||||
LocalLibFolder = "~/KiCad"
|
||||
|
||||
Setting.check_footprintlib(SearchLib)
|
||||
Setting.check_symbollib(SearchLib)
|
||||
Setting.check_GlobalVar(SearchLib)
|
166
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_migration.py
vendored
Normal file
166
3rdparty/plugins/com_github_Steffen-W_impartGUI/impart_migration.py
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
from pathlib import Path
|
||||
import logging
|
||||
|
||||
from .kicad_cli import kicad_cli
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
cli = kicad_cli()
|
||||
|
||||
|
||||
def find_old_lib_files(
|
||||
folder_path: str,
|
||||
libs: list[str] = ["Octopart", "Samacsys", "UltraLibrarian", "Snapeda", "EasyEDA"],
|
||||
) -> list:
|
||||
|
||||
folder_path = Path(folder_path).expanduser()
|
||||
found_files = {}
|
||||
|
||||
if not folder_path.exists():
|
||||
return found_files
|
||||
|
||||
for file in folder_path.iterdir():
|
||||
if not file.is_file():
|
||||
continue
|
||||
|
||||
if not (file.name.endswith(".lib") or file.name.endswith(".kicad_sym")):
|
||||
continue
|
||||
|
||||
for lib in libs:
|
||||
if file.name.startswith(lib):
|
||||
|
||||
if lib in found_files:
|
||||
entry = found_files[lib]
|
||||
else:
|
||||
entry = {}
|
||||
|
||||
# Check whether the file ends with ".lib"
|
||||
if file.name.endswith(".lib"):
|
||||
entry["old_lib"] = file
|
||||
|
||||
blk_file = file.with_suffix(".lib.blk")
|
||||
if blk_file.exists() and blk_file.is_file():
|
||||
entry["old_lib_blk"] = blk_file # backup file
|
||||
|
||||
dcm_file = file.with_suffix(".dcm")
|
||||
if dcm_file.exists() and dcm_file.is_file():
|
||||
entry["old_lib_dcm"] = dcm_file # description file
|
||||
|
||||
# Check whether the file with the old kicad v6 name exists
|
||||
elif file.name.endswith("_kicad_sym.kicad_sym"):
|
||||
entry["oldV6"] = file
|
||||
|
||||
dcm_file = file.with_suffix(".dcm")
|
||||
if dcm_file.exists() and dcm_file.is_file():
|
||||
entry["oldV6_dcm"] = dcm_file # description file
|
||||
|
||||
blk_file = file.with_suffix(".kicad_sym.blk")
|
||||
if blk_file.exists() and blk_file.is_file():
|
||||
entry["oldV6_blk"] = blk_file # backup file
|
||||
|
||||
# Check whether the file with the normal ".kicad_sym" extension exists
|
||||
elif file.name.endswith(".kicad_sym"):
|
||||
entry["V6"] = file
|
||||
|
||||
dcm_file = file.with_suffix(".dcm")
|
||||
if dcm_file.exists() and dcm_file.is_file():
|
||||
entry["V6_dcm"] = dcm_file # description file
|
||||
|
||||
blk_file = file.with_suffix(".kicad_sym.blk")
|
||||
if blk_file.exists() and blk_file.is_file():
|
||||
entry["V6_blk"] = blk_file # backup file
|
||||
|
||||
kicad_sym_file = file.with_name(lib + "_old_lib.kicad_sym")
|
||||
if kicad_sym_file.exists() and kicad_sym_file.is_file():
|
||||
# Possible conversion name
|
||||
entry["old_lib_kicad_sym"] = kicad_sym_file
|
||||
|
||||
if entry:
|
||||
found_files[lib] = entry
|
||||
return found_files
|
||||
|
||||
|
||||
def convert_lib(SRC: Path, DES: Path, drymode=True):
|
||||
|
||||
BLK_file = SRC.with_suffix(SRC.suffix + ".blk") # Backup
|
||||
|
||||
msg = []
|
||||
|
||||
if drymode:
|
||||
msg.append([SRC.name, DES.name])
|
||||
msg.append([SRC.name, BLK_file.name])
|
||||
|
||||
else:
|
||||
|
||||
SRC_dcm = SRC.with_suffix(".dcm")
|
||||
DES_dcm = DES.with_suffix(".dcm")
|
||||
if DES_dcm.exists() and DES_dcm.is_file():
|
||||
return []
|
||||
|
||||
if not cli.upgrade_sym_lib(SRC, DES) or not DES.exists():
|
||||
logger.error(f"converting {SRC.name} to {DES.name} produced an error")
|
||||
return []
|
||||
msg.append([SRC.stem, DES.stem])
|
||||
|
||||
if SRC_dcm.exists() and SRC_dcm.is_file():
|
||||
SRC_dcm.rename(DES_dcm)
|
||||
|
||||
SRC.rename(BLK_file)
|
||||
msg.append([SRC.name, BLK_file.name])
|
||||
|
||||
return msg
|
||||
|
||||
|
||||
def convert_lib_list(libs_dict, drymode=True):
|
||||
|
||||
if not cli.exists():
|
||||
logger.error("kicad_cli not found! Conversion is not possible.")
|
||||
drymode = True
|
||||
|
||||
convertlist = []
|
||||
for lib, paths in libs_dict.items():
|
||||
|
||||
# if "V6" in paths:
|
||||
# print(f"No conversion needed for {lib}.")
|
||||
|
||||
if "old_lib" in paths:
|
||||
file = paths["old_lib"]
|
||||
if "V6" in paths or "oldV6" in paths:
|
||||
if "old_lib_kicad_sym" in paths:
|
||||
logger.error(f"{lib} old_lib_kicad_sym already exists")
|
||||
else:
|
||||
kicad_sym_file = file.with_name(file.stem + "_old_lib.kicad_sym")
|
||||
res = convert_lib(SRC=file, DES=kicad_sym_file, drymode=drymode)
|
||||
convertlist.extend(res)
|
||||
else:
|
||||
name_V6 = file.with_name(lib + ".kicad_sym")
|
||||
res = convert_lib(SRC=file, DES=name_V6, drymode=drymode)
|
||||
convertlist.extend(res)
|
||||
|
||||
if "oldV6" in paths:
|
||||
file = paths["oldV6"]
|
||||
if "V6" in paths:
|
||||
logger.error(f"{lib} V6 already exists")
|
||||
else:
|
||||
name_V6 = file.with_name(lib + ".kicad_sym")
|
||||
res = convert_lib(SRC=file, DES=name_V6, drymode=drymode)
|
||||
convertlist.extend(res)
|
||||
return convertlist
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from pprint import pprint
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
path = "~/KiCad"
|
||||
ret = find_old_lib_files(folder_path=path)
|
||||
if ret:
|
||||
print("#######################")
|
||||
pprint(ret)
|
||||
print("#######################")
|
||||
|
||||
conv = convert_lib_list(ret, drymode=True)
|
||||
if conv:
|
||||
print("#######################")
|
||||
pprint(conv)
|
||||
print("#######################")
|
65
3rdparty/plugins/com_github_Steffen-W_impartGUI/kicad_cli/__init__.py
vendored
Normal file
65
3rdparty/plugins/com_github_Steffen-W_impartGUI/kicad_cli/__init__.py
vendored
Normal file
@ -0,0 +1,65 @@
|
||||
import subprocess
|
||||
|
||||
|
||||
class kicad_cli:
|
||||
def run_kicad_cli(self, command):
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["kicad-cli"] + command,
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
# print(result.stdout.strip())
|
||||
return True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(" ".join(["kicad-cli"] + command))
|
||||
print(f"Error: {e.stderr}")
|
||||
return None
|
||||
|
||||
def exists(self):
|
||||
|
||||
def version_to_tuple(version_str):
|
||||
try:
|
||||
return tuple(map(int, version_str.split("-")[0].split(".")))
|
||||
except (ValueError, AttributeError) as e:
|
||||
print(f"Version extractions error '{version_str}': {e}")
|
||||
return None
|
||||
|
||||
try:
|
||||
result = subprocess.run(
|
||||
["kicad-cli", "--version"],
|
||||
check=True,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
text=True,
|
||||
)
|
||||
version = result.stdout.strip()
|
||||
minVersion = "8.0.4"
|
||||
KiCadVers = version_to_tuple(version)
|
||||
if not KiCadVers or KiCadVers < version_to_tuple(minVersion):
|
||||
print("KiCad Version", version)
|
||||
print("Minimum required KiCad version is", minVersion)
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
except (subprocess.CalledProcessError, FileNotFoundError):
|
||||
print("kicad-cli does not exist")
|
||||
return False
|
||||
|
||||
def upgrade_sym_lib(self, input_file, output_file):
|
||||
return self.run_kicad_cli(["sym", "upgrade", input_file, "-o", output_file])
|
||||
|
||||
def upgrade_footprint_lib(self, pretty_folder):
|
||||
return self.run_kicad_cli(["fp", "upgrade", pretty_folder])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
cli = kicad_cli()
|
||||
if cli.exists():
|
||||
input_file = "UltraLibrarian_kicad_sym.kicad_sym"
|
||||
output_file = "UltraLibrarian.kicad_sym"
|
||||
|
||||
cli.upgrade_sym_lib(input_file, output_file)
|
||||
cli.upgrade_footprint_lib("test.pretty")
|
141
3rdparty/plugins/com_github_Steffen-W_impartGUI/s_expression_parse/__init__.py
vendored
Normal file
141
3rdparty/plugins/com_github_Steffen-W_impartGUI/s_expression_parse/__init__.py
vendored
Normal file
@ -0,0 +1,141 @@
|
||||
import re
|
||||
from os.path import exists, expanduser
|
||||
|
||||
|
||||
term_regex = r"""(?mx)
|
||||
\s*(?:
|
||||
(?P<brackl>\()|
|
||||
(?P<brackr>\))|
|
||||
(?P<num>\-?\d+\.\d+|\-?\d+)|
|
||||
(?P<sq>"[^"]*")|
|
||||
(?P<s>[^(^)\s]+)
|
||||
)"""
|
||||
|
||||
|
||||
# taken from https://rosettacode.org/wiki/S-Expressions#Python
|
||||
def parse_sexp(sexp, dbg=False):
|
||||
stack = []
|
||||
out = []
|
||||
if dbg:
|
||||
print("%-6s %-14s %-44s %-s" % tuple("term value out stack".split()))
|
||||
for termtypes in re.finditer(term_regex, sexp):
|
||||
term, value = [(t, v) for t, v in termtypes.groupdict().items() if v][0]
|
||||
if dbg:
|
||||
print("%-7s %-14s %-44r %-r" % (term, value, out, stack))
|
||||
if term == "brackl":
|
||||
stack.append(out)
|
||||
out = []
|
||||
elif term == "brackr":
|
||||
assert stack, "Trouble with nesting of brackets"
|
||||
tmpout, out = out, stack.pop(-1)
|
||||
out.append(tmpout)
|
||||
elif term == "num":
|
||||
v = float(value)
|
||||
if v.is_integer():
|
||||
v = int(v)
|
||||
out.append(v)
|
||||
elif term == "sq":
|
||||
out.append(value[1:-1])
|
||||
elif term == "s":
|
||||
out.append(value)
|
||||
else:
|
||||
raise NotImplementedError("Error: %r" % (term, value))
|
||||
assert not stack, "Trouble with nesting of brackets"
|
||||
return out[0]
|
||||
|
||||
|
||||
def print_sexp(exp):
|
||||
out = ""
|
||||
if type(exp) == type([]):
|
||||
out += "(" + " ".join(print_sexp(x) for x in exp) + ")"
|
||||
elif type(exp) == type("") and re.search(r"[\s()]", exp):
|
||||
out += '"%s"' % repr(exp)[1:-1].replace('"', '"')
|
||||
else:
|
||||
out += "%s" % exp
|
||||
return out
|
||||
|
||||
|
||||
def readFile2var(path):
|
||||
path = expanduser(path)
|
||||
|
||||
if not exists(path):
|
||||
return None
|
||||
|
||||
with open(path, "r") as file:
|
||||
data = file.read()
|
||||
return data
|
||||
|
||||
|
||||
def convert_list_to_dicts(data):
|
||||
dict_list = []
|
||||
for entry in data:
|
||||
if not type(entry) == list:
|
||||
continue
|
||||
entry_dict = {}
|
||||
for item in entry:
|
||||
if type(item) == list and len(item) == 2:
|
||||
key, value = item
|
||||
entry_dict[key] = value
|
||||
if len(entry_dict):
|
||||
dict_list.append(entry_dict)
|
||||
return dict_list
|
||||
|
||||
|
||||
def search_recursive(line: list, entry: str, all=False):
|
||||
if type(line[0]) == str and line[0] == entry:
|
||||
if all:
|
||||
return line
|
||||
else:
|
||||
return line[1]
|
||||
|
||||
for e in line:
|
||||
if type(e) == list:
|
||||
res = search_recursive(line=e, entry=entry, all=all)
|
||||
if not res == None:
|
||||
return res
|
||||
return None
|
||||
|
||||
|
||||
def extract_properties(sexp_list: list):
|
||||
"""
|
||||
Extracts all “property” entries from a nested list and saves them in a dictionary.
|
||||
|
||||
:param sexp_list: The nested list
|
||||
:return: A dictionary with “property” keys and their values
|
||||
"""
|
||||
properties = {}
|
||||
|
||||
def traverse_list(lst):
|
||||
"""Recursive function to run through the list and extract 'property' entries."""
|
||||
if isinstance(lst, list):
|
||||
if len(lst) > 1 and lst[0] == "property":
|
||||
key = lst[1]
|
||||
value = lst[2] if len(lst) > 2 else None
|
||||
properties[key] = value
|
||||
for item in lst:
|
||||
traverse_list(item)
|
||||
|
||||
traverse_list(sexp_list)
|
||||
return properties
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
from pprint import pprint
|
||||
|
||||
sexp = """(sym_lib_table
|
||||
(version 7)
|
||||
(lib (name "4xxx")(type "KiCad")(uri "${KICAD7_SYMBOL_DIR}/4xxx.kicad_sym")(options "")(descr "4xxx series symbols"))
|
||||
(lib (name "4xxx_IEEE")(type "KiCad")(uri "${KICAD7_SYMBOL_DIR}/4xxx_IEEE.kicad_sym")(options "")(descr "4xxx series IEEE symbols"))
|
||||
(lib (name "74xGxx")(type "KiCad")(uri "${KICAD7_SYMBOL_DIR}/74xGxx.kicad_sym")(options "")(descr "74xGxx symbols"))
|
||||
(lib (name "74xx")(type "KiCad")(uri "${KICAD7_SYMBOL_DIR}/74xx.kicad_sym")(options "")(descr "74xx symbols"))
|
||||
(lib (name "74xx_IEEE")(type "KiCad")(uri "${KICAD7_SYMBOL_DIR}/74xx_IEEE.kicad_sym")(options "")(descr "74xx series IEEE symbols"))
|
||||
)"""
|
||||
|
||||
dir_ = "~/.config/kicad/8.0/sym-lib-table"
|
||||
dir_ = "~/.config/kicad/8.0/fp-lib-table"
|
||||
sexp = readFile2var(dir_)
|
||||
|
||||
parsed = parse_sexp(sexp)
|
||||
# pprint(parsed)
|
||||
parsed_dict = convert_list_to_dicts(parsed[0:10])
|
||||
pprint(parsed_dict)
|
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/bin/kicad
vendored
Executable file
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/bin/kicad
vendored
Executable file
Binary file not shown.
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/bin/python
vendored
Executable file
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/bin/python
vendored
Executable file
Binary file not shown.
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/bin/python3
vendored
Executable file
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/bin/python3
vendored
Executable file
Binary file not shown.
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/bin/python3.9
vendored
Executable file
BIN
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/bin/python3.9
vendored
Executable file
Binary file not shown.
3
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/pyvenv.cfg
vendored
Normal file
3
3rdparty/plugins/com_github_Steffen-W_impartGUI/venv/pyvenv.cfg
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
home = /Applications/KiCad/KiCad.app/Contents/MacOS
|
||||
include-system-site-packages = false
|
||||
version = 3.9.13
|
Reference in New Issue
Block a user