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