Re-design of submodules used in blender.git
This commit implements described in the #104573. The goal is to fix the confusion of the submodule hashes change, which are not ideal for any of the supported git-module configuration (they are either always visible causing confusion, or silently staged and committed, also causing confusion). This commit replaces submodules with a checkout of addons and addons_contrib, covered by the .gitignore, and locale and developer tools are moved to the main repository. This also changes the paths: - /release/scripts are moved to the /scripts - /source/tools are moved to the /tools - /release/datafiles/locale is moved to /locale This is done to avoid conflicts when using bisect, and also allow buildbot to automatically "recover" wgen building older or newer branches/patches. Running `make update` will initialize the local checkout to the changed repository configuration. Another aspect of the change is that the make update will support Github style of remote organization (origin remote pointing to thy fork, upstream remote pointing to the upstream blender/blender.git). Pull Request #104755
This commit is contained in:
committed by
Sergey Sharybin
parent
3e721195b0
commit
03806d0b67
534
scripts/modules/addon_utils.py
Normal file
534
scripts/modules/addon_utils.py
Normal file
@@ -0,0 +1,534 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"paths",
|
||||
"modules",
|
||||
"check",
|
||||
"enable",
|
||||
"disable",
|
||||
"disable_all",
|
||||
"reset_all",
|
||||
"module_bl_info",
|
||||
)
|
||||
|
||||
import bpy as _bpy
|
||||
_preferences = _bpy.context.preferences
|
||||
|
||||
error_encoding = False
|
||||
# (name, file, path)
|
||||
error_duplicates = []
|
||||
addons_fake_modules = {}
|
||||
|
||||
|
||||
# called only once at startup, avoids calling 'reset_all', correct but slower.
|
||||
def _initialize():
|
||||
path_list = paths()
|
||||
for path in path_list:
|
||||
_bpy.utils._sys_path_ensure_append(path)
|
||||
for addon in _preferences.addons:
|
||||
enable(addon.module)
|
||||
|
||||
|
||||
def paths():
|
||||
# RELEASE SCRIPTS: official scripts distributed in Blender releases
|
||||
addon_paths = _bpy.utils.script_paths(subdir="addons")
|
||||
|
||||
# CONTRIB SCRIPTS: good for testing but not official scripts yet
|
||||
# if folder addons_contrib/ exists, scripts in there will be loaded too
|
||||
addon_paths += _bpy.utils.script_paths(subdir="addons_contrib")
|
||||
|
||||
return addon_paths
|
||||
|
||||
|
||||
def modules_refresh(*, module_cache=addons_fake_modules):
|
||||
global error_encoding
|
||||
import os
|
||||
|
||||
error_encoding = False
|
||||
error_duplicates.clear()
|
||||
|
||||
path_list = paths()
|
||||
|
||||
# fake module importing
|
||||
def fake_module(mod_name, mod_path, speedy=True, force_support=None):
|
||||
global error_encoding
|
||||
|
||||
if _bpy.app.debug_python:
|
||||
print("fake_module", mod_path, mod_name)
|
||||
import ast
|
||||
ModuleType = type(ast)
|
||||
try:
|
||||
file_mod = open(mod_path, "r", encoding='UTF-8')
|
||||
except OSError as ex:
|
||||
print("Error opening file:", mod_path, ex)
|
||||
return None
|
||||
|
||||
with file_mod:
|
||||
if speedy:
|
||||
lines = []
|
||||
line_iter = iter(file_mod)
|
||||
l = ""
|
||||
while not l.startswith("bl_info"):
|
||||
try:
|
||||
l = line_iter.readline()
|
||||
except UnicodeDecodeError as ex:
|
||||
if not error_encoding:
|
||||
error_encoding = True
|
||||
print("Error reading file as UTF-8:", mod_path, ex)
|
||||
return None
|
||||
|
||||
if len(l) == 0:
|
||||
break
|
||||
while l.rstrip():
|
||||
lines.append(l)
|
||||
try:
|
||||
l = line_iter.readline()
|
||||
except UnicodeDecodeError as ex:
|
||||
if not error_encoding:
|
||||
error_encoding = True
|
||||
print("Error reading file as UTF-8:", mod_path, ex)
|
||||
return None
|
||||
|
||||
data = "".join(lines)
|
||||
|
||||
else:
|
||||
data = file_mod.read()
|
||||
del file_mod
|
||||
|
||||
try:
|
||||
ast_data = ast.parse(data, filename=mod_path)
|
||||
except:
|
||||
print("Syntax error 'ast.parse' can't read:", repr(mod_path))
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
ast_data = None
|
||||
|
||||
body_info = None
|
||||
|
||||
if ast_data:
|
||||
for body in ast_data.body:
|
||||
if body.__class__ == ast.Assign:
|
||||
if len(body.targets) == 1:
|
||||
if getattr(body.targets[0], "id", "") == "bl_info":
|
||||
body_info = body
|
||||
break
|
||||
|
||||
if body_info:
|
||||
try:
|
||||
mod = ModuleType(mod_name)
|
||||
mod.bl_info = ast.literal_eval(body.value)
|
||||
mod.__file__ = mod_path
|
||||
mod.__time__ = os.path.getmtime(mod_path)
|
||||
except:
|
||||
print("AST error parsing bl_info for:", repr(mod_path))
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
if force_support is not None:
|
||||
mod.bl_info["support"] = force_support
|
||||
|
||||
return mod
|
||||
else:
|
||||
print(
|
||||
"fake_module: addon missing 'bl_info' "
|
||||
"gives bad performance!:",
|
||||
repr(mod_path),
|
||||
)
|
||||
return None
|
||||
|
||||
modules_stale = set(module_cache.keys())
|
||||
|
||||
for path in path_list:
|
||||
|
||||
# force all contrib addons to be 'TESTING'
|
||||
if path.endswith(("addons_contrib", )):
|
||||
force_support = 'TESTING'
|
||||
else:
|
||||
force_support = None
|
||||
|
||||
for mod_name, mod_path in _bpy.path.module_names(path):
|
||||
modules_stale.discard(mod_name)
|
||||
mod = module_cache.get(mod_name)
|
||||
if mod:
|
||||
if mod.__file__ != mod_path:
|
||||
print(
|
||||
"multiple addons with the same name:\n"
|
||||
" %r\n"
|
||||
" %r" % (mod.__file__, mod_path)
|
||||
)
|
||||
error_duplicates.append((mod.bl_info["name"], mod.__file__, mod_path))
|
||||
|
||||
elif mod.__time__ != os.path.getmtime(mod_path):
|
||||
print(
|
||||
"reloading addon:",
|
||||
mod_name,
|
||||
mod.__time__,
|
||||
os.path.getmtime(mod_path),
|
||||
repr(mod_path),
|
||||
)
|
||||
del module_cache[mod_name]
|
||||
mod = None
|
||||
|
||||
if mod is None:
|
||||
mod = fake_module(
|
||||
mod_name,
|
||||
mod_path,
|
||||
force_support=force_support,
|
||||
)
|
||||
if mod:
|
||||
module_cache[mod_name] = mod
|
||||
|
||||
# just in case we get stale modules, not likely
|
||||
for mod_stale in modules_stale:
|
||||
del module_cache[mod_stale]
|
||||
del modules_stale
|
||||
|
||||
|
||||
def modules(*, module_cache=addons_fake_modules, refresh=True):
|
||||
if refresh or ((module_cache is addons_fake_modules) and modules._is_first):
|
||||
modules_refresh(module_cache=module_cache)
|
||||
modules._is_first = False
|
||||
|
||||
mod_list = list(module_cache.values())
|
||||
mod_list.sort(
|
||||
key=lambda mod: (
|
||||
mod.bl_info.get("category", ""),
|
||||
mod.bl_info.get("name", ""),
|
||||
)
|
||||
)
|
||||
return mod_list
|
||||
|
||||
|
||||
modules._is_first = True
|
||||
|
||||
|
||||
def check(module_name):
|
||||
"""
|
||||
Returns the loaded state of the addon.
|
||||
|
||||
:arg module_name: The name of the addon and module.
|
||||
:type module_name: string
|
||||
:return: (loaded_default, loaded_state)
|
||||
:rtype: tuple of booleans
|
||||
"""
|
||||
import sys
|
||||
loaded_default = module_name in _preferences.addons
|
||||
|
||||
mod = sys.modules.get(module_name)
|
||||
loaded_state = (
|
||||
(mod is not None) and
|
||||
getattr(mod, "__addon_enabled__", Ellipsis)
|
||||
)
|
||||
|
||||
if loaded_state is Ellipsis:
|
||||
print(
|
||||
"Warning: addon-module", module_name, "found module "
|
||||
"but without '__addon_enabled__' field, "
|
||||
"possible name collision from file:",
|
||||
repr(getattr(mod, "__file__", "<unknown>")),
|
||||
)
|
||||
|
||||
loaded_state = False
|
||||
|
||||
if mod and getattr(mod, "__addon_persistent__", False):
|
||||
loaded_default = True
|
||||
|
||||
return loaded_default, loaded_state
|
||||
|
||||
# utility functions
|
||||
|
||||
|
||||
def _addon_ensure(module_name):
|
||||
addons = _preferences.addons
|
||||
addon = addons.get(module_name)
|
||||
if not addon:
|
||||
addon = addons.new()
|
||||
addon.module = module_name
|
||||
|
||||
|
||||
def _addon_remove(module_name):
|
||||
addons = _preferences.addons
|
||||
|
||||
while module_name in addons:
|
||||
addon = addons.get(module_name)
|
||||
if addon:
|
||||
addons.remove(addon)
|
||||
|
||||
|
||||
def enable(module_name, *, default_set=False, persistent=False, handle_error=None):
|
||||
"""
|
||||
Enables an addon by name.
|
||||
|
||||
:arg module_name: the name of the addon and module.
|
||||
:type module_name: string
|
||||
:arg default_set: Set the user-preference.
|
||||
:type default_set: bool
|
||||
:arg persistent: Ensure the addon is enabled for the entire session (after loading new files).
|
||||
:type persistent: bool
|
||||
:arg handle_error: Called in the case of an error, taking an exception argument.
|
||||
:type handle_error: function
|
||||
:return: the loaded module or None on failure.
|
||||
:rtype: module
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
from bpy_restrict_state import RestrictBlend
|
||||
|
||||
if handle_error is None:
|
||||
def handle_error(_ex):
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# reload if the mtime changes
|
||||
mod = sys.modules.get(module_name)
|
||||
# chances of the file _not_ existing are low, but it could be removed
|
||||
if mod and os.path.exists(mod.__file__):
|
||||
|
||||
if getattr(mod, "__addon_enabled__", False):
|
||||
# This is an unlikely situation,
|
||||
# re-register if the module is enabled.
|
||||
# Note: the UI doesn't allow this to happen,
|
||||
# in most cases the caller should 'check()' first.
|
||||
try:
|
||||
mod.unregister()
|
||||
except Exception as ex:
|
||||
print(
|
||||
"Exception in module unregister():",
|
||||
repr(getattr(mod, "__file__", module_name)),
|
||||
)
|
||||
handle_error(ex)
|
||||
return None
|
||||
|
||||
mod.__addon_enabled__ = False
|
||||
mtime_orig = getattr(mod, "__time__", 0)
|
||||
mtime_new = os.path.getmtime(mod.__file__)
|
||||
if mtime_orig != mtime_new:
|
||||
import importlib
|
||||
print("module changed on disk:", repr(mod.__file__), "reloading...")
|
||||
|
||||
try:
|
||||
importlib.reload(mod)
|
||||
except Exception as ex:
|
||||
handle_error(ex)
|
||||
del sys.modules[module_name]
|
||||
return None
|
||||
mod.__addon_enabled__ = False
|
||||
|
||||
# add the addon first it may want to initialize its own preferences.
|
||||
# must remove on fail through.
|
||||
if default_set:
|
||||
_addon_ensure(module_name)
|
||||
|
||||
# Split registering up into 3 steps so we can undo
|
||||
# if it fails par way through.
|
||||
|
||||
# Disable the context: using the context at all
|
||||
# while loading an addon is really bad, don't do it!
|
||||
with RestrictBlend():
|
||||
|
||||
# 1) try import
|
||||
try:
|
||||
mod = __import__(module_name)
|
||||
if mod.__file__ is None:
|
||||
# This can happen when the addon has been removed but there are
|
||||
# residual `.pyc` files left behind.
|
||||
raise ImportError(name=module_name)
|
||||
mod.__time__ = os.path.getmtime(mod.__file__)
|
||||
mod.__addon_enabled__ = False
|
||||
except Exception as ex:
|
||||
# if the addon doesn't exist, don't print full traceback
|
||||
if type(ex) is ImportError and ex.name == module_name:
|
||||
print("addon not loaded:", repr(module_name))
|
||||
print("cause:", str(ex))
|
||||
else:
|
||||
handle_error(ex)
|
||||
|
||||
if default_set:
|
||||
_addon_remove(module_name)
|
||||
return None
|
||||
|
||||
# 1.1) Fail when add-on is too old.
|
||||
# This is a temporary 2.8x migration check, so we can manage addons that are supported.
|
||||
|
||||
if mod.bl_info.get("blender", (0, 0, 0)) < (2, 80, 0):
|
||||
if _bpy.app.debug:
|
||||
print("Warning: Add-on '%s' was not upgraded for 2.80, ignoring" % module_name)
|
||||
return None
|
||||
|
||||
# 2) Try register collected modules.
|
||||
# Removed register_module, addons need to handle their own registration now.
|
||||
|
||||
from _bpy import _bl_owner_id_get, _bl_owner_id_set
|
||||
owner_id_prev = _bl_owner_id_get()
|
||||
_bl_owner_id_set(module_name)
|
||||
|
||||
# 3) Try run the modules register function.
|
||||
try:
|
||||
mod.register()
|
||||
except Exception as ex:
|
||||
print(
|
||||
"Exception in module register():",
|
||||
getattr(mod, "__file__", module_name),
|
||||
)
|
||||
handle_error(ex)
|
||||
del sys.modules[module_name]
|
||||
if default_set:
|
||||
_addon_remove(module_name)
|
||||
return None
|
||||
finally:
|
||||
_bl_owner_id_set(owner_id_prev)
|
||||
|
||||
# * OK loaded successfully! *
|
||||
mod.__addon_enabled__ = True
|
||||
mod.__addon_persistent__ = persistent
|
||||
|
||||
if _bpy.app.debug_python:
|
||||
print("\taddon_utils.enable", mod.__name__)
|
||||
|
||||
return mod
|
||||
|
||||
|
||||
def disable(module_name, *, default_set=False, handle_error=None):
|
||||
"""
|
||||
Disables an addon by name.
|
||||
|
||||
:arg module_name: The name of the addon and module.
|
||||
:type module_name: string
|
||||
:arg default_set: Set the user-preference.
|
||||
:type default_set: bool
|
||||
:arg handle_error: Called in the case of an error, taking an exception argument.
|
||||
:type handle_error: function
|
||||
"""
|
||||
import sys
|
||||
|
||||
if handle_error is None:
|
||||
def handle_error(_ex):
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
mod = sys.modules.get(module_name)
|
||||
|
||||
# possible this addon is from a previous session and didn't load a
|
||||
# module this time. So even if the module is not found, still disable
|
||||
# the addon in the user prefs.
|
||||
if mod and getattr(mod, "__addon_enabled__", False) is not False:
|
||||
mod.__addon_enabled__ = False
|
||||
mod.__addon_persistent = False
|
||||
|
||||
try:
|
||||
mod.unregister()
|
||||
except Exception as ex:
|
||||
mod_path = getattr(mod, "__file__", module_name)
|
||||
print("Exception in module unregister():", repr(mod_path))
|
||||
del mod_path
|
||||
handle_error(ex)
|
||||
else:
|
||||
print(
|
||||
"addon_utils.disable: %s not %s" % (
|
||||
module_name,
|
||||
"disabled" if mod is None else "loaded")
|
||||
)
|
||||
|
||||
# could be in more than once, unlikely but better do this just in case.
|
||||
if default_set:
|
||||
_addon_remove(module_name)
|
||||
|
||||
if _bpy.app.debug_python:
|
||||
print("\taddon_utils.disable", module_name)
|
||||
|
||||
|
||||
def reset_all(*, reload_scripts=False):
|
||||
"""
|
||||
Sets the addon state based on the user preferences.
|
||||
"""
|
||||
import sys
|
||||
|
||||
# initializes addons_fake_modules
|
||||
modules_refresh()
|
||||
|
||||
# RELEASE SCRIPTS: official scripts distributed in Blender releases
|
||||
paths_list = paths()
|
||||
|
||||
for path in paths_list:
|
||||
_bpy.utils._sys_path_ensure_append(path)
|
||||
for mod_name, _mod_path in _bpy.path.module_names(path):
|
||||
is_enabled, is_loaded = check(mod_name)
|
||||
|
||||
# first check if reload is needed before changing state.
|
||||
if reload_scripts:
|
||||
import importlib
|
||||
mod = sys.modules.get(mod_name)
|
||||
if mod:
|
||||
importlib.reload(mod)
|
||||
|
||||
if is_enabled == is_loaded:
|
||||
pass
|
||||
elif is_enabled:
|
||||
enable(mod_name)
|
||||
elif is_loaded:
|
||||
print("\taddon_utils.reset_all unloading", mod_name)
|
||||
disable(mod_name)
|
||||
|
||||
|
||||
def disable_all():
|
||||
import sys
|
||||
# Collect modules to disable first because dict can be modified as we disable.
|
||||
addon_modules = [
|
||||
item for item in sys.modules.items()
|
||||
if getattr(item[1], "__addon_enabled__", False)
|
||||
]
|
||||
# Check the enabled state again since it's possible the disable call
|
||||
# of one add-on disables others.
|
||||
for mod_name, mod in addon_modules:
|
||||
if getattr(mod, "__addon_enabled__", False):
|
||||
disable(mod_name)
|
||||
|
||||
|
||||
def _blender_manual_url_prefix():
|
||||
return "https://docs.blender.org/manual/%s/%d.%d" % (_bpy.utils.manual_language_code(), *_bpy.app.version[:2])
|
||||
|
||||
|
||||
def module_bl_info(mod, *, info_basis=None):
|
||||
if info_basis is None:
|
||||
info_basis = {
|
||||
"name": "",
|
||||
"author": "",
|
||||
"version": (),
|
||||
"blender": (),
|
||||
"location": "",
|
||||
"description": "",
|
||||
"doc_url": "",
|
||||
"support": 'COMMUNITY',
|
||||
"category": "",
|
||||
"warning": "",
|
||||
"show_expanded": False,
|
||||
}
|
||||
|
||||
addon_info = getattr(mod, "bl_info", {})
|
||||
|
||||
# avoid re-initializing
|
||||
if "_init" in addon_info:
|
||||
return addon_info
|
||||
|
||||
if not addon_info:
|
||||
mod.bl_info = addon_info
|
||||
|
||||
for key, value in info_basis.items():
|
||||
addon_info.setdefault(key, value)
|
||||
|
||||
if not addon_info["name"]:
|
||||
addon_info["name"] = mod.__name__
|
||||
|
||||
doc_url = addon_info["doc_url"]
|
||||
if doc_url:
|
||||
doc_url_prefix = "{BLENDER_MANUAL_URL}"
|
||||
if doc_url_prefix in doc_url:
|
||||
addon_info["doc_url"] = doc_url.replace(
|
||||
doc_url_prefix,
|
||||
_blender_manual_url_prefix(),
|
||||
)
|
||||
|
||||
addon_info["_init"] = None
|
||||
return addon_info
|
||||
210
scripts/modules/animsys_refactor.py
Normal file
210
scripts/modules/animsys_refactor.py
Normal file
@@ -0,0 +1,210 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module has utility functions for renaming
|
||||
rna values in fcurves and drivers.
|
||||
|
||||
Currently unused, but might become useful later again.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import bpy
|
||||
|
||||
|
||||
IS_TESTING = False
|
||||
|
||||
|
||||
def classes_recursive(base_type, clss=None):
|
||||
if clss is None:
|
||||
clss = [base_type]
|
||||
else:
|
||||
clss.append(base_type)
|
||||
|
||||
for base_type_iter in base_type.__bases__:
|
||||
if base_type_iter is not object:
|
||||
classes_recursive(base_type_iter, clss)
|
||||
|
||||
return clss
|
||||
|
||||
|
||||
class DataPathBuilder:
|
||||
"""Dummy class used to parse fcurve and driver data paths."""
|
||||
__slots__ = ("data_path", )
|
||||
|
||||
def __init__(self, attrs):
|
||||
self.data_path = attrs
|
||||
|
||||
def __getattr__(self, attr):
|
||||
str_value = ".%s" % attr
|
||||
return DataPathBuilder(self.data_path + (str_value, ))
|
||||
|
||||
def __getitem__(self, key):
|
||||
if type(key) is int:
|
||||
str_value = '[%d]' % key
|
||||
elif type(key) is str:
|
||||
str_value = '["%s"]' % bpy.utils.escape_identifier(key)
|
||||
else:
|
||||
raise Exception("unsupported accessor %r of type %r (internal error)" % (key, type(key)))
|
||||
return DataPathBuilder(self.data_path + (str_value, ))
|
||||
|
||||
def resolve(self, real_base, rna_update_from_map, fcurve, log):
|
||||
"""Return (attribute, value) pairs."""
|
||||
pairs = []
|
||||
base = real_base
|
||||
for item in self.data_path:
|
||||
if base is not Ellipsis:
|
||||
base_new = Ellipsis
|
||||
# find the new name
|
||||
if item.startswith("."):
|
||||
for class_name, item_new, options in (
|
||||
rna_update_from_map.get(item[1:], []) +
|
||||
[(None, item[1:], None)]
|
||||
):
|
||||
if callable(item_new):
|
||||
# No type check here, callback is assumed to know what it's doing.
|
||||
base_new, item_new = item_new(base, class_name, item[1:], fcurve, options)
|
||||
if base_new is not Ellipsis:
|
||||
break # found, don't keep looking
|
||||
else:
|
||||
# Type check!
|
||||
type_ok = True
|
||||
if class_name is not None:
|
||||
type_ok = False
|
||||
for base_type in classes_recursive(type(base)):
|
||||
if base_type.__name__ == class_name:
|
||||
type_ok = True
|
||||
break
|
||||
if type_ok:
|
||||
try:
|
||||
#print("base." + item_new)
|
||||
base_new = eval("base." + item_new)
|
||||
break # found, don't keep looking
|
||||
except:
|
||||
pass
|
||||
item_new = "." + item_new
|
||||
else:
|
||||
item_new = item
|
||||
try:
|
||||
base_new = eval("base" + item_new)
|
||||
except:
|
||||
pass
|
||||
|
||||
if base_new is Ellipsis:
|
||||
print("Failed to resolve data path:", self.data_path, file=log)
|
||||
base = base_new
|
||||
else:
|
||||
item_new = item
|
||||
|
||||
pairs.append((item_new, base))
|
||||
return pairs
|
||||
|
||||
|
||||
def id_iter():
|
||||
type_iter = type(bpy.data.objects)
|
||||
|
||||
for attr in dir(bpy.data):
|
||||
data_iter = getattr(bpy.data, attr, None)
|
||||
if type(data_iter) == type_iter:
|
||||
for id_data in data_iter:
|
||||
if id_data.library is None:
|
||||
yield id_data
|
||||
|
||||
|
||||
def anim_data_actions(anim_data):
|
||||
actions = []
|
||||
actions.append(anim_data.action)
|
||||
for track in anim_data.nla_tracks:
|
||||
for strip in track.strips:
|
||||
actions.append(strip.action)
|
||||
|
||||
# filter out None
|
||||
return [act for act in actions if act]
|
||||
|
||||
|
||||
def find_path_new(id_data, data_path, rna_update_from_map, fcurve, log):
|
||||
# note!, id_data can be ID type or a node tree
|
||||
# ignore ID props for now
|
||||
if data_path.startswith("["):
|
||||
return data_path
|
||||
|
||||
# recursive path fixing, likely will be one in most cases.
|
||||
data_path_builder = eval("DataPathBuilder(tuple())." + data_path)
|
||||
data_resolve = data_path_builder.resolve(id_data, rna_update_from_map, fcurve, log)
|
||||
|
||||
path_new = [pair[0] for pair in data_resolve]
|
||||
|
||||
return "".join(path_new)[1:] # skip the first "."
|
||||
|
||||
|
||||
def update_data_paths(rna_update, log=sys.stdout):
|
||||
"""
|
||||
rna_update triple [(class_name, from, to or to_callback, callback options), ...]
|
||||
to_callback is a function with this signature: update_cb(base, class_name, old_path, fcurve, options)
|
||||
where base is current object, class_name is the expected type name of base (callback has to handle
|
||||
this), old_path it the org name of base's property, fcurve is the affected fcurve (!),
|
||||
and options is an opaque data.
|
||||
class_name, fcurve and options may be None!
|
||||
"""
|
||||
|
||||
rna_update_from_map = {}
|
||||
for ren_class, ren_from, ren_to, options in rna_update:
|
||||
rna_update_from_map.setdefault(ren_from, []).append((ren_class, ren_to, options))
|
||||
|
||||
for id_data in id_iter():
|
||||
# check node-trees too
|
||||
anim_data_ls = [(id_data, getattr(id_data, "animation_data", None))]
|
||||
node_tree = getattr(id_data, "node_tree", None)
|
||||
if node_tree:
|
||||
anim_data_ls.append((node_tree, node_tree.animation_data))
|
||||
|
||||
for anim_data_base, anim_data in anim_data_ls:
|
||||
if anim_data is None:
|
||||
continue
|
||||
|
||||
for fcurve in anim_data.drivers:
|
||||
data_path = fcurve.data_path
|
||||
data_path_new = find_path_new(anim_data_base, data_path, rna_update_from_map, fcurve, log)
|
||||
# print(data_path_new)
|
||||
if data_path_new != data_path:
|
||||
if not IS_TESTING:
|
||||
fcurve.data_path = data_path_new
|
||||
fcurve.driver.is_valid = True # reset to allow this to work again
|
||||
print("driver-fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new), file=log)
|
||||
|
||||
for var in fcurve.driver.variables:
|
||||
if var.type == 'SINGLE_PROP':
|
||||
for tar in var.targets:
|
||||
id_data_other = tar.id
|
||||
data_path = tar.data_path
|
||||
|
||||
if id_data_other and data_path:
|
||||
data_path_new = find_path_new(id_data_other, data_path, rna_update_from_map, None, log)
|
||||
# print(data_path_new)
|
||||
if data_path_new != data_path:
|
||||
if not IS_TESTING:
|
||||
tar.data_path = data_path_new
|
||||
print("driver (%s): %s -> %s" % (id_data_other.name, data_path, data_path_new),
|
||||
file=log)
|
||||
|
||||
for action in anim_data_actions(anim_data):
|
||||
for fcu in action.fcurves:
|
||||
data_path = fcu.data_path
|
||||
data_path_new = find_path_new(anim_data_base, data_path, rna_update_from_map, fcu, log)
|
||||
# print(data_path_new)
|
||||
if data_path_new != data_path:
|
||||
if not IS_TESTING:
|
||||
fcu.data_path = data_path_new
|
||||
print("fcurve (%s): %s -> %s" % (id_data.name, data_path, data_path_new), file=log)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# Example, should be called externally
|
||||
# (class, from, to or to_callback, callback_options)
|
||||
replace_ls = [
|
||||
("AnimVizMotionPaths", "frame_after", "frame_after", None),
|
||||
("AnimVizMotionPaths", "frame_before", "frame_before", None),
|
||||
("AnimVizOnionSkinning", "frame_after", "frame_after", None),
|
||||
]
|
||||
|
||||
update_data_paths(replace_ls)
|
||||
203
scripts/modules/bl_app_override/__init__.py
Normal file
203
scripts/modules/bl_app_override/__init__.py
Normal file
@@ -0,0 +1,203 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Module to manage overriding various parts of Blender.
|
||||
|
||||
Intended for use with 'app_templates', though it can be used from anywhere.
|
||||
"""
|
||||
|
||||
|
||||
# TODO, how to check these aren't from add-ons.
|
||||
# templates might need to un-register while filtering.
|
||||
def class_filter(cls_parent, **kw):
|
||||
whitelist = kw.pop("whitelist", None)
|
||||
blacklist = kw.pop("blacklist", None)
|
||||
kw_items = tuple(kw.items())
|
||||
for cls in cls_parent.__subclasses__():
|
||||
# same as is_registered()
|
||||
if "bl_rna" in cls.__dict__:
|
||||
if blacklist is not None and cls.__name__ in blacklist:
|
||||
continue
|
||||
if ((whitelist is not None and cls.__name__ is whitelist) or
|
||||
all((getattr(cls, attr) in expect) for attr, expect in kw_items)):
|
||||
yield cls
|
||||
|
||||
|
||||
def ui_draw_filter_register(
|
||||
*,
|
||||
ui_ignore_classes=None,
|
||||
ui_ignore_operator=None,
|
||||
ui_ignore_property=None,
|
||||
ui_ignore_menu=None,
|
||||
ui_ignore_label=None
|
||||
):
|
||||
import bpy
|
||||
|
||||
UILayout = bpy.types.UILayout
|
||||
|
||||
if ui_ignore_classes is None:
|
||||
ui_ignore_classes = (
|
||||
bpy.types.Panel,
|
||||
bpy.types.Menu,
|
||||
bpy.types.Header,
|
||||
)
|
||||
|
||||
class OperatorProperties_Fake:
|
||||
pass
|
||||
|
||||
class UILayout_Fake(bpy.types.UILayout):
|
||||
__slots__ = ()
|
||||
|
||||
def __getattribute__(self, attr):
|
||||
# ensure we always pass down UILayout_Fake instances
|
||||
if attr in {"row", "split", "column", "box", "column_flow"}:
|
||||
real_func = UILayout.__getattribute__(self, attr)
|
||||
|
||||
def dummy_func(*args, **kw):
|
||||
# print("wrapped", attr)
|
||||
ret = real_func(*args, **kw)
|
||||
return UILayout_Fake(ret)
|
||||
return dummy_func
|
||||
|
||||
elif attr in {"operator", "operator_menu_enum", "operator_enum", "operator_menu_hold"}:
|
||||
if ui_ignore_operator is None:
|
||||
return UILayout.__getattribute__(self, attr)
|
||||
|
||||
real_func = UILayout.__getattribute__(self, attr)
|
||||
|
||||
def dummy_func(*args, **kw):
|
||||
# print("wrapped", attr)
|
||||
ui_test = ui_ignore_operator(args[0])
|
||||
if ui_test is False:
|
||||
ret = real_func(*args, **kw)
|
||||
else:
|
||||
if ui_test is None:
|
||||
UILayout.__getattribute__(self, "label")(text="")
|
||||
else:
|
||||
assert ui_test is True
|
||||
# may need to be set
|
||||
ret = OperatorProperties_Fake()
|
||||
return ret
|
||||
return dummy_func
|
||||
|
||||
elif attr in {"prop", "prop_enum"}:
|
||||
if ui_ignore_property is None:
|
||||
return UILayout.__getattribute__(self, attr)
|
||||
|
||||
real_func = UILayout.__getattribute__(self, attr)
|
||||
|
||||
def dummy_func(*args, **kw):
|
||||
# print("wrapped", attr)
|
||||
ui_test = ui_ignore_property(args[0].__class__.__name__, args[1])
|
||||
if ui_test is False:
|
||||
ret = real_func(*args, **kw)
|
||||
else:
|
||||
if ui_test is None:
|
||||
UILayout.__getattribute__(self, "label")(text="")
|
||||
else:
|
||||
assert ui_test is True
|
||||
ret = None
|
||||
return ret
|
||||
return dummy_func
|
||||
|
||||
elif attr == "menu":
|
||||
if ui_ignore_menu is None:
|
||||
return UILayout.__getattribute__(self, attr)
|
||||
|
||||
real_func = UILayout.__getattribute__(self, attr)
|
||||
|
||||
def dummy_func(*args, **kw):
|
||||
# print("wrapped", attr)
|
||||
ui_test = ui_ignore_menu(args[0])
|
||||
if ui_test is False:
|
||||
ret = real_func(*args, **kw)
|
||||
else:
|
||||
if ui_test is None:
|
||||
UILayout.__getattribute__(self, "label")(text="")
|
||||
else:
|
||||
assert ui_test is True
|
||||
ret = None
|
||||
return ret
|
||||
return dummy_func
|
||||
|
||||
elif attr == "label":
|
||||
if ui_ignore_label is None:
|
||||
return UILayout.__getattribute__(self, attr)
|
||||
|
||||
real_func = UILayout.__getattribute__(self, attr)
|
||||
|
||||
def dummy_func(*args, **kw):
|
||||
# print("wrapped", attr)
|
||||
ui_test = ui_ignore_label(args[0] if args else kw.get("text", ""))
|
||||
if ui_test is False:
|
||||
ret = real_func(*args, **kw)
|
||||
else:
|
||||
if ui_test is None:
|
||||
real_func(text="")
|
||||
else:
|
||||
assert ui_test is True
|
||||
ret = None
|
||||
return ret
|
||||
return dummy_func
|
||||
else:
|
||||
return UILayout.__getattribute__(self, attr)
|
||||
# print(self, attr)
|
||||
|
||||
def operator(*args, **kw):
|
||||
return super().operator(*args, **kw)
|
||||
|
||||
def draw_override(func_orig, self_real, context):
|
||||
cls_real = self_real.__class__
|
||||
if cls_real is super:
|
||||
# simple, no wrapping
|
||||
return func_orig(self_real, context)
|
||||
|
||||
class Wrapper(cls_real):
|
||||
__slots__ = ()
|
||||
|
||||
def __getattribute__(self, attr):
|
||||
if attr == "layout":
|
||||
return UILayout_Fake(self_real.layout)
|
||||
else:
|
||||
cls = super()
|
||||
try:
|
||||
return cls.__getattr__(self, attr)
|
||||
except AttributeError:
|
||||
# class variable
|
||||
try:
|
||||
return getattr(cls, attr)
|
||||
except AttributeError:
|
||||
# for preset bl_idname access
|
||||
return getattr(UILayout(self), attr)
|
||||
|
||||
@property
|
||||
def layout(self):
|
||||
# print("wrapped")
|
||||
return self_real.layout
|
||||
|
||||
return func_orig(Wrapper(self_real), context)
|
||||
|
||||
ui_ignore_store = []
|
||||
|
||||
for cls in ui_ignore_classes:
|
||||
for subcls in list(cls.__subclasses__()):
|
||||
if "draw" in subcls.__dict__: # don't want to get parents draw()
|
||||
|
||||
def replace_draw():
|
||||
# function also serves to hold draw_old in a local name-space
|
||||
draw_orig = subcls.draw
|
||||
|
||||
def draw(self, context):
|
||||
return draw_override(draw_orig, self, context)
|
||||
subcls.draw = draw
|
||||
|
||||
ui_ignore_store.append((subcls, "draw", subcls.draw))
|
||||
|
||||
replace_draw()
|
||||
|
||||
return ui_ignore_store
|
||||
|
||||
|
||||
def ui_draw_filter_unregister(ui_ignore_store):
|
||||
for (obj, attr, value) in ui_ignore_store:
|
||||
setattr(obj, attr, value)
|
||||
147
scripts/modules/bl_app_override/helpers.py
Normal file
147
scripts/modules/bl_app_override/helpers.py
Normal file
@@ -0,0 +1,147 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# AppOverrideState
|
||||
|
||||
|
||||
class AppOverrideState:
|
||||
"""
|
||||
Utility class to encapsulate overriding the application state
|
||||
so that settings can be restored afterwards.
|
||||
"""
|
||||
__slots__ = (
|
||||
# setup_classes
|
||||
"_class_store",
|
||||
# setup_ui_ignore
|
||||
"_ui_ignore_store",
|
||||
# setup_addons
|
||||
"_addon_store",
|
||||
)
|
||||
|
||||
# ---------
|
||||
# Callbacks
|
||||
#
|
||||
# Set as None, to make it simple to check if they're being overridden.
|
||||
|
||||
# setup/teardown classes
|
||||
class_ignore = None
|
||||
|
||||
# setup/teardown ui_ignore
|
||||
ui_ignore_classes = None
|
||||
ui_ignore_operator = None
|
||||
ui_ignore_property = None
|
||||
ui_ignore_menu = None
|
||||
ui_ignore_label = None
|
||||
|
||||
addon_paths = None
|
||||
addons = None
|
||||
|
||||
# End callbacks
|
||||
|
||||
def __init__(self):
|
||||
self._class_store = None
|
||||
self._addon_store = None
|
||||
self._ui_ignore_store = None
|
||||
|
||||
def _setup_classes(self):
|
||||
assert self._class_store is None
|
||||
self._class_store = self.class_ignore()
|
||||
from bpy.utils import unregister_class
|
||||
for cls in self._class_store:
|
||||
unregister_class(cls)
|
||||
|
||||
def _teardown_classes(self):
|
||||
assert self._class_store is not None
|
||||
|
||||
from bpy.utils import register_class
|
||||
for cls in self._class_store:
|
||||
register_class(cls)
|
||||
self._class_store = None
|
||||
|
||||
def _setup_ui_ignore(self):
|
||||
import bl_app_override
|
||||
|
||||
self._ui_ignore_store = bl_app_override.ui_draw_filter_register(
|
||||
ui_ignore_classes=(
|
||||
None if self.ui_ignore_classes is None
|
||||
else self.ui_ignore_classes()
|
||||
),
|
||||
ui_ignore_operator=self.ui_ignore_operator,
|
||||
ui_ignore_property=self.ui_ignore_property,
|
||||
ui_ignore_menu=self.ui_ignore_menu,
|
||||
ui_ignore_label=self.ui_ignore_label,
|
||||
)
|
||||
|
||||
def _teardown_ui_ignore(self):
|
||||
import bl_app_override
|
||||
bl_app_override.ui_draw_filter_unregister(
|
||||
self._ui_ignore_store
|
||||
)
|
||||
self._ui_ignore_store = None
|
||||
|
||||
def _setup_addons(self):
|
||||
import sys
|
||||
|
||||
sys_path = []
|
||||
if self.addon_paths is not None:
|
||||
for path in self.addon_paths():
|
||||
if path not in sys.path:
|
||||
sys.path.append(path)
|
||||
|
||||
import addon_utils
|
||||
addons = []
|
||||
if self.addons is not None:
|
||||
addons.extend(self.addons())
|
||||
for addon in addons:
|
||||
addon_utils.enable(addon)
|
||||
|
||||
self._addon_store = {
|
||||
"sys_path": sys_path,
|
||||
"addons": addons,
|
||||
}
|
||||
|
||||
def _teardown_addons(self):
|
||||
import sys
|
||||
|
||||
sys_path = self._addon_store["sys_path"]
|
||||
for path in sys_path:
|
||||
# should always succeed, but if not it doesn't matter
|
||||
# (someone else was changing the sys.path), ignore!
|
||||
try:
|
||||
sys.path.remove(path)
|
||||
except:
|
||||
pass
|
||||
|
||||
addons = self._addon_store["addons"]
|
||||
import addon_utils
|
||||
for addon in addons:
|
||||
addon_utils.disable(addon)
|
||||
|
||||
self._addon_store.clear()
|
||||
self._addon_store = None
|
||||
|
||||
def setup(self):
|
||||
if self.class_ignore is not None:
|
||||
self._setup_classes()
|
||||
|
||||
if any((self.addon_paths,
|
||||
self.addons,
|
||||
)):
|
||||
self._setup_addons()
|
||||
|
||||
if any((self.ui_ignore_operator,
|
||||
self.ui_ignore_property,
|
||||
self.ui_ignore_menu,
|
||||
self.ui_ignore_label,
|
||||
)):
|
||||
self._setup_ui_ignore()
|
||||
|
||||
def teardown(self):
|
||||
if self._class_store is not None:
|
||||
self._teardown_classes()
|
||||
|
||||
if self._addon_store is not None:
|
||||
self._teardown_addons()
|
||||
|
||||
if self._ui_ignore_store is not None:
|
||||
self._teardown_ui_ignore()
|
||||
173
scripts/modules/bl_app_template_utils.py
Normal file
173
scripts/modules/bl_app_template_utils.py
Normal file
@@ -0,0 +1,173 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Similar to ``addon_utils``, except we can only have one active at a time.
|
||||
|
||||
In most cases users of this module will simply call 'activate'.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"activate",
|
||||
"import_from_path",
|
||||
"import_from_id",
|
||||
"reset",
|
||||
)
|
||||
|
||||
import bpy as _bpy
|
||||
|
||||
# Normally matches 'preferences.app_template_id',
|
||||
# but loading new preferences will get us out of sync.
|
||||
_app_template = {
|
||||
"id": "",
|
||||
}
|
||||
|
||||
# instead of sys.modules
|
||||
# note that we only ever have one template enabled at a time
|
||||
# so it may not seem necessary to use this.
|
||||
#
|
||||
# However, templates may want to share between each-other,
|
||||
# so any loaded modules are stored here?
|
||||
#
|
||||
# Note that the ID here is the app_template_id , not the modules __name__.
|
||||
_modules = {}
|
||||
|
||||
|
||||
def _enable(template_id, *, handle_error=None, ignore_not_found=False):
|
||||
from bpy_restrict_state import RestrictBlend
|
||||
|
||||
if handle_error is None:
|
||||
def handle_error(_ex):
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# Split registering up into 2 steps so we can undo
|
||||
# if it fails par way through.
|
||||
|
||||
# disable the context, using the context at all is
|
||||
# really bad while loading an template, don't do it!
|
||||
with RestrictBlend():
|
||||
|
||||
# 1) try import
|
||||
try:
|
||||
mod = import_from_id(template_id, ignore_not_found=ignore_not_found)
|
||||
except Exception as ex:
|
||||
handle_error(ex)
|
||||
return None
|
||||
|
||||
_modules[template_id] = mod
|
||||
if mod is None:
|
||||
return None
|
||||
mod.__template_enabled__ = False
|
||||
|
||||
# 2) try run the modules register function
|
||||
try:
|
||||
mod.register()
|
||||
except Exception as ex:
|
||||
print("Exception in module register(): %r" %
|
||||
getattr(mod, "__file__", template_id))
|
||||
handle_error(ex)
|
||||
del _modules[template_id]
|
||||
return None
|
||||
|
||||
# * OK loaded successfully! *
|
||||
mod.__template_enabled__ = True
|
||||
|
||||
if _bpy.app.debug_python:
|
||||
print("\tapp_template_utils.enable", mod.__name__)
|
||||
|
||||
return mod
|
||||
|
||||
|
||||
def _disable(template_id, *, handle_error=None):
|
||||
"""
|
||||
Disables a template by name.
|
||||
|
||||
:arg template_id: The name of the template and module.
|
||||
:type template_id: string
|
||||
:arg handle_error: Called in the case of an error,
|
||||
taking an exception argument.
|
||||
:type handle_error: function
|
||||
"""
|
||||
|
||||
if handle_error is None:
|
||||
def handle_error(_ex):
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
mod = _modules.get(template_id, False)
|
||||
|
||||
if mod is None:
|
||||
# Loaded but has no module, remove since there is no use in keeping it.
|
||||
del _modules[template_id]
|
||||
elif getattr(mod, "__template_enabled__", False) is not False:
|
||||
mod.__template_enabled__ = False
|
||||
|
||||
try:
|
||||
mod.unregister()
|
||||
except Exception as ex:
|
||||
print("Exception in module unregister(): %r" %
|
||||
getattr(mod, "__file__", template_id))
|
||||
handle_error(ex)
|
||||
else:
|
||||
print("\tapp_template_utils.disable: %s not %s." %
|
||||
(template_id, "disabled" if mod is False else "loaded"))
|
||||
|
||||
if _bpy.app.debug_python:
|
||||
print("\tapp_template_utils.disable", template_id)
|
||||
|
||||
|
||||
def import_from_path(path, *, ignore_not_found=False):
|
||||
import os
|
||||
from importlib import import_module
|
||||
base_module, template_id = path.rsplit(os.sep, 2)[-2:]
|
||||
module_name = base_module + "." + template_id
|
||||
|
||||
try:
|
||||
return import_module(module_name)
|
||||
except ModuleNotFoundError as ex:
|
||||
if ignore_not_found and ex.name == module_name:
|
||||
return None
|
||||
raise ex
|
||||
|
||||
|
||||
def import_from_id(template_id, *, ignore_not_found=False):
|
||||
import os
|
||||
path = next(iter(_bpy.utils.app_template_paths(path=template_id)), None)
|
||||
if path is None:
|
||||
if ignore_not_found:
|
||||
return None
|
||||
else:
|
||||
raise Exception("%r template not found!" % template_id)
|
||||
else:
|
||||
if ignore_not_found:
|
||||
if not os.path.exists(os.path.join(path, "__init__.py")):
|
||||
return None
|
||||
return import_from_path(path, ignore_not_found=ignore_not_found)
|
||||
|
||||
|
||||
def activate(*, template_id=None, reload_scripts=False):
|
||||
template_id_prev = _app_template["id"]
|
||||
|
||||
# not needed but may as well avoids redundant
|
||||
# disable/enable for all add-ons on 'File -> New'
|
||||
if not reload_scripts and template_id_prev == template_id:
|
||||
return
|
||||
|
||||
if template_id_prev:
|
||||
_disable(template_id_prev)
|
||||
|
||||
# ignore_not_found so modules that don't contain scripts don't raise errors
|
||||
_mod = _enable(template_id, ignore_not_found=True) if template_id else None
|
||||
|
||||
_app_template["id"] = template_id
|
||||
|
||||
|
||||
def reset(*, reload_scripts=False):
|
||||
"""
|
||||
Sets default state.
|
||||
"""
|
||||
template_id = _bpy.context.preferences.app_template
|
||||
if _bpy.app.debug_python:
|
||||
print("bl_app_template_utils.reset('%s')" % template_id)
|
||||
|
||||
activate(template_id=template_id, reload_scripts=reload_scripts)
|
||||
4
scripts/modules/bl_console_utils/__init__.py
Normal file
4
scripts/modules/bl_console_utils/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
"""
|
||||
Utilities relating to text mode console interactions.
|
||||
"""
|
||||
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Copyright (c) 2009 www.stani.be
|
||||
|
||||
"""Package for console specific modules."""
|
||||
@@ -0,0 +1,172 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Copyright (c) 2009 www.stani.be
|
||||
|
||||
import inspect
|
||||
import re
|
||||
|
||||
|
||||
# regular expression constants
|
||||
DEF_DOC = r'%s\s*(\(.*?\))'
|
||||
DEF_SOURCE = r'def\s+%s\s*(\(.*?\)):'
|
||||
RE_EMPTY_LINE = re.compile(r'^\s*\n')
|
||||
RE_FLAG = re.MULTILINE | re.DOTALL
|
||||
RE_NEWLINE = re.compile('\n+')
|
||||
RE_SPACE = re.compile(r'\s+')
|
||||
RE_DEF_COMPLETE = re.compile(
|
||||
# don't start with a quote
|
||||
'''(?:^|[^"'a-zA-Z0-9_])'''
|
||||
# start with a \w = [a-zA-Z0-9_]
|
||||
r'''((\w+'''
|
||||
# allow also dots and closed bracket pairs []
|
||||
r'''(?:\w|[.]|\[.+?\])*'''
|
||||
# allow empty string
|
||||
'''|)'''
|
||||
# allow opening bracket(s)
|
||||
r'''(?:\(|\s)*)$''')
|
||||
|
||||
|
||||
def reduce_newlines(text):
|
||||
"""Reduces multiple newlines to a single newline.
|
||||
|
||||
:arg text: text with multiple newlines
|
||||
:type text: str
|
||||
:returns: text with single newlines
|
||||
:rtype: str
|
||||
|
||||
>>> reduce_newlines('hello\\n\\nworld')
|
||||
'hello\\nworld'
|
||||
"""
|
||||
return RE_NEWLINE.sub('\n', text)
|
||||
|
||||
|
||||
def reduce_spaces(text):
|
||||
"""Reduces multiple whitespaces to a single space.
|
||||
|
||||
:arg text: text with multiple spaces
|
||||
:type text: str
|
||||
:returns: text with single spaces
|
||||
:rtype: str
|
||||
|
||||
>>> reduce_spaces('hello \\nworld')
|
||||
'hello world'
|
||||
"""
|
||||
return RE_SPACE.sub(' ', text)
|
||||
|
||||
|
||||
def get_doc(obj):
|
||||
"""Get the doc string or comments for an object.
|
||||
|
||||
:arg object: object
|
||||
:returns: doc string
|
||||
:rtype: str
|
||||
|
||||
>>> get_doc(abs)
|
||||
'abs(number) -> number\\n\\nReturn the absolute value of the argument.'
|
||||
"""
|
||||
result = inspect.getdoc(obj) or inspect.getcomments(obj)
|
||||
return result and RE_EMPTY_LINE.sub('', result.rstrip()) or ''
|
||||
|
||||
|
||||
def get_argspec(func, *, strip_self=True, doc=None, source=None):
|
||||
"""Get argument specifications.
|
||||
|
||||
:arg strip_self: strip `self` from argspec
|
||||
:type strip_self: bool
|
||||
:arg doc: doc string of func (optional)
|
||||
:type doc: str
|
||||
:arg source: source code of func (optional)
|
||||
:type source: str
|
||||
:returns: argument specification
|
||||
:rtype: str
|
||||
|
||||
>>> get_argspec(inspect.getclasstree)
|
||||
'(classes, unique=0)'
|
||||
>>> get_argspec(abs)
|
||||
'(number)'
|
||||
"""
|
||||
# get the function object of the class
|
||||
try:
|
||||
func = func.__func__
|
||||
except AttributeError:
|
||||
pass
|
||||
# is callable?
|
||||
if not hasattr(func, '__call__'):
|
||||
return ''
|
||||
# func should have a name
|
||||
try:
|
||||
func_name = func.__name__
|
||||
except AttributeError:
|
||||
return ''
|
||||
# from docstring
|
||||
if doc is None:
|
||||
doc = get_doc(func)
|
||||
match = re.search(DEF_DOC % func_name, doc, RE_FLAG)
|
||||
# from source code
|
||||
if not match:
|
||||
if source is None:
|
||||
try:
|
||||
source = inspect.getsource(func)
|
||||
except (TypeError, IOError):
|
||||
source = ''
|
||||
if source:
|
||||
match = re.search(DEF_SOURCE % func_name, source, RE_FLAG)
|
||||
if match:
|
||||
argspec = reduce_spaces(match.group(1))
|
||||
else:
|
||||
# try with the inspect.getarg* functions
|
||||
try:
|
||||
argspec = inspect.formatargspec(*inspect.getfullargspec(func))
|
||||
except:
|
||||
try:
|
||||
argspec = inspect.formatargvalues(
|
||||
*inspect.getargvalues(func))
|
||||
except:
|
||||
argspec = ''
|
||||
if strip_self:
|
||||
argspec = argspec.replace('self, ', '')
|
||||
return argspec
|
||||
|
||||
|
||||
def complete(line, cursor, namespace):
|
||||
"""Complete callable with calltip.
|
||||
|
||||
:arg line: incomplete text line
|
||||
:type line: str
|
||||
:arg cursor: current character position
|
||||
:type cursor: int
|
||||
:arg namespace: namespace
|
||||
:type namespace: dict
|
||||
:returns: (matches, world, scrollback)
|
||||
:rtype: (list of str, str, str)
|
||||
|
||||
>>> import os
|
||||
>>> complete('os.path.isdir(', 14, {'os': os})[-1]
|
||||
'isdir(s)\\nReturn true if the pathname refers to an existing directory.'
|
||||
>>> complete('abs(', 4, {})[-1]
|
||||
'abs(number) -> number\\nReturn the absolute value of the argument.'
|
||||
"""
|
||||
matches = []
|
||||
word = ''
|
||||
scrollback = ''
|
||||
match = RE_DEF_COMPLETE.search(line[:cursor])
|
||||
|
||||
if match:
|
||||
word = match.group(1)
|
||||
func_word = match.group(2)
|
||||
try:
|
||||
func = eval(func_word, namespace)
|
||||
except Exception:
|
||||
func = None
|
||||
|
||||
if func:
|
||||
doc = get_doc(func)
|
||||
argspec = get_argspec(func, doc=doc)
|
||||
scrollback = func_word.split('.')[-1] + (argspec or '()')
|
||||
if doc.startswith(scrollback):
|
||||
scrollback = doc
|
||||
elif doc:
|
||||
scrollback += '\n' + doc
|
||||
scrollback = reduce_newlines(scrollback)
|
||||
|
||||
return matches, word, scrollback
|
||||
179
scripts/modules/bl_console_utils/autocomplete/complete_import.py
Normal file
179
scripts/modules/bl_console_utils/autocomplete/complete_import.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Copyright (c) 2009 Fernando Perez, www.stani.be
|
||||
|
||||
# Original copyright (see docstring):
|
||||
# ****************************************************************************
|
||||
# Copyright (C) 2001-2006 Fernando Perez <fperez@colorado.edu>
|
||||
#
|
||||
# Distributed under the terms of the BSD License. The full license is in
|
||||
# the file COPYING, distributed as part of this software.
|
||||
# ****************************************************************************
|
||||
|
||||
"""Completer for import statements
|
||||
|
||||
Original code was from IPython/Extensions/ipy_completers.py. The following
|
||||
changes have been made:
|
||||
- ported to python3
|
||||
- pep8 polishing
|
||||
- limit list of modules to prefix in case of "from w"
|
||||
- sorted modules
|
||||
- added sphinx documentation
|
||||
- complete() returns a blank list of the module isn't found
|
||||
"""
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
|
||||
TIMEOUT_STORAGE = 3 # Time in secs after which the root-modules will be stored
|
||||
TIMEOUT_GIVEUP = 20 # Time in secs after which we give up
|
||||
|
||||
ROOT_MODULES = None
|
||||
|
||||
|
||||
def get_root_modules():
|
||||
"""
|
||||
Returns a list containing the names of all the modules available in the
|
||||
folders of the python-path.
|
||||
|
||||
:returns: modules
|
||||
:rtype: list
|
||||
"""
|
||||
global ROOT_MODULES
|
||||
modules = []
|
||||
if not (ROOT_MODULES is None):
|
||||
return ROOT_MODULES
|
||||
from time import time
|
||||
t = time()
|
||||
store = False
|
||||
for path in sys.path:
|
||||
modules += module_list(path)
|
||||
if time() - t >= TIMEOUT_STORAGE and not store:
|
||||
# Caching the list of root modules, please wait!
|
||||
store = True
|
||||
if time() - t > TIMEOUT_GIVEUP:
|
||||
# This is taking too long, we give up.
|
||||
ROOT_MODULES = []
|
||||
return []
|
||||
|
||||
modules += sys.builtin_module_names
|
||||
|
||||
# needed for modules defined in C
|
||||
modules += sys.modules.keys()
|
||||
|
||||
modules = list(set(modules))
|
||||
if '__init__' in modules:
|
||||
modules.remove('__init__')
|
||||
modules = sorted(modules)
|
||||
if store:
|
||||
ROOT_MODULES = modules
|
||||
return modules
|
||||
|
||||
|
||||
def module_list(path):
|
||||
"""
|
||||
Return the list containing the names of the modules available in
|
||||
the given folder.
|
||||
|
||||
:arg path: folder path
|
||||
:type path: str
|
||||
:returns: modules
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
if os.path.isdir(path):
|
||||
folder_list = os.listdir(path)
|
||||
elif path.endswith('.egg'):
|
||||
from zipimport import zipimporter
|
||||
try:
|
||||
folder_list = [f for f in zipimporter(path)._files]
|
||||
except:
|
||||
folder_list = []
|
||||
else:
|
||||
folder_list = []
|
||||
#folder_list = glob.glob(os.path.join(path,'*'))
|
||||
folder_list = [
|
||||
p for p in folder_list
|
||||
if (os.path.exists(os.path.join(path, p, '__init__.py')) or
|
||||
p[-3:] in {'.py', '.so'} or
|
||||
p[-4:] in {'.pyc', '.pyo', '.pyd'})]
|
||||
|
||||
folder_list = [os.path.basename(p).split('.')[0] for p in folder_list]
|
||||
return folder_list
|
||||
|
||||
|
||||
def complete(line):
|
||||
"""
|
||||
Returns a list containing the completion possibilities for an import line.
|
||||
|
||||
:arg line:
|
||||
|
||||
incomplete line which contains an import statement::
|
||||
|
||||
import xml.d
|
||||
from xml.dom import
|
||||
|
||||
:type line: str
|
||||
:returns: list of completion possibilities
|
||||
:rtype: list
|
||||
|
||||
>>> complete('import weak')
|
||||
['weakref']
|
||||
>>> complete('from weakref import C')
|
||||
['CallableProxyType']
|
||||
"""
|
||||
import inspect
|
||||
|
||||
def try_import(mod, *, only_modules=False):
|
||||
|
||||
def is_importable(module, attr):
|
||||
if only_modules:
|
||||
return inspect.ismodule(getattr(module, attr))
|
||||
else:
|
||||
return not (attr[:2] == '__' and attr[-2:] == '__')
|
||||
|
||||
try:
|
||||
m = __import__(mod)
|
||||
except:
|
||||
return []
|
||||
mods = mod.split('.')
|
||||
for module in mods[1:]:
|
||||
m = getattr(m, module)
|
||||
if (not hasattr(m, '__file__')) or (not only_modules) or\
|
||||
(hasattr(m, '__file__') and '__init__' in m.__file__):
|
||||
completion_list = [attr for attr in dir(m)
|
||||
if is_importable(m, attr)]
|
||||
else:
|
||||
completion_list = []
|
||||
completion_list.extend(getattr(m, '__all__', []))
|
||||
if hasattr(m, '__file__') and '__init__' in m.__file__:
|
||||
completion_list.extend(module_list(os.path.dirname(m.__file__)))
|
||||
completion_list = list(set(completion_list))
|
||||
if '__init__' in completion_list:
|
||||
completion_list.remove('__init__')
|
||||
return completion_list
|
||||
|
||||
def filter_prefix(names, prefix):
|
||||
return [name for name in names if name.startswith(prefix)]
|
||||
|
||||
words = line.split(' ')
|
||||
if len(words) == 3 and words[0] == 'from':
|
||||
return ['import ']
|
||||
if len(words) < 3 and (words[0] in {'import', 'from'}):
|
||||
if len(words) == 1:
|
||||
return get_root_modules()
|
||||
mod = words[1].split('.')
|
||||
if len(mod) < 2:
|
||||
return filter_prefix(get_root_modules(), words[-1])
|
||||
completion_list = try_import('.'.join(mod[:-1]), only_modules=True)
|
||||
completion_list = ['.'.join(mod[:-1] + [el]) for el in completion_list]
|
||||
return filter_prefix(completion_list, words[-1])
|
||||
if len(words) >= 3 and words[0] == 'from':
|
||||
mod = words[1]
|
||||
return filter_prefix(try_import(mod), words[-1])
|
||||
|
||||
# get here if the import is not found
|
||||
# import invalidmodule
|
||||
# ^, in this case return nothing
|
||||
return []
|
||||
@@ -0,0 +1,190 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Copyright (c) 2009 www.stani.be
|
||||
|
||||
"""Autocomplete with the standard library"""
|
||||
|
||||
import re
|
||||
import rlcompleter
|
||||
|
||||
|
||||
RE_INCOMPLETE_INDEX = re.compile(r'(.*?)\[[^\]]+$')
|
||||
|
||||
TEMP = '__tEmP__' # only \w characters are allowed!
|
||||
TEMP_N = len(TEMP)
|
||||
|
||||
|
||||
def is_dict(obj):
|
||||
"""Returns whether obj is a dictionary"""
|
||||
return hasattr(obj, 'keys') and hasattr(getattr(obj, 'keys'), '__call__')
|
||||
|
||||
|
||||
def is_struct_seq(obj):
|
||||
"""Returns whether obj is a structured sequence subclass: sys.float_info"""
|
||||
return isinstance(obj, tuple) and hasattr(obj, 'n_fields')
|
||||
|
||||
|
||||
def complete_names(word, namespace):
|
||||
"""Complete variable names or attributes
|
||||
|
||||
:arg word: word to be completed
|
||||
:type word: str
|
||||
:arg namespace: namespace
|
||||
:type namespace: dict
|
||||
:returns: completion matches
|
||||
:rtype: list of str
|
||||
|
||||
>>> complete_names('fo', {'foo': 'bar'})
|
||||
['foo', 'for', 'format(']
|
||||
"""
|
||||
# start completer
|
||||
completer = rlcompleter.Completer(namespace)
|
||||
# find matches with std library (don't try to implement this yourself)
|
||||
completer.complete(word, 0)
|
||||
return sorted(set(completer.matches))
|
||||
|
||||
|
||||
def complete_indices(word, namespace, *, obj=None, base=None):
|
||||
"""Complete a list or dictionary with its indices:
|
||||
|
||||
* integer numbers for list
|
||||
* any keys for dictionary
|
||||
|
||||
:arg word: word to be completed
|
||||
:type word: str
|
||||
:arg namespace: namespace
|
||||
:type namespace: dict
|
||||
:arg obj: object evaluated from base
|
||||
:arg base: sub-string which can be evaluated into an object.
|
||||
:type base: str
|
||||
:returns: completion matches
|
||||
:rtype: list of str
|
||||
|
||||
>>> complete_indices('foo', {'foo': range(5)})
|
||||
['foo[0]', 'foo[1]', 'foo[2]', 'foo[3]', 'foo[4]']
|
||||
>>> complete_indices('foo', {'foo': {'bar':0, 1:2}})
|
||||
['foo[1]', "foo['bar']"]
|
||||
>>> complete_indices("foo['b", {'foo': {'bar':0, 1:2}}, base='foo')
|
||||
["foo['bar']"]
|
||||
"""
|
||||
# FIXME: 'foo["b'
|
||||
if base is None:
|
||||
base = word
|
||||
if obj is None:
|
||||
try:
|
||||
obj = eval(base, namespace)
|
||||
except Exception:
|
||||
return []
|
||||
if not hasattr(obj, '__getitem__'):
|
||||
# obj is not a list or dictionary
|
||||
return []
|
||||
|
||||
obj_is_dict = is_dict(obj)
|
||||
|
||||
# rare objects have a __getitem__ but no __len__ (eg. BMEdge)
|
||||
if not obj_is_dict:
|
||||
try:
|
||||
obj_len = len(obj)
|
||||
except TypeError:
|
||||
return []
|
||||
|
||||
if obj_is_dict:
|
||||
# dictionary type
|
||||
matches = ['%s[%r]' % (base, key) for key in sorted(obj.keys())]
|
||||
else:
|
||||
# list type
|
||||
matches = ['%s[%d]' % (base, idx) for idx in range(obj_len)]
|
||||
if word != base:
|
||||
matches = [match for match in matches if match.startswith(word)]
|
||||
return matches
|
||||
|
||||
|
||||
def complete(word, namespace, *, private=True):
|
||||
"""Complete word within a namespace with the standard rlcompleter
|
||||
module. Also supports index or key access [].
|
||||
|
||||
:arg word: word to be completed
|
||||
:type word: str
|
||||
:arg namespace: namespace
|
||||
:type namespace: dict
|
||||
:arg private: whether private attribute/methods should be returned
|
||||
:type private: bool
|
||||
:returns: completion matches
|
||||
:rtype: list of str
|
||||
|
||||
>>> complete('foo[1', {'foo': range(14)})
|
||||
['foo[1]', 'foo[10]', 'foo[11]', 'foo[12]', 'foo[13]']
|
||||
>>> complete('foo[0]', {'foo': [range(5)]})
|
||||
['foo[0][0]', 'foo[0][1]', 'foo[0][2]', 'foo[0][3]', 'foo[0][4]']
|
||||
>>> complete('foo[0].i', {'foo': [range(5)]})
|
||||
['foo[0].index(', 'foo[0].insert(']
|
||||
>>> complete('rlcompleter', {'rlcompleter': rlcompleter})
|
||||
['rlcompleter.']
|
||||
"""
|
||||
#
|
||||
# if word is empty -> nothing to complete
|
||||
if not word:
|
||||
return []
|
||||
|
||||
re_incomplete_index = RE_INCOMPLETE_INDEX.search(word)
|
||||
if re_incomplete_index:
|
||||
# ignore incomplete index at the end, e.g 'a[1' -> 'a'
|
||||
matches = complete_indices(word, namespace,
|
||||
base=re_incomplete_index.group(1))
|
||||
|
||||
elif not ('[' in word):
|
||||
matches = complete_names(word, namespace)
|
||||
|
||||
elif word[-1] == ']':
|
||||
matches = [word]
|
||||
|
||||
elif '.' in word:
|
||||
# brackets are normally not allowed -> work around
|
||||
|
||||
# remove brackets by using a temp var without brackets
|
||||
obj, attr = word.rsplit('.', 1)
|
||||
try:
|
||||
# do not run the obj expression in the console
|
||||
namespace[TEMP] = eval(obj, namespace)
|
||||
except Exception:
|
||||
return []
|
||||
matches = complete_names(TEMP + '.' + attr, namespace)
|
||||
matches = [obj + match[TEMP_N:] for match in matches]
|
||||
del namespace[TEMP]
|
||||
|
||||
else:
|
||||
# safety net, but when would this occur?
|
||||
return []
|
||||
|
||||
if not matches:
|
||||
return []
|
||||
|
||||
# add '.', '(' or '[' if no match has been found
|
||||
elif len(matches) == 1 and matches[0] == word:
|
||||
|
||||
# try to retrieve the object
|
||||
try:
|
||||
obj = eval(word, namespace)
|
||||
except Exception:
|
||||
return []
|
||||
# ignore basic types
|
||||
if type(obj) in {bool, float, int, str}:
|
||||
return []
|
||||
# an extra char '[', '(' or '.' will be added
|
||||
if hasattr(obj, '__getitem__') and not is_struct_seq(obj):
|
||||
# list or dictionary
|
||||
matches = complete_indices(word, namespace, obj=obj)
|
||||
elif hasattr(obj, '__call__'):
|
||||
# callables
|
||||
matches = [word + '(']
|
||||
else:
|
||||
# any other type
|
||||
matches = [word + '.']
|
||||
|
||||
# separate public from private
|
||||
public_matches = [match for match in matches if not ('._' in match)]
|
||||
if private:
|
||||
private_matches = [match for match in matches if '._' in match]
|
||||
return public_matches + private_matches
|
||||
else:
|
||||
return public_matches
|
||||
136
scripts/modules/bl_console_utils/autocomplete/intellisense.py
Normal file
136
scripts/modules/bl_console_utils/autocomplete/intellisense.py
Normal file
@@ -0,0 +1,136 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Copyright (c) 2009 www.stani.be
|
||||
|
||||
"""This module provides intellisense features such as:
|
||||
|
||||
* autocompletion
|
||||
* calltips
|
||||
|
||||
It unifies all completion plugins and only loads them on demand.
|
||||
"""
|
||||
|
||||
# TODO: file complete if startswith quotes
|
||||
import os
|
||||
import re
|
||||
|
||||
# regular expressions to find out which completer we need
|
||||
|
||||
# line which starts with an import statement
|
||||
RE_MODULE = re.compile(r'''^import(\s|$)|from.+''')
|
||||
|
||||
# The following regular expression means an 'unquoted' word
|
||||
RE_UNQUOTED_WORD = re.compile(
|
||||
# don't start with a quote
|
||||
r'''(?:^|[^"'a-zA-Z0-9_])'''
|
||||
# start with a \w = [a-zA-Z0-9_]
|
||||
r'''((?:\w+'''
|
||||
# allow also dots and closed bracket pairs []
|
||||
r'''(?:\w|[.]|\[.+?\])*'''
|
||||
# allow empty string
|
||||
r'''|)'''
|
||||
# allow an unfinished index at the end (including quotes)
|
||||
r'''(?:\[[^\]]*$)?)$''',
|
||||
# allow unicode as theoretically this is possible
|
||||
re.UNICODE)
|
||||
|
||||
|
||||
def complete(line, cursor, namespace, private):
|
||||
"""Returns a list of possible completions:
|
||||
|
||||
* name completion
|
||||
* attribute completion (obj.attr)
|
||||
* index completion for lists and dictionaries
|
||||
* module completion (from/import)
|
||||
|
||||
:arg line: incomplete text line
|
||||
:type line: str
|
||||
:arg cursor: current character position
|
||||
:type cursor: int
|
||||
:arg namespace: namespace
|
||||
:type namespace: dict
|
||||
:arg private: whether private variables should be listed
|
||||
:type private: bool
|
||||
:returns: list of completions, word
|
||||
:rtype: list, str
|
||||
|
||||
>>> complete('re.sr', 5, {'re': re})
|
||||
(['re.sre_compile', 're.sre_parse'], 're.sr')
|
||||
"""
|
||||
re_unquoted_word = RE_UNQUOTED_WORD.search(line[:cursor])
|
||||
if re_unquoted_word:
|
||||
# unquoted word -> module or attribute completion
|
||||
word = re_unquoted_word.group(1)
|
||||
if RE_MODULE.match(line):
|
||||
from . import complete_import
|
||||
matches = complete_import.complete(line)
|
||||
if not private:
|
||||
matches[:] = [m for m in matches if m[:1] != "_"]
|
||||
matches.sort()
|
||||
else:
|
||||
from . import complete_namespace
|
||||
matches = complete_namespace.complete(word, namespace, private=private)
|
||||
else:
|
||||
# for now we don't have completers for strings
|
||||
# TODO: add file auto completer for strings
|
||||
word = ''
|
||||
matches = []
|
||||
return matches, word
|
||||
|
||||
|
||||
def expand(line, cursor, namespace, *, private=True):
|
||||
"""This method is invoked when the user asks autocompletion,
|
||||
e.g. when Ctrl+Space is clicked.
|
||||
|
||||
:arg line: incomplete text line
|
||||
:type line: str
|
||||
:arg cursor: current character position
|
||||
:type cursor: int
|
||||
:arg namespace: namespace
|
||||
:type namespace: dict
|
||||
:arg private: whether private variables should be listed
|
||||
:type private: bool
|
||||
:returns:
|
||||
|
||||
current expanded line, updated cursor position and scrollback
|
||||
|
||||
:rtype: str, int, str
|
||||
|
||||
>>> expand('os.path.isdir(', 14, {'os': os})[-1]
|
||||
'isdir(s)\\nReturn true if the pathname refers to an existing directory.'
|
||||
>>> expand('abs(', 4, {})[-1]
|
||||
'abs(number) -> number\\nReturn the absolute value of the argument.'
|
||||
"""
|
||||
if line[:cursor].strip().endswith('('):
|
||||
from . import complete_calltip
|
||||
matches, word, scrollback = complete_calltip.complete(
|
||||
line, cursor, namespace)
|
||||
prefix = os.path.commonprefix(matches)[len(word):]
|
||||
no_calltip = False
|
||||
else:
|
||||
matches, word = complete(line, cursor, namespace, private)
|
||||
prefix = os.path.commonprefix(matches)[len(word):]
|
||||
if len(matches) == 1:
|
||||
scrollback = ''
|
||||
else:
|
||||
# causes blender bug #27495 since string keys may contain '.'
|
||||
# scrollback = ' '.join([m.split('.')[-1] for m in matches])
|
||||
|
||||
# add white space to align with the cursor
|
||||
white_space = " " + (" " * (cursor + len(prefix)))
|
||||
word_prefix = word + prefix
|
||||
scrollback = '\n'.join(
|
||||
[white_space + m[len(word_prefix):]
|
||||
if (word_prefix and m.startswith(word_prefix))
|
||||
else
|
||||
white_space + m.split('.')[-1]
|
||||
for m in matches])
|
||||
|
||||
no_calltip = True
|
||||
|
||||
if prefix:
|
||||
line = line[:cursor] + prefix + line[cursor:]
|
||||
cursor += len(prefix.encode('utf-8'))
|
||||
if no_calltip and prefix.endswith('('):
|
||||
return expand(line, cursor, namespace, private=private)
|
||||
return line, cursor, scrollback
|
||||
3
scripts/modules/bl_i18n_utils/__init__.py
Normal file
3
scripts/modules/bl_i18n_utils/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""Package for translation (i18n) tools."""
|
||||
1137
scripts/modules/bl_i18n_utils/bl_extract_messages.py
Normal file
1137
scripts/modules/bl_i18n_utils/bl_extract_messages.py
Normal file
@@ -0,0 +1,1137 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Populate a template file (POT format currently) from Blender RNA/py/C data.
|
||||
# XXX: This script is meant to be used from inside Blender!
|
||||
# You should not directly use this script, rather use update_msg.py!
|
||||
|
||||
import datetime
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import glob
|
||||
|
||||
# XXX Relative import does not work here when used from Blender...
|
||||
from bl_i18n_utils import settings as settings_i18n, utils
|
||||
|
||||
import bpy
|
||||
|
||||
##### Utils #####
|
||||
|
||||
# check for strings like "+%f°"
|
||||
ignore_reg = re.compile(r"^(?:[-*.()/\\+%°0-9]|%d|%f|%s|%r|\s)*$")
|
||||
filter_message = ignore_reg.match
|
||||
|
||||
|
||||
def init_spell_check(settings, lang="en_US"):
|
||||
try:
|
||||
from bl_i18n_utils import utils_spell_check
|
||||
return utils_spell_check.SpellChecker(settings, lang)
|
||||
except Exception as e:
|
||||
print("Failed to import utils_spell_check ({})".format(str(e)))
|
||||
return None
|
||||
|
||||
|
||||
def _gen_check_ctxt(settings):
|
||||
return {
|
||||
"multi_rnatip": set(),
|
||||
"multi_lines": set(),
|
||||
"py_in_rna": set(),
|
||||
"not_capitalized": set(),
|
||||
"end_point": set(),
|
||||
"undoc_ops": set(),
|
||||
"spell_checker": init_spell_check(settings),
|
||||
"spell_errors": {},
|
||||
}
|
||||
|
||||
|
||||
def _diff_check_ctxt(check_ctxt, minus_check_ctxt):
|
||||
"""Removes minus_check_ctxt from check_ctxt"""
|
||||
for key in check_ctxt:
|
||||
if isinstance(check_ctxt[key], set):
|
||||
for warning in minus_check_ctxt[key]:
|
||||
if warning in check_ctxt[key]:
|
||||
check_ctxt[key].remove(warning)
|
||||
elif isinstance(check_ctxt[key], dict):
|
||||
for warning in minus_check_ctxt[key]:
|
||||
if warning in check_ctxt[key]:
|
||||
del check_ctxt[key][warning]
|
||||
|
||||
|
||||
def _gen_reports(check_ctxt):
|
||||
return {
|
||||
"check_ctxt": check_ctxt,
|
||||
"rna_structs": [],
|
||||
"rna_structs_skipped": [],
|
||||
"rna_props": [],
|
||||
"rna_props_skipped": [],
|
||||
"py_messages": [],
|
||||
"py_messages_skipped": [],
|
||||
"src_messages": [],
|
||||
"src_messages_skipped": [],
|
||||
"messages_skipped": set(),
|
||||
}
|
||||
|
||||
|
||||
def check(check_ctxt, msgs, key, msgsrc, settings):
|
||||
"""
|
||||
Performs a set of checks over the given key (context, message)...
|
||||
"""
|
||||
if check_ctxt is None:
|
||||
return
|
||||
multi_rnatip = check_ctxt.get("multi_rnatip")
|
||||
multi_lines = check_ctxt.get("multi_lines")
|
||||
py_in_rna = check_ctxt.get("py_in_rna")
|
||||
not_capitalized = check_ctxt.get("not_capitalized")
|
||||
end_point = check_ctxt.get("end_point")
|
||||
undoc_ops = check_ctxt.get("undoc_ops")
|
||||
spell_checker = check_ctxt.get("spell_checker")
|
||||
spell_errors = check_ctxt.get("spell_errors")
|
||||
|
||||
if multi_rnatip is not None:
|
||||
if key in msgs and key not in multi_rnatip:
|
||||
multi_rnatip.add(key)
|
||||
if multi_lines is not None:
|
||||
if '\n' in key[1]:
|
||||
multi_lines.add(key)
|
||||
if py_in_rna is not None:
|
||||
if key in py_in_rna[1]:
|
||||
py_in_rna[0].add(key)
|
||||
if not_capitalized is not None:
|
||||
if (key[1] not in settings.WARN_MSGID_NOT_CAPITALIZED_ALLOWED and
|
||||
key[1][0].isalpha() and not key[1][0].isupper()):
|
||||
not_capitalized.add(key)
|
||||
if end_point is not None:
|
||||
if (
|
||||
key[1].strip().endswith('.') and
|
||||
(not key[1].strip().endswith('...')) and
|
||||
key[1] not in settings.WARN_MSGID_END_POINT_ALLOWED
|
||||
):
|
||||
end_point.add(key)
|
||||
if undoc_ops is not None:
|
||||
if key[1] == settings.UNDOC_OPS_STR:
|
||||
undoc_ops.add(key)
|
||||
if spell_checker is not None and spell_errors is not None:
|
||||
err = spell_checker.check(key[1])
|
||||
if err:
|
||||
spell_errors[key] = err
|
||||
|
||||
|
||||
def print_info(reports, pot):
|
||||
def _print(*args, **kwargs):
|
||||
kwargs["file"] = sys.stderr
|
||||
print(*args, **kwargs)
|
||||
|
||||
pot.update_info()
|
||||
|
||||
_print("{} RNA structs were processed (among which {} were skipped), containing {} RNA properties "
|
||||
"(among which {} were skipped).".format(len(reports["rna_structs"]), len(reports["rna_structs_skipped"]),
|
||||
len(reports["rna_props"]), len(reports["rna_props_skipped"])))
|
||||
_print("{} messages were extracted from Python UI code (among which {} were skipped), and {} from C source code "
|
||||
"(among which {} were skipped).".format(len(reports["py_messages"]), len(reports["py_messages_skipped"]),
|
||||
len(reports["src_messages"]), len(reports["src_messages_skipped"])))
|
||||
_print("{} messages were rejected.".format(len(reports["messages_skipped"])))
|
||||
_print("\n")
|
||||
_print("Current POT stats:")
|
||||
pot.print_info(prefix="\t", output=_print)
|
||||
_print("\n")
|
||||
|
||||
check_ctxt = reports["check_ctxt"]
|
||||
if check_ctxt is None:
|
||||
return
|
||||
multi_rnatip = check_ctxt.get("multi_rnatip")
|
||||
multi_lines = check_ctxt.get("multi_lines")
|
||||
py_in_rna = check_ctxt.get("py_in_rna")
|
||||
not_capitalized = check_ctxt.get("not_capitalized")
|
||||
end_point = check_ctxt.get("end_point")
|
||||
undoc_ops = check_ctxt.get("undoc_ops")
|
||||
spell_errors = check_ctxt.get("spell_errors")
|
||||
|
||||
# XXX Temp, no multi_rnatip nor py_in_rna, see below.
|
||||
# Also, multi-lines tooltips are valid now.
|
||||
keys = not_capitalized | end_point | undoc_ops | spell_errors.keys()
|
||||
if keys:
|
||||
_print("WARNINGS:")
|
||||
for key in keys:
|
||||
if undoc_ops and key in undoc_ops:
|
||||
_print("\tThe following operators are undocumented!")
|
||||
else:
|
||||
_print("\t“{}”|“{}”:".format(*key))
|
||||
# We support multi-lines tooltips now...
|
||||
# ~ if multi_lines and key in multi_lines:
|
||||
# ~ _print("\t\t-> newline in this message!")
|
||||
if not_capitalized and key in not_capitalized:
|
||||
_print("\t\t-> message not capitalized!")
|
||||
if end_point and key in end_point:
|
||||
_print("\t\t-> message with endpoint!")
|
||||
# XXX Hide this one for now, too much false positives.
|
||||
# if multi_rnatip and key in multi_rnatip:
|
||||
# _print("\t\t-> tip used in several RNA items")
|
||||
# if py_in_rna and key in py_in_rna:
|
||||
# _print("\t\t-> RNA message also used in py UI code!")
|
||||
if spell_errors and spell_errors.get(key):
|
||||
lines = [
|
||||
"\t\t-> {}: misspelled, suggestions are ({})".format(w, "'" + "', '".join(errs) + "'")
|
||||
for w, errs in spell_errors[key]
|
||||
]
|
||||
_print("\n".join(lines))
|
||||
_print("\t\t{}".format("\n\t\t".join(pot.msgs[key].sources)))
|
||||
|
||||
|
||||
def process_msg(msgs, msgctxt, msgid, msgsrc, reports, check_ctxt, settings):
|
||||
if filter_message(msgid):
|
||||
reports["messages_skipped"].add((msgid, msgsrc))
|
||||
return
|
||||
if not msgctxt:
|
||||
# We do *not* want any "" context!
|
||||
msgctxt = settings.DEFAULT_CONTEXT
|
||||
# Always unescape keys!
|
||||
msgctxt = utils.I18nMessage.do_unescape(msgctxt)
|
||||
msgid = utils.I18nMessage.do_unescape(msgid)
|
||||
key = (msgctxt, msgid)
|
||||
check(check_ctxt, msgs, key, msgsrc, settings)
|
||||
msgsrc = settings.PO_COMMENT_PREFIX_SOURCE_CUSTOM + msgsrc
|
||||
if key not in msgs:
|
||||
msgs[key] = utils.I18nMessage([msgctxt], [msgid], [], [msgsrc], settings=settings)
|
||||
else:
|
||||
msgs[key].comment_lines.append(msgsrc)
|
||||
|
||||
|
||||
##### RNA #####
|
||||
def dump_rna_messages(msgs, reports, settings, verbose=False):
|
||||
"""
|
||||
Dump into messages dict all RNA-defined UI messages (labels en tooltips).
|
||||
"""
|
||||
def class_blacklist():
|
||||
blacklist_rna_class = {getattr(bpy.types, cls_id) for cls_id in (
|
||||
# core classes
|
||||
"Context", "Event", "Function", "UILayout", "UnknownType", "Property", "Struct",
|
||||
# registerable classes
|
||||
"Panel", "Menu", "Header", "RenderEngine", "Operator", "OperatorMacro", "Macro", "KeyingSetInfo",
|
||||
)
|
||||
}
|
||||
|
||||
# More builtin classes we don't need to parse.
|
||||
blacklist_rna_class |= {cls for cls in bpy.types.Property.__subclasses__()}
|
||||
|
||||
return blacklist_rna_class
|
||||
|
||||
check_ctxt_rna = check_ctxt_rna_tip = None
|
||||
check_ctxt = reports["check_ctxt"]
|
||||
if check_ctxt:
|
||||
check_ctxt_rna = {
|
||||
"multi_lines": check_ctxt.get("multi_lines"),
|
||||
"not_capitalized": check_ctxt.get("not_capitalized"),
|
||||
"end_point": check_ctxt.get("end_point"),
|
||||
"undoc_ops": check_ctxt.get("undoc_ops"),
|
||||
"spell_checker": check_ctxt.get("spell_checker"),
|
||||
"spell_errors": check_ctxt.get("spell_errors"),
|
||||
}
|
||||
check_ctxt_rna_tip = check_ctxt_rna
|
||||
check_ctxt_rna_tip["multi_rnatip"] = check_ctxt.get("multi_rnatip")
|
||||
|
||||
default_context = settings.DEFAULT_CONTEXT
|
||||
|
||||
# Function definitions
|
||||
def walk_properties(cls):
|
||||
# This handles properties whose name is the same as their identifier.
|
||||
# Usually, it means that those are internal properties not exposed in the UI, however there are some cases
|
||||
# where the UI label is actually defined and same as the identifier (color spaces e.g., `RGB` etc.).
|
||||
# So we only exclude those properties in case they belong to an operator for now.
|
||||
def prop_name_validate(cls, prop_name, prop_identifier):
|
||||
if prop_name != prop_identifier:
|
||||
return True
|
||||
# Heuristic: A lot of operator's HIDDEN properties have no UI label/description.
|
||||
# While this is not ideal (for API doc purposes, description should always be provided),
|
||||
# for now skip those properties.
|
||||
# NOTE: keep in sync with C code in ui_searchbox_region_draw_cb__operator().
|
||||
if issubclass(cls, bpy.types.OperatorProperties) and "_OT_" in cls.__name__:
|
||||
return False
|
||||
# Heuristic: If UI label is not capitalized, it is likely a private (undocumented) property,
|
||||
# that can be skipped.
|
||||
if prop_name and not prop_name[0].isupper():
|
||||
return False
|
||||
return True
|
||||
|
||||
bl_rna = cls.bl_rna
|
||||
# Get our parents' properties, to not export them multiple times.
|
||||
bl_rna_base = bl_rna.base
|
||||
bl_rna_base_props = set()
|
||||
if bl_rna_base:
|
||||
bl_rna_base_props |= set(bl_rna_base.properties.values())
|
||||
if hasattr(cls, "__bases__"):
|
||||
for cls_base in cls.__bases__:
|
||||
bl_rna_base = getattr(cls_base, "bl_rna", None)
|
||||
if not bl_rna_base:
|
||||
continue
|
||||
bl_rna_base_props |= set(bl_rna_base.properties.values())
|
||||
|
||||
props = sorted(bl_rna.properties, key=lambda p: p.identifier)
|
||||
for prop in props:
|
||||
# Only write this property if our parent hasn't got it.
|
||||
if prop in bl_rna_base_props:
|
||||
continue
|
||||
if prop.identifier in {"rna_type", "bl_icon", "icon"}:
|
||||
continue
|
||||
reports["rna_props"].append((cls, prop))
|
||||
|
||||
msgsrc = "bpy.types.{}.{}".format(bl_rna.identifier, prop.identifier)
|
||||
msgctxt = prop.translation_context or default_context
|
||||
|
||||
if prop.name and prop_name_validate(cls, prop.name, prop.identifier):
|
||||
process_msg(msgs, msgctxt, prop.name, msgsrc, reports, check_ctxt_rna, settings)
|
||||
if prop.description:
|
||||
process_msg(msgs, default_context, prop.description, msgsrc, reports, check_ctxt_rna_tip, settings)
|
||||
|
||||
if isinstance(prop, bpy.types.EnumProperty):
|
||||
done_items = set()
|
||||
for item in prop.enum_items:
|
||||
msgsrc = "bpy.types.{}.{}:'{}'".format(bl_rna.identifier, prop.identifier, item.identifier)
|
||||
done_items.add(item.identifier)
|
||||
if item.name and prop_name_validate(cls, item.name, item.identifier):
|
||||
process_msg(msgs, msgctxt, item.name, msgsrc, reports, check_ctxt_rna, settings)
|
||||
if item.description:
|
||||
process_msg(msgs, default_context, item.description, msgsrc, reports, check_ctxt_rna_tip,
|
||||
settings)
|
||||
for item in prop.enum_items_static:
|
||||
if item.identifier in done_items:
|
||||
continue
|
||||
msgsrc = "bpy.types.{}.{}:'{}'".format(bl_rna.identifier, prop.identifier, item.identifier)
|
||||
done_items.add(item.identifier)
|
||||
if item.name and prop_name_validate(cls, item.name, item.identifier):
|
||||
process_msg(msgs, msgctxt, item.name, msgsrc, reports, check_ctxt_rna, settings)
|
||||
if item.description:
|
||||
process_msg(msgs, default_context, item.description, msgsrc, reports, check_ctxt_rna_tip,
|
||||
settings)
|
||||
|
||||
def walk_tools_definitions(cls):
|
||||
from bl_ui.space_toolsystem_common import ToolDef
|
||||
|
||||
bl_rna = cls.bl_rna
|
||||
op_default_context = bpy.app.translations.contexts.operator_default
|
||||
|
||||
def process_tooldef(tool_context, tool):
|
||||
if not isinstance(tool, ToolDef):
|
||||
if callable(tool):
|
||||
for t in tool(None):
|
||||
process_tooldef(tool_context, t)
|
||||
return
|
||||
msgsrc = "bpy.types.{} Tools: '{}', '{}'".format(bl_rna.identifier, tool_context, tool.idname)
|
||||
if tool.label:
|
||||
process_msg(msgs, op_default_context, tool.label, msgsrc, reports, check_ctxt_rna, settings)
|
||||
# Callable (function) descriptions must handle their translations themselves.
|
||||
if tool.description and not callable(tool.description):
|
||||
process_msg(msgs, default_context, tool.description, msgsrc, reports, check_ctxt_rna_tip, settings)
|
||||
|
||||
for tool_context, tools_defs in cls.tools_all():
|
||||
for tools_group in tools_defs:
|
||||
if tools_group is None:
|
||||
continue
|
||||
elif isinstance(tools_group, tuple) and not isinstance(tools_group, ToolDef):
|
||||
for tool in tools_group:
|
||||
process_tooldef(tool_context, tool)
|
||||
else:
|
||||
process_tooldef(tool_context, tools_group)
|
||||
|
||||
blacklist_rna_class = class_blacklist()
|
||||
|
||||
def walk_class(cls):
|
||||
bl_rna = cls.bl_rna
|
||||
msgsrc = "bpy.types." + bl_rna.identifier
|
||||
msgctxt = bl_rna.translation_context or default_context
|
||||
|
||||
if bl_rna.name and (bl_rna.name != bl_rna.identifier or
|
||||
(msgctxt != default_context and not hasattr(bl_rna, 'bl_label'))):
|
||||
process_msg(msgs, msgctxt, bl_rna.name, msgsrc, reports, check_ctxt_rna, settings)
|
||||
|
||||
if bl_rna.description:
|
||||
process_msg(msgs, default_context, bl_rna.description, msgsrc, reports, check_ctxt_rna_tip, settings)
|
||||
elif cls.__doc__: # XXX Some classes (like KeyingSetInfo subclasses) have void description... :(
|
||||
process_msg(msgs, default_context, cls.__doc__, msgsrc, reports, check_ctxt_rna_tip, settings)
|
||||
|
||||
# Panels' "tabs" system.
|
||||
if hasattr(bl_rna, 'bl_category') and bl_rna.bl_category:
|
||||
process_msg(msgs, default_context, bl_rna.bl_category, msgsrc, reports, check_ctxt_rna, settings)
|
||||
|
||||
if hasattr(bl_rna, 'bl_label') and bl_rna.bl_label:
|
||||
process_msg(msgs, msgctxt, bl_rna.bl_label, msgsrc, reports, check_ctxt_rna, settings)
|
||||
|
||||
# Tools Panels definitions.
|
||||
if hasattr(bl_rna, 'tools_all') and bl_rna.tools_all:
|
||||
walk_tools_definitions(cls)
|
||||
|
||||
walk_properties(cls)
|
||||
|
||||
def walk_keymap_modal_events(keyconfigs, keymap_name, msgsrc_prev, km_i18n_context):
|
||||
for keyconfig in keyconfigs:
|
||||
keymap = keyconfig.keymaps.get(keymap_name, None)
|
||||
if keymap and keymap.is_modal:
|
||||
for modal_event in keymap.modal_event_values:
|
||||
msgsrc = msgsrc_prev + ":'{}'".format(modal_event.identifier)
|
||||
if modal_event.name:
|
||||
process_msg(msgs, km_i18n_context, modal_event.name, msgsrc, reports, None, settings)
|
||||
if modal_event.description:
|
||||
process_msg(msgs, default_context, modal_event.description, msgsrc, reports, None, settings)
|
||||
|
||||
def walk_keymap_hierarchy(hier, msgsrc_prev):
|
||||
km_i18n_context = bpy.app.translations.contexts.id_windowmanager
|
||||
for lvl in hier:
|
||||
msgsrc = msgsrc_prev + "." + lvl[1]
|
||||
if isinstance(lvl[0], str): # Can be a function too, now, with tool system...
|
||||
keymap_name = lvl[0]
|
||||
process_msg(msgs, km_i18n_context, keymap_name, msgsrc, reports, None, settings)
|
||||
walk_keymap_modal_events(bpy.data.window_managers[0].keyconfigs, keymap_name, msgsrc, km_i18n_context)
|
||||
if lvl[3]:
|
||||
walk_keymap_hierarchy(lvl[3], msgsrc)
|
||||
|
||||
# Dump Messages
|
||||
operator_categories = {}
|
||||
|
||||
def process_cls_list(cls_list):
|
||||
if not cls_list:
|
||||
return
|
||||
|
||||
def full_class_id(cls):
|
||||
"""Gives us 'ID.Light.AreaLight' which is best for sorting."""
|
||||
# Always the same issue, some classes listed in blacklist should actually no more exist (they have been
|
||||
# unregistered), but are still listed by __subclasses__() calls... :/
|
||||
if cls in blacklist_rna_class:
|
||||
return cls.__name__
|
||||
cls_id = ""
|
||||
bl_rna = getattr(cls, "bl_rna", None)
|
||||
# It seems that py-defined 'wrappers' RNA classes (like `MeshEdge` in `bpy_types.py`) need to be accessed
|
||||
# once from `bpy.types` before they have a valid `bl_rna` member.
|
||||
# Weirdly enough, this is only triggered on release builds, debug builds somehow do not have that issue.
|
||||
if bl_rna is None:
|
||||
if getattr(bpy.types, cls.__name__, None) is not None:
|
||||
bl_rna = getattr(cls, "bl_rna", None)
|
||||
if bl_rna is None:
|
||||
raise TypeError("Unknown RNA class")
|
||||
while bl_rna:
|
||||
cls_id = bl_rna.identifier + "." + cls_id
|
||||
bl_rna = bl_rna.base
|
||||
return cls_id
|
||||
|
||||
def operator_category(cls):
|
||||
"""Extract operators' categories, as displayed in 'search' space menu."""
|
||||
# NOTE: keep in sync with C code in ui_searchbox_region_draw_cb__operator().
|
||||
if issubclass(cls, bpy.types.OperatorProperties) and "_OT_" in cls.__name__:
|
||||
cat_id = cls.__name__.split("_OT_")[0]
|
||||
if cat_id not in operator_categories:
|
||||
cat_str = cat_id.capitalize() + ":"
|
||||
operator_categories[cat_id] = cat_str
|
||||
|
||||
if verbose:
|
||||
print(cls_list)
|
||||
cls_list.sort(key=full_class_id)
|
||||
for cls in cls_list:
|
||||
if verbose:
|
||||
print(cls)
|
||||
reports["rna_structs"].append(cls)
|
||||
# Ignore those Operator sub-classes (anyway, will get the same from OperatorProperties sub-classes!)...
|
||||
if (cls in blacklist_rna_class) or issubclass(cls, bpy.types.Operator):
|
||||
reports["rna_structs_skipped"].append(cls)
|
||||
else:
|
||||
operator_category(cls)
|
||||
walk_class(cls)
|
||||
# Recursively process subclasses.
|
||||
process_cls_list(cls.__subclasses__())
|
||||
|
||||
# FIXME Workaround weird new (blender 3.2) issue where some classes (like `bpy.types.Modifier`)
|
||||
# are not listed by `bpy.types.ID.__base__.__subclasses__()` until they are accessed from
|
||||
# `bpy.types` (eg just executing `bpy.types.Modifier`).
|
||||
cls_dir = dir(bpy.types)
|
||||
for cls_name in cls_dir:
|
||||
getattr(bpy.types, cls_name)
|
||||
|
||||
# Parse everything (recursively parsing from bpy_struct "class"...).
|
||||
process_cls_list(bpy.types.ID.__base__.__subclasses__())
|
||||
|
||||
# Finalize generated 'operator categories' messages.
|
||||
for cat_str in operator_categories.values():
|
||||
process_msg(msgs, bpy.app.translations.contexts.operator_default, cat_str, "Generated operator category",
|
||||
reports, check_ctxt_rna, settings)
|
||||
|
||||
# Parse keymap preset preferences
|
||||
for preset_filename in sorted(
|
||||
os.listdir(os.path.join(settings.PRESETS_DIR, "keyconfig"))):
|
||||
preset_path = os.path.join(settings.PRESETS_DIR, "keyconfig", preset_filename)
|
||||
if not (os.path.isfile(preset_path) and preset_filename.endswith(".py")):
|
||||
continue
|
||||
preset_name, _ = os.path.splitext(preset_filename)
|
||||
|
||||
bpy.utils.keyconfig_set(preset_path)
|
||||
preset = bpy.data.window_managers[0].keyconfigs[preset_name]
|
||||
if preset.preferences is not None:
|
||||
walk_properties(preset.preferences)
|
||||
|
||||
# And parse keymaps!
|
||||
from bl_keymap_utils import keymap_hierarchy
|
||||
walk_keymap_hierarchy(keymap_hierarchy.generate(), "KM_HIERARCHY")
|
||||
|
||||
|
||||
##### Python source code #####
|
||||
def dump_py_messages_from_files(msgs, reports, files, settings):
|
||||
"""
|
||||
Dump text inlined in the python files given, e.g. 'My Name' in:
|
||||
layout.prop("someprop", text="My Name")
|
||||
"""
|
||||
import ast
|
||||
|
||||
bpy_struct = bpy.types.ID.__base__
|
||||
i18n_contexts = bpy.app.translations.contexts
|
||||
|
||||
root_paths = tuple(bpy.utils.resource_path(t) for t in ('USER', 'LOCAL', 'SYSTEM'))
|
||||
|
||||
def make_rel(path):
|
||||
for rp in root_paths:
|
||||
if path.startswith(rp):
|
||||
try: # can't always find the relative path (between drive letters on windows)
|
||||
return os.path.relpath(path, rp)
|
||||
except ValueError:
|
||||
return path
|
||||
# Use binary's dir as fallback...
|
||||
try: # can't always find the relative path (between drive letters on windows)
|
||||
return os.path.relpath(path, os.path.dirname(bpy.app.binary_path))
|
||||
except ValueError:
|
||||
return path
|
||||
|
||||
# Helper function
|
||||
def extract_strings_ex(node, is_split=False):
|
||||
"""
|
||||
Recursively get strings, needed in case we have "Blah" + "Blah", passed as an argument in that case it won't
|
||||
evaluate to a string. However, break on some kind of stopper nodes, like e.g. Subscript.
|
||||
"""
|
||||
# New in py 3.8: all constants are of type 'ast.Constant'.
|
||||
# 'ast.Str' will have to be removed when we officially switch to this version.
|
||||
if type(node) in {ast.Str, getattr(ast, "Constant", None)}:
|
||||
eval_str = ast.literal_eval(node)
|
||||
if eval_str and type(eval_str) == str:
|
||||
yield (is_split, eval_str, (node,))
|
||||
else:
|
||||
is_split = (type(node) in separate_nodes)
|
||||
for nd in ast.iter_child_nodes(node):
|
||||
if type(nd) not in stopper_nodes:
|
||||
yield from extract_strings_ex(nd, is_split=is_split)
|
||||
|
||||
def _extract_string_merge(estr_ls, nds_ls):
|
||||
return "".join(s for s in estr_ls if s is not None), tuple(n for n in nds_ls if n is not None)
|
||||
|
||||
def extract_strings(node):
|
||||
estr_ls = []
|
||||
nds_ls = []
|
||||
for is_split, estr, nds in extract_strings_ex(node):
|
||||
estr_ls.append(estr)
|
||||
nds_ls.extend(nds)
|
||||
ret = _extract_string_merge(estr_ls, nds_ls)
|
||||
return ret
|
||||
|
||||
def extract_strings_split(node):
|
||||
"""
|
||||
Returns a list args as returned by 'extract_strings()', but split into groups based on separate_nodes, this way
|
||||
expressions like ("A" if test else "B") won't be merged but "A" + "B" will.
|
||||
"""
|
||||
estr_ls = []
|
||||
nds_ls = []
|
||||
bag = []
|
||||
for is_split, estr, nds in extract_strings_ex(node):
|
||||
if is_split:
|
||||
bag.append((estr_ls, nds_ls))
|
||||
estr_ls = []
|
||||
nds_ls = []
|
||||
|
||||
estr_ls.append(estr)
|
||||
nds_ls.extend(nds)
|
||||
|
||||
bag.append((estr_ls, nds_ls))
|
||||
|
||||
return [_extract_string_merge(estr_ls, nds_ls) for estr_ls, nds_ls in bag]
|
||||
|
||||
i18n_ctxt_ids = {v for v in bpy.app.translations.contexts_C_to_py.values()}
|
||||
|
||||
def _ctxt_to_ctxt(node):
|
||||
# We must try, to some extend, to get contexts from vars instead of only literal strings...
|
||||
ctxt = extract_strings(node)[0]
|
||||
if ctxt:
|
||||
return ctxt
|
||||
# Basically, we search for attributes matching py context names, for now.
|
||||
# So non-literal contexts should be used that way:
|
||||
# i18n_ctxt = bpy.app.translations.contexts
|
||||
# foobar(text="Foo", text_ctxt=i18n_ctxt.id_object)
|
||||
if type(node) == ast.Attribute:
|
||||
if node.attr in i18n_ctxt_ids:
|
||||
#print(node, node.attr, getattr(i18n_contexts, node.attr))
|
||||
return getattr(i18n_contexts, node.attr)
|
||||
return i18n_contexts.default
|
||||
|
||||
def _op_to_ctxt(node):
|
||||
# Some smart coders like things like:
|
||||
# >>> row.operator("preferences.addon_disable" if is_enabled else "preferences.addon_enable", ...)
|
||||
# We only take first arg into account here!
|
||||
bag = extract_strings_split(node)
|
||||
opname, _ = bag[0]
|
||||
if not opname:
|
||||
return i18n_contexts.operator_default
|
||||
op = bpy.ops
|
||||
for n in opname.split('.'):
|
||||
op = getattr(op, n)
|
||||
try:
|
||||
return op.get_rna_type().translation_context
|
||||
except Exception as e:
|
||||
default_op_context = i18n_contexts.operator_default
|
||||
print("ERROR: ", str(e))
|
||||
print(" Assuming default operator context '{}'".format(default_op_context))
|
||||
return default_op_context
|
||||
|
||||
# Gather function names.
|
||||
# In addition of UI func, also parse pgettext ones...
|
||||
# Tuples of (module name, (short names, ...)).
|
||||
pgettext_variants = (
|
||||
("pgettext", ("_",)),
|
||||
("pgettext_iface", ("iface_",)),
|
||||
("pgettext_tip", ("tip_",)),
|
||||
("pgettext_data", ("data_",)),
|
||||
)
|
||||
pgettext_variants_args = {"msgid": (0, {"msgctxt": 1})}
|
||||
|
||||
# key: msgid keywords.
|
||||
# val: tuples of ((keywords,), context_getter_func) to get a context for that msgid.
|
||||
# Note: order is important, first one wins!
|
||||
translate_kw = {
|
||||
"text": ((("text_ctxt",), _ctxt_to_ctxt),
|
||||
(("operator",), _op_to_ctxt),
|
||||
),
|
||||
"msgid": ((("msgctxt",), _ctxt_to_ctxt),
|
||||
),
|
||||
"message": (),
|
||||
"heading": (),
|
||||
}
|
||||
|
||||
context_kw_set = {}
|
||||
for k, ctxts in translate_kw.items():
|
||||
s = set()
|
||||
for c, _ in ctxts:
|
||||
s |= set(c)
|
||||
context_kw_set[k] = s
|
||||
|
||||
# {func_id: {msgid: (arg_pos,
|
||||
# {msgctxt: arg_pos,
|
||||
# ...
|
||||
# }
|
||||
# ),
|
||||
# ...
|
||||
# },
|
||||
# ...
|
||||
# }
|
||||
func_translate_args = {}
|
||||
|
||||
# First, functions from UILayout
|
||||
# First loop is for msgid args, second one is for msgctxt args.
|
||||
for func_id, func in bpy.types.UILayout.bl_rna.functions.items():
|
||||
# check it has one or more arguments as defined in translate_kw
|
||||
for arg_pos, (arg_kw, arg) in enumerate(func.parameters.items()):
|
||||
if ((arg_kw in translate_kw) and (not arg.is_output) and (arg.type == 'STRING')):
|
||||
func_translate_args.setdefault(func_id, {})[arg_kw] = (arg_pos, {})
|
||||
for func_id, func in bpy.types.UILayout.bl_rna.functions.items():
|
||||
if func_id not in func_translate_args:
|
||||
continue
|
||||
for arg_pos, (arg_kw, arg) in enumerate(func.parameters.items()):
|
||||
if (not arg.is_output) and (arg.type == 'STRING'):
|
||||
for msgid, msgctxts in context_kw_set.items():
|
||||
if arg_kw in msgctxts:
|
||||
func_translate_args[func_id][msgid][1][arg_kw] = arg_pos
|
||||
# The report() func of operators.
|
||||
for func_id, func in bpy.types.Operator.bl_rna.functions.items():
|
||||
# check it has one or more arguments as defined in translate_kw
|
||||
for arg_pos, (arg_kw, arg) in enumerate(func.parameters.items()):
|
||||
if ((arg_kw in translate_kw) and (not arg.is_output) and (arg.type == 'STRING')):
|
||||
func_translate_args.setdefault(func_id, {})[arg_kw] = (arg_pos, {})
|
||||
# We manually add funcs from bpy.app.translations
|
||||
for func_id, func_ids in pgettext_variants:
|
||||
func_translate_args[func_id] = pgettext_variants_args
|
||||
for sub_func_id in func_ids:
|
||||
func_translate_args[sub_func_id] = pgettext_variants_args
|
||||
# print(func_translate_args)
|
||||
|
||||
# Break recursive nodes look up on some kind of nodes.
|
||||
# E.g. we don't want to get strings inside subscripts (blah["foo"])!
|
||||
# we don't want to get strings from comparisons (foo.type == 'BAR').
|
||||
stopper_nodes = {ast.Subscript, ast.Compare}
|
||||
# Consider strings separate: ("a" if test else "b")
|
||||
separate_nodes = {ast.IfExp}
|
||||
|
||||
check_ctxt_py = None
|
||||
if reports["check_ctxt"]:
|
||||
check_ctxt = reports["check_ctxt"]
|
||||
check_ctxt_py = {
|
||||
"py_in_rna": (check_ctxt.get("py_in_rna"), set(msgs.keys())),
|
||||
"multi_lines": check_ctxt.get("multi_lines"),
|
||||
"not_capitalized": check_ctxt.get("not_capitalized"),
|
||||
"end_point": check_ctxt.get("end_point"),
|
||||
"spell_checker": check_ctxt.get("spell_checker"),
|
||||
"spell_errors": check_ctxt.get("spell_errors"),
|
||||
}
|
||||
|
||||
for fp in files:
|
||||
# ~ print("Checking File ", fp)
|
||||
with open(fp, 'r', encoding="utf8") as filedata:
|
||||
root_node = ast.parse(filedata.read(), fp, 'exec')
|
||||
|
||||
fp_rel = make_rel(fp)
|
||||
|
||||
for node in ast.walk(root_node):
|
||||
if type(node) == ast.Call:
|
||||
# ~ print("found function at")
|
||||
# ~ print("%s:%d" % (fp, node.lineno))
|
||||
|
||||
# We can't skip such situations! from blah import foo\nfoo("bar") would also be an ast.Name func!
|
||||
if type(node.func) == ast.Name:
|
||||
func_id = node.func.id
|
||||
elif hasattr(node.func, "attr"):
|
||||
func_id = node.func.attr
|
||||
# Ugly things like getattr(self, con.type)(context, box, con)
|
||||
else:
|
||||
continue
|
||||
|
||||
func_args = func_translate_args.get(func_id, {})
|
||||
|
||||
# First try to get i18n contexts, for every possible msgid id.
|
||||
msgctxts = dict.fromkeys(func_args.keys(), "")
|
||||
for msgid, (_, context_args) in func_args.items():
|
||||
context_elements = {}
|
||||
for arg_kw, arg_pos in context_args.items():
|
||||
if arg_pos < len(node.args):
|
||||
context_elements[arg_kw] = node.args[arg_pos]
|
||||
else:
|
||||
for kw in node.keywords:
|
||||
if kw.arg == arg_kw:
|
||||
context_elements[arg_kw] = kw.value
|
||||
break
|
||||
# ~ print(context_elements)
|
||||
for kws, proc in translate_kw[msgid]:
|
||||
if set(kws) <= context_elements.keys():
|
||||
args = tuple(context_elements[k] for k in kws)
|
||||
# ~ print("running ", proc, " with ", args)
|
||||
ctxt = proc(*args)
|
||||
if ctxt:
|
||||
msgctxts[msgid] = ctxt
|
||||
break
|
||||
|
||||
# ~ print(func_args)
|
||||
# do nothing if not found
|
||||
for arg_kw, (arg_pos, _) in func_args.items():
|
||||
msgctxt = msgctxts[arg_kw]
|
||||
estr_lst = [(None, ())]
|
||||
if arg_pos < len(node.args):
|
||||
estr_lst = extract_strings_split(node.args[arg_pos])
|
||||
else:
|
||||
for kw in node.keywords:
|
||||
if kw.arg == arg_kw:
|
||||
# ~ print(kw.arg, kw.value)
|
||||
estr_lst = extract_strings_split(kw.value)
|
||||
break
|
||||
for estr, nds in estr_lst:
|
||||
# ~ print(estr, nds)
|
||||
if estr:
|
||||
if nds:
|
||||
msgsrc = "{}:{}".format(fp_rel, sorted({nd.lineno for nd in nds})[0])
|
||||
else:
|
||||
msgsrc = "{}:???".format(fp_rel)
|
||||
process_msg(msgs, msgctxt, estr, msgsrc, reports, check_ctxt_py, settings)
|
||||
reports["py_messages"].append((msgctxt, estr, msgsrc))
|
||||
|
||||
|
||||
def dump_py_messages(msgs, reports, addons, settings, addons_only=False):
|
||||
def _get_files(path):
|
||||
if not os.path.exists(path):
|
||||
return []
|
||||
if os.path.isdir(path):
|
||||
return [os.path.join(dpath, fn) for dpath, _, fnames in os.walk(path) for fn in fnames
|
||||
if not fn.startswith("_") and fn.endswith(".py")]
|
||||
return [path]
|
||||
|
||||
files = []
|
||||
if not addons_only:
|
||||
for path in settings.CUSTOM_PY_UI_FILES:
|
||||
for root in (bpy.utils.resource_path(t) for t in ('USER', 'LOCAL', 'SYSTEM')):
|
||||
files += _get_files(os.path.join(root, path))
|
||||
|
||||
# Add all given addons.
|
||||
for mod in addons:
|
||||
fn = mod.__file__
|
||||
if os.path.basename(fn) == "__init__.py":
|
||||
files += _get_files(os.path.dirname(fn))
|
||||
else:
|
||||
files.append(fn)
|
||||
|
||||
dump_py_messages_from_files(msgs, reports, sorted(files), settings)
|
||||
|
||||
|
||||
##### C source code #####
|
||||
def dump_src_messages(msgs, reports, settings):
|
||||
def get_contexts():
|
||||
"""Return a mapping {C_CTXT_NAME: ctxt_value}."""
|
||||
return {k: getattr(bpy.app.translations.contexts, n) for k, n in bpy.app.translations.contexts_C_to_py.items()}
|
||||
|
||||
contexts = get_contexts()
|
||||
|
||||
# Build regexes to extract messages (with optional contexts) from C source.
|
||||
pygettexts = tuple(re.compile(r).search for r in settings.PYGETTEXT_KEYWORDS)
|
||||
|
||||
_clean_str = re.compile(settings.str_clean_re).finditer
|
||||
|
||||
def clean_str(s):
|
||||
# The encode/decode to/from 'raw_unicode_escape' allows to transform the C-type unicode hexadecimal escapes
|
||||
# (like '\u2715' for the '×' symbol) back into a proper unicode character.
|
||||
return "".join(
|
||||
m.group("clean") for m in _clean_str(s)
|
||||
).encode('raw_unicode_escape').decode('raw_unicode_escape')
|
||||
|
||||
def dump_src_file(path, rel_path, msgs, reports, settings):
|
||||
def process_entry(_msgctxt, _msgid):
|
||||
# Context.
|
||||
msgctxt = settings.DEFAULT_CONTEXT
|
||||
if _msgctxt:
|
||||
if _msgctxt in contexts:
|
||||
msgctxt = contexts[_msgctxt]
|
||||
elif '"' in _msgctxt or "'" in _msgctxt:
|
||||
msgctxt = clean_str(_msgctxt)
|
||||
else:
|
||||
print("WARNING: raw context “{}” couldn’t be resolved!".format(_msgctxt))
|
||||
# Message.
|
||||
msgid = ""
|
||||
if _msgid:
|
||||
if '"' in _msgid or "'" in _msgid:
|
||||
msgid = clean_str(_msgid)
|
||||
else:
|
||||
print("WARNING: raw message “{}” couldn’t be resolved!".format(_msgid))
|
||||
return msgctxt, msgid
|
||||
|
||||
check_ctxt_src = None
|
||||
if reports["check_ctxt"]:
|
||||
check_ctxt = reports["check_ctxt"]
|
||||
check_ctxt_src = {
|
||||
"multi_lines": check_ctxt.get("multi_lines"),
|
||||
"not_capitalized": check_ctxt.get("not_capitalized"),
|
||||
"end_point": check_ctxt.get("end_point"),
|
||||
"spell_checker": check_ctxt.get("spell_checker"),
|
||||
"spell_errors": check_ctxt.get("spell_errors"),
|
||||
}
|
||||
|
||||
data = ""
|
||||
with open(path, encoding="utf8") as f:
|
||||
data = f.read()
|
||||
for srch in pygettexts:
|
||||
m = srch(data)
|
||||
line = pos = 0
|
||||
while m:
|
||||
d = m.groupdict()
|
||||
# Line.
|
||||
line += data[pos:m.start()].count('\n')
|
||||
msgsrc = rel_path + ":" + str(line)
|
||||
_msgid = d.get("msg_raw")
|
||||
if _msgid not in {'""', "''"}:
|
||||
# First, try the "multi-contexts" stuff!
|
||||
_msgctxts = tuple(d.get("ctxt_raw{}".format(i)) for i in range(settings.PYGETTEXT_MAX_MULTI_CTXT))
|
||||
if _msgctxts[0]:
|
||||
for _msgctxt in _msgctxts:
|
||||
if not _msgctxt:
|
||||
break
|
||||
msgctxt, msgid = process_entry(_msgctxt, _msgid)
|
||||
process_msg(msgs, msgctxt, msgid, msgsrc, reports, check_ctxt_src, settings)
|
||||
reports["src_messages"].append((msgctxt, msgid, msgsrc))
|
||||
else:
|
||||
_msgctxt = d.get("ctxt_raw")
|
||||
msgctxt, msgid = process_entry(_msgctxt, _msgid)
|
||||
process_msg(msgs, msgctxt, msgid, msgsrc, reports, check_ctxt_src, settings)
|
||||
reports["src_messages"].append((msgctxt, msgid, msgsrc))
|
||||
|
||||
pos = m.end()
|
||||
line += data[m.start():pos].count('\n')
|
||||
m = srch(data, pos)
|
||||
|
||||
forbidden = set()
|
||||
forced = set()
|
||||
if os.path.isfile(settings.SRC_POTFILES):
|
||||
with open(settings.SRC_POTFILES, encoding="utf8") as src:
|
||||
for l in src:
|
||||
if l[0] == '-':
|
||||
forbidden.add(l[1:].rstrip('\n'))
|
||||
elif l[0] != '#':
|
||||
forced.add(l.rstrip('\n'))
|
||||
for root, dirs, files in os.walk(settings.POTFILES_SOURCE_DIR):
|
||||
if "/.svn" in root:
|
||||
continue
|
||||
for fname in files:
|
||||
if os.path.splitext(fname)[1] not in settings.PYGETTEXT_ALLOWED_EXTS:
|
||||
continue
|
||||
path = os.path.join(root, fname)
|
||||
try: # can't always find the relative path (between drive letters on windows)
|
||||
rel_path = os.path.relpath(path, settings.SOURCE_DIR)
|
||||
except ValueError:
|
||||
rel_path = path
|
||||
if rel_path in forbidden:
|
||||
continue
|
||||
elif rel_path not in forced:
|
||||
forced.add(rel_path)
|
||||
for rel_path in sorted(forced):
|
||||
path = os.path.join(settings.SOURCE_DIR, rel_path)
|
||||
if os.path.exists(path):
|
||||
dump_src_file(path, rel_path, msgs, reports, settings)
|
||||
|
||||
|
||||
def dump_preset_messages(msgs, reports, settings):
|
||||
files = []
|
||||
for dpath, _, fnames in os.walk(settings.PRESETS_DIR):
|
||||
for fname in fnames:
|
||||
if fname.startswith("_") or not fname.endswith(".py"):
|
||||
continue
|
||||
path = os.path.join(dpath, fname)
|
||||
try: # can't always find the relative path (between drive letters on windows)
|
||||
rel_path = os.path.relpath(path, settings.PRESETS_DIR)
|
||||
except ValueError:
|
||||
rel_path = path
|
||||
files.append(rel_path)
|
||||
for rel_path in sorted(files):
|
||||
msgsrc, msgid = os.path.split(rel_path)
|
||||
msgsrc = "Preset from " + msgsrc
|
||||
msgid = bpy.path.display_name(msgid, title_case=False)
|
||||
process_msg(msgs, settings.DEFAULT_CONTEXT, msgid, msgsrc, reports, None, settings)
|
||||
|
||||
|
||||
def dump_template_messages(msgs, reports, settings):
|
||||
bfiles = [""] # General template, no name needed.
|
||||
bfiles += glob.glob(settings.TEMPLATES_DIR + "/**/*.blend", recursive=True)
|
||||
|
||||
workspace_names = {}
|
||||
|
||||
for bfile in bfiles:
|
||||
template = os.path.dirname(bfile)
|
||||
template = os.path.basename(template)
|
||||
bpy.ops.wm.read_homefile(use_factory_startup=True, app_template=template)
|
||||
for ws in bpy.data.workspaces:
|
||||
names = workspace_names.setdefault(ws.name, [])
|
||||
names.append(template or "General")
|
||||
|
||||
from bpy.app.translations import contexts as i18n_contexts
|
||||
msgctxt = i18n_contexts.id_workspace
|
||||
for workspace_name in sorted(workspace_names):
|
||||
for msgsrc in sorted(workspace_names[workspace_name]):
|
||||
msgsrc = "Workspace from template " + msgsrc
|
||||
process_msg(msgs, msgctxt, workspace_name, msgsrc,
|
||||
reports, None, settings)
|
||||
|
||||
|
||||
def dump_addon_bl_info(msgs, reports, module, settings):
|
||||
for prop in ('name', 'location', 'description'):
|
||||
process_msg(
|
||||
msgs,
|
||||
settings.DEFAULT_CONTEXT,
|
||||
module.bl_info[prop],
|
||||
"Add-on " +
|
||||
module.bl_info['name'] +
|
||||
" info: " +
|
||||
prop,
|
||||
reports,
|
||||
None,
|
||||
settings,
|
||||
)
|
||||
|
||||
|
||||
##### Main functions! #####
|
||||
def dump_messages(do_messages, do_checks, settings):
|
||||
bl_ver = "Blender " + bpy.app.version_string
|
||||
bl_hash = bpy.app.build_hash
|
||||
bl_date = datetime.datetime.strptime(bpy.app.build_date.decode() + "T" + bpy.app.build_time.decode(),
|
||||
"%Y-%m-%dT%H:%M:%S")
|
||||
pot = utils.I18nMessages.gen_empty_messages(settings.PARSER_TEMPLATE_ID, bl_ver, bl_hash, bl_date, bl_date.year,
|
||||
settings=settings)
|
||||
msgs = pot.msgs
|
||||
|
||||
# Enable all wanted addons.
|
||||
# For now, enable all official addons, before extracting msgids.
|
||||
addons = utils.enable_addons(support={"OFFICIAL"})
|
||||
# Note this is not needed if we have been started with factory settings, but just in case...
|
||||
# XXX This is not working well, spent a whole day trying to understand *why* we still have references of
|
||||
# those removed classes in things like `bpy.types.OperatorProperties.__subclasses__()`
|
||||
# (could not even reproduce it from regular py console in Blender with UI...).
|
||||
# For some reasons, cleanup does not happen properly, *and* we have no way to tell which class is valid
|
||||
# and which has been unregistered. So for now, just go for the dirty, easy way: do not disable add-ons. :(
|
||||
# ~ utils.enable_addons(support={"COMMUNITY", "TESTING"}, disable=True)
|
||||
|
||||
reports = _gen_reports(_gen_check_ctxt(settings) if do_checks else None)
|
||||
|
||||
# Get strings from RNA.
|
||||
dump_rna_messages(msgs, reports, settings)
|
||||
|
||||
# Get strings from UI layout definitions text="..." args.
|
||||
dump_py_messages(msgs, reports, addons, settings)
|
||||
|
||||
# Get strings from C source code.
|
||||
dump_src_messages(msgs, reports, settings)
|
||||
|
||||
# Get strings from presets.
|
||||
dump_preset_messages(msgs, reports, settings)
|
||||
|
||||
# Get strings from startup templates.
|
||||
dump_template_messages(msgs, reports, settings)
|
||||
|
||||
# Get strings from addons' bl_info.
|
||||
import addon_utils
|
||||
for module in addon_utils.modules():
|
||||
# Only process official add-ons, i.e. those marked as 'OFFICIAL' and
|
||||
# existing in the system add-ons directory (not user-installed ones).
|
||||
if (module.bl_info['support'] != 'OFFICIAL'
|
||||
or not bpy.path.is_subdir(module.__file__, bpy.utils.system_resource('SCRIPTS'))):
|
||||
continue
|
||||
dump_addon_bl_info(msgs, reports, module, settings)
|
||||
|
||||
# Get strings from addons' categories.
|
||||
system_categories = set()
|
||||
for module in addon_utils.modules():
|
||||
if bpy.path.is_subdir(module.__file__, bpy.utils.system_resource('SCRIPTS')):
|
||||
system_categories.add(module.bl_info['category'])
|
||||
for uid, label, tip in bpy.types.WindowManager.addon_filter.keywords['items'](
|
||||
bpy.context.window_manager,
|
||||
bpy.context,
|
||||
):
|
||||
if label in system_categories:
|
||||
# Only process add-on if it a system one (i.e shipped with Blender). Also,
|
||||
# we do want to translate official categories, even if they have no official add-ons,
|
||||
# hence the different test than below.
|
||||
process_msg(msgs, settings.DEFAULT_CONTEXT, label, "Add-ons' categories", reports, None, settings)
|
||||
elif tip:
|
||||
# Only special categories get a tip (All and User).
|
||||
process_msg(msgs, settings.DEFAULT_CONTEXT, label, "Add-ons' categories", reports, None, settings)
|
||||
process_msg(msgs, settings.DEFAULT_CONTEXT, tip, "Add-ons' categories", reports, None, settings)
|
||||
|
||||
# Get strings specific to translations' menu.
|
||||
for lng in settings.LANGUAGES:
|
||||
process_msg(msgs, settings.DEFAULT_CONTEXT, lng[1], "Languages’ labels from bl_i18n_utils/settings.py",
|
||||
reports, None, settings)
|
||||
for cat in settings.LANGUAGES_CATEGORIES:
|
||||
process_msg(msgs, settings.DEFAULT_CONTEXT, cat[1],
|
||||
"Language categories’ labels from bl_i18n_utils/settings.py", reports, None, settings)
|
||||
|
||||
# pot.check()
|
||||
pot.unescape() # Strings gathered in py/C source code may contain escaped chars...
|
||||
print_info(reports, pot)
|
||||
# pot.check()
|
||||
|
||||
if do_messages:
|
||||
print("Writing messages…")
|
||||
pot.write('PO', settings.FILE_NAME_POT)
|
||||
|
||||
print("Finished extracting UI messages!")
|
||||
|
||||
return pot # Not used currently, but may be useful later (and to be consistent with dump_addon_messages!).
|
||||
|
||||
|
||||
def dump_addon_messages(module_name, do_checks, settings):
|
||||
import addon_utils
|
||||
|
||||
# Get current addon state (loaded or not):
|
||||
was_loaded = addon_utils.check(module_name)[1]
|
||||
|
||||
# Enable our addon.
|
||||
addon = utils.enable_addons(addons={module_name})[0]
|
||||
|
||||
addon_info = addon_utils.module_bl_info(addon)
|
||||
ver = addon_info["name"] + " " + ".".join(str(v) for v in addon_info["version"])
|
||||
rev = 0
|
||||
date = datetime.datetime.now()
|
||||
pot = utils.I18nMessages.gen_empty_messages(settings.PARSER_TEMPLATE_ID, ver, rev, date, date.year,
|
||||
settings=settings)
|
||||
msgs = pot.msgs
|
||||
|
||||
minus_pot = utils.I18nMessages.gen_empty_messages(settings.PARSER_TEMPLATE_ID, ver, rev, date, date.year,
|
||||
settings=settings)
|
||||
minus_msgs = minus_pot.msgs
|
||||
|
||||
check_ctxt = _gen_check_ctxt(settings) if do_checks else None
|
||||
minus_check_ctxt = _gen_check_ctxt(settings) if do_checks else None
|
||||
|
||||
# Get strings from RNA, our addon being enabled.
|
||||
print("A")
|
||||
reports = _gen_reports(check_ctxt)
|
||||
print("B")
|
||||
dump_rna_messages(msgs, reports, settings)
|
||||
print("C")
|
||||
|
||||
# Now disable our addon, and rescan RNA.
|
||||
utils.enable_addons(addons={module_name}, disable=True)
|
||||
print("D")
|
||||
reports["check_ctxt"] = minus_check_ctxt
|
||||
print("E")
|
||||
dump_rna_messages(minus_msgs, reports, settings)
|
||||
print("F")
|
||||
|
||||
# Restore previous state if needed!
|
||||
if was_loaded:
|
||||
utils.enable_addons(addons={module_name})
|
||||
|
||||
# and make the diff!
|
||||
for key in minus_msgs:
|
||||
if key != settings.PO_HEADER_KEY:
|
||||
if key in msgs:
|
||||
del msgs[key]
|
||||
else:
|
||||
# This should not happen, but some messages seem to have
|
||||
# leaked on add-on unregister and register?
|
||||
print(f"Key not found in msgs: {key}")
|
||||
|
||||
if check_ctxt:
|
||||
_diff_check_ctxt(check_ctxt, minus_check_ctxt)
|
||||
|
||||
# and we are done with those!
|
||||
del minus_pot
|
||||
del minus_msgs
|
||||
del minus_check_ctxt
|
||||
|
||||
# get strings from UI layout definitions text="..." args
|
||||
reports["check_ctxt"] = check_ctxt
|
||||
dump_py_messages(msgs, reports, {addon}, settings, addons_only=True)
|
||||
|
||||
# Get strings from the addon's bl_info
|
||||
dump_addon_bl_info(msgs, reports, addon, settings)
|
||||
|
||||
pot.unescape() # Strings gathered in py/C source code may contain escaped chars...
|
||||
print_info(reports, pot)
|
||||
|
||||
print("Finished extracting UI messages!")
|
||||
|
||||
return pot
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
import bpy
|
||||
except ImportError:
|
||||
print("This script must run from inside blender")
|
||||
return
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
# Get rid of Blender args!
|
||||
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
||||
|
||||
parser = argparse.ArgumentParser(description="Process UI messages from inside Blender.")
|
||||
parser.add_argument('-c', '--no_checks', default=True, action="store_false", help="No checks over UI messages.")
|
||||
parser.add_argument('-m', '--no_messages', default=True, action="store_false", help="No export of UI messages.")
|
||||
parser.add_argument('-o', '--output', default=None, help="Output POT file path.")
|
||||
parser.add_argument('-s', '--settings', default=None,
|
||||
help="Override (some) default settings. Either a JSon file name, or a JSon string.")
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
settings = settings_i18n.I18nSettings()
|
||||
settings.load(args.settings)
|
||||
|
||||
if args.output:
|
||||
settings.FILE_NAME_POT = args.output
|
||||
|
||||
dump_messages(do_messages=args.no_messages, do_checks=args.no_checks, settings=settings)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n\n *** Running {} *** \n".format(__file__))
|
||||
main()
|
||||
130
scripts/modules/bl_i18n_utils/merge_po.py
Executable file
130
scripts/modules/bl_i18n_utils/merge_po.py
Executable file
@@ -0,0 +1,130 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Merge one or more .po files into the first dest one.
|
||||
# If a msgkey is present in more than one merged po, the one in the first file wins, unless
|
||||
# it’s marked as fuzzy and one later is not.
|
||||
# The fuzzy flag is removed if necessary.
|
||||
# All other comments are never modified.
|
||||
# However, commented messages in dst will always remain commented, and commented messages are
|
||||
# never merged from sources.
|
||||
|
||||
import sys
|
||||
|
||||
if __package__ is None:
|
||||
import settings
|
||||
import utils
|
||||
else:
|
||||
from . import (
|
||||
settings,
|
||||
utils,
|
||||
)
|
||||
|
||||
|
||||
# XXX This is a quick hack to make it work with new I18n... objects! To be reworked!
|
||||
def main():
|
||||
import argparse
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Merge one or more .po files into the first dest one.\n"
|
||||
"If a msgkey (msgctxt, msgid) is present in more than one merged po, the one in the first file "
|
||||
"wins, unless it’s marked as fuzzy and one later is not.\n"
|
||||
"The fuzzy flag is removed if necessary.\n"
|
||||
"All other comments are never modified.\n"
|
||||
"Commented messages in dst will always remain commented, and commented messages are never merged "
|
||||
"from sources."
|
||||
),
|
||||
)
|
||||
parser.add_argument('-s', '--stats', action="store_true", help="Show statistics info.")
|
||||
parser.add_argument('-r', '--replace', action="store_true",
|
||||
help="Replace existing messages of same \"level\" already in dest po.")
|
||||
parser.add_argument('dst', metavar='dst.po', help="The dest po into which merge the others.")
|
||||
parser.add_argument('src', metavar='src.po', nargs='+', help="The po's to merge into the dst.po one.")
|
||||
args = parser.parse_args()
|
||||
|
||||
ret = 0
|
||||
done_msgkeys = set()
|
||||
done_fuzzy_msgkeys = set()
|
||||
nbr_merged = 0
|
||||
nbr_replaced = 0
|
||||
nbr_added = 0
|
||||
nbr_unfuzzied = 0
|
||||
|
||||
dst_msgs = utils.I18nMessages(kind='PO', src=args.dst)
|
||||
if dst_msgs.parsing_errors:
|
||||
print("Dest po is BROKEN, aborting.")
|
||||
return 1
|
||||
if args.stats:
|
||||
print("Dest po, before merging:")
|
||||
dst_msgs.print_stats(prefix="\t")
|
||||
# If we don’t want to replace existing valid translations, pre-populate done_msgkeys and done_fuzzy_msgkeys.
|
||||
if not args.replace:
|
||||
done_msgkeys = dst_msgs.trans_msgs.copy()
|
||||
done_fuzzy_msgkeys = dst_msgs.fuzzy_msgs.copy()
|
||||
for po in args.src:
|
||||
msgs = utils.I18nMessages(kind='PO', src=po)
|
||||
if msgs.parsing_errors:
|
||||
print("\tSrc po {} is BROKEN, skipping.".format(po))
|
||||
ret = 1
|
||||
continue
|
||||
print("\tMerging {}...".format(po))
|
||||
if args.stats:
|
||||
print("\t\tMerged po stats:")
|
||||
msgs.print_stats(prefix="\t\t\t")
|
||||
for msgkey, msg in msgs.msgs.items():
|
||||
msgctxt, msgid = msgkey
|
||||
# This msgkey has already been completely merged, or is a commented one,
|
||||
# or the new message is commented, skip it.
|
||||
if msgkey in (done_msgkeys | dst_msgs.comm_msgs | msgs.comm_msgs):
|
||||
continue
|
||||
is_ttip = msg.is_tooltip
|
||||
# New messages does not yet exists in dest.
|
||||
if msgkey not in dst_msgs.msgs:
|
||||
dst_msgs[msgkey] = msgs.msgs[msgkey]
|
||||
if msgkey in msgs.fuzzy_msgs:
|
||||
done_fuzzy_msgkeys.add(msgkey)
|
||||
dst_msgs.fuzzy_msgs.add(msgkey)
|
||||
elif msgkey in msgs.trans_msgs:
|
||||
done_msgkeys.add(msgkey)
|
||||
dst_msgs.trans_msgs.add(msgkey)
|
||||
nbr_added += 1
|
||||
# From now on, the new messages is already in dst.
|
||||
# New message is neither translated nor fuzzy, skip it.
|
||||
elif msgkey not in (msgs.trans_msgs | msgs.fuzzy_msgs):
|
||||
continue
|
||||
# From now on, the new message is either translated or fuzzy!
|
||||
# The new message is translated.
|
||||
elif msgkey in msgs.trans_msgs:
|
||||
dst_msgs.msgs[msgkey].msgstr = msg.msgstr
|
||||
done_msgkeys.add(msgkey)
|
||||
done_fuzzy_msgkeys.discard(msgkey)
|
||||
if msgkey in dst_msgs.fuzzy_msgs:
|
||||
dst_msgs.fuzzy_msgs.remove(msgkey)
|
||||
nbr_unfuzzied += 1
|
||||
if msgkey not in dst_msgs.trans_msgs:
|
||||
dst_msgs.trans_msgs.add(msgkey)
|
||||
else:
|
||||
nbr_replaced += 1
|
||||
nbr_merged += 1
|
||||
# The new message is fuzzy, org one is fuzzy too, and this msgkey has not yet been merged.
|
||||
elif msgkey not in (dst_msgs.trans_msgs | done_fuzzy_msgkeys):
|
||||
dst_msgs[msgkey].msgstr = msg.msgstr
|
||||
done_fuzzy_msgkeys.add(msgkey)
|
||||
dst_msgs.fuzzy_msgs.add(msgkey)
|
||||
nbr_merged += 1
|
||||
nbr_replaced += 1
|
||||
|
||||
dst_msgs.write(kind='PO', dest=args.dst)
|
||||
|
||||
print("Merged completed. {} messages were merged (among which {} were replaced), {} were added, "
|
||||
"{} were \"un-fuzzied\".".format(nbr_merged, nbr_replaced, nbr_added, nbr_unfuzzied))
|
||||
if args.stats:
|
||||
dst_msgs.update_info()
|
||||
print("Final merged po stats:")
|
||||
dst_msgs.print_stats(prefix="\t")
|
||||
return ret
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n\n *** Running {} *** \n".format(__file__))
|
||||
sys.exit(main())
|
||||
710
scripts/modules/bl_i18n_utils/settings.py
Normal file
710
scripts/modules/bl_i18n_utils/settings.py
Normal file
@@ -0,0 +1,710 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Global settings used by all scripts in this dir.
|
||||
# XXX Before any use of the tools in this dir, please make a copy of this file
|
||||
# named "setting.py"
|
||||
# XXX This is a template, most values should be OK, but some you’ll have to
|
||||
# edit (most probably, BLENDER_EXEC and SOURCE_DIR).
|
||||
|
||||
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import types
|
||||
|
||||
try:
|
||||
import bpy
|
||||
except ModuleNotFoundError:
|
||||
print("Could not import bpy, some features are not available when not run from Blender.")
|
||||
bpy = None
|
||||
|
||||
###############################################################################
|
||||
# MISC
|
||||
###############################################################################
|
||||
|
||||
# The languages defined in Blender.
|
||||
LANGUAGES_CATEGORIES = (
|
||||
# Min completeness level, UI english label.
|
||||
(0.95, "Complete"),
|
||||
(0.33, "In Progress"),
|
||||
(-1.0, "Starting"),
|
||||
)
|
||||
LANGUAGES = (
|
||||
# ID, UI english label, ISO code.
|
||||
(0, "Automatic (Automatic)", "DEFAULT"),
|
||||
(1, "English (English)", "en_US"),
|
||||
(2, "Japanese (日本語)", "ja_JP"),
|
||||
(3, "Dutch (Nederlandse taal)", "nl_NL"),
|
||||
(4, "Italian (Italiano)", "it_IT"),
|
||||
(5, "German (Deutsch)", "de_DE"),
|
||||
(6, "Finnish (Suomi)", "fi_FI"),
|
||||
(7, "Swedish (Svenska)", "sv_SE"),
|
||||
(8, "French (Français)", "fr_FR"),
|
||||
(9, "Spanish (Español)", "es"),
|
||||
(10, "Catalan (Català)", "ca_AD"),
|
||||
(11, "Czech (Český)", "cs_CZ"),
|
||||
(12, "Portuguese (Português)", "pt_PT"),
|
||||
(13, "Simplified Chinese (简体中文)", "zh_CN"),
|
||||
(14, "Traditional Chinese (繁體中文)", "zh_TW"),
|
||||
(15, "Russian (Русский)", "ru_RU"),
|
||||
(16, "Croatian (Hrvatski)", "hr_HR"),
|
||||
(17, "Serbian (Српски)", "sr_RS"),
|
||||
(18, "Ukrainian (Українська)", "uk_UA"),
|
||||
(19, "Polish (Polski)", "pl_PL"),
|
||||
(20, "Romanian (Român)", "ro_RO"),
|
||||
# Using the utf8 flipped form of Arabic (العربية).
|
||||
(21, "Arabic (ﺔﻴﺑﺮﻌﻟﺍ)", "ar_EG"),
|
||||
(22, "Bulgarian (Български)", "bg_BG"),
|
||||
(23, "Greek (Ελληνικά)", "el_GR"),
|
||||
(24, "Korean (한국어)", "ko_KR"),
|
||||
(25, "Nepali (नेपाली)", "ne_NP"),
|
||||
# Using the utf8 flipped form of Persian (فارسی).
|
||||
(26, "Persian (ﯽﺳﺭﺎﻓ)", "fa_IR"),
|
||||
(27, "Indonesian (Bahasa indonesia)", "id_ID"),
|
||||
(28, "Serbian Latin (Srpski latinica)", "sr_RS@latin"),
|
||||
(29, "Kyrgyz (Кыргыз тили)", "ky_KG"),
|
||||
(30, "Turkish (Türkçe)", "tr_TR"),
|
||||
(31, "Hungarian (Magyar)", "hu_HU"),
|
||||
(32, "Brazilian Portuguese (Português do Brasil)", "pt_BR"),
|
||||
# Using the utf8 flipped form of Hebrew (עִבְרִית)).
|
||||
(33, "Hebrew (תירִבְעִ)", "he_IL"),
|
||||
(34, "Estonian (Eestlane)", "et_EE"),
|
||||
(35, "Esperanto (Esperanto)", "eo"),
|
||||
(36, "Spanish from Spain (Español de España)", "es_ES"),
|
||||
(37, "Amharic (አማርኛ)", "am_ET"),
|
||||
(38, "Uzbek (Oʻzbek)", "uz_UZ"),
|
||||
(39, "Uzbek Cyrillic (Ўзбек)", "uz_UZ@cyrillic"),
|
||||
(40, "Hindi (मानक हिन्दी)", "hi_IN"),
|
||||
(41, "Vietnamese (tiếng Việt)", "vi_VN"),
|
||||
(42, "Basque (Euskara)", "eu_EU"),
|
||||
(43, "Hausa (Hausa)", "ha"),
|
||||
(44, "Kazakh (қазақша)", "kk_KZ"),
|
||||
(45, "Abkhaz (Аԥсуа бызшәа)", "ab"),
|
||||
(46, "Thai (ภาษาไทย)", "th_TH"),
|
||||
(47, "Slovak (Slovenčina)", "sk_SK"),
|
||||
(48, "Georgian (ქართული)", "ka"),
|
||||
)
|
||||
|
||||
# Default context, in py (keep in sync with `BLT_translation.h`)!
|
||||
if bpy is not None:
|
||||
assert bpy.app.translations.contexts.default == "*"
|
||||
DEFAULT_CONTEXT = "*"
|
||||
|
||||
# Name of language file used by Blender to generate translations' menu.
|
||||
LANGUAGES_FILE = "languages"
|
||||
|
||||
# The min level of completeness for a po file to be imported from /branches into /trunk, as a percentage.
|
||||
IMPORT_MIN_LEVEL = 0.0
|
||||
|
||||
# Languages in /branches we do not want to import in /trunk currently...
|
||||
IMPORT_LANGUAGES_SKIP = {
|
||||
'am_ET', 'bg_BG', 'el_GR', 'et_EE', 'ne_NP', 'ro_RO', 'uz_UZ', 'uz_UZ@cyrillic', 'kk_KZ', 'es_ES',
|
||||
}
|
||||
|
||||
# Languages that need RTL pre-processing.
|
||||
IMPORT_LANGUAGES_RTL = {
|
||||
'ar_EG', 'fa_IR', 'he_IL',
|
||||
}
|
||||
|
||||
# The comment prefix used in generated messages.txt file.
|
||||
MSG_COMMENT_PREFIX = "#~ "
|
||||
|
||||
# The comment prefix used in generated messages.txt file.
|
||||
MSG_CONTEXT_PREFIX = "MSGCTXT:"
|
||||
|
||||
# The default comment prefix used in po's.
|
||||
PO_COMMENT_PREFIX = "# "
|
||||
|
||||
# The comment prefix used to mark sources of msgids, in po's.
|
||||
PO_COMMENT_PREFIX_SOURCE = "#: "
|
||||
|
||||
# The comment prefix used to mark sources of msgids, in po's.
|
||||
PO_COMMENT_PREFIX_SOURCE_CUSTOM = "#. :src: "
|
||||
|
||||
# The general "generated" comment prefix, in po's.
|
||||
PO_COMMENT_PREFIX_GENERATED = "#. "
|
||||
|
||||
# The comment prefix used to comment entries in po's.
|
||||
PO_COMMENT_PREFIX_MSG = "#~ "
|
||||
|
||||
# The comment prefix used to mark fuzzy msgids, in po's.
|
||||
PO_COMMENT_FUZZY = "#, fuzzy"
|
||||
|
||||
# The prefix used to define context, in po's.
|
||||
PO_MSGCTXT = "msgctxt "
|
||||
|
||||
# The prefix used to define msgid, in po's.
|
||||
PO_MSGID = "msgid "
|
||||
|
||||
# The prefix used to define msgstr, in po's.
|
||||
PO_MSGSTR = "msgstr "
|
||||
|
||||
# The 'header' key of po files.
|
||||
PO_HEADER_KEY = (DEFAULT_CONTEXT, "")
|
||||
|
||||
PO_HEADER_MSGSTR = (
|
||||
"Project-Id-Version: {blender_ver} ({blender_hash})\\n\n"
|
||||
"Report-Msgid-Bugs-To: \\n\n"
|
||||
"POT-Creation-Date: {time}\\n\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\\n\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\\n\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\\n\n"
|
||||
"Language: {uid}\\n\n"
|
||||
"MIME-Version: 1.0\\n\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\\n\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
)
|
||||
PO_HEADER_COMMENT_COPYRIGHT = (
|
||||
"# Blender's translation file (po format).\n"
|
||||
"# Copyright (C) {year} The Blender Foundation.\n"
|
||||
"# This file is distributed under the same license as the Blender package.\n"
|
||||
"#\n"
|
||||
)
|
||||
PO_HEADER_COMMENT = (
|
||||
"# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n"
|
||||
"#"
|
||||
)
|
||||
|
||||
TEMPLATE_ISO_ID = "__TEMPLATE__"
|
||||
|
||||
# Num buttons report their label with a trailing ': '...
|
||||
NUM_BUTTON_SUFFIX = ": "
|
||||
|
||||
# Undocumented operator placeholder string.
|
||||
UNDOC_OPS_STR = "(undocumented operator)"
|
||||
|
||||
# The gettext domain.
|
||||
DOMAIN = "blender"
|
||||
|
||||
# Our own "gettext" stuff.
|
||||
# File type (ext) to parse.
|
||||
PYGETTEXT_ALLOWED_EXTS = {".c", ".cc", ".cpp", ".cxx", ".hh", ".hpp", ".hxx", ".h"}
|
||||
|
||||
# Max number of contexts into a BLT_I18N_MSGID_MULTI_CTXT macro...
|
||||
PYGETTEXT_MAX_MULTI_CTXT = 16
|
||||
|
||||
# Where to search contexts definitions, relative to SOURCE_DIR (defined below).
|
||||
PYGETTEXT_CONTEXTS_DEFSRC = os.path.join("source", "blender", "blentranslation", "BLT_translation.h")
|
||||
|
||||
# Regex to extract contexts defined in BLT_translation.h
|
||||
# XXX Not full-proof, but should be enough here!
|
||||
PYGETTEXT_CONTEXTS = "#define\\s+(BLT_I18NCONTEXT_[A-Z_0-9]+)\\s+\"([^\"]*)\""
|
||||
|
||||
# autopep8: off
|
||||
|
||||
# Keywords' regex.
|
||||
# XXX Most unfortunately, we can't use named backreferences inside character sets,
|
||||
# which makes the regexes even more twisty... :/
|
||||
_str_base = (
|
||||
# Match void string
|
||||
"(?P<{_}1>[\"'])(?P={_}1)" # Get opening quote (' or "), and closing immediately.
|
||||
"|"
|
||||
# Or match non-void string
|
||||
"(?P<{_}2>[\"'])" # Get opening quote (' or ").
|
||||
"(?{capt}(?:"
|
||||
# This one is for crazy things like "hi \\\\\" folks!"...
|
||||
r"(?:(?!<\\)(?:\\\\)*\\(?=(?P={_}2)))|"
|
||||
# The most common case.
|
||||
".(?!(?P={_}2))"
|
||||
")+.)" # Don't forget the last char!
|
||||
"(?P={_}2)" # And closing quote.
|
||||
)
|
||||
str_clean_re = _str_base.format(_="g", capt="P<clean>")
|
||||
_inbetween_str_re = (
|
||||
# XXX Strings may have comments between their pieces too, not only spaces!
|
||||
r"(?:\s*(?:"
|
||||
# A C comment
|
||||
r"/\*.*(?!\*/).\*/|"
|
||||
# Or a C++ one!
|
||||
r"//[^\n]*\n"
|
||||
# And we are done!
|
||||
r")?)*"
|
||||
)
|
||||
# Here we have to consider two different cases (empty string and other).
|
||||
_str_whole_re = (
|
||||
_str_base.format(_="{_}1_", capt=":") +
|
||||
# Optional loop start, this handles "split" strings...
|
||||
"(?:(?<=[\"'])" + _inbetween_str_re + "(?=[\"'])(?:"
|
||||
+ _str_base.format(_="{_}2_", capt=":") +
|
||||
# End of loop.
|
||||
"))*"
|
||||
)
|
||||
_ctxt_re_gen = lambda uid : r"(?P<ctxt_raw{uid}>(?:".format(uid=uid) + \
|
||||
_str_whole_re.format(_="_ctxt{uid}".format(uid=uid)) + \
|
||||
r")|(?:[A-Z_0-9]+))"
|
||||
_ctxt_re = _ctxt_re_gen("")
|
||||
_msg_re = r"(?P<msg_raw>" + _str_whole_re.format(_="_msg") + r")"
|
||||
PYGETTEXT_KEYWORDS = (() +
|
||||
tuple((r"{}\(\s*" + _msg_re + r"\s*\)").format(it)
|
||||
for it in ("IFACE_", "TIP_", "DATA_", "N_")) +
|
||||
|
||||
tuple((r"{}\(\s*" + _ctxt_re + r"\s*,\s*" + _msg_re + r"\s*\)").format(it)
|
||||
for it in ("CTX_IFACE_", "CTX_TIP_", "CTX_DATA_", "CTX_N_")) +
|
||||
|
||||
tuple(("{}\\((?:[^\"',]+,){{1,2}}\\s*" + _msg_re + r"\s*(?:\)|,)").format(it)
|
||||
for it in ("BKE_report", "BKE_reportf", "BKE_reports_prepend", "BKE_reports_prependf",
|
||||
"CTX_wm_operator_poll_msg_set")) +
|
||||
|
||||
tuple(("{}\\((?:[^\"',]+,){{3}}\\s*" + _msg_re + r"\s*\)").format(it)
|
||||
for it in ("BMO_error_raise",)) +
|
||||
|
||||
tuple(("{}\\((?:[^\"',]+,){{2}}\\s*" + _msg_re + r"\s*(?:\)|,)").format(it)
|
||||
for it in ("BKE_modifier_set_error",)) +
|
||||
|
||||
# This one is a tad more risky, but in practice would not expect a name/uid string parameter
|
||||
# (the second one in those functions) to ever have a comma in it, so think this is fine.
|
||||
tuple(("{}\\((?:[^,]+,){{2}}\\s*" + _msg_re + r"\s*(?:\)|,)").format(it)
|
||||
for it in ("modifier_subpanel_register", "gpencil_modifier_subpanel_register")) +
|
||||
|
||||
# bUnitDef unit names.
|
||||
# NOTE: regex is a bit more complex than it would need too. Since the actual
|
||||
# identifier (`B_UNIT_DEF_`) is at the end, if it's simpler/too general it
|
||||
# becomes extremely slow to process some (unrelated) source files.
|
||||
((r"\{(?:(?:\s*\"[^\",]+\"\s*,)|(?:\s*\"\\\"\",)|(?:\s*NULL\s*,)){4}\s*" +
|
||||
_msg_re + r"\s*,(?:(?:\s*\"[^\"',]+\"\s*,)|(?:\s*NULL\s*,))(?:[^,]+,){2}"
|
||||
+ "(?:\|?\s*B_UNIT_DEF_[_A-Z]+\s*)+\}"),) +
|
||||
|
||||
tuple((r"{}\(\s*" + _msg_re + r"\s*,\s*(?:" +
|
||||
r"\s*,\s*)?(?:".join(_ctxt_re_gen(i) for i in range(PYGETTEXT_MAX_MULTI_CTXT)) + r")?\s*\)").format(it)
|
||||
for it in ("BLT_I18N_MSGID_MULTI_CTXT",))
|
||||
)
|
||||
|
||||
# autopep8: on
|
||||
|
||||
|
||||
# Check printf mismatches between msgid and msgstr.
|
||||
CHECK_PRINTF_FORMAT = (
|
||||
r"(?!<%)(?:%%)*%" # Beginning, with handling for crazy things like '%%%%%s'
|
||||
r"[-+#0]?" # Flags (note: do not add the ' ' (space) flag here, generates too much false positives!)
|
||||
r"(?:\*|[0-9]+)?" # Width
|
||||
r"(?:\.(?:\*|[0-9]+))?" # Precision
|
||||
r"(?:[hljztL]|hh|ll)?" # Length
|
||||
r"[tldiuoxXfFeEgGaAcspn]" # Specifiers (note we have Blender-specific %t and %l ones too)
|
||||
)
|
||||
|
||||
# Should po parser warn when finding a first letter not capitalized?
|
||||
WARN_MSGID_NOT_CAPITALIZED = True
|
||||
|
||||
# Strings that should not raise above warning!
|
||||
WARN_MSGID_NOT_CAPITALIZED_ALLOWED = {
|
||||
"", # Simplifies things... :p
|
||||
"ac3",
|
||||
"along X",
|
||||
"along Y",
|
||||
"along Z",
|
||||
"along %s X",
|
||||
"along %s Y",
|
||||
"along %s Z",
|
||||
"along local Z",
|
||||
"arccos(A)",
|
||||
"arcsin(A)",
|
||||
"arctan(A)",
|
||||
"ascii",
|
||||
"author", # Addons' field. :/
|
||||
"bItasc",
|
||||
"blender.org",
|
||||
"color_index is invalid",
|
||||
"cos(A)",
|
||||
"cosh(A)",
|
||||
"dbl-", # Compacted for 'double', for keymap items.
|
||||
"description", # Addons' field. :/
|
||||
"dx",
|
||||
"fBM",
|
||||
"flac",
|
||||
"fps: %.2f",
|
||||
"fps: %i",
|
||||
"gimbal",
|
||||
"global",
|
||||
"glTF 2.0 (.glb/.gltf)",
|
||||
"glTF Binary (.glb)",
|
||||
"glTF Embedded (.gltf)",
|
||||
"glTF Material Output",
|
||||
"glTF Original PBR data",
|
||||
"glTF Separate (.gltf + .bin + textures)",
|
||||
"invoke() needs to be called before execute()",
|
||||
"iScale",
|
||||
"iso-8859-15",
|
||||
"iTaSC",
|
||||
"iTaSC parameters",
|
||||
"kb",
|
||||
"local",
|
||||
"location", # Addons' field. :/
|
||||
"locking %s X",
|
||||
"locking %s Y",
|
||||
"locking %s Z",
|
||||
"mkv",
|
||||
"mm",
|
||||
"mp2",
|
||||
"mp3",
|
||||
"normal",
|
||||
"ogg",
|
||||
"oneAPI",
|
||||
"p0",
|
||||
"px",
|
||||
"re",
|
||||
"res",
|
||||
"rv",
|
||||
"sin(A)",
|
||||
"sin(x) / x",
|
||||
"sinh(A)",
|
||||
"sqrt(x*x+y*y+z*z)",
|
||||
"sRGB",
|
||||
"sRGB display space",
|
||||
"sRGB display space with Filmic view transform",
|
||||
"tan(A)",
|
||||
"tanh(A)",
|
||||
"utf-8",
|
||||
"uv_on_emitter() requires a modifier from an evaluated object",
|
||||
"var",
|
||||
"vBVH",
|
||||
"view",
|
||||
"wav",
|
||||
"wmOwnerID '%s' not in workspace '%s'",
|
||||
"y",
|
||||
"y = (Ax + B)",
|
||||
# Sub-strings.
|
||||
"all",
|
||||
"all and invert unselected",
|
||||
"and AMD driver version 22.10 or newer",
|
||||
"and AMD Radeon Pro 21.Q4 driver or newer",
|
||||
"and Linux driver version xx.xx.23904 or newer",
|
||||
"and NVIDIA driver version 470 or newer",
|
||||
"and Windows driver version 101.3430 or newer",
|
||||
"available with",
|
||||
"brown fox",
|
||||
"can't save image while rendering",
|
||||
"category",
|
||||
"constructive modifier",
|
||||
"cursor",
|
||||
"custom",
|
||||
"custom matrix",
|
||||
"custom orientation",
|
||||
"edge data",
|
||||
"exp(A)",
|
||||
"expected a timeline/animation area to be active",
|
||||
"expected a view3d region",
|
||||
"expected a view3d region & editcurve",
|
||||
"expected a view3d region & editmesh",
|
||||
"face data",
|
||||
"gimbal",
|
||||
"global",
|
||||
"glTF Settings",
|
||||
"image file not found",
|
||||
"image format is read-only",
|
||||
"image path can't be written to",
|
||||
"in memory to enable editing!",
|
||||
"insufficient content",
|
||||
"into",
|
||||
"jumps over",
|
||||
"left",
|
||||
"local",
|
||||
"matrices", "no matrices",
|
||||
"multi-res modifier",
|
||||
"name",
|
||||
"non-triangle face",
|
||||
"normal",
|
||||
"or AMD with macOS 12.3 or newer",
|
||||
"performance impact!",
|
||||
"positions", "no positions",
|
||||
"read",
|
||||
"remove",
|
||||
"right",
|
||||
"selected",
|
||||
"selected and lock unselected",
|
||||
"selected and unlock unselected",
|
||||
"screen",
|
||||
"the lazy dog",
|
||||
"this legacy pose library to pose assets",
|
||||
"to the top level of the tree",
|
||||
"unable to load movie clip",
|
||||
"unable to load text",
|
||||
"unable to open the file",
|
||||
"unknown error reading file",
|
||||
"unknown error stating file",
|
||||
"unknown error writing file",
|
||||
"unselected",
|
||||
"unsupported font format",
|
||||
"unsupported format",
|
||||
"unsupported image format",
|
||||
"unsupported movie clip format",
|
||||
"untitled",
|
||||
"vertex data",
|
||||
"verts only",
|
||||
"view",
|
||||
"virtual parents",
|
||||
"which was replaced by the Asset Browser",
|
||||
"write",
|
||||
}
|
||||
WARN_MSGID_NOT_CAPITALIZED_ALLOWED |= set(lng[2] for lng in LANGUAGES)
|
||||
|
||||
WARN_MSGID_END_POINT_ALLOWED = {
|
||||
"Circle|Alt .",
|
||||
"Float Neg. Exp.",
|
||||
"Max Ext.",
|
||||
"Newer graphics drivers may be available to improve Blender support.",
|
||||
"Numpad .",
|
||||
"Pad.",
|
||||
" RNA Path: bpy.types.",
|
||||
"Temp. Diff.",
|
||||
"Temperature Diff.",
|
||||
"The program will now close.",
|
||||
"Your graphics card or driver has limited support. It may work, but with issues.",
|
||||
"Your graphics card or driver is not supported.",
|
||||
"Invalid surface UVs on %d curves.",
|
||||
}
|
||||
|
||||
PARSER_CACHE_HASH = 'sha1'
|
||||
|
||||
PARSER_TEMPLATE_ID = "__POT__"
|
||||
PARSER_PY_ID = "__PY__"
|
||||
|
||||
PARSER_PY_MARKER_BEGIN = "\n# ##### BEGIN AUTOGENERATED I18N SECTION #####\n"
|
||||
PARSER_PY_MARKER_END = "\n# ##### END AUTOGENERATED I18N SECTION #####\n"
|
||||
|
||||
PARSER_MAX_FILE_SIZE = 2 ** 24 # in bytes, i.e. 16 Mb.
|
||||
|
||||
###############################################################################
|
||||
# PATHS
|
||||
###############################################################################
|
||||
|
||||
# The Python3 executable.You’ll likely have to edit it in your user_settings.py
|
||||
# if you’re under Windows.
|
||||
PYTHON3_EXEC = "python3"
|
||||
|
||||
# The Blender executable!
|
||||
# This is just an example, you’ll have to edit it in your user_settings.py!
|
||||
BLENDER_EXEC = os.path.abspath(os.path.join("foo", "bar", "blender"))
|
||||
# check for blender.bin
|
||||
if not os.path.exists(BLENDER_EXEC):
|
||||
if os.path.exists(BLENDER_EXEC + ".bin"):
|
||||
BLENDER_EXEC = BLENDER_EXEC + ".bin"
|
||||
|
||||
# The gettext msgfmt "compiler". You’ll likely have to edit it in your user_settings.py if you’re under Windows.
|
||||
GETTEXT_MSGFMT_EXECUTABLE = "msgfmt"
|
||||
|
||||
# The FriBidi C compiled library (.so under Linux, .dll under windows...).
|
||||
# You’ll likely have to edit it in your user_settings.py if you’re under Windows., e.g. using the included one:
|
||||
# FRIBIDI_LIB = os.path.join(TOOLS_DIR, "libfribidi.dll")
|
||||
FRIBIDI_LIB = "libfribidi.so.0"
|
||||
|
||||
# The name of the (currently empty) file that must be present in a po's directory to enable rtl-preprocess.
|
||||
RTL_PREPROCESS_FILE = "is_rtl"
|
||||
|
||||
# The Blender source root path.
|
||||
# This is just an example, you’ll have to override it in your user_settings.py!
|
||||
SOURCE_DIR = os.path.abspath(os.path.join("blender"))
|
||||
|
||||
# The bf-translation repository (you'll have to override this in your user_settings.py).
|
||||
I18N_DIR = os.path.abspath(os.path.join("i18n"))
|
||||
|
||||
# The /branches path (relative to I18N_DIR).
|
||||
REL_BRANCHES_DIR = os.path.join("branches")
|
||||
|
||||
# The /trunk path (relative to I18N_DIR).
|
||||
REL_TRUNK_DIR = os.path.join("trunk")
|
||||
|
||||
# The /trunk/po path (relative to I18N_DIR).
|
||||
REL_TRUNK_PO_DIR = os.path.join(REL_TRUNK_DIR, "po")
|
||||
|
||||
# The /trunk/mo path (relative to I18N_DIR).
|
||||
REL_TRUNK_MO_DIR = os.path.join(REL_TRUNK_DIR, "locale")
|
||||
|
||||
|
||||
# The path to the *git* translation repository (relative to SOURCE_DIR).
|
||||
REL_GIT_I18N_DIR = os.path.join("locale")
|
||||
|
||||
|
||||
# The /po path of the *git* translation repository (relative to REL_GIT_I18N_DIR).
|
||||
REL_GIT_I18N_PO_DIR = os.path.join("po")
|
||||
|
||||
|
||||
# The Blender source path to check for i18n macros (relative to SOURCE_DIR).
|
||||
REL_POTFILES_SOURCE_DIR = os.path.join("source")
|
||||
|
||||
# Where to search for preset names (relative to SOURCE_DIR).
|
||||
REL_PRESETS_DIR = os.path.join("release", "scripts", "presets")
|
||||
|
||||
# Where to search for templates (relative to SOURCE_DIR).
|
||||
REL_TEMPLATES_DIR = os.path.join("release", "scripts", "startup",
|
||||
"bl_app_templates_system")
|
||||
|
||||
# The template messages file (relative to I18N_DIR).
|
||||
REL_FILE_NAME_POT = os.path.join(REL_BRANCHES_DIR, DOMAIN + ".pot")
|
||||
|
||||
# Mo root datapath.
|
||||
REL_MO_PATH_ROOT = os.path.join(REL_TRUNK_DIR, "locale")
|
||||
|
||||
# Mo path generator for a given language.
|
||||
REL_MO_PATH_TEMPLATE = os.path.join(REL_MO_PATH_ROOT, "{}", "LC_MESSAGES")
|
||||
|
||||
# Mo path generator for a given language (relative to any "locale" dir).
|
||||
MO_PATH_ROOT_RELATIVE = os.path.join("locale")
|
||||
MO_PATH_TEMPLATE_RELATIVE = os.path.join(MO_PATH_ROOT_RELATIVE, "{}", "LC_MESSAGES")
|
||||
|
||||
# Mo file name.
|
||||
MO_FILE_NAME = DOMAIN + ".mo"
|
||||
|
||||
# Where to search for py files that may contain ui strings (relative to one of the 'resource_path' of Blender).
|
||||
CUSTOM_PY_UI_FILES = [
|
||||
os.path.join("scripts", "startup", "bl_ui"),
|
||||
os.path.join("scripts", "startup", "bl_operators"),
|
||||
os.path.join("scripts", "modules", "rna_prop_ui.py"),
|
||||
os.path.join("scripts", "modules", "rna_keymap_ui.py"),
|
||||
os.path.join("scripts", "modules", "bpy_types.py"),
|
||||
os.path.join("scripts", "presets", "keyconfig"),
|
||||
]
|
||||
|
||||
# An optional text file listing files to force include/exclude from py_xgettext process.
|
||||
SRC_POTFILES = ""
|
||||
|
||||
# A cache storing validated msgids, to avoid re-spellchecking them.
|
||||
SPELL_CACHE = os.path.join("/tmp", ".spell_cache")
|
||||
|
||||
# Threshold defining whether a new msgid is similar enough with an old one to reuse its translation...
|
||||
SIMILAR_MSGID_THRESHOLD = 0.75
|
||||
|
||||
# Additional import paths to add to sys.path (';' separated)...
|
||||
INTERN_PY_SYS_PATHS = ""
|
||||
|
||||
# Custom override settings must be one dir above i18n tools itself!
|
||||
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
||||
try:
|
||||
from bl_i18n_settings_override import *
|
||||
except ImportError: # If no i18n_override_settings available, it’s no error!
|
||||
pass
|
||||
|
||||
# Override with custom user settings, if available.
|
||||
try:
|
||||
from settings_user import *
|
||||
except ImportError: # If no user_settings available, it’s no error!
|
||||
pass
|
||||
|
||||
|
||||
for p in set(INTERN_PY_SYS_PATHS.split(";")):
|
||||
if p:
|
||||
sys.path.append(p)
|
||||
|
||||
|
||||
# The settings class itself!
|
||||
def _do_get(ref, path):
|
||||
return os.path.normpath(os.path.join(ref, path))
|
||||
|
||||
|
||||
def _do_set(ref, path):
|
||||
path = os.path.normpath(path)
|
||||
# If given path is absolute, make it relative to current ref one (else we consider it is already the case!)
|
||||
if os.path.isabs(path):
|
||||
# can't always find the relative path (between drive letters on windows)
|
||||
try:
|
||||
return os.path.relpath(path, ref)
|
||||
except ValueError:
|
||||
pass
|
||||
return path
|
||||
|
||||
|
||||
def _gen_get_set_path(ref, name):
|
||||
def _get(self):
|
||||
return _do_get(getattr(self, ref), getattr(self, name))
|
||||
|
||||
def _set(self, value):
|
||||
setattr(self, name, _do_set(getattr(self, ref), value))
|
||||
return _get, _set
|
||||
|
||||
|
||||
def _check_valid_data(uid, val):
|
||||
return not uid.startswith("_") and type(val) not in tuple(types.__dict__.values()) + (type,)
|
||||
|
||||
|
||||
class I18nSettings:
|
||||
"""
|
||||
Class allowing persistence of our settings!
|
||||
Saved in JSon format, so settings should be JSon'able objects!
|
||||
"""
|
||||
_settings = None
|
||||
|
||||
def __new__(cls, *args, **kwargs):
|
||||
# Addon preferences are singleton by definition, so is this class!
|
||||
if not I18nSettings._settings:
|
||||
cls._settings = super(I18nSettings, cls).__new__(cls)
|
||||
cls._settings.__dict__ = {uid: val for uid, val in globals().items() if _check_valid_data(uid, val)}
|
||||
return I18nSettings._settings
|
||||
|
||||
def __getstate__(self):
|
||||
return self.to_dict()
|
||||
|
||||
def __setstate__(self, mapping):
|
||||
return self.from_dict(mapping)
|
||||
|
||||
def from_dict(self, mapping):
|
||||
# Special case... :/
|
||||
if "INTERN_PY_SYS_PATHS" in mapping:
|
||||
self.PY_SYS_PATHS = mapping["INTERN_PY_SYS_PATHS"]
|
||||
self.__dict__.update(mapping)
|
||||
|
||||
def to_dict(self):
|
||||
glob = globals()
|
||||
return {uid: val for uid, val in self.__dict__.items() if _check_valid_data(uid, val) and uid in glob}
|
||||
|
||||
def from_json(self, string):
|
||||
self.from_dict(dict(json.loads(string)))
|
||||
|
||||
def to_json(self):
|
||||
# Only save the diff from default i18n_settings!
|
||||
glob = globals()
|
||||
export_dict = {
|
||||
uid: val for uid, val in self.__dict__.items()
|
||||
if _check_valid_data(uid, val) and glob.get(uid) != val
|
||||
}
|
||||
return json.dumps(export_dict)
|
||||
|
||||
def load(self, fname, reset=False):
|
||||
reset = reset or fname is None
|
||||
if reset:
|
||||
self.__dict__ = {uid: data for uid, data in globals().items() if not uid.startswith("_")}
|
||||
if fname is None:
|
||||
return
|
||||
if isinstance(fname, str):
|
||||
if not os.path.isfile(fname):
|
||||
# Assume it is already real JSon string...
|
||||
self.from_json(fname)
|
||||
return
|
||||
with open(fname, encoding="utf8") as f:
|
||||
self.from_json(f.read())
|
||||
# Else assume fname is already a file(like) object!
|
||||
else:
|
||||
self.from_json(fname.read())
|
||||
|
||||
def save(self, fname):
|
||||
if isinstance(fname, str):
|
||||
with open(fname, 'w', encoding="utf8") as f:
|
||||
f.write(self.to_json())
|
||||
# Else assume fname is already a file(like) object!
|
||||
else:
|
||||
fname.write(self.to_json())
|
||||
|
||||
BRANCHES_DIR = property(*(_gen_get_set_path("I18N_DIR", "REL_BRANCHES_DIR")))
|
||||
TRUNK_DIR = property(*(_gen_get_set_path("I18N_DIR", "REL_TRUNK_DIR")))
|
||||
TRUNK_PO_DIR = property(*(_gen_get_set_path("I18N_DIR", "REL_TRUNK_PO_DIR")))
|
||||
TRUNK_MO_DIR = property(*(_gen_get_set_path("I18N_DIR", "REL_TRUNK_MO_DIR")))
|
||||
GIT_I18N_ROOT = property(*(_gen_get_set_path("SOURCE_DIR", "REL_GIT_I18N_DIR")))
|
||||
GIT_I18N_PO_DIR = property(*(_gen_get_set_path("GIT_I18N_ROOT", "REL_GIT_I18N_PO_DIR")))
|
||||
POTFILES_SOURCE_DIR = property(*(_gen_get_set_path("SOURCE_DIR", "REL_POTFILES_SOURCE_DIR")))
|
||||
PRESETS_DIR = property(*(_gen_get_set_path("SOURCE_DIR", "REL_PRESETS_DIR")))
|
||||
TEMPLATES_DIR = property(*(_gen_get_set_path("SOURCE_DIR", "REL_TEMPLATES_DIR")))
|
||||
FILE_NAME_POT = property(*(_gen_get_set_path("I18N_DIR", "REL_FILE_NAME_POT")))
|
||||
MO_PATH_ROOT = property(*(_gen_get_set_path("I18N_DIR", "REL_MO_PATH_ROOT")))
|
||||
MO_PATH_TEMPLATE = property(*(_gen_get_set_path("I18N_DIR", "REL_MO_PATH_TEMPLATE")))
|
||||
|
||||
def _get_py_sys_paths(self):
|
||||
return self.INTERN_PY_SYS_PATHS
|
||||
|
||||
def _set_py_sys_paths(self, val):
|
||||
old_paths = set(self.INTERN_PY_SYS_PATHS.split(";")) - {""}
|
||||
new_paths = set(val.split(";")) - {""}
|
||||
for p in old_paths - new_paths:
|
||||
if p in sys.path:
|
||||
sys.path.remove(p)
|
||||
for p in new_paths - old_paths:
|
||||
sys.path.append(p)
|
||||
self.INTERN_PY_SYS_PATHS = val
|
||||
PY_SYS_PATHS = property(_get_py_sys_paths, _set_py_sys_paths)
|
||||
5
scripts/modules/bl_i18n_utils/settings_user.py
Normal file
5
scripts/modules/bl_i18n_utils/settings_user.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import os
|
||||
|
||||
import settings
|
||||
1592
scripts/modules/bl_i18n_utils/utils.py
Normal file
1592
scripts/modules/bl_i18n_utils/utils.py
Normal file
@@ -0,0 +1,1592 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Some misc utilities...
|
||||
|
||||
import collections
|
||||
import os
|
||||
import re
|
||||
import struct
|
||||
import tempfile
|
||||
#import time
|
||||
|
||||
from bl_i18n_utils import (
|
||||
settings,
|
||||
utils_rtl,
|
||||
)
|
||||
|
||||
|
||||
##### Misc Utils #####
|
||||
_valid_po_path_re = re.compile(r"^\S+:[0-9]+$")
|
||||
|
||||
|
||||
def is_valid_po_path(path):
|
||||
return bool(_valid_po_path_re.match(path))
|
||||
|
||||
|
||||
def get_best_similar(data):
|
||||
import difflib
|
||||
key, use_similar, similar_pool = data
|
||||
|
||||
# try to find some close key in existing messages...
|
||||
# Optimized code inspired by difflib.get_close_matches (as we only need the best match).
|
||||
# We also consider to never make a match when len differs more than -len_key / 2, +len_key * 2 (which is valid
|
||||
# as long as use_similar is not below ~0.7).
|
||||
# Gives an overall ~20% of improvement!
|
||||
|
||||
# tmp = difflib.get_close_matches(key[1], similar_pool, n=1, cutoff=use_similar)
|
||||
# if tmp:
|
||||
# tmp = tmp[0]
|
||||
tmp = None
|
||||
s = difflib.SequenceMatcher()
|
||||
s.set_seq2(key[1])
|
||||
len_key = len(key[1])
|
||||
min_len = len_key // 2
|
||||
max_len = len_key * 2
|
||||
for x in similar_pool:
|
||||
if min_len < len(x) < max_len:
|
||||
s.set_seq1(x)
|
||||
if s.real_quick_ratio() >= use_similar and s.quick_ratio() >= use_similar:
|
||||
sratio = s.ratio()
|
||||
if sratio >= use_similar:
|
||||
tmp = x
|
||||
use_similar = sratio
|
||||
return key, tmp
|
||||
|
||||
|
||||
_locale_explode_re = re.compile(r"^([a-z]{2,})(?:_([A-Z]{2,}))?(?:@([a-z]{2,}))?$")
|
||||
|
||||
|
||||
def locale_explode(locale):
|
||||
"""Copies behavior of `BLT_lang_locale_explode`, keep them in sync."""
|
||||
ret = (None, None, None, None, None)
|
||||
m = _locale_explode_re.match(locale)
|
||||
if m:
|
||||
lang, country, variant = m.groups()
|
||||
return (lang, country, variant,
|
||||
"%s_%s" % (lang, country) if country else None,
|
||||
"%s@%s" % (lang, variant) if variant else None)
|
||||
|
||||
try:
|
||||
import bpy.app.translations as bpy_translations
|
||||
assert ret == bpy_translations.locale_explode(locale)
|
||||
except ModuleNotFoundError:
|
||||
pass
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
def locale_match(loc1, loc2):
|
||||
"""
|
||||
Return:
|
||||
-n if loc1 is a subtype of loc2 (e.g. 'fr_FR' is a subtype of 'fr').
|
||||
+n if loc2 is a subtype of loc1.
|
||||
n becomes smaller when both locales are more similar (e.g. (sr, sr_SR) are more similar than (sr, sr_SR@latin)).
|
||||
0 if they are exactly the same.
|
||||
... (Ellipsis) if they cannot match!
|
||||
Note: We consider that 'sr_SR@latin' is a subtype of 'sr@latin', 'sr_SR' and 'sr', but 'sr_SR' and 'sr@latin' won't
|
||||
match (will return ...)!
|
||||
Note: About similarity, diff in variants are more important than diff in countries, currently here are the cases:
|
||||
(sr, sr_SR) -> 1
|
||||
(sr@latin, sr_SR@latin) -> 1
|
||||
(sr, sr@latin) -> 2
|
||||
(sr_SR, sr_SR@latin) -> 2
|
||||
(sr, sr_SR@latin) -> 3
|
||||
"""
|
||||
if loc1 == loc2:
|
||||
return 0
|
||||
l1, c1, v1, *_1 = locale_explode(loc1)
|
||||
l2, c2, v2, *_2 = locale_explode(loc2)
|
||||
|
||||
if l1 == l2:
|
||||
if c1 == c2:
|
||||
if v1 == v2:
|
||||
return 0
|
||||
elif v2 is None:
|
||||
return -2
|
||||
elif v1 is None:
|
||||
return 2
|
||||
return ...
|
||||
elif c2 is None:
|
||||
if v1 == v2:
|
||||
return -1
|
||||
elif v2 is None:
|
||||
return -3
|
||||
return ...
|
||||
elif c1 is None:
|
||||
if v1 == v2:
|
||||
return 1
|
||||
elif v1 is None:
|
||||
return 3
|
||||
return ...
|
||||
return ...
|
||||
|
||||
|
||||
def find_best_isocode_matches(uid, iso_codes):
|
||||
"""
|
||||
Return an ordered tuple of elements in iso_codes that can match the given uid, from most similar to lesser ones.
|
||||
"""
|
||||
tmp = ((e, locale_match(e, uid)) for e in iso_codes)
|
||||
return tuple(e[0] for e in sorted((e for e in tmp if e[1] is not ... and e[1] >= 0), key=lambda e: e[1]))
|
||||
|
||||
|
||||
def get_po_files_from_dir(root_dir, langs=set()):
|
||||
"""
|
||||
Yield tuples (uid, po_path) of translations for each po file found in the given dir, which should be either
|
||||
a dir containing po files using language uid's as names (e.g. fr.po, es_ES.po, etc.), or
|
||||
a dir containing dirs which names are language uids, and containing po files of the same names.
|
||||
"""
|
||||
found_uids = set()
|
||||
for p in os.listdir(root_dir):
|
||||
uid = None
|
||||
po_file = os.path.join(root_dir, p)
|
||||
print(p)
|
||||
if p.endswith(".po") and os.path.isfile(po_file):
|
||||
uid = p[:-3]
|
||||
if langs and uid not in langs:
|
||||
continue
|
||||
elif os.path.isdir(p):
|
||||
uid = p
|
||||
if langs and uid not in langs:
|
||||
continue
|
||||
po_file = os.path.join(root_dir, p, p + ".po")
|
||||
if not os.path.isfile(po_file):
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
if uid in found_uids:
|
||||
print("WARNING! {} id has been found more than once! only first one has been loaded!".format(uid))
|
||||
continue
|
||||
found_uids.add(uid)
|
||||
yield uid, po_file
|
||||
|
||||
|
||||
def list_po_dir(root_path, settings):
|
||||
"""
|
||||
Generator. List given directory (expecting one sub-directory per languages)
|
||||
and return all files matching languages listed in settings.
|
||||
|
||||
Yield tuples (can_use, uid, num_id, name, isocode, po_path)
|
||||
|
||||
Note that po_path may not actually exists.
|
||||
"""
|
||||
isocodes = ((e, os.path.join(root_path, e, e + ".po")) for e in os.listdir(root_path))
|
||||
isocodes = dict(e for e in isocodes if os.path.isfile(e[1]))
|
||||
for num_id, name, uid in settings.LANGUAGES[2:]: # Skip "default" and "en" languages!
|
||||
best_po = find_best_isocode_matches(uid, isocodes)
|
||||
#print(uid, "->", best_po)
|
||||
if best_po:
|
||||
isocode = best_po[0]
|
||||
yield (True, uid, num_id, name, isocode, isocodes[isocode])
|
||||
else:
|
||||
yielded = False
|
||||
language, _1, _2, language_country, language_variant = locale_explode(uid)
|
||||
for isocode in (language, language_variant, language_country, uid):
|
||||
p = os.path.join(root_path, isocode, isocode + ".po")
|
||||
if not os.path.exists(p):
|
||||
yield (True, uid, num_id, name, isocode, p)
|
||||
yielded = True
|
||||
break
|
||||
if not yielded:
|
||||
yield (False, uid, num_id, name, None, None)
|
||||
|
||||
|
||||
def enable_addons(addons=None, support=None, disable=False, check_only=False):
|
||||
"""
|
||||
Enable (or disable) addons based either on a set of names, or a set of 'support' types.
|
||||
Returns the list of all affected addons (as fake modules)!
|
||||
If "check_only" is set, no addon will be enabled nor disabled.
|
||||
"""
|
||||
import addon_utils
|
||||
|
||||
try:
|
||||
import bpy
|
||||
except ModuleNotFoundError:
|
||||
print("Could not import bpy, enable_addons must be run from within Blender.")
|
||||
return
|
||||
|
||||
if addons is None:
|
||||
addons = {}
|
||||
if support is None:
|
||||
support = {}
|
||||
|
||||
prefs = bpy.context.preferences
|
||||
used_ext = {ext.module for ext in prefs.addons}
|
||||
# In case we need to blacklist some add-ons...
|
||||
black_list = {}
|
||||
|
||||
ret = [
|
||||
mod for mod in addon_utils.modules()
|
||||
if (((addons and mod.__name__ in addons) or
|
||||
(not addons and addon_utils.module_bl_info(mod)["support"] in support)) and
|
||||
(mod.__name__ not in black_list))
|
||||
]
|
||||
|
||||
if not check_only:
|
||||
for mod in ret:
|
||||
try:
|
||||
module_name = mod.__name__
|
||||
if disable:
|
||||
if module_name not in used_ext:
|
||||
continue
|
||||
print(" Disabling module ", module_name)
|
||||
bpy.ops.preferences.addon_disable(module=module_name)
|
||||
else:
|
||||
if module_name in used_ext:
|
||||
continue
|
||||
print(" Enabling module ", module_name)
|
||||
bpy.ops.preferences.addon_enable(module=module_name)
|
||||
except Exception as e: # XXX TEMP WORKAROUND
|
||||
print(e)
|
||||
|
||||
# XXX There are currently some problems with bpy/rna...
|
||||
# *Very* tricky to solve!
|
||||
# So this is a hack to make all newly added operator visible by
|
||||
# bpy.types.OperatorProperties.__subclasses__()
|
||||
for cat in dir(bpy.ops):
|
||||
cat = getattr(bpy.ops, cat)
|
||||
for op in dir(cat):
|
||||
getattr(cat, op).get_rna_type()
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
##### Main Classes #####
|
||||
|
||||
class I18nMessage:
|
||||
"""
|
||||
Internal representation of a message.
|
||||
"""
|
||||
__slots__ = ("msgctxt_lines", "msgid_lines", "msgstr_lines", "comment_lines", "is_fuzzy", "is_commented",
|
||||
"settings")
|
||||
|
||||
def __init__(self, msgctxt_lines=None, msgid_lines=None, msgstr_lines=None, comment_lines=None,
|
||||
is_commented=False, is_fuzzy=False, settings=settings):
|
||||
self.settings = settings
|
||||
self.msgctxt_lines = msgctxt_lines or []
|
||||
self.msgid_lines = msgid_lines or []
|
||||
self.msgstr_lines = msgstr_lines or []
|
||||
self.comment_lines = comment_lines or []
|
||||
self.is_fuzzy = is_fuzzy
|
||||
self.is_commented = is_commented
|
||||
|
||||
# ~ def __getstate__(self):
|
||||
# ~ return {key: getattr(self, key) for key in self.__slots__}
|
||||
|
||||
# ~ def __getstate__(self):
|
||||
# ~ return {key: getattr(self, key) for key in self.__slots__}
|
||||
|
||||
def _get_msgctxt(self):
|
||||
return "".join(self.msgctxt_lines)
|
||||
|
||||
def _set_msgctxt(self, ctxt):
|
||||
self.msgctxt_lines = [ctxt]
|
||||
msgctxt = property(_get_msgctxt, _set_msgctxt)
|
||||
|
||||
def _get_msgid(self):
|
||||
return "".join(self.msgid_lines)
|
||||
|
||||
def _set_msgid(self, msgid):
|
||||
self.msgid_lines = [msgid]
|
||||
msgid = property(_get_msgid, _set_msgid)
|
||||
|
||||
def _get_msgstr(self):
|
||||
return "".join(self.msgstr_lines)
|
||||
|
||||
def _set_msgstr(self, msgstr):
|
||||
self.msgstr_lines = [msgstr]
|
||||
msgstr = property(_get_msgstr, _set_msgstr)
|
||||
|
||||
def _get_sources(self):
|
||||
lstrip1 = len(self.settings.PO_COMMENT_PREFIX_SOURCE)
|
||||
lstrip2 = len(self.settings.PO_COMMENT_PREFIX_SOURCE_CUSTOM)
|
||||
return ([l[lstrip1:] for l in self.comment_lines if l.startswith(self.settings.PO_COMMENT_PREFIX_SOURCE)] +
|
||||
[l[lstrip2:] for l in self.comment_lines
|
||||
if l.startswith(self.settings.PO_COMMENT_PREFIX_SOURCE_CUSTOM)])
|
||||
|
||||
def _set_sources(self, sources):
|
||||
cmmlines = self.comment_lines.copy()
|
||||
for l in cmmlines:
|
||||
if (
|
||||
l.startswith(self.settings.PO_COMMENT_PREFIX_SOURCE) or
|
||||
l.startswith(self.settings.PO_COMMENT_PREFIX_SOURCE_CUSTOM)
|
||||
):
|
||||
self.comment_lines.remove(l)
|
||||
lines_src = []
|
||||
lines_src_custom = []
|
||||
for src in sources:
|
||||
if is_valid_po_path(src):
|
||||
lines_src.append(self.settings.PO_COMMENT_PREFIX_SOURCE + src)
|
||||
else:
|
||||
lines_src_custom.append(self.settings.PO_COMMENT_PREFIX_SOURCE_CUSTOM + src)
|
||||
self.comment_lines += lines_src_custom + lines_src
|
||||
sources = property(_get_sources, _set_sources)
|
||||
|
||||
def _get_is_tooltip(self):
|
||||
# XXX For now, we assume that all messages > 30 chars are tooltips!
|
||||
return len(self.msgid) > 30
|
||||
is_tooltip = property(_get_is_tooltip)
|
||||
|
||||
def copy(self):
|
||||
# Deepcopy everything but the settings!
|
||||
return self.__class__(msgctxt_lines=self.msgctxt_lines[:], msgid_lines=self.msgid_lines[:],
|
||||
msgstr_lines=self.msgstr_lines[:], comment_lines=self.comment_lines[:],
|
||||
is_commented=self.is_commented, is_fuzzy=self.is_fuzzy, settings=self.settings)
|
||||
|
||||
def normalize(self, max_len=80):
|
||||
"""
|
||||
Normalize this message, call this before exporting it...
|
||||
Currently normalize msgctxt, msgid and msgstr lines to given max_len (if below 1, make them single line).
|
||||
"""
|
||||
max_len -= 2 # The two quotes!
|
||||
|
||||
def _splitlines(text):
|
||||
lns = text.splitlines()
|
||||
return [l + "\n" for l in lns[:-1]] + lns[-1:]
|
||||
|
||||
# We do not need the full power of textwrap... We just split first at escaped new lines, then into each line
|
||||
# if needed... No word splitting, nor fancy spaces handling!
|
||||
def _wrap(text, max_len, init_len):
|
||||
if len(text) + init_len < max_len:
|
||||
return [text]
|
||||
lines = _splitlines(text)
|
||||
ret = []
|
||||
for l in lines:
|
||||
tmp = []
|
||||
cur_len = 0
|
||||
words = l.split(' ')
|
||||
for w in words:
|
||||
cur_len += len(w) + 1
|
||||
if cur_len > (max_len - 1) and tmp:
|
||||
ret.append(" ".join(tmp) + " ")
|
||||
del tmp[:]
|
||||
cur_len = len(w) + 1
|
||||
tmp.append(w)
|
||||
if tmp:
|
||||
ret.append(" ".join(tmp))
|
||||
return ret
|
||||
|
||||
if max_len < 1:
|
||||
self.msgctxt_lines = _splitlines(self.msgctxt)
|
||||
self.msgid_lines = _splitlines(self.msgid)
|
||||
self.msgstr_lines = _splitlines(self.msgstr)
|
||||
else:
|
||||
init_len = len(self.settings.PO_MSGCTXT) + 1
|
||||
if self.is_commented:
|
||||
init_len += len(self.settings.PO_COMMENT_PREFIX_MSG)
|
||||
self.msgctxt_lines = _wrap(self.msgctxt, max_len, init_len)
|
||||
|
||||
init_len = len(self.settings.PO_MSGID) + 1
|
||||
if self.is_commented:
|
||||
init_len += len(self.settings.PO_COMMENT_PREFIX_MSG)
|
||||
self.msgid_lines = _wrap(self.msgid, max_len, init_len)
|
||||
|
||||
init_len = len(self.settings.PO_MSGSTR) + 1
|
||||
if self.is_commented:
|
||||
init_len += len(self.settings.PO_COMMENT_PREFIX_MSG)
|
||||
self.msgstr_lines = _wrap(self.msgstr, max_len, init_len)
|
||||
|
||||
# Be sure comment lines are not duplicated (can happen with sources...).
|
||||
tmp = []
|
||||
for l in self.comment_lines:
|
||||
if l not in tmp:
|
||||
tmp.append(l)
|
||||
self.comment_lines = tmp
|
||||
|
||||
_esc_quotes = re.compile(r'(?!<\\)((?:\\\\)*)"')
|
||||
_unesc_quotes = re.compile(r'(?!<\\)((?:\\\\)*)\\"')
|
||||
_esc_names = ("msgctxt_lines", "msgid_lines", "msgstr_lines")
|
||||
_esc_names_all = _esc_names + ("comment_lines",)
|
||||
|
||||
@classmethod
|
||||
def do_escape(cls, txt):
|
||||
"""Replace some chars by their escaped versions!"""
|
||||
if "\n" in txt:
|
||||
txt = txt.replace("\n", r"\n")
|
||||
if "\t" in txt:
|
||||
txt.replace("\t", r"\t")
|
||||
if '"' in txt:
|
||||
txt = cls._esc_quotes.sub(r'\1\"', txt)
|
||||
return txt
|
||||
|
||||
@classmethod
|
||||
def do_unescape(cls, txt):
|
||||
"""Replace escaped chars by real ones!"""
|
||||
if r"\n" in txt:
|
||||
txt = txt.replace(r"\n", "\n")
|
||||
if r"\t" in txt:
|
||||
txt = txt.replace(r"\t", "\t")
|
||||
if r'\"' in txt:
|
||||
txt = cls._unesc_quotes.sub(r'\1"', txt)
|
||||
return txt
|
||||
|
||||
def escape(self, do_all=False):
|
||||
names = self._esc_names_all if do_all else self._esc_names
|
||||
for name in names:
|
||||
setattr(self, name, [self.do_escape(l) for l in getattr(self, name)])
|
||||
|
||||
def unescape(self, do_all=True):
|
||||
names = self._esc_names_all if do_all else self._esc_names
|
||||
for name in names:
|
||||
setattr(self, name, [self.do_unescape(l) for l in getattr(self, name)])
|
||||
|
||||
|
||||
class I18nMessages:
|
||||
"""
|
||||
Internal representation of messages for one language (iso code), with additional stats info.
|
||||
"""
|
||||
|
||||
# Avoid parsing again!
|
||||
# Keys should be (pseudo) file-names, values are tuples (hash, I18nMessages)
|
||||
# Note: only used by po parser currently!
|
||||
#_parser_cache = {}
|
||||
|
||||
def __init__(self, uid=None, kind=None, key=None, src=None, settings=settings):
|
||||
self.settings = settings
|
||||
self.uid = uid if uid is not None else settings.PARSER_TEMPLATE_ID
|
||||
self.msgs = self._new_messages()
|
||||
self.trans_msgs = set()
|
||||
self.fuzzy_msgs = set()
|
||||
self.comm_msgs = set()
|
||||
self.ttip_msgs = set()
|
||||
self.contexts = set()
|
||||
self.nbr_msgs = 0
|
||||
self.nbr_trans_msgs = 0
|
||||
self.nbr_ttips = 0
|
||||
self.nbr_trans_ttips = 0
|
||||
self.nbr_comm_msgs = 0
|
||||
self.nbr_signs = 0
|
||||
self.nbr_trans_signs = 0
|
||||
self.parsing_errors = []
|
||||
if kind and src:
|
||||
self.parse(kind, key, src)
|
||||
self.update_info()
|
||||
|
||||
self._reverse_cache = None
|
||||
|
||||
def __getstate__(self):
|
||||
return (self.settings, self.uid, self.msgs, self.parsing_errors)
|
||||
|
||||
def __setstate__(self, data):
|
||||
self.__init__()
|
||||
self.settings, self.uid, self.msgs, self.parsing_errors = data
|
||||
self.update_info()
|
||||
|
||||
@staticmethod
|
||||
def _new_messages():
|
||||
return getattr(collections, 'OrderedDict', dict)()
|
||||
|
||||
@classmethod
|
||||
def gen_empty_messages(cls, uid, blender_ver, blender_hash, time, year, default_copyright=True, settings=settings):
|
||||
"""Generate an empty I18nMessages object (only header is present!)."""
|
||||
fmt = settings.PO_HEADER_MSGSTR
|
||||
msgstr = fmt.format(blender_ver=str(blender_ver), blender_hash=blender_hash, time=str(time), uid=str(uid))
|
||||
comment = ""
|
||||
if default_copyright:
|
||||
comment = settings.PO_HEADER_COMMENT_COPYRIGHT.format(year=str(year))
|
||||
comment = comment + settings.PO_HEADER_COMMENT
|
||||
|
||||
msgs = cls(uid=uid, settings=settings)
|
||||
key = settings.PO_HEADER_KEY
|
||||
msgs.msgs[key] = I18nMessage([key[0]], [key[1]], msgstr.split("\n"), comment.split("\n"),
|
||||
False, False, settings=settings)
|
||||
msgs.update_info()
|
||||
|
||||
return msgs
|
||||
|
||||
def normalize(self, max_len=80):
|
||||
for msg in self.msgs.values():
|
||||
msg.normalize(max_len)
|
||||
|
||||
def escape(self, do_all=False):
|
||||
for msg in self.msgs.values():
|
||||
msg.escape(do_all)
|
||||
|
||||
def unescape(self, do_all=True):
|
||||
for msg in self.msgs.values():
|
||||
msg.unescape(do_all)
|
||||
|
||||
def check(self, fix=False):
|
||||
"""
|
||||
Check consistency between messages and their keys!
|
||||
Check messages using format stuff are consistent between msgid and msgstr!
|
||||
If fix is True, tries to fix the issues.
|
||||
Return a list of found errors (empty if everything went OK!).
|
||||
"""
|
||||
ret = []
|
||||
default_context = self.settings.DEFAULT_CONTEXT
|
||||
_format = re.compile(self.settings.CHECK_PRINTF_FORMAT).findall
|
||||
done_keys = set()
|
||||
rem = set()
|
||||
tmp = {}
|
||||
for key, msg in self.msgs.items():
|
||||
msgctxt, msgid, msgstr = msg.msgctxt, msg.msgid, msg.msgstr
|
||||
real_key = (msgctxt or default_context, msgid)
|
||||
if key != real_key:
|
||||
ret.append("Error! msg's context/message do not match its key ({} / {})".format(real_key, key))
|
||||
if real_key in self.msgs:
|
||||
ret.append("Error! msg's real_key already used!")
|
||||
if fix:
|
||||
rem.add(real_key)
|
||||
elif fix:
|
||||
tmp[real_key] = msg
|
||||
done_keys.add(key)
|
||||
if '%' in msgid and msgstr and _format(msgid) != _format(msgstr):
|
||||
if not msg.is_fuzzy:
|
||||
ret.append("Error! msg's format entities are not matched in msgid and msgstr ({} / \"{}\")"
|
||||
"".format(real_key, msgstr))
|
||||
if fix:
|
||||
msg.msgstr = ""
|
||||
for k in rem:
|
||||
del self.msgs[k]
|
||||
self.msgs.update(tmp)
|
||||
return ret
|
||||
|
||||
def clean_commented(self):
|
||||
self.update_info()
|
||||
nbr = len(self.comm_msgs)
|
||||
for k in self.comm_msgs:
|
||||
del self.msgs[k]
|
||||
return nbr
|
||||
|
||||
def rtl_process(self):
|
||||
keys = []
|
||||
trans = []
|
||||
for k, m in self.msgs.items():
|
||||
keys.append(k)
|
||||
trans.append(m.msgstr)
|
||||
trans = utils_rtl.log2vis(trans, self.settings)
|
||||
for k, t in zip(keys, trans):
|
||||
self.msgs[k].msgstr = t
|
||||
|
||||
def merge(self, msgs, replace=False):
|
||||
"""
|
||||
Merge translations from msgs into self, following those rules:
|
||||
* If a msg is in self and not in msgs, keep self untouched.
|
||||
* If a msg is in msgs and not in self, skip it.
|
||||
* Else (msg both in self and msgs):
|
||||
* If self is not translated and msgs is translated or fuzzy, replace by msgs.
|
||||
* If self is fuzzy, and msgs is translated, replace by msgs.
|
||||
* If self is fuzzy, and msgs is fuzzy, and replace is True, replace by msgs.
|
||||
* If self is translated, and msgs is translated, and replace is True, replace by msgs.
|
||||
* Else, skip it!
|
||||
"""
|
||||
for k, m in msgs.msgs.items():
|
||||
if k not in self.msgs:
|
||||
continue
|
||||
sm = self.msgs[k]
|
||||
if (sm.is_commented or m.is_commented or not m.msgstr):
|
||||
continue
|
||||
if (not sm.msgstr or replace or (sm.is_fuzzy and (not m.is_fuzzy or replace))):
|
||||
sm.msgstr = m.msgstr
|
||||
sm.is_fuzzy = m.is_fuzzy
|
||||
|
||||
def update(self, ref, use_similar=None, keep_old_commented=True):
|
||||
"""
|
||||
Update this I18nMessage with the ref one. Translations from ref are never used. Source comments from ref
|
||||
completely replace current ones. If use_similar is not 0.0, it will try to match new messages in ref with an
|
||||
existing one. Messages no more found in ref will be marked as commented if keep_old_commented is True,
|
||||
or removed.
|
||||
"""
|
||||
if use_similar is None:
|
||||
use_similar = self.settings.SIMILAR_MSGID_THRESHOLD
|
||||
|
||||
similar_pool = {}
|
||||
if use_similar > 0.0:
|
||||
for key, msg in self.msgs.items():
|
||||
if msg.msgstr: # No need to waste time with void translations!
|
||||
similar_pool.setdefault(key[1], set()).add(key)
|
||||
|
||||
msgs = self._new_messages().fromkeys(ref.msgs.keys())
|
||||
ref_keys = set(ref.msgs.keys())
|
||||
org_keys = set(self.msgs.keys())
|
||||
new_keys = ref_keys - org_keys
|
||||
removed_keys = org_keys - ref_keys
|
||||
|
||||
# First process keys present in both org and ref messages.
|
||||
for key in ref_keys - new_keys:
|
||||
msg, refmsg = self.msgs[key], ref.msgs[key]
|
||||
msg.sources = refmsg.sources
|
||||
msg.is_commented = refmsg.is_commented
|
||||
msgs[key] = msg
|
||||
|
||||
# Next process new keys.
|
||||
if use_similar > 0.0:
|
||||
for key, msgid in map(get_best_similar,
|
||||
tuple((nk, use_similar, tuple(similar_pool.keys())) for nk in new_keys)):
|
||||
if msgid:
|
||||
# Try to get the same context, else just get one...
|
||||
skey = (key[0], msgid)
|
||||
if skey not in similar_pool[msgid]:
|
||||
skey = tuple(similar_pool[msgid])[0]
|
||||
# We keep org translation and comments, and mark message as fuzzy.
|
||||
msg, refmsg = self.msgs[skey].copy(), ref.msgs[key]
|
||||
msg.msgctxt = refmsg.msgctxt
|
||||
msg.msgid = refmsg.msgid
|
||||
msg.sources = refmsg.sources
|
||||
msg.is_fuzzy = True
|
||||
msg.is_commented = refmsg.is_commented
|
||||
msgs[key] = msg
|
||||
else:
|
||||
msgs[key] = ref.msgs[key]
|
||||
else:
|
||||
for key in new_keys:
|
||||
msgs[key] = ref.msgs[key]
|
||||
|
||||
# Add back all "old" and already commented messages as commented ones, if required
|
||||
# (and translation was not void!).
|
||||
if keep_old_commented:
|
||||
for key in removed_keys:
|
||||
msgs[key] = self.msgs[key]
|
||||
msgs[key].is_commented = True
|
||||
msgs[key].sources = []
|
||||
|
||||
# Special 'meta' message, change project ID version and pot creation date...
|
||||
key = self.settings.PO_HEADER_KEY
|
||||
rep = []
|
||||
markers = ("Project-Id-Version:", "POT-Creation-Date:")
|
||||
for mrk in markers:
|
||||
for rl in ref.msgs[key].msgstr_lines:
|
||||
if rl.startswith(mrk):
|
||||
for idx, ml in enumerate(msgs[key].msgstr_lines):
|
||||
if ml.startswith(mrk):
|
||||
rep.append((idx, rl))
|
||||
for idx, txt in rep:
|
||||
msgs[key].msgstr_lines[idx] = txt
|
||||
|
||||
# And finalize the update!
|
||||
self.msgs = msgs
|
||||
|
||||
def update_info(self):
|
||||
self.trans_msgs.clear()
|
||||
self.fuzzy_msgs.clear()
|
||||
self.comm_msgs.clear()
|
||||
self.ttip_msgs.clear()
|
||||
self.contexts.clear()
|
||||
self.nbr_signs = 0
|
||||
self.nbr_trans_signs = 0
|
||||
for key, msg in self.msgs.items():
|
||||
if key == self.settings.PO_HEADER_KEY:
|
||||
continue
|
||||
if msg.is_commented:
|
||||
self.comm_msgs.add(key)
|
||||
else:
|
||||
if msg.msgstr:
|
||||
self.trans_msgs.add(key)
|
||||
if msg.is_fuzzy:
|
||||
self.fuzzy_msgs.add(key)
|
||||
if msg.is_tooltip:
|
||||
self.ttip_msgs.add(key)
|
||||
self.contexts.add(key[0])
|
||||
self.nbr_signs += len(msg.msgid)
|
||||
self.nbr_trans_signs += len(msg.msgstr)
|
||||
self.nbr_msgs = len(self.msgs)
|
||||
self.nbr_trans_msgs = len(self.trans_msgs - self.fuzzy_msgs)
|
||||
self.nbr_ttips = len(self.ttip_msgs)
|
||||
self.nbr_trans_ttips = len(self.ttip_msgs & (self.trans_msgs - self.fuzzy_msgs))
|
||||
self.nbr_comm_msgs = len(self.comm_msgs)
|
||||
|
||||
def print_info(self, prefix="", output=print, print_stats=True, print_errors=True):
|
||||
"""
|
||||
Print out some info about an I18nMessages object.
|
||||
"""
|
||||
lvl = 0.0
|
||||
lvl_ttips = 0.0
|
||||
lvl_comm = 0.0
|
||||
lvl_trans_ttips = 0.0
|
||||
lvl_ttips_in_trans = 0.0
|
||||
if self.nbr_msgs > 0:
|
||||
lvl = float(self.nbr_trans_msgs) / float(self.nbr_msgs)
|
||||
lvl_ttips = float(self.nbr_ttips) / float(self.nbr_msgs)
|
||||
lvl_comm = float(self.nbr_comm_msgs) / float(self.nbr_msgs + self.nbr_comm_msgs)
|
||||
if self.nbr_ttips > 0:
|
||||
lvl_trans_ttips = float(self.nbr_trans_ttips) / float(self.nbr_ttips)
|
||||
if self.nbr_trans_msgs > 0:
|
||||
lvl_ttips_in_trans = float(self.nbr_trans_ttips) / float(self.nbr_trans_msgs)
|
||||
|
||||
lines = []
|
||||
if print_stats:
|
||||
lines += [
|
||||
"",
|
||||
"{:>6.1%} done! ({} translated messages over {}).\n"
|
||||
"".format(lvl, self.nbr_trans_msgs, self.nbr_msgs),
|
||||
"{:>6.1%} of messages are tooltips ({} over {}).\n"
|
||||
"".format(lvl_ttips, self.nbr_ttips, self.nbr_msgs),
|
||||
"{:>6.1%} of tooltips are translated ({} over {}).\n"
|
||||
"".format(lvl_trans_ttips, self.nbr_trans_ttips, self.nbr_ttips),
|
||||
"{:>6.1%} of translated messages are tooltips ({} over {}).\n"
|
||||
"".format(lvl_ttips_in_trans, self.nbr_trans_ttips, self.nbr_trans_msgs),
|
||||
"{:>6.1%} of messages are commented ({} over {}).\n"
|
||||
"".format(lvl_comm, self.nbr_comm_msgs, self.nbr_comm_msgs + self.nbr_msgs),
|
||||
"This translation is currently made of {} signs.\n".format(self.nbr_trans_signs)
|
||||
]
|
||||
if print_errors and self.parsing_errors:
|
||||
lines += ["WARNING! Errors during parsing:\n"]
|
||||
lines += [" Around line {}: {}\n".format(line, error) for line, error in self.parsing_errors]
|
||||
output(prefix.join(lines))
|
||||
|
||||
def invalidate_reverse_cache(self, rebuild_now=False):
|
||||
"""
|
||||
Invalidate the reverse cache used by find_best_messages_matches.
|
||||
"""
|
||||
self._reverse_cache = None
|
||||
if rebuild_now:
|
||||
src_to_msg, ctxt_to_msg, msgid_to_msg, msgstr_to_msg = {}, {}, {}, {}
|
||||
for key, msg in self.msgs.items():
|
||||
if msg.is_commented:
|
||||
continue
|
||||
ctxt, msgid = key
|
||||
ctxt_to_msg.setdefault(ctxt, set()).add(key)
|
||||
msgid_to_msg.setdefault(msgid, set()).add(key)
|
||||
msgstr_to_msg.setdefault(msg.msgstr, set()).add(key)
|
||||
for src in msg.sources:
|
||||
src_to_msg.setdefault(src, set()).add(key)
|
||||
self._reverse_cache = (src_to_msg, ctxt_to_msg, msgid_to_msg, msgstr_to_msg)
|
||||
|
||||
def find_best_messages_matches(self, msgs, msgmap, rna_ctxt, rna_struct_name, rna_prop_name, rna_enum_name):
|
||||
"""
|
||||
Try to find the best I18nMessages (i.e. context/msgid pairs) for the given UI messages:
|
||||
msgs: an object containing properties listed in msgmap's values.
|
||||
msgmap: a dict of various messages to use for search:
|
||||
{"but_label": subdict, "rna_label": subdict, "enum_label": subdict,
|
||||
"but_tip": subdict, "rna_tip": subdict, "enum_tip": subdict}
|
||||
each subdict being like that:
|
||||
{"msgstr": id, "msgid": id, "msg_flags": id, "key": set()}
|
||||
where msgstr and msgid are identifiers of string props in msgs (resp. translated and org message),
|
||||
msg_flags is not used here, and key is a set of matching (msgctxt, msgid) keys for the item.
|
||||
The other parameters are about the RNA element from which the strings come from, if it could be determined:
|
||||
rna_ctxt: the labels' i18n context.
|
||||
rna_struct_name, rna_prop_name, rna_enum_name: should be self-explanatory!
|
||||
"""
|
||||
try:
|
||||
import bpy
|
||||
except ModuleNotFoundError:
|
||||
print("Could not import bpy, find_best_messages_matches must be run from within Blender.")
|
||||
return
|
||||
|
||||
# Build helper mappings.
|
||||
# Note it's user responsibility to know when to invalidate (and hence force rebuild) this cache!
|
||||
if self._reverse_cache is None:
|
||||
self.invalidate_reverse_cache(True)
|
||||
src_to_msg, ctxt_to_msg, msgid_to_msg, msgstr_to_msg = self._reverse_cache
|
||||
|
||||
# print(len(src_to_msg), len(ctxt_to_msg), len(msgid_to_msg), len(msgstr_to_msg))
|
||||
|
||||
# Build RNA key.
|
||||
src, src_rna, src_enum = bpy.utils.make_rna_paths(rna_struct_name, rna_prop_name, rna_enum_name)
|
||||
print("src: ", src_rna, src_enum)
|
||||
|
||||
# Labels.
|
||||
elbl = getattr(msgs, msgmap["enum_label"]["msgstr"])
|
||||
if elbl:
|
||||
# Enum items' labels have no i18n context...
|
||||
k = ctxt_to_msg[self.settings.DEFAULT_CONTEXT].copy()
|
||||
if elbl in msgid_to_msg:
|
||||
k &= msgid_to_msg[elbl]
|
||||
elif elbl in msgstr_to_msg:
|
||||
k &= msgstr_to_msg[elbl]
|
||||
else:
|
||||
k = set()
|
||||
# We assume if we already have only one key, it's the good one!
|
||||
if len(k) > 1 and src_enum in src_to_msg:
|
||||
k &= src_to_msg[src_enum]
|
||||
msgmap["enum_label"]["key"] = k
|
||||
rlbl = getattr(msgs, msgmap["rna_label"]["msgstr"])
|
||||
#print("rna label: " + rlbl, rlbl in msgid_to_msg, rlbl in msgstr_to_msg)
|
||||
if rlbl:
|
||||
k = ctxt_to_msg[rna_ctxt].copy()
|
||||
if k and rlbl in msgid_to_msg:
|
||||
k &= msgid_to_msg[rlbl]
|
||||
elif k and rlbl in msgstr_to_msg:
|
||||
k &= msgstr_to_msg[rlbl]
|
||||
else:
|
||||
k = set()
|
||||
# We assume if we already have only one key, it's the good one!
|
||||
if len(k) > 1 and src_rna in src_to_msg:
|
||||
k &= src_to_msg[src_rna]
|
||||
msgmap["rna_label"]["key"] = k
|
||||
blbl = getattr(msgs, msgmap["but_label"]["msgstr"])
|
||||
blbls = [blbl]
|
||||
if blbl.endswith(self.settings.NUM_BUTTON_SUFFIX):
|
||||
# Num buttons report their label with a trailing ': '...
|
||||
blbls.append(blbl[:-len(self.settings.NUM_BUTTON_SUFFIX)])
|
||||
print("button label: " + blbl)
|
||||
if blbl and elbl not in blbls and (rlbl not in blbls or rna_ctxt != self.settings.DEFAULT_CONTEXT):
|
||||
# Always Default context for button label :/
|
||||
k = ctxt_to_msg[self.settings.DEFAULT_CONTEXT].copy()
|
||||
found = False
|
||||
for bl in blbls:
|
||||
if bl in msgid_to_msg:
|
||||
k &= msgid_to_msg[bl]
|
||||
found = True
|
||||
break
|
||||
elif bl in msgstr_to_msg:
|
||||
k &= msgstr_to_msg[bl]
|
||||
found = True
|
||||
break
|
||||
if not found:
|
||||
k = set()
|
||||
# XXX No need to check against RNA path here, if blabel is different
|
||||
# from rlabel, should not match anyway!
|
||||
msgmap["but_label"]["key"] = k
|
||||
|
||||
# Tips (they never have a specific context).
|
||||
etip = getattr(msgs, msgmap["enum_tip"]["msgstr"])
|
||||
#print("enum tip: " + etip)
|
||||
if etip:
|
||||
k = ctxt_to_msg[self.settings.DEFAULT_CONTEXT].copy()
|
||||
if etip in msgid_to_msg:
|
||||
k &= msgid_to_msg[etip]
|
||||
elif etip in msgstr_to_msg:
|
||||
k &= msgstr_to_msg[etip]
|
||||
else:
|
||||
k = set()
|
||||
# We assume if we already have only one key, it's the good one!
|
||||
if len(k) > 1 and src_enum in src_to_msg:
|
||||
k &= src_to_msg[src_enum]
|
||||
msgmap["enum_tip"]["key"] = k
|
||||
rtip = getattr(msgs, msgmap["rna_tip"]["msgstr"])
|
||||
#print("rna tip: " + rtip)
|
||||
if rtip:
|
||||
k = ctxt_to_msg[self.settings.DEFAULT_CONTEXT].copy()
|
||||
if k and rtip in msgid_to_msg:
|
||||
k &= msgid_to_msg[rtip]
|
||||
elif k and rtip in msgstr_to_msg:
|
||||
k &= msgstr_to_msg[rtip]
|
||||
else:
|
||||
k = set()
|
||||
# We assume if we already have only one key, it's the good one!
|
||||
if len(k) > 1 and src_rna in src_to_msg:
|
||||
k &= src_to_msg[src_rna]
|
||||
msgmap["rna_tip"]["key"] = k
|
||||
# print(k)
|
||||
btip = getattr(msgs, msgmap["but_tip"]["msgstr"])
|
||||
#print("button tip: " + btip)
|
||||
if btip and btip not in {rtip, etip}:
|
||||
k = ctxt_to_msg[self.settings.DEFAULT_CONTEXT].copy()
|
||||
if btip in msgid_to_msg:
|
||||
k &= msgid_to_msg[btip]
|
||||
elif btip in msgstr_to_msg:
|
||||
k &= msgstr_to_msg[btip]
|
||||
else:
|
||||
k = set()
|
||||
# XXX No need to check against RNA path here, if btip is different from rtip, should not match anyway!
|
||||
msgmap["but_tip"]["key"] = k
|
||||
|
||||
def parse(self, kind, key, src):
|
||||
del self.parsing_errors[:]
|
||||
self.parsers[kind](self, src, key)
|
||||
if self.parsing_errors:
|
||||
print("{} ({}):".format(key, src))
|
||||
self.print_info(print_stats=False)
|
||||
print("The parser solved them as well as it could...")
|
||||
self.update_info()
|
||||
|
||||
def parse_messages_from_po(self, src, key=None):
|
||||
"""
|
||||
Parse a po file.
|
||||
Note: This function will silently "arrange" mis-formatted entries, thus using afterward write_messages() should
|
||||
always produce a po-valid file, though not correct!
|
||||
"""
|
||||
reading_msgid = False
|
||||
reading_msgstr = False
|
||||
reading_msgctxt = False
|
||||
reading_comment = False
|
||||
is_commented = False
|
||||
is_fuzzy = False
|
||||
msgctxt_lines = []
|
||||
msgid_lines = []
|
||||
msgstr_lines = []
|
||||
comment_lines = []
|
||||
|
||||
default_context = self.settings.DEFAULT_CONTEXT
|
||||
|
||||
# Helper function
|
||||
def finalize_message(self, line_nr):
|
||||
nonlocal reading_msgid, reading_msgstr, reading_msgctxt, reading_comment
|
||||
nonlocal is_commented, is_fuzzy, msgid_lines, msgstr_lines, msgctxt_lines, comment_lines
|
||||
|
||||
msgid = I18nMessage.do_unescape("".join(msgid_lines))
|
||||
msgctxt = I18nMessage.do_unescape("".join(msgctxt_lines))
|
||||
msgkey = (msgctxt or default_context, msgid)
|
||||
|
||||
# Never allow overriding existing msgid/msgctxt pairs!
|
||||
if msgkey in self.msgs:
|
||||
self.parsing_errors.append((line_nr, "{} context/msgid is already in current messages!".format(msgkey)))
|
||||
return
|
||||
|
||||
self.msgs[msgkey] = I18nMessage(msgctxt_lines, msgid_lines, msgstr_lines, comment_lines,
|
||||
is_commented, is_fuzzy, settings=self.settings)
|
||||
|
||||
# Let's clean up and get ready for next message!
|
||||
reading_msgid = reading_msgstr = reading_msgctxt = reading_comment = False
|
||||
is_commented = is_fuzzy = False
|
||||
msgctxt_lines = []
|
||||
msgid_lines = []
|
||||
msgstr_lines = []
|
||||
comment_lines = []
|
||||
|
||||
# try to use src as file name...
|
||||
if os.path.isfile(src):
|
||||
if os.stat(src).st_size > self.settings.PARSER_MAX_FILE_SIZE:
|
||||
# Security, else we could read arbitrary huge files!
|
||||
print("WARNING: skipping file {}, too huge!".format(src))
|
||||
return
|
||||
if not key:
|
||||
key = src
|
||||
with open(src, 'r', encoding="utf-8") as f:
|
||||
src = f.read()
|
||||
|
||||
_msgctxt = self.settings.PO_MSGCTXT
|
||||
_comm_msgctxt = self.settings.PO_COMMENT_PREFIX_MSG + _msgctxt
|
||||
_len_msgctxt = len(_msgctxt + '"')
|
||||
_len_comm_msgctxt = len(_comm_msgctxt + '"')
|
||||
_msgid = self.settings.PO_MSGID
|
||||
_comm_msgid = self.settings.PO_COMMENT_PREFIX_MSG + _msgid
|
||||
_len_msgid = len(_msgid + '"')
|
||||
_len_comm_msgid = len(_comm_msgid + '"')
|
||||
_msgstr = self.settings.PO_MSGSTR
|
||||
_comm_msgstr = self.settings.PO_COMMENT_PREFIX_MSG + _msgstr
|
||||
_len_msgstr = len(_msgstr + '"')
|
||||
_len_comm_msgstr = len(_comm_msgstr + '"')
|
||||
_comm_str = self.settings.PO_COMMENT_PREFIX_MSG
|
||||
_comm_fuzzy = self.settings.PO_COMMENT_FUZZY
|
||||
_len_comm_str = len(_comm_str + '"')
|
||||
|
||||
# Main loop over all lines in src...
|
||||
for line_nr, line in enumerate(src.splitlines()):
|
||||
if line == "":
|
||||
if reading_msgstr:
|
||||
finalize_message(self, line_nr)
|
||||
continue
|
||||
|
||||
elif line.startswith(_msgctxt) or line.startswith(_comm_msgctxt):
|
||||
reading_comment = False
|
||||
reading_ctxt = True
|
||||
if line.startswith(_comm_str):
|
||||
is_commented = True
|
||||
line = line[_len_comm_msgctxt:-1]
|
||||
else:
|
||||
line = line[_len_msgctxt:-1]
|
||||
msgctxt_lines.append(line)
|
||||
|
||||
elif line.startswith(_msgid) or line.startswith(_comm_msgid):
|
||||
reading_comment = False
|
||||
reading_msgid = True
|
||||
if line.startswith(_comm_str):
|
||||
if not is_commented and reading_ctxt:
|
||||
self.parsing_errors.append((line_nr, "commented msgid following regular msgctxt"))
|
||||
is_commented = True
|
||||
line = line[_len_comm_msgid:-1]
|
||||
else:
|
||||
line = line[_len_msgid:-1]
|
||||
reading_ctxt = False
|
||||
msgid_lines.append(line)
|
||||
|
||||
elif line.startswith(_msgstr) or line.startswith(_comm_msgstr):
|
||||
if not reading_msgid:
|
||||
self.parsing_errors.append((line_nr, "msgstr without a prior msgid"))
|
||||
else:
|
||||
reading_msgid = False
|
||||
reading_msgstr = True
|
||||
if line.startswith(_comm_str):
|
||||
line = line[_len_comm_msgstr:-1]
|
||||
if not is_commented:
|
||||
self.parsing_errors.append((line_nr, "commented msgstr following regular msgid"))
|
||||
else:
|
||||
line = line[_len_msgstr:-1]
|
||||
if is_commented:
|
||||
self.parsing_errors.append((line_nr, "regular msgstr following commented msgid"))
|
||||
msgstr_lines.append(line)
|
||||
|
||||
elif line.startswith(_comm_str[0]):
|
||||
if line.startswith(_comm_str):
|
||||
if reading_msgctxt:
|
||||
if is_commented:
|
||||
msgctxt_lines.append(line[_len_comm_str:-1])
|
||||
else:
|
||||
msgctxt_lines.append(line)
|
||||
self.parsing_errors.append((line_nr, "commented string while reading regular msgctxt"))
|
||||
elif reading_msgid:
|
||||
if is_commented:
|
||||
msgid_lines.append(line[_len_comm_str:-1])
|
||||
else:
|
||||
msgid_lines.append(line)
|
||||
self.parsing_errors.append((line_nr, "commented string while reading regular msgid"))
|
||||
elif reading_msgstr:
|
||||
if is_commented:
|
||||
msgstr_lines.append(line[_len_comm_str:-1])
|
||||
else:
|
||||
msgstr_lines.append(line)
|
||||
self.parsing_errors.append((line_nr, "commented string while reading regular msgstr"))
|
||||
else:
|
||||
if reading_msgctxt or reading_msgid or reading_msgstr:
|
||||
self.parsing_errors.append((line_nr,
|
||||
"commented string within msgctxt, msgid or msgstr scope, ignored"))
|
||||
elif line.startswith(_comm_fuzzy):
|
||||
is_fuzzy = True
|
||||
else:
|
||||
comment_lines.append(line)
|
||||
reading_comment = True
|
||||
|
||||
else:
|
||||
if reading_msgctxt:
|
||||
msgctxt_lines.append(line[1:-1])
|
||||
elif reading_msgid:
|
||||
msgid_lines.append(line[1:-1])
|
||||
elif reading_msgstr:
|
||||
line = line[1:-1]
|
||||
msgstr_lines.append(line)
|
||||
else:
|
||||
self.parsing_errors.append((line_nr, "regular string outside msgctxt, msgid or msgstr scope"))
|
||||
#self.parsing_errors += (str(comment_lines), str(msgctxt_lines), str(msgid_lines), str(msgstr_lines))
|
||||
|
||||
# If no final empty line, last message is not finalized!
|
||||
if reading_msgstr:
|
||||
finalize_message(self, line_nr)
|
||||
self.unescape()
|
||||
|
||||
def write(self, kind, dest):
|
||||
self.writers[kind](self, dest)
|
||||
|
||||
def write_messages_to_po(self, fname, compact=False):
|
||||
"""
|
||||
Write messages in fname po file.
|
||||
"""
|
||||
default_context = self.settings.DEFAULT_CONTEXT
|
||||
|
||||
def _write(self, f, compact):
|
||||
_msgctxt = self.settings.PO_MSGCTXT
|
||||
_msgid = self.settings.PO_MSGID
|
||||
_msgstr = self.settings.PO_MSGSTR
|
||||
_comm = self.settings.PO_COMMENT_PREFIX_MSG
|
||||
|
||||
self.escape()
|
||||
|
||||
for num, msg in enumerate(self.msgs.values()):
|
||||
if compact and (msg.is_commented or msg.is_fuzzy or not msg.msgstr_lines):
|
||||
continue
|
||||
if not compact:
|
||||
f.write("\n".join(msg.comment_lines))
|
||||
# Only mark as fuzzy if msgstr is not empty!
|
||||
if msg.is_fuzzy and msg.msgstr_lines:
|
||||
f.write("\n" + self.settings.PO_COMMENT_FUZZY)
|
||||
_p = _comm if msg.is_commented else ""
|
||||
chunks = []
|
||||
if msg.msgctxt and msg.msgctxt != default_context:
|
||||
if len(msg.msgctxt_lines) > 1:
|
||||
chunks += [
|
||||
"\n" + _p + _msgctxt + "\"\"\n" + _p + "\"",
|
||||
("\"\n" + _p + "\"").join(msg.msgctxt_lines),
|
||||
"\"",
|
||||
]
|
||||
else:
|
||||
chunks += ["\n" + _p + _msgctxt + "\"" + msg.msgctxt + "\""]
|
||||
if len(msg.msgid_lines) > 1:
|
||||
chunks += [
|
||||
"\n" + _p + _msgid + "\"\"\n" + _p + "\"",
|
||||
("\"\n" + _p + "\"").join(msg.msgid_lines),
|
||||
"\"",
|
||||
]
|
||||
else:
|
||||
chunks += ["\n" + _p + _msgid + "\"" + msg.msgid + "\""]
|
||||
if len(msg.msgstr_lines) > 1:
|
||||
chunks += [
|
||||
"\n" + _p + _msgstr + "\"\"\n" + _p + "\"",
|
||||
("\"\n" + _p + "\"").join(msg.msgstr_lines),
|
||||
"\"",
|
||||
]
|
||||
else:
|
||||
chunks += ["\n" + _p + _msgstr + "\"" + msg.msgstr + "\""]
|
||||
chunks += ["\n\n"]
|
||||
f.write("".join(chunks))
|
||||
|
||||
self.unescape()
|
||||
|
||||
self.normalize(max_len=0) # No wrapping for now...
|
||||
if isinstance(fname, str):
|
||||
with open(fname, 'w', encoding="utf-8") as f:
|
||||
_write(self, f, compact)
|
||||
# Else assume fname is already a file(like) object!
|
||||
else:
|
||||
_write(self, fname, compact)
|
||||
|
||||
def write_messages_to_mo(self, fname):
|
||||
"""
|
||||
Write messages in fname mo file.
|
||||
"""
|
||||
# XXX Temp solution, until I can make own mo generator working...
|
||||
import subprocess
|
||||
with tempfile.NamedTemporaryFile(mode='w+', encoding="utf-8") as tmp_po_f:
|
||||
os.makedirs(os.path.dirname(fname), exist_ok=True)
|
||||
self.write_messages_to_po(tmp_po_f)
|
||||
cmd = (
|
||||
self.settings.GETTEXT_MSGFMT_EXECUTABLE,
|
||||
"--statistics", # show stats
|
||||
tmp_po_f.name,
|
||||
"-o",
|
||||
fname,
|
||||
)
|
||||
ret = subprocess.call(cmd)
|
||||
return
|
||||
# XXX Code below is currently broken (generates corrupted mo files it seems :( )!
|
||||
# Using http://www.gnu.org/software/gettext/manual/html_node/MO-Files.html notation.
|
||||
# Not generating hash table!
|
||||
# Only translated, unfuzzy messages are taken into account!
|
||||
default_context = self.settings.DEFAULT_CONTEXT
|
||||
msgs = tuple(v for v in self.msgs.values() if not (v.is_fuzzy or v.is_commented) and v.msgstr and v.msgid)
|
||||
msgs = sorted(msgs[:2],
|
||||
key=lambda e: (e.msgctxt + e.msgid) if (e.msgctxt and e.msgctxt != default_context) else e.msgid)
|
||||
magic_nbr = 0x950412de
|
||||
format_rev = 0
|
||||
N = len(msgs)
|
||||
O = 32
|
||||
T = O + N * 8
|
||||
S = 0
|
||||
H = T + N * 8
|
||||
# Prepare our data! we need key (optional context and msgid), translation, and offset and length of both.
|
||||
# Offset are relative to start of their own list.
|
||||
EOT = b"0x04" # Used to concatenate context and msgid
|
||||
_msgid_offset = 0
|
||||
_msgstr_offset = 0
|
||||
|
||||
def _gen(v):
|
||||
nonlocal _msgid_offset, _msgstr_offset
|
||||
msgid = v.msgid.encode("utf-8")
|
||||
msgstr = v.msgstr.encode("utf-8")
|
||||
if v.msgctxt and v.msgctxt != default_context:
|
||||
msgctxt = v.msgctxt.encode("utf-8")
|
||||
msgid = msgctxt + EOT + msgid
|
||||
# Don't forget the final NULL char!
|
||||
_msgid_len = len(msgid) + 1
|
||||
_msgstr_len = len(msgstr) + 1
|
||||
ret = ((msgid, _msgid_len, _msgid_offset), (msgstr, _msgstr_len, _msgstr_offset))
|
||||
_msgid_offset += _msgid_len
|
||||
_msgstr_offset += _msgstr_len
|
||||
return ret
|
||||
msgs = tuple(_gen(v) for v in msgs)
|
||||
msgid_start = H
|
||||
msgstr_start = msgid_start + _msgid_offset
|
||||
print(N, msgstr_start + _msgstr_offset)
|
||||
print(msgs)
|
||||
|
||||
with open(fname, 'wb') as f:
|
||||
# Header...
|
||||
f.write(struct.pack("=8I", magic_nbr, format_rev, N, O, T, S, H, 0))
|
||||
# Msgid's length and offset.
|
||||
f.write(b"".join(struct.pack("=2I", length, msgid_start + offset) for (_1, length, offset), _2 in msgs))
|
||||
# Msgstr's length and offset.
|
||||
f.write(b"".join(struct.pack("=2I", length, msgstr_start + offset) for _1, (_2, length, offset) in msgs))
|
||||
# No hash table!
|
||||
# Msgid's.
|
||||
f.write(b"\0".join(msgid for (msgid, _1, _2), _3 in msgs) + b"\0")
|
||||
# Msgstr's.
|
||||
f.write(b"\0".join(msgstr for _1, (msgstr, _2, _3) in msgs) + b"\0")
|
||||
|
||||
parsers = {
|
||||
"PO": parse_messages_from_po,
|
||||
}
|
||||
|
||||
writers = {
|
||||
"PO": write_messages_to_po,
|
||||
"PO_COMPACT": lambda s, fn: s.write_messages_to_po(fn, True),
|
||||
"MO": write_messages_to_mo,
|
||||
}
|
||||
|
||||
|
||||
class I18n:
|
||||
"""
|
||||
Internal representation of a whole translation set.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _parser_check_file(path, maxsize=settings.PARSER_MAX_FILE_SIZE,
|
||||
_begin_marker=settings.PARSER_PY_MARKER_BEGIN,
|
||||
_end_marker=settings.PARSER_PY_MARKER_END):
|
||||
if os.stat(path).st_size > maxsize:
|
||||
# Security, else we could read arbitrary huge files!
|
||||
print("WARNING: skipping file {}, too huge!".format(path))
|
||||
return None, None, None, False
|
||||
txt = ""
|
||||
with open(path, encoding="utf8") as f:
|
||||
txt = f.read()
|
||||
_in = 0
|
||||
_out = len(txt)
|
||||
if _begin_marker:
|
||||
_in = None
|
||||
if _begin_marker in txt:
|
||||
_in = txt.index(_begin_marker) + len(_begin_marker)
|
||||
if _end_marker:
|
||||
_out = None
|
||||
if _end_marker in txt:
|
||||
_out = txt.index(_end_marker)
|
||||
if _in is not None and _out is not None:
|
||||
in_txt, txt, out_txt = txt[:_in], txt[_in:_out], txt[_out:]
|
||||
elif _in is not None:
|
||||
in_txt, txt, out_txt = txt[:_in], txt[_in:], None
|
||||
elif _out is not None:
|
||||
in_txt, txt, out_txt = None, txt[:_out], txt[_out:]
|
||||
else:
|
||||
in_txt, txt, out_txt = None, txt, None
|
||||
return in_txt, txt, out_txt, (True if "translations_tuple" in txt else False)
|
||||
|
||||
@staticmethod
|
||||
def _dst(self, path, uid, kind):
|
||||
if isinstance(path, str):
|
||||
if kind == 'PO':
|
||||
if uid == self.settings.PARSER_TEMPLATE_ID:
|
||||
if not path.endswith(".pot"):
|
||||
return os.path.join(os.path.dirname(path), "blender.pot")
|
||||
if not path.endswith(".po"):
|
||||
return os.path.join(os.path.dirname(path), uid + ".po")
|
||||
elif kind == 'PY':
|
||||
if not path.endswith(".py"):
|
||||
if os.path.isdir(path):
|
||||
return os.path.join(path, "translations.py")
|
||||
return os.path.join(os.path.dirname(path), "translations.py")
|
||||
return path
|
||||
|
||||
def __init__(self, kind=None, src=None, langs=set(), settings=settings):
|
||||
self.settings = settings
|
||||
self.trans = {}
|
||||
self.src = {} # Should have the same keys as self.trans (plus PARSER_PY_ID for py file)!
|
||||
self.dst = self._dst # A callable that transforms src_path into dst_path!
|
||||
if kind and src:
|
||||
self.parse(kind, src, langs)
|
||||
self.update_info()
|
||||
|
||||
def _py_file_get(self):
|
||||
return self.src.get(self.settings.PARSER_PY_ID)
|
||||
|
||||
def _py_file_set(self, value):
|
||||
self.src[self.settings.PARSER_PY_ID] = value
|
||||
py_file = property(_py_file_get, _py_file_set)
|
||||
|
||||
def escape(self, do_all=False):
|
||||
for trans in self.trans.values():
|
||||
trans.escape(do_all)
|
||||
|
||||
def unescape(self, do_all=True):
|
||||
for trans in self.trans.values():
|
||||
trans.unescape(do_all)
|
||||
|
||||
def update_info(self):
|
||||
self.nbr_trans = 0
|
||||
self.lvl = 0.0
|
||||
self.lvl_ttips = 0.0
|
||||
self.lvl_trans_ttips = 0.0
|
||||
self.lvl_ttips_in_trans = 0.0
|
||||
self.lvl_comm = 0.0
|
||||
self.nbr_signs = 0
|
||||
self.nbr_trans_signs = 0
|
||||
self.contexts = set()
|
||||
|
||||
if self.settings.PARSER_TEMPLATE_ID in self.trans:
|
||||
self.nbr_trans = len(self.trans) - 1
|
||||
self.nbr_signs = self.trans[self.settings.PARSER_TEMPLATE_ID].nbr_signs
|
||||
else:
|
||||
self.nbr_trans = len(self.trans)
|
||||
for msgs in self.trans.values():
|
||||
msgs.update_info()
|
||||
if msgs.nbr_msgs > 0:
|
||||
self.lvl += float(msgs.nbr_trans_msgs) / float(msgs.nbr_msgs)
|
||||
self.lvl_ttips += float(msgs.nbr_ttips) / float(msgs.nbr_msgs)
|
||||
self.lvl_comm += float(msgs.nbr_comm_msgs) / float(msgs.nbr_msgs + msgs.nbr_comm_msgs)
|
||||
if msgs.nbr_ttips > 0:
|
||||
self.lvl_trans_ttips = float(msgs.nbr_trans_ttips) / float(msgs.nbr_ttips)
|
||||
if msgs.nbr_trans_msgs > 0:
|
||||
self.lvl_ttips_in_trans = float(msgs.nbr_trans_ttips) / float(msgs.nbr_trans_msgs)
|
||||
if self.nbr_signs == 0:
|
||||
self.nbr_signs = msgs.nbr_signs
|
||||
self.nbr_trans_signs += msgs.nbr_trans_signs
|
||||
self.contexts |= msgs.contexts
|
||||
|
||||
def print_stats(self, prefix="", print_msgs=True):
|
||||
"""
|
||||
Print out some stats about an I18n object.
|
||||
If print_msgs is True, it will also print all its translations' stats.
|
||||
"""
|
||||
if print_msgs:
|
||||
msgs_prefix = prefix + " "
|
||||
for key, msgs in self.trans.items():
|
||||
if key == self.settings.PARSER_TEMPLATE_ID:
|
||||
continue
|
||||
print(prefix + key + ":")
|
||||
msgs.print_stats(prefix=msgs_prefix)
|
||||
print(prefix)
|
||||
|
||||
nbr_contexts = len(self.contexts - {self.settings.DEFAULT_CONTEXT})
|
||||
if nbr_contexts != 1:
|
||||
if nbr_contexts == 0:
|
||||
nbr_contexts = "No"
|
||||
_ctx_txt = "s are"
|
||||
else:
|
||||
_ctx_txt = " is"
|
||||
lines = ((
|
||||
"",
|
||||
"Average stats for all {} translations:\n".format(self.nbr_trans),
|
||||
" {:>6.1%} done!\n".format(self.lvl / self.nbr_trans),
|
||||
" {:>6.1%} of messages are tooltips.\n".format(self.lvl_ttips / self.nbr_trans),
|
||||
" {:>6.1%} of tooltips are translated.\n".format(self.lvl_trans_ttips / self.nbr_trans),
|
||||
" {:>6.1%} of translated messages are tooltips.\n".format(self.lvl_ttips_in_trans / self.nbr_trans),
|
||||
" {:>6.1%} of messages are commented.\n".format(self.lvl_comm / self.nbr_trans),
|
||||
" The org msgids are currently made of {} signs.\n".format(self.nbr_signs),
|
||||
" All processed translations are currently made of {} signs.\n".format(self.nbr_trans_signs),
|
||||
" {} specific context{} present:\n".format(self.nbr_contexts, _ctx_txt)) +
|
||||
tuple(" " + c + "\n" for c in self.contexts - {self.settings.DEFAULT_CONTEXT}) +
|
||||
("\n",)
|
||||
)
|
||||
print(prefix.join(lines))
|
||||
|
||||
@classmethod
|
||||
def check_py_module_has_translations(cls, src, settings=settings):
|
||||
"""
|
||||
Check whether a given src (a py module, either a directory or a py file) has some i18n translation data,
|
||||
and returns a tuple (src_file, translations_tuple) if yes, else (None, None).
|
||||
"""
|
||||
txts = []
|
||||
if os.path.isdir(src):
|
||||
for root, dnames, fnames in os.walk(src):
|
||||
for fname in fnames:
|
||||
if not fname.endswith(".py"):
|
||||
continue
|
||||
path = os.path.join(root, fname)
|
||||
_1, txt, _2, has_trans = cls._parser_check_file(path)
|
||||
if has_trans:
|
||||
txts.append((path, txt))
|
||||
elif src.endswith(".py") and os.path.isfile(src):
|
||||
_1, txt, _2, has_trans = cls._parser_check_file(src)
|
||||
if has_trans:
|
||||
txts.append((src, txt))
|
||||
for path, txt in txts:
|
||||
tuple_id = "translations_tuple"
|
||||
env = globals().copy()
|
||||
exec(txt, env)
|
||||
if tuple_id in env:
|
||||
return path, env[tuple_id]
|
||||
return None, None # No data...
|
||||
|
||||
def parse(self, kind, src, langs=set()):
|
||||
self.parsers[kind](self, src, langs)
|
||||
|
||||
def parse_from_po(self, src, langs=set()):
|
||||
"""
|
||||
src must be a tuple (dir_of_pos, pot_file), where:
|
||||
* dir_of_pos may either contains iso_CODE.po files, and/or iso_CODE/iso_CODE.po files.
|
||||
* pot_file may be None (in which case there will be no ref messages).
|
||||
if langs set is void, all languages found are loaded.
|
||||
"""
|
||||
root_dir, pot_file = src
|
||||
if pot_file and os.path.isfile(pot_file):
|
||||
self.trans[self.settings.PARSER_TEMPLATE_ID] = I18nMessages(self.settings.PARSER_TEMPLATE_ID, 'PO',
|
||||
pot_file, pot_file, settings=self.settings)
|
||||
self.src_po[self.settings.PARSER_TEMPLATE_ID] = pot_file
|
||||
|
||||
for uid, po_file in get_po_files_from_dir(root_dir, langs):
|
||||
self.trans[uid] = I18nMessages(uid, 'PO', po_file, po_file, settings=self.settings)
|
||||
self.src_po[uid] = po_file
|
||||
|
||||
def parse_from_py(self, src, langs=set()):
|
||||
"""
|
||||
src must be a valid path, either a py file or a module directory (in which case all py files inside it
|
||||
will be checked, first file matching will win!).
|
||||
if langs set is void, all languages found are loaded.
|
||||
"""
|
||||
default_context = self.settings.DEFAULT_CONTEXT
|
||||
self.py_file, msgs = self.check_py_module_has_translations(src, self.settings)
|
||||
if msgs is None:
|
||||
self.py_file = src
|
||||
msgs = ()
|
||||
for key, (sources, gen_comments), *translations in msgs:
|
||||
if self.settings.PARSER_TEMPLATE_ID not in self.trans:
|
||||
self.trans[self.settings.PARSER_TEMPLATE_ID] = I18nMessages(self.settings.PARSER_TEMPLATE_ID,
|
||||
settings=self.settings)
|
||||
self.src[self.settings.PARSER_TEMPLATE_ID] = self.py_file
|
||||
if key in self.trans[self.settings.PARSER_TEMPLATE_ID].msgs:
|
||||
print("ERROR! key {} is defined more than once! Skipping re-definitions!")
|
||||
continue
|
||||
custom_src = [c for c in sources if c.startswith("bpy.")]
|
||||
src = [c for c in sources if not c.startswith("bpy.")]
|
||||
common_comment_lines = [self.settings.PO_COMMENT_PREFIX_GENERATED + c for c in gen_comments] + \
|
||||
[self.settings.PO_COMMENT_PREFIX_SOURCE_CUSTOM + c for c in custom_src] + \
|
||||
[self.settings.PO_COMMENT_PREFIX_SOURCE + c for c in src]
|
||||
ctxt = [key[0]] if key[0] else [default_context]
|
||||
self.trans[self.settings.PARSER_TEMPLATE_ID].msgs[key] = I18nMessage(ctxt, [key[1]], [""],
|
||||
common_comment_lines, False, False,
|
||||
settings=self.settings)
|
||||
for uid, msgstr, (is_fuzzy, user_comments) in translations:
|
||||
if uid not in self.trans:
|
||||
self.trans[uid] = I18nMessages(uid, settings=self.settings)
|
||||
self.src[uid] = self.py_file
|
||||
comment_lines = [self.settings.PO_COMMENT_PREFIX + c for c in user_comments] + common_comment_lines
|
||||
self.trans[uid].msgs[key] = I18nMessage(ctxt, [key[1]], [msgstr], comment_lines, False, is_fuzzy,
|
||||
settings=self.settings)
|
||||
# key = self.settings.PO_HEADER_KEY
|
||||
# for uid, trans in self.trans.items():
|
||||
# if key not in trans.msgs:
|
||||
# trans.msgs[key]
|
||||
self.unescape()
|
||||
|
||||
def write(self, kind, langs=set()):
|
||||
self.writers[kind](self, langs)
|
||||
|
||||
def write_to_po(self, langs=set()):
|
||||
"""
|
||||
Write all translations into po files. By default, write in the same files (or dir) as the source, specify
|
||||
a custom self.dst function to write somewhere else!
|
||||
Note: If langs is set and you want to export the pot template as well, langs must contain PARSER_TEMPLATE_ID
|
||||
({} currently).
|
||||
""".format(self.settings.PARSER_TEMPLATE_ID)
|
||||
keys = self.trans.keys()
|
||||
if langs:
|
||||
keys &= langs
|
||||
for uid in keys:
|
||||
dst = self.dst(self, self.src.get(uid, ""), uid, 'PO')
|
||||
self.trans[uid].write('PO', dst)
|
||||
|
||||
def write_to_py(self, langs=set()):
|
||||
"""
|
||||
Write all translations as python code, either in a "translations.py" file under same dir as source(s), or in
|
||||
specified file if self.py_file is set (default, as usual can be customized with self.dst callable!).
|
||||
Note: If langs is set and you want to export the pot template as well, langs must contain PARSER_TEMPLATE_ID
|
||||
({} currently).
|
||||
""".format(self.settings.PARSER_TEMPLATE_ID)
|
||||
default_context = self.settings.DEFAULT_CONTEXT
|
||||
|
||||
def _gen_py(self, langs, tab=" "):
|
||||
_lencomm = len(self.settings.PO_COMMENT_PREFIX)
|
||||
_lengen = len(self.settings.PO_COMMENT_PREFIX_GENERATED)
|
||||
_lensrc = len(self.settings.PO_COMMENT_PREFIX_SOURCE)
|
||||
_lencsrc = len(self.settings.PO_COMMENT_PREFIX_SOURCE_CUSTOM)
|
||||
ret = [
|
||||
"# NOTE: You can safely move around this auto-generated block (with the begin/end markers!),",
|
||||
"# and edit the translations by hand.",
|
||||
"# Just carefully respect the format of the tuple!",
|
||||
"",
|
||||
"# Tuple of tuples:",
|
||||
"# ((msgctxt, msgid), (sources, gen_comments), (lang, translation, (is_fuzzy, comments)), ...)",
|
||||
"translations_tuple = (",
|
||||
]
|
||||
# First gather all keys (msgctxt, msgid) - theoretically, all translations should share the same, but...
|
||||
# Note: using an ordered dict if possible (stupid sets cannot be ordered :/ ).
|
||||
keys = I18nMessages._new_messages()
|
||||
for trans in self.trans.values():
|
||||
keys.update(trans.msgs)
|
||||
# Get the ref translation (ideally, PARSER_TEMPLATE_ID one, else the first one that pops up!
|
||||
# Ref translation will be used to generate sources "comments"
|
||||
ref = self.trans.get(self.settings.PARSER_TEMPLATE_ID) or self.trans[list(self.trans.keys())[0]]
|
||||
# Get all languages (uids) and sort them (PARSER_TEMPLATE_ID and PARSER_PY_ID excluded!)
|
||||
translations = self.trans.keys() - {self.settings.PARSER_TEMPLATE_ID, self.settings.PARSER_PY_ID}
|
||||
if langs:
|
||||
translations &= langs
|
||||
translations = [('"' + lng + '"', " " * (len(lng) + 6), self.trans[lng]) for lng in sorted(translations)]
|
||||
print(*(k for k in keys.keys()))
|
||||
for key in keys.keys():
|
||||
if ref.msgs[key].is_commented:
|
||||
continue
|
||||
# Key (context + msgid).
|
||||
msgctxt, msgid = ref.msgs[key].msgctxt, ref.msgs[key].msgid
|
||||
if not msgctxt:
|
||||
msgctxt = default_context
|
||||
ret.append(tab + "(({}, \"{}\"),".format('"' + msgctxt + '"' if msgctxt else "None", msgid))
|
||||
# Common comments (mostly sources!).
|
||||
sources = []
|
||||
gen_comments = []
|
||||
for comment in ref.msgs[key].comment_lines:
|
||||
if comment.startswith(self.settings.PO_COMMENT_PREFIX_SOURCE_CUSTOM):
|
||||
sources.append(comment[_lencsrc:])
|
||||
elif comment.startswith(self.settings.PO_COMMENT_PREFIX_SOURCE):
|
||||
sources.append(comment[_lensrc:])
|
||||
elif comment.startswith(self.settings.PO_COMMENT_PREFIX_GENERATED):
|
||||
gen_comments.append(comment[_lengen:])
|
||||
if not (sources or gen_comments):
|
||||
ret.append(tab + " ((), ()),")
|
||||
else:
|
||||
if len(sources) > 1:
|
||||
ret.append(tab + ' (("' + sources[0] + '",')
|
||||
ret += [tab + ' "' + s + '",' for s in sources[1:-1]]
|
||||
ret.append(tab + ' "' + sources[-1] + '"),')
|
||||
else:
|
||||
ret.append(tab + " ((" + ('"' + sources[0] + '",' if sources else "") + "),")
|
||||
if len(gen_comments) > 1:
|
||||
ret.append(tab + ' ("' + gen_comments[0] + '",')
|
||||
ret += [tab + ' "' + s + '",' for s in gen_comments[1:-1]]
|
||||
ret.append(tab + ' "' + gen_comments[-1] + '")),')
|
||||
else:
|
||||
ret.append(tab + " (" + ('"' + gen_comments[0] + '",' if gen_comments else "") + ")),")
|
||||
# All languages
|
||||
for lngstr, lngsp, trans in translations:
|
||||
if trans.msgs[key].is_commented:
|
||||
continue
|
||||
# Language code and translation.
|
||||
ret.append(tab + " (" + lngstr + ', "' + trans.msgs[key].msgstr + '",')
|
||||
# User comments and fuzzy.
|
||||
comments = []
|
||||
for comment in trans.msgs[key].comment_lines:
|
||||
if comment.startswith(self.settings.PO_COMMENT_PREFIX):
|
||||
comments.append(comment[_lencomm:])
|
||||
ret.append(tab + lngsp + "(" + ("True" if trans.msgs[key].is_fuzzy else "False") + ",")
|
||||
if len(comments) > 1:
|
||||
ret.append(tab + lngsp + ' ("' + comments[0] + '",')
|
||||
ret += [tab + lngsp + ' "' + s + '",' for s in comments[1:-1]]
|
||||
ret.append(tab + lngsp + ' "' + comments[-1] + '"))),')
|
||||
else:
|
||||
ret[-1] = ret[-1] + " (" + (('"' + comments[0] + '",') if comments else "") + "))),"
|
||||
|
||||
ret.append(tab + "),")
|
||||
ret += [
|
||||
")",
|
||||
"",
|
||||
"translations_dict = {}",
|
||||
"for msg in translations_tuple:",
|
||||
tab + "key = msg[0]",
|
||||
tab + "for lang, trans, (is_fuzzy, comments) in msg[2:]:",
|
||||
tab * 2 + "if trans and not is_fuzzy:",
|
||||
tab * 3 + "translations_dict.setdefault(lang, {})[key] = trans",
|
||||
"",
|
||||
]
|
||||
return ret
|
||||
|
||||
self.escape(True)
|
||||
dst = self.dst(self, self.src.get(self.settings.PARSER_PY_ID, ""), self.settings.PARSER_PY_ID, 'PY')
|
||||
print(dst)
|
||||
prev = txt = nxt = ""
|
||||
if os.path.exists(dst):
|
||||
if not os.path.isfile(dst):
|
||||
print("WARNING: trying to write as python code into {}, which is not a file! Aborting.".format(dst))
|
||||
return
|
||||
prev, txt, nxt, _has_trans = self._parser_check_file(dst)
|
||||
if prev is None and nxt is None:
|
||||
print("WARNING: Looks like given python file {} has no auto-generated translations yet, will be added "
|
||||
"at the end of the file, you can move that section later if needed...".format(dst))
|
||||
txt = ([txt, "", self.settings.PARSER_PY_MARKER_BEGIN] +
|
||||
_gen_py(self, langs) +
|
||||
["", self.settings.PARSER_PY_MARKER_END])
|
||||
else:
|
||||
# We completely replace the text found between start and end markers...
|
||||
txt = _gen_py(self, langs)
|
||||
else:
|
||||
print("Creating python file {} containing translations.".format(dst))
|
||||
txt = [
|
||||
"# SPDX-License-Identifier: GPL-2.0-or-later",
|
||||
"",
|
||||
self.settings.PARSER_PY_MARKER_BEGIN,
|
||||
"",
|
||||
]
|
||||
txt += _gen_py(self, langs)
|
||||
txt += [
|
||||
"",
|
||||
self.settings.PARSER_PY_MARKER_END,
|
||||
]
|
||||
with open(dst, 'w', encoding="utf8") as f:
|
||||
f.write((prev or "") + "\n".join(txt) + (nxt or ""))
|
||||
self.unescape()
|
||||
|
||||
parsers = {
|
||||
"PO": parse_from_po,
|
||||
"PY": parse_from_py,
|
||||
}
|
||||
|
||||
writers = {
|
||||
"PO": write_to_po,
|
||||
"PY": write_to_py,
|
||||
}
|
||||
142
scripts/modules/bl_i18n_utils/utils_cli.py
Normal file
142
scripts/modules/bl_i18n_utils/utils_cli.py
Normal file
@@ -0,0 +1,142 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Some useful operations from utils' I18nMessages class exposed as a CLI.
|
||||
|
||||
import os
|
||||
|
||||
if __package__ is None:
|
||||
import settings as settings_i18n
|
||||
import utils as utils_i18n
|
||||
import utils_languages_menu
|
||||
else:
|
||||
from . import settings as settings_i18n
|
||||
from . import utils as utils_i18n
|
||||
from . import utils_languages_menu
|
||||
|
||||
|
||||
def update_po(args, settings):
|
||||
pot = utils_i18n.I18nMessages(uid=None, kind='PO', src=args.template, settings=settings)
|
||||
if os.path.isfile(args.dst):
|
||||
uid = os.path.splitext(os.path.basename(args.dst))[0]
|
||||
po = utils_i18n.I18nMessages(uid=uid, kind='PO', src=args.dst, settings=settings)
|
||||
po.update(pot)
|
||||
else:
|
||||
po = pot
|
||||
po.write(kind="PO", dest=args.dst)
|
||||
|
||||
|
||||
def cleanup_po(args, settings):
|
||||
uid = os.path.splitext(os.path.basename(args.src))[0]
|
||||
if not args.dst:
|
||||
args.dst = args.src
|
||||
po = utils_i18n.I18nMessages(uid=uid, kind='PO', src=args.src, settings=settings)
|
||||
po.check(fix=True)
|
||||
po.clean_commented()
|
||||
po.write(kind="PO", dest=args.dst)
|
||||
|
||||
|
||||
def strip_po(args, settings):
|
||||
uid = os.path.splitext(os.path.basename(args.src))[0]
|
||||
if not args.dst:
|
||||
args.dst = args.src
|
||||
po = utils_i18n.I18nMessages(uid=uid, kind='PO', src=args.src, settings=settings)
|
||||
po.clean_commented()
|
||||
po.write(kind="PO_COMPACT", dest=args.dst)
|
||||
|
||||
|
||||
def rtl_process_po(args, settings):
|
||||
uid = os.path.splitext(os.path.basename(args.src))[0]
|
||||
if not args.dst:
|
||||
args.dst = args.src
|
||||
po = utils_i18n.I18nMessages(uid=uid, kind='PO', src=args.src, settings=settings)
|
||||
po.rtl_process()
|
||||
po.write(kind="PO", dest=args.dst)
|
||||
|
||||
|
||||
def language_menu(args, settings):
|
||||
# 'DEFAULT' and en_US are always valid, fully-translated "languages"!
|
||||
stats = {"DEFAULT": 1.0, "en_US": 1.0}
|
||||
|
||||
po_to_uid = {
|
||||
os.path.basename(po_path_branch): uid
|
||||
for can_use, uid, _num_id, _name, _isocode, po_path_branch
|
||||
in utils_i18n.list_po_dir(settings.BRANCHES_DIR, settings)
|
||||
if can_use
|
||||
}
|
||||
for po_dir in os.listdir(settings.BRANCHES_DIR):
|
||||
po_dir = os.path.join(settings.BRANCHES_DIR, po_dir)
|
||||
if not os.path.isdir(po_dir):
|
||||
continue
|
||||
for po_path in os.listdir(po_dir):
|
||||
uid = po_to_uid.get(po_path, None)
|
||||
#print("Checking %s, found uid %s" % (po_path, uid))
|
||||
po_path = os.path.join(settings.TRUNK_PO_DIR, po_path)
|
||||
if uid is not None:
|
||||
po = utils_i18n.I18nMessages(uid=uid, kind='PO', src=po_path, settings=settings)
|
||||
stats[uid] = po.nbr_trans_msgs / po.nbr_msgs if po.nbr_msgs > 0 else 0
|
||||
utils_languages_menu.gen_menu_file(stats, settings)
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="Tool to perform common actions over PO/MO files.")
|
||||
parser.add_argument(
|
||||
'-s', '--settings', default=None,
|
||||
help="Override (some) default settings. Either a JSon file name, or a JSon string.",
|
||||
)
|
||||
sub_parsers = parser.add_subparsers()
|
||||
|
||||
sub_parser = sub_parsers.add_parser('update_po', help="Update a PO file from a given POT template file")
|
||||
sub_parser.add_argument(
|
||||
'--template', metavar='template.pot', required=True,
|
||||
help="The source pot file to use as template for the update.",
|
||||
)
|
||||
sub_parser.add_argument('--dst', metavar='dst.po', required=True, help="The destination po to update.")
|
||||
sub_parser.set_defaults(func=update_po)
|
||||
|
||||
sub_parser = sub_parsers.add_parser(
|
||||
'cleanup_po',
|
||||
help="Cleanup a PO file (check for and fix some common errors, remove commented messages).",
|
||||
)
|
||||
sub_parser.add_argument('--src', metavar='src.po', required=True, help="The source po file to clean up.")
|
||||
sub_parser.add_argument('--dst', metavar='dst.po', help="The destination po to write to.")
|
||||
sub_parser.set_defaults(func=cleanup_po)
|
||||
|
||||
sub_parser = sub_parsers.add_parser(
|
||||
'strip_po',
|
||||
help="Reduce all non-essential data from given PO file (reduce its size).",
|
||||
)
|
||||
sub_parser.add_argument('--src', metavar='src.po', required=True, help="The source po file to strip.")
|
||||
sub_parser.add_argument('--dst', metavar='dst.po', help="The destination po to write to.")
|
||||
sub_parser.set_defaults(func=strip_po)
|
||||
|
||||
sub_parser = sub_parsers.add_parser(
|
||||
'rtl_process_po',
|
||||
help="Pre-process PO files for RTL languages.",
|
||||
)
|
||||
sub_parser.add_argument('--src', metavar='src.po', required=True, help="The source po file to process.")
|
||||
sub_parser.add_argument('--dst', metavar='dst.po', help="The destination po to write to.")
|
||||
sub_parser.set_defaults(func=rtl_process_po)
|
||||
|
||||
sub_parser = sub_parsers.add_parser(
|
||||
'language_menu',
|
||||
help="Generate the text file used by Blender to create its language menu.",
|
||||
)
|
||||
sub_parser.set_defaults(func=language_menu)
|
||||
|
||||
args = parser.parse_args(sys.argv[1:])
|
||||
|
||||
settings = settings_i18n.I18nSettings()
|
||||
settings.load(args.settings)
|
||||
|
||||
if getattr(args, 'template', None) is not None:
|
||||
settings.FILE_NAME_POT = args.template
|
||||
|
||||
args.func(args=args, settings=settings)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n\n *** Running {} *** \n".format(__file__))
|
||||
main()
|
||||
81
scripts/modules/bl_i18n_utils/utils_languages_menu.py
Executable file
81
scripts/modules/bl_i18n_utils/utils_languages_menu.py
Executable file
@@ -0,0 +1,81 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Update "languages" text file used by Blender at runtime to build translations menu.
|
||||
|
||||
|
||||
import os
|
||||
|
||||
|
||||
OK = 0
|
||||
MISSING = 1
|
||||
TOOLOW = 2
|
||||
SKIPPED = 3
|
||||
FLAG_MESSAGES = {
|
||||
OK: "",
|
||||
MISSING: "No translation yet.",
|
||||
TOOLOW: "Not complete enough to be included.",
|
||||
SKIPPED: "Skipped (see IMPORT_LANGUAGES_SKIP in settings.py).",
|
||||
}
|
||||
|
||||
|
||||
def gen_menu_file(stats, settings):
|
||||
# Generate languages file used by Blender's i18n system.
|
||||
# First, match all entries in LANGUAGES to a lang in stats, if possible!
|
||||
tmp = []
|
||||
for uid_num, label, uid in settings.LANGUAGES:
|
||||
if uid in stats:
|
||||
if uid in settings.IMPORT_LANGUAGES_SKIP:
|
||||
tmp.append((stats[uid], uid_num, label, uid, SKIPPED))
|
||||
else:
|
||||
tmp.append((stats[uid], uid_num, label, uid, OK))
|
||||
else:
|
||||
tmp.append((0.0, uid_num, label, uid, MISSING))
|
||||
stats = tmp
|
||||
limits = sorted(settings.LANGUAGES_CATEGORIES, key=lambda it: it[0], reverse=True)
|
||||
idx = 0
|
||||
stats = sorted(stats, key=lambda it: it[0], reverse=True)
|
||||
langs_cats = [[] for i in range(len(limits))]
|
||||
highest_uid = 0
|
||||
for lvl, uid_num, label, uid, flag in stats:
|
||||
if lvl < limits[idx][0]:
|
||||
# Sub-sort languages by iso-codes.
|
||||
langs_cats[idx].sort(key=lambda it: it[2])
|
||||
idx += 1
|
||||
if lvl < settings.IMPORT_MIN_LEVEL and flag == OK:
|
||||
flag = TOOLOW
|
||||
langs_cats[idx].append((uid_num, label, uid, flag))
|
||||
if abs(uid_num) > highest_uid:
|
||||
highest_uid = abs(uid_num)
|
||||
# Sub-sort last group of languages by iso-codes!
|
||||
langs_cats[idx].sort(key=lambda it: it[2])
|
||||
data_lines = [
|
||||
"# File used by Blender to know which languages (translations) are available, ",
|
||||
"# and to generate translation menu.",
|
||||
"#",
|
||||
"# File format:",
|
||||
"# ID:MENULABEL:ISOCODE",
|
||||
"# ID must be unique, except for 0 value (marks categories for menu).",
|
||||
"# Line starting with a # are comments!",
|
||||
"#",
|
||||
"# Automatically generated by bl_i18n_utils/update_languages_menu.py script.",
|
||||
"# Highest ID currently in use: {}".format(highest_uid),
|
||||
]
|
||||
for cat, langs_cat in zip(limits, langs_cats):
|
||||
data_lines.append("#")
|
||||
# Write "category menu label"...
|
||||
if langs_cat:
|
||||
data_lines.append("0:{}:".format(cat[1]))
|
||||
else:
|
||||
# Do not write the category if it has no language!
|
||||
data_lines.append("# Void category! #0:{}:".format(cat[1]))
|
||||
# ...and all matching language entries!
|
||||
for uid_num, label, uid, flag in langs_cat:
|
||||
if flag == OK:
|
||||
data_lines.append("{}:{}:{}".format(uid_num, label, uid))
|
||||
else:
|
||||
# Non-existing, commented entry!
|
||||
data_lines.append("# {} #{}:{}:{}".format(FLAG_MESSAGES[flag], uid_num, label, uid))
|
||||
with open(os.path.join(settings.TRUNK_MO_DIR, settings.LANGUAGES_FILE), 'w', encoding="utf8") as f:
|
||||
f.write("\n".join(data_lines))
|
||||
with open(os.path.join(settings.GIT_I18N_ROOT, settings.LANGUAGES_FILE), 'w', encoding="utf8") as f:
|
||||
f.write("\n".join(data_lines))
|
||||
175
scripts/modules/bl_i18n_utils/utils_rtl.py
Executable file
175
scripts/modules/bl_i18n_utils/utils_rtl.py
Executable file
@@ -0,0 +1,175 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Pre-process right-to-left languages.
|
||||
# You can use it either standalone, or through import_po_from_branches or
|
||||
# update_trunk.
|
||||
#
|
||||
# Notes: This has been tested on Linux, not 100% it will work nicely on
|
||||
# Windows or OsX.
|
||||
# This uses ctypes, as there is no py3 binding for fribidi currently.
|
||||
# This implies you only need the compiled C library to run it.
|
||||
# Finally, note that it handles some formatting/escape codes (like
|
||||
# \", %s, %x12, %.4f, etc.), protecting them from ugly (evil) fribidi,
|
||||
# which seems completely unaware of such things (as unicode is...).
|
||||
|
||||
import ctypes
|
||||
import re
|
||||
|
||||
|
||||
# define FRIBIDI_MASK_NEUTRAL 0x00000040L /* Is neutral */
|
||||
FRIBIDI_PAR_ON = 0x00000040
|
||||
|
||||
|
||||
# define FRIBIDI_FLAG_SHAPE_MIRRORING 0x00000001
|
||||
# define FRIBIDI_FLAG_REORDER_NSM 0x00000002
|
||||
|
||||
# define FRIBIDI_FLAG_SHAPE_ARAB_PRES 0x00000100
|
||||
# define FRIBIDI_FLAG_SHAPE_ARAB_LIGA 0x00000200
|
||||
# define FRIBIDI_FLAG_SHAPE_ARAB_CONSOLE 0x00000400
|
||||
|
||||
# define FRIBIDI_FLAG_REMOVE_BIDI 0x00010000
|
||||
# define FRIBIDI_FLAG_REMOVE_JOINING 0x00020000
|
||||
# define FRIBIDI_FLAG_REMOVE_SPECIALS 0x00040000
|
||||
|
||||
# define FRIBIDI_FLAGS_DEFAULT ( \
|
||||
# FRIBIDI_FLAG_SHAPE_MIRRORING | \
|
||||
# FRIBIDI_FLAG_REORDER_NSM | \
|
||||
# FRIBIDI_FLAG_REMOVE_SPECIALS )
|
||||
|
||||
# define FRIBIDI_FLAGS_ARABIC ( \
|
||||
# FRIBIDI_FLAG_SHAPE_ARAB_PRES | \
|
||||
# FRIBIDI_FLAG_SHAPE_ARAB_LIGA )
|
||||
|
||||
FRIBIDI_FLAG_SHAPE_MIRRORING = 0x00000001
|
||||
FRIBIDI_FLAG_REORDER_NSM = 0x00000002
|
||||
FRIBIDI_FLAG_REMOVE_SPECIALS = 0x00040000
|
||||
|
||||
FRIBIDI_FLAG_SHAPE_ARAB_PRES = 0x00000100
|
||||
FRIBIDI_FLAG_SHAPE_ARAB_LIGA = 0x00000200
|
||||
|
||||
FRIBIDI_FLAGS_DEFAULT = FRIBIDI_FLAG_SHAPE_MIRRORING | FRIBIDI_FLAG_REORDER_NSM | FRIBIDI_FLAG_REMOVE_SPECIALS
|
||||
|
||||
FRIBIDI_FLAGS_ARABIC = FRIBIDI_FLAG_SHAPE_ARAB_PRES | FRIBIDI_FLAG_SHAPE_ARAB_LIGA
|
||||
|
||||
|
||||
MENU_DETECT_REGEX = re.compile("%x\\d+\\|")
|
||||
|
||||
|
||||
##### Kernel processing funcs. #####
|
||||
def protect_format_seq(msg):
|
||||
"""
|
||||
Find some specific escaping/formatting sequences (like \", %s, etc.,
|
||||
and protect them from any modification!
|
||||
"""
|
||||
# LRM = "\u200E"
|
||||
# RLM = "\u200F"
|
||||
LRE = "\u202A"
|
||||
# RLE = "\u202B"
|
||||
PDF = "\u202C"
|
||||
LRO = "\u202D"
|
||||
# RLO = "\u202E"
|
||||
# uctrl = {LRE, RLE, PDF, LRO, RLO}
|
||||
# Most likely incomplete, but seems to cover current needs.
|
||||
format_codes = set("tslfd")
|
||||
digits = set(".0123456789")
|
||||
|
||||
if not msg:
|
||||
return msg
|
||||
elif MENU_DETECT_REGEX.search(msg):
|
||||
# An ugly "menu" message, just force it whole LRE if not yet done.
|
||||
if msg[0] not in {LRE, LRO}:
|
||||
msg = LRE + msg
|
||||
|
||||
idx = 0
|
||||
ret = []
|
||||
ln = len(msg)
|
||||
while idx < ln:
|
||||
dlt = 1
|
||||
# # If we find a control char, skip any additional protection!
|
||||
# if msg[idx] in uctrl:
|
||||
# ret.append(msg[idx:])
|
||||
# break
|
||||
# \" or \'
|
||||
if idx < (ln - 1) and msg[idx] == '\\' and msg[idx + 1] in "\"\'":
|
||||
dlt = 2
|
||||
# %x12|
|
||||
elif idx < (ln - 2) and msg[idx] == '%' and msg[idx + 1] in "x" and msg[idx + 2] in digits:
|
||||
dlt = 2
|
||||
while (idx + dlt) < ln and msg[idx + dlt] in digits:
|
||||
dlt += 1
|
||||
if (idx + dlt) < ln and msg[idx + dlt] == '|':
|
||||
dlt += 1
|
||||
# %.4f
|
||||
elif idx < (ln - 3) and msg[idx] == '%' and msg[idx + 1] in digits:
|
||||
dlt = 2
|
||||
while (idx + dlt) < ln and msg[idx + dlt] in digits:
|
||||
dlt += 1
|
||||
if (idx + dlt) < ln and msg[idx + dlt] in format_codes:
|
||||
dlt += 1
|
||||
else:
|
||||
dlt = 1
|
||||
# %s
|
||||
elif idx < (ln - 1) and msg[idx] == '%' and msg[idx + 1] in format_codes:
|
||||
dlt = 2
|
||||
|
||||
if dlt > 1:
|
||||
ret.append(LRE)
|
||||
ret += msg[idx:idx + dlt]
|
||||
idx += dlt
|
||||
if dlt > 1:
|
||||
ret.append(PDF)
|
||||
|
||||
return "".join(ret)
|
||||
|
||||
|
||||
def log2vis(msgs, settings):
|
||||
"""
|
||||
Globally mimics deprecated fribidi_log2vis.
|
||||
msgs should be an iterable of messages to rtl-process.
|
||||
"""
|
||||
fbd = ctypes.CDLL(settings.FRIBIDI_LIB)
|
||||
|
||||
for msg in msgs:
|
||||
msg = protect_format_seq(msg)
|
||||
|
||||
fbc_str = ctypes.create_unicode_buffer(msg)
|
||||
ln = len(fbc_str) - 1
|
||||
# print(fbc_str.value, ln)
|
||||
btypes = (ctypes.c_int * ln)()
|
||||
embed_lvl = (ctypes.c_uint8 * ln)()
|
||||
pbase_dir = ctypes.c_int(FRIBIDI_PAR_ON)
|
||||
jtypes = (ctypes.c_uint8 * ln)()
|
||||
flags = FRIBIDI_FLAGS_DEFAULT | FRIBIDI_FLAGS_ARABIC
|
||||
|
||||
# Find out direction of each char.
|
||||
fbd.fribidi_get_bidi_types(fbc_str, ln, ctypes.byref(btypes))
|
||||
|
||||
# print(*btypes)
|
||||
|
||||
fbd.fribidi_get_par_embedding_levels(btypes, ln,
|
||||
ctypes.byref(pbase_dir),
|
||||
embed_lvl)
|
||||
|
||||
# print(*embed_lvl)
|
||||
|
||||
# Joinings for arabic chars.
|
||||
fbd.fribidi_get_joining_types(fbc_str, ln, jtypes)
|
||||
# print(*jtypes)
|
||||
fbd.fribidi_join_arabic(btypes, ln, embed_lvl, jtypes)
|
||||
# print(*jtypes)
|
||||
|
||||
# Final Shaping!
|
||||
fbd.fribidi_shape(flags, embed_lvl, ln, jtypes, fbc_str)
|
||||
|
||||
# print(fbc_str.value)
|
||||
# print(*(ord(c) for c in fbc_str))
|
||||
# And now, the reordering.
|
||||
# Note that here, we expect a single line, so no need to do
|
||||
# fancy things...
|
||||
fbd.fribidi_reorder_line(flags, btypes, ln, 0, pbase_dir, embed_lvl,
|
||||
fbc_str, None)
|
||||
# print(fbc_str.value)
|
||||
# print(*(ord(c) for c in fbc_str))
|
||||
|
||||
yield fbc_str.value
|
||||
885
scripts/modules/bl_i18n_utils/utils_spell_check.py
Normal file
885
scripts/modules/bl_i18n_utils/utils_spell_check.py
Normal file
@@ -0,0 +1,885 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import enchant
|
||||
import os
|
||||
import pickle
|
||||
import re
|
||||
|
||||
|
||||
class SpellChecker:
|
||||
"""
|
||||
A basic spell checker.
|
||||
"""
|
||||
|
||||
# These must be all lower case for comparisons
|
||||
uimsgs = {
|
||||
# OK words
|
||||
"adaptively", "adaptivity",
|
||||
"aren", # aren't
|
||||
"betweens", # yuck! in-betweens!
|
||||
"boolean", "booleans",
|
||||
"chamfer",
|
||||
"couldn", # couldn't
|
||||
"customizable",
|
||||
"decrement",
|
||||
"derivate",
|
||||
"deterministically",
|
||||
"doesn", # doesn't
|
||||
"duplications",
|
||||
"effector",
|
||||
"equi", # equi-angular, etc.
|
||||
"fader",
|
||||
"globbing",
|
||||
"gridded",
|
||||
"haptics",
|
||||
"hasn", # hasn't
|
||||
"hetero",
|
||||
"hoc", # ad-hoc
|
||||
"incompressible",
|
||||
"indices",
|
||||
"instantiation",
|
||||
"iridas",
|
||||
"isn", # isn't
|
||||
"iterable",
|
||||
"kyrgyz",
|
||||
"latin",
|
||||
"merchantability",
|
||||
"mplayer",
|
||||
"ons", # add-ons
|
||||
"pong", # ping pong
|
||||
"resumable",
|
||||
"runtimes",
|
||||
"scalable",
|
||||
"shadeless",
|
||||
"shouldn", # shouldn't
|
||||
"smoothen",
|
||||
"spacings",
|
||||
"teleport", "teleporting",
|
||||
"tangency",
|
||||
"vertices",
|
||||
"wasn", # wasn't
|
||||
"zig", "zag",
|
||||
|
||||
# Brands etc.
|
||||
"htc",
|
||||
"huawei",
|
||||
"radeon",
|
||||
"vive",
|
||||
"xbox",
|
||||
|
||||
# Merged words
|
||||
"antialiasing", "antialias",
|
||||
"arcsine", "arccosine", "arctangent",
|
||||
"autoclip",
|
||||
"autocomplete",
|
||||
"autoexec",
|
||||
"autoexecution",
|
||||
"autogenerated",
|
||||
"autolock",
|
||||
"automask", "automasking",
|
||||
"automerge",
|
||||
"autoname",
|
||||
"autopack",
|
||||
"autosave",
|
||||
"autoscale",
|
||||
"autosmooth",
|
||||
"autosplit",
|
||||
"backface", "backfacing",
|
||||
"backimage",
|
||||
"backscattered",
|
||||
"bandnoise",
|
||||
"bindcode",
|
||||
"bitdepth",
|
||||
"bitflag", "bitflags",
|
||||
"bitrate",
|
||||
"blackbody",
|
||||
"blendfile",
|
||||
"blendin",
|
||||
"bonesize",
|
||||
"boundbox",
|
||||
"boxpack",
|
||||
"buffersize",
|
||||
"builtin", "builtins",
|
||||
"bytecode",
|
||||
"chunksize",
|
||||
"codebase",
|
||||
"customdata",
|
||||
"dataset", "datasets",
|
||||
"de",
|
||||
"deadzone",
|
||||
"deconstruct",
|
||||
"defocus",
|
||||
"denoise", "denoised", "denoising", "denoiser",
|
||||
"deselect", "deselecting", "deselection",
|
||||
"despill", "despilling",
|
||||
"dirtree",
|
||||
"editcurve",
|
||||
"editmesh",
|
||||
"faceforward",
|
||||
"filebrowser",
|
||||
"filelist",
|
||||
"filename", "filenames",
|
||||
"filepath", "filepaths",
|
||||
"forcefield", "forcefields",
|
||||
"framerange",
|
||||
"frontmost",
|
||||
"fulldome", "fulldomes",
|
||||
"fullscreen",
|
||||
"gamepad",
|
||||
"gridline", "gridlines",
|
||||
"hardlight",
|
||||
"hemi",
|
||||
"hostname",
|
||||
"inbetween",
|
||||
"inscatter", "inscattering",
|
||||
"libdata",
|
||||
"lightcache",
|
||||
"lightgroup", "lightgroups",
|
||||
"lightprobe", "lightprobes",
|
||||
"lightless",
|
||||
"lineset",
|
||||
"linestyle", "linestyles",
|
||||
"localview",
|
||||
"lookup", "lookups",
|
||||
"mathutils",
|
||||
"micropolygon",
|
||||
"midlevel",
|
||||
"midground",
|
||||
"mixdown",
|
||||
"monospaced",
|
||||
"multi",
|
||||
"multifractal",
|
||||
"multiframe",
|
||||
"multilayer",
|
||||
"multipaint",
|
||||
"multires", "multiresolution",
|
||||
"multisampling",
|
||||
"multiscatter",
|
||||
"multitexture",
|
||||
"multithreaded",
|
||||
"multiuser",
|
||||
"multiview",
|
||||
"namespace",
|
||||
"nodetree", "nodetrees",
|
||||
"keyconfig",
|
||||
"offscreen",
|
||||
"online",
|
||||
"playhead",
|
||||
"popup", "popups",
|
||||
"pointcloud",
|
||||
"pre",
|
||||
"precache", "precaching",
|
||||
"precalculate",
|
||||
"precomputing",
|
||||
"prefetch",
|
||||
"prefilter", "prefiltering",
|
||||
"preload",
|
||||
"premultiply", "premultiplied",
|
||||
"prepass",
|
||||
"prepend",
|
||||
"preprocess", "preprocessing", "preprocessor", "preprocessed",
|
||||
"preseek",
|
||||
"preselect", "preselected",
|
||||
"promillage",
|
||||
"pushdown",
|
||||
"raytree",
|
||||
"readonly",
|
||||
"realtime",
|
||||
"reinject", "reinjected",
|
||||
"rekey",
|
||||
"relink",
|
||||
"remesh",
|
||||
"reprojection", "reproject", "reprojecting",
|
||||
"resample",
|
||||
"resize",
|
||||
"restpose",
|
||||
"resync", "resynced",
|
||||
"retarget", "retargets", "retargeting", "retargeted",
|
||||
"retiming",
|
||||
"rigidbody",
|
||||
"ringnoise",
|
||||
"rolloff",
|
||||
"runtime",
|
||||
"scanline",
|
||||
"screenshot", "screenshots",
|
||||
"seekability",
|
||||
"selfcollision",
|
||||
"shadowbuffer", "shadowbuffers",
|
||||
"singletexture",
|
||||
"softbox",
|
||||
"spellcheck", "spellchecking",
|
||||
"startup",
|
||||
"stateful",
|
||||
"starfield",
|
||||
"studiolight",
|
||||
"subflare", "subflares",
|
||||
"subframe", "subframes",
|
||||
"subclass", "subclasses", "subclassing",
|
||||
"subdirectory", "subdirectories", "subdir", "subdirs",
|
||||
"subitem",
|
||||
"submode",
|
||||
"submodule", "submodules",
|
||||
"subpath",
|
||||
"subsize",
|
||||
"substep", "substeps",
|
||||
"substring",
|
||||
"targetless",
|
||||
"textbox", "textboxes",
|
||||
"tilemode",
|
||||
"timestamp", "timestamps",
|
||||
"timestep", "timesteps",
|
||||
"todo",
|
||||
"tradeoff",
|
||||
"un",
|
||||
"unadjust", "unadjusted",
|
||||
"unassociate", "unassociated",
|
||||
"unbake",
|
||||
"uncheck",
|
||||
"unclosed",
|
||||
"uncomment",
|
||||
"unculled",
|
||||
"undeformed",
|
||||
"undistort", "undistorted", "undistortion",
|
||||
"ungroup", "ungrouped",
|
||||
"unhide",
|
||||
"unindent",
|
||||
"unitless",
|
||||
"unkeyed",
|
||||
"unlink", "unlinked",
|
||||
"unmute",
|
||||
"unphysical",
|
||||
"unpremultiply",
|
||||
"unprojected",
|
||||
"unprotect",
|
||||
"unreacted",
|
||||
"unreferenced",
|
||||
"unregister",
|
||||
"unselect", "unselected", "unselectable",
|
||||
"unsets",
|
||||
"unshadowed",
|
||||
"unspill",
|
||||
"unstitchable", "unstitch",
|
||||
"unsubdivided", "unsubdivide",
|
||||
"untrusted",
|
||||
"vectorscope",
|
||||
"whitespace", "whitespaces",
|
||||
"worldspace",
|
||||
"workflow",
|
||||
"workspace", "workspaces",
|
||||
|
||||
# Neologisms, slangs
|
||||
"affectable",
|
||||
"animatable",
|
||||
"automagic", "automagically",
|
||||
"blobby",
|
||||
"blockiness", "blocky",
|
||||
"collider", "colliders",
|
||||
"deformer", "deformers",
|
||||
"determinator",
|
||||
"editability",
|
||||
"effectors",
|
||||
"expander",
|
||||
"instancer",
|
||||
"keyer",
|
||||
"lacunarity",
|
||||
"linkable",
|
||||
"numerics",
|
||||
"occluder", "occluders",
|
||||
"overridable",
|
||||
"passepartout",
|
||||
"perspectively",
|
||||
"pixelate",
|
||||
"pointiness",
|
||||
"polycount",
|
||||
"polygonization", "polygonalization", # yuck!
|
||||
"scalings",
|
||||
"selectable", "selectability",
|
||||
"shaper",
|
||||
"smoothen", "smoothening",
|
||||
"spherize", "spherized",
|
||||
"stitchable",
|
||||
"symmetrize",
|
||||
"trackability",
|
||||
"transmissivity",
|
||||
"rasterized", "rasterization", "rasterizer",
|
||||
"renderer", "renderers", "renderable", "renderability",
|
||||
|
||||
# Really bad!!!
|
||||
"convertor",
|
||||
"fullscr",
|
||||
|
||||
# Abbreviations
|
||||
"aero",
|
||||
"amb",
|
||||
"anim",
|
||||
"aov",
|
||||
"app",
|
||||
"bbox", "bboxes",
|
||||
"bksp", # Backspace
|
||||
"bool",
|
||||
"calc",
|
||||
"cfl",
|
||||
"config", "configs",
|
||||
"const",
|
||||
"coord", "coords",
|
||||
"degr",
|
||||
"diff",
|
||||
"dof",
|
||||
"dupli", "duplis",
|
||||
"eg",
|
||||
"esc",
|
||||
"expr",
|
||||
"fac",
|
||||
"fra",
|
||||
"fract",
|
||||
"frs",
|
||||
"grless",
|
||||
"http",
|
||||
"init",
|
||||
"irr", # Irradiance
|
||||
"kbit", "kb",
|
||||
"lang", "langs",
|
||||
"lclick", "rclick",
|
||||
"lensdist",
|
||||
"loc", "rot", "pos",
|
||||
"lorem",
|
||||
"luma",
|
||||
"mbs", # mouse button 'select'.
|
||||
"mem",
|
||||
"multicam",
|
||||
"num",
|
||||
"ok",
|
||||
"orco",
|
||||
"ortho",
|
||||
"pano",
|
||||
"persp",
|
||||
"pref", "prefs",
|
||||
"prev",
|
||||
"param",
|
||||
"premul",
|
||||
"quad", "quads",
|
||||
"quat", "quats",
|
||||
"recalc", "recalcs",
|
||||
"refl",
|
||||
"sce",
|
||||
"sel",
|
||||
"spec",
|
||||
"struct", "structs",
|
||||
"subdiv",
|
||||
"sys",
|
||||
"tex",
|
||||
"texcoord",
|
||||
"tmr", # timer
|
||||
"tri", "tris",
|
||||
"udim", "udims",
|
||||
"upres", # Upresolution
|
||||
"usd",
|
||||
"uv", "uvs", "uvw", "uw", "uvmap",
|
||||
"ve",
|
||||
"vec",
|
||||
"vel", # velocity!
|
||||
"vert", "verts",
|
||||
"vis",
|
||||
"vram",
|
||||
"xor",
|
||||
"xyz", "xzy", "yxz", "yzx", "zxy", "zyx",
|
||||
"xy", "xz", "yx", "yz", "zx", "zy",
|
||||
|
||||
# General computer/science terms
|
||||
"affine",
|
||||
"albedo",
|
||||
"anamorphic",
|
||||
"anisotropic", "anisotropy",
|
||||
"arcminute", "arcminutes",
|
||||
"arcsecond", "arcseconds",
|
||||
"bimanual", # OpenXR?
|
||||
"bitangent",
|
||||
"boid", "boids",
|
||||
"ceil",
|
||||
"centum", # From 'centum weight'
|
||||
"compressibility",
|
||||
"coplanar",
|
||||
"curvilinear",
|
||||
"dekameter", "dekameters",
|
||||
"equiangular",
|
||||
"equisolid",
|
||||
"euler", "eulers",
|
||||
"fribidi",
|
||||
"gettext",
|
||||
"hashable",
|
||||
"hotspot",
|
||||
"hydrostatic",
|
||||
"interocular",
|
||||
"intrinsics",
|
||||
"irradiance",
|
||||
"isosurface",
|
||||
"jitter", "jittering", "jittered",
|
||||
"keymap", "keymaps",
|
||||
"lambertian",
|
||||
"laplacian",
|
||||
"metadata",
|
||||
"microwatt", "microwatts",
|
||||
"milliwatt", "milliwatts",
|
||||
"msgfmt",
|
||||
"nand", "xnor",
|
||||
"nanowatt", "nanowatts",
|
||||
"normals",
|
||||
"numpad",
|
||||
"octahedral",
|
||||
"octree",
|
||||
"omnidirectional",
|
||||
"opengl",
|
||||
"openmp",
|
||||
"parametrization",
|
||||
"photoreceptor",
|
||||
"poly",
|
||||
"polyline", "polylines",
|
||||
"probabilistically",
|
||||
"pulldown", "pulldowns",
|
||||
"quadratically",
|
||||
"quantized",
|
||||
"quartic",
|
||||
"quaternion", "quaternions",
|
||||
"quintic",
|
||||
"samplerate",
|
||||
"sawtooth",
|
||||
"scrollback",
|
||||
"scrollbar",
|
||||
"scroller",
|
||||
"searchable",
|
||||
"spacebar",
|
||||
"subtractive",
|
||||
"superellipse",
|
||||
"thumbstick",
|
||||
"tooltip", "tooltips",
|
||||
"touchpad", "trackpad",
|
||||
"tuple",
|
||||
"unicode",
|
||||
"viewport", "viewports",
|
||||
"viscoelastic",
|
||||
"vorticity",
|
||||
"waveform", "waveforms",
|
||||
"wildcard", "wildcards",
|
||||
"wintab", # Some Windows tablet API
|
||||
|
||||
# General computer graphics terms
|
||||
"anaglyph",
|
||||
"bezier", "beziers",
|
||||
"bicubic",
|
||||
"bilinear",
|
||||
"bindpose",
|
||||
"binormal",
|
||||
"blackpoint", "whitepoint",
|
||||
"blinn",
|
||||
"bokeh",
|
||||
"catadioptric",
|
||||
"centroid",
|
||||
"chroma",
|
||||
"chrominance",
|
||||
"clearcoat",
|
||||
"codec", "codecs",
|
||||
"collada",
|
||||
"compositing",
|
||||
"crossfade",
|
||||
"cubemap", "cubemaps",
|
||||
"cuda",
|
||||
"deinterlace",
|
||||
"dropoff",
|
||||
"duotone",
|
||||
"dv",
|
||||
"eigenvectors",
|
||||
"emissive",
|
||||
"equirectangular",
|
||||
"filmlike",
|
||||
"fisheye",
|
||||
"framerate",
|
||||
"gimbal",
|
||||
"grayscale",
|
||||
"icosahedron",
|
||||
"icosphere",
|
||||
"inpaint",
|
||||
"kerning",
|
||||
"lightmap",
|
||||
"linearlight",
|
||||
"lossless", "lossy",
|
||||
"luminance",
|
||||
"mantaflow",
|
||||
"matcap",
|
||||
"microfacet",
|
||||
"midtones",
|
||||
"mipmap", "mipmaps", "mip",
|
||||
"ngon", "ngons",
|
||||
"ntsc",
|
||||
"nurb", "nurbs",
|
||||
"perlin",
|
||||
"phong",
|
||||
"photorealistic",
|
||||
"pinlight",
|
||||
"posterize",
|
||||
"qi",
|
||||
"radiosity",
|
||||
"raycast", "raycasting",
|
||||
"raytrace", "raytracing", "raytraced",
|
||||
"refractions",
|
||||
"remesher", "remeshing", "remesh",
|
||||
"renderfarm",
|
||||
"scanfill",
|
||||
"shader", "shaders",
|
||||
"shadowmap", "shadowmaps",
|
||||
"softlight",
|
||||
"specular", "specularity",
|
||||
"spillmap",
|
||||
"sobel",
|
||||
"stereoscopy",
|
||||
"texel",
|
||||
"timecode",
|
||||
"tonemap",
|
||||
"toon",
|
||||
"transmissive",
|
||||
"uvproject",
|
||||
"vividlight",
|
||||
"volumetrics",
|
||||
"voronoi",
|
||||
"voxel", "voxels",
|
||||
"vsync",
|
||||
"vulkan",
|
||||
"wireframe",
|
||||
"zmask",
|
||||
"ztransp",
|
||||
|
||||
# Blender terms
|
||||
"audaspace",
|
||||
"azone", # action zone
|
||||
"backwire",
|
||||
"bbone",
|
||||
"bendy", # bones
|
||||
"bmesh",
|
||||
"breakdowner",
|
||||
"bspline",
|
||||
"bweight",
|
||||
"colorband",
|
||||
"crazyspace",
|
||||
"datablock", "datablocks",
|
||||
"despeckle",
|
||||
"depsgraph",
|
||||
"dopesheet",
|
||||
"dupliface", "duplifaces",
|
||||
"dupliframe", "dupliframes",
|
||||
"dupliobject", "dupliob",
|
||||
"dupligroup",
|
||||
"duplivert",
|
||||
"dyntopo",
|
||||
"editbone",
|
||||
"editmode",
|
||||
"eevee",
|
||||
"fcurve", "fcurves",
|
||||
"fedge", "fedges",
|
||||
"filmic",
|
||||
"fluidsim",
|
||||
"freestyle",
|
||||
"enum", "enums",
|
||||
"gizmogroup",
|
||||
"gon", "gons", # N-Gon(s)
|
||||
"gpencil",
|
||||
"idcol",
|
||||
"keyframe", "keyframes", "keyframing", "keyframed",
|
||||
"lookdev",
|
||||
"luminocity",
|
||||
"mathvis",
|
||||
"metaball", "metaballs", "mball",
|
||||
"metaelement", "metaelements",
|
||||
"metastrip", "metastrips",
|
||||
"movieclip",
|
||||
"mpoly",
|
||||
"mtex",
|
||||
"nabla",
|
||||
"navmesh",
|
||||
"outliner",
|
||||
"overscan",
|
||||
"paintmap", "paintmaps",
|
||||
"polygroup", "polygroups",
|
||||
"poselib",
|
||||
"pushpull",
|
||||
"pyconstraint", "pyconstraints",
|
||||
"qe", # keys...
|
||||
"shaderfx", "shaderfxs",
|
||||
"shapekey", "shapekeys",
|
||||
"shrinkfatten",
|
||||
"shrinkwrap",
|
||||
"softbody",
|
||||
"stucci",
|
||||
"subdiv",
|
||||
"subtype",
|
||||
"sunsky",
|
||||
"tessface", "tessfaces",
|
||||
"texface",
|
||||
"timeline", "timelines",
|
||||
"tosphere",
|
||||
"uilist",
|
||||
"userpref",
|
||||
"vcol", "vcols",
|
||||
"vgroup", "vgroups",
|
||||
"vinterlace",
|
||||
"vse",
|
||||
"wasd", "wasdqe", # keys...
|
||||
"wetmap", "wetmaps",
|
||||
"wpaint",
|
||||
"uvwarp",
|
||||
|
||||
# UOC (Ugly Operator Categories)
|
||||
"cachefile",
|
||||
"paintcurve",
|
||||
"ptcache",
|
||||
"dpaint",
|
||||
|
||||
# Algorithm/library names
|
||||
"ashikhmin", # Ashikhmin-Shirley
|
||||
"arsloe", # Texel-Marsen-Arsloe
|
||||
"beckmann",
|
||||
"blackman", # Blackman-Harris
|
||||
"blosc",
|
||||
"burley", # Christensen-Burley
|
||||
"catmull",
|
||||
"catrom",
|
||||
"chebychev",
|
||||
"conrady", # Brown-Conrady
|
||||
"courant",
|
||||
"cryptomatte", "crypto",
|
||||
"embree",
|
||||
"gmp",
|
||||
"hosek",
|
||||
"kutta",
|
||||
"lennard",
|
||||
"marsen", # Texel-Marsen-Arsloe
|
||||
"mikktspace",
|
||||
"minkowski",
|
||||
"minnaert",
|
||||
"mises", # von Mises-Fisher
|
||||
"moskowitz", # Pierson-Moskowitz
|
||||
"musgrave",
|
||||
"nayar",
|
||||
"netravali",
|
||||
"nishita",
|
||||
"ogawa",
|
||||
"oren",
|
||||
"peucker", # Ramer-Douglas-Peucker
|
||||
"pierson", # Pierson-Moskowitz
|
||||
"preetham",
|
||||
"prewitt",
|
||||
"ramer", # Ramer-Douglas-Peucker
|
||||
"runge",
|
||||
"sobol",
|
||||
"verlet",
|
||||
"von", # von Mises-Fisher
|
||||
"wilkie",
|
||||
"worley",
|
||||
|
||||
# Acronyms
|
||||
"aa", "msaa",
|
||||
"acescg", # ACEScg color space.
|
||||
"ao",
|
||||
"aov", "aovs",
|
||||
"api",
|
||||
"apic", # Affine Particle-In-Cell
|
||||
"asc", "cdl",
|
||||
"ascii",
|
||||
"atrac",
|
||||
"avx",
|
||||
"bsdf", "bsdfs",
|
||||
"bssrdf",
|
||||
"bw",
|
||||
"ccd",
|
||||
"cmd",
|
||||
"cmos",
|
||||
"cpus",
|
||||
"ctrl",
|
||||
"cw", "ccw",
|
||||
"dev",
|
||||
"dls",
|
||||
"djv",
|
||||
"dpi",
|
||||
"dvar",
|
||||
"dx",
|
||||
"eo",
|
||||
"ewa",
|
||||
"fh",
|
||||
"fk",
|
||||
"fov",
|
||||
"fft",
|
||||
"futura",
|
||||
"fx",
|
||||
"gfx",
|
||||
"ggx",
|
||||
"gl",
|
||||
"glsl",
|
||||
"gpl",
|
||||
"gpu", "gpus",
|
||||
"hc",
|
||||
"hdc",
|
||||
"hdr", "hdri", "hdris",
|
||||
"hh", "mm", "ss", "ff", # hh:mm:ss:ff timecode
|
||||
"hpg", # Intel Xe-HPG architecture
|
||||
"hsv", "hsva", "hsl",
|
||||
"id",
|
||||
"ies",
|
||||
"ior",
|
||||
"itu",
|
||||
"jonswap",
|
||||
"lfe",
|
||||
"lhs",
|
||||
"lmb", "mmb", "rmb",
|
||||
"lscm",
|
||||
"lx", # Lux light unit
|
||||
"kb",
|
||||
"mis",
|
||||
"mocap",
|
||||
"msgid", "msgids",
|
||||
"mux",
|
||||
"ndof",
|
||||
"pbr", # Physically Based Rendering
|
||||
"ppc",
|
||||
"precisa",
|
||||
"px",
|
||||
"qmc",
|
||||
"rdna",
|
||||
"rdp",
|
||||
"rgb", "rgba",
|
||||
"rhs",
|
||||
"rv",
|
||||
"sdl",
|
||||
"sdls",
|
||||
"sl",
|
||||
"smpte",
|
||||
"ssao",
|
||||
"ssr",
|
||||
"svn",
|
||||
"tma",
|
||||
"ui",
|
||||
"unix",
|
||||
"uuid",
|
||||
"vbo", "vbos",
|
||||
"vfx",
|
||||
"vmm",
|
||||
"vr",
|
||||
"wxyz",
|
||||
"xr",
|
||||
"ycc", "ycca",
|
||||
"yrgb",
|
||||
"yuv", "yuva",
|
||||
|
||||
# Blender acronyms
|
||||
"bli",
|
||||
"bpy",
|
||||
"bvh",
|
||||
"dbvt",
|
||||
"dop", # BLI K-Dop BVH
|
||||
"ik",
|
||||
"nla",
|
||||
"py",
|
||||
"qbvh",
|
||||
"rna",
|
||||
"rvo",
|
||||
"simd",
|
||||
"sph",
|
||||
"svbvh",
|
||||
|
||||
# Files types/formats
|
||||
"aac",
|
||||
"avi",
|
||||
"attrac",
|
||||
"autocad",
|
||||
"autodesk",
|
||||
"bmp",
|
||||
"btx",
|
||||
"cineon",
|
||||
"dpx",
|
||||
"dwaa",
|
||||
"dwab",
|
||||
"dxf",
|
||||
"eps",
|
||||
"exr",
|
||||
"fbx",
|
||||
"fbxnode",
|
||||
"ffmpeg",
|
||||
"flac",
|
||||
"gltf",
|
||||
"gzip",
|
||||
"ico",
|
||||
"jpg", "jpeg", "jpegs",
|
||||
"json",
|
||||
"lzw",
|
||||
"matroska",
|
||||
"mdd",
|
||||
"mkv",
|
||||
"mpeg", "mjpeg",
|
||||
"mtl",
|
||||
"ogg",
|
||||
"openjpeg",
|
||||
"osl",
|
||||
"oso",
|
||||
"pcm",
|
||||
"piz",
|
||||
"png", "pngs",
|
||||
"po",
|
||||
"quicktime",
|
||||
"rle",
|
||||
"sgi",
|
||||
"stl",
|
||||
"svg",
|
||||
"targa", "tga",
|
||||
"tiff",
|
||||
"theora",
|
||||
"vorbis",
|
||||
"vp9",
|
||||
"wav",
|
||||
"webm",
|
||||
"xiph",
|
||||
"xml",
|
||||
"xna",
|
||||
"xvid",
|
||||
}
|
||||
|
||||
_valid_before = "(?<=[\\s*'\"`])|(?<=[a-zA-Z][/-])|(?<=^)"
|
||||
_valid_after = "(?=[\\s'\"`.!?,;:])|(?=[/-]\\s*[a-zA-Z])|(?=$)"
|
||||
_valid_words = "(?:{})(?:(?:[A-Z]+[a-z]*)|[A-Z]*|[a-z]*)(?:{})".format(_valid_before, _valid_after)
|
||||
_split_words = re.compile(_valid_words).findall
|
||||
|
||||
@classmethod
|
||||
def split_words(cls, text):
|
||||
return [w for w in cls._split_words(text) if w]
|
||||
|
||||
def __init__(self, settings, lang="en_US"):
|
||||
self.settings = settings
|
||||
self.dict_spelling = enchant.Dict(lang)
|
||||
self.cache = set(self.uimsgs)
|
||||
|
||||
cache = self.settings.SPELL_CACHE
|
||||
if cache and os.path.exists(cache):
|
||||
with open(cache, 'rb') as f:
|
||||
self.cache |= set(pickle.load(f))
|
||||
|
||||
def __del__(self):
|
||||
cache = self.settings.SPELL_CACHE
|
||||
if cache and os.path.exists(cache):
|
||||
with open(cache, 'wb') as f:
|
||||
pickle.dump(self.cache, f)
|
||||
|
||||
def check(self, txt):
|
||||
ret = []
|
||||
|
||||
if txt in self.cache:
|
||||
return ret
|
||||
|
||||
for w in self.split_words(txt):
|
||||
w_lower = w.lower()
|
||||
if w_lower in self.cache:
|
||||
continue
|
||||
if not self.dict_spelling.check(w):
|
||||
ret.append((w, self.dict_spelling.suggest(w)))
|
||||
else:
|
||||
self.cache.add(w_lower)
|
||||
|
||||
if not ret:
|
||||
self.cache.add(txt)
|
||||
|
||||
return ret
|
||||
7
scripts/modules/bl_keymap_utils/__init__.py
Normal file
7
scripts/modules/bl_keymap_utils/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"io",
|
||||
"keymap_from_toolbar",
|
||||
"keymap_hierarchy",
|
||||
)
|
||||
306
scripts/modules/bl_keymap_utils/io.py
Normal file
306
scripts/modules/bl_keymap_utils/io.py
Normal file
@@ -0,0 +1,306 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Export Functions
|
||||
|
||||
__all__ = (
|
||||
"_init_properties_from_data", # Shared with gizmo default property initialization.
|
||||
"keyconfig_export_as_data",
|
||||
"keyconfig_import_from_data",
|
||||
"keyconfig_init_from_data",
|
||||
"keyconfig_merge",
|
||||
"keymap_init_from_data",
|
||||
)
|
||||
|
||||
|
||||
def indent(levels):
|
||||
return levels * " "
|
||||
|
||||
|
||||
def round_float_32(f):
|
||||
from struct import pack, unpack
|
||||
return unpack("f", pack("f", f))[0]
|
||||
|
||||
|
||||
def repr_f32(f):
|
||||
f_round = round_float_32(f)
|
||||
f_str = repr(f)
|
||||
f_str_frac = f_str.partition(".")[2]
|
||||
if not f_str_frac:
|
||||
return f_str
|
||||
for i in range(1, len(f_str_frac)):
|
||||
f_test = round(f, i)
|
||||
f_test_round = round_float_32(f_test)
|
||||
if f_test_round == f_round:
|
||||
return "%.*f" % (i, f_test)
|
||||
return f_str
|
||||
|
||||
|
||||
def kmi_args_as_data(kmi):
|
||||
s = [
|
||||
f"\"type\": '{kmi.type}'",
|
||||
f"\"value\": '{kmi.value}'"
|
||||
]
|
||||
|
||||
if kmi.any:
|
||||
s.append("\"any\": True")
|
||||
else:
|
||||
for attr in ("shift", "ctrl", "alt", "oskey"):
|
||||
if mod := getattr(kmi, attr):
|
||||
s.append(f"\"{attr:s}\": " + ("-1" if mod == -1 else "True"))
|
||||
if (mod := kmi.key_modifier) and (mod != 'NONE'):
|
||||
s.append(f"\"key_modifier\": '{mod:s}'")
|
||||
if (direction := kmi.direction) and (direction != 'ANY'):
|
||||
s.append(f"\"direction\": '{direction:s}'")
|
||||
|
||||
if kmi.repeat:
|
||||
if (
|
||||
(kmi.map_type == 'KEYBOARD' and kmi.value in {'PRESS', 'ANY'}) or
|
||||
(kmi.map_type == 'TEXTINPUT')
|
||||
):
|
||||
s.append("\"repeat\": True")
|
||||
|
||||
return "{" + ", ".join(s) + "}"
|
||||
|
||||
|
||||
def _kmi_properties_to_lines_recursive(level, properties, lines):
|
||||
from bpy.types import OperatorProperties
|
||||
|
||||
def string_value(value):
|
||||
if isinstance(value, (str, bool, int, set)):
|
||||
return repr(value)
|
||||
elif isinstance(value, float):
|
||||
return repr_f32(value)
|
||||
elif getattr(value, '__len__', False):
|
||||
return repr(tuple(value))
|
||||
raise Exception(f"Export key configuration: can't write {value!r}")
|
||||
|
||||
for pname in properties.bl_rna.properties.keys():
|
||||
if pname != "rna_type":
|
||||
value = getattr(properties, pname)
|
||||
if isinstance(value, OperatorProperties):
|
||||
lines_test = []
|
||||
_kmi_properties_to_lines_recursive(level + 2, value, lines_test)
|
||||
if lines_test:
|
||||
lines.append(f"(")
|
||||
lines.append(f"\"{pname}\",\n")
|
||||
lines.append(f"{indent(level + 3)}" "[")
|
||||
lines.extend(lines_test)
|
||||
lines.append("],\n")
|
||||
lines.append(f"{indent(level + 3)}" "),\n" f"{indent(level + 2)}")
|
||||
del lines_test
|
||||
elif properties.is_property_set(pname):
|
||||
value = string_value(value)
|
||||
lines.append((f"(\"{pname}\", {value:s}),\n" f"{indent(level + 2)}"))
|
||||
|
||||
|
||||
def _kmi_properties_to_lines(level, kmi_props, lines):
|
||||
if kmi_props is None:
|
||||
return
|
||||
|
||||
lines_test = [f"\"properties\":\n" f"{indent(level + 1)}" "["]
|
||||
_kmi_properties_to_lines_recursive(level, kmi_props, lines_test)
|
||||
if len(lines_test) > 1:
|
||||
lines_test.append("],\n")
|
||||
lines.extend(lines_test)
|
||||
|
||||
|
||||
def _kmi_attrs_or_none(level, kmi):
|
||||
lines = []
|
||||
_kmi_properties_to_lines(level + 1, kmi.properties, lines)
|
||||
if kmi.active is False:
|
||||
lines.append(f"{indent(level)}\"active\":" "False,\n")
|
||||
if not lines:
|
||||
return None
|
||||
return "".join(lines)
|
||||
|
||||
|
||||
def keyconfig_export_as_data(wm, kc, filepath, *, all_keymaps=False):
|
||||
# Alternate format
|
||||
|
||||
# Generate a list of keymaps to export:
|
||||
#
|
||||
# First add all user_modified keymaps (found in keyconfigs.user.keymaps list),
|
||||
# then add all remaining keymaps from the currently active custom keyconfig.
|
||||
#
|
||||
# Sort the resulting list according to top context name,
|
||||
# while this isn't essential, it makes comparing keymaps simpler.
|
||||
#
|
||||
# This will create a final list of keymaps that can be used as a "diff" against
|
||||
# the default blender keyconfig, recreating the current setup from a fresh blender
|
||||
# without needing to export keymaps which haven't been edited.
|
||||
|
||||
class FakeKeyConfig:
|
||||
keymaps = []
|
||||
edited_kc = FakeKeyConfig()
|
||||
for km in wm.keyconfigs.user.keymaps:
|
||||
if all_keymaps or km.is_user_modified:
|
||||
edited_kc.keymaps.append(km)
|
||||
# merge edited keymaps with non-default keyconfig, if it exists
|
||||
if kc != wm.keyconfigs.default:
|
||||
export_keymaps = keyconfig_merge(edited_kc, kc)
|
||||
else:
|
||||
export_keymaps = keyconfig_merge(edited_kc, edited_kc)
|
||||
|
||||
# Sort the keymap list by top context name before exporting,
|
||||
# not essential, just convenient to order them predictably.
|
||||
export_keymaps.sort(key=lambda k: k[0].name)
|
||||
|
||||
with open(filepath, "w", encoding="utf-8") as fh:
|
||||
fw = fh.write
|
||||
|
||||
# Use the file version since it includes the sub-version
|
||||
# which we can bump multiple times between releases.
|
||||
from bpy.app import version_file
|
||||
fw(f"keyconfig_version = {version_file!r}\n")
|
||||
del version_file
|
||||
|
||||
fw("keyconfig_data = \\\n[")
|
||||
|
||||
for km, _kc_x in export_keymaps:
|
||||
km = km.active()
|
||||
fw("(")
|
||||
fw(f"\"{km.name:s}\",\n")
|
||||
fw(f"{indent(2)}" "{")
|
||||
fw(f"\"space_type\": '{km.space_type:s}'")
|
||||
fw(f", \"region_type\": '{km.region_type:s}'")
|
||||
# We can detect from the kind of items.
|
||||
if km.is_modal:
|
||||
fw(", \"modal\": True")
|
||||
fw("},\n")
|
||||
fw(f"{indent(2)}" "{")
|
||||
is_modal = km.is_modal
|
||||
fw(f"\"items\":\n")
|
||||
fw(f"{indent(3)}[")
|
||||
for kmi in km.keymap_items:
|
||||
if is_modal:
|
||||
kmi_id = kmi.propvalue
|
||||
else:
|
||||
kmi_id = kmi.idname
|
||||
fw(f"(")
|
||||
kmi_args = kmi_args_as_data(kmi)
|
||||
kmi_data = _kmi_attrs_or_none(4, kmi)
|
||||
fw(f"\"{kmi_id:s}\"")
|
||||
if kmi_data is None:
|
||||
fw(f", ")
|
||||
else:
|
||||
fw(",\n" f"{indent(5)}")
|
||||
|
||||
fw(kmi_args)
|
||||
if kmi_data is None:
|
||||
fw(", None),\n")
|
||||
else:
|
||||
fw(",\n")
|
||||
fw(f"{indent(5)}" "{")
|
||||
fw(kmi_data)
|
||||
fw(f"{indent(6)}")
|
||||
fw("},\n" f"{indent(5)}")
|
||||
fw("),\n")
|
||||
fw(f"{indent(4)}")
|
||||
fw("],\n" f"{indent(3)}")
|
||||
fw("},\n" f"{indent(2)}")
|
||||
fw("),\n" f"{indent(1)}")
|
||||
|
||||
fw("]\n")
|
||||
fw("\n\n")
|
||||
fw("if __name__ == \"__main__\":\n")
|
||||
|
||||
# We could remove this in the future, as loading new key-maps in older Blender versions
|
||||
# makes less and less sense as Blender changes.
|
||||
fw(" # Only add keywords that are supported.\n")
|
||||
fw(" from bpy.app import version as blender_version\n")
|
||||
fw(" keywords = {}\n")
|
||||
fw(" if blender_version >= (2, 92, 0):\n")
|
||||
fw(" keywords[\"keyconfig_version\"] = keyconfig_version\n")
|
||||
|
||||
fw(" import os\n")
|
||||
fw(" from bl_keymap_utils.io import keyconfig_import_from_data\n")
|
||||
fw(" keyconfig_import_from_data(\n")
|
||||
fw(" os.path.splitext(os.path.basename(__file__))[0],\n")
|
||||
fw(" keyconfig_data,\n")
|
||||
fw(" **keywords,\n")
|
||||
fw(" )\n")
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Import Functions
|
||||
#
|
||||
# NOTE: unlike export, this runs on startup.
|
||||
# Take care making changes that could impact performance.
|
||||
|
||||
def _init_properties_from_data(base_props, base_value):
|
||||
assert type(base_value) is list
|
||||
for attr, value in base_value:
|
||||
if type(value) is list:
|
||||
base_props.property_unset(attr)
|
||||
props = getattr(base_props, attr)
|
||||
_init_properties_from_data(props, value)
|
||||
else:
|
||||
try:
|
||||
setattr(base_props, attr, value)
|
||||
except AttributeError:
|
||||
print(f"Warning: property '{attr}' not found in item '{base_props.__class__.__name__}'")
|
||||
except Exception as ex:
|
||||
print(f"Warning: {ex!r}")
|
||||
|
||||
|
||||
def keymap_init_from_data(km, km_items, is_modal=False):
|
||||
new_fn = getattr(km.keymap_items, "new_modal" if is_modal else "new")
|
||||
for (kmi_idname, kmi_args, kmi_data) in km_items:
|
||||
kmi = new_fn(kmi_idname, **kmi_args)
|
||||
if kmi_data is not None:
|
||||
if not kmi_data.get("active", True):
|
||||
kmi.active = False
|
||||
kmi_props_data = kmi_data.get("properties", None)
|
||||
if kmi_props_data is not None:
|
||||
kmi_props = kmi.properties
|
||||
assert type(kmi_props_data) is list
|
||||
_init_properties_from_data(kmi_props, kmi_props_data)
|
||||
|
||||
|
||||
def keyconfig_init_from_data(kc, keyconfig_data):
|
||||
# Load data in the format defined above.
|
||||
#
|
||||
# Runs at load time, keep this fast!
|
||||
for (km_name, km_args, km_content) in keyconfig_data:
|
||||
km = kc.keymaps.new(km_name, **km_args)
|
||||
km_items = km_content["items"]
|
||||
# Check here instead of inside 'keymap_init_from_data'
|
||||
# because we want to allow both tuple & list types in that case.
|
||||
#
|
||||
# For full keymaps, ensure these are always lists to allow for extending them
|
||||
# in a generic way that doesn't have to check for the type each time.
|
||||
assert type(km_items) is list
|
||||
keymap_init_from_data(km, km_items, is_modal=km_args.get("modal", False))
|
||||
|
||||
|
||||
def keyconfig_import_from_data(name, keyconfig_data, *, keyconfig_version=(0, 0, 0)):
|
||||
# Load data in the format defined above.
|
||||
#
|
||||
# Runs at load time, keep this fast!
|
||||
|
||||
import bpy
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.new(name)
|
||||
if keyconfig_version is not None:
|
||||
from .versioning import keyconfig_update
|
||||
keyconfig_data = keyconfig_update(keyconfig_data, keyconfig_version)
|
||||
keyconfig_init_from_data(kc, keyconfig_data)
|
||||
return kc
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utility Functions
|
||||
|
||||
def keyconfig_merge(kc1, kc2):
|
||||
""" note: kc1 takes priority over kc2
|
||||
"""
|
||||
kc1_names = {km.name for km in kc1.keymaps}
|
||||
merged_keymaps = [(km, kc1) for km in kc1.keymaps]
|
||||
if kc1 != kc2:
|
||||
merged_keymaps.extend(
|
||||
(km, kc2)
|
||||
for km in kc2.keymaps
|
||||
if km.name not in kc1_names
|
||||
)
|
||||
return merged_keymaps
|
||||
436
scripts/modules/bl_keymap_utils/keymap_from_toolbar.py
Normal file
436
scripts/modules/bl_keymap_utils/keymap_from_toolbar.py
Normal file
@@ -0,0 +1,436 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Dynamically create a keymap which is used by the popup toolbar
|
||||
# for accelerator key access.
|
||||
|
||||
__all__ = (
|
||||
"generate",
|
||||
)
|
||||
|
||||
|
||||
def generate(context, space_type, *, use_fallback_keys=True, use_reset=True):
|
||||
"""
|
||||
Keymap for popup toolbar, currently generated each time.
|
||||
"""
|
||||
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
|
||||
|
||||
def modifier_keywords_from_item(kmi):
|
||||
kw = {}
|
||||
for (attr, default) in (
|
||||
("any", False),
|
||||
("shift", False),
|
||||
("ctrl", False),
|
||||
("alt", False),
|
||||
("oskey", False),
|
||||
("key_modifier", 'NONE'),
|
||||
):
|
||||
val = getattr(kmi, attr)
|
||||
if val != default:
|
||||
kw[attr] = val
|
||||
return kw
|
||||
|
||||
def dict_as_tuple(d):
|
||||
return tuple((k, v) for (k, v) in sorted(d.items()))
|
||||
|
||||
cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
|
||||
|
||||
items_all = [
|
||||
# 0: tool
|
||||
# 1: keymap item (direct access)
|
||||
# 2: keymap item (newly calculated for toolbar)
|
||||
[item, None, None]
|
||||
for item in ToolSelectPanelHelper._tools_flatten(cls.tools_from_context(context))
|
||||
if item is not None
|
||||
]
|
||||
items_all_id = {item_container[0].idname for item_container in items_all}
|
||||
|
||||
# Press the toolbar popup key again to set the default tool,
|
||||
# this is useful because the select box tool is useful as a way
|
||||
# to 'drop' currently active tools (it's basically a 'none' tool).
|
||||
# so this allows us to quickly go back to a state that allows
|
||||
# a shortcut based workflow (before the tool system was added).
|
||||
use_tap_reset = use_reset
|
||||
# TODO: support other tools for modes which don't use this tool.
|
||||
tap_reset_tool = "builtin.cursor"
|
||||
# Check the tool is available in the current context.
|
||||
if tap_reset_tool not in items_all_id:
|
||||
use_tap_reset = False
|
||||
|
||||
# Pie-menu style release to activate.
|
||||
use_release_confirm = use_reset
|
||||
|
||||
# Generate items when no keys are mapped.
|
||||
use_auto_keymap_alpha = False # Map manually in the default key-map.
|
||||
use_auto_keymap_num = use_fallback_keys
|
||||
|
||||
# Temporary, only create so we can pass 'properties' to find_item_from_operator.
|
||||
use_hack_properties = True
|
||||
|
||||
km_name_default = "Toolbar Popup"
|
||||
km_name = km_name_default + " <temp>"
|
||||
wm = context.window_manager
|
||||
keyconf_user = wm.keyconfigs.user
|
||||
keyconf_active = wm.keyconfigs.active
|
||||
|
||||
keymap = keyconf_active.keymaps.get(km_name)
|
||||
if keymap is None:
|
||||
keymap = keyconf_active.keymaps.new(km_name, space_type='EMPTY', region_type='TEMPORARY')
|
||||
for kmi in keymap.keymap_items:
|
||||
keymap.keymap_items.remove(kmi)
|
||||
|
||||
keymap_src = keyconf_user.keymaps.get(km_name_default)
|
||||
if keymap_src is not None:
|
||||
for kmi_src in keymap_src.keymap_items:
|
||||
# Skip tools that aren't currently shown.
|
||||
if (
|
||||
(kmi_src.idname == "wm.tool_set_by_id") and
|
||||
(kmi_src.properties.name not in items_all_id)
|
||||
):
|
||||
continue
|
||||
keymap.keymap_items.new_from_item(kmi_src)
|
||||
del keymap_src
|
||||
del items_all_id
|
||||
|
||||
kmi_unique_args = set()
|
||||
|
||||
def kmi_unique_or_pass(kmi_args):
|
||||
kmi_unique_len = len(kmi_unique_args)
|
||||
kmi_unique_args.add(dict_as_tuple(kmi_args))
|
||||
return kmi_unique_len != len(kmi_unique_args)
|
||||
|
||||
cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
|
||||
|
||||
if use_hack_properties:
|
||||
kmi_hack = keymap.keymap_items.new("wm.tool_set_by_id", 'NONE', 'PRESS')
|
||||
kmi_hack_properties = kmi_hack.properties
|
||||
kmi_hack.active = False
|
||||
|
||||
kmi_hack_brush_select = keymap.keymap_items.new("paint.brush_select", 'NONE', 'PRESS')
|
||||
kmi_hack_brush_select_properties = kmi_hack_brush_select.properties
|
||||
kmi_hack_brush_select.active = False
|
||||
|
||||
if use_release_confirm or use_tap_reset:
|
||||
kmi_toolbar = wm.keyconfigs.find_item_from_operator(
|
||||
idname="wm.toolbar",
|
||||
)[1]
|
||||
kmi_toolbar_type = None if not kmi_toolbar else kmi_toolbar.type
|
||||
if use_tap_reset and kmi_toolbar_type is not None:
|
||||
kmi_toolbar_args_type_only = {"type": kmi_toolbar_type}
|
||||
kmi_toolbar_args = {**kmi_toolbar_args_type_only, **modifier_keywords_from_item(kmi_toolbar)}
|
||||
else:
|
||||
use_tap_reset = False
|
||||
del kmi_toolbar
|
||||
|
||||
if use_tap_reset:
|
||||
kmi_found = None
|
||||
if use_hack_properties:
|
||||
# First check for direct assignment, if this tool already has a key, no need to add a new one.
|
||||
kmi_hack_properties.name = tap_reset_tool
|
||||
kmi_found = wm.keyconfigs.find_item_from_operator(
|
||||
idname="wm.tool_set_by_id",
|
||||
context='INVOKE_REGION_WIN',
|
||||
# properties={"name": item.idname},
|
||||
properties=kmi_hack_properties,
|
||||
include={'KEYBOARD'},
|
||||
)[1]
|
||||
if kmi_found:
|
||||
use_tap_reset = False
|
||||
del kmi_found
|
||||
|
||||
if use_tap_reset:
|
||||
use_tap_reset = kmi_unique_or_pass(kmi_toolbar_args)
|
||||
|
||||
if use_tap_reset:
|
||||
items_all[:] = [
|
||||
item_container
|
||||
for item_container in items_all
|
||||
if item_container[0].idname != tap_reset_tool
|
||||
]
|
||||
|
||||
# -----------------------
|
||||
# Begin Keymap Generation
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Direct Tool Assignment & Brushes
|
||||
|
||||
for item_container in items_all:
|
||||
item = item_container[0]
|
||||
# Only check the first item in the tools key-map (a little arbitrary).
|
||||
if use_hack_properties:
|
||||
# First check for direct assignment.
|
||||
kmi_hack_properties.name = item.idname
|
||||
kmi_found = wm.keyconfigs.find_item_from_operator(
|
||||
idname="wm.tool_set_by_id",
|
||||
context='INVOKE_REGION_WIN',
|
||||
# properties={"name": item.idname},
|
||||
properties=kmi_hack_properties,
|
||||
include={'KEYBOARD'},
|
||||
)[1]
|
||||
|
||||
if kmi_found is None:
|
||||
if item.data_block:
|
||||
# PAINT_OT_brush_select
|
||||
mode = context.active_object.mode
|
||||
# See: BKE_paint_get_tool_prop_id_from_paintmode
|
||||
if space_type == 'IMAGE_EDITOR':
|
||||
if context.space_data.mode == 'PAINT':
|
||||
attr = "image_tool"
|
||||
else:
|
||||
attr = None
|
||||
elif space_type == 'VIEW_3D':
|
||||
attr = {
|
||||
'SCULPT': "sculpt_tool",
|
||||
'VERTEX_PAINT': "vertex_tool",
|
||||
'WEIGHT_PAINT': "weight_tool",
|
||||
'TEXTURE_PAINT': "image_tool",
|
||||
'PAINT_GPENCIL': "gpencil_tool",
|
||||
'VERTEX_GPENCIL': "gpencil_vertex_tool",
|
||||
'SCULPT_GPENCIL': "gpencil_sculpt_tool",
|
||||
'WEIGHT_GPENCIL': "gpencil_weight_tool",
|
||||
'SCULPT_CURVES': "curves_sculpt_tool",
|
||||
}.get(mode, None)
|
||||
else:
|
||||
attr = None
|
||||
|
||||
if attr is not None:
|
||||
setattr(kmi_hack_brush_select_properties, attr, item.data_block)
|
||||
kmi_found = wm.keyconfigs.find_item_from_operator(
|
||||
idname="paint.brush_select",
|
||||
context='INVOKE_REGION_WIN',
|
||||
properties=kmi_hack_brush_select_properties,
|
||||
include={'KEYBOARD'},
|
||||
)[1]
|
||||
elif mode in {'EDIT', 'PARTICLE_EDIT', 'SCULPT_GPENCIL'}:
|
||||
# Doesn't use brushes
|
||||
pass
|
||||
else:
|
||||
print("Unsupported mode:", mode)
|
||||
del mode, attr
|
||||
|
||||
else:
|
||||
kmi_found = None
|
||||
|
||||
if kmi_found is not None:
|
||||
pass
|
||||
elif item.operator is not None:
|
||||
kmi_found = wm.keyconfigs.find_item_from_operator(
|
||||
idname=item.operator,
|
||||
context='INVOKE_REGION_WIN',
|
||||
include={'KEYBOARD'},
|
||||
)[1]
|
||||
elif item.keymap is not None:
|
||||
km = keyconf_user.keymaps.get(item.keymap[0])
|
||||
if km is None:
|
||||
print("Keymap", repr(item.keymap[0]), "not found for tool", item.idname)
|
||||
kmi_found = None
|
||||
else:
|
||||
kmi_first = km.keymap_items
|
||||
kmi_first = kmi_first[0] if kmi_first else None
|
||||
if kmi_first is not None:
|
||||
kmi_found = wm.keyconfigs.find_item_from_operator(
|
||||
idname=kmi_first.idname,
|
||||
# properties=kmi_first.properties, # prevents matches, don't use.
|
||||
context='INVOKE_REGION_WIN',
|
||||
include={'KEYBOARD'},
|
||||
)[1]
|
||||
if kmi_found is None:
|
||||
# We need non-keyboard events so keys with 'key_modifier' key is found.
|
||||
kmi_found = wm.keyconfigs.find_item_from_operator(
|
||||
idname=kmi_first.idname,
|
||||
# properties=kmi_first.properties, # prevents matches, don't use.
|
||||
context='INVOKE_REGION_WIN',
|
||||
exclude={'KEYBOARD'},
|
||||
)[1]
|
||||
if kmi_found is not None:
|
||||
if kmi_found.key_modifier == 'NONE':
|
||||
kmi_found = None
|
||||
else:
|
||||
kmi_found = None
|
||||
del kmi_first
|
||||
del km
|
||||
else:
|
||||
kmi_found = None
|
||||
item_container[1] = kmi_found
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Single Key Access
|
||||
|
||||
# More complex multi-pass test.
|
||||
for item_container in items_all:
|
||||
item, kmi_found = item_container[:2]
|
||||
if kmi_found is None:
|
||||
continue
|
||||
kmi_found_type = kmi_found.type
|
||||
|
||||
# Only for single keys.
|
||||
if (
|
||||
(len(kmi_found_type) == 1) or
|
||||
# When a tool is being activated instead of running an operator, just copy the shortcut.
|
||||
(kmi_found.idname in {"wm.tool_set_by_id", "WM_OT_tool_set_by_id"})
|
||||
):
|
||||
kmi_args = {"type": kmi_found_type, **modifier_keywords_from_item(kmi_found)}
|
||||
if kmi_unique_or_pass(kmi_args):
|
||||
kmi = keymap.keymap_items.new(idname="wm.tool_set_by_id", value='PRESS', **kmi_args)
|
||||
kmi.properties.name = item.idname
|
||||
item_container[2] = kmi
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Single Key Modifier
|
||||
#
|
||||
#
|
||||
# Test for key_modifier, where alpha key is used as a 'key_modifier'
|
||||
# (grease pencil holding 'D' for example).
|
||||
|
||||
for item_container in items_all:
|
||||
item, kmi_found, kmi_exist = item_container
|
||||
if kmi_found is None or kmi_exist:
|
||||
continue
|
||||
|
||||
kmi_found_type = kmi_found.type
|
||||
if kmi_found_type in {
|
||||
'LEFTMOUSE',
|
||||
'RIGHTMOUSE',
|
||||
'MIDDLEMOUSE',
|
||||
'BUTTON4MOUSE',
|
||||
'BUTTON5MOUSE',
|
||||
'BUTTON6MOUSE',
|
||||
'BUTTON7MOUSE',
|
||||
}:
|
||||
kmi_found_type = kmi_found.key_modifier
|
||||
# excludes 'NONE'
|
||||
if len(kmi_found_type) == 1:
|
||||
kmi_args = {"type": kmi_found_type, **modifier_keywords_from_item(kmi_found)}
|
||||
del kmi_args["key_modifier"]
|
||||
if kmi_unique_or_pass(kmi_args):
|
||||
kmi = keymap.keymap_items.new(idname="wm.tool_set_by_id", value='PRESS', **kmi_args)
|
||||
kmi.properties.name = item.idname
|
||||
item_container[2] = kmi
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Assign A-Z to Keys
|
||||
#
|
||||
# When the keys are free.
|
||||
|
||||
if use_auto_keymap_alpha:
|
||||
# Map all unmapped keys to numbers,
|
||||
# while this is a bit strange it means users will not confuse regular key bindings to ordered bindings.
|
||||
|
||||
# First map A-Z.
|
||||
kmi_type_alpha_char = [chr(i) for i in range(65, 91)]
|
||||
kmi_type_alpha_args = {c: {"type": c} for c in kmi_type_alpha_char}
|
||||
kmi_type_alpha_args_tuple = {c: dict_as_tuple(kmi_type_alpha_args[c]) for c in kmi_type_alpha_char}
|
||||
for item_container in items_all:
|
||||
item, kmi_found, kmi_exist = item_container
|
||||
if kmi_exist:
|
||||
continue
|
||||
kmi_type = item.label[0].upper()
|
||||
kmi_tuple = kmi_type_alpha_args_tuple.get(kmi_type)
|
||||
if kmi_tuple and kmi_tuple not in kmi_unique_args:
|
||||
kmi_unique_args.add(kmi_tuple)
|
||||
kmi = keymap.keymap_items.new(
|
||||
idname="wm.tool_set_by_id",
|
||||
value='PRESS',
|
||||
**kmi_type_alpha_args[kmi_type],
|
||||
)
|
||||
kmi.properties.name = item.idname
|
||||
item_container[2] = kmi
|
||||
del kmi_type_alpha_char, kmi_type_alpha_args, kmi_type_alpha_args_tuple
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Assign Numbers to Keys
|
||||
|
||||
if use_auto_keymap_num:
|
||||
# Free events (last used first).
|
||||
kmi_type_auto = ('ONE', 'TWO', 'THREE', 'FOUR', 'FIVE', 'SIX', 'SEVEN', 'EIGHT', 'NINE', 'ZERO')
|
||||
# Map both numbers and num-pad.
|
||||
kmi_type_dupe = {
|
||||
'ONE': 'NUMPAD_1',
|
||||
'TWO': 'NUMPAD_2',
|
||||
'THREE': 'NUMPAD_3',
|
||||
'FOUR': 'NUMPAD_4',
|
||||
'FIVE': 'NUMPAD_5',
|
||||
'SIX': 'NUMPAD_6',
|
||||
'SEVEN': 'NUMPAD_7',
|
||||
'EIGHT': 'NUMPAD_8',
|
||||
'NINE': 'NUMPAD_9',
|
||||
'ZERO': 'NUMPAD_0',
|
||||
}
|
||||
|
||||
def iter_free_events():
|
||||
for mod in ({}, {"shift": True}, {"ctrl": True}, {"alt": True}):
|
||||
for e in kmi_type_auto:
|
||||
yield (e, mod)
|
||||
|
||||
iter_events = iter(iter_free_events())
|
||||
|
||||
for item_container in items_all:
|
||||
item, kmi_found, kmi_exist = item_container
|
||||
if kmi_exist:
|
||||
continue
|
||||
kmi_args = None
|
||||
while True:
|
||||
key, mod = next(iter_events, (None, None))
|
||||
if key is None:
|
||||
break
|
||||
kmi_args = {"type": key, **mod}
|
||||
kmi_tuple = dict_as_tuple(kmi_args)
|
||||
if kmi_tuple in kmi_unique_args:
|
||||
kmi_args = None
|
||||
else:
|
||||
break
|
||||
|
||||
if kmi_args is not None:
|
||||
kmi = keymap.keymap_items.new(idname="wm.tool_set_by_id", value='PRESS', **kmi_args)
|
||||
kmi.properties.name = item.idname
|
||||
item_container[2] = kmi
|
||||
kmi_unique_args.add(kmi_tuple)
|
||||
|
||||
key = kmi_type_dupe.get(kmi_args["type"])
|
||||
if key is not None:
|
||||
kmi_args["type"] = key
|
||||
kmi_tuple = dict_as_tuple(kmi_args)
|
||||
if kmi_tuple not in kmi_unique_args:
|
||||
kmi = keymap.keymap_items.new(idname="wm.tool_set_by_id", value='PRESS', **kmi_args)
|
||||
kmi.properties.name = item.idname
|
||||
kmi_unique_args.add(kmi_tuple)
|
||||
|
||||
# ---------------------
|
||||
# End Keymap Generation
|
||||
|
||||
if use_hack_properties:
|
||||
keymap.keymap_items.remove(kmi_hack)
|
||||
keymap.keymap_items.remove(kmi_hack_brush_select)
|
||||
|
||||
# Keep last so we can try add a key without any modifiers
|
||||
# in the case this toolbar was activated with modifiers.
|
||||
if use_tap_reset:
|
||||
if len(kmi_toolbar_args_type_only) == len(kmi_toolbar_args):
|
||||
kmi_toolbar_args_available = kmi_toolbar_args
|
||||
else:
|
||||
# We have modifiers, see if we have a free key w/o modifiers.
|
||||
kmi_toolbar_tuple = dict_as_tuple(kmi_toolbar_args_type_only)
|
||||
if kmi_toolbar_tuple not in kmi_unique_args:
|
||||
kmi_toolbar_args_available = kmi_toolbar_args_type_only
|
||||
kmi_unique_args.add(kmi_toolbar_tuple)
|
||||
else:
|
||||
kmi_toolbar_args_available = kmi_toolbar_args
|
||||
del kmi_toolbar_tuple
|
||||
|
||||
kmi = keymap.keymap_items.new(
|
||||
"wm.tool_set_by_id",
|
||||
value='DOUBLE_CLICK',
|
||||
**kmi_toolbar_args_available,
|
||||
)
|
||||
kmi.properties.name = tap_reset_tool
|
||||
|
||||
if use_release_confirm and (kmi_toolbar_type is not None):
|
||||
kmi = keymap.keymap_items.new(
|
||||
"ui.button_execute",
|
||||
type=kmi_toolbar_type,
|
||||
value='RELEASE',
|
||||
any=True,
|
||||
)
|
||||
kmi.properties.skip_depressed = True
|
||||
|
||||
wm.keyconfigs.update()
|
||||
return keymap
|
||||
229
scripts/modules/bl_keymap_utils/keymap_hierarchy.py
Normal file
229
scripts/modules/bl_keymap_utils/keymap_hierarchy.py
Normal file
@@ -0,0 +1,229 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"generate",
|
||||
)
|
||||
|
||||
|
||||
def _km_expand_from_toolsystem(space_type, context_mode):
|
||||
def _fn():
|
||||
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
|
||||
for cls in ToolSelectPanelHelper.__subclasses__():
|
||||
if cls.bl_space_type == space_type:
|
||||
return cls.keymap_ui_hierarchy(context_mode)
|
||||
raise Exception("keymap not found")
|
||||
return _fn
|
||||
|
||||
|
||||
def _km_hierarchy_iter_recursive(items):
|
||||
for sub in items:
|
||||
if callable(sub):
|
||||
yield from sub()
|
||||
else:
|
||||
yield (*sub[:3], list(_km_hierarchy_iter_recursive(sub[3])))
|
||||
|
||||
|
||||
def generate():
|
||||
return list(_km_hierarchy_iter_recursive(_km_hierarchy))
|
||||
|
||||
|
||||
# bpy.type.KeyMap: (km.name, km.space_type, km.region_type, [...])
|
||||
|
||||
# ('Script', 'EMPTY', 'WINDOW', []),
|
||||
|
||||
|
||||
# Access via 'km_hierarchy'.
|
||||
_km_hierarchy = [
|
||||
('Window', 'EMPTY', 'WINDOW', []), # file save, window change, exit
|
||||
('Screen', 'EMPTY', 'WINDOW', [ # full screen, undo, screenshot
|
||||
('Screen Editing', 'EMPTY', 'WINDOW', []), # re-sizing, action corners
|
||||
('Region Context Menu', 'EMPTY', 'WINDOW', []), # header/footer/navigation_bar stuff (per region)
|
||||
]),
|
||||
|
||||
('View2D', 'EMPTY', 'WINDOW', []), # view 2d navigation (per region)
|
||||
('View2D Buttons List', 'EMPTY', 'WINDOW', []), # view 2d with buttons navigation
|
||||
|
||||
('User Interface', 'EMPTY', 'WINDOW', []),
|
||||
|
||||
('3D View', 'VIEW_3D', 'WINDOW', [ # view 3d navigation and generic stuff (select, transform)
|
||||
('Object Mode', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'OBJECT'),
|
||||
]),
|
||||
('Mesh', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_MESH'),
|
||||
]),
|
||||
('Curve', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_CURVE'),
|
||||
]),
|
||||
('Curves', 'EMPTY', 'WINDOW', []),
|
||||
('Armature', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_ARMATURE'),
|
||||
]),
|
||||
('Metaball', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_METABALL'),
|
||||
]),
|
||||
('Lattice', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_LATTICE'),
|
||||
]),
|
||||
('Font', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'EDIT_TEXT'),
|
||||
]),
|
||||
|
||||
('Pose', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'POSE'),
|
||||
]),
|
||||
|
||||
('Vertex Paint', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'PAINT_VERTEX'),
|
||||
]),
|
||||
('Weight Paint', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'PAINT_WEIGHT'),
|
||||
]),
|
||||
('Paint Vertex Selection (Weight, Vertex)', 'EMPTY', 'WINDOW', []),
|
||||
('Paint Face Mask (Weight, Vertex, Texture)', 'EMPTY', 'WINDOW', []),
|
||||
# image and view3d
|
||||
('Image Paint', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'PAINT_TEXTURE'),
|
||||
]),
|
||||
('Sculpt', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'SCULPT'),
|
||||
]),
|
||||
|
||||
('Sculpt Curves', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'CURVES_SCULPT'),
|
||||
]),
|
||||
|
||||
('Particle', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', 'PARTICLE'),
|
||||
]),
|
||||
|
||||
('Knife Tool Modal Map', 'EMPTY', 'WINDOW', []),
|
||||
('Custom Normals Modal Map', 'EMPTY', 'WINDOW', []),
|
||||
('Bevel Modal Map', 'EMPTY', 'WINDOW', []),
|
||||
('Paint Stroke Modal', 'EMPTY', 'WINDOW', []),
|
||||
('Sculpt Expand Modal', 'EMPTY', 'WINDOW', []),
|
||||
('Paint Curve', 'EMPTY', 'WINDOW', []),
|
||||
('Curve Pen Modal Map', 'EMPTY', 'WINDOW', []),
|
||||
|
||||
('Object Non-modal', 'EMPTY', 'WINDOW', []), # mode change
|
||||
|
||||
('View3D Placement Modal', 'EMPTY', 'WINDOW', []),
|
||||
('View3D Walk Modal', 'EMPTY', 'WINDOW', []),
|
||||
('View3D Fly Modal', 'EMPTY', 'WINDOW', []),
|
||||
('View3D Rotate Modal', 'EMPTY', 'WINDOW', []),
|
||||
('View3D Move Modal', 'EMPTY', 'WINDOW', []),
|
||||
('View3D Zoom Modal', 'EMPTY', 'WINDOW', []),
|
||||
('View3D Dolly Modal', 'EMPTY', 'WINDOW', []),
|
||||
|
||||
# toolbar and properties
|
||||
('3D View Generic', 'VIEW_3D', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('VIEW_3D', None),
|
||||
]),
|
||||
]),
|
||||
|
||||
('Graph Editor', 'GRAPH_EDITOR', 'WINDOW', [
|
||||
('Graph Editor Generic', 'GRAPH_EDITOR', 'WINDOW', []),
|
||||
]),
|
||||
('Dopesheet', 'DOPESHEET_EDITOR', 'WINDOW', [
|
||||
('Dopesheet Generic', 'DOPESHEET_EDITOR', 'WINDOW', []),
|
||||
]),
|
||||
('NLA Editor', 'NLA_EDITOR', 'WINDOW', [
|
||||
('NLA Channels', 'NLA_EDITOR', 'WINDOW', []),
|
||||
('NLA Generic', 'NLA_EDITOR', 'WINDOW', []),
|
||||
]),
|
||||
('Timeline', 'TIMELINE', 'WINDOW', []),
|
||||
|
||||
('Image', 'IMAGE_EDITOR', 'WINDOW', [
|
||||
# Image (reverse order, UVEdit before Image).
|
||||
('UV Editor', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('IMAGE_EDITOR', 'UV'),
|
||||
]),
|
||||
('UV Sculpt', 'EMPTY', 'WINDOW', []),
|
||||
# Image and view3d.
|
||||
('Image Paint', 'EMPTY', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('IMAGE_EDITOR', 'PAINT'),
|
||||
]),
|
||||
('Image View', 'IMAGE_EDITOR', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('IMAGE_EDITOR', 'VIEW'),
|
||||
]),
|
||||
('Image Generic', 'IMAGE_EDITOR', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('IMAGE_EDITOR', None),
|
||||
]),
|
||||
]),
|
||||
|
||||
('Outliner', 'OUTLINER', 'WINDOW', []),
|
||||
|
||||
('Node Editor', 'NODE_EDITOR', 'WINDOW', [
|
||||
('Node Generic', 'NODE_EDITOR', 'WINDOW', []),
|
||||
]),
|
||||
('SequencerCommon', 'SEQUENCE_EDITOR', 'WINDOW', [
|
||||
('Sequencer', 'SEQUENCE_EDITOR', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('SEQUENCE_EDITOR', 'SEQUENCER'),
|
||||
]),
|
||||
('SequencerPreview', 'SEQUENCE_EDITOR', 'WINDOW', [
|
||||
_km_expand_from_toolsystem('SEQUENCE_EDITOR', 'PREVIEW'),
|
||||
]),
|
||||
]),
|
||||
|
||||
('File Browser', 'FILE_BROWSER', 'WINDOW', [
|
||||
('File Browser Main', 'FILE_BROWSER', 'WINDOW', []),
|
||||
('File Browser Buttons', 'FILE_BROWSER', 'WINDOW', []),
|
||||
]),
|
||||
|
||||
('Info', 'INFO', 'WINDOW', []),
|
||||
|
||||
('Property Editor', 'PROPERTIES', 'WINDOW', []), # align context menu
|
||||
|
||||
('Text', 'TEXT_EDITOR', 'WINDOW', [
|
||||
('Text Generic', 'TEXT_EDITOR', 'WINDOW', []),
|
||||
]),
|
||||
('Console', 'CONSOLE', 'WINDOW', []),
|
||||
('Clip', 'CLIP_EDITOR', 'WINDOW', [
|
||||
('Clip Editor', 'CLIP_EDITOR', 'WINDOW', []),
|
||||
('Clip Graph Editor', 'CLIP_EDITOR', 'WINDOW', []),
|
||||
('Clip Dopesheet Editor', 'CLIP_EDITOR', 'WINDOW', []),
|
||||
]),
|
||||
|
||||
('Grease Pencil', 'EMPTY', 'WINDOW', [ # grease pencil stuff (per region)
|
||||
('Grease Pencil Stroke Curve Edit Mode', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Edit Mode', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Paint (Draw brush)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Paint (Fill)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Paint (Erase)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Paint (Tint)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Paint Mode', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt Mode', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt (Smooth)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt (Thickness)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt (Strength)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt (Grab)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt (Push)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt (Twist)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt (Pinch)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt (Randomize)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Sculpt (Clone)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Weight Mode', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Weight (Draw)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Vertex Mode', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Vertex (Draw)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Vertex (Blur)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Vertex (Average)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Vertex (Smear)', 'EMPTY', 'WINDOW', []),
|
||||
('Grease Pencil Stroke Vertex (Replace)', 'EMPTY', 'WINDOW', []),
|
||||
]),
|
||||
('Mask Editing', 'EMPTY', 'WINDOW', []),
|
||||
('Frames', 'EMPTY', 'WINDOW', []), # frame navigation (per region)
|
||||
('Markers', 'EMPTY', 'WINDOW', []), # markers (per region)
|
||||
('Animation', 'EMPTY', 'WINDOW', []), # frame change on click, preview range (per region)
|
||||
('Animation Channels', 'EMPTY', 'WINDOW', []),
|
||||
|
||||
('View3D Gesture Circle', 'EMPTY', 'WINDOW', []),
|
||||
('Gesture Straight Line', 'EMPTY', 'WINDOW', []),
|
||||
('Gesture Zoom Border', 'EMPTY', 'WINDOW', []),
|
||||
('Gesture Box', 'EMPTY', 'WINDOW', []),
|
||||
|
||||
('Standard Modal Map', 'EMPTY', 'WINDOW', []),
|
||||
('Transform Modal Map', 'EMPTY', 'WINDOW', []),
|
||||
('Eyedropper Modal Map', 'EMPTY', 'WINDOW', []),
|
||||
('Eyedropper ColorRamp PointSampling Map', 'EMPTY', 'WINDOW', []),
|
||||
]
|
||||
48
scripts/modules/bl_keymap_utils/platform_helpers.py
Normal file
48
scripts/modules/bl_keymap_utils/platform_helpers.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
def keyconfig_data_oskey_from_ctrl(keyconfig_data_src, *, filter_fn=None):
|
||||
keyconfig_data_dst = []
|
||||
for km_name, km_parms, km_items_data_src in keyconfig_data_src:
|
||||
km_items_data_dst = km_items_data_src.copy()
|
||||
items_dst = []
|
||||
km_items_data_dst["items"] = items_dst
|
||||
for item_src in km_items_data_src["items"]:
|
||||
item_op, item_event, item_prop = item_src
|
||||
if "ctrl" in item_event:
|
||||
if filter_fn is None or filter_fn(item_event):
|
||||
item_event = item_event.copy()
|
||||
item_event["oskey"] = item_event["ctrl"]
|
||||
del item_event["ctrl"]
|
||||
items_dst.append((item_op, item_event, item_prop))
|
||||
items_dst.append(item_src)
|
||||
keyconfig_data_dst.append((km_name, km_parms, km_items_data_dst))
|
||||
return keyconfig_data_dst
|
||||
|
||||
|
||||
def keyconfig_data_oskey_from_ctrl_for_macos(keyconfig_data_src):
|
||||
"""Use for apple since Cmd is typically used in-place of Ctrl."""
|
||||
def filter_fn(item_event):
|
||||
if item_event.get("ctrl"):
|
||||
event_type = item_event["type"]
|
||||
# Ctrl-{Key}
|
||||
if (event_type in {
|
||||
'H',
|
||||
'M',
|
||||
'SPACE',
|
||||
'W',
|
||||
'ACCENT_GRAVE',
|
||||
'PERIOD',
|
||||
'TAB',
|
||||
}):
|
||||
if (not item_event.get("alt")) and (not item_event.get("shift")):
|
||||
return False
|
||||
# Ctrl-Alt-{Key}
|
||||
if (event_type in {
|
||||
'Q',
|
||||
}):
|
||||
if item_event.get("alt") and (not item_event.get("shift")):
|
||||
return False
|
||||
return True
|
||||
|
||||
return keyconfig_data_oskey_from_ctrl(keyconfig_data_src, filter_fn=filter_fn)
|
||||
63
scripts/modules/bl_keymap_utils/versioning.py
Normal file
63
scripts/modules/bl_keymap_utils/versioning.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Update Blender version this key-map was written in:
|
||||
#
|
||||
# When the version is `(0, 0, 0)`, the key-map being loaded didn't contain any versioning information.
|
||||
# This will older than `(2, 92, 0)`.
|
||||
|
||||
def keyconfig_update(keyconfig_data, keyconfig_version):
|
||||
from bpy.app import version_file as blender_version
|
||||
if keyconfig_version >= blender_version:
|
||||
return keyconfig_data
|
||||
|
||||
# Version the key-map.
|
||||
import copy
|
||||
# Only copy once.
|
||||
has_copy = False
|
||||
|
||||
# Default repeat to false.
|
||||
if keyconfig_version <= (2, 92, 0):
|
||||
if not has_copy:
|
||||
keyconfig_data = copy.deepcopy(keyconfig_data)
|
||||
has_copy = True
|
||||
|
||||
for _km_name, _km_parms, km_items_data in keyconfig_data:
|
||||
for (_item_op, item_event, _item_prop) in km_items_data["items"]:
|
||||
if item_event.get("value") == 'PRESS':
|
||||
# Unfortunately we don't know the 'map_type' at this point.
|
||||
# Setting repeat true on other kinds of events is harmless.
|
||||
item_event["repeat"] = True
|
||||
|
||||
if keyconfig_version <= (3, 2, 5):
|
||||
if not has_copy:
|
||||
keyconfig_data = copy.deepcopy(keyconfig_data)
|
||||
has_copy = True
|
||||
|
||||
for _km_name, _km_parms, km_items_data in keyconfig_data:
|
||||
for (_item_op, item_event, _item_prop) in km_items_data["items"]:
|
||||
if ty_new := {
|
||||
'EVT_TWEAK_L': 'LEFTMOUSE',
|
||||
'EVT_TWEAK_M': 'MIDDLEMOUSE',
|
||||
'EVT_TWEAK_R': 'RIGHTMOUSE',
|
||||
}.get(item_event.get("type")):
|
||||
item_event["type"] = ty_new
|
||||
if (value := item_event["value"]) != 'ANY':
|
||||
item_event["direction"] = value
|
||||
item_event["value"] = 'CLICK_DRAG'
|
||||
|
||||
if keyconfig_version <= (3, 2, 6):
|
||||
if not has_copy:
|
||||
keyconfig_data = copy.deepcopy(keyconfig_data)
|
||||
has_copy = True
|
||||
|
||||
for _km_name, _km_parms, km_items_data in keyconfig_data:
|
||||
for (_item_op, item_event, _item_prop) in km_items_data["items"]:
|
||||
if ty_new := {
|
||||
'NDOF_BUTTON_ESC': 'ESC',
|
||||
'NDOF_BUTTON_ALT': 'LEFT_ALT',
|
||||
'NDOF_BUTTON_SHIFT': 'LEFT_SHIFT',
|
||||
'NDOF_BUTTON_CTRL': 'LEFT_CTRL',
|
||||
}.get(item_event.get("type")):
|
||||
item_event["type"] = ty_new
|
||||
|
||||
return keyconfig_data
|
||||
531
scripts/modules/bl_previews_utils/bl_previews_render.py
Normal file
531
scripts/modules/bl_previews_utils/bl_previews_render.py
Normal file
@@ -0,0 +1,531 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Populate a template file (POT format currently) from Blender RNA/py/C data.
|
||||
# Note: This script is meant to be used from inside Blender!
|
||||
|
||||
import os
|
||||
|
||||
import bpy
|
||||
from mathutils import (
|
||||
Euler,
|
||||
Matrix,
|
||||
Vector,
|
||||
)
|
||||
|
||||
|
||||
OBJECT_TYPES_RENDER = {'MESH', 'CURVE', 'SURFACE', 'META', 'FONT'}
|
||||
|
||||
|
||||
def ids_nolib(bids):
|
||||
return (bid for bid in bids if not bid.library)
|
||||
|
||||
|
||||
def ids_nolib_with_preview(bids):
|
||||
return (bid for bid in bids if (not bid.library and bid.preview))
|
||||
|
||||
|
||||
def rna_backup_gen(data, include_props=None, exclude_props=None, root=()):
|
||||
# only writable properties...
|
||||
for p in data.bl_rna.properties:
|
||||
pid = p.identifier
|
||||
if pid == "rna_type" or pid == "original":
|
||||
continue
|
||||
path = root + (pid,)
|
||||
if include_props is not None and path not in include_props:
|
||||
continue
|
||||
if exclude_props is not None and path in exclude_props:
|
||||
continue
|
||||
val = getattr(data, pid)
|
||||
if val is not None and p.type == 'POINTER':
|
||||
# recurse!
|
||||
yield from rna_backup_gen(val, include_props, exclude_props, root=path)
|
||||
elif data.is_property_readonly(pid):
|
||||
continue
|
||||
else:
|
||||
yield path, val
|
||||
|
||||
|
||||
def rna_backup_restore(data, backup):
|
||||
for path, val in backup:
|
||||
dt = data
|
||||
for pid in path[:-1]:
|
||||
dt = getattr(dt, pid)
|
||||
setattr(dt, path[-1], val)
|
||||
|
||||
|
||||
def do_previews(do_objects, do_collections, do_scenes, do_data_intern):
|
||||
import collections
|
||||
|
||||
# Helpers.
|
||||
RenderContext = collections.namedtuple("RenderContext", (
|
||||
"scene", "world", "camera", "light", "camera_data", "light_data", "image", # All those are names!
|
||||
"backup_scene", "backup_world", "backup_camera", "backup_light", "backup_camera_data", "backup_light_data",
|
||||
))
|
||||
|
||||
RENDER_PREVIEW_SIZE = bpy.app.render_preview_size
|
||||
|
||||
def render_context_create(engine, objects_ignored):
|
||||
if engine == '__SCENE':
|
||||
backup_scene, backup_world, backup_camera, backup_light, backup_camera_data, backup_light_data = [()] * 6
|
||||
scene = bpy.context.window.scene
|
||||
exclude_props = {('world',), ('camera',), ('tool_settings',), ('preview',)}
|
||||
backup_scene = tuple(rna_backup_gen(scene, exclude_props=exclude_props))
|
||||
world = scene.world
|
||||
camera = scene.camera
|
||||
if camera:
|
||||
camera_data = camera.data
|
||||
else:
|
||||
backup_camera, backup_camera_data = [None] * 2
|
||||
camera_data = bpy.data.cameras.new("TEMP_preview_render_camera")
|
||||
camera = bpy.data.objects.new("TEMP_preview_render_camera", camera_data)
|
||||
camera.rotation_euler = Euler((1.1635528802871704, 0.0, 0.7853981852531433), 'XYZ') # (66.67, 0, 45)
|
||||
scene.camera = camera
|
||||
scene.collection.objects.link(camera)
|
||||
# TODO: add light if none found in scene?
|
||||
light = None
|
||||
light_data = None
|
||||
else:
|
||||
backup_scene, backup_world, backup_camera, backup_light, backup_camera_data, backup_light_data = [None] * 6
|
||||
|
||||
scene = bpy.data.scenes.new("TEMP_preview_render_scene")
|
||||
world = bpy.data.worlds.new("TEMP_preview_render_world")
|
||||
camera_data = bpy.data.cameras.new("TEMP_preview_render_camera")
|
||||
camera = bpy.data.objects.new("TEMP_preview_render_camera", camera_data)
|
||||
light_data = bpy.data.lights.new("TEMP_preview_render_light", 'SPOT')
|
||||
light = bpy.data.objects.new("TEMP_preview_render_light", light_data)
|
||||
|
||||
objects_ignored.add((camera.name, light.name))
|
||||
|
||||
scene.world = world
|
||||
|
||||
camera.rotation_euler = Euler((1.1635528802871704, 0.0, 0.7853981852531433), 'XYZ') # (66.67, 0, 45)
|
||||
scene.camera = camera
|
||||
scene.collection.objects.link(camera)
|
||||
|
||||
light.rotation_euler = Euler((0.7853981852531433, 0.0, 1.7453292608261108), 'XYZ') # (45, 0, 100)
|
||||
light_data.falloff_type = 'CONSTANT'
|
||||
light_data.spot_size = 1.0471975803375244 # 60
|
||||
scene.collection.objects.link(light)
|
||||
|
||||
scene.render.engine = 'CYCLES'
|
||||
scene.render.film_transparent = True
|
||||
# TODO: define Cycles world?
|
||||
|
||||
scene.render.image_settings.file_format = 'PNG'
|
||||
scene.render.image_settings.color_depth = '8'
|
||||
scene.render.image_settings.color_mode = 'RGBA'
|
||||
scene.render.image_settings.compression = 25
|
||||
scene.render.resolution_x = RENDER_PREVIEW_SIZE
|
||||
scene.render.resolution_y = RENDER_PREVIEW_SIZE
|
||||
scene.render.resolution_percentage = 100
|
||||
scene.render.filepath = os.path.join(bpy.app.tempdir, 'TEMP_preview_render.png')
|
||||
scene.render.use_overwrite = True
|
||||
scene.render.use_stamp = False
|
||||
scene.render.threads_mode = 'AUTO'
|
||||
|
||||
image = bpy.data.images.new("TEMP_render_image", RENDER_PREVIEW_SIZE, RENDER_PREVIEW_SIZE, alpha=True)
|
||||
image.source = 'FILE'
|
||||
image.filepath = scene.render.filepath
|
||||
|
||||
return RenderContext(
|
||||
scene.name, world.name if world else None, camera.name, light.name if light else None,
|
||||
camera_data.name, light_data.name if light_data else None, image.name,
|
||||
backup_scene, backup_world, backup_camera, backup_light, backup_camera_data, backup_light_data,
|
||||
)
|
||||
|
||||
def render_context_delete(render_context):
|
||||
# We use try/except blocks here to avoid crash, too much things can go wrong, and we want to leave the current
|
||||
# .blend as clean as possible!
|
||||
success = True
|
||||
|
||||
scene = bpy.data.scenes[render_context.scene, None]
|
||||
try:
|
||||
if render_context.backup_scene is None:
|
||||
scene.world = None
|
||||
scene.camera = None
|
||||
if render_context.camera:
|
||||
scene.collection.objects.unlink(bpy.data.objects[render_context.camera, None])
|
||||
if render_context.light:
|
||||
scene.collection.objects.unlink(bpy.data.objects[render_context.light, None])
|
||||
bpy.data.scenes.remove(scene, do_unlink=True)
|
||||
scene = None
|
||||
else:
|
||||
rna_backup_restore(scene, render_context.backup_scene)
|
||||
except Exception as e:
|
||||
print("ERROR:", e)
|
||||
success = False
|
||||
|
||||
if render_context.world is not None:
|
||||
try:
|
||||
world = bpy.data.worlds[render_context.world, None]
|
||||
if render_context.backup_world is None:
|
||||
if scene is not None:
|
||||
scene.world = None
|
||||
world.user_clear()
|
||||
bpy.data.worlds.remove(world)
|
||||
else:
|
||||
rna_backup_restore(world, render_context.backup_world)
|
||||
except Exception as e:
|
||||
print("ERROR:", e)
|
||||
success = False
|
||||
|
||||
if render_context.camera:
|
||||
try:
|
||||
camera = bpy.data.objects[render_context.camera, None]
|
||||
if render_context.backup_camera is None:
|
||||
if scene is not None:
|
||||
scene.camera = None
|
||||
scene.collection.objects.unlink(camera)
|
||||
camera.user_clear()
|
||||
bpy.data.objects.remove(camera)
|
||||
bpy.data.cameras.remove(bpy.data.cameras[render_context.camera_data, None])
|
||||
else:
|
||||
rna_backup_restore(camera, render_context.backup_camera)
|
||||
rna_backup_restore(bpy.data.cameras[render_context.camera_data, None],
|
||||
render_context.backup_camera_data)
|
||||
except Exception as e:
|
||||
print("ERROR:", e)
|
||||
success = False
|
||||
|
||||
if render_context.light:
|
||||
try:
|
||||
light = bpy.data.objects[render_context.light, None]
|
||||
if render_context.backup_light is None:
|
||||
if scene is not None:
|
||||
scene.collection.objects.unlink(light)
|
||||
light.user_clear()
|
||||
bpy.data.objects.remove(light)
|
||||
bpy.data.lights.remove(bpy.data.lights[render_context.light_data, None])
|
||||
else:
|
||||
rna_backup_restore(light, render_context.backup_light)
|
||||
rna_backup_restore(bpy.data.lights[render_context.light_data,
|
||||
None], render_context.backup_light_data)
|
||||
except Exception as e:
|
||||
print("ERROR:", e)
|
||||
success = False
|
||||
|
||||
try:
|
||||
image = bpy.data.images[render_context.image, None]
|
||||
image.user_clear()
|
||||
bpy.data.images.remove(image)
|
||||
except Exception as e:
|
||||
print("ERROR:", e)
|
||||
success = False
|
||||
|
||||
return success
|
||||
|
||||
def object_bbox_merge(bbox, ob, ob_space, offset_matrix):
|
||||
# Take collections instances into account (including linked one in this case).
|
||||
if ob.type == 'EMPTY' and ob.instance_type == 'COLLECTION':
|
||||
grp_objects = tuple((ob.name, ob.library.filepath if ob.library else None)
|
||||
for ob in ob.instance_collection.all_objects)
|
||||
if (len(grp_objects) == 0):
|
||||
ob_bbox = ob.bound_box
|
||||
else:
|
||||
coords = objects_bbox_calc(ob_space, grp_objects,
|
||||
Matrix.Translation(ob.instance_collection.instance_offset).inverted())
|
||||
ob_bbox = ((coords[0], coords[1], coords[2]), (coords[21], coords[22], coords[23]))
|
||||
elif ob.bound_box:
|
||||
ob_bbox = ob.bound_box
|
||||
else:
|
||||
ob_bbox = ((-ob.scale.x, -ob.scale.y, -ob.scale.z), (ob.scale.x, ob.scale.y, ob.scale.z))
|
||||
|
||||
for v in ob_bbox:
|
||||
v = offset_matrix @ Vector(v) if offset_matrix is not None else Vector(v)
|
||||
v = ob_space.matrix_world.inverted() @ ob.matrix_world @ v
|
||||
if bbox[0].x > v.x:
|
||||
bbox[0].x = v.x
|
||||
if bbox[0].y > v.y:
|
||||
bbox[0].y = v.y
|
||||
if bbox[0].z > v.z:
|
||||
bbox[0].z = v.z
|
||||
if bbox[1].x < v.x:
|
||||
bbox[1].x = v.x
|
||||
if bbox[1].y < v.y:
|
||||
bbox[1].y = v.y
|
||||
if bbox[1].z < v.z:
|
||||
bbox[1].z = v.z
|
||||
|
||||
def objects_bbox_calc(camera, objects, offset_matrix):
|
||||
bbox = (Vector((1e24, 1e24, 1e24)), Vector((-1e24, -1e24, -1e24)))
|
||||
for obname, libpath in objects:
|
||||
ob = bpy.data.objects[obname, libpath]
|
||||
object_bbox_merge(bbox, ob, camera, offset_matrix)
|
||||
# Our bbox has been generated in camera local space, bring it back in world one
|
||||
bbox[0][:] = camera.matrix_world @ bbox[0]
|
||||
bbox[1][:] = camera.matrix_world @ bbox[1]
|
||||
cos = (
|
||||
bbox[0].x, bbox[0].y, bbox[0].z,
|
||||
bbox[0].x, bbox[0].y, bbox[1].z,
|
||||
bbox[0].x, bbox[1].y, bbox[0].z,
|
||||
bbox[0].x, bbox[1].y, bbox[1].z,
|
||||
bbox[1].x, bbox[0].y, bbox[0].z,
|
||||
bbox[1].x, bbox[0].y, bbox[1].z,
|
||||
bbox[1].x, bbox[1].y, bbox[0].z,
|
||||
bbox[1].x, bbox[1].y, bbox[1].z,
|
||||
)
|
||||
return cos
|
||||
|
||||
def preview_render_do(render_context, item_container, item_name, objects, offset_matrix=None):
|
||||
# Unused.
|
||||
# scene = bpy.data.scenes[render_context.scene, None]
|
||||
if objects is not None:
|
||||
camera = bpy.data.objects[render_context.camera, None]
|
||||
light = bpy.data.objects[render_context.light, None] if render_context.light is not None else None
|
||||
cos = objects_bbox_calc(camera, objects, offset_matrix)
|
||||
depsgraph = bpy.context.evaluated_depsgraph_get()
|
||||
loc, _ortho_scale = camera.camera_fit_coords(depsgraph, cos)
|
||||
camera.location = loc
|
||||
# Set camera clipping accordingly to computed bbox.
|
||||
min_dist = 1e24
|
||||
max_dist = -1e24
|
||||
for co in zip(*(iter(cos),) * 3):
|
||||
dist = (Vector(co) - loc).length
|
||||
if dist < min_dist:
|
||||
min_dist = dist
|
||||
if dist > max_dist:
|
||||
max_dist = dist
|
||||
camera.data.clip_start = min_dist / 2
|
||||
camera.data.clip_end = max_dist * 2
|
||||
if light:
|
||||
loc, _ortho_scale = light.camera_fit_coords(depsgraph, cos)
|
||||
light.location = loc
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
bpy.ops.render.render(write_still=True)
|
||||
|
||||
image = bpy.data.images[render_context.image, None]
|
||||
item = getattr(bpy.data, item_container)[item_name, None]
|
||||
image.reload()
|
||||
preview = item.preview_ensure()
|
||||
preview.image_size = (RENDER_PREVIEW_SIZE, RENDER_PREVIEW_SIZE)
|
||||
preview.image_pixels_float[:] = image.pixels
|
||||
|
||||
# And now, main code!
|
||||
do_save = True
|
||||
|
||||
if do_data_intern:
|
||||
bpy.ops.wm.previews_clear(id_type={'SHADING'})
|
||||
bpy.ops.wm.previews_ensure()
|
||||
|
||||
render_contexts = {}
|
||||
|
||||
objects_ignored = set()
|
||||
collections_ignored = set()
|
||||
|
||||
prev_scenename = bpy.context.window.scene.name
|
||||
|
||||
if do_objects:
|
||||
prev_shown = {ob.name: ob.hide_render for ob in ids_nolib(bpy.data.objects)}
|
||||
for ob in ids_nolib(bpy.data.objects):
|
||||
if ob in objects_ignored:
|
||||
continue
|
||||
ob.hide_render = True
|
||||
for root in ids_nolib(bpy.data.objects):
|
||||
if root.name in objects_ignored:
|
||||
continue
|
||||
if root.type not in OBJECT_TYPES_RENDER:
|
||||
continue
|
||||
objects = ((root.name, None),)
|
||||
|
||||
render_context = render_contexts.get('CYCLES', None)
|
||||
if render_context is None:
|
||||
render_context = render_context_create('CYCLES', objects_ignored)
|
||||
render_contexts['CYCLES'] = render_context
|
||||
|
||||
scene = bpy.data.scenes[render_context.scene, None]
|
||||
bpy.context.window.scene = scene
|
||||
|
||||
for obname, libpath in objects:
|
||||
ob = bpy.data.objects[obname, libpath]
|
||||
if obname not in scene.objects:
|
||||
scene.collection.objects.link(ob)
|
||||
ob.hide_render = False
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
preview_render_do(render_context, 'objects', root.name, objects)
|
||||
|
||||
# XXX Hyper Super Uber Suspicious Hack!
|
||||
# Without this, on windows build, script excepts with following message:
|
||||
# Traceback (most recent call last):
|
||||
# File "<string>", line 1, in <module>
|
||||
# File "<string>", line 451, in <module>
|
||||
# File "<string>", line 443, in main
|
||||
# File "<string>", line 327, in do_previews
|
||||
# OverflowError: Python int too large to convert to C long
|
||||
# ... :(
|
||||
scene = bpy.data.scenes[render_context.scene, None]
|
||||
for obname, libpath in objects:
|
||||
ob = bpy.data.objects[obname, libpath]
|
||||
scene.collection.objects.unlink(ob)
|
||||
ob.hide_render = True
|
||||
|
||||
for ob in ids_nolib(bpy.data.objects):
|
||||
is_rendered = prev_shown.get(ob.name, ...)
|
||||
if is_rendered is not ...:
|
||||
ob.hide_render = is_rendered
|
||||
|
||||
if do_collections:
|
||||
for grp in ids_nolib(bpy.data.collections):
|
||||
if grp.name in collections_ignored:
|
||||
continue
|
||||
# Here too, we do want to keep linked objects members of local collection...
|
||||
objects = tuple((ob.name, ob.library.filepath if ob.library else None) for ob in grp.objects)
|
||||
|
||||
render_context = render_contexts.get('CYCLES', None)
|
||||
if render_context is None:
|
||||
render_context = render_context_create('CYCLES', objects_ignored)
|
||||
render_contexts['CYCLES'] = render_context
|
||||
|
||||
scene = bpy.data.scenes[render_context.scene, None]
|
||||
bpy.context.window.scene = scene
|
||||
|
||||
bpy.ops.object.collection_instance_add(collection=grp.name)
|
||||
grp_ob = next((
|
||||
ob for ob in scene.objects
|
||||
if ob.instance_collection and ob.instance_collection.name == grp.name
|
||||
))
|
||||
grp_obname = grp_ob.name
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
offset_matrix = Matrix.Translation(grp.instance_offset).inverted()
|
||||
|
||||
preview_render_do(render_context, 'collections', grp.name, objects, offset_matrix)
|
||||
|
||||
scene = bpy.data.scenes[render_context.scene, None]
|
||||
scene.collection.objects.unlink(bpy.data.objects[grp_obname, None])
|
||||
|
||||
bpy.context.window.scene = bpy.data.scenes[prev_scenename, None]
|
||||
for render_context in render_contexts.values():
|
||||
if not render_context_delete(render_context):
|
||||
do_save = False # Do not save file if something went wrong here, we could 'pollute' it with temp data...
|
||||
|
||||
if do_scenes:
|
||||
for scene in ids_nolib(bpy.data.scenes):
|
||||
has_camera = scene.camera is not None
|
||||
bpy.context.window.scene = scene
|
||||
render_context = render_context_create('__SCENE', objects_ignored)
|
||||
bpy.context.view_layer.update()
|
||||
|
||||
objects = None
|
||||
if not has_camera:
|
||||
# We had to add a temp camera, now we need to place it to see interesting objects!
|
||||
objects = tuple((ob.name, ob.library.filepath if ob.library else None) for ob in scene.objects
|
||||
if (not ob.hide_render) and (ob.type in OBJECT_TYPES_RENDER))
|
||||
|
||||
preview_render_do(render_context, 'scenes', scene.name, objects)
|
||||
|
||||
if not render_context_delete(render_context):
|
||||
do_save = False
|
||||
|
||||
bpy.context.window.scene = bpy.data.scenes[prev_scenename, None]
|
||||
if do_save:
|
||||
print("Saving %s..." % bpy.data.filepath)
|
||||
try:
|
||||
bpy.ops.wm.save_mainfile()
|
||||
except Exception as e:
|
||||
# Might fail in some odd cases, like e.g. in regression files we have glsl/ram_glsl.blend which
|
||||
# references an inexistent texture... Better not break in this case, just spit error to console.
|
||||
print("ERROR:", e)
|
||||
else:
|
||||
print("*NOT* Saving %s, because some error(s) happened while deleting temp render data..." % bpy.data.filepath)
|
||||
|
||||
|
||||
def do_clear_previews(do_objects, do_collections, do_scenes, do_data_intern):
|
||||
if do_data_intern:
|
||||
bpy.ops.wm.previews_clear(id_type={'SHADING'})
|
||||
|
||||
if do_objects:
|
||||
for ob in ids_nolib_with_preview(bpy.data.objects):
|
||||
ob.preview.image_size = (0, 0)
|
||||
|
||||
if do_collections:
|
||||
for grp in ids_nolib_with_preview(bpy.data.collections):
|
||||
grp.preview.image_size = (0, 0)
|
||||
|
||||
if do_scenes:
|
||||
for scene in ids_nolib_with_preview(bpy.data.scenes):
|
||||
scene.preview.image_size = (0, 0)
|
||||
|
||||
print("Saving %s..." % bpy.data.filepath)
|
||||
bpy.ops.wm.save_mainfile()
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
import bpy
|
||||
except ImportError:
|
||||
print("This script must run from inside blender")
|
||||
return
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
# Get rid of Blender args!
|
||||
argv = sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else []
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Use Blender to generate previews for currently open Blender file's items.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--clear',
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Clear previews instead of generating them.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--no_backups',
|
||||
default=False,
|
||||
action="store_true",
|
||||
help="Do not generate a backup .blend1 file when saving processed ones.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--no_scenes',
|
||||
default=True,
|
||||
action="store_false",
|
||||
help="Do not generate/clear previews for scene IDs.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--no_collections',
|
||||
default=True,
|
||||
action="store_false",
|
||||
help="Do not generate/clear previews for collection IDs.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--no_objects',
|
||||
default=True,
|
||||
action="store_false",
|
||||
help="Do not generate/clear previews for object IDs.",
|
||||
)
|
||||
parser.add_argument(
|
||||
'--no_data_intern',
|
||||
default=True,
|
||||
action="store_false",
|
||||
help="Do not generate/clear previews for mat/tex/image/etc. IDs (those handled by core Blender code).",
|
||||
)
|
||||
args = parser.parse_args(argv)
|
||||
|
||||
orig_save_version = bpy.context.preferences.filepaths.save_version
|
||||
if args.no_backups:
|
||||
bpy.context.preferences.filepaths.save_version = 0
|
||||
elif orig_save_version < 1:
|
||||
bpy.context.preferences.filepaths.save_version = 1
|
||||
|
||||
if args.clear:
|
||||
print("clear!")
|
||||
do_clear_previews(do_objects=args.no_objects, do_collections=args.no_collections, do_scenes=args.no_scenes,
|
||||
do_data_intern=args.no_data_intern)
|
||||
else:
|
||||
print("render!")
|
||||
do_previews(do_objects=args.no_objects, do_collections=args.no_collections, do_scenes=args.no_scenes,
|
||||
do_data_intern=args.no_data_intern)
|
||||
|
||||
# Not really necessary, but better be consistent.
|
||||
bpy.context.preferences.filepaths.save_version = orig_save_version
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print("\n\n *** Running %s *** \n" % __file__)
|
||||
print(" *** Blend file %s *** \n" % bpy.data.filepath)
|
||||
main()
|
||||
bpy.ops.wm.quit_blender()
|
||||
0
scripts/modules/bl_rna_utils/__init__.py
Normal file
0
scripts/modules/bl_rna_utils/__init__.py
Normal file
74
scripts/modules/bl_rna_utils/data_path.py
Normal file
74
scripts/modules/bl_rna_utils/data_path.py
Normal file
@@ -0,0 +1,74 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"property_definition_from_data_path",
|
||||
"decompose_data_path",
|
||||
)
|
||||
|
||||
|
||||
class _TokenizeDataPath:
|
||||
"""
|
||||
Class to split up tokens of a data-path.
|
||||
|
||||
Note that almost all access generates new objects with additional paths,
|
||||
with the exception of iteration which is the intended way to access the resulting data."""
|
||||
__slots__ = (
|
||||
"data_path",
|
||||
)
|
||||
|
||||
def __init__(self, attrs):
|
||||
self.data_path = attrs
|
||||
|
||||
def __getattr__(self, attr):
|
||||
return _TokenizeDataPath(self.data_path + ((".%s" % attr),))
|
||||
|
||||
def __getitem__(self, key):
|
||||
return _TokenizeDataPath(self.data_path + (("[%r]" % (key,)),))
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
value_str = ", ".join([
|
||||
val for val in (
|
||||
", ".join(repr(value) for value in args),
|
||||
", ".join(["%s=%r" % (key, value) for key, value in kw.items()]),
|
||||
) if val])
|
||||
return _TokenizeDataPath(self.data_path + ('(%s)' % value_str, ))
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.data_path)
|
||||
|
||||
|
||||
def decompose_data_path(data_path):
|
||||
"""
|
||||
Return the components of a data path split into a list.
|
||||
"""
|
||||
ns = {"base": _TokenizeDataPath(())}
|
||||
return list(eval("base" + data_path, ns, ns))
|
||||
|
||||
|
||||
def property_definition_from_data_path(base, data_path):
|
||||
"""
|
||||
Return an RNA property definition from an object and a data path.
|
||||
|
||||
In Blender this is often used with ``context`` as the base and a
|
||||
path that it references, for example ``.space_data.lock_camera``.
|
||||
"""
|
||||
data = decompose_data_path(data_path)
|
||||
while data and (not data[-1].startswith(".")):
|
||||
data.pop()
|
||||
|
||||
if (not data) or (not data[-1].startswith(".")) or (len(data) < 2):
|
||||
return None
|
||||
|
||||
data_path_head = "".join(data[:-1])
|
||||
data_path_tail = data[-1]
|
||||
|
||||
value_head = eval("base" + data_path_head)
|
||||
value_head_rna = getattr(value_head, "bl_rna", None)
|
||||
if value_head_rna is None:
|
||||
return None
|
||||
|
||||
value_tail = value_head.bl_rna.properties.get(data_path_tail[1:])
|
||||
if not value_tail:
|
||||
return None
|
||||
|
||||
return value_tail
|
||||
0
scripts/modules/bl_ui_utils/__init__.py
Normal file
0
scripts/modules/bl_ui_utils/__init__.py
Normal file
66
scripts/modules/bl_ui_utils/bug_report_url.py
Normal file
66
scripts/modules/bl_ui_utils/bug_report_url.py
Normal file
@@ -0,0 +1,66 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
def url_prefill_from_blender(*, addon_info=None):
|
||||
import bpy
|
||||
import gpu
|
||||
import struct
|
||||
import platform
|
||||
import urllib.parse
|
||||
import io
|
||||
|
||||
fh = io.StringIO()
|
||||
|
||||
fh.write("**System Information**\n")
|
||||
fh.write(
|
||||
"Operating system: %s %d Bits\n" % (
|
||||
platform.platform(),
|
||||
struct.calcsize("P") * 8,
|
||||
)
|
||||
)
|
||||
fh.write(
|
||||
"Graphics card: %s %s %s\n" % (
|
||||
gpu.platform.renderer_get(),
|
||||
gpu.platform.vendor_get(),
|
||||
gpu.platform.version_get(),
|
||||
)
|
||||
)
|
||||
fh.write(
|
||||
"\n"
|
||||
"**Blender Version**\n"
|
||||
)
|
||||
fh.write(
|
||||
"Broken: version: %s, branch: %s, commit date: %s %s, hash: `%s`\n" % (
|
||||
bpy.app.version_string,
|
||||
bpy.app.build_branch.decode('utf-8', 'replace'),
|
||||
bpy.app.build_commit_date.decode('utf-8', 'replace'),
|
||||
bpy.app.build_commit_time.decode('utf-8', 'replace'),
|
||||
bpy.app.build_hash.decode('ascii'),
|
||||
)
|
||||
)
|
||||
fh.write(
|
||||
"Worked: (newest version of Blender that worked as expected)\n"
|
||||
)
|
||||
if addon_info:
|
||||
fh.write(
|
||||
"\n"
|
||||
"**Addon Information**\n"
|
||||
)
|
||||
fh.write(addon_info)
|
||||
|
||||
fh.write(
|
||||
"\n"
|
||||
"**Short description of error**\n"
|
||||
"[Please fill out a short description of the error here]\n"
|
||||
"\n"
|
||||
"**Exact steps for others to reproduce the error**\n"
|
||||
"[Please describe the exact steps needed to reproduce the issue]\n"
|
||||
"[Based on the default startup or an attached .blend file (as simple as possible)]\n"
|
||||
"\n"
|
||||
)
|
||||
|
||||
form_number = 2 if addon_info else 1
|
||||
return (
|
||||
"https://developer.blender.org/maniphest/task/edit/form/%i?description=" % form_number +
|
||||
urllib.parse.quote(fh.getvalue())
|
||||
)
|
||||
145
scripts/modules/blend_render_info.py
Executable file
145
scripts/modules/blend_render_info.py
Executable file
@@ -0,0 +1,145 @@
|
||||
#!/usr/bin/env python3
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# This module can get render info without running from inside blender.
|
||||
#
|
||||
# This struct won't change according to Ton.
|
||||
# Note that the size differs on 32/64bit
|
||||
#
|
||||
# typedef struct BHead {
|
||||
# int code, len;
|
||||
# void *old;
|
||||
# int SDNAnr, nr;
|
||||
# } BHead;
|
||||
|
||||
__all__ = (
|
||||
"read_blend_rend_chunk",
|
||||
)
|
||||
|
||||
|
||||
class RawBlendFileReader:
|
||||
"""
|
||||
Return a file handle to the raw blend file data (abstracting compressed formats).
|
||||
"""
|
||||
__slots__ = (
|
||||
# The path to load.
|
||||
"_filepath",
|
||||
# The file base file handler or None (only set for compressed formats).
|
||||
"_blendfile_base",
|
||||
# The file handler to return to the caller (always uncompressed data).
|
||||
"_blendfile",
|
||||
)
|
||||
|
||||
def __init__(self, filepath):
|
||||
self._filepath = filepath
|
||||
self._blendfile_base = None
|
||||
self._blendfile = None
|
||||
|
||||
def __enter__(self):
|
||||
blendfile = open(self._filepath, "rb")
|
||||
blendfile_base = None
|
||||
head = blendfile.read(4)
|
||||
blendfile.seek(0)
|
||||
if head[0:2] == b'\x1f\x8b': # GZIP magic.
|
||||
import gzip
|
||||
blendfile_base = blendfile
|
||||
blendfile = gzip.open(blendfile, "rb")
|
||||
elif head[0:4] == b'\x28\xb5\x2f\xfd': # Z-standard magic.
|
||||
import zstandard
|
||||
blendfile_base = blendfile
|
||||
blendfile = zstandard.open(blendfile, "rb")
|
||||
|
||||
self._blendfile_base = blendfile_base
|
||||
self._blendfile = blendfile
|
||||
|
||||
return self._blendfile
|
||||
|
||||
def __exit__(self, exc_type, exc_value, exc_traceback):
|
||||
self._blendfile.close()
|
||||
if self._blendfile_base is not None:
|
||||
self._blendfile_base.close()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def _read_blend_rend_chunk_from_file(blendfile, filepath):
|
||||
import struct
|
||||
import sys
|
||||
|
||||
from os import SEEK_CUR
|
||||
|
||||
head = blendfile.read(7)
|
||||
if head != b'BLENDER':
|
||||
sys.stderr.write("Not a blend file: %s\n" % filepath)
|
||||
return []
|
||||
|
||||
is_64_bit = (blendfile.read(1) == b'-')
|
||||
|
||||
# true for PPC, false for X86
|
||||
is_big_endian = (blendfile.read(1) == b'V')
|
||||
|
||||
# Now read the bhead chunk!
|
||||
blendfile.seek(3, SEEK_CUR) # Skip the version.
|
||||
|
||||
scenes = []
|
||||
|
||||
sizeof_bhead = 24 if is_64_bit else 20
|
||||
|
||||
# Should always be 4, but a malformed/corrupt file may be less.
|
||||
while (bhead_id := blendfile.read(4)) != b'ENDB':
|
||||
|
||||
if len(bhead_id) != 4:
|
||||
sys.stderr.write("Unable to read until ENDB block (corrupt file): %s\n" % filepath)
|
||||
break
|
||||
|
||||
sizeof_data_left = struct.unpack('>i' if is_big_endian else '<i', blendfile.read(4))[0]
|
||||
if sizeof_data_left < 0:
|
||||
# Very unlikely, but prevent other errors.
|
||||
sys.stderr.write("Negative block size found (corrupt file): %s\n" % filepath)
|
||||
break
|
||||
|
||||
# 4 from the `head_id`, another 4 for the size of the BHEAD.
|
||||
sizeof_bhead_left = sizeof_bhead - 8
|
||||
|
||||
# The remainder of the BHEAD struct is not used.
|
||||
blendfile.seek(sizeof_bhead_left, SEEK_CUR)
|
||||
|
||||
if bhead_id == b'REND':
|
||||
# Now we want the scene name, start and end frame. this is 32bits long.
|
||||
start_frame, end_frame = struct.unpack('>2i' if is_big_endian else '<2i', blendfile.read(8))
|
||||
sizeof_data_left -= 8
|
||||
|
||||
scene_name = blendfile.read(64)
|
||||
sizeof_data_left -= 64
|
||||
|
||||
scene_name = scene_name[:scene_name.index(b'\0')]
|
||||
# It's possible old blend files are not UTF8 compliant, use `surrogateescape`.
|
||||
scene_name = scene_name.decode("utf8", errors='surrogateescape')
|
||||
|
||||
scenes.append((start_frame, end_frame, scene_name))
|
||||
|
||||
if sizeof_data_left > 0:
|
||||
blendfile.seek(sizeof_data_left, SEEK_CUR)
|
||||
elif sizeof_data_left < 0:
|
||||
# Very unlikely, but prevent attempting to further parse corrupt data.
|
||||
sys.stderr.write("Error calculating next block (corrupt file): %s\n" % filepath)
|
||||
break
|
||||
|
||||
return scenes
|
||||
|
||||
|
||||
def read_blend_rend_chunk(filepath):
|
||||
with RawBlendFileReader(filepath) as blendfile:
|
||||
return _read_blend_rend_chunk_from_file(blendfile, filepath)
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
|
||||
for filepath in sys.argv[1:]:
|
||||
for value in read_blend_rend_chunk(filepath):
|
||||
print("%d %d %s" % value)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
63
scripts/modules/bpy/__init__.py
Normal file
63
scripts/modules/bpy/__init__.py
Normal file
@@ -0,0 +1,63 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Give access to blender data and utility functions.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"app",
|
||||
"context",
|
||||
"data",
|
||||
"ops",
|
||||
"path",
|
||||
"props",
|
||||
"types",
|
||||
"utils",
|
||||
)
|
||||
|
||||
|
||||
# internal blender C module
|
||||
from _bpy import (
|
||||
app,
|
||||
context,
|
||||
data,
|
||||
msgbus,
|
||||
props,
|
||||
types,
|
||||
)
|
||||
|
||||
# python modules
|
||||
from . import (
|
||||
ops,
|
||||
path,
|
||||
utils,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
import sys
|
||||
|
||||
# Possibly temp. addons path
|
||||
from os.path import join, dirname
|
||||
sys.path.extend([
|
||||
join(dirname(dirname(dirname(__file__))), "addons", "modules"),
|
||||
join(utils.user_resource('SCRIPTS'), "addons", "modules"),
|
||||
])
|
||||
|
||||
# fake module to allow:
|
||||
# from bpy.types import Panel
|
||||
sys.modules.update({
|
||||
"bpy.app": app,
|
||||
"bpy.app.handlers": app.handlers,
|
||||
"bpy.app.translations": app.translations,
|
||||
"bpy.types": types,
|
||||
})
|
||||
|
||||
# Initializes Python classes.
|
||||
# (good place to run a profiler or trace).
|
||||
utils.load_scripts()
|
||||
|
||||
|
||||
main()
|
||||
|
||||
del main
|
||||
185
scripts/modules/bpy/ops.py
Normal file
185
scripts/modules/bpy/ops.py
Normal file
@@ -0,0 +1,185 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# for slightly faster access
|
||||
from _bpy import ops as _ops_module
|
||||
|
||||
# op_add = _ops_module.add
|
||||
_op_dir = _ops_module.dir
|
||||
_op_poll = _ops_module.poll
|
||||
_op_call = _ops_module.call
|
||||
_op_as_string = _ops_module.as_string
|
||||
_op_get_rna_type = _ops_module.get_rna_type
|
||||
_op_get_bl_options = _ops_module.get_bl_options
|
||||
|
||||
_ModuleType = type(_ops_module)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Callable Operator Wrapper
|
||||
|
||||
class _BPyOpsSubModOp:
|
||||
"""
|
||||
Utility class to fake submodule operators.
|
||||
|
||||
eg. bpy.ops.object.somefunc
|
||||
"""
|
||||
|
||||
__slots__ = ("_module", "_func")
|
||||
|
||||
def _get_doc(self):
|
||||
idname = self.idname()
|
||||
sig = _op_as_string(self.idname())
|
||||
# XXX You never quite know what you get from bpy.types,
|
||||
# with operators... Operator and OperatorProperties
|
||||
# are shadowing each other, and not in the same way for
|
||||
# native ops and py ones! See #39158.
|
||||
# op_class = getattr(bpy.types, idname)
|
||||
op_class = _op_get_rna_type(idname)
|
||||
descr = op_class.description
|
||||
return "%s\n%s" % (sig, descr)
|
||||
|
||||
@staticmethod
|
||||
def _parse_args(args):
|
||||
C_dict = None
|
||||
C_exec = 'EXEC_DEFAULT'
|
||||
C_undo = False
|
||||
|
||||
is_dict = is_exec = is_undo = False
|
||||
|
||||
for arg in args:
|
||||
if is_dict is False and isinstance(arg, dict):
|
||||
if is_exec is True or is_undo is True:
|
||||
raise ValueError("dict arg must come first")
|
||||
C_dict = arg
|
||||
is_dict = True
|
||||
elif is_exec is False and isinstance(arg, str):
|
||||
if is_undo is True:
|
||||
raise ValueError("string arg must come before the boolean")
|
||||
C_exec = arg
|
||||
is_exec = True
|
||||
elif is_undo is False and isinstance(arg, int):
|
||||
C_undo = arg
|
||||
is_undo = True
|
||||
else:
|
||||
raise ValueError("1-3 args execution context is supported")
|
||||
|
||||
return C_dict, C_exec, C_undo
|
||||
|
||||
@staticmethod
|
||||
def _view_layer_update(context):
|
||||
view_layer = context.view_layer
|
||||
if view_layer: # None in background mode
|
||||
view_layer.update()
|
||||
else:
|
||||
import bpy
|
||||
for scene in bpy.data.scenes:
|
||||
for view_layer in scene.view_layers:
|
||||
view_layer.update()
|
||||
|
||||
__doc__ = property(_get_doc)
|
||||
|
||||
def __init__(self, module, func):
|
||||
self._module = module
|
||||
self._func = func
|
||||
|
||||
def poll(self, *args):
|
||||
C_dict, C_exec, _C_undo = _BPyOpsSubModOp._parse_args(args)
|
||||
return _op_poll(self.idname_py(), C_dict, C_exec)
|
||||
|
||||
def idname(self):
|
||||
# submod.foo -> SUBMOD_OT_foo
|
||||
return self._module.upper() + "_OT_" + self._func
|
||||
|
||||
def idname_py(self):
|
||||
return self._module + "." + self._func
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
import bpy
|
||||
context = bpy.context
|
||||
|
||||
# Get the operator from blender
|
||||
wm = context.window_manager
|
||||
|
||||
# Run to account for any RNA values the user changes.
|
||||
# NOTE: We only update active view-layer, since that's what
|
||||
# operators are supposed to operate on. There might be some
|
||||
# corner cases when operator need a full scene update though.
|
||||
_BPyOpsSubModOp._view_layer_update(context)
|
||||
|
||||
if args:
|
||||
C_dict, C_exec, C_undo = _BPyOpsSubModOp._parse_args(args)
|
||||
ret = _op_call(self.idname_py(), C_dict, kw, C_exec, C_undo)
|
||||
else:
|
||||
ret = _op_call(self.idname_py(), None, kw)
|
||||
|
||||
if 'FINISHED' in ret and context.window_manager == wm:
|
||||
_BPyOpsSubModOp._view_layer_update(context)
|
||||
|
||||
return ret
|
||||
|
||||
def get_rna_type(self):
|
||||
"""Internal function for introspection"""
|
||||
return _op_get_rna_type(self.idname())
|
||||
|
||||
@property
|
||||
def bl_options(self):
|
||||
return _op_get_bl_options(self.idname())
|
||||
|
||||
def __repr__(self): # useful display, repr(op)
|
||||
return _op_as_string(self.idname())
|
||||
|
||||
def __str__(self): # used for print(...)
|
||||
return ("<function bpy.ops.%s.%s at 0x%x'>" %
|
||||
(self._module, self._func, id(self)))
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Sub-Module Access
|
||||
|
||||
def _bpy_ops_submodule__getattr__(module, func):
|
||||
# Return a value from `bpy.ops.{module}.{func}`
|
||||
if func.startswith("__"):
|
||||
raise AttributeError(func)
|
||||
return _BPyOpsSubModOp(module, func)
|
||||
|
||||
|
||||
def _bpy_ops_submodule__dir__(module):
|
||||
functions = set()
|
||||
module_upper = module.upper()
|
||||
|
||||
for id_name in _op_dir():
|
||||
id_split = id_name.split("_OT_", 1)
|
||||
if len(id_split) == 2 and module_upper == id_split[0]:
|
||||
functions.add(id_split[1])
|
||||
|
||||
return list(functions)
|
||||
|
||||
|
||||
def _bpy_ops_submodule(module):
|
||||
result = _ModuleType("bpy.ops." + module)
|
||||
result.__getattr__ = lambda func: _bpy_ops_submodule__getattr__(module, func)
|
||||
result.__dir__ = lambda: _bpy_ops_submodule__dir__(module)
|
||||
return result
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Module Access
|
||||
|
||||
def __getattr__(module):
|
||||
# Return a value from `bpy.ops.{module}`.
|
||||
if module.startswith("__"):
|
||||
raise AttributeError(module)
|
||||
return _bpy_ops_submodule(module)
|
||||
|
||||
|
||||
def __dir__():
|
||||
submodules = set()
|
||||
for id_name in _op_dir():
|
||||
id_split = id_name.split("_OT_", 1)
|
||||
|
||||
if len(id_split) == 2:
|
||||
submodules.add(id_split[0].lower())
|
||||
else:
|
||||
submodules.add(id_split[0])
|
||||
|
||||
return list(submodules)
|
||||
446
scripts/modules/bpy/path.py
Normal file
446
scripts/modules/bpy/path.py
Normal file
@@ -0,0 +1,446 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module has a similar scope to os.path, containing utility
|
||||
functions for dealing with paths in Blender.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"abspath",
|
||||
"basename",
|
||||
"clean_name",
|
||||
"display_name",
|
||||
"display_name_to_filepath",
|
||||
"display_name_from_filepath",
|
||||
"ensure_ext",
|
||||
"extensions_image",
|
||||
"extensions_movie",
|
||||
"extensions_audio",
|
||||
"is_subdir",
|
||||
"module_names",
|
||||
"native_pathsep",
|
||||
"reduce_dirs",
|
||||
"relpath",
|
||||
"resolve_ncase",
|
||||
)
|
||||
|
||||
import bpy as _bpy
|
||||
import os as _os
|
||||
|
||||
from _bpy_path import (
|
||||
extensions_audio,
|
||||
extensions_movie,
|
||||
extensions_image,
|
||||
)
|
||||
|
||||
|
||||
def _getattr_bytes(var, attr):
|
||||
return var.path_resolve(attr, False).as_bytes()
|
||||
|
||||
|
||||
def abspath(path, *, start=None, library=None):
|
||||
"""
|
||||
Returns the absolute path relative to the current blend file
|
||||
using the "//" prefix.
|
||||
|
||||
:arg start: Relative to this path,
|
||||
when not set the current filename is used.
|
||||
:type start: string or bytes
|
||||
:arg library: The library this path is from. This is only included for
|
||||
convenience, when the library is not None its path replaces *start*.
|
||||
:type library: :class:`bpy.types.Library`
|
||||
:return: The absolute path.
|
||||
:rtype: string
|
||||
"""
|
||||
if isinstance(path, bytes):
|
||||
if path.startswith(b"//"):
|
||||
if library:
|
||||
start = _os.path.dirname(
|
||||
abspath(_getattr_bytes(library, "filepath")))
|
||||
return _os.path.join(
|
||||
_os.path.dirname(_getattr_bytes(_bpy.data, "filepath"))
|
||||
if start is None else start,
|
||||
path[2:],
|
||||
)
|
||||
else:
|
||||
if path.startswith("//"):
|
||||
if library:
|
||||
start = _os.path.dirname(
|
||||
abspath(library.filepath))
|
||||
return _os.path.join(
|
||||
_os.path.dirname(_bpy.data.filepath)
|
||||
if start is None else start,
|
||||
path[2:],
|
||||
)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def relpath(path, *, start=None):
|
||||
"""
|
||||
Returns the path relative to the current blend file using the "//" prefix.
|
||||
|
||||
:arg path: An absolute path.
|
||||
:type path: string or bytes
|
||||
:arg start: Relative to this path,
|
||||
when not set the current filename is used.
|
||||
:type start: string or bytes
|
||||
:return: The relative path.
|
||||
:rtype: string
|
||||
"""
|
||||
if isinstance(path, bytes):
|
||||
if not path.startswith(b"//"):
|
||||
if start is None:
|
||||
start = _os.path.dirname(_getattr_bytes(_bpy.data, "filepath"))
|
||||
return b"//" + _os.path.relpath(path, start)
|
||||
else:
|
||||
if not path.startswith("//"):
|
||||
if start is None:
|
||||
start = _os.path.dirname(_bpy.data.filepath)
|
||||
return "//" + _os.path.relpath(path, start)
|
||||
|
||||
return path
|
||||
|
||||
|
||||
def is_subdir(path, directory):
|
||||
"""
|
||||
Returns true if *path* in a subdirectory of *directory*.
|
||||
Both paths must be absolute.
|
||||
|
||||
:arg path: An absolute path.
|
||||
:type path: string or bytes
|
||||
:return: Whether or not the path is a subdirectory.
|
||||
:rtype: boolean
|
||||
"""
|
||||
from os.path import normpath, normcase, sep
|
||||
path = normpath(normcase(path))
|
||||
directory = normpath(normcase(directory))
|
||||
if len(path) > len(directory):
|
||||
sep = sep.encode('ascii') if isinstance(directory, bytes) else sep
|
||||
if path.startswith(directory.rstrip(sep) + sep):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def clean_name(name, *, replace="_"):
|
||||
"""
|
||||
Returns a name with characters replaced that
|
||||
may cause problems under various circumstances,
|
||||
such as writing to a file.
|
||||
All characters besides A-Z/a-z, 0-9 are replaced with "_"
|
||||
or the *replace* argument if defined.
|
||||
:arg name: The path name.
|
||||
:type name: string or bytes
|
||||
:arg replace: The replacement for non-valid characters.
|
||||
:type replace: string
|
||||
:return: The cleaned name.
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
if replace != "_":
|
||||
if len(replace) != 1 or ord(replace) > 255:
|
||||
raise ValueError("Value must be a single ascii character")
|
||||
|
||||
def maketrans_init():
|
||||
trans_cache = clean_name._trans_cache
|
||||
trans = trans_cache.get(replace)
|
||||
if trans is None:
|
||||
bad_chars = (
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f,
|
||||
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
|
||||
0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2e, 0x2f, 0x3a,
|
||||
0x3b, 0x3c, 0x3d, 0x3e, 0x3f, 0x40, 0x5b, 0x5c,
|
||||
0x5d, 0x5e, 0x60, 0x7b, 0x7c, 0x7d, 0x7e, 0x7f,
|
||||
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
||||
0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f,
|
||||
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
|
||||
0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f,
|
||||
0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
|
||||
0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf,
|
||||
0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7,
|
||||
0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf,
|
||||
0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7,
|
||||
0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf,
|
||||
0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7,
|
||||
0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf,
|
||||
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
|
||||
0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
|
||||
0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7,
|
||||
0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe,
|
||||
)
|
||||
trans = str.maketrans({char: replace for char in bad_chars})
|
||||
trans_cache[replace] = trans
|
||||
return trans
|
||||
|
||||
trans = maketrans_init()
|
||||
return name.translate(trans)
|
||||
|
||||
|
||||
clean_name._trans_cache = {}
|
||||
|
||||
|
||||
def _clean_utf8(name):
|
||||
if type(name) == bytes:
|
||||
return name.decode("utf8", "replace")
|
||||
else:
|
||||
return name.encode("utf8", "replace").decode("utf8")
|
||||
|
||||
|
||||
_display_name_literals = {
|
||||
":": "_colon_",
|
||||
"+": "_plus_",
|
||||
"/": "_slash_",
|
||||
}
|
||||
|
||||
|
||||
def display_name(name, *, has_ext=True, title_case=True):
|
||||
"""
|
||||
Creates a display string from name to be used menus and the user interface.
|
||||
Intended for use with filenames and module names.
|
||||
|
||||
:arg name: The name to be used for displaying the user interface.
|
||||
:type name: string
|
||||
:arg has_ext: Remove file extension from name.
|
||||
:type has_ext: boolean
|
||||
:arg title_case: Convert lowercase names to title case.
|
||||
:type title_case: boolean
|
||||
:return: The display string.
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
if has_ext:
|
||||
name = _os.path.splitext(basename(name))[0]
|
||||
|
||||
# string replacements
|
||||
for disp_value, file_value in _display_name_literals.items():
|
||||
name = name.replace(file_value, disp_value)
|
||||
|
||||
# strip to allow underscore prefix
|
||||
# (when paths can't start with numbers for eg).
|
||||
name = name.replace("_", " ").lstrip(" ")
|
||||
|
||||
if title_case and name.islower():
|
||||
name = name.lower().title()
|
||||
|
||||
name = _clean_utf8(name)
|
||||
return name
|
||||
|
||||
|
||||
def display_name_to_filepath(name):
|
||||
"""
|
||||
Performs the reverse of display_name using literal versions of characters
|
||||
which aren't supported in a filepath.
|
||||
:arg name: The display name to convert.
|
||||
:type name: string
|
||||
:return: The file path.
|
||||
:rtype: string
|
||||
"""
|
||||
for disp_value, file_value in _display_name_literals.items():
|
||||
name = name.replace(disp_value, file_value)
|
||||
return name
|
||||
|
||||
|
||||
def display_name_from_filepath(name):
|
||||
"""
|
||||
Returns the path stripped of directory and extension,
|
||||
ensured to be utf8 compatible.
|
||||
:arg name: The file path to convert.
|
||||
:type name: string
|
||||
:return: The display name.
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
name = _os.path.splitext(basename(name))[0]
|
||||
name = _clean_utf8(name)
|
||||
return name
|
||||
|
||||
|
||||
def resolve_ncase(path):
|
||||
"""
|
||||
Resolve a case insensitive path on a case sensitive system,
|
||||
returning a string with the path if found else return the original path.
|
||||
:arg path: The path name to resolve.
|
||||
:type path: string
|
||||
:return: The resolved path.
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
def _ncase_path_found(path):
|
||||
if not path or _os.path.exists(path):
|
||||
return path, True
|
||||
|
||||
# filename may be a directory or a file
|
||||
filename = _os.path.basename(path)
|
||||
dirpath = _os.path.dirname(path)
|
||||
|
||||
suffix = path[:0] # "" but ensure byte/str match
|
||||
if not filename: # dir ends with a slash?
|
||||
if len(dirpath) < len(path):
|
||||
suffix = path[:len(path) - len(dirpath)]
|
||||
|
||||
filename = _os.path.basename(dirpath)
|
||||
dirpath = _os.path.dirname(dirpath)
|
||||
|
||||
if not _os.path.exists(dirpath):
|
||||
if dirpath == path:
|
||||
return path, False
|
||||
|
||||
dirpath, found = _ncase_path_found(dirpath)
|
||||
|
||||
if not found:
|
||||
return path, False
|
||||
|
||||
# at this point, the directory exists but not the file
|
||||
|
||||
# we are expecting 'dirpath' to be a directory, but it could be a file
|
||||
if _os.path.isdir(dirpath):
|
||||
try:
|
||||
files = _os.listdir(dirpath)
|
||||
except PermissionError:
|
||||
# We might not have the permission to list dirpath...
|
||||
return path, False
|
||||
else:
|
||||
return path, False
|
||||
|
||||
filename_low = filename.lower()
|
||||
f_iter_nocase = None
|
||||
|
||||
for f_iter in files:
|
||||
if f_iter.lower() == filename_low:
|
||||
f_iter_nocase = f_iter
|
||||
break
|
||||
|
||||
if f_iter_nocase:
|
||||
return _os.path.join(dirpath, f_iter_nocase) + suffix, True
|
||||
else:
|
||||
# can't find the right one, just return the path as is.
|
||||
return path, False
|
||||
|
||||
ncase_path, found = _ncase_path_found(path)
|
||||
return ncase_path if found else path
|
||||
|
||||
|
||||
def ensure_ext(filepath, ext, *, case_sensitive=False):
|
||||
"""
|
||||
Return the path with the extension added if it is not already set.
|
||||
|
||||
:arg filepath: The file path.
|
||||
:type filepath: string
|
||||
:arg ext: The extension to check for, can be a compound extension. Should
|
||||
start with a dot, such as '.blend' or '.tar.gz'.
|
||||
:type ext: string
|
||||
:arg case_sensitive: Check for matching case when comparing extensions.
|
||||
:type case_sensitive: boolean
|
||||
:return: The file path with the given extension.
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
if case_sensitive:
|
||||
if filepath.endswith(ext):
|
||||
return filepath
|
||||
else:
|
||||
if filepath[-len(ext):].lower().endswith(ext.lower()):
|
||||
return filepath
|
||||
|
||||
return filepath + ext
|
||||
|
||||
|
||||
def module_names(path, *, recursive=False):
|
||||
"""
|
||||
Return a list of modules which can be imported from *path*.
|
||||
|
||||
:arg path: a directory to scan.
|
||||
:type path: string
|
||||
:arg recursive: Also return submodule names for packages.
|
||||
:type recursive: bool
|
||||
:return: a list of string pairs (module_name, module_file).
|
||||
:rtype: list of strings
|
||||
"""
|
||||
|
||||
from os.path import join, isfile
|
||||
|
||||
modules = []
|
||||
|
||||
for filename in sorted(_os.listdir(path)):
|
||||
if filename == "modules":
|
||||
pass # XXX, hard coded exception.
|
||||
elif filename.endswith(".py") and filename != "__init__.py":
|
||||
fullpath = join(path, filename)
|
||||
modules.append((filename[0:-3], fullpath))
|
||||
elif not filename.startswith("."):
|
||||
# Skip hidden files since they are used by for version control.
|
||||
directory = join(path, filename)
|
||||
fullpath = join(directory, "__init__.py")
|
||||
if isfile(fullpath):
|
||||
modules.append((filename, fullpath))
|
||||
if recursive:
|
||||
for mod_name, mod_path in module_names(directory, recursive=True):
|
||||
modules.append(("%s.%s" % (filename, mod_name),
|
||||
mod_path,
|
||||
))
|
||||
|
||||
return modules
|
||||
|
||||
|
||||
def basename(path):
|
||||
"""
|
||||
Equivalent to ``os.path.basename``, but skips a "//" prefix.
|
||||
|
||||
Use for Windows compatibility.
|
||||
:return: The base name of the given path.
|
||||
:rtype: string
|
||||
"""
|
||||
return _os.path.basename(path[2:] if path[:2] in {"//", b"//"} else path)
|
||||
|
||||
|
||||
def native_pathsep(path):
|
||||
"""
|
||||
Replace the path separator with the systems native ``os.sep``.
|
||||
:arg path: The path to replace.
|
||||
:type path: string
|
||||
:return: The path with system native separators.
|
||||
:rtype: string
|
||||
"""
|
||||
if type(path) is str:
|
||||
if _os.sep == "/":
|
||||
return path.replace("\\", "/")
|
||||
else:
|
||||
if path.startswith("//"):
|
||||
return "//" + path[2:].replace("/", "\\")
|
||||
else:
|
||||
return path.replace("/", "\\")
|
||||
else: # bytes
|
||||
if _os.sep == "/":
|
||||
return path.replace(b"\\", b"/")
|
||||
else:
|
||||
if path.startswith(b"//"):
|
||||
return b"//" + path[2:].replace(b"/", b"\\")
|
||||
else:
|
||||
return path.replace(b"/", b"\\")
|
||||
|
||||
|
||||
def reduce_dirs(dirs):
|
||||
"""
|
||||
Given a sequence of directories, remove duplicates and
|
||||
any directories nested in one of the other paths.
|
||||
(Useful for recursive path searching).
|
||||
|
||||
:arg dirs: Sequence of directory paths.
|
||||
:type dirs: sequence of strings
|
||||
:return: A unique list of paths.
|
||||
:rtype: list of strings
|
||||
"""
|
||||
dirs = list({_os.path.normpath(_os.path.abspath(d)) for d in dirs})
|
||||
dirs.sort(key=lambda d: len(d))
|
||||
for i in range(len(dirs) - 1, -1, -1):
|
||||
for j in range(i):
|
||||
print(i, j)
|
||||
if len(dirs[i]) == len(dirs[j]):
|
||||
break
|
||||
elif is_subdir(dirs[i], dirs[j]):
|
||||
del dirs[i]
|
||||
break
|
||||
return dirs
|
||||
1110
scripts/modules/bpy/utils/__init__.py
Normal file
1110
scripts/modules/bpy/utils/__init__.py
Normal file
@@ -0,0 +1,1110 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module contains utility functions specific to blender but
|
||||
not associated with blenders internal data.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"blend_paths",
|
||||
"escape_identifier",
|
||||
"flip_name",
|
||||
"unescape_identifier",
|
||||
"keyconfig_init",
|
||||
"keyconfig_set",
|
||||
"load_scripts",
|
||||
"modules_from_path",
|
||||
"preset_find",
|
||||
"preset_paths",
|
||||
"refresh_script_paths",
|
||||
"app_template_paths",
|
||||
"register_class",
|
||||
"register_manual_map",
|
||||
"unregister_manual_map",
|
||||
"register_classes_factory",
|
||||
"register_submodule_factory",
|
||||
"register_tool",
|
||||
"make_rna_paths",
|
||||
"manual_map",
|
||||
"manual_language_code",
|
||||
"previews",
|
||||
"resource_path",
|
||||
"script_path_user",
|
||||
"script_path_pref",
|
||||
"script_paths",
|
||||
"smpte_from_frame",
|
||||
"smpte_from_seconds",
|
||||
"units",
|
||||
"unregister_class",
|
||||
"unregister_tool",
|
||||
"user_resource",
|
||||
"execfile",
|
||||
)
|
||||
|
||||
from _bpy import (
|
||||
_utils_units as units,
|
||||
blend_paths,
|
||||
escape_identifier,
|
||||
flip_name,
|
||||
unescape_identifier,
|
||||
register_class,
|
||||
resource_path,
|
||||
script_paths as _bpy_script_paths,
|
||||
unregister_class,
|
||||
user_resource as _user_resource,
|
||||
system_resource,
|
||||
)
|
||||
|
||||
import bpy as _bpy
|
||||
import os as _os
|
||||
import sys as _sys
|
||||
|
||||
import addon_utils as _addon_utils
|
||||
|
||||
_preferences = _bpy.context.preferences
|
||||
_is_factory_startup = _bpy.app.factory_startup
|
||||
|
||||
# Directories added to the start of `sys.path` for all of Blender's "scripts" directories.
|
||||
_script_module_dirs = "startup", "modules"
|
||||
|
||||
# Base scripts, this points to the directory containing: "modules" & "startup" (see `_script_module_dirs`).
|
||||
# In Blender's code-base this is `./scripts`.
|
||||
#
|
||||
# NOTE: in virtually all cases this should match `BLENDER_SYSTEM_SCRIPTS` as this script is it's self a system script,
|
||||
# it must be in the `BLENDER_SYSTEM_SCRIPTS` by definition and there is no need for a look-up from `_bpy_script_paths`.
|
||||
_script_base_dir = _os.path.dirname(_os.path.dirname(_os.path.dirname(_os.path.dirname(__file__))))
|
||||
|
||||
|
||||
def execfile(filepath, *, mod=None):
|
||||
"""
|
||||
Execute a file path as a Python script.
|
||||
|
||||
:arg filepath: Path of the script to execute.
|
||||
:type filepath: string
|
||||
:arg mod: Optional cached module, the result of a previous execution.
|
||||
:type mod: Module or None
|
||||
:return: The module which can be passed back in as ``mod``.
|
||||
:rtype: ModuleType
|
||||
"""
|
||||
|
||||
import importlib.util
|
||||
mod_name = "__main__"
|
||||
mod_spec = importlib.util.spec_from_file_location(mod_name, filepath)
|
||||
if mod is None:
|
||||
mod = importlib.util.module_from_spec(mod_spec)
|
||||
|
||||
# While the module name is not added to `sys.modules`, it's important to temporarily
|
||||
# include this so statements such as `sys.modules[cls.__module__].__dict__` behave as expected.
|
||||
# See: https://bugs.python.org/issue9499 for details.
|
||||
modules = _sys.modules
|
||||
mod_orig = modules.get(mod_name, None)
|
||||
modules[mod_name] = mod
|
||||
|
||||
# No error suppression, just ensure `sys.modules[mod_name]` is properly restored in the case of an error.
|
||||
try:
|
||||
mod_spec.loader.exec_module(mod)
|
||||
finally:
|
||||
if mod_orig is None:
|
||||
modules.pop(mod_name, None)
|
||||
else:
|
||||
modules[mod_name] = mod_orig
|
||||
|
||||
return mod
|
||||
|
||||
|
||||
def _test_import(module_name, loaded_modules):
|
||||
use_time = _bpy.app.debug_python
|
||||
|
||||
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
|
||||
|
||||
|
||||
# Check before adding paths as reloading would add twice.
|
||||
|
||||
# Storing and restoring the full `sys.path` is risky as this may be intentionally modified
|
||||
# by technical users/developers.
|
||||
#
|
||||
# Instead, track which paths have been added, clearing them before refreshing.
|
||||
# This supports the case of loading a new preferences file which may reset scripts path.
|
||||
_sys_path_ensure_paths = set()
|
||||
|
||||
|
||||
def _sys_path_ensure_prepend(path):
|
||||
if path not in _sys.path:
|
||||
_sys.path.insert(0, path)
|
||||
_sys_path_ensure_paths.add(path)
|
||||
|
||||
|
||||
def _sys_path_ensure_append(path):
|
||||
if path not in _sys.path:
|
||||
_sys.path.append(path)
|
||||
_sys_path_ensure_paths.add(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 = use_class_register_check = _bpy.app.debug_python
|
||||
use_user = not _is_factory_startup
|
||||
|
||||
if use_time:
|
||||
import time
|
||||
t_main = time.time()
|
||||
|
||||
loaded_modules = set()
|
||||
|
||||
if refresh_scripts:
|
||||
original_modules = _sys.modules.values()
|
||||
|
||||
if reload_scripts:
|
||||
# just unload, don't change user defaults, this means we can sync
|
||||
# to reload. note that they will only actually reload of the
|
||||
# modification time changes. This `won't` work for packages so...
|
||||
# its not perfect.
|
||||
for module_name in [ext.module for ext in _preferences.addons]:
|
||||
_addon_utils.disable(module_name)
|
||||
|
||||
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 importlib
|
||||
# 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 importlib.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)
|
||||
|
||||
del _global_loaded_modules[:]
|
||||
|
||||
from bpy_restrict_state import RestrictBlend
|
||||
|
||||
with RestrictBlend():
|
||||
for base_path in script_paths(use_user=use_user):
|
||||
for path_subdir in _script_module_dirs:
|
||||
path = _os.path.join(base_path, path_subdir)
|
||||
if _os.path.isdir(path):
|
||||
_sys_path_ensure_prepend(path)
|
||||
|
||||
# Only add to 'sys.modules' unless this is 'startup'.
|
||||
if path_subdir == "startup":
|
||||
for mod in modules_from_path(path, loaded_modules):
|
||||
test_register(mod)
|
||||
|
||||
# load template (if set)
|
||||
if any(_bpy.utils.app_template_paths()):
|
||||
import bl_app_template_utils
|
||||
bl_app_template_utils.reset(reload_scripts=reload_scripts)
|
||||
del bl_app_template_utils
|
||||
|
||||
# deal with addons separately
|
||||
_initialize = getattr(_addon_utils, "_initialize", None)
|
||||
if _initialize is not None:
|
||||
# first time, use fast-path
|
||||
_initialize()
|
||||
del _addon_utils._initialize
|
||||
else:
|
||||
_addon_utils.reset_all(reload_scripts=reload_scripts)
|
||||
del _initialize
|
||||
|
||||
if reload_scripts:
|
||||
_bpy.context.window_manager.tag_script_reload()
|
||||
|
||||
import gc
|
||||
print("gc.collect() -> %d" % gc.collect())
|
||||
|
||||
if use_time:
|
||||
print("Python Script Load Time %.4f" % (time.time() - t_main))
|
||||
|
||||
if use_class_register_check:
|
||||
for cls in _bpy.types.bpy_struct.__subclasses__():
|
||||
if getattr(cls, "is_registered", False):
|
||||
for subcls in cls.__subclasses__():
|
||||
if not subcls.is_registered:
|
||||
print(
|
||||
"Warning, unregistered class: %s(%s)" %
|
||||
(subcls.__name__, cls.__name__)
|
||||
)
|
||||
|
||||
|
||||
def script_path_user():
|
||||
"""returns the env var and falls back to home dir or None"""
|
||||
path = _user_resource('SCRIPTS')
|
||||
return _os.path.normpath(path) if path else None
|
||||
|
||||
|
||||
def script_path_pref():
|
||||
"""returns the user preference or None"""
|
||||
path = _preferences.filepaths.script_directory
|
||||
return _os.path.normpath(path) if path else None
|
||||
|
||||
|
||||
def script_paths(*, subdir=None, user_pref=True, check_all=False, use_user=True):
|
||||
"""
|
||||
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 check_all: Include local, user and system paths rather just the paths Blender uses.
|
||||
:type check_all: bool
|
||||
:return: script paths.
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
if check_all or use_user:
|
||||
path_system, path_user = _bpy_script_paths()
|
||||
|
||||
base_paths = []
|
||||
|
||||
if check_all:
|
||||
# Order: 'LOCAL', 'USER', 'SYSTEM' (where user is optional).
|
||||
if path_local := resource_path('LOCAL'):
|
||||
base_paths.append(_os.path.join(path_local, "scripts"))
|
||||
if use_user:
|
||||
base_paths.append(path_user)
|
||||
base_paths.append(path_system) # Same as: `system_resource('SCRIPTS')`.
|
||||
|
||||
# Note that `_script_base_dir` may be either:
|
||||
# - `os.path.join(bpy.utils.resource_path('LOCAL'), "scripts")`
|
||||
# - `bpy.utils.system_resource('SCRIPTS')`.
|
||||
# When `check_all` is enabled duplicate paths will be added however
|
||||
# paths are de-duplicated so it wont cause problems.
|
||||
base_paths.append(_script_base_dir)
|
||||
|
||||
if not check_all:
|
||||
if use_user:
|
||||
base_paths.append(path_user)
|
||||
|
||||
if user_pref:
|
||||
base_paths.append(script_path_pref())
|
||||
|
||||
scripts = []
|
||||
for path in base_paths:
|
||||
if not path:
|
||||
continue
|
||||
|
||||
path = _os.path.normpath(path)
|
||||
if subdir is not None:
|
||||
path = _os.path.join(path, subdir)
|
||||
|
||||
if path in scripts:
|
||||
continue
|
||||
if not _os.path.isdir(path):
|
||||
continue
|
||||
scripts.append(path)
|
||||
|
||||
return scripts
|
||||
|
||||
|
||||
def refresh_script_paths():
|
||||
"""
|
||||
Run this after creating new script paths to update sys.path
|
||||
"""
|
||||
|
||||
for path in _sys_path_ensure_paths:
|
||||
try:
|
||||
_sys.path.remove(path)
|
||||
except ValueError:
|
||||
pass
|
||||
_sys_path_ensure_paths.clear()
|
||||
|
||||
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_prepend(path)
|
||||
|
||||
for path in _addon_utils.paths():
|
||||
_sys_path_ensure_append(path)
|
||||
path = _os.path.join(path, "modules")
|
||||
if _os.path.isdir(path):
|
||||
_sys_path_ensure_append(path)
|
||||
|
||||
|
||||
def app_template_paths(*, path=None):
|
||||
"""
|
||||
Returns valid application template paths.
|
||||
|
||||
:arg path: Optional subdir.
|
||||
:type path: string
|
||||
:return: app template paths.
|
||||
:rtype: generator
|
||||
"""
|
||||
subdir_args = (path,) if path is not None else ()
|
||||
# Note: keep in sync with: Blender's 'BKE_appdir_app_template_any'.
|
||||
# Uses 'BLENDER_USER_SCRIPTS', 'BLENDER_SYSTEM_SCRIPTS'
|
||||
# ... in this case 'system' accounts for 'local' too.
|
||||
for resource_fn, module_name in (
|
||||
(_user_resource, "bl_app_templates_user"),
|
||||
(system_resource, "bl_app_templates_system"),
|
||||
):
|
||||
path_test = resource_fn('SCRIPTS', path=_os.path.join("startup", module_name, *subdir_args))
|
||||
if path_test and _os.path.isdir(path_test):
|
||||
yield path_test
|
||||
|
||||
|
||||
def preset_paths(subdir):
|
||||
"""
|
||||
Returns a list of paths for a specific preset.
|
||||
|
||||
:arg subdir: preset subdirectory (must not be an absolute path).
|
||||
:type subdir: string
|
||||
:return: script paths.
|
||||
:rtype: list
|
||||
"""
|
||||
dirs = []
|
||||
for path in script_paths(subdir="presets", check_all=True):
|
||||
directory = _os.path.join(path, subdir)
|
||||
if not directory.startswith(path):
|
||||
raise Exception("invalid subdir given %r" % subdir)
|
||||
elif _os.path.isdir(directory):
|
||||
dirs.append(directory)
|
||||
|
||||
# Find addons preset paths
|
||||
for path in _addon_utils.paths():
|
||||
directory = _os.path.join(path, "presets", subdir)
|
||||
if _os.path.isdir(directory):
|
||||
dirs.append(directory)
|
||||
|
||||
return dirs
|
||||
|
||||
|
||||
def is_path_builtin(path):
|
||||
"""
|
||||
Returns True if the path is one of the built-in paths used by Blender.
|
||||
|
||||
:arg path: Path you want to check if it is in the built-in settings directory
|
||||
:type path: str
|
||||
:rtype: bool
|
||||
"""
|
||||
# Note that this function isn't optimized for speed,
|
||||
# it's intended to be used to check if it's OK to remove presets.
|
||||
#
|
||||
# If this is used in a draw-loop for example, we could cache some of the values.
|
||||
user_path = resource_path('USER')
|
||||
|
||||
for res in ('SYSTEM', 'LOCAL'):
|
||||
parent_path = resource_path(res)
|
||||
if not parent_path or parent_path == user_path:
|
||||
# Make sure that the current path is not empty string and that it is
|
||||
# not the same as the user config path. IE "~/.config/blender" on Linux
|
||||
# This can happen on portable installs.
|
||||
continue
|
||||
|
||||
try:
|
||||
if _os.path.samefile(
|
||||
_os.path.commonpath([parent_path]),
|
||||
_os.path.commonpath([parent_path, path])
|
||||
):
|
||||
return True
|
||||
except FileNotFoundError:
|
||||
# The path we tried to look up doesn't exist.
|
||||
pass
|
||||
except ValueError:
|
||||
# Happens on Windows when paths don't have the same drive.
|
||||
pass
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def smpte_from_seconds(time, *, fps=None, fps_base=None):
|
||||
"""
|
||||
Returns an SMPTE formatted string from the *time*:
|
||||
``HH:MM:SS:FF``.
|
||||
|
||||
If *fps* and *fps_base* are not given the current scene is used.
|
||||
|
||||
:arg time: time in seconds.
|
||||
:type time: int, float or ``datetime.timedelta``.
|
||||
:return: the frame string.
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
return smpte_from_frame(
|
||||
time_to_frame(time, fps=fps, fps_base=fps_base),
|
||||
fps=fps,
|
||||
fps_base=fps_base
|
||||
)
|
||||
|
||||
|
||||
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.
|
||||
|
||||
:arg frame: frame number.
|
||||
:type frame: int or float.
|
||||
:return: the frame string.
|
||||
:rtype: string
|
||||
"""
|
||||
|
||||
if fps is None:
|
||||
fps = _bpy.context.scene.render.fps
|
||||
|
||||
if fps_base is None:
|
||||
fps_base = _bpy.context.scene.render.fps_base
|
||||
|
||||
fps = fps / fps_base
|
||||
sign = "-" if frame < 0 else ""
|
||||
frame = abs(frame)
|
||||
|
||||
return (
|
||||
"%s%02d:%02d:%02d:%02d" % (
|
||||
sign,
|
||||
int(frame / (3600 * fps)), # HH
|
||||
int((frame / (60 * fps)) % 60), # MM
|
||||
int((frame / fps) % 60), # SS
|
||||
int(frame % fps), # FF
|
||||
))
|
||||
|
||||
|
||||
def time_from_frame(frame, *, fps=None, fps_base=None):
|
||||
"""
|
||||
Returns the time from a frame number .
|
||||
|
||||
If *fps* and *fps_base* are not given the current scene is used.
|
||||
|
||||
:arg frame: number.
|
||||
:type frame: int or float.
|
||||
:return: the time in seconds.
|
||||
:rtype: datetime.timedelta
|
||||
"""
|
||||
|
||||
if fps is None:
|
||||
fps = _bpy.context.scene.render.fps
|
||||
|
||||
if fps_base is None:
|
||||
fps_base = _bpy.context.scene.render.fps_base
|
||||
|
||||
fps = fps / fps_base
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
return timedelta(0, frame / fps)
|
||||
|
||||
|
||||
def time_to_frame(time, *, fps=None, fps_base=None):
|
||||
"""
|
||||
Returns a float frame number from a time given in seconds or
|
||||
as a datetime.timedelta object.
|
||||
|
||||
If *fps* and *fps_base* are not given the current scene is used.
|
||||
|
||||
:arg time: time in seconds.
|
||||
:type time: number or a ``datetime.timedelta`` object
|
||||
:return: the frame.
|
||||
:rtype: float
|
||||
"""
|
||||
|
||||
if fps is None:
|
||||
fps = _bpy.context.scene.render.fps
|
||||
|
||||
if fps_base is None:
|
||||
fps_base = _bpy.context.scene.render.fps_base
|
||||
|
||||
fps = fps / fps_base
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
if isinstance(time, timedelta):
|
||||
time = time.total_seconds()
|
||||
|
||||
return time * fps
|
||||
|
||||
|
||||
def preset_find(name, preset_path, *, display_name=False, ext=".py"):
|
||||
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(ext) and name == _bpy.path.display_name(fn, title_case=False):
|
||||
filename = fn
|
||||
break
|
||||
else:
|
||||
filename = name + ext
|
||||
|
||||
if filename:
|
||||
filepath = _os.path.join(directory, filename)
|
||||
if _os.path.exists(filepath):
|
||||
return filepath
|
||||
|
||||
|
||||
def keyconfig_init():
|
||||
# Key configuration initialization and refresh, called from the Blender
|
||||
# window manager on startup and refresh.
|
||||
active_config = _preferences.keymap.active_keyconfig
|
||||
|
||||
# Load the default key configuration.
|
||||
default_filepath = preset_find("Blender", "keyconfig")
|
||||
keyconfig_set(default_filepath)
|
||||
|
||||
# Set the active key configuration if different
|
||||
filepath = preset_find(active_config, "keyconfig")
|
||||
|
||||
if filepath and filepath != default_filepath:
|
||||
keyconfig_set(filepath)
|
||||
|
||||
|
||||
def keyconfig_set(filepath, *, report=None):
|
||||
from os.path import basename, splitext
|
||||
|
||||
if _bpy.app.debug_python:
|
||||
print("loading preset:", filepath)
|
||||
|
||||
keyconfigs = _bpy.context.window_manager.keyconfigs
|
||||
|
||||
try:
|
||||
error_msg = ""
|
||||
execfile(filepath)
|
||||
except:
|
||||
import traceback
|
||||
error_msg = traceback.format_exc()
|
||||
|
||||
name = splitext(basename(filepath))[0]
|
||||
kc_new = keyconfigs.get(name)
|
||||
|
||||
if error_msg:
|
||||
if report is not None:
|
||||
report({'ERROR'}, error_msg)
|
||||
print(error_msg)
|
||||
if kc_new is not None:
|
||||
keyconfigs.remove(kc_new)
|
||||
return False
|
||||
|
||||
# Get name, exception for default keymap to keep backwards compatibility.
|
||||
if kc_new is None:
|
||||
if report is not None:
|
||||
report({'ERROR'}, "Failed to load keymap %r" % filepath)
|
||||
return False
|
||||
else:
|
||||
keyconfigs.active = kc_new
|
||||
return True
|
||||
|
||||
|
||||
def user_resource(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 path: Optional subdirectory.
|
||||
:type path: 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(resource_type, path=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 register_classes_factory(classes):
|
||||
"""
|
||||
Utility function to create register and unregister functions
|
||||
which simply registers and unregisters a sequence of classes.
|
||||
"""
|
||||
def register():
|
||||
for cls in classes:
|
||||
register_class(cls)
|
||||
|
||||
def unregister():
|
||||
for cls in reversed(classes):
|
||||
unregister_class(cls)
|
||||
|
||||
return register, unregister
|
||||
|
||||
|
||||
def register_submodule_factory(module_name, submodule_names):
|
||||
"""
|
||||
Utility function to create register and unregister functions
|
||||
which simply load submodules,
|
||||
calling their register & unregister functions.
|
||||
|
||||
.. note::
|
||||
|
||||
Modules are registered in the order given,
|
||||
unregistered in reverse order.
|
||||
|
||||
:arg module_name: The module name, typically ``__name__``.
|
||||
:type module_name: string
|
||||
:arg submodule_names: List of submodule names to load and unload.
|
||||
:type submodule_names: list of strings
|
||||
:return: register and unregister functions.
|
||||
:rtype: tuple pair of functions
|
||||
"""
|
||||
|
||||
module = None
|
||||
submodules = []
|
||||
|
||||
def register():
|
||||
nonlocal module
|
||||
module = __import__(name=module_name, fromlist=submodule_names)
|
||||
submodules[:] = [getattr(module, name) for name in submodule_names]
|
||||
for mod in submodules:
|
||||
mod.register()
|
||||
|
||||
def unregister():
|
||||
from sys import modules
|
||||
for mod in reversed(submodules):
|
||||
mod.unregister()
|
||||
name = mod.__name__
|
||||
delattr(module, name.partition(".")[2])
|
||||
del modules[name]
|
||||
submodules.clear()
|
||||
|
||||
return register, unregister
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Tool Registration
|
||||
|
||||
|
||||
def register_tool(tool_cls, *, after=None, separator=False, group=False):
|
||||
"""
|
||||
Register a tool in the toolbar.
|
||||
|
||||
:arg tool: A tool subclass.
|
||||
:type tool: :class:`bpy.types.WorkSpaceTool` subclass.
|
||||
:arg space_type: Space type identifier.
|
||||
:type space_type: string
|
||||
:arg after: Optional identifiers this tool will be added after.
|
||||
:type after: collection of strings or None.
|
||||
:arg separator: When true, add a separator before this tool.
|
||||
:type separator: bool
|
||||
:arg group: When true, add a new nested group of tools.
|
||||
:type group: bool
|
||||
"""
|
||||
space_type = tool_cls.bl_space_type
|
||||
context_mode = tool_cls.bl_context_mode
|
||||
|
||||
from bl_ui.space_toolsystem_common import (
|
||||
ToolSelectPanelHelper,
|
||||
ToolDef,
|
||||
)
|
||||
|
||||
cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
|
||||
if cls is None:
|
||||
raise Exception("Space type %r has no toolbar" % space_type)
|
||||
tools = cls._tools[context_mode]
|
||||
|
||||
# First sanity check
|
||||
from bpy.types import WorkSpaceTool
|
||||
tools_id = {
|
||||
item.idname for item in ToolSelectPanelHelper._tools_flatten(tools)
|
||||
if item is not None
|
||||
}
|
||||
if not issubclass(tool_cls, WorkSpaceTool):
|
||||
raise Exception("Expected WorkSpaceTool subclass, not %r" % type(tool_cls))
|
||||
if tool_cls.bl_idname in tools_id:
|
||||
raise Exception("Tool %r already exists!" % tool_cls.bl_idname)
|
||||
del tools_id, WorkSpaceTool
|
||||
|
||||
# Convert the class into a ToolDef.
|
||||
def tool_from_class(tool_cls):
|
||||
# Convert class to tuple, store in the class for removal.
|
||||
tool_def = ToolDef.from_dict({
|
||||
"idname": tool_cls.bl_idname,
|
||||
"label": tool_cls.bl_label,
|
||||
"description": getattr(tool_cls, "bl_description", tool_cls.__doc__),
|
||||
"icon": getattr(tool_cls, "bl_icon", None),
|
||||
"cursor": getattr(tool_cls, "bl_cursor", None),
|
||||
"options": getattr(tool_cls, "bl_options", None),
|
||||
"widget": getattr(tool_cls, "bl_widget", None),
|
||||
"widget_properties": getattr(tool_cls, "bl_widget_properties", None),
|
||||
"keymap": getattr(tool_cls, "bl_keymap", None),
|
||||
"data_block": getattr(tool_cls, "bl_data_block", None),
|
||||
"operator": getattr(tool_cls, "bl_operator", None),
|
||||
"draw_settings": getattr(tool_cls, "draw_settings", None),
|
||||
"draw_cursor": getattr(tool_cls, "draw_cursor", None),
|
||||
})
|
||||
tool_cls._bl_tool = tool_def
|
||||
|
||||
keymap_data = tool_def.keymap
|
||||
if keymap_data is not None:
|
||||
if context_mode is None:
|
||||
context_descr = "All"
|
||||
else:
|
||||
context_descr = context_mode.replace("_", " ").title()
|
||||
from bpy import context
|
||||
wm = context.window_manager
|
||||
keyconfigs = wm.keyconfigs
|
||||
kc_default = keyconfigs.default
|
||||
# Note that Blender's default tools use the default key-config for both.
|
||||
# We need to use the add-ons for 3rd party tools so reloading the key-map doesn't clear them.
|
||||
kc = keyconfigs.addon
|
||||
if callable(keymap_data[0]):
|
||||
cls._km_action_simple(kc_default, kc, context_descr, tool_def.label, keymap_data)
|
||||
return tool_def
|
||||
|
||||
tool_converted = tool_from_class(tool_cls)
|
||||
|
||||
if group:
|
||||
# Create a new group
|
||||
tool_converted = (tool_converted,)
|
||||
|
||||
tool_def_insert = (
|
||||
(None, tool_converted) if separator else
|
||||
(tool_converted,)
|
||||
)
|
||||
|
||||
def skip_to_end_of_group(seq, i):
|
||||
i_prev = i
|
||||
while i < len(seq) and seq[i] is not None:
|
||||
i_prev = i
|
||||
i += 1
|
||||
return i_prev
|
||||
|
||||
changed = False
|
||||
if after is not None:
|
||||
for i, item in enumerate(tools):
|
||||
if item is None:
|
||||
pass
|
||||
elif isinstance(item, ToolDef):
|
||||
if item.idname in after:
|
||||
i = skip_to_end_of_group(item, i)
|
||||
tools[i + 1:i + 1] = tool_def_insert
|
||||
changed = True
|
||||
break
|
||||
elif isinstance(item, tuple):
|
||||
for j, sub_item in enumerate(item, 1):
|
||||
if isinstance(sub_item, ToolDef):
|
||||
if sub_item.idname in after:
|
||||
if group:
|
||||
# Can't add a group within a group,
|
||||
# add a new group after this group.
|
||||
i = skip_to_end_of_group(tools, i)
|
||||
tools[i + 1:i + 1] = tool_def_insert
|
||||
else:
|
||||
j = skip_to_end_of_group(item, j)
|
||||
item = item[:j + 1] + tool_def_insert + item[j + 1:]
|
||||
tools[i] = item
|
||||
changed = True
|
||||
break
|
||||
if changed:
|
||||
break
|
||||
|
||||
if not changed:
|
||||
print("bpy.utils.register_tool: could not find 'after'", after)
|
||||
if not changed:
|
||||
tools.extend(tool_def_insert)
|
||||
|
||||
|
||||
def unregister_tool(tool_cls):
|
||||
space_type = tool_cls.bl_space_type
|
||||
context_mode = tool_cls.bl_context_mode
|
||||
|
||||
from bl_ui.space_toolsystem_common import ToolSelectPanelHelper
|
||||
cls = ToolSelectPanelHelper._tool_class_from_space_type(space_type)
|
||||
if cls is None:
|
||||
raise Exception("Space type %r has no toolbar" % space_type)
|
||||
tools = cls._tools[context_mode]
|
||||
|
||||
tool_def = tool_cls._bl_tool
|
||||
try:
|
||||
i = tools.index(tool_def)
|
||||
except ValueError:
|
||||
i = -1
|
||||
|
||||
def tool_list_clean(tool_list):
|
||||
# Trim separators.
|
||||
while tool_list and tool_list[-1] is None:
|
||||
del tool_list[-1]
|
||||
while tool_list and tool_list[0] is None:
|
||||
del tool_list[0]
|
||||
# Remove duplicate separators.
|
||||
for i in range(len(tool_list) - 1, -1, -1):
|
||||
is_none = tool_list[i] is None
|
||||
if is_none and prev_is_none:
|
||||
del tool_list[i]
|
||||
prev_is_none = is_none
|
||||
|
||||
changed = False
|
||||
if i != -1:
|
||||
del tools[i]
|
||||
tool_list_clean(tools)
|
||||
changed = True
|
||||
|
||||
if not changed:
|
||||
for i, item in enumerate(tools):
|
||||
if isinstance(item, tuple):
|
||||
try:
|
||||
j = item.index(tool_def)
|
||||
except ValueError:
|
||||
j = -1
|
||||
|
||||
if j != -1:
|
||||
item_clean = list(item)
|
||||
del item_clean[j]
|
||||
tool_list_clean(item_clean)
|
||||
if item_clean:
|
||||
tools[i] = tuple(item_clean)
|
||||
else:
|
||||
del tools[i]
|
||||
tool_list_clean(tools)
|
||||
del item_clean
|
||||
|
||||
# tuple(sub_item for sub_item in items if sub_item is not tool_def)
|
||||
changed = True
|
||||
break
|
||||
|
||||
if not changed:
|
||||
raise Exception("Unable to remove %r" % tool_cls)
|
||||
del tool_cls._bl_tool
|
||||
|
||||
keymap_data = tool_def.keymap
|
||||
if keymap_data is not None:
|
||||
from bpy import context
|
||||
wm = context.window_manager
|
||||
keyconfigs = wm.keyconfigs
|
||||
for kc in (keyconfigs.default, keyconfigs.addon):
|
||||
km = kc.keymaps.get(keymap_data[0])
|
||||
if km is None:
|
||||
print("Warning keymap %r not found in %r!" % (keymap_data[0], kc.name))
|
||||
else:
|
||||
kc.keymaps.remove(km)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Manual lookups, each function has to return a basepath and a sequence
|
||||
# of...
|
||||
|
||||
# we start with the built-in default mapping
|
||||
def _blender_default_map():
|
||||
# NOTE(@ideasman42): Avoid importing this as there is no need to keep the lookup table in memory.
|
||||
# As this runs when the user accesses the "Online Manual", the overhead loading the file is acceptable.
|
||||
# In my tests it's under 1/100th of a second loading from a `pyc`.
|
||||
ref_mod = execfile(_os.path.join(_script_base_dir, "modules", "rna_manual_reference.py"))
|
||||
return (ref_mod.url_manual_prefix, ref_mod.url_manual_mapping)
|
||||
|
||||
|
||||
# hooks for doc lookups
|
||||
_manual_map = [_blender_default_map]
|
||||
|
||||
|
||||
def register_manual_map(manual_hook):
|
||||
_manual_map.append(manual_hook)
|
||||
|
||||
|
||||
def unregister_manual_map(manual_hook):
|
||||
_manual_map.remove(manual_hook)
|
||||
|
||||
|
||||
def manual_map():
|
||||
# reverse so default is called last
|
||||
for cb in reversed(_manual_map):
|
||||
try:
|
||||
prefix, url_manual_mapping = cb()
|
||||
except:
|
||||
print("Error calling %r" % cb)
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
continue
|
||||
|
||||
yield prefix, url_manual_mapping
|
||||
|
||||
|
||||
# Languages which are supported by the user manual (commented when there is no translation).
|
||||
_manual_language_codes = {
|
||||
"ar_EG": "ar", # Arabic
|
||||
# "bg_BG": "bg", # Bulgarian
|
||||
# "ca_AD": "ca", # Catalan
|
||||
# "cs_CZ": "cz", # Czech
|
||||
"de_DE": "de", # German
|
||||
# "el_GR": "el", # Greek
|
||||
"es": "es", # Spanish
|
||||
"fi_FI": "fi", # Finnish
|
||||
"fr_FR": "fr", # French
|
||||
"id_ID": "id", # Indonesian
|
||||
"it_IT": "it", # Italian
|
||||
"ja_JP": "ja", # Japanese
|
||||
"ko_KR": "ko", # Korean
|
||||
# "nb": "nb", # Norwegian
|
||||
# "nl_NL": "nl", # Dutch
|
||||
# "pl_PL": "pl", # Polish
|
||||
"pt_PT": "pt", # Portuguese
|
||||
# Portuguese - Brazil, for until we have a pt_BR version.
|
||||
"pt_BR": "pt",
|
||||
"ru_RU": "ru", # Russian
|
||||
"sk_SK": "sk", # Slovak
|
||||
# "sl": "sl", # Slovenian
|
||||
"sr_RS": "sr", # Serbian
|
||||
# "sv_SE": "sv", # Swedish
|
||||
# "tr_TR": "th", # Thai
|
||||
"uk_UA": "uk", # Ukrainian
|
||||
"vi_VN": "vi", # Vietnamese
|
||||
"zh_CN": "zh-hans", # Simplified Chinese
|
||||
"zh_TW": "zh-hant", # Traditional Chinese
|
||||
}
|
||||
|
||||
|
||||
def manual_language_code(default="en"):
|
||||
"""
|
||||
:return:
|
||||
The language code used for user manual URL component based on the current language user-preference,
|
||||
falling back to the ``default`` when unavailable.
|
||||
:rtype: str
|
||||
"""
|
||||
language = _bpy.context.preferences.view.language
|
||||
if language == 'DEFAULT':
|
||||
language = _os.getenv("LANG", "").split(".")[0]
|
||||
return _manual_language_codes.get(language, default)
|
||||
|
||||
|
||||
# Build an RNA path from struct/property/enum names.
|
||||
def make_rna_paths(struct_name, prop_name, enum_name):
|
||||
"""
|
||||
Create RNA "paths" from given names.
|
||||
|
||||
:arg struct_name: Name of a RNA struct (like e.g. "Scene").
|
||||
:type struct_name: string
|
||||
:arg prop_name: Name of a RNA struct's property.
|
||||
:type prop_name: string
|
||||
:arg enum_name: Name of a RNA enum identifier.
|
||||
:type enum_name: string
|
||||
:return: A triple of three "RNA paths"
|
||||
(most_complete_path, "struct.prop", "struct.prop:'enum'").
|
||||
If no enum_name is given, the third element will always be void.
|
||||
:rtype: tuple of strings
|
||||
"""
|
||||
src = src_rna = src_enum = ""
|
||||
if struct_name:
|
||||
if prop_name:
|
||||
src = src_rna = ".".join((struct_name, prop_name))
|
||||
if enum_name:
|
||||
src = src_enum = "%s:'%s'" % (src_rna, enum_name)
|
||||
else:
|
||||
src = src_rna = struct_name
|
||||
return src, src_rna, src_enum
|
||||
137
scripts/modules/bpy/utils/previews.py
Normal file
137
scripts/modules/bpy/utils/previews.py
Normal file
@@ -0,0 +1,137 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module contains utility functions to handle custom previews.
|
||||
|
||||
It behaves as a high-level 'cached' previews manager.
|
||||
|
||||
This allows scripts to generate their own previews, and use them as icons in UI widgets
|
||||
('icon_value' for UILayout functions).
|
||||
|
||||
|
||||
Custom Icon Example
|
||||
-------------------
|
||||
|
||||
.. literalinclude:: __/__/__/scripts/templates_py/ui_previews_custom_icon.py
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"new",
|
||||
"remove",
|
||||
"ImagePreviewCollection",
|
||||
)
|
||||
|
||||
import _bpy
|
||||
_utils_previews = _bpy._utils_previews
|
||||
del _bpy
|
||||
|
||||
|
||||
_uuid_open = set()
|
||||
|
||||
|
||||
# High-level previews manager.
|
||||
# not accessed directly
|
||||
class ImagePreviewCollection(dict):
|
||||
"""
|
||||
Dictionary-like class of previews.
|
||||
|
||||
This is a subclass of Python's built-in dict type,
|
||||
used to store multiple image previews.
|
||||
|
||||
.. note::
|
||||
|
||||
- instance with :mod:`bpy.utils.previews.new`
|
||||
- keys must be ``str`` type.
|
||||
- values will be :class:`bpy.types.ImagePreview`
|
||||
"""
|
||||
|
||||
# Internal notes:
|
||||
# - Blender's internal 'PreviewImage' struct uses 'self._uuid' prefix.
|
||||
# - Blender's preview.new/load return the data if it exists,
|
||||
# don't do this for the Python API as it allows accidental re-use of names,
|
||||
# anyone who wants to reuse names can use dict.get() to check if it exists.
|
||||
# We could use this for the C API too (would need some investigation).
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self._uuid = hex(id(self))
|
||||
_uuid_open.add(self._uuid)
|
||||
|
||||
def __del__(self):
|
||||
if self._uuid not in _uuid_open:
|
||||
return
|
||||
|
||||
raise ResourceWarning(
|
||||
"%r: left open, remove with 'bpy.utils.previews.remove()'" % self
|
||||
)
|
||||
self.close()
|
||||
|
||||
def _gen_key(self, name):
|
||||
return ":".join((self._uuid, name))
|
||||
|
||||
def new(self, name):
|
||||
if name in self:
|
||||
raise KeyError("key %r already exists" % name)
|
||||
p = self[name] = _utils_previews.new(
|
||||
self._gen_key(name))
|
||||
return p
|
||||
new.__doc__ = _utils_previews.new.__doc__
|
||||
|
||||
def load(self, name, path, path_type, force_reload=False):
|
||||
if name in self:
|
||||
raise KeyError("key %r already exists" % name)
|
||||
p = self[name] = _utils_previews.load(
|
||||
self._gen_key(name), path, path_type, force_reload)
|
||||
return p
|
||||
load.__doc__ = _utils_previews.load.__doc__
|
||||
|
||||
def clear(self):
|
||||
"""Clear all previews."""
|
||||
for name in self.keys():
|
||||
_utils_previews.release(self._gen_key(name))
|
||||
super().clear()
|
||||
|
||||
def close(self):
|
||||
"""Close the collection and clear all previews."""
|
||||
self.clear()
|
||||
_uuid_open.remove(self._uuid)
|
||||
|
||||
def __delitem__(self, key):
|
||||
_utils_previews.release(self._gen_key(key))
|
||||
super().__delitem__(key)
|
||||
|
||||
def __repr__(self):
|
||||
return "<%s id=%s[%d], %r>" % (
|
||||
self.__class__.__name__, self._uuid, len(self), super()
|
||||
)
|
||||
|
||||
|
||||
def new():
|
||||
"""
|
||||
:return: a new preview collection.
|
||||
:rtype: :class:`ImagePreviewCollection`
|
||||
"""
|
||||
|
||||
return ImagePreviewCollection()
|
||||
|
||||
|
||||
def remove(pcoll):
|
||||
"""
|
||||
Remove the specified previews collection.
|
||||
|
||||
:arg pcoll: Preview collection to close.
|
||||
:type pcoll: :class:`ImagePreviewCollection`
|
||||
"""
|
||||
pcoll.close()
|
||||
|
||||
|
||||
# don't complain about resources on exit (only unregister)
|
||||
import atexit
|
||||
|
||||
|
||||
def exit_clear_warning():
|
||||
del ImagePreviewCollection.__del__
|
||||
|
||||
|
||||
atexit.register(exit_clear_warning)
|
||||
del atexit, exit_clear_warning
|
||||
5
scripts/modules/bpy/utils/toolsystem.py
Normal file
5
scripts/modules/bpy/utils/toolsystem.py
Normal file
@@ -0,0 +1,5 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# Until we untangle ToolDef from bl_ui internals,
|
||||
# use this module to document ToolDef.
|
||||
from bl_ui.space_toolsystem_common import ToolDef
|
||||
18
scripts/modules/bpy_extras/__init__.py
Normal file
18
scripts/modules/bpy_extras/__init__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Utility modules associated with the bpy module.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"anim_utils",
|
||||
"asset_utils",
|
||||
"object_utils",
|
||||
"io_utils",
|
||||
"image_utils",
|
||||
"keyconfig_utils",
|
||||
"mesh_utils",
|
||||
"node_utils",
|
||||
"view3d_utils",
|
||||
"id_map_utils",
|
||||
)
|
||||
574
scripts/modules/bpy_extras/anim_utils.py
Normal file
574
scripts/modules/bpy_extras/anim_utils.py
Normal file
@@ -0,0 +1,574 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"bake_action",
|
||||
"bake_action_objects",
|
||||
|
||||
"bake_action_iter",
|
||||
"bake_action_objects_iter",
|
||||
)
|
||||
|
||||
import bpy
|
||||
from bpy.types import Action
|
||||
|
||||
from typing import (
|
||||
List,
|
||||
Mapping,
|
||||
Sequence,
|
||||
Tuple,
|
||||
)
|
||||
|
||||
FCurveKey = Tuple[
|
||||
# `fcurve.data_path`.
|
||||
str,
|
||||
# `fcurve.array_index`.
|
||||
int,
|
||||
]
|
||||
|
||||
# List of `[frame0, value0, frame1, value1, ...]` pairs.
|
||||
ListKeyframes = List[float]
|
||||
|
||||
|
||||
def bake_action(
|
||||
obj,
|
||||
*,
|
||||
action, frames,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
:arg obj: Object to bake.
|
||||
:type obj: :class:`bpy.types.Object`
|
||||
:arg action: An action to bake the data into, or None for a new action
|
||||
to be created.
|
||||
:type action: :class:`bpy.types.Action` or None
|
||||
:arg frames: Frames to bake.
|
||||
:type frames: iterable of int
|
||||
|
||||
:return: an action or None
|
||||
:rtype: :class:`bpy.types.Action`
|
||||
"""
|
||||
if not (kwargs.get("do_pose") or kwargs.get("do_object")):
|
||||
return None
|
||||
|
||||
action, = bake_action_objects(
|
||||
[(obj, action)],
|
||||
frames=frames,
|
||||
**kwargs,
|
||||
)
|
||||
return action
|
||||
|
||||
|
||||
def bake_action_objects(
|
||||
object_action_pairs,
|
||||
*,
|
||||
frames,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
A version of :func:`bake_action_objects_iter` that takes frames and returns the output.
|
||||
|
||||
:arg frames: Frames to bake.
|
||||
:type frames: iterable of int
|
||||
|
||||
:return: A sequence of Action or None types (aligned with `object_action_pairs`)
|
||||
:rtype: sequence of :class:`bpy.types.Action`
|
||||
"""
|
||||
iter = bake_action_objects_iter(object_action_pairs, **kwargs)
|
||||
iter.send(None)
|
||||
for frame in frames:
|
||||
iter.send(frame)
|
||||
return iter.send(None)
|
||||
|
||||
|
||||
def bake_action_objects_iter(
|
||||
object_action_pairs,
|
||||
**kwargs
|
||||
):
|
||||
"""
|
||||
An coroutine that bakes actions for multiple objects.
|
||||
|
||||
:arg object_action_pairs: Sequence of object action tuples,
|
||||
action is the destination for the baked data. When None a new action will be created.
|
||||
:type object_action_pairs: Sequence of (:class:`bpy.types.Object`, :class:`bpy.types.Action`)
|
||||
"""
|
||||
scene = bpy.context.scene
|
||||
frame_back = scene.frame_current
|
||||
iter_all = tuple(
|
||||
bake_action_iter(obj, action=action, **kwargs)
|
||||
for (obj, action) in object_action_pairs
|
||||
)
|
||||
for iter in iter_all:
|
||||
iter.send(None)
|
||||
while True:
|
||||
frame = yield None
|
||||
if frame is None:
|
||||
break
|
||||
scene.frame_set(frame)
|
||||
bpy.context.view_layer.update()
|
||||
for iter in iter_all:
|
||||
iter.send(frame)
|
||||
scene.frame_set(frame_back)
|
||||
yield tuple(iter.send(None) for iter in iter_all)
|
||||
|
||||
|
||||
# XXX visual keying is actually always considered as True in this code...
|
||||
def bake_action_iter(
|
||||
obj,
|
||||
*,
|
||||
action,
|
||||
only_selected=False,
|
||||
do_pose=True,
|
||||
do_object=True,
|
||||
do_visual_keying=True,
|
||||
do_constraint_clear=False,
|
||||
do_parents_clear=False,
|
||||
do_clean=False
|
||||
):
|
||||
"""
|
||||
An coroutine that bakes action for a single object.
|
||||
|
||||
:arg obj: Object to bake.
|
||||
:type obj: :class:`bpy.types.Object`
|
||||
:arg action: An action to bake the data into, or None for a new action
|
||||
to be created.
|
||||
:type action: :class:`bpy.types.Action` or None
|
||||
:arg only_selected: Only bake selected bones.
|
||||
:type only_selected: bool
|
||||
:arg do_pose: Bake pose channels.
|
||||
:type do_pose: bool
|
||||
:arg do_object: Bake objects.
|
||||
:type do_object: bool
|
||||
:arg do_visual_keying: Use the final transformations for baking ('visual keying')
|
||||
:type do_visual_keying: bool
|
||||
:arg do_constraint_clear: Remove constraints after baking.
|
||||
:type do_constraint_clear: bool
|
||||
:arg do_parents_clear: Unparent after baking objects.
|
||||
:type do_parents_clear: bool
|
||||
:arg do_clean: Remove redundant keyframes after baking.
|
||||
:type do_clean: bool
|
||||
|
||||
:return: an action or None
|
||||
:rtype: :class:`bpy.types.Action`
|
||||
"""
|
||||
# -------------------------------------------------------------------------
|
||||
# Helper Functions and vars
|
||||
|
||||
# Note: BBONE_PROPS is a list so we can preserve the ordering
|
||||
BBONE_PROPS = [
|
||||
"bbone_curveinx", "bbone_curveoutx",
|
||||
"bbone_curveinz", "bbone_curveoutz",
|
||||
"bbone_rollin", "bbone_rollout",
|
||||
"bbone_scalein", "bbone_scaleout",
|
||||
"bbone_easein", "bbone_easeout",
|
||||
]
|
||||
BBONE_PROPS_LENGTHS = {
|
||||
"bbone_curveinx": 1,
|
||||
"bbone_curveoutx": 1,
|
||||
"bbone_curveinz": 1,
|
||||
"bbone_curveoutz": 1,
|
||||
"bbone_rollin": 1,
|
||||
"bbone_rollout": 1,
|
||||
"bbone_scalein": 3,
|
||||
"bbone_scaleout": 3,
|
||||
"bbone_easein": 1,
|
||||
"bbone_easeout": 1,
|
||||
}
|
||||
|
||||
def pose_frame_info(obj):
|
||||
matrix = {}
|
||||
bbones = {}
|
||||
for name, pbone in obj.pose.bones.items():
|
||||
if do_visual_keying:
|
||||
# Get the final transform of the bone in its own local space...
|
||||
matrix[name] = obj.convert_space(pose_bone=pbone, matrix=pbone.matrix,
|
||||
from_space='POSE', to_space='LOCAL')
|
||||
else:
|
||||
matrix[name] = pbone.matrix_basis.copy()
|
||||
|
||||
# Bendy Bones
|
||||
if pbone.bone.bbone_segments > 1:
|
||||
bbones[name] = {bb_prop: getattr(pbone, bb_prop) for bb_prop in BBONE_PROPS}
|
||||
return matrix, bbones
|
||||
|
||||
if do_parents_clear:
|
||||
if do_visual_keying:
|
||||
def obj_frame_info(obj):
|
||||
return obj.matrix_world.copy()
|
||||
else:
|
||||
def obj_frame_info(obj):
|
||||
parent = obj.parent
|
||||
matrix = obj.matrix_basis
|
||||
if parent:
|
||||
return parent.matrix_world @ matrix
|
||||
else:
|
||||
return matrix.copy()
|
||||
else:
|
||||
if do_visual_keying:
|
||||
def obj_frame_info(obj):
|
||||
parent = obj.parent
|
||||
matrix = obj.matrix_world
|
||||
if parent:
|
||||
return parent.matrix_world.inverted_safe() @ matrix
|
||||
else:
|
||||
return matrix.copy()
|
||||
else:
|
||||
def obj_frame_info(obj):
|
||||
return obj.matrix_basis.copy()
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Setup the Context
|
||||
|
||||
if obj.pose is None:
|
||||
do_pose = False
|
||||
|
||||
if not (do_pose or do_object):
|
||||
raise Exception("Pose and object baking is disabled, no action needed")
|
||||
|
||||
pose_info = []
|
||||
obj_info = []
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Collect transformations
|
||||
|
||||
while True:
|
||||
# Caller is responsible for setting the frame and updating the scene.
|
||||
frame = yield None
|
||||
|
||||
# Signal we're done!
|
||||
if frame is None:
|
||||
break
|
||||
|
||||
if do_pose:
|
||||
pose_info.append((frame, *pose_frame_info(obj)))
|
||||
if do_object:
|
||||
obj_info.append((frame, obj_frame_info(obj)))
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Clean (store initial data)
|
||||
if do_clean and action is not None:
|
||||
clean_orig_data = {fcu: {p.co[1] for p in fcu.keyframe_points} for fcu in action.fcurves}
|
||||
else:
|
||||
clean_orig_data = {}
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Create action
|
||||
|
||||
# in case animation data hasn't been created
|
||||
atd = obj.animation_data_create()
|
||||
is_new_action = action is None
|
||||
if is_new_action:
|
||||
action = bpy.data.actions.new("Action")
|
||||
|
||||
# Only leave tweak mode if we actually need to modify the action (#57159)
|
||||
if action != atd.action:
|
||||
# Leave tweak mode before trying to modify the action (#48397)
|
||||
if atd.use_tweak_mode:
|
||||
atd.use_tweak_mode = False
|
||||
|
||||
atd.action = action
|
||||
|
||||
# Baking the action only makes sense in Replace mode, so force it (#69105)
|
||||
if not atd.use_tweak_mode:
|
||||
atd.action_blend_type = 'REPLACE'
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Apply transformations to action
|
||||
|
||||
# pose
|
||||
lookup_fcurves = {(fcurve.data_path, fcurve.array_index): fcurve for fcurve in action.fcurves}
|
||||
if do_pose:
|
||||
for name, pbone in obj.pose.bones.items():
|
||||
if only_selected and not pbone.bone.select:
|
||||
continue
|
||||
|
||||
if do_constraint_clear:
|
||||
while pbone.constraints:
|
||||
pbone.constraints.remove(pbone.constraints[0])
|
||||
|
||||
# Create compatible eulers, quats.
|
||||
euler_prev = None
|
||||
quat_prev = None
|
||||
|
||||
base_fcurve_path = pbone.path_from_id() + "."
|
||||
path_location = base_fcurve_path + "location"
|
||||
path_quaternion = base_fcurve_path + "rotation_quaternion"
|
||||
path_axis_angle = base_fcurve_path + "rotation_axis_angle"
|
||||
path_euler = base_fcurve_path + "rotation_euler"
|
||||
path_scale = base_fcurve_path + "scale"
|
||||
paths_bbprops = [(base_fcurve_path + bbprop) for bbprop in BBONE_PROPS]
|
||||
|
||||
keyframes = KeyframesCo()
|
||||
keyframes.add_paths(path_location, 3)
|
||||
keyframes.add_paths(path_quaternion, 4)
|
||||
keyframes.add_paths(path_axis_angle, 4)
|
||||
keyframes.add_paths(path_euler, 3)
|
||||
keyframes.add_paths(path_scale, 3)
|
||||
|
||||
if pbone.bone.bbone_segments > 1:
|
||||
for prop_name, path in zip(BBONE_PROPS, paths_bbprops):
|
||||
keyframes.add_paths(path, BBONE_PROPS_LENGTHS[prop_name])
|
||||
|
||||
rotation_mode = pbone.rotation_mode
|
||||
total_new_keys = len(pose_info)
|
||||
for (f, matrix, bbones) in pose_info:
|
||||
pbone.matrix_basis = matrix[name].copy()
|
||||
|
||||
keyframes.extend_co_values(path_location, 3, f, pbone.location)
|
||||
|
||||
if rotation_mode == 'QUATERNION':
|
||||
if quat_prev is not None:
|
||||
quat = pbone.rotation_quaternion.copy()
|
||||
quat.make_compatible(quat_prev)
|
||||
pbone.rotation_quaternion = quat
|
||||
quat_prev = quat
|
||||
del quat
|
||||
else:
|
||||
quat_prev = pbone.rotation_quaternion.copy()
|
||||
keyframes.extend_co_values(path_quaternion, 4, f, pbone.rotation_quaternion)
|
||||
elif rotation_mode == 'AXIS_ANGLE':
|
||||
keyframes.extend_co_values(path_axis_angle, 4, f, pbone.rotation_axis_angle)
|
||||
else: # euler, XYZ, ZXY etc
|
||||
if euler_prev is not None:
|
||||
euler = pbone.matrix_basis.to_euler(pbone.rotation_mode, euler_prev)
|
||||
pbone.rotation_euler = euler
|
||||
del euler
|
||||
euler_prev = pbone.rotation_euler.copy()
|
||||
keyframes.extend_co_values(path_euler, 3, f, pbone.rotation_euler)
|
||||
|
||||
keyframes.extend_co_values(path_scale, 3, f, pbone.scale)
|
||||
|
||||
# Bendy Bones
|
||||
if pbone.bone.bbone_segments > 1:
|
||||
bbone_shape = bbones[name]
|
||||
for prop_index, prop_name in enumerate(BBONE_PROPS):
|
||||
prop_len = BBONE_PROPS_LENGTHS[prop_name]
|
||||
if prop_len > 1:
|
||||
keyframes.extend_co_values(
|
||||
paths_bbprops[prop_index], prop_len, f, bbone_shape[prop_name]
|
||||
)
|
||||
else:
|
||||
keyframes.extend_co_value(
|
||||
paths_bbprops[prop_index], f, bbone_shape[prop_name]
|
||||
)
|
||||
|
||||
if is_new_action:
|
||||
keyframes.insert_keyframes_into_new_action(total_new_keys, action, name)
|
||||
else:
|
||||
keyframes.insert_keyframes_into_existing_action(lookup_fcurves, total_new_keys, action, name)
|
||||
|
||||
# object. TODO. multiple objects
|
||||
if do_object:
|
||||
if do_constraint_clear:
|
||||
while obj.constraints:
|
||||
obj.constraints.remove(obj.constraints[0])
|
||||
|
||||
# Create compatible eulers, quats.
|
||||
euler_prev = None
|
||||
quat_prev = None
|
||||
|
||||
path_location = "location"
|
||||
path_quaternion = "rotation_quaternion"
|
||||
path_axis_angle = "rotation_axis_angle"
|
||||
path_euler = "rotation_euler"
|
||||
path_scale = "scale"
|
||||
|
||||
keyframes = KeyframesCo()
|
||||
keyframes.add_paths(path_location, 3)
|
||||
keyframes.add_paths(path_quaternion, 4)
|
||||
keyframes.add_paths(path_axis_angle, 4)
|
||||
keyframes.add_paths(path_euler, 3)
|
||||
keyframes.add_paths(path_scale, 3)
|
||||
|
||||
rotation_mode = obj.rotation_mode
|
||||
total_new_keys = len(obj_info)
|
||||
for (f, matrix) in obj_info:
|
||||
name = "Action Bake" # XXX: placeholder
|
||||
obj.matrix_basis = matrix
|
||||
|
||||
keyframes.extend_co_values(path_location, 3, f, obj.location)
|
||||
|
||||
if rotation_mode == 'QUATERNION':
|
||||
if quat_prev is not None:
|
||||
quat = obj.rotation_quaternion.copy()
|
||||
quat.make_compatible(quat_prev)
|
||||
obj.rotation_quaternion = quat
|
||||
quat_prev = quat
|
||||
del quat
|
||||
else:
|
||||
quat_prev = obj.rotation_quaternion.copy()
|
||||
keyframes.extend_co_values(path_quaternion, 4, f, obj.rotation_quaternion)
|
||||
|
||||
elif rotation_mode == 'AXIS_ANGLE':
|
||||
keyframes.extend_co_values(path_axis_angle, 4, f, obj.rotation_axis_angle)
|
||||
else: # euler, XYZ, ZXY etc
|
||||
if euler_prev is not None:
|
||||
obj.rotation_euler = matrix.to_euler(obj.rotation_mode, euler_prev)
|
||||
euler_prev = obj.rotation_euler.copy()
|
||||
keyframes.extend_co_values(path_euler, 3, f, obj.rotation_euler)
|
||||
|
||||
keyframes.extend_co_values(path_scale, 3, f, obj.scale)
|
||||
|
||||
if is_new_action:
|
||||
keyframes.insert_keyframes_into_new_action(total_new_keys, action, name)
|
||||
else:
|
||||
keyframes.insert_keyframes_into_existing_action(lookup_fcurves, total_new_keys, action, name)
|
||||
|
||||
if do_parents_clear:
|
||||
obj.parent = None
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Clean
|
||||
|
||||
if do_clean:
|
||||
for fcu in action.fcurves:
|
||||
fcu_orig_data = clean_orig_data.get(fcu, set())
|
||||
|
||||
keyframe_points = fcu.keyframe_points
|
||||
i = 1
|
||||
while i < len(keyframe_points) - 1:
|
||||
val = keyframe_points[i].co[1]
|
||||
|
||||
if val in fcu_orig_data:
|
||||
i += 1
|
||||
continue
|
||||
|
||||
val_prev = keyframe_points[i - 1].co[1]
|
||||
val_next = keyframe_points[i + 1].co[1]
|
||||
|
||||
if abs(val - val_prev) + abs(val - val_next) < 0.0001:
|
||||
keyframe_points.remove(keyframe_points[i])
|
||||
else:
|
||||
i += 1
|
||||
|
||||
yield action
|
||||
|
||||
|
||||
class KeyframesCo:
|
||||
"""
|
||||
A buffer for keyframe Co unpacked values per ``FCurveKey``. ``FCurveKeys`` are added using
|
||||
``add_paths()``, Co values stored using extend_co_values(), then finally use
|
||||
``insert_keyframes_into_*_action()`` for efficiently inserting keys into the F-curves.
|
||||
|
||||
Users are limited to one Action Group per instance.
|
||||
"""
|
||||
__slots__ = (
|
||||
"keyframes_from_fcurve",
|
||||
)
|
||||
|
||||
# `keyframes[(rna_path, array_index)] = list(time0,value0, time1,value1,...)`.
|
||||
keyframes_from_fcurve: Mapping[FCurveKey, ListKeyframes]
|
||||
|
||||
def __init__(self):
|
||||
self.keyframes_from_fcurve = {}
|
||||
|
||||
def add_paths(
|
||||
self,
|
||||
rna_path: str,
|
||||
total_indices: int,
|
||||
) -> None:
|
||||
keyframes_from_fcurve = self.keyframes_from_fcurve
|
||||
for array_index in range(0, total_indices):
|
||||
keyframes_from_fcurve[(rna_path, array_index)] = []
|
||||
|
||||
def extend_co_values(
|
||||
self,
|
||||
rna_path: str,
|
||||
total_indices: int,
|
||||
frame: float,
|
||||
values: Sequence[float],
|
||||
) -> None:
|
||||
keyframes_from_fcurve = self.keyframes_from_fcurve
|
||||
for array_index in range(0, total_indices):
|
||||
keyframes_from_fcurve[(rna_path, array_index)].extend((frame, values[array_index]))
|
||||
|
||||
def extend_co_value(
|
||||
self,
|
||||
rna_path: str,
|
||||
frame: float,
|
||||
value: float,
|
||||
) -> None:
|
||||
self.keyframes_from_fcurve[(rna_path, 0)].extend((frame, value))
|
||||
|
||||
def insert_keyframes_into_new_action(
|
||||
self,
|
||||
total_new_keys: int,
|
||||
action: Action,
|
||||
action_group_name: str,
|
||||
) -> None:
|
||||
"""
|
||||
Assumes the action is new, that it has no F-curves. Otherwise, the only difference between versions is
|
||||
performance and implementation simplicity.
|
||||
|
||||
:arg action_group_name: Name of Action Group that F-curves are added to.
|
||||
:type action_group_name: str
|
||||
"""
|
||||
linear_enum_values = [
|
||||
bpy.types.Keyframe.bl_rna.properties["interpolation"].enum_items["LINEAR"].value
|
||||
] * total_new_keys
|
||||
|
||||
for fc_key, key_values in self.keyframes_from_fcurve.items():
|
||||
if len(key_values) == 0:
|
||||
continue
|
||||
|
||||
data_path, array_index = fc_key
|
||||
keyframe_points = action.fcurves.new(
|
||||
data_path, index=array_index, action_group=action_group_name
|
||||
).keyframe_points
|
||||
|
||||
keyframe_points.add(total_new_keys)
|
||||
keyframe_points.foreach_set("co", key_values)
|
||||
keyframe_points.foreach_set("interpolation", linear_enum_values)
|
||||
|
||||
# There's no need to do fcurve.update() because the keys are already ordered, have
|
||||
# no duplicates and all handles are Linear.
|
||||
|
||||
def insert_keyframes_into_existing_action(
|
||||
self,
|
||||
lookup_fcurves: Mapping[FCurveKey, bpy.types.FCurve],
|
||||
total_new_keys: int,
|
||||
action: Action,
|
||||
action_group_name: str,
|
||||
) -> None:
|
||||
"""
|
||||
Assumes the action already exists, that it might already have F-curves. Otherwise, the
|
||||
only difference between versions is performance and implementation simplicity.
|
||||
|
||||
:arg lookup_fcurves: : This is only used for efficiency.
|
||||
It's a substitute for ``action.fcurves.find()`` which is a potentially expensive linear search.
|
||||
:type lookup_fcurves: ``Mapping[FCurveKey, bpy.types.FCurve]``
|
||||
:arg action_group_name: Name of Action Group that F-curves are added to.
|
||||
:type action_group_name: str
|
||||
"""
|
||||
linear_enum_values = [
|
||||
bpy.types.Keyframe.bl_rna.properties["interpolation"].enum_items["LINEAR"].value
|
||||
] * total_new_keys
|
||||
|
||||
for fc_key, key_values in self.keyframes_from_fcurve.items():
|
||||
if len(key_values) == 0:
|
||||
continue
|
||||
|
||||
fcurve = lookup_fcurves.get(fc_key, None)
|
||||
if fcurve is None:
|
||||
data_path, array_index = fc_key
|
||||
fcurve = action.fcurves.new(
|
||||
data_path, index=array_index, action_group=action_group_name
|
||||
)
|
||||
|
||||
keyframe_points = fcurve.keyframe_points
|
||||
|
||||
co_buffer = [0] * (2 * len(keyframe_points))
|
||||
keyframe_points.foreach_get("co", co_buffer)
|
||||
co_buffer.extend(key_values)
|
||||
|
||||
ipo_buffer = [None] * len(keyframe_points)
|
||||
keyframe_points.foreach_get("interpolation", ipo_buffer)
|
||||
ipo_buffer.extend(linear_enum_values)
|
||||
|
||||
# XXX: Currently baking inserts the same number of keys for all baked properties.
|
||||
# This block of code breaks if that's no longer true since we then will not be properly
|
||||
# initializing all the data.
|
||||
keyframe_points.add(total_new_keys)
|
||||
keyframe_points.foreach_set("co", co_buffer)
|
||||
keyframe_points.foreach_set("interpolation", ipo_buffer)
|
||||
|
||||
fcurve.update()
|
||||
52
scripts/modules/bpy_extras/asset_utils.py
Normal file
52
scripts/modules/bpy_extras/asset_utils.py
Normal file
@@ -0,0 +1,52 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
Helpers for asset management tasks.
|
||||
"""
|
||||
|
||||
import bpy
|
||||
from bpy.types import (
|
||||
Context,
|
||||
)
|
||||
|
||||
__all__ = (
|
||||
"SpaceAssetInfo",
|
||||
)
|
||||
|
||||
|
||||
class SpaceAssetInfo:
|
||||
@classmethod
|
||||
def is_asset_browser(cls, space_data: bpy.types.Space):
|
||||
return space_data and space_data.type == 'FILE_BROWSER' and space_data.browse_mode == 'ASSETS'
|
||||
|
||||
@classmethod
|
||||
def is_asset_browser_poll(cls, context: Context):
|
||||
return cls.is_asset_browser(context.space_data)
|
||||
|
||||
@classmethod
|
||||
def get_active_asset(cls, context: Context):
|
||||
if hasattr(context, "active_file"):
|
||||
active_file = context.active_file
|
||||
return active_file.asset_data if active_file else None
|
||||
|
||||
|
||||
class AssetBrowserPanel:
|
||||
bl_space_type = 'FILE_BROWSER'
|
||||
|
||||
@classmethod
|
||||
def asset_browser_panel_poll(cls, context):
|
||||
return SpaceAssetInfo.is_asset_browser_poll(context)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return cls.asset_browser_panel_poll(context)
|
||||
|
||||
|
||||
class AssetMetaDataPanel:
|
||||
bl_space_type = 'FILE_BROWSER'
|
||||
bl_region_type = 'TOOL_PROPS'
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
active_file = context.active_file
|
||||
return SpaceAssetInfo.is_asset_browser_poll(context) and active_file and active_file.asset_data
|
||||
56
scripts/modules/bpy_extras/bmesh_utils.py
Normal file
56
scripts/modules/bpy_extras/bmesh_utils.py
Normal file
@@ -0,0 +1,56 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"bmesh_linked_uv_islands",
|
||||
)
|
||||
|
||||
import bmesh
|
||||
|
||||
|
||||
def match_uv(face, vert, uv, uv_layer):
|
||||
for loop in face.loops:
|
||||
if loop.vert == vert:
|
||||
return uv == loop[uv_layer].uv
|
||||
return False
|
||||
|
||||
|
||||
def bmesh_linked_uv_islands(bm, uv_layer):
|
||||
"""
|
||||
Returns lists of faces connected by UV islands.
|
||||
|
||||
For meshes use :class:`bpy.types.Mesh.mesh_linked_uv_islands` instead.
|
||||
|
||||
:arg bm: the bmesh used to group with.
|
||||
:type bmesh: :class:`BMesh`
|
||||
:arg uv_layer: the UV layer to source UVs from.
|
||||
:type bmesh: :class:`BMLayerItem`
|
||||
:return: list of lists containing polygon indices
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
result = []
|
||||
used = set()
|
||||
for seed_face in bm.faces:
|
||||
if seed_face in used:
|
||||
continue # Face has already been processed.
|
||||
used.add(seed_face)
|
||||
island = [seed_face]
|
||||
stack = [seed_face] # Faces still to consider on this island.
|
||||
while stack:
|
||||
current_face = stack.pop()
|
||||
for loop in current_face.loops:
|
||||
v = loop.vert
|
||||
uv = loop[uv_layer].uv
|
||||
for f in v.link_faces:
|
||||
if f is current_face or f in used:
|
||||
continue
|
||||
if not match_uv(f, v, uv, uv_layer):
|
||||
continue
|
||||
|
||||
# `f` is part of island, add to island and stack
|
||||
used.add(f)
|
||||
island.append(f)
|
||||
stack.append(f)
|
||||
result.append(island)
|
||||
|
||||
return result
|
||||
47
scripts/modules/bpy_extras/id_map_utils.py
Normal file
47
scripts/modules/bpy_extras/id_map_utils.py
Normal file
@@ -0,0 +1,47 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from typing import Dict, Set
|
||||
import bpy
|
||||
from bpy.types import ID
|
||||
|
||||
|
||||
__all__ = (
|
||||
"get_id_reference_map",
|
||||
"get_all_referenced_ids",
|
||||
)
|
||||
|
||||
|
||||
def get_id_reference_map() -> Dict[ID, Set[ID]]:
|
||||
"""Return a dictionary of direct datablock references for every datablock in the blend file."""
|
||||
inv_map = {}
|
||||
for key, values in bpy.data.user_map().items():
|
||||
for value in values:
|
||||
if value == key:
|
||||
# So an object is not considered to be referencing itself.
|
||||
continue
|
||||
inv_map.setdefault(value, set()).add(key)
|
||||
return inv_map
|
||||
|
||||
|
||||
def recursive_get_referenced_ids(
|
||||
ref_map: Dict[ID, Set[ID]], id: ID, referenced_ids: Set, visited: Set
|
||||
):
|
||||
"""Recursively populate referenced_ids with IDs referenced by id."""
|
||||
if id in visited:
|
||||
# Avoid infinite recursion from circular references.
|
||||
return
|
||||
visited.add(id)
|
||||
for ref in ref_map.get(id, []):
|
||||
referenced_ids.add(ref)
|
||||
recursive_get_referenced_ids(
|
||||
ref_map=ref_map, id=ref, referenced_ids=referenced_ids, visited=visited
|
||||
)
|
||||
|
||||
|
||||
def get_all_referenced_ids(id: ID, ref_map: Dict[ID, Set[ID]]) -> Set[ID]:
|
||||
"""Return a set of IDs directly or indirectly referenced by id."""
|
||||
referenced_ids = set()
|
||||
recursive_get_referenced_ids(
|
||||
ref_map=ref_map, id=id, referenced_ids=referenced_ids, visited=set()
|
||||
)
|
||||
return referenced_ids
|
||||
192
scripts/modules/bpy_extras/image_utils.py
Normal file
192
scripts/modules/bpy_extras/image_utils.py
Normal file
@@ -0,0 +1,192 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"load_image",
|
||||
)
|
||||
|
||||
|
||||
# limited replacement for BPyImage.comprehensiveImageLoad
|
||||
def load_image(
|
||||
imagepath,
|
||||
dirname="",
|
||||
place_holder=False,
|
||||
recursive=False,
|
||||
ncase_cmp=True,
|
||||
convert_callback=None,
|
||||
verbose=False,
|
||||
relpath=None,
|
||||
check_existing=False,
|
||||
force_reload=False,
|
||||
):
|
||||
"""
|
||||
Return an image from the file path with options to search multiple paths
|
||||
and return a placeholder if its not found.
|
||||
|
||||
:arg filepath: The image filename
|
||||
If a path precedes it, this will be searched as well.
|
||||
:type filepath: string
|
||||
:arg dirname: is the directory where the image may be located - any file at
|
||||
the end will be ignored.
|
||||
:type dirname: string
|
||||
:arg place_holder: if True a new place holder image will be created.
|
||||
this is useful so later you can relink the image to its original data.
|
||||
:type place_holder: bool
|
||||
:arg recursive: If True, directories will be recursively searched.
|
||||
Be careful with this if you have files in your root directory because
|
||||
it may take a long time.
|
||||
:type recursive: bool
|
||||
:arg ncase_cmp: on non windows systems, find the correct case for the file.
|
||||
:type ncase_cmp: bool
|
||||
:arg convert_callback: a function that takes an existing path and returns
|
||||
a new one. Use this when loading image formats blender may not support,
|
||||
the CONVERT_CALLBACK can take the path for a GIF (for example),
|
||||
convert it to a PNG and return the PNG's path.
|
||||
For formats blender can read, simply return the path that is given.
|
||||
:type convert_callback: function
|
||||
:arg relpath: If not None, make the file relative to this path.
|
||||
:type relpath: None or string
|
||||
:arg check_existing: If true,
|
||||
returns already loaded image datablock if possible
|
||||
(based on file path).
|
||||
:type check_existing: bool
|
||||
:arg force_reload: If true,
|
||||
force reloading of image (only useful when `check_existing`
|
||||
is also enabled).
|
||||
:type force_reload: bool
|
||||
:return: an image or None
|
||||
:rtype: :class:`bpy.types.Image`
|
||||
"""
|
||||
import os
|
||||
import bpy
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Utility Functions
|
||||
|
||||
def _image_load_placeholder(path):
|
||||
name = path
|
||||
if type(path) is str:
|
||||
name = name.encode("utf-8", "replace")
|
||||
name = name.decode("utf-8", "replace")
|
||||
name = os.path.basename(name)
|
||||
|
||||
image = bpy.data.images.new(name, 128, 128)
|
||||
# allow the path to be resolved later
|
||||
image.filepath = path
|
||||
image.source = 'FILE'
|
||||
return image
|
||||
|
||||
def _image_load(path):
|
||||
import bpy
|
||||
|
||||
if convert_callback:
|
||||
path = convert_callback(path)
|
||||
|
||||
# Ensure we're not relying on the 'CWD' to resolve the path.
|
||||
if not os.path.isabs(path):
|
||||
path = os.path.abspath(path)
|
||||
|
||||
try:
|
||||
image = bpy.data.images.load(path, check_existing=check_existing)
|
||||
except RuntimeError:
|
||||
image = None
|
||||
|
||||
if verbose:
|
||||
if image:
|
||||
print(" image loaded '%s'" % path)
|
||||
else:
|
||||
print(" image load failed '%s'" % path)
|
||||
|
||||
# image path has been checked so the path could not be read for some
|
||||
# reason, so be sure to return a placeholder
|
||||
if place_holder and image is None:
|
||||
image = _image_load_placeholder(path)
|
||||
|
||||
if image:
|
||||
if force_reload:
|
||||
image.reload()
|
||||
if relpath is not None:
|
||||
# make relative
|
||||
from bpy.path import relpath as relpath_fn
|
||||
# can't always find the relative path
|
||||
# (between drive letters on windows)
|
||||
try:
|
||||
filepath_rel = relpath_fn(path, start=relpath)
|
||||
except ValueError:
|
||||
filepath_rel = None
|
||||
|
||||
if filepath_rel is not None:
|
||||
image.filepath_raw = filepath_rel
|
||||
|
||||
return image
|
||||
|
||||
def _recursive_search(paths, filename_check):
|
||||
for path in paths:
|
||||
for dirpath, _dirnames, filenames in os.walk(path):
|
||||
|
||||
# skip '.svn'
|
||||
if dirpath[0] in {".", b'.'}:
|
||||
continue
|
||||
|
||||
for filename in filenames:
|
||||
if filename_check(filename):
|
||||
yield os.path.join(dirpath, filename)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
|
||||
imagepath = bpy.path.native_pathsep(imagepath)
|
||||
|
||||
if verbose:
|
||||
print("load_image('%s', '%s', ...)" % (imagepath, dirname))
|
||||
|
||||
if os.path.exists(imagepath):
|
||||
return _image_load(imagepath)
|
||||
|
||||
variants = [imagepath]
|
||||
|
||||
if dirname:
|
||||
variants += [
|
||||
os.path.join(dirname, imagepath),
|
||||
os.path.join(dirname, bpy.path.basename(imagepath)),
|
||||
]
|
||||
|
||||
for filepath_test in variants:
|
||||
if ncase_cmp:
|
||||
ncase_variants = (
|
||||
filepath_test,
|
||||
bpy.path.resolve_ncase(filepath_test),
|
||||
)
|
||||
else:
|
||||
ncase_variants = (filepath_test, )
|
||||
|
||||
for nfilepath in ncase_variants:
|
||||
if os.path.exists(nfilepath):
|
||||
return _image_load(nfilepath)
|
||||
|
||||
if recursive:
|
||||
search_paths = []
|
||||
|
||||
for dirpath_test in (os.path.dirname(imagepath), dirname):
|
||||
if os.path.exists(dirpath_test):
|
||||
search_paths.append(dirpath_test)
|
||||
search_paths[:] = bpy.path.reduce_dirs(search_paths)
|
||||
|
||||
imagepath_base = bpy.path.basename(imagepath)
|
||||
if ncase_cmp:
|
||||
imagepath_base = imagepath_base.lower()
|
||||
|
||||
def image_filter(fn):
|
||||
return (imagepath_base == fn.lower())
|
||||
else:
|
||||
def image_filter(fn):
|
||||
return (imagepath_base == fn)
|
||||
|
||||
nfilepath = next(_recursive_search(search_paths, image_filter), None)
|
||||
if nfilepath is not None:
|
||||
return _image_load(nfilepath)
|
||||
|
||||
# None of the paths exist so return placeholder
|
||||
if place_holder:
|
||||
return _image_load_placeholder(imagepath)
|
||||
|
||||
# TODO comprehensiveImageLoad also searched in bpy.config.textureDir
|
||||
return None
|
||||
576
scripts/modules/bpy_extras/io_utils.py
Normal file
576
scripts/modules/bpy_extras/io_utils.py
Normal file
@@ -0,0 +1,576 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"ExportHelper",
|
||||
"ImportHelper",
|
||||
"orientation_helper",
|
||||
"axis_conversion",
|
||||
"axis_conversion_ensure",
|
||||
"create_derived_objects",
|
||||
"unpack_list",
|
||||
"unpack_face_list",
|
||||
"path_reference",
|
||||
"path_reference_copy",
|
||||
"path_reference_mode",
|
||||
"unique_name",
|
||||
)
|
||||
|
||||
import bpy
|
||||
from bpy.props import (
|
||||
BoolProperty,
|
||||
EnumProperty,
|
||||
StringProperty,
|
||||
)
|
||||
from bpy.app.translations import pgettext_data as data_
|
||||
|
||||
|
||||
def _check_axis_conversion(op):
|
||||
if hasattr(op, "axis_forward") and hasattr(op, "axis_up"):
|
||||
return axis_conversion_ensure(
|
||||
op,
|
||||
"axis_forward",
|
||||
"axis_up",
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
class ExportHelper:
|
||||
filepath: StringProperty(
|
||||
name="File Path",
|
||||
description="Filepath used for exporting the file",
|
||||
maxlen=1024,
|
||||
subtype='FILE_PATH',
|
||||
)
|
||||
check_existing: BoolProperty(
|
||||
name="Check Existing",
|
||||
description="Check and warn on overwriting existing files",
|
||||
default=True,
|
||||
options={'HIDDEN'},
|
||||
)
|
||||
|
||||
# subclasses can override with decorator
|
||||
# True == use ext, False == no ext, None == do nothing.
|
||||
check_extension = True
|
||||
|
||||
def invoke(self, context, _event):
|
||||
import os
|
||||
if not self.filepath:
|
||||
blend_filepath = context.blend_data.filepath
|
||||
if not blend_filepath:
|
||||
blend_filepath = data_("untitled")
|
||||
else:
|
||||
blend_filepath = os.path.splitext(blend_filepath)[0]
|
||||
|
||||
self.filepath = blend_filepath + self.filename_ext
|
||||
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def check(self, _context):
|
||||
import os
|
||||
change_ext = False
|
||||
change_axis = _check_axis_conversion(self)
|
||||
|
||||
check_extension = self.check_extension
|
||||
|
||||
if check_extension is not None:
|
||||
filepath = self.filepath
|
||||
if os.path.basename(filepath):
|
||||
if check_extension:
|
||||
filepath = bpy.path.ensure_ext(
|
||||
os.path.splitext(filepath)[0],
|
||||
self.filename_ext,
|
||||
)
|
||||
if filepath != self.filepath:
|
||||
self.filepath = filepath
|
||||
change_ext = True
|
||||
|
||||
return (change_ext or change_axis)
|
||||
|
||||
|
||||
class ImportHelper:
|
||||
filepath: StringProperty(
|
||||
name="File Path",
|
||||
description="Filepath used for importing the file",
|
||||
maxlen=1024,
|
||||
subtype='FILE_PATH',
|
||||
)
|
||||
|
||||
def invoke(self, context, _event):
|
||||
context.window_manager.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def check(self, _context):
|
||||
return _check_axis_conversion(self)
|
||||
|
||||
|
||||
def orientation_helper(axis_forward='Y', axis_up='Z'):
|
||||
"""
|
||||
A decorator for import/export classes, generating properties needed by the axis conversion system and IO helpers,
|
||||
with specified default values (axes).
|
||||
"""
|
||||
def wrapper(cls):
|
||||
# Without that, we may end up adding those fields to some **parent** class' __annotations__ property
|
||||
# (like the ImportHelper or ExportHelper ones)! See #58772.
|
||||
if "__annotations__" not in cls.__dict__:
|
||||
setattr(cls, "__annotations__", {})
|
||||
|
||||
def _update_axis_forward(self, _context):
|
||||
if self.axis_forward[-1] == self.axis_up[-1]:
|
||||
self.axis_up = (
|
||||
self.axis_up[0:-1] +
|
||||
'XYZ'[('XYZ'.index(self.axis_up[-1]) + 1) % 3]
|
||||
)
|
||||
|
||||
cls.__annotations__['axis_forward'] = EnumProperty(
|
||||
name="Forward",
|
||||
items=(
|
||||
('X', "X Forward", ""),
|
||||
('Y', "Y Forward", ""),
|
||||
('Z', "Z Forward", ""),
|
||||
('-X', "-X Forward", ""),
|
||||
('-Y', "-Y Forward", ""),
|
||||
('-Z', "-Z Forward", ""),
|
||||
),
|
||||
default=axis_forward,
|
||||
update=_update_axis_forward,
|
||||
)
|
||||
|
||||
def _update_axis_up(self, _context):
|
||||
if self.axis_up[-1] == self.axis_forward[-1]:
|
||||
self.axis_forward = (
|
||||
self.axis_forward[0:-1] +
|
||||
'XYZ'[('XYZ'.index(self.axis_forward[-1]) + 1) % 3]
|
||||
)
|
||||
|
||||
cls.__annotations__['axis_up'] = EnumProperty(
|
||||
name="Up",
|
||||
items=(
|
||||
('X', "X Up", ""),
|
||||
('Y', "Y Up", ""),
|
||||
('Z', "Z Up", ""),
|
||||
('-X', "-X Up", ""),
|
||||
('-Y', "-Y Up", ""),
|
||||
('-Z', "-Z Up", ""),
|
||||
),
|
||||
default=axis_up,
|
||||
update=_update_axis_up,
|
||||
)
|
||||
|
||||
return cls
|
||||
|
||||
return wrapper
|
||||
|
||||
|
||||
# Axis conversion function, not pretty LUT
|
||||
# use lookup table to convert between any axis
|
||||
_axis_convert_matrix = (
|
||||
((-1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, 1.0)),
|
||||
((-1.0, 0.0, 0.0), (0.0, 0.0, -1.0), (0.0, -1.0, 0.0)),
|
||||
((-1.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, 1.0, 0.0)),
|
||||
((-1.0, 0.0, 0.0), (0.0, 1.0, 0.0), (0.0, 0.0, -1.0)),
|
||||
((0.0, -1.0, 0.0), (-1.0, 0.0, 0.0), (0.0, 0.0, -1.0)),
|
||||
((0.0, 0.0, 1.0), (-1.0, 0.0, 0.0), (0.0, -1.0, 0.0)),
|
||||
((0.0, 0.0, -1.0), (-1.0, 0.0, 0.0), (0.0, 1.0, 0.0)),
|
||||
((0.0, 1.0, 0.0), (-1.0, 0.0, 0.0), (0.0, 0.0, 1.0)),
|
||||
((0.0, -1.0, 0.0), (0.0, 0.0, 1.0), (-1.0, 0.0, 0.0)),
|
||||
((0.0, 0.0, -1.0), (0.0, -1.0, 0.0), (-1.0, 0.0, 0.0)),
|
||||
((0.0, 0.0, 1.0), (0.0, 1.0, 0.0), (-1.0, 0.0, 0.0)),
|
||||
((0.0, 1.0, 0.0), (0.0, 0.0, -1.0), (-1.0, 0.0, 0.0)),
|
||||
((0.0, -1.0, 0.0), (0.0, 0.0, -1.0), (1.0, 0.0, 0.0)),
|
||||
((0.0, 0.0, 1.0), (0.0, -1.0, 0.0), (1.0, 0.0, 0.0)),
|
||||
((0.0, 0.0, -1.0), (0.0, 1.0, 0.0), (1.0, 0.0, 0.0)),
|
||||
((0.0, 1.0, 0.0), (0.0, 0.0, 1.0), (1.0, 0.0, 0.0)),
|
||||
((0.0, -1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, 1.0)),
|
||||
((0.0, 0.0, -1.0), (1.0, 0.0, 0.0), (0.0, -1.0, 0.0)),
|
||||
((0.0, 0.0, 1.0), (1.0, 0.0, 0.0), (0.0, 1.0, 0.0)),
|
||||
((0.0, 1.0, 0.0), (1.0, 0.0, 0.0), (0.0, 0.0, -1.0)),
|
||||
((1.0, 0.0, 0.0), (0.0, -1.0, 0.0), (0.0, 0.0, -1.0)),
|
||||
((1.0, 0.0, 0.0), (0.0, 0.0, 1.0), (0.0, -1.0, 0.0)),
|
||||
((1.0, 0.0, 0.0), (0.0, 0.0, -1.0), (0.0, 1.0, 0.0)),
|
||||
)
|
||||
|
||||
# store args as a single int
|
||||
# (X Y Z -X -Y -Z) --> (0, 1, 2, 3, 4, 5)
|
||||
# each value is ((src_forward, src_up), (dst_forward, dst_up))
|
||||
# where all 4 values are or'd into a single value...
|
||||
# (i1<<0 | i1<<3 | i1<<6 | i1<<9)
|
||||
_axis_convert_lut = (
|
||||
{0x8C8, 0x4D0, 0x2E0, 0xAE8, 0x701, 0x511, 0x119, 0xB29, 0x682, 0x88A,
|
||||
0x09A, 0x2A2, 0x80B, 0x413, 0x223, 0xA2B, 0x644, 0x454, 0x05C, 0xA6C,
|
||||
0x745, 0x94D, 0x15D, 0x365},
|
||||
{0xAC8, 0x8D0, 0x4E0, 0x2E8, 0x741, 0x951, 0x159, 0x369, 0x702, 0xB0A,
|
||||
0x11A, 0x522, 0xA0B, 0x813, 0x423, 0x22B, 0x684, 0x894, 0x09C, 0x2AC,
|
||||
0x645, 0xA4D, 0x05D, 0x465},
|
||||
{0x4C8, 0x2D0, 0xAE0, 0x8E8, 0x681, 0x291, 0x099, 0x8A9, 0x642, 0x44A,
|
||||
0x05A, 0xA62, 0x40B, 0x213, 0xA23, 0x82B, 0x744, 0x354, 0x15C, 0x96C,
|
||||
0x705, 0x50D, 0x11D, 0xB25},
|
||||
{0x2C8, 0xAD0, 0x8E0, 0x4E8, 0x641, 0xA51, 0x059, 0x469, 0x742, 0x34A,
|
||||
0x15A, 0x962, 0x20B, 0xA13, 0x823, 0x42B, 0x704, 0xB14, 0x11C, 0x52C,
|
||||
0x685, 0x28D, 0x09D, 0x8A5},
|
||||
{0x708, 0xB10, 0x120, 0x528, 0x8C1, 0xAD1, 0x2D9, 0x4E9, 0x942, 0x74A,
|
||||
0x35A, 0x162, 0x64B, 0xA53, 0x063, 0x46B, 0x804, 0xA14, 0x21C, 0x42C,
|
||||
0x885, 0x68D, 0x29D, 0x0A5},
|
||||
{0xB08, 0x110, 0x520, 0x728, 0x941, 0x151, 0x359, 0x769, 0x802, 0xA0A,
|
||||
0x21A, 0x422, 0xA4B, 0x053, 0x463, 0x66B, 0x884, 0x094, 0x29C, 0x6AC,
|
||||
0x8C5, 0xACD, 0x2DD, 0x4E5},
|
||||
{0x508, 0x710, 0xB20, 0x128, 0x881, 0x691, 0x299, 0x0A9, 0x8C2, 0x4CA,
|
||||
0x2DA, 0xAE2, 0x44B, 0x653, 0xA63, 0x06B, 0x944, 0x754, 0x35C, 0x16C,
|
||||
0x805, 0x40D, 0x21D, 0xA25},
|
||||
{0x108, 0x510, 0x720, 0xB28, 0x801, 0x411, 0x219, 0xA29, 0x882, 0x08A,
|
||||
0x29A, 0x6A2, 0x04B, 0x453, 0x663, 0xA6B, 0x8C4, 0x4D4, 0x2DC, 0xAEC,
|
||||
0x945, 0x14D, 0x35D, 0x765},
|
||||
{0x748, 0x350, 0x160, 0x968, 0xAC1, 0x2D1, 0x4D9, 0x8E9, 0xA42, 0x64A,
|
||||
0x45A, 0x062, 0x68B, 0x293, 0x0A3, 0x8AB, 0xA04, 0x214, 0x41C, 0x82C,
|
||||
0xB05, 0x70D, 0x51D, 0x125},
|
||||
{0x948, 0x750, 0x360, 0x168, 0xB01, 0x711, 0x519, 0x129, 0xAC2, 0x8CA,
|
||||
0x4DA, 0x2E2, 0x88B, 0x693, 0x2A3, 0x0AB, 0xA44, 0x654, 0x45C, 0x06C,
|
||||
0xA05, 0x80D, 0x41D, 0x225},
|
||||
{0x348, 0x150, 0x960, 0x768, 0xA41, 0x051, 0x459, 0x669, 0xA02, 0x20A,
|
||||
0x41A, 0x822, 0x28B, 0x093, 0x8A3, 0x6AB, 0xB04, 0x114, 0x51C, 0x72C,
|
||||
0xAC5, 0x2CD, 0x4DD, 0x8E5},
|
||||
{0x148, 0x950, 0x760, 0x368, 0xA01, 0x811, 0x419, 0x229, 0xB02, 0x10A,
|
||||
0x51A, 0x722, 0x08B, 0x893, 0x6A3, 0x2AB, 0xAC4, 0x8D4, 0x4DC, 0x2EC,
|
||||
0xA45, 0x04D, 0x45D, 0x665},
|
||||
{0x688, 0x890, 0x0A0, 0x2A8, 0x4C1, 0x8D1, 0xAD9, 0x2E9, 0x502, 0x70A,
|
||||
0xB1A, 0x122, 0x74B, 0x953, 0x163, 0x36B, 0x404, 0x814, 0xA1C, 0x22C,
|
||||
0x445, 0x64D, 0xA5D, 0x065},
|
||||
{0x888, 0x090, 0x2A0, 0x6A8, 0x501, 0x111, 0xB19, 0x729, 0x402, 0x80A,
|
||||
0xA1A, 0x222, 0x94B, 0x153, 0x363, 0x76B, 0x444, 0x054, 0xA5C, 0x66C,
|
||||
0x4C5, 0x8CD, 0xADD, 0x2E5},
|
||||
{0x288, 0x690, 0x8A0, 0x0A8, 0x441, 0x651, 0xA59, 0x069, 0x4C2, 0x2CA,
|
||||
0xADA, 0x8E2, 0x34B, 0x753, 0x963, 0x16B, 0x504, 0x714, 0xB1C, 0x12C,
|
||||
0x405, 0x20D, 0xA1D, 0x825},
|
||||
{0x088, 0x290, 0x6A0, 0x8A8, 0x401, 0x211, 0xA19, 0x829, 0x442, 0x04A,
|
||||
0xA5A, 0x662, 0x14B, 0x353, 0x763, 0x96B, 0x4C4, 0x2D4, 0xADC, 0x8EC,
|
||||
0x505, 0x10D, 0xB1D, 0x725},
|
||||
{0x648, 0x450, 0x060, 0xA68, 0x2C1, 0x4D1, 0x8D9, 0xAE9, 0x282, 0x68A,
|
||||
0x89A, 0x0A2, 0x70B, 0x513, 0x123, 0xB2B, 0x204, 0x414, 0x81C, 0xA2C,
|
||||
0x345, 0x74D, 0x95D, 0x165},
|
||||
{0xA48, 0x650, 0x460, 0x068, 0x341, 0x751, 0x959, 0x169, 0x2C2, 0xACA,
|
||||
0x8DA, 0x4E2, 0xB0B, 0x713, 0x523, 0x12B, 0x284, 0x694, 0x89C, 0x0AC,
|
||||
0x205, 0xA0D, 0x81D, 0x425},
|
||||
{0x448, 0x050, 0xA60, 0x668, 0x281, 0x091, 0x899, 0x6A9, 0x202, 0x40A,
|
||||
0x81A, 0xA22, 0x50B, 0x113, 0xB23, 0x72B, 0x344, 0x154, 0x95C, 0x76C,
|
||||
0x2C5, 0x4CD, 0x8DD, 0xAE5},
|
||||
{0x048, 0xA50, 0x660, 0x468, 0x201, 0xA11, 0x819, 0x429, 0x342, 0x14A,
|
||||
0x95A, 0x762, 0x10B, 0xB13, 0x723, 0x52B, 0x2C4, 0xAD4, 0x8DC, 0x4EC,
|
||||
0x285, 0x08D, 0x89D, 0x6A5},
|
||||
{0x808, 0xA10, 0x220, 0x428, 0x101, 0xB11, 0x719, 0x529, 0x142, 0x94A,
|
||||
0x75A, 0x362, 0x8CB, 0xAD3, 0x2E3, 0x4EB, 0x044, 0xA54, 0x65C, 0x46C,
|
||||
0x085, 0x88D, 0x69D, 0x2A5},
|
||||
{0xA08, 0x210, 0x420, 0x828, 0x141, 0x351, 0x759, 0x969, 0x042, 0xA4A,
|
||||
0x65A, 0x462, 0xACB, 0x2D3, 0x4E3, 0x8EB, 0x084, 0x294, 0x69C, 0x8AC,
|
||||
0x105, 0xB0D, 0x71D, 0x525},
|
||||
{0x408, 0x810, 0xA20, 0x228, 0x081, 0x891, 0x699, 0x2A9, 0x102, 0x50A,
|
||||
0x71A, 0xB22, 0x4CB, 0x8D3, 0xAE3, 0x2EB, 0x144, 0x954, 0x75C, 0x36C,
|
||||
0x045, 0x44D, 0x65D, 0xA65},
|
||||
)
|
||||
|
||||
_axis_convert_num = {'X': 0, 'Y': 1, 'Z': 2, '-X': 3, '-Y': 4, '-Z': 5}
|
||||
|
||||
|
||||
def axis_conversion(from_forward='Y', from_up='Z', to_forward='Y', to_up='Z'):
|
||||
"""
|
||||
Each argument us an axis in ['X', 'Y', 'Z', '-X', '-Y', '-Z']
|
||||
where the first 2 are a source and the second 2 are the target.
|
||||
"""
|
||||
from mathutils import Matrix
|
||||
from functools import reduce
|
||||
|
||||
if from_forward == to_forward and from_up == to_up:
|
||||
return Matrix().to_3x3()
|
||||
|
||||
if from_forward[-1] == from_up[-1] or to_forward[-1] == to_up[-1]:
|
||||
raise Exception("Invalid axis arguments passed, "
|
||||
"can't use up/forward on the same axis")
|
||||
|
||||
value = reduce(int.__or__, (_axis_convert_num[a] << (i * 3)
|
||||
for i, a in enumerate((from_forward,
|
||||
from_up,
|
||||
to_forward,
|
||||
to_up,
|
||||
))))
|
||||
|
||||
for i, axis_lut in enumerate(_axis_convert_lut):
|
||||
if value in axis_lut:
|
||||
return Matrix(_axis_convert_matrix[i])
|
||||
assert 0
|
||||
|
||||
|
||||
def axis_conversion_ensure(operator, forward_attr, up_attr):
|
||||
"""
|
||||
Function to ensure an operator has valid axis conversion settings, intended
|
||||
to be used from :class:`bpy.types.Operator.check`.
|
||||
|
||||
:arg operator: the operator to access axis attributes from.
|
||||
:type operator: :class:`bpy.types.Operator`
|
||||
:arg forward_attr: attribute storing the forward axis
|
||||
:type forward_attr: string
|
||||
:arg up_attr: attribute storing the up axis
|
||||
:type up_attr: string
|
||||
:return: True if the value was modified.
|
||||
:rtype: boolean
|
||||
"""
|
||||
def validate(axis_forward, axis_up):
|
||||
if axis_forward[-1] == axis_up[-1]:
|
||||
axis_up = axis_up[0:-1] + 'XYZ'[('XYZ'.index(axis_up[-1]) + 1) % 3]
|
||||
|
||||
return axis_forward, axis_up
|
||||
|
||||
axis = getattr(operator, forward_attr), getattr(operator, up_attr)
|
||||
axis_new = validate(*axis)
|
||||
|
||||
if axis != axis_new:
|
||||
setattr(operator, forward_attr, axis_new[0])
|
||||
setattr(operator, up_attr, axis_new[1])
|
||||
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def create_derived_objects(depsgraph, objects):
|
||||
"""
|
||||
This function takes a sequence of objects, returning their instances.
|
||||
|
||||
:arg depsgraph: The evaluated depsgraph.
|
||||
:type depsgraph: :class:`bpy.types.Depsgraph`
|
||||
:arg objects: A sequencer of objects.
|
||||
:type objects: sequence of :class:`bpy.types.Object`
|
||||
:return: A dictionary where each key is an object from `objects`,
|
||||
values are lists of (:class:`bpy.types.Object`, :class:`mathutils.Matrix`) tuples representing instances.
|
||||
:rtype: dict
|
||||
"""
|
||||
result = {}
|
||||
for ob in objects:
|
||||
ob_parent = ob.parent
|
||||
if ob_parent and ob_parent.instance_type in {'VERTS', 'FACES'}:
|
||||
continue
|
||||
result[ob] = [] if ob.is_instancer else [(ob, ob.matrix_world.copy())]
|
||||
|
||||
if result:
|
||||
for dup in depsgraph.object_instances:
|
||||
dup_parent = dup.parent
|
||||
if dup_parent is None:
|
||||
continue
|
||||
dup_parent_original = dup_parent.original
|
||||
if not dup_parent_original.is_instancer:
|
||||
# The instance has already been added (on assignment).
|
||||
continue
|
||||
instance_list = result.get(dup_parent_original)
|
||||
if instance_list is None:
|
||||
continue
|
||||
instance_list.append((dup.instance_object.original, dup.matrix_world.copy()))
|
||||
return result
|
||||
|
||||
|
||||
def unpack_list(list_of_tuples):
|
||||
flat_list = []
|
||||
flat_list_extend = flat_list.extend # a tiny bit faster
|
||||
for t in list_of_tuples:
|
||||
flat_list_extend(t)
|
||||
return flat_list
|
||||
|
||||
|
||||
# same as above except that it adds 0 for triangle faces
|
||||
def unpack_face_list(list_of_tuples):
|
||||
# allocate the entire list
|
||||
flat_ls = [0] * (len(list_of_tuples) * 4)
|
||||
i = 0
|
||||
|
||||
for t in list_of_tuples:
|
||||
if len(t) == 3:
|
||||
if t[2] == 0:
|
||||
t = t[1], t[2], t[0]
|
||||
else: # assume quad
|
||||
if t[3] == 0 or t[2] == 0:
|
||||
t = t[2], t[3], t[0], t[1]
|
||||
|
||||
flat_ls[i:i + len(t)] = t
|
||||
i += 4
|
||||
return flat_ls
|
||||
|
||||
|
||||
path_reference_mode = EnumProperty(
|
||||
name="Path Mode",
|
||||
description="Method used to reference paths",
|
||||
items=(
|
||||
('AUTO', "Auto", "Use relative paths with subdirectories only"),
|
||||
('ABSOLUTE', "Absolute", "Always write absolute paths"),
|
||||
('RELATIVE', "Relative", "Always write relative paths "
|
||||
"(where possible)"),
|
||||
('MATCH', "Match", "Match absolute/relative "
|
||||
"setting with input path"),
|
||||
('STRIP', "Strip Path", "Filename only"),
|
||||
('COPY', "Copy", "Copy the file to the destination path "
|
||||
"(or subdirectory)"),
|
||||
),
|
||||
default='AUTO',
|
||||
)
|
||||
|
||||
|
||||
def path_reference(
|
||||
filepath,
|
||||
base_src,
|
||||
base_dst,
|
||||
mode='AUTO',
|
||||
copy_subdir="",
|
||||
copy_set=None,
|
||||
library=None,
|
||||
):
|
||||
"""
|
||||
Return a filepath relative to a destination directory, for use with
|
||||
exporters.
|
||||
|
||||
:arg filepath: the file path to return,
|
||||
supporting blenders relative '//' prefix.
|
||||
:type filepath: string
|
||||
:arg base_src: the directory the *filepath* is relative too
|
||||
(normally the blend file).
|
||||
:type base_src: string
|
||||
:arg base_dst: the directory the *filepath* will be referenced from
|
||||
(normally the export path).
|
||||
:type base_dst: string
|
||||
:arg mode: the method used get the path in
|
||||
['AUTO', 'ABSOLUTE', 'RELATIVE', 'MATCH', 'STRIP', 'COPY']
|
||||
:type mode: string
|
||||
:arg copy_subdir: the subdirectory of *base_dst* to use when mode='COPY'.
|
||||
:type copy_subdir: string
|
||||
:arg copy_set: collect from/to pairs when mode='COPY',
|
||||
pass to *path_reference_copy* when exporting is done.
|
||||
:type copy_set: set
|
||||
:arg library: The library this path is relative to.
|
||||
:type library: :class:`bpy.types.Library` or None
|
||||
:return: the new filepath.
|
||||
:rtype: string
|
||||
"""
|
||||
import os
|
||||
is_relative = filepath.startswith("//")
|
||||
filepath_abs = bpy.path.abspath(filepath, start=base_src, library=library)
|
||||
filepath_abs = os.path.normpath(filepath_abs)
|
||||
|
||||
if mode in {'ABSOLUTE', 'RELATIVE', 'STRIP'}:
|
||||
pass
|
||||
elif mode == 'MATCH':
|
||||
mode = 'RELATIVE' if is_relative else 'ABSOLUTE'
|
||||
elif mode == 'AUTO':
|
||||
mode = ('RELATIVE'
|
||||
if bpy.path.is_subdir(filepath_abs, base_dst)
|
||||
else 'ABSOLUTE')
|
||||
elif mode == 'COPY':
|
||||
subdir_abs = os.path.normpath(base_dst)
|
||||
if copy_subdir:
|
||||
subdir_abs = os.path.join(subdir_abs, copy_subdir)
|
||||
|
||||
filepath_cpy = os.path.join(subdir_abs, os.path.basename(filepath_abs))
|
||||
|
||||
copy_set.add((filepath_abs, filepath_cpy))
|
||||
|
||||
filepath_abs = filepath_cpy
|
||||
mode = 'RELATIVE'
|
||||
else:
|
||||
raise Exception("invalid mode given %r" % mode)
|
||||
|
||||
if mode == 'ABSOLUTE':
|
||||
return filepath_abs
|
||||
elif mode == 'RELATIVE':
|
||||
# can't always find the relative path
|
||||
# (between drive letters on windows)
|
||||
try:
|
||||
return os.path.relpath(filepath_abs, base_dst)
|
||||
except ValueError:
|
||||
return filepath_abs
|
||||
elif mode == 'STRIP':
|
||||
return os.path.basename(filepath_abs)
|
||||
|
||||
|
||||
def path_reference_copy(copy_set, report=print):
|
||||
"""
|
||||
Execute copying files of path_reference
|
||||
|
||||
:arg copy_set: set of (from, to) pairs to copy.
|
||||
:type copy_set: set
|
||||
:arg report: function used for reporting warnings, takes a string argument.
|
||||
:type report: function
|
||||
"""
|
||||
if not copy_set:
|
||||
return
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
for file_src, file_dst in copy_set:
|
||||
if not os.path.exists(file_src):
|
||||
report("missing %r, not copying" % file_src)
|
||||
elif os.path.exists(file_dst) and os.path.samefile(file_src, file_dst):
|
||||
pass
|
||||
else:
|
||||
dir_to = os.path.dirname(file_dst)
|
||||
|
||||
try:
|
||||
os.makedirs(dir_to, exist_ok=True)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
try:
|
||||
shutil.copy(file_src, file_dst)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
def unique_name(key, name, name_dict, name_max=-1, clean_func=None, sep="."):
|
||||
"""
|
||||
Helper function for storing unique names which may have special characters
|
||||
stripped and restricted to a maximum length.
|
||||
|
||||
:arg key: unique item this name belongs to, name_dict[key] will be reused
|
||||
when available.
|
||||
This can be the object, mesh, material, etc instance itself.
|
||||
:type key: any hashable object associated with the *name*.
|
||||
:arg name: The name used to create a unique value in *name_dict*.
|
||||
:type name: string
|
||||
:arg name_dict: This is used to cache namespace to ensure no collisions
|
||||
occur, this should be an empty dict initially and only modified by this
|
||||
function.
|
||||
:type name_dict: dict
|
||||
:arg clean_func: Function to call on *name* before creating a unique value.
|
||||
:type clean_func: function
|
||||
:arg sep: Separator to use when between the name and a number when a
|
||||
duplicate name is found.
|
||||
:type sep: string
|
||||
"""
|
||||
name_new = name_dict.get(key)
|
||||
if name_new is None:
|
||||
count = 1
|
||||
name_dict_values = name_dict.values()
|
||||
name_new = name_new_orig = (
|
||||
name if clean_func is None
|
||||
else clean_func(name)
|
||||
)
|
||||
|
||||
if name_max == -1:
|
||||
while name_new in name_dict_values:
|
||||
name_new = "%s%s%03d" % (
|
||||
name_new_orig,
|
||||
sep,
|
||||
count,
|
||||
)
|
||||
count += 1
|
||||
else:
|
||||
name_new = name_new[:name_max]
|
||||
while name_new in name_dict_values:
|
||||
count_str = "%03d" % count
|
||||
name_new = "%.*s%s%s" % (
|
||||
name_max - (len(count_str) + 1),
|
||||
name_new_orig,
|
||||
sep,
|
||||
count_str,
|
||||
)
|
||||
count += 1
|
||||
|
||||
name_dict[key] = name_new
|
||||
|
||||
return name_new
|
||||
127
scripts/modules/bpy_extras/keyconfig_utils.py
Normal file
127
scripts/modules/bpy_extras/keyconfig_utils.py
Normal file
@@ -0,0 +1,127 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Add-on helpers to properly (un)register their own keymaps.
|
||||
|
||||
def addon_keymap_register(keymap_data):
|
||||
"""
|
||||
Register a set of keymaps for addons using a list of keymaps.
|
||||
|
||||
See 'blender_defaults.py' for examples of the format this takes.
|
||||
"""
|
||||
import bpy
|
||||
wm = bpy.context.window_manager
|
||||
|
||||
from bl_keymap_utils.io import keymap_init_from_data
|
||||
|
||||
kconf = wm.keyconfigs.addon
|
||||
if not kconf:
|
||||
return # happens in background mode...
|
||||
for km_name, km_args, km_content in keymap_data:
|
||||
km_space_type = km_args["space_type"]
|
||||
km_region_type = km_args["region_type"]
|
||||
km_modal = km_args.get("modal", False)
|
||||
kmap = next(iter(
|
||||
k for k in kconf.keymaps
|
||||
if k.name == km_name and
|
||||
k.region_type == km_region_type and
|
||||
k.space_type == km_space_type and
|
||||
k.is_modal == km_modal
|
||||
), None)
|
||||
if kmap is None:
|
||||
kmap = kconf.keymaps.new(km_name, **km_args)
|
||||
keymap_init_from_data(kmap, km_content["items"], is_modal=km_modal)
|
||||
|
||||
|
||||
def addon_keymap_unregister(keymap_data):
|
||||
"""
|
||||
Unregister a set of keymaps for addons.
|
||||
"""
|
||||
# NOTE: We must also clean up user keyconfig, else, if user has customized one of add-on's shortcut, this
|
||||
# customization remains in memory, and comes back when re-enabling the addon, causing a segfault... :/
|
||||
import bpy
|
||||
wm = bpy.context.window_manager
|
||||
|
||||
kconfs = wm.keyconfigs
|
||||
for kconf in (kconfs.user, kconfs.addon):
|
||||
for km_name, km_args, km_content in keymap_data:
|
||||
km_space_type = km_args["space_type"]
|
||||
km_region_type = km_args["region_type"]
|
||||
km_modal = km_args.get("modal", False)
|
||||
kmaps = (
|
||||
k for k in kconf.keymaps
|
||||
if k.name == km_name and
|
||||
k.region_type == km_region_type and
|
||||
k.space_type == km_space_type and
|
||||
k.is_modal == km_modal
|
||||
)
|
||||
for kmap in kmaps:
|
||||
for kmi_idname, _, _ in km_content["items"]:
|
||||
for kmi in kmap.keymap_items:
|
||||
if kmi.idname == kmi_idname:
|
||||
kmap.keymap_items.remove(kmi)
|
||||
# NOTE: We won't remove addons keymaps themselves, other addons might also use them!
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utility Functions
|
||||
|
||||
def keyconfig_test(kc):
|
||||
|
||||
def testEntry(kc, entry, src=None, parent=None):
|
||||
result = False
|
||||
|
||||
idname, spaceid, regionid, children = entry
|
||||
|
||||
km = kc.keymaps.find(idname, space_type=spaceid, region_type=regionid)
|
||||
|
||||
if km:
|
||||
km = km.active()
|
||||
is_modal = km.is_modal
|
||||
|
||||
if src:
|
||||
for item in km.keymap_items:
|
||||
if src.compare(item):
|
||||
print("===========")
|
||||
print(parent.name)
|
||||
print(_kmistr(src, is_modal).strip())
|
||||
print(km.name)
|
||||
print(_kmistr(item, is_modal).strip())
|
||||
result = True
|
||||
|
||||
for child in children:
|
||||
if testEntry(kc, child, src, parent):
|
||||
result = True
|
||||
else:
|
||||
for i in range(len(km.keymap_items)):
|
||||
src = km.keymap_items[i]
|
||||
|
||||
for child in children:
|
||||
if testEntry(kc, child, src, km):
|
||||
result = True
|
||||
|
||||
for j in range(len(km.keymap_items) - i - 1):
|
||||
item = km.keymap_items[j + i + 1]
|
||||
if src.compare(item):
|
||||
print("===========")
|
||||
print(km.name)
|
||||
print(_kmistr(src, is_modal).strip())
|
||||
print(_kmistr(item, is_modal).strip())
|
||||
result = True
|
||||
|
||||
for child in children:
|
||||
if testEntry(kc, child):
|
||||
result = True
|
||||
|
||||
return result
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# Function body
|
||||
|
||||
from bl_keymap_utils import keymap_hierarchy
|
||||
result = False
|
||||
for entry in keymap_hierarchy.generate():
|
||||
if testEntry(kc, entry):
|
||||
result = True
|
||||
return result
|
||||
463
scripts/modules/bpy_extras/mesh_utils.py
Normal file
463
scripts/modules/bpy_extras/mesh_utils.py
Normal file
@@ -0,0 +1,463 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"mesh_linked_uv_islands",
|
||||
"mesh_linked_triangles",
|
||||
"edge_face_count_dict",
|
||||
"edge_face_count",
|
||||
"edge_loops_from_edges",
|
||||
"ngon_tessellate",
|
||||
"triangle_random_points",
|
||||
)
|
||||
|
||||
|
||||
def mesh_linked_uv_islands(mesh):
|
||||
"""
|
||||
Returns lists of polygon indices connected by UV islands.
|
||||
|
||||
:arg mesh: the mesh used to group with.
|
||||
:type mesh: :class:`bpy.types.Mesh`
|
||||
:return: list of lists containing polygon indices
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
if mesh.polygons and not mesh.uv_layers.active.data:
|
||||
# Currently, when in edit mode, UV Layer data will always be empty
|
||||
# when accessed though RNA. This may change in the future.
|
||||
raise ValueError(
|
||||
"UV Layers are not currently available from python in Edit Mode. "
|
||||
"Use bmesh and bpy_extras.bmesh_utils.bmesh_linked_uv_islands instead."
|
||||
)
|
||||
|
||||
uv_loops = [luv.uv[:] for luv in mesh.uv_layers.active.data]
|
||||
poly_loops = [poly.loop_indices for poly in mesh.polygons]
|
||||
luv_hash = {}
|
||||
luv_hash_get = luv_hash.get
|
||||
luv_hash_ls = [None] * len(uv_loops)
|
||||
for pi, poly_indices in enumerate(poly_loops):
|
||||
for li in poly_indices:
|
||||
uv = uv_loops[li]
|
||||
uv_hub = luv_hash_get(uv)
|
||||
if uv_hub is None:
|
||||
uv_hub = luv_hash[uv] = [pi]
|
||||
else:
|
||||
uv_hub.append(pi)
|
||||
luv_hash_ls[li] = uv_hub
|
||||
|
||||
poly_islands = []
|
||||
|
||||
# 0 = none, 1 = added, 2 = searched
|
||||
poly_tag = [0] * len(poly_loops)
|
||||
|
||||
while True:
|
||||
poly_index = -1
|
||||
for i in range(len(poly_loops)):
|
||||
if poly_tag[i] == 0:
|
||||
poly_index = i
|
||||
break
|
||||
|
||||
if poly_index != -1:
|
||||
island = [poly_index]
|
||||
poly_tag[poly_index] = 1
|
||||
poly_islands.append(island)
|
||||
else:
|
||||
break # we're done
|
||||
|
||||
added = True
|
||||
while added:
|
||||
added = False
|
||||
for poly_index in island[:]:
|
||||
if poly_tag[poly_index] == 1:
|
||||
for li in poly_loops[poly_index]:
|
||||
for poly_index_shared in luv_hash_ls[li]:
|
||||
if poly_tag[poly_index_shared] == 0:
|
||||
added = True
|
||||
poly_tag[poly_index_shared] = 1
|
||||
island.append(poly_index_shared)
|
||||
poly_tag[poly_index] = 2
|
||||
|
||||
return poly_islands
|
||||
|
||||
|
||||
def mesh_linked_triangles(mesh):
|
||||
"""
|
||||
Splits the mesh into connected triangles, use this for separating cubes from
|
||||
other mesh elements within 1 mesh datablock.
|
||||
|
||||
:arg mesh: the mesh used to group with.
|
||||
:type mesh: :class:`bpy.types.Mesh`
|
||||
:return: lists of lists containing triangles.
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
# Build vert face connectivity
|
||||
vert_tris = [[] for i in range(len(mesh.vertices))]
|
||||
for t in mesh.loop_triangles:
|
||||
for v in t.vertices:
|
||||
vert_tris[v].append(t)
|
||||
|
||||
# sort triangles into connectivity groups
|
||||
tri_groups = [[t] for t in mesh.loop_triangles]
|
||||
# map old, new tri location
|
||||
tri_mapping = list(range(len(mesh.loop_triangles)))
|
||||
|
||||
# Now clump triangles iteratively
|
||||
ok = True
|
||||
while ok:
|
||||
ok = False
|
||||
|
||||
for t in mesh.loop_triangles:
|
||||
mapped_index = tri_mapping[t.index]
|
||||
mapped_group = tri_groups[mapped_index]
|
||||
|
||||
for v in t.vertices:
|
||||
for nxt_t in vert_tris[v]:
|
||||
if nxt_t != t:
|
||||
nxt_mapped_index = tri_mapping[nxt_t.index]
|
||||
|
||||
# We are not a part of the same group
|
||||
if mapped_index != nxt_mapped_index:
|
||||
ok = True
|
||||
|
||||
# Assign mapping to this group so they
|
||||
# all map to this group
|
||||
for grp_t in tri_groups[nxt_mapped_index]:
|
||||
tri_mapping[grp_t.index] = mapped_index
|
||||
|
||||
# Move triangles into this group
|
||||
mapped_group.extend(tri_groups[nxt_mapped_index])
|
||||
|
||||
# remove reference to the list
|
||||
tri_groups[nxt_mapped_index] = None
|
||||
|
||||
# return all tri groups that are not null
|
||||
# this is all the triangles that are connected in their own lists.
|
||||
return [tg for tg in tri_groups if tg]
|
||||
|
||||
|
||||
def edge_face_count_dict(mesh):
|
||||
"""
|
||||
:return: dict of edge keys with their value set to the number of
|
||||
faces using each edge.
|
||||
:rtype: dict
|
||||
"""
|
||||
|
||||
face_edge_count = {}
|
||||
loops = mesh.loops
|
||||
edges = mesh.edges
|
||||
for poly in mesh.polygons:
|
||||
for i in poly.loop_indices:
|
||||
key = edges[loops[i].edge_index].key
|
||||
try:
|
||||
face_edge_count[key] += 1
|
||||
except:
|
||||
face_edge_count[key] = 1
|
||||
|
||||
return face_edge_count
|
||||
|
||||
|
||||
def edge_face_count(mesh):
|
||||
"""
|
||||
:return: list face users for each item in mesh.edges.
|
||||
:rtype: list
|
||||
"""
|
||||
edge_face_count = edge_face_count_dict(mesh)
|
||||
get = dict.get
|
||||
return [get(edge_face_count, ed.key, 0) for ed in mesh.edges]
|
||||
|
||||
|
||||
def edge_loops_from_edges(mesh, edges=None):
|
||||
"""
|
||||
Edge loops defined by edges
|
||||
|
||||
Takes me.edges or a list of edges and returns the edge loops
|
||||
|
||||
return a list of vertex indices.
|
||||
[ [1, 6, 7, 2], ...]
|
||||
|
||||
closed loops have matching start and end values.
|
||||
"""
|
||||
line_polys = []
|
||||
|
||||
# Get edges not used by a face
|
||||
if edges is None:
|
||||
edges = mesh.edges
|
||||
|
||||
if not hasattr(edges, "pop"):
|
||||
edges = edges[:]
|
||||
|
||||
while edges:
|
||||
current_edge = edges.pop()
|
||||
vert_end, vert_start = current_edge.vertices[:]
|
||||
line_poly = [vert_start, vert_end]
|
||||
|
||||
ok = True
|
||||
while ok:
|
||||
ok = False
|
||||
# for i, ed in enumerate(edges):
|
||||
i = len(edges)
|
||||
while i:
|
||||
i -= 1
|
||||
ed = edges[i]
|
||||
v1, v2 = ed.vertices
|
||||
if v1 == vert_end:
|
||||
line_poly.append(v2)
|
||||
vert_end = line_poly[-1]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
# break
|
||||
elif v2 == vert_end:
|
||||
line_poly.append(v1)
|
||||
vert_end = line_poly[-1]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
# break
|
||||
elif v1 == vert_start:
|
||||
line_poly.insert(0, v2)
|
||||
vert_start = line_poly[0]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
# break
|
||||
elif v2 == vert_start:
|
||||
line_poly.insert(0, v1)
|
||||
vert_start = line_poly[0]
|
||||
ok = 1
|
||||
del edges[i]
|
||||
# break
|
||||
line_polys.append(line_poly)
|
||||
|
||||
return line_polys
|
||||
|
||||
|
||||
def ngon_tessellate(from_data, indices, fix_loops=True, debug_print=True):
|
||||
"""
|
||||
Takes a polyline of indices (ngon) and returns a list of face
|
||||
index lists. Designed to be used for importers that need indices for an
|
||||
ngon to create from existing verts.
|
||||
|
||||
:arg from_data: either a mesh, or a list/tuple of vectors.
|
||||
:type from_data: list or :class:`bpy.types.Mesh`
|
||||
:arg indices: a list of indices to use this list
|
||||
is the ordered closed polyline
|
||||
to fill, and can be a subset of the data given.
|
||||
:type indices: list
|
||||
:arg fix_loops: If this is enabled polylines
|
||||
that use loops to make multiple
|
||||
polylines are delt with correctly.
|
||||
:type fix_loops: bool
|
||||
"""
|
||||
|
||||
from mathutils.geometry import tessellate_polygon
|
||||
from mathutils import Vector
|
||||
vector_to_tuple = Vector.to_tuple
|
||||
|
||||
if not indices:
|
||||
return []
|
||||
|
||||
def mlen(co):
|
||||
# Manhatten length of a vector, faster then length.
|
||||
return abs(co[0]) + abs(co[1]) + abs(co[2])
|
||||
|
||||
def vert_from_vector_with_extra_data(v, i):
|
||||
# Calculate data per-vector, for reuse.
|
||||
return v, vector_to_tuple(v, 6), i, mlen(v)
|
||||
|
||||
def ed_key_mlen(v1, v2):
|
||||
if v1[3] > v2[3]:
|
||||
return v2[1], v1[1]
|
||||
else:
|
||||
return v1[1], v2[1]
|
||||
|
||||
if not fix_loops:
|
||||
# Normal single concave loop filling.
|
||||
|
||||
if type(from_data) in {tuple, list}:
|
||||
verts = [Vector(from_data[i]) for ii, i in enumerate(indices)]
|
||||
else:
|
||||
verts = [from_data.vertices[i].co for ii, i in enumerate(indices)]
|
||||
|
||||
# same as reversed(range(1, len(verts))):
|
||||
for i in range(len(verts) - 1, 0, -1):
|
||||
if verts[i][1] == verts[i - 1][0]:
|
||||
verts.pop(i - 1)
|
||||
|
||||
fill = tessellate_polygon([verts])
|
||||
|
||||
else:
|
||||
# Separate this loop into multiple loops be finding edges that are
|
||||
# used twice. This is used by Light-Wave LWO files a lot.
|
||||
|
||||
if type(from_data) in {tuple, list}:
|
||||
verts = [
|
||||
vert_from_vector_with_extra_data(Vector(from_data[i]), ii)
|
||||
for ii, i in enumerate(indices)
|
||||
]
|
||||
else:
|
||||
verts = [
|
||||
vert_from_vector_with_extra_data(from_data.vertices[i].co, ii)
|
||||
for ii, i in enumerate(indices)
|
||||
]
|
||||
|
||||
edges = [(i, i - 1) for i in range(len(verts))]
|
||||
if edges:
|
||||
edges[0] = (0, len(verts) - 1)
|
||||
|
||||
if not verts:
|
||||
return []
|
||||
|
||||
edges_used = set()
|
||||
edges_doubles = set()
|
||||
# We need to check if any edges are used twice location based.
|
||||
for ed in edges:
|
||||
edkey = ed_key_mlen(verts[ed[0]], verts[ed[1]])
|
||||
if edkey in edges_used:
|
||||
edges_doubles.add(edkey)
|
||||
else:
|
||||
edges_used.add(edkey)
|
||||
|
||||
# Store a list of unconnected loop segments split by double edges.
|
||||
# will join later
|
||||
loop_segments = []
|
||||
|
||||
v_prev = verts[0]
|
||||
context_loop = [v_prev]
|
||||
loop_segments = [context_loop]
|
||||
|
||||
for v in verts:
|
||||
if v != v_prev:
|
||||
# Are we crossing an edge we removed?
|
||||
if ed_key_mlen(v, v_prev) in edges_doubles:
|
||||
context_loop = [v]
|
||||
loop_segments.append(context_loop)
|
||||
else:
|
||||
if context_loop and context_loop[-1][1] == v[1]:
|
||||
pass
|
||||
else:
|
||||
context_loop.append(v)
|
||||
|
||||
v_prev = v
|
||||
# Now join loop segments
|
||||
|
||||
def join_seg(s1, s2):
|
||||
if s2[-1][1] == s1[0][1]:
|
||||
s1, s2 = s2, s1
|
||||
elif s1[-1][1] == s2[0][1]:
|
||||
pass
|
||||
else:
|
||||
return False
|
||||
|
||||
# If were still here s1 and s2 are 2 segments in the same poly-line.
|
||||
s1.pop() # remove the last vert from s1
|
||||
s1.extend(s2) # add segment 2 to segment 1
|
||||
|
||||
if s1[0][1] == s1[-1][1]: # remove endpoints double
|
||||
s1.pop()
|
||||
|
||||
del s2[:] # Empty this segment s2 so we don't use it again.
|
||||
return True
|
||||
|
||||
joining_segments = True
|
||||
while joining_segments:
|
||||
joining_segments = False
|
||||
segcount = len(loop_segments)
|
||||
|
||||
for j in range(segcount - 1, -1, -1): # reversed(range(segcount)):
|
||||
seg_j = loop_segments[j]
|
||||
if seg_j:
|
||||
for k in range(j - 1, -1, -1): # reversed(range(j)):
|
||||
if not seg_j:
|
||||
break
|
||||
seg_k = loop_segments[k]
|
||||
|
||||
if seg_k and join_seg(seg_j, seg_k):
|
||||
joining_segments = True
|
||||
|
||||
loop_list = loop_segments
|
||||
|
||||
for verts in loop_list:
|
||||
while verts and verts[0][1] == verts[-1][1]:
|
||||
verts.pop()
|
||||
|
||||
loop_list = [verts for verts in loop_list if len(verts) > 2]
|
||||
# DONE DEALING WITH LOOP FIXING
|
||||
|
||||
# vert mapping
|
||||
vert_map = [None] * len(indices)
|
||||
ii = 0
|
||||
for verts in loop_list:
|
||||
if len(verts) > 2:
|
||||
for i, vert in enumerate(verts):
|
||||
vert_map[i + ii] = vert[2]
|
||||
ii += len(verts)
|
||||
|
||||
fill = tessellate_polygon([[v[0] for v in loop] for loop in loop_list])
|
||||
# draw_loops(loop_list)
|
||||
#raise Exception("done loop")
|
||||
# map to original indices
|
||||
fill = [[vert_map[i] for i in f] for f in fill]
|
||||
|
||||
if not fill:
|
||||
if debug_print:
|
||||
print('Warning Cannot scanfill, fallback on a triangle fan.')
|
||||
fill = [[0, i - 1, i] for i in range(2, len(indices))]
|
||||
else:
|
||||
# Use real scan-fill.
|
||||
# See if its flipped the wrong way.
|
||||
flip = None
|
||||
for fi in fill:
|
||||
if flip is not None:
|
||||
break
|
||||
for i, vi in enumerate(fi):
|
||||
if vi == 0 and fi[i - 1] == 1:
|
||||
flip = False
|
||||
break
|
||||
elif vi == 1 and fi[i - 1] == 0:
|
||||
flip = True
|
||||
break
|
||||
|
||||
if not flip:
|
||||
for i, fi in enumerate(fill):
|
||||
fill[i] = tuple([ii for ii in reversed(fi)])
|
||||
|
||||
return fill
|
||||
|
||||
|
||||
def triangle_random_points(num_points, loop_triangles):
|
||||
"""
|
||||
Generates a list of random points over mesh loop triangles.
|
||||
|
||||
:arg num_points: the number of random points to generate on each triangle.
|
||||
:type int:
|
||||
:arg loop_triangles: list of the triangles to generate points on.
|
||||
:type loop_triangles: :class:`bpy.types.MeshLoopTriangle`, sequence
|
||||
:return: list of random points over all triangles.
|
||||
:rtype: list
|
||||
"""
|
||||
|
||||
from random import random
|
||||
|
||||
# For each triangle, generate the required number of random points
|
||||
sampled_points = [None] * (num_points * len(loop_triangles))
|
||||
for i, lt in enumerate(loop_triangles):
|
||||
# Get triangle vertex coordinates
|
||||
verts = lt.id_data.vertices
|
||||
ltv = lt.vertices[:]
|
||||
tv = (verts[ltv[0]].co, verts[ltv[1]].co, verts[ltv[2]].co)
|
||||
|
||||
for k in range(num_points):
|
||||
u1 = random()
|
||||
u2 = random()
|
||||
u_tot = u1 + u2
|
||||
|
||||
if u_tot > 1:
|
||||
u1 = 1.0 - u1
|
||||
u2 = 1.0 - u2
|
||||
|
||||
side1 = tv[1] - tv[0]
|
||||
side2 = tv[2] - tv[0]
|
||||
|
||||
p = tv[0] + u1 * side1 + u2 * side2
|
||||
|
||||
sampled_points[num_points * i + k] = p
|
||||
|
||||
return sampled_points
|
||||
814
scripts/modules/bpy_extras/node_shader_utils.py
Normal file
814
scripts/modules/bpy_extras/node_shader_utils.py
Normal file
@@ -0,0 +1,814 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from mathutils import Color, Vector
|
||||
|
||||
__all__ = (
|
||||
"PrincipledBSDFWrapper",
|
||||
)
|
||||
|
||||
|
||||
def _set_check(func):
|
||||
from functools import wraps
|
||||
|
||||
@wraps(func)
|
||||
def wrapper(self, *args, **kwargs):
|
||||
if self.is_readonly:
|
||||
assert not "Trying to set value to read-only shader!"
|
||||
return
|
||||
return func(self, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def rgb_to_rgba(rgb):
|
||||
return list(rgb) + [1.0]
|
||||
|
||||
|
||||
def rgba_to_rgb(rgba):
|
||||
return Color((rgba[0], rgba[1], rgba[2]))
|
||||
|
||||
|
||||
# All clamping value shall follow Blender's defined min/max (check relevant node definition .c file).
|
||||
def values_clamp(val, minv, maxv):
|
||||
if hasattr(val, "__iter__"):
|
||||
return tuple(max(minv, min(maxv, v)) for v in val)
|
||||
else:
|
||||
return max(minv, min(maxv, val))
|
||||
|
||||
|
||||
class ShaderWrapper():
|
||||
"""
|
||||
Base class with minimal common ground for all types of shader interfaces we may want/need to implement.
|
||||
"""
|
||||
|
||||
# The two mandatory nodes any children class should support.
|
||||
NODES_LIST = (
|
||||
"node_out",
|
||||
|
||||
"_node_texcoords",
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
"is_readonly",
|
||||
"material",
|
||||
"_textures",
|
||||
"_grid_locations",
|
||||
*NODES_LIST,
|
||||
)
|
||||
|
||||
_col_size = 300
|
||||
_row_size = 300
|
||||
|
||||
def _grid_to_location(self, x, y, dst_node=None, ref_node=None):
|
||||
if ref_node is not None: # x and y are relative to this node location.
|
||||
nx = round(ref_node.location.x / self._col_size)
|
||||
ny = round(ref_node.location.y / self._row_size)
|
||||
x += nx
|
||||
y += ny
|
||||
loc = None
|
||||
while True:
|
||||
loc = (x * self._col_size, y * self._row_size)
|
||||
if loc not in self._grid_locations:
|
||||
break
|
||||
loc = (x * self._col_size, (y - 1) * self._row_size)
|
||||
if loc not in self._grid_locations:
|
||||
break
|
||||
loc = (x * self._col_size, (y - 2) * self._row_size)
|
||||
if loc not in self._grid_locations:
|
||||
break
|
||||
x -= 1
|
||||
self._grid_locations.add(loc)
|
||||
if dst_node is not None:
|
||||
dst_node.location = loc
|
||||
dst_node.width = min(dst_node.width, self._col_size - 20)
|
||||
return loc
|
||||
|
||||
def __init__(self, material, is_readonly=True, use_nodes=True):
|
||||
self.is_readonly = is_readonly
|
||||
self.material = material
|
||||
if not is_readonly:
|
||||
self.use_nodes = use_nodes
|
||||
self.update()
|
||||
|
||||
def update(self): # Should be re-implemented by children classes...
|
||||
for node in self.NODES_LIST:
|
||||
setattr(self, node, None)
|
||||
self._textures = {}
|
||||
self._grid_locations = set()
|
||||
|
||||
def use_nodes_get(self):
|
||||
return self.material.use_nodes
|
||||
|
||||
@_set_check
|
||||
def use_nodes_set(self, val):
|
||||
self.material.use_nodes = val
|
||||
self.update()
|
||||
|
||||
use_nodes = property(use_nodes_get, use_nodes_set)
|
||||
|
||||
def node_texcoords_get(self):
|
||||
if not self.use_nodes:
|
||||
return None
|
||||
if self._node_texcoords is ...:
|
||||
# Running only once, trying to find a valid texcoords node.
|
||||
for n in self.material.node_tree.nodes:
|
||||
if n.bl_idname == 'ShaderNodeTexCoord':
|
||||
self._node_texcoords = n
|
||||
self._grid_to_location(0, 0, ref_node=n)
|
||||
break
|
||||
if self._node_texcoords is ...:
|
||||
self._node_texcoords = None
|
||||
if self._node_texcoords is None and not self.is_readonly:
|
||||
tree = self.material.node_tree
|
||||
nodes = tree.nodes
|
||||
# links = tree.links
|
||||
|
||||
node_texcoords = nodes.new(type='ShaderNodeTexCoord')
|
||||
node_texcoords.label = "Texture Coords"
|
||||
self._grid_to_location(-5, 1, dst_node=node_texcoords)
|
||||
self._node_texcoords = node_texcoords
|
||||
return self._node_texcoords
|
||||
|
||||
node_texcoords = property(node_texcoords_get)
|
||||
|
||||
|
||||
class PrincipledBSDFWrapper(ShaderWrapper):
|
||||
"""
|
||||
Hard coded shader setup, based in Principled BSDF.
|
||||
Should cover most common cases on import, and gives a basic nodal shaders support for export.
|
||||
Supports basic: diffuse/spec/reflect/transparency/normal, with texturing.
|
||||
"""
|
||||
NODES_LIST = (
|
||||
"node_out",
|
||||
"node_principled_bsdf",
|
||||
|
||||
"_node_normalmap",
|
||||
"_node_texcoords",
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
"is_readonly",
|
||||
"material",
|
||||
*NODES_LIST,
|
||||
)
|
||||
|
||||
NODES_LIST = ShaderWrapper.NODES_LIST + NODES_LIST
|
||||
|
||||
def __init__(self, material, is_readonly=True, use_nodes=True):
|
||||
super(PrincipledBSDFWrapper, self).__init__(material, is_readonly, use_nodes)
|
||||
|
||||
def update(self):
|
||||
super(PrincipledBSDFWrapper, self).update()
|
||||
|
||||
if not self.use_nodes:
|
||||
return
|
||||
|
||||
tree = self.material.node_tree
|
||||
|
||||
nodes = tree.nodes
|
||||
links = tree.links
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Main output and shader.
|
||||
node_out = None
|
||||
node_principled = None
|
||||
for n in nodes:
|
||||
if n.bl_idname == 'ShaderNodeOutputMaterial' and n.inputs[0].is_linked:
|
||||
node_out = n
|
||||
node_principled = n.inputs[0].links[0].from_node
|
||||
elif n.bl_idname == 'ShaderNodeBsdfPrincipled' and n.outputs[0].is_linked:
|
||||
node_principled = n
|
||||
for lnk in n.outputs[0].links:
|
||||
node_out = lnk.to_node
|
||||
if node_out.bl_idname == 'ShaderNodeOutputMaterial':
|
||||
break
|
||||
if (
|
||||
node_out is not None and node_principled is not None and
|
||||
node_out.bl_idname == 'ShaderNodeOutputMaterial' and
|
||||
node_principled.bl_idname == 'ShaderNodeBsdfPrincipled'
|
||||
):
|
||||
break
|
||||
node_out = node_principled = None # Could not find a valid pair, let's try again
|
||||
|
||||
if node_out is not None:
|
||||
self._grid_to_location(0, 0, ref_node=node_out)
|
||||
elif not self.is_readonly:
|
||||
node_out = nodes.new(type='ShaderNodeOutputMaterial')
|
||||
node_out.label = "Material Out"
|
||||
node_out.target = 'ALL'
|
||||
self._grid_to_location(1, 1, dst_node=node_out)
|
||||
self.node_out = node_out
|
||||
|
||||
if node_principled is not None:
|
||||
self._grid_to_location(0, 0, ref_node=node_principled)
|
||||
elif not self.is_readonly:
|
||||
node_principled = nodes.new(type='ShaderNodeBsdfPrincipled')
|
||||
node_principled.label = "Principled BSDF"
|
||||
self._grid_to_location(0, 1, dst_node=node_principled)
|
||||
# Link
|
||||
links.new(node_principled.outputs["BSDF"], self.node_out.inputs["Surface"])
|
||||
self.node_principled_bsdf = node_principled
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Normal Map, lazy initialization...
|
||||
self._node_normalmap = ...
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Tex Coords, lazy initialization...
|
||||
self._node_texcoords = ...
|
||||
|
||||
def node_normalmap_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return None
|
||||
node_principled = self.node_principled_bsdf
|
||||
if self._node_normalmap is ...:
|
||||
# Running only once, trying to find a valid normalmap node.
|
||||
if node_principled.inputs["Normal"].is_linked:
|
||||
node_normalmap = node_principled.inputs["Normal"].links[0].from_node
|
||||
if node_normalmap.bl_idname == 'ShaderNodeNormalMap':
|
||||
self._node_normalmap = node_normalmap
|
||||
self._grid_to_location(0, 0, ref_node=node_normalmap)
|
||||
if self._node_normalmap is ...:
|
||||
self._node_normalmap = None
|
||||
if self._node_normalmap is None and not self.is_readonly:
|
||||
tree = self.material.node_tree
|
||||
nodes = tree.nodes
|
||||
links = tree.links
|
||||
|
||||
node_normalmap = nodes.new(type='ShaderNodeNormalMap')
|
||||
node_normalmap.label = "Normal/Map"
|
||||
self._grid_to_location(-1, -2, dst_node=node_normalmap, ref_node=node_principled)
|
||||
# Link
|
||||
links.new(node_normalmap.outputs["Normal"], node_principled.inputs["Normal"])
|
||||
self._node_normalmap = node_normalmap
|
||||
return self._node_normalmap
|
||||
|
||||
node_normalmap = property(node_normalmap_get)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Base Color.
|
||||
|
||||
def base_color_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return self.material.diffuse_color
|
||||
return rgba_to_rgb(self.node_principled_bsdf.inputs["Base Color"].default_value)
|
||||
|
||||
@_set_check
|
||||
def base_color_set(self, color):
|
||||
color = values_clamp(color, 0.0, 1.0)
|
||||
color = rgb_to_rgba(color)
|
||||
self.material.diffuse_color = color
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
self.node_principled_bsdf.inputs["Base Color"].default_value = color
|
||||
|
||||
base_color = property(base_color_get, base_color_set)
|
||||
|
||||
def base_color_texture_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_principled_bsdf,
|
||||
self.node_principled_bsdf.inputs["Base Color"],
|
||||
grid_row_diff=1,
|
||||
)
|
||||
|
||||
base_color_texture = property(base_color_texture_get)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Specular.
|
||||
|
||||
def specular_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return self.material.specular_intensity
|
||||
return self.node_principled_bsdf.inputs["Specular"].default_value
|
||||
|
||||
@_set_check
|
||||
def specular_set(self, value):
|
||||
value = values_clamp(value, 0.0, 1.0)
|
||||
self.material.specular_intensity = value
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
self.node_principled_bsdf.inputs["Specular"].default_value = value
|
||||
|
||||
specular = property(specular_get, specular_set)
|
||||
|
||||
def specular_tint_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return 0.0
|
||||
return self.node_principled_bsdf.inputs["Specular Tint"].default_value
|
||||
|
||||
@_set_check
|
||||
def specular_tint_set(self, value):
|
||||
value = values_clamp(value, 0.0, 1.0)
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
self.node_principled_bsdf.inputs["Specular Tint"].default_value = value
|
||||
|
||||
specular_tint = property(specular_tint_get, specular_tint_set)
|
||||
|
||||
# Will only be used as gray-scale one...
|
||||
def specular_texture_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
print("NO NODES!")
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_principled_bsdf,
|
||||
self.node_principled_bsdf.inputs["Specular"],
|
||||
grid_row_diff=0,
|
||||
colorspace_name='Non-Color',
|
||||
)
|
||||
|
||||
specular_texture = property(specular_texture_get)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Roughness (also sort of inverse of specular hardness...).
|
||||
|
||||
def roughness_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return self.material.roughness
|
||||
return self.node_principled_bsdf.inputs["Roughness"].default_value
|
||||
|
||||
@_set_check
|
||||
def roughness_set(self, value):
|
||||
value = values_clamp(value, 0.0, 1.0)
|
||||
self.material.roughness = value
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
self.node_principled_bsdf.inputs["Roughness"].default_value = value
|
||||
|
||||
roughness = property(roughness_get, roughness_set)
|
||||
|
||||
# Will only be used as gray-scale one...
|
||||
def roughness_texture_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_principled_bsdf,
|
||||
self.node_principled_bsdf.inputs["Roughness"],
|
||||
grid_row_diff=0,
|
||||
colorspace_name='Non-Color',
|
||||
)
|
||||
|
||||
roughness_texture = property(roughness_texture_get)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Metallic (a.k.a reflection, mirror).
|
||||
|
||||
def metallic_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return self.material.metallic
|
||||
return self.node_principled_bsdf.inputs["Metallic"].default_value
|
||||
|
||||
@_set_check
|
||||
def metallic_set(self, value):
|
||||
value = values_clamp(value, 0.0, 1.0)
|
||||
self.material.metallic = value
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
self.node_principled_bsdf.inputs["Metallic"].default_value = value
|
||||
|
||||
metallic = property(metallic_get, metallic_set)
|
||||
|
||||
# Will only be used as gray-scale one...
|
||||
def metallic_texture_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_principled_bsdf,
|
||||
self.node_principled_bsdf.inputs["Metallic"],
|
||||
grid_row_diff=0,
|
||||
colorspace_name='Non-Color',
|
||||
)
|
||||
|
||||
metallic_texture = property(metallic_texture_get)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Transparency settings.
|
||||
|
||||
def ior_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return 1.0
|
||||
return self.node_principled_bsdf.inputs["IOR"].default_value
|
||||
|
||||
@_set_check
|
||||
def ior_set(self, value):
|
||||
value = values_clamp(value, 0.0, 1000.0)
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
self.node_principled_bsdf.inputs["IOR"].default_value = value
|
||||
|
||||
ior = property(ior_get, ior_set)
|
||||
|
||||
# Will only be used as gray-scale one...
|
||||
def ior_texture_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_principled_bsdf,
|
||||
self.node_principled_bsdf.inputs["IOR"],
|
||||
grid_row_diff=-1,
|
||||
colorspace_name='Non-Color',
|
||||
)
|
||||
|
||||
ior_texture = property(ior_texture_get)
|
||||
|
||||
def transmission_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return 0.0
|
||||
return self.node_principled_bsdf.inputs["Transmission"].default_value
|
||||
|
||||
@_set_check
|
||||
def transmission_set(self, value):
|
||||
value = values_clamp(value, 0.0, 1.0)
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
self.node_principled_bsdf.inputs["Transmission"].default_value = value
|
||||
|
||||
transmission = property(transmission_get, transmission_set)
|
||||
|
||||
# Will only be used as gray-scale one...
|
||||
def transmission_texture_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_principled_bsdf,
|
||||
self.node_principled_bsdf.inputs["Transmission"],
|
||||
grid_row_diff=-1,
|
||||
colorspace_name='Non-Color',
|
||||
)
|
||||
|
||||
transmission_texture = property(transmission_texture_get)
|
||||
|
||||
def alpha_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return 1.0
|
||||
return self.node_principled_bsdf.inputs["Alpha"].default_value
|
||||
|
||||
@_set_check
|
||||
def alpha_set(self, value):
|
||||
value = values_clamp(value, 0.0, 1.0)
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
self.node_principled_bsdf.inputs["Alpha"].default_value = value
|
||||
|
||||
alpha = property(alpha_get, alpha_set)
|
||||
|
||||
# Will only be used as gray-scale one...
|
||||
def alpha_texture_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_principled_bsdf,
|
||||
self.node_principled_bsdf.inputs["Alpha"],
|
||||
use_alpha=True,
|
||||
grid_row_diff=-1,
|
||||
colorspace_name='Non-Color',
|
||||
)
|
||||
|
||||
alpha_texture = property(alpha_texture_get)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Emission color.
|
||||
|
||||
def emission_color_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return Color((0.0, 0.0, 0.0))
|
||||
return rgba_to_rgb(self.node_principled_bsdf.inputs["Emission"].default_value)
|
||||
|
||||
@_set_check
|
||||
def emission_color_set(self, color):
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
color = values_clamp(color, 0.0, 1000000.0)
|
||||
color = rgb_to_rgba(color)
|
||||
self.node_principled_bsdf.inputs["Emission"].default_value = color
|
||||
|
||||
emission_color = property(emission_color_get, emission_color_set)
|
||||
|
||||
def emission_color_texture_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_principled_bsdf,
|
||||
self.node_principled_bsdf.inputs["Emission"],
|
||||
grid_row_diff=1,
|
||||
)
|
||||
|
||||
emission_color_texture = property(emission_color_texture_get)
|
||||
|
||||
def emission_strength_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return 1.0
|
||||
return self.node_principled_bsdf.inputs["Emission Strength"].default_value
|
||||
|
||||
@_set_check
|
||||
def emission_strength_set(self, value):
|
||||
value = values_clamp(value, 0.0, 1000000.0)
|
||||
if self.use_nodes and self.node_principled_bsdf is not None:
|
||||
self.node_principled_bsdf.inputs["Emission Strength"].default_value = value
|
||||
|
||||
emission_strength = property(emission_strength_get, emission_strength_set)
|
||||
|
||||
def emission_strength_texture_get(self):
|
||||
if not self.use_nodes or self.node_principled_bsdf is None:
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_principled_bsdf,
|
||||
self.node_principled_bsdf.inputs["Emission Strength"],
|
||||
grid_row_diff=-1,
|
||||
colorspace_name='Non-Color',
|
||||
)
|
||||
|
||||
emission_strength_texture = property(emission_strength_texture_get)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Normal map.
|
||||
|
||||
def normalmap_strength_get(self):
|
||||
if not self.use_nodes or self.node_normalmap is None:
|
||||
return 0.0
|
||||
return self.node_normalmap.inputs["Strength"].default_value
|
||||
|
||||
@_set_check
|
||||
def normalmap_strength_set(self, value):
|
||||
value = values_clamp(value, 0.0, 10.0)
|
||||
if self.use_nodes and self.node_normalmap is not None:
|
||||
self.node_normalmap.inputs["Strength"].default_value = value
|
||||
|
||||
normalmap_strength = property(normalmap_strength_get, normalmap_strength_set)
|
||||
|
||||
def normalmap_texture_get(self):
|
||||
if not self.use_nodes or self.node_normalmap is None:
|
||||
return None
|
||||
return ShaderImageTextureWrapper(
|
||||
self, self.node_normalmap,
|
||||
self.node_normalmap.inputs["Color"],
|
||||
grid_row_diff=-2,
|
||||
colorspace_is_data=True,
|
||||
)
|
||||
|
||||
normalmap_texture = property(normalmap_texture_get)
|
||||
|
||||
|
||||
class ShaderImageTextureWrapper():
|
||||
"""
|
||||
Generic 'image texture'-like wrapper, handling image node, some mapping (texture coordinates transformations),
|
||||
and texture coordinates source.
|
||||
"""
|
||||
|
||||
# Note: this class assumes we are using nodes, otherwise it should never be used...
|
||||
|
||||
NODES_LIST = (
|
||||
"node_dst",
|
||||
"socket_dst",
|
||||
|
||||
"_node_image",
|
||||
"_node_mapping",
|
||||
)
|
||||
|
||||
__slots__ = (
|
||||
"owner_shader",
|
||||
"is_readonly",
|
||||
"grid_row_diff",
|
||||
"use_alpha",
|
||||
"colorspace_is_data",
|
||||
"colorspace_name",
|
||||
*NODES_LIST,
|
||||
)
|
||||
|
||||
def __new__(cls, owner_shader: ShaderWrapper, node_dst, socket_dst, *_args, **_kwargs):
|
||||
instance = owner_shader._textures.get((node_dst, socket_dst), None)
|
||||
if instance is not None:
|
||||
return instance
|
||||
instance = super(ShaderImageTextureWrapper, cls).__new__(cls)
|
||||
owner_shader._textures[(node_dst, socket_dst)] = instance
|
||||
return instance
|
||||
|
||||
def __init__(self, owner_shader: ShaderWrapper, node_dst, socket_dst, grid_row_diff=0,
|
||||
use_alpha=False, colorspace_is_data=..., colorspace_name=...):
|
||||
self.owner_shader = owner_shader
|
||||
self.is_readonly = owner_shader.is_readonly
|
||||
self.node_dst = node_dst
|
||||
self.socket_dst = socket_dst
|
||||
self.grid_row_diff = grid_row_diff
|
||||
self.use_alpha = use_alpha
|
||||
self.colorspace_is_data = colorspace_is_data
|
||||
self.colorspace_name = colorspace_name
|
||||
|
||||
self._node_image = ...
|
||||
self._node_mapping = ...
|
||||
|
||||
# tree = node_dst.id_data
|
||||
# nodes = tree.nodes
|
||||
# links = tree.links
|
||||
|
||||
if socket_dst.is_linked:
|
||||
from_node = socket_dst.links[0].from_node
|
||||
if from_node.bl_idname == 'ShaderNodeTexImage':
|
||||
self._node_image = from_node
|
||||
|
||||
if self.node_image is not None:
|
||||
socket_dst = self.node_image.inputs["Vector"]
|
||||
if socket_dst.is_linked:
|
||||
from_node = socket_dst.links[0].from_node
|
||||
if from_node.bl_idname == 'ShaderNodeMapping':
|
||||
self._node_mapping = from_node
|
||||
|
||||
def copy_from(self, tex):
|
||||
# Avoid generating any node in source texture.
|
||||
is_readonly_back = tex.is_readonly
|
||||
tex.is_readonly = True
|
||||
|
||||
if tex.node_image is not None:
|
||||
self.image = tex.image
|
||||
self.projection = tex.projection
|
||||
self.texcoords = tex.texcoords
|
||||
self.copy_mapping_from(tex)
|
||||
|
||||
tex.is_readonly = is_readonly_back
|
||||
|
||||
def copy_mapping_from(self, tex):
|
||||
# Avoid generating any node in source texture.
|
||||
is_readonly_back = tex.is_readonly
|
||||
tex.is_readonly = True
|
||||
|
||||
if tex.node_mapping is None: # Used to actually remove mapping node.
|
||||
if self.has_mapping_node():
|
||||
# We assume node_image can never be None in that case...
|
||||
# Find potential existing link into image's Vector input.
|
||||
socket_dst = socket_src = None
|
||||
if self.node_mapping.inputs["Vector"].is_linked:
|
||||
socket_dst = self.node_image.inputs["Vector"]
|
||||
socket_src = self.node_mapping.inputs["Vector"].links[0].from_socket
|
||||
|
||||
tree = self.owner_shader.material.node_tree
|
||||
tree.nodes.remove(self.node_mapping)
|
||||
self._node_mapping = None
|
||||
|
||||
# If previously existing, re-link texcoords -> image
|
||||
if socket_src is not None:
|
||||
tree.links.new(socket_src, socket_dst)
|
||||
elif self.node_mapping is not None:
|
||||
self.translation = tex.translation
|
||||
self.rotation = tex.rotation
|
||||
self.scale = tex.scale
|
||||
|
||||
tex.is_readonly = is_readonly_back
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Image.
|
||||
|
||||
def node_image_get(self):
|
||||
if self._node_image is ...:
|
||||
# Running only once, trying to find a valid image node.
|
||||
if self.socket_dst.is_linked:
|
||||
node_image = self.socket_dst.links[0].from_node
|
||||
if node_image.bl_idname == 'ShaderNodeTexImage':
|
||||
self._node_image = node_image
|
||||
self.owner_shader._grid_to_location(0, 0, ref_node=node_image)
|
||||
if self._node_image is ...:
|
||||
self._node_image = None
|
||||
if self._node_image is None and not self.is_readonly:
|
||||
tree = self.owner_shader.material.node_tree
|
||||
|
||||
node_image = tree.nodes.new(type='ShaderNodeTexImage')
|
||||
self.owner_shader._grid_to_location(
|
||||
-1, 0 + self.grid_row_diff,
|
||||
dst_node=node_image, ref_node=self.node_dst,
|
||||
)
|
||||
|
||||
tree.links.new(node_image.outputs["Alpha" if self.use_alpha else "Color"], self.socket_dst)
|
||||
if self.use_alpha:
|
||||
self.owner_shader.material.blend_method = 'BLEND'
|
||||
|
||||
self._node_image = node_image
|
||||
return self._node_image
|
||||
|
||||
node_image = property(node_image_get)
|
||||
|
||||
def image_get(self):
|
||||
return self.node_image.image if self.node_image is not None else None
|
||||
|
||||
@_set_check
|
||||
def image_set(self, image):
|
||||
if self.colorspace_is_data is not ...:
|
||||
if image.colorspace_settings.is_data != self.colorspace_is_data and image.users >= 1:
|
||||
image = image.copy()
|
||||
image.colorspace_settings.is_data = self.colorspace_is_data
|
||||
if self.colorspace_name is not ...:
|
||||
if image.colorspace_settings.name != self.colorspace_name and image.users >= 1:
|
||||
image = image.copy()
|
||||
image.colorspace_settings.name = self.colorspace_name
|
||||
if self.use_alpha:
|
||||
# Try to be smart, and only use image's alpha output if image actually has alpha data.
|
||||
tree = self.owner_shader.material.node_tree
|
||||
if image.channels < 4 or image.depth in {24, 8}:
|
||||
tree.links.new(self.node_image.outputs["Color"], self.socket_dst)
|
||||
else:
|
||||
tree.links.new(self.node_image.outputs["Alpha"], self.socket_dst)
|
||||
self.node_image.image = image
|
||||
|
||||
image = property(image_get, image_set)
|
||||
|
||||
def projection_get(self):
|
||||
return self.node_image.projection if self.node_image is not None else 'FLAT'
|
||||
|
||||
@_set_check
|
||||
def projection_set(self, projection):
|
||||
self.node_image.projection = projection
|
||||
|
||||
projection = property(projection_get, projection_set)
|
||||
|
||||
def texcoords_get(self):
|
||||
if self.node_image is not None:
|
||||
socket = (self.node_mapping if self.has_mapping_node() else self.node_image).inputs["Vector"]
|
||||
if socket.is_linked:
|
||||
return socket.links[0].from_socket.name
|
||||
return 'UV'
|
||||
|
||||
@_set_check
|
||||
def texcoords_set(self, texcoords):
|
||||
# Image texture node already defaults to UVs, no extra node needed.
|
||||
# ONLY in case we do not have any texcoords mapping!!!
|
||||
if texcoords == 'UV' and not self.has_mapping_node():
|
||||
return
|
||||
tree = self.node_image.id_data
|
||||
links = tree.links
|
||||
node_dst = self.node_mapping if self.has_mapping_node() else self.node_image
|
||||
socket_src = self.owner_shader.node_texcoords.outputs[texcoords]
|
||||
links.new(socket_src, node_dst.inputs["Vector"])
|
||||
|
||||
texcoords = property(texcoords_get, texcoords_set)
|
||||
|
||||
def extension_get(self):
|
||||
return self.node_image.extension if self.node_image is not None else 'REPEAT'
|
||||
|
||||
@_set_check
|
||||
def extension_set(self, extension):
|
||||
self.node_image.extension = extension
|
||||
|
||||
extension = property(extension_get, extension_set)
|
||||
|
||||
# --------------------------------------------------------------------
|
||||
# Mapping.
|
||||
|
||||
def has_mapping_node(self):
|
||||
return self._node_mapping not in {None, ...}
|
||||
|
||||
def node_mapping_get(self):
|
||||
if self._node_mapping is ...:
|
||||
# Running only once, trying to find a valid mapping node.
|
||||
if self.node_image is None:
|
||||
return None
|
||||
if self.node_image.inputs["Vector"].is_linked:
|
||||
node_mapping = self.node_image.inputs["Vector"].links[0].from_node
|
||||
if node_mapping.bl_idname == 'ShaderNodeMapping':
|
||||
self._node_mapping = node_mapping
|
||||
self.owner_shader._grid_to_location(0, 0 + self.grid_row_diff, ref_node=node_mapping)
|
||||
if self._node_mapping is ...:
|
||||
self._node_mapping = None
|
||||
if self._node_mapping is None and not self.is_readonly:
|
||||
# Find potential existing link into image's Vector input.
|
||||
socket_dst = self.node_image.inputs["Vector"]
|
||||
# If not already existing, we need to create texcoords -> mapping link (from UV).
|
||||
socket_src = (socket_dst.links[0].from_socket if socket_dst.is_linked
|
||||
else self.owner_shader.node_texcoords.outputs['UV'])
|
||||
|
||||
tree = self.owner_shader.material.node_tree
|
||||
node_mapping = tree.nodes.new(type='ShaderNodeMapping')
|
||||
node_mapping.vector_type = 'TEXTURE'
|
||||
self.owner_shader._grid_to_location(-1, 0, dst_node=node_mapping, ref_node=self.node_image)
|
||||
|
||||
# Link mapping -> image node.
|
||||
tree.links.new(node_mapping.outputs["Vector"], socket_dst)
|
||||
# Link texcoords -> mapping.
|
||||
tree.links.new(socket_src, node_mapping.inputs["Vector"])
|
||||
|
||||
self._node_mapping = node_mapping
|
||||
return self._node_mapping
|
||||
|
||||
node_mapping = property(node_mapping_get)
|
||||
|
||||
def translation_get(self):
|
||||
if self.node_mapping is None:
|
||||
return Vector((0.0, 0.0, 0.0))
|
||||
return self.node_mapping.inputs['Location'].default_value
|
||||
|
||||
@_set_check
|
||||
def translation_set(self, translation):
|
||||
self.node_mapping.inputs['Location'].default_value = translation
|
||||
|
||||
translation = property(translation_get, translation_set)
|
||||
|
||||
def rotation_get(self):
|
||||
if self.node_mapping is None:
|
||||
return Vector((0.0, 0.0, 0.0))
|
||||
return self.node_mapping.inputs['Rotation'].default_value
|
||||
|
||||
@_set_check
|
||||
def rotation_set(self, rotation):
|
||||
self.node_mapping.inputs['Rotation'].default_value = rotation
|
||||
|
||||
rotation = property(rotation_get, rotation_set)
|
||||
|
||||
def scale_get(self):
|
||||
if self.node_mapping is None:
|
||||
return Vector((1.0, 1.0, 1.0))
|
||||
return self.node_mapping.inputs['Scale'].default_value
|
||||
|
||||
@_set_check
|
||||
def scale_set(self, scale):
|
||||
self.node_mapping.inputs['Scale'].default_value = scale
|
||||
|
||||
scale = property(scale_get, scale_set)
|
||||
14
scripts/modules/bpy_extras/node_utils.py
Normal file
14
scripts/modules/bpy_extras/node_utils.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"find_node_input",
|
||||
)
|
||||
|
||||
|
||||
# XXX Names are not unique. Returns the first match.
|
||||
def find_node_input(node, name):
|
||||
for input in node.inputs:
|
||||
if input.name == name:
|
||||
return input
|
||||
|
||||
return None
|
||||
262
scripts/modules/bpy_extras/object_utils.py
Normal file
262
scripts/modules/bpy_extras/object_utils.py
Normal file
@@ -0,0 +1,262 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
from __future__ import annotations
|
||||
|
||||
__all__ = (
|
||||
"add_object_align_init",
|
||||
"object_data_add",
|
||||
"AddObjectHelper",
|
||||
"object_add_grid_scale",
|
||||
"object_add_grid_scale_apply_operator",
|
||||
"world_to_camera_view",
|
||||
)
|
||||
|
||||
|
||||
import bpy
|
||||
|
||||
from bpy.props import (
|
||||
FloatVectorProperty,
|
||||
EnumProperty,
|
||||
)
|
||||
|
||||
|
||||
def add_object_align_init(context, operator):
|
||||
"""
|
||||
Return a matrix using the operator settings and view context.
|
||||
|
||||
:arg context: The context to use.
|
||||
:type context: :class:`bpy.types.Context`
|
||||
:arg operator: The operator, checked for location and rotation properties.
|
||||
:type operator: :class:`bpy.types.Operator`
|
||||
:return: the matrix from the context and settings.
|
||||
:rtype: :class:`mathutils.Matrix`
|
||||
"""
|
||||
|
||||
from mathutils import Matrix, Vector
|
||||
properties = operator.properties if operator is not None else None
|
||||
|
||||
space_data = context.space_data
|
||||
if space_data and space_data.type != 'VIEW_3D':
|
||||
space_data = None
|
||||
|
||||
# location
|
||||
if operator and properties.is_property_set("location"):
|
||||
location = Matrix.Translation(Vector(properties.location))
|
||||
else:
|
||||
location = Matrix.Translation(context.scene.cursor.location)
|
||||
|
||||
if operator:
|
||||
properties.location = location.to_translation()
|
||||
|
||||
# rotation
|
||||
add_align_preference = context.preferences.edit.object_align
|
||||
if operator:
|
||||
if not properties.is_property_set("rotation"):
|
||||
# So one of "align" and "rotation" will be set
|
||||
properties.align = add_align_preference
|
||||
|
||||
if properties.align == 'WORLD':
|
||||
rotation = properties.rotation.to_matrix().to_4x4()
|
||||
elif properties.align == 'VIEW':
|
||||
rotation = space_data.region_3d.view_matrix.to_3x3().inverted()
|
||||
rotation.resize_4x4()
|
||||
properties.rotation = rotation.to_euler()
|
||||
elif properties.align == 'CURSOR':
|
||||
rotation = context.scene.cursor.matrix
|
||||
rotation.col[3][0:3] = 0.0, 0.0, 0.0
|
||||
properties.rotation = rotation.to_euler()
|
||||
else:
|
||||
rotation = properties.rotation.to_matrix().to_4x4()
|
||||
else:
|
||||
if (add_align_preference == 'VIEW') and space_data:
|
||||
rotation = space_data.region_3d.view_matrix.to_3x3().inverted()
|
||||
rotation.resize_4x4()
|
||||
elif add_align_preference == 'CURSOR':
|
||||
rotation = context.scene.cursor.rotation_euler.to_matrix().to_4x4()
|
||||
else:
|
||||
rotation = Matrix()
|
||||
|
||||
return location @ rotation
|
||||
|
||||
|
||||
def object_data_add(context, obdata, operator=None, name=None):
|
||||
"""
|
||||
Add an object using the view context and preference to initialize the
|
||||
location, rotation and layer.
|
||||
|
||||
:arg context: The context to use.
|
||||
:type context: :class:`bpy.types.Context`
|
||||
:arg obdata: the data used for the new object.
|
||||
:type obdata: valid object data type or None.
|
||||
:arg operator: The operator, checked for location and rotation properties.
|
||||
:type operator: :class:`bpy.types.Operator`
|
||||
:arg name: Optional name
|
||||
:type name: string
|
||||
:return: the newly created object in the scene.
|
||||
:rtype: :class:`bpy.types.Object`
|
||||
"""
|
||||
layer = context.view_layer
|
||||
layer_collection = context.layer_collection or layer.active_layer_collection
|
||||
scene_collection = layer_collection.collection
|
||||
|
||||
for ob in layer.objects:
|
||||
ob.select_set(False)
|
||||
|
||||
if name is None:
|
||||
name = "Object" if obdata is None else obdata.name
|
||||
|
||||
obj_act = layer.objects.active
|
||||
obj_new = bpy.data.objects.new(name, obdata)
|
||||
scene_collection.objects.link(obj_new)
|
||||
obj_new.select_set(True)
|
||||
obj_new.matrix_world = add_object_align_init(context, operator)
|
||||
|
||||
space_data = context.space_data
|
||||
if space_data and space_data.type != 'VIEW_3D':
|
||||
space_data = None
|
||||
|
||||
if space_data:
|
||||
if space_data.local_view:
|
||||
obj_new.local_view_set(space_data, True)
|
||||
|
||||
if obj_act and obj_act.mode == 'EDIT' and obj_act.type == obj_new.type:
|
||||
bpy.ops.mesh.select_all(action='DESELECT')
|
||||
obj_act.select_set(True)
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
obj_act.select_set(True)
|
||||
layer.update() # apply location
|
||||
# layer.objects.active = obj_new
|
||||
|
||||
# Match up UV layers, this is needed so adding an object with UVs
|
||||
# doesn't create new layers when there happens to be a naming mismatch.
|
||||
uv_new = obdata.uv_layers.active
|
||||
if uv_new is not None:
|
||||
uv_act = obj_act.data.uv_layers.active
|
||||
if uv_act is not None:
|
||||
uv_new.name = uv_act.name
|
||||
|
||||
bpy.ops.object.join() # join into the active.
|
||||
if obdata:
|
||||
bpy.data.meshes.remove(obdata)
|
||||
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
else:
|
||||
layer.objects.active = obj_new
|
||||
if context.preferences.edit.use_enter_edit_mode:
|
||||
if obdata and obdata.library is None:
|
||||
obtype = obj_new.type
|
||||
mode = None
|
||||
if obtype in {'ARMATURE', 'CURVE', 'CURVES', 'FONT', 'LATTICE', 'MESH', 'META', 'SURFACE'}:
|
||||
mode = 'EDIT'
|
||||
elif obtype == 'GPENCIL':
|
||||
mode = 'EDIT_GPENCIL'
|
||||
|
||||
if mode is not None:
|
||||
bpy.ops.object.mode_set(mode=mode)
|
||||
|
||||
return obj_new
|
||||
|
||||
|
||||
class AddObjectHelper:
|
||||
def align_update_callback(self, _context):
|
||||
if self.align == 'WORLD':
|
||||
self.rotation.zero()
|
||||
|
||||
align: EnumProperty(
|
||||
name="Align",
|
||||
items=(
|
||||
('WORLD', "World", "Align the new object to the world"),
|
||||
('VIEW', "View", "Align the new object to the view"),
|
||||
('CURSOR', "3D Cursor", "Use the 3D cursor orientation for the new object"),
|
||||
),
|
||||
default='WORLD',
|
||||
update=AddObjectHelper.align_update_callback,
|
||||
)
|
||||
location: FloatVectorProperty(
|
||||
name="Location",
|
||||
subtype='TRANSLATION',
|
||||
)
|
||||
rotation: FloatVectorProperty(
|
||||
name="Rotation",
|
||||
subtype='EULER',
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene.library is None
|
||||
|
||||
|
||||
def object_add_grid_scale(context):
|
||||
"""
|
||||
Return scale which should be applied on object
|
||||
data to align it to grid scale
|
||||
"""
|
||||
|
||||
space_data = context.space_data
|
||||
|
||||
if space_data and space_data.type == 'VIEW_3D':
|
||||
return space_data.overlay.grid_scale_unit
|
||||
|
||||
return 1.0
|
||||
|
||||
|
||||
def object_add_grid_scale_apply_operator(operator, context):
|
||||
"""
|
||||
Scale an operators distance values by the grid size.
|
||||
"""
|
||||
# This is a Python version of the C function `WM_operator_view3d_unit_defaults`.
|
||||
grid_scale = object_add_grid_scale(context)
|
||||
|
||||
properties = operator.properties
|
||||
properties_def = properties.bl_rna.properties
|
||||
for prop_id in properties_def.keys():
|
||||
if not properties.is_property_set(prop_id, ghost=False):
|
||||
prop_def = properties_def[prop_id]
|
||||
if prop_def.unit == 'LENGTH' and prop_def.subtype == 'DISTANCE':
|
||||
setattr(operator, prop_id,
|
||||
getattr(operator, prop_id) * grid_scale)
|
||||
|
||||
|
||||
def world_to_camera_view(scene, obj, coord):
|
||||
"""
|
||||
Returns the camera space coords for a 3d point.
|
||||
(also known as: normalized device coordinates - NDC).
|
||||
|
||||
Where (0, 0) is the bottom left and (1, 1)
|
||||
is the top right of the camera frame.
|
||||
values outside 0-1 are also supported.
|
||||
A negative 'z' value means the point is behind the camera.
|
||||
|
||||
Takes shift-x/y, lens angle and sensor size into account
|
||||
as well as perspective/ortho projections.
|
||||
|
||||
:arg scene: Scene to use for frame size.
|
||||
:type scene: :class:`bpy.types.Scene`
|
||||
:arg obj: Camera object.
|
||||
:type obj: :class:`bpy.types.Object`
|
||||
:arg coord: World space location.
|
||||
:type coord: :class:`mathutils.Vector`
|
||||
:return: a vector where X and Y map to the view plane and
|
||||
Z is the depth on the view axis.
|
||||
:rtype: :class:`mathutils.Vector`
|
||||
"""
|
||||
from mathutils import Vector
|
||||
|
||||
co_local = obj.matrix_world.normalized().inverted() @ coord
|
||||
z = -co_local.z
|
||||
|
||||
camera = obj.data
|
||||
frame = [v for v in camera.view_frame(scene=scene)[:3]]
|
||||
if camera.type != 'ORTHO':
|
||||
if z == 0.0:
|
||||
return Vector((0.5, 0.5, 0.0))
|
||||
else:
|
||||
frame = [-(v / (v.z / z)) for v in frame]
|
||||
|
||||
min_x, max_x = frame[2].x, frame[1].x
|
||||
min_y, max_y = frame[1].y, frame[0].y
|
||||
|
||||
x = (co_local.x - min_x) / (max_x - min_x)
|
||||
y = (co_local.y - min_y) / (max_y - min_y)
|
||||
|
||||
return Vector((x, y, z))
|
||||
179
scripts/modules/bpy_extras/view3d_utils.py
Normal file
179
scripts/modules/bpy_extras/view3d_utils.py
Normal file
@@ -0,0 +1,179 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"region_2d_to_vector_3d",
|
||||
"region_2d_to_origin_3d",
|
||||
"region_2d_to_location_3d",
|
||||
"location_3d_to_region_2d",
|
||||
)
|
||||
|
||||
|
||||
def region_2d_to_vector_3d(region, rv3d, coord):
|
||||
"""
|
||||
Return a direction vector from the viewport at the specific 2d region
|
||||
coordinate.
|
||||
|
||||
:arg region: region of the 3D viewport, typically bpy.context.region.
|
||||
:type region: :class:`bpy.types.Region`
|
||||
:arg rv3d: 3D region data, typically bpy.context.space_data.region_3d.
|
||||
:type rv3d: :class:`bpy.types.RegionView3D`
|
||||
:arg coord: 2d coordinates relative to the region:
|
||||
(event.mouse_region_x, event.mouse_region_y) for example.
|
||||
:type coord: 2d vector
|
||||
:return: normalized 3d vector.
|
||||
:rtype: :class:`mathutils.Vector`
|
||||
"""
|
||||
from mathutils import Vector
|
||||
|
||||
viewinv = rv3d.view_matrix.inverted()
|
||||
if rv3d.is_perspective:
|
||||
persinv = rv3d.perspective_matrix.inverted()
|
||||
|
||||
out = Vector((
|
||||
(2.0 * coord[0] / region.width) - 1.0,
|
||||
(2.0 * coord[1] / region.height) - 1.0,
|
||||
-0.5
|
||||
))
|
||||
|
||||
w = out.dot(persinv[3].xyz) + persinv[3][3]
|
||||
|
||||
view_vector = ((persinv @ out) / w) - viewinv.translation
|
||||
else:
|
||||
view_vector = -viewinv.col[2].xyz
|
||||
|
||||
view_vector.normalize()
|
||||
|
||||
return view_vector
|
||||
|
||||
|
||||
def region_2d_to_origin_3d(region, rv3d, coord, *, clamp=None):
|
||||
"""
|
||||
Return the 3d view origin from the region relative 2d coords.
|
||||
|
||||
.. note::
|
||||
|
||||
Orthographic views have a less obvious origin,
|
||||
the far clip is used to define the viewport near/far extents.
|
||||
Since far clip can be a very large value,
|
||||
the result may give with numeric precision issues.
|
||||
|
||||
To avoid this problem, you can optionally clamp the far clip to a
|
||||
smaller value based on the data you're operating on.
|
||||
|
||||
:arg region: region of the 3D viewport, typically bpy.context.region.
|
||||
:type region: :class:`bpy.types.Region`
|
||||
:arg rv3d: 3D region data, typically bpy.context.space_data.region_3d.
|
||||
:type rv3d: :class:`bpy.types.RegionView3D`
|
||||
:arg coord: 2d coordinates relative to the region;
|
||||
(event.mouse_region_x, event.mouse_region_y) for example.
|
||||
:type coord: 2d vector
|
||||
:arg clamp: Clamp the maximum far-clip value used.
|
||||
(negative value will move the offset away from the view_location)
|
||||
:type clamp: float or None
|
||||
:return: The origin of the viewpoint in 3d space.
|
||||
:rtype: :class:`mathutils.Vector`
|
||||
"""
|
||||
viewinv = rv3d.view_matrix.inverted()
|
||||
|
||||
if rv3d.is_perspective:
|
||||
origin_start = viewinv.translation.copy()
|
||||
else:
|
||||
persmat = rv3d.perspective_matrix.copy()
|
||||
dx = (2.0 * coord[0] / region.width) - 1.0
|
||||
dy = (2.0 * coord[1] / region.height) - 1.0
|
||||
persinv = persmat.inverted()
|
||||
origin_start = (
|
||||
(persinv.col[0].xyz * dx) +
|
||||
(persinv.col[1].xyz * dy) +
|
||||
persinv.translation
|
||||
)
|
||||
|
||||
if clamp != 0.0:
|
||||
if rv3d.view_perspective != 'CAMERA':
|
||||
# this value is scaled to the far clip already
|
||||
origin_offset = persinv.col[2].xyz
|
||||
if clamp is not None:
|
||||
if clamp < 0.0:
|
||||
origin_offset.negate()
|
||||
clamp = -clamp
|
||||
if origin_offset.length > clamp:
|
||||
origin_offset.length = clamp
|
||||
|
||||
origin_start -= origin_offset
|
||||
|
||||
return origin_start
|
||||
|
||||
|
||||
def region_2d_to_location_3d(region, rv3d, coord, depth_location):
|
||||
"""
|
||||
Return a 3d location from the region relative 2d coords, aligned with
|
||||
*depth_location*.
|
||||
|
||||
:arg region: region of the 3D viewport, typically bpy.context.region.
|
||||
:type region: :class:`bpy.types.Region`
|
||||
:arg rv3d: 3D region data, typically bpy.context.space_data.region_3d.
|
||||
:type rv3d: :class:`bpy.types.RegionView3D`
|
||||
:arg coord: 2d coordinates relative to the region;
|
||||
(event.mouse_region_x, event.mouse_region_y) for example.
|
||||
:type coord: 2d vector
|
||||
:arg depth_location: the returned vectors depth is aligned with this since
|
||||
there is no defined depth with a 2d region input.
|
||||
:type depth_location: 3d vector
|
||||
:return: normalized 3d vector.
|
||||
:rtype: :class:`mathutils.Vector`
|
||||
"""
|
||||
from mathutils import Vector
|
||||
|
||||
coord_vec = region_2d_to_vector_3d(region, rv3d, coord)
|
||||
depth_location = Vector(depth_location)
|
||||
|
||||
origin_start = region_2d_to_origin_3d(region, rv3d, coord)
|
||||
origin_end = origin_start + coord_vec
|
||||
|
||||
if rv3d.is_perspective:
|
||||
from mathutils.geometry import intersect_line_plane
|
||||
viewinv = rv3d.view_matrix.inverted()
|
||||
view_vec = viewinv.col[2].copy()
|
||||
return intersect_line_plane(
|
||||
origin_start,
|
||||
origin_end,
|
||||
depth_location,
|
||||
view_vec, 1,
|
||||
)
|
||||
else:
|
||||
from mathutils.geometry import intersect_point_line
|
||||
return intersect_point_line(
|
||||
depth_location,
|
||||
origin_start,
|
||||
origin_end,
|
||||
)[0]
|
||||
|
||||
|
||||
def location_3d_to_region_2d(region, rv3d, coord, *, default=None):
|
||||
"""
|
||||
Return the *region* relative 2d location of a 3d position.
|
||||
|
||||
:arg region: region of the 3D viewport, typically bpy.context.region.
|
||||
:type region: :class:`bpy.types.Region`
|
||||
:arg rv3d: 3D region data, typically bpy.context.space_data.region_3d.
|
||||
:type rv3d: :class:`bpy.types.RegionView3D`
|
||||
:arg coord: 3d worldspace location.
|
||||
:type coord: 3d vector
|
||||
:arg default: Return this value if ``coord``
|
||||
is behind the origin of a perspective view.
|
||||
:return: 2d location
|
||||
:rtype: :class:`mathutils.Vector` or ``default`` argument.
|
||||
"""
|
||||
from mathutils import Vector
|
||||
|
||||
prj = rv3d.perspective_matrix @ Vector((coord[0], coord[1], coord[2], 1.0))
|
||||
if prj.w > 0.0:
|
||||
width_half = region.width / 2.0
|
||||
height_half = region.height / 2.0
|
||||
|
||||
return Vector((
|
||||
width_half + width_half * (prj.x / prj.w),
|
||||
height_half + height_half * (prj.y / prj.w),
|
||||
))
|
||||
else:
|
||||
return default
|
||||
149
scripts/modules/bpy_extras/wm_utils/progress_report.py
Normal file
149
scripts/modules/bpy_extras/wm_utils/progress_report.py
Normal file
@@ -0,0 +1,149 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import time
|
||||
|
||||
|
||||
class ProgressReport:
|
||||
"""
|
||||
A basic 'progress report' using either simple prints in console, or WindowManager's 'progress' API.
|
||||
|
||||
This object can be used as a context manager.
|
||||
|
||||
It supports multiple levels of 'substeps' - you shall always enter at least one substep (because level 0
|
||||
has only one single step, representing the whole 'area' of the progress stuff).
|
||||
|
||||
You should give the expected number of substeps each time you enter a new one (you may then step more or less then
|
||||
given number, but this will give incoherent progression).
|
||||
|
||||
Leaving a substep automatically steps by one the parent level.
|
||||
|
||||
with ProgressReport() as progress: # Not giving a WindowManager here will default to console printing.
|
||||
progress.enter_substeps(10)
|
||||
for i in range(10):
|
||||
progress.enter_substeps(100)
|
||||
for j in range(100):
|
||||
progress.step()
|
||||
progress.leave_substeps() # No need to step here, this implicitly does it.
|
||||
progress.leave_substeps("Finished!") # You may pass some message too.
|
||||
"""
|
||||
__slots__ = ('wm', 'running', 'steps', 'curr_step', 'start_time')
|
||||
|
||||
def __init__(self, wm=None):
|
||||
self_wm = getattr(self, 'wm', None)
|
||||
if self_wm:
|
||||
self.finalize()
|
||||
self.running = False
|
||||
|
||||
self.wm = wm
|
||||
self.steps = [100000]
|
||||
self.curr_step = [0]
|
||||
|
||||
initialize = __init__
|
||||
|
||||
def __enter__(self):
|
||||
self.start_time = [time.time()]
|
||||
if self.wm:
|
||||
self.wm.progress_begin(0, self.steps[0])
|
||||
self.update()
|
||||
self.running = True
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type=None, exc_value=None, traceback=None):
|
||||
self.running = False
|
||||
if self.wm:
|
||||
self.wm.progress_end()
|
||||
self.wm = None
|
||||
print("\n")
|
||||
self.steps = [100000]
|
||||
self.curr_step = [0]
|
||||
self.start_time = [time.time()]
|
||||
|
||||
def start(self):
|
||||
self.__enter__()
|
||||
|
||||
def finalize(self):
|
||||
self.__exit__()
|
||||
|
||||
def update(self, msg=""):
|
||||
steps = sum(s * cs for (s, cs) in zip(self.steps, self.curr_step))
|
||||
steps_percent = steps / self.steps[0] * 100.0
|
||||
tm = time.time()
|
||||
loc_tm = tm - self.start_time[-1]
|
||||
tm -= self.start_time[0]
|
||||
if self.wm and self.running:
|
||||
self.wm.progress_update(steps)
|
||||
if msg:
|
||||
prefix = " " * (len(self.steps) - 1)
|
||||
print(prefix + "(%8.4f sec | %8.4f sec) %s\nProgress: %6.2f%%\r" %
|
||||
(tm, loc_tm, msg, steps_percent), end='')
|
||||
else:
|
||||
print("Progress: %6.2f%%\r" % (steps_percent,), end='')
|
||||
|
||||
def enter_substeps(self, nbr, msg=""):
|
||||
if msg:
|
||||
self.update(msg)
|
||||
self.steps.append(self.steps[-1] / max(nbr, 1))
|
||||
self.curr_step.append(0)
|
||||
self.start_time.append(time.time())
|
||||
|
||||
def step(self, msg="", nbr=1):
|
||||
self.curr_step[-1] += nbr
|
||||
self.update(msg)
|
||||
|
||||
def leave_substeps(self, msg=""):
|
||||
if (msg):
|
||||
self.update(msg)
|
||||
assert len(self.steps) > 1
|
||||
del self.steps[-1]
|
||||
del self.curr_step[-1]
|
||||
del self.start_time[-1]
|
||||
self.step()
|
||||
|
||||
|
||||
class ProgressReportSubstep:
|
||||
"""
|
||||
A sub-step context manager for ProgressReport.
|
||||
|
||||
It can be used to generate other sub-step contexts too, and can act as a (limited) proxy of its real ProgressReport.
|
||||
|
||||
Its exit method always ensure ProgressReport is back on 'level' it was before entering this context.
|
||||
This means it is especially useful to ensure a coherent behavior around code that could return/continue/break
|
||||
from many places, without having to bother to explicitly leave substep in each and every possible place!
|
||||
|
||||
with ProgressReport() as progress: # Not giving a WindowManager here will default to console printing.
|
||||
with ProgressReportSubstep(progress, 10, final_msg="Finished!") as subprogress1:
|
||||
for i in range(10):
|
||||
with ProgressReportSubstep(subprogress1, 100) as subprogress2:
|
||||
for j in range(100):
|
||||
subprogress2.step()
|
||||
"""
|
||||
__slots__ = ('progress', 'nbr', 'msg', 'final_msg', 'level')
|
||||
|
||||
def __init__(self, progress, nbr, msg="", final_msg=""):
|
||||
# Allows to generate a subprogress context handler from another one.
|
||||
progress = getattr(progress, 'progress', progress)
|
||||
|
||||
self.progress = progress
|
||||
self.nbr = nbr
|
||||
self.msg = msg
|
||||
self.final_msg = final_msg
|
||||
|
||||
def __enter__(self):
|
||||
self.level = len(self.progress.steps)
|
||||
self.progress.enter_substeps(self.nbr, self.msg)
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback):
|
||||
assert len(self.progress.steps) > self.level
|
||||
while len(self.progress.steps) > self.level + 1:
|
||||
self.progress.leave_substeps()
|
||||
self.progress.leave_substeps(self.final_msg)
|
||||
|
||||
def enter_substeps(self, nbr, msg=""):
|
||||
self.progress.enter_substeps(nbr, msg)
|
||||
|
||||
def step(self, msg="", nbr=1):
|
||||
self.progress.step(msg, nbr)
|
||||
|
||||
def leave_substeps(self, msg=""):
|
||||
self.progress.leave_substeps(msg)
|
||||
48
scripts/modules/bpy_restrict_state.py
Normal file
48
scripts/modules/bpy_restrict_state.py
Normal file
@@ -0,0 +1,48 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This module contains RestrictBlend context manager.
|
||||
"""
|
||||
|
||||
__all__ = (
|
||||
"RestrictBlend",
|
||||
)
|
||||
|
||||
import bpy as _bpy
|
||||
|
||||
|
||||
class _RestrictContext:
|
||||
__slots__ = ()
|
||||
_real_data = _bpy.data
|
||||
# safe, the pointer never changes
|
||||
_real_pref = _bpy.context.preferences
|
||||
|
||||
@property
|
||||
def window_manager(self):
|
||||
return self._real_data.window_managers[0]
|
||||
|
||||
@property
|
||||
def preferences(self):
|
||||
return self._real_pref
|
||||
|
||||
|
||||
class _RestrictData:
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
_context_restrict = _RestrictContext()
|
||||
_data_restrict = _RestrictData()
|
||||
|
||||
|
||||
class RestrictBlend:
|
||||
__slots__ = ("context", "data")
|
||||
|
||||
def __enter__(self):
|
||||
self.data = _bpy.data
|
||||
self.context = _bpy.context
|
||||
_bpy.data = _data_restrict
|
||||
_bpy.context = _context_restrict
|
||||
|
||||
def __exit__(self, type, value, traceback):
|
||||
_bpy.data = self.data
|
||||
_bpy.context = self.context
|
||||
1169
scripts/modules/bpy_types.py
Normal file
1169
scripts/modules/bpy_types.py
Normal file
@@ -0,0 +1,1169 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
from _bpy import types as bpy_types
|
||||
|
||||
StructRNA = bpy_types.bpy_struct
|
||||
StructMetaPropGroup = bpy_types.bpy_struct_meta_idprop
|
||||
# StructRNA = bpy_types.Struct
|
||||
|
||||
# Private dummy object use for comparison only.
|
||||
_sentinel = object()
|
||||
|
||||
# Note that methods extended in C are defined in: 'bpy_rna_types_capi.c'
|
||||
|
||||
|
||||
class Context(StructRNA):
|
||||
__slots__ = ()
|
||||
|
||||
def path_resolve(self, path, coerce=True):
|
||||
"""
|
||||
Returns the property from the path, raise an exception when not found.
|
||||
|
||||
:arg path: patch which this property resolves.
|
||||
:type path: string
|
||||
:arg coerce: optional argument, when True, the property will be converted into its Python representation.
|
||||
:type coerce: boolean
|
||||
"""
|
||||
# This is a convenience wrapper around `StructRNA.path_resolve` which doesn't support accessing context members.
|
||||
# Without this wrapper many users were writing `exec("context.%s" % data_path)` which is a security
|
||||
# concern if the `data_path` comes from an unknown source.
|
||||
# This function performs the initial lookup, after that the regular `path_resolve` function is used.
|
||||
|
||||
# Extract the initial attribute into `(attr, path_rest)`.
|
||||
sep = len(path)
|
||||
div = ""
|
||||
for div_test in (".", "["):
|
||||
sep_test = path.find(div_test, 0, sep)
|
||||
if sep_test != -1 and sep_test < sep:
|
||||
sep = sep_test
|
||||
div = div_test
|
||||
if div:
|
||||
attr = path[:sep]
|
||||
if div == ".":
|
||||
sep += 1
|
||||
path_rest = path[sep:]
|
||||
else:
|
||||
attr = path
|
||||
path_rest = ""
|
||||
|
||||
# Retrieve the value for `attr`.
|
||||
# Match the value error exception with that of "path_resolve"
|
||||
# to simplify exception handling for the caller.
|
||||
value = getattr(self, attr, _sentinel)
|
||||
if value is _sentinel:
|
||||
raise ValueError("Path could not be resolved: %r" % attr)
|
||||
|
||||
if value is None:
|
||||
return value
|
||||
|
||||
# If the attribute is a list property, apply subscripting.
|
||||
if isinstance(value, list) and path_rest.startswith("["):
|
||||
index_str, div, index_tail = path_rest[1:].partition("]")
|
||||
if not div:
|
||||
raise ValueError("Path index is not terminated: %s%s" % (attr, path_rest))
|
||||
try:
|
||||
index = int(index_str)
|
||||
except ValueError:
|
||||
raise ValueError("Path index is invalid: %s[%s]" % (attr, index_str))
|
||||
if 0 <= index < len(value):
|
||||
path_rest = index_tail
|
||||
value = value[index]
|
||||
else:
|
||||
raise IndexError("Path index out of range: %s[%s]" % (attr, index_str))
|
||||
|
||||
# Resolve the rest of the path if necessary.
|
||||
if path_rest:
|
||||
path_resolve_fn = getattr(value, "path_resolve", None)
|
||||
if path_resolve_fn is None:
|
||||
raise ValueError("Path %s resolves to a non RNA value" % attr)
|
||||
return path_resolve_fn(path_rest, coerce)
|
||||
|
||||
return value
|
||||
|
||||
def copy(self):
|
||||
from types import BuiltinMethodType
|
||||
new_context = {}
|
||||
generic_attrs = (
|
||||
*StructRNA.__dict__.keys(),
|
||||
"bl_rna", "rna_type", "copy",
|
||||
)
|
||||
for attr in dir(self):
|
||||
if not (attr.startswith("_") or attr in generic_attrs):
|
||||
value = getattr(self, attr)
|
||||
if type(value) != BuiltinMethodType:
|
||||
new_context[attr] = value
|
||||
|
||||
return new_context
|
||||
|
||||
|
||||
class Library(bpy_types.ID):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def users_id(self):
|
||||
"""ID data blocks which use this library"""
|
||||
import bpy
|
||||
|
||||
# See: readblenentry.c, IDTYPE_FLAGS_ISLINKABLE,
|
||||
# we could make this an attribute in rna.
|
||||
attr_links = (
|
||||
"actions", "armatures", "brushes", "cameras",
|
||||
"curves", "grease_pencils", "collections", "images",
|
||||
"lights", "lattices", "materials", "metaballs",
|
||||
"meshes", "node_groups", "objects", "scenes",
|
||||
"sounds", "speakers", "textures", "texts",
|
||||
"fonts", "worlds",
|
||||
)
|
||||
|
||||
return tuple(id_block
|
||||
for attr in attr_links
|
||||
for id_block in getattr(bpy.data, attr)
|
||||
if id_block.library == self)
|
||||
|
||||
|
||||
class Texture(bpy_types.ID):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def users_material(self):
|
||||
"""Materials that use this texture"""
|
||||
import bpy
|
||||
return tuple(mat for mat in bpy.data.materials
|
||||
if self in [slot.texture
|
||||
for slot in mat.texture_slots
|
||||
if slot]
|
||||
)
|
||||
|
||||
@property
|
||||
def users_object_modifier(self):
|
||||
"""Object modifiers that use this texture"""
|
||||
import bpy
|
||||
return tuple(
|
||||
obj for obj in bpy.data.objects if
|
||||
self in [
|
||||
mod.texture
|
||||
for mod in obj.modifiers
|
||||
if mod.type == 'DISPLACE']
|
||||
)
|
||||
|
||||
|
||||
class Collection(bpy_types.ID):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def children_recursive(self):
|
||||
"""A list of all children from this collection."""
|
||||
children_recursive = []
|
||||
|
||||
def recurse(parent):
|
||||
for child in parent.children:
|
||||
children_recursive.append(child)
|
||||
recurse(child)
|
||||
|
||||
recurse(self)
|
||||
return children_recursive
|
||||
|
||||
@property
|
||||
def users_dupli_group(self):
|
||||
"""The collection instance objects this collection is used in"""
|
||||
import bpy
|
||||
return tuple(obj for obj in bpy.data.objects
|
||||
if self == obj.instance_collection)
|
||||
|
||||
|
||||
class Object(bpy_types.ID):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
"""All the children of this object.
|
||||
|
||||
.. note:: Takes ``O(len(bpy.data.objects))`` time."""
|
||||
import bpy
|
||||
return tuple(child for child in bpy.data.objects
|
||||
if child.parent == self)
|
||||
|
||||
@property
|
||||
def children_recursive(self):
|
||||
"""A list of all children from this object.
|
||||
|
||||
.. note:: Takes ``O(len(bpy.data.objects))`` time."""
|
||||
import bpy
|
||||
parent_child_map = {}
|
||||
for child in bpy.data.objects:
|
||||
if (parent := child.parent) is not None:
|
||||
parent_child_map.setdefault(parent, []).append(child)
|
||||
|
||||
children_recursive = []
|
||||
|
||||
def recurse(parent):
|
||||
for child in parent_child_map.get(parent, ()):
|
||||
children_recursive.append(child)
|
||||
recurse(child)
|
||||
|
||||
recurse(self)
|
||||
return children_recursive
|
||||
|
||||
@property
|
||||
def users_collection(self):
|
||||
"""
|
||||
The collections this object is in.
|
||||
|
||||
.. note:: Takes ``O(len(bpy.data.collections) + len(bpy.data.scenes))`` time."""
|
||||
import bpy
|
||||
return (
|
||||
tuple(
|
||||
collection for collection in bpy.data.collections
|
||||
if self in collection.objects[:]
|
||||
) + tuple(
|
||||
scene.collection for scene in bpy.data.scenes
|
||||
if self in scene.collection.objects[:]
|
||||
)
|
||||
)
|
||||
|
||||
@property
|
||||
def users_scene(self):
|
||||
"""The scenes this object is in.
|
||||
|
||||
.. note:: Takes ``O(len(bpy.data.scenes) * len(bpy.data.objects))`` time."""
|
||||
import bpy
|
||||
return tuple(scene for scene in bpy.data.scenes
|
||||
if self in scene.objects[:])
|
||||
|
||||
|
||||
class WindowManager(bpy_types.ID):
|
||||
__slots__ = ()
|
||||
|
||||
def popup_menu(
|
||||
self, draw_func, *,
|
||||
title="",
|
||||
icon='NONE',
|
||||
):
|
||||
import bpy
|
||||
popup = self.popmenu_begin__internal(title, icon=icon)
|
||||
|
||||
try:
|
||||
draw_func(popup, bpy.context)
|
||||
finally:
|
||||
self.popmenu_end__internal(popup)
|
||||
|
||||
def popover(
|
||||
self, draw_func, *,
|
||||
ui_units_x=0,
|
||||
keymap=None,
|
||||
from_active_button=False,
|
||||
):
|
||||
import bpy
|
||||
popup = self.popover_begin__internal(
|
||||
ui_units_x=ui_units_x,
|
||||
from_active_button=from_active_button,
|
||||
)
|
||||
|
||||
try:
|
||||
draw_func(popup, bpy.context)
|
||||
finally:
|
||||
self.popover_end__internal(popup, keymap=keymap)
|
||||
|
||||
def popup_menu_pie(
|
||||
self, event, draw_func, *,
|
||||
title="",
|
||||
icon='NONE',
|
||||
):
|
||||
import bpy
|
||||
pie = self.piemenu_begin__internal(title, icon=icon, event=event)
|
||||
|
||||
if pie:
|
||||
try:
|
||||
draw_func(pie, bpy.context)
|
||||
finally:
|
||||
self.piemenu_end__internal(pie)
|
||||
|
||||
|
||||
class WorkSpace(bpy_types.ID):
|
||||
__slots__ = ()
|
||||
|
||||
def status_text_set(self, text):
|
||||
"""
|
||||
Set the status text or None to clear,
|
||||
When text is a function, this will be called with the (header, context) arguments.
|
||||
"""
|
||||
from bl_ui.space_statusbar import STATUSBAR_HT_header
|
||||
draw_fn = getattr(STATUSBAR_HT_header, "_draw_orig", None)
|
||||
if draw_fn is None:
|
||||
draw_fn = STATUSBAR_HT_header._draw_orig = STATUSBAR_HT_header.draw
|
||||
|
||||
if not (text is None or isinstance(text, str)):
|
||||
draw_fn = text
|
||||
text = None
|
||||
|
||||
self.status_text_set_internal(text)
|
||||
STATUSBAR_HT_header.draw = draw_fn
|
||||
|
||||
|
||||
class _GenericBone:
|
||||
"""
|
||||
functions for bones, common between Armature/Pose/Edit bones.
|
||||
internal subclassing use only.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
def translate(self, vec):
|
||||
"""Utility function to add *vec* to the head and tail of this bone"""
|
||||
self.head += vec
|
||||
self.tail += vec
|
||||
|
||||
def parent_index(self, parent_test):
|
||||
"""
|
||||
The same as 'bone in other_bone.parent_recursive'
|
||||
but saved generating a list.
|
||||
"""
|
||||
# use the name so different types can be tested.
|
||||
name = parent_test.name
|
||||
|
||||
parent = self.parent
|
||||
i = 1
|
||||
while parent:
|
||||
if parent.name == name:
|
||||
return i
|
||||
parent = parent.parent
|
||||
i += 1
|
||||
|
||||
return 0
|
||||
|
||||
@property
|
||||
def x_axis(self):
|
||||
""" Vector pointing down the x-axis of the bone.
|
||||
"""
|
||||
from mathutils import Vector
|
||||
return self.matrix.to_3x3() @ Vector((1.0, 0.0, 0.0))
|
||||
|
||||
@property
|
||||
def y_axis(self):
|
||||
""" Vector pointing down the y-axis of the bone.
|
||||
"""
|
||||
from mathutils import Vector
|
||||
return self.matrix.to_3x3() @ Vector((0.0, 1.0, 0.0))
|
||||
|
||||
@property
|
||||
def z_axis(self):
|
||||
""" Vector pointing down the z-axis of the bone.
|
||||
"""
|
||||
from mathutils import Vector
|
||||
return self.matrix.to_3x3() @ Vector((0.0, 0.0, 1.0))
|
||||
|
||||
@property
|
||||
def basename(self):
|
||||
"""The name of this bone before any '.' character"""
|
||||
# return self.name.rsplit(".", 1)[0]
|
||||
return self.name.split(".")[0]
|
||||
|
||||
@property
|
||||
def parent_recursive(self):
|
||||
"""A list of parents, starting with the immediate parent"""
|
||||
parent_list = []
|
||||
parent = self.parent
|
||||
|
||||
while parent:
|
||||
if parent:
|
||||
parent_list.append(parent)
|
||||
|
||||
parent = parent.parent
|
||||
|
||||
return parent_list
|
||||
|
||||
@property
|
||||
def center(self):
|
||||
"""The midpoint between the head and the tail."""
|
||||
return (self.head + self.tail) * 0.5
|
||||
|
||||
@property
|
||||
def vector(self):
|
||||
"""
|
||||
The direction this bone is pointing.
|
||||
Utility function for (tail - head)
|
||||
"""
|
||||
return (self.tail - self.head)
|
||||
|
||||
# NOTE: each bone type is responsible for implementing `children`.
|
||||
# This is done since `Bone` has direct access to this data in RNA.
|
||||
@property
|
||||
def children_recursive(self):
|
||||
"""A list of all children from this bone.
|
||||
|
||||
.. note:: Takes ``O(len(bones)**2)`` time."""
|
||||
bones_children = []
|
||||
for bone in self._other_bones:
|
||||
index = bone.parent_index(self)
|
||||
if index:
|
||||
bones_children.append((index, bone))
|
||||
|
||||
# sort by distance to parent
|
||||
bones_children.sort(key=lambda bone_pair: bone_pair[0])
|
||||
return [bone for index, bone in bones_children]
|
||||
|
||||
@property
|
||||
def children_recursive_basename(self):
|
||||
"""
|
||||
Returns a chain of children with the same base name as this bone.
|
||||
Only direct chains are supported, forks caused by multiple children
|
||||
with matching base names will terminate the function
|
||||
and not be returned.
|
||||
|
||||
.. note:: Takes ``O(len(bones)**2)`` time.
|
||||
"""
|
||||
basename = self.basename
|
||||
chain = []
|
||||
|
||||
child = self
|
||||
while True:
|
||||
children = child.children
|
||||
children_basename = []
|
||||
|
||||
for child in children:
|
||||
if basename == child.basename:
|
||||
children_basename.append(child)
|
||||
|
||||
if len(children_basename) == 1:
|
||||
child = children_basename[0]
|
||||
chain.append(child)
|
||||
else:
|
||||
if children_basename:
|
||||
print("multiple basenames found, "
|
||||
"this is probably not what you want!",
|
||||
self.name, children_basename)
|
||||
|
||||
break
|
||||
|
||||
return chain
|
||||
|
||||
@property
|
||||
def _other_bones(self):
|
||||
id_data = self.id_data
|
||||
|
||||
# `id_data` is an 'Object' for `PosePone`, otherwise it's an `Armature`.
|
||||
if isinstance(self, PoseBone):
|
||||
return id_data.pose.bones
|
||||
if isinstance(self, EditBone):
|
||||
return id_data.edit_bones
|
||||
if isinstance(self, Bone):
|
||||
return id_data.bones
|
||||
raise RuntimeError("Invalid type %r" % self)
|
||||
|
||||
|
||||
class PoseBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
obj = self.id_data
|
||||
pbones = obj.pose.bones
|
||||
|
||||
# Use Bone.children, which is a native RNA property.
|
||||
return tuple(pbones[bone.name] for bone in self.bone.children)
|
||||
|
||||
|
||||
class Bone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup):
|
||||
__slots__ = ()
|
||||
|
||||
# NOTE: `children` is implemented in RNA.
|
||||
|
||||
|
||||
class EditBone(StructRNA, _GenericBone, metaclass=StructMetaPropGroup):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def children(self):
|
||||
"""A list of all the bones children.
|
||||
|
||||
.. note:: Takes ``O(len(bones))`` time."""
|
||||
return [child for child in self._other_bones if child.parent == self]
|
||||
|
||||
def align_orientation(self, other):
|
||||
"""
|
||||
Align this bone to another by moving its tail and settings its roll
|
||||
the length of the other bone is not used.
|
||||
"""
|
||||
vec = other.vector.normalized() * self.length
|
||||
self.tail = self.head + vec
|
||||
self.roll = other.roll
|
||||
|
||||
def transform(self, matrix, *, scale=True, roll=True):
|
||||
"""
|
||||
Transform the the bones head, tail, roll and envelope
|
||||
(when the matrix has a scale component).
|
||||
|
||||
:arg matrix: 3x3 or 4x4 transformation matrix.
|
||||
:type matrix: :class:`mathutils.Matrix`
|
||||
:arg scale: Scale the bone envelope by the matrix.
|
||||
:type scale: bool
|
||||
:arg roll:
|
||||
|
||||
Correct the roll to point in the same relative
|
||||
direction to the head and tail.
|
||||
|
||||
:type roll: bool
|
||||
"""
|
||||
from mathutils import Vector
|
||||
z_vec = self.matrix.to_3x3() @ Vector((0.0, 0.0, 1.0))
|
||||
self.tail = matrix @ self.tail
|
||||
self.head = matrix @ self.head
|
||||
|
||||
if scale:
|
||||
scalar = matrix.median_scale
|
||||
self.head_radius *= scalar
|
||||
self.tail_radius *= scalar
|
||||
|
||||
if roll:
|
||||
self.align_roll(matrix @ z_vec)
|
||||
|
||||
|
||||
def ord_ind(i1, i2):
|
||||
if i1 < i2:
|
||||
return i1, i2
|
||||
return i2, i1
|
||||
|
||||
|
||||
class Mesh(bpy_types.ID):
|
||||
__slots__ = ()
|
||||
|
||||
def from_pydata(self, vertices, edges, faces):
|
||||
"""
|
||||
Make a mesh from a list of vertices/edges/faces
|
||||
Until we have a nicer way to make geometry, use this.
|
||||
|
||||
:arg vertices:
|
||||
|
||||
float triplets each representing (X, Y, Z)
|
||||
eg: [(0.0, 1.0, 0.5), ...].
|
||||
|
||||
:type vertices: iterable object
|
||||
:arg edges:
|
||||
|
||||
int pairs, each pair contains two indices to the
|
||||
*vertices* argument. eg: [(1, 2), ...]
|
||||
|
||||
When an empty iterable is passed in, the edges are inferred from the polygons.
|
||||
|
||||
:type edges: iterable object
|
||||
:arg faces:
|
||||
|
||||
iterator of faces, each faces contains three or more indices to
|
||||
the *vertices* argument. eg: [(5, 6, 8, 9), (1, 2, 3), ...]
|
||||
|
||||
:type faces: iterable object
|
||||
|
||||
.. warning::
|
||||
|
||||
Invalid mesh data
|
||||
*(out of range indices, edges with matching indices,
|
||||
2 sided faces... etc)* are **not** prevented.
|
||||
If the data used for mesh creation isn't known to be valid,
|
||||
run :class:`Mesh.validate` after this function.
|
||||
"""
|
||||
from itertools import chain, islice, accumulate
|
||||
|
||||
face_lengths = tuple(map(len, faces))
|
||||
|
||||
# NOTE: check non-empty lists by length because of how `numpy` handles truth tests, see: #90268.
|
||||
vertices_len = len(vertices)
|
||||
edges_len = len(edges)
|
||||
faces_len = len(faces)
|
||||
|
||||
self.vertices.add(vertices_len)
|
||||
self.edges.add(edges_len)
|
||||
self.loops.add(sum(face_lengths))
|
||||
self.polygons.add(faces_len)
|
||||
|
||||
self.vertices.foreach_set("co", tuple(chain.from_iterable(vertices)))
|
||||
self.edges.foreach_set("vertices", tuple(chain.from_iterable(edges)))
|
||||
|
||||
vertex_indices = tuple(chain.from_iterable(faces))
|
||||
loop_starts = tuple(islice(chain([0], accumulate(face_lengths)), faces_len))
|
||||
|
||||
self.polygons.foreach_set("loop_total", face_lengths)
|
||||
self.polygons.foreach_set("loop_start", loop_starts)
|
||||
self.polygons.foreach_set("vertices", vertex_indices)
|
||||
|
||||
if edges_len or faces_len:
|
||||
self.update(
|
||||
# Needed to either:
|
||||
# - Calculate edges that don't exist for polygons.
|
||||
# - Assign edges to polygon loops.
|
||||
calc_edges=bool(faces_len),
|
||||
# Flag loose edges.
|
||||
calc_edges_loose=bool(edges_len),
|
||||
)
|
||||
|
||||
@property
|
||||
def edge_keys(self):
|
||||
return [ed.key for ed in self.edges]
|
||||
|
||||
|
||||
class MeshEdge(StructRNA):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
return ord_ind(*tuple(self.vertices))
|
||||
|
||||
|
||||
class MeshLoopTriangle(StructRNA):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def center(self):
|
||||
"""The midpoint of the face."""
|
||||
face_verts = self.vertices[:]
|
||||
mesh_verts = self.id_data.vertices
|
||||
return (
|
||||
mesh_verts[face_verts[0]].co +
|
||||
mesh_verts[face_verts[1]].co +
|
||||
mesh_verts[face_verts[2]].co
|
||||
) / 3.0
|
||||
|
||||
@property
|
||||
def edge_keys(self):
|
||||
verts = self.vertices[:]
|
||||
return (
|
||||
ord_ind(verts[0], verts[1]),
|
||||
ord_ind(verts[1], verts[2]),
|
||||
ord_ind(verts[2], verts[0]),
|
||||
)
|
||||
|
||||
|
||||
class MeshPolygon(StructRNA):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def edge_keys(self):
|
||||
verts = self.vertices[:]
|
||||
vlen = len(self.vertices)
|
||||
return [ord_ind(verts[i], verts[(i + 1) % vlen]) for i in range(vlen)]
|
||||
|
||||
@property
|
||||
def loop_indices(self):
|
||||
start = self.loop_start
|
||||
end = start + self.loop_total
|
||||
return range(start, end)
|
||||
|
||||
|
||||
class Text(bpy_types.ID):
|
||||
__slots__ = ()
|
||||
|
||||
def as_module(self):
|
||||
import bpy
|
||||
from os.path import splitext, join
|
||||
from types import ModuleType
|
||||
name = self.name
|
||||
mod = ModuleType(splitext(name)[0])
|
||||
# This is a fake file-path, set this since some scripts check `__file__`,
|
||||
# error messages may include this as well.
|
||||
# NOTE: the file path may be a blank string if the file hasn't been saved.
|
||||
mod.__dict__.update({
|
||||
"__file__": join(bpy.data.filepath, name),
|
||||
})
|
||||
# TODO: We could use Text.compiled (C struct member)
|
||||
# if this is called often it will be much faster.
|
||||
exec(self.as_string(), mod.__dict__)
|
||||
return mod
|
||||
|
||||
|
||||
class Sound(bpy_types.ID):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def factory(self):
|
||||
"""The aud.Factory object of the sound."""
|
||||
import aud
|
||||
return aud._sound_from_pointer(self.as_pointer())
|
||||
|
||||
|
||||
class RNAMeta(type):
|
||||
# TODO(campbell): move to C-API
|
||||
@property
|
||||
def is_registered(cls):
|
||||
return "bl_rna" in cls.__dict__
|
||||
|
||||
|
||||
class RNAMetaPropGroup(StructMetaPropGroup, RNAMeta):
|
||||
pass
|
||||
|
||||
|
||||
# Same as 'Operator'
|
||||
# only without 'as_keywords'
|
||||
class Gizmo(StructRNA):
|
||||
__slots__ = ()
|
||||
|
||||
def __getattribute__(self, attr):
|
||||
properties = StructRNA.path_resolve(self, "properties")
|
||||
bl_rna = getattr(properties, "bl_rna", None)
|
||||
if (bl_rna is not None) and (attr in bl_rna.properties):
|
||||
return getattr(properties, attr)
|
||||
return super().__getattribute__(attr)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
properties = StructRNA.path_resolve(self, "properties")
|
||||
bl_rna = getattr(properties, "bl_rna", None)
|
||||
if (bl_rna is not None) and (attr in bl_rna.properties):
|
||||
return setattr(properties, attr, value)
|
||||
return super().__setattr__(attr, value)
|
||||
|
||||
def __delattr__(self, attr):
|
||||
properties = StructRNA.path_resolve(self, "properties")
|
||||
bl_rna = getattr(properties, "bl_rna", None)
|
||||
if (bl_rna is not None) and (attr in bl_rna.properties):
|
||||
return delattr(properties, attr)
|
||||
return super().__delattr__(attr)
|
||||
|
||||
from _bpy import (
|
||||
_rna_gizmo_target_set_handler as target_set_handler,
|
||||
_rna_gizmo_target_get_value as target_get_value,
|
||||
_rna_gizmo_target_set_value as target_set_value,
|
||||
_rna_gizmo_target_get_range as target_get_range,
|
||||
)
|
||||
|
||||
# Convenience wrappers around private `_gpu` module.
|
||||
def draw_custom_shape(self, shape, *, matrix=None, select_id=None):
|
||||
"""
|
||||
Draw a shape created form :class:`bpy.types.Gizmo.draw_custom_shape`.
|
||||
|
||||
:arg shape: The cached shape to draw.
|
||||
:type shape: Undefined.
|
||||
:arg matrix: 4x4 matrix, when not given
|
||||
:class:`bpy.types.Gizmo.matrix_world` is used.
|
||||
:type matrix: :class:`mathutils.Matrix`
|
||||
:arg select_id: The selection id.
|
||||
Only use when drawing within :class:`bpy.types.Gizmo.draw_select`.
|
||||
:type select_it: int
|
||||
"""
|
||||
import gpu
|
||||
|
||||
if matrix is None:
|
||||
matrix = self.matrix_world
|
||||
|
||||
batch, shader = shape
|
||||
|
||||
if select_id is not None:
|
||||
gpu.select.load_id(select_id)
|
||||
use_blend = False
|
||||
else:
|
||||
if self.is_highlight:
|
||||
color = (*self.color_highlight, self.alpha_highlight)
|
||||
else:
|
||||
color = (*self.color, self.alpha)
|
||||
shader.uniform_float("color", color)
|
||||
use_blend = color[3] < 1.0
|
||||
|
||||
if use_blend:
|
||||
gpu.state.blend_set('ALPHA')
|
||||
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.multiply_matrix(matrix)
|
||||
batch.draw()
|
||||
|
||||
if use_blend:
|
||||
gpu.state.blend_set('NONE')
|
||||
|
||||
@staticmethod
|
||||
def new_custom_shape(type, verts):
|
||||
"""
|
||||
Create a new shape that can be passed to :class:`bpy.types.Gizmo.draw_custom_shape`.
|
||||
|
||||
:arg type: The type of shape to create in (POINTS, LINES, TRIS, LINE_STRIP).
|
||||
:type type: string
|
||||
:arg verts: Coordinates.
|
||||
:type verts: sequence of of 2D or 3D coordinates.
|
||||
:arg display_name: Optional callback that takes the full path, returns the name to display.
|
||||
:type display_name: Callable that takes a string and returns a string.
|
||||
:return: The newly created shape.
|
||||
:rtype: Undefined (it may change).
|
||||
"""
|
||||
import gpu
|
||||
from gpu.types import (
|
||||
GPUBatch,
|
||||
GPUVertBuf,
|
||||
GPUVertFormat,
|
||||
)
|
||||
dims = len(verts[0])
|
||||
if dims not in {2, 3}:
|
||||
raise ValueError("Expected 2D or 3D vertex")
|
||||
fmt = GPUVertFormat()
|
||||
pos_id = fmt.attr_add(id="pos", comp_type='F32', len=dims, fetch_mode='FLOAT')
|
||||
vbo = GPUVertBuf(len=len(verts), format=fmt)
|
||||
vbo.attr_fill(id=pos_id, data=verts)
|
||||
batch = GPUBatch(type=type, buf=vbo)
|
||||
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
|
||||
batch.program_set(shader)
|
||||
return (batch, shader)
|
||||
|
||||
|
||||
# Dummy class to keep the reference in `bpy_types_dict` and avoid
|
||||
# errors like: "TypeError: expected GizmoGroup subclass of class ..."
|
||||
class GizmoGroup(StructRNA):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
# Only defined so operators members can be used by accessing self.order
|
||||
# with doc generation 'self.properties.bl_rna.properties' can fail
|
||||
class Operator(StructRNA, metaclass=RNAMeta):
|
||||
__slots__ = ()
|
||||
|
||||
def __getattribute__(self, attr):
|
||||
properties = StructRNA.path_resolve(self, "properties")
|
||||
bl_rna = getattr(properties, "bl_rna", None)
|
||||
if (bl_rna is not None) and (attr in bl_rna.properties):
|
||||
return getattr(properties, attr)
|
||||
return super().__getattribute__(attr)
|
||||
|
||||
def __setattr__(self, attr, value):
|
||||
properties = StructRNA.path_resolve(self, "properties")
|
||||
bl_rna = getattr(properties, "bl_rna", None)
|
||||
if (bl_rna is not None) and (attr in bl_rna.properties):
|
||||
return setattr(properties, attr, value)
|
||||
return super().__setattr__(attr, value)
|
||||
|
||||
def __delattr__(self, attr):
|
||||
properties = StructRNA.path_resolve(self, "properties")
|
||||
bl_rna = getattr(properties, "bl_rna", None)
|
||||
if (bl_rna is not None) and (attr in bl_rna.properties):
|
||||
return delattr(properties, attr)
|
||||
return super().__delattr__(attr)
|
||||
|
||||
def as_keywords(self, *, ignore=()):
|
||||
"""Return a copy of the properties as a dictionary"""
|
||||
ignore = ignore + ("rna_type",)
|
||||
return {attr: getattr(self, attr)
|
||||
for attr in self.properties.rna_type.properties.keys()
|
||||
if attr not in ignore}
|
||||
|
||||
|
||||
class Macro(StructRNA):
|
||||
# bpy_types is imported before ops is defined
|
||||
# so we have to do a local import on each run
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def define(cls, opname):
|
||||
from _bpy import ops
|
||||
return ops.macro_define(cls, opname)
|
||||
|
||||
|
||||
class PropertyGroup(StructRNA, metaclass=RNAMetaPropGroup):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class RenderEngine(StructRNA, metaclass=RNAMeta):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class KeyingSetInfo(StructRNA, metaclass=RNAMeta):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class AddonPreferences(StructRNA, metaclass=RNAMeta):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class _GenericUI:
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def _dyn_ui_initialize(cls):
|
||||
draw_funcs = getattr(cls.draw, "_draw_funcs", None)
|
||||
|
||||
if draw_funcs is None:
|
||||
|
||||
def draw_ls(self, context):
|
||||
# ensure menus always get default context
|
||||
operator_context_default = self.layout.operator_context
|
||||
|
||||
# Support filtering out by owner
|
||||
workspace = context.workspace
|
||||
if workspace.use_filter_by_owner:
|
||||
owner_names = {owner_id.name for owner_id in workspace.owner_ids}
|
||||
else:
|
||||
owner_names = None
|
||||
|
||||
for func in draw_ls._draw_funcs:
|
||||
|
||||
# Begin 'owner_id' filter.
|
||||
# Exclude Import/Export menus from this filtering (io addons should always show there)
|
||||
if not getattr(self, "bl_owner_use_filter", True):
|
||||
pass
|
||||
elif owner_names is not None:
|
||||
owner_id = getattr(func, "_owner", None)
|
||||
if owner_id is not None:
|
||||
if func._owner not in owner_names:
|
||||
continue
|
||||
# End 'owner_id' filter.
|
||||
|
||||
# so bad menu functions don't stop
|
||||
# the entire menu from drawing
|
||||
try:
|
||||
func(self, context)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
self.layout.operator_context = operator_context_default
|
||||
|
||||
draw_funcs = draw_ls._draw_funcs = [cls.draw]
|
||||
cls.draw = draw_ls
|
||||
|
||||
return draw_funcs
|
||||
|
||||
@staticmethod
|
||||
def _dyn_owner_apply(draw_func):
|
||||
from _bpy import _bl_owner_id_get
|
||||
owner_id = _bl_owner_id_get()
|
||||
if owner_id is not None:
|
||||
draw_func._owner = owner_id
|
||||
|
||||
@classmethod
|
||||
def is_extended(cls):
|
||||
return bool(getattr(cls.draw, "_draw_funcs", None))
|
||||
|
||||
@classmethod
|
||||
def append(cls, draw_func):
|
||||
"""
|
||||
Append a draw function to this menu,
|
||||
takes the same arguments as the menus draw function
|
||||
"""
|
||||
draw_funcs = cls._dyn_ui_initialize()
|
||||
cls._dyn_owner_apply(draw_func)
|
||||
draw_funcs.append(draw_func)
|
||||
|
||||
@classmethod
|
||||
def prepend(cls, draw_func):
|
||||
"""
|
||||
Prepend a draw function to this menu, takes the same arguments as
|
||||
the menus draw function
|
||||
"""
|
||||
draw_funcs = cls._dyn_ui_initialize()
|
||||
cls._dyn_owner_apply(draw_func)
|
||||
draw_funcs.insert(0, draw_func)
|
||||
|
||||
@classmethod
|
||||
def remove(cls, draw_func):
|
||||
"""Remove a draw function that has been added to this menu"""
|
||||
draw_funcs = cls._dyn_ui_initialize()
|
||||
try:
|
||||
draw_funcs.remove(draw_func)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
|
||||
class Panel(StructRNA, _GenericUI, metaclass=RNAMeta):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class UIList(StructRNA, _GenericUI, metaclass=RNAMeta):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class Header(StructRNA, _GenericUI, metaclass=RNAMeta):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class Menu(StructRNA, _GenericUI, metaclass=RNAMeta):
|
||||
__slots__ = ()
|
||||
|
||||
def path_menu(self, searchpaths, operator, *,
|
||||
props_default=None, prop_filepath="filepath",
|
||||
filter_ext=None, filter_path=None, display_name=None,
|
||||
add_operator=None):
|
||||
"""
|
||||
Populate a menu from a list of paths.
|
||||
|
||||
:arg searchpaths: Paths to scan.
|
||||
:type searchpaths: sequence of strings.
|
||||
:arg operator: The operator id to use with each file.
|
||||
:type operator: string
|
||||
:arg prop_filepath: Optional operator filepath property (defaults to "filepath").
|
||||
:type prop_filepath: string
|
||||
:arg props_default: Properties to assign to each operator.
|
||||
:type props_default: dict
|
||||
:arg filter_ext: Optional callback that takes the file extensions.
|
||||
|
||||
Returning false excludes the file from the list.
|
||||
|
||||
:type filter_ext: Callable that takes a string and returns a bool.
|
||||
:arg display_name: Optional callback that takes the full path, returns the name to display.
|
||||
:type display_name: Callable that takes a string and returns a string.
|
||||
"""
|
||||
|
||||
layout = self.layout
|
||||
|
||||
import os
|
||||
import re
|
||||
import bpy.utils
|
||||
|
||||
layout = self.layout
|
||||
|
||||
if not searchpaths:
|
||||
layout.label(text="* Missing Paths *")
|
||||
|
||||
# collect paths
|
||||
files = []
|
||||
for directory in searchpaths:
|
||||
files.extend([
|
||||
(f, os.path.join(directory, f))
|
||||
for f in os.listdir(directory)
|
||||
if (not f.startswith("."))
|
||||
if ((filter_ext is None) or
|
||||
(filter_ext(os.path.splitext(f)[1])))
|
||||
if ((filter_path is None) or
|
||||
(filter_path(f)))
|
||||
])
|
||||
|
||||
# Perform a "natural sort", so 20 comes after 3 (for example).
|
||||
files.sort(
|
||||
key=lambda file_path:
|
||||
tuple(int(t) if t.isdigit() else t for t in re.split(r"(\d+)", file_path[0].lower())),
|
||||
)
|
||||
|
||||
col = layout.column(align=True)
|
||||
|
||||
for f, filepath in files:
|
||||
# Intentionally pass the full path to 'display_name' callback,
|
||||
# since the callback may want to use part a directory in the name.
|
||||
row = col.row(align=True)
|
||||
name = display_name(filepath) if display_name else bpy.path.display_name(f)
|
||||
props = row.operator(
|
||||
operator,
|
||||
text=name,
|
||||
translate=False,
|
||||
)
|
||||
|
||||
if props_default is not None:
|
||||
for attr, value in props_default.items():
|
||||
setattr(props, attr, value)
|
||||
|
||||
setattr(props, prop_filepath, filepath)
|
||||
if operator == "script.execute_preset":
|
||||
props.menu_idname = self.bl_idname
|
||||
|
||||
if add_operator:
|
||||
props = row.operator(add_operator, text="", icon='REMOVE')
|
||||
props.name = name
|
||||
props.remove_name = True
|
||||
|
||||
if add_operator:
|
||||
wm = bpy.data.window_managers[0]
|
||||
|
||||
layout.separator()
|
||||
row = layout.row()
|
||||
|
||||
sub = row.row()
|
||||
sub.emboss = 'NORMAL'
|
||||
sub.prop(wm, "preset_name", text="")
|
||||
|
||||
props = row.operator(add_operator, text="", icon='ADD')
|
||||
props.name = wm.preset_name
|
||||
|
||||
def draw_preset(self, _context):
|
||||
"""
|
||||
Define these on the subclass:
|
||||
- preset_operator (string)
|
||||
- preset_subdir (string)
|
||||
|
||||
Optionally:
|
||||
- preset_add_operator (string)
|
||||
- preset_extensions (set of strings)
|
||||
- preset_operator_defaults (dict of keyword args)
|
||||
"""
|
||||
import bpy
|
||||
from bpy.app.translations import pgettext_iface as iface_
|
||||
ext_valid = getattr(self, "preset_extensions", {".py", ".xml"})
|
||||
props_default = getattr(self, "preset_operator_defaults", None)
|
||||
add_operator = getattr(self, "preset_add_operator", None)
|
||||
self.path_menu(
|
||||
bpy.utils.preset_paths(self.preset_subdir),
|
||||
self.preset_operator,
|
||||
props_default=props_default,
|
||||
filter_ext=lambda ext: ext.lower() in ext_valid,
|
||||
add_operator=add_operator,
|
||||
display_name=lambda name: iface_(
|
||||
bpy.path.display_name(name, title_case=False))
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def draw_collapsible(cls, context, layout):
|
||||
# helper function for (optionally) collapsed header menus
|
||||
# only usable within headers
|
||||
if context.area.show_menus:
|
||||
# Align menus to space them closely.
|
||||
layout.row(align=True).menu_contents(cls.__name__)
|
||||
else:
|
||||
layout.menu(cls.__name__, icon='COLLAPSEMENU')
|
||||
|
||||
|
||||
class NodeTree(bpy_types.ID, metaclass=RNAMetaPropGroup):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class Node(StructRNA, metaclass=RNAMetaPropGroup):
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, _ntree):
|
||||
return True
|
||||
|
||||
|
||||
class NodeInternal(Node):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
class NodeSocket(StructRNA, metaclass=RNAMetaPropGroup):
|
||||
__slots__ = ()
|
||||
|
||||
@property
|
||||
def links(self):
|
||||
"""
|
||||
List of node links from or to this socket.
|
||||
|
||||
.. note:: Takes ``O(len(nodetree.links))`` time."""
|
||||
return tuple(
|
||||
link for link in self.id_data.links
|
||||
if (link.from_socket == self or
|
||||
link.to_socket == self))
|
||||
|
||||
|
||||
class NodeSocketInterface(StructRNA, metaclass=RNAMetaPropGroup):
|
||||
__slots__ = ()
|
||||
|
||||
|
||||
# These are intermediate subclasses, need a bpy type too
|
||||
class CompositorNode(NodeInternal):
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, ntree):
|
||||
return ntree.bl_idname == 'CompositorNodeTree'
|
||||
|
||||
def update(self):
|
||||
self.tag_need_exec()
|
||||
|
||||
|
||||
class ShaderNode(NodeInternal):
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, ntree):
|
||||
return ntree.bl_idname == 'ShaderNodeTree'
|
||||
|
||||
|
||||
class TextureNode(NodeInternal):
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, ntree):
|
||||
return ntree.bl_idname == 'TextureNodeTree'
|
||||
|
||||
|
||||
class GeometryNode(NodeInternal):
|
||||
__slots__ = ()
|
||||
|
||||
@classmethod
|
||||
def poll(cls, ntree):
|
||||
return ntree.bl_idname == 'GeometryNodeTree'
|
||||
337
scripts/modules/console_python.py
Normal file
337
scripts/modules/console_python.py
Normal file
@@ -0,0 +1,337 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
import sys
|
||||
import bpy
|
||||
|
||||
language_id = "python"
|
||||
|
||||
# store our own __main__ module, not 100% needed
|
||||
# but python expects this in some places
|
||||
_BPY_MAIN_OWN = True
|
||||
|
||||
|
||||
def add_scrollback(text, text_type):
|
||||
for l in text.split("\n"):
|
||||
bpy.ops.console.scrollback_append(text=l.replace("\t", " "),
|
||||
type=text_type)
|
||||
|
||||
|
||||
def replace_help(namespace):
|
||||
def _help(*args):
|
||||
# because of how the console works. we need our own help() pager func.
|
||||
# replace the bold function because it adds crazy chars
|
||||
import pydoc
|
||||
pydoc.getpager = lambda: pydoc.plainpager
|
||||
pydoc.Helper.getline = lambda self, prompt: None
|
||||
pydoc.TextDoc.use_bold = lambda self, text: text
|
||||
|
||||
pydoc.help(*args)
|
||||
|
||||
namespace["help"] = _help
|
||||
|
||||
|
||||
def get_console(console_id):
|
||||
"""
|
||||
helper function for console operators
|
||||
currently each text data block gets its own
|
||||
console - code.InteractiveConsole()
|
||||
...which is stored in this function.
|
||||
|
||||
console_id can be any hashable type
|
||||
"""
|
||||
from code import InteractiveConsole
|
||||
|
||||
consoles = getattr(get_console, "consoles", None)
|
||||
hash_next = hash(bpy.context.window_manager)
|
||||
|
||||
if consoles is None:
|
||||
consoles = get_console.consoles = {}
|
||||
get_console.consoles_namespace_hash = hash_next
|
||||
else:
|
||||
# check if clearing the namespace is needed to avoid a memory leak.
|
||||
# the window manager is normally loaded with new blend files
|
||||
# so this is a reasonable way to deal with namespace clearing.
|
||||
# bpy.data hashing is reset by undo so can't be used.
|
||||
hash_prev = getattr(get_console, "consoles_namespace_hash", 0)
|
||||
|
||||
if hash_prev != hash_next:
|
||||
get_console.consoles_namespace_hash = hash_next
|
||||
consoles.clear()
|
||||
|
||||
console_data = consoles.get(console_id)
|
||||
|
||||
if console_data:
|
||||
console, stdout, stderr = console_data
|
||||
|
||||
# XXX, bug in python 3.1.2, 3.2 ? (worked in 3.1.1)
|
||||
# seems there is no way to clear StringIO objects for writing, have to
|
||||
# make new ones each time.
|
||||
import io
|
||||
stdout = io.StringIO()
|
||||
stderr = io.StringIO()
|
||||
else:
|
||||
if _BPY_MAIN_OWN:
|
||||
import types
|
||||
bpy_main_mod = types.ModuleType("__main__")
|
||||
namespace = bpy_main_mod.__dict__
|
||||
else:
|
||||
namespace = {}
|
||||
|
||||
namespace["__builtins__"] = sys.modules["builtins"]
|
||||
namespace["bpy"] = bpy
|
||||
|
||||
# weak! - but highly convenient
|
||||
namespace["C"] = bpy.context
|
||||
namespace["D"] = bpy.data
|
||||
|
||||
replace_help(namespace)
|
||||
|
||||
console = InteractiveConsole(locals=namespace,
|
||||
filename="<blender_console>")
|
||||
|
||||
console.push("from mathutils import *")
|
||||
console.push("from math import *")
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
console._bpy_main_mod = bpy_main_mod
|
||||
|
||||
import io
|
||||
stdout = io.StringIO()
|
||||
stderr = io.StringIO()
|
||||
|
||||
consoles[console_id] = console, stdout, stderr
|
||||
|
||||
return console, stdout, stderr
|
||||
|
||||
|
||||
# Both prompts must be the same length
|
||||
PROMPT = '>>> '
|
||||
PROMPT_MULTI = '... '
|
||||
|
||||
|
||||
def execute(context, is_interactive):
|
||||
sc = context.space_data
|
||||
|
||||
try:
|
||||
line_object = sc.history[-1]
|
||||
except:
|
||||
return {'CANCELLED'}
|
||||
|
||||
console, stdout, stderr = get_console(hash(context.region))
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
main_mod_back = sys.modules["__main__"]
|
||||
sys.modules["__main__"] = console._bpy_main_mod
|
||||
|
||||
# redirect output
|
||||
from contextlib import (
|
||||
redirect_stdout,
|
||||
redirect_stderr,
|
||||
)
|
||||
|
||||
# not included with Python
|
||||
class redirect_stdin(redirect_stdout.__base__):
|
||||
_stream = "stdin"
|
||||
|
||||
# don't allow the stdin to be used, can lock blender.
|
||||
with redirect_stdout(stdout), \
|
||||
redirect_stderr(stderr), \
|
||||
redirect_stdin(None):
|
||||
|
||||
# in case exception happens
|
||||
line = "" # in case of encoding error
|
||||
is_multiline = False
|
||||
|
||||
try:
|
||||
line = line_object.body
|
||||
|
||||
# run the console, "\n" executes a multi line statement
|
||||
line_exec = line if line.strip() else "\n"
|
||||
|
||||
is_multiline = console.push(line_exec)
|
||||
except:
|
||||
# unlikely, but this can happen with unicode errors for example.
|
||||
import traceback
|
||||
stderr.write(traceback.format_exc())
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
sys.modules["__main__"] = main_mod_back
|
||||
|
||||
output = stdout.getvalue()
|
||||
output_err = stderr.getvalue()
|
||||
|
||||
# cleanup
|
||||
sys.last_traceback = None
|
||||
|
||||
# So we can reuse, clear all data
|
||||
stdout.truncate(0)
|
||||
stderr.truncate(0)
|
||||
|
||||
# special exception. its possible the command loaded a new user interface
|
||||
if hash(sc) != hash(context.space_data):
|
||||
return {'FINISHED'}
|
||||
|
||||
bpy.ops.console.scrollback_append(text=sc.prompt + line, type='INPUT')
|
||||
|
||||
if is_multiline:
|
||||
sc.prompt = PROMPT_MULTI
|
||||
if is_interactive:
|
||||
indent = line[:len(line) - len(line.lstrip())]
|
||||
if line.rstrip().endswith(":"):
|
||||
indent += " "
|
||||
else:
|
||||
indent = ""
|
||||
else:
|
||||
sc.prompt = PROMPT
|
||||
indent = ""
|
||||
|
||||
# insert a new blank line
|
||||
bpy.ops.console.history_append(text=indent, current_character=0,
|
||||
remove_duplicates=True)
|
||||
sc.history[-1].current_character = len(indent)
|
||||
|
||||
# Insert the output into the editor
|
||||
# not quite correct because the order might have changed,
|
||||
# but ok 99% of the time.
|
||||
if output:
|
||||
add_scrollback(output, 'OUTPUT')
|
||||
if output_err:
|
||||
add_scrollback(output_err, 'ERROR')
|
||||
|
||||
# execute any hooks
|
||||
for func, args in execute.hooks:
|
||||
func(*args)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
execute.hooks = []
|
||||
|
||||
|
||||
def autocomplete(context):
|
||||
from bl_console_utils.autocomplete import intellisense
|
||||
|
||||
sc = context.space_data
|
||||
|
||||
console = get_console(hash(context.region))[0]
|
||||
|
||||
if not console:
|
||||
return {'CANCELLED'}
|
||||
|
||||
# don't allow the stdin to be used, can lock blender.
|
||||
# note: unlikely stdin would be used for autocomplete. but its possible.
|
||||
stdin_backup = sys.stdin
|
||||
sys.stdin = None
|
||||
|
||||
scrollback = ""
|
||||
scrollback_error = ""
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
main_mod_back = sys.modules["__main__"]
|
||||
sys.modules["__main__"] = console._bpy_main_mod
|
||||
|
||||
try:
|
||||
current_line = sc.history[-1]
|
||||
line = current_line.body
|
||||
|
||||
# This function isn't aware of the text editor or being an operator
|
||||
# just does the autocomplete then copy its results back
|
||||
result = intellisense.expand(
|
||||
line=line,
|
||||
cursor=current_line.current_character,
|
||||
namespace=console.locals,
|
||||
private=bpy.app.debug_python)
|
||||
|
||||
line_new = result[0]
|
||||
current_line.body, current_line.current_character, scrollback = result
|
||||
del result
|
||||
|
||||
# update selection. setting body should really do this!
|
||||
ofs = len(line_new) - len(line)
|
||||
sc.select_start += ofs
|
||||
sc.select_end += ofs
|
||||
except:
|
||||
# unlikely, but this can happen with unicode errors for example.
|
||||
# or if the api attribute access itself causes an error.
|
||||
import traceback
|
||||
scrollback_error = traceback.format_exc()
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
sys.modules["__main__"] = main_mod_back
|
||||
|
||||
# Separate autocomplete output by command prompts
|
||||
if scrollback != '':
|
||||
bpy.ops.console.scrollback_append(text=sc.prompt + current_line.body,
|
||||
type='INPUT')
|
||||
|
||||
# Now we need to copy back the line from blender back into the
|
||||
# text editor. This will change when we don't use the text editor
|
||||
# anymore
|
||||
if scrollback:
|
||||
add_scrollback(scrollback, 'INFO')
|
||||
|
||||
if scrollback_error:
|
||||
add_scrollback(scrollback_error, 'ERROR')
|
||||
|
||||
# restore the stdin
|
||||
sys.stdin = stdin_backup
|
||||
|
||||
context.area.tag_redraw()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def copy_as_script(context):
|
||||
sc = context.space_data
|
||||
lines = [
|
||||
"import bpy",
|
||||
"from bpy import data as D",
|
||||
"from bpy import context as C",
|
||||
"from mathutils import *",
|
||||
"from math import *",
|
||||
"",
|
||||
]
|
||||
|
||||
for line in sc.scrollback:
|
||||
text = line.body
|
||||
type = line.type
|
||||
|
||||
if type == 'INFO': # ignore autocomp.
|
||||
continue
|
||||
if type == 'INPUT':
|
||||
if text.startswith(PROMPT):
|
||||
text = text[len(PROMPT):]
|
||||
elif text.startswith(PROMPT_MULTI):
|
||||
text = text[len(PROMPT_MULTI):]
|
||||
elif type == 'OUTPUT':
|
||||
text = "#~ " + text
|
||||
elif type == 'ERROR':
|
||||
text = "#! " + text
|
||||
|
||||
lines.append(text)
|
||||
|
||||
context.window_manager.clipboard = "\n".join(lines)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def banner(context):
|
||||
sc = context.space_data
|
||||
version_string = sys.version.strip().replace('\n', ' ')
|
||||
|
||||
message = (
|
||||
"PYTHON INTERACTIVE CONSOLE %s" % version_string,
|
||||
"",
|
||||
"Builtin Modules: "
|
||||
"bpy, bpy.data, bpy.ops, bpy.props, bpy.types, bpy.context, bpy.utils, bgl, gpu, blf, mathutils",
|
||||
|
||||
"Convenience Imports: from mathutils import *; from math import *",
|
||||
"Convenience Variables: C = bpy.context, D = bpy.data",
|
||||
"",
|
||||
)
|
||||
|
||||
for line in message:
|
||||
add_scrollback(line, 'OUTPUT')
|
||||
|
||||
sc.prompt = PROMPT
|
||||
|
||||
return {'FINISHED'}
|
||||
61
scripts/modules/console_shell.py
Normal file
61
scripts/modules/console_shell.py
Normal file
@@ -0,0 +1,61 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
import os
|
||||
import bpy
|
||||
|
||||
language_id = "shell"
|
||||
|
||||
|
||||
def add_scrollback(text, text_type):
|
||||
for l in text.split("\n"):
|
||||
bpy.ops.console.scrollback_append(text=l.replace("\t", " "),
|
||||
type=text_type)
|
||||
|
||||
|
||||
def shell_run(text):
|
||||
import subprocess
|
||||
val, output = subprocess.getstatusoutput(text)
|
||||
|
||||
if not val:
|
||||
style = 'OUTPUT'
|
||||
else:
|
||||
style = 'ERROR'
|
||||
|
||||
add_scrollback(output, style)
|
||||
|
||||
|
||||
PROMPT = "$ "
|
||||
|
||||
|
||||
def execute(context, _is_interactive):
|
||||
sc = context.space_data
|
||||
|
||||
try:
|
||||
line = sc.history[-1].body
|
||||
except:
|
||||
return {'CANCELLED'}
|
||||
|
||||
bpy.ops.console.scrollback_append(text=sc.prompt + line, type='INPUT')
|
||||
|
||||
shell_run(line)
|
||||
|
||||
# insert a new blank line
|
||||
bpy.ops.console.history_append(text="", current_character=0,
|
||||
remove_duplicates=True)
|
||||
|
||||
sc.prompt = os.getcwd() + PROMPT
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def autocomplete(_context):
|
||||
# sc = context.space_data
|
||||
# TODO
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
def banner(context):
|
||||
sc = context.space_data
|
||||
|
||||
shell_run("bash --version")
|
||||
sc.prompt = os.getcwd() + PROMPT
|
||||
|
||||
return {'FINISHED'}
|
||||
6
scripts/modules/gpu_extras/__init__.py
Normal file
6
scripts/modules/gpu_extras/__init__.py
Normal file
@@ -0,0 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"batch",
|
||||
"presets",
|
||||
)
|
||||
76
scripts/modules/gpu_extras/batch.py
Normal file
76
scripts/modules/gpu_extras/batch.py
Normal file
@@ -0,0 +1,76 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"batch_for_shader",
|
||||
)
|
||||
|
||||
|
||||
def batch_for_shader(shader, type, content, *, indices=None):
|
||||
"""
|
||||
Return a batch already configured and compatible with the shader.
|
||||
|
||||
:arg shader: shader for which a compatible format will be computed.
|
||||
:type shader: :class:`gpu.types.GPUShader`
|
||||
:arg type: "'POINTS', 'LINES', 'TRIS' or 'LINES_ADJ'".
|
||||
:type type: str
|
||||
:arg content: Maps the name of the shader attribute with the data to fill the vertex buffer.
|
||||
:type content: dict
|
||||
:return: compatible batch
|
||||
:rtype: :class:`gpu.types.Batch`
|
||||
"""
|
||||
from gpu.types import (
|
||||
GPUBatch,
|
||||
GPUIndexBuf,
|
||||
GPUVertBuf,
|
||||
GPUVertFormat,
|
||||
)
|
||||
|
||||
def recommended_comp_type(attr_type):
|
||||
if attr_type in {'FLOAT', 'VEC2', 'VEC3', 'VEC4', 'MAT3', 'MAT4'}:
|
||||
return 'F32'
|
||||
if attr_type in {'UINT', 'UVEC2', 'UVEC3', 'UVEC4'}:
|
||||
return 'U32'
|
||||
# `attr_type` in {'INT', 'IVEC2', 'IVEC3', 'IVEC4', 'BOOL'}.
|
||||
return 'I32'
|
||||
|
||||
def recommended_attr_len(attr_name):
|
||||
attr_len = 1
|
||||
try:
|
||||
item = content[attr_name][0]
|
||||
while True:
|
||||
attr_len *= len(item)
|
||||
item = item[0]
|
||||
except (TypeError, IndexError):
|
||||
pass
|
||||
return attr_len
|
||||
|
||||
def recommended_fetch_mode(comp_type):
|
||||
if comp_type == 'F32':
|
||||
return 'FLOAT'
|
||||
return 'INT'
|
||||
|
||||
for data in content.values():
|
||||
vbo_len = len(data)
|
||||
break
|
||||
else:
|
||||
raise ValueError("Empty 'content'")
|
||||
|
||||
vbo_format = GPUVertFormat()
|
||||
attrs_info = shader.attrs_info_get()
|
||||
for name, attr_type in attrs_info:
|
||||
comp_type = recommended_comp_type(attr_type)
|
||||
attr_len = recommended_attr_len(name)
|
||||
vbo_format.attr_add(id=name, comp_type=comp_type, len=attr_len, fetch_mode=recommended_fetch_mode(comp_type))
|
||||
|
||||
vbo = GPUVertBuf(vbo_format, vbo_len)
|
||||
|
||||
for id, data in content.items():
|
||||
if len(data) != vbo_len:
|
||||
raise ValueError("Length mismatch for 'content' values")
|
||||
vbo.attr_fill(id, data)
|
||||
|
||||
if indices is None:
|
||||
return GPUBatch(type=type, buf=vbo)
|
||||
else:
|
||||
ibo = GPUIndexBuf(type=type, seq=indices)
|
||||
return GPUBatch(type=type, buf=vbo, elem=ibo)
|
||||
91
scripts/modules/gpu_extras/presets.py
Normal file
91
scripts/modules/gpu_extras/presets.py
Normal file
@@ -0,0 +1,91 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
def draw_circle_2d(position, color, radius, *, segments=None):
|
||||
"""
|
||||
Draw a circle.
|
||||
|
||||
:arg position: Position where the circle will be drawn.
|
||||
:type position: 2D Vector
|
||||
:arg color: Color of the circle. To use transparency GL_BLEND has to be enabled.
|
||||
:type color: tuple containing RGBA values
|
||||
:arg radius: Radius of the circle.
|
||||
:type radius: float
|
||||
:arg segments: How many segments will be used to draw the circle.
|
||||
Higher values give better results but the drawing will take longer.
|
||||
If None or not specified, an automatic value will be calculated.
|
||||
:type segments: int or None
|
||||
"""
|
||||
from math import sin, cos, pi, ceil, acos
|
||||
import gpu
|
||||
from gpu.types import (
|
||||
GPUBatch,
|
||||
GPUVertBuf,
|
||||
GPUVertFormat,
|
||||
)
|
||||
|
||||
if segments is None:
|
||||
max_pixel_error = 0.25 # TODO: multiply 0.5 by display dpi
|
||||
segments = int(ceil(pi / acos(1.0 - max_pixel_error / radius)))
|
||||
segments = max(segments, 8)
|
||||
segments = min(segments, 1000)
|
||||
|
||||
if segments <= 0:
|
||||
raise ValueError("Amount of segments must be greater than 0.")
|
||||
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.translate(position)
|
||||
gpu.matrix.scale_uniform(radius)
|
||||
mul = (1.0 / (segments - 1)) * (pi * 2)
|
||||
verts = [(sin(i * mul), cos(i * mul)) for i in range(segments)]
|
||||
fmt = GPUVertFormat()
|
||||
pos_id = fmt.attr_add(id="pos", comp_type='F32', len=2, fetch_mode='FLOAT')
|
||||
vbo = GPUVertBuf(len=len(verts), format=fmt)
|
||||
vbo.attr_fill(id=pos_id, data=verts)
|
||||
batch = GPUBatch(type='LINE_STRIP', buf=vbo)
|
||||
shader = gpu.shader.from_builtin('UNIFORM_COLOR')
|
||||
batch.program_set(shader)
|
||||
shader.uniform_float("color", color)
|
||||
batch.draw()
|
||||
|
||||
|
||||
def draw_texture_2d(texture, position, width, height):
|
||||
"""
|
||||
Draw a 2d texture.
|
||||
|
||||
:arg texture: GPUTexture to draw (e.g. gpu.texture.from_image(image) for :class:`bpy.types.Image`).
|
||||
:type texture: :class:`gpu.types.GPUTexture`
|
||||
:arg position: Position of the lower left corner.
|
||||
:type position: 2D Vector
|
||||
:arg width: Width of the image when drawn (not necessarily
|
||||
the original width of the texture).
|
||||
:type width: float
|
||||
:arg height: Height of the image when drawn.
|
||||
:type height: float
|
||||
"""
|
||||
import gpu
|
||||
from . batch import batch_for_shader
|
||||
|
||||
coords = ((0, 0), (1, 0), (1, 1), (0, 1))
|
||||
|
||||
shader = gpu.shader.from_builtin('IMAGE')
|
||||
batch = batch_for_shader(
|
||||
shader, 'TRI_FAN',
|
||||
{"pos": coords, "texCoord": coords},
|
||||
)
|
||||
|
||||
with gpu.matrix.push_pop():
|
||||
gpu.matrix.translate(position)
|
||||
gpu.matrix.scale((width, height))
|
||||
|
||||
shader = gpu.shader.from_builtin('IMAGE')
|
||||
|
||||
if isinstance(texture, int):
|
||||
# Call the legacy bgl to not break the existing API
|
||||
import bgl
|
||||
bgl.glActiveTexture(bgl.GL_TEXTURE0)
|
||||
bgl.glBindTexture(bgl.GL_TEXTURE_2D, texture)
|
||||
shader.uniform_int("image", 0)
|
||||
else:
|
||||
shader.uniform_sampler("image", texture)
|
||||
|
||||
batch.draw(shader)
|
||||
191
scripts/modules/graphviz_export.py
Normal file
191
scripts/modules/graphviz_export.py
Normal file
@@ -0,0 +1,191 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import bpy
|
||||
|
||||
header = '''
|
||||
digraph ancestors {
|
||||
graph [fontsize=30 labelloc="t" label="" splines=false overlap=true, rankdir=BT];
|
||||
ratio = "auto" ;
|
||||
'''
|
||||
|
||||
footer = '''
|
||||
}
|
||||
'''
|
||||
|
||||
|
||||
def compat_str(text, line_length=0):
|
||||
|
||||
if line_length:
|
||||
text_ls = []
|
||||
while len(text) > line_length:
|
||||
text_ls.append(text[:line_length])
|
||||
text = text[line_length:]
|
||||
|
||||
if text:
|
||||
text_ls.append(text)
|
||||
text = '\n '.join(text_ls)
|
||||
|
||||
#text = text.replace('.', '.\n')
|
||||
#text = text.replace(']', ']\n')
|
||||
text = text.replace("\n", "\\n")
|
||||
text = text.replace('"', '\\"')
|
||||
return text
|
||||
|
||||
|
||||
def graph_armature(obj, filepath, FAKE_PARENT=True, CONSTRAINTS=True, DRIVERS=True, XTRA_INFO=True):
|
||||
CONSTRAINTS = DRIVERS = True
|
||||
|
||||
fileobject = open(filepath, "w")
|
||||
fw = fileobject.write
|
||||
fw(header)
|
||||
fw('label = "%s::%s" ;' % (bpy.data.filepath.split("/")[-1].split("\\")[-1], obj.name))
|
||||
|
||||
arm = obj.data
|
||||
|
||||
bones = [bone.name for bone in arm.bones]
|
||||
bones.sort()
|
||||
print("")
|
||||
for bone in bones:
|
||||
b = arm.bones[bone]
|
||||
print(">>", bone, ["*>", "->"][b.use_connect], getattr(getattr(b, "parent", ""), "name", ""))
|
||||
label = [bone]
|
||||
bone = arm.bones[bone]
|
||||
|
||||
for key, value in obj.pose.bones[bone.name].items():
|
||||
if key.startswith("_"):
|
||||
continue
|
||||
|
||||
if type(value) == float:
|
||||
value = "%.3f" % value
|
||||
elif type(value) == str:
|
||||
value = compat_str(value)
|
||||
|
||||
label.append("%s = %s" % (key, value))
|
||||
|
||||
opts = [
|
||||
"shape=box",
|
||||
"regular=1",
|
||||
"style=filled",
|
||||
"fixedsize=false",
|
||||
'label="%s"' % compat_str('\n'.join(label)),
|
||||
]
|
||||
|
||||
if bone.name.startswith('ORG'):
|
||||
opts.append("fillcolor=yellow")
|
||||
else:
|
||||
opts.append("fillcolor=white")
|
||||
|
||||
fw('"%s" [%s];\n' % (bone.name, ','.join(opts)))
|
||||
|
||||
fw('\n\n# Hierarchy:\n')
|
||||
|
||||
# Root node.
|
||||
if FAKE_PARENT:
|
||||
fw('"Object::%s" [];\n' % obj.name)
|
||||
|
||||
for bone in bones:
|
||||
bone = arm.bones[bone]
|
||||
|
||||
parent = bone.parent
|
||||
if parent:
|
||||
parent_name = parent.name
|
||||
connected = bone.use_connect
|
||||
elif FAKE_PARENT:
|
||||
parent_name = 'Object::%s' % obj.name
|
||||
connected = False
|
||||
else:
|
||||
continue
|
||||
|
||||
opts = ["dir=forward", "weight=2", "arrowhead=normal"]
|
||||
if not connected:
|
||||
opts.append("style=dotted")
|
||||
|
||||
fw('"%s" -> "%s" [%s] ;\n' % (bone.name, parent_name, ','.join(opts)))
|
||||
del bone
|
||||
|
||||
# constraints
|
||||
if CONSTRAINTS:
|
||||
fw('\n\n# Constraints:\n')
|
||||
for bone in bones:
|
||||
pbone = obj.pose.bones[bone]
|
||||
# must be ordered
|
||||
for constraint in pbone.constraints:
|
||||
subtarget = getattr(constraint, "subtarget", "")
|
||||
if subtarget:
|
||||
# TODO, not internal links
|
||||
opts = [
|
||||
'dir=forward',
|
||||
"weight=1",
|
||||
"arrowhead=normal",
|
||||
"arrowtail=none",
|
||||
"constraint=false",
|
||||
'color="red"',
|
||||
'labelfontsize=4',
|
||||
]
|
||||
if XTRA_INFO:
|
||||
label = "%s\n%s" % (constraint.type, constraint.name)
|
||||
opts.append('label="%s"' % compat_str(label))
|
||||
fw('"%s" -> "%s" [%s] ;\n' % (pbone.name, subtarget, ','.join(opts)))
|
||||
|
||||
# Drivers
|
||||
if DRIVERS:
|
||||
fw('\n\n# Drivers:\n')
|
||||
|
||||
def rna_path_as_pbone(rna_path):
|
||||
if not rna_path.startswith("pose.bones["):
|
||||
return None
|
||||
|
||||
#rna_path_bone = rna_path[:rna_path.index("]") + 1]
|
||||
# return obj.path_resolve(rna_path_bone)
|
||||
bone_name = rna_path.split("[")[1].split("]")[0]
|
||||
return obj.pose.bones[bone_name[1:-1]]
|
||||
|
||||
animation_data = obj.animation_data
|
||||
if animation_data:
|
||||
|
||||
fcurve_drivers = [fcurve_driver for fcurve_driver in animation_data.drivers]
|
||||
fcurve_drivers.sort(key=lambda fcurve_driver: fcurve_driver.data_path)
|
||||
|
||||
for fcurve_driver in fcurve_drivers:
|
||||
rna_path = fcurve_driver.data_path
|
||||
pbone = rna_path_as_pbone(rna_path)
|
||||
|
||||
if pbone:
|
||||
for var in fcurve_driver.driver.variables:
|
||||
for target in var.targets:
|
||||
pbone_target = rna_path_as_pbone(target.data_path)
|
||||
rna_path_target = target.data_path
|
||||
if pbone_target:
|
||||
opts = [
|
||||
'dir=forward',
|
||||
"weight=1",
|
||||
"arrowhead=normal",
|
||||
"arrowtail=none",
|
||||
"constraint=false",
|
||||
'color="blue"',
|
||||
"labelfontsize=4",
|
||||
]
|
||||
display_source = rna_path.replace("pose.bones", "")
|
||||
display_target = rna_path_target.replace("pose.bones", "")
|
||||
if XTRA_INFO:
|
||||
label = "%s\\n%s" % (display_source, display_target)
|
||||
opts.append('label="%s"' % compat_str(label))
|
||||
fw('"%s" -> "%s" [%s] ;\n' % (pbone_target.name, pbone.name, ','.join(opts)))
|
||||
|
||||
fw(footer)
|
||||
fileobject.close()
|
||||
|
||||
'''
|
||||
print(".", end="")
|
||||
import sys
|
||||
sys.stdout.flush()
|
||||
'''
|
||||
print("\nSaved:", filepath)
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import os
|
||||
tmppath = "/tmp/test.dot"
|
||||
graph_armature(bpy.context.object, tmppath, CONSTRAINTS=True, DRIVERS=True)
|
||||
os.system("dot -Tpng %s > %s; eog %s &" % (tmppath, tmppath + '.png', tmppath + '.png'))
|
||||
281
scripts/modules/keyingsets_utils.py
Normal file
281
scripts/modules/keyingsets_utils.py
Normal file
@@ -0,0 +1,281 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# This file defines a set of methods that are useful for various
|
||||
# Relative Keying Set (RKS) related operations, such as: callbacks
|
||||
# for polling, iterator callbacks, and also generate callbacks.
|
||||
# All of these can be used in conjunction with the others.
|
||||
|
||||
__all__ = (
|
||||
"path_add_property",
|
||||
"RKS_POLL_selected_objects",
|
||||
"RKS_POLL_selected_bones",
|
||||
"RKS_POLL_selected_items",
|
||||
"RKS_ITER_selected_objects",
|
||||
"RKS_ITER_selected_bones",
|
||||
"RKS_ITER_selected_item",
|
||||
"RKS_GEN_available",
|
||||
"RKS_GEN_location",
|
||||
"RKS_GEN_rotation",
|
||||
"RKS_GEN_scaling",
|
||||
"RKS_GEN_bendy_bones",
|
||||
)
|
||||
|
||||
import bpy
|
||||
|
||||
###########################
|
||||
# General Utilities
|
||||
|
||||
|
||||
# Append the specified property name on the the existing path
|
||||
def path_add_property(path, prop):
|
||||
if path:
|
||||
return path + "." + prop
|
||||
else:
|
||||
return prop
|
||||
|
||||
###########################
|
||||
# Poll Callbacks
|
||||
|
||||
|
||||
# selected objects (active object must be in object mode)
|
||||
def RKS_POLL_selected_objects(_ksi, context):
|
||||
ob = context.active_object
|
||||
if ob:
|
||||
return ob.mode == 'OBJECT'
|
||||
else:
|
||||
return bool(context.selected_objects)
|
||||
|
||||
|
||||
# selected bones
|
||||
def RKS_POLL_selected_bones(_ksi, context):
|
||||
# we must be in Pose Mode, and there must be some bones selected
|
||||
ob = context.active_object
|
||||
if ob and ob.mode == 'POSE':
|
||||
if context.active_pose_bone or context.selected_pose_bones:
|
||||
return True
|
||||
|
||||
# nothing selected
|
||||
return False
|
||||
|
||||
|
||||
# selected bones or objects
|
||||
def RKS_POLL_selected_items(ksi, context):
|
||||
return (RKS_POLL_selected_bones(ksi, context) or
|
||||
RKS_POLL_selected_objects(ksi, context))
|
||||
|
||||
###########################
|
||||
# Iterator Callbacks
|
||||
|
||||
|
||||
# All selected objects or pose bones, depending on which we've got.
|
||||
def RKS_ITER_selected_item(ksi, context, ks):
|
||||
ob = context.active_object
|
||||
if ob and ob.mode == 'POSE':
|
||||
for bone in context.selected_pose_bones:
|
||||
ksi.generate(context, ks, bone)
|
||||
else:
|
||||
for ob in context.selected_objects:
|
||||
ksi.generate(context, ks, ob)
|
||||
|
||||
|
||||
# All selected objects only.
|
||||
def RKS_ITER_selected_objects(ksi, context, ks):
|
||||
for ob in context.selected_objects:
|
||||
ksi.generate(context, ks, ob)
|
||||
|
||||
|
||||
# All selected bones only.
|
||||
def RKS_ITER_selected_bones(ksi, context, ks):
|
||||
for bone in context.selected_pose_bones:
|
||||
ksi.generate(context, ks, bone)
|
||||
|
||||
###########################
|
||||
# Generate Callbacks
|
||||
|
||||
|
||||
# 'Available' F-Curves
|
||||
def RKS_GEN_available(_ksi, _context, ks, data):
|
||||
# try to get the animation data associated with the closest
|
||||
# ID-block to the data (neither of which may exist/be easy to find)
|
||||
id_block = data.id_data
|
||||
adt = getattr(id_block, "animation_data", None)
|
||||
|
||||
# there must also be an active action...
|
||||
if adt is None or adt.action is None:
|
||||
return
|
||||
|
||||
# if we haven't got an ID-block as 'data', try to restrict
|
||||
# paths added to only those which branch off from here
|
||||
# i.e. for bones
|
||||
if id_block != data:
|
||||
basePath = data.path_from_id()
|
||||
else:
|
||||
basePath = None # this is not needed...
|
||||
|
||||
# for each F-Curve, include a path to key it
|
||||
# NOTE: we don't need to set the group settings here
|
||||
for fcu in adt.action.fcurves:
|
||||
if basePath:
|
||||
if basePath in fcu.data_path:
|
||||
ks.paths.add(id_block, fcu.data_path, index=fcu.array_index)
|
||||
else:
|
||||
ks.paths.add(id_block, fcu.data_path, index=fcu.array_index)
|
||||
|
||||
# ------
|
||||
|
||||
|
||||
# get ID block and based ID path for transform generators
|
||||
# private function
|
||||
def get_transform_generators_base_info(data):
|
||||
# ID-block for the data
|
||||
id_block = data.id_data
|
||||
|
||||
# get base path and grouping method/name
|
||||
if isinstance(data, bpy.types.ID):
|
||||
# no path in this case
|
||||
path = ""
|
||||
|
||||
# transform data on ID-blocks directly should get grouped under a
|
||||
# hardcoded label ("Object Transforms") so that they get grouped
|
||||
# consistently when keyframed directly
|
||||
grouping = "Object Transforms"
|
||||
else:
|
||||
# get the path to the ID-block
|
||||
path = data.path_from_id()
|
||||
|
||||
# try to use the name of the data element to group the F-Curve
|
||||
# else fallback on the KeyingSet name
|
||||
grouping = getattr(data, "name", None)
|
||||
|
||||
# return the ID-block and the path
|
||||
return id_block, path, grouping
|
||||
|
||||
|
||||
# Location
|
||||
def RKS_GEN_location(_ksi, _context, ks, data):
|
||||
# get id-block and path info
|
||||
id_block, base_path, grouping = get_transform_generators_base_info(data)
|
||||
|
||||
# add the property name to the base path
|
||||
path = path_add_property(base_path, "location")
|
||||
|
||||
# add Keying Set entry for this...
|
||||
if grouping:
|
||||
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
|
||||
else:
|
||||
ks.paths.add(id_block, path)
|
||||
|
||||
|
||||
# Rotation
|
||||
def RKS_GEN_rotation(_ksi, _context, ks, data):
|
||||
# get id-block and path info
|
||||
id_block, base_path, grouping = get_transform_generators_base_info(data)
|
||||
|
||||
# add the property name to the base path
|
||||
# rotation mode affects the property used
|
||||
if data.rotation_mode == 'QUATERNION':
|
||||
path = path_add_property(base_path, "rotation_quaternion")
|
||||
elif data.rotation_mode == 'AXIS_ANGLE':
|
||||
path = path_add_property(base_path, "rotation_axis_angle")
|
||||
else:
|
||||
path = path_add_property(base_path, "rotation_euler")
|
||||
|
||||
# add Keying Set entry for this...
|
||||
if grouping:
|
||||
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
|
||||
else:
|
||||
ks.paths.add(id_block, path)
|
||||
|
||||
|
||||
# Scaling
|
||||
def RKS_GEN_scaling(_ksi, _context, ks, data):
|
||||
# get id-block and path info
|
||||
id_block, base_path, grouping = get_transform_generators_base_info(data)
|
||||
|
||||
# add the property name to the base path
|
||||
path = path_add_property(base_path, "scale")
|
||||
|
||||
# add Keying Set entry for this...
|
||||
if grouping:
|
||||
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
|
||||
else:
|
||||
ks.paths.add(id_block, path)
|
||||
|
||||
|
||||
# Custom Properties
|
||||
def RKS_GEN_custom_props(_ksi, _context, ks, data):
|
||||
# get id-block and path info
|
||||
id_block, base_path, grouping = get_transform_generators_base_info(data)
|
||||
|
||||
# Only some RNA types can be animated.
|
||||
prop_type_compat = {bpy.types.BoolProperty,
|
||||
bpy.types.IntProperty,
|
||||
bpy.types.FloatProperty}
|
||||
|
||||
# When working with a pose, 'id_block' is the armature object (which should
|
||||
# get the animation data), whereas 'data' is the bone being keyed.
|
||||
for cprop_name in data.keys():
|
||||
# ignore special "_RNA_UI" used for UI editing
|
||||
if cprop_name == "_RNA_UI":
|
||||
continue
|
||||
|
||||
prop_path = '["%s"]' % bpy.utils.escape_identifier(cprop_name)
|
||||
|
||||
try:
|
||||
rna_property = data.path_resolve(prop_path, False)
|
||||
except ValueError:
|
||||
# Can technically happen, but there is no known case.
|
||||
continue
|
||||
if rna_property is None:
|
||||
# In this case the property cannot be converted to an
|
||||
# FCurve-compatible value, so we can't keyframe it anyways.
|
||||
continue
|
||||
if rna_property.rna_type not in prop_type_compat:
|
||||
continue
|
||||
|
||||
path = "%s%s" % (base_path, prop_path)
|
||||
if grouping:
|
||||
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
|
||||
else:
|
||||
ks.paths.add(id_block, path)
|
||||
|
||||
# ------
|
||||
|
||||
|
||||
# Property identifiers for Bendy Bones
|
||||
bbone_property_ids = (
|
||||
"bbone_curveinx",
|
||||
"bbone_curveiny",
|
||||
"bbone_curveoutx",
|
||||
"bbone_curveouty",
|
||||
|
||||
"bbone_rollin",
|
||||
"bbone_rollout",
|
||||
|
||||
"bbone_scalein",
|
||||
"bbone_scaleout",
|
||||
|
||||
# NOTE: These are in the nested bone struct
|
||||
# Do it this way to force them to be included
|
||||
# in whatever actions are being keyed here
|
||||
"bone.bbone_in",
|
||||
"bone.bbone_out",
|
||||
)
|
||||
|
||||
|
||||
# Add Keying Set entries for bendy bones
|
||||
def RKS_GEN_bendy_bones(_ksi, _context, ks, data):
|
||||
# get id-block and path info
|
||||
# NOTE: This assumes that we're dealing with a bone here...
|
||||
id_block, base_path, grouping = get_transform_generators_base_info(data)
|
||||
|
||||
# for each of the bendy bone properties, add a Keying Set entry for it...
|
||||
for propname in bbone_property_ids:
|
||||
# add the property name to the base path
|
||||
path = path_add_property(base_path, propname)
|
||||
|
||||
# add Keying Set entry for this...
|
||||
if grouping:
|
||||
ks.paths.add(id_block, path, group_method='NAMED', group_name=grouping)
|
||||
else:
|
||||
ks.paths.add(id_block, path)
|
||||
164
scripts/modules/nodeitems_utils.py
Normal file
164
scripts/modules/nodeitems_utils.py
Normal file
@@ -0,0 +1,164 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
import bpy
|
||||
|
||||
|
||||
class NodeCategory:
|
||||
@classmethod
|
||||
def poll(cls, _context):
|
||||
return True
|
||||
|
||||
def __init__(self, identifier, name, *, description="", items=None):
|
||||
self.identifier = identifier
|
||||
self.name = name
|
||||
self.description = description
|
||||
|
||||
if items is None:
|
||||
self.items = lambda context: []
|
||||
elif callable(items):
|
||||
self.items = items
|
||||
else:
|
||||
def items_gen(context):
|
||||
for item in items:
|
||||
if item.poll is None or context is None or item.poll(context):
|
||||
yield item
|
||||
self.items = items_gen
|
||||
|
||||
|
||||
class NodeItem:
|
||||
def __init__(self, nodetype, *, label=None, settings=None, poll=None):
|
||||
|
||||
if settings is None:
|
||||
settings = {}
|
||||
|
||||
self.nodetype = nodetype
|
||||
self._label = label
|
||||
self.settings = settings
|
||||
self.poll = poll
|
||||
|
||||
@property
|
||||
def label(self):
|
||||
if self._label:
|
||||
return self._label
|
||||
else:
|
||||
# if no custom label is defined, fall back to the node type UI name
|
||||
bl_rna = bpy.types.Node.bl_rna_get_subclass(self.nodetype)
|
||||
if bl_rna is not None:
|
||||
return bl_rna.name
|
||||
else:
|
||||
return "Unknown"
|
||||
|
||||
@property
|
||||
def translation_context(self):
|
||||
if self._label:
|
||||
return bpy.app.translations.contexts.default
|
||||
else:
|
||||
# if no custom label is defined, fall back to the node type UI name
|
||||
bl_rna = bpy.types.Node.bl_rna_get_subclass(self.nodetype)
|
||||
if bl_rna is not None:
|
||||
return bl_rna.translation_context
|
||||
else:
|
||||
return bpy.app.translations.contexts.default
|
||||
|
||||
# NOTE: is a staticmethod because called with an explicit self argument
|
||||
# NodeItemCustom sets this as a variable attribute in __init__
|
||||
@staticmethod
|
||||
def draw(self, layout, _context):
|
||||
props = layout.operator("node.add_node", text=self.label, text_ctxt=self.translation_context)
|
||||
props.type = self.nodetype
|
||||
props.use_transform = True
|
||||
|
||||
for setting in self.settings.items():
|
||||
ops = props.settings.add()
|
||||
ops.name = setting[0]
|
||||
ops.value = setting[1]
|
||||
|
||||
|
||||
class NodeItemCustom:
|
||||
def __init__(self, *, poll=None, draw=None):
|
||||
self.poll = poll
|
||||
self.draw = draw
|
||||
|
||||
|
||||
_node_categories = {}
|
||||
|
||||
|
||||
def register_node_categories(identifier, cat_list):
|
||||
if identifier in _node_categories:
|
||||
raise KeyError("Node categories list '%s' already registered" % identifier)
|
||||
return
|
||||
|
||||
# works as draw function for menus
|
||||
def draw_node_item(self, context):
|
||||
layout = self.layout
|
||||
col = layout.column(align=True)
|
||||
for item in self.category.items(context):
|
||||
item.draw(item, col, context)
|
||||
|
||||
menu_types = []
|
||||
for cat in cat_list:
|
||||
menu_type = type("NODE_MT_category_" + cat.identifier, (bpy.types.Menu,), {
|
||||
"bl_space_type": 'NODE_EDITOR',
|
||||
"bl_label": cat.name,
|
||||
"category": cat,
|
||||
"poll": cat.poll,
|
||||
"draw": draw_node_item,
|
||||
})
|
||||
|
||||
menu_types.append(menu_type)
|
||||
|
||||
bpy.utils.register_class(menu_type)
|
||||
|
||||
def draw_add_menu(self, context):
|
||||
layout = self.layout
|
||||
|
||||
for cat in cat_list:
|
||||
if cat.poll(context):
|
||||
layout.menu("NODE_MT_category_%s" % cat.identifier)
|
||||
|
||||
# stores: (categories list, menu draw function, submenu types)
|
||||
_node_categories[identifier] = (cat_list, draw_add_menu, menu_types)
|
||||
|
||||
|
||||
def node_categories_iter(context):
|
||||
for cat_type in _node_categories.values():
|
||||
for cat in cat_type[0]:
|
||||
if cat.poll and ((context is None) or cat.poll(context)):
|
||||
yield cat
|
||||
|
||||
|
||||
def has_node_categories(context):
|
||||
for cat_type in _node_categories.values():
|
||||
for cat in cat_type[0]:
|
||||
if cat.poll and ((context is None) or cat.poll(context)):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def node_items_iter(context):
|
||||
for cat in node_categories_iter(context):
|
||||
for item in cat.items(context):
|
||||
yield item
|
||||
|
||||
|
||||
def unregister_node_cat_types(cats):
|
||||
for mt in cats[2]:
|
||||
bpy.utils.unregister_class(mt)
|
||||
|
||||
|
||||
def unregister_node_categories(identifier=None):
|
||||
# unregister existing UI classes
|
||||
if identifier:
|
||||
cat_types = _node_categories.get(identifier, None)
|
||||
if cat_types:
|
||||
unregister_node_cat_types(cat_types)
|
||||
del _node_categories[identifier]
|
||||
|
||||
else:
|
||||
for cat_types in _node_categories.values():
|
||||
unregister_node_cat_types(cat_types)
|
||||
_node_categories.clear()
|
||||
|
||||
|
||||
def draw_node_categories_menu(self, context):
|
||||
for cats in _node_categories.values():
|
||||
cats[1](self, context)
|
||||
902
scripts/modules/rna_info.py
Normal file
902
scripts/modules/rna_info.py
Normal file
@@ -0,0 +1,902 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# classes for extracting info from blenders internal classes
|
||||
|
||||
import bpy
|
||||
|
||||
# use to strip python paths
|
||||
script_paths = bpy.utils.script_paths()
|
||||
|
||||
_FAKE_STRUCT_SUBCLASS = True
|
||||
|
||||
|
||||
def _get_direct_attr(rna_type, attr):
|
||||
props = getattr(rna_type, attr)
|
||||
base = rna_type.base
|
||||
|
||||
if not base:
|
||||
return [prop for prop in props]
|
||||
else:
|
||||
props_base = getattr(base, attr).values()
|
||||
return [prop for prop in props if prop not in props_base]
|
||||
|
||||
|
||||
def get_direct_properties(rna_type):
|
||||
return _get_direct_attr(rna_type, "properties")
|
||||
|
||||
|
||||
def get_direct_functions(rna_type):
|
||||
return _get_direct_attr(rna_type, "functions")
|
||||
|
||||
|
||||
def rna_id_ignore(rna_id):
|
||||
if rna_id == "rna_type":
|
||||
return True
|
||||
|
||||
if "_OT_" in rna_id:
|
||||
return True
|
||||
if "_MT_" in rna_id:
|
||||
return True
|
||||
if "_PT_" in rna_id:
|
||||
return True
|
||||
if "_HT_" in rna_id:
|
||||
return True
|
||||
if "_KSI_" in rna_id:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def range_str(val):
|
||||
if val < -10000000:
|
||||
return "-inf"
|
||||
elif val > 10000000:
|
||||
return "inf"
|
||||
elif type(val) == float:
|
||||
return '%g' % val
|
||||
else:
|
||||
return str(val)
|
||||
|
||||
|
||||
def float_as_string(f):
|
||||
val_str = "%g" % f
|
||||
# Ensure a `.0` suffix for whole numbers, excluding scientific notation such as `1e-05` or `1e+5`.
|
||||
if '.' not in val_str and 'e' not in val_str:
|
||||
val_str += '.0'
|
||||
return val_str
|
||||
|
||||
|
||||
def get_py_class_from_rna(rna_type):
|
||||
""" Gets the Python type for a class which isn't necessarily added to ``bpy.types``.
|
||||
"""
|
||||
identifier = rna_type.identifier
|
||||
py_class = getattr(bpy.types, identifier, None)
|
||||
if py_class is not None:
|
||||
return py_class
|
||||
|
||||
def subclasses_recurse(cls):
|
||||
for c in cls.__subclasses__():
|
||||
# is_registered
|
||||
if "bl_rna" in cls.__dict__:
|
||||
yield c
|
||||
yield from subclasses_recurse(c)
|
||||
|
||||
while py_class is None:
|
||||
base = rna_type.base
|
||||
if base is None:
|
||||
raise Exception("can't find type")
|
||||
py_class_base = getattr(bpy.types, base.identifier, None)
|
||||
if py_class_base is not None:
|
||||
for cls in subclasses_recurse(py_class_base):
|
||||
if cls.bl_rna.identifier == identifier:
|
||||
return cls
|
||||
|
||||
|
||||
class InfoStructRNA:
|
||||
__slots__ = (
|
||||
"bl_rna",
|
||||
"identifier",
|
||||
"name",
|
||||
"description",
|
||||
"base",
|
||||
"nested",
|
||||
"full_path",
|
||||
"functions",
|
||||
"children",
|
||||
"references",
|
||||
"properties",
|
||||
"py_class",
|
||||
"module_name",
|
||||
)
|
||||
|
||||
global_lookup = {}
|
||||
|
||||
def __init__(self, rna_type):
|
||||
self.bl_rna = rna_type
|
||||
|
||||
self.identifier = rna_type.identifier
|
||||
self.name = rna_type.name
|
||||
self.description = rna_type.description.strip()
|
||||
|
||||
# set later
|
||||
self.base = None
|
||||
self.nested = None
|
||||
self.full_path = ""
|
||||
|
||||
self.functions = []
|
||||
self.children = []
|
||||
self.references = []
|
||||
self.properties = []
|
||||
|
||||
self.py_class = get_py_class_from_rna(self.bl_rna)
|
||||
self.module_name = (
|
||||
self.py_class.__module__
|
||||
if (self.py_class and not hasattr(bpy.types, self.identifier)) else
|
||||
"bpy.types"
|
||||
)
|
||||
if self.module_name == "bpy_types":
|
||||
self.module_name = "bpy.types"
|
||||
|
||||
def build(self):
|
||||
rna_type = self.bl_rna
|
||||
parent_id = self.identifier
|
||||
self.properties[:] = [GetInfoPropertyRNA(rna_prop, parent_id)
|
||||
for rna_prop in get_direct_properties(rna_type) if rna_prop.identifier != "rna_type"]
|
||||
self.functions[:] = [GetInfoFunctionRNA(rna_prop, parent_id)
|
||||
for rna_prop in get_direct_functions(rna_type)]
|
||||
|
||||
def get_bases(self):
|
||||
bases = []
|
||||
item = self
|
||||
|
||||
while item:
|
||||
item = item.base
|
||||
if item:
|
||||
bases.append(item)
|
||||
|
||||
return bases
|
||||
|
||||
def get_nested_properties(self, ls=None):
|
||||
if not ls:
|
||||
ls = self.properties[:]
|
||||
|
||||
if self.nested:
|
||||
self.nested.get_nested_properties(ls)
|
||||
|
||||
return ls
|
||||
|
||||
def _get_py_visible_attrs(self):
|
||||
attrs = []
|
||||
py_class = self.py_class
|
||||
|
||||
for attr_str in dir(py_class):
|
||||
if attr_str.startswith("_"):
|
||||
continue
|
||||
attrs.append((attr_str, getattr(py_class, attr_str)))
|
||||
return attrs
|
||||
|
||||
def get_py_properties(self):
|
||||
properties = []
|
||||
for identifier, attr in self._get_py_visible_attrs():
|
||||
if type(attr) is property:
|
||||
properties.append((identifier, attr))
|
||||
return properties
|
||||
|
||||
def get_py_functions(self):
|
||||
import types
|
||||
functions = []
|
||||
for identifier, attr in self._get_py_visible_attrs():
|
||||
# methods may be python wrappers to C functions
|
||||
attr_func = getattr(attr, "__func__", attr)
|
||||
if type(attr_func) in {types.FunctionType, types.MethodType}:
|
||||
functions.append((identifier, attr))
|
||||
return functions
|
||||
|
||||
def get_py_c_functions(self):
|
||||
import types
|
||||
functions = []
|
||||
for identifier, attr in self._get_py_visible_attrs():
|
||||
# methods may be python wrappers to C functions
|
||||
attr_func = getattr(attr, "__func__", attr)
|
||||
if (
|
||||
(type(attr_func) in {types.BuiltinMethodType, types.BuiltinFunctionType}) or
|
||||
# Without the `objclass` check, many inherited methods are included.
|
||||
(type(attr_func) == types.MethodDescriptorType and attr_func.__objclass__ == self.py_class)
|
||||
):
|
||||
functions.append((identifier, attr))
|
||||
return functions
|
||||
|
||||
def get_py_c_properties_getset(self):
|
||||
import types
|
||||
properties_getset = []
|
||||
for identifier, descr in self.py_class.__dict__.items():
|
||||
if type(descr) == types.GetSetDescriptorType:
|
||||
properties_getset.append((identifier, descr))
|
||||
return properties_getset
|
||||
|
||||
def __str__(self):
|
||||
|
||||
txt = ""
|
||||
txt += self.identifier
|
||||
if self.base:
|
||||
txt += "(%s)" % self.base.identifier
|
||||
txt += ": " + self.description + "\n"
|
||||
|
||||
for prop in self.properties:
|
||||
txt += prop.__repr__() + "\n"
|
||||
|
||||
for func in self.functions:
|
||||
txt += func.__repr__() + "\n"
|
||||
|
||||
return txt
|
||||
|
||||
|
||||
class InfoPropertyRNA:
|
||||
__slots__ = (
|
||||
"bl_prop",
|
||||
"srna",
|
||||
"identifier",
|
||||
"name",
|
||||
"description",
|
||||
"default_str",
|
||||
"default",
|
||||
"enum_items",
|
||||
"enum_pointer",
|
||||
"min",
|
||||
"max",
|
||||
"array_length",
|
||||
"array_dimensions",
|
||||
"collection_type",
|
||||
"type",
|
||||
"fixed_type",
|
||||
"subtype",
|
||||
"is_argument_optional",
|
||||
"is_enum_flag",
|
||||
"is_required",
|
||||
"is_readonly",
|
||||
"is_never_none",
|
||||
)
|
||||
global_lookup = {}
|
||||
|
||||
def __init__(self, rna_prop):
|
||||
self.bl_prop = rna_prop
|
||||
self.identifier = rna_prop.identifier
|
||||
self.name = rna_prop.name
|
||||
self.description = rna_prop.description.strip()
|
||||
self.default_str = "<UNKNOWN>"
|
||||
|
||||
def build(self):
|
||||
rna_prop = self.bl_prop
|
||||
|
||||
self.enum_items = []
|
||||
self.min = getattr(rna_prop, "hard_min", -1)
|
||||
self.max = getattr(rna_prop, "hard_max", -1)
|
||||
self.array_length = getattr(rna_prop, "array_length", 0)
|
||||
self.array_dimensions = getattr(rna_prop, "array_dimensions", ())[:]
|
||||
self.collection_type = GetInfoStructRNA(rna_prop.srna)
|
||||
self.subtype = getattr(rna_prop, "subtype", "")
|
||||
self.is_required = rna_prop.is_required
|
||||
self.is_readonly = rna_prop.is_readonly
|
||||
self.is_never_none = rna_prop.is_never_none
|
||||
self.is_argument_optional = rna_prop.is_argument_optional
|
||||
|
||||
self.type = rna_prop.type.lower()
|
||||
fixed_type = getattr(rna_prop, "fixed_type", "")
|
||||
if fixed_type:
|
||||
self.fixed_type = GetInfoStructRNA(fixed_type) # valid for pointer/collections
|
||||
else:
|
||||
self.fixed_type = None
|
||||
|
||||
self.enum_pointer = 0
|
||||
if self.type == "enum":
|
||||
# WARNING: don't convert to a tuple as this causes dynamically allocated enums to access freed memory
|
||||
# since freeing the iterator may free the memory used to store the internal `EnumPropertyItem` array.
|
||||
# To support this properly RNA would have to support owning the dynamically allocated memory.
|
||||
items = rna_prop.enum_items
|
||||
items_static = tuple(rna_prop.enum_items_static)
|
||||
self.enum_items[:] = [(item.identifier, item.name, item.description) for item in items]
|
||||
self.is_enum_flag = rna_prop.is_enum_flag
|
||||
# Prioritize static items as this is never going to be allocated data and is therefor
|
||||
# will be a stable match to compare against.
|
||||
item = (items_static or items)
|
||||
if item:
|
||||
self.enum_pointer = item[0].as_pointer()
|
||||
del items, items_static, item
|
||||
else:
|
||||
self.is_enum_flag = False
|
||||
|
||||
self.default_str = "" # fallback
|
||||
|
||||
if self.array_length:
|
||||
self.default = tuple(getattr(rna_prop, "default_array", ()))
|
||||
if self.array_dimensions[1] != 0: # Multi-dimensional array, convert default flat one accordingly.
|
||||
self.default_str = tuple(float_as_string(v) if self.type == "float" else str(v) for v in self.default)
|
||||
for dim in self.array_dimensions[::-1]:
|
||||
if dim != 0:
|
||||
self.default = tuple(zip(*((iter(self.default),) * dim)))
|
||||
self.default_str = tuple(
|
||||
"(%s)" % ", ".join(s for s in b) for b in zip(*((iter(self.default_str),) * dim))
|
||||
)
|
||||
self.default_str = self.default_str[0]
|
||||
elif self.type == "enum" and self.is_enum_flag:
|
||||
self.default = getattr(rna_prop, "default_flag", set())
|
||||
else:
|
||||
self.default = getattr(rna_prop, "default", None)
|
||||
|
||||
if self.type == "pointer":
|
||||
# pointer has no default, just set as None
|
||||
self.default = None
|
||||
self.default_str = "None"
|
||||
elif self.type == "string":
|
||||
self.default_str = "\"%s\"" % self.default
|
||||
elif self.type == "enum":
|
||||
if self.is_enum_flag:
|
||||
# self.default_str = "%r" % self.default # repr or set()
|
||||
self.default_str = "{%s}" % repr(list(sorted(self.default)))[1:-1]
|
||||
else:
|
||||
self.default_str = "'%s'" % self.default
|
||||
elif self.array_length:
|
||||
if self.array_dimensions[1] == 0: # single dimension array, we already took care of multi-dimensions ones.
|
||||
# special case for floats
|
||||
if self.type == "float" and len(self.default) > 0:
|
||||
self.default_str = "(%s)" % ", ".join(float_as_string(f) for f in self.default)
|
||||
else:
|
||||
self.default_str = str(self.default)
|
||||
else:
|
||||
if self.type == "float":
|
||||
self.default_str = float_as_string(self.default)
|
||||
else:
|
||||
self.default_str = str(self.default)
|
||||
|
||||
self.srna = GetInfoStructRNA(rna_prop.srna) # valid for pointer/collections
|
||||
|
||||
def get_arg_default(self, force=True):
|
||||
default = self.default_str
|
||||
if default and (force or self.is_required is False):
|
||||
return "%s=%s" % (self.identifier, default)
|
||||
return self.identifier
|
||||
|
||||
def get_type_description(
|
||||
self, *,
|
||||
as_ret=False,
|
||||
as_arg=False,
|
||||
class_fmt="%s",
|
||||
mathutils_fmt="%s",
|
||||
collection_id="Collection",
|
||||
enum_descr_override=None,
|
||||
):
|
||||
"""
|
||||
:arg enum_descr_override: Optionally override items for enum.
|
||||
Otherwise expand the literal items.
|
||||
:type enum_descr_override: string or None when unset.
|
||||
"""
|
||||
type_str = ""
|
||||
if self.fixed_type is None:
|
||||
type_str += self.type
|
||||
if self.array_length:
|
||||
if self.array_dimensions[1] != 0:
|
||||
dimension_str = " of %s items" % (
|
||||
" * ".join(str(d) for d in self.array_dimensions if d != 0)
|
||||
)
|
||||
type_str += " multi-dimensional array" + dimension_str
|
||||
else:
|
||||
dimension_str = " of %d items" % (self.array_length)
|
||||
type_str += " array" + dimension_str
|
||||
|
||||
# Describe mathutils types; logic mirrors pyrna_math_object_from_array
|
||||
if self.type == "float":
|
||||
if self.subtype == "MATRIX":
|
||||
if self.array_length in {9, 16}:
|
||||
type_str = (mathutils_fmt % "Matrix") + dimension_str
|
||||
elif self.subtype in {"COLOR", "COLOR_GAMMA"}:
|
||||
if self.array_length == 3:
|
||||
type_str = (mathutils_fmt % "Color") + dimension_str
|
||||
elif self.subtype in {"EULER", "QUATERNION"}:
|
||||
if self.array_length == 3:
|
||||
type_str = (mathutils_fmt % "Euler") + " rotation" + dimension_str
|
||||
elif self.array_length == 4:
|
||||
type_str = (mathutils_fmt % "Quaternion") + " rotation" + dimension_str
|
||||
elif self.subtype in {"COORDINATES", "TRANSLATION", "DIRECTION", "VELOCITY",
|
||||
"ACCELERATION", "XYZ", "XYZ_LENGTH"}:
|
||||
if 2 <= self.array_length <= 4:
|
||||
type_str = (mathutils_fmt % "Vector") + dimension_str
|
||||
|
||||
if self.type in {"float", "int"}:
|
||||
type_str += " in [%s, %s]" % (range_str(self.min), range_str(self.max))
|
||||
elif self.type == "enum":
|
||||
enum_descr = enum_descr_override
|
||||
if not enum_descr:
|
||||
if self.is_enum_flag:
|
||||
enum_descr = "{%s}" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
|
||||
else:
|
||||
enum_descr = "[%s]" % ", ".join(("'%s'" % s[0]) for s in self.enum_items)
|
||||
if self.is_enum_flag:
|
||||
type_str += " set in %s" % enum_descr
|
||||
else:
|
||||
type_str += " in %s" % enum_descr
|
||||
del enum_descr
|
||||
|
||||
if not (as_arg or as_ret):
|
||||
# write default property, ignore function args for this
|
||||
if self.type != "pointer":
|
||||
if self.default_str:
|
||||
type_str += ", default %s" % self.default_str
|
||||
|
||||
else:
|
||||
if self.type == "collection":
|
||||
if self.collection_type:
|
||||
collection_str = (class_fmt % self.collection_type.identifier) + (" %s of " % collection_id)
|
||||
else:
|
||||
collection_str = "%s of " % collection_id
|
||||
else:
|
||||
collection_str = ""
|
||||
|
||||
type_str += collection_str + (class_fmt % self.fixed_type.identifier)
|
||||
|
||||
# setup qualifiers for this value.
|
||||
type_info = []
|
||||
if as_ret:
|
||||
pass
|
||||
elif as_arg:
|
||||
if not self.is_required:
|
||||
type_info.append("optional")
|
||||
if self.is_argument_optional:
|
||||
type_info.append("optional argument")
|
||||
else: # readonly is only useful for self's, not args
|
||||
if self.is_readonly:
|
||||
type_info.append("readonly")
|
||||
|
||||
if self.is_never_none:
|
||||
type_info.append("never None")
|
||||
|
||||
if type_info:
|
||||
type_str += (", (%s)" % ", ".join(type_info))
|
||||
|
||||
return type_str
|
||||
|
||||
def __str__(self):
|
||||
txt = ""
|
||||
txt += " * " + self.identifier + ": " + self.description
|
||||
|
||||
return txt
|
||||
|
||||
|
||||
class InfoFunctionRNA:
|
||||
__slots__ = (
|
||||
"bl_func",
|
||||
"identifier",
|
||||
"description",
|
||||
"args",
|
||||
"return_values",
|
||||
"is_classmethod",
|
||||
)
|
||||
global_lookup = {}
|
||||
|
||||
def __init__(self, rna_func):
|
||||
self.bl_func = rna_func
|
||||
self.identifier = rna_func.identifier
|
||||
# self.name = rna_func.name # functions have no name!
|
||||
self.description = rna_func.description.strip()
|
||||
self.is_classmethod = not rna_func.use_self
|
||||
|
||||
self.args = []
|
||||
self.return_values = ()
|
||||
|
||||
def build(self):
|
||||
rna_func = self.bl_func
|
||||
parent_id = rna_func
|
||||
self.return_values = []
|
||||
|
||||
for rna_prop in rna_func.parameters.values():
|
||||
prop = GetInfoPropertyRNA(rna_prop, parent_id)
|
||||
if rna_prop.is_output:
|
||||
self.return_values.append(prop)
|
||||
else:
|
||||
self.args.append(prop)
|
||||
|
||||
self.return_values = tuple(self.return_values)
|
||||
|
||||
def __str__(self):
|
||||
txt = ''
|
||||
txt += ' * ' + self.identifier + '('
|
||||
|
||||
for arg in self.args:
|
||||
txt += arg.identifier + ', '
|
||||
txt += '): ' + self.description
|
||||
return txt
|
||||
|
||||
|
||||
class InfoOperatorRNA:
|
||||
__slots__ = (
|
||||
"bl_op",
|
||||
"identifier",
|
||||
"name",
|
||||
"module_name",
|
||||
"func_name",
|
||||
"description",
|
||||
"args",
|
||||
)
|
||||
global_lookup = {}
|
||||
|
||||
def __init__(self, rna_op):
|
||||
self.bl_op = rna_op
|
||||
self.identifier = rna_op.identifier
|
||||
|
||||
mod, name = self.identifier.split("_OT_", 1)
|
||||
self.module_name = mod.lower()
|
||||
self.func_name = name
|
||||
|
||||
# self.name = rna_func.name # functions have no name!
|
||||
self.description = rna_op.description.strip()
|
||||
|
||||
self.args = []
|
||||
|
||||
def build(self):
|
||||
rna_op = self.bl_op
|
||||
parent_id = self.identifier
|
||||
for rna_id, rna_prop in rna_op.properties.items():
|
||||
if rna_id == "rna_type":
|
||||
continue
|
||||
|
||||
prop = GetInfoPropertyRNA(rna_prop, parent_id)
|
||||
self.args.append(prop)
|
||||
|
||||
def get_location(self):
|
||||
try:
|
||||
op_class = getattr(bpy.types, self.identifier)
|
||||
except AttributeError:
|
||||
# defined in C.
|
||||
return None, None
|
||||
op_func = getattr(op_class, "execute", None)
|
||||
if op_func is None:
|
||||
op_func = getattr(op_class, "invoke", None)
|
||||
if op_func is None:
|
||||
op_func = getattr(op_class, "poll", None)
|
||||
|
||||
if op_func:
|
||||
op_code = op_func.__code__
|
||||
source_path = op_code.co_filename
|
||||
|
||||
# clear the prefix
|
||||
for p in script_paths:
|
||||
source_path = source_path.split(p)[-1]
|
||||
|
||||
if source_path[0] in "/\\":
|
||||
source_path = source_path[1:]
|
||||
|
||||
return source_path, op_code.co_firstlineno
|
||||
else:
|
||||
return None, None
|
||||
|
||||
|
||||
def _GetInfoRNA(bl_rna, cls, parent_id=""):
|
||||
|
||||
if bl_rna is None:
|
||||
return None
|
||||
|
||||
key = parent_id, bl_rna.identifier
|
||||
try:
|
||||
return cls.global_lookup[key]
|
||||
except KeyError:
|
||||
instance = cls.global_lookup[key] = cls(bl_rna)
|
||||
return instance
|
||||
|
||||
|
||||
def GetInfoStructRNA(bl_rna):
|
||||
return _GetInfoRNA(bl_rna, InfoStructRNA)
|
||||
|
||||
|
||||
def GetInfoPropertyRNA(bl_rna, parent_id):
|
||||
return _GetInfoRNA(bl_rna, InfoPropertyRNA, parent_id)
|
||||
|
||||
|
||||
def GetInfoFunctionRNA(bl_rna, parent_id):
|
||||
return _GetInfoRNA(bl_rna, InfoFunctionRNA, parent_id)
|
||||
|
||||
|
||||
def GetInfoOperatorRNA(bl_rna):
|
||||
return _GetInfoRNA(bl_rna, InfoOperatorRNA)
|
||||
|
||||
|
||||
def BuildRNAInfo():
|
||||
|
||||
# needed on successive calls to prevent stale data access
|
||||
for cls in (InfoStructRNA, InfoFunctionRNA, InfoOperatorRNA, InfoPropertyRNA):
|
||||
cls.global_lookup.clear()
|
||||
del cls
|
||||
|
||||
# Use for faster lookups
|
||||
# use rna_struct.identifier as the key for each dict
|
||||
rna_struct_dict = {} # store identifier:rna lookups
|
||||
rna_full_path_dict = {} # store the result of full_rna_struct_path(rna_struct)
|
||||
rna_children_dict = {} # store all rna_structs nested from here
|
||||
rna_references_dict = {} # store a list of rna path strings that reference this type
|
||||
# rna_functions_dict = {} # store all functions directly in this type (not inherited)
|
||||
|
||||
def full_rna_struct_path(rna_struct):
|
||||
"""
|
||||
Needed when referencing one struct from another
|
||||
"""
|
||||
nested = rna_struct.nested
|
||||
if nested:
|
||||
return "%s.%s" % (full_rna_struct_path(nested), rna_struct.identifier)
|
||||
else:
|
||||
return rna_struct.identifier
|
||||
|
||||
# def write_func(rna_func, ident):
|
||||
def base_id(rna_struct):
|
||||
try:
|
||||
return rna_struct.base.identifier
|
||||
except:
|
||||
return "" # invalid id
|
||||
|
||||
#structs = [(base_id(rna_struct), rna_struct.identifier, rna_struct) for rna_struct in bpy.doc.structs.values()]
|
||||
'''
|
||||
structs = []
|
||||
for rna_struct in bpy.doc.structs.values():
|
||||
structs.append( (base_id(rna_struct), rna_struct.identifier, rna_struct) )
|
||||
'''
|
||||
structs = []
|
||||
|
||||
def _bpy_types_iterator():
|
||||
# Don't report when these types are ignored.
|
||||
suppress_warning = {
|
||||
"bpy_func",
|
||||
"bpy_prop",
|
||||
"bpy_prop_array",
|
||||
"bpy_prop_collection",
|
||||
"bpy_struct",
|
||||
"bpy_struct_meta_idprop",
|
||||
}
|
||||
|
||||
names_unique = set()
|
||||
rna_type_list = []
|
||||
for rna_type_name in dir(bpy.types):
|
||||
names_unique.add(rna_type_name)
|
||||
rna_type = getattr(bpy.types, rna_type_name)
|
||||
rna_struct = getattr(rna_type, "bl_rna", None)
|
||||
if rna_struct is not None:
|
||||
rna_type_list.append(rna_type)
|
||||
yield (rna_type_name, rna_struct)
|
||||
elif rna_type_name.startswith("_"):
|
||||
# Ignore "__dir__", "__getattr__" .. etc.
|
||||
pass
|
||||
elif rna_type_name in suppress_warning:
|
||||
pass
|
||||
else:
|
||||
print("rna_info.BuildRNAInfo(..): ignoring type", repr(rna_type_name))
|
||||
|
||||
# Now, there are some sub-classes in add-ons we also want to include.
|
||||
# Cycles for e.g. these are referenced from the Scene, but not part of
|
||||
# bpy.types module.
|
||||
# Include all sub-classes we didn't already get from 'bpy.types'.
|
||||
i = 0
|
||||
while i < len(rna_type_list):
|
||||
rna_type = rna_type_list[i]
|
||||
for rna_sub_type in rna_type.__subclasses__():
|
||||
rna_sub_struct = getattr(rna_sub_type, "bl_rna", None)
|
||||
if rna_sub_struct is not None:
|
||||
rna_sub_type_name = rna_sub_struct.identifier
|
||||
if rna_sub_type_name not in names_unique:
|
||||
names_unique.add(rna_sub_type_name)
|
||||
rna_type_list.append(rna_sub_type)
|
||||
# The bl_idname may not match the class name in the file.
|
||||
# Always use the 'bl_idname' because using the Python
|
||||
# class name causes confusion - having two names for the same thing.
|
||||
# Since having two names for the same thing is trickier to support
|
||||
# without a significant benefit.
|
||||
yield (rna_sub_type_name, rna_sub_struct)
|
||||
i += 1
|
||||
|
||||
for (_rna_type_name, rna_struct) in _bpy_types_iterator():
|
||||
# if not _rna_type_name.startswith('__'):
|
||||
|
||||
identifier = rna_struct.identifier
|
||||
|
||||
if not rna_id_ignore(identifier):
|
||||
structs.append((base_id(rna_struct), identifier, rna_struct))
|
||||
|
||||
# Simple lookup
|
||||
rna_struct_dict[identifier] = rna_struct
|
||||
|
||||
# Store full rna path 'GameObjectSettings' -> 'Object.GameObjectSettings'
|
||||
rna_full_path_dict[identifier] = full_rna_struct_path(rna_struct)
|
||||
|
||||
# Store a list of functions, remove inherited later
|
||||
# NOT USED YET
|
||||
## rna_functions_dict[identifier] = get_direct_functions(rna_struct)
|
||||
|
||||
# fill in these later
|
||||
rna_children_dict[identifier] = []
|
||||
rna_references_dict[identifier] = []
|
||||
|
||||
del _bpy_types_iterator
|
||||
|
||||
structs.sort() # not needed but speeds up sort below, setting items without an inheritance first
|
||||
|
||||
# Arrange so classes are always defined in the correct order
|
||||
deps_ok = False
|
||||
while deps_ok is False:
|
||||
deps_ok = True
|
||||
rna_done = set()
|
||||
|
||||
for i, (rna_base, identifier, rna_struct) in enumerate(structs):
|
||||
|
||||
rna_done.add(identifier)
|
||||
|
||||
if rna_base and rna_base not in rna_done:
|
||||
deps_ok = False
|
||||
data = structs.pop(i)
|
||||
ok = False
|
||||
while i < len(structs):
|
||||
if structs[i][1] == rna_base:
|
||||
structs.insert(i + 1, data) # insert after the item we depend on.
|
||||
ok = True
|
||||
break
|
||||
i += 1
|
||||
|
||||
if not ok:
|
||||
print('Dependancy "%s" could not be found for "%s"' % (identifier, rna_base))
|
||||
|
||||
break
|
||||
|
||||
# Done ordering structs
|
||||
|
||||
# precalculate vars to avoid a lot of looping
|
||||
for (rna_base, identifier, rna_struct) in structs:
|
||||
|
||||
# rna_struct_path = full_rna_struct_path(rna_struct)
|
||||
rna_struct_path = rna_full_path_dict[identifier]
|
||||
|
||||
for rna_prop in get_direct_properties(rna_struct):
|
||||
rna_prop_identifier = rna_prop.identifier
|
||||
|
||||
if rna_prop_identifier == 'RNA' or rna_id_ignore(rna_prop_identifier):
|
||||
continue
|
||||
|
||||
for rna_prop_ptr in (getattr(rna_prop, "fixed_type", None), getattr(rna_prop, "srna", None)):
|
||||
# Does this property point to me?
|
||||
if rna_prop_ptr and rna_prop_ptr.identifier in rna_references_dict:
|
||||
rna_references_dict[rna_prop_ptr.identifier].append(
|
||||
"%s.%s" % (rna_struct_path, rna_prop_identifier))
|
||||
|
||||
for rna_func in get_direct_functions(rna_struct):
|
||||
for rna_prop_identifier, rna_prop in rna_func.parameters.items():
|
||||
|
||||
if rna_prop_identifier == 'RNA' or rna_id_ignore(rna_prop_identifier):
|
||||
continue
|
||||
|
||||
rna_prop_ptr = getattr(rna_prop, "fixed_type", None)
|
||||
|
||||
# Does this property point to me?
|
||||
if rna_prop_ptr and rna_prop_ptr.identifier in rna_references_dict:
|
||||
rna_references_dict[rna_prop_ptr.identifier].append(
|
||||
"%s.%s" % (rna_struct_path, rna_func.identifier))
|
||||
|
||||
# Store nested children
|
||||
nested = rna_struct.nested
|
||||
if nested:
|
||||
rna_children_dict[nested.identifier].append(rna_struct)
|
||||
|
||||
# Sort the refs, just reads nicer
|
||||
for rna_refs in rna_references_dict.values():
|
||||
rna_refs.sort()
|
||||
|
||||
info_structs = []
|
||||
for (rna_base, identifier, rna_struct) in structs:
|
||||
# if rna_struct.nested:
|
||||
# continue
|
||||
|
||||
#write_struct(rna_struct, '')
|
||||
info_struct = GetInfoStructRNA(rna_struct)
|
||||
if rna_base:
|
||||
info_struct.base = GetInfoStructRNA(rna_struct_dict[rna_base])
|
||||
info_struct.nested = GetInfoStructRNA(rna_struct.nested)
|
||||
info_struct.children[:] = rna_children_dict[identifier]
|
||||
info_struct.references[:] = rna_references_dict[identifier]
|
||||
info_struct.full_path = rna_full_path_dict[identifier]
|
||||
|
||||
info_structs.append(info_struct)
|
||||
|
||||
for rna_info_prop in InfoPropertyRNA.global_lookup.values():
|
||||
rna_info_prop.build()
|
||||
|
||||
for rna_info_prop in InfoFunctionRNA.global_lookup.values():
|
||||
rna_info_prop.build()
|
||||
|
||||
done_keys = set()
|
||||
new_keys = set(InfoStructRNA.global_lookup.keys())
|
||||
while new_keys:
|
||||
for rna_key in new_keys:
|
||||
rna_info = InfoStructRNA.global_lookup[rna_key]
|
||||
rna_info.build()
|
||||
for prop in rna_info.properties:
|
||||
prop.build()
|
||||
for func in rna_info.functions:
|
||||
func.build()
|
||||
for prop in func.args:
|
||||
prop.build()
|
||||
for prop in func.return_values:
|
||||
prop.build()
|
||||
done_keys |= new_keys
|
||||
new_keys = set(InfoStructRNA.global_lookup.keys()) - done_keys
|
||||
|
||||
# there are too many invalid defaults, unless we intend to fix, leave this off
|
||||
if 0:
|
||||
for rna_info in InfoStructRNA.global_lookup.values():
|
||||
for prop in rna_info.properties:
|
||||
# ERROR CHECK
|
||||
default = prop.default
|
||||
if type(default) in {float, int}:
|
||||
if default < prop.min or default > prop.max:
|
||||
print("\t %s.%s, %s not in [%s - %s]" %
|
||||
(rna_info.identifier, prop.identifier, default, prop.min, prop.max))
|
||||
|
||||
# now for operators
|
||||
op_mods = dir(bpy.ops)
|
||||
|
||||
for op_mod_name in sorted(op_mods):
|
||||
if op_mod_name.startswith('__'):
|
||||
continue
|
||||
|
||||
op_mod = getattr(bpy.ops, op_mod_name)
|
||||
operators = dir(op_mod)
|
||||
for op in sorted(operators):
|
||||
try:
|
||||
rna_prop = getattr(op_mod, op).get_rna_type()
|
||||
except AttributeError:
|
||||
rna_prop = None
|
||||
except TypeError:
|
||||
rna_prop = None
|
||||
|
||||
if rna_prop:
|
||||
GetInfoOperatorRNA(rna_prop)
|
||||
|
||||
for rna_info in InfoOperatorRNA.global_lookup.values():
|
||||
rna_info.build()
|
||||
for rna_prop in rna_info.args:
|
||||
rna_prop.build()
|
||||
|
||||
# for rna_info in InfoStructRNA.global_lookup.values():
|
||||
# print(rna_info)
|
||||
return (
|
||||
InfoStructRNA.global_lookup,
|
||||
InfoFunctionRNA.global_lookup,
|
||||
InfoOperatorRNA.global_lookup,
|
||||
InfoPropertyRNA.global_lookup,
|
||||
)
|
||||
|
||||
|
||||
def main():
|
||||
struct = BuildRNAInfo()[0]
|
||||
data = []
|
||||
for _struct_id, v in sorted(struct.items()):
|
||||
struct_id_str = v.identifier # "".join(sid for sid in struct_id if struct_id)
|
||||
|
||||
for base in v.get_bases():
|
||||
struct_id_str = base.identifier + "|" + struct_id_str
|
||||
|
||||
props = [(prop.identifier, prop) for prop in v.properties]
|
||||
for _prop_id, prop in sorted(props):
|
||||
# if prop.type == "boolean":
|
||||
# continue
|
||||
prop_type = prop.type
|
||||
if prop.array_length > 0:
|
||||
prop_type += "[%d]" % prop.array_length
|
||||
|
||||
data.append(
|
||||
"%s.%s -> %s: %s%s %s" %
|
||||
(struct_id_str, prop.identifier, prop.identifier, prop_type,
|
||||
", (read-only)" if prop.is_readonly else "", prop.description))
|
||||
data.sort()
|
||||
|
||||
if bpy.app.background:
|
||||
import sys
|
||||
sys.stderr.write("\n".join(data))
|
||||
sys.stderr.write("\n\nEOF\n")
|
||||
else:
|
||||
text = bpy.data.texts.new(name="api.py")
|
||||
text.from_string("\n".join(data))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
441
scripts/modules/rna_keymap_ui.py
Normal file
441
scripts/modules/rna_keymap_ui.py
Normal file
@@ -0,0 +1,441 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
__all__ = (
|
||||
"draw_entry",
|
||||
"draw_km",
|
||||
"draw_kmi",
|
||||
"draw_filtered",
|
||||
"draw_hierarchy",
|
||||
"draw_keymaps",
|
||||
)
|
||||
|
||||
|
||||
import bpy
|
||||
from bpy.app.translations import (
|
||||
contexts as i18n_contexts,
|
||||
pgettext_iface as iface_,
|
||||
)
|
||||
|
||||
|
||||
def _indented_layout(layout, level):
|
||||
indentpx = 16
|
||||
if level == 0:
|
||||
level = 0.0001 # Tweak so that a percentage of 0 won't split by half
|
||||
indent = level * indentpx / bpy.context.region.width
|
||||
|
||||
split = layout.split(factor=indent)
|
||||
col = split.column()
|
||||
col = split.column()
|
||||
return col
|
||||
|
||||
|
||||
def draw_entry(display_keymaps, entry, col, level=0):
|
||||
idname, spaceid, regionid, children = entry
|
||||
|
||||
for km, kc in display_keymaps:
|
||||
if km.name == idname and km.space_type == spaceid and km.region_type == regionid:
|
||||
draw_km(display_keymaps, kc, km, children, col, level)
|
||||
|
||||
'''
|
||||
km = kc.keymaps.find(idname, space_type=spaceid, region_type=regionid)
|
||||
if not km:
|
||||
kc = defkc
|
||||
km = kc.keymaps.find(idname, space_type=spaceid, region_type=regionid)
|
||||
|
||||
if km:
|
||||
draw_km(kc, km, children, col, level)
|
||||
'''
|
||||
|
||||
|
||||
def draw_km(display_keymaps, kc, km, children, layout, level):
|
||||
km = km.active()
|
||||
|
||||
layout.context_pointer_set("keymap", km)
|
||||
|
||||
col = _indented_layout(layout, level)
|
||||
|
||||
row = col.row(align=True)
|
||||
row.prop(km, "show_expanded_children", text="", emboss=False)
|
||||
row.label(text=km.name, text_ctxt=i18n_contexts.id_windowmanager)
|
||||
|
||||
if km.is_user_modified or km.is_modal:
|
||||
subrow = row.row()
|
||||
subrow.alignment = 'RIGHT'
|
||||
|
||||
if km.is_user_modified:
|
||||
subrow.operator("preferences.keymap_restore", text="Restore")
|
||||
if km.is_modal:
|
||||
subrow.label(text="", icon='LINKED')
|
||||
del subrow
|
||||
|
||||
if km.show_expanded_children:
|
||||
if children:
|
||||
# Put the Parent key map's entries in a 'global' sub-category
|
||||
# equal in hierarchy to the other children categories
|
||||
subcol = _indented_layout(col, level + 1)
|
||||
subrow = subcol.row(align=True)
|
||||
subrow.prop(km, "show_expanded_items", text="", emboss=False)
|
||||
subrow.label(text=iface_("%s (Global)") % iface_(km.name, i18n_contexts.id_windowmanager), translate=False)
|
||||
else:
|
||||
km.show_expanded_items = True
|
||||
|
||||
# Key Map items
|
||||
if km.show_expanded_items:
|
||||
kmi_level = level + 3 if children else level + 1
|
||||
for kmi in km.keymap_items:
|
||||
draw_kmi(display_keymaps, kc, km, kmi, col, kmi_level)
|
||||
|
||||
# "Add New" at end of keymap item list
|
||||
subcol = _indented_layout(col, kmi_level)
|
||||
subcol = subcol.split(factor=0.2).column()
|
||||
subcol.operator("preferences.keyitem_add", text="Add New", text_ctxt=i18n_contexts.id_windowmanager,
|
||||
icon='ADD')
|
||||
|
||||
col.separator()
|
||||
|
||||
# Child key maps
|
||||
if children:
|
||||
for entry in children:
|
||||
draw_entry(display_keymaps, entry, col, level + 1)
|
||||
|
||||
col.separator()
|
||||
|
||||
|
||||
def draw_kmi(display_keymaps, kc, km, kmi, layout, level):
|
||||
map_type = kmi.map_type
|
||||
|
||||
col = _indented_layout(layout, level)
|
||||
|
||||
if kmi.show_expanded:
|
||||
col = col.column(align=True)
|
||||
box = col.box()
|
||||
else:
|
||||
box = col.column()
|
||||
|
||||
split = box.split()
|
||||
|
||||
# header bar
|
||||
row = split.row(align=True)
|
||||
row.prop(kmi, "show_expanded", text="", emboss=False)
|
||||
row.prop(kmi, "active", text="", emboss=False)
|
||||
|
||||
if km.is_modal:
|
||||
row.separator()
|
||||
row.prop(kmi, "propvalue", text="")
|
||||
else:
|
||||
row.label(text=kmi.name)
|
||||
|
||||
row = split.row()
|
||||
row.prop(kmi, "map_type", text="")
|
||||
if map_type == 'KEYBOARD':
|
||||
row.prop(kmi, "type", text="", full_event=True)
|
||||
elif map_type == 'MOUSE':
|
||||
row.prop(kmi, "type", text="", full_event=True)
|
||||
elif map_type == 'NDOF':
|
||||
row.prop(kmi, "type", text="", full_event=True)
|
||||
elif map_type == 'TWEAK':
|
||||
subrow = row.row()
|
||||
subrow.prop(kmi, "type", text="")
|
||||
subrow.prop(kmi, "value", text="")
|
||||
elif map_type == 'TIMER':
|
||||
row.prop(kmi, "type", text="")
|
||||
else:
|
||||
row.label()
|
||||
|
||||
if (not kmi.is_user_defined) and kmi.is_user_modified:
|
||||
row.operator("preferences.keyitem_restore", text="", icon='BACK').item_id = kmi.id
|
||||
else:
|
||||
row.operator(
|
||||
"preferences.keyitem_remove",
|
||||
text="",
|
||||
# Abusing the tracking icon, but it works pretty well here.
|
||||
icon=('TRACKING_CLEAR_BACKWARDS' if kmi.is_user_defined else 'X')
|
||||
).item_id = kmi.id
|
||||
|
||||
# Expanded, additional event settings
|
||||
if kmi.show_expanded:
|
||||
box = col.box()
|
||||
|
||||
split = box.split(factor=0.4)
|
||||
sub = split.row()
|
||||
|
||||
if km.is_modal:
|
||||
sub.prop(kmi, "propvalue", text="")
|
||||
else:
|
||||
# One day...
|
||||
# sub.prop_search(kmi, "idname", bpy.context.window_manager, "operators_all", text="")
|
||||
sub.prop(kmi, "idname", text="")
|
||||
|
||||
if map_type not in {'TEXTINPUT', 'TIMER'}:
|
||||
sub = split.column()
|
||||
subrow = sub.row(align=True)
|
||||
|
||||
if map_type == 'KEYBOARD':
|
||||
subrow.prop(kmi, "type", text="", event=True)
|
||||
subrow.prop(kmi, "value", text="")
|
||||
subrow_repeat = subrow.row(align=True)
|
||||
subrow_repeat.active = kmi.value in {'ANY', 'PRESS'}
|
||||
subrow_repeat.prop(kmi, "repeat", text="Repeat")
|
||||
elif map_type in {'MOUSE', 'NDOF'}:
|
||||
subrow.prop(kmi, "type", text="")
|
||||
subrow.prop(kmi, "value", text="")
|
||||
|
||||
if map_type in {'KEYBOARD', 'MOUSE'} and kmi.value == 'CLICK_DRAG':
|
||||
subrow = sub.row()
|
||||
subrow.prop(kmi, "direction")
|
||||
|
||||
subrow = sub.row()
|
||||
subrow.scale_x = 0.75
|
||||
subrow.prop(kmi, "any", toggle=True)
|
||||
# Use `*_ui` properties as integers aren't practical.
|
||||
subrow.prop(kmi, "shift_ui", toggle=True)
|
||||
subrow.prop(kmi, "ctrl_ui", toggle=True)
|
||||
subrow.prop(kmi, "alt_ui", toggle=True)
|
||||
subrow.prop(kmi, "oskey_ui", text="Cmd", toggle=True)
|
||||
|
||||
subrow.prop(kmi, "key_modifier", text="", event=True)
|
||||
|
||||
# Operator properties
|
||||
box.template_keymap_item_properties(kmi)
|
||||
|
||||
# Modal key maps attached to this operator
|
||||
if not km.is_modal:
|
||||
kmm = kc.keymaps.find_modal(kmi.idname)
|
||||
if kmm:
|
||||
draw_km(display_keymaps, kc, kmm, None, layout, level + 1)
|
||||
layout.context_pointer_set("keymap", km)
|
||||
|
||||
|
||||
_EVENT_TYPES = set()
|
||||
_EVENT_TYPE_MAP = {}
|
||||
_EVENT_TYPE_MAP_EXTRA = {}
|
||||
|
||||
|
||||
def draw_filtered(display_keymaps, filter_type, filter_text, layout):
|
||||
|
||||
if filter_type == 'NAME':
|
||||
def filter_func(kmi):
|
||||
return (filter_text in kmi.idname.lower() or
|
||||
filter_text in kmi.name.lower())
|
||||
else:
|
||||
if not _EVENT_TYPES:
|
||||
enum = bpy.types.Event.bl_rna.properties["type"].enum_items
|
||||
_EVENT_TYPES.update(enum.keys())
|
||||
_EVENT_TYPE_MAP.update({item.name.replace(" ", "_").upper(): key
|
||||
for key, item in enum.items()})
|
||||
|
||||
del enum
|
||||
_EVENT_TYPE_MAP_EXTRA.update({
|
||||
"`": 'ACCENT_GRAVE',
|
||||
"*": 'NUMPAD_ASTERIX',
|
||||
"/": 'NUMPAD_SLASH',
|
||||
'+': 'NUMPAD_PLUS',
|
||||
"-": 'NUMPAD_MINUS',
|
||||
".": 'NUMPAD_PERIOD',
|
||||
"'": 'QUOTE',
|
||||
"RMB": 'RIGHTMOUSE',
|
||||
"LMB": 'LEFTMOUSE',
|
||||
"MMB": 'MIDDLEMOUSE',
|
||||
})
|
||||
_EVENT_TYPE_MAP_EXTRA.update({
|
||||
"%d" % i: "NUMPAD_%d" % i for i in range(10)
|
||||
})
|
||||
# done with once off init
|
||||
|
||||
filter_text_split = filter_text.strip()
|
||||
filter_text_split = filter_text.split()
|
||||
|
||||
# Modifier {kmi.attribute: name} mapping
|
||||
key_mod = {
|
||||
"ctrl": "ctrl",
|
||||
"alt": "alt",
|
||||
"shift": "shift",
|
||||
"cmd": "oskey",
|
||||
"oskey": "oskey",
|
||||
"any": "any",
|
||||
}
|
||||
# KeyMapItem like dict, use for comparing against
|
||||
# attr: {states, ...}
|
||||
kmi_test_dict = {}
|
||||
# Special handling of 'type' using a list if sets,
|
||||
# keymap items must match against all.
|
||||
kmi_test_type = []
|
||||
|
||||
# initialize? - so if a kmi has a MOD assigned it won't show up.
|
||||
# for kv in key_mod.values():
|
||||
# kmi_test_dict[kv] = {False}
|
||||
|
||||
# altname: attr
|
||||
for kk, kv in key_mod.items():
|
||||
if kk in filter_text_split:
|
||||
filter_text_split.remove(kk)
|
||||
kmi_test_dict[kv] = {True}
|
||||
|
||||
# what's left should be the event type
|
||||
def kmi_type_set_from_string(kmi_type):
|
||||
kmi_type = kmi_type.upper()
|
||||
kmi_type_set = set()
|
||||
|
||||
if kmi_type in _EVENT_TYPES:
|
||||
kmi_type_set.add(kmi_type)
|
||||
|
||||
if not kmi_type_set or len(kmi_type) > 1:
|
||||
# replacement table
|
||||
for event_type_map in (_EVENT_TYPE_MAP, _EVENT_TYPE_MAP_EXTRA):
|
||||
kmi_type_test = event_type_map.get(kmi_type)
|
||||
if kmi_type_test is not None:
|
||||
kmi_type_set.add(kmi_type_test)
|
||||
else:
|
||||
# print("Unknown Type:", kmi_type)
|
||||
|
||||
# Partial match
|
||||
for k, v in event_type_map.items():
|
||||
if (kmi_type in k) or (kmi_type in v):
|
||||
kmi_type_set.add(v)
|
||||
return kmi_type_set
|
||||
|
||||
for i, kmi_type in enumerate(filter_text_split):
|
||||
kmi_type_set = kmi_type_set_from_string(kmi_type)
|
||||
|
||||
if not kmi_type_set:
|
||||
return False
|
||||
|
||||
kmi_test_type.append(kmi_type_set)
|
||||
# tiny optimization, sort sets so the smallest is first
|
||||
# improve chances of failing early
|
||||
kmi_test_type.sort(key=lambda kmi_type_set: len(kmi_type_set))
|
||||
|
||||
# main filter func, runs many times
|
||||
def filter_func(kmi):
|
||||
for kk, ki in kmi_test_dict.items():
|
||||
val = getattr(kmi, kk)
|
||||
if val not in ki:
|
||||
return False
|
||||
|
||||
# special handling of 'type'
|
||||
for ki in kmi_test_type:
|
||||
val = kmi.type
|
||||
if val == 'NONE' or val not in ki:
|
||||
# exception for 'type'
|
||||
# also inspect 'key_modifier' as a fallback
|
||||
val = kmi.key_modifier
|
||||
if not (val == 'NONE' or val not in ki):
|
||||
continue
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
for km, kc in display_keymaps:
|
||||
km = km.active()
|
||||
layout.context_pointer_set("keymap", km)
|
||||
|
||||
filtered_items = [kmi for kmi in km.keymap_items if filter_func(kmi)]
|
||||
|
||||
if filtered_items:
|
||||
col = layout.column()
|
||||
|
||||
row = col.row()
|
||||
row.label(text=km.name, icon='DOT',
|
||||
text_ctxt=i18n_contexts.id_windowmanager)
|
||||
|
||||
row.label()
|
||||
row.label()
|
||||
|
||||
if km.is_user_modified:
|
||||
row.operator("preferences.keymap_restore", text="Restore")
|
||||
else:
|
||||
row.label()
|
||||
|
||||
for kmi in filtered_items:
|
||||
draw_kmi(display_keymaps, kc, km, kmi, col, 1)
|
||||
return True
|
||||
|
||||
|
||||
def draw_hierarchy(display_keymaps, layout):
|
||||
from bl_keymap_utils import keymap_hierarchy
|
||||
for entry in keymap_hierarchy.generate():
|
||||
draw_entry(display_keymaps, entry, layout)
|
||||
|
||||
|
||||
def draw_keymaps(context, layout):
|
||||
from bl_keymap_utils.io import keyconfig_merge
|
||||
|
||||
wm = context.window_manager
|
||||
kc_user = wm.keyconfigs.user
|
||||
kc_active = wm.keyconfigs.active
|
||||
spref = context.space_data
|
||||
|
||||
# row.prop_search(wm.keyconfigs, "active", wm, "keyconfigs", text="Key Config")
|
||||
text = bpy.path.display_name(kc_active.name, has_ext=False)
|
||||
if not text:
|
||||
text = "Blender (default)"
|
||||
|
||||
split = layout.split(factor=0.6)
|
||||
|
||||
row = split.row()
|
||||
|
||||
rowsub = row.row(align=True)
|
||||
|
||||
rowsub.menu("USERPREF_MT_keyconfigs", text=text)
|
||||
rowsub.operator("wm.keyconfig_preset_add", text="", icon='ADD')
|
||||
rowsub.operator("wm.keyconfig_preset_add", text="", icon='REMOVE').remove_active = True
|
||||
|
||||
rowsub = split.row(align=True)
|
||||
rowsub.operator("preferences.keyconfig_import", text="Import...", icon='IMPORT')
|
||||
rowsub.operator("preferences.keyconfig_export", text="Export...", icon='EXPORT')
|
||||
|
||||
row = layout.row()
|
||||
col = layout.column()
|
||||
|
||||
# layout.context_pointer_set("keyconfig", wm.keyconfigs.active)
|
||||
# row.operator("preferences.keyconfig_remove", text="", icon='X')
|
||||
rowsub = row.split(factor=0.4, align=True)
|
||||
# postpone drawing into rowsub, so we can set alert!
|
||||
|
||||
layout.separator()
|
||||
display_keymaps = keyconfig_merge(kc_user, kc_user)
|
||||
filter_type = spref.filter_type
|
||||
filter_text = spref.filter_text.strip()
|
||||
if filter_text:
|
||||
filter_text = filter_text.lower()
|
||||
ok = draw_filtered(display_keymaps, filter_type, filter_text, layout)
|
||||
else:
|
||||
draw_hierarchy(display_keymaps, layout)
|
||||
ok = True
|
||||
|
||||
# go back and fill in rowsub
|
||||
rowsubsub = rowsub.row(align=True)
|
||||
rowsubsub.prop(spref, "filter_type", expand=True)
|
||||
rowsubsub = rowsub.row(align=True)
|
||||
if not ok:
|
||||
rowsubsub.alert = True
|
||||
rowsubsub.prop(spref, "filter_text", text="", icon='VIEWZOOM')
|
||||
|
||||
if not filter_text:
|
||||
# When the keyconfig defines its own preferences.
|
||||
kc_prefs = kc_active.preferences
|
||||
if kc_prefs is not None:
|
||||
box = col.box()
|
||||
row = box.row(align=True)
|
||||
|
||||
pref = context.preferences
|
||||
keymappref = pref.keymap
|
||||
show_ui_keyconfig = keymappref.show_ui_keyconfig
|
||||
row.prop(
|
||||
keymappref,
|
||||
"show_ui_keyconfig",
|
||||
text="",
|
||||
icon='DISCLOSURE_TRI_DOWN' if show_ui_keyconfig else 'DISCLOSURE_TRI_RIGHT',
|
||||
emboss=False,
|
||||
)
|
||||
row.label(text="Preferences")
|
||||
|
||||
if show_ui_keyconfig:
|
||||
# Defined by user preset, may contain mistakes out of our control.
|
||||
try:
|
||||
kc_prefs.draw(box)
|
||||
except Exception:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
del box
|
||||
del kc_prefs
|
||||
3397
scripts/modules/rna_manual_reference.py
Normal file
3397
scripts/modules/rna_manual_reference.py
Normal file
@@ -0,0 +1,3397 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
# Do not edit this file. This file is auto generated from rna_manual_reference_updater.py
|
||||
|
||||
# autopep8: off
|
||||
import bpy
|
||||
|
||||
url_manual_prefix = "https://docs.blender.org/manual/%s/%d.%d/" % (
|
||||
bpy.utils.manual_language_code(),
|
||||
*bpy.app.version[:2],
|
||||
)
|
||||
|
||||
url_manual_mapping = (
|
||||
("bpy.types.movietrackingsettings.refine_intrinsics_tangential_distortion*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-tangential-distortion"),
|
||||
("bpy.types.spacesequesequencertimelineoverlaynceeditor.show_strip_offset*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequesequencertimelineoverlaynceeditor-show-strip-offset"),
|
||||
("bpy.types.movietrackingsettings.refine_intrinsics_radial_distortion*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-radial-distortion"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_potential_max_trappedair*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-trappedair"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_potential_min_trappedair*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-trappedair"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_potential_max_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-wavecrest"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_potential_min_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-wavecrest"),
|
||||
("bpy.types.lineartgpencilmodifier.use_offset_towards_custom_camera*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-offset-towards-custom-camera"),
|
||||
("bpy.types.movietrackingsettings.refine_intrinsics_principal_point*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-principal-point"),
|
||||
("bpy.types.cyclesobjectsettings.shadow_terminator_geometry_offset*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-shadow-terminator-geometry-offset"),
|
||||
("bpy.types.sequencertoolsettings.use_snap_current_frame_to_strips*", "video_editing/edit/montage/editing.html#bpy-types-sequencertoolsettings-use-snap-current-frame-to-strips"),
|
||||
("bpy.types.clothcollisionsettings.vertex_group_object_collisions*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-vertex-group-object-collisions"),
|
||||
("bpy.types.gpencilsculptsettings.use_automasking_material_active*", "grease_pencil/modes/sculpting/introduction.html#bpy-types-gpencilsculptsettings-use-automasking-material-active"),
|
||||
("bpy.types.gpencilsculptsettings.use_automasking_material_stroke*", "grease_pencil/modes/sculpting/introduction.html#bpy-types-gpencilsculptsettings-use-automasking-material-stroke"),
|
||||
("bpy.types.cycleslightsettings.use_multiple_importance_sampling*", "render/cycles/light_settings.html#bpy-types-cycleslightsettings-use-multiple-importance-sampling"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_potential_max_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-max-energy"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_potential_min_energy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-min-energy"),
|
||||
("bpy.types.lineartgpencilmodifier.use_overlap_edge_type_support*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-overlap-edge-type-support"),
|
||||
("bpy.types.movietrackingsettings.refine_intrinsics_focal_length*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-refine-intrinsics-focal-length"),
|
||||
("bpy.types.rigidbodyconstraint.rigidbodyconstraint.use_breaking*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-rigidbodyconstraint-use-breaking"),
|
||||
("bpy.types.clothcollisionsettings.vertex_group_self_collisions*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-vertex-group-self-collisions"),
|
||||
("bpy.types.cyclesrendersettings.preview_denoising_input_passes*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-denoising-input-passes"),
|
||||
("bpy.types.cyclesrendersettings.preview_denoising_start_sample*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-denoising-start-sample"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_sampling_trappedair*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-sampling-trappedair"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_sampling_wavecrest*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-sampling-wavecrest"),
|
||||
("bpy.types.gpencilsculptsettings.use_automasking_layer_active*", "grease_pencil/modes/sculpting/introduction.html#bpy-types-gpencilsculptsettings-use-automasking-layer-active"),
|
||||
("bpy.types.gpencilsculptsettings.use_automasking_layer_stroke*", "grease_pencil/modes/sculpting/introduction.html#bpy-types-gpencilsculptsettings-use-automasking-layer-stroke"),
|
||||
("bpy.types.lineartgpencilmodifier.use_image_boundary_trimming*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-image-boundary-trimming"),
|
||||
("bpy.types.materiallineart.use_intersection_priority_override*", "render/materials/line_art.html#bpy-types-materiallineart-use-intersection-priority-override"),
|
||||
("bpy.types.rigidbodyconstraint.use_override_solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-override-solver-iterations"),
|
||||
("bpy.types.toolsettings.use_transform_correct_face_attributes*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-face-attributes"),
|
||||
("bpy.types.brushcurvessculptsettings.interpolate_point_count*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-interpolate-point-count"),
|
||||
("bpy.types.cyclesrendersettings.adaptive_scrambling_distance*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-adaptive-scrambling-distance"),
|
||||
("bpy.types.cyclesrendersettings.preview_adaptive_min_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-adaptive-min-samples"),
|
||||
("bpy.types.lineartgpencilmodifier.use_face_mark_keep_contour*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-face-mark-keep-contour"),
|
||||
("bpy.types.rendersettings.use_sequencer_override_scene_strip*", "editors/video_sequencer/preview/sidebar.html#bpy-types-rendersettings-use-sequencer-override-scene-strip"),
|
||||
("bpy.types.toolsettings.use_transform_correct_keep_connected*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-transform-correct-keep-connected"),
|
||||
("bpy.types.clothsettings.internal_compression_stiffness_max*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-compression-stiffness-max"),
|
||||
("bpy.types.cyclesrendersettings.preview_denoising_prefilter*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-denoising-prefilter"),
|
||||
("bpy.types.cyclesrendersettings.preview_scrambling_distance*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-scrambling-distance"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_potential_radius*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-potential-radius"),
|
||||
("bpy.types.objectlineart.use_intersection_priority_override*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-use-intersection-priority-override"),
|
||||
("bpy.types.brushgpencilsettings.use_stroke_random_strength*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-stroke-random-strength"),
|
||||
("bpy.types.clothsettings.vertex_group_structural_stiffness*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-vertex-group-structural-stiffness"),
|
||||
("bpy.types.cyclesrendersettings.film_transparent_roughness*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-transparent-roughness"),
|
||||
("bpy.types.cyclesrendersettings.preview_adaptive_threshold*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-adaptive-threshold"),
|
||||
("bpy.types.fluiddomainsettings.openvdb_cache_compress_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-openvdb-cache-compress-type"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_bubble_buoyancy*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-bubble-buoyancy"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_combined_export*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-combined-export"),
|
||||
("bpy.types.fluiddomainsettings.use_collision_border_bottom*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-bottom"),
|
||||
("bpy.types.fluiddomainsettings.vector_scale_with_magnitude*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-scale-with-magnitude"),
|
||||
("bpy.types.lineartgpencilmodifier.use_face_mark_boundaries*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-face-mark-boundaries"),
|
||||
("bpy.types.movietrackingstabilization.use_2d_stabilization*", "movie_clip/tracking/clip/sidebar/stabilization/panel.html#bpy-types-movietrackingstabilization-use-2d-stabilization"),
|
||||
("bpy.types.spacespreadsheet.display_context_path_collapsed*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-display-context-path-collapsed"),
|
||||
("bpy.types.toolsettings.annotation_stroke_placement_view2d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view2d"),
|
||||
("bpy.types.toolsettings.annotation_stroke_placement_view3d*", "interface/annotate_tool.html#bpy-types-toolsettings-annotation-stroke-placement-view3d"),
|
||||
("bpy.types.brushcurvessculptsettings.density_add_attempts*", "sculpt_paint/curves_sculpting/tools/density_curves.html#bpy-types-brushcurvessculptsettings-density-add-attempts"),
|
||||
("bpy.types.brushgpencilsettings.use_random_press_strength*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-random-press-strength"),
|
||||
("bpy.types.fluiddomainsettings.use_collision_border_front*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-front"),
|
||||
("bpy.types.fluiddomainsettings.use_collision_border_right*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-right"),
|
||||
("bpy.types.lineartgpencilmodifier.shadow_region_filtering*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-shadow-region-filtering"),
|
||||
("bpy.types.sequencertimelineoverlay.waveform_display_type*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-waveform-display-type"),
|
||||
("bpy.types.view3doverlay.use_normals_constant_screen_size*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-use-normals-constant-screen-size"),
|
||||
("bpy.types.brushgpencilsettings.random_saturation_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random-saturation-factor"),
|
||||
("bpy.types.brushgpencilsettings.use_settings_postprocess*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-settings-postprocess"),
|
||||
("bpy.types.brushgpencilsettings.use_stroke_random_radius*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-stroke-random-radius"),
|
||||
("bpy.types.cyclesmaterialsettings.use_transparent_shadow*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-use-transparent-shadow"),
|
||||
("bpy.types.cyclesobjectsettings.shadow_terminator_offset*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-shadow-terminator-offset"),
|
||||
("bpy.types.cyclesobjectsettings.use_adaptive_subdivision*", "render/cycles/object_settings/adaptive_subdiv.html#bpy-types-cyclesobjectsettings-use-adaptive-subdivision"),
|
||||
("bpy.types.cyclesrendersettings.debug_use_spatial_splits*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-debug-use-spatial-splits"),
|
||||
("bpy.types.cyclesrendersettings.guiding_training_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-guiding-training-samples"),
|
||||
("bpy.types.cyclesrendersettings.light_sampling_threshold*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-light-sampling-threshold"),
|
||||
("bpy.types.cyclesrendersettings.rolling_shutter_duration*", "render/cycles/render_settings/motion_blur.html#bpy-types-cyclesrendersettings-rolling-shutter-duration"),
|
||||
("bpy.types.cyclesrendersettings.volume_preview_step_rate*", "render/cycles/render_settings/volumes.html#bpy-types-cyclesrendersettings-volume-preview-step-rate"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_update_radius*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-update-radius"),
|
||||
("bpy.types.fluiddomainsettings.use_collision_border_back*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-back"),
|
||||
("bpy.types.fluiddomainsettings.use_collision_border_left*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-left"),
|
||||
("bpy.types.lineartgpencilmodifier.use_intersection_match*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-intersection-match"),
|
||||
("bpy.types.rendersettings_simplify_gpencil_view_modifier*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-view-modifier"),
|
||||
("bpy.types.brushcurvessculptsettings.interpolate_length*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-interpolate-length"),
|
||||
("bpy.types.brushgpencilsettings.eraser_thickness_factor*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-eraser-thickness-factor"),
|
||||
("bpy.types.brushgpencilsettings.use_random_press_radius*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-random-press-radius"),
|
||||
("bpy.types.brushgpencilsettings.use_settings_stabilizer*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-settings-stabilizer"),
|
||||
("bpy.types.clothsettings.internal_compression_stiffness*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-compression-stiffness"),
|
||||
("bpy.types.clothsettings.internal_tension_stiffness_max*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-tension-stiffness-max"),
|
||||
("bpy.types.collection.use_lineart_intersection_priority*", "scene_layout/collections/collections.html#bpy-types-collection-use-lineart-intersection-priority"),
|
||||
("bpy.types.colormanagedsequencercolorspacesettings.name*", "render/color_management.html#bpy-types-colormanagedsequencercolorspacesettings-name"),
|
||||
("bpy.types.cyclesrendersettings.max_transparent_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-max-transparent-bounces"),
|
||||
("bpy.types.cyclesrendersettings.min_transparent_bounces*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-min-transparent-bounces"),
|
||||
("bpy.types.fluiddomainsettings.use_collision_border_top*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-collision-border-top"),
|
||||
("bpy.types.gpencilsculptsettings.intersection_threshold*", "grease_pencil/modes/draw/tools/cutter.html#bpy-types-gpencilsculptsettings-intersection-threshold"),
|
||||
("bpy.types.gpencilsculptsettings.use_automasking_stroke*", "grease_pencil/modes/sculpting/introduction.html#bpy-types-gpencilsculptsettings-use-automasking-stroke"),
|
||||
("bpy.types.gpencilsculptsettings.use_multiframe_falloff*", "grease_pencil/multiframe.html#bpy-types-gpencilsculptsettings-use-multiframe-falloff"),
|
||||
("bpy.types.lineartgpencilmodifier.use_back_face_culling*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-back-face-culling"),
|
||||
("bpy.types.lineartgpencilmodifier.use_intersection_mask*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-intersection-mask"),
|
||||
("bpy.types.lineartgpencilmodifier.use_invert_collection*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-invert-collection"),
|
||||
("bpy.types.lineartgpencilmodifier.use_invert_silhouette*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-invert-silhouette"),
|
||||
("bpy.types.movietrackingsettings.use_keyframe_selection*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-keyframe-selection"),
|
||||
("bpy.types.rendersettings.simplify_gpencil_antialiasing*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-antialiasing"),
|
||||
("bpy.types.sequencertimelineoverlay.show_strip_duration*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-strip-duration"),
|
||||
("bpy.types.spaceoutliner.use_filter_lib_override_system*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-lib-override-system"),
|
||||
("bpy.types.toolsettings.use_transform_pivot_point_align*", "scene_layout/object/tools/tool_settings.html#bpy-types-toolsettings-use-transform-pivot-point-align"),
|
||||
("bpy.types.animvizmotionpaths.show_keyframe_action_all*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-show-keyframe-action-all"),
|
||||
("bpy.types.brush.show_multiplane_scrape_planes_preview*", "sculpt_paint/sculpting/tools/multiplane_scrape.html#bpy-types-brush-show-multiplane-scrape-planes-preview"),
|
||||
("bpy.types.brushcurvessculptsettings.interpolate_shape*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-interpolate-shape"),
|
||||
("bpy.types.brushgpencilsettings.eraser_strength_factor*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-eraser-strength-factor"),
|
||||
("bpy.types.clothsettings.internal_spring_max_diversion*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-spring-max-diversion"),
|
||||
("bpy.types.cyclesmaterialsettings.volume_interpolation*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-volume-interpolation"),
|
||||
("bpy.types.cyclesrendersettings.denoising_input_passes*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoising-input-passes"),
|
||||
("bpy.types.cyclesrendersettings.film_transparent_glass*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-transparent-glass"),
|
||||
("bpy.types.cyclesrendersettings.offscreen_dicing_scale*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-offscreen-dicing-scale"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_bubble_drag*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-bubble-drag"),
|
||||
("bpy.types.lineartgpencilmodifier.light_contour_object*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-light-contour-object"),
|
||||
("bpy.types.lineartgpencilmodifier.silhouette_filtering*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-silhouette-filtering"),
|
||||
("bpy.types.lineartgpencilmodifier.use_crease_on_smooth*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-crease-on-smooth"),
|
||||
("bpy.types.lineartgpencilmodifier.use_face_mark_invert*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-face-mark-invert"),
|
||||
("bpy.types.linestylegeometrymodifier_backbonestretcher*", "render/freestyle/view_layer/line_style/modifiers/geometry/backbone_stretcher.html#bpy-types-linestylegeometrymodifier-backbonestretcher"),
|
||||
("bpy.types.linestylegeometrymodifier_sinusdisplacement*", "render/freestyle/view_layer/line_style/modifiers/geometry/sinus_displacement.html#bpy-types-linestylegeometrymodifier-sinusdisplacement"),
|
||||
("bpy.types.sequencertoolsettings.snap_to_current_frame*", "video_editing/edit/montage/editing.html#bpy-types-sequencertoolsettings-snap-to-current-frame"),
|
||||
("bpy.types.view3doverlay.sculpt_mode_face_sets_opacity*", "sculpt_paint/sculpting/editing/face_sets.html#bpy-types-view3doverlay-sculpt-mode-face-sets-opacity"),
|
||||
("bpy.ops.object.geometry_nodes_input_attribute_toggle*", "modeling/modifiers/generate/geometry_nodes.html#bpy-ops-object-geometry-nodes-input-attribute-toggle"),
|
||||
("bpy.types.animvizmotionpaths.show_keyframe_highlight*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-show-keyframe-highlight"),
|
||||
("bpy.types.brushcurvessculptsettings.minimum_distance*", "sculpt_paint/curves_sculpting/tools/density_curves.html#bpy-types-brushcurvessculptsettings-minimum-distance"),
|
||||
("bpy.types.brushcurvessculptsettings.points_per_curve*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-points-per-curve"),
|
||||
("bpy.types.brushgpencilsettings.pen_subdivision_steps*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-pen-subdivision-steps"),
|
||||
("bpy.types.brushgpencilsettings.use_strength_pressure*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-use-strength-pressure"),
|
||||
("bpy.types.brushgpencilsettings.use_stroke_random_hue*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-stroke-random-hue"),
|
||||
("bpy.types.brushgpencilsettings.use_stroke_random_sat*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-stroke-random-sat"),
|
||||
("bpy.types.brushgpencilsettings.use_stroke_random_val*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-stroke-random-val"),
|
||||
("bpy.types.clothsettings.internal_spring_normal_check*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-spring-normal-check"),
|
||||
("bpy.types.clothsettings.vertex_group_shear_stiffness*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-vertex-group-shear-stiffness"),
|
||||
("bpy.types.colormanageddisplaysettings.display_device*", "render/color_management.html#bpy-types-colormanageddisplaysettings-display-device"),
|
||||
("bpy.types.colormanagedviewsettings.use_curve_mapping*", "render/color_management.html#bpy-types-colormanagedviewsettings-use-curve-mapping"),
|
||||
("bpy.types.cyclesrendersettings.sample_clamp_indirect*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-sample-clamp-indirect"),
|
||||
("bpy.types.cyclesrendersettings.use_adaptive_sampling*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-adaptive-sampling"),
|
||||
("bpy.types.cyclesrendersettings.use_preview_denoising*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-preview-denoising"),
|
||||
("bpy.types.fluiddomainsettings.color_ramp_field_scale*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-color-ramp-field-scale"),
|
||||
("bpy.types.fluiddomainsettings.use_adaptive_timesteps*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-adaptive-timesteps"),
|
||||
("bpy.types.fluiddomainsettings.use_dissolve_smoke_log*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-dissolve-smoke-log"),
|
||||
("bpy.types.freestylelineset.select_suggestive_contour*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-suggestive-contour"),
|
||||
("bpy.types.gpencillayer.annotation_onion_before_color*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-before-color"),
|
||||
("bpy.types.gpencillayer.annotation_onion_before_range*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-before-range"),
|
||||
("bpy.types.gpencillayer.use_annotation_onion_skinning*", "interface/annotate_tool.html#bpy-types-gpencillayer-use-annotation-onion-skinning"),
|
||||
("bpy.types.greasepencil.use_adaptive_curve_resolution*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-use-adaptive-curve-resolution"),
|
||||
("bpy.types.lineartgpencilmodifier.stroke_depth_offset*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-stroke-depth-offset"),
|
||||
("bpy.types.lineartgpencilmodifier.use_crease_on_sharp*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-crease-on-sharp"),
|
||||
("bpy.types.linestylegeometrymodifier_polygonalization*", "render/freestyle/view_layer/line_style/modifiers/geometry/polygonization.html#bpy-types-linestylegeometrymodifier-polygonalization"),
|
||||
("bpy.types.sequencertimelineoverlay.show_strip_source*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-strip-source"),
|
||||
("bpy.types.toolsettings.use_gpencil_automerge_strokes*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-automerge-strokes"),
|
||||
("bpy.types.toolsettings.use_proportional_edit_objects*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-edit-objects"),
|
||||
("bpy.ops.outliner.liboverride_troubleshoot_operation*", "files/linked_libraries/library_overrides.html#bpy-ops-outliner-liboverride-troubleshoot-operation"),
|
||||
("bpy.ops.view3d.edit_mesh_extrude_move_shrink_fatten*", "modeling/meshes/editing/face/extrude_faces_normal.html#bpy-ops-view3d-edit-mesh-extrude-move-shrink-fatten"),
|
||||
("bpy.types.brushgpencilsettings.active_smooth_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-active-smooth-factor"),
|
||||
("bpy.types.brushgpencilsettings.extend_stroke_factor*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-extend-stroke-factor"),
|
||||
("bpy.types.brushgpencilsettings.use_random_press_hue*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-random-press-hue"),
|
||||
("bpy.types.brushgpencilsettings.use_random_press_sat*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-random-press-sat"),
|
||||
("bpy.types.brushgpencilsettings.use_random_press_val*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-random-press-val"),
|
||||
("bpy.types.brushgpencilsettings.use_settings_outline*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-settings-outline"),
|
||||
("bpy.types.brushgpencilsettings.use_stroke_random_uv*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-stroke-random-uv"),
|
||||
("bpy.types.clothcollisionsettings.self_impulse_clamp*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-self-impulse-clamp"),
|
||||
("bpy.types.clothcollisionsettings.use_self_collision*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-use-self-collision"),
|
||||
("bpy.types.cyclesmaterialsettings.homogeneous_volume*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-homogeneous-volume"),
|
||||
("bpy.types.cyclesobjectsettings.is_caustics_receiver*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-is-caustics-receiver"),
|
||||
("bpy.types.cyclesrendersettings.adaptive_min_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-adaptive-min-samples"),
|
||||
("bpy.types.cyclesrendersettings.debug_bvh_time_steps*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-debug-bvh-time-steps"),
|
||||
("bpy.types.cyclesrendersettings.distance_cull_margin*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-distance-cull-margin"),
|
||||
("bpy.types.cyclesrendersettings.motion_blur_position*", "render/cycles/render_settings/motion_blur.html#bpy-types-cyclesrendersettings-motion-blur-position"),
|
||||
("bpy.types.cyclesrendersettings.rolling_shutter_type*", "render/cycles/render_settings/motion_blur.html#bpy-types-cyclesrendersettings-rolling-shutter-type"),
|
||||
("bpy.types.cyclesrendersettings.transmission_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-transmission-bounces"),
|
||||
("bpy.types.cyclesworldsettings.sample_map_resolution*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-sample-map-resolution"),
|
||||
("bpy.types.fluiddomainsettings.display_interpolation*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-display-interpolation"),
|
||||
("bpy.types.fluiddomainsettings.gridlines_cell_filter*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-cell-filter"),
|
||||
("bpy.types.fluiddomainsettings.gridlines_color_field*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-color-field"),
|
||||
("bpy.types.fluiddomainsettings.gridlines_lower_bound*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-lower-bound"),
|
||||
("bpy.types.fluiddomainsettings.gridlines_range_color*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-range-color"),
|
||||
("bpy.types.fluiddomainsettings.gridlines_upper_bound*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-gridlines-upper-bound"),
|
||||
("bpy.types.freestylelineset.select_material_boundary*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-material-boundary"),
|
||||
("bpy.types.gpencillayer.annotation_onion_after_color*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-after-color"),
|
||||
("bpy.types.gpencillayer.annotation_onion_after_range*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-onion-after-range"),
|
||||
("bpy.types.lineartgpencilmodifier.shadow_camera_near*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-shadow-camera-near"),
|
||||
("bpy.types.lineartgpencilmodifier.shadow_camera_size*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-shadow-camera-size"),
|
||||
("bpy.types.materialgpencilstyle.use_fill_texture_mix*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-fill-texture-mix"),
|
||||
("bpy.types.rendersettings_simplify_gpencil_shader_fx*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-shader-fx"),
|
||||
("bpy.types.rendersettings_simplify_gpencil_view_fill*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-view-fill"),
|
||||
("bpy.types.sculpt.use_automasking_boundary_face_sets*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-use-automasking-boundary-face-sets"),
|
||||
("bpy.types.sequencertoolsettings.snap_to_hold_offset*", "video_editing/edit/montage/editing.html#bpy-types-sequencertoolsettings-snap-to-hold-offset"),
|
||||
("bpy.types.toolsettings.use_mesh_automerge_and_split*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-mesh-automerge-and-split"),
|
||||
("bpy.ops.scene.view_layer_remove_unused_lightgroups*", "render/layers/passes.html#bpy-ops-scene-view-layer-remove-unused-lightgroups"),
|
||||
("bpy.types.animvizmotionpaths.show_keyframe_numbers*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-show-keyframe-numbers"),
|
||||
("bpy.types.brush.cloth_constraint_softbody_strength*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-constraint-softbody-strength"),
|
||||
("bpy.types.brush.elastic_deform_volume_preservation*", "sculpt_paint/sculpting/tools/elastic_deform.html#bpy-types-brush-elastic-deform-volume-preservation"),
|
||||
("bpy.types.brushcurvessculptsettings.minimum_length*", "sculpt_paint/curves_sculpting/tools/grow_shrink_curves.html#bpy-types-brushcurvessculptsettings-minimum-length"),
|
||||
("bpy.types.brushgpencilsettings.fill_simplify_level*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-simplify-level"),
|
||||
("bpy.types.brushgpencilsettings.random_value_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random-value-factor"),
|
||||
("bpy.types.brushgpencilsettings.use_collide_strokes*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-use-collide-strokes"),
|
||||
("bpy.types.brushgpencilsettings.use_jitter_pressure*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-jitter-pressure"),
|
||||
("bpy.types.brushgpencilsettings.use_random_press_uv*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-random-press-uv"),
|
||||
("bpy.types.brushgpencilsettings.use_settings_random*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-settings-random"),
|
||||
("bpy.types.clothcollisionsettings.collision_quality*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-collision-quality"),
|
||||
("bpy.types.clothcollisionsettings.self_distance_min*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-self-distance-min"),
|
||||
("bpy.types.clothsettings.internal_spring_max_length*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-spring-max-length"),
|
||||
("bpy.types.clothsettings.internal_tension_stiffness*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-internal-tension-stiffness"),
|
||||
("bpy.types.collection.lineart_intersection_priority*", "scene_layout/collections/collections.html#bpy-types-collection-lineart-intersection-priority"),
|
||||
("bpy.types.collection.lineart_use_intersection_mask*", "scene_layout/collections/collections.html#bpy-types-collection-lineart-use-intersection-mask"),
|
||||
("bpy.types.colormanagedinputcolorspacesettings.name*", "editors/image/image_settings.html#bpy-types-colormanagedinputcolorspacesettings-name"),
|
||||
("bpy.types.cyclesmaterialsettings.emission_sampling*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-emission-sampling"),
|
||||
("bpy.types.cyclesrendersettings.denoising_prefilter*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoising-prefilter"),
|
||||
("bpy.types.cyclesrendersettings.preview_dicing_rate*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-preview-dicing-rate"),
|
||||
("bpy.types.cyclesrendersettings.sample_clamp_direct*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-sample-clamp-direct"),
|
||||
("bpy.types.cyclesrendersettings.scrambling_distance*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-scrambling-distance"),
|
||||
("bpy.types.cyclesrendersettings.use_surface_guiding*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-surface-guiding"),
|
||||
("bpy.types.cyclesworldsettings.volume_interpolation*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-interpolation"),
|
||||
("bpy.types.fluiddomainsettings.mesh_particle_radius*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-particle-radius"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_boundary*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-boundary"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_life_max*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-life-max"),
|
||||
("bpy.types.fluiddomainsettings.sndparticle_life_min*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-sndparticle-life-min"),
|
||||
("bpy.types.fluiddomainsettings.sys_particle_maximum*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-sys-particle-maximum"),
|
||||
("bpy.types.fluiddomainsettings.use_bubble_particles*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-use-bubble-particles"),
|
||||
("bpy.types.freestylelineset.select_external_contour*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-external-contour"),
|
||||
("bpy.types.lineartgpencilmodifier.shadow_camera_far*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-shadow-camera-far"),
|
||||
("bpy.types.lineartgpencilmodifier.use_custom_camera*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-custom-camera"),
|
||||
("bpy.types.lineartgpencilmodifier.use_light_contour*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-light-contour"),
|
||||
("bpy.types.linestylegeometrymodifier_simplification*", "render/freestyle/view_layer/line_style/modifiers/geometry/simplification.html#bpy-types-linestylegeometrymodifier-simplification"),
|
||||
("bpy.types.materialgpencilstyle.use_overlap_strokes*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-overlap-strokes"),
|
||||
("bpy.types.movietrackingtrack.use_grayscale_preview*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-use-grayscale-preview"),
|
||||
("bpy.types.sequencertimelineoverlay.show_strip_name*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-strip-name"),
|
||||
("bpy.types.sequencertimelineoverlay.show_thumbnails*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-thumbnails"),
|
||||
("bpy.types.spacespreadsheet.geometry_component_type*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-geometry-component-type"),
|
||||
("bpy.types.toolsettings.use_gpencil_weight_data_add*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-weight-data-add"),
|
||||
("bpy.types.view3doverlay.texture_paint_mode_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-texture-paint-mode-opacity"),
|
||||
("bpy.ops.mesh.customdata_bevel_weight_vertex_clear*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-bevel-weight-vertex-clear"),
|
||||
("bpy.ops.mesh.customdata_custom_splitnormals_clear*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-custom-splitnormals-clear"),
|
||||
("bpy.types.bakesettings.use_pass_ambient_occlusion*", "render/cycles/baking.html#bpy-types-bakesettings-use-pass-ambient-occlusion"),
|
||||
("bpy.types.brush.surface_smooth_shape_preservation*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-surface-smooth-shape-preservation"),
|
||||
("bpy.types.brush.use_cloth_pin_simulation_boundary*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-use-cloth-pin-simulation-boundary"),
|
||||
("bpy.types.brushcurvessculptsettings.scale_uniform*", "sculpt_paint/curves_sculpting/tools/grow_shrink_curves.html#bpy-types-brushcurvessculptsettings-scale-uniform"),
|
||||
("bpy.types.brushgpencilsettings.show_fill_boundary*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-show-fill-boundary"),
|
||||
("bpy.types.brushgpencilsettings.use_default_eraser*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-use-default-eraser"),
|
||||
("bpy.types.clothsettings.compression_stiffness_max*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-compression-stiffness-max"),
|
||||
("bpy.types.colormanagedsequencercolorspacesettings*", "render/color_management.html#bpy-types-colormanagedsequencercolorspacesettings"),
|
||||
("bpy.types.colormanagedviewsettings.view_transform*", "render/color_management.html#bpy-types-colormanagedviewsettings-view-transform"),
|
||||
("bpy.types.cyclesmaterialsettings.volume_step_rate*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-volume-step-rate"),
|
||||
("bpy.types.cyclesobjectsettings.is_caustics_caster*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-is-caustics-caster"),
|
||||
("bpy.types.cyclesrendersettings.adaptive_threshold*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-adaptive-threshold"),
|
||||
("bpy.types.cyclesrendersettings.camera_cull_margin*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-camera-cull-margin"),
|
||||
("bpy.types.cyclesrendersettings.debug_use_hair_bvh*", "render/cycles/render_settings/performance.html#bpy-types-cyclesrendersettings-debug-use-hair-bvh"),
|
||||
("bpy.types.cyclesrendersettings.use_volume_guiding*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-volume-guiding"),
|
||||
("bpy.types.fileassetselectparams.asset_library_ref*", "editors/asset_browser.html#bpy-types-fileassetselectparams-asset-library-ref"),
|
||||
("bpy.types.fluiddomainsettings.export_manta_script*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-export-manta-script"),
|
||||
("bpy.types.fluiddomainsettings.fractions_threshold*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-fractions-threshold"),
|
||||
("bpy.types.fluiddomainsettings.particle_band_width*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-particle-band-width"),
|
||||
("bpy.types.fluiddomainsettings.particle_randomness*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-particle-randomness"),
|
||||
("bpy.types.fluiddomainsettings.use_adaptive_domain*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-use-adaptive-domain"),
|
||||
("bpy.types.fluiddomainsettings.use_resumable_cache*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-use-resumable-cache"),
|
||||
("bpy.types.fluiddomainsettings.use_spray_particles*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-use-spray-particles"),
|
||||
("bpy.types.fluiddomainsettings.vector_display_type*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-display-type"),
|
||||
("bpy.types.freestylelineset.select_by_image_border*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-by-image-border"),
|
||||
("bpy.types.freestylesettings.kr_derivative_epsilon*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-kr-derivative-epsilon"),
|
||||
("bpy.types.geometrynodecurveprimitivebeziersegment*", "modeling/geometry_nodes/curve/primitives/bezier_segment.html#bpy-types-geometrynodecurveprimitivebeziersegment"),
|
||||
("bpy.types.geometrynodecurveprimitivequadrilateral*", "modeling/geometry_nodes/curve/primitives/quadrilateral.html#bpy-types-geometrynodecurveprimitivequadrilateral"),
|
||||
("bpy.types.lineartgpencilmodifier.crease_threshold*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-crease-threshold"),
|
||||
("bpy.types.lineartgpencilmodifier.smooth_tolerance*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-smooth-tolerance"),
|
||||
("bpy.types.lineartgpencilmodifier.use_intersection*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-intersection"),
|
||||
("bpy.types.linestylegeometrymodifier_perlinnoise1d*", "render/freestyle/view_layer/line_style/modifiers/geometry/perlin_noise_1d.html#bpy-types-linestylegeometrymodifier-perlinnoise1d"),
|
||||
("bpy.types.linestylegeometrymodifier_perlinnoise2d*", "render/freestyle/view_layer/line_style/modifiers/geometry/perlin_noise_2d.html#bpy-types-linestylegeometrymodifier-perlinnoise2d"),
|
||||
("bpy.types.materialgpencilstyle.use_stroke_holdout*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-stroke-holdout"),
|
||||
("bpy.types.movietrackingplanetrack.use_auto_keying*", "movie_clip/tracking/clip/sidebar/track/plane_track.html#bpy-types-movietrackingplanetrack-use-auto-keying"),
|
||||
("bpy.types.movietrackingsettings.use_tripod_solver*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingsettings-use-tripod-solver"),
|
||||
("bpy.types.rendersettings.simplify_child_particles*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-child-particles"),
|
||||
("bpy.types.rendersettings.use_high_quality_normals*", "render/eevee/render_settings/performance.html#bpy-types-rendersettings-use-high-quality-normals"),
|
||||
("bpy.types.sculpt.automasking_start_normal_falloff*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-automasking-start-normal-falloff"),
|
||||
("bpy.types.sequencerpreviewoverlay.show_annotation*", "editors/video_sequencer/preview/display/overlays.html#bpy-types-sequencerpreviewoverlay-show-annotation"),
|
||||
("bpy.types.sequencerpreviewoverlay.show_safe_areas*", "editors/video_sequencer/preview/display/overlays.html#bpy-types-sequencerpreviewoverlay-show-safe-areas"),
|
||||
("bpy.types.sequencertoolsettings.snap_ignore_muted*", "video_editing/edit/montage/editing.html#bpy-types-sequencertoolsettings-snap-ignore-muted"),
|
||||
("bpy.types.sequencertoolsettings.snap_ignore_sound*", "video_editing/edit/montage/editing.html#bpy-types-sequencertoolsettings-snap-ignore-sound"),
|
||||
("bpy.types.spaceoutliner.use_filter_case_sensitive*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-case-sensitive"),
|
||||
("bpy.types.spaceoutliner.use_filter_object_content*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-content"),
|
||||
("bpy.types.spacesequenceeditor.show_gizmo_navigate*", "editors/video_sequencer/preview/display/gizmos.html#bpy-types-spacesequenceeditor-show-gizmo-navigate"),
|
||||
("bpy.types.toolsettings.use_proportional_connected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-connected"),
|
||||
("bpy.types.toolsettings.use_proportional_projected*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-projected"),
|
||||
("bpy.types.view3doverlay.vertex_paint_mode_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-vertex-paint-mode-opacity"),
|
||||
("bpy.types.viewlayer.use_pass_cryptomatte_material*", "render/layers/passes.html#bpy-types-viewlayer-use-pass-cryptomatte-material"),
|
||||
("bpy.ops.gpencil.vertex_color_brightness_contrast*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-brightness-contrast"),
|
||||
("bpy.ops.view3d.edit_mesh_extrude_individual_move*", "modeling/meshes/editing/face/extrude_faces.html#bpy-ops-view3d-edit-mesh-extrude-individual-move"),
|
||||
("bpy.ops.view3d.edit_mesh_extrude_manifold_normal*", "modeling/meshes/tools/extrude_manifold.html#bpy-ops-view3d-edit-mesh-extrude-manifold-normal"),
|
||||
("bpy.types.brushcurvessculptsettings.curve_length*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-curve-length"),
|
||||
("bpy.types.brushcurvessculptsettings.density_mode*", "sculpt_paint/curves_sculpting/tools/density_curves.html#bpy-types-brushcurvessculptsettings-density-mode"),
|
||||
("bpy.types.brushgpencilsettings.pen_smooth_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-pen-smooth-factor"),
|
||||
("bpy.types.brushgpencilsettings.random_hue_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random-hue-factor"),
|
||||
("bpy.types.cyclescurverendersettings.subdivisions*", "render/cycles/render_settings/hair.html#bpy-types-cyclescurverendersettings-subdivisions"),
|
||||
("bpy.types.cyclesmaterialsettings.volume_sampling*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-volume-sampling"),
|
||||
("bpy.types.cyclesobjectsettings.use_deform_motion*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-deform-motion"),
|
||||
("bpy.types.cyclesobjectsettings.use_distance_cull*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-distance-cull"),
|
||||
("bpy.types.cyclesrendersettings.ao_bounces_render*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-ao-bounces-render"),
|
||||
("bpy.types.cyclesrendersettings.min_light_bounces*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-min-light-bounces"),
|
||||
("bpy.types.cyclesrendersettings.pixel_filter_type*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-pixel-filter-type"),
|
||||
("bpy.types.cyclesrendersettings.use_animated_seed*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-animated-seed"),
|
||||
("bpy.types.cyclesrendersettings.use_distance_cull*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-use-distance-cull"),
|
||||
("bpy.types.cyclesrendersettings.use_layer_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-layer-samples"),
|
||||
("bpy.types.cyclesworldsettings.homogeneous_volume*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-homogeneous-volume"),
|
||||
("bpy.types.fluiddomainsettings.cache_frame_offset*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-frame-offset"),
|
||||
("bpy.types.fluiddomainsettings.delete_in_obstacle*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-delete-in-obstacle"),
|
||||
("bpy.types.fluiddomainsettings.fractions_distance*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-fractions-distance"),
|
||||
("bpy.types.fluiddomainsettings.mesh_concave_lower*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-concave-lower"),
|
||||
("bpy.types.fluiddomainsettings.mesh_concave_upper*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-concave-upper"),
|
||||
("bpy.types.fluiddomainsettings.openvdb_data_depth*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-openvdb-data-depth"),
|
||||
("bpy.types.fluiddomainsettings.use_dissolve_smoke*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-dissolve-smoke"),
|
||||
("bpy.types.fluiddomainsettings.use_flip_particles*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-flip-particles"),
|
||||
("bpy.types.fluiddomainsettings.use_foam_particles*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-use-foam-particles"),
|
||||
("bpy.types.fluiddomainsettings.viscosity_exponent*", "physics/fluid/type/domain/liquid/diffusion.html#bpy-types-fluiddomainsettings-viscosity-exponent"),
|
||||
("bpy.types.fluideffectorsettings.surface_distance*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-surface-distance"),
|
||||
("bpy.types.fluidflowsettings.density_vertex_group*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-density-vertex-group"),
|
||||
("bpy.types.fluidflowsettings.use_initial_velocity*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-initial-velocity"),
|
||||
("bpy.types.linestylegeometrymodifier_guidinglines*", "render/freestyle/view_layer/line_style/modifiers/geometry/guiding_lines.html#bpy-types-linestylegeometrymodifier-guidinglines"),
|
||||
("bpy.types.linestylegeometrymodifier_spatialnoise*", "render/freestyle/view_layer/line_style/modifiers/geometry/spatial_noise.html#bpy-types-linestylegeometrymodifier-spatialnoise"),
|
||||
("bpy.types.linestylethicknessmodifier_calligraphy*", "render/freestyle/view_layer/line_style/modifiers/thickness/calligraphy.html#bpy-types-linestylethicknessmodifier-calligraphy"),
|
||||
("bpy.types.materiallineart.use_material_mask_bits*", "render/materials/line_art.html#bpy-types-materiallineart-use-material-mask-bits"),
|
||||
("bpy.types.movietrackingdopesheet.use_invert_sort*", "movie_clip/tracking/dope_sheet.html#bpy-types-movietrackingdopesheet-use-invert-sort"),
|
||||
("bpy.types.rendersettings_simplify_gpencil_onplay*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-onplay"),
|
||||
("bpy.types.rigidbodyconstraint.breaking_threshold*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-breaking-threshold"),
|
||||
("bpy.types.sculpt.use_automasking_cavity_inverted*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-use-automasking-cavity-inverted"),
|
||||
("bpy.types.spaceclipeditor.use_manual_calibration*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-use-manual-calibration"),
|
||||
("bpy.types.spacedopesheeteditor.show_pose_markers*", "animation/markers.html#bpy-types-spacedopesheeteditor-show-pose-markers"),
|
||||
("bpy.types.spaceimageoverlay.show_grid_background*", "editors/uv/overlays.html#bpy-types-spaceimageoverlay-show-grid-background"),
|
||||
("bpy.types.spacenodeoverlay.show_named_attributes*", "modeling/geometry_nodes/inspection.html#bpy-types-spacenodeoverlay-show-named-attributes"),
|
||||
("bpy.types.spaceoutliner.use_filter_object_camera*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-camera"),
|
||||
("bpy.types.spaceoutliner.use_filter_object_others*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-others"),
|
||||
("bpy.types.spacesequenceeditor.overlay_frame_type*", "editors/video_sequencer/preview/sidebar.html#bpy-types-spacesequenceeditor-overlay-frame-type"),
|
||||
("bpy.types.spacesequenceeditor.show_strip_overlay*", "editors/video_sequencer/sequencer/display.html#bpy-types-spacesequenceeditor-show-strip-overlay"),
|
||||
("bpy.types.spaceuveditor.custom_grid_subdivisions*", "editors/uv/overlays.html#bpy-types-spaceuveditor-custom-grid-subdivisions"),
|
||||
("bpy.types.toolsettings.proportional_edit_falloff*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-proportional-edit-falloff"),
|
||||
("bpy.types.toolsettings.use_edge_path_live_unwrap*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-edge-path-live-unwrap"),
|
||||
("bpy.types.toolsettings.use_gpencil_draw_additive*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-draw-additive"),
|
||||
("bpy.types.toolsettings.use_snap_backface_culling*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-backface-culling"),
|
||||
("bpy.types.toolsettings.use_snap_uv_grid_absolute*", "editors/uv/controls/snapping.html#bpy-types-toolsettings-use-snap-uv-grid-absolute"),
|
||||
("bpy.types.toolsettings.use_transform_data_origin*", "scene_layout/object/tools/tool_settings.html#bpy-types-toolsettings-use-transform-data-origin"),
|
||||
("bpy.types.view3doverlay.sculpt_mode_mask_opacity*", "sculpt_paint/sculpting/editing/mask.html#bpy-types-view3doverlay-sculpt-mode-mask-opacity"),
|
||||
("bpy.types.view3doverlay.viewer_attribute_opacity*", "modeling/geometry_nodes/output/viewer.html#bpy-types-view3doverlay-viewer-attribute-opacity"),
|
||||
("bpy.ops.mesh.customdata_bevel_weight_edge_clear*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-bevel-weight-edge-clear"),
|
||||
("bpy.ops.mesh.customdata_bevel_weight_vertex_add*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-bevel-weight-vertex-add"),
|
||||
("bpy.ops.mesh.customdata_custom_splitnormals_add*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-custom-splitnormals-add"),
|
||||
("bpy.ops.outliner.collection_indirect_only_clear*", "render/layers/introduction.html#bpy-ops-outliner-collection-indirect-only-clear"),
|
||||
("bpy.types.animvizmotionpaths.show_frame_numbers*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-show-frame-numbers"),
|
||||
("bpy.types.brushgpencilsettings.fill_extend_mode*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-extend-mode"),
|
||||
("bpy.types.brushgpencilsettings.pen_smooth_steps*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-pen-smooth-steps"),
|
||||
("bpy.types.brushgpencilsettings.show_fill_extend*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-show-fill-extend"),
|
||||
("bpy.types.brushgpencilsettings.use_collide_only*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-use-collide-only"),
|
||||
("bpy.types.cycleslightsettings.is_caustics_light*", "render/cycles/light_settings.html#bpy-types-cycleslightsettings-is-caustics-light"),
|
||||
("bpy.types.cyclesrendersettings.max_subdivisions*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-max-subdivisions"),
|
||||
("bpy.types.cyclesrendersettings.preview_denoiser*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-denoiser"),
|
||||
("bpy.types.cyclesrendersettings.volume_max_steps*", "render/cycles/render_settings/volumes.html#bpy-types-cyclesrendersettings-volume-max-steps"),
|
||||
("bpy.types.cyclesrendersettings.volume_step_rate*", "render/cycles/render_settings/volumes.html#bpy-types-cyclesrendersettings-volume-step-rate"),
|
||||
("bpy.types.cyclesworldsettings.is_caustics_light*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-is-caustics-light"),
|
||||
("bpy.types.dynamicpaintbrushsettings.wave_factor*", "physics/dynamic_paint/brush.html#bpy-types-dynamicpaintbrushsettings-wave-factor"),
|
||||
("bpy.types.editbone.bbone_handle_use_scale_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-scale-start"),
|
||||
("bpy.types.fluiddomainsettings.cache_data_format*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-data-format"),
|
||||
("bpy.types.fluiddomainsettings.cache_frame_start*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-frame-start"),
|
||||
("bpy.types.fluiddomainsettings.cache_mesh_format*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-mesh-format"),
|
||||
("bpy.types.fluiddomainsettings.display_thickness*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-display-thickness"),
|
||||
("bpy.types.fluiddomainsettings.flame_smoke_color*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flame-smoke-color"),
|
||||
("bpy.types.fluiddomainsettings.mesh_smoothen_neg*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-smoothen-neg"),
|
||||
("bpy.types.fluiddomainsettings.mesh_smoothen_pos*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-smoothen-pos"),
|
||||
("bpy.types.fluiddomainsettings.simulation_method*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-simulation-method"),
|
||||
("bpy.types.fluiddomainsettings.use_speed_vectors*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-use-speed-vectors"),
|
||||
("bpy.types.fluiddomainsettings.vector_show_mac_x*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-show-mac-x"),
|
||||
("bpy.types.fluiddomainsettings.vector_show_mac_y*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-show-mac-y"),
|
||||
("bpy.types.fluiddomainsettings.vector_show_mac_z*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-show-mac-z"),
|
||||
("bpy.types.fluideffectorsettings.velocity_factor*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-velocity-factor"),
|
||||
("bpy.types.freestylelineset.select_by_collection*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-by-collection"),
|
||||
("bpy.types.freestylelineset.select_by_edge_types*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-by-edge-types"),
|
||||
("bpy.types.freestylelineset.select_by_face_marks*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-by-face-marks"),
|
||||
("bpy.types.geometrynodeinputcurvehandlepositions*", "modeling/geometry_nodes/curve/read/curve_handle_position.html#bpy-types-geometrynodeinputcurvehandlepositions"),
|
||||
("bpy.types.geometrynodeinputedgepathstoselection*", "modeling/geometry_nodes/mesh/operations/edge_paths_to_selection.html#bpy-types-geometrynodeinputedgepathstoselection"),
|
||||
("bpy.types.linestyle*modifier_distancefromcamera*", "render/freestyle/view_layer/line_style/modifiers/color/distance_from_camera.html#bpy-types-linestyle-modifier-distancefromcamera"),
|
||||
("bpy.types.linestyle*modifier_distancefromobject*", "render/freestyle/view_layer/line_style/modifiers/color/distance_from_object.html#bpy-types-linestyle-modifier-distancefromobject"),
|
||||
("bpy.types.linestylegeometrymodifier_2dtransform*", "render/freestyle/view_layer/line_style/modifiers/geometry/2d_transform.html#bpy-types-linestylegeometrymodifier-2dtransform"),
|
||||
("bpy.types.linestylegeometrymodifier_beziercurve*", "render/freestyle/view_layer/line_style/modifiers/geometry/bezier_curve.html#bpy-types-linestylegeometrymodifier-beziercurve"),
|
||||
("bpy.types.materialgpencilstyle.use_fill_holdout*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-use-fill-holdout"),
|
||||
("bpy.types.materiallineart.intersection_priority*", "render/materials/line_art.html#bpy-types-materiallineart-intersection-priority"),
|
||||
("bpy.types.movietrackingplanetrack.image_opacity*", "movie_clip/tracking/clip/sidebar/track/plane_track.html#bpy-types-movietrackingplanetrack-image-opacity"),
|
||||
("bpy.types.particlesettings.use_parent_particles*", "physics/particles/emitter/render.html#bpy-types-particlesettings-use-parent-particles"),
|
||||
("bpy.types.rigidbodyconstraint.solver_iterations*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-solver-iterations"),
|
||||
("bpy.types.rigidbodyobject.collision_collections*", "physics/rigid_body/properties/collisions.html#bpy-types-rigidbodyobject-collision-collections"),
|
||||
("bpy.types.sculpt.automasking_start_normal_limit*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-automasking-start-normal-limit"),
|
||||
("bpy.types.sculpt.use_automasking_boundary_edges*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-use-automasking-boundary-edges"),
|
||||
("bpy.types.sequenceeditor.use_overlay_frame_lock*", "editors/video_sequencer/preview/sidebar.html#bpy-types-sequenceeditor-use-overlay-frame-lock"),
|
||||
("bpy.types.sequencerpreviewoverlay.show_metadata*", "editors/video_sequencer/preview/display/overlays.html#bpy-types-sequencerpreviewoverlay-show-metadata"),
|
||||
("bpy.types.sequencertimelineoverlay.show_fcurves*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-fcurves"),
|
||||
("bpy.types.spaceclipeditor.use_grayscale_preview*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-use-grayscale-preview"),
|
||||
("bpy.types.spaceoutliner.use_filter_object_empty*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-empty"),
|
||||
("bpy.types.spaceoutliner.use_filter_object_light*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-light"),
|
||||
("bpy.types.spacesequenceeditor.proxy_render_size*", "editors/video_sequencer/preview/sidebar.html#bpy-types-spacesequenceeditor-proxy-render-size"),
|
||||
("bpy.types.spacespreadsheetrowfilter.column_name*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-column-name"),
|
||||
("bpy.types.toolsettings.gpencil_stroke_placement*", "grease_pencil/modes/draw/stroke_placement.html#bpy-types-toolsettings-gpencil-stroke-placement"),
|
||||
("bpy.types.toolsettings.use_keyframe_cycle_aware*", "editors/timeline.html#bpy-types-toolsettings-use-keyframe-cycle-aware"),
|
||||
("bpy.types.toolsettings.use_keyframe_insert_auto*", "editors/timeline.html#bpy-types-toolsettings-use-keyframe-insert-auto"),
|
||||
("bpy.types.viewlayer.use_pass_cryptomatte_object*", "render/layers/passes.html#bpy-types-viewlayer-use-pass-cryptomatte-object"),
|
||||
("bpy.ops.armature.rigify_apply_selection_colors*", "addons/rigging/rigify/metarigs.html#bpy-ops-armature-rigify-apply-selection-colors"),
|
||||
("bpy.ops.ed.lib_id_generate_preview_from_object*", "editors/asset_browser.html#bpy-ops-ed-lib-id-generate-preview-from-object"),
|
||||
("bpy.types.brushcurvessculptsettings.add_amount*", "sculpt_paint/curves_sculpting/tools/add_curves.html#bpy-types-brushcurvessculptsettings-add-amount"),
|
||||
("bpy.types.brushgpencilsettings.fill_layer_mode*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-layer-mode"),
|
||||
("bpy.types.brushgpencilsettings.random_strength*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random-strength"),
|
||||
("bpy.types.brushgpencilsettings.simplify_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-simplify-factor"),
|
||||
("bpy.types.clothcollisionsettings.impulse_clamp*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-impulse-clamp"),
|
||||
("bpy.types.clothcollisionsettings.self_friction*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-self-friction"),
|
||||
("bpy.types.clothcollisionsettings.use_collision*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-use-collision"),
|
||||
("bpy.types.clothsettings.uniform_pressure_force*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-uniform-pressure-force"),
|
||||
("bpy.types.collection.lineart_intersection_mask*", "scene_layout/collections/collections.html#bpy-types-collection-lineart-intersection-mask"),
|
||||
("bpy.types.cyclesobjectsettings.use_camera_cull*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-camera-cull"),
|
||||
("bpy.types.cyclesobjectsettings.use_motion_blur*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-use-motion-blur"),
|
||||
("bpy.types.cyclesrendersettings.diffuse_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-diffuse-bounces"),
|
||||
("bpy.types.cyclesrendersettings.preview_samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-preview-samples"),
|
||||
("bpy.types.cyclesrendersettings.use_camera_cull*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-use-camera-cull"),
|
||||
("bpy.types.cyclesworldsettings.volume_step_size*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-step-size"),
|
||||
("bpy.types.dynamicpaintbrushsettings.wave_clamp*", "physics/dynamic_paint/brush.html#bpy-types-dynamicpaintbrushsettings-wave-clamp"),
|
||||
("bpy.types.editbone.bbone_handle_use_ease_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-ease-start"),
|
||||
("bpy.types.fluiddomainsettings.color_ramp_field*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-color-ramp-field"),
|
||||
("bpy.types.fluiddomainsettings.guide_vel_factor*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-vel-factor"),
|
||||
("bpy.types.fluideffectorsettings.use_plane_init*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-use-plane-init"),
|
||||
("bpy.types.freestylelineset.collection_negation*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-collection-negation"),
|
||||
("bpy.types.freestylelineset.face_mark_condition*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-face-mark-condition"),
|
||||
("bpy.types.freestylelineset.select_ridge_valley*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-ridge-valley"),
|
||||
("bpy.types.freestylelinestyle.material_boundary*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-material-boundary"),
|
||||
("bpy.types.freestylelinestyle.use_split_pattern*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-split-pattern"),
|
||||
("bpy.types.freestylesettings.use_view_map_cache*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-use-view-map-cache"),
|
||||
("bpy.types.geometrynodecurvehandletypeselection*", "modeling/geometry_nodes/curve/read/handle_type_selection.html#bpy-types-geometrynodecurvehandletypeselection"),
|
||||
("bpy.types.geometrynodedistributepointsinvolume*", "modeling/geometry_nodes/point/distribute_points_in_volume.html#bpy-types-geometrynodedistributepointsinvolume"),
|
||||
("bpy.types.geometrynodeinputmeshvertexneighbors*", "modeling/geometry_nodes/mesh/read/vertex_neighbors.html#bpy-types-geometrynodeinputmeshvertexneighbors"),
|
||||
("bpy.types.greasepencil.curve_edit_corner_angle*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-curve-edit-corner-angle"),
|
||||
("bpy.types.imageformatsettings.color_management*", "render/output/properties/output.html#bpy-types-imageformatsettings-color-management"),
|
||||
("bpy.types.lineartgpencilmodifier.source_camera*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-source-camera"),
|
||||
("bpy.types.lineartgpencilmodifier.use_edge_mark*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-edge-mark"),
|
||||
("bpy.types.lineartgpencilmodifier.use_face_mark*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-face-mark"),
|
||||
("bpy.types.linestylegeometrymodifier_tipremover*", "render/freestyle/view_layer/line_style/modifiers/geometry/tip_remover.html#bpy-types-linestylegeometrymodifier-tipremover"),
|
||||
("bpy.types.movieclipuser.use_render_undistorted*", "editors/clip/display/clip_display.html#bpy-types-movieclipuser-use-render-undistorted"),
|
||||
("bpy.types.moviesequence.animation_offset_start*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-moviesequence-animation-offset-start"),
|
||||
("bpy.types.movietrackingcamera.distortion_model*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-distortion-model"),
|
||||
("bpy.types.movietrackingtrack.use_alpha_preview*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-use-alpha-preview"),
|
||||
("bpy.types.movietrackingtrack.use_green_channel*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-use-green-channel"),
|
||||
("bpy.types.rendersettings.resolution_percentage*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-percentage"),
|
||||
("bpy.types.rendersettings_simplify_gpencil_tint*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-gpencil-tint"),
|
||||
("bpy.types.softbodysettings.use_estimate_matrix*", "physics/soft_body/settings/solver.html#bpy-types-softbodysettings-use-estimate-matrix"),
|
||||
("bpy.types.softbodysettings.vertex_group_spring*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-vertex-group-spring"),
|
||||
("bpy.types.spaceimageeditor.show_gizmo_navigate*", "editors/image/introduction.html#bpy-types-spaceimageeditor-show-gizmo-navigate"),
|
||||
("bpy.types.spaceoutliner.lib_override_view_mode*", "editors/outliner/interface.html#bpy-types-spaceoutliner-lib-override-view-mode"),
|
||||
("bpy.types.spaceoutliner.use_filter_object_mesh*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object-mesh"),
|
||||
("bpy.types.spaceoutliner.use_filter_view_layers*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-view-layers"),
|
||||
("bpy.types.spacesequenceeditor.show_overexposed*", "editors/video_sequencer/preview/sidebar.html#bpy-types-spacesequenceeditor-show-overexposed"),
|
||||
("bpy.types.toolsettings.snap_face_nearest_steps*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-snap-face-nearest-steps"),
|
||||
("bpy.types.toolsettings.use_gpencil_draw_onback*", "grease_pencil/modes/draw/introduction.html#bpy-types-toolsettings-use-gpencil-draw-onback"),
|
||||
("bpy.types.toolsettings.use_snap_align_rotation*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-align-rotation"),
|
||||
("bpy.types.toolsettings.use_snap_to_same_target*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-to-same-target"),
|
||||
("bpy.types.viewlayer.use_pass_cryptomatte_asset*", "render/layers/passes.html#bpy-types-viewlayer-use-pass-cryptomatte-asset"),
|
||||
("bpy.ops.mesh.customdata_bevel_weight_edge_add*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-bevel-weight-edge-add"),
|
||||
("bpy.ops.outliner.collection_indirect_only_set*", "render/layers/introduction.html#bpy-ops-outliner-collection-indirect-only-set"),
|
||||
("bpy.ops.scene.freestyle_geometry_modifier_add*", "render/freestyle/view_layer/line_style/geometry.html#bpy-ops-scene-freestyle-geometry-modifier-add"),
|
||||
("bpy.ops.scene.view_layer_add_used_lightgroups*", "render/layers/passes.html#bpy-ops-scene-view-layer-add-used-lightgroups"),
|
||||
("bpy.ops.sequencer.deinterlace_selected_movies*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-deinterlace-selected-movies"),
|
||||
("bpy.types.bakesettings.use_selected_to_active*", "render/cycles/baking.html#bpy-types-bakesettings-use-selected-to-active"),
|
||||
("bpy.types.brush.surface_smooth_current_vertex*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-surface-smooth-current-vertex"),
|
||||
("bpy.types.brush.use_multiplane_scrape_dynamic*", "sculpt_paint/sculpting/tools/multiplane_scrape.html#bpy-types-brush-use-multiplane-scrape-dynamic"),
|
||||
("bpy.types.brushgpencilsettings.fill_direction*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-direction"),
|
||||
("bpy.types.brushgpencilsettings.fill_draw_mode*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-draw-mode"),
|
||||
("bpy.types.brushgpencilsettings.fill_threshold*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-threshold"),
|
||||
("bpy.types.brushgpencilsettings.use_fill_limit*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-use-fill-limit"),
|
||||
("bpy.types.clothcollisionsettings.distance_min*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-distance-min"),
|
||||
("bpy.types.clothsettings.bending_stiffness_max*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-bending-stiffness-max"),
|
||||
("bpy.types.clothsettings.compression_stiffness*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-compression-stiffness"),
|
||||
("bpy.types.clothsettings.tension_stiffness_max*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-tension-stiffness-max"),
|
||||
("bpy.types.clothsettings.vertex_group_pressure*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-vertex-group-pressure"),
|
||||
("bpy.types.cyclesmaterialsettings.displacement*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings-displacement"),
|
||||
("bpy.types.cyclesrendersettings.fast_gi_method*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-fast-gi-method"),
|
||||
("bpy.types.cyclesrendersettings.glossy_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-glossy-bounces"),
|
||||
("bpy.types.cyclesrendersettings.use_light_tree*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-light-tree"),
|
||||
("bpy.types.cyclesrendersettings.volume_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-volume-bounces"),
|
||||
("bpy.types.cyclesworldsettings.sampling_method*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-sampling-method"),
|
||||
("bpy.types.cyclesworldsettings.volume_sampling*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-volume-sampling"),
|
||||
("bpy.types.dynamicpaintbrushsettings.wave_type*", "physics/dynamic_paint/brush.html#bpy-types-dynamicpaintbrushsettings-wave-type"),
|
||||
("bpy.types.editbone.bbone_handle_use_scale_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-scale-end"),
|
||||
("bpy.types.ffmpegsettings.constant_rate_factor*", "render/output/properties/output.html#bpy-types-ffmpegsettings-constant-rate-factor"),
|
||||
("bpy.types.fieldsettings.use_guide_path_weight*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-use-guide-path-weight"),
|
||||
("bpy.types.fluiddomainsettings.adapt_threshold*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-adapt-threshold"),
|
||||
("bpy.types.fluiddomainsettings.cache_directory*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-directory"),
|
||||
("bpy.types.fluiddomainsettings.cache_frame_end*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-frame-end"),
|
||||
("bpy.types.fluiddomainsettings.flame_vorticity*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flame-vorticity"),
|
||||
("bpy.types.fluiddomainsettings.noise_pos_scale*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-noise-pos-scale"),
|
||||
("bpy.types.fluiddomainsettings.noise_time_anim*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-noise-time-anim"),
|
||||
("bpy.types.fluiddomainsettings.particle_number*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-particle-number"),
|
||||
("bpy.types.fluiddomainsettings.particle_radius*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-particle-radius"),
|
||||
("bpy.types.fluiddomainsettings.slice_per_voxel*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-slice-per-voxel"),
|
||||
("bpy.types.fluiddomainsettings.surface_tension*", "physics/fluid/type/domain/liquid/diffusion.html#bpy-types-fluiddomainsettings-surface-tension"),
|
||||
("bpy.types.fluiddomainsettings.viscosity_value*", "physics/fluid/type/domain/liquid/viscosity.html#bpy-types-fluiddomainsettings-viscosity-value"),
|
||||
("bpy.types.fluideffectorsettings.effector_type*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-effector-type"),
|
||||
("bpy.types.fluidflowsettings.use_particle_size*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-particle-size"),
|
||||
("bpy.types.freestylelineset.face_mark_negation*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-face-mark-negation"),
|
||||
("bpy.types.freestylelinestyle.integration_type*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-integration-type"),
|
||||
("bpy.types.freestylelinestyle.use_split_length*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-split-length"),
|
||||
("bpy.types.geometrynodedistributepointsonfaces*", "modeling/geometry_nodes/point/distribute_points_on_faces.html#bpy-types-geometrynodedistributepointsonfaces"),
|
||||
("bpy.types.geometrynodesetcurvehandlepositions*", "modeling/geometry_nodes/curve/write/set_handle_positions.html#bpy-types-geometrynodesetcurvehandlepositions"),
|
||||
("bpy.types.greasepencil.stroke_thickness_space*", "grease_pencil/properties/strokes.html#bpy-types-greasepencil-stroke-thickness-space"),
|
||||
("bpy.types.lineartgpencilmodifier.use_material*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-material"),
|
||||
("bpy.types.linestylegeometrymodifier_blueprint*", "render/freestyle/view_layer/line_style/modifiers/geometry/blueprint.html#bpy-types-linestylegeometrymodifier-blueprint"),
|
||||
("bpy.types.materialgpencilstyle.alignment_mode*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-alignment-mode"),
|
||||
("bpy.types.movietrackingtrack.use_blue_channel*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-use-blue-channel"),
|
||||
("bpy.types.movietrackingtrack.use_custom_color*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-use-custom-color"),
|
||||
("bpy.types.objectlineart.intersection_priority*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-intersection-priority"),
|
||||
("bpy.types.particlesettings.use_modifier_stack*", "physics/particles/emitter/emission.html#bpy-types-particlesettings-use-modifier-stack"),
|
||||
("bpy.types.rendersettings.sequencer_gl_preview*", "editors/video_sequencer/preview/sidebar.html#bpy-types-rendersettings-sequencer-gl-preview"),
|
||||
("bpy.types.rendersettings.simplify_subdivision*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-subdivision"),
|
||||
("bpy.types.sculpt.use_automasking_start_normal*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-use-automasking-start-normal"),
|
||||
("bpy.types.sequencerpreviewoverlay.show_cursor*", "editors/video_sequencer/preview/display/overlays.html#bpy-types-sequencerpreviewoverlay-show-cursor"),
|
||||
("bpy.types.softbodysettings.use_edge_collision*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-use-edge-collision"),
|
||||
("bpy.types.softbodysettings.use_face_collision*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-use-face-collision"),
|
||||
("bpy.types.softbodysettings.use_self_collision*", "physics/soft_body/settings/self_collision.html#bpy-types-softbodysettings-use-self-collision"),
|
||||
("bpy.types.spacegrapheditor.show_extrapolation*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-show-extrapolation"),
|
||||
("bpy.types.spaceoutliner.use_filter_collection*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-collection"),
|
||||
("bpy.types.spacesequenceeditor.cursor_location*", "editors/video_sequencer/preview/sidebar.html#bpy-types-spacesequenceeditor-cursor-location"),
|
||||
("bpy.types.spacesequenceeditor.display_channel*", "editors/video_sequencer/preview/sidebar.html#bpy-types-spacesequenceeditor-display-channel"),
|
||||
("bpy.types.spacesequenceeditor.show_gizmo_tool*", "editors/video_sequencer/preview/display/gizmos.html#bpy-types-spacesequenceeditor-show-gizmo-tool"),
|
||||
("bpy.types.spacesequenceeditor.show_region_hud*", "editors/video_sequencer/sequencer/navigating.html#bpy-types-spacesequenceeditor-show-region-hud"),
|
||||
("bpy.types.spacespreadsheet.show_only_selected*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-show-only-selected"),
|
||||
("bpy.types.spacespreadsheetrowfilter.operation*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-operation"),
|
||||
("bpy.types.spacespreadsheetrowfilter.threshold*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-threshold"),
|
||||
("bpy.types.toolsettings.use_snap_grid_absolute*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-grid-absolute"),
|
||||
("bpy.types.view3doverlay.show_face_orientation*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-face-orientation"),
|
||||
("bpy.types.view3doverlay.show_viewer_attribute*", "modeling/geometry_nodes/output/viewer.html#bpy-types-view3doverlay-show-viewer-attribute"),
|
||||
("bpy.ops.gpencil.bake_grease_pencil_animation*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-bake-grease-pencil-animation"),
|
||||
("bpy.ops.object.multires_higher_levels_delete*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-higher-levels-delete"),
|
||||
("bpy.ops.object.vertex_group_copy_to_selected*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-copy-to-selected"),
|
||||
("bpy.ops.outliner.collection_duplicate_linked*", "editors/outliner/editing.html#bpy-ops-outliner-collection-duplicate-linked"),
|
||||
("bpy.ops.view3d.edit_mesh_extrude_move_normal*", "modeling/meshes/editing/face/extrude_faces.html#bpy-ops-view3d-edit-mesh-extrude-move-normal"),
|
||||
("bpy.types.bakesettings.use_pass_transmission*", "render/cycles/baking.html#bpy-types-bakesettings-use-pass-transmission"),
|
||||
("bpy.types.brushgpencilsettings.input_samples*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-input-samples"),
|
||||
("bpy.types.clothsettings.use_internal_springs*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-use-internal-springs"),
|
||||
("bpy.types.clothsettings.vertex_group_bending*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-vertex-group-bending"),
|
||||
("bpy.types.cyclescamerasettings.panorama_type*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-panorama-type"),
|
||||
("bpy.types.cyclesrendersettings.dicing_camera*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-dicing-camera"),
|
||||
("bpy.types.cyclesrendersettings.film_exposure*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-film-exposure"),
|
||||
("bpy.types.cyclesrendersettings.sample_offset*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-sample-offset"),
|
||||
("bpy.types.cyclesrendersettings.texture_limit*", "render/cycles/render_settings/simplify.html#bpy-types-cyclesrendersettings-texture-limit"),
|
||||
("bpy.types.cyclesrendersettings.use_denoising*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-denoising"),
|
||||
("bpy.types.editbone.bbone_custom_handle_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-custom-handle-start"),
|
||||
("bpy.types.editbone.bbone_handle_use_ease_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-use-ease-end"),
|
||||
("bpy.types.fieldsettings.guide_kink_amplitude*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-amplitude"),
|
||||
("bpy.types.fieldsettings.guide_kink_frequency*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-frequency"),
|
||||
("bpy.types.fluiddomainsettings.additional_res*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-additional-res"),
|
||||
("bpy.types.fluiddomainsettings.dissolve_speed*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-dissolve-speed"),
|
||||
("bpy.types.fluiddomainsettings.effector_group*", "physics/fluid/type/domain/collections.html#bpy-types-fluiddomainsettings-effector-group"),
|
||||
("bpy.types.fluiddomainsettings.flame_ignition*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flame-ignition"),
|
||||
("bpy.types.fluiddomainsettings.flame_max_temp*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flame-max-temp"),
|
||||
("bpy.types.fluiddomainsettings.mesh_generator*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-generator"),
|
||||
("bpy.types.fluiddomainsettings.noise_strength*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-noise-strength"),
|
||||
("bpy.types.fluiddomainsettings.particle_scale*", "physics/fluid/type/domain/liquid/particles.html#bpy-types-fluiddomainsettings-particle-scale"),
|
||||
("bpy.types.fluiddomainsettings.resolution_max*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-resolution-max"),
|
||||
("bpy.types.fluiddomainsettings.show_gridlines*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-show-gridlines"),
|
||||
("bpy.types.fluiddomainsettings.use_color_ramp*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-use-color-ramp"),
|
||||
("bpy.types.fluiddomainsettings.viscosity_base*", "physics/fluid/type/domain/liquid/diffusion.html#bpy-types-fluiddomainsettings-viscosity-base"),
|
||||
("bpy.types.fluideffectorsettings.use_effector*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-use-effector"),
|
||||
("bpy.types.fluidflowsettings.surface_distance*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-surface-distance"),
|
||||
("bpy.types.fluidflowsettings.texture_map_type*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-texture-map-type"),
|
||||
("bpy.types.freestylelineset.select_silhouette*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-silhouette"),
|
||||
("bpy.types.freestylelinestyle.texture_spacing*", "render/freestyle/view_layer/line_style/texture.html#bpy-types-freestylelinestyle-texture-spacing"),
|
||||
("bpy.types.freestylelinestyle.use_chain_count*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-chain-count"),
|
||||
("bpy.types.freestylelinestyle.use_dashed_line*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-dashed-line"),
|
||||
("bpy.types.freestylelinestyle.use_same_object*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-same-object"),
|
||||
("bpy.types.functionnodeinputspecialcharacters*", "modeling/geometry_nodes/utilities/text/special_characters.html#bpy-types-functionnodeinputspecialcharacters"),
|
||||
("bpy.types.geometrynodecurveendpointselection*", "modeling/geometry_nodes/curve/read/endpoint_selection.html#bpy-types-geometrynodecurveendpointselection"),
|
||||
("bpy.types.geometrynodeinputedgepathstocurves*", "modeling/geometry_nodes/mesh/operations/edge_paths_to_curves.html#bpy-types-geometrynodeinputedgepathstocurves"),
|
||||
("bpy.types.geometrynodeinputmeshedgeneighbors*", "modeling/geometry_nodes/mesh/read/edge_neighbors.html#bpy-types-geometrynodeinputmeshedgeneighbors"),
|
||||
("bpy.types.geometrynodeinputmeshfaceneighbors*", "modeling/geometry_nodes/mesh/read/face_neighbors.html#bpy-types-geometrynodeinputmeshfaceneighbors"),
|
||||
("bpy.types.geometrynodeinputshortestedgepaths*", "modeling/geometry_nodes/mesh/read/shortest_edge_paths.html#bpy-types-geometrynodeinputshortestedgepaths"),
|
||||
("bpy.types.gpencilsculptguide.reference_point*", "grease_pencil/modes/draw/guides.html#bpy-types-gpencilsculptguide-reference-point"),
|
||||
("bpy.types.greasepencil.edit_curve_resolution*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-edit-curve-resolution"),
|
||||
("bpy.types.lineartgpencilmodifier.use_contour*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-contour"),
|
||||
("bpy.types.linestylegeometrymodifier_2doffset*", "render/freestyle/view_layer/line_style/modifiers/geometry/2d_offset.html#bpy-types-linestylegeometrymodifier-2doffset"),
|
||||
("bpy.types.linestylegeometrymodifier_sampling*", "render/freestyle/view_layer/line_style/modifiers/geometry/sampling.html#bpy-types-linestylegeometrymodifier-sampling"),
|
||||
("bpy.types.moviesequence.animation_offset_end*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-moviesequence-animation-offset-end"),
|
||||
("bpy.types.movietrackingdopesheet.sort_method*", "movie_clip/tracking/dope_sheet.html#bpy-types-movietrackingdopesheet-sort-method"),
|
||||
("bpy.types.movietrackingtrack.use_red_channel*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-use-red-channel"),
|
||||
("bpy.types.nodesocketinterface*.default_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-default-value"),
|
||||
("bpy.types.object.add_rest_position_attribute*", "animation/shape_keys/shape_keys_panel.html#bpy-types-object-add-rest-position-attribute"),
|
||||
("bpy.types.rendersettings.line_thickness_mode*", "render/freestyle/render.html#bpy-types-rendersettings-line-thickness-mode"),
|
||||
("bpy.types.rendersettings.motion_blur_shutter*", "render/cycles/render_settings/motion_blur.html#bpy-types-rendersettings-motion-blur-shutter"),
|
||||
("bpy.types.rendersettings.use_persistent_data*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-use-persistent-data"),
|
||||
("bpy.types.sculpt.use_automasking_view_normal*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-use-automasking-view-normal"),
|
||||
("bpy.types.sequencertimelineoverlay.show_grid*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay-show-grid"),
|
||||
("bpy.types.sequencertoolsettings.overlap_mode*", "video_editing/edit/montage/editing.html#bpy-types-sequencertoolsettings-overlap-mode"),
|
||||
("bpy.types.softbodysettings.aerodynamics_type*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-aerodynamics-type"),
|
||||
("bpy.types.softbodysettings.vertex_group_goal*", "physics/soft_body/settings/goal.html#bpy-types-softbodysettings-vertex-group-goal"),
|
||||
("bpy.types.softbodysettings.vertex_group_mass*", "physics/soft_body/settings/object.html#bpy-types-softbodysettings-vertex-group-mass"),
|
||||
("bpy.types.spaceclipeditor.show_green_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-green-channel"),
|
||||
("bpy.types.spacenodeoverlay.show_context_path*", "interface/controls/nodes/introduction.html#bpy-types-spacenodeoverlay-show-context-path"),
|
||||
("bpy.types.spaceoutliner.show_restrict_column*", "editors/outliner/interface.html#bpy-types-spaceoutliner-show-restrict-column"),
|
||||
("bpy.types.spacesequenceeditor.use_clamp_view*", "editors/video_sequencer/sequencer/navigating.html#bpy-types-spacesequenceeditor-use-clamp-view"),
|
||||
("bpy.types.spacespreadsheet.object_eval_state*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-object-eval-state"),
|
||||
("bpy.types.spaceuveditor.display_stretch_type*", "editors/uv/overlays.html#bpy-types-spaceuveditor-display-stretch-type"),
|
||||
("bpy.types.spaceuveditor.show_grid_over_image*", "editors/uv/overlays.html#bpy-types-spaceuveditor-show-grid-over-image"),
|
||||
("bpy.types.toolsettings.transform_pivot_point*", "editors/3dview/controls/pivot_point/index.html#bpy-types-toolsettings-transform-pivot-point"),
|
||||
("bpy.types.toolsettings.use_proportional_edit*", "editors/3dview/controls/proportional_editing.html#bpy-types-toolsettings-use-proportional-edit"),
|
||||
("bpy.types.toolsettings.uv_sticky_select_mode*", "editors/uv/selecting.html#bpy-types-toolsettings-uv-sticky-select-mode"),
|
||||
("bpy.types.volumedisplay.interpolation_method*", "modeling/volumes/properties.html#bpy-types-volumedisplay-interpolation-method"),
|
||||
("bpy.ops.geometry.color_attribute_render_set*", "modeling/meshes/properties/object_data.html#bpy-ops-geometry-color-attribute-render-set"),
|
||||
("bpy.ops.mesh.customdata_crease_vertex_clear*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-crease-vertex-clear"),
|
||||
("bpy.types.brushgpencilsettings.angle_factor*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-angle-factor"),
|
||||
("bpy.types.brushgpencilsettings.pen_strength*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-pen-strength"),
|
||||
("bpy.types.clothcollisionsettings.collection*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings-collection"),
|
||||
("bpy.types.clothsettings.compression_damping*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-compression-damping"),
|
||||
("bpy.types.clothsettings.shear_stiffness_max*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-shear-stiffness-max"),
|
||||
("bpy.types.clothsettings.use_pressure_volume*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-use-pressure-volume"),
|
||||
("bpy.types.clothsettings.vertex_group_intern*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-vertex-group-intern"),
|
||||
("bpy.types.clothsettings.vertex_group_shrink*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-vertex-group-shrink"),
|
||||
("bpy.types.colormanagedviewsettings.exposure*", "render/color_management.html#bpy-types-colormanagedviewsettings-exposure"),
|
||||
("bpy.types.cyclescamerasettings.fisheye_lens*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-fisheye-lens"),
|
||||
("bpy.types.cyclesobjectsettings.motion_steps*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-motion-steps"),
|
||||
("bpy.types.cyclesrendersettings.filter_width*", "render/cycles/render_settings/film.html#bpy-types-cyclesrendersettings-filter-width"),
|
||||
("bpy.types.fluiddomainsettings.cfl_condition*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-cfl-condition"),
|
||||
("bpy.types.fluiddomainsettings.show_velocity*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-show-velocity"),
|
||||
("bpy.types.fluiddomainsettings.timesteps_max*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-timesteps-max"),
|
||||
("bpy.types.fluiddomainsettings.timesteps_min*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-timesteps-min"),
|
||||
("bpy.types.fluiddomainsettings.use_diffusion*", "physics/fluid/type/domain/liquid/diffusion.html#bpy-types-fluiddomainsettings-use-diffusion"),
|
||||
("bpy.types.fluiddomainsettings.use_fractions*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-use-fractions"),
|
||||
("bpy.types.fluiddomainsettings.use_viscosity*", "physics/fluid/type/domain/liquid/viscosity.html#bpy-types-fluiddomainsettings-use-viscosity"),
|
||||
("bpy.types.fluidflowsettings.particle_system*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-particle-system"),
|
||||
("bpy.types.fluidflowsettings.velocity_factor*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-velocity-factor"),
|
||||
("bpy.types.fluidflowsettings.velocity_normal*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-velocity-normal"),
|
||||
("bpy.types.freestylelineset.select_edge_mark*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-edge-mark"),
|
||||
("bpy.types.freestylelinestyle.use_length_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-length-max"),
|
||||
("bpy.types.freestylelinestyle.use_length_min*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-length-min"),
|
||||
("bpy.types.geometrynodedeformcurvesonsurface*", "modeling/geometry_nodes/curve/operations/deform_curves_on_surface.html#bpy-types-geometrynodedeformcurvesonsurface"),
|
||||
("bpy.types.geometrynodeinputinstancerotation*", "modeling/geometry_nodes/instances/instance_rotation.html#bpy-types-geometrynodeinputinstancerotation"),
|
||||
("bpy.types.geometrynodeinputmeshedgevertices*", "modeling/geometry_nodes/mesh/read/edge_vertices.html#bpy-types-geometrynodeinputmeshedgevertices"),
|
||||
("bpy.types.geometrynodeinputmeshfaceisplanar*", "modeling/geometry_nodes/mesh/read/face_is_planar.html#bpy-types-geometrynodeinputmeshfaceisplanar"),
|
||||
("bpy.types.geometrynodeinputsplineresolution*", "modeling/geometry_nodes/curve/read/spline_resolution.html#bpy-types-geometrynodeinputsplineresolution"),
|
||||
("bpy.types.geometrynodemeshfacesetboundaries*", "modeling/geometry_nodes/mesh/read/face_group_boundaries.html#bpy-types-geometrynodemeshfacesetboundaries"),
|
||||
("bpy.types.greasepencil.curve_edit_threshold*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-curve-edit-threshold"),
|
||||
("bpy.types.lineartgpencilmodifier.use_crease*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-crease"),
|
||||
("bpy.types.lineartgpencilmodifier.use_shadow*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-shadow"),
|
||||
("bpy.types.materialgpencilstyle.stroke_style*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-stroke-style"),
|
||||
("bpy.types.materiallineart.use_material_mask*", "render/materials/line_art.html#bpy-types-materiallineart-use-material-mask"),
|
||||
("bpy.types.movietracking.active_object_index*", "movie_clip/tracking/clip/sidebar/track/objects.html#bpy-types-movietracking-active-object-index"),
|
||||
("bpy.types.objectlineart.use_crease_override*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-use-crease-override"),
|
||||
("bpy.types.rendersettings.preview_pixel_size*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-preview-pixel-size"),
|
||||
("bpy.types.rendersettings.use_crop_to_border*", "render/output/properties/format.html#bpy-types-rendersettings-use-crop-to-border"),
|
||||
("bpy.types.rendersettings.use_file_extension*", "render/output/properties/output.html#bpy-types-rendersettings-use-file-extension"),
|
||||
("bpy.types.sculpt.constant_detail_resolution*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-constant-detail-resolution"),
|
||||
("bpy.types.sequencemodifier.input_mask_strip*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-sequencemodifier-input-mask-strip"),
|
||||
("bpy.types.sequencertoolsettings.pivot_point*", "editors/video_sequencer/preview/controls/pivot_point.html#bpy-types-sequencertoolsettings-pivot-point"),
|
||||
("bpy.types.spaceclipeditor.annotation_source*", "movie_clip/tracking/clip/sidebar/view.html#bpy-types-spaceclipeditor-annotation-source"),
|
||||
("bpy.types.spaceclipeditor.mask_display_type*", "editors/clip/display/mask_display.html#bpy-types-spaceclipeditor-mask-display-type"),
|
||||
("bpy.types.spaceclipeditor.mask_overlay_mode*", "editors/clip/display/mask_display.html#bpy-types-spaceclipeditor-mask-overlay-mode"),
|
||||
("bpy.types.spaceclipeditor.show_blue_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-blue-channel"),
|
||||
("bpy.types.spaceclipeditor.show_mask_overlay*", "editors/clip/display/mask_display.html#bpy-types-spaceclipeditor-show-mask-overlay"),
|
||||
("bpy.types.spacefilebrowser.system_bookmarks*", "editors/file_browser.html#bpy-types-spacefilebrowser-system-bookmarks"),
|
||||
("bpy.types.spaceimageeditor.display_channels*", "editors/image/introduction.html#bpy-types-spaceimageeditor-display-channels"),
|
||||
("bpy.types.spaceoutliner.use_filter_children*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-children"),
|
||||
("bpy.types.spaceoutliner.use_filter_complete*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-complete"),
|
||||
("bpy.types.spacespreadsheet.attribute_domain*", "editors/spreadsheet.html#bpy-types-spacespreadsheet-attribute-domain"),
|
||||
("bpy.types.spacespreadsheetrowfilter.enabled*", "editors/spreadsheet.html#bpy-types-spacespreadsheetrowfilter-enabled"),
|
||||
("bpy.types.spaceuveditor.show_modified_edges*", "editors/uv/overlays.html#bpy-types-spaceuveditor-show-modified-edges"),
|
||||
("bpy.types.spaceview3d.transform_orientation*", "editors/3dview/controls/orientation.html#bpy-types-spaceview3d-transform-orientation"),
|
||||
("bpy.types.spaceview3d.use_local_collections*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-use-local-collections"),
|
||||
("bpy.types.toolsettings.use_snap_peel_object*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-peel-object"),
|
||||
("bpy.types.view3doverlay.fade_inactive_alpha*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-fade-inactive-alpha"),
|
||||
("bpy.types.view3doverlay.wireframe_threshold*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-wireframe-threshold"),
|
||||
("bpy.types.viewlayer.active_lightgroup_index*", "render/layers/passes.html#bpy-types-viewlayer-active-lightgroup-index"),
|
||||
("bpy.ops.ed.lib_id_override_editable_toggle*", "editors/outliner/interface.html#bpy-ops-ed-lib-id-override-editable-toggle"),
|
||||
("bpy.ops.geometry.color_attribute_duplicate*", "modeling/meshes/properties/object_data.html#bpy-ops-geometry-color-attribute-duplicate"),
|
||||
("bpy.ops.object.constraint_add_with_targets*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-constraint-add-with-targets"),
|
||||
("bpy.ops.object.material_slot_remove_unused*", "scene_layout/object/editing/cleanup.html#bpy-ops-object-material-slot-remove-unused"),
|
||||
("bpy.ops.outliner.collection_disable_render*", "editors/outliner/editing.html#bpy-ops-outliner-collection-disable-render"),
|
||||
("bpy.ops.scene.freestyle_alpha_modifier_add*", "render/freestyle/view_layer/line_style/alpha.html#bpy-ops-scene-freestyle-alpha-modifier-add"),
|
||||
("bpy.ops.scene.freestyle_color_modifier_add*", "render/freestyle/view_layer/line_style/color.html#bpy-ops-scene-freestyle-color-modifier-add"),
|
||||
("bpy.ops.scene.view_layer_remove_lightgroup*", "render/layers/passes.html#bpy-ops-scene-view-layer-remove-lightgroup"),
|
||||
("bpy.types.brush.cloth_simulation_area_type*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-simulation-area-type"),
|
||||
("bpy.types.brushgpencilsettings.eraser_mode*", "grease_pencil/modes/draw/tools/erase.html#bpy-types-brushgpencilsettings-eraser-mode"),
|
||||
("bpy.types.brushgpencilsettings.fill_factor*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-fill-factor"),
|
||||
("bpy.types.clothsettings.use_sewing_springs*", "physics/cloth/settings/shape.html#bpy-types-clothsettings-use-sewing-springs"),
|
||||
("bpy.types.curve.bevel_factor_mapping_start*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-mapping-start"),
|
||||
("bpy.types.cyclescamerasettings.fisheye_fov*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-fisheye-fov"),
|
||||
("bpy.types.cyclesobjectsettings.ao_distance*", "render/cycles/object_settings/object_data.html#bpy-types-cyclesobjectsettings-ao-distance"),
|
||||
("bpy.types.cyclesobjectsettings.dicing_rate*", "render/cycles/object_settings/adaptive_subdiv.html#bpy-types-cyclesobjectsettings-dicing-rate"),
|
||||
("bpy.types.cyclesrendersettings.blur_glossy*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-blur-glossy"),
|
||||
("bpy.types.cyclesrendersettings.dicing_rate*", "render/cycles/render_settings/subdivision.html#bpy-types-cyclesrendersettings-dicing-rate"),
|
||||
("bpy.types.cyclesrendersettings.max_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-max-bounces"),
|
||||
("bpy.types.cyclesrendersettings.use_fast_gi*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-use-fast-gi"),
|
||||
("bpy.types.cyclesrendersettings.use_guiding*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-use-guiding"),
|
||||
("bpy.types.editbone.bbone_custom_handle_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-custom-handle-end"),
|
||||
("bpy.types.editbone.bbone_handle_type_start*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-type-start"),
|
||||
("bpy.types.fieldsettings.guide_clump_amount*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-clump-amount"),
|
||||
("bpy.types.fieldsettings.use_guide_path_add*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-use-guide-path-add"),
|
||||
("bpy.types.fileselectparams.recursion_level*", "editors/file_browser.html#bpy-types-fileselectparams-recursion-level"),
|
||||
("bpy.types.fluiddomainsettings.adapt_margin*", "physics/fluid/type/domain/gas/adaptive_domain.html#bpy-types-fluiddomainsettings-adapt-margin"),
|
||||
("bpy.types.fluiddomainsettings.burning_rate*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-burning-rate"),
|
||||
("bpy.types.fluiddomainsettings.guide_parent*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-parent"),
|
||||
("bpy.types.fluiddomainsettings.guide_source*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-source"),
|
||||
("bpy.types.fluiddomainsettings.particle_max*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-particle-max"),
|
||||
("bpy.types.fluiddomainsettings.particle_min*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-particle-min"),
|
||||
("bpy.types.fluiddomainsettings.vector_field*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-field"),
|
||||
("bpy.types.fluiddomainsettings.vector_scale*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-vector-scale"),
|
||||
("bpy.types.fluideffectorsettings.guide_mode*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-guide-mode"),
|
||||
("bpy.types.fluidflowsettings.texture_offset*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-texture-offset"),
|
||||
("bpy.types.fluidflowsettings.use_plane_init*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-plane-init"),
|
||||
("bpy.types.fluidflowsettings.velocity_coord*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-velocity-coord"),
|
||||
("bpy.types.fluidflowsettings.volume_density*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-volume-density"),
|
||||
("bpy.types.freestylelinestyle.use_angle_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-angle-max"),
|
||||
("bpy.types.freestylelinestyle.use_angle_min*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-angle-min"),
|
||||
("bpy.types.freestylesettings.as_render_pass*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-as-render-pass"),
|
||||
("bpy.types.freestylesettings.use_smoothness*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-use-smoothness"),
|
||||
("bpy.types.geometrynodecurveprimitivecircle*", "modeling/geometry_nodes/curve/primitives/curve_circle.html#bpy-types-geometrynodecurveprimitivecircle"),
|
||||
("bpy.types.geometrynodecurvequadraticbezier*", "modeling/geometry_nodes/curve/primitives/quadratic_bezier.html#bpy-types-geometrynodecurvequadraticbezier"),
|
||||
("bpy.types.geometrynoderemovenamedattribute*", "modeling/geometry_nodes/attribute/remove_named_attribute.html#bpy-types-geometrynoderemovenamedattribute"),
|
||||
("bpy.types.geometrynodesamplenearestsurface*", "modeling/geometry_nodes/mesh/operations/sample_nearest_surface.html#bpy-types-geometrynodesamplenearestsurface"),
|
||||
("bpy.types.gpencillayer.use_viewlayer_masks*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-viewlayer-masks"),
|
||||
("bpy.types.greasepencil.onion_keyframe_type*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-onion-keyframe-type"),
|
||||
("bpy.types.lineartgpencilmodifier.use_cache*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-cache"),
|
||||
("bpy.types.lineartgpencilmodifier.use_loose*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-use-loose"),
|
||||
("bpy.types.materialgpencilstyle.show_stroke*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-show-stroke"),
|
||||
("bpy.types.movietrackingcamera.focal_length*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-focal-length"),
|
||||
("bpy.types.movietrackingcamera.pixel_aspect*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-pixel-aspect"),
|
||||
("bpy.types.movietrackingcamera.sensor_width*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-sensor-width"),
|
||||
("bpy.types.posebone.use_ik_rotation_control*", "animation/armatures/posing/bone_constraints/inverse_kinematics/introduction.html#bpy-types-posebone-use-ik-rotation-control"),
|
||||
("bpy.types.rendersettings.use_bake_multires*", "render/cycles/baking.html#bpy-types-rendersettings-use-bake-multires"),
|
||||
("bpy.types.rigidbodyobject.collision_margin*", "physics/rigid_body/properties/collisions.html#bpy-types-rigidbodyobject-collision-margin"),
|
||||
("bpy.types.scenegpencil.antialias_threshold*", "render/cycles/render_settings/grease_pencil.html#bpy-types-scenegpencil-antialias-threshold"),
|
||||
("bpy.types.sculpt.use_automasking_face_sets*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-use-automasking-face-sets"),
|
||||
("bpy.types.sequencemodifier.input_mask_type*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-sequencemodifier-input-mask-type"),
|
||||
("bpy.types.softbodysettings.error_threshold*", "physics/soft_body/settings/solver.html#bpy-types-softbodysettings-error-threshold"),
|
||||
("bpy.types.softbodysettings.use_stiff_quads*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-use-stiff-quads"),
|
||||
("bpy.types.spaceclipeditor.show_mask_spline*", "editors/clip/display/mask_display.html#bpy-types-spaceclipeditor-show-mask-spline"),
|
||||
("bpy.types.spaceclipeditor.show_red_channel*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-red-channel"),
|
||||
("bpy.types.spaceclipeditor.use_mute_footage*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-use-mute-footage"),
|
||||
("bpy.types.spacenodeoverlay.show_wire_color*", "interface/controls/nodes/introduction.html#bpy-types-spacenodeoverlay-show-wire-color"),
|
||||
("bpy.types.spacesequenceeditor.display_mode*", "editors/video_sequencer/preview/display/display_mode.html#bpy-types-spacesequenceeditor-display-mode"),
|
||||
("bpy.types.spaceview3d.show_object_viewport*", "editors/3dview/display/visibility.html#bpy-types-spaceview3d-show-object-viewport"),
|
||||
("bpy.types.toolsettings.use_snap_selectable*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-selectable"),
|
||||
("bpy.types.view3doverlay.show_fade_inactive*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-fade-inactive"),
|
||||
("bpy.types.viewlayer.pass_cryptomatte_depth*", "render/layers/passes.html#bpy-types-viewlayer-pass-cryptomatte-depth"),
|
||||
("bpy.ops.clip.stabilize_2d_rotation_select*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-stabilize-2d-rotation-select"),
|
||||
("bpy.ops.constraint.disable_keep_transform*", "animation/constraints/interface/common.html#bpy-ops-constraint-disable-keep-transform"),
|
||||
("bpy.ops.curves.convert_to_particle_system*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-curves-convert-to-particle-system"),
|
||||
("bpy.ops.gpencil.stroke_reset_vertex_color*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-stroke-reset-vertex-color"),
|
||||
("bpy.ops.mesh.customdata_crease_edge_clear*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-crease-edge-clear"),
|
||||
("bpy.ops.mesh.customdata_crease_vertex_add*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-crease-vertex-add"),
|
||||
("bpy.ops.object.modifier_apply_as_shapekey*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-apply-as-shapekey"),
|
||||
("bpy.ops.object.vertex_group_normalize_all*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-normalize-all"),
|
||||
("bpy.ops.outliner.collection_color_tag_set*", "editors/outliner/editing.html#bpy-ops-outliner-collection-color-tag-set"),
|
||||
("bpy.ops.outliner.collection_enable_render*", "editors/outliner/editing.html#bpy-ops-outliner-collection-enable-render"),
|
||||
("bpy.ops.outliner.collection_exclude_clear*", "editors/outliner/editing.html#bpy-ops-outliner-collection-exclude-clear"),
|
||||
("bpy.ops.outliner.collection_holdout_clear*", "render/layers/introduction.html#bpy-ops-outliner-collection-holdout-clear"),
|
||||
("bpy.ops.sculpt.face_set_change_visibility*", "sculpt_paint/sculpting/editing/face_sets.html#bpy-ops-sculpt-face-set-change-visibility"),
|
||||
("bpy.ops.sculpt.face_sets_randomize_colors*", "sculpt_paint/sculpting/editing/face_sets.html#bpy-ops-sculpt-face-sets-randomize-colors"),
|
||||
("bpy.types.animvizmotionpaths.frame_before*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-frame-before"),
|
||||
("bpy.types.brush.disconnected_distance_max*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-disconnected-distance-max"),
|
||||
("bpy.types.brush.surface_smooth_iterations*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-surface-smooth-iterations"),
|
||||
("bpy.types.brushgpencilsettings.pen_jitter*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-pen-jitter"),
|
||||
("bpy.types.brushgpencilsettings.show_lasso*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-show-lasso"),
|
||||
("bpy.types.clothsettings.bending_stiffness*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-bending-stiffness"),
|
||||
("bpy.types.clothsettings.tension_stiffness*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-tension-stiffness"),
|
||||
("bpy.types.clothsettings.vertex_group_mass*", "physics/cloth/settings/shape.html#bpy-types-clothsettings-vertex-group-mass"),
|
||||
("bpy.types.compositornodeconvertcolorspace*", "compositing/types/converter/color_space.html#bpy-types-compositornodeconvertcolorspace"),
|
||||
("bpy.types.cyclescurverendersettings.shape*", "render/cycles/render_settings/hair.html#bpy-types-cyclescurverendersettings-shape"),
|
||||
("bpy.types.cycleslightsettings.cast_shadow*", "render/cycles/light_settings.html#bpy-types-cycleslightsettings-cast-shadow"),
|
||||
("bpy.types.cycleslightsettings.max_bounces*", "render/cycles/light_settings.html#bpy-types-cycleslightsettings-max-bounces"),
|
||||
("bpy.types.cyclesrendersettings.ao_bounces*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-ao-bounces"),
|
||||
("bpy.types.cyclesrendersettings.time_limit*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-time-limit"),
|
||||
("bpy.types.cyclesvisibilitysettings.camera*", "render/cycles/world_settings.html#bpy-types-cyclesvisibilitysettings-camera"),
|
||||
("bpy.types.cyclesworldsettings.max_bounces*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings-max-bounces"),
|
||||
("bpy.types.ffmpegsettings.use_max_b_frames*", "render/output/properties/output.html#bpy-types-ffmpegsettings-use-max-b-frames"),
|
||||
("bpy.types.fieldsettings.apply_to_location*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-apply-to-location"),
|
||||
("bpy.types.fieldsettings.apply_to_rotation*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-apply-to-rotation"),
|
||||
("bpy.types.fieldsettings.guide_clump_shape*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-clump-shape"),
|
||||
("bpy.types.fluiddomainsettings.domain_type*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-domain-type"),
|
||||
("bpy.types.fluiddomainsettings.flame_smoke*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flame-smoke"),
|
||||
("bpy.types.fluiddomainsettings.fluid_group*", "physics/fluid/type/domain/collections.html#bpy-types-fluiddomainsettings-fluid-group"),
|
||||
("bpy.types.fluiddomainsettings.guide_alpha*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-alpha"),
|
||||
("bpy.types.fluiddomainsettings.noise_scale*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-noise-scale"),
|
||||
("bpy.types.fluiddomainsettings.slice_depth*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-slice-depth"),
|
||||
("bpy.types.fluideffectorsettings.subframes*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings-subframes"),
|
||||
("bpy.types.fluidflowsettings.flow_behavior*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-flow-behavior"),
|
||||
("bpy.types.fluidflowsettings.noise_texture*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-noise-texture"),
|
||||
("bpy.types.freestylelineset.select_contour*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-contour"),
|
||||
("bpy.types.freestylelinestyle.split_length*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-split-length"),
|
||||
("bpy.types.freestylelinestyle.use_chaining*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-chaining"),
|
||||
("bpy.types.freestylesettings.sphere_radius*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-sphere-radius"),
|
||||
("bpy.types.geometrynodeattributedomainsize*", "modeling/geometry_nodes/attribute/domain_size.html#bpy-types-geometrynodeattributedomainsize"),
|
||||
("bpy.types.geometrynodesetsplineresolution*", "modeling/geometry_nodes/curve/write/set_spline_resolution.html#bpy-types-geometrynodesetsplineresolution"),
|
||||
("bpy.types.geometrynodestorenamedattribute*", "modeling/geometry_nodes/attribute/store_named_attribute.html#bpy-types-geometrynodestorenamedattribute"),
|
||||
("bpy.types.gpencillayer.annotation_opacity*", "interface/annotate_tool.html#bpy-types-gpencillayer-annotation-opacity"),
|
||||
("bpy.types.gpencillayer.use_onion_skinning*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-onion-skinning"),
|
||||
("bpy.types.gpencilsculptguide.use_snapping*", "grease_pencil/modes/draw/guides.html#bpy-types-gpencilsculptguide-use-snapping"),
|
||||
("bpy.types.gpencilsculptsettings.lock_axis*", "grease_pencil/modes/draw/drawing_planes.html#bpy-types-gpencilsculptsettings-lock-axis"),
|
||||
("bpy.types.greasepencil.ghost_before_range*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-ghost-before-range"),
|
||||
("bpy.types.greasepencil.stroke_depth_order*", "grease_pencil/properties/strokes.html#bpy-types-greasepencil-stroke-depth-order"),
|
||||
("bpy.types.imageformatsettings.file_format*", "render/output/properties/output.html#bpy-types-imageformatsettings-file-format"),
|
||||
("bpy.types.imagepaint.use_backface_culling*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-use-backface-culling"),
|
||||
("bpy.types.lineartgpencilmodifier.overscan*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier-overscan"),
|
||||
("bpy.types.linestyle*modifier_curvature_3d*", "render/freestyle/view_layer/line_style/modifiers/color/curvature_3d.html#bpy-types-linestyle-modifier-curvature-3d"),
|
||||
("bpy.types.materialgpencilstyle.fill_color*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-fill-color"),
|
||||
("bpy.types.materialgpencilstyle.fill_style*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-fill-style"),
|
||||
("bpy.types.materialgpencilstyle.mix_factor*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-mix-factor"),
|
||||
("bpy.types.materialgpencilstyle.pass_index*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-pass-index"),
|
||||
("bpy.types.nodesocketinterface.description*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-description"),
|
||||
("bpy.types.rendersettings.dither_intensity*", "render/output/properties/post_processing.html#bpy-types-rendersettings-dither-intensity"),
|
||||
("bpy.types.rendersettings.film_transparent*", "render/cycles/render_settings/film.html#bpy-types-rendersettings-film-transparent"),
|
||||
("bpy.types.rendersettings.simplify_volumes*", "render/cycles/render_settings/simplify.html#bpy-types-rendersettings-simplify-volumes"),
|
||||
("bpy.types.rendersettings.use_render_cache*", "render/output/properties/output.html#bpy-types-rendersettings-use-render-cache"),
|
||||
("bpy.types.rendersettings.use_single_layer*", "render/layers/view_layer.html#bpy-types-rendersettings-use-single-layer"),
|
||||
("bpy.types.rigidbodyobject.collision_shape*", "physics/rigid_body/properties/collisions.html#bpy-types-rigidbodyobject-collision-shape"),
|
||||
("bpy.types.sceneeevee.use_taa_reprojection*", "render/eevee/render_settings/sampling.html#bpy-types-sceneeevee-use-taa-reprojection"),
|
||||
("bpy.types.sculpt.use_automasking_topology*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-use-automasking-topology"),
|
||||
("bpy.types.softbodysettings.collision_type*", "physics/soft_body/settings/self_collision.html#bpy-types-softbodysettings-collision-type"),
|
||||
("bpy.types.spaceclipeditor.cursor_location*", "editors/clip/sidebar.html#bpy-types-spaceclipeditor-cursor-location"),
|
||||
("bpy.types.spacefilebrowser.recent_folders*", "editors/file_browser.html#bpy-types-spacefilebrowser-recent-folders"),
|
||||
("bpy.types.spacefilebrowser.system_folders*", "editors/file_browser.html#bpy-types-spacefilebrowser-system-folders"),
|
||||
("bpy.types.spacenodeeditor.show_annotation*", "interface/controls/nodes/introduction.html#bpy-types-spacenodeeditor-show-annotation"),
|
||||
("bpy.types.spaceoutliner.use_filter_object*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-filter-object"),
|
||||
("bpy.types.spacesequenceeditor.use_proxies*", "editors/video_sequencer/preview/sidebar.html#bpy-types-spacesequenceeditor-use-proxies"),
|
||||
("bpy.types.spaceuveditor.edge_display_type*", "editors/uv/overlays.html#bpy-types-spaceuveditor-edge-display-type"),
|
||||
("bpy.types.spaceuveditor.grid_shape_source*", "editors/uv/overlays.html#bpy-types-spaceuveditor-grid-shape-source"),
|
||||
("bpy.types.spaceuveditor.show_pixel_coords*", "editors/uv/sidebar.html#bpy-types-spaceuveditor-show-pixel-coords"),
|
||||
("bpy.types.spaceview3d.show_reconstruction*", "editors/3dview/display/overlays.html#bpy-types-spaceview3d-show-reconstruction"),
|
||||
("bpy.types.toolsettings.gpencil_selectmode*", "grease_pencil/selecting.html#bpy-types-toolsettings-gpencil-selectmode"),
|
||||
("bpy.types.toolsettings.use_auto_normalize*", "sculpt_paint/weight_paint/tool_settings/options.html#bpy-types-toolsettings-use-auto-normalize"),
|
||||
("bpy.types.toolsettings.use_mesh_automerge*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-use-mesh-automerge"),
|
||||
("bpy.types.toolsettings.use_snap_sequencer*", "video_editing/edit/montage/editing.html#bpy-types-toolsettings-use-snap-sequencer"),
|
||||
("bpy.types.toolsettings.use_snap_translate*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-translate"),
|
||||
("bpy.types.toolsettings.use_uv_select_sync*", "editors/uv/selecting.html#bpy-types-toolsettings-use-uv-select-sync"),
|
||||
("bpy.types.view3doverlay.wireframe_opacity*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-wireframe-opacity"),
|
||||
("bpy.ops.asset.open_containing_blend_file*", "editors/asset_browser.html#bpy-ops-asset-open-containing-blend-file"),
|
||||
("bpy.ops.gpencil.active_frames_delete_all*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-active-frames-delete-all"),
|
||||
("bpy.ops.gpencil.stroke_merge_by_distance*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-stroke-merge-by-distance"),
|
||||
("bpy.ops.node.collapse_hide_unused_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-collapse-hide-unused-toggle"),
|
||||
("bpy.ops.object.anim_transforms_to_deltas*", "scene_layout/object/editing/apply.html#bpy-ops-object-anim-transforms-to-deltas"),
|
||||
("bpy.ops.object.modifier_copy_to_selected*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-copy-to-selected"),
|
||||
("bpy.ops.preferences.app_template_install*", "advanced/app_templates.html#bpy-ops-preferences-app-template-install"),
|
||||
("bpy.types.animvizmotionpaths.frame_after*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-frame-after"),
|
||||
("bpy.types.animvizmotionpaths.frame_start*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-frame-start"),
|
||||
("bpy.types.bakesettings.use_pass_indirect*", "render/cycles/baking.html#bpy-types-bakesettings-use-pass-indirect"),
|
||||
("bpy.types.brush.cloth_force_falloff_type*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-force-falloff-type"),
|
||||
("bpy.types.brush.use_automasking_topology*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-use-automasking-topology"),
|
||||
("bpy.types.brushgpencilsettings.caps_type*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-caps-type"),
|
||||
("bpy.types.brushgpencilsettings.show_fill*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-show-fill"),
|
||||
("bpy.types.brushgpencilsettings.uv_random*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-uv-random"),
|
||||
("bpy.types.brushtextureslot.mask_map_mode*", "sculpt_paint/brush/texture.html#bpy-types-brushtextureslot-mask-map-mode"),
|
||||
("bpy.types.clothsettings.sewing_force_max*", "physics/cloth/settings/shape.html#bpy-types-clothsettings-sewing-force-max"),
|
||||
("bpy.types.clothsettings.use_dynamic_mesh*", "physics/cloth/settings/shape.html#bpy-types-clothsettings-use-dynamic-mesh"),
|
||||
("bpy.types.colormanagedviewsettings.gamma*", "render/color_management.html#bpy-types-colormanagedviewsettings-gamma"),
|
||||
("bpy.types.compositornodeplanetrackdeform*", "compositing/types/distort/plane_track_deform.html#bpy-types-compositornodeplanetrackdeform"),
|
||||
("bpy.types.curve.bevel_factor_mapping_end*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-mapping-end"),
|
||||
("bpy.types.cyclescamerasettings.longitude*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-longitude"),
|
||||
("bpy.types.editbone.bbone_handle_type_end*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-handle-type-end"),
|
||||
("bpy.types.editbone.use_endroll_as_inroll*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-use-endroll-as-inroll"),
|
||||
("bpy.types.fieldsettings.guide_kink_shape*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-shape"),
|
||||
("bpy.types.fieldsettings.use_max_distance*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-use-max-distance"),
|
||||
("bpy.types.fieldsettings.use_min_distance*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-use-min-distance"),
|
||||
("bpy.types.fileselectparams.filter_search*", "editors/file_browser.html#bpy-types-fileselectparams-filter-search"),
|
||||
("bpy.types.fluiddomainsettings.cache_type*", "physics/fluid/type/domain/cache.html#bpy-types-fluiddomainsettings-cache-type"),
|
||||
("bpy.types.fluiddomainsettings.flip_ratio*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-flip-ratio"),
|
||||
("bpy.types.fluiddomainsettings.guide_beta*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-guide-beta"),
|
||||
("bpy.types.fluiddomainsettings.mesh_scale*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-mesh-scale"),
|
||||
("bpy.types.fluiddomainsettings.slice_axis*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-slice-axis"),
|
||||
("bpy.types.fluiddomainsettings.time_scale*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-time-scale"),
|
||||
("bpy.types.fluidflowsettings.texture_size*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-texture-size"),
|
||||
("bpy.types.fluidflowsettings.use_absolute*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-absolute"),
|
||||
("bpy.types.freestylelineset.select_border*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-border"),
|
||||
("bpy.types.freestylelineset.select_crease*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-select-crease"),
|
||||
("bpy.types.freestylelinestyle.chain_count*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-chain-count"),
|
||||
("bpy.types.freestylelinestyle.use_sorting*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-use-sorting"),
|
||||
("bpy.types.freestylesettings.crease_angle*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-crease-angle"),
|
||||
("bpy.types.functionnodealigneulertovector*", "modeling/geometry_nodes/utilities/rotation/align_euler_to_vector.html#bpy-types-functionnodealigneulertovector"),
|
||||
("bpy.types.geometrynodeattributestatistic*", "modeling/geometry_nodes/attribute/attribute_statistic.html#bpy-types-geometrynodeattributestatistic"),
|
||||
("bpy.types.geometrynodecurveprimitiveline*", "modeling/geometry_nodes/curve/primitives/curve_line.html#bpy-types-geometrynodecurveprimitiveline"),
|
||||
("bpy.types.geometrynodegeometrytoinstance*", "modeling/geometry_nodes/geometry/geometry_to_instance.html#bpy-types-geometrynodegeometrytoinstance"),
|
||||
("bpy.types.geometrynodeinputinstancescale*", "modeling/geometry_nodes/instances/instance_scale.html#bpy-types-geometrynodeinputinstancescale"),
|
||||
("bpy.types.geometrynodeinputmaterialindex*", "modeling/geometry_nodes/material/material_index.html#bpy-types-geometrynodeinputmaterialindex"),
|
||||
("bpy.types.geometrynodeinputmeshedgeangle*", "modeling/geometry_nodes/mesh/read/edge_angle.html#bpy-types-geometrynodeinputmeshedgeangle"),
|
||||
("bpy.types.geometrynodeoffsetcornerinface*", "modeling/geometry_nodes/mesh/topology/offset_corner_in_face.html#bpy-types-geometrynodeoffsetcornerinface"),
|
||||
("bpy.types.geometrynodeoffsetpointincurve*", "modeling/geometry_nodes/curve/topology/offset_point_in_curve.html#bpy-types-geometrynodeoffsetpointincurve"),
|
||||
("bpy.types.geometrynodeseparatecomponents*", "modeling/geometry_nodes/geometry/operations/separate_components.html#bpy-types-geometrynodeseparatecomponents"),
|
||||
("bpy.types.geometrynodesubdivisionsurface*", "modeling/geometry_nodes/mesh/operations/subdivision_surface.html#bpy-types-geometrynodesubdivisionsurface"),
|
||||
("bpy.types.geometrynodetranslateinstances*", "modeling/geometry_nodes/instances/translate_instances.html#bpy-types-geometrynodetranslateinstances"),
|
||||
("bpy.types.greasepencil.ghost_after_range*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-ghost-after-range"),
|
||||
("bpy.types.greasepencil.use_ghosts_always*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-use-ghosts-always"),
|
||||
("bpy.types.imageformatsettings.color_mode*", "render/output/properties/output.html#bpy-types-imageformatsettings-color-mode"),
|
||||
("bpy.types.linestyle*modifier_alongstroke*", "render/freestyle/view_layer/line_style/modifiers/color/along_stroke.html#bpy-types-linestyle-modifier-alongstroke"),
|
||||
("bpy.types.linestyle*modifier_creaseangle*", "render/freestyle/view_layer/line_style/modifiers/color/crease_angle.html#bpy-types-linestyle-modifier-creaseangle"),
|
||||
("bpy.types.linestylecolormodifier_tangent*", "render/freestyle/view_layer/line_style/modifiers/color/tangent.html#bpy-types-linestylecolormodifier-tangent"),
|
||||
("bpy.types.material.show_transparent_back*", "render/eevee/materials/settings.html#bpy-types-material-show-transparent-back"),
|
||||
("bpy.types.material.use_screen_refraction*", "render/eevee/materials/settings.html#bpy-types-material-use-screen-refraction"),
|
||||
("bpy.types.materialgpencilstyle.mix_color*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-mix-color"),
|
||||
("bpy.types.materialgpencilstyle.show_fill*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-show-fill"),
|
||||
("bpy.types.mesh.use_mirror_vertex_group_x*", "sculpt_paint/weight_paint/tool_settings/symmetry.html#bpy-types-mesh-use-mirror-vertex-group-x"),
|
||||
("bpy.types.movietrackingcamera.division_k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-division-k"),
|
||||
("bpy.types.movietrackingobject.keyframe_a*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingobject-keyframe-a"),
|
||||
("bpy.types.movietrackingobject.keyframe_b*", "movie_clip/tracking/clip/toolbar/solve.html#bpy-types-movietrackingobject-keyframe-b"),
|
||||
("bpy.types.movietrackingtrack.weight_stab*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-weight-stab"),
|
||||
("bpy.types.nodesocketinterface*.max_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-max-value"),
|
||||
("bpy.types.nodesocketinterface*.min_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-min-value"),
|
||||
("bpy.types.nodesocketinterface.hide_value*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-hide-value"),
|
||||
("bpy.types.object.use_shape_key_edit_mode*", "animation/shape_keys/shape_keys_panel.html#bpy-types-object-use-shape-key-edit-mode"),
|
||||
("bpy.types.objectlineart.crease_threshold*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-crease-threshold"),
|
||||
("bpy.types.rendersettings.use_compositing*", "render/output/properties/post_processing.html#bpy-types-rendersettings-use-compositing"),
|
||||
("bpy.types.rendersettings.use_motion_blur*", "render/cycles/render_settings/motion_blur.html#bpy-types-rendersettings-use-motion-blur"),
|
||||
("bpy.types.rendersettings.use_placeholder*", "render/output/properties/output.html#bpy-types-rendersettings-use-placeholder"),
|
||||
("bpy.types.sequencemodifier.input_mask_id*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-sequencemodifier-input-mask-id"),
|
||||
("bpy.types.shadernodesubsurfacescattering*", "render/shader_nodes/shader/sss.html#bpy-types-shadernodesubsurfacescattering"),
|
||||
("bpy.types.softbodysettings.goal_friction*", "physics/soft_body/settings/goal.html#bpy-types-softbodysettings-goal-friction"),
|
||||
("bpy.types.softbodysettings.spring_length*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-spring-length"),
|
||||
("bpy.types.softbodysettings.use_auto_step*", "physics/soft_body/settings/solver.html#bpy-types-softbodysettings-use-auto-step"),
|
||||
("bpy.types.spaceclipeditor.lock_selection*", "editors/clip/introduction.html#bpy-types-spaceclipeditor-lock-selection"),
|
||||
("bpy.types.spacedopesheeteditor.auto_snap*", "editors/dope_sheet/editing.html#bpy-types-spacedopesheeteditor-auto-snap"),
|
||||
("bpy.types.spacenodeoverlay.show_overlays*", "interface/controls/nodes/introduction.html#bpy-types-spacenodeoverlay-show-overlays"),
|
||||
("bpy.types.spaceoutliner.show_mode_column*", "editors/outliner/interface.html#bpy-types-spaceoutliner-show-mode-column"),
|
||||
("bpy.types.spacesequenceeditor.show_gizmo*", "editors/video_sequencer/preview/display/gizmos.html#bpy-types-spacesequenceeditor-show-gizmo"),
|
||||
("bpy.types.spacetexteditor.use_match_case*", "editors/text_editor.html#bpy-types-spacetexteditor-use-match-case"),
|
||||
("bpy.types.spaceuveditor.pixel_round_mode*", "modeling/meshes/uv/editing.html#bpy-types-spaceuveditor-pixel-round-mode"),
|
||||
("bpy.types.spaceview3d.show_object_select*", "editors/3dview/display/visibility.html#bpy-types-spaceview3d-show-object-select"),
|
||||
("bpy.types.toolsettings.use_lock_relative*", "sculpt_paint/weight_paint/tool_settings/options.html#bpy-types-toolsettings-use-lock-relative"),
|
||||
("bpy.types.vertexpaint.use_group_restrict*", "sculpt_paint/weight_paint/tool_settings/options.html#bpy-types-vertexpaint-use-group-restrict"),
|
||||
("bpy.types.volumedisplay.wireframe_detail*", "modeling/volumes/properties.html#bpy-types-volumedisplay-wireframe-detail"),
|
||||
("bpy.types.windowmanager.asset_path_dummy*", "editors/asset_browser.html#bpy-types-windowmanager-asset-path-dummy"),
|
||||
("bpy.ops.armature.rigify_add_bone_groups*", "addons/rigging/rigify/metarigs.html#bpy-ops-armature-rigify-add-bone-groups"),
|
||||
("bpy.ops.geometry.color_attribute_remove*", "modeling/meshes/properties/object_data.html#bpy-ops-geometry-color-attribute-remove"),
|
||||
("bpy.ops.mesh.customdata_crease_edge_add*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-crease-edge-add"),
|
||||
("bpy.ops.object.assign_property_defaults*", "animation/armatures/posing/editing/apply.html#bpy-ops-object-assign-property-defaults"),
|
||||
("bpy.ops.object.vertex_group_limit_total*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-limit-total"),
|
||||
("bpy.ops.object.vertex_group_remove_from*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-remove-from"),
|
||||
("bpy.ops.outliner.collection_exclude_set*", "editors/outliner/editing.html#bpy-ops-outliner-collection-exclude-set"),
|
||||
("bpy.ops.outliner.collection_hide_inside*", "editors/outliner/editing.html#bpy-ops-outliner-collection-hide-inside"),
|
||||
("bpy.ops.outliner.collection_holdout_set*", "render/layers/introduction.html#bpy-ops-outliner-collection-holdout-set"),
|
||||
("bpy.ops.outliner.collection_show_inside*", "editors/outliner/editing.html#bpy-ops-outliner-collection-show-inside"),
|
||||
("bpy.ops.poselib.pose_asset_select_bones*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-pose-asset-select-bones"),
|
||||
("bpy.ops.poselib.restore_previous_action*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-restore-previous-action"),
|
||||
("bpy.ops.preferences.reset_default_theme*", "editors/preferences/themes.html#bpy-ops-preferences-reset-default-theme"),
|
||||
("bpy.ops.scene.view_layer_add_lightgroup*", "render/layers/passes.html#bpy-ops-scene-view-layer-add-lightgroup"),
|
||||
("bpy.ops.sculpt.dyntopo_detail_size_edit*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-ops-sculpt-dyntopo-detail-size-edit"),
|
||||
("bpy.ops.sculpt_curves.min_distance_edit*", "sculpt_paint/curves_sculpting/tools/density_curves.html#bpy-ops-sculpt-curves-min-distance-edit"),
|
||||
("bpy.ops.sequencer.strip_transform_clear*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-strip-transform-clear"),
|
||||
("bpy.ops.spreadsheet.add_row_filter_rule*", "editors/spreadsheet.html#bpy-ops-spreadsheet-add-row-filter-rule"),
|
||||
("bpy.types.animdata.action_extrapolation*", "editors/nla/sidebar.html#bpy-types-animdata-action-extrapolation"),
|
||||
("bpy.types.animvizmotionpaths.frame_step*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-frame-step"),
|
||||
("bpy.types.bakesettings.max_ray_distance*", "render/cycles/baking.html#bpy-types-bakesettings-max-ray-distance"),
|
||||
("bpy.types.brush.multiplane_scrape_angle*", "sculpt_paint/sculpting/tools/multiplane_scrape.html#bpy-types-brush-multiplane-scrape-angle"),
|
||||
("bpy.types.brushgpencilsettings.hardness*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-hardness"),
|
||||
("bpy.types.brushgpencilsettings.use_trim*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-use-trim"),
|
||||
("bpy.types.brushtextureslot.random_angle*", "sculpt_paint/brush/texture.html#bpy-types-brushtextureslot-random-angle"),
|
||||
("bpy.types.clothsettings.bending_damping*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-bending-damping"),
|
||||
("bpy.types.clothsettings.pressure_factor*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-pressure-factor"),
|
||||
("bpy.types.clothsettings.shear_stiffness*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-shear-stiffness"),
|
||||
("bpy.types.clothsettings.tension_damping*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-tension-damping"),
|
||||
("bpy.types.colormanagedviewsettings.look*", "render/color_management.html#bpy-types-colormanagedviewsettings-look"),
|
||||
("bpy.types.compositornodecolorcorrection*", "compositing/types/color/color_correction.html#bpy-types-compositornodecolorcorrection"),
|
||||
("bpy.types.compositornodemoviedistortion*", "compositing/types/distort/movie_distortion.html#bpy-types-compositornodemoviedistortion"),
|
||||
("bpy.types.cyclescamerasettings.latitude*", "render/cycles/object_settings/cameras.html#bpy-types-cyclescamerasettings-latitude"),
|
||||
("bpy.types.cyclesrendersettings.caustics*", "render/cycles/render_settings/light_paths.html#bpy-types-cyclesrendersettings-caustics"),
|
||||
("bpy.types.cyclesrendersettings.denoiser*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-denoiser"),
|
||||
("bpy.types.editbone.use_inherit_rotation*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-use-inherit-rotation"),
|
||||
("bpy.types.ffmpegsettings.audio_channels*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio-channels"),
|
||||
("bpy.types.fieldsettings.guide_kink_axis*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-axis"),
|
||||
("bpy.types.fieldsettings.guide_kink_type*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-kink-type"),
|
||||
("bpy.types.fileselectparams.display_size*", "editors/file_browser.html#bpy-types-fileselectparams-display-size"),
|
||||
("bpy.types.fileselectparams.display_type*", "editors/file_browser.html#bpy-types-fileselectparams-display-type"),
|
||||
("bpy.types.fluiddomainsettings.use_guide*", "physics/fluid/type/domain/guides.html#bpy-types-fluiddomainsettings-use-guide"),
|
||||
("bpy.types.fluiddomainsettings.use_noise*", "physics/fluid/type/domain/gas/noise.html#bpy-types-fluiddomainsettings-use-noise"),
|
||||
("bpy.types.fluiddomainsettings.use_slice*", "physics/fluid/type/domain/gas/viewport_display.html#bpy-types-fluiddomainsettings-use-slice"),
|
||||
("bpy.types.fluiddomainsettings.vorticity*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-vorticity"),
|
||||
("bpy.types.fluidflowsettings.flow_source*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-flow-source"),
|
||||
("bpy.types.fluidflowsettings.fuel_amount*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-fuel-amount"),
|
||||
("bpy.types.fluidflowsettings.smoke_color*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-smoke-color"),
|
||||
("bpy.types.fluidflowsettings.temperature*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-temperature"),
|
||||
("bpy.types.fluidflowsettings.use_texture*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-texture"),
|
||||
("bpy.types.fmodifierenvelopecontrolpoint*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelopecontrolpoint"),
|
||||
("bpy.types.freestylelinestyle.length_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-length-max"),
|
||||
("bpy.types.freestylelinestyle.length_min*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-length-min"),
|
||||
("bpy.types.freestylelinestyle.sort_order*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-sort-order"),
|
||||
("bpy.types.freestylelinestyle.split_dash*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-split-dash"),
|
||||
("bpy.types.freestylesettings.use_culling*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-use-culling"),
|
||||
("bpy.types.geometrynodeduplicateelements*", "modeling/geometry_nodes/geometry/operations/duplicate_elements.html#bpy-types-geometrynodeduplicateelements"),
|
||||
("bpy.types.geometrynodeinputmeshfacearea*", "modeling/geometry_nodes/mesh/read/face_area.html#bpy-types-geometrynodeinputmeshfacearea"),
|
||||
("bpy.types.geometrynodeinputsplinecyclic*", "modeling/geometry_nodes/curve/read/is_spline_cyclic.html#bpy-types-geometrynodeinputsplinecyclic"),
|
||||
("bpy.types.geometrynodeinstancestopoints*", "modeling/geometry_nodes/instances/instances_to_points.html#bpy-types-geometrynodeinstancestopoints"),
|
||||
("bpy.types.geometrynodematerialselection*", "modeling/geometry_nodes/material/material_selection.html#bpy-types-geometrynodematerialselection"),
|
||||
("bpy.types.gpencillayer.viewlayer_render*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-viewlayer-render"),
|
||||
("bpy.types.imagepaint.use_normal_falloff*", "sculpt_paint/brush/falloff.html#bpy-types-imagepaint-use-normal-falloff"),
|
||||
("bpy.types.layercollection.hide_viewport*", "editors/outliner/interface.html#bpy-types-layercollection-hide-viewport"),
|
||||
("bpy.types.layercollection.indirect_only*", "editors/outliner/interface.html#bpy-types-layercollection-indirect-only"),
|
||||
("bpy.types.material.use_backface_culling*", "render/eevee/materials/settings.html#bpy-types-material-use-backface-culling"),
|
||||
("bpy.types.material.use_sss_translucency*", "render/eevee/materials/settings.html#bpy-types-material-use-sss-translucency"),
|
||||
("bpy.types.materiallineart.mat_occlusion*", "render/materials/line_art.html#bpy-types-materiallineart-mat-occlusion"),
|
||||
("bpy.types.movietrackingcamera.principal*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-principal"),
|
||||
("bpy.types.object.active_shape_key_index*", "animation/shape_keys/shape_keys_panel.html#bpy-types-object-active-shape-key-index"),
|
||||
("bpy.types.object.use_camera_lock_parent*", "scene_layout/object/properties/relations.html#bpy-types-object-use-camera-lock-parent"),
|
||||
("bpy.types.object.visible_volume_scatter*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-volume-scatter"),
|
||||
("bpy.types.rendersettings.line_thickness*", "render/freestyle/render.html#bpy-types-rendersettings-line-thickness"),
|
||||
("bpy.types.rendersettings.pixel_aspect_x*", "render/output/properties/format.html#bpy-types-rendersettings-pixel-aspect-x"),
|
||||
("bpy.types.rendersettings.pixel_aspect_y*", "render/output/properties/format.html#bpy-types-rendersettings-pixel-aspect-y"),
|
||||
("bpy.types.rigidbodyconstraint.use_limit*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-use-limit"),
|
||||
("bpy.types.sceneeevee.taa_render_samples*", "render/eevee/render_settings/sampling.html#bpy-types-sceneeevee-taa-render-samples"),
|
||||
("bpy.types.sculpt.use_automasking_cavity*", "sculpt_paint/sculpting/controls.html#bpy-types-sculpt-use-automasking-cavity"),
|
||||
("bpy.types.sequence.frame_final_duration*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequence-frame-final-duration"),
|
||||
("bpy.types.softbodysettings.goal_default*", "physics/soft_body/settings/goal.html#bpy-types-softbodysettings-goal-default"),
|
||||
("bpy.types.softbodysettings.use_diagnose*", "physics/soft_body/settings/solver.html#bpy-types-softbodysettings-use-diagnose"),
|
||||
("bpy.types.spaceoutliner.use_sync_select*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-sync-select"),
|
||||
("bpy.types.spaceproperties.outliner_sync*", "editors/properties_editor.html#bpy-types-spaceproperties-outliner-sync"),
|
||||
("bpy.types.spaceproperties.search_filter*", "editors/properties_editor.html#bpy-types-spaceproperties-search-filter"),
|
||||
("bpy.types.spacesequenceeditor.view_type*", "editors/video_sequencer/introduction.html#bpy-types-spacesequenceeditor-view-type"),
|
||||
("bpy.types.spacetexteditor.margin_column*", "editors/text_editor.html#bpy-types-spacetexteditor-margin-column"),
|
||||
("bpy.types.spacetexteditor.use_find_wrap*", "editors/text_editor.html#bpy-types-spacetexteditor-use-find-wrap"),
|
||||
("bpy.types.spaceuveditor.tile_grid_shape*", "editors/uv/overlays.html#bpy-types-spaceuveditor-tile-grid-shape"),
|
||||
("bpy.types.spaceuveditor.use_live_unwrap*", "modeling/meshes/uv/editing.html#bpy-types-spaceuveditor-use-live-unwrap"),
|
||||
("bpy.types.spaceview3d.use_render_border*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-use-render-border"),
|
||||
("bpy.types.toolsettings.double_threshold*", "modeling/meshes/tools/tool_settings.html#bpy-types-toolsettings-double-threshold"),
|
||||
("bpy.types.toolsettings.lock_object_mode*", "interface/window_system/topbar.html#bpy-types-toolsettings-lock-object-mode"),
|
||||
("bpy.types.toolsettings.mesh_select_mode*", "modeling/meshes/selecting/introduction.html#bpy-types-toolsettings-mesh-select-mode"),
|
||||
("bpy.types.toolsettings.use_snap_nonedit*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-nonedit"),
|
||||
("bpy.types.toolsettings.use_snap_project*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-project"),
|
||||
("bpy.types.transformorientationslot.type*", "editors/3dview/controls/orientation.html#bpy-types-transformorientationslot-type"),
|
||||
("bpy.types.unitsettings.temperature_unit*", "scene_layout/scene/properties.html#bpy-types-unitsettings-temperature-unit"),
|
||||
("bpy.types.vertexweightproximitymodifier*", "modeling/modifiers/modify/weight_proximity.html#bpy-types-vertexweightproximitymodifier"),
|
||||
("bpy.types.view3doverlay.show_wireframes*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-show-wireframes"),
|
||||
("bpy.types.view3dshading.background_type*", "editors/3dview/display/shading.html#bpy-types-view3dshading-background-type"),
|
||||
("bpy.types.windowmanager.poselib_flipped*", "animation/armatures/posing/editing/pose_library.html#bpy-types-windowmanager-poselib-flipped"),
|
||||
("bpy.types.workspace.use_filter_by_owner*", "interface/window_system/workspaces.html#bpy-types-workspace-use-filter-by-owner"),
|
||||
("bpy.ops.brush.stencil_fit_image_aspect*", "sculpt_paint/brush/texture.html#bpy-ops-brush-stencil-fit-image-aspect"),
|
||||
("bpy.ops.gpencil.image_to_grease_pencil*", "editors/image/editing.html#bpy-ops-gpencil-image-to-grease-pencil"),
|
||||
("bpy.ops.mesh.vertices_smooth_laplacian*", "modeling/meshes/editing/vertex/laplacian_smooth.html#bpy-ops-mesh-vertices-smooth-laplacian"),
|
||||
("bpy.ops.object.multires_rebuild_subdiv*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-rebuild-subdiv"),
|
||||
("bpy.ops.outliner.liboverride_operation*", "files/linked_libraries/library_overrides.html#bpy-ops-outliner-liboverride-operation"),
|
||||
("bpy.ops.screen.space_type_set_or_cycle*", "editors/index.html#bpy-ops-screen-space-type-set-or-cycle"),
|
||||
("bpy.ops.sculpt.dynamic_topology_toggle*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-ops-sculpt-dynamic-topology-toggle"),
|
||||
("bpy.ops.sequencer.select_side_of_frame*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-side-of-frame"),
|
||||
("bpy.types.animvizmotionpaths.frame_end*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-frame-end"),
|
||||
("bpy.types.armature.rigify_colors_index*", "addons/rigging/rigify/metarigs.html#bpy-types-armature-rigify-colors-index"),
|
||||
("bpy.types.armature.rigify_theme_to_add*", "addons/rigging/rigify/metarigs.html#bpy-types-armature-rigify-theme-to-add"),
|
||||
("bpy.types.bakesettings.use_pass_direct*", "render/cycles/baking.html#bpy-types-bakesettings-use-pass-direct"),
|
||||
("bpy.types.bakesettings.use_pass_glossy*", "render/cycles/baking.html#bpy-types-bakesettings-use-pass-glossy"),
|
||||
("bpy.types.brush.pose_smooth_iterations*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-pose-smooth-iterations"),
|
||||
("bpy.types.brush.snake_hook_deform_type*", "sculpt_paint/sculpting/tools/snake_hook.html#bpy-types-brush-snake-hook-deform-type"),
|
||||
("bpy.types.brush.use_grab_active_vertex*", "sculpt_paint/sculpting/tools/grab.html#bpy-types-brush-use-grab-active-vertex"),
|
||||
("bpy.types.brush.use_pose_lock_rotation*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-use-pose-lock-rotation"),
|
||||
("bpy.types.clothsettings.rest_shape_key*", "physics/cloth/settings/shape.html#bpy-types-clothsettings-rest-shape-key"),
|
||||
("bpy.types.compositornodebrightcontrast*", "compositing/types/color/bright_contrast.html#bpy-types-compositornodebrightcontrast"),
|
||||
("bpy.types.compositornodedoubleedgemask*", "compositing/types/matte/double_edge_mask.html#bpy-types-compositornodedoubleedgemask"),
|
||||
("bpy.types.cyclesrendersettings.samples*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-samples"),
|
||||
("bpy.types.dopesheet.show_only_selected*", "editors/dope_sheet/introduction.html#bpy-types-dopesheet-show-only-selected"),
|
||||
("bpy.types.ffmpegsettings.audio_bitrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio-bitrate"),
|
||||
("bpy.types.ffmpegsettings.audio_mixrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio-mixrate"),
|
||||
("bpy.types.ffmpegsettings.ffmpeg_preset*", "render/output/properties/output.html#bpy-types-ffmpegsettings-ffmpeg-preset"),
|
||||
("bpy.types.ffmpegsettings.use_autosplit*", "render/output/properties/output.html#bpy-types-ffmpegsettings-use-autosplit"),
|
||||
("bpy.types.ffmpegsettings.video_bitrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-video-bitrate"),
|
||||
("bpy.types.fieldsettings.use_absorption*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-use-absorption"),
|
||||
("bpy.types.fileselectparams.show_hidden*", "editors/file_browser.html#bpy-types-fileselectparams-show-hidden"),
|
||||
("bpy.types.fileselectparams.sort_method*", "editors/file_browser.html#bpy-types-fileselectparams-sort-method"),
|
||||
("bpy.types.fluiddomainsettings.clipping*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-clipping"),
|
||||
("bpy.types.fluiddomainsettings.use_mesh*", "physics/fluid/type/domain/liquid/mesh.html#bpy-types-fluiddomainsettings-use-mesh"),
|
||||
("bpy.types.freestylelinestyle.angle_max*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-angle-max"),
|
||||
("bpy.types.freestylelinestyle.angle_min*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-angle-min"),
|
||||
("bpy.types.freestylelinestyle.split_gap*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-split-gap"),
|
||||
("bpy.types.freestylelinestyle.use_nodes*", "render/freestyle/view_layer/line_style/texture.html#bpy-types-freestylelinestyle-use-nodes"),
|
||||
("bpy.types.geometrynodecaptureattribute*", "modeling/geometry_nodes/attribute/capture_attribute.html#bpy-types-geometrynodecaptureattribute"),
|
||||
("bpy.types.geometrynodeinputshadesmooth*", "modeling/geometry_nodes/mesh/read/is_shade_smooth.html#bpy-types-geometrynodeinputshadesmooth"),
|
||||
("bpy.types.geometrynodeinstanceonpoints*", "modeling/geometry_nodes/instances/instance_on_points.html#bpy-types-geometrynodeinstanceonpoints"),
|
||||
("bpy.types.geometrynodepointstovertices*", "modeling/geometry_nodes/point/points_to_vertices.html#bpy-types-geometrynodepointstovertices"),
|
||||
("bpy.types.geometrynoderealizeinstances*", "modeling/geometry_nodes/instances/realize_instances.html#bpy-types-geometrynoderealizeinstances"),
|
||||
("bpy.types.geometrynodeseparategeometry*", "modeling/geometry_nodes/geometry/operations/separate_geometry.html#bpy-types-geometrynodeseparategeometry"),
|
||||
("bpy.types.geometrynodesetmaterialindex*", "modeling/geometry_nodes/material/set_material_index.html#bpy-types-geometrynodesetmaterialindex"),
|
||||
("bpy.types.greasepencil.edit_line_color*", "grease_pencil/properties/display.html#bpy-types-greasepencil-edit-line-color"),
|
||||
("bpy.types.material.preview_render_type*", "render/materials/preview.html#bpy-types-material-preview-render-type"),
|
||||
("bpy.types.materialgpencilstyle.pattern*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-pattern"),
|
||||
("bpy.types.materialgpencilstyle.texture*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-texture"),
|
||||
("bpy.types.modifier.use_apply_on_spline*", "modeling/modifiers/introduction.html#bpy-types-modifier-use-apply-on-spline"),
|
||||
("bpy.types.movietrackingplanetrack.name*", "movie_clip/tracking/clip/sidebar/track/plane_track.html#bpy-types-movietrackingplanetrack-name"),
|
||||
("bpy.types.object.use_empty_image_alpha*", "modeling/empties.html#bpy-types-object-use-empty-image-alpha"),
|
||||
("bpy.types.rendersettings.frame_map_new*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-new"),
|
||||
("bpy.types.rendersettings.frame_map_old*", "render/output/properties/frame_range.html#bpy-types-rendersettings-frame-map-old"),
|
||||
("bpy.types.rendersettings.use_auto_tile*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-use-auto-tile"),
|
||||
("bpy.types.rendersettings.use_overwrite*", "render/output/properties/output.html#bpy-types-rendersettings-use-overwrite"),
|
||||
("bpy.types.rendersettings.use_sequencer*", "render/output/properties/post_processing.html#bpy-types-rendersettings-use-sequencer"),
|
||||
("bpy.types.sceneeevee.volumetric_shadow*", "render/eevee/render_settings/volumetrics.html#bpy-types-sceneeevee-volumetric-shadow"),
|
||||
("bpy.types.sequenceeditor.overlay_frame*", "editors/video_sequencer/preview/sidebar.html#bpy-types-sequenceeditor-overlay-frame"),
|
||||
("bpy.types.sequencetimelinechannel.lock*", "editors/video_sequencer/sequencer/channels.html#bpy-types-sequencetimelinechannel-lock"),
|
||||
("bpy.types.sequencetimelinechannel.mute*", "editors/video_sequencer/sequencer/channels.html#bpy-types-sequencetimelinechannel-mute"),
|
||||
("bpy.types.sequencetimelinechannel.name*", "editors/video_sequencer/sequencer/channels.html#bpy-types-sequencetimelinechannel-name"),
|
||||
("bpy.types.shadernodebsdfhairprincipled*", "render/shader_nodes/shader/hair_principled.html#bpy-types-shadernodebsdfhairprincipled"),
|
||||
("bpy.types.shadernodevectordisplacement*", "render/shader_nodes/vector/vector_displacement.html#bpy-types-shadernodevectordisplacement"),
|
||||
("bpy.types.softbodysettings.goal_spring*", "physics/soft_body/settings/goal.html#bpy-types-softbodysettings-goal-spring"),
|
||||
("bpy.types.spaceclipeditor.blend_factor*", "editors/clip/display/mask_display.html#bpy-types-spaceclipeditor-blend-factor"),
|
||||
("bpy.types.spacegrapheditor.show_cursor*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-show-cursor"),
|
||||
("bpy.types.spaceimageeditor.show_repeat*", "editors/image/sidebar.html#bpy-types-spaceimageeditor-show-repeat"),
|
||||
("bpy.types.spacenodeoverlay.show_timing*", "modeling/geometry_nodes/inspection.html#bpy-types-spacenodeoverlay-show-timing"),
|
||||
("bpy.types.spaceoutliner.use_sort_alpha*", "editors/outliner/interface.html#bpy-types-spaceoutliner-use-sort-alpha"),
|
||||
("bpy.types.spacepreferences.filter_text*", "editors/preferences/keymap.html#bpy-types-spacepreferences-filter-text"),
|
||||
("bpy.types.spacepreferences.filter_type*", "editors/preferences/keymap.html#bpy-types-spacepreferences-filter-type"),
|
||||
("bpy.types.spacetexteditor.replace_text*", "editors/text_editor.html#bpy-types-spacetexteditor-replace-text"),
|
||||
("bpy.types.spacetexteditor.use_find_all*", "editors/text_editor.html#bpy-types-spacetexteditor-use-find-all"),
|
||||
("bpy.types.spaceview3d.use_local_camera*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-use-local-camera"),
|
||||
("bpy.types.toolsettings.snap_uv_element*", "editors/uv/controls/snapping.html#bpy-types-toolsettings-snap-uv-element"),
|
||||
("bpy.types.toolsettings.use_snap_rotate*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-rotate"),
|
||||
("bpy.types.toolsettings.uv_relax_method*", "modeling/meshes/uv/tools/relax.html#bpy-types-toolsettings-uv-relax-method"),
|
||||
("bpy.types.unitsettings.system_rotation*", "scene_layout/scene/properties.html#bpy-types-unitsettings-system-rotation"),
|
||||
("bpy.types.view3doverlay.display_handle*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay-display-handle"),
|
||||
("bpy.types.volumedisplay.wireframe_type*", "modeling/volumes/properties.html#bpy-types-volumedisplay-wireframe-type"),
|
||||
("bpy.ops.anim.channels_editable_toggle*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-editable-toggle"),
|
||||
("bpy.ops.anim.channels_setting_disable*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-setting-disable"),
|
||||
("bpy.ops.brush.stencil_reset_transform*", "sculpt_paint/brush/texture.html#bpy-ops-brush-stencil-reset-transform"),
|
||||
("bpy.ops.curve.normals_make_consistent*", "modeling/curves/editing/control_points.html#bpy-ops-curve-normals-make-consistent"),
|
||||
("bpy.ops.curves.snap_curves_to_surface*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-curves-snap-curves-to-surface"),
|
||||
("bpy.ops.ed.lib_id_load_custom_preview*", "editors/asset_browser.html#bpy-ops-ed-lib-id-load-custom-preview"),
|
||||
("bpy.ops.gpencil.frame_clean_duplicate*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-frame-clean-duplicate"),
|
||||
("bpy.ops.gpencil.stroke_simplify_fixed*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-simplify-fixed"),
|
||||
("bpy.ops.mask.add_feather_vertex_slide*", "movie_clip/masking/editing.html#bpy-ops-mask-add-feather-vertex-slide"),
|
||||
("bpy.ops.mesh.faces_select_linked_flat*", "modeling/meshes/selecting/linked.html#bpy-ops-mesh-faces-select-linked-flat"),
|
||||
("bpy.ops.mesh.primitive_ico_sphere_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-ico-sphere-add"),
|
||||
("bpy.ops.object.clear_override_library*", "files/linked_libraries/library_overrides.html#bpy-ops-object-clear-override-library"),
|
||||
("bpy.ops.object.gpencil_modifier_apply*", "grease_pencil/modifiers/introduction.html#bpy-ops-object-gpencil-modifier-apply"),
|
||||
("bpy.ops.object.material_slot_deselect*", "render/materials/assignment.html#bpy-ops-object-material-slot-deselect"),
|
||||
("bpy.ops.object.modifier_move_to_index*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-move-to-index"),
|
||||
("bpy.ops.object.multires_external_save*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-external-save"),
|
||||
("bpy.ops.object.reset_override_library*", "files/linked_libraries/library_overrides.html#bpy-ops-object-reset-override-library"),
|
||||
("bpy.ops.object.vertex_group_normalize*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-normalize"),
|
||||
("bpy.ops.object.visual_transform_apply*", "scene_layout/object/editing/apply.html#bpy-ops-object-visual-transform-apply"),
|
||||
("bpy.ops.outliner.collection_duplicate*", "editors/outliner/editing.html#bpy-ops-outliner-collection-duplicate"),
|
||||
("bpy.ops.pose.select_constraint_target*", "animation/armatures/posing/selecting.html#bpy-ops-pose-select-constraint-target"),
|
||||
("bpy.ops.sequencer.change_effect_input*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-change-effect-input"),
|
||||
("bpy.ops.sequencer.strip_color_tag_set*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-ops-sequencer-strip-color-tag-set"),
|
||||
("bpy.ops.sequencer.strip_transform_fit*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-strip-transform-fit"),
|
||||
("bpy.ops.wm.doc_view_manual_ui_context*", "getting_started/help.html#bpy-ops-wm-doc-view-manual-ui-context"),
|
||||
("bpy.types.armature.rigify_colors_lock*", "addons/rigging/rigify/metarigs.html#bpy-types-armature-rigify-colors-lock"),
|
||||
("bpy.types.bakesettings.cage_extrusion*", "render/cycles/baking.html#bpy-types-bakesettings-cage-extrusion"),
|
||||
("bpy.types.bakesettings.use_pass_color*", "render/cycles/baking.html#bpy-types-bakesettings-use-pass-color"),
|
||||
("bpy.types.brush.boundary_falloff_type*", "sculpt_paint/sculpting/tools/boundary.html#bpy-types-brush-boundary-falloff-type"),
|
||||
("bpy.types.brush.cursor_color_subtract*", "sculpt_paint/brush/cursor.html#bpy-types-brush-cursor-color-subtract"),
|
||||
("bpy.types.brush.texture_overlay_alpha*", "sculpt_paint/brush/cursor.html#bpy-types-brush-texture-overlay-alpha"),
|
||||
("bpy.types.brush.use_frontface_falloff*", "sculpt_paint/brush/falloff.html#bpy-types-brush-use-frontface-falloff"),
|
||||
("bpy.types.brush.use_space_attenuation*", "sculpt_paint/brush/stroke.html#bpy-types-brush-use-space-attenuation"),
|
||||
("bpy.types.brushgpencilsettings.aspect*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-aspect"),
|
||||
("bpy.types.brushgpencilsettings.dilate*", "grease_pencil/modes/draw/tools/fill.html#bpy-types-brushgpencilsettings-dilate"),
|
||||
("bpy.types.brushgpencilsettings.random*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-random"),
|
||||
("bpy.types.brushtextureslot.use_random*", "sculpt_paint/brush/texture.html#bpy-types-brushtextureslot-use-random"),
|
||||
("bpy.types.clothsettings.bending_model*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-bending-model"),
|
||||
("bpy.types.clothsettings.fluid_density*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-fluid-density"),
|
||||
("bpy.types.clothsettings.pin_stiffness*", "physics/cloth/settings/shape.html#bpy-types-clothsettings-pin-stiffness"),
|
||||
("bpy.types.clothsettings.shear_damping*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-shear-damping"),
|
||||
("bpy.types.clothsettings.target_volume*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-target-volume"),
|
||||
("bpy.types.colormanageddisplaysettings*", "render/color_management.html#bpy-types-colormanageddisplaysettings"),
|
||||
("bpy.types.colorramp.hue_interpolation*", "interface/controls/templates/color_ramp.html#bpy-types-colorramp-hue-interpolation"),
|
||||
("bpy.types.compositornodebilateralblur*", "compositing/types/filter/bilateral_blur.html#bpy-types-compositornodebilateralblur"),
|
||||
("bpy.types.compositornodecryptomattev2*", "compositing/types/matte/cryptomatte.html#bpy-types-compositornodecryptomattev2"),
|
||||
("bpy.types.compositornodedistancematte*", "compositing/types/matte/distance_key.html#bpy-types-compositornodedistancematte"),
|
||||
("bpy.types.compositornodeseparatecolor*", "compositing/types/converter/separate_color.html#bpy-types-compositornodeseparatecolor"),
|
||||
("bpy.types.compositornodesetalpha.mode*", "compositing/types/converter/set_alpha.html#bpy-types-compositornodesetalpha-mode"),
|
||||
("bpy.types.dopesheet.use_filter_invert*", "editors/graph_editor/channels.html#bpy-types-dopesheet-use-filter-invert"),
|
||||
("bpy.types.editbone.use_local_location*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-use-local-location"),
|
||||
("bpy.types.ffmpegsettings.audio_volume*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio-volume"),
|
||||
("bpy.types.ffmpegsettings.max_b_frames*", "render/output/properties/output.html#bpy-types-ffmpegsettings-max-b-frames"),
|
||||
("bpy.types.fieldsettings.falloff_power*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-falloff-power"),
|
||||
("bpy.types.fieldsettings.guide_minimum*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-minimum"),
|
||||
("bpy.types.fileselectparams.use_filter*", "editors/file_browser.html#bpy-types-fileselectparams-use-filter"),
|
||||
("bpy.types.fluiddomainsettings.gravity*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-gravity"),
|
||||
("bpy.types.fluidflowsettings.flow_type*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-flow-type"),
|
||||
("bpy.types.fluidflowsettings.subframes*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-subframes"),
|
||||
("bpy.types.freestylelineset.collection*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-collection"),
|
||||
("bpy.types.freestylelineset.visibility*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-visibility"),
|
||||
("bpy.types.freestylelinestyle.chaining*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-chaining"),
|
||||
("bpy.types.freestylelinestyle.sort_key*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-sort-key"),
|
||||
("bpy.types.geometrynodeaccumulatefield*", "modeling/geometry_nodes/utilities/field/accumulate_field.html#bpy-types-geometrynodeaccumulatefield"),
|
||||
("bpy.types.geometrynodecornersofvertex*", "modeling/geometry_nodes/mesh/topology/corners_of_vertex.html#bpy-types-geometrynodecornersofvertex"),
|
||||
("bpy.types.geometrynodecurvesethandles*", "modeling/geometry_nodes/curve/write/set_handle_type.html#bpy-types-geometrynodecurvesethandles"),
|
||||
("bpy.types.geometrynodecurvesplinetype*", "modeling/geometry_nodes/curve/write/set_spline_type.html#bpy-types-geometrynodecurvesplinetype"),
|
||||
("bpy.types.geometrynodeinputmeshisland*", "modeling/geometry_nodes/mesh/read/mesh_island.html#bpy-types-geometrynodeinputmeshisland"),
|
||||
("bpy.types.geometrynodemergebydistance*", "modeling/geometry_nodes/geometry/operations/merge_by_distance.html#bpy-types-geometrynodemergebydistance"),
|
||||
("bpy.types.geometrynodereplacematerial*", "modeling/geometry_nodes/material/replace_material.html#bpy-types-geometrynodereplacematerial"),
|
||||
("bpy.types.geometrynoderotateinstances*", "modeling/geometry_nodes/instances/rotate_instances.html#bpy-types-geometrynoderotateinstances"),
|
||||
("bpy.types.geometrynodesampleuvsurface*", "modeling/geometry_nodes/mesh/operations/sample_uv_surface.html#bpy-types-geometrynodesampleuvsurface"),
|
||||
("bpy.types.geometrynodesetsplinecyclic*", "modeling/geometry_nodes/curve/write/set_spline_cyclic.html#bpy-types-geometrynodesetsplinecyclic"),
|
||||
("bpy.types.geometrynodesplineparameter*", "modeling/geometry_nodes/curve/read/spline_parameter.html#bpy-types-geometrynodesplineparameter"),
|
||||
("bpy.types.gpencillayer.use_mask_layer*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-mask-layer"),
|
||||
("bpy.types.greasepencil.use_curve_edit*", "grease_pencil/modes/edit/curve_editing.html#bpy-types-greasepencil-use-curve-edit"),
|
||||
("bpy.types.greasepencil.use_onion_fade*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-use-onion-fade"),
|
||||
("bpy.types.greasepencil.use_onion_loop*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-use-onion-loop"),
|
||||
("bpy.types.imagepaint.screen_grab_size*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-screen-grab-size"),
|
||||
("bpy.types.linestyle*modifier_material*", "render/freestyle/view_layer/line_style/modifiers/color/material.html#bpy-types-linestyle-modifier-material"),
|
||||
("bpy.types.motionpath.use_custom_color*", "animation/motion_paths.html#bpy-types-motionpath-use-custom-color"),
|
||||
("bpy.types.movietrackingcamera.brown_k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-brown-k"),
|
||||
("bpy.types.movietrackingcamera.brown_p*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-brown-p"),
|
||||
("bpy.types.object.visible_transmission*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-transmission"),
|
||||
("bpy.types.particlesettingstextureslot*", "physics/particles/texture_influence.html#bpy-types-particlesettingstextureslot"),
|
||||
("bpy.types.posebone.ik_rotation_weight*", "animation/armatures/posing/bone_constraints/inverse_kinematics/introduction.html#bpy-types-posebone-ik-rotation-weight"),
|
||||
("bpy.types.regionview3d.show_sync_view*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-show-sync-view"),
|
||||
("bpy.types.rendersettings.resolution_x*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-x"),
|
||||
("bpy.types.rendersettings.resolution_y*", "render/output/properties/format.html#bpy-types-rendersettings-resolution-y"),
|
||||
("bpy.types.rendersettings.threads_mode*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-threads-mode"),
|
||||
("bpy.types.rigidbodyconstraint.enabled*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-enabled"),
|
||||
("bpy.types.rigidbodyconstraint.object1*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-object1"),
|
||||
("bpy.types.rigidbodyconstraint.object2*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-object2"),
|
||||
("bpy.types.rigidbodyobject.mesh_source*", "physics/rigid_body/properties/collisions.html#bpy-types-rigidbodyobject-mesh-source"),
|
||||
("bpy.types.rigidbodyobject.restitution*", "physics/rigid_body/properties/collisions.html#bpy-types-rigidbodyobject-restitution"),
|
||||
("bpy.types.sceneeevee.volumetric_light*", "render/eevee/render_settings/volumetrics.html#bpy-types-sceneeevee-volumetric-light"),
|
||||
("bpy.types.sculpt.detail_refine_method*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-detail-refine-method"),
|
||||
("bpy.types.sculpt.symmetrize_direction*", "sculpt_paint/sculpting/tool_settings/symmetry.html#bpy-types-sculpt-symmetrize-direction"),
|
||||
("bpy.types.sequence.frame_offset_start*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequence-frame-offset-start"),
|
||||
("bpy.types.sequenceeditor.show_overlay*", "editors/video_sequencer/preview/sidebar.html#bpy-types-sequenceeditor-show-overlay"),
|
||||
("bpy.types.sequenceeditor.use_prefetch*", "editors/video_sequencer/preview/sidebar.html#bpy-types-sequenceeditor-use-prefetch"),
|
||||
("bpy.types.softbodysettings.ball_stiff*", "physics/soft_body/settings/self_collision.html#bpy-types-softbodysettings-ball-stiff"),
|
||||
("bpy.types.soundsequence.show_waveform*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-soundsequence-show-waveform"),
|
||||
("bpy.types.spaceclipeditor.show_stable*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-stable"),
|
||||
("bpy.types.spaceimageeditor.show_gizmo*", "editors/image/introduction.html#bpy-types-spaceimageeditor-show-gizmo"),
|
||||
("bpy.types.spaceoutliner.filter_invert*", "editors/outliner/interface.html#bpy-types-spaceoutliner-filter-invert"),
|
||||
("bpy.types.spacetexteditor.show_margin*", "editors/text_editor.html#bpy-types-spacetexteditor-show-margin"),
|
||||
("bpy.types.spline.radius_interpolation*", "modeling/curves/properties/active_spline.html#bpy-types-spline-radius-interpolation"),
|
||||
("bpy.types.toolsettings.use_multipaint*", "sculpt_paint/weight_paint/tool_settings/options.html#bpy-types-toolsettings-use-multipaint"),
|
||||
("bpy.types.toolsettings.use_snap_scale*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-scale"),
|
||||
("bpy.types.toolsettings.uv_select_mode*", "editors/uv/selecting.html#bpy-types-toolsettings-uv-select-mode"),
|
||||
("bpy.types.viewlayer.material_override*", "render/layers/introduction.html#bpy-types-viewlayer-material-override"),
|
||||
("bpy.ops.anim.channels_fcurves_enable*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-fcurves-enable"),
|
||||
("bpy.ops.anim.channels_setting_enable*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-setting-enable"),
|
||||
("bpy.ops.anim.channels_setting_toggle*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-setting-toggle"),
|
||||
("bpy.ops.clip.set_viewport_background*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-set-viewport-background"),
|
||||
("bpy.ops.geometry.color_attribute_add*", "modeling/meshes/properties/object_data.html#bpy-ops-geometry-color-attribute-add"),
|
||||
("bpy.ops.gpencil.interpolate_sequence*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-interpolate-sequence"),
|
||||
("bpy.ops.mask.normals_make_consistent*", "movie_clip/masking/editing.html#bpy-ops-mask-normals-make-consistent"),
|
||||
("bpy.ops.mesh.normals_make_consistent*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-normals-make-consistent"),
|
||||
("bpy.ops.mesh.offset_edge_loops_slide*", "modeling/meshes/editing/edge/offset_edge_slide.html#bpy-ops-mesh-offset-edge-loops-slide"),
|
||||
("bpy.ops.mesh.primitive_uv_sphere_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-uv-sphere-add"),
|
||||
("bpy.ops.object.curves_empty_hair_add*", "modeling/curves/primitives.html#bpy-ops-object-curves-empty-hair-add"),
|
||||
("bpy.ops.object.duplicate_move_linked*", "scene_layout/object/editing/duplicate_linked.html#bpy-ops-object-duplicate-move-linked"),
|
||||
("bpy.ops.object.make_override_library*", "files/linked_libraries/library_overrides.html#bpy-ops-object-make-override-library"),
|
||||
("bpy.ops.object.parent_no_inverse_set*", "scene_layout/object/editing/parent.html#bpy-ops-object-parent-no-inverse-set"),
|
||||
("bpy.ops.object.vertex_group_quantize*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-quantize"),
|
||||
("bpy.ops.outliner.collection_instance*", "editors/outliner/editing.html#bpy-ops-outliner-collection-instance"),
|
||||
("bpy.ops.sequencer.change_effect_type*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-change-effect-type"),
|
||||
("bpy.ops.transform.create_orientation*", "editors/3dview/controls/orientation.html#bpy-ops-transform-create-orientation"),
|
||||
("bpy.ops.transform.delete_orientation*", "editors/3dview/controls/orientation.html#bpy-ops-transform-delete-orientation"),
|
||||
("bpy.ops.ui.override_idtemplate_clear*", "files/linked_libraries/library_overrides.html#bpy-ops-ui-override-idtemplate-clear"),
|
||||
("bpy.ops.ui.override_idtemplate_reset*", "files/linked_libraries/library_overrides.html#bpy-ops-ui-override-idtemplate-reset"),
|
||||
("bpy.ops.view3d.localview_remove_from*", "editors/3dview/navigate/local_view.html#bpy-ops-view3d-localview-remove-from"),
|
||||
("bpy.types.animdata.action_blend_type*", "editors/nla/sidebar.html#bpy-types-animdata-action-blend-type"),
|
||||
("bpy.types.bakesettings.use_pass_emit*", "render/cycles/baking.html#bpy-types-bakesettings-use-pass-emit"),
|
||||
("bpy.types.bone.use_envelope_multiply*", "animation/armatures/bones/properties/deform.html#bpy-types-bone-use-envelope-multiply"),
|
||||
("bpy.types.brush.boundary_deform_type*", "sculpt_paint/sculpting/tools/boundary.html#bpy-types-brush-boundary-deform-type"),
|
||||
("bpy.types.brush.cursor_overlay_alpha*", "sculpt_paint/brush/cursor.html#bpy-types-brush-cursor-overlay-alpha"),
|
||||
("bpy.types.brush.normal_radius_factor*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-normal-radius-factor"),
|
||||
("bpy.types.brush.smooth_stroke_factor*", "sculpt_paint/brush/stroke.html#bpy-types-brush-smooth-stroke-factor"),
|
||||
("bpy.types.brush.smooth_stroke_radius*", "sculpt_paint/brush/stroke.html#bpy-types-brush-smooth-stroke-radius"),
|
||||
("bpy.types.brush.topology_rake_factor*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-topology-rake-factor"),
|
||||
("bpy.types.brush.use_pose_ik_anchored*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-use-pose-ik-anchored"),
|
||||
("bpy.types.brush.use_pressure_masking*", "sculpt_paint/brush/texture.html#bpy-types-brush-use-pressure-masking"),
|
||||
("bpy.types.brush.use_pressure_spacing*", "sculpt_paint/brush/stroke.html#bpy-types-brush-use-pressure-spacing"),
|
||||
("bpy.types.brushgpencilsettings.angle*", "grease_pencil/modes/draw/tools/draw.html#bpy-types-brushgpencilsettings-angle"),
|
||||
("bpy.types.clothsettings.use_pressure*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-use-pressure"),
|
||||
("bpy.types.compositornodeantialiasing*", "compositing/types/filter/anti_aliasing.html#bpy-types-compositornodeantialiasing"),
|
||||
("bpy.types.compositornodechannelmatte*", "compositing/types/matte/channel_key.html#bpy-types-compositornodechannelmatte"),
|
||||
("bpy.types.compositornodecolorbalance*", "compositing/types/color/color_balance.html#bpy-types-compositornodecolorbalance"),
|
||||
("bpy.types.compositornodecombinecolor*", "compositing/types/converter/combine_color.html#bpy-types-compositornodecombinecolor"),
|
||||
("bpy.types.compositornodekeyingscreen*", "compositing/types/matte/keying_screen.html#bpy-types-compositornodekeyingscreen"),
|
||||
("bpy.types.dynamicpaintcanvassettings*", "physics/dynamic_paint/canvas.html#bpy-types-dynamicpaintcanvassettings"),
|
||||
("bpy.types.ffmpegsettings.audio_codec*", "render/output/properties/output.html#bpy-types-ffmpegsettings-audio-codec"),
|
||||
("bpy.types.fieldsettings.distance_max*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-distance-max"),
|
||||
("bpy.types.fieldsettings.distance_min*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-distance-min"),
|
||||
("bpy.types.fieldsettings.falloff_type*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-falloff-type"),
|
||||
("bpy.types.fileselectparams.directory*", "editors/file_browser.html#bpy-types-fileselectparams-directory"),
|
||||
("bpy.types.fluidflowsettings.use_flow*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-use-flow"),
|
||||
("bpy.types.fmodifierfunctiongenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierfunctiongenerator"),
|
||||
("bpy.types.geometrynodecollectioninfo*", "modeling/geometry_nodes/input/scene/collection_info.html#bpy-types-geometrynodecollectioninfo"),
|
||||
("bpy.types.geometrynodedeletegeometry*", "modeling/geometry_nodes/geometry/operations/delete_geometry.html#bpy-types-geometrynodedeletegeometry"),
|
||||
("bpy.types.geometrynodeinputcurvetilt*", "modeling/geometry_nodes/curve/read/curve_tilt.html#bpy-types-geometrynodeinputcurvetilt"),
|
||||
("bpy.types.geometrynodeinputscenetime*", "modeling/geometry_nodes/input/scene/scene_time.html#bpy-types-geometrynodeinputscenetime"),
|
||||
("bpy.types.geometrynodenamedattribute*", "modeling/geometry_nodes/geometry/read/named_attribute.html#bpy-types-geometrynodenamedattribute"),
|
||||
("bpy.types.geometrynodepointstovolume*", "modeling/geometry_nodes/point/points_to_volume.html#bpy-types-geometrynodepointstovolume"),
|
||||
("bpy.types.geometrynodescaleinstances*", "modeling/geometry_nodes/instances/scale_instances.html#bpy-types-geometrynodescaleinstances"),
|
||||
("bpy.types.geometrynodesetcurvenormal*", "modeling/geometry_nodes/curve/write/set_curve_normal.html#bpy-types-geometrynodesetcurvenormal"),
|
||||
("bpy.types.geometrynodesetcurveradius*", "modeling/geometry_nodes/curve/write/set_curve_radius.html#bpy-types-geometrynodesetcurveradius"),
|
||||
("bpy.types.geometrynodesetpointradius*", "modeling/geometry_nodes/point/set_point_radius.html#bpy-types-geometrynodesetpointradius"),
|
||||
("bpy.types.geometrynodesetshadesmooth*", "modeling/geometry_nodes/mesh/write/set_shade_smooth.html#bpy-types-geometrynodesetshadesmooth"),
|
||||
("bpy.types.geometrynodestringtocurves*", "modeling/geometry_nodes/utilities/text/string_to_curves.html#bpy-types-geometrynodestringtocurves"),
|
||||
("bpy.types.geometrynodesubdividecurve*", "modeling/geometry_nodes/curve/operations/subdivide_curve.html#bpy-types-geometrynodesubdividecurve"),
|
||||
("bpy.types.geometrynodevertexofcorner*", "modeling/geometry_nodes/mesh/topology/vertex_of_corner.html#bpy-types-geometrynodevertexofcorner"),
|
||||
("bpy.types.gpencillayer.channel_color*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-channel-color"),
|
||||
("bpy.types.gpencillayer.use_solo_mode*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-solo-mode"),
|
||||
("bpy.types.greasepencil.use_multiedit*", "grease_pencil/multiframe.html#bpy-types-greasepencil-use-multiedit"),
|
||||
("bpy.types.keyframe.handle_right_type*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-right-type"),
|
||||
("bpy.types.materialgpencilstyle.color*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-color"),
|
||||
("bpy.types.movietrackingcamera.nuke_k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-nuke-k"),
|
||||
("bpy.types.movietrackingstabilization*", "movie_clip/tracking/clip/sidebar/stabilization/index.html#bpy-types-movietrackingstabilization"),
|
||||
("bpy.types.object.display_bounds_type*", "scene_layout/object/properties/display.html#bpy-types-object-display-bounds-type"),
|
||||
("bpy.types.object.show_only_shape_key*", "animation/shape_keys/shape_keys_panel.html#bpy-types-object-show-only-shape-key"),
|
||||
("bpy.types.regionview3d.lock_rotation*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-lock-rotation"),
|
||||
("bpy.types.rendersettings.hair_subdiv*", "render/eevee/render_settings/hair.html#bpy-types-rendersettings-hair-subdiv"),
|
||||
("bpy.types.rigidbodyobject.use_deform*", "physics/rigid_body/properties/collisions.html#bpy-types-rigidbodyobject-use-deform"),
|
||||
("bpy.types.scene.audio_distance_model*", "scene_layout/scene/properties.html#bpy-types-scene-audio-distance-model"),
|
||||
("bpy.types.scene.audio_doppler_factor*", "scene_layout/scene/properties.html#bpy-types-scene-audio-doppler-factor"),
|
||||
("bpy.types.sequencemodifier.mask_time*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-sequencemodifier-mask-time"),
|
||||
("bpy.types.sequencetransform.rotation*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequencetransform-rotation"),
|
||||
("bpy.types.shadernodeambientocclusion*", "render/shader_nodes/input/ao.html#bpy-types-shadernodeambientocclusion"),
|
||||
("bpy.types.shadernodevolumeabsorption*", "render/shader_nodes/shader/volume_absorption.html#bpy-types-shadernodevolumeabsorption"),
|
||||
("bpy.types.shadernodevolumeprincipled*", "render/shader_nodes/shader/volume_principled.html#bpy-types-shadernodevolumeprincipled"),
|
||||
("bpy.types.softbodysettings.ball_damp*", "physics/soft_body/settings/self_collision.html#bpy-types-softbodysettings-ball-damp"),
|
||||
("bpy.types.softbodysettings.ball_size*", "physics/soft_body/settings/self_collision.html#bpy-types-softbodysettings-ball-size"),
|
||||
("bpy.types.softbodysettings.use_edges*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-use-edges"),
|
||||
("bpy.types.spacefilebrowser.bookmarks*", "editors/file_browser.html#bpy-types-spacefilebrowser-bookmarks"),
|
||||
("bpy.types.spaceoutliner.display_mode*", "editors/outliner/interface.html#bpy-types-spaceoutliner-display-mode"),
|
||||
("bpy.types.spaceoutliner.filter_state*", "editors/outliner/interface.html#bpy-types-spaceoutliner-filter-state"),
|
||||
("bpy.types.spaceuveditor.show_stretch*", "editors/uv/overlays.html#bpy-types-spaceuveditor-show-stretch"),
|
||||
("bpy.types.toolsettings.keyframe_type*", "editors/timeline.html#bpy-types-toolsettings-keyframe-type"),
|
||||
("bpy.types.toolsettings.snap_elements*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-snap-elements"),
|
||||
("bpy.types.toolsettings.use_snap_edit*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-edit"),
|
||||
("bpy.types.toolsettings.use_snap_node*", "interface/controls/nodes/arranging.html#bpy-types-toolsettings-use-snap-node"),
|
||||
("bpy.types.toolsettings.use_snap_self*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap-self"),
|
||||
("bpy.types.viewlayer.active_aov_index*", "render/layers/passes.html#bpy-types-viewlayer-active-aov-index"),
|
||||
("bpy.ops.clip.tracking_object_remove*", "movie_clip/tracking/clip/sidebar/track/objects.html#bpy-ops-clip-tracking-object-remove"),
|
||||
("bpy.ops.constraint.copy_to_selected*", "animation/constraints/interface/header.html#bpy-ops-constraint-copy-to-selected"),
|
||||
("bpy.ops.gpencil.bake_mesh_animation*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-bake-mesh-animation"),
|
||||
("bpy.ops.gpencil.select_vertex_color*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-vertex-color"),
|
||||
("bpy.ops.gpencil.set_active_material*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-set-active-material"),
|
||||
("bpy.ops.gpencil.stroke_change_color*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-change-color"),
|
||||
("bpy.ops.gpencil.stroke_cyclical_set*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-cyclical-set"),
|
||||
("bpy.ops.gpencil.vertex_color_invert*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-invert"),
|
||||
("bpy.ops.gpencil.vertex_color_levels*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-levels"),
|
||||
("bpy.ops.mask.slide_spline_curvature*", "movie_clip/masking/editing.html#bpy-ops-mask-slide-spline-curvature"),
|
||||
("bpy.ops.mesh.primitive_cylinder_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-cylinder-add"),
|
||||
("bpy.ops.mesh.set_normals_from_faces*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-set-normals-from-faces"),
|
||||
("bpy.ops.mesh.shape_propagate_to_all*", "modeling/meshes/editing/vertex/propagate_shapes.html#bpy-ops-mesh-shape-propagate-to-all"),
|
||||
("bpy.ops.mesh.vert_connect_nonplanar*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-vert-connect-nonplanar"),
|
||||
("bpy.ops.object.duplicates_make_real*", "scene_layout/object/editing/apply.html#bpy-ops-object-duplicates-make-real"),
|
||||
("bpy.ops.object.material_slot_assign*", "render/materials/assignment.html#bpy-ops-object-material-slot-assign"),
|
||||
("bpy.ops.object.material_slot_select*", "render/materials/assignment.html#bpy-ops-object-material-slot-select"),
|
||||
("bpy.ops.object.multires_unsubdivide*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-unsubdivide"),
|
||||
("bpy.ops.object.parent_inverse_apply*", "scene_layout/object/editing/apply.html#bpy-ops-object-parent-inverse-apply"),
|
||||
("bpy.ops.object.paths_update_visible*", "animation/motion_paths.html#bpy-ops-object-paths-update-visible"),
|
||||
("bpy.ops.object.transforms_to_deltas*", "scene_layout/object/editing/apply.html#bpy-ops-object-transforms-to-deltas"),
|
||||
("bpy.ops.outliner.collection_disable*", "editors/outliner/editing.html#bpy-ops-outliner-collection-disable"),
|
||||
("bpy.ops.outliner.collection_isolate*", "editors/outliner/editing.html#bpy-ops-outliner-collection-isolate"),
|
||||
("bpy.ops.pose.visual_transform_apply*", "animation/armatures/posing/editing/apply.html#bpy-ops-pose-visual-transform-apply"),
|
||||
("bpy.ops.poselib.convert_old_poselib*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-convert-old-poselib"),
|
||||
("bpy.ops.render.shutter_curve_preset*", "render/cycles/render_settings/motion_blur.html#bpy-ops-render-shutter-curve-preset"),
|
||||
("bpy.ops.sculpt_curves.select_random*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-sculpt-curves-select-random"),
|
||||
("bpy.ops.sequencer.view_ghost_border*", "editors/video_sequencer/preview/sidebar.html#bpy-ops-sequencer-view-ghost-border"),
|
||||
("bpy.ops.ui.override_idtemplate_make*", "files/linked_libraries/library_overrides.html#bpy-ops-ui-override-idtemplate-make"),
|
||||
("bpy.ops.ui.override_type_set_button*", "files/linked_libraries/library_overrides.html#bpy-ops-ui-override-type-set-button"),
|
||||
("bpy.types.animdata.action_influence*", "editors/nla/sidebar.html#bpy-types-animdata-action-influence"),
|
||||
("bpy.types.armature.layers_protected*", "animation/armatures/properties/skeleton.html#bpy-types-armature-layers-protected"),
|
||||
("bpy.types.assetmetadata.description*", "editors/asset_browser.html#bpy-types-assetmetadata-description"),
|
||||
("bpy.types.bakesettings.normal_space*", "render/cycles/baking.html#bpy-types-bakesettings-normal-space"),
|
||||
("bpy.types.brush.crease_pinch_factor*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-crease-pinch-factor"),
|
||||
("bpy.types.brush.elastic_deform_type*", "sculpt_paint/sculpting/tools/elastic_deform.html#bpy-types-brush-elastic-deform-type"),
|
||||
("bpy.types.brush.texture_sample_bias*", "sculpt_paint/brush/texture.html#bpy-types-brush-texture-sample-bias"),
|
||||
("bpy.types.brush.use_cloth_collision*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-use-cloth-collision"),
|
||||
("bpy.types.brush.use_grab_silhouette*", "sculpt_paint/sculpting/tools/grab.html#bpy-types-brush-use-grab-silhouette"),
|
||||
("bpy.types.brush.use_original_normal*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-use-original-normal"),
|
||||
("bpy.types.brush.use_pressure_jitter*", "sculpt_paint/brush/stroke.html#bpy-types-brush-use-pressure-jitter"),
|
||||
("bpy.types.brush.use_primary_overlay*", "sculpt_paint/brush/cursor.html#bpy-types-brush-use-primary-overlay"),
|
||||
("bpy.types.brushcurvessculptsettings*", "sculpt_paint/curves_sculpting/index.html#bpy-types-brushcurvessculptsettings"),
|
||||
("bpy.types.brushtextureslot.map_mode*", "sculpt_paint/brush/texture.html#bpy-types-brushtextureslot-map-mode"),
|
||||
("bpy.types.brushtextureslot.use_rake*", "sculpt_paint/brush/texture.html#bpy-types-brushtextureslot-use-rake"),
|
||||
("bpy.types.camera.passepartout_alpha*", "render/cameras.html#bpy-types-camera-passepartout-alpha"),
|
||||
("bpy.types.clothsettings.air_damping*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-air-damping"),
|
||||
("bpy.types.colorrampelement.position*", "interface/controls/templates/color_ramp.html#bpy-types-colorrampelement-position"),
|
||||
("bpy.types.compositornodechromamatte*", "compositing/types/matte/chroma_key.html#bpy-types-compositornodechromamatte"),
|
||||
("bpy.types.compositornodecryptomatte*", "compositing/types/matte/cryptomatte_legacy.html#bpy-types-compositornodecryptomatte"),
|
||||
("bpy.types.compositornodedilateerode*", "compositing/types/filter/dilate_erode.html#bpy-types-compositornodedilateerode"),
|
||||
("bpy.types.compositornodeellipsemask*", "compositing/types/matte/ellipse_mask.html#bpy-types-compositornodeellipsemask"),
|
||||
("bpy.types.compositornodeseparatexyz*", "compositing/types/converter/separate_xyz.html#bpy-types-compositornodeseparatexyz"),
|
||||
("bpy.types.compositornodesplitviewer*", "compositing/types/output/split_viewer.html#bpy-types-compositornodesplitviewer"),
|
||||
("bpy.types.curve.render_resolution_u*", "modeling/curves/properties/shape.html#bpy-types-curve-render-resolution-u"),
|
||||
("bpy.types.cyclesrendersettings.seed*", "render/cycles/render_settings/sampling.html#bpy-types-cyclesrendersettings-seed"),
|
||||
("bpy.types.dynamicpaintbrushsettings*", "physics/dynamic_paint/brush.html#bpy-types-dynamicpaintbrushsettings"),
|
||||
("bpy.types.editbone.use_scale_easing*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-use-scale-easing"),
|
||||
("bpy.types.ffmpegsettings.buffersize*", "render/output/properties/output.html#bpy-types-ffmpegsettings-buffersize"),
|
||||
("bpy.types.ffmpegsettings.packetsize*", "render/output/properties/output.html#bpy-types-ffmpegsettings-packetsize"),
|
||||
("bpy.types.fieldsettings.wind_factor*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-wind-factor"),
|
||||
("bpy.types.fieldsettings.z_direction*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-z-direction"),
|
||||
("bpy.types.fileselectparams.filename*", "editors/file_browser.html#bpy-types-fileselectparams-filename"),
|
||||
("bpy.types.fluiddomainsettings.alpha*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-alpha"),
|
||||
("bpy.types.fluidflowsettings.density*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings-density"),
|
||||
("bpy.types.freestylelineset.qi_start*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-qi-start"),
|
||||
("bpy.types.freestylelinestyle.rounds*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-rounds"),
|
||||
("bpy.types.functionnodereplacestring*", "modeling/geometry_nodes/utilities/text/replace_string.html#bpy-types-functionnodereplacestring"),
|
||||
("bpy.types.functionnodeseparatecolor*", "modeling/geometry_nodes/utilities/color/separate_color.html#bpy-types-functionnodeseparatecolor"),
|
||||
("bpy.types.functionnodevaluetostring*", "modeling/geometry_nodes/utilities/text/value_to_string.html#bpy-types-functionnodevaluetostring"),
|
||||
("bpy.types.geometrynodeblurattribute*", "modeling/geometry_nodes/attribute/blur_attribute.html#bpy-types-geometrynodeblurattribute"),
|
||||
("bpy.types.geometrynodecurvetopoints*", "modeling/geometry_nodes/curve/operations/curve_to_points.html#bpy-types-geometrynodecurvetopoints"),
|
||||
("bpy.types.geometrynodeedgesofcorner*", "modeling/geometry_nodes/mesh/topology/edges_of_corner.html#bpy-types-geometrynodeedgesofcorner"),
|
||||
("bpy.types.geometrynodeedgesofvertex*", "modeling/geometry_nodes/mesh/topology/edges_of_vertex.html#bpy-types-geometrynodeedgesofvertex"),
|
||||
("bpy.types.geometrynodefieldondomain*", "modeling/geometry_nodes/utilities/field/evaluate_on_domain.html#bpy-types-geometrynodefieldondomain"),
|
||||
("bpy.types.geometrynodeinputmaterial*", "modeling/geometry_nodes/input/constant/material.html#bpy-types-geometrynodeinputmaterial"),
|
||||
("bpy.types.geometrynodeinputposition*", "modeling/geometry_nodes/geometry/read/position.html#bpy-types-geometrynodeinputposition"),
|
||||
("bpy.types.geometrynodemeshicosphere*", "modeling/geometry_nodes/mesh/primitives/icosphere.html#bpy-types-geometrynodemeshicosphere"),
|
||||
("bpy.types.geometrynodepointsofcurve*", "modeling/geometry_nodes/curve/topology/points_of_curve.html#bpy-types-geometrynodepointsofcurve"),
|
||||
("bpy.types.geometrynoderesamplecurve*", "modeling/geometry_nodes/curve/operations/resample_curve.html#bpy-types-geometrynoderesamplecurve"),
|
||||
("bpy.types.geometrynodesamplenearest*", "modeling/geometry_nodes/geometry/sample/sample_nearest.html#bpy-types-geometrynodesamplenearest"),
|
||||
("bpy.types.geometrynodescaleelements*", "modeling/geometry_nodes/mesh/operations/scale_elements.html#bpy-types-geometrynodescaleelements"),
|
||||
("bpy.types.geometrynodesubdividemesh*", "modeling/geometry_nodes/mesh/operations/subdivide_mesh.html#bpy-types-geometrynodesubdividemesh"),
|
||||
("bpy.types.geometrynodeuvpackislands*", "modeling/geometry_nodes/mesh/uv/pack_uv_islands.html#bpy-types-geometrynodeuvpackislands"),
|
||||
("bpy.types.greasepencil.before_color*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-before-color"),
|
||||
("bpy.types.greasepencil.onion_factor*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-onion-factor"),
|
||||
("bpy.types.greasepencil.pixel_factor*", "grease_pencil/properties/strokes.html#bpy-types-greasepencil-pixel-factor"),
|
||||
("bpy.types.keyframe.handle_left_type*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-left-type"),
|
||||
("bpy.types.light.use_custom_distance*", "render/eevee/lighting.html#bpy-types-light-use-custom-distance"),
|
||||
("bpy.types.material.refraction_depth*", "render/eevee/materials/settings.html#bpy-types-material-refraction-depth"),
|
||||
("bpy.types.materialgpencilstyle.flip*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-flip"),
|
||||
("bpy.types.materialgpencilstyle.mode*", "grease_pencil/materials/properties.html#bpy-types-materialgpencilstyle-mode"),
|
||||
("bpy.types.meshsequencecachemodifier*", "modeling/modifiers/modify/mesh_sequence_cache.html#bpy-types-meshsequencecachemodifier"),
|
||||
("bpy.types.modifier.show_in_editmode*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-in-editmode"),
|
||||
("bpy.types.motionpath.line_thickness*", "animation/motion_paths.html#bpy-types-motionpath-line-thickness"),
|
||||
("bpy.types.movietrackingtrack.weight*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-weight"),
|
||||
("bpy.types.object.empty_display_size*", "modeling/empties.html#bpy-types-object-empty-display-size"),
|
||||
("bpy.types.object.empty_display_type*", "modeling/empties.html#bpy-types-object-empty-display-type"),
|
||||
("bpy.types.regionview3d.use_box_clip*", "editors/3dview/navigate/views.html#bpy-types-regionview3d-use-box-clip"),
|
||||
("bpy.types.rendersettings.use_border*", "render/output/properties/format.html#bpy-types-rendersettings-use-border"),
|
||||
("bpy.types.rigidbodyconstraint.limit*", "physics/rigid_body/constraints/introduction.html#bpy-types-rigidbodyconstraint-limit"),
|
||||
("bpy.types.rigidbodyobject.kinematic*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-kinematic"),
|
||||
("bpy.types.scene.audio_doppler_speed*", "scene_layout/scene/properties.html#bpy-types-scene-audio-doppler-speed"),
|
||||
("bpy.types.sceneeevee.bokeh_max_size*", "render/eevee/render_settings/depth_of_field.html#bpy-types-sceneeevee-bokeh-max-size"),
|
||||
("bpy.types.sculpt.detail_type_method*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-detail-type-method"),
|
||||
("bpy.types.sculpt.use_smooth_shading*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-use-smooth-shading"),
|
||||
("bpy.types.sequence.frame_offset_end*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequence-frame-offset-end"),
|
||||
("bpy.types.sequenceeditor.show_cache*", "editors/video_sequencer/sequencer/navigating.html#bpy-types-sequenceeditor-show-cache"),
|
||||
("bpy.types.shadernodebsdfanisotropic*", "render/shader_nodes/shader/anisotropic.html#bpy-types-shadernodebsdfanisotropic"),
|
||||
("bpy.types.shadernodebsdftranslucent*", "render/shader_nodes/shader/translucent.html#bpy-types-shadernodebsdftranslucent"),
|
||||
("bpy.types.shadernodebsdftransparent*", "render/shader_nodes/shader/transparent.html#bpy-types-shadernodebsdftransparent"),
|
||||
("bpy.types.shadernodetexpointdensity*", "render/shader_nodes/textures/point_density.html#bpy-types-shadernodetexpointdensity"),
|
||||
("bpy.types.shadernodevectortransform*", "render/shader_nodes/vector/transform.html#bpy-types-shadernodevectortransform"),
|
||||
("bpy.types.shrinkwrapgpencilmodifier*", "grease_pencil/modifiers/deform/shrinkwrap.html#bpy-types-shrinkwrapgpencilmodifier"),
|
||||
("bpy.types.softbodysettings.friction*", "physics/soft_body/settings/object.html#bpy-types-softbodysettings-friction"),
|
||||
("bpy.types.softbodysettings.goal_max*", "physics/soft_body/settings/goal.html#bpy-types-softbodysettings-goal-max"),
|
||||
("bpy.types.softbodysettings.goal_min*", "physics/soft_body/settings/goal.html#bpy-types-softbodysettings-goal-min"),
|
||||
("bpy.types.softbodysettings.step_max*", "physics/soft_body/settings/solver.html#bpy-types-softbodysettings-step-max"),
|
||||
("bpy.types.softbodysettings.step_min*", "physics/soft_body/settings/solver.html#bpy-types-softbodysettings-step-min"),
|
||||
("bpy.types.softbodysettings.use_goal*", "physics/soft_body/settings/goal.html#bpy-types-softbodysettings-use-goal"),
|
||||
("bpy.types.spaceclipeditor.show_grid*", "editors/clip/display/clip_display.html#bpy-types-spaceclipeditor-show-grid"),
|
||||
("bpy.types.spaceoutliner.filter_text*", "editors/outliner/interface.html#bpy-types-spaceoutliner-filter-text"),
|
||||
("bpy.types.spacetexteditor.find_text*", "editors/text_editor.html#bpy-types-spacetexteditor-find-text"),
|
||||
("bpy.types.spacetexteditor.font_size*", "editors/text_editor.html#bpy-types-spacetexteditor-font-size"),
|
||||
("bpy.types.spacetexteditor.tab_width*", "editors/text_editor.html#bpy-types-spacetexteditor-tab-width"),
|
||||
("bpy.types.spaceuveditor.lock_bounds*", "modeling/meshes/uv/editing.html#bpy-types-spaceuveditor-lock-bounds"),
|
||||
("bpy.types.spline.tilt_interpolation*", "modeling/curves/properties/active_spline.html#bpy-types-spline-tilt-interpolation"),
|
||||
("bpy.types.transformorientation.name*", "editors/3dview/controls/orientation.html#bpy-types-transformorientation-name"),
|
||||
("bpy.types.unitsettings.scale_length*", "scene_layout/scene/properties.html#bpy-types-unitsettings-scale-length"),
|
||||
("bpy.types.unitsettings.use_separate*", "scene_layout/scene/properties.html#bpy-types-unitsettings-use-separate"),
|
||||
("bpy.types.viewlayer.use_motion_blur*", "render/layers/introduction.html#bpy-types-viewlayer-use-motion-blur"),
|
||||
("bpy.types.volumedisplay.slice_depth*", "modeling/volumes/properties.html#bpy-types-volumedisplay-slice-depth"),
|
||||
("bpy.types.worldmistsettings.falloff*", "render/cycles/world_settings.html#bpy-types-worldmistsettings-falloff"),
|
||||
("bpy.ops.clip.lock_selection_toggle*", "editors/clip/introduction.html#bpy-ops-clip-lock-selection-toggle"),
|
||||
("bpy.ops.ed.lib_id_generate_preview*", "editors/asset_browser.html#bpy-ops-ed-lib-id-generate-preview"),
|
||||
("bpy.ops.mesh.customdata_mask_clear*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-mask-clear"),
|
||||
("bpy.ops.mesh.customdata_skin_clear*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-skin-clear"),
|
||||
("bpy.ops.mesh.extrude_vertices_move*", "modeling/meshes/editing/vertex/extrude_vertices.html#bpy-ops-mesh-extrude-vertices-move"),
|
||||
("bpy.ops.mesh.mod_weighted_strength*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-mod-weighted-strength"),
|
||||
("bpy.ops.mesh.quads_convert_to_tris*", "modeling/meshes/editing/face/triangulate_faces.html#bpy-ops-mesh-quads-convert-to-tris"),
|
||||
("bpy.ops.mesh.select_interior_faces*", "modeling/meshes/selecting/all_by_trait.html#bpy-ops-mesh-select-interior-faces"),
|
||||
("bpy.ops.mesh.select_similar_region*", "modeling/meshes/selecting/similar.html#bpy-ops-mesh-select-similar-region"),
|
||||
("bpy.ops.mesh.tris_convert_to_quads*", "modeling/meshes/editing/face/triangles_quads.html#bpy-ops-mesh-tris-convert-to-quads"),
|
||||
("bpy.ops.node.duplicate_move_linked*", "interface/controls/nodes/editing.html#bpy-ops-node-duplicate-move-linked"),
|
||||
("bpy.ops.object.datalayout_transfer*", "scene_layout/object/editing/link_transfer/transfer_mesh_data_layout.html#bpy-ops-object-datalayout-transfer"),
|
||||
("bpy.ops.object.multires_base_apply*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-base-apply"),
|
||||
("bpy.ops.object.randomize_transform*", "scene_layout/object/editing/transform/randomize.html#bpy-ops-object-randomize-transform"),
|
||||
("bpy.ops.object.vertex_group_invert*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-invert"),
|
||||
("bpy.ops.object.vertex_group_levels*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-levels"),
|
||||
("bpy.ops.object.vertex_group_mirror*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-mirror"),
|
||||
("bpy.ops.object.vertex_group_remove*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-remove"),
|
||||
("bpy.ops.object.vertex_group_smooth*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-smooth"),
|
||||
("bpy.ops.outliner.collection_enable*", "editors/outliner/editing.html#bpy-ops-outliner-collection-enable"),
|
||||
("bpy.ops.palette.extract_from_image*", "editors/image/editing.html#bpy-ops-palette-extract-from-image"),
|
||||
("bpy.ops.pose.user_transforms_clear*", "animation/armatures/posing/editing/clear.html#bpy-ops-pose-user-transforms-clear"),
|
||||
("bpy.ops.screen.region_context_menu*", "interface/controls/buttons/menus.html#bpy-ops-screen-region-context-menu"),
|
||||
("bpy.ops.sculpt.set_persistent_base*", "sculpt_paint/sculpting/tools/layer.html#bpy-ops-sculpt-set-persistent-base"),
|
||||
("bpy.ops.sequencer.crossfade_sounds*", "video_editing/edit/montage/strips/transitions/sound_crossfade.html#bpy-ops-sequencer-crossfade-sounds"),
|
||||
("bpy.ops.sequencer.export_subtitles*", "editors/video_sequencer/preview/header.html#bpy-ops-sequencer-export-subtitles"),
|
||||
("bpy.ops.transform.edge_bevelweight*", "modeling/meshes/editing/edge/edge_data.html#bpy-ops-transform-edge-bevelweight"),
|
||||
("bpy.ops.view3d.clear_render_border*", "editors/3dview/navigate/regions.html#bpy-ops-view3d-clear-render-border"),
|
||||
("bpy.ops.wm.previews_batch_generate*", "files/blend/previews.html#bpy-ops-wm-previews-batch-generate"),
|
||||
("bpy.types.animvizmotionpaths.range*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-range"),
|
||||
("bpy.types.assetmetadata.active_tag*", "editors/asset_browser.html#bpy-types-assetmetadata-active-tag"),
|
||||
("bpy.types.bakesettings.cage_object*", "render/cycles/baking.html#bpy-types-bakesettings-cage-object"),
|
||||
("bpy.types.bakesettings.margin_type*", "render/cycles/baking.html#bpy-types-bakesettings-margin-type"),
|
||||
("bpy.types.bone.use_relative_parent*", "animation/armatures/bones/properties/relations.html#bpy-types-bone-use-relative-parent"),
|
||||
("bpy.types.brush.area_radius_factor*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-area-radius-factor"),
|
||||
("bpy.types.brush.auto_smooth_factor*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-auto-smooth-factor"),
|
||||
("bpy.types.brush.smooth_deform_type*", "sculpt_paint/sculpting/tools/smooth.html#bpy-types-brush-smooth-deform-type"),
|
||||
("bpy.types.brush.use_connected_only*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-use-connected-only"),
|
||||
("bpy.types.brush.use_cursor_overlay*", "sculpt_paint/brush/cursor.html#bpy-types-brush-use-cursor-overlay"),
|
||||
("bpy.types.brush.use_original_plane*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-use-original-plane"),
|
||||
("bpy.types.camera.show_passepartout*", "render/cameras.html#bpy-types-camera-show-passepartout"),
|
||||
("bpy.types.clothsettings.shrink_max*", "physics/cloth/settings/property_weights.html#bpy-types-clothsettings-shrink-max"),
|
||||
("bpy.types.clothsettings.shrink_min*", "physics/cloth/settings/shape.html#bpy-types-clothsettings-shrink-min"),
|
||||
("bpy.types.clothsettings.time_scale*", "physics/cloth/settings/index.html#bpy-types-clothsettings-time-scale"),
|
||||
("bpy.types.collection.lineart_usage*", "scene_layout/collections/collections.html#bpy-types-collection-lineart-usage"),
|
||||
("bpy.types.colormanagedviewsettings*", "render/color_management.html#bpy-types-colormanagedviewsettings"),
|
||||
("bpy.types.compositornodebokehimage*", "compositing/types/input/bokeh_image.html#bpy-types-compositornodebokehimage"),
|
||||
("bpy.types.compositornodecolormatte*", "compositing/types/matte/color_key.html#bpy-types-compositornodecolormatte"),
|
||||
("bpy.types.compositornodecolorspill*", "compositing/types/matte/color_spill.html#bpy-types-compositornodecolorspill"),
|
||||
("bpy.types.compositornodecombinexyz*", "compositing/types/converter/combine_xyz.html#bpy-types-compositornodecombinexyz"),
|
||||
("bpy.types.compositornodehuecorrect*", "compositing/types/color/hue_correct.html#bpy-types-compositornodehuecorrect"),
|
||||
("bpy.types.compositornodeoutputfile*", "compositing/types/output/file.html#bpy-types-compositornodeoutputfile"),
|
||||
("bpy.types.compositornodeswitchview*", "compositing/types/converter/switch_view.html#bpy-types-compositornodeswitchview"),
|
||||
("bpy.types.copytransformsconstraint*", "animation/constraints/transform/copy_transforms.html#bpy-types-copytransformsconstraint"),
|
||||
("bpy.types.correctivesmoothmodifier*", "modeling/modifiers/deform/corrective_smooth.html#bpy-types-correctivesmoothmodifier"),
|
||||
("bpy.types.curve.bevel_factor_start*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-start"),
|
||||
("bpy.types.fieldsettings.guide_free*", "physics/forces/force_fields/types/curve_guide.html#bpy-types-fieldsettings-guide-free"),
|
||||
("bpy.types.fluiddomainsettings.beta*", "physics/fluid/type/domain/settings.html#bpy-types-fluiddomainsettings-beta"),
|
||||
("bpy.types.fluidmodifier.fluid_type*", "physics/fluid/type/index.html#bpy-types-fluidmodifier-fluid-type"),
|
||||
("bpy.types.freestylelineset.exclude*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-exclude"),
|
||||
("bpy.types.freestylelinestyle.alpha*", "render/freestyle/view_layer/line_style/alpha.html#bpy-types-freestylelinestyle-alpha"),
|
||||
("bpy.types.freestylelinestyle.color*", "render/freestyle/view_layer/line_style/color.html#bpy-types-freestylelinestyle-color"),
|
||||
("bpy.types.functionnodecombinecolor*", "modeling/geometry_nodes/utilities/color/combine_color.html#bpy-types-functionnodecombinecolor"),
|
||||
("bpy.types.functionnodestringlength*", "modeling/geometry_nodes/utilities/text/string_length.html#bpy-types-functionnodestringlength"),
|
||||
("bpy.types.geometrynodecurveofpoint*", "modeling/geometry_nodes/curve/topology/curve_of_point.html#bpy-types-geometrynodecurveofpoint"),
|
||||
("bpy.types.geometrynodefaceofcorner*", "modeling/geometry_nodes/mesh/topology/face_of_corner.html#bpy-types-geometrynodefaceofcorner"),
|
||||
("bpy.types.geometrynodefieldatindex*", "modeling/geometry_nodes/utilities/field/evaluate_at_index.html#bpy-types-geometrynodefieldatindex"),
|
||||
("bpy.types.geometrynodeimagetexture*", "modeling/geometry_nodes/texture/image.html#bpy-types-geometrynodeimagetexture"),
|
||||
("bpy.types.geometrynodeinputtangent*", "modeling/geometry_nodes/curve/read/curve_tangent.html#bpy-types-geometrynodeinputtangent"),
|
||||
("bpy.types.geometrynodejoingeometry*", "modeling/geometry_nodes/geometry/join_geometry.html#bpy-types-geometrynodejoingeometry"),
|
||||
("bpy.types.geometrynodemeshcylinder*", "modeling/geometry_nodes/mesh/primitives/cylinder.html#bpy-types-geometrynodemeshcylinder"),
|
||||
("bpy.types.geometrynodemeshtopoints*", "modeling/geometry_nodes/mesh/operations/mesh_to_points.html#bpy-types-geometrynodemeshtopoints"),
|
||||
("bpy.types.geometrynodemeshtovolume*", "modeling/geometry_nodes/mesh/operations/mesh_to_volume.html#bpy-types-geometrynodemeshtovolume"),
|
||||
("bpy.types.geometrynodemeshuvsphere*", "modeling/geometry_nodes/mesh/primitives/uv_sphere.html#bpy-types-geometrynodemeshuvsphere"),
|
||||
("bpy.types.geometrynodereversecurve*", "modeling/geometry_nodes/curve/operations/reverse_curve.html#bpy-types-geometrynodereversecurve"),
|
||||
("bpy.types.geometrynodesetcurvetilt*", "modeling/geometry_nodes/curve/write/set_curve_tilt.html#bpy-types-geometrynodesetcurvetilt"),
|
||||
("bpy.types.geometrynodesplinelength*", "modeling/geometry_nodes/curve/read/spline_length.html#bpy-types-geometrynodesplinelength"),
|
||||
("bpy.types.geometrynodevolumetomesh*", "modeling/geometry_nodes/volume/volume_to_mesh.html#bpy-types-geometrynodevolumetomesh"),
|
||||
("bpy.types.gpencillayer.line_change*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-line-change"),
|
||||
("bpy.types.gpencillayer.parent_type*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-parent-type"),
|
||||
("bpy.types.gpencillayer.tint_factor*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-tint-factor"),
|
||||
("bpy.types.greasepencil.after_color*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-after-color"),
|
||||
("bpy.types.image.use_half_precision*", "editors/image/image_settings.html#bpy-types-image-use-half-precision"),
|
||||
("bpy.types.image.use_view_as_render*", "editors/image/image_settings.html#bpy-types-image-use-view-as-render"),
|
||||
("bpy.types.imagepaint.interpolation*", "sculpt_paint/texture_paint/tool_settings/texture_slots.html#bpy-types-imagepaint-interpolation"),
|
||||
("bpy.types.linestyle*modifier_noise*", "render/freestyle/view_layer/line_style/modifiers/color/noise.html#bpy-types-linestyle-modifier-noise"),
|
||||
("bpy.types.maintainvolumeconstraint*", "animation/constraints/transform/maintain_volume.html#bpy-types-maintainvolumeconstraint"),
|
||||
("bpy.types.material.alpha_threshold*", "render/eevee/materials/settings.html#bpy-types-material-alpha-threshold"),
|
||||
("bpy.types.mesh.use_mirror_topology*", "modeling/meshes/tools/tool_settings.html#bpy-types-mesh-use-mirror-topology"),
|
||||
("bpy.types.movieclip.display_aspect*", "editors/clip/display/clip_display.html#bpy-types-movieclip-display-aspect"),
|
||||
("bpy.types.movietrackingtrack.color*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-color"),
|
||||
("bpy.types.nodesocketinterface.name*", "interface/controls/nodes/groups.html#bpy-types-nodesocketinterface-name"),
|
||||
("bpy.types.object.is_shadow_catcher*", "render/cycles/object_settings/object_data.html#bpy-types-object-is-shadow-catcher"),
|
||||
("bpy.types.particleinstancemodifier*", "modeling/modifiers/physics/particle_instance.html#bpy-types-particleinstancemodifier"),
|
||||
("bpy.types.rendersettings.hair_type*", "render/eevee/render_settings/hair.html#bpy-types-rendersettings-hair-type"),
|
||||
("bpy.types.rendersettings.tile_size*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-tile-size"),
|
||||
("bpy.types.rigidbodyobject.friction*", "physics/rigid_body/properties/collisions.html#bpy-types-rigidbodyobject-friction"),
|
||||
("bpy.types.scenedisplay.viewport_aa*", "render/workbench/sampling.html#bpy-types-scenedisplay-viewport-aa"),
|
||||
("bpy.types.sequencertimelineoverlay*", "editors/video_sequencer/sequencer/display.html#bpy-types-sequencertimelineoverlay"),
|
||||
("bpy.types.sequencetransform.filter*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequencetransform-filter"),
|
||||
("bpy.types.sequencetransform.offset*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequencetransform-offset"),
|
||||
("bpy.types.shadernodebrightcontrast*", "render/shader_nodes/color/bright_contrast.html#bpy-types-shadernodebrightcontrast"),
|
||||
("bpy.types.shadernodebsdfprincipled*", "render/shader_nodes/shader/principled.html#bpy-types-shadernodebsdfprincipled"),
|
||||
("bpy.types.shadernodebsdfrefraction*", "render/shader_nodes/shader/refraction.html#bpy-types-shadernodebsdfrefraction"),
|
||||
("bpy.types.shadernodeoutputmaterial*", "render/shader_nodes/output/material.html#bpy-types-shadernodeoutputmaterial"),
|
||||
("bpy.types.shadernodetexenvironment*", "render/shader_nodes/textures/environment.html#bpy-types-shadernodetexenvironment"),
|
||||
("bpy.types.softbodysettings.damping*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-damping"),
|
||||
("bpy.types.softbodysettings.plastic*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-plastic"),
|
||||
("bpy.types.spacesequenceeditor.show*", "editors/video_sequencer/preview/header.html#bpy-types-spacesequenceeditor-show"),
|
||||
("bpy.types.spaceuveditor.show_faces*", "editors/uv/overlays.html#bpy-types-spaceuveditor-show-faces"),
|
||||
("bpy.types.spaceuveditor.uv_opacity*", "editors/uv/overlays.html#bpy-types-spaceuveditor-uv-opacity"),
|
||||
("bpy.types.subdividegpencilmodifier*", "grease_pencil/modifiers/generate/subdivide.html#bpy-types-subdividegpencilmodifier"),
|
||||
("bpy.types.texturenodeseparatecolor*", "editors/texture_node/types/color/separate_color.html#bpy-types-texturenodeseparatecolor"),
|
||||
("bpy.types.thicknessgpencilmodifier*", "grease_pencil/modifiers/deform/thickness.html#bpy-types-thicknessgpencilmodifier"),
|
||||
("bpy.types.toolsettings.snap_target*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-snap-target"),
|
||||
("bpy.types.transformcacheconstraint*", "animation/constraints/transform/transform_cache.html#bpy-types-transformcacheconstraint"),
|
||||
("bpy.types.unitsettings.length_unit*", "scene_layout/scene/properties.html#bpy-types-unitsettings-length-unit"),
|
||||
("bpy.types.vertexweighteditmodifier*", "modeling/modifiers/modify/weight_edit.html#bpy-types-vertexweighteditmodifier"),
|
||||
("bpy.types.view3dshading.color_type*", "render/workbench/color.html#bpy-types-view3dshading-color-type"),
|
||||
("bpy.types.volumedisplay.slice_axis*", "modeling/volumes/properties.html#bpy-types-volumedisplay-slice-axis"),
|
||||
("bpy.ops.action.markers_make_local*", "animation/markers.html#bpy-ops-action-markers-make-local"),
|
||||
("bpy.ops.anim.channels_clean_empty*", "editors/nla/editing.html#bpy-ops-anim-channels-clean-empty"),
|
||||
("bpy.ops.anim.driver_button_remove*", "animation/drivers/usage.html#bpy-ops-anim-driver-button-remove"),
|
||||
("bpy.ops.anim.keyingset_button_add*", "animation/keyframes/keying_sets.html#bpy-ops-anim-keyingset-button-add"),
|
||||
("bpy.ops.armature.select_hierarchy*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-hierarchy"),
|
||||
("bpy.ops.armature.switch_direction*", "animation/armatures/bones/editing/switch_direction.html#bpy-ops-armature-switch-direction"),
|
||||
("bpy.ops.clip.apply_solution_scale*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-apply-solution-scale"),
|
||||
("bpy.ops.clip.set_center_principal*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-set-center-principal"),
|
||||
("bpy.ops.clip.setup_tracking_scene*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-setup-tracking-scene"),
|
||||
("bpy.ops.curve.match_texture_space*", "modeling/meshes/uv/uv_texture_spaces.html#bpy-ops-curve-match-texture-space"),
|
||||
("bpy.ops.font.text_paste_from_file*", "modeling/texts/editing.html#bpy-ops-font-text-paste-from-file"),
|
||||
("bpy.ops.geometry.attribute_remove*", "modeling/geometry_nodes/attributes_reference.html#bpy-ops-geometry-attribute-remove"),
|
||||
("bpy.ops.gpencil.frame_clean_loose*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-frame-clean-loose"),
|
||||
("bpy.ops.gpencil.selectmode_toggle*", "grease_pencil/selecting.html#bpy-ops-gpencil-selectmode-toggle"),
|
||||
("bpy.ops.mask.feather_weight_clear*", "movie_clip/masking/editing.html#bpy-ops-mask-feather-weight-clear"),
|
||||
("bpy.ops.mask.primitive_circle_add*", "movie_clip/masking/scurve.html#bpy-ops-mask-primitive-circle-add"),
|
||||
("bpy.ops.mask.primitive_square_add*", "movie_clip/masking/scurve.html#bpy-ops-mask-primitive-square-add"),
|
||||
("bpy.ops.mesh.dupli_extrude_cursor*", "modeling/meshes/editing/vertex/extrude_cursor.html#bpy-ops-mesh-dupli-extrude-cursor"),
|
||||
("bpy.ops.mesh.primitive_circle_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-circle-add"),
|
||||
("bpy.ops.mesh.primitive_monkey_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-monkey-add"),
|
||||
("bpy.ops.mesh.select_face_by_sides*", "modeling/meshes/selecting/all_by_trait.html#bpy-ops-mesh-select-face-by-sides"),
|
||||
("bpy.ops.mesh.shortest_path_select*", "modeling/meshes/selecting/linked.html#bpy-ops-mesh-shortest-path-select"),
|
||||
("bpy.ops.mesh.vert_connect_concave*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-vert-connect-concave"),
|
||||
("bpy.ops.nla.duplicate_linked_move*", "editors/nla/editing.html#bpy-ops-nla-duplicate-linked-move"),
|
||||
("bpy.ops.object.multires_subdivide*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-subdivide"),
|
||||
("bpy.ops.object.shape_key_transfer*", "animation/shape_keys/shape_keys_panel.html#bpy-ops-object-shape-key-transfer"),
|
||||
("bpy.ops.object.vertex_group_clean*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-clean"),
|
||||
("bpy.ops.poselib.create_pose_asset*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-create-pose-asset"),
|
||||
("bpy.ops.preferences.theme_install*", "editors/preferences/themes.html#bpy-ops-preferences-theme-install"),
|
||||
("bpy.ops.render.play_rendered_anim*", "render/output/animation_player.html#bpy-ops-render-play-rendered-anim"),
|
||||
("bpy.ops.sculpt.set_pivot_position*", "sculpt_paint/sculpting/editing/sculpt.html#bpy-ops-sculpt-set-pivot-position"),
|
||||
("bpy.ops.sculpt_curves.select_grow*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-sculpt-curves-select-grow"),
|
||||
("bpy.ops.sequencer.image_strip_add*", "video_editing/edit/montage/strips/image.html#bpy-ops-sequencer-image-strip-add"),
|
||||
("bpy.ops.sequencer.images_separate*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-images-separate"),
|
||||
("bpy.ops.sequencer.movie_strip_add*", "video_editing/edit/montage/strips/movie.html#bpy-ops-sequencer-movie-strip-add"),
|
||||
("bpy.ops.sequencer.reassign_inputs*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-reassign-inputs"),
|
||||
("bpy.ops.sequencer.sound_strip_add*", "video_editing/edit/montage/strips/sound.html#bpy-ops-sequencer-sound-strip-add"),
|
||||
("bpy.ops.ui.remove_override_button*", "files/linked_libraries/library_overrides.html#bpy-ops-ui-remove-override-button"),
|
||||
("bpy.ops.view3d.view_center_camera*", "editors/3dview/navigate/camera_view.html#bpy-ops-view3d-view-center-camera"),
|
||||
("bpy.ops.view3d.zoom_camera_1_to_1*", "editors/3dview/navigate/camera_view.html#bpy-ops-view3d-zoom-camera-1-to-1"),
|
||||
("bpy.types.animvizmotionpaths.type*", "animation/motion_paths.html#bpy-types-animvizmotionpaths-type"),
|
||||
("bpy.types.armaturegpencilmodifier*", "grease_pencil/modifiers/deform/armature.html#bpy-types-armaturegpencilmodifier"),
|
||||
("bpy.types.brush.cloth_deform_type*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-deform-type"),
|
||||
("bpy.types.brush.cloth_sim_falloff*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-sim-falloff"),
|
||||
("bpy.types.brush.slide_deform_type*", "sculpt_paint/sculpting/tools/slide_relax.html#bpy-types-brush-slide-deform-type"),
|
||||
("bpy.types.brush.use_scene_spacing*", "sculpt_paint/brush/stroke.html#bpy-types-brush-use-scene-spacing"),
|
||||
("bpy.types.brush.use_smooth_stroke*", "sculpt_paint/brush/stroke.html#bpy-types-brush-use-smooth-stroke"),
|
||||
("bpy.types.camera.show_composition*", "render/cameras.html#bpy-types-camera-show-composition"),
|
||||
("bpy.types.colorramp.interpolation*", "interface/controls/templates/color_ramp.html#bpy-types-colorramp-interpolation"),
|
||||
("bpy.types.compositornodealphaover*", "compositing/types/color/alpha_over.html#bpy-types-compositornodealphaover"),
|
||||
("bpy.types.compositornodebokehblur*", "compositing/types/filter/bokeh_blur.html#bpy-types-compositornodebokehblur"),
|
||||
("bpy.types.compositornodecomposite*", "compositing/types/output/composite.html#bpy-types-compositornodecomposite"),
|
||||
("bpy.types.compositornodedespeckle*", "compositing/types/filter/despeckle.html#bpy-types-compositornodedespeckle"),
|
||||
("bpy.types.compositornodediffmatte*", "compositing/types/matte/difference_key.html#bpy-types-compositornodediffmatte"),
|
||||
("bpy.types.compositornodelumamatte*", "compositing/types/matte/luminance_key.html#bpy-types-compositornodelumamatte"),
|
||||
("bpy.types.compositornodemovieclip*", "compositing/types/input/movie_clip.html#bpy-types-compositornodemovieclip"),
|
||||
("bpy.types.compositornodenormalize*", "compositing/types/vector/normalize.html#bpy-types-compositornodenormalize"),
|
||||
("bpy.types.compositornodeposterize*", "compositing/types/color/posterize.html#bpy-types-compositornodeposterize"),
|
||||
("bpy.types.compositornodepremulkey*", "compositing/types/converter/alpha_convert.html#bpy-types-compositornodepremulkey"),
|
||||
("bpy.types.compositornodescenetime*", "compositing/types/input/scene_time.html#bpy-types-compositornodescenetime"),
|
||||
("bpy.types.compositornodestabilize*", "compositing/types/distort/stabilize_2d.html#bpy-types-compositornodestabilize"),
|
||||
("bpy.types.compositornodetransform*", "compositing/types/distort/transform.html#bpy-types-compositornodetransform"),
|
||||
("bpy.types.compositornodetranslate*", "compositing/types/distort/translate.html#bpy-types-compositornodetranslate"),
|
||||
("bpy.types.constraint.target_space*", "animation/constraints/interface/common.html#bpy-types-constraint-target-space"),
|
||||
("bpy.types.curve.use_deform_bounds*", "modeling/curves/properties/shape.html#bpy-types-curve-use-deform-bounds"),
|
||||
("bpy.types.editbone.bbone_curveinx*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-curveinx"),
|
||||
("bpy.types.editbone.bbone_curveinz*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-curveinz"),
|
||||
("bpy.types.editbone.bbone_scaleout*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-scaleout"),
|
||||
("bpy.types.editbone.bbone_segments*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-segments"),
|
||||
("bpy.types.envelopegpencilmodifier*", "grease_pencil/modifiers/generate/envelope.html#bpy-types-envelopegpencilmodifier"),
|
||||
("bpy.types.freestylelineset.qi_end*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset-qi-end"),
|
||||
("bpy.types.freestylelinestyle.caps*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-caps"),
|
||||
("bpy.types.freestylelinestyle.dash*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-dash"),
|
||||
("bpy.types.freestylemodulesettings*", "render/freestyle/python.html#bpy-types-freestylemodulesettings"),
|
||||
("bpy.types.functionnodebooleanmath*", "modeling/geometry_nodes/utilities/math/boolean_math.html#bpy-types-functionnodebooleanmath"),
|
||||
("bpy.types.functionnodeinputstring*", "modeling/geometry_nodes/input/constant/string.html#bpy-types-functionnodeinputstring"),
|
||||
("bpy.types.functionnodeinputvector*", "modeling/geometry_nodes/input/constant/vector.html#bpy-types-functionnodeinputvector"),
|
||||
("bpy.types.functionnoderandomvalue*", "modeling/geometry_nodes/utilities/random_value.html#bpy-types-functionnoderandomvalue"),
|
||||
("bpy.types.functionnoderotateeuler*", "modeling/geometry_nodes/utilities/rotation/rotate_euler.html#bpy-types-functionnoderotateeuler"),
|
||||
("bpy.types.functionnodeslicestring*", "modeling/geometry_nodes/utilities/text/slice_string.html#bpy-types-functionnodeslicestring"),
|
||||
("bpy.types.geometrynodecurvelength*", "modeling/geometry_nodes/curve/read/curve_length.html#bpy-types-geometrynodecurvelength"),
|
||||
("bpy.types.geometrynodecurvespiral*", "modeling/geometry_nodes/curve/primitives/curve_spiral.html#bpy-types-geometrynodecurvespiral"),
|
||||
("bpy.types.geometrynodecurvetomesh*", "modeling/geometry_nodes/curve/operations/curve_to_mesh.html#bpy-types-geometrynodecurvetomesh"),
|
||||
("bpy.types.geometrynodeextrudemesh*", "modeling/geometry_nodes/mesh/operations/extrude_mesh.html#bpy-types-geometrynodeextrudemesh"),
|
||||
("bpy.types.geometrynodefilletcurve*", "modeling/geometry_nodes/curve/operations/fillet_curve.html#bpy-types-geometrynodefilletcurve"),
|
||||
("bpy.types.geometrynodeinputnormal*", "modeling/geometry_nodes/geometry/read/normal.html#bpy-types-geometrynodeinputnormal"),
|
||||
("bpy.types.geometrynodeinputradius*", "modeling/geometry_nodes/geometry/read/radius.html#bpy-types-geometrynodeinputradius"),
|
||||
("bpy.types.geometrynodemeshboolean*", "modeling/geometry_nodes/mesh/operations/mesh_boolean.html#bpy-types-geometrynodemeshboolean"),
|
||||
("bpy.types.geometrynodemeshtocurve*", "modeling/geometry_nodes/mesh/operations/mesh_to_curve.html#bpy-types-geometrynodemeshtocurve"),
|
||||
("bpy.types.geometrynodesamplecurve*", "modeling/geometry_nodes/curve/operations/sample_curve.html#bpy-types-geometrynodesamplecurve"),
|
||||
("bpy.types.geometrynodesampleindex*", "modeling/geometry_nodes/geometry/sample/sample_index.html#bpy-types-geometrynodesampleindex"),
|
||||
("bpy.types.geometrynodesetmaterial*", "modeling/geometry_nodes/material/set_material.html#bpy-types-geometrynodesetmaterial"),
|
||||
("bpy.types.geometrynodesetposition*", "modeling/geometry_nodes/geometry/write/set_position.html#bpy-types-geometrynodesetposition"),
|
||||
("bpy.types.geometrynodetriangulate*", "modeling/geometry_nodes/mesh/operations/triangulate.html#bpy-types-geometrynodetriangulate"),
|
||||
("bpy.types.gpencillayer.blend_mode*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-blend-mode"),
|
||||
("bpy.types.gpencillayer.pass_index*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-pass-index"),
|
||||
("bpy.types.gpencillayer.tint_color*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-tint-color"),
|
||||
("bpy.types.gpencillayer.use_lights*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-use-lights"),
|
||||
("bpy.types.gpencilsculptguide.type*", "grease_pencil/modes/draw/guides.html#bpy-types-gpencilsculptguide-type"),
|
||||
("bpy.types.greasepencil.onion_mode*", "grease_pencil/properties/onion_skinning.html#bpy-types-greasepencil-onion-mode"),
|
||||
("bpy.types.greasepencilgrid.offset*", "grease_pencil/properties/display.html#bpy-types-greasepencilgrid-offset"),
|
||||
("bpy.types.imagepaint.normal_angle*", "sculpt_paint/brush/falloff.html#bpy-types-imagepaint-normal-angle"),
|
||||
("bpy.types.laplaciandeformmodifier*", "modeling/modifiers/deform/laplacian_deform.html#bpy-types-laplaciandeformmodifier"),
|
||||
("bpy.types.laplaciansmoothmodifier*", "modeling/modifiers/deform/laplacian_smooth.html#bpy-types-laplaciansmoothmodifier"),
|
||||
("bpy.types.layercollection.exclude*", "editors/outliner/interface.html#bpy-types-layercollection-exclude"),
|
||||
("bpy.types.layercollection.holdout*", "editors/outliner/interface.html#bpy-types-layercollection-holdout"),
|
||||
("bpy.types.limitdistanceconstraint*", "animation/constraints/transform/limit_distance.html#bpy-types-limitdistanceconstraint"),
|
||||
("bpy.types.limitlocationconstraint*", "animation/constraints/transform/limit_location.html#bpy-types-limitlocationconstraint"),
|
||||
("bpy.types.limitrotationconstraint*", "animation/constraints/transform/limit_rotation.html#bpy-types-limitrotationconstraint"),
|
||||
("bpy.types.movietrackingtrack.lock*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-lock"),
|
||||
("bpy.types.movietrackingtrack.name*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-types-movietrackingtrack-name"),
|
||||
("bpy.types.multiplygpencilmodifier*", "grease_pencil/modifiers/generate/multiple_strokes.html#bpy-types-multiplygpencilmodifier"),
|
||||
("bpy.types.rendersettings.filepath*", "render/output/properties/output.html#bpy-types-rendersettings-filepath"),
|
||||
("bpy.types.rendersettings.fps_base*", "render/output/properties/format.html#bpy-types-rendersettings-fps-base"),
|
||||
("bpy.types.rigidbodyobject.enabled*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-enabled"),
|
||||
("bpy.types.sceneeevee.use_overscan*", "render/eevee/render_settings/film.html#bpy-types-sceneeevee-use-overscan"),
|
||||
("bpy.types.sequencerpreviewoverlay*", "editors/video_sequencer/preview/display/overlays.html#bpy-types-sequencerpreviewoverlay"),
|
||||
("bpy.types.sequencetimelinechannel*", "editors/video_sequencer/sequencer/channels.html#bpy-types-sequencetimelinechannel"),
|
||||
("bpy.types.sequencetransform.scale*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequencetransform-scale"),
|
||||
("bpy.types.shadernodeeeveespecular*", "render/shader_nodes/shader/specular_bsdf.html#bpy-types-shadernodeeeveespecular"),
|
||||
("bpy.types.shadernodehuesaturation*", "render/shader_nodes/color/hue_saturation.html#bpy-types-shadernodehuesaturation"),
|
||||
("bpy.types.shadernodeseparatecolor*", "render/shader_nodes/converter/separate_color.html#bpy-types-shadernodeseparatecolor"),
|
||||
("bpy.types.shadernodetexwhitenoise*", "render/shader_nodes/textures/white_noise.html#bpy-types-shadernodetexwhitenoise"),
|
||||
("bpy.types.shadernodevolumescatter*", "render/shader_nodes/shader/volume_scatter.html#bpy-types-shadernodevolumescatter"),
|
||||
("bpy.types.simplifygpencilmodifier*", "grease_pencil/modifiers/generate/simplify.html#bpy-types-simplifygpencilmodifier"),
|
||||
("bpy.types.spacegrapheditor.cursor*", "editors/graph_editor/introduction.html#bpy-types-spacegrapheditor-cursor"),
|
||||
("bpy.types.spaceview3d.lock_camera*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-lock-camera"),
|
||||
("bpy.types.spaceview3d.lock_cursor*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-lock-cursor"),
|
||||
("bpy.types.spaceview3d.lock_object*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-lock-object"),
|
||||
("bpy.types.spaceview3d.show_viewer*", "modeling/geometry_nodes/output/viewer.html#bpy-types-spaceview3d-show-viewer"),
|
||||
("bpy.types.texturenodecombinecolor*", "editors/texture_node/types/color/combine_color.html#bpy-types-texturenodecombinecolor"),
|
||||
("bpy.types.texturenodetexdistnoise*", "editors/texture_node/types/textures/distorted_noise.html#bpy-types-texturenodetexdistnoise"),
|
||||
("bpy.types.vertexweightmixmodifier*", "modeling/modifiers/modify/weight_mix.html#bpy-types-vertexweightmixmodifier"),
|
||||
("bpy.types.viewlayer.use_freestyle*", "render/freestyle/view_layer/freestyle.html#bpy-types-viewlayer-use-freestyle"),
|
||||
("bpy.types.volumedisplay.use_slice*", "modeling/volumes/properties.html#bpy-types-volumedisplay-use-slice"),
|
||||
("bpy.types.worldlighting.ao_factor*", "render/cycles/render_settings/light_paths.html#bpy-types-worldlighting-ao-factor"),
|
||||
("bpy.types.worldmistsettings.depth*", "render/cycles/world_settings.html#bpy-types-worldmistsettings-depth"),
|
||||
("bpy.types.worldmistsettings.start*", "render/cycles/world_settings.html#bpy-types-worldmistsettings-start"),
|
||||
("bpy.ops.armature.armature_layers*", "animation/armatures/bones/editing/change_layers.html#bpy-ops-armature-armature-layers"),
|
||||
("bpy.ops.clip.stabilize_2d_select*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-stabilize-2d-select"),
|
||||
("bpy.ops.clip.tracking_object_new*", "movie_clip/tracking/clip/sidebar/track/objects.html#bpy-ops-clip-tracking-object-new"),
|
||||
("bpy.ops.constraint.move_to_index*", "animation/constraints/interface/header.html#bpy-ops-constraint-move-to-index"),
|
||||
("bpy.ops.gpencil.frame_clean_fill*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-frame-clean-fill"),
|
||||
("bpy.ops.gpencil.select_alternate*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-alternate"),
|
||||
("bpy.ops.gpencil.stroke_start_set*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-start-set"),
|
||||
("bpy.ops.gpencil.stroke_subdivide*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-subdivide"),
|
||||
("bpy.ops.gpencil.vertex_color_hsv*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-hsv"),
|
||||
("bpy.ops.gpencil.vertex_color_set*", "grease_pencil/modes/vertex_paint/editing.html#bpy-ops-gpencil-vertex-color-set"),
|
||||
("bpy.ops.graph.extrapolation_type*", "editors/graph_editor/channels.html#bpy-ops-graph-extrapolation-type"),
|
||||
("bpy.ops.graph.interpolation_type*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-interpolation-type"),
|
||||
("bpy.ops.mesh.customdata_skin_add*", "modeling/meshes/properties/custom_data.html#bpy-ops-mesh-customdata-skin-add"),
|
||||
("bpy.ops.mesh.dissolve_degenerate*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-dissolve-degenerate"),
|
||||
("bpy.ops.mesh.face_split_by_edges*", "modeling/meshes/editing/face/weld_edges_faces.html#bpy-ops-mesh-face-split-by-edges"),
|
||||
("bpy.ops.mesh.mark_freestyle_face*", "modeling/meshes/editing/face/face_data.html#bpy-ops-mesh-mark-freestyle-face"),
|
||||
("bpy.ops.mesh.primitive_plane_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-plane-add"),
|
||||
("bpy.ops.mesh.primitive_torus_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-torus-add"),
|
||||
("bpy.ops.mesh.select_non_manifold*", "modeling/meshes/selecting/all_by_trait.html#bpy-ops-mesh-select-non-manifold"),
|
||||
("bpy.ops.object.attribute_convert*", "modeling/geometry_nodes/attributes_reference.html#bpy-ops-object-attribute-convert"),
|
||||
("bpy.ops.object.constraints_clear*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-constraints-clear"),
|
||||
("bpy.ops.object.quadriflow_remesh*", "modeling/meshes/retopology.html#bpy-ops-object-quadriflow-remesh"),
|
||||
("bpy.ops.object.vertex_group_copy*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-copy"),
|
||||
("bpy.ops.object.vertex_group_lock*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-lock"),
|
||||
("bpy.ops.object.vertex_group_move*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-move"),
|
||||
("bpy.ops.object.vertex_group_sort*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-sort"),
|
||||
("bpy.ops.object.vertex_parent_set*", "modeling/meshes/editing/vertex/make_vertex_parent.html#bpy-ops-object-vertex-parent-set"),
|
||||
("bpy.ops.outliner.collection_hide*", "editors/outliner/editing.html#bpy-ops-outliner-collection-hide"),
|
||||
("bpy.ops.outliner.collection_show*", "editors/outliner/editing.html#bpy-ops-outliner-collection-show"),
|
||||
("bpy.ops.paint.mask_lasso_gesture*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-paint-mask-lasso-gesture"),
|
||||
("bpy.ops.poselib.apply_pose_asset*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-apply-pose-asset"),
|
||||
("bpy.ops.poselib.blend_pose_asset*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-blend-pose-asset"),
|
||||
("bpy.ops.rigidbody.mass_calculate*", "scene_layout/object/editing/rigid_body.html#bpy-ops-rigidbody-mass-calculate"),
|
||||
("bpy.ops.screen.spacedata_cleanup*", "advanced/operators.html#bpy-ops-screen-spacedata-cleanup"),
|
||||
("bpy.ops.sculpt.detail_flood_fill*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-ops-sculpt-detail-flood-fill"),
|
||||
("bpy.ops.sculpt_curves.select_end*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-sculpt-curves-select-end"),
|
||||
("bpy.ops.sequencer.duplicate_move*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-duplicate-move"),
|
||||
("bpy.ops.sequencer.select_grouped*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-grouped"),
|
||||
("bpy.ops.sequencer.select_handles*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-handles"),
|
||||
("bpy.ops.ui.copy_data_path_button*", "interface/controls/buttons/menus.html#bpy-ops-ui-copy-data-path-button"),
|
||||
("bpy.ops.uv.average_islands_scale*", "modeling/meshes/uv/editing.html#bpy-ops-uv-average-islands-scale"),
|
||||
("bpy.ops.wm.read_factory_settings*", "interface/window_system/topbar.html#bpy-ops-wm-read-factory-settings"),
|
||||
("bpy.types.action.use_frame_range*", "animation/actions.html#bpy-types-action-use-frame-range"),
|
||||
("bpy.types.armature.axes_position*", "animation/armatures/properties/display.html#bpy-types-armature-axes-position"),
|
||||
("bpy.types.armature.pose_position*", "animation/armatures/properties/skeleton.html#bpy-types-armature-pose-position"),
|
||||
("bpy.types.bakesettings.use_clear*", "render/cycles/baking.html#bpy-types-bakesettings-use-clear"),
|
||||
("bpy.types.bakesettings.view_from*", "render/cycles/baking.html#bpy-types-bakesettings-view-from"),
|
||||
("bpy.types.bone.envelope_distance*", "animation/armatures/bones/properties/deform.html#bpy-types-bone-envelope-distance"),
|
||||
("bpy.types.brightcontrastmodifier*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-brightcontrastmodifier"),
|
||||
("bpy.types.brush.cursor_color_add*", "sculpt_paint/brush/cursor.html#bpy-types-brush-cursor-color-add"),
|
||||
("bpy.types.brush.pose_deform_type*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-pose-deform-type"),
|
||||
("bpy.types.brush.pose_ik_segments*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-pose-ik-segments"),
|
||||
("bpy.types.brush.pose_origin_type*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-pose-origin-type"),
|
||||
("bpy.types.brush.use_edge_to_edge*", "sculpt_paint/brush/stroke.html#bpy-types-brush-use-edge-to-edge"),
|
||||
("bpy.types.brushtextureslot.angle*", "sculpt_paint/brush/texture.html#bpy-types-brushtextureslot-angle"),
|
||||
("bpy.types.camerasolverconstraint*", "animation/constraints/motion_tracking/camera_solver.html#bpy-types-camerasolverconstraint"),
|
||||
("bpy.types.clothcollisionsettings*", "physics/cloth/settings/collisions.html#bpy-types-clothcollisionsettings"),
|
||||
("bpy.types.collection.hide_select*", "editors/outliner/interface.html#bpy-types-collection-hide-select"),
|
||||
("bpy.types.colorrampelement.color*", "interface/controls/templates/color_ramp.html#bpy-types-colorrampelement-color"),
|
||||
("bpy.types.compositornodecurvergb*", "compositing/types/color/rgb_curves.html#bpy-types-compositornodecurvergb"),
|
||||
("bpy.types.compositornodecurvevec*", "compositing/types/vector/vector_curves.html#bpy-types-compositornodecurvevec"),
|
||||
("bpy.types.compositornodedisplace*", "compositing/types/distort/displace.html#bpy-types-compositornodedisplace"),
|
||||
("bpy.types.compositornodeexposure*", "compositing/types/color/exposure.html#bpy-types-compositornodeexposure"),
|
||||
("bpy.types.compositornodelensdist*", "compositing/types/distort/lens_distortion.html#bpy-types-compositornodelensdist"),
|
||||
("bpy.types.compositornodemaprange*", "compositing/types/vector/map_range.html#bpy-types-compositornodemaprange"),
|
||||
("bpy.types.compositornodemapvalue*", "compositing/types/vector/map_value.html#bpy-types-compositornodemapvalue"),
|
||||
("bpy.types.compositornodepixelate*", "compositing/types/filter/pixelate.html#bpy-types-compositornodepixelate"),
|
||||
("bpy.types.compositornodesetalpha*", "compositing/types/converter/set_alpha.html#bpy-types-compositornodesetalpha"),
|
||||
("bpy.types.compositornodesunbeams*", "compositing/types/filter/sun_beams.html#bpy-types-compositornodesunbeams"),
|
||||
("bpy.types.compositornodetrackpos*", "compositing/types/input/track_position.html#bpy-types-compositornodetrackpos"),
|
||||
("bpy.types.compositornodezcombine*", "compositing/types/color/z_combine.html#bpy-types-compositornodezcombine"),
|
||||
("bpy.types.constraint.owner_space*", "animation/constraints/interface/common.html#bpy-types-constraint-owner-space"),
|
||||
("bpy.types.copylocationconstraint*", "animation/constraints/transform/copy_location.html#bpy-types-copylocationconstraint"),
|
||||
("bpy.types.copyrotationconstraint*", "animation/constraints/transform/copy_rotation.html#bpy-types-copyrotationconstraint"),
|
||||
("bpy.types.curve.bevel_factor_end*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-factor-end"),
|
||||
("bpy.types.curve.bevel_resolution*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-resolution"),
|
||||
("bpy.types.cyclesmaterialsettings*", "render/cycles/material_settings.html#bpy-types-cyclesmaterialsettings"),
|
||||
("bpy.types.dopesheet.show_summary*", "editors/dope_sheet/introduction.html#bpy-types-dopesheet-show-summary"),
|
||||
("bpy.types.editbone.bbone_easeout*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-easeout"),
|
||||
("bpy.types.editbone.bbone_rollout*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-rollout"),
|
||||
("bpy.types.editbone.bbone_scalein*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-scalein"),
|
||||
("bpy.types.editbone.inherit_scale*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-inherit-scale"),
|
||||
("bpy.types.ffmpegsettings.gopsize*", "render/output/properties/output.html#bpy-types-ffmpegsettings-gopsize"),
|
||||
("bpy.types.ffmpegsettings.maxrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-maxrate"),
|
||||
("bpy.types.ffmpegsettings.minrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-minrate"),
|
||||
("bpy.types.ffmpegsettings.muxrate*", "render/output/properties/output.html#bpy-types-ffmpegsettings-muxrate"),
|
||||
("bpy.types.fieldsettings.strength*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-strength"),
|
||||
("bpy.types.freestylelinestyle.gap*", "render/freestyle/view_layer/line_style/strokes.html#bpy-types-freestylelinestyle-gap"),
|
||||
("bpy.types.freestylesettings.mode*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings-mode"),
|
||||
("bpy.types.functionnodefloattoint*", "modeling/geometry_nodes/utilities/math/float_to_integer.html#bpy-types-functionnodefloattoint"),
|
||||
("bpy.types.functionnodeinputcolor*", "modeling/geometry_nodes/input/constant/color.html#bpy-types-functionnodeinputcolor"),
|
||||
("bpy.types.geometrynodeconvexhull*", "modeling/geometry_nodes/geometry/operations/convex_hull.html#bpy-types-geometrynodeconvexhull"),
|
||||
("bpy.types.geometrynodeimageinput*", "modeling/geometry_nodes/input/constant/image.html#bpy-types-geometrynodeimageinput"),
|
||||
("bpy.types.geometrynodeinputindex*", "modeling/geometry_nodes/geometry/read/input_index.html#bpy-types-geometrynodeinputindex"),
|
||||
("bpy.types.geometrynodeisviewport*", "modeling/geometry_nodes/input/scene/is_viewport.html#bpy-types-geometrynodeisviewport"),
|
||||
("bpy.types.geometrynodemeshcircle*", "modeling/geometry_nodes/mesh/primitives/mesh_circle.html#bpy-types-geometrynodemeshcircle"),
|
||||
("bpy.types.geometrynodeobjectinfo*", "modeling/geometry_nodes/input/scene/object_info.html#bpy-types-geometrynodeobjectinfo"),
|
||||
("bpy.types.geometrynodeselfobject*", "modeling/geometry_nodes/input/scene/self_object.html#bpy-types-geometrynodeselfobject"),
|
||||
("bpy.types.geometrynodesplitedges*", "modeling/geometry_nodes/mesh/operations/split_edges.html#bpy-types-geometrynodesplitedges"),
|
||||
("bpy.types.geometrynodestringjoin*", "modeling/geometry_nodes/utilities/text/join_strings.html#bpy-types-geometrynodestringjoin"),
|
||||
("bpy.types.geometrynodevolumecube*", "modeling/geometry_nodes/volume/volume_cube.html#bpy-types-geometrynodevolumecube"),
|
||||
("bpy.types.greasepencilgrid.color*", "grease_pencil/properties/display.html#bpy-types-greasepencilgrid-color"),
|
||||
("bpy.types.greasepencilgrid.lines*", "grease_pencil/properties/display.html#bpy-types-greasepencilgrid-lines"),
|
||||
("bpy.types.greasepencilgrid.scale*", "grease_pencil/properties/display.html#bpy-types-greasepencilgrid-scale"),
|
||||
("bpy.types.imagepaint.use_occlude*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-use-occlude"),
|
||||
("bpy.types.imagesequence.use_flip*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-imagesequence-use-flip"),
|
||||
("bpy.types.keyframe.interpolation*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-interpolation"),
|
||||
("bpy.types.latticegpencilmodifier*", "grease_pencil/modifiers/deform/lattice.html#bpy-types-latticegpencilmodifier"),
|
||||
("bpy.types.lineartgpencilmodifier*", "grease_pencil/modifiers/generate/line_art.html#bpy-types-lineartgpencilmodifier"),
|
||||
("bpy.types.material.diffuse_color*", "render/materials/settings.html#bpy-types-material-diffuse-color"),
|
||||
("bpy.types.material.line_priority*", "render/freestyle/material.html#bpy-types-material-line-priority"),
|
||||
("bpy.types.material.shadow_method*", "render/eevee/materials/settings.html#bpy-types-material-shadow-method"),
|
||||
("bpy.types.mesh.auto_smooth_angle*", "modeling/meshes/structure.html#bpy-types-mesh-auto-smooth-angle"),
|
||||
("bpy.types.modifier.show_viewport*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-viewport"),
|
||||
("bpy.types.motionpath.frame_start*", "animation/motion_paths.html#bpy-types-motionpath-frame-start"),
|
||||
("bpy.types.object.visible_diffuse*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-diffuse"),
|
||||
("bpy.types.objectsolverconstraint*", "animation/constraints/motion_tracking/object_solver.html#bpy-types-objectsolverconstraint"),
|
||||
("bpy.types.opacitygpencilmodifier*", "grease_pencil/modifiers/color/opacity.html#bpy-types-opacitygpencilmodifier"),
|
||||
("bpy.types.outlinegpencilmodifier*", "grease_pencil/modifiers/generate/outline.html#bpy-types-outlinegpencilmodifier"),
|
||||
("bpy.types.particlesystemmodifier*", "physics/particles/index.html#bpy-types-particlesystemmodifier"),
|
||||
("bpy.types.rendersettings.threads*", "render/cycles/render_settings/performance.html#bpy-types-rendersettings-threads"),
|
||||
("bpy.types.scenedisplay.render_aa*", "render/workbench/sampling.html#bpy-types-scenedisplay-render-aa"),
|
||||
("bpy.types.sceneeevee.motion_blur*", "render/eevee/render_settings/motion_blur.html#bpy-types-sceneeevee-motion-blur"),
|
||||
("bpy.types.sceneeevee.taa_samples*", "render/eevee/render_settings/sampling.html#bpy-types-sceneeevee-taa-samples"),
|
||||
("bpy.types.sculpt.radial_symmetry*", "sculpt_paint/sculpting/tool_settings/symmetry.html#bpy-types-sculpt-radial-symmetry"),
|
||||
("bpy.types.shadernodecombinecolor*", "render/shader_nodes/converter/combine_color.html#bpy-types-shadernodecombinecolor"),
|
||||
("bpy.types.shadernodedisplacement*", "render/shader_nodes/vector/displacement.html#bpy-types-shadernodedisplacement"),
|
||||
("bpy.types.shadernodelightfalloff*", "render/shader_nodes/color/light_falloff.html#bpy-types-shadernodelightfalloff"),
|
||||
("bpy.types.shadernodeparticleinfo*", "render/shader_nodes/input/particle_info.html#bpy-types-shadernodeparticleinfo"),
|
||||
("bpy.types.shadernodevectorrotate*", "render/shader_nodes/vector/vector_rotate.html#bpy-types-shadernodevectorrotate"),
|
||||
("bpy.types.shapekey.interpolation*", "animation/shape_keys/shape_keys_panel.html#bpy-types-shapekey-interpolation"),
|
||||
("bpy.types.softbodysettings.choke*", "physics/soft_body/settings/solver.html#bpy-types-softbodysettings-choke"),
|
||||
("bpy.types.softbodysettings.fuzzy*", "physics/soft_body/settings/solver.html#bpy-types-softbodysettings-fuzzy"),
|
||||
("bpy.types.softbodysettings.shear*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-shear"),
|
||||
("bpy.types.softbodysettings.speed*", "physics/soft_body/settings/simulation.html#bpy-types-softbodysettings-speed"),
|
||||
("bpy.types.sound.use_memory_cache*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sound-use-memory-cache"),
|
||||
("bpy.types.spaceview3d.clip_start*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-clip-start"),
|
||||
("bpy.types.spaceview3d.show_gizmo*", "editors/3dview/display/gizmo.html#bpy-types-spaceview3d-show-gizmo"),
|
||||
("bpy.types.texturegpencilmodifier*", "grease_pencil/modifiers/modify/texture_mapping.html#bpy-types-texturegpencilmodifier"),
|
||||
("bpy.types.texturenodecoordinates*", "editors/texture_node/types/input/coordinates.html#bpy-types-texturenodecoordinates"),
|
||||
("bpy.types.texturenodetexmusgrave*", "editors/texture_node/types/textures/musgrave.html#bpy-types-texturenodetexmusgrave"),
|
||||
("bpy.types.unitsettings.mass_unit*", "scene_layout/scene/properties.html#bpy-types-unitsettings-mass-unit"),
|
||||
("bpy.types.unitsettings.time_unit*", "scene_layout/scene/properties.html#bpy-types-unitsettings-time-unit"),
|
||||
("bpy.types.volumedisplacemodifier*", "modeling/modifiers/deform/volume_displace.html#bpy-types-volumedisplacemodifier"),
|
||||
("bpy.types.volumerender.precision*", "modeling/volumes/properties.html#bpy-types-volumerender-precision"),
|
||||
("bpy.types.volumerender.step_size*", "modeling/volumes/properties.html#bpy-types-volumerender-step-size"),
|
||||
("bpy.types.weightednormalmodifier*", "modeling/modifiers/modify/weighted_normal.html#bpy-types-weightednormalmodifier"),
|
||||
("bpy.types.worldlighting.distance*", "render/cycles/render_settings/light_paths.html#bpy-types-worldlighting-distance"),
|
||||
("bpy.ops.armature.autoside_names*", "animation/armatures/bones/editing/naming.html#bpy-ops-armature-autoside-names"),
|
||||
("bpy.ops.armature.calculate_roll*", "animation/armatures/bones/editing/bone_roll.html#bpy-ops-armature-calculate-roll"),
|
||||
("bpy.ops.armature.duplicate_move*", "animation/armatures/bones/editing/duplicate.html#bpy-ops-armature-duplicate-move"),
|
||||
("bpy.ops.armature.extrude_forked*", "animation/armatures/bones/editing/extrude.html#bpy-ops-armature-extrude-forked"),
|
||||
("bpy.ops.armature.select_similar*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-similar"),
|
||||
("bpy.ops.clip.create_plane_track*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-create-plane-track"),
|
||||
("bpy.ops.curve.spline_weight_set*", "modeling/curves/editing/other.html#bpy-ops-curve-spline-weight-set"),
|
||||
("bpy.ops.gpencil.blank_frame_add*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-blank-frame-add"),
|
||||
("bpy.ops.gpencil.frame_duplicate*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-frame-duplicate"),
|
||||
("bpy.ops.gpencil.layer_duplicate*", "grease_pencil/properties/layers.html#bpy-ops-gpencil-layer-duplicate"),
|
||||
("bpy.ops.gpencil.recalc_geometry*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-recalc-geometry"),
|
||||
("bpy.ops.gpencil.stroke_caps_set*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-caps-set"),
|
||||
("bpy.ops.gpencil.stroke_separate*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-stroke-separate"),
|
||||
("bpy.ops.gpencil.stroke_simplify*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-simplify"),
|
||||
("bpy.ops.graph.blend_to_neighbor*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-blend-to-neighbor"),
|
||||
("bpy.ops.graph.snap_cursor_value*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-snap-cursor-value"),
|
||||
("bpy.ops.image.save_all_modified*", "editors/image/editing.html#bpy-ops-image-save-all-modified"),
|
||||
("bpy.ops.marker.make_links_scene*", "animation/markers.html#bpy-ops-marker-make-links-scene"),
|
||||
("bpy.ops.marker.select_leftright*", "animation/markers.html#bpy-ops-marker-select-leftright"),
|
||||
("bpy.ops.mesh.extrude_edges_move*", "modeling/meshes/editing/edge/extrude_edges.html#bpy-ops-mesh-extrude-edges-move"),
|
||||
("bpy.ops.mesh.extrude_faces_move*", "modeling/meshes/editing/face/extrude_individual_faces.html#bpy-ops-mesh-extrude-faces-move"),
|
||||
("bpy.ops.mesh.faces_shade_smooth*", "modeling/meshes/editing/face/shading.html#bpy-ops-mesh-faces-shade-smooth"),
|
||||
("bpy.ops.mesh.paint_mask_extract*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-mesh-paint-mask-extract"),
|
||||
("bpy.ops.mesh.primitive_cone_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-cone-add"),
|
||||
("bpy.ops.mesh.primitive_cube_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-cube-add"),
|
||||
("bpy.ops.mesh.primitive_grid_add*", "modeling/meshes/primitives.html#bpy-ops-mesh-primitive-grid-add"),
|
||||
("bpy.ops.mesh.shortest_path_pick*", "modeling/meshes/selecting/linked.html#bpy-ops-mesh-shortest-path-pick"),
|
||||
("bpy.ops.mesh.subdivide_edgering*", "modeling/meshes/editing/edge/subdivide_edge_ring.html#bpy-ops-mesh-subdivide-edgering"),
|
||||
("bpy.ops.node.hide_socket_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-hide-socket-toggle"),
|
||||
("bpy.ops.node.tree_socket_remove*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-remove"),
|
||||
("bpy.ops.object.attribute_remove*", "modeling/geometry_nodes/attributes_reference.html#bpy-ops-object-attribute-remove"),
|
||||
("bpy.ops.object.constraints_copy*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-constraints-copy"),
|
||||
("bpy.ops.object.gpencil_modifier*", "grease_pencil/modifiers/index.html#bpy-ops-object-gpencil-modifier"),
|
||||
("bpy.ops.object.make_links_scene*", "scene_layout/object/editing/link_transfer/link_scene.html#bpy-ops-object-make-links-scene"),
|
||||
("bpy.ops.object.make_single_user*", "scene_layout/object/editing/relations/make_single_user.html#bpy-ops-object-make-single-user"),
|
||||
("bpy.ops.object.multires_reshape*", "modeling/modifiers/generate/multiresolution.html#bpy-ops-object-multires-reshape"),
|
||||
("bpy.ops.object.select_hierarchy*", "scene_layout/object/selecting.html#bpy-ops-object-select-hierarchy"),
|
||||
("bpy.ops.object.shape_key_mirror*", "animation/shape_keys/shape_keys_panel.html#bpy-ops-object-shape-key-mirror"),
|
||||
("bpy.ops.object.shape_key_remove*", "animation/shape_keys/shape_keys_panel.html#bpy-ops-object-shape-key-remove"),
|
||||
("bpy.ops.object.shape_key_retime*", "animation/shape_keys/shape_keys_panel.html#bpy-ops-object-shape-key-retime"),
|
||||
("bpy.ops.object.vertex_group_add*", "modeling/meshes/properties/vertex_groups/vertex_groups.html#bpy-ops-object-vertex-group-add"),
|
||||
("bpy.ops.object.vertex_group_fix*", "sculpt_paint/weight_paint/editing.html#bpy-ops-object-vertex-group-fix"),
|
||||
("bpy.ops.outliner.collection_new*", "editors/outliner/editing.html#bpy-ops-outliner-collection-new"),
|
||||
("bpy.ops.outliner.show_hierarchy*", "editors/outliner/editing.html#bpy-ops-outliner-show-hierarchy"),
|
||||
("bpy.ops.outliner.show_one_level*", "editors/outliner/editing.html#bpy-ops-outliner-show-one-level"),
|
||||
("bpy.ops.paint.brush_colors_flip*", "sculpt_paint/brush/brush_settings.html#bpy-ops-paint-brush-colors-flip"),
|
||||
("bpy.ops.paint.weight_from_bones*", "sculpt_paint/weight_paint/editing.html#bpy-ops-paint-weight-from-bones"),
|
||||
("bpy.ops.paintcurve.delete_point*", "sculpt_paint/brush/stroke.html#bpy-ops-paintcurve-delete-point"),
|
||||
("bpy.ops.pose.paths_range_update*", "animation/motion_paths.html#bpy-ops-pose-paths-range-update"),
|
||||
("bpy.ops.preferences.studiolight*", "editors/preferences/lights.html#bpy-ops-preferences-studiolight"),
|
||||
("bpy.ops.scene.view_layer_remove*", "render/layers/introduction.html#bpy-ops-scene-view-layer-remove"),
|
||||
("bpy.ops.screen.screen_full_area*", "interface/window_system/areas.html#bpy-ops-screen-screen-full-area"),
|
||||
("bpy.ops.sculpt.face_sets_create*", "sculpt_paint/sculpting/editing/face_sets.html#bpy-ops-sculpt-face-sets-create"),
|
||||
("bpy.ops.sequencer.select_linked*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-linked"),
|
||||
("bpy.ops.transform.rotate_normal*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-transform-rotate-normal"),
|
||||
("bpy.ops.transform.shrink_fatten*", "modeling/meshes/editing/mesh/transform/shrink-fatten.html#bpy-ops-transform-shrink-fatten"),
|
||||
("bpy.ops.transform.vertex_random*", "modeling/meshes/editing/mesh/transform/randomize.html#bpy-ops-transform-vertex-random"),
|
||||
("bpy.ops.ui.reset_default_button*", "interface/controls/buttons/menus.html#bpy-ops-ui-reset-default-button"),
|
||||
("bpy.ops.uv.shortest_path_select*", "editors/uv/selecting.html#bpy-ops-uv-shortest-path-select"),
|
||||
("bpy.ops.view3d.object_as_camera*", "editors/3dview/navigate/camera_view.html#bpy-ops-view3d-object-as-camera"),
|
||||
("bpy.ops.wm.operator_cheat_sheet*", "advanced/operators.html#bpy-ops-wm-operator-cheat-sheet"),
|
||||
("bpy.ops.wm.previews_batch_clear*", "files/blend/previews.html#bpy-ops-wm-previews-batch-clear"),
|
||||
("bpy.ops.wm.recover_last_session*", "files/blend/open_save.html#bpy-ops-wm-recover-last-session"),
|
||||
("bpy.ops.wm.toolbar_fallback_pie*", "interface/tool_system.html#bpy-ops-wm-toolbar-fallback-pie"),
|
||||
("bpy.types.armature.display_type*", "animation/armatures/properties/display.html#bpy-types-armature-display-type"),
|
||||
("bpy.types.armature.use_mirror_x*", "animation/armatures/bones/tools/tool_settings.html#bpy-types-armature-use-mirror-x"),
|
||||
("bpy.types.bakesettings.normal_b*", "render/cycles/baking.html#bpy-types-bakesettings-normal-b"),
|
||||
("bpy.types.bakesettings.normal_g*", "render/cycles/baking.html#bpy-types-bakesettings-normal-g"),
|
||||
("bpy.types.bakesettings.normal_r*", "render/cycles/baking.html#bpy-types-bakesettings-normal-r"),
|
||||
("bpy.types.bakesettings.use_cage*", "render/cycles/baking.html#bpy-types-bakesettings-use-cage"),
|
||||
("bpy.types.brush.boundary_offset*", "sculpt_paint/sculpting/tools/boundary.html#bpy-types-brush-boundary-offset"),
|
||||
("bpy.types.brush.cloth_sim_limit*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-sim-limit"),
|
||||
("bpy.types.brush.use_custom_icon*", "sculpt_paint/brush/brush.html#bpy-types-brush-use-custom-icon"),
|
||||
("bpy.types.camerabackgroundimage*", "render/cameras.html#bpy-types-camerabackgroundimage"),
|
||||
("bpy.types.clothsettings.quality*", "physics/cloth/settings/index.html#bpy-types-clothsettings-quality"),
|
||||
("bpy.types.compositornodeboxmask*", "compositing/types/matte/box_mask.html#bpy-types-compositornodeboxmask"),
|
||||
("bpy.types.compositornodedefocus*", "compositing/types/filter/defocus.html#bpy-types-compositornodedefocus"),
|
||||
("bpy.types.compositornodedenoise*", "compositing/types/filter/denoise.html#bpy-types-compositornodedenoise"),
|
||||
("bpy.types.compositornodeinpaint*", "compositing/types/filter/inpaint.html#bpy-types-compositornodeinpaint"),
|
||||
("bpy.types.compositornodergbtobw*", "compositing/types/converter/rgb_to_bw.html#bpy-types-compositornodergbtobw"),
|
||||
("bpy.types.compositornoderlayers*", "compositing/types/input/render_layers.html#bpy-types-compositornoderlayers"),
|
||||
("bpy.types.compositornodetexture*", "compositing/types/input/texture.html#bpy-types-compositornodetexture"),
|
||||
("bpy.types.compositornodetonemap*", "compositing/types/color/tone_map.html#bpy-types-compositornodetonemap"),
|
||||
("bpy.types.compositornodevecblur*", "compositing/types/filter/vector_blur.html#bpy-types-compositornodevecblur"),
|
||||
("bpy.types.curve.use_fill_deform*", "modeling/curves/properties/shape.html#bpy-types-curve-use-fill-deform"),
|
||||
("bpy.types.curve.use_path_follow*", "modeling/curves/properties/path_animation.html#bpy-types-curve-use-path-follow"),
|
||||
("bpy.types.curves.surface_uv_map*", "modeling/curves/primitives.html#bpy-types-curves-surface-uv-map"),
|
||||
("bpy.types.dampedtrackconstraint*", "animation/constraints/tracking/damped_track.html#bpy-types-dampedtrackconstraint"),
|
||||
("bpy.types.distortednoisetexture*", "render/materials/legacy_textures/types/distorted_noise.html#bpy-types-distortednoisetexture"),
|
||||
("bpy.types.dopesheet.filter_text*", "editors/graph_editor/channels.html#bpy-types-dopesheet-filter-text"),
|
||||
("bpy.types.editbone.bbone_easein*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-easein"),
|
||||
("bpy.types.editbone.bbone_rollin*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-rollin"),
|
||||
("bpy.types.fcurve.auto_smoothing*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-auto-smoothing"),
|
||||
("bpy.types.ffmpegsettings.format*", "render/output/properties/output.html#bpy-types-ffmpegsettings-format"),
|
||||
("bpy.types.fluideffectorsettings*", "physics/fluid/type/effector.html#bpy-types-fluideffectorsettings"),
|
||||
("bpy.types.followtrackconstraint*", "animation/constraints/motion_tracking/follow_track.html#bpy-types-followtrackconstraint"),
|
||||
("bpy.types.functionnodeinputbool*", "modeling/geometry_nodes/input/constant/boolean.html#bpy-types-functionnodeinputbool"),
|
||||
("bpy.types.geometrycornersofface*", "modeling/geometry_nodes/mesh/topology/corners_of_face.html#bpy-types-geometrycornersofface"),
|
||||
("bpy.types.geometrynodecurvestar*", "modeling/geometry_nodes/curve/primitives/star.html#bpy-types-geometrynodecurvestar"),
|
||||
("bpy.types.geometrynodefillcurve*", "modeling/geometry_nodes/curve/operations/fill_curve.html#bpy-types-geometrynodefillcurve"),
|
||||
("bpy.types.geometrynodeflipfaces*", "modeling/geometry_nodes/mesh/operations/flip_faces.html#bpy-types-geometrynodeflipfaces"),
|
||||
("bpy.types.geometrynodeimageinfo*", "modeling/geometry_nodes/input/scene/image_info.html#bpy-types-geometrynodeimageinfo"),
|
||||
("bpy.types.geometrynodeproximity*", "modeling/geometry_nodes/geometry/sample/geometry_proximity.html#bpy-types-geometrynodeproximity"),
|
||||
("bpy.types.geometrynodetransform*", "modeling/geometry_nodes/geometry/operations/transform_geometry.html#bpy-types-geometrynodetransform"),
|
||||
("bpy.types.geometrynodetrimcurve*", "modeling/geometry_nodes/curve/operations/trim_curve.html#bpy-types-geometrynodetrimcurve"),
|
||||
("bpy.types.gpencillayer.location*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-location"),
|
||||
("bpy.types.gpencillayer.rotation*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-rotation"),
|
||||
("bpy.types.gpencilsculptsettings*", "grease_pencil/properties/index.html#bpy-types-gpencilsculptsettings"),
|
||||
("bpy.types.keyframe.handle_right*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-right"),
|
||||
("bpy.types.lengthgpencilmodifier*", "grease_pencil/modifiers/generate/length.html#bpy-types-lengthgpencilmodifier"),
|
||||
("bpy.types.light.cutoff_distance*", "render/eevee/lighting.html#bpy-types-light-cutoff-distance"),
|
||||
("bpy.types.lockedtrackconstraint*", "animation/constraints/tracking/locked_track.html#bpy-types-lockedtrackconstraint"),
|
||||
("bpy.types.material.blend_method*", "render/eevee/materials/settings.html#bpy-types-material-blend-method"),
|
||||
("bpy.types.mirrorgpencilmodifier*", "grease_pencil/modifiers/generate/mirror.html#bpy-types-mirrorgpencilmodifier"),
|
||||
("bpy.types.modifier.show_on_cage*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-on-cage"),
|
||||
("bpy.types.movietrackingcamera.k*", "movie_clip/tracking/clip/sidebar/track/camera.html#bpy-types-movietrackingcamera-k"),
|
||||
("bpy.types.node.use_custom_color*", "interface/controls/nodes/sidebar.html#bpy-types-node-use-custom-color"),
|
||||
("bpy.types.object.visible_camera*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-camera"),
|
||||
("bpy.types.object.visible_glossy*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-glossy"),
|
||||
("bpy.types.object.visible_shadow*", "render/cycles/object_settings/object_data.html#bpy-types-object-visible-shadow"),
|
||||
("bpy.types.offsetgpencilmodifier*", "grease_pencil/modifiers/deform/offset.html#bpy-types-offsetgpencilmodifier"),
|
||||
("bpy.types.posebone.custom_shape*", "animation/armatures/bones/properties/display.html#bpy-types-posebone-custom-shape"),
|
||||
("bpy.types.rigifyselectioncolors*", "addons/rigging/rigify/metarigs.html#bpy-types-rigifyselectioncolors"),
|
||||
("bpy.types.sceneeevee.volumetric*", "render/eevee/render_settings/volumetrics.html#bpy-types-sceneeevee-volumetric"),
|
||||
("bpy.types.screen.show_statusbar*", "interface/window_system/topbar.html#bpy-types-screen-show-statusbar"),
|
||||
("bpy.types.sculpt.detail_percent*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-detail-percent"),
|
||||
("bpy.types.sculpt.gravity_object*", "sculpt_paint/sculpting/tool_settings/options.html#bpy-types-sculpt-gravity-object"),
|
||||
("bpy.types.sculpt.show_face_sets*", "sculpt_paint/sculpting/editing/face_sets.html#bpy-types-sculpt-show-face-sets"),
|
||||
("bpy.types.shadernodebsdfdiffuse*", "render/shader_nodes/shader/diffuse.html#bpy-types-shadernodebsdfdiffuse"),
|
||||
("bpy.types.shadernodelayerweight*", "render/shader_nodes/input/layer_weight.html#bpy-types-shadernodelayerweight"),
|
||||
("bpy.types.shadernodenewgeometry*", "render/shader_nodes/input/geometry.html#bpy-types-shadernodenewgeometry"),
|
||||
("bpy.types.shadernodeoutputlight*", "render/shader_nodes/output/light.html#bpy-types-shadernodeoutputlight"),
|
||||
("bpy.types.shadernodeoutputworld*", "render/shader_nodes/output/world.html#bpy-types-shadernodeoutputworld"),
|
||||
("bpy.types.shadernodeseparatexyz*", "render/shader_nodes/converter/separate_xyz.html#bpy-types-shadernodeseparatexyz"),
|
||||
("bpy.types.shadernodeshadertorgb*", "render/shader_nodes/converter/shader_to_rgb.html#bpy-types-shadernodeshadertorgb"),
|
||||
("bpy.types.shadernodetexgradient*", "render/shader_nodes/textures/gradient.html#bpy-types-shadernodetexgradient"),
|
||||
("bpy.types.shadernodevectorcurve*", "render/shader_nodes/vector/curves.html#bpy-types-shadernodevectorcurve"),
|
||||
("bpy.types.shadernodevertexcolor*", "render/shader_nodes/input/vertex_color.html#bpy-types-shadernodevertexcolor"),
|
||||
("bpy.types.shapekey.relative_key*", "animation/shape_keys/shape_keys_panel.html#bpy-types-shapekey-relative-key"),
|
||||
("bpy.types.shapekey.vertex_group*", "animation/shape_keys/shape_keys_panel.html#bpy-types-shapekey-vertex-group"),
|
||||
("bpy.types.smoothgpencilmodifier*", "grease_pencil/modifiers/deform/smooth.html#bpy-types-smoothgpencilmodifier"),
|
||||
("bpy.types.softbodysettings.aero*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-aero"),
|
||||
("bpy.types.softbodysettings.bend*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-bend"),
|
||||
("bpy.types.softbodysettings.mass*", "physics/soft_body/settings/object.html#bpy-types-softbodysettings-mass"),
|
||||
("bpy.types.softbodysettings.pull*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-pull"),
|
||||
("bpy.types.softbodysettings.push*", "physics/soft_body/settings/edges.html#bpy-types-softbodysettings-push"),
|
||||
("bpy.types.spline.use_endpoint_u*", "modeling/curves/properties/active_spline.html#bpy-types-spline-use-endpoint-u"),
|
||||
("bpy.types.surfacedeformmodifier*", "modeling/modifiers/deform/surface_deform.html#bpy-types-surfacedeformmodifier"),
|
||||
("bpy.types.texturenodetexvoronoi*", "editors/texture_node/types/textures/voronoi.html#bpy-types-texturenodetexvoronoi"),
|
||||
("bpy.types.toolsettings.use_snap*", "editors/3dview/controls/snapping.html#bpy-types-toolsettings-use-snap"),
|
||||
("bpy.types.viewlayer.use_volumes*", "render/layers/introduction.html#bpy-types-viewlayer-use-volumes"),
|
||||
("bpy.types.volume.frame_duration*", "modeling/volumes/properties.html#bpy-types-volume-frame-duration"),
|
||||
("bpy.types.volumedisplay.density*", "modeling/volumes/properties.html#bpy-types-volumedisplay-density"),
|
||||
("bpy.types.volumerender.clipping*", "modeling/volumes/properties.html#bpy-types-volumerender-clipping"),
|
||||
("bpy.types.workspace.object_mode*", "interface/window_system/workspaces.html#bpy-types-workspace-object-mode"),
|
||||
("bpy.ops.anim.channels_collapse*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-collapse"),
|
||||
("bpy.ops.anim.driver_button_add*", "animation/drivers/usage.html#bpy-ops-anim-driver-button-add"),
|
||||
("bpy.ops.armature.click_extrude*", "animation/armatures/bones/editing/extrude.html#bpy-ops-armature-click-extrude"),
|
||||
("bpy.ops.armature.select_linked*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-linked"),
|
||||
("bpy.ops.armature.select_mirror*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-mirror"),
|
||||
("bpy.ops.curve.switch_direction*", "modeling/curves/editing/segments.html#bpy-ops-curve-switch-direction"),
|
||||
("bpy.ops.geometry.attribute_add*", "modeling/geometry_nodes/attributes_reference.html#bpy-ops-geometry-attribute-add"),
|
||||
("bpy.ops.gpencil.duplicate_move*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-duplicate-move"),
|
||||
("bpy.ops.gpencil.select_grouped*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-grouped"),
|
||||
("bpy.ops.gpencil.stroke_arrange*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-arrange"),
|
||||
("bpy.ops.gpencil.stroke_outline*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-outline"),
|
||||
("bpy.ops.graph.blend_to_default*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-blend-to-default"),
|
||||
("bpy.ops.graph.equalize_handles*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-equalize-handles"),
|
||||
("bpy.ops.mball.delete_metaelems*", "modeling/metas/editing.html#bpy-ops-mball-delete-metaelems"),
|
||||
("bpy.ops.mball.reveal_metaelems*", "modeling/metas/properties.html#bpy-ops-mball-reveal-metaelems"),
|
||||
("bpy.ops.mesh.bridge_edge_loops*", "modeling/meshes/editing/edge/bridge_edge_loops.html#bpy-ops-mesh-bridge-edge-loops"),
|
||||
("bpy.ops.mesh.intersect_boolean*", "modeling/meshes/editing/face/intersect_boolean.html#bpy-ops-mesh-intersect-boolean"),
|
||||
("bpy.ops.mesh.loop_multi_select*", "modeling/meshes/selecting/loops.html#bpy-ops-mesh-loop-multi-select"),
|
||||
("bpy.ops.mesh.vert_connect_path*", "modeling/meshes/editing/vertex/connect_vertex_path.html#bpy-ops-mesh-vert-connect-path"),
|
||||
("bpy.ops.nla.action_sync_length*", "editors/nla/editing.html#bpy-ops-nla-action-sync-length"),
|
||||
("bpy.ops.node.move_detach_links*", "interface/controls/nodes/editing.html#bpy-ops-node-move-detach-links"),
|
||||
("bpy.ops.object.make_links_data*", "scene_layout/object/editing/link_transfer/link_data.html#bpy-ops-object-make-links-data"),
|
||||
("bpy.ops.object.modifier_remove*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-remove"),
|
||||
("bpy.ops.object.paths_calculate*", "animation/motion_paths.html#bpy-ops-object-paths-calculate"),
|
||||
("bpy.ops.object.shape_key_clear*", "animation/shape_keys/shape_keys_panel.html#bpy-ops-object-shape-key-clear"),
|
||||
("bpy.ops.object.transform_apply*", "scene_layout/object/editing/apply.html#bpy-ops-object-transform-apply"),
|
||||
("bpy.ops.outliner.lib_operation*", "files/linked_libraries/link_append.html#bpy-ops-outliner-lib-operation"),
|
||||
("bpy.ops.outliner.orphans_purge*", "editors/outliner/interface.html#bpy-ops-outliner-orphans-purge"),
|
||||
("bpy.ops.paint.mask_box_gesture*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-paint-mask-box-gesture"),
|
||||
("bpy.ops.pose.blend_to_neighbor*", "animation/armatures/posing/editing/in_betweens.html#bpy-ops-pose-blend-to-neighbor"),
|
||||
("bpy.ops.screen.region_quadview*", "editors/3dview/navigate/views.html#bpy-ops-screen-region-quadview"),
|
||||
("bpy.ops.screen.screenshot_area*", "interface/window_system/topbar.html#bpy-ops-screen-screenshot-area"),
|
||||
("bpy.ops.screen.workspace_cycle*", "interface/window_system/workspaces.html#bpy-ops-screen-workspace-cycle"),
|
||||
("bpy.ops.sequencer.change_scene*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-change-scene"),
|
||||
("bpy.ops.sequencer.offset_clear*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-offset-clear"),
|
||||
("bpy.ops.spreadsheet.toggle_pin*", "editors/spreadsheet.html#bpy-ops-spreadsheet-toggle-pin"),
|
||||
("bpy.ops.uv.follow_active_quads*", "modeling/meshes/editing/uv.html#bpy-ops-uv-follow-active-quads"),
|
||||
("bpy.ops.view3d.view_persportho*", "editors/3dview/navigate/projections.html#bpy-ops-view3d-view-persportho"),
|
||||
("bpy.types.arraygpencilmodifier*", "grease_pencil/modifiers/generate/array.html#bpy-types-arraygpencilmodifier"),
|
||||
("bpy.types.assetmetadata.author*", "editors/asset_browser.html#bpy-types-assetmetadata-author"),
|
||||
("bpy.types.bone.envelope_weight*", "animation/armatures/bones/properties/deform.html#bpy-types-bone-envelope-weight"),
|
||||
("bpy.types.brush.use_persistent*", "sculpt_paint/sculpting/tools/layer.html#bpy-types-brush-use-persistent"),
|
||||
("bpy.types.brushgpencilsettings*", "grease_pencil/modes/draw/tools/index.html#bpy-types-brushgpencilsettings"),
|
||||
("bpy.types.buildgpencilmodifier*", "grease_pencil/modifiers/generate/build.html#bpy-types-buildgpencilmodifier"),
|
||||
("bpy.types.camera.sensor_height*", "render/cameras.html#bpy-types-camera-sensor-height"),
|
||||
("bpy.types.colorbalancemodifier*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-colorbalancemodifier"),
|
||||
("bpy.types.colorgpencilmodifier*", "grease_pencil/modifiers/color/hue_saturation.html#bpy-types-colorgpencilmodifier"),
|
||||
("bpy.types.colorramp.color_mode*", "interface/controls/templates/color_ramp.html#bpy-types-colorramp-color-mode"),
|
||||
("bpy.types.compositornodefilter*", "compositing/types/filter/filter_node.html#bpy-types-compositornodefilter"),
|
||||
("bpy.types.compositornodehuesat*", "compositing/types/color/hue_saturation.html#bpy-types-compositornodehuesat"),
|
||||
("bpy.types.compositornodeidmask*", "compositing/types/converter/id_mask.html#bpy-types-compositornodeidmask"),
|
||||
("bpy.types.compositornodeinvert*", "compositing/types/color/invert.html#bpy-types-compositornodeinvert"),
|
||||
("bpy.types.compositornodekeying*", "compositing/types/matte/keying.html#bpy-types-compositornodekeying"),
|
||||
("bpy.types.compositornodelevels*", "compositing/types/output/levels.html#bpy-types-compositornodelevels"),
|
||||
("bpy.types.compositornodemixrgb*", "compositing/types/color/mix.html#bpy-types-compositornodemixrgb"),
|
||||
("bpy.types.compositornodenormal*", "compositing/types/vector/normal.html#bpy-types-compositornodenormal"),
|
||||
("bpy.types.compositornoderotate*", "compositing/types/distort/rotate.html#bpy-types-compositornoderotate"),
|
||||
("bpy.types.compositornodeswitch*", "compositing/types/layout/switch.html#bpy-types-compositornodeswitch"),
|
||||
("bpy.types.compositornodeviewer*", "compositing/types/output/viewer.html#bpy-types-compositornodeviewer"),
|
||||
("bpy.types.constraint.influence*", "animation/constraints/interface/common.html#bpy-types-constraint-influence"),
|
||||
("bpy.types.curve.use_path_clamp*", "modeling/curves/properties/path_animation.html#bpy-types-curve-use-path-clamp"),
|
||||
("bpy.types.datatransfermodifier*", "modeling/modifiers/modify/data_transfer.html#bpy-types-datatransfermodifier"),
|
||||
("bpy.types.dynamicpaintmodifier*", "physics/dynamic_paint/index.html#bpy-types-dynamicpaintmodifier"),
|
||||
("bpy.types.editbone.use_connect*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-use-connect"),
|
||||
("bpy.types.ffmpegsettings.codec*", "render/output/properties/output.html#bpy-types-ffmpegsettings-codec"),
|
||||
("bpy.types.followpathconstraint*", "animation/constraints/relationship/follow_path.html#bpy-types-followpathconstraint"),
|
||||
("bpy.types.functionnodeinputint*", "modeling/geometry_nodes/input/constant/integer.html#bpy-types-functionnodeinputint"),
|
||||
("bpy.types.gaussianblursequence*", "video_editing/edit/montage/strips/effects/blur.html#bpy-types-gaussianblursequence"),
|
||||
("bpy.types.geometrynodeboundbox*", "modeling/geometry_nodes/geometry/operations/bounding_box.html#bpy-types-geometrynodeboundbox"),
|
||||
("bpy.types.geometrynodecurvearc*", "modeling/geometry_nodes/curve/primitives/arc.html#bpy-types-geometrynodecurvearc"),
|
||||
("bpy.types.geometrynodedualmesh*", "modeling/geometry_nodes/mesh/operations/dual_mesh.html#bpy-types-geometrynodedualmesh"),
|
||||
("bpy.types.geometrynodemeshcone*", "modeling/geometry_nodes/mesh/primitives/cone.html#bpy-types-geometrynodemeshcone"),
|
||||
("bpy.types.geometrynodemeshcube*", "modeling/geometry_nodes/mesh/primitives/cube.html#bpy-types-geometrynodemeshcube"),
|
||||
("bpy.types.geometrynodemeshgrid*", "modeling/geometry_nodes/mesh/primitives/grid.html#bpy-types-geometrynodemeshgrid"),
|
||||
("bpy.types.geometrynodemeshline*", "modeling/geometry_nodes/mesh/primitives/mesh_line.html#bpy-types-geometrynodemeshline"),
|
||||
("bpy.types.geometrynodeuvunwrap*", "modeling/geometry_nodes/mesh/uv/uv_unwrap.html#bpy-types-geometrynodeuvunwrap"),
|
||||
("bpy.types.gpencillayer.opacity*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-opacity"),
|
||||
("bpy.types.image.display_aspect*", "editors/image/sidebar.html#bpy-types-image-display-aspect"),
|
||||
("bpy.types.keyframe.handle_left*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-handle-left"),
|
||||
("bpy.types.keyingsetsall.active*", "editors/timeline.html#bpy-types-keyingsetsall-active"),
|
||||
("bpy.types.limitscaleconstraint*", "animation/constraints/transform/limit_scale.html#bpy-types-limitscaleconstraint"),
|
||||
("bpy.types.materialgpencilstyle*", "grease_pencil/materials/index.html#bpy-types-materialgpencilstyle"),
|
||||
("bpy.types.mesh.use_auto_smooth*", "modeling/meshes/structure.html#bpy-types-mesh-use-auto-smooth"),
|
||||
("bpy.types.meshtovolumemodifier*", "modeling/modifiers/generate/mesh_to_volume.html#bpy-types-meshtovolumemodifier"),
|
||||
("bpy.types.modifier.show_render*", "modeling/modifiers/introduction.html#bpy-types-modifier-show-render"),
|
||||
("bpy.types.motionpath.frame_end*", "animation/motion_paths.html#bpy-types-motionpath-frame-end"),
|
||||
("bpy.types.noisegpencilmodifier*", "grease_pencil/modifiers/deform/noise.html#bpy-types-noisegpencilmodifier"),
|
||||
("bpy.types.object.hide_viewport*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-viewport"),
|
||||
("bpy.types.object.rotation_mode*", "scene_layout/object/properties/transforms.html#bpy-types-object-rotation-mode"),
|
||||
("bpy.types.object.show_in_front*", "scene_layout/object/properties/display.html#bpy-types-object-show-in-front"),
|
||||
("bpy.types.posebone.rigify_type*", "addons/rigging/rigify/rig_types/index.html#bpy-types-posebone-rigify-type"),
|
||||
("bpy.types.preferencesfilepaths*", "editors/preferences/file_paths.html#bpy-types-preferencesfilepaths"),
|
||||
("bpy.types.rigidbodyobject.mass*", "physics/rigid_body/properties/settings.html#bpy-types-rigidbodyobject-mass"),
|
||||
("bpy.types.scene.background_set*", "scene_layout/scene/properties.html#bpy-types-scene-background-set"),
|
||||
("bpy.types.sequence.frame_start*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequence-frame-start"),
|
||||
("bpy.types.shadernodebackground*", "render/shader_nodes/shader/background.html#bpy-types-shadernodebackground"),
|
||||
("bpy.types.shadernodebsdfglossy*", "render/shader_nodes/shader/glossy.html#bpy-types-shadernodebsdfglossy"),
|
||||
("bpy.types.shadernodebsdfvelvet*", "render/shader_nodes/shader/velvet.html#bpy-types-shadernodebsdfvelvet"),
|
||||
("bpy.types.shadernodecameradata*", "render/shader_nodes/input/camera_data.html#bpy-types-shadernodecameradata"),
|
||||
("bpy.types.shadernodecombinexyz*", "render/shader_nodes/converter/combine_xyz.html#bpy-types-shadernodecombinexyz"),
|
||||
("bpy.types.shadernodefloatcurve*", "render/shader_nodes/converter/float_curve.html#bpy-types-shadernodefloatcurve"),
|
||||
("bpy.types.shadernodeobjectinfo*", "render/shader_nodes/input/object_info.html#bpy-types-shadernodeobjectinfo"),
|
||||
("bpy.types.shadernodetexchecker*", "render/shader_nodes/textures/checker.html#bpy-types-shadernodetexchecker"),
|
||||
("bpy.types.shadernodetexvoronoi*", "render/shader_nodes/textures/voronoi.html#bpy-types-shadernodetexvoronoi"),
|
||||
("bpy.types.shadernodevectormath*", "render/shader_nodes/converter/vector_math.html#bpy-types-shadernodevectormath"),
|
||||
("bpy.types.shadernodevolumeinfo*", "render/shader_nodes/input/volume_info.html#bpy-types-shadernodevolumeinfo"),
|
||||
("bpy.types.shadernodewavelength*", "render/shader_nodes/converter/wavelength.html#bpy-types-shadernodewavelength"),
|
||||
("bpy.types.shrinkwrapconstraint*", "animation/constraints/relationship/shrinkwrap.html#bpy-types-shrinkwrapconstraint"),
|
||||
("bpy.types.simpledeformmodifier*", "modeling/modifiers/deform/simple_deform.html#bpy-types-simpledeformmodifier"),
|
||||
("bpy.types.soundsequence.volume*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-soundsequence-volume"),
|
||||
("bpy.types.spaceclipeditor.show*", "editors/clip/display/index.html#bpy-types-spaceclipeditor-show"),
|
||||
("bpy.types.spacedopesheeteditor*", "editors/dope_sheet/index.html#bpy-types-spacedopesheeteditor"),
|
||||
("bpy.types.spaceview3d.clip_end*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-clip-end"),
|
||||
("bpy.types.speedcontrolsequence*", "video_editing/edit/montage/strips/effects/speed_control.html#bpy-types-speedcontrolsequence"),
|
||||
("bpy.types.texturenodecurvetime*", "editors/texture_node/types/input/time.html#bpy-types-texturenodecurvetime"),
|
||||
("bpy.types.texturenodetexclouds*", "editors/texture_node/types/textures/clouds.html#bpy-types-texturenodetexclouds"),
|
||||
("bpy.types.texturenodetexmarble*", "editors/texture_node/types/textures/marble.html#bpy-types-texturenodetexmarble"),
|
||||
("bpy.types.texturenodetexstucci*", "editors/texture_node/types/textures/stucci.html#bpy-types-texturenodetexstucci"),
|
||||
("bpy.types.texturenodetranslate*", "editors/texture_node/types/distort/translate.html#bpy-types-texturenodetranslate"),
|
||||
("bpy.types.transformorientation*", "editors/3dview/controls/orientation.html#bpy-types-transformorientation"),
|
||||
("bpy.types.unifiedpaintsettings*", "sculpt_paint/brush/brush.html#bpy-types-unifiedpaintsettings"),
|
||||
("bpy.types.viewlayer.use_strand*", "render/layers/introduction.html#bpy-types-viewlayer-use-strand"),
|
||||
("bpy.types.volume.sequence_mode*", "modeling/volumes/properties.html#bpy-types-volume-sequence-mode"),
|
||||
("bpy.types.volumetomeshmodifier*", "modeling/modifiers/generate/volume_to_mesh.html#bpy-types-volumetomeshmodifier"),
|
||||
("bpy.types.whitebalancemodifier*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-whitebalancemodifier"),
|
||||
("bpy.ops.anim.channels_ungroup*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-ungroup"),
|
||||
("bpy.ops.armature.extrude_move*", "animation/armatures/bones/editing/extrude.html#bpy-ops-armature-extrude-move"),
|
||||
("bpy.ops.armature.parent_clear*", "animation/armatures/bones/editing/parenting.html#bpy-ops-armature-parent-clear"),
|
||||
("bpy.ops.clip.clear_track_path*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-clear-track-path"),
|
||||
("bpy.ops.clip.set_scene_frames*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-set-scene-frames"),
|
||||
("bpy.ops.clip.track_copy_color*", "movie_clip/tracking/clip/sidebar/track/track.html#bpy-ops-clip-track-copy-color"),
|
||||
("bpy.ops.curve.handle_type_set*", "modeling/curves/editing/control_points.html#bpy-ops-curve-handle-type-set"),
|
||||
("bpy.ops.curve.spline_type_set*", "modeling/curves/editing/curve.html#bpy-ops-curve-spline-type-set"),
|
||||
("bpy.ops.file.unpack_libraries*", "files/blend/packed_data.html#bpy-ops-file-unpack-libraries"),
|
||||
("bpy.ops.gpencil.layer_isolate*", "grease_pencil/properties/layers.html#bpy-ops-gpencil-layer-isolate"),
|
||||
("bpy.ops.gpencil.move_to_layer*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-move-to-layer"),
|
||||
("bpy.ops.gpencil.select_linked*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-linked"),
|
||||
("bpy.ops.gpencil.select_random*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-random"),
|
||||
("bpy.ops.gpencil.stroke_sample*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-sample"),
|
||||
("bpy.ops.gpencil.stroke_smooth*", "grease_pencil/modes/edit/point_menu.html#bpy-ops-gpencil-stroke-smooth"),
|
||||
("bpy.ops.graph.keyframe_insert*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-keyframe-insert"),
|
||||
("bpy.ops.image.read_viewlayers*", "editors/image/editing.html#bpy-ops-image-read-viewlayers"),
|
||||
("bpy.ops.mask.add_vertex_slide*", "movie_clip/masking/editing.html#bpy-ops-mask-add-vertex-slide"),
|
||||
("bpy.ops.mask.shape_key_insert*", "movie_clip/masking/editing.html#bpy-ops-mask-shape-key-insert"),
|
||||
("bpy.ops.mask.switch_direction*", "movie_clip/masking/editing.html#bpy-ops-mask-switch-direction"),
|
||||
("bpy.ops.mesh.blend_from_shape*", "modeling/meshes/editing/vertex/blend_shape.html#bpy-ops-mesh-blend-from-shape"),
|
||||
("bpy.ops.mesh.dissolve_limited*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-dissolve-limited"),
|
||||
("bpy.ops.mesh.face_make_planar*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-face-make-planar"),
|
||||
("bpy.ops.mesh.face_set_extract*", "sculpt_paint/sculpting/editing/face_sets.html#bpy-ops-mesh-face-set-extract"),
|
||||
("bpy.ops.mesh.faces_shade_flat*", "modeling/meshes/editing/face/shading.html#bpy-ops-mesh-faces-shade-flat"),
|
||||
("bpy.ops.mesh.paint_mask_slice*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-mesh-paint-mask-slice"),
|
||||
("bpy.ops.mesh.select_edge_ring*", "modeling/meshes/selecting/loops.html#bpy-ops-mesh-select-edge-ring"),
|
||||
("bpy.ops.mesh.select_next_item*", "modeling/meshes/selecting/more_less.html#bpy-ops-mesh-select-next-item"),
|
||||
("bpy.ops.mesh.select_prev_item*", "modeling/meshes/selecting/more_less.html#bpy-ops-mesh-select-prev-item"),
|
||||
("bpy.ops.mesh.select_ungrouped*", "modeling/meshes/selecting/all_by_trait.html#bpy-ops-mesh-select-ungrouped"),
|
||||
("bpy.ops.node.delete_reconnect*", "interface/controls/nodes/editing.html#bpy-ops-node-delete-reconnect"),
|
||||
("bpy.ops.node.tree_path_parent*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-path-parent"),
|
||||
("bpy.ops.node.tree_socket_move*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-move"),
|
||||
("bpy.ops.object.duplicate_move*", "scene_layout/object/editing/duplicate.html#bpy-ops-object-duplicate-move"),
|
||||
("bpy.ops.object.hook_add_selob*", "modeling/meshes/editing/vertex/hooks.html#bpy-ops-object-hook-add-selob"),
|
||||
("bpy.ops.object.modifier_apply*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-apply"),
|
||||
("bpy.ops.object.select_by_type*", "scene_layout/object/selecting.html#bpy-ops-object-select-by-type"),
|
||||
("bpy.ops.object.select_grouped*", "scene_layout/object/selecting.html#bpy-ops-object-select-grouped"),
|
||||
("bpy.ops.object.select_pattern*", "scene_layout/object/selecting.html#bpy-ops-object-select-pattern"),
|
||||
("bpy.ops.outliner.id_operation*", "editors/outliner/editing.html#bpy-ops-outliner-id-operation"),
|
||||
("bpy.ops.paint.mask_flood_fill*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-paint-mask-flood-fill"),
|
||||
("bpy.ops.pose.quaternions_flip*", "animation/armatures/posing/editing/flip_quats.html#bpy-ops-pose-quaternions-flip"),
|
||||
("bpy.ops.pose.select_hierarchy*", "animation/armatures/posing/selecting.html#bpy-ops-pose-select-hierarchy"),
|
||||
("bpy.ops.pose.transforms_clear*", "animation/armatures/posing/editing/clear.html#bpy-ops-pose-transforms-clear"),
|
||||
("bpy.ops.poselib.copy_as_asset*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib-copy-as-asset"),
|
||||
("bpy.ops.preferences.copy_prev*", "editors/preferences/introduction.html#bpy-ops-preferences-copy-prev"),
|
||||
("bpy.ops.preferences.keyconfig*", "editors/preferences/keymap.html#bpy-ops-preferences-keyconfig"),
|
||||
("bpy.ops.screen.repeat_history*", "interface/undo_redo.html#bpy-ops-screen-repeat-history"),
|
||||
("bpy.ops.script.execute_preset*", "interface/window_system/tabs_panels.html#bpy-ops-script-execute-preset"),
|
||||
("bpy.ops.sculpt.face_sets_init*", "sculpt_paint/sculpting/editing/face_sets.html#bpy-ops-sculpt-face-sets-init"),
|
||||
("bpy.ops.sequencer.change_path*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-change-path"),
|
||||
("bpy.ops.sequencer.refresh_all*", "editors/video_sequencer/sequencer/navigating.html#bpy-ops-sequencer-refresh-all"),
|
||||
("bpy.ops.sequencer.select_less*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-less"),
|
||||
("bpy.ops.sequencer.select_more*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-more"),
|
||||
("bpy.ops.sequencer.select_side*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-side"),
|
||||
("bpy.ops.sequencer.swap_inputs*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-swap-inputs"),
|
||||
("bpy.ops.surface.primitive*add*", "modeling/surfaces/primitives.html#bpy-ops-surface-primitive-add"),
|
||||
("bpy.ops.text.resolve_conflict*", "editors/text_editor.html#bpy-ops-text-resolve-conflict"),
|
||||
("bpy.ops.transform.edge_crease*", "modeling/meshes/editing/edge/edge_data.html#bpy-ops-transform-edge-crease"),
|
||||
("bpy.ops.transform.skin_resize*", "modeling/meshes/editing/mesh/transform/skin_resize.html#bpy-ops-transform-skin-resize"),
|
||||
("bpy.ops.uv.seams_from_islands*", "modeling/meshes/uv/editing.html#bpy-ops-uv-seams-from-islands"),
|
||||
("bpy.ops.uv.shortest_path_pick*", "editors/uv/selecting.html#bpy-ops-uv-shortest-path-pick"),
|
||||
("bpy.ops.view3d.camera_to_view*", "editors/3dview/navigate/camera_view.html#bpy-ops-view3d-camera-to-view"),
|
||||
("bpy.types.bakesettings.margin*", "render/cycles/baking.html#bpy-types-bakesettings-margin"),
|
||||
("bpy.types.bakesettings.target*", "render/cycles/baking.html#bpy-types-bakesettings-target"),
|
||||
("bpy.types.brush.cloth_damping*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-damping"),
|
||||
("bpy.types.brush.deform_target*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-deform-target"),
|
||||
("bpy.types.brush.falloff_shape*", "sculpt_paint/brush/falloff.html#bpy-types-brush-falloff-shape"),
|
||||
("bpy.types.brush.icon_filepath*", "sculpt_paint/brush/brush.html#bpy-types-brush-icon-filepath"),
|
||||
("bpy.types.brush.stroke_method*", "sculpt_paint/brush/stroke.html#bpy-types-brush-stroke-method"),
|
||||
("bpy.types.brush.tip_roundness*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-tip-roundness"),
|
||||
("bpy.types.camera.display_size*", "render/cameras.html#bpy-types-camera-display-size"),
|
||||
("bpy.types.camera.sensor_width*", "render/cameras.html#bpy-types-camera-sensor-width"),
|
||||
("bpy.types.compositornodedblur*", "compositing/types/filter/directional_blur.html#bpy-types-compositornodedblur"),
|
||||
("bpy.types.compositornodegamma*", "compositing/types/color/gamma.html#bpy-types-compositornodegamma"),
|
||||
("bpy.types.compositornodeglare*", "compositing/types/filter/glare.html#bpy-types-compositornodeglare"),
|
||||
("bpy.types.compositornodegroup*", "compositing/types/groups.html#bpy-types-compositornodegroup"),
|
||||
("bpy.types.compositornodeimage*", "compositing/types/input/image.html#bpy-types-compositornodeimage"),
|
||||
("bpy.types.compositornodemapuv*", "compositing/types/distort/map_uv.html#bpy-types-compositornodemapuv"),
|
||||
("bpy.types.compositornodescale*", "compositing/types/distort/scale.html#bpy-types-compositornodescale"),
|
||||
("bpy.types.compositornodevalue*", "compositing/types/input/value.html#bpy-types-compositornodevalue"),
|
||||
("bpy.types.copyscaleconstraint*", "animation/constraints/transform/copy_scale.html#bpy-types-copyscaleconstraint"),
|
||||
("bpy.types.curve.path_duration*", "modeling/curves/properties/path_animation.html#bpy-types-curve-path-duration"),
|
||||
("bpy.types.curve.use_fill_caps*", "modeling/curves/properties/geometry.html#bpy-types-curve-use-fill-caps"),
|
||||
("bpy.types.curve.use_map_taper*", "modeling/curves/properties/geometry.html#bpy-types-curve-use-map-taper"),
|
||||
("bpy.types.cyclesworldsettings*", "render/cycles/world_settings.html#bpy-types-cyclesworldsettings"),
|
||||
("bpy.types.dashgpencilmodifier*", "grease_pencil/modifiers/generate/dash.html#bpy-types-dashgpencilmodifier"),
|
||||
("bpy.types.fieldsettings.noise*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-noise"),
|
||||
("bpy.types.fieldsettings.shape*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-shape"),
|
||||
("bpy.types.fluiddomainsettings*", "physics/fluid/type/domain/index.html#bpy-types-fluiddomainsettings"),
|
||||
("bpy.types.functionnodecompare*", "modeling/geometry_nodes/utilities/math/compare.html#bpy-types-functionnodecompare"),
|
||||
("bpy.types.geometrynodeinputid*", "modeling/geometry_nodes/geometry/read/id.html#bpy-types-geometrynodeinputid"),
|
||||
("bpy.types.geometrynoderaycast*", "modeling/geometry_nodes/geometry/sample/raycast.html#bpy-types-geometrynoderaycast"),
|
||||
("bpy.types.gpencillayer.parent*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-parent"),
|
||||
("bpy.types.hookgpencilmodifier*", "grease_pencil/modifiers/deform/hook.html#bpy-types-hookgpencilmodifier"),
|
||||
("bpy.types.imageformatsettings*", "files/media/image_formats.html#bpy-types-imageformatsettings"),
|
||||
("bpy.types.kinematicconstraint*", "animation/constraints/tracking/ik_solver.html#bpy-types-kinematicconstraint"),
|
||||
("bpy.types.material.line_color*", "render/freestyle/material.html#bpy-types-material-line-color"),
|
||||
("bpy.types.material.pass_index*", "render/materials/settings.html#bpy-types-material-pass-index"),
|
||||
("bpy.types.mesh.use_paint_mask*", "sculpt_paint/selection_visibility.html#bpy-types-mesh-use-paint-mask"),
|
||||
("bpy.types.object.display_type*", "scene_layout/object/properties/display.html#bpy-types-object-display-type"),
|
||||
("bpy.types.objectlineart.usage*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart-usage"),
|
||||
("bpy.types.paint.input_samples*", "sculpt_paint/brush/stroke.html#bpy-types-paint-input-samples"),
|
||||
("bpy.types.particledupliweight*", "physics/particles/emitter/vertex_groups.html#bpy-types-particledupliweight"),
|
||||
("bpy.types.posebone.bone_group*", "animation/armatures/bones/properties/relations.html#bpy-types-posebone-bone-group"),
|
||||
("bpy.types.poseboneconstraints*", "animation/armatures/posing/bone_constraints/index.html#bpy-types-poseboneconstraints"),
|
||||
("bpy.types.rigidbodyconstraint*", "physics/rigid_body/constraints/index.html#bpy-types-rigidbodyconstraint"),
|
||||
("bpy.types.rigifyarmaturelayer*", "addons/rigging/rigify/metarigs.html#bpy-types-rigifyarmaturelayer"),
|
||||
("bpy.types.scene.show_subframe*", "editors/timeline.html#bpy-types-scene-show-subframe"),
|
||||
("bpy.types.shadernodeaddshader*", "render/shader_nodes/shader/add.html#bpy-types-shadernodeaddshader"),
|
||||
("bpy.types.shadernodeattribute*", "render/shader_nodes/input/attribute.html#bpy-types-shadernodeattribute"),
|
||||
("bpy.types.shadernodeblackbody*", "render/shader_nodes/converter/blackbody.html#bpy-types-shadernodeblackbody"),
|
||||
("bpy.types.shadernodebsdfglass*", "render/shader_nodes/shader/glass.html#bpy-types-shadernodebsdfglass"),
|
||||
("bpy.types.shadernodelightpath*", "render/shader_nodes/input/light_path.html#bpy-types-shadernodelightpath"),
|
||||
("bpy.types.shadernodemixshader*", "render/shader_nodes/shader/mix.html#bpy-types-shadernodemixshader"),
|
||||
("bpy.types.shadernodenormalmap*", "render/shader_nodes/vector/normal_map.html#bpy-types-shadernodenormalmap"),
|
||||
("bpy.types.shadernodeoutputaov*", "render/shader_nodes/output/aov.html#bpy-types-shadernodeoutputaov"),
|
||||
("bpy.types.shadernodepointinfo*", "render/shader_nodes/input/point_info.html#bpy-types-shadernodepointinfo"),
|
||||
("bpy.types.shadernodewireframe*", "render/shader_nodes/input/wireframe.html#bpy-types-shadernodewireframe"),
|
||||
("bpy.types.shapekey.slider_max*", "animation/shape_keys/shape_keys_panel.html#bpy-types-shapekey-slider-max"),
|
||||
("bpy.types.shapekey.slider_min*", "animation/shape_keys/shape_keys_panel.html#bpy-types-shapekey-slider-min"),
|
||||
("bpy.types.spacesequenceeditor*", "editors/video_sequencer/index.html#bpy-types-spacesequenceeditor"),
|
||||
("bpy.types.spline.resolution_u*", "modeling/curves/properties/active_spline.html#bpy-types-spline-resolution-u"),
|
||||
("bpy.types.spline.use_bezier_u*", "modeling/curves/properties/active_spline.html#bpy-types-spline-use-bezier-u"),
|
||||
("bpy.types.spline.use_cyclic_u*", "modeling/curves/properties/active_spline.html#bpy-types-spline-use-cyclic-u"),
|
||||
("bpy.types.stretchtoconstraint*", "animation/constraints/tracking/stretch_to.html#bpy-types-stretchtoconstraint"),
|
||||
("bpy.types.texturenodecurvergb*", "editors/texture_node/types/color/rgb_curves.html#bpy-types-texturenodecurvergb"),
|
||||
("bpy.types.texturenodedistance*", "editors/texture_node/types/converter/distance.html#bpy-types-texturenodedistance"),
|
||||
("bpy.types.texturenodetexblend*", "editors/texture_node/types/textures/blend.html#bpy-types-texturenodetexblend"),
|
||||
("bpy.types.texturenodetexmagic*", "editors/texture_node/types/textures/magic.html#bpy-types-texturenodetexmagic"),
|
||||
("bpy.types.texturenodetexnoise*", "editors/texture_node/types/textures/noise.html#bpy-types-texturenodetexnoise"),
|
||||
("bpy.types.texturenodevaltonor*", "editors/texture_node/types/converter/value_to_normal.html#bpy-types-texturenodevaltonor"),
|
||||
("bpy.types.texturenodevaltorgb*", "editors/texture_node/types/converter/rgb_to_bw.html#bpy-types-texturenodevaltorgb"),
|
||||
("bpy.types.timegpencilmodifier*", "grease_pencil/modifiers/modify/time_offset.html#bpy-types-timegpencilmodifier"),
|
||||
("bpy.types.tintgpencilmodifier*", "grease_pencil/modifiers/color/tint.html#bpy-types-tintgpencilmodifier"),
|
||||
("bpy.types.transformconstraint*", "animation/constraints/transform/transformation.html#bpy-types-transformconstraint"),
|
||||
("bpy.types.triangulatemodifier*", "modeling/modifiers/generate/triangulate.html#bpy-types-triangulatemodifier"),
|
||||
("bpy.types.unitsettings.system*", "scene_layout/scene/properties.html#bpy-types-unitsettings-system"),
|
||||
("bpy.types.view3dshading.light*", "render/workbench/lighting.html#bpy-types-view3dshading-light"),
|
||||
("bpy.types.viewlayer.use_solid*", "render/layers/introduction.html#bpy-types-viewlayer-use-solid"),
|
||||
("bpy.types.volume.frame_offset*", "modeling/volumes/properties.html#bpy-types-volume-frame-offset"),
|
||||
("bpy.types.windowmanager.addon*", "editors/preferences/addons.html#bpy-types-windowmanager-addon"),
|
||||
("bpy.ops.anim.channels_delete*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-delete"),
|
||||
("bpy.ops.anim.channels_expand*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-expand"),
|
||||
("bpy.ops.anim.keyframe_delete*", "animation/keyframes/editing.html#bpy-ops-anim-keyframe-delete"),
|
||||
("bpy.ops.anim.keyframe_insert*", "animation/keyframes/editing.html#bpy-ops-anim-keyframe-insert"),
|
||||
("bpy.ops.armature.bone_layers*", "animation/armatures/bones/editing/change_layers.html#bpy-ops-armature-bone-layers"),
|
||||
("bpy.ops.armature.select_less*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-less"),
|
||||
("bpy.ops.armature.select_more*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-more"),
|
||||
("bpy.ops.asset.bundle_install*", "editors/asset_browser.html#bpy-ops-asset-bundle-install"),
|
||||
("bpy.ops.buttons.clear_filter*", "interface/controls/buttons/fields.html#bpy-ops-buttons-clear-filter"),
|
||||
("bpy.ops.buttons.context_menu*", "interface/controls/buttons/menus.html#bpy-ops-buttons-context-menu"),
|
||||
("bpy.ops.buttons.start_filter*", "interface/controls/buttons/fields.html#bpy-ops-buttons-start-filter"),
|
||||
("bpy.ops.clip.add_marker_move*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-add-marker-move"),
|
||||
("bpy.ops.clip.bundles_to_mesh*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-bundles-to-mesh"),
|
||||
("bpy.ops.clip.detect_features*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-detect-features"),
|
||||
("bpy.ops.console.autocomplete*", "editors/python_console.html#bpy-ops-console-autocomplete"),
|
||||
("bpy.ops.curve.dissolve_verts*", "modeling/curves/editing/curve.html#bpy-ops-curve-dissolve-verts"),
|
||||
("bpy.ops.curve.duplicate_move*", "modeling/curves/editing/curve.html#bpy-ops-curve-duplicate-move"),
|
||||
("bpy.ops.file.autopack_toggle*", "files/blend/packed_data.html#bpy-ops-file-autopack-toggle"),
|
||||
("bpy.ops.fluid.bake_particles*", "physics/fluid/type/domain/liquid/particles.html#bpy-ops-fluid-bake-particles"),
|
||||
("bpy.ops.fluid.free_particles*", "physics/fluid/type/domain/liquid/particles.html#bpy-ops-fluid-free-particles"),
|
||||
("bpy.ops.gpencil.extrude_move*", "grease_pencil/modes/edit/point_menu.html#bpy-ops-gpencil-extrude-move"),
|
||||
("bpy.ops.gpencil.select_first*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-first"),
|
||||
("bpy.ops.gpencil.stroke_merge*", "grease_pencil/modes/edit/point_menu.html#bpy-ops-gpencil-stroke-merge"),
|
||||
("bpy.ops.gpencil.stroke_split*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-stroke-split"),
|
||||
("bpy.ops.graph.duplicate_move*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-duplicate-move"),
|
||||
("bpy.ops.mask.handle_type_set*", "movie_clip/masking/editing.html#bpy-ops-mask-handle-type-set"),
|
||||
("bpy.ops.mask.hide_view_clear*", "movie_clip/masking/editing.html#bpy-ops-mask-hide-view-clear"),
|
||||
("bpy.ops.mask.shape_key_clear*", "movie_clip/masking/editing.html#bpy-ops-mask-shape-key-clear"),
|
||||
("bpy.ops.mball.hide_metaelems*", "modeling/metas/properties.html#bpy-ops-mball-hide-metaelems"),
|
||||
("bpy.ops.mesh.average_normals*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-average-normals"),
|
||||
("bpy.ops.mesh.delete_edgeloop*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-delete-edgeloop"),
|
||||
("bpy.ops.mesh.vertices_smooth*", "modeling/meshes/editing/vertex/smooth_vertices.html#bpy-ops-mesh-vertices-smooth"),
|
||||
("bpy.ops.nla.make_single_user*", "editors/nla/editing.html#bpy-ops-nla-make-single-user"),
|
||||
("bpy.ops.node.clipboard_paste*", "interface/controls/nodes/editing.html#bpy-ops-node-clipboard-paste"),
|
||||
("bpy.ops.node.node_copy_color*", "interface/controls/nodes/sidebar.html#bpy-ops-node-node-copy-color"),
|
||||
("bpy.ops.node.read_viewlayers*", "interface/controls/nodes/editing.html#bpy-ops-node-read-viewlayers"),
|
||||
("bpy.ops.node.tree_socket_add*", "interface/controls/nodes/groups.html#bpy-ops-node-tree-socket-add"),
|
||||
("bpy.ops.object.attribute_add*", "modeling/geometry_nodes/attributes_reference.html#bpy-ops-object-attribute-add"),
|
||||
("bpy.ops.object.data_transfer*", "scene_layout/object/editing/link_transfer/transfer_mesh_data.html#bpy-ops-object-data-transfer"),
|
||||
("bpy.ops.object.modifier_copy*", "modeling/modifiers/introduction.html#bpy-ops-object-modifier-copy"),
|
||||
("bpy.ops.object.select_camera*", "scene_layout/object/selecting.html#bpy-ops-object-select-camera"),
|
||||
("bpy.ops.object.select_linked*", "scene_layout/object/selecting.html#bpy-ops-object-select-linked"),
|
||||
("bpy.ops.object.select_mirror*", "scene_layout/object/selecting.html#bpy-ops-object-select-mirror"),
|
||||
("bpy.ops.object.select_random*", "scene_layout/object/selecting.html#bpy-ops-object-select-random"),
|
||||
("bpy.ops.object.shape_key_add*", "animation/shape_keys/shape_keys_panel.html#bpy-ops-object-shape-key-add"),
|
||||
("bpy.ops.object.transfer_mode*", "editors/3dview/modes.html#bpy-ops-object-transfer-mode"),
|
||||
("bpy.ops.outliner.show_active*", "editors/outliner/editing.html#bpy-ops-outliner-show-active"),
|
||||
("bpy.ops.paint.add_simple_uvs*", "sculpt_paint/texture_paint/tool_settings/texture_slots.html#bpy-ops-paint-add-simple-uvs"),
|
||||
("bpy.ops.pose.paths_calculate*", "animation/motion_paths.html#bpy-ops-pose-paths-calculate"),
|
||||
("bpy.ops.pose.rigify_generate*", "addons/rigging/rigify/basics.html#bpy-ops-pose-rigify-generate"),
|
||||
("bpy.ops.preferences.autoexec*", "editors/preferences/save_load.html#bpy-ops-preferences-autoexec"),
|
||||
("bpy.ops.scene.view_layer_add*", "render/layers/introduction.html#bpy-ops-scene-view-layer-add"),
|
||||
("bpy.ops.sculpt.face_set_edit*", "sculpt_paint/sculpting/editing/face_sets.html#bpy-ops-sculpt-face-set-edit"),
|
||||
("bpy.ops.sequencer.gap_insert*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-gap-insert"),
|
||||
("bpy.ops.sequencer.gap_remove*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-gap-remove"),
|
||||
("bpy.ops.sequencer.rendersize*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-rendersize"),
|
||||
("bpy.ops.sequencer.select_all*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-all"),
|
||||
("bpy.ops.sequencer.select_box*", "video_editing/edit/montage/selecting.html#bpy-ops-sequencer-select-box"),
|
||||
("bpy.ops.sound.bake_animation*", "scene_layout/scene/properties.html#bpy-ops-sound-bake-animation"),
|
||||
("bpy.ops.transform.edge_slide*", "modeling/meshes/editing/edge/edge_slide.html#bpy-ops-transform-edge-slide"),
|
||||
("bpy.ops.transform.vert_slide*", "modeling/meshes/editing/vertex/slide_vertices.html#bpy-ops-transform-vert-slide"),
|
||||
("bpy.ops.ui.list_start_filter*", "interface/controls/templates/list_view.html#bpy-ops-ui-list-start-filter"),
|
||||
("bpy.ops.uv.project_from_view*", "modeling/meshes/editing/uv.html#bpy-ops-uv-project-from-view"),
|
||||
("bpy.ops.view3d.render_border*", "editors/3dview/navigate/regions.html#bpy-ops-view3d-render-border"),
|
||||
("bpy.ops.view3d.view_selected*", "editors/3dview/navigate/navigation.html#bpy-ops-view3d-view-selected"),
|
||||
("bpy.ops.wm.memory_statistics*", "advanced/operators.html#bpy-ops-wm-memory-statistics"),
|
||||
("bpy.ops.wm.recover_auto_save*", "files/blend/open_save.html#bpy-ops-wm-recover-auto-save"),
|
||||
("bpy.types.adjustmentsequence*", "video_editing/edit/montage/strips/adjustment.html#bpy-types-adjustmentsequence"),
|
||||
("bpy.types.alphaundersequence*", "video_editing/edit/montage/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaundersequence"),
|
||||
("bpy.types.animvizmotionpaths*", "animation/motion_paths.html#bpy-types-animvizmotionpaths"),
|
||||
("bpy.types.armature.show_axes*", "animation/armatures/properties/display.html#bpy-types-armature-show-axes"),
|
||||
("bpy.types.armatureconstraint*", "animation/constraints/relationship/armature.html#bpy-types-armatureconstraint"),
|
||||
("bpy.types.brush.dash_samples*", "sculpt_paint/brush/stroke.html#bpy-types-brush-dash-samples"),
|
||||
("bpy.types.brush.sculpt_plane*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-sculpt-plane"),
|
||||
("bpy.types.clothsettings.mass*", "physics/cloth/settings/physical_properties.html#bpy-types-clothsettings-mass"),
|
||||
("bpy.types.compositornodeblur*", "compositing/types/filter/blur_node.html#bpy-types-compositornodeblur"),
|
||||
("bpy.types.compositornodecrop*", "compositing/types/distort/crop.html#bpy-types-compositornodecrop"),
|
||||
("bpy.types.compositornodeflip*", "compositing/types/distort/flip.html#bpy-types-compositornodeflip"),
|
||||
("bpy.types.compositornodemask*", "compositing/types/input/mask.html#bpy-types-compositornodemask"),
|
||||
("bpy.types.compositornodemath*", "compositing/types/converter/math.html#bpy-types-compositornodemath"),
|
||||
("bpy.types.compositornodetime*", "compositing/types/input/time_curve.html#bpy-types-compositornodetime"),
|
||||
("bpy.types.constraint.enabled*", "animation/constraints/interface/header.html#bpy-types-constraint-enabled"),
|
||||
("bpy.types.curve.bevel_object*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-object"),
|
||||
("bpy.types.curve.resolution_u*", "modeling/curves/properties/shape.html#bpy-types-curve-resolution-u"),
|
||||
("bpy.types.curve.resolution_v*", "modeling/surfaces/properties/shape.html#bpy-types-curve-resolution-v"),
|
||||
("bpy.types.curve.taper_object*", "modeling/curves/properties/geometry.html#bpy-types-curve-taper-object"),
|
||||
("bpy.types.curve.twist_smooth*", "modeling/curves/properties/shape.html#bpy-types-curve-twist-smooth"),
|
||||
("bpy.types.curvepaintsettings*", "modeling/curves/tools/draw.html#bpy-types-curvepaintsettings"),
|
||||
("bpy.types.fcurve.array_index*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-array-index"),
|
||||
("bpy.types.fieldsettings.flow*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-flow"),
|
||||
("bpy.types.fieldsettings.seed*", "physics/forces/force_fields/introduction.html#bpy-types-fieldsettings-seed"),
|
||||
("bpy.types.fieldsettings.size*", "physics/forces/force_fields/types/turbulence.html#bpy-types-fieldsettings-size"),
|
||||
("bpy.types.fileselectidfilter*", "editors/file_browser.html#bpy-types-fileselectidfilter"),
|
||||
("bpy.types.fmodifiergenerator*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiergenerator"),
|
||||
("bpy.types.freestylelinestyle*", "render/freestyle/view_layer/line_style/index.html#bpy-types-freestylelinestyle"),
|
||||
("bpy.types.gammacrosssequence*", "video_editing/edit/montage/strips/transitions/gamma_cross.html#bpy-types-gammacrosssequence"),
|
||||
("bpy.types.geometrynodepoints*", "modeling/geometry_nodes/point/points.html#bpy-types-geometrynodepoints"),
|
||||
("bpy.types.geometrynodeswitch*", "modeling/geometry_nodes/utilities/switch.html#bpy-types-geometrynodeswitch"),
|
||||
("bpy.types.geometrynodeviewer*", "modeling/geometry_nodes/output/viewer.html#bpy-types-geometrynodeviewer"),
|
||||
("bpy.types.gpencillayer.scale*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-scale"),
|
||||
("bpy.types.gpencilsculptguide*", "grease_pencil/modes/draw/guides.html#bpy-types-gpencilsculptguide"),
|
||||
("bpy.types.huecorrectmodifier*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-huecorrectmodifier"),
|
||||
("bpy.types.image.is_multiview*", "editors/image/image_settings.html#bpy-types-image-is-multiview"),
|
||||
("bpy.types.imagepaint.stencil*", "sculpt_paint/texture_paint/tool_settings/mask.html#bpy-types-imagepaint-stencil"),
|
||||
("bpy.types.material.roughness*", "render/materials/settings.html#bpy-types-material-roughness"),
|
||||
("bpy.types.meshdeformmodifier*", "modeling/modifiers/deform/mesh_deform.html#bpy-types-meshdeformmodifier"),
|
||||
("bpy.types.movietrackingtrack*", "movie_clip/tracking/clip/sidebar/track/index.html#bpy-types-movietrackingtrack"),
|
||||
("bpy.types.nodeoutputfileslot*", "compositing/types/output/file.html#bpy-types-nodeoutputfileslot"),
|
||||
("bpy.types.normaleditmodifier*", "modeling/modifiers/modify/normal_edit.html#bpy-types-normaleditmodifier"),
|
||||
("bpy.types.object.empty_image*", "modeling/empties.html#bpy-types-object-empty-image"),
|
||||
("bpy.types.object.hide_render*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-render"),
|
||||
("bpy.types.object.hide_select*", "scene_layout/object/properties/visibility.html#bpy-types-object-hide-select"),
|
||||
("bpy.types.object.parent_type*", "scene_layout/object/properties/relations.html#bpy-types-object-parent-type"),
|
||||
("bpy.types.object.show_bounds*", "scene_layout/object/properties/display.html#bpy-types-object-show-bounds"),
|
||||
("bpy.types.rendersettings.fps*", "render/output/properties/format.html#bpy-types-rendersettings-fps"),
|
||||
("bpy.types.scene.audio_volume*", "scene_layout/scene/properties.html#bpy-types-scene-audio-volume"),
|
||||
("bpy.types.sculpt.detail_size*", "sculpt_paint/sculpting/tool_settings/dyntopo.html#bpy-types-sculpt-detail-size"),
|
||||
("bpy.types.shadernodebsdfhair*", "render/shader_nodes/shader/hair.html#bpy-types-shadernodebsdfhair"),
|
||||
("bpy.types.shadernodebsdftoon*", "render/shader_nodes/shader/toon.html#bpy-types-shadernodebsdftoon"),
|
||||
("bpy.types.shadernodeemission*", "render/shader_nodes/shader/emission.html#bpy-types-shadernodeemission"),
|
||||
("bpy.types.shadernodehairinfo*", "render/shader_nodes/input/hair_info.html#bpy-types-shadernodehairinfo"),
|
||||
("bpy.types.shadernodemaprange*", "render/shader_nodes/converter/map_range.html#bpy-types-shadernodemaprange"),
|
||||
("bpy.types.shadernodergbcurve*", "modeling/geometry_nodes/utilities/color/rgb_curves.html#bpy-types-shadernodergbcurve"),
|
||||
("bpy.types.shadernodetexbrick*", "render/shader_nodes/textures/brick.html#bpy-types-shadernodetexbrick"),
|
||||
("bpy.types.shadernodetexcoord*", "render/shader_nodes/input/texture_coordinate.html#bpy-types-shadernodetexcoord"),
|
||||
("bpy.types.shadernodeteximage*", "render/shader_nodes/textures/image.html#bpy-types-shadernodeteximage"),
|
||||
("bpy.types.shadernodetexmagic*", "render/shader_nodes/textures/magic.html#bpy-types-shadernodetexmagic"),
|
||||
("bpy.types.shadernodetexnoise*", "render/shader_nodes/textures/noise.html#bpy-types-shadernodetexnoise"),
|
||||
("bpy.types.shrinkwrapmodifier*", "modeling/modifiers/deform/shrinkwrap.html#bpy-types-shrinkwrapmodifier"),
|
||||
("bpy.types.spaceview3d.camera*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-camera"),
|
||||
("bpy.types.splineikconstraint*", "animation/constraints/tracking/spline_ik.html#bpy-types-splineikconstraint"),
|
||||
("bpy.types.texturenodechecker*", "editors/texture_node/types/patterns/checker.html#bpy-types-texturenodechecker"),
|
||||
("bpy.types.texturenodetexture*", "editors/texture_node/types/input/texture.html#bpy-types-texturenodetexture"),
|
||||
("bpy.types.texturenodetexwood*", "editors/texture_node/types/textures/wood.html#bpy-types-texturenodetexwood"),
|
||||
("bpy.types.textureslot.offset*", "sculpt_paint/brush/texture.html#bpy-types-textureslot-offset"),
|
||||
("bpy.types.view3dshading.type*", "editors/3dview/display/shading.html#bpy-types-view3dshading-type"),
|
||||
("bpy.types.volume.frame_start*", "modeling/volumes/properties.html#bpy-types-volume-frame-start"),
|
||||
("bpy.types.volume.is_sequence*", "modeling/volumes/properties.html#bpy-types-volume-is-sequence"),
|
||||
("bpy.types.volumerender.space*", "modeling/volumes/properties.html#bpy-types-volumerender-space"),
|
||||
("bpy.ops.anim.channels_group*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-group"),
|
||||
("bpy.ops.anim.keyframe_clear*", "animation/keyframes/editing.html#bpy-ops-anim-keyframe-clear"),
|
||||
("bpy.ops.armature.flip_names*", "animation/armatures/bones/editing/naming.html#bpy-ops-armature-flip-names"),
|
||||
("bpy.ops.armature.parent_set*", "animation/armatures/bones/editing/parenting.html#bpy-ops-armature-parent-set"),
|
||||
("bpy.ops.armature.select_all*", "animation/armatures/bones/selecting.html#bpy-ops-armature-select-all"),
|
||||
("bpy.ops.armature.symmetrize*", "animation/armatures/bones/editing/symmetrize.html#bpy-ops-armature-symmetrize"),
|
||||
("bpy.ops.clip.average_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-average-tracks"),
|
||||
("bpy.ops.clip.refine_markers*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-refine-markers"),
|
||||
("bpy.ops.clip.select_grouped*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-grouped"),
|
||||
("bpy.ops.clip.track_to_empty*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-track-to-empty"),
|
||||
("bpy.ops.curve.cyclic_toggle*", "modeling/curves/editing/curve.html#bpy-ops-curve-cyclic-toggle"),
|
||||
("bpy.ops.curve.primitive*add*", "modeling/curves/primitives.html#bpy-ops-curve-primitive-add"),
|
||||
("bpy.ops.curve.smooth_radius*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-radius"),
|
||||
("bpy.ops.curve.smooth_weight*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-weight"),
|
||||
("bpy.ops.file.pack_libraries*", "files/blend/packed_data.html#bpy-ops-file-pack-libraries"),
|
||||
("bpy.ops.font.change_spacing*", "modeling/texts/editing.html#bpy-ops-font-change-spacing"),
|
||||
("bpy.ops.gpencil.interpolate*", "grease_pencil/modes/draw/tools/interpolate.html#bpy-ops-gpencil-interpolate"),
|
||||
("bpy.ops.gpencil.layer_merge*", "grease_pencil/properties/layers.html#bpy-ops-gpencil-layer-merge"),
|
||||
("bpy.ops.gpencil.select_last*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-last"),
|
||||
("bpy.ops.gpencil.select_less*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-less"),
|
||||
("bpy.ops.gpencil.select_more*", "grease_pencil/selecting.html#bpy-ops-gpencil-select-more"),
|
||||
("bpy.ops.gpencil.stroke_flip*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-flip"),
|
||||
("bpy.ops.gpencil.stroke_join*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-join"),
|
||||
("bpy.ops.gpencil.stroke_trim*", "grease_pencil/modes/edit/stroke_menu.html#bpy-ops-gpencil-stroke-trim"),
|
||||
("bpy.ops.gpencil.trace_image*", "grease_pencil/modes/object/trace_image.html#bpy-ops-gpencil-trace-image"),
|
||||
("bpy.ops.graph.fmodifier_add*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-fmodifier-add"),
|
||||
("bpy.ops.image.external_edit*", "editors/image/editing.html#bpy-ops-image-external-edit"),
|
||||
("bpy.ops.mesh.colors_reverse*", "modeling/meshes/editing/face/face_data.html#bpy-ops-mesh-colors-reverse"),
|
||||
("bpy.ops.mesh.dissolve_edges*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-dissolve-edges"),
|
||||
("bpy.ops.mesh.dissolve_faces*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-dissolve-faces"),
|
||||
("bpy.ops.mesh.dissolve_verts*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-dissolve-verts"),
|
||||
("bpy.ops.mesh.duplicate_move*", "modeling/meshes/editing/mesh/duplicate.html#bpy-ops-mesh-duplicate-move"),
|
||||
("bpy.ops.mesh.extrude_region*", "modeling/meshes/tools/extrude_region.html#bpy-ops-mesh-extrude-region"),
|
||||
("bpy.ops.mesh.extrude_repeat*", "modeling/meshes/editing/mesh/extrude.html#bpy-ops-mesh-extrude-repeat"),
|
||||
("bpy.ops.mesh.loop_to_region*", "modeling/meshes/selecting/loops.html#bpy-ops-mesh-loop-to-region"),
|
||||
("bpy.ops.mesh.region_to_loop*", "modeling/meshes/selecting/loops.html#bpy-ops-mesh-region-to-loop"),
|
||||
("bpy.ops.mesh.remove_doubles*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-remove-doubles"),
|
||||
("bpy.ops.mesh.select_similar*", "modeling/meshes/selecting/similar.html#bpy-ops-mesh-select-similar"),
|
||||
("bpy.ops.mesh.smooth_normals*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-smooth-normals"),
|
||||
("bpy.ops.nla.action_pushdown*", "editors/nla/tracks.html#bpy-ops-nla-action-pushdown"),
|
||||
("bpy.ops.nla.tweakmode_enter*", "editors/nla/editing.html#bpy-ops-nla-tweakmode-enter"),
|
||||
("bpy.ops.node.clipboard_copy*", "interface/controls/nodes/editing.html#bpy-ops-node-clipboard-copy"),
|
||||
("bpy.ops.node.duplicate_move*", "interface/controls/nodes/editing.html#bpy-ops-node-duplicate-move"),
|
||||
("bpy.ops.node.options_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-options-toggle"),
|
||||
("bpy.ops.node.preview_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-preview-toggle"),
|
||||
("bpy.ops.object.origin_clear*", "scene_layout/object/editing/clear.html#bpy-ops-object-origin-clear"),
|
||||
("bpy.ops.object.parent_clear*", "scene_layout/object/editing/parent.html#bpy-ops-object-parent-clear"),
|
||||
("bpy.ops.object.paths_update*", "animation/motion_paths.html#bpy-ops-object-paths-update"),
|
||||
("bpy.ops.object.shade_smooth*", "scene_layout/object/editing/shading.html#bpy-ops-object-shade-smooth"),
|
||||
("bpy.ops.object.voxel_remesh*", "modeling/meshes/retopology.html#bpy-ops-object-voxel-remesh"),
|
||||
("bpy.ops.pose.armature_apply*", "animation/armatures/posing/editing/apply.html#bpy-ops-pose-armature-apply"),
|
||||
("bpy.ops.pose.group_deselect*", "animation/armatures/properties/bone_groups.html#bpy-ops-pose-group-deselect"),
|
||||
("bpy.ops.pose.group_unassign*", "animation/armatures/properties/bone_groups.html#bpy-ops-pose-group-unassign"),
|
||||
("bpy.ops.pose.select_grouped*", "animation/armatures/posing/selecting.html#bpy-ops-pose-select-grouped"),
|
||||
("bpy.ops.preferences.keyitem*", "editors/preferences/keymap.html#bpy-ops-preferences-keyitem"),
|
||||
("bpy.ops.screen.area_options*", "interface/window_system/areas.html#bpy-ops-screen-area-options"),
|
||||
("bpy.ops.screen.region_blend*", "interface/window_system/regions.html#bpy-ops-screen-region-blend"),
|
||||
("bpy.ops.screen.region_scale*", "interface/window_system/regions.html#bpy-ops-screen-region-scale"),
|
||||
("bpy.ops.sequencer.swap_data*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-swap-data"),
|
||||
("bpy.ops.transform.push_pull*", "modeling/meshes/editing/mesh/transform/push_pull.html#bpy-ops-transform-push-pull"),
|
||||
("bpy.ops.transform.seq_slide*", "video_editing/edit/montage/editing.html#bpy-ops-transform-seq-slide"),
|
||||
("bpy.ops.transform.trackball*", "scene_layout/object/editing/transform/rotate.html#bpy-ops-transform-trackball"),
|
||||
("bpy.ops.transform.transform*", "scene_layout/object/editing/transform/align_transform_orientation.html#bpy-ops-transform-transform"),
|
||||
("bpy.ops.transform.translate*", "scene_layout/object/editing/transform/move.html#bpy-ops-transform-translate"),
|
||||
("bpy.ops.ui.eyedropper_color*", "interface/controls/templates/color_picker.html#bpy-ops-ui-eyedropper-color"),
|
||||
("bpy.ops.uv.cylinder_project*", "modeling/meshes/editing/uv.html#bpy-ops-uv-cylinder-project"),
|
||||
("bpy.ops.uv.minimize_stretch*", "modeling/meshes/uv/editing.html#bpy-ops-uv-minimize-stretch"),
|
||||
("bpy.ops.uv.select_edge_ring*", "editors/uv/selecting.html#bpy-ops-uv-select-edge-ring"),
|
||||
("bpy.ops.wm.save_as_mainfile*", "files/blend/open_save.html#bpy-ops-wm-save-as-mainfile"),
|
||||
("bpy.types.action.use_cyclic*", "animation/actions.html#bpy-types-action-use-cyclic"),
|
||||
("bpy.types.alphaoversequence*", "video_editing/edit/montage/strips/effects/alpha_over_under_overdrop.html#bpy-types-alphaoversequence"),
|
||||
("bpy.types.armatureeditbones*", "animation/armatures/bones/editing/index.html#bpy-types-armatureeditbones"),
|
||||
("bpy.types.brush.jitter_unit*", "sculpt_paint/brush/stroke.html#bpy-types-brush-jitter-unit"),
|
||||
("bpy.types.brush.pose_offset*", "sculpt_paint/sculpting/tools/pose.html#bpy-types-brush-pose-offset"),
|
||||
("bpy.types.brush.rake_factor*", "sculpt_paint/sculpting/tools/snake_hook.html#bpy-types-brush-rake-factor"),
|
||||
("bpy.types.camera.sensor_fit*", "render/cameras.html#bpy-types-camera-sensor-fit"),
|
||||
("bpy.types.cameradofsettings*", "render/cameras.html#bpy-types-cameradofsettings"),
|
||||
("bpy.types.childofconstraint*", "animation/constraints/relationship/child_of.html#bpy-types-childofconstraint"),
|
||||
("bpy.types.clamptoconstraint*", "animation/constraints/tracking/clamp_to.html#bpy-types-clamptoconstraint"),
|
||||
("bpy.types.collisionmodifier*", "physics/collision.html#bpy-types-collisionmodifier"),
|
||||
("bpy.types.collisionsettings*", "physics/collision.html#bpy-types-collisionsettings"),
|
||||
("bpy.types.compositornodergb*", "compositing/types/input/rgb.html#bpy-types-compositornodergb"),
|
||||
("bpy.types.curve.bevel_depth*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel-depth"),
|
||||
("bpy.types.curve.use_stretch*", "modeling/curves/properties/shape.html#bpy-types-curve-use-stretch"),
|
||||
("bpy.types.edgesplitmodifier*", "modeling/modifiers/generate/edge_split.html#bpy-types-edgesplitmodifier"),
|
||||
("bpy.types.fcurve.color_mode*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-color-mode"),
|
||||
("bpy.types.fluidflowsettings*", "physics/fluid/type/flow.html#bpy-types-fluidflowsettings"),
|
||||
("bpy.types.fmodifierenvelope*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierenvelope"),
|
||||
("bpy.types.freestylesettings*", "render/freestyle/view_layer/freestyle.html#bpy-types-freestylesettings"),
|
||||
("bpy.types.geometrynodegroup*", "modeling/geometry_nodes/group.html#bpy-types-geometrynodegroup"),
|
||||
("bpy.types.geometrynodesetid*", "modeling/geometry_nodes/geometry/write/set_id.html#bpy-types-geometrynodesetid"),
|
||||
("bpy.types.gpencillayer.hide*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-hide"),
|
||||
("bpy.types.gpencillayer.lock*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer-lock"),
|
||||
("bpy.types.imagepaint.dither*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-types-imagepaint-dither"),
|
||||
("bpy.types.material.metallic*", "render/materials/settings.html#bpy-types-material-metallic"),
|
||||
("bpy.types.materialslot.link*", "render/materials/assignment.html#bpy-types-materialslot-link"),
|
||||
("bpy.types.mesh.texture_mesh*", "modeling/meshes/uv/uv_texture_spaces.html#bpy-types-mesh-texture-mesh"),
|
||||
("bpy.types.mesh.use_mirror_x*", "modeling/meshes/tools/tool_settings.html#bpy-types-mesh-use-mirror-x"),
|
||||
("bpy.types.mesh.use_mirror_y*", "modeling/meshes/tools/tool_settings.html#bpy-types-mesh-use-mirror-y"),
|
||||
("bpy.types.mesh.use_mirror_z*", "modeling/meshes/tools/tool_settings.html#bpy-types-mesh-use-mirror-z"),
|
||||
("bpy.types.meshcachemodifier*", "modeling/modifiers/modify/mesh_cache.html#bpy-types-meshcachemodifier"),
|
||||
("bpy.types.movieclipsequence*", "video_editing/edit/montage/strips/clip.html#bpy-types-movieclipsequence"),
|
||||
("bpy.types.object.dimensions*", "scene_layout/object/properties/transforms.html#bpy-types-object-dimensions"),
|
||||
("bpy.types.object.is_holdout*", "scene_layout/object/properties/visibility.html#bpy-types-object-is-holdout"),
|
||||
("bpy.types.object.lightgroup*", "render/cycles/object_settings/object_data.html#bpy-types-object-lightgroup"),
|
||||
("bpy.types.object.pass_index*", "scene_layout/object/properties/relations.html#bpy-types-object-pass-index"),
|
||||
("bpy.types.object.track_axis*", "scene_layout/object/properties/relations.html#bpy-types-object-track-axis"),
|
||||
("bpy.types.pose.use_mirror_x*", "animation/armatures/posing/tool_settings.html#bpy-types-pose-use-mirror-x"),
|
||||
("bpy.types.preferencessystem*", "editors/preferences/system.html#bpy-types-preferencessystem"),
|
||||
("bpy.types.scene.active_clip*", "scene_layout/scene/properties.html#bpy-types-scene-active-clip"),
|
||||
("bpy.types.scene.frame_start*", "render/output/properties/frame_range.html#bpy-types-scene-frame-start"),
|
||||
("bpy.types.sceneeevee.shadow*", "render/eevee/render_settings/shadows.html#bpy-types-sceneeevee-shadow"),
|
||||
("bpy.types.screen.use_follow*", "editors/timeline.html#bpy-types-screen-use-follow"),
|
||||
("bpy.types.sequencetransform*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequencetransform"),
|
||||
("bpy.types.shadernodefresnel*", "render/shader_nodes/input/fresnel.html#bpy-types-shadernodefresnel"),
|
||||
("bpy.types.shadernodeholdout*", "render/shader_nodes/shader/holdout.html#bpy-types-shadernodeholdout"),
|
||||
("bpy.types.shadernodemapping*", "render/shader_nodes/vector/mapping.html#bpy-types-shadernodemapping"),
|
||||
("bpy.types.shadernodergbtobw*", "render/shader_nodes/converter/rgb_to_bw.html#bpy-types-shadernodergbtobw"),
|
||||
("bpy.types.shadernodetangent*", "render/shader_nodes/input/tangent.html#bpy-types-shadernodetangent"),
|
||||
("bpy.types.shadernodetexwave*", "render/shader_nodes/textures/wave.html#bpy-types-shadernodetexwave"),
|
||||
("bpy.types.soundsequence.pan*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-soundsequence-pan"),
|
||||
("bpy.types.spline.use_smooth*", "modeling/curves/properties/active_spline.html#bpy-types-spline-use-smooth"),
|
||||
("bpy.types.texturenodebricks*", "editors/texture_node/types/patterns/bricks.html#bpy-types-texturenodebricks"),
|
||||
("bpy.types.texturenodemixrgb*", "editors/texture_node/types/color/mix_rgb.html#bpy-types-texturenodemixrgb"),
|
||||
("bpy.types.texturenodeoutput*", "editors/texture_node/types/output/output.html#bpy-types-texturenodeoutput"),
|
||||
("bpy.types.texturenoderotate*", "editors/texture_node/types/distort/rotate.html#bpy-types-texturenoderotate"),
|
||||
("bpy.types.texturenodeviewer*", "editors/texture_node/types/output/viewer.html#bpy-types-texturenodeviewer"),
|
||||
("bpy.types.textureslot.scale*", "sculpt_paint/brush/texture.html#bpy-types-textureslot-scale"),
|
||||
("bpy.types.tracktoconstraint*", "animation/constraints/tracking/track_to.html#bpy-types-tracktoconstraint"),
|
||||
("bpy.types.transformsequence*", "video_editing/edit/montage/strips/effects/transform.html#bpy-types-transformsequence"),
|
||||
("bpy.types.uvprojectmodifier*", "modeling/modifiers/modify/uv_project.html#bpy-types-uvprojectmodifier"),
|
||||
("bpy.types.viewlayer.samples*", "render/layers/introduction.html#bpy-types-viewlayer-samples"),
|
||||
("bpy.types.viewlayer.use_sky*", "render/layers/introduction.html#bpy-types-viewlayer-use-sky"),
|
||||
("bpy.types.wireframemodifier*", "modeling/modifiers/generate/wireframe.html#bpy-types-wireframemodifier"),
|
||||
("bpy.types.worldmistsettings*", "render/cycles/world_settings.html#bpy-types-worldmistsettings"),
|
||||
("bpy.ops.anim.channels_move*", "editors/graph_editor/channels.html#bpy-ops-anim-channels-move"),
|
||||
("bpy.ops.armature.subdivide*", "animation/armatures/bones/editing/subdivide.html#bpy-ops-armature-subdivide"),
|
||||
("bpy.ops.brush.curve_preset*", "sculpt_paint/brush/falloff.html#bpy-ops-brush-curve-preset"),
|
||||
("bpy.ops.buttons.toggle_pin*", "editors/properties_editor.html#bpy-ops-buttons-toggle-pin"),
|
||||
("bpy.ops.clip.delete_marker*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-delete-marker"),
|
||||
("bpy.ops.clip.filter_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-filter-tracks"),
|
||||
("bpy.ops.clip.select_circle*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-circle"),
|
||||
("bpy.ops.clip.track_markers*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-track-markers"),
|
||||
("bpy.ops.curve.extrude_move*", "modeling/curves/editing/control_points.html#bpy-ops-curve-extrude-move"),
|
||||
("bpy.ops.curve.make_segment*", "modeling/curves/editing/control_points.html#bpy-ops-curve-make-segment"),
|
||||
("bpy.ops.file.directory_new*", "editors/file_browser.html#bpy-ops-file-directory-new"),
|
||||
("bpy.ops.graph.euler_filter*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-euler-filter"),
|
||||
("bpy.ops.marker.camera_bind*", "animation/markers.html#bpy-ops-marker-camera-bind"),
|
||||
("bpy.ops.mask.cyclic_toggle*", "movie_clip/masking/editing.html#bpy-ops-mask-cyclic-toggle"),
|
||||
("bpy.ops.mask.hide_view_set*", "movie_clip/masking/editing.html#bpy-ops-mask-hide-view-set"),
|
||||
("bpy.ops.mask.paste_splines*", "movie_clip/masking/editing.html#bpy-ops-mask-paste-splines"),
|
||||
("bpy.ops.mask.select_circle*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-circle"),
|
||||
("bpy.ops.mask.select_linked*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-linked"),
|
||||
("bpy.ops.mesh.beautify_fill*", "modeling/meshes/editing/face/beautify_faces.html#bpy-ops-mesh-beautify-fill"),
|
||||
("bpy.ops.mesh.colors_rotate*", "modeling/meshes/editing/face/face_data.html#bpy-ops-mesh-colors-rotate"),
|
||||
("bpy.ops.mesh.edge_collapse*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-edge-collapse"),
|
||||
("bpy.ops.mesh.edge_face_add*", "modeling/meshes/editing/vertex/make_face_edge.html#bpy-ops-mesh-edge-face-add"),
|
||||
("bpy.ops.mesh.extrude_indiv*", "modeling/meshes/editing/face/extrude_faces.html#bpy-ops-mesh-extrude-indiv"),
|
||||
("bpy.ops.mesh.knife_project*", "modeling/meshes/editing/mesh/knife_project.html#bpy-ops-mesh-knife-project"),
|
||||
("bpy.ops.mesh.loopcut_slide*", "modeling/meshes/editing/edge/loopcut_slide.html#bpy-ops-mesh-loopcut-slide"),
|
||||
("bpy.ops.mesh.merge_normals*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-merge-normals"),
|
||||
("bpy.ops.mesh.normals_tools*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-normals-tools"),
|
||||
("bpy.ops.mesh.point_normals*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-point-normals"),
|
||||
("bpy.ops.mesh.rip_edge_move*", "modeling/meshes/editing/vertex/rip_vertices_extend.html#bpy-ops-mesh-rip-edge-move"),
|
||||
("bpy.ops.mesh.select_linked*", "modeling/meshes/selecting/linked.html#bpy-ops-mesh-select-linked"),
|
||||
("bpy.ops.mesh.select_mirror*", "modeling/meshes/selecting/mirror.html#bpy-ops-mesh-select-mirror"),
|
||||
("bpy.ops.mesh.select_random*", "modeling/meshes/selecting/random.html#bpy-ops-mesh-select-random"),
|
||||
("bpy.ops.mesh.sort_elements*", "modeling/meshes/editing/mesh/sort_elements.html#bpy-ops-mesh-sort-elements"),
|
||||
("bpy.ops.mesh.split_normals*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-split-normals"),
|
||||
("bpy.ops.mesh.symmetry_snap*", "modeling/meshes/editing/mesh/snap_symmetry.html#bpy-ops-mesh-symmetry-snap"),
|
||||
("bpy.ops.node.group_ungroup*", "interface/controls/nodes/groups.html#bpy-ops-node-group-ungroup"),
|
||||
("bpy.ops.object.gpencil_add*", "grease_pencil/primitives.html#bpy-ops-object-gpencil-add"),
|
||||
("bpy.ops.object.join_shapes*", "animation/shape_keys/shape_keys_panel.html#bpy-ops-object-join-shapes"),
|
||||
("bpy.ops.object.paths_clear*", "animation/motion_paths.html#bpy-ops-object-paths-clear"),
|
||||
("bpy.ops.object.select_less*", "scene_layout/object/selecting.html#bpy-ops-object-select-less"),
|
||||
("bpy.ops.object.select_more*", "scene_layout/object/selecting.html#bpy-ops-object-select-more"),
|
||||
("bpy.ops.object.track_clear*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-track-clear"),
|
||||
("bpy.ops.pose.select_linked*", "animation/armatures/posing/selecting.html#bpy-ops-pose-select-linked"),
|
||||
("bpy.ops.pose.select_mirror*", "animation/armatures/posing/selecting.html#bpy-ops-pose-select-mirror"),
|
||||
("bpy.ops.screen.marker_jump*", "animation/markers.html#bpy-ops-screen-marker-jump"),
|
||||
("bpy.ops.screen.repeat_last*", "interface/undo_redo.html#bpy-ops-screen-repeat-last"),
|
||||
("bpy.ops.sculpt.mask_expand*", "sculpt_paint/sculpting/editing/expand.html#bpy-ops-sculpt-mask-expand"),
|
||||
("bpy.ops.sculpt.mask_filter*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-sculpt-mask-filter"),
|
||||
("bpy.ops.transform.tosphere*", "modeling/meshes/editing/mesh/transform/to_sphere.html#bpy-ops-transform-tosphere"),
|
||||
("bpy.ops.view3d.clip_border*", "editors/3dview/navigate/regions.html#bpy-ops-view3d-clip-border"),
|
||||
("bpy.ops.view3d.toggle_xray*", "modeling/meshes/selecting/introduction.html#bpy-ops-view3d-toggle-xray"),
|
||||
("bpy.ops.view3d.view_camera*", "editors/3dview/navigate/camera_view.html#bpy-ops-view3d-view-camera"),
|
||||
("bpy.ops.view3d.zoom_border*", "editors/3dview/navigate/navigation.html#bpy-ops-view3d-zoom-border"),
|
||||
("bpy.ops.wm.previews_ensure*", "files/blend/previews.html#bpy-ops-wm-previews-ensure"),
|
||||
("bpy.ops.wm.properties_edit*", "files/data_blocks.html#bpy-ops-wm-properties-edit"),
|
||||
("bpy.ops.wm.search_operator*", "interface/controls/templates/operator_search.html#bpy-ops-wm-search-operator"),
|
||||
("bpy.types.actionconstraint*", "animation/constraints/relationship/action.html#bpy-types-actionconstraint"),
|
||||
("bpy.types.addonpreferences*", "editors/preferences/addons.html#bpy-types-addonpreferences"),
|
||||
("bpy.types.arealight.spread*", "render/cycles/light_settings.html#bpy-types-arealight-spread"),
|
||||
("bpy.types.armaturemodifier*", "modeling/modifiers/deform/armature.html#bpy-types-armaturemodifier"),
|
||||
("bpy.types.bone.head_radius*", "animation/armatures/bones/properties/deform.html#bpy-types-bone-head-radius"),
|
||||
("bpy.types.bone.tail_radius*", "animation/armatures/bones/properties/deform.html#bpy-types-bone-tail-radius"),
|
||||
("bpy.types.brush.cloth_mass*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth-mass"),
|
||||
("bpy.types.brush.dash_ratio*", "sculpt_paint/brush/stroke.html#bpy-types-brush-dash-ratio"),
|
||||
("bpy.types.brushtextureslot*", "sculpt_paint/brush/texture.html#bpy-types-brushtextureslot"),
|
||||
("bpy.types.colormixsequence*", "video_editing/edit/montage/strips/effects/color_mix.html#bpy-types-colormixsequence"),
|
||||
("bpy.types.curve.dimensions*", "modeling/curves/properties/shape.html#bpy-types-curve-dimensions"),
|
||||
("bpy.types.curve.taper_mode*", "modeling/curves/properties/geometry.html#bpy-types-curve-taper-mode"),
|
||||
("bpy.types.curve.twist_mode*", "modeling/curves/properties/shape.html#bpy-types-curve-twist-mode"),
|
||||
("bpy.types.curve.use_radius*", "modeling/curves/properties/shape.html#bpy-types-curve-use-radius"),
|
||||
("bpy.types.decimatemodifier*", "modeling/modifiers/generate/decimate.html#bpy-types-decimatemodifier"),
|
||||
("bpy.types.displacemodifier*", "modeling/modifiers/deform/displace.html#bpy-types-displacemodifier"),
|
||||
("bpy.types.displaysafeareas*", "render/cameras.html#bpy-types-displaysafeareas"),
|
||||
("bpy.types.editbone.bbone_x*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-x"),
|
||||
("bpy.types.editbone.bbone_z*", "animation/armatures/bones/properties/bendy_bones.html#bpy-types-editbone-bbone-z"),
|
||||
("bpy.types.fcurve.data_path*", "editors/graph_editor/fcurves/properties.html#bpy-types-fcurve-data-path"),
|
||||
("bpy.types.fileselectparams*", "editors/file_browser.html#bpy-types-fileselectparams"),
|
||||
("bpy.types.fmodifierstepped*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierstepped"),
|
||||
("bpy.types.freestylelineset*", "render/freestyle/view_layer/line_set.html#bpy-types-freestylelineset"),
|
||||
("bpy.types.greasepencilgrid*", "grease_pencil/properties/display.html#bpy-types-greasepencilgrid"),
|
||||
("bpy.types.image.alpha_mode*", "editors/image/image_settings.html#bpy-types-image-alpha-mode"),
|
||||
("bpy.types.key.use_relative*", "animation/shape_keys/shape_keys_panel.html#bpy-types-key-use-relative"),
|
||||
("bpy.types.mask.frame_start*", "movie_clip/masking/sidebar.html#bpy-types-mask-frame-start"),
|
||||
("bpy.types.motionpath.color*", "animation/motion_paths.html#bpy-types-motionpath-color"),
|
||||
("bpy.types.motionpath.lines*", "animation/motion_paths.html#bpy-types-motionpath-lines"),
|
||||
("bpy.types.multicamsequence*", "video_editing/edit/montage/strips/effects/multicam.html#bpy-types-multicamsequence"),
|
||||
("bpy.types.multiplysequence*", "video_editing/edit/montage/strips/effects/multiply.html#bpy-types-multiplysequence"),
|
||||
("bpy.types.multiresmodifier*", "modeling/modifiers/generate/multiresolution.html#bpy-types-multiresmodifier"),
|
||||
("bpy.types.overdropsequence*", "video_editing/edit/montage/strips/effects/alpha_over_under_overdrop.html#bpy-types-overdropsequence"),
|
||||
("bpy.types.paint.show_brush*", "sculpt_paint/brush/cursor.html#bpy-types-paint-show-brush"),
|
||||
("bpy.types.paint.use_cavity*", "sculpt_paint/texture_paint/tool_settings/mask.html#bpy-types-paint-use-cavity"),
|
||||
("bpy.types.particlesettings*", "physics/particles/index.html#bpy-types-particlesettings"),
|
||||
("bpy.types.pose.use_auto_ik*", "animation/armatures/posing/tool_settings.html#bpy-types-pose-use-auto-ik"),
|
||||
("bpy.types.preferencesinput*", "editors/preferences/input.html#bpy-types-preferencesinput"),
|
||||
("bpy.types.rigifyparameters*", "addons/rigging/rigify/rig_types/index.html#bpy-types-rigifyparameters"),
|
||||
("bpy.types.scene.frame_step*", "render/output/properties/frame_range.html#bpy-types-scene-frame-step"),
|
||||
("bpy.types.sceneeevee.bloom*", "render/eevee/render_settings/bloom.html#bpy-types-sceneeevee-bloom"),
|
||||
("bpy.types.sculpt.show_mask*", "sculpt_paint/sculpting/editing/mask.html#bpy-types-sculpt-show-mask"),
|
||||
("bpy.types.sequence.channel*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequence-channel"),
|
||||
("bpy.types.sequencemodifier*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-sequencemodifier"),
|
||||
("bpy.types.shaderfxcolorize*", "grease_pencil/visual_effects/colorize.html#bpy-types-shaderfxcolorize"),
|
||||
("bpy.types.shaderfxpixelate*", "grease_pencil/visual_effects/pixelate.html#bpy-types-shaderfxpixelate"),
|
||||
("bpy.types.shadernodeinvert*", "render/shader_nodes/color/invert.html#bpy-types-shadernodeinvert"),
|
||||
("bpy.types.shadernodemixrgb*", "modeling/geometry_nodes/utilities/color/mix_rgb.html#bpy-types-shadernodemixrgb"),
|
||||
("bpy.types.shadernodenormal*", "render/shader_nodes/vector/normal.html#bpy-types-shadernodenormal"),
|
||||
("bpy.types.shadernodescript*", "render/shader_nodes/osl.html#bpy-types-shadernodescript"),
|
||||
("bpy.types.shadernodetexies*", "render/shader_nodes/textures/ies.html#bpy-types-shadernodetexies"),
|
||||
("bpy.types.shadernodetexsky*", "render/shader_nodes/textures/sky.html#bpy-types-shadernodetexsky"),
|
||||
("bpy.types.softbodymodifier*", "physics/soft_body/index.html#bpy-types-softbodymodifier"),
|
||||
("bpy.types.softbodysettings*", "physics/soft_body/settings/index.html#bpy-types-softbodysettings"),
|
||||
("bpy.types.solidifymodifier*", "modeling/modifiers/generate/solidify.html#bpy-types-solidifymodifier"),
|
||||
("bpy.types.spacefilebrowser*", "editors/file_browser.html#bpy-types-spacefilebrowser"),
|
||||
("bpy.types.spacegrapheditor*", "editors/graph_editor/index.html#bpy-types-spacegrapheditor"),
|
||||
("bpy.types.spaceimageeditor*", "editors/image/index.html#bpy-types-spaceimageeditor"),
|
||||
("bpy.types.spacepreferences*", "editors/preferences/index.html#bpy-types-spacepreferences"),
|
||||
("bpy.types.spacespreadsheet*", "editors/spreadsheet.html#bpy-types-spacespreadsheet"),
|
||||
("bpy.types.spaceview3d.lens*", "editors/3dview/sidebar.html#bpy-types-spaceview3d-lens"),
|
||||
("bpy.types.spaceview3d.show*", "editors/3dview/display/index.html#bpy-types-spaceview3d-show"),
|
||||
("bpy.types.sphfluidsettings*", "physics/particles/emitter/physics/fluid.html#bpy-types-sphfluidsettings"),
|
||||
("bpy.types.subtractsequence*", "video_editing/edit/montage/strips/effects/subtract.html#bpy-types-subtractsequence"),
|
||||
("bpy.types.text.indentation*", "editors/text_editor.html#bpy-types-text-indentation"),
|
||||
("bpy.types.texture.contrast*", "render/materials/legacy_textures/colors.html#bpy-types-texture-contrast"),
|
||||
("bpy.types.texturenodegroup*", "editors/texture_node/types/groups.html#bpy-types-texturenodegroup"),
|
||||
("bpy.types.texturenodeimage*", "editors/texture_node/types/input/image.html#bpy-types-texturenodeimage"),
|
||||
("bpy.types.texturenodescale*", "editors/texture_node/types/distort/scale.html#bpy-types-texturenodescale"),
|
||||
("bpy.types.world.lightgroup*", "render/cycles/world_settings.html#bpy-types-world-lightgroup"),
|
||||
("bpy.ops.armature.dissolve*", "animation/armatures/bones/editing/delete.html#bpy-ops-armature-dissolve"),
|
||||
("bpy.ops.armature.separate*", "animation/armatures/bones/editing/separate_bones.html#bpy-ops-armature-separate"),
|
||||
("bpy.ops.clip.clean_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-clean-tracks"),
|
||||
("bpy.ops.clip.delete_track*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-delete-track"),
|
||||
("bpy.ops.clip.select_lasso*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-lasso"),
|
||||
("bpy.ops.clip.solve_camera*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-solve-camera"),
|
||||
("bpy.ops.constraint.delete*", "animation/constraints/interface/header.html#bpy-ops-constraint-delete"),
|
||||
("bpy.ops.curve.smooth_tilt*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth-tilt"),
|
||||
("bpy.ops.curves.select_all*", "sculpt_paint/curves_sculpting/introduction.html#bpy-ops-curves-select-all"),
|
||||
("bpy.ops.file.reset_recent*", "editors/file_browser.html#bpy-ops-file-reset-recent"),
|
||||
("bpy.ops.fluid.bake_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-bake-guides"),
|
||||
("bpy.ops.fluid.free_guides*", "physics/fluid/type/domain/guides.html#bpy-ops-fluid-free-guides"),
|
||||
("bpy.ops.font.style_toggle*", "modeling/texts/editing.html#bpy-ops-font-style-toggle"),
|
||||
("bpy.ops.gpencil.mesh_bake*", "grease_pencil/animation/tools.html#bpy-ops-gpencil-mesh-bake"),
|
||||
("bpy.ops.gpencil.reproject*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-reproject"),
|
||||
("bpy.ops.graph.easing_type*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-easing-type"),
|
||||
("bpy.ops.graph.handle_type*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-handle-type"),
|
||||
("bpy.ops.marker.select_all*", "animation/markers.html#bpy-ops-marker-select-all"),
|
||||
("bpy.ops.mask.copy_splines*", "movie_clip/masking/editing.html#bpy-ops-mask-copy-splines"),
|
||||
("bpy.ops.mask.parent_clear*", "movie_clip/masking/editing.html#bpy-ops-mask-parent-clear"),
|
||||
("bpy.ops.mask.select_lasso*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-lasso"),
|
||||
("bpy.ops.mesh.bevel.vertex*", "modeling/meshes/editing/vertex/bevel_vertices.html#bpy-ops-mesh-bevel-vertex"),
|
||||
("bpy.ops.mesh.delete_loose*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-delete-loose"),
|
||||
("bpy.ops.mesh.face_shading*", "modeling/meshes/editing/face/shading.html#bpy-ops-mesh-face-shading"),
|
||||
("bpy.ops.mesh.flip_normals*", "modeling/meshes/editing/mesh/normals.html#bpy-ops-mesh-flip-normals"),
|
||||
("bpy.ops.mesh.select_loose*", "modeling/meshes/selecting/all_by_trait.html#bpy-ops-mesh-select-loose"),
|
||||
("bpy.ops.mesh.vert_connect*", "modeling/meshes/editing/vertex/connect_vertex_pairs.html#bpy-ops-mesh-vert-connect"),
|
||||
("bpy.ops.nla.tracks_delete*", "editors/nla/editing.html#bpy-ops-nla-tracks-delete"),
|
||||
("bpy.ops.node.group_insert*", "interface/controls/nodes/groups.html#bpy-ops-node-group-insert"),
|
||||
("bpy.ops.object.lightprobe*", "render/eevee/light_probes/index.html#bpy-ops-object-lightprobe"),
|
||||
("bpy.ops.object.make_local*", "files/linked_libraries/link_append.html#bpy-ops-object-make-local"),
|
||||
("bpy.ops.object.origin_set*", "scene_layout/object/origin.html#bpy-ops-object-origin-set"),
|
||||
("bpy.ops.object.parent_set*", "scene_layout/object/editing/parent.html#bpy-ops-object-parent-set"),
|
||||
("bpy.ops.object.pointcloud*", "modeling/point_cloud.html#bpy-ops-object-pointcloud"),
|
||||
("bpy.ops.object.select_all*", "scene_layout/object/selecting.html#bpy-ops-object-select-all"),
|
||||
("bpy.ops.object.shade_flat*", "scene_layout/object/editing/shading.html#bpy-ops-object-shade-flat"),
|
||||
("bpy.ops.paintcurve.select*", "sculpt_paint/brush/stroke.html#bpy-ops-paintcurve-select"),
|
||||
("bpy.ops.pose.group_assign*", "animation/armatures/properties/bone_groups.html#bpy-ops-pose-group-assign"),
|
||||
("bpy.ops.pose.group_select*", "animation/armatures/properties/bone_groups.html#bpy-ops-pose-group-select"),
|
||||
("bpy.ops.pose.paths_update*", "animation/motion_paths.html#bpy-ops-pose-paths-update"),
|
||||
("bpy.ops.preferences.addon*", "editors/preferences/addons.html#bpy-ops-preferences-addon"),
|
||||
("bpy.ops.scene.light_cache*", "render/eevee/render_settings/indirect_lighting.html#bpy-ops-scene-light-cache"),
|
||||
("bpy.ops.screen.actionzone*", "interface/window_system/areas.html#bpy-ops-screen-actionzone"),
|
||||
("bpy.ops.screen.area_dupli*", "interface/window_system/areas.html#bpy-ops-screen-area-dupli"),
|
||||
("bpy.ops.screen.area_split*", "interface/window_system/areas.html#bpy-ops-screen-area-split"),
|
||||
("bpy.ops.screen.screenshot*", "interface/window_system/topbar.html#bpy-ops-screen-screenshot"),
|
||||
("bpy.ops.sculpt.dirty_mask*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-sculpt-dirty-mask"),
|
||||
("bpy.ops.sculpt.reveal_all*", "sculpt_paint/sculpting/editing/face_sets.html#bpy-ops-sculpt-reveal-all"),
|
||||
("bpy.ops.sculpt.symmetrize*", "sculpt_paint/sculpting/tool_settings/symmetry.html#bpy-ops-sculpt-symmetrize"),
|
||||
("bpy.ops.uv.align_rotation*", "modeling/meshes/uv/editing.html#bpy-ops-uv-align-rotation"),
|
||||
("bpy.ops.uv.remove_doubles*", "modeling/meshes/uv/editing.html#bpy-ops-uv-remove-doubles"),
|
||||
("bpy.ops.uv.select_similar*", "editors/uv/selecting.html#bpy-ops-uv-select-similar"),
|
||||
("bpy.ops.uv.sphere_project*", "modeling/meshes/editing/uv.html#bpy-ops-uv-sphere-project"),
|
||||
("bpy.ops.view3d.view_orbit*", "editors/3dview/navigate/navigation.html#bpy-ops-view3d-view-orbit"),
|
||||
("bpy.ops.wm.previews_clear*", "files/blend/previews.html#bpy-ops-wm-previews-clear"),
|
||||
("bpy.types.armature.layers*", "animation/armatures/properties/skeleton.html#bpy-types-armature-layers"),
|
||||
("bpy.types.armature.rigify*", "addons/rigging/rigify/basics.html#bpy-types-armature-rigify"),
|
||||
("bpy.types.bone.use_deform*", "animation/armatures/bones/properties/deform.html#bpy-types-bone-use-deform"),
|
||||
("bpy.types.booleanmodifier*", "modeling/modifiers/generate/booleans.html#bpy-types-booleanmodifier"),
|
||||
("bpy.types.brush.direction*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-direction"),
|
||||
("bpy.types.brush.mask_tool*", "sculpt_paint/sculpting/tools/mask.html#bpy-types-brush-mask-tool"),
|
||||
("bpy.types.constraint.mute*", "animation/constraints/interface/header.html#bpy-types-constraint-mute"),
|
||||
("bpy.types.constraint.name*", "animation/constraints/interface/header.html#bpy-types-constraint-name"),
|
||||
("bpy.types.curve.eval_time*", "modeling/curves/properties/path_animation.html#bpy-types-curve-eval-time"),
|
||||
("bpy.types.curve.fill_mode*", "modeling/curves/properties/shape.html#bpy-types-curve-fill-mode"),
|
||||
("bpy.types.editbone.layers*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-layers"),
|
||||
("bpy.types.editbone.parent*", "animation/armatures/bones/properties/relations.html#bpy-types-editbone-parent"),
|
||||
("bpy.types.explodemodifier*", "modeling/modifiers/physics/explode.html#bpy-types-explodemodifier"),
|
||||
("bpy.types.fcurvemodifiers*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fcurvemodifiers"),
|
||||
("bpy.types.floorconstraint*", "animation/constraints/relationship/floor.html#bpy-types-floorconstraint"),
|
||||
("bpy.types.fmodifiercycles*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiercycles"),
|
||||
("bpy.types.fmodifierlimits*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifierlimits"),
|
||||
("bpy.types.imagepaint.mode*", "sculpt_paint/texture_paint/tool_settings/texture_slots.html#bpy-types-imagepaint-mode"),
|
||||
("bpy.types.latticemodifier*", "modeling/modifiers/deform/lattice.html#bpy-types-latticemodifier"),
|
||||
("bpy.types.materiallineart*", "render/materials/line_art.html#bpy-types-materiallineart"),
|
||||
("bpy.types.musgravetexture*", "render/materials/legacy_textures/types/musgrave.html#bpy-types-musgravetexture"),
|
||||
("bpy.types.object.location*", "scene_layout/object/properties/transforms.html#bpy-types-object-location"),
|
||||
("bpy.types.object.rotation*", "scene_layout/object/properties/transforms.html#bpy-types-object-rotation"),
|
||||
("bpy.types.particlehairkey*", "physics/particles/emitter/physics/keyed.html#bpy-types-particlehairkey"),
|
||||
("bpy.types.pivotconstraint*", "animation/constraints/relationship/pivot.html#bpy-types-pivotconstraint"),
|
||||
("bpy.types.preferencesedit*", "editors/preferences/editing.html#bpy-types-preferencesedit"),
|
||||
("bpy.types.preferencesview*", "editors/preferences/interface.html#bpy-types-preferencesview"),
|
||||
("bpy.types.rigidbodyobject*", "physics/rigid_body/index.html#bpy-types-rigidbodyobject"),
|
||||
("bpy.types.scene.frame_end*", "render/output/properties/frame_range.html#bpy-types-scene-frame-end"),
|
||||
("bpy.types.scene.sync_mode*", "editors/timeline.html#bpy-types-scene-sync-mode"),
|
||||
("bpy.types.sceneeevee.gtao*", "render/eevee/render_settings/ambient_occlusion.html#bpy-types-sceneeevee-gtao"),
|
||||
("bpy.types.screen.use_play*", "editors/timeline.html#bpy-types-screen-use-play"),
|
||||
("bpy.types.shadernodebevel*", "render/shader_nodes/input/bevel.html#bpy-types-shadernodebevel"),
|
||||
("bpy.types.shadernodeclamp*", "render/shader_nodes/converter/clamp.html#bpy-types-shadernodeclamp"),
|
||||
("bpy.types.shadernodegamma*", "render/shader_nodes/color/gamma.html#bpy-types-shadernodegamma"),
|
||||
("bpy.types.shadernodegroup*", "render/shader_nodes/groups.html#bpy-types-shadernodegroup"),
|
||||
("bpy.types.shadernodeuvmap*", "render/shader_nodes/input/uv_map.html#bpy-types-shadernodeuvmap"),
|
||||
("bpy.types.shadernodevalue*", "render/shader_nodes/input/value.html#bpy-types-shadernodevalue"),
|
||||
("bpy.types.spaceclipeditor*", "movie_clip/index.html#bpy-types-spaceclipeditor"),
|
||||
("bpy.types.spaceproperties*", "editors/properties_editor.html#bpy-types-spaceproperties"),
|
||||
("bpy.types.spacetexteditor*", "editors/text_editor.html#bpy-types-spacetexteditor"),
|
||||
("bpy.types.subsurfmodifier*", "modeling/modifiers/generate/subdivision_surface.html#bpy-types-subsurfmodifier"),
|
||||
("bpy.types.texturenodemath*", "editors/texture_node/types/converter/math.html#bpy-types-texturenodemath"),
|
||||
("bpy.types.volume.filepath*", "modeling/volumes/properties.html#bpy-types-volume-filepath"),
|
||||
("bpy.ops.asset.tag_remove*", "editors/asset_browser.html#bpy-ops-asset-tag-remove"),
|
||||
("bpy.ops.clip.join_tracks*", "movie_clip/tracking/clip/editing/track.html#bpy-ops-clip-join-tracks"),
|
||||
("bpy.ops.constraint.apply*", "animation/constraints/interface/header.html#bpy-ops-constraint-apply"),
|
||||
("bpy.ops.curve.select_row*", "modeling/surfaces/selecting.html#bpy-ops-curve-select-row"),
|
||||
("bpy.ops.curve.tilt_clear*", "modeling/curves/editing/control_points.html#bpy-ops-curve-tilt-clear"),
|
||||
("bpy.ops.curve.vertex_add*", "modeling/curves/editing/other.html#bpy-ops-curve-vertex-add"),
|
||||
("bpy.ops.fluid.bake_noise*", "physics/fluid/type/domain/gas/noise.html#bpy-ops-fluid-bake-noise"),
|
||||
("bpy.ops.fluid.free_noise*", "physics/fluid/type/domain/gas/noise.html#bpy-ops-fluid-free-noise"),
|
||||
("bpy.ops.font.move_select*", "modeling/texts/selecting.html#bpy-ops-font-move-select"),
|
||||
("bpy.ops.gpencil.dissolve*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-dissolve"),
|
||||
("bpy.ops.graph.frame_jump*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-frame-jump"),
|
||||
("bpy.ops.graph.sound_bake*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-sound-bake"),
|
||||
("bpy.ops.marker.duplicate*", "animation/markers.html#bpy-ops-marker-duplicate"),
|
||||
("bpy.ops.mask.select_less*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-less"),
|
||||
("bpy.ops.mask.select_more*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-more"),
|
||||
("bpy.ops.mesh.convex_hull*", "modeling/meshes/editing/mesh/convex_hull.html#bpy-ops-mesh-convex-hull"),
|
||||
("bpy.ops.mesh.edge_rotate*", "modeling/meshes/editing/edge/rotate_edge.html#bpy-ops-mesh-edge-rotate"),
|
||||
("bpy.ops.mesh.loop_select*", "modeling/meshes/selecting/loops.html#bpy-ops-mesh-loop-select"),
|
||||
("bpy.ops.mesh.select_less*", "modeling/meshes/selecting/more_less.html#bpy-ops-mesh-select-less"),
|
||||
("bpy.ops.mesh.select_mode*", "modeling/meshes/selecting/introduction.html#bpy-ops-mesh-select-mode"),
|
||||
("bpy.ops.mesh.select_more*", "modeling/meshes/selecting/more_less.html#bpy-ops-mesh-select-more"),
|
||||
("bpy.ops.mesh.unsubdivide*", "modeling/meshes/editing/edge/unsubdivide.html#bpy-ops-mesh-unsubdivide"),
|
||||
("bpy.ops.mesh.uvs_reverse*", "modeling/meshes/uv/editing.html#bpy-ops-mesh-uvs-reverse"),
|
||||
("bpy.ops.node.hide_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-hide-toggle"),
|
||||
("bpy.ops.node.mute_toggle*", "interface/controls/nodes/editing.html#bpy-ops-node-mute-toggle"),
|
||||
("bpy.ops.object.hide_view*", "scene_layout/object/editing/show_hide.html#bpy-ops-object-hide-view"),
|
||||
("bpy.ops.object.track_set*", "animation/constraints/interface/adding_removing.html#bpy-ops-object-track-set"),
|
||||
("bpy.ops.pose.paths_clear*", "animation/motion_paths.html#bpy-ops-pose-paths-clear"),
|
||||
("bpy.ops.pose.scale_clear*", "animation/armatures/posing/editing/clear.html#bpy-ops-pose-scale-clear"),
|
||||
("bpy.ops.scene.view_layer*", "render/layers/introduction.html#bpy-ops-scene-view-layer"),
|
||||
("bpy.ops.screen.area_join*", "interface/window_system/areas.html#bpy-ops-screen-area-join"),
|
||||
("bpy.ops.screen.area_move*", "interface/window_system/areas.html#bpy-ops-screen-area-move"),
|
||||
("bpy.ops.screen.area_swap*", "interface/window_system/areas.html#bpy-ops-screen-area-swap"),
|
||||
("bpy.ops.screen.redo_last*", "interface/undo_redo.html#bpy-ops-screen-redo-last"),
|
||||
("bpy.ops.sculpt.mask_init*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-sculpt-mask-init"),
|
||||
("bpy.ops.sequencer.delete*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-delete"),
|
||||
("bpy.ops.sequencer.reload*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-reload"),
|
||||
("bpy.ops.sequencer.unlock*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-unlock"),
|
||||
("bpy.ops.sequencer.unmute*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-unmute"),
|
||||
("bpy.ops.transform.mirror*", "scene_layout/object/editing/mirror.html#bpy-ops-transform-mirror"),
|
||||
("bpy.ops.transform.resize*", "scene_layout/object/editing/transform/scale.html#bpy-ops-transform-resize"),
|
||||
("bpy.ops.transform.rotate*", "scene_layout/object/editing/transform/rotate.html#bpy-ops-transform-rotate"),
|
||||
("bpy.ops.uv.lightmap_pack*", "modeling/meshes/editing/uv.html#bpy-ops-uv-lightmap-pack"),
|
||||
("bpy.ops.uv.smart_project*", "modeling/meshes/editing/uv.html#bpy-ops-uv-smart-project"),
|
||||
("bpy.ops.uv.snap_selected*", "modeling/meshes/uv/editing.html#bpy-ops-uv-snap-selected"),
|
||||
("bpy.ops.view3d.localview*", "editors/3dview/navigate/local_view.html#bpy-ops-view3d-localview"),
|
||||
("bpy.ops.view3d.view_axis*", "editors/3dview/navigate/viewpoint.html#bpy-ops-view3d-view-axis"),
|
||||
("bpy.ops.view3d.view_roll*", "editors/3dview/navigate/navigation.html#bpy-ops-view3d-view-roll"),
|
||||
("bpy.ops.wm.open_mainfile*", "files/blend/open_save.html#bpy-ops-wm-open-mainfile"),
|
||||
("bpy.ops.wm.owner_disable*", "interface/window_system/workspaces.html#bpy-ops-wm-owner-disable"),
|
||||
("bpy.ops.wm.save_homefile*", "interface/window_system/topbar.html#bpy-ops-wm-save-homefile"),
|
||||
("bpy.ops.wm.save_mainfile*", "files/blend/open_save.html#bpy-ops-wm-save-mainfile"),
|
||||
("bpy.types.bone.show_wire*", "animation/armatures/bones/properties/display.html#bpy-types-bone-show-wire"),
|
||||
("bpy.types.brush.hardness*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-hardness"),
|
||||
("bpy.types.brush.strength*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-strength"),
|
||||
("bpy.types.curves.surface*", "modeling/curves/primitives.html#bpy-types-curves-surface"),
|
||||
("bpy.types.curvesmodifier*", "editors/video_sequencer/sequencer/sidebar/modifiers.html#bpy-types-curvesmodifier"),
|
||||
("bpy.types.ffmpegsettings*", "render/output/properties/output.html#bpy-types-ffmpegsettings"),
|
||||
("bpy.types.fmodifiernoise*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifiernoise"),
|
||||
("bpy.types.image.filepath*", "editors/image/image_settings.html#bpy-types-image-filepath"),
|
||||
("bpy.types.keyframe.co_ui*", "editors/graph_editor/fcurves/properties.html#bpy-types-keyframe-co-ui"),
|
||||
("bpy.types.mask.frame_end*", "movie_clip/masking/sidebar.html#bpy-types-mask-frame-end"),
|
||||
("bpy.types.material.paint*", "sculpt_paint/texture_paint/index.html#bpy-types-material-paint"),
|
||||
("bpy.types.mirrormodifier*", "modeling/modifiers/generate/mirror.html#bpy-types-mirrormodifier"),
|
||||
("bpy.types.movieclipproxy*", "editors/clip/sidebar.html#bpy-types-movieclipproxy"),
|
||||
("bpy.types.object.up_axis*", "scene_layout/object/properties/relations.html#bpy-types-object-up-axis"),
|
||||
("bpy.types.particlesystem*", "physics/particles/index.html#bpy-types-particlesystem"),
|
||||
("bpy.types.particletarget*", "physics/particles/emitter/physics/keyed.html#bpy-types-particletarget"),
|
||||
("bpy.types.remeshmodifier*", "modeling/modifiers/generate/remesh.html#bpy-types-remeshmodifier"),
|
||||
("bpy.types.rendersettings*", "render/index.html#bpy-types-rendersettings"),
|
||||
("bpy.types.rigidbodyworld*", "physics/rigid_body/world.html#bpy-types-rigidbodyworld"),
|
||||
("bpy.types.sceneeevee.ssr*", "render/eevee/render_settings/screen_space_reflections.html#bpy-types-sceneeevee-ssr"),
|
||||
("bpy.types.sceneeevee.sss*", "render/eevee/render_settings/subsurface_scattering.html#bpy-types-sceneeevee-sss"),
|
||||
("bpy.types.sculpt.gravity*", "sculpt_paint/sculpting/tool_settings/options.html#bpy-types-sculpt-gravity"),
|
||||
("bpy.types.shaderfxshadow*", "grease_pencil/visual_effects/shadow.html#bpy-types-shaderfxshadow"),
|
||||
("bpy.types.shadernodebump*", "render/shader_nodes/vector/bump.html#bpy-types-shadernodebump"),
|
||||
("bpy.types.shadernodemath*", "render/shader_nodes/converter/math.html#bpy-types-shadernodemath"),
|
||||
("bpy.types.shapekey.frame*", "animation/shape_keys/shape_keys_panel.html#bpy-types-shapekey-frame"),
|
||||
("bpy.types.shapekey.value*", "animation/shape_keys/shape_keys_panel.html#bpy-types-shapekey-value"),
|
||||
("bpy.types.smoothmodifier*", "modeling/modifiers/deform/smooth.html#bpy-types-smoothmodifier"),
|
||||
("bpy.types.sound.use_mono*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sound-use-mono"),
|
||||
("bpy.types.spline.order_u*", "modeling/curves/properties/active_spline.html#bpy-types-spline-order-u"),
|
||||
("bpy.types.timelinemarker*", "animation/markers.html#bpy-types-timelinemarker"),
|
||||
("bpy.types.usersolidlight*", "editors/preferences/lights.html#bpy-types-usersolidlight"),
|
||||
("bpy.types.uvwarpmodifier*", "modeling/modifiers/modify/uv_warp.html#bpy-types-uvwarpmodifier"),
|
||||
("bpy.types.viewlayer.name*", "render/layers/introduction.html#bpy-types-viewlayer-name"),
|
||||
("bpy.types.voronoitexture*", "render/materials/legacy_textures/types/voronoi.html#bpy-types-voronoitexture"),
|
||||
("bpy.types.walknavigation*", "editors/3dview/navigate/walk_fly.html#bpy-types-walknavigation"),
|
||||
("bpy.ops.*.select_circle*", "interface/selecting.html#bpy-ops-select-circle"),
|
||||
("bpy.ops.anim.keying_set*", "animation/keyframes/keying_sets.html#bpy-ops-anim-keying-set"),
|
||||
("bpy.ops.armature.delete*", "animation/armatures/bones/editing/delete.html#bpy-ops-armature-delete"),
|
||||
("bpy.ops.clip.select_all*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-all"),
|
||||
("bpy.ops.clip.select_box*", "movie_clip/tracking/clip/selecting.html#bpy-ops-clip-select-box"),
|
||||
("bpy.ops.clip.set_origin*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-set-origin"),
|
||||
("bpy.ops.constraint.copy*", "animation/constraints/interface/header.html#bpy-ops-constraint-copy"),
|
||||
("bpy.ops.curve.subdivide*", "modeling/curves/editing/segments.html#bpy-ops-curve-subdivide"),
|
||||
("bpy.ops.ed.undo_history*", "interface/undo_redo.html#bpy-ops-ed-undo-history"),
|
||||
("bpy.ops.file.unpack_all*", "files/blend/packed_data.html#bpy-ops-file-unpack-all"),
|
||||
("bpy.ops.fluid.bake_data*", "physics/fluid/type/domain/settings.html#bpy-ops-fluid-bake-data"),
|
||||
("bpy.ops.fluid.bake_mesh*", "physics/fluid/type/domain/liquid/mesh.html#bpy-ops-fluid-bake-mesh"),
|
||||
("bpy.ops.fluid.free_data*", "physics/fluid/type/domain/settings.html#bpy-ops-fluid-free-data"),
|
||||
("bpy.ops.fluid.free_mesh*", "physics/fluid/type/domain/liquid/mesh.html#bpy-ops-fluid-free-mesh"),
|
||||
("bpy.ops.font.select_all*", "modeling/texts/selecting.html#bpy-ops-font-select-all"),
|
||||
("bpy.ops.gpencil.convert*", "grease_pencil/modes/object/convert_to_geometry.html#bpy-ops-gpencil-convert"),
|
||||
("bpy.ops.graph.breakdown*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-breakdown"),
|
||||
("bpy.ops.mask.parent_set*", "movie_clip/masking/editing.html#bpy-ops-mask-parent-set"),
|
||||
("bpy.ops.mask.select_all*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-all"),
|
||||
("bpy.ops.mask.select_box*", "movie_clip/masking/selecting.html#bpy-ops-mask-select-box"),
|
||||
("bpy.ops.mesh.edge_split*", "modeling/meshes/editing/mesh/split.html#bpy-ops-mesh-edge-split"),
|
||||
("bpy.ops.mesh.fill_holes*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-fill-holes"),
|
||||
("bpy.ops.mesh.mark_sharp*", "modeling/meshes/editing/edge/edge_data.html#bpy-ops-mesh-mark-sharp"),
|
||||
("bpy.ops.mesh.select_nth*", "modeling/meshes/selecting/checker_deselect.html#bpy-ops-mesh-select-nth"),
|
||||
("bpy.ops.mesh.symmetrize*", "modeling/meshes/editing/mesh/symmetrize.html#bpy-ops-mesh-symmetrize"),
|
||||
("bpy.ops.mesh.uvs_rotate*", "modeling/meshes/uv/editing.html#bpy-ops-mesh-uvs-rotate"),
|
||||
("bpy.ops.nla.apply_scale*", "editors/nla/editing.html#bpy-ops-nla-apply-scale"),
|
||||
("bpy.ops.nla.clear_scale*", "editors/nla/editing.html#bpy-ops-nla-clear-scale"),
|
||||
("bpy.ops.nla.mute_toggle*", "editors/nla/editing.html#bpy-ops-nla-mute-toggle"),
|
||||
("bpy.ops.node.group_edit*", "interface/controls/nodes/groups.html#bpy-ops-node-group-edit"),
|
||||
("bpy.ops.node.group_make*", "interface/controls/nodes/groups.html#bpy-ops-node-group-make"),
|
||||
("bpy.ops.node.links_mute*", "interface/controls/nodes/editing.html#bpy-ops-node-links-mute"),
|
||||
("bpy.ops.node.parent_set*", "interface/controls/nodes/frame.html#bpy-ops-node-parent-set"),
|
||||
("bpy.ops.object.armature*", "animation/armatures/index.html#bpy-ops-object-armature"),
|
||||
("bpy.ops.object.face_map*", "modeling/meshes/properties/object_data.html#bpy-ops-object-face-map"),
|
||||
("bpy.ops.object.join_uvs*", "scene_layout/object/editing/link_transfer/copy_uvmaps.html#bpy-ops-object-join-uvs"),
|
||||
("bpy.ops.object.mode_set*", "editors/3dview/modes.html#bpy-ops-object-mode-set"),
|
||||
("bpy.ops.outliner.delete*", "editors/outliner/editing.html#bpy-ops-outliner-delete"),
|
||||
("bpy.ops.paintcurve.draw*", "sculpt_paint/brush/stroke.html#bpy-ops-paintcurve-draw"),
|
||||
("bpy.ops.pose.relax_rest*", "animation/armatures/posing/editing/in_betweens.html#bpy-ops-pose-relax-rest"),
|
||||
("bpy.ops.pose.select_all*", "animation/armatures/posing/selecting.html#bpy-ops-pose-select-all"),
|
||||
("bpy.ops.rigidbody.world*", "physics/rigid_body/world.html#bpy-ops-rigidbody-world"),
|
||||
("bpy.ops.sculpt.optimize*", "sculpt_paint/sculpting/editing/sculpt.html#bpy-ops-sculpt-optimize"),
|
||||
("bpy.ops.sequencer.split*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-split"),
|
||||
("bpy.ops.transform.shear*", "modeling/meshes/editing/mesh/transform/shear.html#bpy-ops-transform-shear"),
|
||||
("bpy.ops.uv.cube_project*", "modeling/meshes/editing/uv.html#bpy-ops-uv-cube-project"),
|
||||
("bpy.ops.uv.pack_islands*", "modeling/meshes/uv/editing.html#bpy-ops-uv-pack-islands"),
|
||||
("bpy.ops.uv.select_split*", "modeling/meshes/uv/editing.html#bpy-ops-uv-select-split"),
|
||||
("bpy.ops.view3d.cursor3d*", "editors/3dview/3d_cursor.html#bpy-ops-view3d-cursor3d"),
|
||||
("bpy.ops.view3d.navigate*", "editors/3dview/navigate/index.html#bpy-ops-view3d-navigate"),
|
||||
("bpy.ops.view3d.view_all*", "editors/3dview/navigate/navigation.html#bpy-ops-view3d-view-all"),
|
||||
("bpy.ops.view3d.view_pan*", "editors/3dview/navigate/navigation.html#bpy-ops-view3d-view-pan"),
|
||||
("bpy.ops.wm.app_template*", "advanced/app_templates.html#bpy-ops-wm-app-template"),
|
||||
("bpy.ops.wm.batch_rename*", "files/blend/rename.html#bpy-ops-wm-batch-rename"),
|
||||
("bpy.ops.wm.owner_enable*", "interface/window_system/workspaces.html#bpy-ops-wm-owner-enable"),
|
||||
("bpy.ops.wm.redraw_timer*", "advanced/operators.html#bpy-ops-wm-redraw-timer"),
|
||||
("bpy.ops.wm.splash_about*", "interface/window_system/topbar.html#bpy-ops-wm-splash-about"),
|
||||
("bpy.types.*light.shadow*", "render/eevee/lighting.html#bpy-types-light-shadow"),
|
||||
("bpy.types.armature.show*", "animation/armatures/properties/display.html#bpy-types-armature-show"),
|
||||
("bpy.types.armaturebones*", "animation/armatures/bones/index.html#bpy-types-armaturebones"),
|
||||
("bpy.types.arraymodifier*", "modeling/modifiers/generate/array.html#bpy-types-arraymodifier"),
|
||||
("bpy.types.assetmetadata*", "editors/asset_browser.html#bpy-types-assetmetadata"),
|
||||
("bpy.types.bevelmodifier*", "modeling/modifiers/generate/bevel.html#bpy-types-bevelmodifier"),
|
||||
("bpy.types.brush.spacing*", "sculpt_paint/brush/stroke.html#bpy-types-brush-spacing"),
|
||||
("bpy.types.buildmodifier*", "modeling/modifiers/generate/build.html#bpy-types-buildmodifier"),
|
||||
("bpy.types.clothmodifier*", "physics/cloth/index.html#bpy-types-clothmodifier"),
|
||||
("bpy.types.clothsettings*", "physics/cloth/settings/index.html#bpy-types-clothsettings"),
|
||||
("bpy.types.cloudstexture*", "render/materials/legacy_textures/types/clouds.html#bpy-types-cloudstexture"),
|
||||
("bpy.types.colorsequence*", "video_editing/edit/montage/strips/color.html#bpy-types-colorsequence"),
|
||||
("bpy.types.crosssequence*", "video_editing/edit/montage/strips/transitions/cross.html#bpy-types-crosssequence"),
|
||||
("bpy.types.curve.extrude*", "modeling/curves/properties/geometry.html#bpy-types-curve-extrude"),
|
||||
("bpy.types.curvemodifier*", "modeling/modifiers/deform/curve.html#bpy-types-curvemodifier"),
|
||||
("bpy.types.editbone.head*", "animation/armatures/bones/properties/transform.html#bpy-types-editbone-head"),
|
||||
("bpy.types.editbone.lock*", "animation/armatures/bones/properties/transform.html#bpy-types-editbone-lock"),
|
||||
("bpy.types.editbone.roll*", "animation/armatures/bones/properties/transform.html#bpy-types-editbone-roll"),
|
||||
("bpy.types.editbone.tail*", "animation/armatures/bones/properties/transform.html#bpy-types-editbone-tail"),
|
||||
("bpy.types.fieldsettings*", "physics/forces/force_fields/index.html#bpy-types-fieldsettings"),
|
||||
("bpy.types.imagesequence*", "video_editing/edit/montage/strips/image.html#bpy-types-imagesequence"),
|
||||
("bpy.types.key.eval_time*", "animation/shape_keys/shape_keys_panel.html#bpy-types-key-eval-time"),
|
||||
("bpy.types.marbletexture*", "render/materials/legacy_textures/types/marble.html#bpy-types-marbletexture"),
|
||||
("bpy.types.modifier.name*", "modeling/modifiers/introduction.html#bpy-types-modifier-name"),
|
||||
("bpy.types.modifier.show*", "modeling/modifiers/introduction.html#bpy-types-modifier-show"),
|
||||
("bpy.types.moviesequence*", "video_editing/edit/montage/strips/movie.html#bpy-types-moviesequence"),
|
||||
("bpy.types.movietracking*", "movie_clip/tracking/index.html#bpy-types-movietracking"),
|
||||
("bpy.types.nlastrip.mute*", "editors/nla/sidebar.html#bpy-types-nlastrip-mute"),
|
||||
("bpy.types.nlastrip.name*", "editors/nla/sidebar.html#bpy-types-nlastrip-name"),
|
||||
("bpy.types.nodesmodifier*", "modeling/modifiers/generate/geometry_nodes.html#bpy-types-nodesmodifier"),
|
||||
("bpy.types.object.parent*", "scene_layout/object/editing/parent.html#bpy-types-object-parent"),
|
||||
("bpy.types.objectlineart*", "scene_layout/object/properties/line_art.html#bpy-types-objectlineart"),
|
||||
("bpy.types.oceanmodifier*", "modeling/modifiers/physics/ocean.html#bpy-types-oceanmodifier"),
|
||||
("bpy.types.particlebrush*", "physics/particles/mode.html#bpy-types-particlebrush"),
|
||||
("bpy.types.scene.gravity*", "physics/forces/gravity.html#bpy-types-scene-gravity"),
|
||||
("bpy.types.sceneeevee.gi*", "render/eevee/render_settings/indirect_lighting.html#bpy-types-sceneeevee-gi"),
|
||||
("bpy.types.scenesequence*", "video_editing/edit/montage/strips/scene.html#bpy-types-scenesequence"),
|
||||
("bpy.types.screwmodifier*", "modeling/modifiers/generate/screw.html#bpy-types-screwmodifier"),
|
||||
("bpy.types.sequence.name*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequence-name"),
|
||||
("bpy.types.sequenceproxy*", "editors/video_sequencer/sequencer/sidebar/proxy.html#bpy-types-sequenceproxy"),
|
||||
("bpy.types.shaderfxswirl*", "grease_pencil/visual_effects/swirl.html#bpy-types-shaderfxswirl"),
|
||||
("bpy.types.shadernodemix*", "render/shader_nodes/converter/mix.html#bpy-types-shadernodemix"),
|
||||
("bpy.types.shadernodergb*", "render/shader_nodes/input/rgb.html#bpy-types-shadernodergb"),
|
||||
("bpy.types.shapekey.mute*", "animation/shape_keys/shape_keys_panel.html#bpy-types-shapekey-mute"),
|
||||
("bpy.types.soundsequence*", "video_editing/edit/montage/strips/sound.html#bpy-types-soundsequence"),
|
||||
("bpy.types.spaceoutliner*", "editors/outliner/index.html#bpy-types-spaceoutliner"),
|
||||
("bpy.types.spacetimeline*", "editors/timeline.html#bpy-types-spacetimeline"),
|
||||
("bpy.types.spaceuveditor*", "editors/uv/index.html#bpy-types-spaceuveditor"),
|
||||
("bpy.types.stuccitexture*", "render/materials/legacy_textures/types/stucci.html#bpy-types-stuccitexture"),
|
||||
("bpy.types.texturenodeat*", "editors/texture_node/types/distort/at.html#bpy-types-texturenodeat"),
|
||||
("bpy.types.view3doverlay*", "editors/3dview/display/overlays.html#bpy-types-view3doverlay"),
|
||||
("bpy.types.viewlayer.use*", "render/layers/view_layer.html#bpy-types-viewlayer-use"),
|
||||
("bpy.types.volumedisplay*", "modeling/volumes/properties.html#bpy-types-volumedisplay"),
|
||||
("bpy.types.windowmanager*", "interface/index.html#bpy-types-windowmanager"),
|
||||
("bpy.types.worldlighting*", "render/cycles/world_settings.html#bpy-types-worldlighting"),
|
||||
("bpy.ops.*.select_lasso*", "interface/selecting.html#bpy-ops-select-lasso"),
|
||||
("bpy.ops.armature.align*", "animation/armatures/bones/editing/transform.html#bpy-ops-armature-align"),
|
||||
("bpy.ops.armature.split*", "animation/armatures/bones/editing/split.html#bpy-ops-armature-split"),
|
||||
("bpy.ops.clip.set_plane*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-set-plane"),
|
||||
("bpy.ops.clip.set_scale*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-set-scale"),
|
||||
("bpy.ops.curve.decimate*", "modeling/curves/editing/curve.html#bpy-ops-curve-decimate"),
|
||||
("bpy.ops.curve.separate*", "modeling/curves/editing/curve.html#bpy-ops-curve-separate"),
|
||||
("bpy.ops.fluid.bake_all*", "physics/fluid/type/domain/cache.html#bpy-ops-fluid-bake-all"),
|
||||
("bpy.ops.fluid.free_all*", "physics/fluid/type/domain/cache.html#bpy-ops-fluid-free-all"),
|
||||
("bpy.ops.gpencil.delete*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-delete"),
|
||||
("bpy.ops.gpencil.reveal*", "grease_pencil/properties/layers.html#bpy-ops-gpencil-reveal"),
|
||||
("bpy.ops.gpencil.select*", "grease_pencil/selecting.html#bpy-ops-gpencil-select"),
|
||||
("bpy.ops.graph.decimate*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-decimate"),
|
||||
("bpy.ops.material.paste*", "render/materials/assignment.html#bpy-ops-material-paste"),
|
||||
("bpy.ops.mesh.fill_grid*", "modeling/meshes/editing/face/grid_fill.html#bpy-ops-mesh-fill-grid"),
|
||||
("bpy.ops.mesh.intersect*", "modeling/meshes/editing/face/intersect_knife.html#bpy-ops-mesh-intersect"),
|
||||
("bpy.ops.mesh.mark_seam*", "modeling/meshes/editing/edge/edge_data.html#bpy-ops-mesh-mark-seam"),
|
||||
("bpy.ops.mesh.polybuild*", "modeling/meshes/tools/poly_build.html#bpy-ops-mesh-polybuild"),
|
||||
("bpy.ops.mesh.subdivide*", "modeling/meshes/editing/edge/subdivide.html#bpy-ops-mesh-subdivide"),
|
||||
("bpy.ops.mesh.wireframe*", "modeling/meshes/editing/face/wireframe.html#bpy-ops-mesh-wireframe"),
|
||||
("bpy.ops.node.link_make*", "interface/controls/nodes/editing.html#bpy-ops-node-link-make"),
|
||||
("bpy.ops.node.links_cut*", "interface/controls/nodes/editing.html#bpy-ops-node-links-cut"),
|
||||
("bpy.ops.object.convert*", "scene_layout/object/editing/convert.html#bpy-ops-object-convert"),
|
||||
("bpy.ops.object.gpencil*", "grease_pencil/index.html#bpy-ops-object-gpencil"),
|
||||
("bpy.ops.object.speaker*", "render/output/audio/speaker.html#bpy-ops-object-speaker"),
|
||||
("bpy.ops.paintcurve.new*", "sculpt_paint/brush/stroke.html#bpy-ops-paintcurve-new"),
|
||||
("bpy.ops.pose.breakdown*", "animation/armatures/posing/editing/in_betweens.html#bpy-ops-pose-breakdown"),
|
||||
("bpy.ops.pose.loc_clear*", "animation/armatures/posing/editing/clear.html#bpy-ops-pose-loc-clear"),
|
||||
("bpy.ops.pose.propagate*", "animation/armatures/posing/editing/propagate.html#bpy-ops-pose-propagate"),
|
||||
("bpy.ops.pose.push_rest*", "animation/armatures/posing/editing/in_betweens.html#bpy-ops-pose-push-rest"),
|
||||
("bpy.ops.pose.rot_clear*", "animation/armatures/posing/editing/clear.html#bpy-ops-pose-rot-clear"),
|
||||
("bpy.ops.sequencer.lock*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-lock"),
|
||||
("bpy.ops.sequencer.mute*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-mute"),
|
||||
("bpy.ops.sequencer.slip*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-slip"),
|
||||
("bpy.ops.sequencer.snap*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-snap"),
|
||||
("bpy.ops.sequencer.swap*", "video_editing/edit/montage/editing.html#bpy-ops-sequencer-swap"),
|
||||
("bpy.ops.transform.bend*", "modeling/meshes/editing/mesh/transform/bend.html#bpy-ops-transform-bend"),
|
||||
("bpy.ops.transform.tilt*", "modeling/curves/editing/control_points.html#bpy-ops-transform-tilt"),
|
||||
("bpy.ops.uv.select_mode*", "editors/uv/selecting.html#bpy-ops-uv-select-mode"),
|
||||
("bpy.ops.uv.snap_cursor*", "modeling/meshes/uv/editing.html#bpy-ops-uv-snap-cursor"),
|
||||
("bpy.ops.wm.search_menu*", "interface/controls/templates/operator_search.html#bpy-ops-wm-search-menu"),
|
||||
("bpy.types.bakesettings*", "render/cycles/baking.html#bpy-types-bakesettings"),
|
||||
("bpy.types.blendtexture*", "render/materials/legacy_textures/types/blend.html#bpy-types-blendtexture"),
|
||||
("bpy.types.brush.height*", "sculpt_paint/sculpting/tools/layer.html#bpy-types-brush-height"),
|
||||
("bpy.types.brush.jitter*", "sculpt_paint/brush/stroke.html#bpy-types-brush-jitter"),
|
||||
("bpy.types.castmodifier*", "modeling/modifiers/deform/cast.html#bpy-types-castmodifier"),
|
||||
("bpy.types.curve.offset*", "modeling/curves/properties/geometry.html#bpy-types-curve-offset"),
|
||||
("bpy.types.geometrynode*", "modeling/geometry_nodes/index.html#bpy-types-geometrynode"),
|
||||
("bpy.types.glowsequence*", "video_editing/edit/montage/strips/effects/glow.html#bpy-types-glowsequence"),
|
||||
("bpy.types.gpencillayer*", "grease_pencil/properties/layers.html#bpy-types-gpencillayer"),
|
||||
("bpy.types.hookmodifier*", "modeling/modifiers/deform/hooks.html#bpy-types-hookmodifier"),
|
||||
("bpy.types.image.source*", "editors/image/image_settings.html#bpy-types-image-source"),
|
||||
("bpy.types.imagetexture*", "render/materials/legacy_textures/types/image_movie.html#bpy-types-imagetexture"),
|
||||
("bpy.types.latticepoint*", "animation/lattice.html#bpy-types-latticepoint"),
|
||||
("bpy.types.magictexture*", "render/materials/legacy_textures/types/magic.html#bpy-types-magictexture"),
|
||||
("bpy.types.maskmodifier*", "modeling/modifiers/generate/mask.html#bpy-types-maskmodifier"),
|
||||
("bpy.types.masksequence*", "video_editing/edit/montage/strips/mask.html#bpy-types-masksequence"),
|
||||
("bpy.types.materialslot*", "render/materials/assignment.html#bpy-types-materialslot"),
|
||||
("bpy.types.metasequence*", "video_editing/edit/montage/meta.html#bpy-types-metasequence"),
|
||||
("bpy.types.object.color*", "scene_layout/object/properties/display.html#bpy-types-object-color"),
|
||||
("bpy.types.object.delta*", "scene_layout/object/properties/transforms.html#bpy-types-object-delta"),
|
||||
("bpy.types.object.empty*", "modeling/empties.html#bpy-types-object-empty"),
|
||||
("bpy.types.object.scale*", "scene_layout/object/properties/transforms.html#bpy-types-object-scale"),
|
||||
("bpy.types.particleedit*", "physics/particles/mode.html#bpy-types-particleedit"),
|
||||
("bpy.types.scene.camera*", "scene_layout/scene/properties.html#bpy-types-scene-camera"),
|
||||
("bpy.types.sequencecrop*", "editors/video_sequencer/sequencer/sidebar/strip.html#bpy-types-sequencecrop"),
|
||||
("bpy.types.shaderfxblur*", "grease_pencil/visual_effects/blur.html#bpy-types-shaderfxblur"),
|
||||
("bpy.types.shaderfxflip*", "grease_pencil/visual_effects/flip.html#bpy-types-shaderfxflip"),
|
||||
("bpy.types.shaderfxglow*", "grease_pencil/visual_effects/glow.html#bpy-types-shaderfxglow"),
|
||||
("bpy.types.shaderfxwave*", "grease_pencil/visual_effects/wave_distortion.html#bpy-types-shaderfxwave"),
|
||||
("bpy.types.skinmodifier*", "modeling/modifiers/generate/skin.html#bpy-types-skinmodifier"),
|
||||
("bpy.types.spaceconsole*", "editors/python_console.html#bpy-types-spaceconsole"),
|
||||
("bpy.types.textsequence*", "video_editing/edit/montage/strips/text.html#bpy-types-textsequence"),
|
||||
("bpy.types.unitsettings*", "scene_layout/scene/properties.html#bpy-types-unitsettings"),
|
||||
("bpy.types.vertexcolors*", "sculpt_paint/vertex_paint/index.html#bpy-types-vertexcolors"),
|
||||
("bpy.types.view3dcursor*", "editors/3dview/3d_cursor.html#bpy-types-view3dcursor"),
|
||||
("bpy.types.volumerender*", "modeling/volumes/properties.html#bpy-types-volumerender"),
|
||||
("bpy.types.warpmodifier*", "modeling/modifiers/deform/warp.html#bpy-types-warpmodifier"),
|
||||
("bpy.types.wavemodifier*", "modeling/modifiers/deform/wave.html#bpy-types-wavemodifier"),
|
||||
("bpy.types.weldmodifier*", "modeling/modifiers/generate/weld.html#bpy-types-weldmodifier"),
|
||||
("bpy.types.wipesequence*", "video_editing/edit/montage/strips/transitions/wipe.html#bpy-types-wipesequence"),
|
||||
("bpy.ops.armature.fill*", "animation/armatures/bones/editing/fill_between_joints.html#bpy-ops-armature-fill"),
|
||||
("bpy.ops.asset.tag_add*", "editors/asset_browser.html#bpy-ops-asset-tag-add"),
|
||||
("bpy.ops.clip.prefetch*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-prefetch"),
|
||||
("bpy.ops.clip.set_axis*", "movie_clip/tracking/clip/editing/reconstruction.html#bpy-ops-clip-set-axis"),
|
||||
("bpy.ops.file.pack_all*", "files/blend/packed_data.html#bpy-ops-file-pack-all"),
|
||||
("bpy.ops.file.previous*", "editors/file_browser.html#bpy-ops-file-previous"),
|
||||
("bpy.ops.gpencil.paste*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-paste"),
|
||||
("bpy.ops.image.project*", "sculpt_paint/texture_paint/tool_settings/options.html#bpy-ops-image-project"),
|
||||
("bpy.ops.image.replace*", "editors/image/editing.html#bpy-ops-image-replace"),
|
||||
("bpy.ops.image.save_as*", "editors/image/editing.html#bpy-ops-image-save-as"),
|
||||
("bpy.ops.marker.delete*", "animation/markers.html#bpy-ops-marker-delete"),
|
||||
("bpy.ops.marker.select*", "animation/markers.html#bpy-ops-marker-select"),
|
||||
("bpy.ops.material.copy*", "render/materials/assignment.html#bpy-ops-material-copy"),
|
||||
("bpy.ops.mesh.decimate*", "modeling/meshes/editing/mesh/cleanup.html#bpy-ops-mesh-decimate"),
|
||||
("bpy.ops.mesh.dissolve*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-dissolve"),
|
||||
("bpy.ops.mesh.rip_move*", "modeling/meshes/editing/vertex/rip_vertices.html#bpy-ops-mesh-rip-move"),
|
||||
("bpy.ops.mesh.separate*", "modeling/meshes/editing/mesh/separate.html#bpy-ops-mesh-separate"),
|
||||
("bpy.ops.mesh.solidify*", "modeling/meshes/editing/face/solidify_faces.html#bpy-ops-mesh-solidify"),
|
||||
("bpy.ops.nla.duplicate*", "editors/nla/editing.html#bpy-ops-nla-duplicate"),
|
||||
("bpy.ops.nla.move_down*", "editors/nla/editing.html#bpy-ops-nla-move-down"),
|
||||
("bpy.ops.object.*clear*", "scene_layout/object/editing/clear.html#bpy-ops-object-clear"),
|
||||
("bpy.ops.object.delete*", "scene_layout/object/editing/delete.html#bpy-ops-object-delete"),
|
||||
("bpy.ops.render.opengl*", "editors/3dview/viewport_render.html#bpy-ops-render-opengl"),
|
||||
("bpy.ops.screen.header*", "interface/window_system/regions.html#bpy-ops-screen-header"),
|
||||
("bpy.ops.script.reload*", "advanced/operators.html#bpy-ops-script-reload"),
|
||||
("bpy.ops.sculpt.expand*", "sculpt_paint/sculpting/editing/expand.html#bpy-ops-sculpt-expand"),
|
||||
("bpy.ops.sculpt_curves*", "sculpt_paint/curves_sculpting/index.html#bpy-ops-sculpt-curves"),
|
||||
("bpy.ops.ui.eyedropper*", "interface/controls/buttons/eyedropper.html#bpy-ops-ui-eyedropper"),
|
||||
("bpy.ops.view3d.select*", "editors/3dview/selecting.html#bpy-ops-view3d-select"),
|
||||
("bpy.ops.wm.debug_menu*", "advanced/operators.html#bpy-ops-wm-debug-menu"),
|
||||
("bpy.ops.wm.obj_export*", "files/import_export/obj.html#bpy-ops-wm-obj-export"),
|
||||
("bpy.ops.wm.obj_import*", "files/import_export/obj.html#bpy-ops-wm-obj-import"),
|
||||
("bpy.ops.wm.properties*", "files/data_blocks.html#bpy-ops-wm-properties"),
|
||||
("bpy.ops.wm.stl_import*", "files/import_export/stl.html#bpy-ops-wm-stl-import"),
|
||||
("bpy.ops.wm.usd_export*", "files/import_export/usd.html#bpy-ops-wm-usd-export"),
|
||||
("bpy.types.addsequence*", "video_editing/edit/montage/strips/effects/add.html#bpy-types-addsequence"),
|
||||
("bpy.types.brush.cloth*", "sculpt_paint/sculpting/tools/cloth.html#bpy-types-brush-cloth"),
|
||||
("bpy.types.brush.curve*", "sculpt_paint/brush/falloff.html#bpy-types-brush-curve"),
|
||||
("bpy.types.camera.show*", "render/cameras.html#bpy-types-camera-show"),
|
||||
("bpy.types.consoleline*", "editors/python_console.html#bpy-types-consoleline"),
|
||||
("bpy.types.curve.bevel*", "modeling/curves/properties/geometry.html#bpy-types-curve-bevel"),
|
||||
("bpy.types.mesh.remesh*", "modeling/meshes/retopology.html#bpy-types-mesh-remesh"),
|
||||
("bpy.types.meshstatvis*", "modeling/meshes/mesh_analysis.html#bpy-types-meshstatvis"),
|
||||
("bpy.types.nodesetting*", "interface/controls/nodes/parts.html#bpy-types-nodesetting"),
|
||||
("bpy.types.object.lock*", "scene_layout/object/properties/transforms.html#bpy-types-object-lock"),
|
||||
("bpy.types.object.show*", "scene_layout/object/properties/display.html#bpy-types-object-show"),
|
||||
("bpy.types.particlekey*", "physics/particles/emitter/physics/keyed.html#bpy-types-particlekey"),
|
||||
("bpy.types.posebone.ik*", "animation/armatures/posing/bone_constraints/inverse_kinematics/introduction.html#bpy-types-posebone-ik"),
|
||||
("bpy.types.preferences*", "editors/preferences/index.html#bpy-types-preferences"),
|
||||
("bpy.types.renderlayer*", "render/layers/passes.html#bpy-types-renderlayer"),
|
||||
("bpy.types.sculpt.lock*", "sculpt_paint/sculpting/tool_settings/symmetry.html#bpy-types-sculpt-lock"),
|
||||
("bpy.types.shaderfxrim*", "grease_pencil/visual_effects/rim.html#bpy-types-shaderfxrim"),
|
||||
("bpy.types.spaceview3d*", "editors/3dview/index.html#bpy-types-spaceview3d"),
|
||||
("bpy.types.uipopupmenu*", "interface/controls/buttons/menus.html#bpy-types-uipopupmenu"),
|
||||
("bpy.types.vertexpaint*", "sculpt_paint/vertex_paint/index.html#bpy-types-vertexpaint"),
|
||||
("bpy.types.volumegrids*", "modeling/volumes/properties.html#bpy-types-volumegrids"),
|
||||
("bpy.types.woodtexture*", "render/materials/legacy_textures/types/wood.html#bpy-types-woodtexture"),
|
||||
("bpy.types.world.color*", "render/lights/world.html#bpy-types-world-color"),
|
||||
("bpy.ops.*.select_box*", "interface/selecting.html#bpy-ops-select-box"),
|
||||
("bpy.ops.curve.delete*", "modeling/curves/editing/curve.html#bpy-ops-curve-delete"),
|
||||
("bpy.ops.curve.reveal*", "modeling/curves/editing/curve.html#bpy-ops-curve-reveal"),
|
||||
("bpy.ops.curve.smooth*", "modeling/curves/editing/control_points.html#bpy-ops-curve-smooth"),
|
||||
("bpy.ops.file.execute*", "editors/file_browser.html#bpy-ops-file-execute"),
|
||||
("bpy.ops.file.refresh*", "editors/file_browser.html#bpy-ops-file-refresh"),
|
||||
("bpy.ops.fluid.preset*", "physics/fluid/type/domain/liquid/diffusion.html#bpy-ops-fluid-preset"),
|
||||
("bpy.ops.gpencil.copy*", "grease_pencil/modes/edit/grease_pencil_menu.html#bpy-ops-gpencil-copy"),
|
||||
("bpy.ops.graph.delete*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-delete"),
|
||||
("bpy.ops.graph.mirror*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-mirror"),
|
||||
("bpy.ops.graph.reveal*", "editors/graph_editor/channels.html#bpy-ops-graph-reveal"),
|
||||
("bpy.ops.graph.sample*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-sample"),
|
||||
("bpy.ops.graph.smooth*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-smooth"),
|
||||
("bpy.ops.graph.unbake*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-unbake"),
|
||||
("bpy.ops.image.invert*", "editors/image/editing.html#bpy-ops-image-invert"),
|
||||
("bpy.ops.image.reload*", "editors/image/editing.html#bpy-ops-image-reload"),
|
||||
("bpy.ops.image.resize*", "editors/image/editing.html#bpy-ops-image-resize"),
|
||||
("bpy.ops.image.unpack*", "editors/image/editing.html#bpy-ops-image-unpack"),
|
||||
("bpy.ops.material.new*", "render/materials/assignment.html#bpy-ops-material-new"),
|
||||
("bpy.ops.object.align*", "scene_layout/object/editing/transform/align_objects.html#bpy-ops-object-align"),
|
||||
("bpy.ops.object.empty*", "modeling/empties.html#bpy-ops-object-empty"),
|
||||
("bpy.ops.object.quick*", "physics/introduction.html#bpy-ops-object-quick"),
|
||||
("bpy.ops.text.replace*", "editors/text_editor.html#bpy-ops-text-replace"),
|
||||
("bpy.ops.uv.mark_seam*", "modeling/meshes/uv/unwrapping/seams.html#bpy-ops-uv-mark-seam"),
|
||||
("bpy.ops.view3d.dolly*", "editors/3dview/navigate/navigation.html#bpy-ops-view3d-dolly"),
|
||||
("bpy.ops.view3d.ruler*", "editors/3dview/toolbar/measure.html#bpy-ops-view3d-ruler"),
|
||||
("bpy.types.areaspaces*", "interface/window_system/areas.html#bpy-types-areaspaces"),
|
||||
("bpy.types.bonegroups*", "animation/armatures/properties/bone_groups.html#bpy-types-bonegroups"),
|
||||
("bpy.types.bpy_struct*", "files/data_blocks.html#bpy-types-bpy-struct"),
|
||||
("bpy.types.brush.rate*", "sculpt_paint/brush/stroke.html#bpy-types-brush-rate"),
|
||||
("bpy.types.brush.size*", "sculpt_paint/brush/brush_settings.html#bpy-types-brush-size"),
|
||||
("bpy.types.collection*", "scene_layout/collections/collections.html#bpy-types-collection"),
|
||||
("bpy.types.compositor*", "compositing/index.html#bpy-types-compositor"),
|
||||
("bpy.types.constraint*", "animation/constraints/index.html#bpy-types-constraint"),
|
||||
("bpy.types.imagepaint*", "sculpt_paint/texture_paint/index.html#bpy-types-imagepaint"),
|
||||
("bpy.types.keymapitem*", "editors/preferences/keymap.html#bpy-types-keymapitem"),
|
||||
("bpy.types.lightprobe*", "render/eevee/light_probes/index.html#bpy-types-lightprobe"),
|
||||
("bpy.types.maskparent*", "movie_clip/masking/sidebar.html#bpy-types-maskparent"),
|
||||
("bpy.types.maskspline*", "movie_clip/masking/sidebar.html#bpy-types-maskspline"),
|
||||
("bpy.types.motionpath*", "animation/motion_paths.html#bpy-types-motionpath"),
|
||||
("bpy.types.node.color*", "interface/controls/nodes/sidebar.html#bpy-types-node-color"),
|
||||
("bpy.types.node.label*", "interface/controls/nodes/sidebar.html#bpy-types-node-label"),
|
||||
("bpy.types.nodesocket*", "interface/controls/nodes/parts.html#bpy-types-nodesocket"),
|
||||
("bpy.types.paint.tile*", "sculpt_paint/texture_paint/tool_settings/tiling.html#bpy-types-paint-tile"),
|
||||
("bpy.types.pointcache*", "physics/baking.html#bpy-types-pointcache"),
|
||||
("bpy.types.pointlight*", "render/lights/light_object.html#bpy-types-pointlight"),
|
||||
("bpy.types.renderview*", "render/output/properties/stereoscopy/index.html#bpy-types-renderview"),
|
||||
("bpy.types.sceneeevee*", "render/eevee/index.html#bpy-types-sceneeevee"),
|
||||
("bpy.types.vectorfont*", "modeling/texts/index.html#bpy-types-vectorfont"),
|
||||
("bpy.ops.asset.clear*", "files/asset_libraries/introduction.html#bpy-ops-asset-clear"),
|
||||
("bpy.ops.clip.reload*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-reload"),
|
||||
("bpy.ops.curve.split*", "modeling/curves/editing/curve.html#bpy-ops-curve-split"),
|
||||
("bpy.ops.file.cancel*", "editors/file_browser.html#bpy-ops-file-cancel"),
|
||||
("bpy.ops.file.parent*", "editors/file_browser.html#bpy-ops-file-parent"),
|
||||
("bpy.ops.graph.clean*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-clean"),
|
||||
("bpy.ops.graph.paste*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-paste"),
|
||||
("bpy.ops.marker.move*", "animation/markers.html#bpy-ops-marker-move"),
|
||||
("bpy.ops.mask.delete*", "movie_clip/masking/editing.html#bpy-ops-mask-delete"),
|
||||
("bpy.ops.mesh.bisect*", "modeling/meshes/editing/mesh/bisect.html#bpy-ops-mesh-bisect"),
|
||||
("bpy.ops.mesh.delete*", "modeling/meshes/editing/mesh/delete.html#bpy-ops-mesh-delete"),
|
||||
("bpy.ops.nla.move_up*", "editors/nla/editing.html#bpy-ops-nla-move-up"),
|
||||
("bpy.ops.node.delete*", "interface/controls/nodes/editing.html#bpy-ops-node-delete"),
|
||||
("bpy.ops.node.detach*", "interface/controls/nodes/frame.html#bpy-ops-node-detach"),
|
||||
("bpy.ops.object.bake*", "render/cycles/baking.html#bpy-ops-object-bake"),
|
||||
("bpy.ops.object.hook*", "modeling/meshes/editing/vertex/hooks.html#bpy-ops-object-hook"),
|
||||
("bpy.ops.object.join*", "scene_layout/object/editing/join.html#bpy-ops-object-join"),
|
||||
("bpy.ops.object.text*", "modeling/texts/index.html#bpy-ops-object-text"),
|
||||
("bpy.ops.preferences*", "editors/preferences/index.html#bpy-ops-preferences"),
|
||||
("bpy.ops.spreadsheet*", "editors/spreadsheet.html#bpy-ops-spreadsheet"),
|
||||
("bpy.ops.uv.rip_move*", "modeling/meshes/uv/tools/rip.html#bpy-ops-uv-rip-move"),
|
||||
("bpy.ops.view3d.snap*", "scene_layout/object/editing/snap.html#bpy-ops-view3d-snap"),
|
||||
("bpy.ops.view3d.walk*", "editors/3dview/navigate/walk_fly.html#bpy-ops-view3d-walk"),
|
||||
("bpy.ops.view3d.zoom*", "editors/3dview/navigate/navigation.html#bpy-ops-view3d-zoom"),
|
||||
("bpy.types.*texspace*", "modeling/meshes/uv/uv_texture_spaces.html#bpy-types-texspace"),
|
||||
("bpy.types.arealight*", "render/lights/light_object.html#bpy-types-arealight"),
|
||||
("bpy.types.blenddata*", "files/data_blocks.html#bpy-types-blenddata"),
|
||||
("bpy.types.bone.hide*", "animation/armatures/bones/properties/display.html#bpy-types-bone-hide"),
|
||||
("bpy.types.colorramp*", "interface/controls/templates/color_ramp.html#bpy-types-colorramp"),
|
||||
("bpy.types.dopesheet*", "editors/dope_sheet/index.html#bpy-types-dopesheet"),
|
||||
("bpy.types.fmodifier*", "editors/graph_editor/fcurves/modifiers.html#bpy-types-fmodifier"),
|
||||
("bpy.types.freestyle*", "render/freestyle/index.html#bpy-types-freestyle"),
|
||||
("bpy.types.masklayer*", "movie_clip/masking/sidebar.html#bpy-types-masklayer"),
|
||||
("bpy.types.movieclip*", "movie_clip/index.html#bpy-types-movieclip"),
|
||||
("bpy.types.node.name*", "interface/controls/nodes/sidebar.html#bpy-types-node-name"),
|
||||
("bpy.types.nodeframe*", "interface/controls/nodes/frame.html#bpy-types-nodeframe"),
|
||||
("bpy.types.nodegroup*", "interface/controls/nodes/groups.html#bpy-types-nodegroup"),
|
||||
("bpy.types.scene.muv*", "addons/uv/magic_uv.html#bpy-types-scene-muv"),
|
||||
("bpy.types.spotlight*", "render/lights/light_object.html#bpy-types-spotlight"),
|
||||
("bpy.types.textcurve*", "modeling/texts/index.html#bpy-types-textcurve"),
|
||||
("bpy.types.udimtiles*", "modeling/meshes/uv/workflows/udims.html#bpy-types-udimtiles"),
|
||||
("bpy.types.uipiemenu*", "interface/controls/buttons/menus.html#bpy-types-uipiemenu"),
|
||||
("bpy.types.uipopover*", "interface/controls/buttons/menus.html#bpy-types-uipopover"),
|
||||
("bpy.types.viewlayer*", "render/layers/introduction.html#bpy-types-viewlayer"),
|
||||
("bpy.ops.asset.mark*", "files/asset_libraries/introduction.html#bpy-ops-asset-mark"),
|
||||
("bpy.ops.collection*", "scene_layout/collections/collections.html#bpy-ops-collection"),
|
||||
("bpy.ops.constraint*", "animation/constraints/index.html#bpy-ops-constraint"),
|
||||
("bpy.ops.curve.draw*", "modeling/curves/tools/draw.html#bpy-ops-curve-draw"),
|
||||
("bpy.ops.curve.hide*", "modeling/curves/editing/curve.html#bpy-ops-curve-hide"),
|
||||
("bpy.ops.curve.spin*", "modeling/surfaces/editing/surface.html#bpy-ops-curve-spin"),
|
||||
("bpy.ops.graph.bake*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-bake"),
|
||||
("bpy.ops.graph.copy*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-copy"),
|
||||
("bpy.ops.graph.hide*", "editors/graph_editor/channels.html#bpy-ops-graph-hide"),
|
||||
("bpy.ops.graph.snap*", "editors/graph_editor/fcurves/editing.html#bpy-ops-graph-snap"),
|
||||
("bpy.ops.image.flip*", "editors/image/editing.html#bpy-ops-image-flip"),
|
||||
("bpy.ops.image.open*", "editors/image/editing.html#bpy-ops-image-open"),
|
||||
("bpy.ops.image.pack*", "editors/image/editing.html#bpy-ops-image-pack"),
|
||||
("bpy.ops.image.save*", "editors/image/editing.html#bpy-ops-image-save"),
|
||||
("bpy.ops.image.tile*", "modeling/meshes/uv/workflows/udims.html#bpy-ops-image-tile"),
|
||||
("bpy.ops.marker.add*", "animation/markers.html#bpy-ops-marker-add"),
|
||||
("bpy.ops.mesh.bevel*", "modeling/meshes/editing/edge/bevel.html#bpy-ops-mesh-bevel"),
|
||||
("bpy.ops.mesh.inset*", "modeling/meshes/editing/face/inset_faces.html#bpy-ops-mesh-inset"),
|
||||
("bpy.ops.mesh.knife*", "modeling/meshes/editing/mesh/knife_topology_tool.html#bpy-ops-mesh-knife"),
|
||||
("bpy.ops.mesh.merge*", "modeling/meshes/editing/mesh/merge.html#bpy-ops-mesh-merge"),
|
||||
("bpy.ops.mesh.relax*", "addons/mesh/edit_mesh_tools.html#bpy-ops-mesh-relax"),
|
||||
("bpy.ops.mesh.screw*", "modeling/meshes/editing/edge/screw.html#bpy-ops-mesh-screw"),
|
||||
("bpy.ops.mesh.split*", "modeling/meshes/editing/mesh/split.html#bpy-ops-mesh-split"),
|
||||
("bpy.ops.nla.delete*", "editors/nla/editing.html#bpy-ops-nla-delete"),
|
||||
("bpy.ops.paint.mask*", "sculpt_paint/sculpting/editing/mask.html#bpy-ops-paint-mask"),
|
||||
("bpy.ops.pose.paste*", "animation/armatures/posing/editing/copy_paste.html#bpy-ops-pose-paste"),
|
||||
("bpy.ops.pose.relax*", "animation/armatures/posing/editing/in_betweens.html#bpy-ops-pose-relax"),
|
||||
("bpy.ops.safe_areas*", "render/cameras.html#bpy-ops-safe-areas"),
|
||||
("bpy.ops.view3d.fly*", "editors/3dview/navigate/walk_fly.html#bpy-ops-view3d-fly"),
|
||||
("bpy.ops.wm.toolbar*", "interface/tool_system.html#bpy-ops-wm-toolbar"),
|
||||
("bpy.types.aov.type*", "render/layers/passes.html#bpy-types-aov-type"),
|
||||
("bpy.types.armature*", "animation/armatures/index.html#bpy-types-armature"),
|
||||
("bpy.types.editbone*", "animation/armatures/bones/editing/index.html#bpy-types-editbone"),
|
||||
("bpy.types.facemaps*", "modeling/meshes/properties/object_data.html#bpy-types-facemaps"),
|
||||
("bpy.types.keyframe*", "animation/keyframes/index.html#bpy-types-keyframe"),
|
||||
("bpy.types.linesets*", "render/freestyle/view_layer/line_set.html#bpy-types-linesets"),
|
||||
("bpy.types.material*", "render/materials/index.html#bpy-types-material"),
|
||||
("bpy.types.metaball*", "modeling/metas/index.html#bpy-types-metaball"),
|
||||
("bpy.types.modifier*", "modeling/modifiers/index.html#bpy-types-modifier"),
|
||||
("bpy.types.nlastrip*", "editors/nla/strips.html#bpy-types-nlastrip"),
|
||||
("bpy.types.nlatrack*", "editors/nla/tracks.html#bpy-types-nlatrack"),
|
||||
("bpy.types.nodelink*", "interface/controls/nodes/parts.html#bpy-types-nodelink"),
|
||||
("bpy.types.nodetree*", "interface/controls/nodes/parts.html#bpy-types-nodetree"),
|
||||
("bpy.types.particle*", "physics/particles/index.html#bpy-types-particle"),
|
||||
("bpy.types.sequence*", "video_editing/index.html#bpy-types-sequence"),
|
||||
("bpy.types.shaderfx*", "grease_pencil/visual_effects/index.html#bpy-types-shaderfx"),
|
||||
("bpy.types.shapekey*", "animation/shape_keys/index.html#bpy-types-shapekey"),
|
||||
("bpy.types.spacenla*", "editors/nla/index.html#bpy-types-spacenla"),
|
||||
("bpy.types.sunlight*", "render/lights/light_object.html#bpy-types-sunlight"),
|
||||
("bpy.ops.clip.open*", "movie_clip/tracking/clip/editing/clip.html#bpy-ops-clip-open"),
|
||||
("bpy.ops.curve.pen*", "modeling/curves/tools/pen.html#bpy-ops-curve-pen"),
|
||||
("bpy.ops.file.next*", "editors/file_browser.html#bpy-ops-file-next"),
|
||||
("bpy.ops.image.new*", "editors/image/editing.html#bpy-ops-image-new"),
|
||||
("bpy.ops.mesh.fill*", "modeling/meshes/editing/face/fill.html#bpy-ops-mesh-fill"),
|
||||
("bpy.ops.mesh.poke*", "modeling/meshes/editing/face/poke_faces.html#bpy-ops-mesh-poke"),
|
||||
("bpy.ops.mesh.spin*", "modeling/meshes/tools/spin.html#bpy-ops-mesh-spin"),
|
||||
("bpy.ops.nla.split*", "editors/nla/editing.html#bpy-ops-nla-split"),
|
||||
("bpy.ops.node.join*", "interface/controls/nodes/frame.html#bpy-ops-node-join"),
|
||||
("bpy.ops.pose.copy*", "animation/armatures/posing/editing/copy_paste.html#bpy-ops-pose-copy"),
|
||||
("bpy.ops.pose.push*", "animation/armatures/posing/editing/in_betweens.html#bpy-ops-pose-push"),
|
||||
("bpy.ops.rigidbody*", "physics/rigid_body/index.html#bpy-ops-rigidbody"),
|
||||
("bpy.ops.sequencer*", "video_editing/index.html#bpy-ops-sequencer"),
|
||||
("bpy.ops.text.find*", "editors/text_editor.html#bpy-ops-text-find"),
|
||||
("bpy.ops.transform*", "scene_layout/object/editing/transform/index.html#bpy-ops-transform"),
|
||||
("bpy.ops.uv.reveal*", "modeling/meshes/uv/editing.html#bpy-ops-uv-reveal"),
|
||||
("bpy.ops.uv.select*", "editors/uv/selecting.html#bpy-ops-uv-select"),
|
||||
("bpy.ops.uv.stitch*", "modeling/meshes/uv/editing.html#bpy-ops-uv-stitch"),
|
||||
("bpy.ops.uv.unwrap*", "modeling/meshes/editing/uv.html#bpy-ops-uv-unwrap"),
|
||||
("bpy.ops.wm.append*", "files/linked_libraries/link_append.html#bpy-ops-wm-append"),
|
||||
("bpy.ops.wm.search*", "interface/controls/templates/operator_search.html#bpy-ops-wm-search"),
|
||||
("bpy.types.lattice*", "animation/lattice.html#bpy-types-lattice"),
|
||||
("bpy.types.library*", "files/linked_libraries/index.html#bpy-types-library"),
|
||||
("bpy.types.speaker*", "render/output/audio/speaker.html#bpy-types-speaker"),
|
||||
("bpy.types.textbox*", "modeling/texts/properties.html#bpy-types-textbox"),
|
||||
("bpy.types.texture*", "render/materials/legacy_textures/index.html#bpy-types-texture"),
|
||||
("bpy.ops.armature*", "animation/armatures/index.html#bpy-ops-armature"),
|
||||
("bpy.ops.geometry*", "modeling/index.html#bpy-ops-geometry"),
|
||||
("bpy.ops.material*", "render/materials/index.html#bpy-ops-material"),
|
||||
("bpy.ops.nla.bake*", "editors/nla/editing.html#bpy-ops-nla-bake"),
|
||||
("bpy.ops.nla.snap*", "editors/nla/editing.html#bpy-ops-nla-snap"),
|
||||
("bpy.ops.nla.swap*", "editors/nla/editing.html#bpy-ops-nla-swap"),
|
||||
("bpy.ops.outliner*", "editors/outliner/index.html#bpy-ops-outliner"),
|
||||
("bpy.ops.particle*", "physics/particles/index.html#bpy-ops-particle"),
|
||||
("bpy.ops.uv.align*", "modeling/meshes/uv/editing.html#bpy-ops-uv-align"),
|
||||
("bpy.ops.uv.paste*", "modeling/meshes/uv/editing.html#bpy-ops-uv-paste"),
|
||||
("bpy.ops.uv.reset*", "modeling/meshes/editing/uv.html#bpy-ops-uv-reset"),
|
||||
("bpy.ops.wm.addon*", "editors/preferences/addons.html#bpy-ops-wm-addon"),
|
||||
("bpy.types.action*", "animation/actions.html#bpy-types-action"),
|
||||
("bpy.types.camera*", "render/cameras.html#bpy-types-camera"),
|
||||
("bpy.types.cycles*", "render/cycles/index.html#bpy-types-cycles"),
|
||||
("bpy.types.driver*", "animation/drivers/index.html#bpy-types-driver"),
|
||||
("bpy.types.fcurve*", "editors/graph_editor/fcurves/index.html#bpy-types-fcurve"),
|
||||
("bpy.types.header*", "interface/window_system/regions.html#bpy-types-header"),
|
||||
("bpy.types.object*", "scene_layout/object/index.html#bpy-types-object"),
|
||||
("bpy.types.region*", "interface/window_system/regions.html#bpy-types-region"),
|
||||
("bpy.types.render*", "render/index.html#bpy-types-render"),
|
||||
("bpy.types.screen*", "interface/index.html#bpy-types-screen"),
|
||||
("bpy.types.sculpt*", "sculpt_paint/sculpting/index.html#bpy-types-sculpt"),
|
||||
("bpy.types.shader*", "render/shader_nodes/shader/index.html#bpy-types-shader"),
|
||||
("bpy.types.spline*", "modeling/curves/properties/active_spline.html#bpy-types-spline"),
|
||||
("bpy.types.volume*", "modeling/volumes/index.html#bpy-types-volume"),
|
||||
("bpy.types.window*", "interface/index.html#bpy-types-window"),
|
||||
("bpy.ops.buttons*", "interface/controls/buttons/buttons.html#bpy-ops-buttons"),
|
||||
("bpy.ops.console*", "editors/python_console.html#bpy-ops-console"),
|
||||
("bpy.ops.ed.redo*", "interface/undo_redo.html#bpy-ops-ed-redo"),
|
||||
("bpy.ops.ed.undo*", "interface/undo_redo.html#bpy-ops-ed-undo"),
|
||||
("bpy.ops.gpencil*", "grease_pencil/index.html#bpy-ops-gpencil"),
|
||||
("bpy.ops.lattice*", "animation/lattice.html#bpy-ops-lattice"),
|
||||
("bpy.ops.poselib*", "animation/armatures/posing/editing/pose_library.html#bpy-ops-poselib"),
|
||||
("bpy.ops.ptcache*", "physics/baking.html#bpy-ops-ptcache"),
|
||||
("bpy.ops.surface*", "modeling/surfaces/index.html#bpy-ops-surface"),
|
||||
("bpy.ops.texture*", "render/materials/legacy_textures/index.html#bpy-ops-texture"),
|
||||
("bpy.ops.uv.copy*", "modeling/meshes/uv/editing.html#bpy-ops-uv-copy"),
|
||||
("bpy.ops.uv.hide*", "modeling/meshes/uv/editing.html#bpy-ops-uv-hide"),
|
||||
("bpy.ops.uv.weld*", "modeling/meshes/uv/editing.html#bpy-ops-uv-weld"),
|
||||
("bpy.ops.wm.link*", "files/linked_libraries/link_append.html#bpy-ops-wm-link"),
|
||||
("bpy.ops.wm.tool*", "interface/tool_system.html#bpy-ops-wm-tool"),
|
||||
("bpy.types.addon*", "editors/preferences/addons.html#bpy-types-addon"),
|
||||
("bpy.types.brush*", "sculpt_paint/brush/brush.html#bpy-types-brush"),
|
||||
("bpy.types.curve*", "modeling/curves/index.html#bpy-types-curve"),
|
||||
("bpy.types.image*", "files/media/image_formats.html#bpy-types-image"),
|
||||
("bpy.types.itasc*", "animation/armatures/posing/bone_constraints/inverse_kinematics/introduction.html#bpy-types-itasc"),
|
||||
("bpy.types.nodes*", "interface/controls/nodes/index.html#bpy-types-nodes"),
|
||||
("bpy.types.paint*", "sculpt_paint/index.html#bpy-types-paint"),
|
||||
("bpy.types.panel*", "interface/window_system/tabs_panels.html#bpy-types-panel"),
|
||||
("bpy.types.scene*", "scene_layout/scene/index.html#bpy-types-scene"),
|
||||
("bpy.types.sound*", "render/output/audio/index.html#bpy-types-sound"),
|
||||
("bpy.types.space*", "editors/index.html#bpy-types-space"),
|
||||
("bpy.types.theme*", "editors/preferences/themes.html#bpy-types-theme"),
|
||||
("bpy.types.world*", "render/lights/world.html#bpy-types-world"),
|
||||
("bpy.ops.action*", "animation/actions.html#bpy-ops-action"),
|
||||
("bpy.ops.camera*", "render/cameras.html#bpy-ops-camera"),
|
||||
("bpy.ops.cycles*", "render/cycles/index.html#bpy-ops-cycles"),
|
||||
("bpy.ops.dpaint*", "physics/dynamic_paint/index.html#bpy-ops-dpaint"),
|
||||
("bpy.ops.export*", "files/import_export.html#bpy-ops-export"),
|
||||
("bpy.ops.import*", "files/import_export.html#bpy-ops-import"),
|
||||
("bpy.ops.marker*", "animation/markers.html#bpy-ops-marker"),
|
||||
("bpy.ops.object*", "scene_layout/object/index.html#bpy-ops-object"),
|
||||
("bpy.ops.render*", "render/index.html#bpy-ops-render"),
|
||||
("bpy.ops.script*", "advanced/scripting/index.html#bpy-ops-script"),
|
||||
("bpy.ops.sculpt*", "sculpt_paint/sculpting/index.html#bpy-ops-sculpt"),
|
||||
("bpy.ops.uv.muv*", "addons/uv/magic_uv.html#bpy-ops-uv-muv"),
|
||||
("bpy.ops.uv.pin*", "modeling/meshes/uv/editing.html#bpy-ops-uv-pin"),
|
||||
("bpy.ops.uv.rip*", "modeling/meshes/uv/tools/rip.html#bpy-ops-uv-rip"),
|
||||
("bpy.ops.view3d*", "editors/3dview/index.html#bpy-ops-view3d"),
|
||||
("bpy.types.area*", "interface/window_system/areas.html#bpy-types-area"),
|
||||
("bpy.types.boid*", "physics/particles/emitter/physics/boids.html#bpy-types-boid"),
|
||||
("bpy.types.bone*", "animation/armatures/bones/index.html#bpy-types-bone"),
|
||||
("bpy.types.mask*", "movie_clip/masking/index.html#bpy-types-mask"),
|
||||
("bpy.types.menu*", "interface/controls/buttons/menus.html#bpy-types-menu"),
|
||||
("bpy.types.mesh*", "modeling/meshes/index.html#bpy-types-mesh"),
|
||||
("bpy.types.node*", "interface/controls/nodes/index.html#bpy-types-node"),
|
||||
("bpy.types.pose*", "animation/armatures/posing/index.html#bpy-types-pose"),
|
||||
("bpy.types.text*", "editors/text_editor.html#bpy-types-text"),
|
||||
("bpy.ops.brush*", "sculpt_paint/brush/brush.html#bpy-ops-brush"),
|
||||
("bpy.ops.cloth*", "physics/cloth/index.html#bpy-ops-cloth"),
|
||||
("bpy.ops.curve*", "modeling/curves/index.html#bpy-ops-curve"),
|
||||
("bpy.ops.graph*", "editors/graph_editor/index.html#bpy-ops-graph"),
|
||||
("bpy.ops.image*", "files/media/image_formats.html#bpy-ops-image"),
|
||||
("bpy.ops.mball*", "modeling/metas/index.html#bpy-ops-mball"),
|
||||
("bpy.ops.paint*", "sculpt_paint/index.html#bpy-ops-paint"),
|
||||
("bpy.ops.scene*", "scene_layout/scene/index.html#bpy-ops-scene"),
|
||||
("bpy.ops.sound*", "render/output/audio/index.html#bpy-ops-sound"),
|
||||
("bpy.types.aov*", "render/layers/passes.html#bpy-types-aov"),
|
||||
("bpy.types.key*", "animation/shape_keys/index.html#bpy-types-key"),
|
||||
("bpy.ops.anim*", "animation/index.html#bpy-ops-anim"),
|
||||
("bpy.ops.boid*", "physics/particles/emitter/physics/boids.html#bpy-ops-boid"),
|
||||
("bpy.ops.clip*", "movie_clip/index.html#bpy-ops-clip"),
|
||||
("bpy.ops.file*", "editors/file_browser.html#bpy-ops-file"),
|
||||
("bpy.ops.font*", "modeling/texts/index.html#bpy-ops-font"),
|
||||
("bpy.ops.mask*", "movie_clip/masking/index.html#bpy-ops-mask"),
|
||||
("bpy.ops.mesh*", "modeling/meshes/index.html#bpy-ops-mesh"),
|
||||
("bpy.ops.node*", "interface/controls/nodes/index.html#bpy-ops-node"),
|
||||
("bpy.ops.pose*", "animation/armatures/posing/index.html#bpy-ops-pose"),
|
||||
("bpy.ops.text*", "editors/text_editor.html#bpy-ops-text"),
|
||||
("bpy.ops.time*", "editors/timeline.html#bpy-ops-time"),
|
||||
("bpy.types.id*", "files/data_blocks.html#bpy-types-id"),
|
||||
("bpy.ops.nla*", "editors/nla/index.html#bpy-ops-nla"),
|
||||
("bpy.ops.ed*", "interface/undo_redo.html#bpy-ops-ed"),
|
||||
("bpy.ops.ui*", "interface/index.html#bpy-ops-ui"),
|
||||
("bpy.ops.wm*", "interface/index.html#bpy-ops-wm"),
|
||||
)
|
||||
|
||||
# autopep8: on
|
||||
231
scripts/modules/rna_prop_ui.py
Normal file
231
scripts/modules/rna_prop_ui.py
Normal file
@@ -0,0 +1,231 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import bpy
|
||||
|
||||
from mathutils import Vector
|
||||
from bpy.types import bpy_prop_array
|
||||
from idprop.types import IDPropertyArray, IDPropertyGroup
|
||||
|
||||
ARRAY_TYPES = (list, tuple, IDPropertyArray, Vector, bpy_prop_array)
|
||||
|
||||
# Maximum length of an array property for which a multi-line
|
||||
# edit field will be displayed in the Custom Properties panel.
|
||||
MAX_DISPLAY_ROWS = 8
|
||||
|
||||
|
||||
def rna_idprop_quote_path(prop):
|
||||
return "[\"%s\"]" % bpy.utils.escape_identifier(prop)
|
||||
|
||||
|
||||
def rna_idprop_ui_prop_update(item, prop):
|
||||
prop_path = rna_idprop_quote_path(prop)
|
||||
prop_rna = item.path_resolve(prop_path, False)
|
||||
if isinstance(prop_rna, bpy.types.bpy_prop):
|
||||
prop_rna.update()
|
||||
|
||||
|
||||
def rna_idprop_ui_prop_clear(item, prop):
|
||||
ui_data = item.id_properties_ui(prop)
|
||||
ui_data.clear()
|
||||
|
||||
|
||||
def rna_idprop_context_value(context, context_member, property_type):
|
||||
space = context.space_data
|
||||
|
||||
if space is None or isinstance(space, bpy.types.SpaceProperties):
|
||||
pin_id = space.pin_id
|
||||
else:
|
||||
pin_id = None
|
||||
|
||||
if pin_id and isinstance(pin_id, property_type):
|
||||
rna_item = pin_id
|
||||
context_member = "space_data.pin_id"
|
||||
else:
|
||||
rna_item = context.path_resolve(context_member)
|
||||
|
||||
return rna_item, context_member
|
||||
|
||||
|
||||
def rna_idprop_has_properties(rna_item):
|
||||
keys = rna_item.keys()
|
||||
return bool(keys)
|
||||
|
||||
|
||||
def rna_idprop_value_to_python(value):
|
||||
if isinstance(value, IDPropertyArray):
|
||||
return value.to_list()
|
||||
elif isinstance(value, IDPropertyGroup):
|
||||
return value.to_dict()
|
||||
else:
|
||||
return value
|
||||
|
||||
|
||||
def rna_idprop_value_item_type(value):
|
||||
is_array = isinstance(value, ARRAY_TYPES) and len(value) > 0
|
||||
item_value = value[0] if is_array else value
|
||||
return type(item_value), is_array
|
||||
|
||||
|
||||
def rna_idprop_ui_prop_default_set(item, prop, value):
|
||||
ui_data = item.id_properties_ui(prop)
|
||||
ui_data.update(default=value)
|
||||
|
||||
|
||||
def rna_idprop_ui_create(
|
||||
item, prop, *, default,
|
||||
min=0.0, max=1.0,
|
||||
soft_min=None, soft_max=None,
|
||||
description=None,
|
||||
overridable=False,
|
||||
subtype=None,
|
||||
):
|
||||
"""Create and initialize a custom property with limits, defaults and other settings."""
|
||||
|
||||
# Assign the value
|
||||
item[prop] = default
|
||||
|
||||
rna_idprop_ui_prop_update(item, prop)
|
||||
ui_data = item.id_properties_ui(prop)
|
||||
proptype, _ = rna_idprop_value_item_type(default)
|
||||
|
||||
if proptype is bool:
|
||||
ui_data = item.id_properties_ui(prop)
|
||||
ui_data.update(
|
||||
description=description,
|
||||
default=default,
|
||||
)
|
||||
return
|
||||
|
||||
if soft_min is None:
|
||||
soft_min = min
|
||||
if soft_max is None:
|
||||
soft_max = max
|
||||
|
||||
ui_data.update(
|
||||
subtype=subtype,
|
||||
min=min,
|
||||
max=max,
|
||||
soft_min=soft_min,
|
||||
soft_max=soft_max,
|
||||
description=description,
|
||||
default=default,
|
||||
)
|
||||
|
||||
prop_path = rna_idprop_quote_path(prop)
|
||||
|
||||
item.property_overridable_library_set(prop_path, overridable)
|
||||
|
||||
|
||||
def draw(layout, context, context_member, property_type, *, use_edit=True):
|
||||
rna_item, context_member = rna_idprop_context_value(context, context_member, property_type)
|
||||
# poll should really get this...
|
||||
if not rna_item:
|
||||
return
|
||||
|
||||
from bpy.utils import escape_identifier
|
||||
|
||||
if rna_item.id_data.library is not None:
|
||||
use_edit = False
|
||||
is_lib_override = rna_item.id_data.override_library and rna_item.id_data.override_library.reference
|
||||
|
||||
assert isinstance(rna_item, property_type)
|
||||
|
||||
items = list(rna_item.items())
|
||||
items.sort()
|
||||
|
||||
# TODO: Allow/support adding new custom props to overrides.
|
||||
if use_edit and not is_lib_override:
|
||||
row = layout.row()
|
||||
props = row.operator("wm.properties_add", text="New", icon='ADD')
|
||||
props.data_path = context_member
|
||||
del row
|
||||
layout.separator()
|
||||
|
||||
show_developer_ui = context.preferences.view.show_developer_ui
|
||||
rna_properties = {prop.identifier for prop in rna_item.bl_rna.properties if prop.is_runtime} if items else None
|
||||
|
||||
layout.use_property_decorate = False
|
||||
|
||||
for key, value in items:
|
||||
is_rna = (key in rna_properties)
|
||||
|
||||
# Only show API defined properties to developers.
|
||||
if is_rna and not show_developer_ui:
|
||||
continue
|
||||
|
||||
to_dict = getattr(value, "to_dict", None)
|
||||
to_list = getattr(value, "to_list", None)
|
||||
|
||||
if to_dict:
|
||||
value = to_dict()
|
||||
elif to_list:
|
||||
value = to_list()
|
||||
|
||||
split = layout.split(factor=0.4, align=True)
|
||||
label_row = split.row()
|
||||
label_row.alignment = 'RIGHT'
|
||||
label_row.label(text=key, translate=False)
|
||||
|
||||
value_row = split.row(align=True)
|
||||
value_column = value_row.column(align=True)
|
||||
|
||||
is_long_array = to_list and len(value) >= MAX_DISPLAY_ROWS
|
||||
|
||||
if is_rna:
|
||||
value_column.prop(rna_item, key, text="")
|
||||
elif to_dict or is_long_array:
|
||||
props = value_column.operator("wm.properties_edit_value", text="Edit Value")
|
||||
props.data_path = context_member
|
||||
props.property_name = key
|
||||
else:
|
||||
value_column.prop(rna_item, '["%s"]' % escape_identifier(key), text="")
|
||||
|
||||
operator_row = value_row.row()
|
||||
operator_row.alignment = 'RIGHT'
|
||||
|
||||
# Do not allow editing of overridden properties (we cannot use a poll function
|
||||
# of the operators here since they's have no access to the specific property).
|
||||
operator_row.enabled = not (is_lib_override and key in rna_item.id_data.override_library.reference)
|
||||
|
||||
if use_edit:
|
||||
if is_rna:
|
||||
operator_row.label(text="API Defined")
|
||||
elif is_lib_override:
|
||||
operator_row.active = False
|
||||
operator_row.label(text="", icon='DECORATE_LIBRARY_OVERRIDE')
|
||||
else:
|
||||
props = operator_row.operator("wm.properties_edit", text="", icon='PREFERENCES', emboss=False)
|
||||
props.data_path = context_member
|
||||
props.property_name = key
|
||||
props = operator_row.operator("wm.properties_remove", text="", icon='X', emboss=False)
|
||||
props.data_path = context_member
|
||||
props.property_name = key
|
||||
else:
|
||||
# Add some spacing, so the right side of the buttons line up with layouts with decorators.
|
||||
operator_row.label(text="", icon='BLANK1')
|
||||
|
||||
|
||||
class PropertyPanel:
|
||||
"""
|
||||
The subclass should have its own poll function
|
||||
and the variable '_context_path' MUST be set.
|
||||
"""
|
||||
bl_label = "Custom Properties"
|
||||
bl_options = {'DEFAULT_CLOSED'}
|
||||
bl_order = 1000 # Order panel after all others
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
rna_item, _context_member = rna_idprop_context_value(context, cls._context_path, cls._property_type)
|
||||
return bool(rna_item)
|
||||
|
||||
"""
|
||||
def draw_header(self, context):
|
||||
rna_item, context_member = rna_idprop_context_value(context, self._context_path, self._property_type)
|
||||
tot = len(rna_item.keys())
|
||||
if tot:
|
||||
self.layout().label(text="%d:" % tot)
|
||||
"""
|
||||
|
||||
def draw(self, context):
|
||||
draw(self.layout, context, self._context_path, self._property_type)
|
||||
391
scripts/modules/rna_xml.py
Normal file
391
scripts/modules/rna_xml.py
Normal file
@@ -0,0 +1,391 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def build_property_typemap(skip_classes, skip_typemap):
|
||||
|
||||
property_typemap = {}
|
||||
|
||||
for attr in dir(bpy.types):
|
||||
# Skip internal methods.
|
||||
if attr.startswith("_"):
|
||||
continue
|
||||
cls = getattr(bpy.types, attr)
|
||||
if issubclass(cls, skip_classes):
|
||||
continue
|
||||
bl_rna = getattr(cls, "bl_rna", None)
|
||||
# Needed to skip classes added to the modules `__dict__`.
|
||||
if bl_rna is None:
|
||||
continue
|
||||
|
||||
# # to support skip-save we can't get all props
|
||||
# properties = bl_rna.properties.keys()
|
||||
properties = []
|
||||
for prop_id, prop in bl_rna.properties.items():
|
||||
if not prop.is_skip_save:
|
||||
properties.append(prop_id)
|
||||
|
||||
properties.remove("rna_type")
|
||||
property_typemap[attr] = properties
|
||||
|
||||
if skip_typemap:
|
||||
for cls_name, properties_blacklist in skip_typemap.items():
|
||||
properties = property_typemap.get(cls_name)
|
||||
if properties is not None:
|
||||
for prop_id in properties_blacklist:
|
||||
try:
|
||||
properties.remove(prop_id)
|
||||
except:
|
||||
print("skip_typemap unknown prop_id '%s.%s'" % (cls_name, prop_id))
|
||||
else:
|
||||
print("skip_typemap unknown class '%s'" % cls_name)
|
||||
|
||||
return property_typemap
|
||||
|
||||
|
||||
def print_ln(data):
|
||||
print(data, end="")
|
||||
|
||||
|
||||
def rna2xml(
|
||||
fw=print_ln,
|
||||
root_node="",
|
||||
root_rna=None, # must be set
|
||||
root_rna_skip=set(),
|
||||
root_ident="",
|
||||
ident_val=" ",
|
||||
skip_classes=(
|
||||
bpy.types.Operator,
|
||||
bpy.types.Panel,
|
||||
bpy.types.KeyingSet,
|
||||
bpy.types.Header,
|
||||
bpy.types.PropertyGroup,
|
||||
),
|
||||
skip_typemap=None,
|
||||
pretty_format=True,
|
||||
method='DATA',
|
||||
):
|
||||
from xml.sax.saxutils import quoteattr
|
||||
property_typemap = build_property_typemap(skip_classes, skip_typemap)
|
||||
|
||||
# don't follow properties of this type, just reference them by name
|
||||
# they MUST have a unique 'name' property.
|
||||
# 'ID' covers most types
|
||||
referenced_classes = (
|
||||
bpy.types.ID,
|
||||
bpy.types.Bone,
|
||||
bpy.types.ActionGroup,
|
||||
bpy.types.PoseBone,
|
||||
bpy.types.Node,
|
||||
bpy.types.Sequence,
|
||||
)
|
||||
|
||||
def number_to_str(val, val_type):
|
||||
if val_type == int:
|
||||
return "%d" % val
|
||||
elif val_type == float:
|
||||
return "%.6g" % val
|
||||
elif val_type == bool:
|
||||
return "TRUE" if val else "FALSE"
|
||||
else:
|
||||
raise NotImplementedError("this type is not a number %s" % val_type)
|
||||
|
||||
def rna2xml_node(ident, value, parent):
|
||||
ident_next = ident + ident_val
|
||||
|
||||
# divide into attrs and nodes.
|
||||
node_attrs = []
|
||||
nodes_items = []
|
||||
nodes_lists = []
|
||||
|
||||
value_type = type(value)
|
||||
|
||||
if issubclass(value_type, skip_classes):
|
||||
return
|
||||
|
||||
# XXX, fixme, pointcache has eternal nested pointer to itself.
|
||||
if value == parent:
|
||||
return
|
||||
|
||||
value_type_name = value_type.__name__
|
||||
for prop in property_typemap[value_type_name]:
|
||||
|
||||
subvalue = getattr(value, prop)
|
||||
subvalue_type = type(subvalue)
|
||||
|
||||
if subvalue_type in {int, bool, float}:
|
||||
node_attrs.append("%s=\"%s\"" % (prop, number_to_str(subvalue, subvalue_type)))
|
||||
elif subvalue_type is str:
|
||||
node_attrs.append("%s=%s" % (prop, quoteattr(subvalue)))
|
||||
elif subvalue_type is set:
|
||||
node_attrs.append("%s=%s" % (prop, quoteattr("{" + ",".join(list(subvalue)) + "}")))
|
||||
elif subvalue is None:
|
||||
node_attrs.append("%s=\"NONE\"" % prop)
|
||||
elif issubclass(subvalue_type, referenced_classes):
|
||||
# special case, ID's are always referenced.
|
||||
node_attrs.append("%s=%s" % (prop, quoteattr(subvalue_type.__name__ + "::" + subvalue.name)))
|
||||
else:
|
||||
try:
|
||||
subvalue_ls = list(subvalue)
|
||||
except:
|
||||
subvalue_ls = None
|
||||
|
||||
if subvalue_ls is None:
|
||||
nodes_items.append((prop, subvalue, subvalue_type))
|
||||
else:
|
||||
# check if the list contains native types
|
||||
subvalue_rna = value.path_resolve(prop, False)
|
||||
if type(subvalue_rna).__name__ == "bpy_prop_array":
|
||||
# check if this is a 0-1 color (rgb, rgba)
|
||||
# in that case write as a hexadecimal
|
||||
prop_rna = value.bl_rna.properties[prop]
|
||||
if (prop_rna.subtype == 'COLOR_GAMMA' and
|
||||
prop_rna.hard_min == 0.0 and
|
||||
prop_rna.hard_max == 1.0 and
|
||||
prop_rna.array_length in {3, 4}):
|
||||
# -----
|
||||
# color
|
||||
array_value = "#" + "".join(("%.2x" % int(v * 255) for v in subvalue_rna))
|
||||
|
||||
else:
|
||||
# default
|
||||
def str_recursive(s):
|
||||
subsubvalue_type = type(s)
|
||||
if subsubvalue_type in {int, float, bool}:
|
||||
return number_to_str(s, subsubvalue_type)
|
||||
else:
|
||||
return " ".join([str_recursive(si) for si in s])
|
||||
|
||||
array_value = " ".join(str_recursive(v) for v in subvalue_rna)
|
||||
|
||||
node_attrs.append("%s=\"%s\"" % (prop, array_value))
|
||||
else:
|
||||
nodes_lists.append((prop, subvalue_ls, subvalue_type))
|
||||
|
||||
# declare + attributes
|
||||
if pretty_format:
|
||||
if node_attrs:
|
||||
fw("%s<%s\n" % (ident, value_type_name))
|
||||
for node_attr in node_attrs:
|
||||
fw("%s%s\n" % (ident_next, node_attr))
|
||||
fw("%s>\n" % (ident_next,))
|
||||
else:
|
||||
fw("%s<%s>\n" % (ident, value_type_name))
|
||||
else:
|
||||
fw("%s<%s %s>\n" % (ident, value_type_name, " ".join(node_attrs)))
|
||||
|
||||
# unique members
|
||||
for prop, subvalue, subvalue_type in nodes_items:
|
||||
fw("%s<%s>\n" % (ident_next, prop)) # XXX, this is awkward, how best to solve?
|
||||
rna2xml_node(ident_next + ident_val, subvalue, value)
|
||||
fw("%s</%s>\n" % (ident_next, prop)) # XXX, need to check on this.
|
||||
|
||||
# list members
|
||||
for prop, subvalue, subvalue_type in nodes_lists:
|
||||
fw("%s<%s>\n" % (ident_next, prop))
|
||||
for subvalue_item in subvalue:
|
||||
if subvalue_item is not None:
|
||||
rna2xml_node(ident_next + ident_val, subvalue_item, value)
|
||||
fw("%s</%s>\n" % (ident_next, prop))
|
||||
|
||||
fw("%s</%s>\n" % (ident, value_type_name))
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# needs re-working to be generic
|
||||
|
||||
if root_node:
|
||||
fw("%s<%s>\n" % (root_ident, root_node))
|
||||
|
||||
# bpy.data
|
||||
if method == 'DATA':
|
||||
ident = root_ident + ident_val
|
||||
for attr in dir(root_rna):
|
||||
|
||||
# exceptions
|
||||
if attr.startswith("_"):
|
||||
continue
|
||||
elif attr in root_rna_skip:
|
||||
continue
|
||||
|
||||
value = getattr(root_rna, attr)
|
||||
try:
|
||||
ls = value[:]
|
||||
except:
|
||||
ls = None
|
||||
|
||||
if type(ls) == list:
|
||||
fw("%s<%s>\n" % (ident, attr))
|
||||
for blend_id in ls:
|
||||
rna2xml_node(ident + ident_val, blend_id, None)
|
||||
fw("%s</%s>\n" % (ident_val, attr))
|
||||
# any attribute
|
||||
elif method == 'ATTR':
|
||||
rna2xml_node(root_ident, root_rna, None)
|
||||
|
||||
if root_node:
|
||||
fw("%s</%s>\n" % (root_ident, root_node))
|
||||
|
||||
|
||||
def xml2rna(
|
||||
root_xml, *,
|
||||
root_rna=None, # must be set
|
||||
):
|
||||
|
||||
def rna2xml_node(xml_node, value):
|
||||
# print("evaluating:", xml_node.nodeName)
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Simple attributes
|
||||
|
||||
for attr in xml_node.attributes.keys():
|
||||
# print(" ", attr)
|
||||
subvalue = getattr(value, attr, Ellipsis)
|
||||
|
||||
if subvalue is Ellipsis:
|
||||
print("%s.%s not found" % (type(value).__name__, attr))
|
||||
else:
|
||||
value_xml = xml_node.attributes[attr].value
|
||||
|
||||
subvalue_type = type(subvalue)
|
||||
# tp_name = 'UNKNOWN'
|
||||
if subvalue_type == float:
|
||||
value_xml_coerce = float(value_xml)
|
||||
# tp_name = 'FLOAT'
|
||||
elif subvalue_type == int:
|
||||
value_xml_coerce = int(value_xml)
|
||||
# tp_name = 'INT'
|
||||
elif subvalue_type == bool:
|
||||
value_xml_coerce = {'TRUE': True, 'FALSE': False}[value_xml]
|
||||
# tp_name = 'BOOL'
|
||||
elif subvalue_type == str:
|
||||
value_xml_coerce = value_xml
|
||||
# tp_name = 'STR'
|
||||
elif hasattr(subvalue, "__len__"):
|
||||
if value_xml.startswith("#"):
|
||||
# read hexadecimal value as float array
|
||||
value_xml_split = value_xml[1:]
|
||||
value_xml_coerce = [int(value_xml_split[i:i + 2], 16) /
|
||||
255 for i in range(0, len(value_xml_split), 2)]
|
||||
del value_xml_split
|
||||
else:
|
||||
value_xml_split = value_xml.split()
|
||||
try:
|
||||
value_xml_coerce = [int(v) for v in value_xml_split]
|
||||
except ValueError:
|
||||
try:
|
||||
value_xml_coerce = [float(v) for v in value_xml_split]
|
||||
except ValueError: # bool vector property
|
||||
value_xml_coerce = [{'TRUE': True, 'FALSE': False}[v] for v in value_xml_split]
|
||||
del value_xml_split
|
||||
# tp_name = 'ARRAY'
|
||||
|
||||
# print(" %s.%s (%s) --- %s" % (type(value).__name__, attr, tp_name, subvalue_type))
|
||||
try:
|
||||
setattr(value, attr, value_xml_coerce)
|
||||
except ValueError:
|
||||
# size mismatch
|
||||
val = getattr(value, attr)
|
||||
if len(val) < len(value_xml_coerce):
|
||||
setattr(value, attr, value_xml_coerce[:len(val)])
|
||||
else:
|
||||
setattr(value, attr, list(value_xml_coerce) + list(val)[len(value_xml_coerce):])
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Complex attributes
|
||||
for child_xml in xml_node.childNodes:
|
||||
if child_xml.nodeType == child_xml.ELEMENT_NODE:
|
||||
# print()
|
||||
# print(child_xml.nodeName)
|
||||
subvalue = getattr(value, child_xml.nodeName, None)
|
||||
if subvalue is not None:
|
||||
|
||||
elems = []
|
||||
for child_xml_real in child_xml.childNodes:
|
||||
if child_xml_real.nodeType == child_xml_real.ELEMENT_NODE:
|
||||
elems.append(child_xml_real)
|
||||
del child_xml_real
|
||||
|
||||
if hasattr(subvalue, "__len__"):
|
||||
# Collection
|
||||
if len(elems) != len(subvalue):
|
||||
print("Size Mismatch! collection:", child_xml.nodeName)
|
||||
else:
|
||||
for i in range(len(elems)):
|
||||
child_xml_real = elems[i]
|
||||
subsubvalue = subvalue[i]
|
||||
|
||||
if child_xml_real is None or subsubvalue is None:
|
||||
print("None found %s - %d collection:", (child_xml.nodeName, i))
|
||||
else:
|
||||
rna2xml_node(child_xml_real, subsubvalue)
|
||||
|
||||
else:
|
||||
# print(elems)
|
||||
if len(elems) == 1:
|
||||
# sub node named by its type
|
||||
child_xml_real, = elems
|
||||
|
||||
# print(child_xml_real, subvalue)
|
||||
rna2xml_node(child_xml_real, subvalue)
|
||||
else:
|
||||
# empty is valid too
|
||||
pass
|
||||
|
||||
rna2xml_node(root_xml, root_rna)
|
||||
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Utility function used by presets.
|
||||
# The idea is you can run a preset like a script with a few args.
|
||||
#
|
||||
# This roughly matches the operator 'bpy.ops.script.python_file_run'
|
||||
|
||||
|
||||
def _get_context_val(context, path):
|
||||
try:
|
||||
value = context.path_resolve(path)
|
||||
except Exception as ex:
|
||||
print("Error: %r, path %r not found" % (ex, path))
|
||||
value = Ellipsis
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def xml_file_run(context, filepath, rna_map):
|
||||
import xml.dom.minidom
|
||||
|
||||
xml_nodes = xml.dom.minidom.parse(filepath)
|
||||
bpy_xml = xml_nodes.getElementsByTagName("bpy")[0]
|
||||
|
||||
for rna_path, xml_tag in rna_map:
|
||||
|
||||
# first get xml
|
||||
# TODO, error check
|
||||
xml_node = bpy_xml.getElementsByTagName(xml_tag)[0]
|
||||
|
||||
value = _get_context_val(context, rna_path)
|
||||
|
||||
if value is not Ellipsis and value is not None:
|
||||
# print(" loading XML: %r -> %r" % (filepath, rna_path))
|
||||
xml2rna(xml_node, root_rna=value)
|
||||
|
||||
|
||||
def xml_file_write(context, filepath, rna_map, *, skip_typemap=None):
|
||||
with open(filepath, "w", encoding="utf-8") as file:
|
||||
fw = file.write
|
||||
fw("<bpy>\n")
|
||||
|
||||
for rna_path, _xml_tag in rna_map:
|
||||
# xml_tag is ignored, we get this from the rna
|
||||
value = _get_context_val(context, rna_path)
|
||||
rna2xml(
|
||||
fw=fw,
|
||||
root_rna=value,
|
||||
method='ATTR',
|
||||
root_ident=" ",
|
||||
ident_val=" ",
|
||||
skip_typemap=skip_typemap,
|
||||
)
|
||||
|
||||
fw("</bpy>\n")
|
||||
232
scripts/modules/sys_info.py
Normal file
232
scripts/modules/sys_info.py
Normal file
@@ -0,0 +1,232 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
# classes for extracting info from blenders internal classes
|
||||
|
||||
|
||||
def write_sysinfo(filepath):
|
||||
import sys
|
||||
import platform
|
||||
|
||||
import subprocess
|
||||
|
||||
import bpy
|
||||
import gpu
|
||||
|
||||
# pretty repr
|
||||
def prepr(v):
|
||||
r = repr(v)
|
||||
vt = type(v)
|
||||
if vt is bytes:
|
||||
r = r[2:-1]
|
||||
elif vt is list or vt is tuple:
|
||||
r = r[1:-1]
|
||||
return r
|
||||
|
||||
with open(filepath, 'w', encoding="utf-8") as output:
|
||||
try:
|
||||
header = "= Blender %s System Information =\n" % bpy.app.version_string
|
||||
lilies = "%s\n\n" % ((len(header) - 1) * "=")
|
||||
output.write(lilies[:-1])
|
||||
output.write(header)
|
||||
output.write(lilies)
|
||||
|
||||
def title(text):
|
||||
return "\n%s:\n%s" % (text, lilies)
|
||||
|
||||
# build info
|
||||
output.write(title("Blender"))
|
||||
output.write(
|
||||
"version: %s, branch: %s, commit date: %s %s, hash: %s, type: %s\n" %
|
||||
(bpy.app.version_string,
|
||||
prepr(bpy.app.build_branch),
|
||||
prepr(bpy.app.build_commit_date),
|
||||
prepr(bpy.app.build_commit_time),
|
||||
prepr(bpy.app.build_hash),
|
||||
prepr(bpy.app.build_type),
|
||||
))
|
||||
|
||||
output.write("build date: %s, %s\n" % (prepr(bpy.app.build_date), prepr(bpy.app.build_time)))
|
||||
output.write("platform: %s\n" % prepr(platform.platform()))
|
||||
output.write("binary path: %s\n" % prepr(bpy.app.binary_path))
|
||||
output.write("build cflags: %s\n" % prepr(bpy.app.build_cflags))
|
||||
output.write("build cxxflags: %s\n" % prepr(bpy.app.build_cxxflags))
|
||||
output.write("build linkflags: %s\n" % prepr(bpy.app.build_linkflags))
|
||||
output.write("build system: %s\n" % prepr(bpy.app.build_system))
|
||||
|
||||
# Windowing Environment (include when dynamically selectable).
|
||||
from _bpy import _ghost_backend
|
||||
ghost_backend = _ghost_backend()
|
||||
if ghost_backend not in {'NONE', 'DEFAULT'}:
|
||||
output.write("windowing environment: %s\n" % prepr(ghost_backend))
|
||||
del _ghost_backend, ghost_backend
|
||||
|
||||
# Python info.
|
||||
output.write(title("Python"))
|
||||
output.write("version: %s\n" % (sys.version.replace("\n", " ")))
|
||||
output.write("file system encoding: %s:%s\n" % (
|
||||
sys.getfilesystemencoding(),
|
||||
sys.getfilesystemencodeerrors(),
|
||||
))
|
||||
output.write("paths:\n")
|
||||
for p in sys.path:
|
||||
output.write("\t%r\n" % p)
|
||||
|
||||
output.write(title("Python (External Binary)"))
|
||||
output.write("binary path: %s\n" % prepr(sys.executable))
|
||||
try:
|
||||
py_ver = prepr(subprocess.check_output([
|
||||
sys.executable,
|
||||
"--version",
|
||||
]).strip())
|
||||
except Exception as e:
|
||||
py_ver = str(e)
|
||||
output.write("version: %s\n" % py_ver)
|
||||
del py_ver
|
||||
|
||||
output.write(title("Directories"))
|
||||
output.write("scripts:\n")
|
||||
for p in bpy.utils.script_paths():
|
||||
output.write("\t%r\n" % p)
|
||||
output.write("user scripts: %r\n" % (bpy.utils.script_path_user()))
|
||||
output.write("pref scripts: %r\n" % (bpy.utils.script_path_pref()))
|
||||
output.write("datafiles: %r\n" % (bpy.utils.user_resource('DATAFILES')))
|
||||
output.write("config: %r\n" % (bpy.utils.user_resource('CONFIG')))
|
||||
output.write("scripts : %r\n" % (bpy.utils.user_resource('SCRIPTS')))
|
||||
output.write("autosave: %r\n" % (bpy.utils.user_resource('AUTOSAVE')))
|
||||
output.write("tempdir: %r\n" % (bpy.app.tempdir))
|
||||
|
||||
output.write(title("FFmpeg"))
|
||||
ffmpeg = bpy.app.ffmpeg
|
||||
if ffmpeg.supported:
|
||||
for lib in ("avcodec", "avdevice", "avformat", "avutil", "swscale"):
|
||||
output.write(
|
||||
"%s:%s%r\n" % (lib, " " * (10 - len(lib)),
|
||||
getattr(ffmpeg, lib + "_version_string")))
|
||||
else:
|
||||
output.write("Blender was built without FFmpeg support\n")
|
||||
|
||||
if bpy.app.build_options.sdl:
|
||||
output.write(title("SDL"))
|
||||
output.write("Version: %s\n" % bpy.app.sdl.version_string)
|
||||
output.write("Loading method: ")
|
||||
if bpy.app.build_options.sdl_dynload:
|
||||
output.write("dynamically loaded by Blender (WITH_SDL_DYNLOAD=ON)\n")
|
||||
else:
|
||||
output.write("linked (WITH_SDL_DYNLOAD=OFF)\n")
|
||||
if not bpy.app.sdl.available:
|
||||
output.write("WARNING: Blender could not load SDL library\n")
|
||||
|
||||
output.write(title("Other Libraries"))
|
||||
ocio = bpy.app.ocio
|
||||
output.write("OpenColorIO: ")
|
||||
if ocio.supported:
|
||||
if ocio.version_string == "fallback":
|
||||
output.write("Blender was built with OpenColorIO, " +
|
||||
"but it currently uses fallback color management.\n")
|
||||
else:
|
||||
output.write("%s\n" % (ocio.version_string))
|
||||
else:
|
||||
output.write("Blender was built without OpenColorIO support\n")
|
||||
|
||||
oiio = bpy.app.oiio
|
||||
output.write("OpenImageIO: ")
|
||||
if ocio.supported:
|
||||
output.write("%s\n" % (oiio.version_string))
|
||||
else:
|
||||
output.write("Blender was built without OpenImageIO support\n")
|
||||
|
||||
output.write("OpenShadingLanguage: ")
|
||||
if bpy.app.build_options.cycles:
|
||||
if bpy.app.build_options.cycles_osl:
|
||||
from _cycles import osl_version_string
|
||||
output.write("%s\n" % (osl_version_string))
|
||||
else:
|
||||
output.write("Blender was built without OpenShadingLanguage support in Cycles\n")
|
||||
else:
|
||||
output.write("Blender was built without Cycles support\n")
|
||||
|
||||
opensubdiv = bpy.app.opensubdiv
|
||||
output.write("OpenSubdiv: ")
|
||||
if opensubdiv.supported:
|
||||
output.write("%s\n" % opensubdiv.version_string)
|
||||
else:
|
||||
output.write("Blender was built without OpenSubdiv support\n")
|
||||
|
||||
openvdb = bpy.app.openvdb
|
||||
output.write("OpenVDB: ")
|
||||
if openvdb.supported:
|
||||
output.write("%s\n" % openvdb.version_string)
|
||||
else:
|
||||
output.write("Blender was built without OpenVDB support\n")
|
||||
|
||||
alembic = bpy.app.alembic
|
||||
output.write("Alembic: ")
|
||||
if alembic.supported:
|
||||
output.write("%s\n" % alembic.version_string)
|
||||
else:
|
||||
output.write("Blender was built without Alembic support\n")
|
||||
|
||||
usd = bpy.app.usd
|
||||
output.write("USD: ")
|
||||
if usd.supported:
|
||||
output.write("%s\n" % usd.version_string)
|
||||
else:
|
||||
output.write("Blender was built without USD support\n")
|
||||
|
||||
if not bpy.app.build_options.sdl:
|
||||
output.write("SDL: Blender was built without SDL support\n")
|
||||
|
||||
if bpy.app.background:
|
||||
output.write("\nOpenGL: missing, background mode\n")
|
||||
else:
|
||||
output.write(title("GPU"))
|
||||
output.write("renderer:\t%r\n" % gpu.platform.renderer_get())
|
||||
output.write("vendor:\t\t%r\n" % gpu.platform.vendor_get())
|
||||
output.write("version:\t%r\n" % gpu.platform.version_get())
|
||||
output.write("device type:\t%r\n" % gpu.platform.device_type_get())
|
||||
output.write("backend type:\t%r\n" % gpu.platform.backend_type_get())
|
||||
output.write("extensions:\n")
|
||||
|
||||
glext = sorted(gpu.capabilities.extensions_get())
|
||||
|
||||
for l in glext:
|
||||
output.write("\t%s\n" % l)
|
||||
|
||||
output.write(title("Implementation Dependent GPU Limits"))
|
||||
output.write("Maximum Batch Vertices:\t%d\n" % gpu.capabilities.max_batch_vertices_get())
|
||||
output.write("Maximum Batch Indices:\t%d\n" % gpu.capabilities.max_batch_indices_get())
|
||||
|
||||
output.write("\nGLSL:\n")
|
||||
output.write("Maximum Varying Floats:\t%d\n" % gpu.capabilities.max_varying_floats_get())
|
||||
output.write("Maximum Vertex Attributes:\t%d\n" % gpu.capabilities.max_vertex_attribs_get())
|
||||
output.write("Maximum Vertex Uniform Components:\t%d\n" % gpu.capabilities.max_uniforms_vert_get())
|
||||
output.write("Maximum Fragment Uniform Components:\t%d\n" % gpu.capabilities.max_uniforms_frag_get())
|
||||
output.write("Maximum Vertex Image Units:\t%d\n" % gpu.capabilities.max_textures_vert_get())
|
||||
output.write("Maximum Fragment Image Units:\t%d\n" % gpu.capabilities.max_textures_frag_get())
|
||||
output.write("Maximum Pipeline Image Units:\t%d\n" % gpu.capabilities.max_textures_get())
|
||||
|
||||
output.write("\nFeatures:\n")
|
||||
output.write("Compute Shader Support: \t%d\n" %
|
||||
gpu.capabilities.compute_shader_support_get())
|
||||
output.write("Shader Storage Buffer Objects Support:\t%d\n" %
|
||||
gpu.capabilities.shader_storage_buffer_objects_support_get())
|
||||
output.write("Image Load/Store Support: \t%d\n" %
|
||||
gpu.capabilities.shader_image_load_store_support_get())
|
||||
|
||||
if bpy.app.build_options.cycles:
|
||||
import cycles
|
||||
output.write(title("Cycles"))
|
||||
output.write(cycles.engine.system_info())
|
||||
|
||||
import addon_utils
|
||||
addon_utils.modules()
|
||||
output.write(title("Enabled add-ons"))
|
||||
for addon in bpy.context.preferences.addons.keys():
|
||||
addon_mod = addon_utils.addons_fake_modules.get(addon, None)
|
||||
if addon_mod is None:
|
||||
output.write("%s (MISSING)\n" % (addon))
|
||||
else:
|
||||
output.write("%s (version: %s, path: %s)\n" %
|
||||
(addon, addon_mod.bl_info.get('version', "UNKNOWN"), addon_mod.__file__))
|
||||
except Exception as e:
|
||||
output.write("ERROR: %s\n" % e)
|
||||
Reference in New Issue
Block a user