def check_load_conf(caplog, dir='kicad', fail=False, catch_conf_error=False, no_conf_path=False): caplog.set_level(logging.DEBUG) kiconf_de_init() import pcbnew GS.kicad_conf_path = None if no_conf_path else pcbnew.GetKicadConfigPath() cov.load() cov.start() if catch_conf_error: with pytest.raises(KiConfError) as err: KiConf.init( os.path.join(context.BOARDS_DIR, 'v5_errors/kibom-test.sch')) else: KiConf.init( os.path.join(context.BOARDS_DIR, 'v5_errors/kibom-test.sch')) # Check we can call it again and nothing is done KiConf.init('bogus') err = None cov.stop() cov.save() ref = 'Reading KiCad config from `tests/data/' + dir + '/kicad_common`' if fail: ref = 'Unable to find KiCad configuration file' assert ref in caplog.text, caplog.text return err
def Run(self): # The entry function of the plugin that is executed on user action board = pcbnew.GetBoard() basename = os.path.splitext(board.GetFileName()) os.system(pcbnew.GetKicadConfigPath() + '\\FreeRouting.jar -de \"' + basename[0] + '.dsn\"')
def _setup(self): """ Setup prior to first buffer creation. Called automatically by base class during init. """ self.notebook = EditorNotebook(parent=self) intro = 'Py %s' % version.VERSION import imp module = imp.new_module('__main__') if sys.version_info >= (3, ): import builtins module.__dict__['__builtins__'] = builtins else: import __builtin__ module.__dict__['__builtins__'] = __builtin__ namespace = module.__dict__.copy() self.config_dir = pcbnew.GetKicadConfigPath() self.dataDir = self.config_dir self._setup_startup() self.history_file = os.path.join(self.config_dir, "PyShell_pcbnew.history") self.config_file = os.path.join(self.config_dir, "PyShell_pcbnew.cfg") self.config = wx.FileConfig(localFilename=self.config_file) self.config.SetRecordDefaults(True) self.autoSaveSettings = False self.autoSaveHistory = False self.LoadSettings() self.crust = crust.Crust(parent=self.notebook, intro=intro, locals=namespace, rootLabel="locals()", startupScript=self.startup_file, execStartupScript=self.execStartupScript) self.shell = self.crust.shell # Override the filling so that status messages go to the status bar. self.crust.filling.tree.setStatusText = self.SetStatusText # Override the shell so that status messages go to the status bar. self.shell.setStatusText = self.SetStatusText # Fix a problem with the sash shrinking to nothing. self.crust.filling.SetSashPosition(200) self.notebook.AddPage(page=self.crust, text='*Shell*', select=True) self.setEditor(self.crust.editor) self.crust.editor.SetFocus() self.LoadHistory()
def createtestsfile(tests=None): testsfile = os.path.join(os.path.dirname(__file__), 'tests.txt') kicommand_config = os.path.join(pcbnew.GetKicadConfigPath(), 'kicommand') #print('dirpath: ',', '.join(dir(os.path))) #print('Config exists? ',os.path.exists(kicommand_config)) if not os.path.exists(kicommand_config): os.mkdir(kicommand_config) teststempfile = os.path.join(kicommand_config, 'tests.txt') #if os.exists(kicommand_config): # structure of dictionary # {'testid':('description','command string',expected_result), ...} # Execute in testid order # strings and results if tests is None: tests = { '00010': ('single value', '0', '0', ''), '00020': ('int', '0 int', 0, 'int'), '00030': ('int single-value list', '0 int list', [0], 'int list'), '00040': ('int list', '0,1 int', [0, 1], 'int'), '00050': ('string list', '0,1 split', ['0', '1'], 'split'), '00060': ('raw string', '0\n1', r'0\n1', ''), '00070': ('encoded', '0\n1 encoded', '0\n1', 'encoded') } # with open(teststempfile, "wb") as fp: #Pickling # pickle.dump(tests, fp) # with open(teststempfile,'r') as f: # tests = eval(f.read()) # print tests structure in a way that eval() works and is easy to hand edit. with open(teststempfile, 'w') as f: f.write( """# TESTID:['Short Description',r'input to kc()','expected result top of stack','commands tested'(optional)] # Place "None" (without quotes) as the expected result to auto-generate result. # After running, the actual result file can be coped over the golden test results. # All tests should be independent and leave only one item on the stack. """) f.write('{\n') for testid, testitem in sorted(tests.iteritems(), key=lambda (k, v): k): f.write(_get_testitem_string(testid, testitem)) # f.write('{}:[{},\n r{},\n {}\n ],\n\n'.format( # *[repr(x) for x in [testid,testitem[0],testitem[1],testitem[2]]])) f.write('}') #with open(testsfile, "rb") as fp: # Unpickling # tests = pickle.load(fp) return teststempfile
def detect_kicad(): # Check if we have to run the nightly KiCad build nightly = False if os.environ.get('KIAUS_USE_NIGHTLY'): # Path to the Python module sys_path.insert(0, '/usr/lib/kicad-nightly/lib/python3/dist-packages') nightly = True try: import pcbnew except ImportError: logger.error("Failed to import pcbnew Python module." " Is KiCad installed?" " Do you need to add it to PYTHONPATH?") sys.exit(NO_PCBNEW_MODULE) try: GS.kicad_version = pcbnew.GetBuildVersion() except AttributeError: logger.warning( W_NOKIVER + "Unknown KiCad version, please install KiCad 5.1.6 or newer") # Assume the best case GS.kicad_version = '5.1.5' m = re.search(r'(\d+)\.(\d+)\.(\d+)', GS.kicad_version) GS.kicad_version_major = int(m.group(1)) GS.kicad_version_minor = int(m.group(2)) GS.kicad_version_patch = int(m.group(3)) GS.kicad_version_n = GS.kicad_version_major * 1000000 + GS.kicad_version_minor * 1000 + GS.kicad_version_patch logger.debug('Detected KiCad v{}.{}.{} ({} {})'.format( GS.kicad_version_major, GS.kicad_version_minor, GS.kicad_version_patch, GS.kicad_version, GS.kicad_version_n)) if GS.kicad_version_n >= KICAD_VERSION_5_99: GS.kicad_conf_path = pcbnew.GetSettingsManager().GetUserSettingsPath() if nightly: # Nightly Debian packages uses `/usr/share/kicad-nightly/kicad-nightly.env` as an environment extension # This script defines KICAD_CONFIG_HOME="$HOME/.config/kicadnightly" # So we just patch it, as we patch the name of the binaries GS.kicad_conf_path = GS.kicad_conf_path.replace( '/kicad/', '/kicadnightly/') else: logger.debug( 'Ignore the next message about creating a wxApp, is a KiCad 5 bug (6989)' ) GS.kicad_conf_path = pcbnew.GetKicadConfigPath() if GS.debug_level > 1: logger.debug('KiCad config path {}'.format(GS.kicad_conf_path))
def archive_symbols(board, allow_missing_libraries=False, alt_files=False, archive_documentation=False): global __name__ logger.info("Starting to archive symbols") # get project name pcb_filename = board.GetFileName() project_name = str(os.path.basename(board.GetFileName())).replace( ".kicad_pcb", "") cache_lib_name = project_name + "-cache.lib" archive_lib_name = project_name + "-archive.lib" logger.info("Pcb filename:" + pcb_filename) # list of failed documentation file accesses documentation_failed = [] # load system symbol library table if __name__ != "__main__": sys_path = os.path.normpath(pcbnew.GetKicadConfigPath()) else: # hardcode the path for my machine - testing works only on my machine sys_path = os.path.normpath( "C://Users//MitjaN//AppData//Roaming//kicad") logger.info("Kicad config path: " + sys_path) global_sym_lib_file_path = os.path.normpath(sys_path + "//sym-lib-table") try: with open(global_sym_lib_file_path) as f: global_sym_lib_file = f.readlines() except IOError: logger.info("Global sym-lib-table does not exist!") raise IOError("Global sym-lib-table does not exist!") # get library nicknames and dictionary of libraries (nickame:uri) libraries = {} nicknames = [] for line in global_sym_lib_file: nick_start = line.find("(name ") + 6 if nick_start >= 6: nick_stop = line.find(")", nick_start) nick = line[nick_start:nick_stop] nicknames.append(nick) # find path to library path_start = line.find("(uri ") + 5 if path_start >= 5: path_stop = line.find(")", path_start) path = line[path_start:path_stop] libraries[path] = nick # load project library table proj_path = os.path.dirname(os.path.abspath(board.GetFileName())) proj_sym_lib_file_path = os.path.normpath(proj_path + "//sym-lib-table") try: with open(proj_sym_lib_file_path) as f: project_sym_lib_file = f.readlines() # if file does not exists, create new except IOError: logger.info("Project sym lib table does not exist") new_sym_lib_file = [u"(sym_lib_table\n", u")\n"] with open(proj_sym_lib_file_path, "w") as f: f.writelines(new_sym_lib_file) project_sym_lib_file = new_sym_lib_file # append nicknames for line in project_sym_lib_file: nick_start = line.find("(name ") + 6 if nick_start >= 6: nick_stop = line.find(")", nick_start) nick = line[nick_start:nick_stop] nicknames.append(nick) path_start = line.find("(uri ") + 5 if path_start >= 5: path_stop = line.find(")", path_start) path = line[path_start:path_stop] libraries[path] = nick # check if archive library is already linked in archive_nick = None for lib in libraries.keys(): # if archive is already linked in, use its nickname if archive_lib_name in lib: logger.info( "project-archive.lib is already in the sym-lib-table, using its nickname" ) archive_nick = libraries[lib] break # if archive is not linked else: # check if default nick is already taken if libraries[lib] == "archive": logger.info("Nickname \"archive\" already taken") raise ValueError( "Nickname \"archive\" already taken by library that is not a project cache library!" ) if archive_nick is None: archive_nick = "archive" logger.info("Entering archive library in sym-lib-table") line_contents = " (lib (name archive)(type Legacy)(uri \"${KIPRJMOD}/" + archive_lib_name + "\")(options \"\")(descr \"\"))\n" project_sym_lib_file.insert(1, line_contents) with open(proj_sym_lib_file_path, "w") as f: f.writelines(project_sym_lib_file) # copy cache library and overwrite acrhive library, if it exists if not os.path.isfile(os.path.join(proj_path, cache_lib_name)): logger.info("Project cache library does not exists!") raise IOError("Project cache library does not exists!") copyfile(os.path.join(proj_path, cache_lib_name), os.path.join(proj_path, archive_lib_name)) if os.path.isfile( os.path.join(proj_path, cache_lib_name.replace(".lib", ".dcm"))): copyfile( os.path.join(proj_path, cache_lib_name.replace(".lib", ".dcm")), os.path.join(proj_path, archive_lib_name.replace(".lib", ".dcm"))) # read_archive library with open(os.path.join(proj_path, archive_lib_name)) as f: project_archive_file = f.readlines() # first find all symbols in the library symbols_list = [] for line in project_archive_file: line_contents = line.split() if line_contents[0] == "DEF": symbols_list.append(line_contents[1].replace("~", "")) # find all symbol references and replace them with correct ones new_symbol_list = [] for symbol in symbols_list: # modify the symbol reference if symbol.startswith(archive_nick): new_symbol = symbol.replace(archive_nick + ":", "") new_symbol = symbol.replace(archive_nick + "_", "") else: new_symbol = symbol.replace(":", "_") new_symbol_list.append(new_symbol) for line in project_archive_file: index = project_archive_file.index(line) if symbol in line and not line.startswith("F2"): project_archive_file[index] = line.replace(symbol, new_symbol) # scan for duplicate symbols and remove them # TODO start_indeces = [] stop_indeces = [] for index, line in enumerate(project_archive_file): if line.startswith("DEF"): start_indeces.append(index - 3) if line.startswith("ENDDEF"): stop_indeces.append(index + 1) component_locations = zip(start_indeces, stop_indeces) # first add initial lines project_archive_file_output = project_archive_file[0:start_indeces[0]] # then only add components which are not duplicated tested_components_hash = set() for loc in component_locations: component = project_archive_file[loc[0]:loc[1]] hash_value = hashlib.md5( "".join(component).encode('utf-8')).hexdigest() if hash_value not in tested_components_hash: tested_components_hash.add(hash_value) project_archive_file_output.extend( project_archive_file[loc[0]:loc[1]]) else: print("found one duplicate") # add remaining lines project_archive_file_output.extend(project_archive_file[stop_indeces[-1]:]) # writeback the archive file with open(os.path.join(proj_path, archive_lib_name), "w") as f: f.writelines(project_archive_file_output) archive_symbols_list = [] for sym in new_symbol_list: if sym.startswith(archive_nick): archive_symbols_list.append(sym.split(':', 1)[-1]) else: archive_symbols_list.append(sym.replace(":", "_")) archive_symbols_list = list(set(archive_symbols_list)) # find all .sch files # open main schematics file and look fo any sbuhiearchical files. In any subhierachical file scan for any sub-sub main_sch_file = os.path.abspath( str(pcb_filename).replace(".kicad_pcb", ".sch")) all_sch_files = [] all_sch_files = find_all_sch_files(main_sch_file, all_sch_files) all_sch_files = list(set(all_sch_files)) logger.info("found all subsheets") # go through each .sch file out_files = {} symbols_form_missing_libraries = set() for filename in all_sch_files: out_files[filename] = [] with open(filename) as f: sch_file = f.readlines() logger.info("Archiving file: " + filename) # go throught the symbols only def_indices = [] enddef_indices = [] for index, line in enumerate(sch_file): if line.startswith('$Comp'): def_indices.append(index) if line.startswith('$EndComp'): enddef_indices.append(index) if len(def_indices) != len(enddef_indices): logger.info("Cache library contains errors") raise LookupError("Cache library contains errors") symbol_locations = zip(def_indices, enddef_indices) sch_file_out = [] # find line starting with L and next word until colon mathes library nickname for index, line in enumerate(sch_file): line_contents = line.split() # if line is within the componend description and line denotes a symbol label for sym_loc in symbol_locations: if (sym_loc[0] < index) and (index < sym_loc[1]): break # if line begins with L it is the library reference if line_contents[0] == "L": libraryname = line_contents[1].split(":")[0] symbolname = line_contents[1].split(":")[1] # replace colon with underscore if libraryname == archive_nick: new_name = symbolname.split(archive_nick + '_', 1)[-1] else: new_name = line_contents[1].replace(":", "_") # make sure that the symbol is in cache and append cache nickname if new_name in archive_symbols_list: line_contents[1] = archive_nick + ":" + new_name # if the symbol is not in cache raise exception else: logger.info( "Trying to remap symbol which does not exist in archive library. Archive library is incomplete" ) raise LookupError( "Symbol \"" + new_name + "\" is not present in archive libray. Archive library is incomplete" ) # join line back again new_line = ' '.join(line_contents) sch_file_out.append(new_line + "\n") # symbol is not from the library present on the system markit for the potential errormessage if libraryname not in nicknames: symbols_form_missing_libraries.add(symbolname) # if it begins with F3 it might be a data sheet entry elif line_contents[0] == "F" and line_contents[ 1] == "3" and archive_documentation is True: link = line_contents[2].lower() if len(link) > 10: logger.info("Trying to archive documentation file: " + link) # if it is an url if (link.startswith("http") or link.startswith("\"http") or link.startswith("https") or link.startswith("\"https") or link.startswith("www") or link.startswith("\"www"))\ and (link.endswith(".pdf") or link.endswith(".pdf\"")): logger.info("File is encoded with URL") if link.startswith("\""): link = link.strip("\"") if link.startswith("www"): link = "https://" + link # try to get the file doc_filename = os.path.basename(link) destination_dir = os.path.join(proj_path, "documentation") destination_path = os.path.join( destination_dir, doc_filename) # check if folder exists if not os.path.exists(destination_dir): os.mkdir(destination_dir) try: # download file logger.info("Downloading file") urlretrieve(link, destination_path) # remap the entry logger.info("Remapping documenation entry") line_contents[ 2] = "\"" + "${KIPRJMOD}/documentation/" + doc_filename + "\"" new_line = ' '.join(line_contents) sch_file_out.append(new_line + "\n") except: logger.info("Failed to download file") documentation_failed.append(link) sch_file_out.append(line) pass # otherwise it is a filepath else: logger.info("File is encoded with filepath") clean_model_path = os.path.normpath(link.strip("\"")) # check if it can be expanded via accessible environment variables if "${" in clean_model_path or "$(" in clean_model_path: start_index = clean_model_path.find( "${") + 2 or clean_model_path.find("$(") + 2 end_index = clean_model_path.find( "}") or clean_model_path.find(")") env_var = clean_model_path[start_index:end_index] if env_var == "KIPRJMOD": path = proj_path else: path = os.getenv(env_var) # if variable is defined, find proper model path if path is not None: logger.info("File had enviroment variable") clean_model_path = os.path.normpath( path + clean_model_path[end_index + 1:]) # if variable is not defined, we can not find the model. Thus don't put it on the list else: logger.info( "Can not find documentation defined with enviroment variable:\n" + link) try: # copy the file localy logger.info("Trying to copy the file") doc_filename = os.path.basename(clean_model_path) destination_dir = os.path.join( proj_path, "documentation") destination_path = os.path.join( destination_dir, doc_filename) shutil.copy2(clean_model_path, destination_path) # and remap the entry logger.info("Remapping documenation entry") line_contents[ 2] = "\"" + "${KIPRJMOD}/documentation/" + doc_filename + "\"" new_line = ' '.join(line_contents) sch_file_out.append(new_line + "\n") # src and dest are the same except shutil.Error: logger.info("File has already been archived") documentation_failed.append(link) sch_file_out.append(line) # file not found except (OSError, IOError) as e: logger.info("Failed to copy file") documentation_failed.append(link) sch_file_out.append(line) else: sch_file_out.append(line) # othrerwise, just copy the line else: sch_file_out.append(line) # prepare for writing out_files[filename] = sch_file_out if symbols_form_missing_libraries: if not allow_missing_libraries: logger.info( "Schematics includes symbols from the libraries not present on the system\n" "Did Not Find:\n" + "\n".join(symbols_form_missing_libraries)) raise NameError( "Schematics includes symbols from the libraries not present on the system\n" "Did Not Find:\n" + "\n".join(symbols_form_missing_libraries)) # if no exceptions has been raised write files logger.info("Writing schematics file(s)") for key in out_files: filename = key # write if alt_files: filename = filename.replace(".sch", "_temp.sch") with open(filename, "w") as f: f.writelines(out_files[key]) # if not testing, delete cache file if not alt_files: os.remove(os.path.join(proj_path, cache_lib_name))
def GetConfigPath(): configpath = pcbnew.GetKicadConfigPath() return configpath + "/kicad_mmccoo.xml"
def detect_kicad(): # Check if we have to run the nightly KiCad build nightly = False if os.environ.get('KIAUS_USE_NIGHTLY'): # pragma: no cover (Ki6) # Path to the Python module sys_path.insert(0, '/usr/lib/kicad-nightly/lib/python3/dist-packages') nightly = True try: import pcbnew except ImportError: logger.error("Failed to import pcbnew Python module." " Is KiCad installed?" " Do you need to add it to PYTHONPATH?") sys.exit(NO_PCBNEW_MODULE) try: GS.kicad_version = pcbnew.GetBuildVersion() except AttributeError: logger.warning( W_NOKIVER + "Unknown KiCad version, please install KiCad 5.1.6 or newer") # Assume the best case GS.kicad_version = '5.1.5' try: # Debian sid may 2021 mess: really_index = GS.kicad_version.index('really') GS.kicad_version = GS.kicad_version[really_index + 6:] except ValueError: pass m = re.search(r'(\d+)\.(\d+)\.(\d+)', GS.kicad_version) GS.kicad_version_major = int(m.group(1)) GS.kicad_version_minor = int(m.group(2)) GS.kicad_version_patch = int(m.group(3)) GS.kicad_version_n = GS.kicad_version_major * 1000000 + GS.kicad_version_minor * 1000 + GS.kicad_version_patch logger.debug('Detected KiCad v{}.{}.{} ({} {})'.format( GS.kicad_version_major, GS.kicad_version_minor, GS.kicad_version_patch, GS.kicad_version, GS.kicad_version_n)) # Used to look for plug-ins. # KICAD_PATH isn't good on my system. # The kicad-nightly package overwrites the regular package!! GS.kicad_share_path = '/usr/share/kicad' if GS.kicad_version_n >= KICAD_VERSION_5_99: # pragma: no cover (Ki6) GS.kicad_conf_path = pcbnew.GetSettingsManager().GetUserSettingsPath() if nightly: # Nightly Debian packages uses `/usr/share/kicad-nightly/kicad-nightly.env` as an environment extension # This script defines KICAD_CONFIG_HOME="$HOME/.config/kicadnightly" # So we just patch it, as we patch the name of the binaries GS.kicad_conf_path = GS.kicad_conf_path.replace( '/kicad/', '/kicadnightly/') GS.kicad_share_path = GS.kicad_share_path.replace( '/kicad/', '/kicadnightly/') else: # Bug in KiCad (#6989), prints to stderr: # `../src/common/stdpbase.cpp(62): assert "traits" failed in Get(test_dir): create wxApp before calling this` # Found in KiCad 5.1.8, 5.1.9 # So we temporarily supress stderr with hide_stderr(): GS.kicad_conf_path = pcbnew.GetKicadConfigPath() # Dirs to look for plugins GS.kicad_plugins_dirs = [] # /usr/share/kicad/* GS.kicad_plugins_dirs.append(os.path.join(GS.kicad_share_path, 'scripting')) GS.kicad_plugins_dirs.append( os.path.join(GS.kicad_share_path, 'scripting', 'plugins')) # ~/.config/kicad/* GS.kicad_plugins_dirs.append(os.path.join(GS.kicad_conf_path, 'scripting')) GS.kicad_plugins_dirs.append( os.path.join(GS.kicad_conf_path, 'scripting', 'plugins')) # ~/.kicad_plugins and ~/.kicad if 'HOME' in os.environ: home = os.environ['HOME'] GS.kicad_plugins_dirs.append(os.path.join(home, '.kicad_plugins')) GS.kicad_plugins_dirs.append(os.path.join(home, '.kicad', 'scripting')) GS.kicad_plugins_dirs.append( os.path.join(home, '.kicad', 'scripting', 'plugins')) if GS.debug_level > 1: logger.debug('KiCad config path {}'.format(GS.kicad_conf_path))
def archive_symbols(board, allow_missing_libraries=False, alt_files=False): global __name__ logger.info("Starting to archive symbols") # get project name pcb_filename = board.GetFileName() project_name = str(os.path.basename(board.GetFileName())).replace( ".kicad_pcb", "") cache_lib_name = project_name + "-cache.lib" archive_lib_name = project_name + "-archive.lib" logger.info("Pcb filename:" + pcb_filename) # load system symbol library table if __name__ != "__main__": sys_path = os.path.normpath(pcbnew.GetKicadConfigPath()) else: # hardcode the path for my machine - testing works only on my machine sys_path = os.path.normpath( "C://Users//MitjaN//AppData//Roaming//kicad//V5") logger.info("Kicad config path: " + sys_path) global_sym_lib_file_path = os.path.normpath(sys_path + "//sym-lib-table") try: with open(global_sym_lib_file_path) as f: global_sym_lib_file = f.readlines() except IOError: logger.info("Global sym-lib-table does not exist!") raise IOError("Global sym-lib-table does not exist!") # get library nicknames and dictionary of libraries (nickame:uri) libraries = {} nicknames = [] for line in global_sym_lib_file: nick_start = line.find("(name ") + 6 if nick_start >= 6: nick_stop = line.find(")", nick_start) nick = line[nick_start:nick_stop] nicknames.append(nick) # find path to library path_start = line.find("(uri ") + 5 if path_start >= 5: path_stop = line.find(")", path_start) path = line[path_start:path_stop] libraries[path] = nick # load project library table proj_path = os.path.dirname(os.path.abspath(board.GetFileName())) proj_sym_lib_file_path = os.path.normpath(proj_path + "//sym-lib-table") try: with open(proj_sym_lib_file_path) as f: project_sym_lib_file = f.readlines() # if file does not exists, create new except IOError: logger.info("Project sym lib table does not exist") new_sym_lib_file = [u"(sym_lib_table\n", u")\n"] with open(proj_sym_lib_file_path, "w") as f: f.writelines(new_sym_lib_file) project_sym_lib_file = new_sym_lib_file # append nicknames for line in project_sym_lib_file: nick_start = line.find("(name ") + 6 if nick_start >= 6: nick_stop = line.find(")", nick_start) nick = line[nick_start:nick_stop] nicknames.append(nick) path_start = line.find("(uri ") + 5 if path_start >= 5: path_stop = line.find(")", path_start) path = line[path_start:path_stop] libraries[path] = nick # check if archive library is already linked in archive_nick = None for lib in libraries.keys(): # if archive is already linked in, use its nickname if archive_lib_name in lib: logger.info( "project-archive.lib is already in the sym-lib-table, using its nickname" ) archive_nick = libraries[lib] # if archive is not linked else: # check if default nick is already taken if libraries[lib] == "archive": logger.info("Nickname \"archive\" already taken") raise ValueError( "Nickname \"archive\" already taken by library that is not a project cache library!" ) if archive_nick is None: archive_nick = "archive" logger.info("Entering archive library in sym-lib-table") line_contents = " (lib (name archive)(type Legacy)(uri \"${KIPRJMOD}/" + archive_lib_name + "\")(options \"\")(descr \"\"))\n" project_sym_lib_file.insert(1, line_contents) with open(proj_sym_lib_file_path, "w") as f: f.writelines(project_sym_lib_file) # copy cache library if not os.path.isfile(os.path.join(proj_path, cache_lib_name)): logger.info("Project cache library does not exists!") raise IOError("Project cache library does not exists!") copyfile(os.path.join(proj_path, cache_lib_name), os.path.join(proj_path, archive_lib_name)) if os.path.isfile( os.path.join(proj_path, cache_lib_name.replace(".lib", ".dcm"))): copyfile( os.path.join(proj_path, cache_lib_name.replace(".lib", ".dcm")), os.path.join(proj_path, archive_lib_name.replace(".lib", ".dcm"))) # read_archive library with open(os.path.join(proj_path, archive_lib_name)) as f: project_archive_file = f.readlines() # first find all symbols in the library symbols_list = [] for line in project_archive_file: line_contents = line.split() if line_contents[0] == "DEF": symbols_list.append(line_contents[1].replace("~", "")) # find all symbol referneces and replace them with correct ones for symbol in symbols_list: # modify the symbol reference if symbol.startswith(archive_nick): new_symbol = symbol.replace(archive_nick + ":", "") else: new_symbol = symbol.replace(":", "_") for line in project_archive_file: index = project_archive_file.index(line) if symbol in line and not line.startswith("F2"): project_archive_file[index] = line.replace(symbol, new_symbol) # scan for duplicate symbols and remove them # TODO start_indeces = [] stop_indeces = [] for index, line in enumerate(project_archive_file): if line.startswith("DEF"): start_indeces.append(index - 3) if line.startswith("ENDDEF"): stop_indeces.append(index + 1) component_locations = zip(start_indeces, stop_indeces) # first add initiali lines project_archive_file_output = project_archive_file[0:start_indeces[0]] # then only add components which are not duplicated tested_components_hash = set() for loc in component_locations: component = project_archive_file[loc[0]:loc[1]] hash_value = hashlib.md5("".join(component)).hexdigest() if hash_value not in tested_components_hash: tested_components_hash.add(hash_value) project_archive_file_output.extend( project_archive_file[loc[0]:loc[1]]) else: print "found one duplicate" # add remaining lines project_archive_file_output.extend(project_archive_file[stop_indeces[-1]:]) # writeback the archive file with open(os.path.join(proj_path, archive_lib_name), "w") as f: f.writelines(project_archive_file_output) archive_symbols_list = [] for sym in symbols_list: if sym.startswith(archive_nick): archive_symbols_list.append(sym.split(':', 1)[-1]) else: archive_symbols_list.append(sym.replace(":", "_")) archive_symbols_list = list(set(archive_symbols_list)) # find all .sch files # open main schematics file and look fo any sbuhiearchical files. In any subhierachical file scan for any sub-sub main_sch_file = os.path.abspath( str(pcb_filename).replace(".kicad_pcb", ".sch")) all_sch_files = [] all_sch_files = find_all_sch_files(main_sch_file, all_sch_files) all_sch_files = list(set(all_sch_files)) # go through each .sch file out_files = {} symbols_form_missing_libraries = set() for filename in all_sch_files: out_files[filename] = [] with open(filename) as f: sch_file = f.readlines() sch_file_out = [] # find line starting with L and next word until colon mathes library nickname for line in sch_file: line_contents = line.split() # find symbol name if line_contents[0] == "L": libraryname = line_contents[1].split(":")[0] symbolname = line_contents[1].split(":")[1] # replace colon with underscore if libraryname == archive_nick: new_name = symbolname.split(archive_nick + '_', 1)[-1] else: new_name = line_contents[1].replace(":", "_") # make sure that the symbol is in cache and append cache nickname if new_name in archive_symbols_list: line_contents[1] = archive_nick + ":" + new_name # if the symbol is not in cache raise exception else: logger.info( "Trying to remap symbol which does not exist in archive library. Archive library is incomplete" ) raise LookupError( "Symbol \"" + new_name + "\" is not present in archive libray. Archive library is incomplete" ) # join line back again new_line = ' '.join(line_contents) sch_file_out.append(new_line + "\n") # symbol is not from the library present on the system markit for the potential errormessage if libraryname not in nicknames: symbols_form_missing_libraries.add(symbolname) else: sch_file_out.append(line) # prepare for writing out_files[filename] = sch_file_out if symbols_form_missing_libraries: if not allow_missing_libraries: logger.info( "Schematics includes symbols from the libraries not present on the system\n" "Did Not Find:\n" + "\n".join(symbols_form_missing_libraries)) raise NameError( "Schematics includes symbols from the libraries not present on the system\n" "Did Not Find:\n" + "\n".join(symbols_form_missing_libraries)) # if no exceptions has been raised write files logger.info("Writing schematics file(s)") for key in out_files: filename = key # write if alt_files: parts = filename.rsplit(".") parts[0] = parts[0] + "_alt" filename = ".".join(parts) with open(filename, "w") as f: f.writelines(out_files[key]) # if not testing, delete cache file if not alt_files: os.remove(os.path.join(proj_path, cache_lib_name))
def archive_symbols(board, allow_missing_libraries=False, alt_files=False): logger.info("Starting to archive symbols") # get project name pcb_filename = board.GetFileName() project_name = str(os.path.basename(board.GetFileName())).replace( ".kicad_pcb", "") cache_lib_name = project_name + "-cache.lib" archive_lib_name = project_name + "-archive.lib" logger.info("Pcb filename:" + pcb_filename) # load system symbol library table if is_pcbnew_running(): sys_path = os.path.normpath(pcbnew.GetKicadConfigPath()) else: # hardcode the path for my machine - testing works only on my machine sys_path = os.path.normpath( "C://Users//MitjaN//AppData//Roaming//kicad//V5") logger.info("Kicad config path: " + sys_path) global_sym_lib_file_path = os.path.normpath(sys_path + "//sym-lib-table") try: with open(global_sym_lib_file_path) as f: global_sym_lib_file = f.readlines() except IOError: logger.info("Global sym-lib-table does not exist!") raise IOError("Global sym-lib-table does not exist!") # get library nicknames and dictionary of libraries (nickame:uri) libraries = {} nicknames = [] for line in global_sym_lib_file: nick_start = line.find("(name ") + 6 if nick_start >= 6: nick_stop = line.find(")", nick_start) nick = line[nick_start:nick_stop] nicknames.append(nick) # find path to library path_start = line.find("(uri ") + 5 if path_start >= 5: path_stop = line.find(")", path_start) path = line[path_start:path_stop] libraries[path] = nick # load project library table proj_path = os.path.dirname(os.path.abspath(board.GetFileName())) proj_sym_lib_file_path = os.path.normpath(proj_path + "//sym-lib-table") try: with open(proj_sym_lib_file_path) as f: project_sym_lib_file = f.readlines() # if file does not exists, create new except IOError: logger.info("Project sym lib table does not exist") new_sym_lib_file = [u"(sym_lib_table\n", u")\n"] with open(proj_sym_lib_file_path, "w") as f: f.writelines(new_sym_lib_file) project_sym_lib_file = new_sym_lib_file # append nicknames for line in project_sym_lib_file: nick_start = line.find("(name ") + 6 if nick_start >= 6: nick_stop = line.find(")", nick_start) nick = line[nick_start:nick_stop] nicknames.append(nick) path_start = line.find("(uri ") + 5 if path_start >= 5: path_stop = line.find(")", path_start) path = line[path_start:path_stop] libraries[path] = nick # check if archive library is already linked in archive_nick = None for lib in libraries.keys(): # if archive is already linked in, use its nickname if archive_lib_name in lib: logger.info( "project-archive.lib is already in the sym-lib-table, using its nickname" ) archive_nick = libraries[lib] # if archive is not linked else: # check if default nick is already taken if libraries[lib] == "archive": logger.info("Nickname \"archive\" already taken") raise ValueError( "Nickname \"archive\" already taken by library that is not a project cache library!" ) if archive_nick is None: archive_nick = "archive" logger.info("Entering archive library in sym-lib-table") line_contents = " (lib (name archive)(type Legacy)(uri \"${KIPRJMOD}/" + archive_lib_name + "\")(options \"\")(descr \"\"))\n" project_sym_lib_file.insert(1, line_contents) with open(proj_sym_lib_file_path, "w") as f: f.writelines(project_sym_lib_file) # copy cache library if not os.path.isfile(os.path.join(proj_path, cache_lib_name)): logger.info("Project cache library does not exists!") raise IOError("Project cache library does not exists!") copyfile(os.path.join(proj_path, cache_lib_name), os.path.join(proj_path, archive_lib_name)) if os.path.isfile( os.path.join(proj_path, cache_lib_name.replace(".lib", ".dcm"))): copyfile( os.path.join(proj_path, cache_lib_name.replace(".lib", ".dcm")), os.path.join(proj_path, archive_lib_name.replace(".lib", ".dcm"))) # read_archive library with open(os.path.join(proj_path, archive_lib_name)) as f: project_archive_file = f.readlines() # get list of symbols in archive library and correct any colons into underscores archive_symbols_list = [] for line in project_archive_file: line_contents = line.split() if line_contents[0] == "DEF": # replace colon with underscore symbol = line_contents[1].replace(":", "_") # send replacement back to file line_nr = project_archive_file.index(line) line_new = list(line_contents) line_new[1] = symbol line_new = " ".join(line_new) + "\n" project_archive_file[line_nr] = line_new # remove any "~" symbol = symbol.replace("~", "") archive_symbols_list.append(symbol) # writeback the archive file with open(os.path.join(proj_path, archive_lib_name), "w") as f: f.writelines(project_archive_file) # find all .sch files # open main schematics file and look fo any sbuhiearchical files. In any subhierachical file scan for any sub-sub main_sch_file = os.path.abspath( str(pcb_filename).replace(".kicad_pcb", ".sch")) all_sch_files = [] all_sch_files = find_all_sch_files(main_sch_file, all_sch_files) all_sch_files = list(set(all_sch_files)) # go through each .sch file out_files = {} symbols_form_missing_libraries = set() for filename in all_sch_files: out_files[filename] = [] with open(filename) as f: sch_file = f.readlines() sch_file_out = [] # find line starting with L and next word until colon mathes library nickname for line in sch_file: line_contents = line.split() # find symbol name if line_contents[0] == "L": libraryname = line_contents[1].split(":")[0] symbolname = line_contents[1].split(":")[1] # replace colon with underscore new_name = line_contents[1].replace(":", "_") # make sure that the symbol is in cache and append cache nickname if new_name in archive_symbols_list: line_contents[1] = archive_nick + ":" + new_name # if the symbol is not in cache raise exception else: logger.info( "Trying to remap symbol which does not exist in archive library. Archive library is incomplete" ) raise LookupError( "Symbol \"" + new_name + "\" is not present in archive libray. Archive library is incomplete" ) # join line back again new_line = ' '.join(line_contents) sch_file_out.append(new_line + "\n") # symbol is not from the library present on the system markit for the potential errormessage if libraryname not in nicknames: symbols_form_missing_libraries.add(symbolname) else: sch_file_out.append(line) # prepare for writing out_files[filename] = sch_file_out if symbols_form_missing_libraries: if not allow_missing_libraries: logger.info( "Schematics includes symbols from the libraries not present on the system\n" "Did Not Find:\n" + "\n".join(symbols_form_missing_libraries)) raise NameError( "Schematics includes symbols from the libraries not present on the system\n" "Did Not Find:\n" + "\n".join(symbols_form_missing_libraries)) # if no exceptions has been raised write files logger.info("Writing schematics file") for key in out_files: filename = key # write if alt_files: filename = key + "_alt" with open(filename, "w") as f: f.writelines(out_files[key]) pass pass
def __init__(self, logger, input_file=None, args=None): self.export_format = 'pdf' if input_file: self.input_file = input_file self.input_no_ext = os.path.splitext(input_file)[0] # # As soon as we init pcbnew the following files are modified: # if os.path.isfile(self.input_no_ext+'.pro'): self.start_pro_stat = os.stat(self.input_no_ext+'.pro') else: self.start_pro_stat = None if os.path.isfile(self.input_no_ext+'.kicad_pro'): self.start_kicad_pro_stat = os.stat(self.input_no_ext+'.kicad_pro') else: self.start_kicad_pro_stat = None if os.path.isfile(self.input_no_ext+'.kicad_prl'): self.start_kicad_prl_stat = os.stat(self.input_no_ext+'.kicad_prl') else: self.start_kicad_prl_stat = None if args: # Session debug self.use_wm = args.use_wm # Use a Window Manager, dialogs behaves in a different way self.start_x11vnc = args.start_x11vnc self.rec_width = args.rec_width self.rec_height = args.rec_height self.record = args.record self.video_dir = args.output_dir self.wait_for_key = args.wait_key self.time_out_scale = args.time_out_scale # Others if hasattr(args, 'file_format'): self.export_format = args.file_format.lower() else: # Session debug self.use_wm = False self.start_x11vnc = False self.rec_width = REC_W self.rec_height = REC_H self.record = False self.video_dir = None self.wait_for_key = False self.time_out_scale = 1.0 self.colordepth = 24 self.video_name = None self.video_dir = self.output_dir = '' # Executable and dirs self.eeschema = 'eeschema' self.pcbnew = 'pcbnew' self.kicad2step = 'kicad2step' self.kicad_conf_dir = 'kicad' ng_ver = os.environ.get('KIAUS_USE_NIGHTLY') if ng_ver: self.eeschema += '-'+NIGHTLY self.pcbnew += '-'+NIGHTLY self.kicad2step += '-'+NIGHTLY self.kicad_conf_dir += os.path.join(NIGHTLY, ng_ver) # Path to the Python module path.insert(0, '/usr/lib/kicad-nightly/lib/python3/dist-packages') # Detect KiCad version try: import pcbnew except ImportError: logger.error("Failed to import pcbnew Python module." " Is KiCad installed?" " Do you need to add it to PYTHONPATH?") exit(NO_PCBNEW_MODULE) kicad_version = pcbnew.GetBuildVersion() try: # Debian sid may 2021 mess: really_index = kicad_version.index('really') kicad_version = kicad_version[really_index+6:] except ValueError: pass m = re.search(r'(\d+)\.(\d+)\.(\d+)', kicad_version) if m is None: logger.error("Unable to detect KiCad version, got: `{}`".format(kicad_version)) exit(NO_PCBNEW_MODULE) self.kicad_version_major = int(m.group(1)) self.kicad_version_minor = int(m.group(2)) self.kicad_version_patch = int(m.group(3)) self.kicad_version = self.kicad_version_major*1000000+self.kicad_version_minor*1000+self.kicad_version_patch logger.debug('Detected KiCad v{}.{}.{} ({} {})'.format(self.kicad_version_major, self.kicad_version_minor, self.kicad_version_patch, kicad_version, self.kicad_version)) self.ki5 = self.kicad_version < KICAD_VERSION_5_99 # Config file names if not self.ki5: self.kicad_conf_path = pcbnew.GetSettingsManager().GetUserSettingsPath() # No longer needed for 202112021512+6.0.0+rc1+287+gbb08ef2f41+deb11 # if ng_ver: # self.kicad_conf_path = self.kicad_conf_path.replace('/kicad/', '/kicadnightly/') else: # Bug in KiCad (#6989), prints to stderr: # `../src/common/stdpbase.cpp(62): assert "traits" failed in Get(test_dir): create wxApp before calling this` # Found in KiCad 5.1.8, 5.1.9 # So we temporarily supress stderr with hide_stderr(): self.kicad_conf_path = pcbnew.GetKicadConfigPath() logger.debug('Config path {}'.format(self.kicad_conf_path)) # First we solve kicad_common because it can redirect to another config dir self.conf_kicad = os.path.join(self.kicad_conf_path, 'kicad_common') self.conf_kicad_bkp = None if not self.ki5: self.conf_kicad += '.json' self.conf_kicad_json = True else: self.conf_kicad_json = False # Read the environment redefinitions used by KiCad if os.path.isfile(self.conf_kicad): self.load_kicad_environment(logger) if 'KICAD_CONFIG_HOME' in self.env and self.ki5: # The user is redirecting the configuration # KiCad 5 unintentionally allows it, is a bug, and won't be fixed: # https://forum.kicad.info/t/kicad-config-home-inconsistencies-and-detail/26875 self.kicad_conf_path = self.env['KICAD_CONFIG_HOME'] logger.debug('Redirecting KiCad config path to: '+self.kicad_conf_path) else: logger.warning('Missing KiCad main config file '+self.conf_kicad) # - eeschema config self.conf_eeschema = os.path.join(self.kicad_conf_path, 'eeschema') self.conf_eeschema_bkp = None # - pcbnew config self.conf_pcbnew = os.path.join(self.kicad_conf_path, 'pcbnew') self.conf_pcbnew_bkp = None # Config files that migrated to JSON # Note that they remain in the old format until saved if not self.ki5: self.conf_eeschema += '.json' self.conf_pcbnew += '.json' self.conf_eeschema_json = True self.conf_pcbnew_json = True self.pro_ext = 'kicad_pro' self.prl_ext = 'kicad_prl' self.conf_colors = os.path.join(self.kicad_conf_path, 'colors', 'user.json') self.conf_colors_bkp = None self.conf_3dview = os.path.join(self.kicad_conf_path, '3d_viewer.json') self.conf_3dview_bkp = None else: self.conf_eeschema_json = False self.conf_pcbnew_json = False self.pro_ext = 'pro' self.prl_ext = None self.conf_colors = self.conf_colors_bkp = None self.conf_3dview = self.conf_3dview_bkp = None # - hotkeys self.conf_hotkeys = os.path.join(self.kicad_conf_path, 'user.hotkeys') self.conf_hotkeys_bkp = None # - sym-lib-table self.user_sym_lib_table = os.path.join(self.kicad_conf_path, 'sym-lib-table') self.user_fp_lib_table = os.path.join(self.kicad_conf_path, 'fp-lib-table') self.sys_sym_lib_table = [KICAD_SHARE+'template/sym-lib-table'] self.sys_fp_lib_table = [KICAD_SHARE+'template/fp-lib-table'] if ng_ver: # 20200912: sym-lib-table is missing self.sys_sym_lib_table.insert(0, KICAD_NIGHTLY_SHARE+'template/sym-lib-table') self.sys_fp_lib_table.insert(0, KICAD_NIGHTLY_SHARE+'template/fp-lib-table') # Some details about the UI if not self.ki5: # KiCad 5.99.0 # self.ee_window_title = r'\[.*\] — Eeschema$' # "PROJECT [HIERARCHY_PATH] - Eeschema" # KiCad 6.0.0 rc1 self.ee_window_title = r'\[.*\] — Schematic Editor$' # "PROJECT [HIERARCHY_PATH] - Schematic Editor" self.pn_window_title = r'.* — PCB Editor$' # "PROJECT - PCB Editor" else: # KiCad 5.1.6 self.ee_window_title = r'Eeschema.*\.sch' # "Eeschema - file.sch" self.pn_window_title = r'^Pcbnew' # Collected errors and unconnecteds (warnings) self.errs = [] self.wrns = [] # Error filters self.err_filters = []