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
Ejemplo n.º 2
0
    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\"')
Ejemplo n.º 3
0
    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()
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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))
Ejemplo n.º 6
0
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))
Ejemplo n.º 7
0
def GetConfigPath():
    configpath = pcbnew.GetKicadConfigPath()
    return configpath + "/kicad_mmccoo.xml"
Ejemplo n.º 8
0
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))
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
 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 = []