FreeCAD
278 строк · 11.1 Кб
1# SPDX-License-Identifier: LGPL-2.1-or-later
2# ***************************************************************************
3# * *
4# * Copyright (c) 2023 FreeCAD Project Association *
5# * *
6# * This file is part of FreeCAD. *
7# * *
8# * FreeCAD is free software: you can redistribute it and/or modify it *
9# * under the terms of the GNU Lesser General Public License as *
10# * published by the Free Software Foundation, either version 2.1 of the *
11# * License, or (at your option) any later version. *
12# * *
13# * FreeCAD is distributed in the hope that it will be useful, but *
14# * WITHOUT ANY WARRANTY; without even the implied warranty of *
15# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
16# * Lesser General Public License for more details. *
17# * *
18# * You should have received a copy of the GNU Lesser General Public *
19# * License along with FreeCAD. If not, see *
20# * <https://www.gnu.org/licenses/>. *
21# * *
22# ***************************************************************************
23
24"""Classes to encapsulate the Addon Manager's interaction with FreeCAD, and to provide
25replacements when the Addon Manager is not run from within FreeCAD (e.g. during unit
26testing).
27
28Usage:
29from addonmanager_freecad_interface import Console, DataPaths, Preferences
30"""
31
32import json33import logging34import os35import tempfile36
37# pylint: disable=too-few-public-methods
38
39try:40import FreeCAD41
42Console = FreeCAD.Console43ParamGet = FreeCAD.ParamGet44Version = FreeCAD.Version45getUserAppDataDir = FreeCAD.getUserAppDataDir46getUserMacroDir = FreeCAD.getUserMacroDir47getUserCachePath = FreeCAD.getUserCachePath48translate = FreeCAD.Qt.translate49
50if FreeCAD.GuiUp:51import FreeCADGui52else:53FreeCADGui = None54
55except ImportError:56FreeCAD = None57FreeCADGui = None58getUserAppDataDir = None59getUserCachePath = None60getUserMacroDir = None61
62def translate(_context: str, string: str, _desc: str = "") -> str:63return string64
65def Version():66return 0, 21, 0, "dev"67
68class ConsoleReplacement:69"""If FreeCAD's Console is not available, create a replacement by redirecting FreeCAD70log calls to Python's built-in logging facility."""
71
72@staticmethod73def PrintLog(arg: str) -> None:74logging.log(logging.DEBUG, arg)75
76@staticmethod77def PrintMessage(arg: str) -> None:78logging.info(arg)79
80@staticmethod81def PrintWarning(arg: str) -> None:82logging.warning(arg)83
84@staticmethod85def PrintError(arg: str) -> None:86logging.error(arg)87
88Console = ConsoleReplacement()89
90class ParametersReplacement:91"""Proxy for FreeCAD's Parameters when not running within FreeCAD. NOT92serialized, only exists for the duration of the program's execution. Only
93provides the functions used by the Addon Manager, this class is not intended
94to be a complete replacement for FreeCAD's preferences system."""
95
96parameters = {}97
98def GetBool(self, name: str, default: bool) -> bool:99return self._Get(name, default)100
101def GetInt(self, name: str, default: int) -> int:102return self._Get(name, default)103
104def GetFloat(self, name: str, default: float) -> float:105return self._Get(name, default)106
107def GetString(self, name: str, default: str) -> str:108return self._Get(name, default)109
110def _Get(self, name, default):111return self.parameters[name] if name in self.parameters else default112
113def SetBool(self, name: str, value: bool) -> None:114self.parameters[name] = value115
116def SetInt(self, name: str, value: int) -> None:117self.parameters[name] = value118
119def SetFloat(self, name: str, value: float) -> None:120self.parameters[name] = value121
122def SetString(self, name: str, value: str) -> None:123self.parameters[name] = value124
125def RemBool(self, name: str) -> None:126self.parameters.pop(name)127
128def RemInt(self, name: str) -> None:129self.parameters.pop(name)130
131def RemFloat(self, name: str) -> None:132self.parameters.pop(name)133
134def RemString(self, name: str) -> None:135self.parameters.pop(name)136
137def ParamGet(_: str):138return ParametersReplacement()139
140
141class DataPaths:142"""Provide access to various data storage paths. If not running within FreeCAD,143all paths are temp directories. If not run within FreeCAD, all directories are
144deleted when the last reference to this class is deleted."""
145
146mod_dir = None147macro_dir = None148cache_dir = None149home_dir = None150
151reference_count = 0152
153def __init__(self):154if FreeCAD:155if self.mod_dir is None:156self.mod_dir = os.path.join(getUserAppDataDir(), "Mod")157if self.cache_dir is None:158self.cache_dir = getUserCachePath()159if self.macro_dir is None:160self.macro_dir = getUserMacroDir(True)161if self.home_dir is None:162self.home_dir = FreeCAD.getHomePath()163else:164self.reference_count += 1165if self.mod_dir is None:166self.mod_dir = tempfile.mkdtemp()167if self.cache_dir is None:168self.cache_dir = tempfile.mkdtemp()169if self.macro_dir is None:170self.macro_dir = tempfile.mkdtemp()171if self.home_dir is None:172self.home_dir = os.path.join(os.path.dirname(__file__), "..", "..")173
174def __del__(self):175self.reference_count -= 1176if not FreeCAD and self.reference_count <= 0:177os.rmdir(self.mod_dir)178os.rmdir(self.cache_dir)179os.rmdir(self.macro_dir)180self.mod_dir = None181self.cache_dir = None182self.macro_dir = None183
184
185class Preferences:186"""Wrap access to all user preferences. If run within FreeCAD, user preferences are187persistent, otherwise they only exist per-run. All preferences are controlled by a
188central JSON file defining their defaults."""
189
190preferences_defaults = {}191
192def __init__(self, defaults_data=None):193"""Set up the preferences, initializing the class statics if necessary. If194defaults_data is provided it is used as the preferences defaults. If it is not
195provided, then the defaults are read in from the standard defaults file,
196addonmanager_preferences_defaults.json, located in the same directory as this
197Python file."""
198if not self.preferences_defaults:199if defaults_data:200self.preferences_defaults = defaults_data201else:202self._load_preferences_defaults()203self.prefs = ParamGet("User parameter:BaseApp/Preferences/Addons")204
205def get(self, name: str):206"""Get the preference value for the given key"""207if name not in self.preferences_defaults:208raise RuntimeError(209f"Unrecognized preference {name} -- did you add "210+ "it to addonmanager_preferences_defaults.json?"211)212if isinstance(self.preferences_defaults[name], bool):213return self.prefs.GetBool(name, self.preferences_defaults[name])214if isinstance(self.preferences_defaults[name], int):215return self.prefs.GetInt(name, self.preferences_defaults[name])216if isinstance(self.preferences_defaults[name], float):217return self.prefs.GetFloat(name, self.preferences_defaults[name])218if isinstance(self.preferences_defaults[name], str):219return self.prefs.GetString(name, self.preferences_defaults[name])220# We don't directly support any other types from the JSON file (e.g. arrays)221type_name = type(self.preferences_defaults[name])222raise RuntimeError(f"Unrecognized type for {name}: {type_name}")223
224def set(self, name: str, value):225"""Set the preference value for the given key. Must exist (e.g. must be in the226addonmanager_preferences_defaults.json file)."""
227if name not in self.preferences_defaults:228raise RuntimeError(229f"Unrecognized preference {name} -- did you add "230+ "it to addonmanager_preferences_defaults.json?"231)232if isinstance(self.preferences_defaults[name], bool):233self.prefs.SetBool(name, value)234elif isinstance(self.preferences_defaults[name], int):235self.prefs.SetInt(name, value)236elif isinstance(self.preferences_defaults[name], float):237self.prefs.SetFloat(name, value)238elif isinstance(self.preferences_defaults[name], str):239self.prefs.SetString(name, value)240else:241# We don't directly support any other types from the JSON file (e.g. arrays)242type_name = type(self.preferences_defaults[name])243raise RuntimeError(f"Unrecognized type for {name}: {type_name}")244
245def rem(self, name: str):246"""Remove the preference. Must have an entry in the247addonmanager_preferences_defaults.json file."""
248if name not in self.preferences_defaults:249raise RuntimeError(250f"Unrecognized preference {name} -- did you add "251+ "it to addonmanager_preferences_defaults.json?"252)253if isinstance(self.preferences_defaults[name], bool):254return self.prefs.RemBool(name)255if isinstance(self.preferences_defaults[name], int):256return self.prefs.RemInt(name)257if isinstance(self.preferences_defaults[name], float):258return self.prefs.RemFloat(name)259if isinstance(self.preferences_defaults[name], str):260return self.prefs.RemString(name)261# We don't directly support any other types from the JSON file (e.g. arrays)262type_name = type(self.preferences_defaults[name])263raise RuntimeError(f"Unrecognized type for {name}: {type_name}")264
265@classmethod266def _load_preferences_defaults(cls, filename=None):267"""Loads the preferences defaults JSON file from either a specified file, or268from the standard addonmanager_preferences_defaults.json file."""
269
270if filename is None:271json_file = os.path.join(272os.path.dirname(__file__), "addonmanager_preferences_defaults.json"273)274else:275json_file = filename276with open(json_file, "r", encoding="utf-8") as f:277file_contents = f.read()278cls.preferences_defaults = json.loads(file_contents)279