blender/release/scripts/modules/bpy/utils.py
Campbell Barton c672b3fb67 [#27439] Console window doesn't hide on startup when presets are used (Windows)
only print preset load messages in when debug is enabled.
2011-05-20 00:21:38 +00:00

496 lines
14 KiB
Python

# ##### BEGIN GPL LICENSE BLOCK #####
#
# 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 2
# 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, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
"""
This module contains utility functions specific to blender but
not assosiated with blenders internal data.
"""
from _bpy import register_class, unregister_class, blend_paths, resource_path
from _bpy import script_paths as _bpy_script_paths
from _bpy import user_resource as _user_resource
import bpy as _bpy
import os as _os
import sys as _sys
import addon_utils as _addon_utils
_script_module_dirs = "startup", "modules"
def _test_import(module_name, loaded_modules):
use_time = _bpy.app.debug
if module_name in loaded_modules:
return None
if "." in module_name:
print("Ignoring '%s', can't import files containing multiple periods." % module_name)
return None
if use_time:
import time
t = time.time()
try:
mod = __import__(module_name)
except:
import traceback
traceback.print_exc()
return None
if use_time:
print("time %s %.4f" % (module_name, time.time() - t))
loaded_modules.add(mod.__name__) # should match mod.__name__ too
return mod
def _sys_path_ensure(path):
if path not in _sys.path: # reloading would add twice
_sys.path.insert(0, path)
def modules_from_path(path, loaded_modules):
"""
Load all modules in a path and return them as a list.
:arg path: this path is scanned for scripts and packages.
:type path: string
:arg loaded_modules: already loaded module names, files matching these names will be ignored.
:type loaded_modules: set
:return: all loaded modules.
:rtype: list
"""
modules = []
for mod_name, mod_path in _bpy.path.module_names(path):
mod = _test_import(mod_name, loaded_modules)
if mod:
modules.append(mod)
return modules
_global_loaded_modules = [] # store loaded module names for reloading.
import bpy_types as _bpy_types # keep for comparisons, never ever reload this.
def load_scripts(reload_scripts=False, refresh_scripts=False):
"""
Load scripts and run each modules register function.
:arg reload_scripts: Causes all scripts to have their unregister method called before loading.
:type reload_scripts: bool
:arg refresh_scripts: only load scripts which are not already loaded as modules.
:type refresh_scripts: bool
"""
use_time = _bpy.app.debug
if use_time:
import time
t_main = time.time()
loaded_modules = set()
if refresh_scripts:
original_modules = _sys.modules.values()
if reload_scripts:
_bpy_types.TypeMap.clear()
# just unload, dont change user defaults, this means we can sync to reload.
# note that they will only actually reload of the modification time changes.
# this `wont` work for packages so... its not perfect.
for module_name in [ext.module for ext in _bpy.context.user_preferences.addons]:
_addon_utils.disable(module_name, default_set=False)
def register_module_call(mod):
register = getattr(mod, "register", None)
if register:
try:
register()
except:
import traceback
traceback.print_exc()
else:
print("\nWarning! '%s' has no register function, this is now a requirement for registerable scripts." % mod.__file__)
def unregister_module_call(mod):
unregister = getattr(mod, "unregister", None)
if unregister:
try:
unregister()
except:
import traceback
traceback.print_exc()
def test_reload(mod):
import imp
# reloading this causes internal errors
# because the classes from this module are stored internally
# possibly to refresh internal references too but for now, best not to.
if mod == _bpy_types:
return mod
try:
return imp.reload(mod)
except:
import traceback
traceback.print_exc()
def test_register(mod):
if refresh_scripts and mod in original_modules:
return
if reload_scripts and mod:
print("Reloading:", mod)
mod = test_reload(mod)
if mod:
register_module_call(mod)
_global_loaded_modules.append(mod.__name__)
if reload_scripts:
# module names -> modules
_global_loaded_modules[:] = [_sys.modules[mod_name] for mod_name in _global_loaded_modules]
# loop over and unload all scripts
_global_loaded_modules.reverse()
for mod in _global_loaded_modules:
unregister_module_call(mod)
for mod in _global_loaded_modules:
test_reload(mod)
_global_loaded_modules[:] = []
for base_path in script_paths():
for path_subdir in _script_module_dirs:
path = _os.path.join(base_path, path_subdir)
if _os.path.isdir(path):
_sys_path_ensure(path)
# only add this to sys.modules, dont run
if path_subdir == "modules":
continue
for mod in modules_from_path(path, loaded_modules):
test_register(mod)
# deal with addons separately
_addon_utils.reset_all(reload_scripts)
# run the active integration preset
filepath = preset_find(_bpy.context.user_preferences.inputs.active_keyconfig, "keyconfig")
if filepath:
keyconfig_set(filepath)
if reload_scripts:
import gc
print("gc.collect() -> %d" % gc.collect())
if use_time:
print("Python Script Load Time %.4f" % (time.time() - t_main))
# base scripts
_scripts = _os.path.join(_os.path.dirname(__file__), _os.path.pardir, _os.path.pardir)
_scripts = (_os.path.normpath(_scripts), )
def user_script_path():
path = _bpy.context.user_preferences.filepaths.script_directory
if path:
path = _os.path.normpath(path)
return path
else:
return None
def script_paths(subdir=None, user_pref=True, all=False):
"""
Returns a list of valid script paths.
:arg subdir: Optional subdir.
:type subdir: string
:arg user_pref: Include the user preference script path.
:type user_pref: bool
:arg all: Include local, user and system paths rather just the paths blender uses.
:type all: bool
:return: script paths.
:rtype: list
"""
scripts = list(_scripts)
# add user scripts dir
if user_pref:
user_script_path = _bpy.context.user_preferences.filepaths.script_directory
else:
user_script_path = None
if all:
# all possible paths
base_paths = tuple(_os.path.join(resource_path(res), "scripts") for res in ('LOCAL', 'USER', 'SYSTEM'))
else:
# only paths blender uses
base_paths = _bpy_script_paths()
for path in base_paths + (user_script_path, ):
if path:
path = _os.path.normpath(path)
if path not in scripts and _os.path.isdir(path):
scripts.append(path)
if subdir is None:
return scripts
script_paths = []
for path in scripts:
path_subdir = _os.path.join(path, subdir)
if _os.path.isdir(path_subdir):
script_paths.append(path_subdir)
return script_paths
def refresh_script_paths():
"""
Run this after creating new script paths to update sys.path
"""
for base_path in script_paths():
for path_subdir in _script_module_dirs:
path = _os.path.join(base_path, path_subdir)
if _os.path.isdir(path):
_sys_path_ensure(path)
for path in _addon_utils.paths():
_sys_path_ensure(path)
path = _os.path.join(path, "modules")
if _os.path.isdir(path):
_sys_path_ensure(path)
_presets = _os.path.join(_scripts[0], "presets") # FIXME - multiple paths
def preset_paths(subdir):
"""
Returns a list of paths for a specific preset.
"""
dirs = []
for path in script_paths("presets", all=True):
directory = _os.path.join(path, subdir)
if _os.path.isdir(directory):
dirs.append(directory)
return dirs
def smpte_from_seconds(time, fps=None):
"""
Returns an SMPTE formatted string from the time in seconds: "HH:MM:SS:FF".
If the *fps* is not given the current scene is used.
"""
import math
if fps is None:
fps = _bpy.context.scene.render.fps
hours = minutes = seconds = frames = 0
if time < 0:
time = - time
neg = "-"
else:
neg = ""
if time >= 3600.0: # hours
hours = int(time / 3600.0)
time = time % 3600.0
if time >= 60.0: # mins
minutes = int(time / 60.0)
time = time % 60.0
seconds = int(time)
frames = int(round(math.floor(((time - seconds) * fps))))
return "%s%02d:%02d:%02d:%02d" % (neg, hours, minutes, seconds, frames)
def smpte_from_frame(frame, fps=None, fps_base=None):
"""
Returns an SMPTE formatted string from the frame: "HH:MM:SS:FF".
If *fps* and *fps_base* are not given the current scene is used.
"""
if fps is None:
fps = _bpy.context.scene.render.fps
if fps_base is None:
fps_base = _bpy.context.scene.render.fps_base
return smpte_from_seconds((frame * fps_base) / fps, fps)
def preset_find(name, preset_path, display_name=False):
if not name:
return None
for directory in preset_paths(preset_path):
if display_name:
filename = ""
for fn in _os.listdir(directory):
if fn.endswith(".py") and name == _bpy.path.display_name(fn):
filename = fn
break
else:
filename = name + ".py"
if filename:
filepath = _os.path.join(directory, filename)
if _os.path.exists(filepath):
return filepath
def keyconfig_set(filepath):
from os.path import basename, splitext
if _bpy.app.debug:
print("loading preset:", filepath)
keyconfigs = _bpy.context.window_manager.keyconfigs
keyconfigs_old = keyconfigs[:]
try:
file = open(filepath)
exec(compile(file.read(), filepath, 'exec'), {"__file__": filepath})
file.close()
except:
import traceback
traceback.print_exc()
kc_new = [kc for kc in keyconfigs if kc not in keyconfigs_old][0]
kc_new.name = ""
# remove duplicates
name = splitext(basename(filepath))[0]
while True:
kc_dupe = keyconfigs.get(name)
if kc_dupe:
keyconfigs.remove(kc_dupe)
else:
break
kc_new.name = name
keyconfigs.active = kc_new
def user_resource(type, path="", create=False):
"""
Return a user resource path (normally from the users home directory).
:arg type: Resource type in ['DATAFILES', 'CONFIG', 'SCRIPTS', 'AUTOSAVE'].
:type type: string
:arg subdir: Optional subdirectory.
:type subdir: string
:arg create: Treat the path as a directory and create it if its not existing.
:type create: boolean
:return: a path.
:rtype: string
"""
target_path = _user_resource(type, path)
if create:
# should always be true.
if target_path:
# create path if not existing.
if not _os.path.exists(target_path):
try:
_os.makedirs(target_path)
except:
import traceback
traceback.print_exc()
target_path = ""
elif not _os.path.isdir(target_path):
print("Path %r found but isn't a directory!" % target_path)
target_path = ""
return target_path
def _bpy_module_classes(module, is_registered=False):
typemap_list = _bpy_types.TypeMap.get(module, ())
i = 0
while i < len(typemap_list):
cls_weakref = typemap_list[i]
cls = cls_weakref()
if cls is None:
del typemap_list[i]
else:
if is_registered == cls.is_registered:
yield cls
i += 1
def register_module(module, verbose=False):
if verbose:
print("bpy.utils.register_module(%r): ..." % module)
cls = None
for cls in _bpy_module_classes(module, is_registered=False):
if verbose:
print(" %r" % cls)
try:
register_class(cls)
except:
print("bpy.utils.register_module(): failed to registering class %r" % cls)
import traceback
traceback.print_exc()
if verbose:
print("done.\n")
if cls is None:
raise Exception("register_module(%r): defines no classes" % module)
def unregister_module(module, verbose=False):
if verbose:
print("bpy.utils.unregister_module(%r): ..." % module)
for cls in _bpy_module_classes(module, is_registered=True):
if verbose:
print(" %r" % cls)
try:
unregister_class(cls)
except:
print("bpy.utils.unregister_module(): failed to unregistering class %r" % cls)
import traceback
traceback.print_exc()
if verbose:
print("done.\n")