fix: preserve caller globals in exec() for module Init.py/InitGui.py loading
Some checks failed
Build and Test / build (pull_request) Has been cancelled
Some checks failed
Build and Test / build (pull_request) Has been cancelled
The previous fix (e10841a6c8) passed {"__file__": ...} as the globals
argument to exec(), which replaced the caller's globals dict entirely.
This stripped FreeCAD, FreeCADGui, Workbench, and other names that
modules expect to be available, causing NameError failures across
Material, Tux, Mesh, ReverseEngineering, OpenSCAD, Inspection, Robot,
AddonManager, MeshPart, and others.
Fix by merging __file__ into the caller's globals with
{**globals(), "__file__": str(init_py)} so both __file__ and all
existing names remain available in the executed code.
This commit is contained in:
@@ -1,28 +1,28 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#***************************************************************************
|
||||
#* Copyright (c) 2001,2002 Jürgen Riegel <juergen.riegel@web.de> *
|
||||
#* Copyright (c) 2025 Frank Martínez <mnesarco at gmail dot com> *
|
||||
#* *
|
||||
#* This file is part of the FreeCAD CAx development system. *
|
||||
#* *
|
||||
#* This program is free software you can redistribute it and/or modify *
|
||||
#* it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
#* as published by the Free Software Foundation either version 2 of *
|
||||
#* the License, or (at your option) any later version. *
|
||||
#* for detail see the LICENCE text file. *
|
||||
#* *
|
||||
#* 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 Library General Public *
|
||||
#* License along with FreeCAD if not, write to the Free Software *
|
||||
#* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
#* USA *
|
||||
#* *
|
||||
#***************************************************************************/
|
||||
# ***************************************************************************
|
||||
# * Copyright (c) 2001,2002 Jürgen Riegel <juergen.riegel@web.de> *
|
||||
# * Copyright (c) 2025 Frank Martínez <mnesarco at gmail dot com> *
|
||||
# * *
|
||||
# * This file is part of the FreeCAD CAx development system. *
|
||||
# * *
|
||||
# * This program is free software you can redistribute it and/or modify *
|
||||
# * it under the terms of the GNU Lesser General Public License (LGPL) *
|
||||
# * as published by the Free Software Foundation either version 2 of *
|
||||
# * the License, or (at your option) any later version. *
|
||||
# * for detail see the LICENCE text file. *
|
||||
# * *
|
||||
# * 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 Library General Public *
|
||||
# * License along with FreeCAD if not, write to the Free Software *
|
||||
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 *
|
||||
# * USA *
|
||||
# * *
|
||||
# ***************************************************************************/
|
||||
|
||||
# FreeCAD init module - App
|
||||
#
|
||||
@@ -47,37 +47,43 @@ App.Console.PrintLog("░░░░█░░█░█░░█░░░█░░
|
||||
App.Console.PrintLog("░░░▀▀▀░▀░▀░▀▀▀░░▀░░░░▀░▀░▀░░░▀░░░░\n")
|
||||
|
||||
try:
|
||||
import sys
|
||||
import os
|
||||
import traceback
|
||||
import inspect
|
||||
from enum import IntEnum # Leak to globals (backwards compat)
|
||||
from datetime import datetime # Leak to globals (backwards compat)
|
||||
from pathlib import Path # Removed manually
|
||||
import dataclasses
|
||||
import collections
|
||||
import collections.abc as coll_abc
|
||||
import platform
|
||||
import types
|
||||
import importlib.resources as resources
|
||||
import importlib
|
||||
import dataclasses
|
||||
import functools
|
||||
import re
|
||||
import importlib
|
||||
import importlib.resources as resources
|
||||
import inspect
|
||||
import os
|
||||
import pkgutil
|
||||
import platform
|
||||
import re
|
||||
import sys
|
||||
import traceback
|
||||
import types
|
||||
from datetime import datetime # Leak to globals (backwards compat)
|
||||
from enum import IntEnum # Leak to globals (backwards compat)
|
||||
from pathlib import Path # Removed manually
|
||||
except ImportError:
|
||||
App.Console.PrintError("\n\nSeems the python standard libs are not installed, bailing out!\n\n")
|
||||
App.Console.PrintError(
|
||||
"\n\nSeems the python standard libs are not installed, bailing out!\n\n"
|
||||
)
|
||||
raise
|
||||
|
||||
# ┌────────────────────────────────────────────────┐
|
||||
# │ Logging Frameworks │
|
||||
# └────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
def __logger(fn):
|
||||
__logger.sep = "\n"
|
||||
|
||||
def wrapper(text: object, *, sep: str | None = None) -> None:
|
||||
fn(f"{text!s}{__logger.sep if sep is None else sep}")
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
Log = __logger(App.Console.PrintLog)
|
||||
Msg = __logger(App.Console.PrintMessage)
|
||||
Err = __logger(App.Console.PrintError)
|
||||
@@ -129,18 +135,18 @@ class FCADLogger:
|
||||
"""
|
||||
|
||||
_levels = {
|
||||
'Error': 0,
|
||||
'error': 0,
|
||||
'Warning': 1,
|
||||
'warn': 1,
|
||||
'Message': 2,
|
||||
'msg': 2,
|
||||
'info': 2,
|
||||
'Log': 3,
|
||||
'log': 3,
|
||||
'debug': 3,
|
||||
'Trace': 4,
|
||||
'trace': 4,
|
||||
"Error": 0,
|
||||
"error": 0,
|
||||
"Warning": 1,
|
||||
"warn": 1,
|
||||
"Message": 2,
|
||||
"msg": 2,
|
||||
"info": 2,
|
||||
"Log": 3,
|
||||
"log": 3,
|
||||
"debug": 3,
|
||||
"Trace": 4,
|
||||
"trace": 4,
|
||||
}
|
||||
|
||||
_printer = (
|
||||
@@ -148,16 +154,16 @@ class FCADLogger:
|
||||
App.Console.PrintWarning,
|
||||
App.Console.PrintMessage,
|
||||
App.Console.PrintLog,
|
||||
App.Console.PrintLog
|
||||
App.Console.PrintLog,
|
||||
)
|
||||
|
||||
_defaults = (
|
||||
('printTag', True),
|
||||
('noUpdateUI', True),
|
||||
('timing', True),
|
||||
('lineno', True),
|
||||
('parent', None),
|
||||
('title', 'FreeCAD'),
|
||||
("printTag", True),
|
||||
("noUpdateUI", True),
|
||||
("timing", True),
|
||||
("lineno", True),
|
||||
("parent", None),
|
||||
("title", "FreeCAD"),
|
||||
)
|
||||
|
||||
printTag: bool
|
||||
@@ -239,7 +245,7 @@ class FCADLogger:
|
||||
|
||||
def log_fn(self, msg: str, *args, **kwargs) -> None:
|
||||
if self._isEnabledFor(level):
|
||||
frame = kwargs.pop('frame', 0) + 1
|
||||
frame = kwargs.pop("frame", 0) + 1
|
||||
self._log(level, msg, frame, args, kwargs)
|
||||
|
||||
log_fn.__doc__ = docstring
|
||||
@@ -247,13 +253,13 @@ class FCADLogger:
|
||||
return log_fn
|
||||
|
||||
def _log(
|
||||
self,
|
||||
level: int,
|
||||
msg: str,
|
||||
frame: int = 0,
|
||||
args: tuple = (),
|
||||
kwargs: dict | None = None,
|
||||
) -> None:
|
||||
self,
|
||||
level: int,
|
||||
msg: str,
|
||||
frame: int = 0,
|
||||
args: tuple = (),
|
||||
kwargs: dict | None = None,
|
||||
) -> None:
|
||||
"""
|
||||
Internal log printing function.
|
||||
|
||||
@@ -279,29 +285,31 @@ class FCADLogger:
|
||||
else:
|
||||
msg = msg.format(*args, **kwargs)
|
||||
|
||||
prefix = ''
|
||||
prefix = ""
|
||||
|
||||
if self.timing:
|
||||
now = datetime.now()
|
||||
prefix += '{} '.format((now-self.laststamp).total_seconds())
|
||||
prefix += "{} ".format((now - self.laststamp).total_seconds())
|
||||
self.laststamp = now
|
||||
|
||||
if self.printTag:
|
||||
prefix += '<{}> '.format(self.tag)
|
||||
prefix += "<{}> ".format(self.tag)
|
||||
|
||||
if self.lineno:
|
||||
try:
|
||||
frame = sys._getframe(frame+1)
|
||||
prefix += '{}({}): '.format(os.path.basename(
|
||||
frame.f_code.co_filename),frame.f_lineno)
|
||||
frame = sys._getframe(frame + 1)
|
||||
prefix += "{}({}): ".format(
|
||||
os.path.basename(frame.f_code.co_filename), frame.f_lineno
|
||||
)
|
||||
except Exception:
|
||||
frame = inspect.stack()[frame+1]
|
||||
prefix += '{}({}): '.format(os.path.basename(frame[1]),frame[2])
|
||||
frame = inspect.stack()[frame + 1]
|
||||
prefix += "{}({}): ".format(os.path.basename(frame[1]), frame[2])
|
||||
|
||||
self.__class__._printer[level]('{}{}\n'.format(prefix,msg))
|
||||
self.__class__._printer[level]("{}{}\n".format(prefix, msg))
|
||||
|
||||
if not self.noUpdateUI and App.GuiUp:
|
||||
import FreeCADGui
|
||||
|
||||
try:
|
||||
FreeCADGui.updateGui()
|
||||
except Exception:
|
||||
@@ -332,13 +340,13 @@ class FCADLogger:
|
||||
return catch_fn
|
||||
|
||||
def _catch(
|
||||
self,
|
||||
level: int,
|
||||
msg: str,
|
||||
func: callable,
|
||||
args: tuple = (),
|
||||
kwargs: dict | None = None,
|
||||
) -> object | None:
|
||||
self,
|
||||
level: int,
|
||||
msg: str,
|
||||
func: callable,
|
||||
args: tuple = (),
|
||||
kwargs: dict | None = None,
|
||||
) -> object | None:
|
||||
"""
|
||||
Internal function to log exception of any callable.
|
||||
|
||||
@@ -380,7 +388,9 @@ class FCADLogger:
|
||||
except Exception as e:
|
||||
self.error(f"{msg}\n{traceback.format_exc()}", frame=1)
|
||||
if App.GuiUp:
|
||||
import FreeCADGui, PySide
|
||||
import FreeCADGui
|
||||
import PySide
|
||||
|
||||
PySide.QtGui.QMessageBox.critical(
|
||||
FreeCADGui.getMainWindow(),
|
||||
self.title,
|
||||
@@ -631,6 +641,7 @@ App.Units.YieldStrength = App.Units.Unit(-1,1,-2)
|
||||
App.Units.YoungsModulus = App.Units.Unit(-1,1,-2)
|
||||
# fmt: on
|
||||
|
||||
|
||||
# The values must match with that of the
|
||||
# C++ enum class UnitSystem
|
||||
class Scheme(IntEnum):
|
||||
@@ -645,15 +656,19 @@ class Scheme(IntEnum):
|
||||
FEM = 8
|
||||
MeterDecimal = 9
|
||||
|
||||
|
||||
App.Units.Scheme = Scheme
|
||||
|
||||
|
||||
class NumberFormat(IntEnum):
|
||||
Default = 0
|
||||
Fixed = 1
|
||||
Scientific = 2
|
||||
|
||||
|
||||
App.Units.NumberFormat = NumberFormat
|
||||
|
||||
|
||||
class ScaleType(IntEnum):
|
||||
Other = -1
|
||||
NoScaling = 0
|
||||
@@ -661,8 +676,10 @@ class ScaleType(IntEnum):
|
||||
NonUniformLeft = 2
|
||||
Uniform = 3
|
||||
|
||||
|
||||
App.ScaleType = ScaleType
|
||||
|
||||
|
||||
class PropertyType(IntEnum):
|
||||
Prop_None = 0
|
||||
Prop_ReadOnly = 1
|
||||
@@ -672,8 +689,10 @@ class PropertyType(IntEnum):
|
||||
Prop_NoRecompute = 16
|
||||
Prop_NoPersist = 32
|
||||
|
||||
|
||||
App.PropertyType = PropertyType
|
||||
|
||||
|
||||
class ReturnType(IntEnum):
|
||||
PyObject = 0
|
||||
DocObject = 1
|
||||
@@ -683,6 +702,7 @@ class ReturnType(IntEnum):
|
||||
LinkAndPlacement = 5
|
||||
LinkAndMatrix = 6
|
||||
|
||||
|
||||
App.ReturnType = ReturnType
|
||||
|
||||
|
||||
@@ -690,6 +710,7 @@ App.ReturnType = ReturnType
|
||||
# │ Init Framework │
|
||||
# └────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
class Transient:
|
||||
"""
|
||||
Mark the symbol for removal from global scope on cleanup.
|
||||
@@ -705,8 +726,12 @@ class Transient:
|
||||
def cleanup(cls) -> None:
|
||||
# Remove imports
|
||||
# os: kept for backwards compat
|
||||
keep = set(("__builtins__", "FreeCAD", "App", "os", "sys", "traceback", "inspect"))
|
||||
names = [name for name, ref in globals().items() if isinstance(ref, types.ModuleType)]
|
||||
keep = set(
|
||||
("__builtins__", "FreeCAD", "App", "os", "sys", "traceback", "inspect")
|
||||
)
|
||||
names = [
|
||||
name for name, ref in globals().items() if isinstance(ref, types.ModuleType)
|
||||
]
|
||||
for name in names:
|
||||
if name not in keep:
|
||||
del globals()[name]
|
||||
@@ -726,6 +751,7 @@ def call_in_place(fn):
|
||||
fn()
|
||||
return fn
|
||||
|
||||
|
||||
@transient
|
||||
class utils:
|
||||
HLine = "-" * 80
|
||||
@@ -755,6 +781,7 @@ class utils:
|
||||
try:
|
||||
import readline
|
||||
import rlcompleter # noqa: F401, import required
|
||||
|
||||
readline.parse_and_bind("tab: complete")
|
||||
except ImportError:
|
||||
# Note: As there is no readline on Windows,
|
||||
@@ -770,6 +797,7 @@ class PathPriority(IntEnum):
|
||||
OverrideLast = 3
|
||||
OverrideFirst = 4
|
||||
|
||||
|
||||
@transient
|
||||
@dataclasses.dataclass
|
||||
class PathSet:
|
||||
@@ -787,10 +815,16 @@ class PathSet:
|
||||
"""
|
||||
|
||||
source: list["Path | PathSet"] = dataclasses.field(default_factory=list)
|
||||
override: collections.deque["Path | PathSet"] = dataclasses.field(default_factory=collections.deque)
|
||||
fallback: collections.deque["Path | PathSet"] = dataclasses.field(default_factory=collections.deque)
|
||||
override: collections.deque["Path | PathSet"] = dataclasses.field(
|
||||
default_factory=collections.deque
|
||||
)
|
||||
fallback: collections.deque["Path | PathSet"] = dataclasses.field(
|
||||
default_factory=collections.deque
|
||||
)
|
||||
|
||||
def add(self, item: "Path | PathSet", priority: PathPriority = PathPriority.OverrideLast) -> None:
|
||||
def add(
|
||||
self, item: "Path | PathSet", priority: PathPriority = PathPriority.OverrideLast
|
||||
) -> None:
|
||||
"""Add item into the corresponding priority slot."""
|
||||
if isinstance(item, Path):
|
||||
item = item.resolve()
|
||||
@@ -828,6 +862,7 @@ class PathSet:
|
||||
"""
|
||||
return list(dict.fromkeys(self.iter()))
|
||||
|
||||
|
||||
@transient
|
||||
class SearchPaths:
|
||||
"""
|
||||
@@ -843,7 +878,9 @@ class SearchPaths:
|
||||
dll_path: PathSet
|
||||
|
||||
def __init__(self):
|
||||
self.env_path = PathSet([Path(p) for p in os.environ.get("PATH", "").split(os.pathsep)])
|
||||
self.env_path = PathSet(
|
||||
[Path(p) for p in os.environ.get("PATH", "").split(os.pathsep)]
|
||||
)
|
||||
self.sys_path = PathSet(sys.path)
|
||||
self.dll_path = PathSet()
|
||||
|
||||
@@ -853,7 +890,7 @@ class SearchPaths:
|
||||
*,
|
||||
env_path: PathPriority = PathPriority.OverrideLast,
|
||||
sys_path: PathPriority = PathPriority.OverrideFirst,
|
||||
dll_path: PathPriority = PathPriority.OverrideLast
|
||||
dll_path: PathPriority = PathPriority.OverrideLast,
|
||||
) -> None:
|
||||
"""
|
||||
Add item to required namespaces with the specified priority.
|
||||
@@ -866,7 +903,9 @@ class SearchPaths:
|
||||
|
||||
def commit(self) -> None:
|
||||
"""Apply changes to underlying namespaces and priorities."""
|
||||
os.environ["PATH"] = os.pathsep.join(str(path) for path in self.env_path.build())
|
||||
os.environ["PATH"] = os.pathsep.join(
|
||||
str(path) for path in self.env_path.build()
|
||||
)
|
||||
sys.path = [str(path) for path in self.sys_path.build()]
|
||||
|
||||
if win32 := WindowsPlatform():
|
||||
@@ -880,8 +919,10 @@ class SearchPaths:
|
||||
class Config:
|
||||
AdditionalModulePaths = utils.str_to_paths(App.ConfigGet("AdditionalModulePaths"))
|
||||
AdditionalMacroPaths = utils.str_to_paths(App.ConfigGet("AdditionalMacroPaths"))
|
||||
RunMode: str = App.ConfigGet('RunMode')
|
||||
DisabledAddons: set[str] = set(mod for mod in App.ConfigGet("DisabledAddons").split(";") if mod)
|
||||
RunMode: str = App.ConfigGet("RunMode")
|
||||
DisabledAddons: set[str] = set(
|
||||
mod for mod in App.ConfigGet("DisabledAddons").split(";") if mod
|
||||
)
|
||||
|
||||
|
||||
@transient
|
||||
@@ -891,7 +932,7 @@ class WindowsPlatform:
|
||||
"""
|
||||
|
||||
initialized = False
|
||||
enabled = platform.system() == 'Windows' and hasattr(os, "add_dll_directory")
|
||||
enabled = platform.system() == "Windows" and hasattr(os, "add_dll_directory")
|
||||
|
||||
def __init__(self) -> None:
|
||||
if not WindowsPlatform.enabled or WindowsPlatform.initialized:
|
||||
@@ -1038,7 +1079,9 @@ class ExtMod(Mod):
|
||||
|
||||
def check_disabled(self) -> bool:
|
||||
with resources.as_file(resources.files(self.name)) as base:
|
||||
return (base / self.ADDON_DISABLED).exists() or (base.parent.parent / self.ADDON_DISABLED).exists()
|
||||
return (base / self.ADDON_DISABLED).exists() or (
|
||||
base.parent.parent / self.ADDON_DISABLED
|
||||
).exists()
|
||||
|
||||
def process_metadata(self, _search_paths: SearchPaths) -> None:
|
||||
meta = self.metadata
|
||||
@@ -1047,21 +1090,23 @@ class ExtMod(Mod):
|
||||
|
||||
if not self.supports_freecad_version():
|
||||
self.state = ModState.Unsupported
|
||||
Msg(f"NOTICE: {self.name} does not support this version of FreeCAD, so is being skipped")
|
||||
Msg(
|
||||
f"NOTICE: {self.name} does not support this version of FreeCAD, so is being skipped"
|
||||
)
|
||||
|
||||
def _init_error(self, ex: Exception, error_msg: str) -> None:
|
||||
Err(f'During initialization the error "{ex!s}" occurred in {self.name}')
|
||||
Err(utils.HLine)
|
||||
Err(error_msg)
|
||||
Err(utils.HLine)
|
||||
Log(f'Init: Initializing {self.name}... failed')
|
||||
Log(f"Init: Initializing {self.name}... failed")
|
||||
Err(utils.HLine)
|
||||
Log(error_msg)
|
||||
Err(utils.HLine)
|
||||
|
||||
def run_init(self) -> None:
|
||||
try:
|
||||
module = importlib.import_module(self.name) # Implicit run of __init__.py
|
||||
module = importlib.import_module(self.name) # Implicit run of __init__.py
|
||||
except Exception as ex:
|
||||
self._init_error(ex, traceback.format_exc())
|
||||
self.state = ModState.Failed
|
||||
@@ -1106,7 +1151,7 @@ class DirMod(Mod):
|
||||
|
||||
@property
|
||||
def init_mode(self) -> str:
|
||||
return "exec" if (self.path / self.INIT_PY).exists() else ''
|
||||
return "exec" if (self.path / self.INIT_PY).exists() else ""
|
||||
|
||||
@property
|
||||
def name(self) -> str:
|
||||
@@ -1126,7 +1171,9 @@ class DirMod(Mod):
|
||||
|
||||
if not self.supports_freecad_version():
|
||||
self.state = ModState.Unsupported
|
||||
Msg(f"NOTICE: {meta.Name} does not support this version of FreeCAD, so is being skipped")
|
||||
Msg(
|
||||
f"NOTICE: {meta.Name} does not support this version of FreeCAD, so is being skipped"
|
||||
)
|
||||
return
|
||||
|
||||
content = meta.Content
|
||||
@@ -1134,10 +1181,16 @@ class DirMod(Mod):
|
||||
workbenches = content["workbench"]
|
||||
for workbench in workbenches:
|
||||
if not workbench.supportsCurrentFreeCAD():
|
||||
Msg(f"NOTICE: {meta.Name} content item {workbench.Name} does not support this version of FreeCAD, so is being skipped")
|
||||
Msg(
|
||||
f"NOTICE: {meta.Name} content item {workbench.Name} does not support this version of FreeCAD, so is being skipped"
|
||||
)
|
||||
continue
|
||||
|
||||
subdirectory = workbench.Name if not workbench.Subdirectory else workbench.Subdirectory
|
||||
subdirectory = (
|
||||
workbench.Name
|
||||
if not workbench.Subdirectory
|
||||
else workbench.Subdirectory
|
||||
)
|
||||
subdirectory = re.split(r"[/\\]+", subdirectory)
|
||||
subdirectory = self.path / Path(*subdirectory)
|
||||
|
||||
@@ -1168,12 +1221,16 @@ class DirMod(Mod):
|
||||
name = self.path.name
|
||||
|
||||
if name in Config.DisabledAddons:
|
||||
Msg(f'NOTICE: Addon "{name}" disabled by presence of "--disable-addon {name}" argument')
|
||||
Msg(
|
||||
f'NOTICE: Addon "{name}" disabled by presence of "--disable-addon {name}" argument'
|
||||
)
|
||||
return True
|
||||
|
||||
for flag in (self.ALL_ADDONS_DISABLED, self.ADDON_DISABLED):
|
||||
if (self.path / flag).exists():
|
||||
Msg(f'NOTICE: Addon "{self.path!s}" disabled by presence of {flag} stopfile')
|
||||
Msg(
|
||||
f'NOTICE: Addon "{self.path!s}" disabled by presence of {flag} stopfile'
|
||||
)
|
||||
return True
|
||||
|
||||
return False
|
||||
@@ -1198,19 +1255,21 @@ class DirMod(Mod):
|
||||
init_py = self.path / self.INIT_PY
|
||||
if not init_py.exists():
|
||||
self.state = ModState.Loaded
|
||||
Log(f"Init: Initializing {self.path!s} ({self.INIT_PY} not found)... ignore")
|
||||
Log(
|
||||
f"Init: Initializing {self.path!s} ({self.INIT_PY} not found)... ignore"
|
||||
)
|
||||
return
|
||||
|
||||
try:
|
||||
source = init_py.read_text(encoding="utf-8")
|
||||
code = compile(source, init_py, 'exec')
|
||||
exec(code, {"__file__": str(init_py)})
|
||||
code = compile(source, init_py, "exec")
|
||||
exec(code, {**globals(), "__file__": str(init_py)})
|
||||
except Exception as ex:
|
||||
Log(f"Init: Initializing {self.path!s}... failed")
|
||||
Log(utils.HLine)
|
||||
Log(f"{traceback.format_exc()}")
|
||||
Log(utils.HLine)
|
||||
Err(f"During initialization the error \"{ex!s}\" occurred in {init_py!s}")
|
||||
Err(f'During initialization the error "{ex!s}" occurred in {init_py!s}')
|
||||
Err("Please look into the log file for further information")
|
||||
self.state = ModState.Failed
|
||||
else:
|
||||
@@ -1231,15 +1290,20 @@ class ExtModScanner:
|
||||
|
||||
def scan(self):
|
||||
import freecad
|
||||
modules = (m[1] for m in pkgutil.iter_modules(freecad.__path__, "freecad.") if m[2])
|
||||
|
||||
modules = (
|
||||
m[1] for m in pkgutil.iter_modules(freecad.__path__, "freecad.") if m[2]
|
||||
)
|
||||
for module_name in modules:
|
||||
mod = ExtMod(module_name)
|
||||
self.mods.append(mod)
|
||||
if module_name in Config.DisabledAddons:
|
||||
mod.state = ModState.Disabled
|
||||
Msg(f'NOTICE: Addon "{module_name}" disabled by presence of "--disable-addon {module_name}" argument')
|
||||
Msg(
|
||||
f'NOTICE: Addon "{module_name}" disabled by presence of "--disable-addon {module_name}" argument'
|
||||
)
|
||||
continue
|
||||
Log(f'Init: Initializing {module_name}')
|
||||
Log(f"Init: Initializing {module_name}")
|
||||
|
||||
def iter(self) -> coll_abc.Iterable[ExtMod]:
|
||||
return self.mods
|
||||
@@ -1251,7 +1315,7 @@ class DirModScanner:
|
||||
Sacan in the filesystem for Dir based Mods in the valid locations.
|
||||
"""
|
||||
|
||||
EXCLUDE: set[str] = set(["", "CVS", "__init__.py"]) # Why?
|
||||
EXCLUDE: set[str] = set(["", "CVS", "__init__.py"]) # Why?
|
||||
mods: dict[str, DirMod]
|
||||
visited: set[str]
|
||||
|
||||
@@ -1267,7 +1331,9 @@ class DirModScanner:
|
||||
"""Paths of all discovered Mods."""
|
||||
return [mod.path for mod in self.mods.values()]
|
||||
|
||||
def scan_and_override(self, base: Path, *, flat: bool = False, warning: str | None = None) -> None:
|
||||
def scan_and_override(
|
||||
self, base: Path, *, flat: bool = False, warning: str | None = None
|
||||
) -> None:
|
||||
"""
|
||||
Scan in base with higher priority.
|
||||
"""
|
||||
@@ -1304,6 +1370,7 @@ class DirModScanner:
|
||||
# │ Init Pipeline Definition │
|
||||
# └────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
@transient
|
||||
class InitPipeline:
|
||||
"""
|
||||
@@ -1318,7 +1385,9 @@ class InitPipeline:
|
||||
# The library path is not strictly required, so if the OS itself raises an error when trying
|
||||
# to resolve it, just fall back to something reasonable. See #26864.
|
||||
std_lib = std_home / "lib"
|
||||
Log(f"Resolving library directory '{App.getLibraryDir()}' failed, using fallback '{std_lib}'")
|
||||
Log(
|
||||
f"Resolving library directory '{App.getLibraryDir()}' failed, using fallback '{std_lib}'"
|
||||
)
|
||||
dir_mod_scanner = DirModScanner()
|
||||
ext_mod_scanner = ExtModScanner()
|
||||
search_paths = SearchPaths()
|
||||
@@ -1351,7 +1420,9 @@ class InitPipeline:
|
||||
|
||||
legacy_user_mod = Path.home() / ".FreeCAD" / "Mod"
|
||||
if legacy_user_mod.exists():
|
||||
Wrn (f"User path has changed to {user_home!s}. Please move user modules and macros")
|
||||
Wrn(
|
||||
f"User path has changed to {user_home!s}. Please move user modules and macros"
|
||||
)
|
||||
|
||||
# Libraries
|
||||
libraries = PathSet()
|
||||
@@ -1492,7 +1563,11 @@ class InitPipeline:
|
||||
def setup_tty(self) -> None:
|
||||
# Note: just checking whether stdin is a TTY is not enough, as the GUI is set up only after this
|
||||
# script has run. And checking only the RunMode is not enough, as we are maybe not interactive.
|
||||
if Config.RunMode == 'Cmd' and hasattr(sys.stdin, 'isatty') and sys.stdin.isatty():
|
||||
if (
|
||||
Config.RunMode == "Cmd"
|
||||
and hasattr(sys.stdin, "isatty")
|
||||
and sys.stdin.isatty()
|
||||
):
|
||||
utils.setup_tty_tab_completion()
|
||||
|
||||
def report(self) -> None:
|
||||
@@ -1510,12 +1585,16 @@ class InitPipeline:
|
||||
output.append(output[0])
|
||||
|
||||
for mod in self.dir_mod_scanner.iter():
|
||||
output.append(f"| {mod.name:<24.24} | {mod.state.name:<10.10} | {mod.init_mode:<6.6} | {mod.path!s}")
|
||||
output.append(
|
||||
f"| {mod.name:<24.24} | {mod.state.name:<10.10} | {mod.init_mode:<6.6} | {mod.path!s}"
|
||||
)
|
||||
for alt in mod.alternative_paths:
|
||||
output.append(f"| {' ':<24.24} | {' ':<10.10} | {' ':<6.6} | {alt!s}")
|
||||
|
||||
for mod in self.ext_mod_scanner.iter():
|
||||
output.append(f"| {mod.name:<24.24} | {mod.state.name:<10.10} | {mod.init_mode:<6.6} | {mod.name}")
|
||||
output.append(
|
||||
f"| {mod.name:<24.24} | {mod.state.name:<10.10} | {mod.init_mode:<6.6} | {mod.name}"
|
||||
)
|
||||
|
||||
for line in output:
|
||||
Log(line)
|
||||
@@ -1537,14 +1616,15 @@ class InitPipeline:
|
||||
# │ Init Applications │
|
||||
# └────────────────────────────────────────────────┘
|
||||
|
||||
|
||||
@transient
|
||||
@call_in_place
|
||||
def init_applications() -> None:
|
||||
try:
|
||||
InitPipeline().run()
|
||||
Log('Init: App::FreeCADInit.py done')
|
||||
Log("Init: App::FreeCADInit.py done")
|
||||
except Exception as ex:
|
||||
Err(f'Error in init_applications {ex!s}')
|
||||
Err(f"Error in init_applications {ex!s}")
|
||||
Err(utils.HLine)
|
||||
Err(traceback.format_exc())
|
||||
Err(utils.HLine)
|
||||
|
||||
@@ -35,13 +35,14 @@
|
||||
# | >>>> | FreeCADGuiInit | only if Gui is up |
|
||||
# +------+------------------+-----------------------------+
|
||||
|
||||
from enum import IntEnum, Enum
|
||||
from dataclasses import dataclass
|
||||
import importlib
|
||||
import re
|
||||
import traceback
|
||||
import typing
|
||||
import re
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum, IntEnum
|
||||
from pathlib import Path
|
||||
import importlib
|
||||
|
||||
import FreeCAD
|
||||
import FreeCADGui
|
||||
|
||||
@@ -104,11 +105,15 @@ class Workbench:
|
||||
ToolTip = ""
|
||||
Icon = None
|
||||
|
||||
__Workbench__: "Workbench" # Injected by FreeCAD, see: Application::activateWorkbench
|
||||
__Workbench__: (
|
||||
"Workbench" # Injected by FreeCAD, see: Application::activateWorkbench
|
||||
)
|
||||
|
||||
def Initialize(self):
|
||||
"""Initializes this workbench."""
|
||||
App.Console.PrintWarning(f"{self!s}: Workbench.Initialize() not implemented in subclass!")
|
||||
App.Console.PrintWarning(
|
||||
f"{self!s}: Workbench.Initialize() not implemented in subclass!"
|
||||
)
|
||||
|
||||
def ContextMenu(self, recipient):
|
||||
pass
|
||||
@@ -295,20 +300,24 @@ class DirModGui(ModGui):
|
||||
try:
|
||||
source = init_gui_py.read_text(encoding="utf-8")
|
||||
code = compile(source, init_gui_py, "exec")
|
||||
exec(code, {"__file__": str(init_gui_py)})
|
||||
exec(code, {**globals(), "__file__": str(init_gui_py)})
|
||||
except Exception as ex:
|
||||
sep = "-" * 100 + "\n"
|
||||
Log(f"Init: Initializing {target!s}... failed\n")
|
||||
Log(sep)
|
||||
Log(traceback.format_exc())
|
||||
Log(sep)
|
||||
Err(f'During initialization the error "{ex!s}" occurred in {init_gui_py!s}\n')
|
||||
Err(
|
||||
f'During initialization the error "{ex!s}" occurred in {init_gui_py!s}\n'
|
||||
)
|
||||
Err("Look into the log file for further information\n")
|
||||
else:
|
||||
Log(f"Init: Initializing {target!s}... done\n")
|
||||
return True
|
||||
else:
|
||||
Log(f"Init: Initializing {target!s} (InitGui.py not found)... ignore\n")
|
||||
Log(
|
||||
f"Init: Initializing {target!s} (InitGui.py not found)... ignore\n"
|
||||
)
|
||||
return False
|
||||
|
||||
def process_metadata(self) -> bool:
|
||||
@@ -325,7 +334,9 @@ class DirModGui(ModGui):
|
||||
if not workbench_metadata.supportsCurrentFreeCAD():
|
||||
continue
|
||||
|
||||
subdirectory = workbench_metadata.Subdirectory or workbench_metadata.Name
|
||||
subdirectory = (
|
||||
workbench_metadata.Subdirectory or workbench_metadata.Name
|
||||
)
|
||||
subdirectory = self.mod.path / Path(*re.split(r"[/\\]+", subdirectory))
|
||||
if not subdirectory.exists():
|
||||
continue
|
||||
@@ -373,7 +384,9 @@ class ExtModGui(ModGui):
|
||||
Err(f'During initialization the error "{ex!s}" occurred\n')
|
||||
except Exception as ex:
|
||||
sep = "-" * 80 + "\n"
|
||||
Err(f'During initialization the error "{ex!s}" occurred in {self.mod.name}\n')
|
||||
Err(
|
||||
f'During initialization the error "{ex!s}" occurred in {self.mod.name}\n'
|
||||
)
|
||||
Err(sep)
|
||||
Err(traceback.format_exc())
|
||||
Err(sep)
|
||||
@@ -400,9 +413,7 @@ def InitApplications():
|
||||
gui = mod_type(mod)
|
||||
gui.load()
|
||||
if mod.init_mode:
|
||||
row = (
|
||||
f"| {mod.name:<24.24} | {mod.state.name:<10.10} | {mod.init_mode:<6.6} |\n"
|
||||
)
|
||||
row = f"| {mod.name:<24.24} | {mod.state.name:<10.10} | {mod.init_mode:<6.6} |\n"
|
||||
output.append(row)
|
||||
|
||||
output = []
|
||||
@@ -457,7 +468,9 @@ FreeCAD.addImportType("Inventor V2.1 (*.iv *.IV)", "FreeCADGui")
|
||||
FreeCAD.addImportType(
|
||||
"VRML V2.0 (*.wrl *.WRL *.vrml *.VRML *.wrz *.WRZ *.wrl.gz *.WRL.GZ)", "FreeCADGui"
|
||||
)
|
||||
FreeCAD.addImportType("Python (*.py *.FCMacro *.FCScript *.fcmacro *.fcscript)", "FreeCADGui")
|
||||
FreeCAD.addImportType(
|
||||
"Python (*.py *.FCMacro *.FCScript *.fcmacro *.fcscript)", "FreeCADGui"
|
||||
)
|
||||
FreeCAD.addExportType("Inventor V2.1 (*.iv)", "FreeCADGui")
|
||||
FreeCAD.addExportType("VRML V2.0 (*.wrl *.vrml *.wrz *.wrl.gz)", "FreeCADGui")
|
||||
FreeCAD.addExportType("X3D Extensible 3D (*.x3d *.x3dz)", "FreeCADGui")
|
||||
|
||||
Reference in New Issue
Block a user