Macro Packages

Other languages:

Macro Packages

Description
Gui front end for pip for installing/removing pypi packages in the FreeCAD Python environment.

Macro version: 0.4
Last modified: 2025.01.18
FreeCAD version: 1.0
Download: ToolBar Icon
Author: TheMarkster
Author
TheMarkster
Download
ToolBar Icon
Links
Macro Version
0.4
Date last modified
2025.01.18
FreeCAD Version(s)
1.0
Default shortcut
None
See also
None

Description

This is a Gui front end for pip for installing pypi packages into the FreeCAD Python environment.

Packages screenshot

Usage

This macro will install pypi packages into a special folder called AdditionalPythonPackages. This is the same exact folder that is also used by the AddonManager for installing dependencies for various addons. Files in this folder might have been installed by the addon manager or by this macro. You can click the button in the bottom right corner to open the folder location in your default file browser. Note that if the folder does not yet exist it will be created for you. If the folder has to be created, then FreeCAD will not recognize packages installed into it until FreeCAD is restarted. After that, no FreeCAD restart should be required again. Packages are installed using pip. These are installed from pypi. Only the top 8000 most downloaded packages are listed, but you can use the search button to install those that are outside the top 8000 most popular packages. The apply filter button will use the filter line edit contents to filter the packages in the top 8000 list. The Sort checkbox toggles sorting between alphabetical and based on popularity (default unchecked state). The open url button opens the pypi page for the selected package in your default browser.

Note that a package might have additional dependencies, which will also be installed, and when you remove the package the dependencies that were installed with it will also be removed unless they were also installed as dependencies for another package. It is possible to delete the entire folder to remove all dependency packages installed with this macro and with the Addon Manager. But obviously doing so might break any addons that require these packages.

The installed packages will go into a subfolder based on the Python version in use. If FreeCAD is updated to a newer version of Python, then those packages will most likely need to be re-installed. You can simply copy them over to the new Python version folder, but it is better to use pip in case the newer Python version requires a newer version of the package.

Script

ToolBar icon

packages.py

# packages.py
# 2024, <TheMarkster> LGPL 2.1 or later
# thanks to forum member hasecilu for help with setting pyside6 icons
# a utility to install Python packages in FreeCAD

__version__ = "0.4"

from PySide import QtWidgets, QtGui, QtCore
QDialog = QtWidgets.QDialog
QVBoxLayout = QtWidgets.QVBoxLayout
QHBoxLayout = QtWidgets.QHBoxLayout
QLabel = QtWidgets.QLabel
QLineEdit = QtWidgets.QLineEdit
QSpinBox = QtWidgets.QSpinBox
QPushButton = QtWidgets.QPushButton
QListWidget = QtWidgets.QListWidget
QMessageBox = QtWidgets.QMessageBox
QCheckBox = QtWidgets.QCheckBox
QApplication = QtGui.QApplication
QFileDialog = QtGui.QFileDialog
Qt = QtCore.Qt

import requests
import re
import webbrowser
import subprocess, sys, platform
import freecad.utils as utils
import addonmanager_utilities as amutils
import shutil
import json
import importlib

class Packages:
    def __init__(self):
        self.python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
        self.pg = FreeCAD.ParamGet(f"User parameter:Plugins/Packages_Macro/{self.python_version}")
        layout = QVBoxLayout()
        self.mw = FreeCADGui.getMainWindow()
        self.pathEdit = QLineEdit()
        self.pathEdit.setToolTip("Path to python executable.")
        self.pathEdit.setText(self.findExecutable())
        self.form = QtWidgets.QWidget()
        self.form.setLayout(layout)
        self.mode = "install" # can also be "uninstall"
        filterLayout = QHBoxLayout()
        self.filterEdit = QLineEdit()
        self.filterEdit.setPlaceholderText("Filter packages")
        self.filterEdit.returnPressed.connect(self.applyFilter)
        self.filterEdit.textChanged.connect(self.enableButtons)
        filterLayout.addWidget(QLabel("Filter:"))
        filterLayout.addWidget(self.filterEdit)
        self.sortedCheckBox = QCheckBox("Sort")
        self.sortedCheckBox.setToolTip("""
Default is sorting by popularity in terms of most downloads in the last 30 days.
Checked = sort A to Z.
Note toggling applies the filter.""")
        self.sortedCheckBox.setChecked(False)
        self.sortedCheckBox.stateChanged.connect(self.applyFilter)
        # this is to initialize stateChanged, otherwise first toggle does not update
        checked = self.sortedCheckBox.isChecked()
        filterLayout.addWidget(self.sortedCheckBox)
        
        self.filterButton = QPushButton("Apply Filter")
        self.filterButton.clicked.connect(self.applyFilter)
        filterLayout.addWidget(self.filterButton)
        
        self.searchButton = QPushButton("Search")
        self.searchButton.setIcon(FreeCADGui.getIcon("internet-web-browser"))
        self.searchButton.setToolTip("Search for filter on pypi.org in default browser")
        self.searchButton.setEnabled(False)
        filterLayout.addWidget(self.searchButton)
        self.searchButton.clicked.connect(self.search)
        
        self.packageList = QListWidget()
        self.packageList.itemSelectionChanged.connect(self.enableButtons)

        self.fetchButton = QPushButton("Fetch summary")
        self.fetchButton.setIcon(FreeCADGui.getIcon("internet-web-browser"))
        self.fetchButton.setEnabled(False)
        self.fetchButton.clicked.connect(self.fetchSummary)

        layout.addLayout(filterLayout)

        layout.addWidget(self.packageList)
        buttonLayout = QHBoxLayout()
        buttonLayout.addWidget(self.fetchButton)
        
        self.installButton = QPushButton("Install / Update")
        self.installButton.clicked.connect(self.install)
        self.installButton.setEnabled(False)
        self.installButton.setToolTip("""
If this is disabled with an active selection it means the package is a base package.
You are allowed to install again an added package because there might be an update
available for it.""")
        buttonLayout.addWidget(self.installButton)
        
        self.uninstallButton = QPushButton("Uninstall")
        style = self.form.style()
        try:
            trashIcon = style.standardIcon(style.SP_TrashIcon)
        except:
            trashIcon = style.standardIcon(style.StandardPixmap.SP_TrashIcon)
        self.uninstallButton.setIcon(trashIcon)
        self.uninstallButton.clicked.connect(self.uninstall)
        self.uninstallButton.setEnabled(False)
        self.uninstallButton.setToolTip("""
If this is disabled with an active selection it means the selected package is
either not already installed or it is a base package included with the packaging
or distribution.""")
        buttonLayout.addWidget(self.uninstallButton)
        
        self.openUrlButton = QPushButton("Open Url")
        self.openUrlButton.setIcon(FreeCADGui.getIcon("internet-web-browser"))
        self.openUrlButton.clicked.connect(self.openUrl)
        self.openUrlButton.setEnabled(False)
        buttonLayout.addWidget(self.openUrlButton)
        
        layout.addLayout(buttonLayout)
        
        pathLayout = QHBoxLayout()
        layout.addLayout(pathLayout)

        self.pathEdit.setPlaceholderText("Path to python executable")
        pathLayout.addWidget(QLabel("Python:"))
        pathLayout.addWidget(self.pathEdit)
        self.pathButton = QPushButton("...")
        try:
            self.pathButton.setIcon(style.standardIcon(style.SP_FileDialogStart))
        except:
            self.pathButton.setIcon(style.standardIcon(style.StandardPixmap.SP_FileDialogStart))
        self.pathButton.setFixedWidth(self.pathButton.sizeHint().width())
        self.pathButton.setToolTip("Click to browse for python executable.")
        self.pathButton.clicked.connect(self.setupPath)
        pathLayout.addWidget(self.pathButton)
        
        additionalLayout = QHBoxLayout()
        layout.addLayout(additionalLayout)
        additionalLayout.addWidget(QLabel("AdditionalPythonPackages folder:"))
        self.additionalEdit = QLineEdit()
        self.additionalEdit.setPlaceholderText("AdditionalPythonPackages folder goes here")
        pathToPackages = self.pg.GetString("PathToPackages","")
        if not pathToPackages:
            pathToPackages = amutils.get_pip_target_directory()
            if not os.path.exists(pathToPackages):
                os.makedirs(pathToPackages)
                self.pg.SetString("PathToPackages", pathToPackages)
                FreeCAD.Console.PrintMessage("AdditionalPythonPackages folder has been created.  You will likely need to restart FreeCAD to use any newly installed packages.\n")
        self.additionalEdit.setText(pathToPackages)
        self.additionalEdit.setReadOnly(True)
        additionalLayout.addWidget(self.additionalEdit)
        self.additionalButton = QPushButton("Open")
        try:
            self.additionalButton.setIcon(style.standardIcon(style.SP_DirOpenIcon))
        except:
            self.additionalButton.setIcon(style.standardIcon(style.StandardPixmap.SP_DirOpenIcon))
        self.additionalButton.clicked.connect(self.openAdditional)
        self.additionalButton.setToolTip("""
Opens the AdditionalPythonPackages folder.  Note that the only things
that should be in this folder are things installed using this macro
or additional dependencies installed via the addon manager.  It should
be relatively safe to remove things from here.  You should take note
of what extra folders were added when you install something.  These
dependencies are not being tracked as pip doesn't support tracking
packages added to custom folders.  When we uninstall something we
only install what we added, not the additional dependencies pip might
have added.""")
        additionalLayout.addWidget(self.additionalButton)
        
        self.packages = self.loadPackageData()
        title = f"Packages {__version__} ({len(self.packages)} packages)"
        self.form.setWindowTitle(title)
        self.applyFilter()

        self.form.setWindowIcon(self.QIconFromXPMString(__icon__))

    def loadPackageData(self):
        # would be faster to cache this locally, but this way it's always up to date
        url = "https://hugovk.github.io/top-pypi-packages/top-pypi-packages-30-days.json"
        try:
            response = requests.get(url)
            response.raise_for_status()
            data = response.json()
            return data["rows"]
        except requests.RequestException as e:
            QMessageBox.critical(self.mw, "Error", f"Could not load data: {e}")
            return []
            
    def openAdditional(self):
        """
        open the addtional python packages folder.  note that this is a different
        folder for each version of python used by various freecad installations, 
        for example if the user has multiple versions in simultaneous use.  this
        folder is really for the addon manager to install dependencies when installing
        various addons, so we do not have exclusive use of it.  but the advantage
        is the addon manager can also update these for use when updates are available.
        this folder is made available to python via initGui,py in src/App, which
        is how addons are able to import the packages installed here.
        """
        sys = platform.system()
        userFolder = self.additionalEdit.text()
        if 'Windows' in sys:
            subprocess.Popen('start explorer.exe '+userFolder, shell=True)
        elif 'Linux' in sys:
            os.system("xdg-open '%s'" % userFolder)
        elif 'Darwin' in sys:
            subprocess.Popen(["open", userFolder])
        else:
            msgBox = QtGui.QMessageBox()
            msg = "We were unable to determine your platform, and thus cannot open your '+userFolder+' for you, but you can still do it manually.\n"
            msgBox.exec_()
                    
    def getStandardButtons(self):
        return (QtWidgets.QDialogButtonBox.Close)
        
    def setupPath(self):
        """
        This is for the user to bre able to set a python that he wants to use
        rather than the default found with the freecad.utils function
        """
        path, ok = QFileDialog.getOpenFileName(self.mw, 
                "Select Python Executable",
                "",
                "Python Executable (python*);;All Files (*)")
        if path:
            self.pathEdit.setText(path)
            self.pg.SetString("PathToPython", path)


    def applyFilter(self):
        """
        This is called when the user clicks the Apply filters button, and also
        when the user toggles the sort checkbox, and when the enter key is pressed
        in the filter line edit widget.  If sorting is active, then the filtered
        packages are sorted alphabetically, otherwise they are sorted based on the 
        popularity as measured by the number of downloads in the last 30 days.
        """
        filter = self.filterEdit.text()
        try:
            filtered = [pkg for pkg in self.packages if re.search(filter, pkg["project"], re.IGNORECASE)]
            exactMatch = False
            self.packageList.clear()
            for pkg in filtered:
                self.packageList.addItem(pkg["project"])
                if pkg == filter:
                    exactMatch = True
            self.packageList.setSortingEnabled(self.sortedCheckBox.isChecked())
            if filter and not exactMatch:
                description, version = self.getInfo(filter)
                if description:
                    self.packageList.insertItem(0, filter + " (not in top 8000)")

        except re.error as e:
            QMessageBox.warning(self.mw, "Invalid Regex", f"Error in regular expression: {e}")
            self.form.setWindowTitle(f"Packages {__version__}")

    def enableButtons(self):
        """enables or disables buttons based on whether a package is selected
        in the list widget and whether that package is already installed."""
        hasSelection = bool(self.packageList.selectedItems())
        self.openUrlButton.setEnabled(hasSelection)
        self.fetchButton.setEnabled(hasSelection)
        hasFilter = bool(self.filterEdit.text())
        self.searchButton.setEnabled(hasFilter)
            
        added, base = (False, False)
        if hasSelection:
            added, base = self.isInstalled()
        self.installButton.setEnabled(not base)
        self.uninstallButton.setEnabled(added)

    def install(self):
        self.mode = "install"
        self.managePackages()
        
    def uninstall(self):
        self.mode = "uninstall"
        self.managePackages()
        
    def findExecutable(self):
        """
        find the python executable in use, luckily there is a function for this
        already in FreeCAD, but we still want to allow the user the flexibility
        of setting a different python executable if the function gets it wrong
        """
        
        path = self.pg.GetString("PathToPython","")
        if path:
            return path
        
        pyEx = utils.get_python_exe()
    
        if os.path.exists(pyEx):
            self.pg.SetString("PathToPython", pyEx)
            return pyEx
        else:
            FreeCAD.Console.PrintError(f"Python executable not found.  You may try to find it manually by clicking the '...' button at the bottom of the dialog.\n")
            return "Path to python executable"

    def managePackages(self):
        """
        Here is where the installing and uninstalling happens.  We track which
        packages we have installed and which dependencies were installed along
        with them.  The caveat here is the AddonManager might also install stuff
        in our folder, but I don't think the AM tracks extra dependencies.
        """
        package = self.packageList.currentItem().text().replace(" (not in top 8000)", "")
        python_exe = self.pathEdit.text()
        vendor_path = self.additionalEdit.text()
        if "APPIMAGE" in os.environ:
            
            os.environ['PYTHONHOME'] = '/usr'
            os.environ['PYTHONPATH'] = f"/usr/lib/python{self.python_version}/site-packages" 

        if self.mode == "uninstall":
            added = self.loadAddedSubfolders(package)
            self.pg.RemString(f"{package}_Subfolders")

            for subfolder in added:
                item_path = os.path.join(vendor_path, subfolder)
                if os.path.exists(item_path):
                    shutil.rmtree(item_path)
                    print(f"{subfolder} removed\n")

            self.enableButtons()
            return

        existing = set(os.listdir(vendor_path))

        command = [python_exe, "-m", "pip", self.mode, "--target", vendor_path, package]

        try:
            result = subprocess.run(
                command,
                stdout=subprocess.PIPE,
                stderr=subprocess.PIPE,
                text=True,
                check=True
            )
            FreeCAD.Console.PrintMessage(f"{result.stdout}\n")


            new_folders = set(os.listdir(vendor_path)) - existing
            added = list(new_folders)

            self.saveAddedSubfolders(package, added)

        except subprocess.CalledProcessError as e:
            FreeCAD.Console.PrintError(f"Error: {e.stderr}\n")
        except Exception as e:
            FreeCAD.Console.PrintError(f"An unexpected error occurred: {e}\n")
        finally:
            self.enableButtons()

    def saveAddedSubfolders(self, package, added):
        """
        Here we track the folders that we added to this AdditionalPythonPackages
        folder and removed them during uninstallation.  The complication is that
        sometimes additional dependencies are also added, and when they are, we 
        should also track them and remove them.  Further complicating this is other
        packages might have also added the same dependencies.  These we reconcile
        in the loadAddedSubfolders() method below.
        """
        json_string = json.dumps(added)
        self.pg.SetString(f"{package}_Subfolders", json_string)

    def loadAddedSubfolders(self, package):
        """
        We get all of the packages that were added by all installations, this is
        in allPackages list.  The added list only includes the new subfolders that
        were added when this package was installed.  Anything in added that is not
        also in allPackages can be safely removed during uninstallation.
        """
        json_string = self.pg.GetString(f"{package}_Subfolders", "")
        added = json.loads(json_string) if json_string else []

        allPackages = []
        for key in self.pg.GetStrings():
            if key.endswith("_Subfolders") and key != f"{package}_Subfolders":
                other_json = self.pg.GetString(key, "")
                try:
                    otherPackages = json.loads(other_json)
                    allPackages.extend(otherPackages)
                except json.JSONDecodeError:
                    print(f"Error decoding subfolder list for {key}.")

        unique = [folder for folder in added if folder not in allPackages]
        return unique

    def isInstalled(self):
        """
        Called only by the enable buttons function.  We return a tuple (added, base)
        added = True means this was something we added to the additional packages 
        folder.  base = True means this is installed, but as a base package either
        through the packaging or user installed into the system environment, either
        way we don't try to install or uninstall anything we didn't add or that the
        addon manager didn't add as an additional dependency in our user addon folder
        """
        added = False
        base = False
        package = self.packageList.currentItem().text().replace(" (not in top 8000)", "")
        python_exe = self.pathEdit.text()
        vendor_path = amutils.get_pip_target_directory()
        if not os.path.exists(vendor_path):
            os.makedirs(vendor_path)
        fullpath = os.path.join(vendor_path,package)
        if os.path.exists(fullpath):
            added = True
        try:
            importlib.import_module(package)
            if not added: # we didn't add this
                base = True
        except ImportError:
            # added might or might not be false because this might not yet be
            # available to import if the additionalpythonpackages folder was
            # only just created this FreeCAD session, so we don't set it False
            # here, but instead rely on the above test
            base = False

        return (added, base)

    def fetchSummary(self):
        selectedItem = self.packageList.currentItem()
        if not selectedItem:
            return
        
        package = selectedItem.text().replace(" (not in top 8000)","")
        description, version = self.getInfo(package)
        
        QMessageBox.information(self.mw, "Package Summary", 
                                f"{package} (v{version}): {description}")
        

    def getInfo(self, package):
        """
        called by fetchSummary(), pops up a very brief description of the package
        in a message box.  User can use the OpenUrl button to get more details.
        We also use this to check if a custom entered package is available outside
        the top 8000 list.
        """
        
        url = f"https://pypi.org/pypi/{package}/json"
        try:
            response = requests.get(url)
            response.raise_for_status()
            data = response.json()
            description = data["info"].get("summary", "No description available.")
            version = data["info"].get("version", "Unknown version")
            return description, version
        except requests.RequestException:
            return "", "Unknown version"

    def search(self):
        filter = self.filterEdit.text()
        if not filter:
            return
        url = f"https://pypi.org/search/?q={filter}"
        try:
            webbrowser.open(url)
        except Exception as e:
            QMessageBox.critical(self.mw, "Error", 
                                f"Unable to open the browser. Error: {e}")

    def openUrl(self):
        package = self.packageList.currentItem().text().replace(" (not in top 8000)", "")
        url = f"https://pypi.org/pypi/{package}"
        try:
            webbrowser.open(url)
        except Exception as e:
            QMessageBox.critical(self.mw, "Error", 
                                f"Unable to open the browser. Error: {e}")

    def QIconFromXPMString(self, xpm_string):
        if "/*pixels*/" in xpm_string:
            xpm = xpm_string.replace("\"","").replace(',','').splitlines()[4:]
        else:
            xpm = xpm_string.replace("\"","").replace(',','').splitlines()[3:]
        for line in reversed(xpm):
            if line.startswith("/*") and line.endswith("*/"):
                xpm.pop(xpm.index(line))
        pixmap = QtGui.QPixmap(xpm)
        icon = QtGui.QIcon(pixmap)
        return icon


__icon__ = """
/* XPM */
static char *dummy[]={
"64 64 1050 2",
"Qt c None",
".g c #010101",
"br c #020202",
"a3 c #030202",
"h3 c #030303",
"ej c #030304",
".I c #040404",
"ao c #050504",
".# c #050505",
"m9 c #060605",
"dI c #060606",
".J c #070707",
"a2 c #090907",
"nl c #090909",
"mW c #0a0a0a",
"mV c #0b0a09",
"aL c #0b0b09",
"bh c #0b0b0b",
".s c #0b0c0c",
"nz c #0c0b0b",
"oj c #0c0c0c",
"d3 c #0c0c0d",
"n9 c #0d0c0c",
"dJ c #0d0d0d",
"#b c #0d0d0e",
".W c #0d0e0f",
"mL c #0e0e0f",
"eI c #0f0f10",
"#T c #0f1317",
"#x c #0f1417",
"mK c #100f0d",
"nN c #11100e",
"nk c #11100f",
"mA c #111111",
"bM c #111112",
"#a c #111417",
"c# c #121212",
"#U c #121415",
"mo c #131313",
".t c #131315",
"mU c #15130e",
"nA c #151516",
"aK c #16140d",
"#y c #16181b",
"mM c #171717",
"fj c #181819",
".X c #181a1c",
"gY c #191919",
".f c #191b1d",
"h# c #1a1a1b",
"#7 c #1a1e21",
"m8 c #1c1a15",
"an c #1d1b15",
"oi c #1d1d1e",
"ot c #1e1e1e",
"mX c #202020",
"ap c #212328",
"aM c #212428",
"nY c #22211c",
"bs c #222223",
"h4 c #232325",
"#8 c #23252a",
"#c c #23272e",
"cx c #242425",
".H c #242629",
"#z c #242b34",
"o. c #252526",
".h c #25272a",
"ek c #262627",
".K c #262a2f",
"cP c #282729",
"d# c #282829",
"iM c #29292a",
"du c #29292b",
"#d c #2c3845",
"#A c #2e3d4b",
"am c #2f2919",
"aJ c #2f2a1c",
"m7 c #2f2a1d",
"nO c #2f2f30",
".A c #2f6491",
"mY c #303030",
".B c #306491",
".Q c #306591",
"ib c #306592",
"nj c #312d23",
"ny c #312f2a",
".Y c #313942",
"#E c #316591",
"eS c #316592",
"ja c #323233",
"a5 c #326592",
".z c #326692",
"mT c #332e21",
"av c #336692",
"#p c #336792",
"dC c #336793",
"a6 c #346792",
".R c #346793",
"aw c #346893",
"n. c #353536",
".Z c #354658",
".l c #35658f",
".m c #356792",
".6 c #356893",
"ev c #356894",
"os c #363637",
".u c #363d46",
".P c #366792",
"#i c #366894",
"dW c #366994",
"bg c #373630",
"mz c #373737",
"#e c #374d61",
"#h c #376893",
"f. c #376994",
"ax c #376a95",
"#R c #3775a9",
".y c #386891",
".n c #386993",
"ac c #386994",
"ew c #386a95",
"ba c #3875a9",
"bA c #3876a9",
"dl c #3876aa",
".o c #39678f",
"lj c #3976a9",
"c2 c #3977aa",
"nm c #3a3a3c",
".x c #3a648a",
"ab c #3a6a94",
".C c #3a6b95",
"dB c #3a6b96",
"aR c #3a6c96",
"#Q c #3a77aa",
"#Z c #3b6c96",
"ad c #3b6c97",
"ea c #3b6d96",
"b# c #3b77aa",
"dK c #3c3b3d",
".a c #3c4149",
".0 c #3c5974",
"#Y c #3c6c94",
"iw c #3c6c96",
"aC c #3c78aa",
"ht c #3c78ab",
".k c #3d6589",
".O c #3d688f",
"#D c #3d6b94",
"fw c #3d6d95",
"b. c #3d78aa",
"k2 c #3d79ab",
"bi c #3e3e40",
".D c #3e6c93",
"fr c #3e6c95",
"eT c #3e6e98",
"ae c #3e6f9a",
"dX c #3e79ab",
"iq c #3f3f42",
".w c #3f607d",
"g# c #3f6c95",
"gm c #3f6e96",
".7 c #3f6e98",
"#F c #3f6f98",
"lE c #3f79ab",
"#5 c #3f7aab",
"cH c #3f7aac",
"nM c #403c30",
"gQ c #406e96",
"au c #406e97",
"g4 c #406f96",
"#o c #406f98",
"cI c #407aab",
"fY c #407aac",
"#V c #414b56",
"aq c #415c76",
".E c #416789",
"ds c #417bab",
"#v c #417bac",
".r c #42474d",
"#B c #425a73",
"a4 c #425c75",
"aN c #425c76",
".S c #426f97",
"ay c #42729b",
"dD c #427bac",
"b0 c #427dae",
"jb c #434343",
"mN c #434344",
"#g c #436b91",
"kk c #437199",
"ic c #43719a",
"hr c #43729a",
"hV c #437cac",
"iN c #444446",
"#f c #446482",
"fs c #446f96",
"hd c #447299",
"ia c #44729a",
"aV c #447dad",
"ok c #454547",
"bN c #454548",
".v c #45576a",
"fP c #456f97",
"fx c #45739b",
"iB c #457dad",
".i c #465161",
".T c #466e91",
"hf c #467dad",
"n8 c #474541",
"a7 c #477198",
"ft c #477298",
"e9 c #477299",
"k1 c #47739b",
"eh c #477ead",
"eZ c #477eae",
"cn c #4780b0",
"ar c #487197",
"fR c #487298",
"aa c #487398",
"e6 c #487399",
"dV c #48759c",
"dE c #487fae",
"nx c #49422f",
"#C c #496c8e",
"fQ c #497398",
"iU c #49749a",
"eu c #49759b",
"gn c #4979a2",
"c3 c #497fae",
"bV c #4980ad",
"nB c #4a4a4c",
"fS c #4a7399",
"j8 c #4a79a2",
"g5 c #4a7aa3",
"bz c #4a80ad",
"go c #4a80ae",
"eY c #4a80af",
".j c #4b647d",
".N c #4b6b87",
"eR c #4b769b",
"cp c #4b80ae",
"bm c #4b81ae",
"fu c #4c7398",
"fv c #4c7499",
"#M c #4c7ba2",
"gS c #4c80aa",
"#P c #4c82af",
"nn c #4d4d4f",
"d4 c #4d4d50",
"e7 c #4d7499",
"#j c #4d789e",
"#q c #4d799f",
"gC c #4d81ad",
"bD c #4d84b2",
"ca c #4e4e4f",
"ex c #4e789e",
"kl c #4e7ea6",
"hs c #4e7ea8",
"i2 c #4e83af",
"he c #4f7ea7",
"n# c #504f51",
".1 c #507495",
"aQ c #50789c",
".5 c #507a9f",
"#L c #507ca2",
"by c #507fa6",
"id c #5083b0",
"lF c #5083b1",
"ci c #5084b0",
"dk c #5086b4",
"#9 c #51667b",
"hU c #517a9f",
"ee c #517ea6",
"ch c #517fa6",
"dY c #5184b0",
"fX c #5184b1",
"aE c #5185b1",
"aO c #527699",
"e8 c #52779a",
"eA c #527ba0",
"af c #527ea3",
"#s c #5280a8",
"c8 c #5284b0",
"fB c #5285b0",
"aj c #5285b1",
"bF c #5285b2",
"nZ c #535355",
".L c #535c67",
".F c #536b81",
".p c #537390",
"#X c #537697",
"a. c #53789a",
"e# c #53799b",
"h9 c #537b9f",
"eU c #537ca0",
"#J c #537ca1",
"fA c #5383ad",
"fc c #5383ae",
"k3 c #5385b1",
"do c #5386b1",
"l2 c #5386b2",
"ni c #544e3b",
"ga c #54799c",
"iX c #547ca0",
"fT c #547ca1",
"f# c #547ea2",
"#r c #5480a6",
"fV c #5482aa",
"cJ c #5485b1",
"aB c #5487b1",
"#I c #557da1",
"fU c #5581a7",
"eF c #5586b2",
"cY c #5588b3",
"iz c #567da1",
"#K c #567ea1",
"bl c #5681a7",
"a9 c #5686af",
"c4 c #5687b2",
".G c #57626e",
"at c #577b9d",
"eB c #5783a8",
"#u c #5788b2",
"al c #58543e",
".b c #586a80",
"a# c #587b9c",
"ix c #587da1",
"#m c #5880a3",
".8 c #5881a6",
"fb c #5886ad",
"fW c #5887b0",
"bB c #588cb7",
"iA c #597fa2",
"iy c #597fa3",
"#l c #5980a3",
".9 c #5984a9",
"fz c #5985ab",
"cD c #598ab4",
"c1 c #598cb7",
"jf c #5a7fa2",
"#t c #5a88b0",
"iC c #5a89b3",
"i# c #5b80a4",
".2 c #5b81a2",
"gp c #5b89ae",
"#4 c #5b8ab3",
"eJ c #5c5c60",
"iW c #5c80a4",
"#G c #5c82a5",
"#N c #5c88ad",
"#. c #5c88ae",
"ig c #5c8bb3",
"ox c #5d5d61",
".U c #5d7386",
"#W c #5d738c",
".c c #5d7c99",
"as c #5d7c9c",
".3 c #5d82a4",
"#n c #5d83a4",
"fy c #5d85a8",
"jj c #5d8bb3",
"c5 c #5d8bb4",
"nX c #5e5742",
"l1 c #5e88ad",
"cL c #5e8bb3",
"fC c #5e8cb4",
"gc c #5e8db4",
"ir c #5f5f61",
".V c #5f6a75",
"ed c #5f83a5",
"aS c #5f86a8",
"lk c #5f8cb5",
"iD c #5f8db4",
"m6 c #60563a",
"i. c #6082a4",
"iY c #6084a5",
"l0 c #6085a5",
"#k c #6185a6",
"cK c #618db5",
"#O c #618eb5",
"oh c #626266",
".e c #626d78",
".M c #627181",
".q c #627384",
"#0 c #6288a9",
"h5 c #636366",
"iV c #6385a5",
"gb c #6388aa",
"fe c #638fb5",
"dp c #638fb6",
"dn c #638fb7",
"nP c #646467",
"aP c #64819f",
"gD c #648ea8",
"bt c #656569",
"fa c #658cae",
"c6 c #658fb6",
"i0 c #6590b6",
"bW c #6594bd",
"nL c #665b3c",
"mJ c #666152",
".4 c #6688a8",
"lG c #6692b9",
"iO c #676768",
"az c #678caf",
"iZ c #678cb0",
"ie c #6790b7",
"if c #6791b7",
"aU c #6792b7",
"dS c #6888a7",
"eb c #688aa9",
"ef c #6891b8",
"li c #698aa9",
"a8 c #698cab",
"ah c #6992b7",
"i1 c #6992b8",
"a1 c #6a6452",
"nC c #6a6a6c",
"dR c #6a87a4",
"#H c #6a8baa",
"dU c #6a8bab",
"#S c #6a8fb1",
"eC c #6a92b6",
"dr c #6a93b7",
"g. c #6b87a3",
"#w c #6b90b1",
"bk c #6c87a1",
"fq c #6c87a3",
"et c #6c89a6",
"ag c #6c91b3",
"#6 c #6c92b2",
"gB c #6c94b8",
"iT c #6d88a5",
"je c #6d89a5",
"jI c #6d89a6",
"kj c #6d8aa6",
"gR c #6d94b8",
"eX c #6d95b8",
".d c #6e879f",
"iv c #6e88a5",
"gl c #6e89a5",
"jv c #6e89a6",
"hg c #6f94b7",
"nw c #70623b",
"bC c #709dc1",
"c7 c #7197b9",
"hc c #728da9",
"gA c #728daa",
"ey c #7290ae",
"hI c #7296b6",
"dj c #729ec2",
"ou c #737376",
"ez c #7391af",
"dZ c #7397bb",
"ak c #7398b6",
"aA c #7398b9",
"ji c #7398bb",
"l3 c #739bbe",
"no c #747474",
"bO c #747477",
"mn c #747478",
"hH c #7499ba",
"lU c #757578",
"jg c #7591ad",
"#3 c #7599b9",
"eg c #7599ba",
"mZ c #767676",
"dq c #7699bb",
"eV c #7795b1",
"gT c #7797aa",
"jh c #7896b5",
"dt c #789cbc",
"d2 c #789dbd",
"e5 c #798fa8",
"## c #7995af",
"ff c #799dbd",
"dA c #7a95ae",
"lD c #7a97b1",
"aF c #7a9cb3",
"eG c #7a9fbf",
"c9 c #7b9ebd",
"aD c #7b9fbe",
"ei c #7b9fbf",
"fD c #7ba0bf",
"aT c #7c9db9",
"eD c #7c9dbe",
"dH c #7c9ebd",
"bn c #7c9fbf",
"co c #7ca0c0",
"dT c #7d96b2",
"fd c #7d9ebc",
"bE c #7d9fc0",
"b1 c #7da0c0",
"h8 c #7e94ac",
"bx c #7e98b1",
"eE c #7e9ebe",
"ll c #7ea2c2",
"cb c #7f7f80",
"el c #7f7f83",
"eQ c #7f97af",
"dm c #7fa1c0",
"e. c #8095ac",
"di c #809ab3",
"gq c #809b9f",
"bU c #819cb4",
"bj c #82858c",
"ec c #829ab4",
"k0 c #8398af",
"#1 c #839eb9",
"cG c #83aaca",
"gd c #84a3bf",
"mB c #858589",
"g6 c #85a2bd",
"bZ c #85aaca",
"nW c #877748",
"n0 c #88888a",
"or c #88888d",
"hT c #889bb1",
"nQ c #89898a",
"hu c #89a3b8",
"cq c #89a5bb",
"dG c #89a5c2",
"dF c #89a6c0",
"ai c #89a7c2",
"n7 c #8a8884",
"e1 c #8a898c",
"d0 c #8aa5c1",
"cM c #8aa6c0",
"mc c #8b8b90",
"j9 c #8ba2a8",
"lZ c #8ba2b9",
"jJ c #8ba3a8",
"kL c #8ba3a9",
"iE c #8ca4a8",
"lH c #8dacc9",
"is c #919192",
"eK c #919195",
"gE c #91a4a1",
"jc c #929293",
"#2 c #92a7bd",
"hW c #92aabf",
"nK c #938146",
"bu c #949496",
"ol c #949498",
"eW c #94a9bf",
"lh c #95abbe",
"mS c #968964",
"ih c #96aab3",
"my c #999797",
"g7 c #99acbb",
"d1 c #9aafc6",
"aI c #9b8c5f",
"nD c #9b9a9b",
"cy c #9b9a9e",
"og c #9b9ba0",
"es c #9ba9ba",
"mg c #9bafc4",
"cm c #9cbad4",
"dQ c #9dabbb",
"k4 c #9daeb4",
"cj c #9dbbd5",
"d5 c #9e9ea3",
"hJ c #9eacaa",
"nh c #9f9473",
"l4 c #9fb6cf",
"bf c #a09268",
"o# c #a1a0a3",
"bP c #a1a1a3",
"hh c #a1b0b6",
"eP c #a3acbb",
"gU c #a3aeaa",
"lC c #a3b3c4",
"mf c #a4b2c3",
"mO c #a5a5a8",
"na c #a7a7aa",
"lm c #a7bed3",
"n1 c #a9a9a8",
"cQ c #acabae",
"n6 c #acacae",
"da c #adacaf",
"mm c #adadb2",
"c0 c #adc6dc",
"lg c #afb7c3",
"dv c #b0afb3",
"iP c #b0b0b2",
"gr c #b1b9ad",
"lY c #b1bcc9",
"mh c #b1c1d4",
"em c #b2b2b5",
"js c #b3b3b6",
"dz c #b3b9c4",
"bX c #b3cade",
"nv c #b49d59",
"m5 c #b6a36a",
"nV c #b6a573",
"cc c #b6b6b6",
"nR c #b6b6b7",
"e2 c #b7b7b9",
"ow c #b7b7bd",
"lI c #b7c7d9",
"cE c #b9cfe1",
"gF c #baba9d",
"np c #bbbabb",
"mI c #bcaa78",
"hv c #bcba99",
"ov c #bcbcbf",
"m0 c #bdbdc0",
"gZ c #bdbdc2",
"g8 c #bebea8",
"fk c #bebec1",
"d9 c #bec4cc",
"hX c #bfbc98",
"om c #bfbec0",
"dL c #bfbec2",
"eL c #bfbfbf",
"gJ c #c0c0c2",
"fK c #c0c0c3",
"bv c #c1c1c0",
"gk c #c1c5cc",
"a0 c #c3ae6f",
"it c #c3c3c5",
"jd c #c3c3c6",
"mC c #c4c3c8",
"lB c #c4cad2",
"bq c #c5b582",
"oa c #c5c4c3",
"mp c #c6c6c6",
"me c #c6cbd1",
"of c #c7c7c9",
"l5 c #c7d1de",
"d6 c #c8c7cb",
"lT c #c8c8c9",
"ln c #cacdc7",
"dy c #cbccd1",
"f9 c #cbd0d5",
"ha c #cccccf",
"bw c #cccfd3",
"hK c #cdbf7a",
"hl c #cdcccf",
"jV c #cdccd0",
"nE c #cdcdcd",
"g0 c #cdcdd2",
"ng c #ceb568",
"hi c #cec9b0",
"hC c #cecdcf",
"h6 c #cecdd1",
"nb c #ceced0",
"j5 c #ceced2",
"kf c #ceced3",
"nJ c #cfb35b",
"kE c #cfcdd1",
"le c #cfced1",
"lx c #cfced2",
"bQ c #cfcfcf",
"mx c #d0be88",
"lz c #d0ced2",
"iu c #d0cfd2",
"jE c #d0cfd3",
"md c #d0d0d3",
"hS c #d0d2d7",
"h7 c #d0d3d7",
"gz c #d0d4da",
"ms c #d1d0d4",
"mE c #d1d0d5",
"jt c #d1d1d3",
"iS c #d1d1d4",
"nc c #d1d1d5",
"kK c #d1d5d8",
"be c #d2b65c",
"n2 c #d2d1d0",
"jW c #d2d1d6",
"dP c #d2d6d9",
"j7 c #d2d6da",
"ki c #d2d7da",
"mR c #d3ba71",
"m1 c #d3d2d4",
"n5 c #d3d3d5",
"gN c #d3d3d7",
"jH c #d3d6da",
"ju c #d3d7da",
"bY c #d3e1ec",
"jD c #d4d3d5",
"eq c #d4d3d6",
"cZ c #d4e1ed",
"hY c #d5c270",
"gV c #d5c78e",
"d7 c #d5d4d6",
"iQ c #d5d5d6",
"lf c #d5d5d8",
"hp c #d5d5d9",
"er c #d5d8da",
"ho c #d6d5d7",
"de c #d6d6d8",
"hG c #d6dbdf",
"j6 c #d7d7d9",
"lX c #d7dadb",
"dh c #d7dadd",
"cd c #d8d8d9",
"fp c #d8dce0",
"aH c #d9bc62",
"cz c #d9d9d9",
"g1 c #d9d9db",
"cS c #dad9db",
"kg c #dad9dd",
"jC c #dadad9",
"e4 c #dadee1",
"g3 c #dadee2",
"gP c #dadfe2",
"jT c #dbdad9",
"gx c #dbdadb",
"lw c #dbdbd9",
"gM c #dbdbdb",
"f8 c #dbdbdc",
"gy c #dbdbdd",
"lJ c #dbe1e8",
"on c #dcdbd8",
"lV c #dcdbda",
"en c #dcdcdb",
"kI c #dcdcde",
"m4 c #ddc67d",
"j4 c #dddcdc",
"fn c #dddcde",
"ko c #ded0a6",
"fL c #dedddd",
"cT c #dedede",
"hb c #dededf",
"ob c #dfdedc",
"dd c #dfdede",
"kq c #dfdee1",
"f7 c #dfdfde",
"nu c #e0c162",
"fi c #e0cd94",
"kH c #e0dfdf",
"mb c #e0e0e2",
"bT c #e0e2e4",
"cg c #e0e2e5",
"mi c #e0e5ea",
"bp c #e1c87e",
"k5 c #e1cc87",
"nU c #e1dbcd",
"df c #e1e1e1",
"gs c #e2cd86",
"jZ c #e2cd8c",
"ka c #e2ce94",
"j0 c #e2ce95",
"fM c #e2e1e1",
"kZ c #e2e1e2",
"eM c #e2e2e0",
"fl c #e2e2e1",
"fO c #e2e2e2",
"oq c #e2e2e3",
"eO c #e2e3e5",
"hw c #e3c865",
"ge c #e3cb75",
"jP c #e3cd8f",
"k# c #e3ce90",
"jz c #e3cf93",
"ip c #e3cf94",
"jA c #e3dac3",
"ly c #e3e2e1",
"mD c #e3e2e2",
"fm c #e3e3e2",
"cA c #e3e3e3",
"gO c #e3e3e4",
"lA c #e3e4e2",
"kM c #e4cc7b",
"hQ c #e4e3e1",
"hR c #e4e3e2",
"kG c #e4e3e3",
"dx c #e4e4e2",
"fN c #e4e4e3",
"kJ c #e4e4e4",
"ii c #e5cd77",
"kn c #e5ce88",
"iL c #e5dfd1",
"gj c #e5e4e2",
"kh c #e5e4e4",
"bR c #e5e5e4",
"aG c #e6c550",
"hL c #e6c960",
"mq c #e6e5e1",
"cR c #e6e5e3",
"hF c #e6e5e4",
"fo c #e6e5e5",
"d8 c #e6e7e4",
"dO c #e6e7e5",
"gG c #e7cc70",
"gw c #e7e6e4",
"jF c #e7e6e6",
"hn c #e7e7e4",
"kp c #e7e7e5",
"cU c #e7e7e6",
"lS c #e7e7e9",
"cN c #e8cd6d",
"fJ c #e8cf82",
"ky c #e8d28f",
"jq c #e8e3d8",
"hE c #e8e7e4",
"nd c #e8e7e5",
"ep c #e8e7e7",
"gL c #e8e8e5",
"cV c #e8e8e8",
"oe c #e8e8e9",
"cr c #e9ca5f",
"hZ c #e9cb64",
"mH c #e9cb6a",
"jK c #e9cc6c",
"ks c #e9cd6c",
"hy c #e9d28a",
"ke c #e9e8e5",
"gv c #e9e8e6",
"nF c #e9e8e7",
"cB c #e9e8e8",
"mt c #e9e8e9",
"kr c #e9e9e7",
"mF c #e9e9e9",
"m2 c #e9e9ea",
"iF c #eacd6c",
"jQ c #eace7d",
"kO c #eacf7a",
"lo c #ead17f",
"jR c #ead38a",
"jp c #eadcb4",
"nS c #eae8e7",
"hm c #eae9e6",
"gK c #eae9e7",
"hq c #eaeae9",
"oo c #eaeaec",
"fG c #ebcf79",
"ku c #ebcf7a",
"kv c #ebd07f",
"jm c #ebd286",
"k. c #ebd287",
"i8 c #ebe2c8",
"j3 c #ebeae7",
"dN c #ebebe7",
"lW c #ebebe8",
"dg c #ebebea",
"jO c #ecce71",
"fh c #ecce74",
"gH c #ecd385",
"mk c #ecd386",
"mr c #eceae7",
"nq c #eceae8",
"dc c #ecebe7",
"gi c #ecebe8",
"nI c #edc95a",
"gg c #edce72",
"bL c #edd076",
"hO c #edd384",
"lp c #edd484",
"kY c #edece8",
"hD c #edece9",
"ne c #edeced",
"eo c #ededeb",
"ce c #ededec",
"n4 c #ededef",
"l6 c #edf0f2",
"aZ c #eec954",
"d. c #eecc53",
"fZ c #eecc54",
"f2 c #eecf70",
"gh c #eecf72",
"i3 c #eed385",
"j2 c #eed483",
"ik c #eed484",
"iG c #eed485",
"ml c #eeebe3",
"i9 c #eeede8",
"dM c #eeede9",
"jU c #eeedea",
"cW c #eeeeed",
"nr c #eeeeee",
"nG c #eeeeef",
"aW c #efc431",
"b2 c #efc432",
"mw c #efc84d",
"nf c #efcb5d",
"eH c #efcc53",
"g9 c #efce60",
"kN c #efcf6a",
"km c #efcf6b",
"f1 c #efcf6c",
"f4 c #efd06e",
"fH c #efd171",
"iI c #efd172",
"gX c #efd47f",
"hA c #efd480",
"lc c #efd483",
"kX c #efd484",
"iJ c #efd992",
"mG c #efe7d3",
"mP c #efe8d3",
"lL c #efe9d3",
"kF c #efedea",
"dw c #efeee9",
"db c #efeeea",
"iR c #efefef",
"cF c #eff4f8",
"bd c #f0c12b",
"bH c #f0cd5d",
"io c #f0cf69",
"kP c #f0d068",
"k7 c #f0d069",
"e0 c #f0d06a",
"h2 c #f0d06b",
"nH c #f0d379",
"h1 c #f0d380",
"im c #f0d480",
"i7 c #f0dea8",
"iK c #f0e4bf",
"ns c #f0e7d3",
"lv c #f0f0f3",
"ck c #f0f5f9",
"fF c #f1cf65",
"k6 c #f1cf67",
"bI c #f1cf68",
"hj c #f1d064",
"jY c #f1d066",
"gf c #f1d067",
"c. c #f1d16b",
"cw c #f1d16c",
"jo c #f1d98d",
"e3 c #f1f1ef",
"ld c #f1f1f2",
"j# c #f1f1f3",
"bo c #f2c848",
"hx c #f2cd52",
"b4 c #f2ce5e",
"f3 c #f2cf63",
"kb c #f2d16a",
"i6 c #f2d478",
"jG c #f2f2f0",
"hM c #f3cf56",
"i5 c #f3d062",
"jn c #f3d26c",
"jB c #f3f2f0",
"bS c #f3f3f0",
"cC c #f3f3f1",
"ma c #f3f3f4",
"mQ c #f4cf5a",
"jy c #f4d05d",
"fI c #f4d05f",
"kQ c #f4d060",
"kw c #f4d061",
"m3 c #f4d370",
"lM c #f4d470",
"g2 c #f4f4f1",
"b3 c #f5cb49",
"b5 c #f5cf57",
"bJ c #f5cf58",
"iH c #f5d05b",
"kt c #f5d05d",
"kR c #f5d15e",
"f5 c #f5d161",
"eN c #f5f5f2",
"op c #f5f5f6",
"cs c #f6cf58",
"f0 c #f6d057",
"ct c #f6d260",
"cX c #f6f6f3",
"od c #f6f6f7",
"nt c #f7d053",
"b6 c #f7d157",
"kx c #f7d158",
"kz c #f7d25d",
"cf c #f7f7f4",
"lK c #f7f7f8",
"gW c #f8d150",
"jx c #f8d153",
"k8 c #f8d154",
"j1 c #f8d157",
"b7 c #f8d259",
"hP c #f8d25b",
"hz c #f8d25c",
"jX c #f8d25d",
"jk c #f8d35b",
"il c #f8d35c",
"lR c #f8f8f9",
"h0 c #f9d14e",
"fg c #f9d151",
"bK c #f9d153",
"gt c #f9d256",
"cu c #f9d257",
"bc c #fac723",
"mv c #facf46",
"i4 c #fad14f",
"jN c #fad151",
"jr c #fafaf9",
"n3 c #fafafb",
"jw c #fbd14c",
"kc c #fbd24d",
"bG c #fcc925",
"hk c #fcd147",
"jM c #fcd14a",
"in c #fcd24a",
"gI c #fcd24b",
"h. c #fcd24c",
"b8 c #fcd24d",
"jS c #fcf9f0",
"mj c #fcfcfc",
"m# c #fcfcfd",
"mu c #fdd145",
"fE c #fdd146",
"jl c #fdd147",
"jL c #fdd246",
"hN c #fdd247",
"kd c #fdd248",
"cO c #fdd249",
"hB c #fdd24c",
"oc c #fdfdfd",
"nT c #fdfdfe",
"aY c #fec91f",
"bb c #fec921",
"gu c #fed243",
"cv c #fed244",
"ij c #fed246",
"j. c #fefefe",
"aX c #ffc91d",
"b9 c #ffd242",
"f6 c #ffd243",
"m. c #ffd347",
"kD c #ffd348",
"l7 c #ffd449",
"kS c #ffd44b",
"kA c #ffd54f",
"lu c #ffd654",
"kW c #ffd757",
"lb c #ffd85d",
"l9 c #ffda65",
"kC c #ffdc6c",
"k9 c #ffdd71",
"l8 c #ffde74",
"lQ c #ffdf77",
"lN c #ffdf78",
"kB c #ffe07e",
"lq c #ffe48e",
"kT c #ffe9a2",
"lt c #ffefbf",
"kV c #fff2c6",
"lO c #fff2c8",
"lP c #fff3cc",
"kU c #fff5d7",
"l. c #fff7de",
"la c #fffae9",
"lr c #fffaec",
"l# c #fffefd",
"ls c #fffffe",
"cl c #ffffff",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt.#.a.b.c.d.e.f.gQtQtQtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt.g.h.i.j.k.l.m.n.o.p.q.r.sQtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt.t.u.v.w.x.y.z.A.A.A.B.C.D.E.F.G.H.IQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt.J.K.L.M.N.O.P.Q.A.A.A.A.A.A.A.A.R.S.T.U.V.WQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt.g.X.Y.Z.0.1.2.3.4.5.6.A.A.A.A.A.A.A.A.7.8.9#.###aQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt#b#c#d#e#f#g#h.A#i#j#k#l#m#n#o.Q.A.A#p#q#r#s#t#u#v#w#xQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt.##y#z#A#B#C#D#E.A.A.A.A.A.A#F#G#H#I#J#K#L#M#N#O#P#Q#R#R#S#TQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt#U#V#W#X#Y.A.A.A.A.A.A.A.A.A.A.A#Z#0#1#2#3#4#5#R#R#R#R#R#6#7.gQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt#8#9a.a#aaabac.z.A.A.A.A.A.zadaeafagahaiaj#R#R#R#R#R#R#RakalamanaoQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtapaq.A#parasatau.mav.QawaxayazaAaBaC#RaDaE#R#R#R#R#R#R#RaFaGaHaIaJaKaL.gQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtaMaN.A.A.A.AabaOaPaQaRaSaTaUaV#R#R#R#RaDaE#R#R#R#R#R#R#RaFaWaXaYaZa0a1a2a3.g",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtaMa4.A.A.A.A.Ba5a6a7a8a9b.b#ba#R#R#R#RaDaE#R#R#R#R#R#R#RaFaWaXaXbbbcbdbebfbg",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtbhbibjbk.A.A.A.A.A.A.A#hblbm#R#R#R#R#R#R#RbnaE#R#R#R#R#R#R#RaFaWaXaXaXaXaYbobpbq",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtbrbsbtbubvbwbx.A.A.A.A.A.A.A#pbybzbAbBbCbD#R#R#RbEbF#R#R#R#R#R#R#RaFaWaXaXbGbHbIbJbKbL",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtbMbNbObPbQbRbSbTbU.A.A.A.A.A.A.A#pbybVbWbXbYbZb0#R#Rb1bF#R#R#R#R#R#R#RaFb2b3b4b5b6b7b8b9c.",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtc#cacbcccdcecfcfcfcgbU.A.A.A.A.A.A.A#pchcicjckclcmcn#R#RcobF#R#R#R#R#Rbacpcqcrcsctcucvb9b9b9cw",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtcxcyczcAcBcCcfcfcfcgbU.A.A.A.A.A.A.A#pchcDcEclcFcGcH#R#RcobF#R#R#RcIcJcKcLcMcNcOb9b9b9b9b9b9cw",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtcPcQcRcScTcUcVcWcXbTbU.A.A.A.A.A.A.A#pbycYcjcZc0c1#R#R#Rb1bFc2c3c4c5c6c7c8c9d.b9b9b9b9b9b9b9c.",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtd#dadbdbdcdddedfdgdhdi.B.A.A.A.A.A.A#pbybzc1djdkdl#R#R#Rdmdndodpdqdrds#R#Rdtd.b9b9b9b9b9b9b9cw",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtdudvdbdbdbdbdwdxdydzdA#odBdC.A.A.A.A#pchbV#R#R#R#Rb#dDdEdFdGdHbmbA#R#R#R#Rdtd.b9b9b9b9b9b9b9cw",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQt.gdIdJdKdLdbdbdMdNdOdPdQdRdSdTdUdVdWav.B.A#pchbz#Rbab#dXdYdZd0d1do#R#R#R#R#R#R#Rd2d.b9b9b9b9b9b9b9cw",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtd3d4d5d6d7dbdbd8d9e.e#.6.B.B.QeaebeceddB.AdCeebV#RdDefd0egehbAeiaE#R#R#R#R#R#R#Rd2d.b9b9b9b9b9b9b9c.",
"QtQtQtQtQtQtQtQtQtQtQtejekelemeneoepeqereseteuacav.A.A.A.A.QevewexeyezeAeBeCeDeEeF#vdXbA#ReGaE#R#R#R#R#R#R#Rd2eHb9b9b9b9b9b9b9cw",
"QtQtQtQtQtQtQtQtQteIeJeKeLeMeoeNcfeOePeQeRaceS.A.A.A.A.A.A.A.A.A.6eTeUeVeWeXeYeZb.#R#R#R#ReGaE#R#R#R#R#R#R#Rd2d.b9b9b9b9b9b9b9e0",
"QtQtQtQtQtQtQtQtbMe1e2cTe3cfcfcfcfe4e5e6e7e8e9eS.A.A.A.A.A.A.Af.f#fafbfcfdfebA#R#R#R#R#R#ReiaE#R#R#R#R#R#R#RffeHb9b9b9b9b9fgfhfi",
"QtQtQtQtQtQtQtQtfjfkflfmfnfocXcfcffpfqavfrfsftfufvfw.A.A#EfxfyfzfAfBeh#QeXfC#R#R#R#R#R#R#RfDaE#R#R#R#R#R#R#Rd2d.b9b9fEfFfGfHfIfJ",
"QtQtQtQtQtQtQtQtfjfKdMcRfLfMfNfOeofpfq.A.A.B#hfPfQfRfSfTfUfVfWfXfY#R#R#ReXfC#R#R#R#R#R#R#RfDaE#R#R#R#R#R#R#Rd2fZf0f1f2f3f4f5f6cw",
"QtQtQtQtQtQtQtQtfjfKdbdbdbdcf7f8cAf9g..A.A.A.A.A.zg#gagb#4dEba#R#R#R#R#ReXgc#R#R#R#R#R#R#RfDaE#R#R#R#R#R#RdDgdgegfggghcOb9b9b9c.",
"QtQtQtQtQtQtQtQtfjfKdbdbdbdbgigjddgkgl.A.A.A.A.A.A.Agmgn#R#R#R#R#R#R#R#ReXfC#R#R#R#R#R#R#RfDaE#R#R#Rb#gogpgqgrgsgtgub9b9b9b9b9cw",
"QtQtQtQtQtQtQtQtfjfKdbdMgvgwcRgxgygzgA.A.A.A.A.A.A.Agmgn#R#R#R#R#R#R#R#RgBfC#R#R#R#R#R#R#ReiaEba#vgCgDgEgFgGgHgIb9b9b9b9b9b9b9c.",
"QtQtQtQtQtQtQtQtfjgJgKgLgMgNgOcXcfgPgA.A.A.A.A.A.A.AgQgn#R#R#R#R#R#R#R#RgRfC#R#R#R#R#R#R#Rbn#4gSgTgUgVgWb9b9gXgIb9b9b9b9b9b9b9cw",
"QtQtQtQtQtQtQtQtgYgZg0g1g2cfcfcfcfg3gA.A.A.A.A.A.A.Ag4g5#R#R#R#R#R#R#R#RgRfC#R#R#R#Rba#Qb.g6g7g8g9cvb9b9b9b9gXh.b9b9b9b9b9b9b9cw",
"QtQtQtQtQtQtQtQth#d6hahbcfcfcfcfcfg3hc.A.A.A.A.A.A.Ahdhe#R#R#R#R#R#R#R#RgRfC#R#R#R#Rhfhghhhihjhkgub9b9b9b9b9gXh.b9b9b9b9b9b9b9cw",
"QtQtQtQtQtQtQtQth#hlhmhnhohphqcfcfgPhc.A.A.A.A.A.A.Ahrhs#R#R#R#R#R#R#R#RgRfC#Rhtc5huhvhwhxhyhzb9b9b9b9b9b9b9hAhBb9b9b9b9b9b9b9cw",
"QtQtQtQtQtQtQtQth#hCdbhDhEhFfOdehbhGhc.A.A.A.A.A.A.Ahrhs#R#R#R#R#R#R#R#ReXhHhIhJhKhLhMhNb9hOhPb9b9b9b9b9b9b9hAgIb9b9b9b9b9b9b9c.",
"QtQtQtQtQtQtQtQth#hCdbdbdbdMgKhQhRhShThUaw.A.A.A.A.Ahrhs#R#R#R#R#R#RhVfChWhXhYhZh0f6b9b9b9hOhPb9b9b9b9b9b9b9h1h.b9b9b9b9b9b9b9h2",
"QtQtQtQtQtQth3h4h5h6dbdbdbdbdbdbhDh7h8h9i.i#iaib.A.Aichs#R#R#RaCidieifigihiiijb9b9b9b9b9b9ikilb9b9b9b9b9b9b9imhBb9b9b9b9b9inioip",
"QtQtQt.gdJiqirisitiudbdbdbdbdbdbdbdPiviwixiyiziAex.ChrhsbAiBiCdpiDcK#4dDiEiFb9b9b9b9b9b9b9iGhzb9b9b9b9b9b9b9imhBb9b9cviHiIiJiKiL",
"QtbriMiNiOiPiQiRe3iSdbdbdbdbdbdbdbdPiT.A.AaviUiViWiXiYiZiCc5i0i1i2#R#R#RiEiFb9b9b9b9b9b9b9i3hzb9b9b9b9b9b9b9imh.i4i5i6i7i8i9j.j#",
"jajbjcjdcUcfcfcfe3iSdbdbdbdbdbdbdbdPje.A.A.A.A.A.Cjfjgjhjijjb##R#R#R#R#RiEiFb9b9b9b9b9b9b9i3jkb9b9b9b9b9b9jljmjnjojpjqjrclclclj#",
"jsjteog2cfcfcfcfe3iSdbdbdbdbdbdbdbjujv.A.A.A.A.A.A.Agmgn#R#R#R#R#R#R#R#RiEiFb9b9b9b9b9b9b9i3ilb9b9b9gujwjxjyjzjAjBj.clclclclclj#",
"jCjDjEjFjGcCcXcfe3iSdbdbdbdbdbdbdbjHjI.A.A.A.A.A.A.Agmgn#R#R#R#R#R#R#R#RjJjKb9b9b9b9b9b9b9i3ilb9jLjMjNjOjPjQjRjSclclclclclclclj#",
"jTdbjUgjjVjWe3cXe3iSdbdbdbdbdbdbdbjHjI.A.A.A.A.A.A.Agmgn#R#R#R#R#R#R#R#RjJjKb9b9b9b9b9b9b9i3jXcvjYjZj0j1f6b9j2jSclclclclclclclj#",
"jTdbdbdMgij3j4j5j6iQdbdbdbdbdbdbdbj7jv.A.A.A.A.A.A.AgQj8#R#R#R#R#R#R#R#Rj9jKb9b9b9b9b9b9b9k.k#kakbkckdcvb9b9j2jSclclclclclclclj#",
"jTdbdbdbdbdbj3kecRkfkgkhdMdbdbdbdbkikj.A.A.A.A.A.A.Akkkl#R#R#R#R#R#R#R#Rj9jKb9b9b9b9inkmknkokbfgcOf6b9b9b9b9j2jSclclclclclclclj#",
"jTdbdbdbdbdbdbdbhmh6kpfNg1kqkrdbdbkikj.A.A.A.A.A.A.Ahrhs#R#R#R#R#R#R#R#Rj9ksb9cvktkukvkwkxkykzb9b9b9kAkBkCkDj2jSclclclclclclclj#",
"jTdbdbdbdbdbdbdbhmkEkFj3kGkhkHkIkJkKjI.A.A.A.A.A.A.Ahrhs#R#R#R#R#R#R#R#RkLkMkNkOkPkQkRgIb9hOhPb9b9kSkTkUkVkWkXjSclclclclclclclj#",
"jTdbdbdbdbdbdbdbhmkEdbdbdbkYcRkZkGbwk0k1eS.A.A.A.A.Ahrhs#R#R#R#R#R#Rk2k3k4k5k6k7k8gub9b9b9hOhPb9b9k9l.l#lalblcjSclclclclclclclld",
"jTdbdbdbdbdbdbdbhmledbdbdbdbdbdbj3lflglhlihUiw.A.A.Ahrhs#R#R#RljeZlklllmlnloinb9b9b9b9b9b9lpilb9b9lqlrlsltlulcjSclclclclclj.lvj6",
"lwdbdbdbdbdbdbdbhmlxdbdbdbdbdbdbdblylzlAlBlClD#Kia.6hrhs#RlElFlGlHlIlJlKlLlMb9b9b9b9b9b9b9iGjkb9b9lNlOlPlQkDkXjSclclcllRlSlTeKlU",
"lVdbdbdbdbdbdbdbhmlxdbdbdbdbdbdbdblyiudbdblWlXlYlZl0#ll1l2l3l4l5l6j.clcllLlMb9b9b9b9b9b9b9i3hzb9b9l7l8l9m.b9lcjSm#mambjsmcd4.#Qt",
"jTdbdbdbdbdbdbdbhmlxdbdbdbdbdbdbdblymddbdbdbdbdbcRmemfmgmhmimjclclclclcllLlMb9b9b9b9b9b9b9i3hzb9b9b9b9b9b9cvmkmliQmmmnmo.gQtQtQt",
"mpmqmrdMdbdbdbdbhmlxdbdbdbdbdbdbdblymsdbdbdbdbdbdbdbgjmtclclclclclclclcllLlMb9b9b9b9b9b9b9i3hzb9b9b9b9mumvmwmxmymz.IQtQtQtQtQtQt",
"mAmBmCkhdbdbdbdbhmlxdbdbdbdbdbdbdbmDmEdbdbdbdbdbdbdbcRmFclclclclclclclclmGlMb9b9b9b9b9b9b9i3ilb9b9b9cvmHmImJmKh3.gQtQtQtQtQtQtQt",
"brmLmMmNmOd7gidbhmlxdbdbdbdbdbdbdbmDmEdbdbdbdbdbdbdbcRmFclclclclclclclclmPlMb9b9b9b9b9b9b9i3ilb9mQmRmSmTmUmV.gQtQtQtQtQtQtQtQtQt",
"QtQtQtmWmXmYmZm0f8m1dbdbdbdbdbdbdbfmmsdbdbdbdbdbdbdbcRm2clclclclclclclclmPm3b9b9b9b9b9b9b9gHm4m5m6m7m8m9QtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtbrh#n.n#nanbkpdbdbdbdbdbmDncdbdbdbdbdbdbdbndneclclclclclclclclmPlMb9b9b9b9ijnfngnhninjnkbrQtQtQtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtnlnmnnnonpgxnqdbdbfmncdbdbdbdbdbdbdbgKnrclclclclclclclclnsm3b9b9ntnunvnwnxnynzQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtbrnAnBnCnDnEhRlyncdbdbdbdbdbdbdbnFnGclclclclclclclclnsnHnInJnKnLnMnNQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQt.JnOnPnQnRh6gKdbdbdbdbdbdbnSnGclclclclclclnTlKnUnVnWnXnYh3QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtc#nZn0n1n2hQhDdbdbdbkrnGclclclcln3n4n5n6n7n8n9QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQth3o.bOo#oaobhmdMgKnGclocodoeofogohoiQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtojokolomonlyooopoqgZorosbrQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt",
"QtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtotouovgNowoxmLQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQtQt"};
"""

if __name__ == "__main__":
    dlg = Packages()
    if not FreeCADGui.Control.activeDialog():
        FreeCADGui.Control.showDialog(dlg)
    else:
        FreeCAD.Console.PrintError("Already there is an active task dialog.\n")

Link

The forum discussion Macro Packages