0321 from macmini
This commit is contained in:
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,
|
||||
)
|
Reference in New Issue
Block a user