Extensions: Support online extensions and move add-ons outside Blender

The extensions system allows to extend Blender with connectivity to the internet. Right now it means Blender can
discover and install add-ons and themes directly from the internet, and notify users about their updates.

By default this is disabled (opt-in), and users can enable it the first time they try to install an extension or visit
the Prefences > Extensions tab. If this is enabled, Blender will automatically check for updates for
extensions.blender.org upon startup.

When will Blender access the remote repositories:

* Every time you open the Preferences → Extensions: ALL the enabled repositories get checked for the latest info (json)
* Every time you try to install by dragging: ALL the enabled repositories get checked for the latest info (json).
* Every time you start Blender: selected repositories get checked for the latest info (json).

------------------

From the Blender code point of view, this means that most of the add-ons and themes originally bundled with Blender
will now be available from the online platform, instead of bundled with Blender. The exception are add-ons which are
deemed core functionality which just happened to be written as Python add-ons.

Links:
* Original Extenesions Platform Announcement: https://code.blender.org/2022/10/blender-extensions-platform/
* Extensions website: https://extensions.blender.org/
* User Manual: https://docs.blender.org/manual/en/4.2/extensions/index.html#extensions-index
* Technical specifications: https://developer.blender.org/docs/features/extensions/
* Changes on add-ons bundling: https://devtalk.blender.org/t/changes-to-add-on-bundling-4-2-onwards/34593

------------------

This PR does the following:

* Move extensions out of experimental.
* No longer install `scripts/addons` & `scripts/addons_contrib`.
* Add `scripts/addons_core` to blender's repository.

These add-ons will still be bundled with Blender and will be always enabled in the future, with their preferences
moved to be more closely integrated with the rest of Blender. This will happen during the remaining bcon2 period.
For more details, see #121830

From scripts/addons:

* copy_global_transform.py
* hydra_storm
* io_anim_bvh
* io_curve_svg
* io_mesh_uv_layout
* io_scene_fbx
* io_scene_gltf2
* pose_library
* ui_translate
* viewport_vr_preview

Extra: bl_pkg (scripts/addons_contrib)

Note: The STL (legacy) add-on is going to be moved to the extensions platform. There is already a C++ version on core
which is enabled by default.

All the other add-ons are already available at extensions.blender.org. To use them you need to:

* Go to User Preferences > Extensions
* You will be greated with an "Online Extensions" message, click on "Enable Repository".
* Search the add-on you are looking for (e.g, Import Images as Planes).
* Click on Install

Over time their maintaince will be transferred over to the community so their development can carry on. If you used to
help maintain a bundled add-on please read: https://devtalk.blender.org/t/changes-to-add-on-bundling-4-2-onwards/34593

Ref: !121825
This commit is contained in:
Campbell Barton 2024-05-15 22:21:00 +10:00 committed by Dalai Felinto
parent 13a3603578
commit c4a0bbb1f4
21 changed files with 96 additions and 182 deletions

@ -115,7 +115,6 @@ def create_manifest(
with outpath.open("w", encoding="utf-8") as outfile:
main_files_to_manifest(blender_srcdir, outfile)
assets_to_manifest(blender_srcdir, outfile)
submodules_to_manifest(blender_srcdir, version, outfile)
if packages_dir:
packages_to_manifest(outfile, packages_dir)
@ -128,21 +127,6 @@ def main_files_to_manifest(blender_srcdir: Path, outfile: TextIO) -> None:
print(path, file=outfile)
def submodules_to_manifest(
blender_srcdir: Path, version: make_utils.BlenderVersion, outfile: TextIO
) -> None:
skip_addon_contrib = version.is_release()
assert not blender_srcdir.is_absolute()
for submodule in ("scripts/addons", "scripts/addons_contrib"):
# Don't use native slashes as GIT for MS-Windows outputs forward slashes.
if skip_addon_contrib and submodule == "scripts/addons_contrib":
continue
for path in git_ls_files(blender_srcdir / submodule):
print(path, file=outfile)
def assets_to_manifest(blender_srcdir: Path, outfile: TextIO) -> None:
assert not blender_srcdir.is_absolute()

@ -576,27 +576,6 @@ def submodules_lib_update(args: argparse.Namespace, branch: Optional[str]) -> st
return msg
def scripts_submodules_update(args: argparse.Namespace, branch: Optional[str]) -> str:
"""Update working trees of addons and addons_contrib within the scripts/ directory"""
msg = ""
msg += external_scripts_update(args, "blender-addons", "addons", branch)
msg += external_scripts_update(args, "blender-addons-contrib", "addons_contrib", branch)
return msg
def submodules_code_update(args: argparse.Namespace, branch: Optional[str]) -> str:
"""Update submodules or other externally tracked source trees"""
print_stage("Updating Submodules")
msg = ""
msg += scripts_submodules_update(args, branch)
return msg
if __name__ == "__main__":
args = parse_arguments()
blender_skip_msg = ""
@ -627,9 +606,6 @@ if __name__ == "__main__":
libraries_skip_msg += initialize_tests_data_files(args)
libraries_skip_msg += submodules_lib_update(args, branch)
if not args.no_submodules:
submodules_skip_msg += submodules_code_update(args, branch)
# Report any skipped repositories at the end, so it's not as easy to miss.
skip_msg = blender_skip_msg + libraries_skip_msg + submodules_skip_msg
if skip_msg:

@ -1830,7 +1830,6 @@ def pyrna2sphinx(basepath):
def write_ops():
API_BASEURL = "https://projects.blender.org/blender/blender/src/branch/main/scripts"
API_BASEURL_ADDON = "https://projects.blender.org/blender/blender-addons"
API_BASEURL_ADDON_CONTRIB = "https://projects.blender.org/blender/blender-addons-contrib"
op_modules = {}
op = None
@ -1868,9 +1867,7 @@ def pyrna2sphinx(basepath):
location = op.get_location()
if location != (None, None):
if location[0].startswith("addons_contrib" + os.sep):
url_base = API_BASEURL_ADDON_CONTRIB
elif location[0].startswith("addons" + os.sep):
if location[0].startswith("addons_core" + os.sep):
url_base = API_BASEURL_ADDON
else:
url_base = API_BASEURL

@ -11,9 +11,9 @@ else()
# be started with --env-system-scripts pointing to the release folder, which will
# lack the cycles addon, and we don't want to write into it.
if(NOT WINDOWS_PYTHON_DEBUG)
set(CYCLES_INSTALL_PATH "scripts/addons/cycles")
set(CYCLES_INSTALL_PATH "scripts/addons_core/cycles")
else()
set(CYCLES_INSTALL_PATH "$ENV{appdata}/blender foundation/blender/${BLENDER_VERSION}/scripts/addons/cycles")
set(CYCLES_INSTALL_PATH "$ENV{appdata}/blender foundation/blender/${BLENDER_VERSION}/scripts/addons_core/cycles")
endif()
endif()

@ -39,8 +39,7 @@ aggressive = 2
# - `./scripts/modules/rna_manual_reference.py` because it's a generated data-file.
exclude = """
./extern/*,
./scripts/addons/*,
./scripts/addons_contrib/*,
./scripts/addons_core/*,
./scripts/modules/rna_manual_reference.py,
./tools/svn_rev_map/sha1_to_rev.py,
./tools/svn_rev_map/rev_to_sha1.py,

@ -115,9 +115,6 @@ def repo_paths_or_none(repo_item):
def repo_active_or_none():
prefs = bpy.context.preferences
if not prefs.experimental.use_extension_repos:
return
extensions = prefs.extensions
active_extension_index = extensions.active_repo
try:
@ -147,19 +144,18 @@ def repos_to_notify():
# if any repositories are marked to run notifications.
prefs = bpy.context.preferences
if prefs.experimental.use_extension_repos:
extension_repos = prefs.extensions.repos
for repo_item in extension_repos:
if not repo_item.enabled:
continue
if not repo_item.use_sync_on_startup:
continue
if not repo_item.use_remote_url:
continue
# Invalid, if there is no remote path this can't update.
if not repo_item.remote_url:
continue
repos_notify.append(repo_item)
extension_repos = prefs.extensions.repos
for repo_item in extension_repos:
if not repo_item.enabled:
continue
if not repo_item.use_sync_on_startup:
continue
if not repo_item.use_remote_url:
continue
# Invalid, if there is no remote path this can't update.
if not repo_item.remote_url:
continue
repos_notify.append(repo_item)
return repos_notify

@ -2218,7 +2218,7 @@ class BlPkgShowUpgrade(Operator):
wm = context.window_manager
prefs = context.preferences
prefs.active_section = 'ADDONS'
prefs.active_section = 'EXTENSIONS'
prefs.view.show_addons_enabled_only = False
# Show only extensions that will be updated.

@ -85,9 +85,28 @@ def pkg_repo_and_id_from_theme_path(repos_all, filepath):
return None
def module_parent_dirname(module_filepath):
"""
Return the name of the directory above the module (it's name only).
"""
import os
parts = module_filepath.rsplit(os.sep, 3)
# Unlikely.
if not parts:
return ""
if parts[-1] == "__init__.py":
if len(parts) >= 3:
return parts[-3]
else:
if len(parts) >= 2:
return parts[-2]
return ""
# -----------------------------------------------------------------------------
# Extensions UI (Legacy)
def extensions_panel_draw_legacy_addons(
layout,
context,
@ -153,7 +172,11 @@ def extensions_panel_draw_legacy_addons(
sub = row.row()
sub.active = is_enabled
sub.label(text="Legacy: " + bl_info["name"])
if module_parent_dirname(mod.__file__) == "addons_core":
sub.label(text="Core: " + bl_info["name"])
else:
sub.label(text="Legacy: " + bl_info["name"])
if bl_info["warning"]:
sub.label(icon='ERROR')
@ -690,11 +713,6 @@ class USERPREF_MT_extensions_bl_pkg_settings(Menu):
def extensions_panel_draw(panel, context):
prefs = context.preferences
if not prefs.experimental.use_extension_repos:
# Unexpected, the extension is disabled but this add-on is.
# In this case don't show the UI as it is confusing.
return
from .bl_extension_ops import (
blender_filter_by_type_map,
)

@ -113,9 +113,6 @@ def blender_test_run(temp_dir_local: str) -> None:
preferences = bpy.context.preferences
preferences.view.show_developer_ui = True
preferences.experimental.use_extension_repos = True
addon_dir = os.path.normpath(os.path.join(BASE_DIR, "..", "blender_addon"))
ensure_script_directory(addon_dir)

@ -34,18 +34,13 @@ VERBOSE_CMD = False
BLENDER_BIN = os.environ.get("BLENDER_BIN")
if BLENDER_BIN is None:
sys.exit(0)
raise Exception("BLENDER_BIN: environment variable not defined")
# Arguments to ensure extensions are enabled (currently it's an experimental feature).
BLENDER_ENABLE_EXTENSION_ARGS = [
"--python-exit-code", "1",
# Code begin/end text because of Blender's chatty reporting of version and that it quit.
"--python-expr", '''\
from bpy import context
context.preferences.view.show_developer_ui = True
context.preferences.experimental.use_extension_repos = True
''',
]
BASE_DIR = os.path.abspath(os.path.dirname(__file__))

@ -13,7 +13,6 @@ from ...io.com import gltf2_io_constants
from ...io.exp import gltf2_io_binary_data
from ..com.gltf2_blender_default import BLENDER_GLTF_SPECIAL_COLLECTION
from . import gltf2_blender_gather_accessors
from .gltf2_blender_gather_joints import gather_joint_vnode
class VExportNode:

@ -37,16 +37,22 @@ def _initialize_once():
def paths():
return [
path for subdir in (
# RELEASE SCRIPTS: official scripts distributed in Blender releases.
"addons",
# CONTRIB SCRIPTS: good for testing but not official scripts yet
# if folder addons_contrib/ exists, scripts in there will be loaded too.
"addons_contrib",
)
for path in _bpy.utils.script_paths(subdir=subdir)
]
import os
paths = []
for p in _bpy.utils.script_paths():
# Bundled add-ons.
addon_dir = os.path.join(p, "addons_core")
if os.path.isdir(addon_dir):
paths.append(addon_dir)
# The system path if for core add-ons only,
# if the `addons` directory exists it's likely from an old "make install" target.
continue
# User defined add-ons, custom scripts directory.
addon_dir = os.path.join(p, "addons")
if os.path.isdir(addon_dir):
paths.append(addon_dir)
return paths
# A version of `paths` that includes extension repositories returning a list `(path, package)` pairs.
@ -65,14 +71,13 @@ def _paths_with_extension_repos():
import os
addon_paths = [(path, "") for path in paths()]
if _preferences.experimental.use_extension_repos:
for repo in _preferences.extensions.repos:
if not repo.enabled:
continue
dirpath = repo.directory
if not os.path.isdir(dirpath):
continue
addon_paths.append((dirpath, "{:s}.{:s}".format(_ext_base_pkg_idname, repo.module)))
for repo in _preferences.extensions.repos:
if not repo.enabled:
continue
dirpath = repo.directory
if not os.path.isdir(dirpath):
continue
addon_paths.append((dirpath, "{:s}.{:s}".format(_ext_base_pkg_idname, repo.module)))
return addon_paths
@ -178,7 +183,11 @@ def modules_refresh(*, module_cache=addons_fake_modules):
for path, pkg_id in _paths_with_extension_repos():
# Force all user contributed add-ons to be 'TESTING'.
force_support = 'TESTING' if ((not pkg_id) and path.endswith("addons_contrib")) else None
# TODO: remove this option entirely.
force_support = None
# Was part of support `addons_contrib`.
# `force_support = 'TESTING' if ((not pkg_id) and path.endswith("addons_contrib")) else None`
for mod_name, mod_path in _bpy.path.module_names(path, package=pkg_id):
modules_stale.discard(mod_name)
@ -756,10 +765,9 @@ def _fake_module_from_extension(mod_name, mod_path, force_support=None):
# Extensions
def _initialize_ensure_extensions_addon():
if _preferences.experimental.use_extension_repos:
module_name = "bl_pkg"
if module_name not in _preferences.addons:
enable(module_name, default_set=True, persistent=True)
module_name = "bl_pkg"
if module_name not in _preferences.addons:
enable(module_name, default_set=True, persistent=True)
# Module-like class, store singletons.
@ -786,22 +794,20 @@ _ext_manifest_filename_toml = "blender_manifest.toml"
def _extension_preferences_idmap():
repos_idmap = {}
repos_idmap_disabled = {}
if _preferences.experimental.use_extension_repos:
for repo in _preferences.extensions.repos:
if repo.enabled:
repos_idmap[repo.as_pointer()] = repo.module
else:
repos_idmap_disabled[repo.as_pointer()] = repo.module
for repo in _preferences.extensions.repos:
if repo.enabled:
repos_idmap[repo.as_pointer()] = repo.module
else:
repos_idmap_disabled[repo.as_pointer()] = repo.module
return repos_idmap, repos_idmap_disabled
def _extension_dirpath_from_preferences():
repos_dict = {}
if _preferences.experimental.use_extension_repos:
for repo in _preferences.extensions.repos:
if not repo.enabled:
continue
repos_dict[repo.module] = repo.directory
for repo in _preferences.extensions.repos:
if not repo.enabled:
continue
repos_dict[repo.module] = repo.directory
return repos_dict
@ -979,7 +985,8 @@ def _initialize_extension_repos_post(*_, is_first=False):
# Map `module_id` -> `repo.as_pointer()`.
repos_idmap_next_reverse = {value: key for key, value in repos_idmap_next.items()}
# Mainly needed when the `preferences.experimental.use_extension_repos` option is enabled at run-time.
# Mainly needed when the state of repositories changes at run-time:
# factory settings then load preferences for example.
#
# Filter `repos_idmap_prev` so only items which were also in the `repos_info_prev` are included.
# This is an awkward situation, they should be in sync, however when enabling the experimental option

@ -870,7 +870,7 @@ class PREFERENCES_OT_addon_show(Operator):
bl_info = addon_utils.module_bl_info(mod)
bl_info["show_expanded"] = True
context.preferences.active_section = 'ADDONS'
context.preferences.active_section = 'EXTENSIONS'
context.preferences.view.show_addons_enabled_only = False
context.window_manager.addon_filter = 'All'
context.window_manager.addon_search = bl_info["name"]

@ -2179,7 +2179,7 @@ class USERPREF_PT_addons_filter(Panel):
class AddOnPanel:
bl_space_type = 'PREFERENCES'
bl_region_type = 'WINDOW'
bl_context = "addons"
bl_context = "extensions"
class USERPREF_PT_addons(AddOnPanel, Panel):
@ -2275,11 +2275,7 @@ class USERPREF_PT_addons(AddOnPanel, Panel):
prefs = context.preferences
if (
prefs.view.show_developer_ui and
prefs.experimental.use_extension_repos and
self.is_extended()
):
if self.is_extended():
# Rely on the draw function being appended to by the extensions add-on.
return
@ -2720,7 +2716,6 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
({"property": "use_grease_pencil_version3"}, ("blender/blender/projects/6", "Grease Pencil 3.0")),
({"property": "use_grease_pencil_version3_convert_on_load"}, ("blender/blender/projects/6", "Grease Pencil 3.0")),
({"property": "enable_overlay_next"}, ("blender/blender/issues/102179", "#102179")),
({"property": "use_extension_repos"}, ("/blender/blender/issues/117286", "#117286")),
({"property": "use_extension_utils"}, ("/blender/blender/issues/117286", "#117286")),
({"property": "use_animation_baklava"}, ("/blender/blender/issues/120406", "#120406")),
),

@ -1206,11 +1206,9 @@ UserDef *BKE_blendfile_userdef_from_defaults()
const char *addons[] = {
"io_anim_bvh",
"io_curve_svg",
"io_mesh_stl",
"io_mesh_uv_layout",
"io_scene_fbx",
"io_scene_gltf2",
"io_scene_x3d",
"cycles",
"pose_library",
};

@ -925,9 +925,6 @@ static void PREFERENCES_OT_unassociate_blend(wmOperatorType *ot)
static bool drop_extension_url_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/)
{
if (!U.experimental.use_extension_repos) {
return false;
}
if (drag->type != WM_DRAG_STRING) {
return false;
}
@ -977,9 +974,6 @@ static void drop_extension_url_copy(bContext * /*C*/, wmDrag *drag, wmDropBox *d
static bool drop_extension_path_poll(bContext * /*C*/, wmDrag *drag, const wmEvent * /*event*/)
{
if (!U.experimental.use_extension_repos) {
return false;
}
if (drag->type != WM_DRAG_PATH) {
return false;
}

@ -732,11 +732,10 @@ typedef struct UserDef_Experimental {
char enable_overlay_next;
char use_new_volume_nodes;
char use_shader_node_previews;
char use_extension_repos;
char use_extension_utils;
char use_grease_pencil_version3_convert_on_load;
char use_animation_baklava;
char _pad[2];
char _pad[3];
/** `makesdna` does not allow empty structs. */
} UserDef_Experimental;
@ -1115,7 +1114,7 @@ typedef enum eUserPref_Section {
USER_SECTION_SYSTEM = 3,
USER_SECTION_THEME = 4,
USER_SECTION_INPUT = 5,
USER_SECTION_ADDONS = 6,
USER_SECTION_EXTENSIONS = 6,
USER_SECTION_LIGHT = 7,
USER_SECTION_KEYMAP = 8,
#ifdef WITH_USERDEF_WORKSPACES
@ -1128,7 +1127,6 @@ typedef enum eUserPref_Section {
USER_SECTION_NAVIGATION = 14,
USER_SECTION_FILE_PATHS = 15,
USER_SECTION_EXPERIMENTAL = 16,
USER_SECTION_EXTENSIONS = 17,
} eUserPref_Section;
/** #UserDef_SpaceData.flag (State of the user preferences UI). */

@ -60,7 +60,7 @@ const EnumPropertyItem rna_enum_preference_section_items[] = {
{USER_SECTION_EDITING, "EDITING", 0, "Editing", ""},
{USER_SECTION_ANIMATION, "ANIMATION", 0, "Animation", ""},
RNA_ENUM_ITEM_SEPR,
{USER_SECTION_ADDONS, "ADDONS", 0, "Add-ons", ""},
{USER_SECTION_EXTENSIONS, "EXTENSIONS", 0, "Extensions", ""},
#if 0 /* def WITH_USERDEF_WORKSPACES */
RNA_ENUM_ITEM_SEPR,
{USER_SECTION_WORKSPACE_CONFIG, "WORKSPACE_CONFIG", 0, "Configuration File", ""},
@ -307,19 +307,6 @@ static void rna_userdef_screen_update_header_default(Main *bmain, Scene *scene,
USERDEF_TAG_DIRTY;
}
static void rna_PreferencesExperimental_use_extension_repos_set(PointerRNA * /*ptr*/, bool value)
{
Main *bmain = G.main;
if (bool(U.experimental.use_extension_repos) != value) {
BKE_callback_exec_null(bmain, BKE_CB_EVT_EXTENSION_REPOS_UPDATE_PRE);
U.experimental.use_extension_repos = value;
BKE_callback_exec_null(bmain, BKE_CB_EVT_EXTENSION_REPOS_UPDATE_POST);
}
/* Needed to redraw the preferences window. */
WM_main_add_notifier(NC_WINDOW, nullptr);
}
static void rna_userdef_font_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA * /*ptr*/)
{
BLF_cache_clear();
@ -721,9 +708,8 @@ static const EnumPropertyItem *rna_UseDef_active_section_itemf(bContext * /*C*/,
UserDef *userdef = static_cast<UserDef *>(ptr->data);
const bool use_developer_ui = (userdef->flag & USER_DEVELOPER_UI) != 0;
const bool use_extension_repos = use_developer_ui && U.experimental.use_extension_repos;
if (use_developer_ui && use_extension_repos == false) {
if (use_developer_ui) {
*r_free = false;
return rna_enum_preference_section_items;
}
@ -741,13 +727,6 @@ static const EnumPropertyItem *rna_UseDef_active_section_itemf(bContext * /*C*/,
}
RNA_enum_item_add(&items, &totitem, it);
/* Rename "Add-ons" to "Extensions" when extensions are enabled. */
if (it->value == USER_SECTION_ADDONS) {
if (use_extension_repos) {
items[totitem - 1].name = "Extensions";
}
}
}
RNA_enum_item_end(&items, &totitem);
@ -7280,14 +7259,6 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
prop, "Shader Node Previews", "Enables previews in the shader node editor");
RNA_def_property_update(prop, 0, "rna_userdef_ui_update");
prop = RNA_def_property(srna, "use_extension_repos", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(prop,
"Extensions",
"Enables support for extensions, accessible from the \"Extensions\" "
"section of the preferences");
RNA_def_property_boolean_funcs(
prop, nullptr, "rna_PreferencesExperimental_use_extension_repos_set");
prop = RNA_def_property(srna, "use_extension_utils", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(
prop, "Extensions Development Utilities", "Developer support utilities for extensions");

@ -443,15 +443,6 @@ endif()
if(WITH_PYTHON)
# install(CODE "message(\"copying blender scripts...\")")
# exclude addons_contrib if release branch
if("${BLENDER_VERSION_CYCLE}" STREQUAL "release" OR
"${BLENDER_VERSION_CYCLE}" STREQUAL "rc" OR
"${BLENDER_VERSION_CYCLE}" STREQUAL "beta")
set(ADDON_EXCLUDE_CONDITIONAL "addons_contrib/*")
else()
set(ADDON_EXCLUDE_CONDITIONAL "_addons_contrib/*") # Dummy, won't do anything.
endif()
# do not install freestyle dir if disabled
if(NOT WITH_FREESTYLE)
set(FREESTYLE_EXCLUDE_CONDITIONAL "freestyle/*")
@ -469,7 +460,9 @@ if(WITH_PYTHON)
PATTERN ".arcconfig" EXCLUDE
PATTERN "__pycache__" EXCLUDE
PATTERN "site" EXCLUDE
PATTERN "${ADDON_EXCLUDE_CONDITIONAL}" EXCLUDE
# Legacy sub-module which is no longer used.
PATTERN "addons/*" EXCLUDE
PATTERN "addons_contrib/*" EXCLUDE
PATTERN "${FREESTYLE_EXCLUDE_CONDITIONAL}" EXCLUDE
)
@ -486,7 +479,6 @@ if(WITH_PYTHON)
)
endif()
endif()
unset(ADDON_EXCLUDE_CONDITIONAL)
unset(FREESTYLE_EXCLUDE_CONDITIONAL)
endif()

@ -497,7 +497,6 @@ def main() -> None:
source_paths_exclude=(
# Directories:
"./extern",
"./scripts/addons_contrib",
"./scripts/templates_osl",
"./tools",
# Exclude library sources (GIT-LFS).
@ -541,8 +540,7 @@ def main() -> None:
"./lib",
# Just data.
"./doc/python_api/examples",
"./scripts/addons/presets",
"./scripts/addons_contrib",
"./scripts/addons_core",
"./scripts/presets",
"./scripts/templates_py",

@ -873,5 +873,5 @@ files_ignore = {
directories_ignore = {
"scripts/addons",
"scripts/addons_contrib",
"scripts/addons_core",
}