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