Source code for __init__

#
##
##  SPDX-FileCopyrightText: © 2007-2023 Benedict Verhegghe <bverheg@gmail.com>
##  SPDX-License-Identifier: GPL-3.0-or-later
##
##  This file is part of pyFormex 3.4  (Thu Nov 16 18:07:39 CET 2023)
##  pyFormex is a tool for generating, manipulating and transforming 3D
##  geometrical models by sequences of mathematical operations.
##  Home page: https://pyformex.org
##  Project page: https://savannah.nongnu.org/projects/pyformex/
##  Development: https://gitlab.com/bverheg/pyformex
##  Distributed under the GNU General Public License version 3 or later.
##
##  This program is free software: you can redistribute it and/or modify
##  it under the terms of the GNU General Public License as published by
##  the Free Software Foundation, either version 3 of the License, or
##  (at your option) any later version.
##
##  This program 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 General Public License for more details.
##
##  You should have received a copy of the GNU General Public License
##  along with this program.  If not, see http://www.gnu.org/licenses/.
##

"""pyFormex core module initialization.

This module initializes the pyFormex global variables and
defines a few essential functions.
It is the very first thing that is loaded when starting pyFormex.
"""
import os
import sys
import datetime
start_time = datetime.datetime.now()
__prog__ = "pyformex"
__version__ = "3.5.dev4"
__revision__ = __version__
__branch__ = "master"
Copyright = 'Copyright (C) 2004-2024 Benedict Verhegghe'
Url = 'http://pyformex.org'
Description = ("pyFormex is a tool for generating, manipulating and "
               "transforming large geometrical models of 3D structures "
               "by sequences of mathematical transformations.")


[docs]def Version(): """Return a string with the pyFormex name and version""" return f"pyFormex {__version__}"
[docs]def fullVersion(): """Return a string with the pyFormex name, version and revision""" if __revision__ == __version__: return Version() else: return f"{Version()} ({__revision__}) DEVELOPMENT VERSION"
startup_warnings = [] started = False # Fatal errors
[docs]class FatalError(Exception): """Raised on fatal or grave errors""" def __init__(self, msg): from inspect import cleandoc as dedent s = '\n' + '#' * 72 s = '\n## '.join([s] + dedent(msg).split('\n')) + s Exception.__init__(self, s)
[docs]class RequirementsError(FatalError): """Raised on missing or invalid requirements""" pass
[docs]class ImplementationError(FatalError): """Raised on clear implementation errors""" pass
######### Check Python version ############# # intended Python version # We support 3.8+ only, because we use self documenting f-strings _minimal_version = 0x03080000 _target_version = 0x030B0000 _numpy_version = '>=1.16'
[docs]def major(v): """Return the major component of version""" return v >> 24
[docs]def minor(v): """Return the minor component of version""" return (v & 0x00FF0000) >> 16
[docs]def human_version(v): """Return the human readable string for version""" return f"{major(v)}.{minor(v)}"
if sys.hexversion < _minimal_version: # Older than minimal y = human_version(sys.hexversion) m = human_version(_minimal_version) raise RequirementsError(f""" Your Python version is {y}, but pyFormex requires Python >= {m}. We advice you to upgrade your Python version. """) if sys.hexversion & 0xFFFF0000 > _target_version: # Major,minor newer than target: y = human_version(sys.hexversion) t = human_version(_target_version) startup_warnings.append(f""" Your Python version is {y}, but {Version()} has only been tested with Python <= {t}. We expect {Version()} to run correctly with your Python version, but if you encounter problems, please contact the developers at http://pyformex.org. """) ################################################################### ## Load Path class ################## from pyformex.path import Path # noqa: E402 # Placeholder for excutable, if pyformex: filled in by startup.py executable = Path(sys.executable) #### Detect install type ######## # Install type. # This can have the following values: # 'G' : unreleased version running from GIT sources: no installation # required. This is set if the directory containing the # pyformex start script is a git repository. # 'U' : normal (source) Release, i.e. tarball installed as user. # Installation is done with 'python setup.py install' # in the unpacked source. # The builtin pyFormex removal is activated. # 'R' : normal (source) Release, i.e. tarball (default). Installation # is done with 'python setup.py install' in the unpacked source. # The builtin pyFormex removal is activated. # 'D' : Distribution package of a released version, official or not # (e.g. Debian package). The distribution package tools should # be used to install/uninstall. The builtin pyFormex removal is # deactivated. # installtype = 'R' pyformexdir = Path(__file__).parent if (pyformexdir.parent / '.git').exists(): installtype = 'G' if installtype == 'R': if os.getuid() != 0 and (pyformexdir / 'doc').is_writable_dir(): installtype = 'U' ########## start the logger #################### # TODO: this is work in progress # import logging # noqa: E402 # LOGGING = { # 'version': 1, # 'disable_existing_loggers': False, # 'formatters': { # 'default': { # 'format': "%(asctime)s - %(name)s - %(levelname)s - %(message)s" # }, # 'short': { # 'format': "%(asctime)s %(message)s", # }, # 'detail': { # 'format': "%(asctime)s::%(levelname)s::%(name)s::" # "%(filename)s::%(lineno)d::%(message)s", # }, # }, # 'handlers': { # 'console': { # 'class': 'logging.StreamHandler', # 'formatter': 'detail', # }, # # 'memory': { # # 'class': 'logging.handlers.MemoryHandler', # # 'capacity': 100, # # 'formatter': 'default', # # }, # # TODO: activate logfile, but we cant use pf.cfg yet! # # maybe first use memory logger and later switch to file logger # # 'logfile': { # # 'level':'DEBUG', # # 'class':'logging.handlers.RotatingFileHandler', # # 'filename': pf.cfg['logfile'], # # 'maxBytes': 1024*1024*5, # 5 MB # # 'backupCount': 100, # # 'formatter':'standard', # # }, # }, # 'loggers': { # 'pyformex': { # 'level': 'INFO', # 'handlers': ['console'], # } # }, # } # import logging.config # noqa: E402 # logging.config.dictConfig(LOGGING) # logger = logging.getLogger('pyformex') # logger.info(f"{fullVersion()} start on Python{human_version(sys.hexversion)}") ########## special global flags ############## # Are we running tests? pytest = 'pytest' in sys.modules doctest = False # Are we importing from Sphinx doc generator? sphinx = 'sphinx' in sys.modules ########## acceleration libraries ############## libraries = ['misc_c', 'nurbs_c', 'clust_c'] ########## Special adjustements for source versions ############ if installtype == 'G': # We are running from a source tree (Git) import importlib.machinery
[docs] def run_cmd(cmd): """Run command""" import subprocess # print(f"{cmd=}") P = subprocess.Popen( str(cmd), stdout=subprocess.PIPE, shell=True, universal_newlines=True ) out, err = P.communicate() if P.returncode: print(out) print(err) return P.returncode, out, err
# Clean the source tree source_clean = pyformexdir.parent / 'source_clean' if source_clean.exists(): try: suffix = importlib.machinery.EXTENSION_SUFFIXES[0] cmd = f"{source_clean} --keep_modules '*{suffix}'" run_cmd(cmd) except Exception as e: print(str(e)) print(f"Error while executing {source_clean}\n" "Ignore error and continue") # Running from source tree: make sure the compiled libraries are up-to-date libdir = pyformexdir / 'lib' def checkLibraries(): # find extension used by compiled library (.so) import sysconfig ext = sysconfig.get_config_var('EXT_SUFFIX') if ext is None: ext = '.so' msg = '' for lib in libraries: ver = libdir / 'version.h' src = libdir / lib + '.c' obj = libdir / lib + ext if not obj.exists() or obj.mtime < src.mtime or obj.mtime < ver.mtime: msg += f"\nThe compiled library '{lib}' is not up to date!" return msg msg = checkLibraries() if msg and not sphinx: print(msg) print("Rebuilding pyFormex libraries, please wait") cmd = f"cd {pyformexdir.parent}; PYTHON={sys.executable} make lib" run_cmd(cmd) msg = checkLibraries() if msg: # No success msg += f""" I had a problem rebuilding the libraries in {libdir}. You should probably exit pyFormex, fix the problem and then restart pyFormex. """ if msg: startup_warnings.append(msg) # Set the proper revision number when running from git sources def set_revision(): global __revision__ cmd = f"cd {pyformexdir} && git describe --always" sta, out, err = run_cmd(cmd) if sta == 0: __revision__ = out.split('\n')[0].strip() else: print("Could not set revision") def set_branch(): global __branch__ cmd = f"cd {pyformexdir} && git rev-parse --abbrev-ref HEAD" sta, out, err = run_cmd(cmd) if sta == 0: __branch__ = out.split('\n')[0] else: print("Could not set branch name") set_revision() set_branch() # logger.info(f"This is {fullVersion()}") # logger.info(f"Installation type {installtype} at {pyformexdir}") ################ Special import mechanisms ############## def import_lazy(name): import importlib.util spec = importlib.util.find_spec(name) loader = importlib.util.LazyLoader(spec.loader) spec.loader = loader module = importlib.util.module_from_spec(spec) sys.modules[name] = module loader.exec_module(module) return module def import_direct(file): import importlib.util import tokenize file_path = tokenize.__file__ module_name = tokenize.__name__ spec = importlib.util.spec_from_file_location(module_name, file_path) module = importlib.util.module_from_spec(spec) sys.modules[module_name] = module spec.loader.exec_module(module) return module ################ pyFormex global variables and functions ############### # The GUI parts app_started = False interactive = False app = None # the QApplication GUI = None # the GUI QMainWindow canvas = None # the OpenGL Drawing widget controlled by the running script console = None # alternate Python console # initialize some global variables used for communication between modules # the options found on the command line # this is replaced with the options read from the command line # but we define it here with an attribute debuglevel, so that we can # use debug (unintentionally) before the debuglevel is set # Important pyFormex globals PF = {} # explicitely exported globals GS = None # only set in gui mode scriptName = None scriptlock = set() scriptMode = None # print function allowing a (nonfunctional) 'color' argument def _print_col(*args, color='black', **kargs): print(*args, **kargs) # Error printing in nongui mode def _print_err(*args, **kargs): file = kargs.pop('file', sys.stderr) print(*args, **kargs, file=file) printc = _print_col warning = _print_err error = _print_err def _busy(state=True): """Flag a busy state to the user""" if state: print("This may take some time...")
[docs]def busy(state=True): """Flag a busy state to the user""" if GUI: GUI.setBusy(state) else: _busy(state)
on_exit = [] # functions to run on exit
[docs]def onExit(func): """Register a function to be called on exit from pyFormex""" if callable(func): on_exit.append(func) else: raise ValueError("Expected a callable")
###################################################################### ### DEBUG items ################################ from enum import Flag # noqa: E402
[docs]class DEBUG(Flag): """A class with debug items. This class holds the defined debug items as attributes. Each debug topic is a binary value with a single bit set (a power of 2). Debug items can be combined with & (AND), | (OR) and ^ (XOR). Two extra values are defined: NONE switches off all debugging, ALL activates all debug items. """ NONE = 0 (INFO, WARNING, CONFIG, DETECT, HELP, MEM, SCRIPT, GUI, MENU, DRAW, CANVAS, OPENGL, LIB, MOUSE, APPS, IMAGE, PICK, MISC, ABQ, WIDGET, PROJECT, WEBGL, MULTI, LEGACY, PLUGIN, UNICODE, FONT, PGF, VTK, DOCTEST, ) = [2**i for i in range(30)] ALL = -1
[docs] @classmethod def item(cls, name): """Convert a string to a DEBUG item The string is case insensitive. Raises ------ ValueError If the name is not a DEBUG item. """ try: return cls[name.upper()] except KeyError: raise ValueError(f"No such {cls.__name__} item: {name}")
[docs] @classmethod def level(cls, names): """Return the combined debug level for a list of debug items Raises ------ ValueError If any of the names is not a DEBUG item. """ from operator import or_ from functools import reduce return reduce(or_, [cls.item(name) for name in names])
class options: debuglevel = DEBUG.NONE # Make sure we can use options.debuglevel verbose = 1 # Make sure we can use uselib = False # initial default to keep sphinx happy
[docs]def debugon(topics): """Return True if any of specified debug topics is in options.debuglevel. Parameters ---------- topics: :class:`DEBUG` A DEBUG topic or any combination of them. Returns ------- bool: True if any of the flags in topics is in options.debuglevel. This is effectively the bool value of the binary AND of topics and options.debuglevel. Note ---- If you need to test that multiple flags are in options.debuglevel, use ``debugon(flag1) & debugon(flag2)`` Examples -------- >>> options.debuglevel = DEBUG.INFO | DEBUG.OPENGL | DEBUG.GUI | DEBUG.MENU >>> debugon(DEBUG.WARNING | DEBUG.OPENGL) True >>> debugon(DEBUG.WARNING & DEBUG.OPENGL) False >>> debugon(DEBUG.WARNING & DEBUG.OPENGL | DEBUG.GUI) True >>> debugon(DEBUG.OPENGL) & debugon(DEBUG.GUI) True >>> debugon(DEBUG.ALL) True >>> options.debuglevel = DEBUG.NONE >>> debugon(DEBUG.ALL) False """ return bool(options.debuglevel & topics)
# Deprecated 2023-12-04 def debug(s, level=DEBUG.ALL): from pyformex import utils utils.warn('depr_debug', stacklevel=3) if debugon(level): print(f"{level}: {s}")
[docs]def verbosity(verbose): """Check that verbosity is equal or higher than given value""" return options.verbose >= verbose
# Deprecated 2023-12-04
[docs]def verbose(verbose, *args, **kargs): """Print a message if the verbosity is high enough""" from pyformex import utils utils.warn('depr_verbose', stacklevel=3) if verbosity(verbose): print(*args, **kargs)
[docs]def run(args): """Run pyFormex from a Python shell Parameters ---------- args: str The arguments for pyformex like thy would be entered on the pyformex command line in a shell. """ import shlex from pyformex import main # We do not return a value here: it is only used when pyformex is # imported in a Python shell main.run(shlex.split(args))
#################################################################### ### Load factory configuration ############################### from pyformex.config import Config # noqa: E402 # Create a config instance cfg = Config() cfg['pyformexdir'] = pyformexdir # load the factory defaults defaults = cfg['pyformexdir'] / 'pyformexrc' cfg.load(defaults) # The default user config path (do not change!) cfg['userprefs'] = cfg['userconfdir'] / 'pyformex.conf' # Set the current session configurations in pyformex module prefcfg = None # the preferenced configuration refcfg = None # the reference configuration preffile = None # the file where the preferenced configuration will be saved # End