From 7b590eace0fa22127942bb7afe3a1453e98a84ee Mon Sep 17 00:00:00 2001 From: Chris Hennes Date: Sun, 12 Feb 2023 13:13:27 -0600 Subject: [PATCH] Addon Manager: Refactor interface to FreeCAD --- .../app/test_freecad_interface.py | 306 ++++++++++++++++++ src/Mod/AddonManager/CMakeLists.txt | 2 + src/Mod/AddonManager/TestAddonManagerApp.py | 43 ++- .../addonmanager_freecad_interface.py | 262 +++++++++++++++ .../addonmanager_preferences_defaults.json | 43 +++ 5 files changed, 646 insertions(+), 10 deletions(-) create mode 100644 src/Mod/AddonManager/AddonManagerTest/app/test_freecad_interface.py create mode 100644 src/Mod/AddonManager/addonmanager_freecad_interface.py create mode 100644 src/Mod/AddonManager/addonmanager_preferences_defaults.json diff --git a/src/Mod/AddonManager/AddonManagerTest/app/test_freecad_interface.py b/src/Mod/AddonManager/AddonManagerTest/app/test_freecad_interface.py new file mode 100644 index 0000000000..3630314d41 --- /dev/null +++ b/src/Mod/AddonManager/AddonManagerTest/app/test_freecad_interface.py @@ -0,0 +1,306 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2023 FreeCAD Project Association * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, but * +# * WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# *************************************************************************** + +"""Tests for the Addon Manager's FreeCAD interface classes.""" + +import json +import os +import sys +import tempfile +import unittest +from unittest.mock import patch, MagicMock + +# pylint: disable=protected-access,import-outside-toplevel + + +class TestConsole(unittest.TestCase): + """Tests for the Console""" + + def setUp(self) -> None: + self.saved_freecad = None + if "FreeCAD" in sys.modules: + self.saved_freecad = sys.modules["FreeCAD"] + sys.modules.pop("FreeCAD") + if "addonmanager_freecad_interface" in sys.modules: + sys.modules.pop("addonmanager_freecad_interface") + sys.path.append("../../") + + def tearDown(self) -> None: + if "FreeCAD" in sys.modules: + sys.modules.pop("FreeCAD") + if self.saved_freecad is not None: + sys.modules["FreeCAD"] = self.saved_freecad + + def test_log_with_freecad(self): + """Ensure that if FreeCAD exists, the appropriate function is called""" + sys.modules["FreeCAD"] = unittest.mock.MagicMock() + import addonmanager_freecad_interface as fc + + fc.Console.PrintLog("Test output") + self.assertTrue(isinstance(fc.Console, unittest.mock.MagicMock)) + self.assertTrue(fc.Console.PrintLog.called) + + def test_log_no_freecad(self): + """Test that if the FreeCAD import fails, the logger is set up correctly, and + implements PrintLog""" + sys.modules["FreeCAD"] = None + with patch( + "addonmanager_freecad_interface.logging", new=MagicMock() + ) as mock_logging: + import addonmanager_freecad_interface as fc + + fc.Console.PrintLog("Test output") + self.assertTrue(isinstance(fc.Console, fc.ConsoleReplacement)) + self.assertTrue(mock_logging.log.called) + + def test_message_no_freecad(self): + """Test that if the FreeCAD import fails the logger implements PrintMessage""" + sys.modules["FreeCAD"] = None + with patch( + "addonmanager_freecad_interface.logging", new=MagicMock() + ) as mock_logging: + import addonmanager_freecad_interface as fc + + fc.Console.PrintMessage("Test output") + self.assertTrue(mock_logging.info.called) + + def test_warning_no_freecad(self): + """Test that if the FreeCAD import fails the logger implements PrintWarning""" + sys.modules["FreeCAD"] = None + with patch( + "addonmanager_freecad_interface.logging", new=MagicMock() + ) as mock_logging: + import addonmanager_freecad_interface as fc + + fc.Console.PrintWarning("Test output") + self.assertTrue(mock_logging.warning.called) + + def test_error_no_freecad(self): + """Test that if the FreeCAD import fails the logger implements PrintError""" + sys.modules["FreeCAD"] = None + with patch( + "addonmanager_freecad_interface.logging", new=MagicMock() + ) as mock_logging: + import addonmanager_freecad_interface as fc + + fc.Console.PrintError("Test output") + self.assertTrue(mock_logging.error.called) + + +class TestParameters(unittest.TestCase): + """Tests for the Parameters""" + + def setUp(self) -> None: + self.saved_freecad = None + if "FreeCAD" in sys.modules: + self.saved_freecad = sys.modules["FreeCAD"] + sys.modules.pop("FreeCAD") + if "addonmanager_freecad_interface" in sys.modules: + sys.modules.pop("addonmanager_freecad_interface") + sys.path.append("../../") + + def tearDown(self) -> None: + if "FreeCAD" in sys.modules: + sys.modules.pop("FreeCAD") + if self.saved_freecad is not None: + sys.modules["FreeCAD"] = self.saved_freecad + + def test_param_get_with_freecad(self): + """Ensure that if FreeCAD exists, the built-in FreeCAD function is called""" + sys.modules["FreeCAD"] = unittest.mock.MagicMock() + import addonmanager_freecad_interface as fc + + prefs = fc.ParamGet("some/fake/path") + self.assertTrue(isinstance(prefs, unittest.mock.MagicMock)) + + def test_param_get_no_freecad(self): + """Test that if the FreeCAD import fails, param_get returns a ParametersReplacement""" + sys.modules["FreeCAD"] = None + import addonmanager_freecad_interface as fc + + prefs = fc.ParamGet("some/fake/path") + self.assertTrue(isinstance(prefs, fc.ParametersReplacement)) + + def test_replacement_getters_and_setters(self): + """Test that ParameterReplacement's getters, setters, and deleters work""" + sys.modules["FreeCAD"] = None + import addonmanager_freecad_interface as fc + + prf = fc.ParamGet("some/fake/path") + gs_types = [ + ("Bool", prf.GetBool, prf.SetBool, prf.RemBool, True, False), + ("Int", prf.GetInt, prf.SetInt, prf.RemInt, 42, 0), + ("Float", prf.GetFloat, prf.SetFloat, prf.RemFloat, 1.2, 3.4), + ("String", prf.GetString, prf.SetString, prf.RemString, "test", "other"), + ] + for gs_type in gs_types: + with self.subTest(msg=f"Testing {gs_type[0]}", gs_type=gs_type): + getter = gs_type[1] + setter = gs_type[2] + deleter = gs_type[3] + value_1 = gs_type[4] + value_2 = gs_type[5] + self.assertEqual(getter("test", value_1), value_1) + self.assertEqual(getter("test", value_2), value_2) + self.assertNotIn("test", prf.parameters) + setter("test", value_1) + self.assertIn("test", prf.parameters) + self.assertEqual(getter("test", value_2), value_1) + deleter("test") + self.assertNotIn("test", prf.parameters) + + +class TestDataPaths(unittest.TestCase): + """Tests for the data paths""" + + def setUp(self) -> None: + self.saved_freecad = None + if "FreeCAD" in sys.modules: + self.saved_freecad = sys.modules["FreeCAD"] + sys.modules.pop("FreeCAD") + if "addonmanager_freecad_interface" in sys.modules: + sys.modules.pop("addonmanager_freecad_interface") + sys.path.append("../../") + + def tearDown(self) -> None: + if "FreeCAD" in sys.modules: + sys.modules.pop("FreeCAD") + if self.saved_freecad is not None: + sys.modules["FreeCAD"] = self.saved_freecad + + def test_init_with_freecad(self): + """Ensure that if FreeCAD exists, the appropriate functions are called""" + sys.modules["FreeCAD"] = unittest.mock.MagicMock() + import addonmanager_freecad_interface as fc + + data_paths = fc.DataPaths() + self.assertTrue(sys.modules["FreeCAD"].getUserAppDataDir.called) + self.assertTrue(sys.modules["FreeCAD"].getUserMacroDir.called) + self.assertTrue(sys.modules["FreeCAD"].getUserCachePath.called) + self.assertIsNotNone(data_paths.mod_dir) + self.assertIsNotNone(data_paths.cache_dir) + self.assertIsNotNone(data_paths.macro_dir) + + def test_init_without_freecad(self): + """Ensure that if FreeCAD does not exist, the appropriate functions are called""" + sys.modules["FreeCAD"] = None + import addonmanager_freecad_interface as fc + + data_paths = fc.DataPaths() + self.assertIsNotNone(data_paths.mod_dir) + self.assertIsNotNone(data_paths.cache_dir) + self.assertIsNotNone(data_paths.macro_dir) + self.assertNotEqual(data_paths.mod_dir, data_paths.cache_dir) + self.assertNotEqual(data_paths.mod_dir, data_paths.macro_dir) + self.assertNotEqual(data_paths.cache_dir, data_paths.macro_dir) + + +class TestPreferences(unittest.TestCase): + """Tests for the preferences wrapper""" + + def setUp(self) -> None: + sys.path.append("../../") + import addonmanager_freecad_interface as fc + + self.fc = fc + + def tearDown(self) -> None: + pass + + def test_load_preferences_defaults(self): + """Preferences are loaded from a given file""" + defaults = self.given_defaults() + with tempfile.TemporaryDirectory() as temp_dir: + json_file = os.path.join(temp_dir, "defaults.json") + with open(json_file, "w", encoding="utf-8") as f: + f.write(json.dumps(defaults)) + self.fc.Preferences._load_preferences_defaults(json_file) + self.assertDictEqual(defaults, self.fc.Preferences.preferences_defaults) + + def test_in_memory_defaults(self): + """Preferences are loaded from memory""" + defaults = self.given_defaults() + prefs = self.fc.Preferences(defaults) + self.assertDictEqual(defaults, prefs.preferences_defaults) + + def test_get_good(self): + """Get returns results when matching an existing preference""" + defaults = self.given_defaults() + prefs = self.fc.Preferences(defaults) + self.assertEqual(prefs.get("TestBool"), defaults["TestBool"]) + self.assertEqual(prefs.get("TestInt"), defaults["TestInt"]) + self.assertEqual(prefs.get("TestFloat"), defaults["TestFloat"]) + self.assertEqual(prefs.get("TestString"), defaults["TestString"]) + + def test_get_nonexistent(self): + """Get raises an exception when asked for a non-existent preference""" + defaults = self.given_defaults() + prefs = self.fc.Preferences(defaults) + with self.assertRaises(RuntimeError): + prefs.get("No_such_thing") + + def test_get_bad_type(self): + """Get raises an exception when getting an unsupported type""" + defaults = self.given_defaults() + defaults["TestArray"] = ["This", "Is", "Legal", "JSON"] + prefs = self.fc.Preferences(defaults) + with self.assertRaises(RuntimeError): + prefs.get("TestArray") + + def test_set_good(self): + """Set works when matching an existing preference""" + defaults = self.given_defaults() + prefs = self.fc.Preferences(defaults) + prefs.set("TestBool", False) + self.assertEqual(prefs.get("TestBool"), False) + prefs.set("TestInt", 4321) + self.assertEqual(prefs.get("TestInt"), 4321) + prefs.set("TestFloat", 3.14159) + self.assertEqual(prefs.get("TestFloat"), 3.14159) + prefs.set("TestString", "Forty two") + self.assertEqual(prefs.get("TestString"), "Forty two") + + def test_set_nonexistent(self): + """Set raises an exception when asked for a non-existent preference""" + defaults = self.given_defaults() + prefs = self.fc.Preferences(defaults) + with self.assertRaises(RuntimeError): + prefs.get("No_such_thing") + + def test_set_bad_type(self): + """Set raises an exception when setting an unsupported type""" + defaults = self.given_defaults() + defaults["TestArray"] = ["This", "Is", "Legal", "JSON"] + prefs = self.fc.Preferences(defaults) + with self.assertRaises(RuntimeError): + prefs.get("TestArray") + + @staticmethod + def given_defaults(): + """Get a dictionary of fake defaults for testing""" + defaults = { + "TestBool": True, + "TestInt": 42, + "TestFloat": 1.2, + "TestString": "Test", + } + return defaults diff --git a/src/Mod/AddonManager/CMakeLists.txt b/src/Mod/AddonManager/CMakeLists.txt index dad3d3d455..d4c2b856e1 100644 --- a/src/Mod/AddonManager/CMakeLists.txt +++ b/src/Mod/AddonManager/CMakeLists.txt @@ -19,6 +19,7 @@ SET(AddonManager_SRCS addonmanager_devmode_predictor.py addonmanager_devmode_validators.py addonmanager_firstrun.py + addonmanager_freecad_interface.py addonmanager_git.py addonmanager_installer.py addonmanager_installer_gui.py @@ -83,6 +84,7 @@ SET(AddonManagerTestsApp_SRCS AddonManagerTest/app/mocks.py AddonManagerTest/app/test_addon.py AddonManagerTest/app/test_dependency_installer.py + AddonManagerTest/app/test_freecad_interface.py AddonManagerTest/app/test_git.py AddonManagerTest/app/test_installer.py AddonManagerTest/app/test_macro.py diff --git a/src/Mod/AddonManager/TestAddonManagerApp.py b/src/Mod/AddonManager/TestAddonManagerApp.py index 49595584f5..cd460b15e4 100644 --- a/src/Mod/AddonManager/TestAddonManagerApp.py +++ b/src/Mod/AddonManager/TestAddonManagerApp.py @@ -44,14 +44,37 @@ from AddonManagerTest.app.test_uninstaller import ( TestAddonUninstaller as AddonManagerTestAddonUninstaller, TestMacroUninstaller as AddonManagerTestMacroUninstaller, ) +from AddonManagerTest.app.test_freecad_interface import ( + TestConsole as AddonManagerTestConsole, + TestParameters as AddonManagerTestParameters, + TestDataPaths as AddonManagerTestDataPaths, +) -# dummy usage to get flake8 and lgtm quiet -False if AddonManagerTestUtilities.__name__ else True -False if AddonManagerTestAddon.__name__ else True -False if AddonManagerTestMacro.__name__ else True -False if AddonManagerTestGit.__name__ else True -False if AddonManagerTestAddonInstaller.__name__ else True -False if AddonManagerTestMacroInstaller.__name__ else True -False if AddonManagerTestDependencyInstaller.__name__ else True -False if AddonManagerTestAddonUninstaller.__name__ else True -False if AddonManagerTestMacroUninstaller.__name__ else True + +class TestListTerminator: + pass + + +# Basic usage mostly to get static analyzers to stop complaining about unused imports +try: + import FreeCAD +except ImportError: + FreeCAD = None +loaded_gui_tests = [ + AddonManagerTestUtilities, + AddonManagerTestAddon, + AddonManagerTestMacro, + AddonManagerTestGit, + AddonManagerTestAddonInstaller, + AddonManagerTestMacroInstaller, + AddonManagerTestDependencyInstaller, + AddonManagerTestAddonUninstaller, + AddonManagerTestMacroUninstaller, + AddonManagerTestConsole, + AddonManagerTestParameters, + AddonManagerTestDataPaths, + TestListTerminator # Needed to prevent the last test from running twice +] +if FreeCAD: + for test in loaded_gui_tests: + FreeCAD.Console.PrintLog(f"Loaded tests from {test.__name__}\n") diff --git a/src/Mod/AddonManager/addonmanager_freecad_interface.py b/src/Mod/AddonManager/addonmanager_freecad_interface.py new file mode 100644 index 0000000000..f0624078bd --- /dev/null +++ b/src/Mod/AddonManager/addonmanager_freecad_interface.py @@ -0,0 +1,262 @@ +# *************************************************************************** +# * * +# * Copyright (c) 2023 FreeCAD Project Association * +# * * +# * This file is part of FreeCAD. * +# * * +# * FreeCAD is free software: you can redistribute it and/or modify it * +# * under the terms of the GNU Lesser General Public License as * +# * published by the Free Software Foundation, either version 2.1 of the * +# * License, or (at your option) any later version. * +# * * +# * FreeCAD is distributed in the hope that it will be useful, but * +# * WITHOUT ANY WARRANTY; without even the implied warranty of * +# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * +# * Lesser General Public License for more details. * +# * * +# * You should have received a copy of the GNU Lesser General Public * +# * License along with FreeCAD. If not, see * +# * . * +# * * +# *************************************************************************** + +"""Classes to encapsulate the Addon Manager's interaction with FreeCAD, and to provide +replacements when the Addon Manager is not run from within FreeCAD (e.g. during unit +testing). + +Usage: +from addonmanager_freecad_interface import Console, DataPaths, Preferences +""" + +import json +import logging +import os +import tempfile + +# pylint: disable=too-few-public-methods + +try: + import FreeCAD + + Console = FreeCAD.Console + ParamGet = FreeCAD.ParamGet + getUserAppDataDir = FreeCAD.getUserAppDataDir + getUserMacroDir = FreeCAD.getUserMacroDir + getUserCachePath = FreeCAD.getUserCachePath + translate = FreeCAD.Qt.translate + +except ImportError: + FreeCAD = None + getUserAppDataDir = None + getUserCachePath = None + getUserMacroDir = None + + def translate(_context: str, string: str, _desc: str = "") -> str: + return string + + class ConsoleReplacement: + """If FreeCAD's Console is not available, create a replacement by redirecting FreeCAD + log calls to Python's built-in logging facility.""" + + @staticmethod + def PrintLog(arg: str) -> None: + logging.log(logging.DEBUG, arg) + + @staticmethod + def PrintMessage(arg: str) -> None: + logging.info(arg) + + @staticmethod + def PrintWarning(arg: str) -> None: + logging.warning(arg) + + @staticmethod + def PrintError(arg: str) -> None: + logging.error(arg) + + Console = ConsoleReplacement() + + class ParametersReplacement: + """Proxy for FreeCAD's Parameters when not running within FreeCAD. NOT + serialized, only exists for the duration of the program's execution. Only + provides the functions used by the Addon Manager, this class is not intended + to be a complete replacement for FreeCAD's preferences system.""" + + parameters = {} + + def GetBool(self, name: str, default: bool) -> bool: + return self._Get(name, default) + + def GetInt(self, name: str, default: int) -> int: + return self._Get(name, default) + + def GetFloat(self, name: str, default: float) -> float: + return self._Get(name, default) + + def GetString(self, name: str, default: str) -> str: + return self._Get(name, default) + + def _Get(self, name, default): + return self.parameters[name] if name in self.parameters else default + + def SetBool(self, name: str, value: bool) -> None: + self.parameters[name] = value + + def SetInt(self, name: str, value: int) -> None: + self.parameters[name] = value + + def SetFloat(self, name: str, value: float) -> None: + self.parameters[name] = value + + def SetString(self, name: str, value: str) -> None: + self.parameters[name] = value + + def RemBool(self, name: str) -> None: + self.parameters.pop(name) + + def RemInt(self, name: str) -> None: + self.parameters.pop(name) + + def RemFloat(self, name: str) -> None: + self.parameters.pop(name) + + def RemString(self, name: str) -> None: + self.parameters.pop(name) + + def ParamGet(_: str): + return ParametersReplacement() + + +class DataPaths: + """Provide access to various data storage paths. If not running within FreeCAD, + all paths are temp directories. If not run within FreeCAD, all directories are + deleted when the last reference to this class is deleted.""" + + mod_dir = None + macro_dir = None + cache_dir = None + + reference_count = 0 + + def __init__(self): + if FreeCAD: + if self.mod_dir is None: + self.mod_dir = os.path.join(getUserAppDataDir(), "Mod") + if self.cache_dir is None: + self.cache_dir = getUserCachePath() + if self.macro_dir is None: + self.macro_dir = getUserMacroDir(True) + else: + self.reference_count += 1 + if self.mod_dir is None: + self.mod_dir = tempfile.mkdtemp() + if self.cache_dir is None: + self.cache_dir = tempfile.mkdtemp() + if self.macro_dir is None: + self.macro_dir = tempfile.mkdtemp() + + def __del__(self): + self.reference_count -= 1 + if not FreeCAD and self.reference_count <= 0: + os.rmdir(self.mod_dir) + os.rmdir(self.cache_dir) + os.rmdir(self.macro_dir) + self.mod_dir = None + self.cache_dir = None + self.macro_dir = None + + +class Preferences: + """Wrap access to all user preferences. If run within FreeCAD, user preferences are + persistent, otherwise they only exist per-run. All preferences are controlled by a + central JSON file defining their defaults.""" + + preferences_defaults = {} + + def __init__(self, defaults_data=None): + """Set up the preferences, initializing the class statics if necessary. If + defaults_data is provided it is used as the preferences defaults. If it is not + provided, then the defaults are read in from the standard defaults file, + addonmanager_preferences_defaults.json, located in the same directory as this + Python file.""" + if not self.preferences_defaults: + if defaults_data: + self.preferences_defaults = defaults_data + else: + self._load_preferences_defaults() + self.prefs = ParamGet("User parameter:BaseApp/Preferences/Addons") + + def get(self, name: str): + """Get the preference value for the given key""" + if name not in self.preferences_defaults: + raise RuntimeError( + f"Unrecognized preference {name} -- did you add " + + "it to addonmanager_preferences_defaults.json?" + ) + if isinstance(self.preferences_defaults[name], bool): + return self.prefs.GetBool(name, self.preferences_defaults[name]) + if isinstance(self.preferences_defaults[name], int): + return self.prefs.GetInt(name, self.preferences_defaults[name]) + if isinstance(self.preferences_defaults[name], float): + return self.prefs.GetFloat(name, self.preferences_defaults[name]) + if isinstance(self.preferences_defaults[name], str): + return self.prefs.GetString(name, self.preferences_defaults[name]) + # We don't directly support any other types from the JSON file (e.g. arrays) + type_name = type(self.preferences_defaults[name]) + raise RuntimeError(f"Unrecognized type for {name}: {type_name}") + + def set(self, name: str, value): + """Set the preference value for the given key. Must exist (e.g. must be in the + addonmanager_preferences_defaults.json file).""" + if name not in self.preferences_defaults: + raise RuntimeError( + f"Unrecognized preference {name} -- did you add " + + "it to addonmanager_preferences_defaults.json?" + ) + if isinstance(self.preferences_defaults[name], bool): + self.prefs.SetBool(name, value) + elif isinstance(self.preferences_defaults[name], int): + self.prefs.SetInt(name, value) + elif isinstance(self.preferences_defaults[name], float): + self.prefs.SetFloat(name, value) + elif isinstance(self.preferences_defaults[name], str): + self.prefs.SetString(name, value) + else: + # We don't directly support any other types from the JSON file (e.g. arrays) + type_name = type(self.preferences_defaults[name]) + raise RuntimeError(f"Unrecognized type for {name}: {type_name}") + + def rem(self, name: str): + """Remove the preference. Must have an entry in the + addonmanager_preferences_defaults.json file.""" + if name not in self.preferences_defaults: + raise RuntimeError( + f"Unrecognized preference {name} -- did you add " + + "it to addonmanager_preferences_defaults.json?" + ) + if isinstance(self.preferences_defaults[name], bool): + return self.prefs.RemBool(name) + if isinstance(self.preferences_defaults[name], int): + return self.prefs.RemInt(name) + if isinstance(self.preferences_defaults[name], float): + return self.prefs.RemFloat(name) + if isinstance(self.preferences_defaults[name], str): + return self.prefs.RemString(name) + # We don't directly support any other types from the JSON file (e.g. arrays) + type_name = type(self.preferences_defaults[name]) + raise RuntimeError(f"Unrecognized type for {name}: {type_name}") + + @classmethod + def _load_preferences_defaults(cls, filename=None): + """Loads the preferences defaults JSON file from either a specified file, or + from the standard addonmanager_preferences_defaults.json file.""" + + if filename is None: + json_file = os.path.join( + os.path.dirname(__file__), "addonmanager_preferences_defaults.json" + ) + else: + json_file = filename + with open(json_file, "r", encoding="utf-8") as f: + file_contents = f.read() + cls.preferences_defaults = json.loads(file_contents) diff --git a/src/Mod/AddonManager/addonmanager_preferences_defaults.json b/src/Mod/AddonManager/addonmanager_preferences_defaults.json new file mode 100644 index 0000000000..4258178ebe --- /dev/null +++ b/src/Mod/AddonManager/addonmanager_preferences_defaults.json @@ -0,0 +1,43 @@ +{ + "AddonFlagsURL": "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/addonflags.json", + "AddonsRemoteCacheURL": "https://addons.freecad.org/metadata.zip", + "AddonsUpdateStatsURL": "https://addons.freecad.org/addon_update_stats.json", + "AutoCheck": false, + "BlockedMacros": "BOLTS,WorkFeatures,how to install,documentation,PartsLibrary,FCGear", + "CustomRepoHash": "", + "CustomRepositories": "", + "CustomToolbarName": "Auto-Created Macro Toolbar", + "DaysBetweenUpdates": -1, + "DownloadMacros": false, + "GitExecutable": "Not set", + "HideNewerFreeCADRequired": true, + "HideObsolete": true, + "HidePy2": true, + "KnownPythonVersions": "[]", + "LastCacheUpdate": "never", + "MacroCacheUpdateFrequency": 7, + "MacroGitURL": "https://github.com/FreeCAD/FreeCAD-Macros", + "MacroUpdateStatsURL": "https://addons.freecad.org/macro_update_stats.json", + "MacroWikiURL": "https://wiki.freecad.org/Macros_recipes", + "NoProxyCheck": "", + "PackageTypeSelection": 1, + "PrimaryAddonsSubmoduleURL": "https://raw.githubusercontent.com/FreeCAD/FreeCAD-addons/master/.gitmodules", + "ProxyUrl": "", + "PythonExecutableForPip": "Not set", + "RemoteIconCacheURL": "https://addons.freecad.org/icon_cache.zip", + "SelectedAddon": "", + "ShowBranchSwitcher": false, + "StatusSelection": 0, + "SystemProxyCheck": "", + "UpdateFrequencyComboEntry": 0, + "UserProxyCheck": "", + "ViewStyle": 1, + "WindowHeight": 600, + "WindowWidth": 800, + "alwaysAskForToolbar": true, + "devModeLastSelectedLicense": "LGPLv2.1", + "developerMode": false, + "disableGit": false, + "dontShowAddMacroButtonDialog": false, + "readWarning2022": false +} \ No newline at end of file