WIP: Basic Blender Project Support (experimental feature) #107655

Draft
Julian Eisel wants to merge 94 commits from blender-projects-basics into main

When changing the target branch, be careful to rebase the branch in your fork to match. See documentation.
78 changed files with 3473 additions and 485 deletions

View File

@ -914,6 +914,33 @@ const bTheme U_theme_default = {
.outline_width = 1,
.facedot_size = 4,
},
.space_project_settings = {
.back = RGBA(0x30303000),
.title = RGBA(0xeeeeeeff),
.text = RGBA(0xe6e6e6ff),
.text_hi = RGBA(0xffffffff),
.header = RGBA(0x303030b3),
.header_text = RGBA(0xeeeeeeff),
.header_text_hi = RGBA(0xffffffff),
.tab_active = RGBA(0x303030ff),
.tab_inactive = RGBA(0x1d1d1dff),
.tab_back = RGBA(0x181818ff),
.tab_outline = RGBA(0x3d3d3dff),
.button = RGBA(0x1d1d1dff),
.button_title = RGBA(0xffffffff),
.button_text = RGBA(0xccccccff),
.button_text_hi = RGBA(0xffffffff),
.navigation_bar = RGBA(0x303030ff),
.execution_buts = RGBA(0x303030ff),
.panelcolors = {
.header = RGBA(0x3d3d3dff),
.back = RGBA(0x3d3d3dff),
.sub_back = RGBA(0x0000001f),
},
.vertex_size = 3,
.outline_width = 1,
.facedot_size = 4,
},
.space_console = {
.back = RGBA(0x1d1d1d00),
.title = RGBA(0xeeeeeeff),

View File

@ -1162,6 +1162,40 @@
</space>
</ThemePreferences>
</preferences>
<project_settings>
<ThemeProjectSettings>
<space>
<ThemeSpaceGeneric
back="#b3b3b3"
title="#181818"
text="#000000"
text_hi="#ffffff"
header="#b3b3b3ff"
header_text="#000000"
header_text_hi="#ffffff"
button="#7272727f"
button_title="#000000"
button_text="#000000"
button_text_hi="#000000"
navigation_bar="#b3b3b3ff"
execution_buts="#b3b3b3ff"
tab_active="#6697e6"
tab_inactive="#535353"
tab_back="#404040ff"
tab_outline="#3c3c3c"
>
<panelcolors>
<ThemePanelColors
header="#b3b3b300"
back="#a3a3a3cc"
sub_back="#00000024"
>
</ThemePanelColors>
</panelcolors>
</ThemeSpaceGeneric>
</space>
</ThemeProjectSettings>
</project_settings>
<console>
<ThemeConsole
line_output="#71a8ff"

View File

@ -78,6 +78,7 @@ _modules = [
"space_nla",
"space_node",
"space_outliner",
"space_project_settings",
"space_properties",
"space_sequencer",
"space_spreadsheet",

View File

@ -0,0 +1,215 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
import bpy
from bpy.types import Header, Menu, Panel
from bpy.app.translations import pgettext_iface as iface_
from bl_ui.utils import CenterAlignMixIn
# -----------------------------------------------------------------------------
# Main Header
class PROJECTSETTINGS_HT_header(Header):
bl_space_type = 'PROJECT_SETTINGS'
@staticmethod
def draw_buttons(layout, context):
project = context.project
layout.operator_context = 'EXEC_AREA'
is_dirty = project and project.is_dirty
# Show '*' to let users know the settings have been modified.
layout.operator(
"project.save_settings",
text=iface_("Save Settings") + (" *" if is_dirty else ""),
translate=False,
)
def draw(self, context):
layout = self.layout
layout.operator_context = 'EXEC_AREA'
layout.template_header()
PROJECTSETTINGS_MT_editor_menus.draw_collapsible(context, layout)
layout.separator_spacer()
self.draw_buttons(layout, context)
# -----------------------------------------------------------------------------
# Main Navigation Bar
class PROJECTSETTINGS_PT_navigation_bar(Panel):
bl_label = "Project Settings Navigation"
bl_space_type = 'PROJECT_SETTINGS'
bl_region_type = 'NAVIGATION_BAR'
bl_options = {'HIDE_HEADER'}
def draw(self, context):
layout = self.layout
space_data = context.space_data
project = context.project
col = layout.column()
col.enabled = project is not None
col.scale_x = 1.3
col.scale_y = 1.3
col.prop(space_data, "active_section", expand=True)
class PROJECTSETTINGS_MT_editor_menus(Menu):
bl_idname = "PROJECTSETTINGS_MT_editor_menus"
bl_label = ""
def draw(self, _context):
layout = self.layout
layout.menu("PROJECTSETTINGS_MT_view")
class PROJECTSETTINGS_MT_view(Menu):
bl_label = "View"
def draw(self, _context):
layout = self.layout
layout.menu("INFO_MT_area")
class PROJECTSETTINGS_MT_advanced_operations(Menu):
bl_label = "Advanced Project Settings Operations"
def draw(self, _context):
layout = self.layout
layout.operator("project.delete_setup")
class PROJECTSETTINGS_PT_save_project_settings(Panel):
bl_label = "Save Project Settings"
bl_space_type = 'PROJECT_SETTINGS'
bl_region_type = 'EXECUTE'
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
# Hide when header is visible
for region in context.area.regions:
if region.type == 'HEADER' and region.height <= 1:
return True
return False
def draw(self, context):
layout = self.layout.row()
layout.operator_context = 'EXEC_AREA'
layout.menu("PROJECTSETTINGS_MT_advanced_operations", text="", icon='COLLAPSEMENU')
PROJECTSETTINGS_HT_header.draw_buttons(layout, context)
class PROJECTSETTINGS_PT_no_project(Panel):
bl_space_type = 'PROJECT_SETTINGS'
bl_region_type = 'WINDOW'
# Special hardcoded context.
bl_context = "no_project"
bl_label = "No Project"
bl_options = {'HIDE_HEADER'}
@classmethod
def poll(cls, context):
return (context.project is None)
def draw(self, context):
layout = self.layout
layout.label(text="No active project.", icon='INFO')
col = layout.column(align=True)
col.label(text="Open/store a file inside of a project directory, or set up a new project")
col.label(text="by choosing a project directory.")
row = layout.row()
split = row.split(factor=0.3)
split.operator("project.new", text="Set up Project...")
class PROJECTSETTINGS_PT_setup(CenterAlignMixIn, Panel):
bl_space_type = 'PROJECT_SETTINGS'
bl_region_type = 'WINDOW'
bl_context = "general"
bl_label = "Setup"
bl_options = {'HIDE_HEADER'}
def draw_centered(self, context, layout):
project = context.project
layout.prop(project, "name")
layout.prop(project, "root_path", text="Location")
class PROJECTSETTINGS_PT_asset_libraries(Panel):
bl_space_type = 'PROJECT_SETTINGS'
bl_region_type = 'WINDOW'
bl_context = "asset_libraries"
bl_label = "Asset Libraries"
def draw(self, context):
layout = self.layout
layout.use_property_split = False
layout.use_property_decorate = False
project = context.project
box = layout.box()
split = box.split(factor=0.35)
name_col = split.column()
path_col = split.column()
row = name_col.row(align=True) # Padding
row.separator()
row.label(text="Name")
row = path_col.row(align=True) # Padding
row.separator()
row.label(text="Path")
for i, library in enumerate(project.asset_libraries):
row = name_col.row()
row.alert = not library.name
row.prop(library, "name", text="")
row = path_col.row()
subrow = row.row()
subrow.alert = not library.path
subrow.prop(library, "path", text="")
row.operator("project.custom_asset_library_remove", text="", icon='X', emboss=False).index = i
row = box.row()
row.alignment = 'RIGHT'
row.operator("project.custom_asset_library_add", text="", icon='ADD', emboss=False)
classes = (
PROJECTSETTINGS_HT_header,
PROJECTSETTINGS_MT_editor_menus,
PROJECTSETTINGS_MT_view,
PROJECTSETTINGS_MT_advanced_operations,
PROJECTSETTINGS_PT_navigation_bar,
PROJECTSETTINGS_PT_save_project_settings,
PROJECTSETTINGS_PT_no_project,
PROJECTSETTINGS_PT_setup,
PROJECTSETTINGS_PT_asset_libraries,
)
if __name__ == "__main__": # only for live edit.
from bpy.utils import register_class
for cls in classes:
register_class(cls)

View File

@ -281,6 +281,10 @@ class TOPBAR_MT_file(Menu):
layout.operator("wm.revert_mainfile")
layout.menu("TOPBAR_MT_file_recover")
if context.preferences.experimental.use_blender_projects:
props = layout.operator("project.new", text="Set up Project...")
props.open_settings_after = True
layout.separator()
layout.operator_context = 'EXEC_AREA' if context.blend_data.is_saved else 'INVOKE_AREA'
@ -636,6 +640,8 @@ class TOPBAR_MT_edit(Menu):
layout.separator()
if context.preferences.experimental.use_blender_projects:
layout.operator("screen.project_settings_show", text="Project Settings...")
layout.operator("screen.userpref_show",
text="Preferences...", icon='PREFERENCES')
@ -722,7 +728,7 @@ class TOPBAR_MT_help(Menu):
class TOPBAR_MT_file_context_menu(Menu):
bl_label = "File"
def draw(self, _context):
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_AREA'
@ -742,6 +748,8 @@ class TOPBAR_MT_file_context_menu(Menu):
layout.separator()
if context.preferences.experimental.use_blender_projects:
layout.operator("screen.project_settings_show", text="Project Settings...")
layout.operator("screen.userpref_show",
text="Preferences...", icon='PREFERENCES')

View File

@ -13,6 +13,7 @@ from bpy.app.translations import (
pgettext_iface as iface_,
pgettext_tip as tip_,
)
from bl_ui.utils import CenterAlignMixIn
from bl_ui.utils import PresetPanel
@ -151,40 +152,6 @@ class USERPREF_PT_save_preferences(Panel):
USERPREF_HT_header.draw_buttons(layout, context)
# -----------------------------------------------------------------------------
# Min-In Helpers
# Panel mix-in.
class CenterAlignMixIn:
"""
Base class for panels to center align contents with some horizontal margin.
Deriving classes need to implement a ``draw_centered(context, layout)`` function.
"""
def draw(self, context):
layout = self.layout
width = context.region.width
ui_scale = context.preferences.system.ui_scale
# No horizontal margin if region is rather small.
is_wide = width > (350 * ui_scale)
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
row = layout.row()
if is_wide:
row.label() # Needed so col below is centered.
col = row.column()
col.ui_units_x = 50
# Implemented by sub-classes.
self.draw_centered(context, col)
if is_wide:
row.label() # Needed so col above is centered.
# -----------------------------------------------------------------------------
# Interface Panels
@ -1337,6 +1304,10 @@ class ThemeGenericClassGenerator:
if theme_area.identifier in {'USER_INTERFACE', 'STYLE', 'BONE_COLOR_SETS'}:
continue
prefs = bpy.context.preferences
if not prefs.experimental.use_blender_projects and theme_area.identifier == 'PROJECT_SETTINGS':
continue
panel_id = "USERPREF_PT_theme_" + theme_area.identifier.lower()
# Generate panel-class from theme_area
yield type(panel_id, (PreferenceThemeSpacePanel, ThemePanel, Panel), {
@ -2562,6 +2533,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
({"property": "use_grease_pencil_version3"}, ("blender/blender/projects/6", "Grease Pencil 3.0")),
({"property": "enable_overlay_next"}, ("blender/blender/issues/102179", "#102179")),
({"property": "use_extension_repos"}, ("/blender/blender/issues/106254", "#106254")),
({"property": "use_blender_projects"}, None),
),
)

View File

@ -38,3 +38,40 @@ class PresetPanel:
layout.operator_context = 'EXEC_DEFAULT'
Menu.draw_preset(self, context)
# -----------------------------------------------------------------------------
# Mix-In Helpers
# Panel mix-in.
class CenterAlignMixIn:
"""
Base class for panels to center align contents with some horizontal margin.
Deriving classes need to implement a ``draw_centered(context, layout)`` function.
Used by Preferences and Project Settings, and optimized for their display. May not work that
well in other cases.
"""
def draw(self, context):
layout = self.layout
width = context.region.width
ui_scale = context.preferences.system.ui_scale
# No horizontal margin if region is rather small.
is_wide = width > (350 * ui_scale)
layout.use_property_split = True
layout.use_property_decorate = False # No animation.
row = layout.row()
if is_wide:
row.label() # Needed so col below is centered.
col = row.column()
col.ui_units_x = 50
# Implemented by sub-classes.
self.draw_centered(context, col)
if is_wide:
row.label() # Needed so col above is centered.

View File

@ -173,6 +173,9 @@ class AssetLibrary {
StringRefNull root_path() const;
};
/**
* \note Excludes asset libraries of type #ASSET_LIBRARY_CUSTOM_PATH.
*/
Vector<AssetLibraryReference> all_valid_asset_library_refs();
AssetLibraryReference all_library_reference();
@ -187,6 +190,7 @@ AssetLibraryReference all_library_reference();
* loaded as well. So a call to #AssetLibrary::foreach_loaded() can be expected to iterate over all
* libraries.
*
* \note Cannot load asset libraries of type #ASSET_LIBRARY_CUSTOM_PATH.
* \warning Catalogs are reloaded, invalidating catalog pointers. Do not store catalog pointers,
* store CatalogIDs instead and lookup the catalog where needed.
*/

View File

@ -6,6 +6,7 @@ set(INC
.
intern
../blenkernel
../makesrna
)
set(INC_SYS

View File

@ -14,8 +14,11 @@
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.hh"
#include "BKE_context.hh"
#include "BKE_main.hh"
#include "BKE_preferences.h"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
@ -58,7 +61,7 @@ asset_system::AssetLibrary *AS_asset_library_load(const Main *bmain,
lib = service->get_asset_library_current_file();
}
else {
lib = service->get_asset_library_on_disk_custom(name, library_dirpath);
lib = service->get_asset_library_on_disk_custom_path(name, library_dirpath);
}
return reinterpret_cast<::AssetLibrary *>(lib);
}
@ -78,8 +81,8 @@ std::string AS_asset_library_root_path_from_library_ref(
std::string AS_asset_library_find_suitable_root_path_from_path(
const blender::StringRefNull input_path)
{
if (bUserAssetLibrary *preferences_lib = BKE_preferences_asset_library_containing_path(
&U, input_path.c_str()))
if (CustomAssetLibraryDefinition *preferences_lib = BKE_asset_library_custom_containing_path(
&U.asset_libraries, input_path.c_str()))
{
return preferences_lib->dirpath;
}
@ -351,16 +354,31 @@ Vector<AssetLibraryReference> all_valid_asset_library_refs()
result.append(library_ref);
}
int i;
LISTBASE_FOREACH_INDEX (const bUserAssetLibrary *, asset_library, &U.asset_libraries, i) {
LISTBASE_FOREACH_INDEX (
const CustomAssetLibraryDefinition *, asset_library, &U.asset_libraries, i) {
if (!BLI_is_dir(asset_library->dirpath)) {
continue;
}
AssetLibraryReference library_ref{};
library_ref.custom_library_index = i;
library_ref.type = ASSET_LIBRARY_CUSTOM;
library_ref.type = ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES;
result.append(library_ref);
}
if (bke::BlenderProject *project = CTX_wm_project()) {
ListBase &project_libraries = project->asset_library_definitions();
LISTBASE_FOREACH_INDEX (
const CustomAssetLibraryDefinition *, asset_library, &project_libraries, i) {
if (!BLI_is_dir(asset_library->dirpath)) {
continue;
}
AssetLibraryReference library_ref{};
library_ref.custom_library_index = i;
library_ref.type = ASSET_LIBRARY_CUSTOM_FROM_PROJECT;
result.append(library_ref);
}
}
AssetLibraryReference library_ref{};
library_ref.custom_library_index = -1;
library_ref.type = ASSET_LIBRARY_LOCAL;

View File

@ -6,8 +6,12 @@
* \ingroup asset_system
*/
#include <memory>
#include <string>
#include "BKE_asset_library_custom.h"
#include "BKE_blender.h"
#include "BKE_preferences.h"
#include "BKE_blender_project.hh"
#include "BLI_path_util.h"
#include "BLI_string_ref.hh"
@ -88,8 +92,9 @@ AssetLibrary *AssetLibraryService::get_asset_library(
}
case ASSET_LIBRARY_ALL:
return get_asset_library_all(bmain);
case ASSET_LIBRARY_CUSTOM: {
bUserAssetLibrary *custom_library = find_custom_asset_library_from_library_ref(
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
CustomAssetLibraryDefinition *custom_library = find_custom_asset_library_from_library_ref(
library_reference);
if (!custom_library) {
return nullptr;
@ -100,13 +105,16 @@ AssetLibrary *AssetLibraryService::get_asset_library(
return nullptr;
}
AssetLibrary *library = get_asset_library_on_disk_custom(custom_library->name, root_path);
AssetLibrary *library = get_asset_library_on_disk(type, custom_library->name, root_path);
library->import_method_ = eAssetImportMethod(custom_library->import_method);
library->may_override_import_method_ = true;
library->use_relative_path_ = (custom_library->flag & ASSET_LIBRARY_RELATIVE_PATH) != 0;
return library;
}
case ASSET_LIBRARY_CUSTOM_PATH: {
return nullptr;
}
}
return nullptr;
@ -144,18 +152,18 @@ AssetLibrary *AssetLibraryService::get_asset_library_on_disk(eAssetLibraryType l
return lib;
}
AssetLibrary *AssetLibraryService::get_asset_library_on_disk_custom(StringRef name,
StringRefNull root_path)
AssetLibrary *AssetLibraryService::get_asset_library_on_disk_custom_path(StringRef name,
StringRefNull root_path)
{
return get_asset_library_on_disk(ASSET_LIBRARY_CUSTOM, name, root_path);
return get_asset_library_on_disk(ASSET_LIBRARY_CUSTOM_PATH, name, root_path);
}
AssetLibrary *AssetLibraryService::get_asset_library_on_disk_builtin(eAssetLibraryType type,
StringRefNull root_path)
{
BLI_assert_msg(
type != ASSET_LIBRARY_CUSTOM,
"Use `get_asset_library_on_disk_custom()` for libraries of type `ASSET_LIBRARY_CUSTOM`");
!ELEM(type, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, ASSET_LIBRARY_CUSTOM_FROM_PROJECT),
"Use `get_asset_library_on_disk_custom()` for libraries of custom asset libraries");
/* Builtin asset libraries don't need a name, the #eAssetLibraryType is enough to identify them
* (and doesn't change, unlike the name). */
@ -234,14 +242,24 @@ AssetLibrary *AssetLibraryService::get_asset_library_all(const Main *bmain)
return all_library_.get();
}
bUserAssetLibrary *AssetLibraryService::find_custom_preferences_asset_library_from_asset_weak_ref(
const AssetWeakReference &asset_reference)
CustomAssetLibraryDefinition *AssetLibraryService::
find_custom_asset_library_definition_from_asset_weak_ref(
const AssetWeakReference &asset_reference)
{
if (!ELEM(asset_reference.asset_library_type, ASSET_LIBRARY_CUSTOM)) {
return nullptr;
switch (eAssetLibraryType(asset_reference.asset_library_type)) {
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
return BKE_asset_library_custom_find_by_name(&U.asset_libraries,
asset_reference.asset_library_identifier);
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
const bke::BlenderProject *project = bke::BlenderProject::get_active();
ListBase &libraries = project->get_settings().asset_library_definitions();
return BKE_asset_library_custom_find_by_name(&libraries,
asset_reference.asset_library_identifier);
}
default:
BLI_assert_unreachable();
return nullptr;
}
return BKE_preferences_asset_library_find_by_name(&U, asset_reference.asset_library_identifier);
}
AssetLibrary *AssetLibraryService::find_loaded_on_disk_asset_library_from_name(
@ -261,16 +279,25 @@ std::string AssetLibraryService::resolve_asset_weak_reference_to_library_path(
StringRefNull library_dirpath;
switch (eAssetLibraryType(asset_reference.asset_library_type)) {
case ASSET_LIBRARY_CUSTOM: {
bUserAssetLibrary *custom_lib = find_custom_preferences_asset_library_from_asset_weak_ref(
asset_reference);
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
CustomAssetLibraryDefinition *custom_lib =
find_custom_asset_library_definition_from_asset_weak_ref(asset_reference);
if (custom_lib) {
library_dirpath = custom_lib->dirpath;
break;
}
/* A bit of an odd-ball, the API supports loading custom libraries from arbitrary paths (used
* by unit tests). So check all loaded on-disk libraries too. */
AssetLibrary *loaded_custom_lib = find_loaded_on_disk_asset_library_from_name(
asset_reference.asset_library_identifier);
if (!loaded_custom_lib) {
return "";
}
library_dirpath = *loaded_custom_lib->root_path_;
break;
}
case ASSET_LIBRARY_CUSTOM_PATH: {
AssetLibrary *loaded_custom_lib = find_loaded_on_disk_asset_library_from_name(
asset_reference.asset_library_identifier);
if (!loaded_custom_lib) {
@ -391,7 +418,9 @@ std::optional<AssetLibraryService::ExplodedPath> AssetLibraryService::
return exploded;
}
case ASSET_LIBRARY_CUSTOM:
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT:
case ASSET_LIBRARY_CUSTOM_PATH:
case ASSET_LIBRARY_ESSENTIALS: {
std::string full_path = resolve_asset_weak_reference_to_full_path(asset_reference);
/* #full_path uses native slashes, so others don't need to be considered in the following. */
@ -428,32 +457,82 @@ std::optional<AssetLibraryService::ExplodedPath> AssetLibraryService::
return std::nullopt;
}
bUserAssetLibrary *AssetLibraryService::find_custom_asset_library_from_library_ref(
CustomAssetLibraryDefinition *AssetLibraryService::find_custom_asset_library_from_library_ref(
const AssetLibraryReference &library_reference)
{
BLI_assert(library_reference.type == ASSET_LIBRARY_CUSTOM);
BLI_assert(ELEM(library_reference.type,
ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES,
ASSET_LIBRARY_CUSTOM_FROM_PROJECT));
BLI_assert(library_reference.custom_library_index >= 0);
return BKE_preferences_asset_library_find_index(&U, library_reference.custom_library_index);
switch (eAssetLibraryType(library_reference.type)) {
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES: {
return BKE_asset_library_custom_find_index(&U.asset_libraries,
library_reference.custom_library_index);
}
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
const bke::BlenderProject *project = bke::BlenderProject::get_active();
if (!project) {
return nullptr;
}
ListBase &project_libraries = project->get_settings().asset_library_definitions();
return BKE_asset_library_custom_find_index(&project_libraries,
library_reference.custom_library_index);
}
case ASSET_LIBRARY_ALL:
case ASSET_LIBRARY_LOCAL:
case ASSET_LIBRARY_ESSENTIALS:
case ASSET_LIBRARY_CUSTOM_PATH:
break;
}
BLI_assert_unreachable();
return NULL;
}
std::string AssetLibraryService::root_path_from_library_ref(
const AssetLibraryReference &library_reference)
{
if (ELEM(library_reference.type, ASSET_LIBRARY_ALL, ASSET_LIBRARY_LOCAL)) {
return "";
}
if (ELEM(library_reference.type, ASSET_LIBRARY_ESSENTIALS)) {
return essentials_directory_path();
switch (eAssetLibraryType(library_reference.type)) {
case ASSET_LIBRARY_ALL:
case ASSET_LIBRARY_LOCAL:
return "";
case ASSET_LIBRARY_ESSENTIALS:
return essentials_directory_path();
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES: {
CustomAssetLibraryDefinition *custom_library = find_custom_asset_library_from_library_ref(
library_reference);
if (!custom_library) {
return "";
}
return custom_library->dirpath;
}
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
CustomAssetLibraryDefinition *project_library = find_custom_asset_library_from_library_ref(
library_reference);
if (!project_library) {
return "";
}
/* Project asset libraries typically use relative paths (relative to project root directory).
*/
if (BLI_path_is_rel(project_library->dirpath)) {
const bke::BlenderProject *project = bke::BlenderProject::get_active();
char path[1024]; /* FILE_MAX */
BLI_path_join(path, sizeof(path), project->root_path().c_str(), project_library->dirpath);
return path;
}
return project_library->dirpath;
}
default:
BLI_assert_unreachable();
break;
}
bUserAssetLibrary *custom_library = find_custom_asset_library_from_library_ref(
library_reference);
if (!custom_library || !custom_library->dirpath[0]) {
return "";
}
return custom_library->dirpath;
return "";
}
void AssetLibraryService::allocate_service_instance()

View File

@ -19,7 +19,6 @@
#include <memory>
struct AssetLibraryReference;
struct bUserAssetLibrary;
namespace blender::asset_system {
@ -66,19 +65,34 @@ class AssetLibraryService {
/** Destroy the AssetLibraryService singleton. It will be reallocated by #get() if necessary. */
static void destroy();
/**
* \note Does not work with asset libraries of type #ASSET_LIBRARY_CUSTOM_PATH.
*/
static std::string root_path_from_library_ref(const AssetLibraryReference &library_reference);
static bUserAssetLibrary *find_custom_asset_library_from_library_ref(
/**
* \note Only works with #ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES and
* #ASSET_LIBRARY_CUSTOM_FROM_PROJECT.
*/
static CustomAssetLibraryDefinition *find_custom_asset_library_from_library_ref(
const AssetLibraryReference &library_reference);
static bUserAssetLibrary *find_custom_preferences_asset_library_from_asset_weak_ref(
/**
* \note Only works with #ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES and
* #ASSET_LIBRARY_CUSTOM_FROM_PROJECT.
*/
static CustomAssetLibraryDefinition *find_custom_asset_library_definition_from_asset_weak_ref(
const AssetWeakReference &asset_reference);
/**
* \note Cannot load asset libraries of type #ASSET_LIBRARY_CUSTOM_PATH.
*/
AssetLibrary *get_asset_library(const Main *bmain,
const AssetLibraryReference &library_reference);
/** Get an asset library of type #ASSET_LIBRARY_CUSTOM. */
AssetLibrary *get_asset_library_on_disk_custom(StringRef name, StringRefNull root_path);
/** Get a custom asset library using \a root_path. The library is of type
* #ASSET_LIBRARY_CUSTOM_PATH. */
AssetLibrary *get_asset_library_on_disk_custom_path(StringRef name, StringRefNull root_path);
/** Get a builtin (not user defined) asset library. I.e. a library that is **not** of type
* #ASSET_LIBRARY_CUSTOM. */
* #ASSET_LIBRARY_CUSTOM_XXX. */
AssetLibrary *get_asset_library_on_disk_builtin(eAssetLibraryType type, StringRefNull root_path);
/** Get the "Current File" asset library. */
AssetLibrary *get_asset_library_current_file();

View File

@ -5,7 +5,7 @@
#include "AS_asset_catalog.hh"
#include "AS_asset_catalog_tree.hh"
#include "BKE_preferences.h"
#include "BKE_asset_library_custom.h"
#include "BLI_fileops.h"
#include "BLI_path_util.h"
@ -94,8 +94,8 @@ class AssetCatalogTest : public AssetLibraryTestBase {
BLI_path_slash_native(cdf_in_subdir.data());
/* Set up a temporary asset library for testing. */
bUserAssetLibrary *asset_lib_pref = BKE_preferences_asset_library_add(
&U, "Test", registered_asset_lib.c_str());
CustomAssetLibraryDefinition *asset_lib_pref = BKE_asset_library_custom_add(
&U.asset_libraries, "Test", registered_asset_lib.c_str());
ASSERT_NE(nullptr, asset_lib_pref);
ASSERT_TRUE(BLI_dir_create_recursive(asset_lib_subdir.c_str()));
@ -146,7 +146,7 @@ class AssetCatalogTest : public AssetLibraryTestBase {
/* Test that the "red herring" CDF has not been touched. */
EXPECT_EQ(0, BLI_file_size(cdf_in_subdir.c_str()));
BKE_preferences_asset_library_remove(&U, asset_lib_pref);
BKE_asset_library_custom_remove(&U.asset_libraries, asset_lib_pref);
}
};

View File

@ -95,11 +95,11 @@ TEST_F(AssetLibraryServiceTest, library_pointers)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetLibrary *const lib = service->get_asset_library_on_disk_custom_path(__func__,
asset_library_root_);
AssetLibrary *const curfile_lib = service->get_asset_library_current_file();
EXPECT_EQ(lib, service->get_asset_library_on_disk_custom(__func__, asset_library_root_))
EXPECT_EQ(lib, service->get_asset_library_on_disk_custom_path(__func__, asset_library_root_))
<< "Calling twice without destroying in between should return the same instance.";
EXPECT_EQ(curfile_lib, service->get_asset_library_current_file())
<< "Calling twice without destroying in between should return the same instance.";
@ -126,8 +126,8 @@ TEST_F(AssetLibraryServiceTest, library_from_reference)
std::string dummy_filepath = asset_library_root_ + SEP + "dummy.blend";
STRNCPY(dummy_main.filepath, dummy_filepath.c_str());
AssetLibrary *custom_lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetLibrary *custom_lib = service->get_asset_library_on_disk_custom_path(__func__,
asset_library_root_);
AssetLibrary *tmp_curfile_lib = service->get_asset_library(&dummy_main, ref);
/* Requested a current file library with a (fake) file saved in the same directory as a custom
@ -160,19 +160,19 @@ TEST_F(AssetLibraryServiceTest, library_path_trailing_slashes)
BLI_path_slash_ensure(asset_lib_with_slash, PATH_MAX);
AssetLibrary *const lib_no_slash = service->get_asset_library_on_disk_custom(__func__,
asset_lib_no_slash);
AssetLibrary *const lib_no_slash = service->get_asset_library_on_disk_custom_path(
__func__, asset_lib_no_slash);
EXPECT_EQ(lib_no_slash,
service->get_asset_library_on_disk_custom(__func__, asset_lib_with_slash))
service->get_asset_library_on_disk_custom_path(__func__, asset_lib_with_slash))
<< "With or without trailing slash shouldn't matter.";
}
TEST_F(AssetLibraryServiceTest, catalogs_loaded)
{
AssetLibraryService *const service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetLibrary *const lib = service->get_asset_library_on_disk_custom_path(__func__,
asset_library_root_);
AssetCatalogService *const cat_service = lib->catalog_service.get();
const bUUID UUID_POSES_ELLIE("df60e1f6-2259-475b-93d9-69a1b4a8db78");
@ -186,8 +186,8 @@ TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs)
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Empty AssetLibraryService should have no unsaved catalogs";
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__,
asset_library_root_);
AssetLibrary *const lib = service->get_asset_library_on_disk_custom_path(__func__,
asset_library_root_);
AssetCatalogService *const cat_service = lib->catalog_service.get();
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Unchanged AssetLibrary should have no unsaved catalogs";
@ -219,7 +219,7 @@ TEST_F(AssetLibraryServiceTest, has_any_unsaved_catalogs_after_write)
ASSERT_EQ(0, BLI_copy(original_cdf_file.c_str(), writable_cdf_file.c_str()));
AssetLibraryService *const service = AssetLibraryService::get();
AssetLibrary *const lib = service->get_asset_library_on_disk_custom(__func__, writable_dir);
AssetLibrary *const lib = service->get_asset_library_on_disk_custom_path(__func__, writable_dir);
EXPECT_FALSE(service->has_any_unsaved_catalogs())
<< "Unchanged AssetLibrary should have no unsaved catalogs";

View File

@ -60,13 +60,13 @@ TEST_F(AssetRepresentationTest, weak_reference__current_file)
TEST_F(AssetRepresentationTest, weak_reference__custom_library)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetLibrary *const library = service->get_asset_library_on_disk_custom_path(
"My custom lib", asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
{
AssetWeakReference *weak_ref = asset.make_weak_reference();
EXPECT_EQ(weak_ref->asset_library_type, ASSET_LIBRARY_CUSTOM);
AssetWeakReference* weak_ref = asset.make_weak_reference();
EXPECT_EQ(weak_ref->asset_library_type, ASSET_LIBRARY_CUSTOM_PATH);
EXPECT_STREQ(weak_ref->asset_library_identifier, "My custom lib");
EXPECT_STREQ(weak_ref->relative_asset_identifier, "path/to/an/asset");
BKE_asset_weak_reference_free(&weak_ref);
@ -90,8 +90,8 @@ TEST_F(AssetRepresentationTest, weak_reference__resolve_to_full_path__current_fi
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_full_path__custom_library)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetLibrary *const library = service->get_asset_library_on_disk_custom_path(
"My custom lib", asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path/to/an/asset");
AssetWeakReference *weak_ref = asset.make_weak_reference();
@ -108,8 +108,8 @@ TEST_F(AssetRepresentationTest,
weak_reference__resolve_to_full_path__custom_library__windows_pathsep)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetLibrary *const library = service->get_asset_library_on_disk_custom_path(
"My custom lib", asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "path\\to\\an\\asset");
AssetWeakReference *weak_ref = asset.make_weak_reference();
@ -147,8 +147,8 @@ TEST_F(AssetRepresentationTest, weak_reference__resolve_to_exploded_path__curren
TEST_F(AssetRepresentationTest, weak_reference__resolve_to_exploded_path__custom_library)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetLibrary *const library = service->get_asset_library_on_disk_custom_path(
"My custom lib", asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "some.blend/Material/asset/name");
AssetWeakReference *weak_ref = asset.make_weak_reference();
@ -174,8 +174,8 @@ TEST_F(AssetRepresentationTest,
weak_reference__resolve_to_exploded_path__custom_library__windows_pathsep)
{
AssetLibraryService *service = AssetLibraryService::get();
AssetLibrary *const library = service->get_asset_library_on_disk_custom("My custom lib",
asset_library_root_);
AssetLibrary *const library = service->get_asset_library_on_disk_custom_path(
"My custom lib", asset_library_root_);
AssetRepresentation &asset = add_dummy_asset(*library, "some.blend\\Material\\asset/name");
AssetWeakReference *weak_ref = asset.make_weak_reference();

View File

@ -0,0 +1,71 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*
* API to manage a list of #CustomAssetLibraryDefinition items.
*/
#pragma once
#include "BLI_compiler_attrs.h"
#include "BLI_utildefines.h"
#ifdef __cplusplus
extern "C" {
#endif
struct CustomAssetLibraryDefinition;
struct ListBase;
struct CustomAssetLibraryDefinition *BKE_asset_library_custom_add(
struct ListBase *custom_libraries,
const char *name CPP_ARG_DEFAULT(nullptr),
const char *path CPP_ARG_DEFAULT(nullptr)) ATTR_NONNULL(1);
/**
* Unlink and free a library preference member.
* \note Free's \a library itself.
*/
void BKE_asset_library_custom_remove(struct ListBase *custom_libraries,
struct CustomAssetLibraryDefinition *library) ATTR_NONNULL();
void BKE_asset_library_custom_name_set(struct ListBase *custom_libraries,
struct CustomAssetLibraryDefinition *library,
const char *name) ATTR_NONNULL();
/**
* Set the library path, ensuring it is pointing to a directory.
* Single blend files can only act as "Current File" library; libraries on disk
* should always be directories. Blindly sets the path without additional checks. The asset system
* can ignore libraries that it can't resolve to a valid location. If the path does not exist,
* that's fine; it can created as directory if necessary later.
*/
void BKE_asset_library_custom_path_set(struct CustomAssetLibraryDefinition *library,
const char *path) ATTR_NONNULL();
struct CustomAssetLibraryDefinition *BKE_asset_library_custom_find_index(
const struct ListBase *custom_libraries, int index) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
struct CustomAssetLibraryDefinition *BKE_asset_library_custom_find_by_name(
const struct ListBase *custom_libraries, const char *name)
ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
/**
* Return the #CustomAssetLibraryDefinition that contains the given file/directory path. The given
* path can be the library's top-level directory, or any path inside that directory.
*
* When more than one asset libraries match, the first matching one is returned (no smartness when
* there nested asset libraries).
*
* Return NULL when no such asset library is found. */
struct CustomAssetLibraryDefinition *BKE_asset_library_custom_containing_path(
const struct ListBase *custom_libraries, const char *path)
ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
int BKE_asset_library_custom_get_index(
const struct ListBase /*#CustomAssetLibraryDefinition*/ *custom_libraries,
const struct CustomAssetLibraryDefinition *library) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,245 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#pragma once
#include <memory>
#include "BLI_string_ref.hh"
struct ListBase;
namespace blender::io::serialize {
class DictionaryValue;
}
namespace blender::bke {
class ProjectSettings;
struct CustomAssetLibraries;
/**
* Entry point / API for core Blender project management.
*
* Responsibilities:
* - Own and give access to the active project.
* - Manage the .blender_project/ directory.
* - Store and manage (including reading & writing) of the .blender_project/settings.json file. The
* implementation of this can be found in the internal #ProjectSettings class.
* - Tag for unsaved changes as needed.
*/
class BlenderProject {
friend class ProjectSettings;
/* Path to the project root using native slashes plus a trailing slash. */
std::string root_path_;
std::unique_ptr<ProjectSettings> settings_;
public:
inline static const StringRefNull SETTINGS_DIRNAME = ".blender_project";
inline static const StringRefNull SETTINGS_FILENAME = "settings.json";
public:
[[nodiscard]] static BlenderProject *get_active();
/**
* \note: When changing the active project, the previously active one will be destroyed, so
* pointers may dangle.
*/
static BlenderProject *set_active(std::unique_ptr<BlenderProject> settings);
/**
* Read project settings from the given \a path, which may point to some directory or file inside
* of the project directory. Both Unix and Windows style slashes are allowed. Path is expected to
* be normalized.
*
* Attempt to read project data from the given \a project_path, which may be either a project
* root directory or the .blender_project directory, and load it into runtime data. Letting the
* returned #unique_pointer run out of scope cleanly destructs the runtime project data.
*
* \note Does NOT set the loaded project active.
*
* \return The loaded project or null on failure.
*/
static std::unique_ptr<BlenderProject> load_from_path(StringRef project_path);
/**
* Attempt to load and activate a project based on the given path. If the path doesn't lead
* to or into a project, the active project is unset. Note that the project will be unset on any
* failure when loading the project.
*
* \note: When setting an active project, the previously active one will be destroyed, so
* pointers may dangle.
*/
static BlenderProject *load_active_from_path(StringRef project_path);
/**
* Initializes a blender project by creating a .blender_project directory at the given \a
* project_root_path.
* Both Unix and Windows style slashes are allowed.
*
* \return True if the settings directory was created, or already existed. False on failure.
*/
static bool create_settings_directory(StringRef project_root_path);
/**
* Remove the .blender_project directory with all of its contents at the given \a
* project_root_path. If this is the path of the active project, it is marked as having changed
* but it is not unloaded. Runtime project data is still valid at this point.
*
* \return True on success.
*/
static bool delete_settings_directory(StringRef project_root_path);
/**
* Check if the directory given by \a path contains a .blender_project directory and should thus
* be considered a project root directory.
* Will return false for paths pointing into a project root directory not to a root directory
* itself.
*/
[[nodiscard]] static bool path_is_project_root(StringRef path);
/**
* Check if \a path points to or into a project root path (i.e. if one of the ancestors of the
* referenced file/directory is a project root directory).
*/
[[nodiscard]] static bool path_is_within_project(StringRef path);
/**
* Check if \a path points into a project and return the root directory path of that project (the
* one containing the .blender_project directory). Walks "upwards" through the path and returns
* the first project found, so if a project is nested inside another one, the nested project is
* used.
* Both Unix and Windows style slashes are allowed.
*
* \return The project root path or an empty path if not found. The referenced string points into
* the input \a path, so slashes are not converted in the returned value.
*/
[[nodiscard]] static StringRef project_root_path_find_from_path(StringRef path);
/**
* Version of #has_unsaved_changes() that allows passing null as \a project for convenience. If
* \a project is null, false will be returned.
*/
[[nodiscard]] static bool has_unsaved_changes(const BlenderProject *project);
/* --- Non-static member functions. --- */
BlenderProject(StringRef project_root_path, std::unique_ptr<ProjectSettings> settings);
/**
* \return True on success. If the .blender_project directory doesn't exist, that's treated
* as failure.
*/
bool save_settings();
/**
* Version of the static #delete_settings_directory() that deletes the settings directory of this
* project. Always tags as having unsaved changes after successful deletion.
* \return True on success (settings directory was deleted).
*/
bool delete_settings_directory();
[[nodiscard]] StringRefNull root_path() const;
[[nodiscard]] ProjectSettings &get_settings() const;
void set_project_name(StringRef new_name);
[[nodiscard]] StringRefNull project_name() const;
[[nodiscard]] const ListBase &asset_library_definitions() const;
[[nodiscard]] ListBase &asset_library_definitions();
/**
* Forcefully tag the project settings for having unsaved changes. This needs to be done if
* project settings data is modified directly by external code, not via a project API function.
* The API functions set the tag for all changes they manage.
*/
void tag_has_unsaved_changes();
/**
* Returns true if there were any changes done to the settings that have not been written to
* disk yet. Project API functions that change data set this, however when external code modifies
* project settings data it may have to manually set the tag, see #tag_has_unsaved_changes().
*
* There's a static version of this that takes a project pointer that may be null, for
* convenience (so the caller doesn't have to null-check).
*/
[[nodiscard]] bool has_unsaved_changes() const;
private:
[[nodiscard]] static std::unique_ptr<BlenderProject> &active_project_ptr();
/**
* Get the project root path from a path that is either already the project root, or the
* .blender_project directory. Returns the path with native slashes plus a trailing slash.
*/
[[nodiscard]] static std::string project_path_to_native_project_root_path(
StringRef project_path);
/**
* Get the .blender_project directory path from a project root path. Returns the path with native
* slashes plus a trailing slash. Assumes the path already ends with a native trailing slash.
*/
[[nodiscard]] static std::string project_root_path_to_settings_path(StringRef project_root_path);
/**
* Returns the path with native slashes.
* Assumes the path already ends with a native trailing slash.
*/
[[nodiscard]] static std::string project_root_path_to_settings_filepath(
StringRef project_root_path);
};
/**
* Runtime representation of the project settings (`.blender_project/settings.json`) with IO
* functionality.
*/
class ProjectSettings {
std::string project_name_;
std::unique_ptr<CustomAssetLibraries> asset_libraries_;
bool has_unsaved_changes_ = false;
public:
/**
* Read project settings from the given \a project_path, which may be either a project root
* directory or the .blender_project directory.
* Both Unix and Windows style slashes are allowed. Path is expected to be normalized.
*
* \return The read project settings or null in case of failure.
*/
[[nodiscard]] static std::unique_ptr<ProjectSettings> load_from_disk(StringRef project_path);
/**
* Read project settings from the given \a path, which may point to some directory or file inside
* of the project directory. Both Unix and Windows style slashes are allowed. Path is expected to
* be normalized.
*
* \return The read project settings or null in case of failure.
*/
[[nodiscard]] static std::unique_ptr<ProjectSettings> load_from_path(StringRef path);
/** Explicit constructor and destructor needed to manage the CustomAssetLibraries unique_ptr. */
ProjectSettings();
/* Implementation defaulted. */
~ProjectSettings();
/**
* Write project settings to the given \a project_path, which may be either a project root
* directory or the .blender_project directory. The .blender_project directory must exist.
* Both Unix and Windows style slashes are allowed. Path is expected to be normalized.
*
* \return True on success. If the .blender_project directory doesn't exist, that's treated
* as failure.
*/
bool save_to_disk(StringRef project_path);
void project_name(StringRef new_name);
[[nodiscard]] StringRefNull project_name() const;
[[nodiscard]] const ListBase &asset_library_definitions() const;
[[nodiscard]] ListBase &asset_library_definitions();
/** See #BlenderProject::tag_has_unsaved_changes(). */
void tag_has_unsaved_changes();
/** See #BlenderProject::has_unsaved_changes. */
[[nodiscard]] bool has_unsaved_changes() const;
private:
std::unique_ptr<io::serialize::DictionaryValue> to_dictionary() const;
};
} // namespace blender::bke

View File

@ -56,6 +56,7 @@ struct SpaceLink;
struct SpaceNla;
struct SpaceNode;
struct SpaceOutliner;
struct SpaceProjectSettings;
struct SpaceProperties;
struct SpaceSeq;
struct SpaceSpreadsheet;
@ -110,9 +111,14 @@ struct bContextStore {
bool used = false;
};
namespace blender::asset_system {
namespace blender {
namespace asset_system {
class AssetRepresentation;
}
namespace bke {
class BlenderProject;
}
} // namespace blender
/* for the context's rna mode enum
* keep aligned with data_mode_strings in context.cc */
@ -192,6 +198,7 @@ ARegion *CTX_wm_menu(const bContext *C);
wmGizmoGroup *CTX_wm_gizmo_group(const bContext *C);
wmMsgBus *CTX_wm_message_bus(const bContext *C);
ReportList *CTX_wm_reports(const bContext *C);
blender::bke::BlenderProject *CTX_wm_project();
View3D *CTX_wm_view3d(const bContext *C);
RegionView3D *CTX_wm_region_view3d(const bContext *C);
@ -211,6 +218,7 @@ SpaceUserPref *CTX_wm_space_userpref(const bContext *C);
SpaceClip *CTX_wm_space_clip(const bContext *C);
SpaceTopBar *CTX_wm_space_topbar(const bContext *C);
SpaceSpreadsheet *CTX_wm_space_spreadsheet(const bContext *C);
SpaceProjectSettings *CTX_wm_space_project_settings(const bContext *C);
void CTX_wm_manager_set(bContext *C, wmWindowManager *wm);
void CTX_wm_window_set(bContext *C, wmWindow *win);

View File

@ -16,62 +16,11 @@ extern "C" {
struct UserDef;
struct bUserExtensionRepo;
struct bUserAssetLibrary;
/* -------------------------------------------------------------------- */
/** \name Assert Libraries
* \{ */
/** Name of the asset library added by default. Needs translation with `DATA_()` still. */
#define BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME N_("User Library")
struct bUserAssetLibrary *BKE_preferences_asset_library_add(struct UserDef *userdef,
const char *name,
const char *dirpath) ATTR_NONNULL(1);
/**
* Unlink and free a library preference member.
* \note Free's \a library itself.
*/
void BKE_preferences_asset_library_remove(struct UserDef *userdef,
struct bUserAssetLibrary *library) ATTR_NONNULL();
void BKE_preferences_asset_library_name_set(struct UserDef *userdef,
struct bUserAssetLibrary *library,
const char *name) ATTR_NONNULL();
/**
* Set the library path, ensuring it is pointing to a directory.
* Single blend files can only act as "Current File" library; libraries on disk
* should always be directories. If the path does not exist, that's fine; it can
* created as directory if necessary later.
*/
void BKE_preferences_asset_library_path_set(struct bUserAssetLibrary *library, const char *path)
ATTR_NONNULL();
struct bUserAssetLibrary *BKE_preferences_asset_library_find_index(const struct UserDef *userdef,
int index)
ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
struct bUserAssetLibrary *BKE_preferences_asset_library_find_by_name(const struct UserDef *userdef,
const char *name)
ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
/**
* Return the bUserAssetLibrary that contains the given file/directory path. The given path can be
* the library's top-level directory, or any path inside that directory.
*
* When more than one asset libraries match, the first matching one is returned (no smartness when
* there nested asset libraries).
*
* Return NULL when no such asset library is found.
*/
struct bUserAssetLibrary *BKE_preferences_asset_library_containing_path(
const struct UserDef *userdef, const char *path) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
int BKE_preferences_asset_library_get_index(const struct UserDef *userdef,
const struct bUserAssetLibrary *library)
ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
void BKE_preferences_asset_library_default_add(struct UserDef *userdef) ATTR_NONNULL();
void BKE_preferences_custom_asset_library_default_add(struct UserDef *userdef) ATTR_NONNULL();
/** \} */

View File

@ -62,6 +62,7 @@ set(SRC
intern/armature_selection.cc
intern/armature_update.cc
intern/asset.cc
intern/asset_library_custom.cc
intern/asset_weak_reference.cc
intern/attribute.cc
intern/attribute_access.cc
@ -74,6 +75,8 @@ set(SRC
intern/bake_items_socket.cc
intern/blender.cc
intern/blender_copybuffer.cc
intern/blender_project.cc
intern/blender_project_settings.cc
intern/blender_undo.cc
intern/blender_user_menu.cc
intern/blendfile.cc
@ -324,6 +327,7 @@ set(SRC
BKE_appdir.h
BKE_armature.hh
BKE_asset.hh
BKE_asset_library_custom.h
BKE_attribute.h
BKE_attribute.hh
BKE_attribute_math.hh
@ -335,6 +339,7 @@ set(SRC
BKE_bake_items_socket.hh
BKE_blender.h
BKE_blender_copybuffer.h
BKE_blender_project.hh
BKE_blender_undo.h
BKE_blender_user_menu.h
BKE_blender_version.h
@ -832,7 +837,7 @@ if(WITH_GTESTS)
set(TEST_SRC
intern/action_test.cc
intern/armature_test.cc
intern/asset_metadata_test.cc
intern/blender_project_test.cc
intern/bpath_test.cc
intern/cryptomatte_test.cc
intern/curves_geometry_test.cc
@ -852,6 +857,7 @@ if(WITH_GTESTS)
)
set(TEST_INC
../editors/include
../blenloader/tests
)
include(GTestTesting)
blender_add_test_lib(bf_blenkernel_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB}")

View File

@ -0,0 +1,104 @@
/* SPDX-FileCopyrightText: 2023 Blender Foundation
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_listbase.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.hh"
#include "DNA_asset_types.h"
#include "DNA_defaults.h"
#include "BKE_asset_library_custom.h"
using namespace blender;
/* -------------------------------------------------------------------- */
/** \name Asset Libraries
* \{ */
CustomAssetLibraryDefinition *BKE_asset_library_custom_add(ListBase *custom_libraries,
const char *name,
const char *dirpath)
{
CustomAssetLibraryDefinition *library = MEM_cnew<CustomAssetLibraryDefinition>(
"CustomAssetLibraryDefinition");
memcpy(library, DNA_struct_default_get(CustomAssetLibraryDefinition), sizeof(*library));
BLI_addtail(custom_libraries, library);
if (name) {
BKE_asset_library_custom_name_set(custom_libraries, library, name);
}
if (dirpath) {
STRNCPY(library->dirpath, dirpath);
}
return library;
}
void BKE_asset_library_custom_remove(ListBase *custom_libraries,
CustomAssetLibraryDefinition *library)
{
BLI_freelinkN(custom_libraries, library);
}
void BKE_asset_library_custom_name_set(ListBase *custom_libraries,
CustomAssetLibraryDefinition *library,
const char *name)
{
STRNCPY_UTF8(library->name, name);
BLI_uniquename(custom_libraries,
library,
name,
'.',
offsetof(CustomAssetLibraryDefinition, name),
sizeof(library->name));
}
void BKE_asset_library_custom_path_set(CustomAssetLibraryDefinition *library, const char *dirpath)
{
STRNCPY(library->dirpath, dirpath);
}
CustomAssetLibraryDefinition *BKE_asset_library_custom_find_index(const ListBase *custom_libraries,
int index)
{
return static_cast<CustomAssetLibraryDefinition *>(BLI_findlink(custom_libraries, index));
}
CustomAssetLibraryDefinition *BKE_asset_library_custom_find_by_name(
const ListBase *custom_libraries, const char *name)
{
return static_cast<CustomAssetLibraryDefinition *>(
BLI_findstring(custom_libraries, name, offsetof(CustomAssetLibraryDefinition, name)));
}
CustomAssetLibraryDefinition *BKE_asset_library_custom_containing_path(
const ListBase *custom_libraries, const char *dirpath)
{
LISTBASE_FOREACH (CustomAssetLibraryDefinition *, asset_lib_pref, custom_libraries) {
if (BLI_path_contains(asset_lib_pref->dirpath, dirpath)) {
return asset_lib_pref;
}
}
return nullptr;
}
int BKE_asset_library_custom_get_index(const ListBase *custom_libraries,
const CustomAssetLibraryDefinition *library)
{
return BLI_findindex(custom_libraries, library);
}
/** \} */

View File

@ -0,0 +1,261 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "BLI_path_util.h"
#include "BLI_fileops.h"
#include "BKE_blender_project.hh"
namespace blender::bke {
BlenderProject::BlenderProject(const StringRef project_root_path,
std::unique_ptr<ProjectSettings> settings)
: settings_(std::move(settings))
{
root_path_ = BlenderProject::project_path_to_native_project_root_path(project_root_path);
BLI_assert(root_path_.back() == SEP);
}
/* ---------------------------------------------------------------------- */
/** \name Active project management (static storage)
* \{ */
/* Construct on First Use idiom. */
std::unique_ptr<BlenderProject> &BlenderProject::active_project_ptr()
{
static std::unique_ptr<BlenderProject> active_;
return active_;
}
BlenderProject *BlenderProject::set_active(std::unique_ptr<BlenderProject> project)
{
std::unique_ptr<BlenderProject> &active = active_project_ptr();
if (project) {
active = std::move(project);
}
else {
active = nullptr;
}
return active.get();
}
BlenderProject *BlenderProject::get_active()
{
std::unique_ptr<BlenderProject> &active = active_project_ptr();
return active.get();
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Project and project settings management.
* \{ */
std::unique_ptr<BlenderProject> BlenderProject::load_from_path(StringRef project_path)
{
const StringRef project_root_path = project_root_path_find_from_path(project_path);
std::unique_ptr<bke::ProjectSettings> project_settings = bke::ProjectSettings::load_from_path(
project_root_path);
if (!project_settings) {
return nullptr;
}
return std::make_unique<BlenderProject>(project_root_path, std::move(project_settings));
}
BlenderProject *BlenderProject::load_active_from_path(StringRef path)
{
/* Project should be unset if the path doesn't contain a project root. Unset in the beginning so
* early exiting behaves correctly. */
bke::BlenderProject::set_active(nullptr);
std::unique_ptr<bke::BlenderProject> project = bke::BlenderProject::load_from_path(path);
return bke::BlenderProject::set_active(std::move(project));
}
bool BlenderProject::create_settings_directory(StringRef project_path)
{
std::string project_root_path = project_path_to_native_project_root_path(project_path);
std::string settings_path = project_root_path_to_settings_path(project_root_path);
return BLI_dir_create_recursive(settings_path.c_str());
}
bool BlenderProject::save_settings()
{
return settings_->save_to_disk(root_path_);
}
bool BlenderProject::delete_settings_directory(StringRef project_path)
{
std::string project_root_path = project_path_to_native_project_root_path(project_path);
std::string settings_path = project_root_path_to_settings_path(project_root_path);
/* Returns 0 on success. */
if (BLI_delete(settings_path.c_str(), true, true)) {
return false;
}
BlenderProject *active_project = get_active();
if (active_project &&
BLI_path_cmp_normalized(project_root_path.c_str(), active_project->root_path().c_str()))
{
active_project->settings_->tag_has_unsaved_changes();
}
return true;
}
bool BlenderProject::has_unsaved_changes(const BlenderProject *project)
{
if (!project) {
return false;
}
return project->has_unsaved_changes();
}
bool BlenderProject::delete_settings_directory()
{
if (!delete_settings_directory(root_path_)) {
return false;
}
settings_->tag_has_unsaved_changes();
return true;
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Simple getters & setters
* \{ */
StringRefNull BlenderProject::root_path() const
{
return root_path_;
}
ProjectSettings &BlenderProject::get_settings() const
{
BLI_assert(settings_ != nullptr);
return *settings_;
}
void BlenderProject::set_project_name(StringRef new_name)
{
settings_->project_name(new_name);
}
StringRefNull BlenderProject::project_name() const
{
return settings_->project_name();
}
const ListBase &BlenderProject::asset_library_definitions() const
{
return settings_->asset_library_definitions();
}
ListBase &BlenderProject::asset_library_definitions()
{
return settings_->asset_library_definitions();
}
void BlenderProject::tag_has_unsaved_changes()
{
settings_->tag_has_unsaved_changes();
}
bool BlenderProject::has_unsaved_changes() const
{
return settings_->has_unsaved_changes();
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name Path stuff
* \{ */
StringRef BlenderProject::project_root_path_find_from_path(StringRef path)
{
/* There are two versions of the path used here: One copy that is converted to native slashes,
* and the unmodified original path from the input. */
std::string path_native = path;
BLI_path_slash_native(path_native.data());
StringRef cur_path = path;
while (cur_path.size()) {
std::string cur_path_native = StringRef(path_native.c_str(), cur_path.size());
if (path_is_project_root(cur_path_native)) {
return path.substr(0, cur_path.size());
}
/* Walk "up the path" (check the parent next). */
const int64_t pos_last_slash = cur_path_native.find_last_of(SEP);
if (pos_last_slash == StringRef::not_found) {
break;
}
cur_path = cur_path.substr(0, pos_last_slash);
}
return "";
}
static StringRef path_strip_trailing_native_slash(StringRef path)
{
const int64_t pos_before_trailing_slash = path.find_last_not_of(SEP);
return (pos_before_trailing_slash == StringRef::not_found) ?
path :
path.substr(0, pos_before_trailing_slash + 1);
}
bool BlenderProject::path_is_project_root(StringRef path)
{
path = path_strip_trailing_native_slash(path);
return BLI_exists(std::string(path + SEP_STR + SETTINGS_DIRNAME).c_str());
}
bool BlenderProject::path_is_within_project(StringRef path)
{
const StringRef found_root_path = project_root_path_find_from_path(path);
return !found_root_path.is_empty();
}
std::string BlenderProject::project_path_to_native_project_root_path(StringRef project_path)
{
std::string project_path_native = project_path;
BLI_path_slash_native(project_path_native.data());
const StringRef path_no_trailing_slashes = path_strip_trailing_native_slash(project_path_native);
if (path_no_trailing_slashes.endswith(SETTINGS_DIRNAME)) {
return StringRef(path_no_trailing_slashes).drop_suffix(SETTINGS_DIRNAME.size());
}
return std::string(path_no_trailing_slashes) + SEP;
}
std::string BlenderProject::project_root_path_to_settings_path(StringRef project_root_path)
{
BLI_assert(project_root_path.back() == SEP);
return project_root_path + SETTINGS_DIRNAME + SEP;
}
std::string BlenderProject::project_root_path_to_settings_filepath(StringRef project_root_path)
{
BLI_assert(project_root_path.back() == SEP);
return project_root_path_to_settings_path(project_root_path) + SETTINGS_FILENAME;
}
/** \} */
} // namespace blender::bke

View File

@ -0,0 +1,321 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <fstream>
#include <memory>
#include <string>
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_utility_mixins.hh"
#include "BLI_serialize.hh"
#include "BKE_asset_library_custom.h"
#include "DNA_asset_types.h"
#include "BKE_blender_project.hh"
namespace serialize = blender::io::serialize;
namespace blender::bke {
/* ---------------------------------------------------------------------- */
struct CustomAssetLibraries : NonCopyable {
ListBase asset_libraries = {nullptr, nullptr}; /* CustomAssetLibraryDefinition */
CustomAssetLibraries() = default;
CustomAssetLibraries(ListBase asset_libraries);
CustomAssetLibraries(CustomAssetLibraries &&other);
~CustomAssetLibraries();
CustomAssetLibraries &operator=(CustomAssetLibraries &&other);
};
CustomAssetLibraries::CustomAssetLibraries(ListBase asset_libraries)
: asset_libraries(asset_libraries)
{
}
CustomAssetLibraries::CustomAssetLibraries(CustomAssetLibraries &&other)
{
*this = std::move(other);
}
CustomAssetLibraries &CustomAssetLibraries::operator=(CustomAssetLibraries &&other)
{
asset_libraries = other.asset_libraries;
BLI_listbase_clear(&other.asset_libraries);
return *this;
}
CustomAssetLibraries::~CustomAssetLibraries()
{
LISTBASE_FOREACH_MUTABLE (CustomAssetLibraryDefinition *, library, &asset_libraries) {
BKE_asset_library_custom_remove(&asset_libraries, library);
}
}
/* ---------------------------------------------------------------------- */
/** \name settings.json Reading (Deserializing)
* \{ */
struct ExtractedSettings {
std::string project_name;
ListBase asset_libraries = {nullptr, nullptr}; /* CustomAssetLibraryDefinition */
};
static std::unique_ptr<serialize::Value> read_settings_file(StringRef settings_filepath)
{
std::ifstream is;
is.open(settings_filepath);
if (is.fail()) {
return nullptr;
}
serialize::JsonFormatter formatter;
/* Will not be a dictionary in case of error (corrupted file). */
std::unique_ptr<serialize::Value> deserialized_values = formatter.deserialize(is);
is.close();
if (deserialized_values->type() != serialize::eValueType::Dictionary) {
return nullptr;
}
return deserialized_values;
}
static std::unique_ptr<ExtractedSettings> extract_settings(
const serialize::DictionaryValue &dictionary)
{
using namespace serialize;
std::unique_ptr extracted_settings = std::make_unique<ExtractedSettings>();
const DictionaryValue::Lookup attributes = dictionary.create_lookup();
/* "project": */ {
const DictionaryValue::LookupValue *project_value = attributes.lookup_ptr("project");
BLI_assert(project_value != nullptr);
const DictionaryValue *project_dict = (*project_value)->as_dictionary_value();
const StringValue *project_name_value =
project_dict->create_lookup().lookup("name")->as_string_value();
if (project_name_value) {
extracted_settings->project_name = project_name_value->value();
}
}
/* "asset_libraries": */ {
const DictionaryValue::LookupValue *asset_libraries_value = attributes.lookup_ptr(
"asset_libraries");
if (asset_libraries_value) {
const ArrayValue *asset_libraries_array = (*asset_libraries_value)->as_array_value();
if (!asset_libraries_array) {
throw std::runtime_error(
"Unexpected asset_library format in settings.json, expected array");
}
for (const ArrayValue::Item &element : asset_libraries_array->elements()) {
const DictionaryValue *object_value = element->as_dictionary_value();
if (!object_value) {
throw std::runtime_error(
"Unexpected asset_library entry in settings.json, expected dictionary entries only");
}
const DictionaryValue::Lookup element_lookup = object_value->create_lookup();
const DictionaryValue::LookupValue *name_value = element_lookup.lookup_ptr("name");
if (name_value && (*name_value)->type() != eValueType::String) {
throw std::runtime_error(
"Unexpected asset_library entry in settings.json, expected name to be string");
}
const DictionaryValue::LookupValue *path_value = element_lookup.lookup_ptr("path");
if (path_value && (*path_value)->type() != eValueType::String) {
throw std::runtime_error(
"Unexpected asset_library entry in settings.json, expected path to be string");
}
/* TODO this isn't really extracting, should be creating data from the settings be a
* separate step? */
CustomAssetLibraryDefinition *library = BKE_asset_library_custom_add(
&extracted_settings->asset_libraries);
/* Name or path may not be set, this is fine. */
if (name_value) {
std::string name = (*name_value)->as_string_value()->value();
BKE_asset_library_custom_name_set(
&extracted_settings->asset_libraries, library, name.c_str());
}
if (path_value) {
std::string path = (*path_value)->as_string_value()->value();
BKE_asset_library_custom_path_set(library, path.c_str());
}
}
}
}
return extracted_settings;
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \name settings.json Writing (Serializing)
* \{ */
std::unique_ptr<serialize::DictionaryValue> ProjectSettings::to_dictionary() const
{
using namespace serialize;
std::unique_ptr<DictionaryValue> root = std::make_unique<DictionaryValue>();
DictionaryValue::Items &root_attributes = root->elements();
/* "project": */ {
std::unique_ptr<DictionaryValue> project_dict = std::make_unique<DictionaryValue>();
DictionaryValue::Items &project_attributes = project_dict->elements();
project_attributes.append_as("name", new StringValue(project_name_));
root_attributes.append_as("project", std::move(project_dict));
}
/* "asset_libraries": */ {
if (!BLI_listbase_is_empty(&asset_libraries_->asset_libraries)) {
std::unique_ptr<ArrayValue> asset_libs_array = std::make_unique<ArrayValue>();
ArrayValue::Items &asset_libs_elements = asset_libs_array->elements();
LISTBASE_FOREACH (
const CustomAssetLibraryDefinition *, library, &asset_libraries_->asset_libraries)
{
std::unique_ptr<DictionaryValue> library_dict = std::make_unique<DictionaryValue>();
DictionaryValue::Items &library_attributes = library_dict->elements();
library_attributes.append_as("name", new StringValue(library->name));
library_attributes.append_as("path", new StringValue(library->dirpath));
asset_libs_elements.append_as(std::move(library_dict));
}
root_attributes.append_as("asset_libraries", std::move(asset_libs_array));
}
}
return root;
}
static void write_settings_file(StringRef settings_filepath,
std::unique_ptr<serialize::DictionaryValue> dictionary)
{
using namespace serialize;
JsonFormatter formatter;
std::ofstream os;
os.open(settings_filepath, std::ios::out | std::ios::trunc);
formatter.serialize(os, *dictionary);
os.close();
}
/** \} */
/* ---------------------------------------------------------------------- */
std::unique_ptr<ProjectSettings> ProjectSettings::load_from_disk(StringRef project_path)
{
const std::string project_root_path = BlenderProject::project_path_to_native_project_root_path(
project_path);
if (!BLI_exists(project_root_path.c_str())) {
return nullptr;
}
if (!BlenderProject::path_is_project_root(project_root_path.c_str())) {
return nullptr;
}
const std::string settings_filepath = BlenderProject::project_root_path_to_settings_filepath(
project_root_path);
std::unique_ptr<serialize::Value> values = read_settings_file(settings_filepath);
std::unique_ptr<ExtractedSettings> extracted_settings = nullptr;
if (values) {
BLI_assert(values->as_dictionary_value() != nullptr);
extracted_settings = extract_settings(*values->as_dictionary_value());
}
std::unique_ptr loaded_settings = std::make_unique<ProjectSettings>();
if (extracted_settings) {
loaded_settings->project_name_ = extracted_settings->project_name;
/* Moves ownership. */
loaded_settings->asset_libraries_ = std::make_unique<CustomAssetLibraries>(
extracted_settings->asset_libraries);
}
return loaded_settings;
}
std::unique_ptr<ProjectSettings> ProjectSettings::load_from_path(StringRef path)
{
StringRef project_root = BlenderProject::project_root_path_find_from_path(path);
if (project_root.is_empty()) {
return nullptr;
}
return bke::ProjectSettings::load_from_disk(project_root);
}
bool ProjectSettings::save_to_disk(StringRef project_path)
{
const std::string project_root_path = BlenderProject::project_path_to_native_project_root_path(
project_path);
if (!BLI_exists(project_root_path.c_str())) {
return false;
}
if (!BlenderProject::path_is_project_root(project_root_path.c_str())) {
return false;
}
const std::string settings_filepath = BlenderProject::project_root_path_to_settings_filepath(
project_root_path);
std::unique_ptr settings_as_dict = to_dictionary();
write_settings_file(settings_filepath, std::move(settings_as_dict));
has_unsaved_changes_ = false;
return true;
}
/* ---------------------------------------------------------------------- */
ProjectSettings::ProjectSettings() : asset_libraries_(std::make_unique<CustomAssetLibraries>()) {}
ProjectSettings::~ProjectSettings() = default;
void ProjectSettings::project_name(StringRef new_name)
{
project_name_ = new_name;
has_unsaved_changes_ = true;
}
StringRefNull ProjectSettings::project_name() const
{
return project_name_;
}
const ListBase &ProjectSettings::asset_library_definitions() const
{
return asset_libraries_->asset_libraries;
}
ListBase &ProjectSettings::asset_library_definitions()
{
return asset_libraries_->asset_libraries;
}
void ProjectSettings::tag_has_unsaved_changes()
{
has_unsaved_changes_ = true;
}
bool ProjectSettings::has_unsaved_changes() const
{
return has_unsaved_changes_;
}
} // namespace blender::bke

View File

@ -0,0 +1,329 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_appdir.h"
#include "BKE_blender_project.hh"
#include "BKE_main.hh"
#include "BLI_fileops.h"
#include "BLI_function_ref.hh"
#include "BLI_path_util.h"
#include "BLI_vector.hh"
#include "BLO_readfile.h"
#include "DNA_asset_types.h"
#include "blendfile_loading_base_test.h"
#include "testing/testing.h"
namespace blender::bke::tests {
struct SVNFiles {
const std::string svn_root = blender::tests::flags_test_asset_dir();
const std::string project_root_rel = "blender_project/the_project";
const std::string project_root = svn_root + "/blender_project/the_project";
};
class ProjectTest : public testing::Test {
/* RAII helper to delete the created directories reliably after testing or on errors. */
struct ProjectDirectoryRAIIWrapper {
std::string project_path_;
/* Path with OS preferred slashes ('/' on Unix, '\' on Windows). Important for some file
* operations. */
std::string native_project_path_;
std::string base_path_;
ProjectDirectoryRAIIWrapper(StringRefNull base_path, StringRefNull relative_project_path)
{
BLI_assert_msg(ELEM(base_path.back(), SEP, ALTSEP),
"Expected base_path to have trailing slash");
std::string project_path = base_path + relative_project_path;
native_project_path_ = project_path;
BLI_path_slash_native(native_project_path_.data());
if (native_project_path_.back() != SEP) {
native_project_path_ += SEP;
}
/** Assert would be preferable but that would only run in debug builds, and #ASSERT_TRUE()
* doesn't support printing a message. */
if (BLI_exists(native_project_path_.c_str())) {
throw std::runtime_error("Can't execute test, temporary path '" + project_path +
"' already exists");
}
BLI_dir_create_recursive(native_project_path_.c_str());
if (!BLI_exists(native_project_path_.c_str())) {
throw std::runtime_error("Can't execute test, failed to create path '" +
native_project_path_ + "'");
}
base_path_ = base_path;
project_path_ = project_path;
BLI_assert(StringRef{&project_path_[base_path.size()]} == relative_project_path);
}
~ProjectDirectoryRAIIWrapper()
{
if (!project_path_.empty()) {
/* Cut the path off at the first slash after the base path, so we delete the directory
* created for the test. */
const size_t first_slash_pos = native_project_path_.find_first_of(SEP, base_path_.size());
std::string path_to_delete = native_project_path_;
if (first_slash_pos != std::string::npos) {
path_to_delete.erase(first_slash_pos);
}
BLI_delete(path_to_delete.c_str(), true, true);
BLI_assert(!BLI_exists(native_project_path_.c_str()));
}
}
};
public:
/* Run the test on multiple paths or variations of the same path. Useful to test things like
* unicode paths, with or without trailing slash, non-native slashes, etc. The callback gets both
* the unmodified path (possibly with non-native slashes), and the path converted to native
* slashes passed. Call functions under test with the former, and use the latter to check the
* results with BLI_fileops.h functions */
void test_foreach_project_path(FunctionRef<void(StringRefNull /* project_path */,
StringRefNull /* project_path_native */)> fn)
{
const Vector<StringRefNull> subpaths = {
"temporary-project-root",
"test-temporary-unicode-dir-новый/temporary-project-root",
/* Same but with trailing slash. */
"test-temporary-unicode-dir-новый/temporary-project-root/",
/* Windows style slash. */
"test-temporary-unicode-dir-новый\\temporary-project-root",
/* Windows style trailing slash. */
"test-temporary-unicode-dir-новый\\temporary-project-root\\",
};
BKE_tempdir_init("");
const std::string tempdir = BKE_tempdir_session();
for (StringRefNull subpath : subpaths) {
ProjectDirectoryRAIIWrapper temp_project_path(tempdir, subpath);
fn(temp_project_path.project_path_, temp_project_path.native_project_path_);
}
}
void TearDown() override
{
bke::BlenderProject::set_active(nullptr);
}
};
TEST_F(ProjectTest, settings_create)
{
test_foreach_project_path([](StringRefNull project_path, StringRefNull project_path_native) {
if (!BlenderProject::create_settings_directory(project_path)) {
/* Not a regular test failure, this may fail if there is a permission issue for example. */
FAIL() << "Failed to create project directory in '" << project_path
<< "', check permissions";
}
std::string project_settings_dir = project_path_native + SEP_STR +
BlenderProject::SETTINGS_DIRNAME;
EXPECT_TRUE(BLI_exists(project_settings_dir.c_str()) &&
BLI_is_dir(project_settings_dir.c_str()))
<< project_settings_dir + " was not created";
});
}
/* Load the project by pointing to the project root directory (as opposed to the .blender_project
* directory). */
TEST_F(ProjectTest, load_from_project_root_path)
{
test_foreach_project_path([](StringRefNull project_path, StringRefNull project_path_native) {
BlenderProject::create_settings_directory(project_path);
std::unique_ptr project = BlenderProject::load_from_path(project_path);
EXPECT_NE(project, nullptr);
EXPECT_EQ(project->root_path(), project_path_native);
EXPECT_EQ(project->get_settings().project_name(), "");
});
}
/* Load the project by pointing to the .blender_project directory (as opposed to the project root
* directory). */
TEST_F(ProjectTest, load_from_project_settings_path)
{
test_foreach_project_path([](StringRefNull project_path, StringRefNull project_path_native) {
BlenderProject::create_settings_directory(project_path);
std::unique_ptr project = BlenderProject::load_from_path(
project_path + (ELEM(project_path.back(), SEP, ALTSEP) ? "" : SEP_STR) +
BlenderProject::SETTINGS_DIRNAME);
EXPECT_NE(project, nullptr);
EXPECT_EQ(project->root_path(), project_path_native);
EXPECT_EQ(project->get_settings().project_name(), "");
});
}
static void project_settings_match_expected_from_svn(const ProjectSettings &project_settings)
{
EXPECT_EQ(project_settings.project_name(), "Ružena");
const ListBase &asset_libraries = project_settings.asset_library_definitions();
CustomAssetLibraryDefinition *first = (CustomAssetLibraryDefinition *)asset_libraries.first;
EXPECT_STREQ(first->name, "Lorem Ipsum");
EXPECT_STREQ(first->dirpath, "assets");
EXPECT_EQ(first->next, asset_libraries.last);
CustomAssetLibraryDefinition *last = (CustomAssetLibraryDefinition *)asset_libraries.last;
EXPECT_EQ(last->prev, asset_libraries.first);
EXPECT_STREQ(last->name, "Материалы");
EXPECT_STREQ(last->dirpath, "новый\\assets");
}
TEST_F(ProjectTest, settings_json_read)
{
SVNFiles svn_files{};
std::unique_ptr from_project_settings = ProjectSettings::load_from_disk(svn_files.project_root);
EXPECT_NE(from_project_settings, nullptr);
project_settings_match_expected_from_svn(*from_project_settings);
}
TEST_F(ProjectTest, settings_json_write)
{
SVNFiles svn_files{};
std::unique_ptr from_project_settings = ProjectSettings::load_from_disk(svn_files.project_root);
/* Take the settings read from the SVN files and write it to /tmp/ projects. */
test_foreach_project_path(
[&from_project_settings](StringRefNull to_project_path, StringRefNull) {
BlenderProject::create_settings_directory(to_project_path);
if (!from_project_settings->save_to_disk(to_project_path)) {
FAIL();
}
/* Now check if the settings written to disk match the expectations. */
std::unique_ptr written_settings = ProjectSettings::load_from_disk(to_project_path);
EXPECT_NE(written_settings, nullptr);
project_settings_match_expected_from_svn(*written_settings);
});
}
TEST_F(ProjectTest, settings_read_change_write)
{
SVNFiles svn_files{};
std::unique_ptr from_project_settings = ProjectSettings::load_from_disk(svn_files.project_root);
EXPECT_FALSE(from_project_settings->has_unsaved_changes());
/* Take the settings read from the SVN files and write it to /tmp/ projects. */
test_foreach_project_path(
[&from_project_settings](StringRefNull to_project_path, StringRefNull) {
BlenderProject::create_settings_directory(to_project_path);
from_project_settings->project_name("новый");
EXPECT_TRUE(from_project_settings->has_unsaved_changes());
if (!from_project_settings->save_to_disk(to_project_path)) {
FAIL();
}
EXPECT_FALSE(from_project_settings->has_unsaved_changes());
/* Now check if the settings written to disk match the expectations. */
std::unique_ptr written_settings = ProjectSettings::load_from_disk(to_project_path);
EXPECT_NE(written_settings, nullptr);
EXPECT_EQ(written_settings->project_name(), "новый");
EXPECT_FALSE(from_project_settings->has_unsaved_changes());
});
}
TEST_F(ProjectTest, project_root_path_find_from_path)
{
/* Test the temporarily created directories with their various path formats. */
test_foreach_project_path([](StringRefNull project_path, StringRefNull /*project_path_native*/) {
/* First test without a .blender_project directory present. */
EXPECT_EQ(BlenderProject::project_root_path_find_from_path(project_path), "");
BlenderProject::create_settings_directory(project_path);
EXPECT_EQ(BlenderProject::project_root_path_find_from_path(project_path), project_path);
});
SVNFiles svn_files{};
/* Test the prepared project directory from the libs SVN repository. */
EXPECT_EQ(BlenderProject::project_root_path_find_from_path(svn_files.project_root +
"/some_project_file.blend"),
svn_files.project_root);
EXPECT_EQ(BlenderProject::project_root_path_find_from_path(
svn_files.project_root +
"/unicode_subdirectory_новый/another_subdirectory/some_nested_project_file.blend"),
svn_files.project_root);
}
class BlendfileProjectLoadingTest : public BlendfileLoadingBaseTest {
};
/* Test if loading the blend file loads the project data as expected. */
TEST_F(BlendfileProjectLoadingTest, load_blend_file)
{
EXPECT_EQ(bke::BlenderProject::get_active(), nullptr);
if (!blendfile_load("blender_project/the_project/some_project_file.blend")) {
FAIL();
}
bke::BlenderProject *svn_project = bke::BlenderProject::load_active_from_path(
bfile->main->filepath);
EXPECT_NE(svn_project, nullptr);
EXPECT_EQ(bke::BlenderProject::get_active(), svn_project);
EXPECT_STREQ("Ružena", svn_project->project_name().c_str());
/* Note: The project above will be freed once a different active project is set. So get the path
* for future comparisons. */
StringRefNull svn_project_path = svn_project->root_path();
blendfile_free();
/* Check if loading a different .blend updates the project properly */
if (!blendfile_load("blender_project/the_project/unicode_subdirectory_новый/"
"another_subdirectory/some_nested_project_file.blend"))
{
FAIL();
}
bke::BlenderProject *svn_project_from_nested = bke::BlenderProject::load_active_from_path(
bfile->main->filepath);
EXPECT_NE(svn_project_from_nested, nullptr);
EXPECT_EQ(bke::BlenderProject::get_active(), svn_project_from_nested);
EXPECT_STREQ(svn_project_path.c_str(), svn_project_from_nested->root_path().c_str());
EXPECT_STREQ("Ružena", svn_project_from_nested->project_name().c_str());
blendfile_free();
/* Check if loading a .blend that's not in the project unsets the project properly. */
if (!blendfile_load("blender_project/not_a_project_file.blend")) {
FAIL();
}
bke::BlenderProject::load_active_from_path(bfile->main->filepath);
EXPECT_EQ(bke::BlenderProject::get_active(), nullptr);
}
TEST_F(ProjectTest, project_load_and_delete)
{
test_foreach_project_path([](StringRefNull project_path, StringRefNull project_path_native) {
BlenderProject::create_settings_directory(project_path);
bke::BlenderProject *project = bke::BlenderProject::load_active_from_path(project_path);
EXPECT_NE(project, nullptr);
EXPECT_FALSE(project->has_unsaved_changes());
std::string project_settings_dir = project_path_native + SEP_STR +
BlenderProject::SETTINGS_DIRNAME;
EXPECT_TRUE(BLI_exists(project_settings_dir.c_str()));
if (!project->delete_settings_directory()) {
FAIL();
}
/* Runtime project should still exist, but with unsaved changes. */
EXPECT_NE(project, nullptr);
EXPECT_TRUE(project->has_unsaved_changes());
EXPECT_FALSE(BLI_exists(project_settings_dir.c_str()));
});
}
} // namespace blender::bke::tests

View File

@ -33,6 +33,7 @@
#include "BKE_addon.h"
#include "BKE_appdir.h"
#include "BKE_blender.h"
#include "BKE_blender_project.hh"
#include "BKE_blender_version.h"
#include "BKE_blendfile.h"
#include "BKE_bpath.h"
@ -972,6 +973,16 @@ static void setup_app_blend_file_data(bContext *C,
}
}
static void setup_app_project_data(BlendFileData *bfd, const struct BlendFileReadParams *params)
{
if (!U.experimental.use_blender_projects) {
return;
}
if ((params->skip_flags & BLO_READ_SKIP_DATA) == 0 && (params->undo_direction == STEP_INVALID)) {
blender::bke::BlenderProject::load_active_from_path(bfd->main->filepath);
}
}
static void handle_subversion_warning(Main *main, BlendFileReadReport *reports)
{
if (main->versionfile > BLENDER_FILE_VERSION || (main->versionfile == BLENDER_FILE_VERSION &&
@ -1006,6 +1017,7 @@ void BKE_blendfile_read_setup_readfile(bContext *C,
BLO_update_defaults_startup_blend(bfd->main, startup_app_template);
}
}
setup_app_project_data(bfd, params);
setup_app_blend_file_data(C, bfd, params, wm_setup_data, reports);
BLO_blendfiledata_free(bfd);
}
@ -1226,7 +1238,7 @@ UserDef *BKE_blendfile_userdef_from_defaults()
/* Default studio light. */
BKE_studiolight_default(userdef->light_param, userdef->light_ambient);
BKE_preferences_asset_library_default_add(userdef);
BKE_preferences_custom_asset_library_default_add(userdef);
return userdef;
}

View File

@ -32,6 +32,7 @@
#include "BLT_translation.h"
#include "BKE_blender_project.hh"
#include "BKE_context.hh"
#include "BKE_layer.h"
#include "BKE_main.hh"
@ -772,6 +773,11 @@ ReportList *CTX_wm_reports(const bContext *C)
return nullptr;
}
blender::bke::BlenderProject *CTX_wm_project()
{
return blender::bke::BlenderProject::get_active();
}
View3D *CTX_wm_view3d(const bContext *C)
{
ScrArea *area = CTX_wm_area(C);
@ -938,6 +944,15 @@ SpaceSpreadsheet *CTX_wm_space_spreadsheet(const bContext *C)
return nullptr;
}
SpaceProjectSettings *CTX_wm_space_project_settings(const bContext *C)
{
ScrArea *area = CTX_wm_area(C);
if (area && area->spacetype == SPACE_PROJECT_SETTINGS) {
return static_cast<SpaceProjectSettings *>(area->spacedata.first);
}
return nullptr;
}
void CTX_wm_manager_set(bContext *C, wmWindowManager *wm)
{
C->wm.manager = wm;

View File

@ -4,17 +4,8 @@
/** \file
* \ingroup bke
*
* User defined asset library API.
*/
#include <cstring>
#include "DNA_asset_types.h"
#include "MEM_guardedalloc.h"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
@ -22,10 +13,12 @@
#include "BLI_string_utils.hh"
#include "BKE_appdir.h"
#include "BKE_asset_library_custom.h"
#include "BKE_preferences.h"
#include "BLT_translation.h"
#include "DNA_asset_types.h"
#include "DNA_defaults.h"
#include "DNA_userdef_types.h"
@ -35,80 +28,7 @@
/** \name Asset Libraries
* \{ */
bUserAssetLibrary *BKE_preferences_asset_library_add(UserDef *userdef,
const char *name,
const char *dirpath)
{
bUserAssetLibrary *library = DNA_struct_default_alloc(bUserAssetLibrary);
BLI_addtail(&userdef->asset_libraries, library);
if (name) {
BKE_preferences_asset_library_name_set(userdef, library, name);
}
if (dirpath) {
STRNCPY(library->dirpath, dirpath);
}
return library;
}
void BKE_preferences_asset_library_remove(UserDef *userdef, bUserAssetLibrary *library)
{
BLI_freelinkN(&userdef->asset_libraries, library);
}
void BKE_preferences_asset_library_name_set(UserDef *userdef,
bUserAssetLibrary *library,
const char *name)
{
STRNCPY_UTF8(library->name, name);
BLI_uniquename(&userdef->asset_libraries,
library,
name,
'.',
offsetof(bUserAssetLibrary, name),
sizeof(library->name));
}
void BKE_preferences_asset_library_path_set(bUserAssetLibrary *library, const char *path)
{
STRNCPY(library->dirpath, path);
if (BLI_is_file(library->dirpath)) {
BLI_path_parent_dir(library->dirpath);
}
}
bUserAssetLibrary *BKE_preferences_asset_library_find_index(const UserDef *userdef, int index)
{
return static_cast<bUserAssetLibrary *>(BLI_findlink(&userdef->asset_libraries, index));
}
bUserAssetLibrary *BKE_preferences_asset_library_find_by_name(const UserDef *userdef,
const char *name)
{
return static_cast<bUserAssetLibrary *>(
BLI_findstring(&userdef->asset_libraries, name, offsetof(bUserAssetLibrary, name)));
}
bUserAssetLibrary *BKE_preferences_asset_library_containing_path(const UserDef *userdef,
const char *path)
{
LISTBASE_FOREACH (bUserAssetLibrary *, asset_lib_pref, &userdef->asset_libraries) {
if (BLI_path_contains(asset_lib_pref->dirpath, path)) {
return asset_lib_pref;
}
}
return nullptr;
}
int BKE_preferences_asset_library_get_index(const UserDef *userdef,
const bUserAssetLibrary *library)
{
return BLI_findindex(&userdef->asset_libraries, library);
}
void BKE_preferences_asset_library_default_add(UserDef *userdef)
void BKE_preferences_custom_asset_library_default_add(UserDef *userdef)
{
char documents_path[FILE_MAXDIR];
@ -117,8 +37,8 @@ void BKE_preferences_asset_library_default_add(UserDef *userdef)
return;
}
bUserAssetLibrary *library = BKE_preferences_asset_library_add(
userdef, DATA_(BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME), nullptr);
CustomAssetLibraryDefinition *library = BKE_asset_library_custom_add(
&userdef->asset_libraries, DATA_(BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME), nullptr);
/* Add new "Default" library under '[doc_path]/Blender/Assets'. */
BLI_path_join(

View File

@ -27,6 +27,7 @@
#include "DNA_windowmanager_types.h"
#include "BKE_addon.h"
#include "BKE_asset_library_custom.h"
#include "BKE_blender_version.h"
#include "BKE_colorband.h"
#include "BKE_idprop.h"
@ -151,6 +152,7 @@ static void do_versions_theme(const UserDef *userdef, bTheme *btheme)
*/
{
/* Keep this block, even when empty. */
btheme->space_project_settings = btheme->space_preferences;
}
#undef FROM_DEFAULT_V4_UCHAR
@ -728,7 +730,7 @@ void blo_do_versions_userdef(UserDef *userdef)
if (!USER_VERSION_ATLEAST(292, 9)) {
if (BLI_listbase_is_empty(&userdef->asset_libraries)) {
BKE_preferences_asset_library_default_add(userdef);
BKE_preferences_custom_asset_library_default_add(userdef);
}
}
@ -781,11 +783,11 @@ void blo_do_versions_userdef(UserDef *userdef)
* since it doesn't handle translations and ignores user changes. But this was an alpha build
* (experimental) feature and the name is just for display in the UI anyway. So it doesn't have
* to work perfectly at all. */
LISTBASE_FOREACH (bUserAssetLibrary *, asset_library, &userdef->asset_libraries) {
LISTBASE_FOREACH (CustomAssetLibraryDefinition *, asset_library, &userdef->asset_libraries) {
/* Ignores translations, since that would depend on the current preferences (global `U`). */
if (STREQ(asset_library->name, "Default")) {
BKE_preferences_asset_library_name_set(
userdef, asset_library, BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME);
BKE_asset_library_custom_name_set(
&userdef->asset_libraries, asset_library, BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME);
}
}
}
@ -823,7 +825,7 @@ void blo_do_versions_userdef(UserDef *userdef)
}
if (!USER_VERSION_ATLEAST(305, 10)) {
LISTBASE_FOREACH (bUserAssetLibrary *, asset_library, &userdef->asset_libraries) {
LISTBASE_FOREACH (CustomAssetLibraryDefinition *, asset_library, &userdef->asset_libraries) {
asset_library->import_method = ASSET_IMPORT_APPEND_REUSE;
}
}
@ -851,7 +853,7 @@ void blo_do_versions_userdef(UserDef *userdef)
}
if (!USER_VERSION_ATLEAST(306, 6)) {
LISTBASE_FOREACH (bUserAssetLibrary *, asset_library, &userdef->asset_libraries) {
LISTBASE_FOREACH (CustomAssetLibraryDefinition *, asset_library, &userdef->asset_libraries) {
asset_library->flag |= ASSET_LIBRARY_RELATIVE_PATH;
}
}

View File

@ -99,6 +99,7 @@
#include "MEM_guardedalloc.h" /* MEM_freeN */
#include "BKE_blender_project.hh"
#include "BKE_blender_version.h"
#include "BKE_bpath.h"
#include "BKE_global.h" /* For #Global `G`. */
@ -930,8 +931,10 @@ static void write_userdef(BlendWriter *writer, const UserDef *userdef)
BLO_write_struct(writer, bUserScriptDirectory, script_dir);
}
LISTBASE_FOREACH (const bUserAssetLibrary *, asset_library_ref, &userdef->asset_libraries) {
BLO_write_struct(writer, bUserAssetLibrary, asset_library_ref);
LISTBASE_FOREACH (
const CustomAssetLibraryDefinition *, asset_library_ref, &userdef->asset_libraries)
{
BLO_write_struct(writer, CustomAssetLibraryDefinition, asset_library_ref);
}
LISTBASE_FOREACH (const bUserExtensionRepo *, repo_ref, &userdef->extension_repos) {
@ -1585,6 +1588,11 @@ static bool BLO_write_file_impl(Main *mainvar,
}
}
/* Update active project information based on the new file location. */
if (U.experimental.use_blender_projects) {
blender::bke::BlenderProject::load_active_from_path(filepath);
}
/* Actual file writing. */
const bool err = write_file_handle(
mainvar, &ww, nullptr, nullptr, write_flags, use_userdef, thumb);

View File

@ -28,6 +28,7 @@ if(WITH_BLENDER)
add_subdirectory(metaball)
add_subdirectory(object)
add_subdirectory(physics)
add_subdirectory(project)
add_subdirectory(render)
add_subdirectory(scene)
add_subdirectory(sculpt_paint)
@ -44,6 +45,7 @@ if(WITH_BLENDER)
add_subdirectory(space_nla)
add_subdirectory(space_node)
add_subdirectory(space_outliner)
add_subdirectory(space_project_settings)
add_subdirectory(space_script)
add_subdirectory(space_sequencer)
add_subdirectory(space_spreadsheet)

View File

@ -39,6 +39,8 @@ AssetLibraryReference ED_asset_library_reference_from_enum_value(int value);
const struct EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(
bool include_generated);
struct CustomAssetLibraryDefinition *ED_asset_library_find_custom_library_from_reference(
const AssetLibraryReference *library_ref);
#ifdef __cplusplus
}
#endif

View File

@ -6,8 +6,15 @@
* \ingroup edasset
*/
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.hh"
#include "BKE_context.hh"
#include "DNA_userdef_types.h"
#include "BLI_hash.hh"
#include "ED_asset_library.h"
#include "asset_library_reference.hh"
namespace blender::ed::asset {
@ -20,14 +27,15 @@ AssetLibraryReferenceWrapper::AssetLibraryReferenceWrapper(const AssetLibraryRef
bool operator==(const AssetLibraryReferenceWrapper &a, const AssetLibraryReferenceWrapper &b)
{
return (a.type == b.type) &&
((a.type == ASSET_LIBRARY_CUSTOM) ? (a.custom_library_index == b.custom_library_index) :
true);
(ELEM(a.type, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, ASSET_LIBRARY_CUSTOM_FROM_PROJECT) ?
(a.custom_library_index == b.custom_library_index) :
true);
}
uint64_t AssetLibraryReferenceWrapper::hash() const
{
uint64_t hash1 = DefaultHash<decltype(type)>{}(type);
if (type != ASSET_LIBRARY_CUSTOM) {
if (!ELEM(type, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, ASSET_LIBRARY_CUSTOM_FROM_PROJECT)) {
return hash1;
}
@ -36,3 +44,26 @@ uint64_t AssetLibraryReferenceWrapper::hash() const
}
} // namespace blender::ed::asset
using namespace blender;
CustomAssetLibraryDefinition *ED_asset_library_find_custom_library_from_reference(
const AssetLibraryReference *library_ref)
{
switch (library_ref->type) {
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
return BKE_asset_library_custom_find_index(&U.asset_libraries,
library_ref->custom_library_index);
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
if (bke::BlenderProject *project = CTX_wm_project()) {
return BKE_asset_library_custom_find_index(&project->asset_library_definitions(),
library_ref->custom_library_index);
}
break;
}
case ASSET_LIBRARY_LOCAL:
return nullptr;
}
return nullptr;
}

View File

@ -13,7 +13,9 @@
#include "BLI_listbase.h"
#include "BKE_preferences.h"
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.hh"
#include "BKE_context.hh"
#include "DNA_userdef_types.h"
@ -25,19 +27,19 @@
#include "ED_asset_library.h"
using namespace blender;
int ED_asset_library_reference_to_enum_value(const AssetLibraryReference *library)
{
/* Simple case: Predefined repository, just set the value. */
if (library->type < ASSET_LIBRARY_CUSTOM) {
/* Simple case: Predefined library, just set the value. */
if (library->type < ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES) {
return library->type;
}
/* Note that the path isn't checked for validity here. If an invalid library path is used, the
* Asset Browser can give a nice hint on what's wrong. */
const bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_index(
&U, library->custom_library_index);
if (user_library) {
return ASSET_LIBRARY_CUSTOM + library->custom_library_index;
if (ED_asset_library_find_custom_library_from_reference(library)) {
return library->type + library->custom_library_index;
}
return ASSET_LIBRARY_LOCAL;
@ -48,32 +50,67 @@ AssetLibraryReference ED_asset_library_reference_from_enum_value(int value)
AssetLibraryReference library;
/* Simple case: Predefined repository, just set the value. */
if (value < ASSET_LIBRARY_CUSTOM) {
if (value < ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES) {
library.type = value;
library.custom_library_index = -1;
BLI_assert(ELEM(value, ASSET_LIBRARY_ALL, ASSET_LIBRARY_LOCAL, ASSET_LIBRARY_ESSENTIALS));
return library;
}
const bUserAssetLibrary *user_library = BKE_preferences_asset_library_find_index(
&U, value - ASSET_LIBRARY_CUSTOM);
const eAssetLibraryType type = (value < ASSET_LIBRARY_CUSTOM_FROM_PROJECT) ?
ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES :
ASSET_LIBRARY_CUSTOM_FROM_PROJECT;
/* Note that there is no check if the path exists here. If an invalid library path is used, the
* Asset Browser can give a nice hint on what's wrong. */
if (!user_library) {
library.type = ASSET_LIBRARY_ALL;
library.custom_library_index = -1;
}
else {
const bool is_valid = (user_library->name[0] && user_library->dirpath[0]);
if (is_valid) {
library.custom_library_index = value - ASSET_LIBRARY_CUSTOM;
library.type = ASSET_LIBRARY_CUSTOM;
const CustomAssetLibraryDefinition *custom_library = nullptr;
library.type = type;
library.custom_library_index = value - type;
{
custom_library = ED_asset_library_find_custom_library_from_reference(&library);
/* Note that there is no check if the path exists here. If an invalid library path is used, the
* Asset Browser can give a nice hint on what's wrong. */
const bool is_valid = custom_library &&
(custom_library->name[0] && custom_library->dirpath[0]);
if (!is_valid) {
library.custom_library_index = -1;
}
}
return library;
}
static void add_custom_asset_library_enum_items(
const ListBase & /*CustomAssetLibraryDefinition*/ libraries,
const eAssetLibraryType library_type,
EnumPropertyItem **items,
int *totitem)
{
int i;
LISTBASE_FOREACH_INDEX (CustomAssetLibraryDefinition *, custom_library, &libraries, i) {
/* Note that the path itself isn't checked for validity here. If an invalid library path is
* used, the Asset Browser can give a nice hint on what's wrong. */
const bool is_valid = (custom_library->name[0] && custom_library->dirpath[0]);
if (!is_valid) {
continue;
}
AssetLibraryReference library_reference;
library_reference.type = library_type;
library_reference.custom_library_index = i;
const int enum_value = ED_asset_library_reference_to_enum_value(&library_reference);
/* Use library path as description, it's a nice hint for users. */
EnumPropertyItem tmp = {enum_value,
custom_library->name,
ICON_NONE,
custom_library->name,
custom_library->dirpath};
RNA_enum_item_add(items, totitem, &tmp);
}
}
const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(const bool include_generated)
{
EnumPropertyItem *item = nullptr;
@ -92,29 +129,19 @@ const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(const bool
RNA_enum_item_add(&item, &totitem, &rna_enum_asset_library_type_items[2]);
}
/* Add separator if needed. */
if (!BLI_listbase_is_empty(&U.asset_libraries)) {
bke::BlenderProject *project = CTX_wm_project();
if (project && !BLI_listbase_is_empty(&project->asset_library_definitions())) {
RNA_enum_item_add_separator(&item, &totitem);
add_custom_asset_library_enum_items(
project->asset_library_definitions(), ASSET_LIBRARY_CUSTOM_FROM_PROJECT, &item, &totitem);
}
int i;
LISTBASE_FOREACH_INDEX (bUserAssetLibrary *, user_library, &U.asset_libraries, i) {
/* Note that the path itself isn't checked for validity here. If an invalid library path is
* used, the Asset Browser can give a nice hint on what's wrong. */
const bool is_valid = (user_library->name[0] && user_library->dirpath[0]);
if (!is_valid) {
continue;
}
if (!BLI_listbase_is_empty(&U.asset_libraries)) {
RNA_enum_item_add_separator(&item, &totitem);
AssetLibraryReference library_reference;
library_reference.type = ASSET_LIBRARY_CUSTOM;
library_reference.custom_library_index = i;
const int enum_value = ED_asset_library_reference_to_enum_value(&library_reference);
/* Use library path as description, it's a nice hint for users. */
EnumPropertyItem tmp = {
enum_value, user_library->name, ICON_NONE, user_library->name, user_library->dirpath};
RNA_enum_item_add(&item, &totitem, &tmp);
add_custom_asset_library_enum_items(
U.asset_libraries, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, &item, &totitem);
}
RNA_enum_item_end(&item, &totitem);

View File

@ -17,10 +17,12 @@
#include "AS_asset_library.hh"
#include "BKE_blender_project.hh"
#include "BKE_context.hh"
#include "BKE_screen.hh"
#include "BLI_map.hh"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_utility_mixins.hh"
@ -35,6 +37,7 @@
#include "../space_file/filelist.hh"
#include "ED_asset_indexer.h"
#include "ED_asset_library.h"
#include "ED_asset_list.h"
#include "ED_asset_list.hh"
#include "ED_screen.hh"
@ -404,7 +407,9 @@ std::optional<eFileSelectType> AssetListStorage::asset_library_reference_to_file
case ASSET_LIBRARY_ALL:
return FILE_ASSET_LIBRARY_ALL;
case ASSET_LIBRARY_ESSENTIALS:
case ASSET_LIBRARY_CUSTOM:
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT:
case ASSET_LIBRARY_CUSTOM_PATH:
return FILE_ASSET_LIBRARY;
case ASSET_LIBRARY_LOCAL:
return FILE_MAIN_ASSET;

View File

@ -11,11 +11,11 @@
#include "AS_asset_representation.hh"
#include "BKE_asset.hh"
#include "BKE_asset_library_custom.h"
#include "BKE_bpath.h"
#include "BKE_context.hh"
#include "BKE_lib_id.h"
#include "BKE_main.hh"
#include "BKE_preferences.h"
#include "BKE_report.h"
#include "BLI_fileops.h" /* MSVC needs this for `PATH_MAX` */
@ -742,7 +742,7 @@ static void ASSET_OT_catalogs_save(wmOperatorType *ot)
/* -------------------------------------------------------------------- */
static bool could_be_asset_bundle(const Main *bmain);
static const bUserAssetLibrary *selected_asset_library(wmOperator *op);
static const CustomAssetLibraryDefinition *selected_asset_library(wmOperator *op);
static bool is_contained_in_selected_asset_library(wmOperator *op, const char *filepath);
static bool set_filepath_for_asset_lib(const Main *bmain, wmOperator *op);
static bool has_external_files(Main *bmain, ReportList *reports);
@ -764,8 +764,8 @@ static bool asset_bundle_install_poll(bContext *C)
}
/* Check whether this file is already located inside any asset library. */
const bUserAssetLibrary *asset_lib = BKE_preferences_asset_library_containing_path(
&U, bmain->filepath);
const CustomAssetLibraryDefinition *asset_lib = BKE_asset_library_custom_containing_path(
&U.asset_libraries, bmain->filepath);
if (asset_lib) {
return false;
}
@ -834,7 +834,7 @@ static int asset_bundle_install_exec(bContext *C, wmOperator *op)
return operator_result;
}
const bUserAssetLibrary *lib = selected_asset_library(op);
const CustomAssetLibraryDefinition *lib = selected_asset_library(op);
BLI_assert_msg(lib, "If the asset library is not known, how did we get here?");
BKE_reportf(op->reports,
RPT_INFO,
@ -894,18 +894,18 @@ static bool could_be_asset_bundle(const Main *bmain)
return fnmatch("*_bundle.blend", bmain->filepath, FNM_CASEFOLD) == 0;
}
static const bUserAssetLibrary *selected_asset_library(wmOperator *op)
static const CustomAssetLibraryDefinition *selected_asset_library(wmOperator *op)
{
const int enum_value = RNA_enum_get(op->ptr, "asset_library_reference");
const AssetLibraryReference lib_ref = ED_asset_library_reference_from_enum_value(enum_value);
const bUserAssetLibrary *lib = BKE_preferences_asset_library_find_index(
&U, lib_ref.custom_library_index);
const CustomAssetLibraryDefinition *lib = BKE_asset_library_custom_find_index(
&U.asset_libraries, lib_ref.custom_library_index);
return lib;
}
static bool is_contained_in_selected_asset_library(wmOperator *op, const char *filepath)
{
const bUserAssetLibrary *lib = selected_asset_library(op);
const CustomAssetLibraryDefinition *lib = selected_asset_library(op);
if (!lib) {
return false;
}
@ -919,7 +919,7 @@ static bool is_contained_in_selected_asset_library(wmOperator *op, const char *f
static bool set_filepath_for_asset_lib(const Main *bmain, wmOperator *op)
{
/* Find the directory path of the selected asset library. */
const bUserAssetLibrary *lib = selected_asset_library(op);
const CustomAssetLibraryDefinition *lib = selected_asset_library(op);
if (lib == nullptr) {
return false;
}

View File

@ -0,0 +1,36 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editors
*
* Editor level functions for Blender projects.
*/
#pragma once
struct Main;
struct ReportList;
namespace blender::bke {
class BlenderProject;
}
void ED_operatortypes_project(void);
/** Sets the project name to the directory name it is located in and registers a "Project Library"
* asset library pointing to `//assets/`. */
void ED_project_set_defaults(blender::bke::BlenderProject &project);
/**
* High level project creation (create project + load if needed + write default settings if
* needed).
*
* Initializes a new project in \a project_root_dir by creating the `.blender_project/` directory
* there. The new project will only be loaded if \a bmain represents a file within the project
* directory.
*
* \return True on success.
*/
bool ED_project_new(const struct Main *bmain,
const char *project_root_dir,
struct ReportList *reports);

View File

@ -26,6 +26,7 @@ struct Depsgraph;
struct IDProperty;
struct Main;
struct MenuType;
struct ReportList;
struct Scene;
struct SpaceLink;
struct WorkSpace;
@ -462,6 +463,9 @@ int ED_screen_animation_play(bContext *C, int sync, int mode);
bScreen *ED_screen_animation_playing(const wmWindowManager *wm);
bScreen *ED_screen_animation_no_scrub(const wmWindowManager *wm);
/** \return True on success. */
bool ED_project_settings_window_show(bContext *C, ReportList *reports);
/* screen keymaps */
/* called in spacetypes.cc */
void ED_operatortypes_screen();

View File

@ -43,6 +43,7 @@ void ED_spacetype_clip();
void ED_spacetype_statusbar();
void ED_spacetype_topbar();
void ED_spacetype_spreadsheet();
void ED_spacetype_project_settings();
/** \} */

View File

@ -687,7 +687,7 @@ static bool ui_rna_is_userdef(PointerRNA *ptr, PropertyRNA *prop)
&RNA_AddonPreferences,
&RNA_KeyConfigPreferences,
&RNA_KeyMapItem,
&RNA_UserAssetLibrary);
&RNA_CustomAssetLibraryDefinition);
}
bool UI_but_is_userdef(const uiBut *but)

View File

@ -648,6 +648,7 @@ static MenuSearch_Data *menu_items_from_ui_create(bContext *C,
SPACE_MENU_MAP(SPACE_NODE, "NODE_MT_editor_menus");
SPACE_MENU_MAP(SPACE_CONSOLE, "CONSOLE_MT_editor_menus");
SPACE_MENU_MAP(SPACE_USERPREF, "USERPREF_MT_editor_menus");
SPACE_MENU_MAP(SPACE_PROJECT_SETTINGS, "PROJECTSETTINGS_MT_editor_menus");
SPACE_MENU_MAP(SPACE_CLIP,
(((const SpaceClip *)sl)->mode == SC_MODE_TRACKING) ?
"CLIP_MT_tracking_editor_menus" :

View File

@ -123,6 +123,9 @@ const uchar *UI_ThemeGetColorPtr(bTheme *btheme, int spacetype, int colorid)
case SPACE_USERPREF:
ts = &btheme->space_preferences;
break;
case SPACE_PROJECT_SETTINGS:
ts = &btheme->space_project_settings;
break;
case SPACE_CONSOLE:
ts = &btheme->space_console;
break;

View File

@ -0,0 +1,29 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
.
../include
../../blenkernel
../../blenlib
../../blentranslation
../../makesdna
../../makesrna
../../windowmanager
../../../../intern/guardedalloc
)
set(INC_SYS
)
set(SRC
project.cc
project_ops.cc
)
set(LIB
bf_blenkernel
)
blender_add_lib(bf_editor_project "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")

View File

@ -0,0 +1,83 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edproject
*/
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLT_translation.h"
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.hh"
#include "BKE_context.hh"
#include "BKE_main.hh"
#include "BKE_report.h"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_project.hh"
using namespace blender;
/** Name of the asset library added by default. Needs translation with `DATA_()`. */
inline static const char *DEFAULT_ASSET_LIBRARY_NAME = N_("Project Library");
inline static const char *DEFAULT_ASSET_LIBRARY_PATH = "//assets/";
void ED_project_set_defaults(bke::BlenderProject &project)
{
char project_root_dir[FILE_MAXDIR];
BLI_strncpy(project_root_dir, project.root_path().c_str(), sizeof(project_root_dir));
/* Set directory name as default project name. */
char dirname[FILE_MAXFILE];
BLI_path_slash_rstrip(project_root_dir);
BLI_path_split_file_part(project_root_dir, dirname, sizeof(dirname));
project.set_project_name(dirname);
ListBase &libraries = project.asset_library_definitions();
BKE_asset_library_custom_add(
&libraries, DATA_(DEFAULT_ASSET_LIBRARY_NAME), DEFAULT_ASSET_LIBRARY_PATH);
}
bool ED_project_new(const Main *bmain, const char *project_root_dir, ReportList *reports)
{
if (!bke::BlenderProject::create_settings_directory(project_root_dir)) {
BKE_reportf(reports,
RPT_ERROR,
"Failed to create project with unknown error. Is the directory read-only?");
return false;
}
std::unique_ptr<bke::BlenderProject> loaded_project = bke::BlenderProject::load_from_path(
project_root_dir);
/* Some default settings for the project. */
if (loaded_project) {
ED_project_set_defaults(*loaded_project);
/* Write defaults to the hard drive. */
loaded_project->save_settings();
}
BKE_reportf(reports, RPT_INFO, "Project created and loaded successfully");
const char *blend_path = BKE_main_blendfile_path(bmain);
const bool blend_is_in_project = blend_path[0] &&
BLI_path_contains(project_root_dir, blend_path);
if (blend_is_in_project) {
bke::BlenderProject::set_active(std::move(loaded_project));
}
else {
BKE_reportf(reports,
RPT_WARNING,
"The current file is not located inside of the new project. This means the new "
"project is not active");
}
return true;
}

View File

@ -0,0 +1,301 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edproject
*/
#include <climits>
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.hh"
#include "BKE_context.hh"
#include "BKE_main.hh"
#include "BKE_report.h"
#include "BLI_path_util.h"
#include "BLT_translation.h"
#include "DNA_space_types.h"
#include "RNA_access.hh"
#include "RNA_define.hh"
#include "WM_api.hh"
#include "WM_types.hh"
#include "ED_project.hh"
#include "ED_screen.hh"
using namespace blender;
static bool has_active_project_poll(bContext *C)
{
const bke::BlenderProject *active_project = CTX_wm_project();
CTX_wm_operator_poll_msg_set(C, TIP_("No active project loaded"));
return active_project != nullptr;
}
/* -------------------------------------------------------------------- */
/** \name New project operator
* \{ */
static bool new_project_poll(bContext *C)
{
if (!U.experimental.use_blender_projects) {
CTX_wm_operator_poll_msg_set(C, "Experimental project support is not enabled");
return false;
}
return true;
}
static int new_project_exec(bContext *C, wmOperator *op)
{
const Main *bmain = CTX_data_main(C);
if (!RNA_struct_property_is_set(op->ptr, "directory")) {
BKE_report(op->reports, RPT_ERROR, "No path defined for creating a new project in");
return OPERATOR_CANCELLED;
}
char project_root_dir[FILE_MAXDIR];
RNA_string_get(op->ptr, "directory", project_root_dir);
if (!ED_project_new(bmain, project_root_dir, op->reports)) {
return OPERATOR_CANCELLED;
}
PropertyRNA *prop_open_settings = RNA_struct_find_property(op->ptr, "open_settings_after");
if (RNA_property_is_set(op->ptr, prop_open_settings) &&
RNA_property_boolean_get(op->ptr, prop_open_settings))
{
ED_project_settings_window_show(C, op->reports);
}
WM_main_add_notifier(NC_PROJECT, nullptr);
/* Update the window title. */
WM_main_add_notifier(NC_WM | ND_DATACHANGED, nullptr);
return OPERATOR_FINISHED;
}
static int new_project_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
const Main *bmain = CTX_data_main(C);
const char *blendfile_path = BKE_main_blendfile_path(bmain);
if (blendfile_path[0]) {
/* Open at the .blend file location if any. */
RNA_string_set(op->ptr, "directory", blendfile_path);
}
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
static void PROJECT_OT_new(wmOperatorType *ot)
{
ot->name = "New Project";
ot->idname = "PROJECT_OT_new";
ot->description = "Choose a directory to use as the root of a project";
ot->invoke = new_project_invoke;
ot->exec = new_project_exec;
/* omit window poll so this can work in background mode */
ot->poll = new_project_poll;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_BLENDER,
FILE_OPENFILE,
WM_FILESEL_DIRECTORY,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
PropertyRNA *prop;
prop = RNA_def_boolean(ot->srna,
"open_settings_after",
false,
"",
"Open the project settings window after successfully creating a project");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Write Project Settings Operator
* \{ */
static int save_settings_exec(bContext * /*C*/, wmOperator * /*op*/)
{
bke::BlenderProject *active_project = CTX_wm_project();
if (!active_project->save_settings()) {
return OPERATOR_CANCELLED;
}
return OPERATOR_FINISHED;
}
static void PROJECT_OT_save_settings(wmOperatorType *ot)
{
ot->name = "Save Project Settings";
ot->idname = "PROJECT_OT_save_settings";
ot->description = "Make the current changes to the project settings permanent";
ot->invoke = WM_operator_confirm;
ot->poll = has_active_project_poll;
ot->exec = save_settings_exec;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Delete project setup operator
* \{ */
static int delete_project_setup_exec(bContext *C, wmOperator *op)
{
bke::BlenderProject *project = CTX_wm_project();
if (!project->delete_settings_directory()) {
BKE_report(op->reports,
RPT_ERROR,
"Failed to delete project settings. Is the project directory read-only?");
return OPERATOR_CANCELLED;
}
bke::BlenderProject::set_active(nullptr);
WM_main_add_notifier(NC_PROJECT, nullptr);
/* Update the window title. */
WM_event_add_notifier_ex(CTX_wm_manager(C), CTX_wm_window(C), NC_WM | ND_DATACHANGED, nullptr);
return OPERATOR_FINISHED;
}
static void PROJECT_OT_delete_setup(wmOperatorType *ot)
{
ot->name = "Delete Project Setup";
ot->idname = "PROJECT_OT_delete_setup";
ot->description =
"Remove the configuration of the current project with all settings, but keep project files "
"(such as .blend files) untouched";
ot->invoke = WM_operator_confirm;
ot->exec = delete_project_setup_exec;
/* omit window poll so this can work in background mode */
ot->poll = has_active_project_poll;
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Add Custom Asset Library
* \{ */
static int custom_asset_library_add_exec(bContext * /*C*/, wmOperator *op)
{
bke::BlenderProject *project = CTX_wm_project();
char path[FILE_MAXDIR];
char dirname[FILE_MAXFILE];
RNA_string_get(op->ptr, "directory", path);
BLI_path_slash_rstrip(path);
/* Always keep project paths relative for now. Adds the "//" prefix which usually denotes a path
* that's relative to the current .blend, for now use it for project relative paths as well. */
BLI_path_rel(path, project->root_path().c_str());
BLI_path_split_file_part(path, dirname, sizeof(dirname));
/* nullptr is a valid directory path here. A library without path will be created then. */
BKE_asset_library_custom_add(&project->asset_library_definitions(), dirname, path);
project->tag_has_unsaved_changes();
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIBRARY, nullptr);
WM_main_add_notifier(NC_PROJECT, nullptr);
return OPERATOR_FINISHED;
}
static int custom_asset_library_add_invoke(bContext *C, wmOperator *op, const wmEvent * /*event*/)
{
if (!RNA_struct_property_is_set(op->ptr, "directory")) {
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
return custom_asset_library_add_exec(C, op);
}
/* Similar to #PREFERENCES_OT_asset_library_add. */
static void PROJECT_OT_custom_asset_library_add(wmOperatorType *ot)
{
ot->name = "Add Asset Library";
ot->idname = "PROJECT_OT_custom_asset_library_add";
ot->description = "Register a directory to be used by the Asset Browser as source of assets";
ot->exec = custom_asset_library_add_exec;
ot->invoke = custom_asset_library_add_invoke;
ot->poll = has_active_project_poll;
ot->flag = OPTYPE_INTERNAL;
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER,
FILE_SPECIAL,
FILE_OPENFILE,
WM_FILESEL_DIRECTORY,
FILE_DEFAULTDISPLAY,
FILE_SORT_DEFAULT);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Remove Custom Asset Library
* \{ */
static int custom_asset_library_remove_exec(bContext * /*C*/, wmOperator *op)
{
const int index = RNA_int_get(op->ptr, "index");
bke::BlenderProject *project = CTX_wm_project();
ListBase &asset_libraries = project->asset_library_definitions();
CustomAssetLibraryDefinition *library = BKE_asset_library_custom_find_index(&asset_libraries,
index);
BKE_asset_library_custom_remove(&asset_libraries, library);
project->tag_has_unsaved_changes();
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIBRARY, nullptr);
WM_main_add_notifier(NC_PROJECT, nullptr);
return OPERATOR_FINISHED;
}
/* Similar to #PREFERENCES_OT_asset_library_remove. */
static void PROJECT_OT_custom_asset_library_remove(wmOperatorType *ot)
{
ot->name = "Remove Asset Library";
ot->idname = "PROJECT_OT_custom_asset_library_remove";
ot->description =
"Unregister a path to a .blend file, so the Asset Browser will not attempt to show it "
"anymore";
ot->exec = custom_asset_library_remove_exec;
ot->poll = has_active_project_poll;
ot->flag = OPTYPE_INTERNAL;
RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
}
/** \} */
void ED_operatortypes_project()
{
WM_operatortype_append(PROJECT_OT_new);
WM_operatortype_append(PROJECT_OT_save_settings);
WM_operatortype_append(PROJECT_OT_delete_setup);
WM_operatortype_append(PROJECT_OT_custom_asset_library_add);
WM_operatortype_append(PROJECT_OT_custom_asset_library_remove);
}

View File

@ -5197,7 +5197,14 @@ static void SCREEN_OT_back_to_previous(wmOperatorType *ot)
/** \name Show User Preferences Operator
* \{ */
static int userpref_show_exec(bContext *C, wmOperator *op)
/**
* Shared window opening logic for preferences and project settings.
* \return True on success.
*/
static bool settings_window_show(bContext *C,
eSpace_Type space_type,
const char *window_title,
ReportList *reports)
{
wmWindow *win_cur = CTX_wm_window(C);
/* Use eventstate, not event from _invoke, so this can be called through exec(). */
@ -5205,17 +5212,6 @@ static int userpref_show_exec(bContext *C, wmOperator *op)
int sizex = (500 + UI_NAVIGATION_REGION_WIDTH) * UI_SCALE_FAC;
int sizey = 520 * UI_SCALE_FAC;
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "section");
if (prop && RNA_property_is_set(op->ptr, prop)) {
/* Set active section via RNA, so it can fail properly. */
PointerRNA pref_ptr = RNA_pointer_create(nullptr, &RNA_Preferences, &U);
PropertyRNA *active_section_prop = RNA_struct_find_property(&pref_ptr, "active_section");
RNA_property_enum_set(&pref_ptr, active_section_prop, RNA_property_enum_get(op->ptr, prop));
RNA_property_update(C, &pref_ptr, active_section_prop);
}
const rcti window_rect = {
/*xmin*/ event->xy[0],
/*xmax*/ event->xy[0] + sizex,
@ -5225,9 +5221,9 @@ static int userpref_show_exec(bContext *C, wmOperator *op)
/* changes context! */
if (WM_window_open(C,
IFACE_("Blender Preferences"),
window_title,
&window_rect,
SPACE_USERPREF,
space_type,
false,
false,
true,
@ -5243,9 +5239,29 @@ static int userpref_show_exec(bContext *C, wmOperator *op)
region->flag |= RGN_FLAG_HIDDEN;
ED_region_visibility_change_update(C, area, region);
return true;
}
BKE_report(reports, RPT_ERROR, "Failed to open window!");
return false;
}
static int userpref_show_exec(bContext *C, wmOperator *op)
{
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "section");
if (prop && RNA_property_is_set(op->ptr, prop)) {
/* Set active section via RNA, so it can fail properly. */
PointerRNA pref_ptr = RNA_pointer_create(nullptr, &RNA_Preferences, &U);
PropertyRNA *active_section_prop = RNA_struct_find_property(&pref_ptr, "active_section");
RNA_property_enum_set(&pref_ptr, active_section_prop, RNA_property_enum_get(op->ptr, prop));
RNA_property_update(C, &pref_ptr, active_section_prop);
}
if (settings_window_show(C, SPACE_USERPREF, IFACE_("Blender Preferences"), op->reports)) {
return OPERATOR_FINISHED;
}
BKE_report(op->reports, RPT_ERROR, "Failed to open window!");
return OPERATOR_CANCELLED;
}
@ -5273,6 +5289,62 @@ static void SCREEN_OT_userpref_show(wmOperatorType *ot)
/** \} */
/* -------------------------------------------------------------------- */
/** \name Show Project Settings Operator
* \{ */
bool ED_project_settings_window_show(bContext *C, ReportList *reports)
{
return settings_window_show(
C, SPACE_PROJECT_SETTINGS, IFACE_("Blender Project Settings"), reports);
}
static int project_settings_show_exec(bContext *C, wmOperator *op)
{
if (!ED_project_settings_window_show(C, op->reports)) {
return OPERATOR_CANCELLED;
}
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "section");
SpaceProjectSettings *space_project = CTX_wm_space_project_settings(C);
if (space_project && prop && RNA_property_is_set(op->ptr, prop)) {
/* Set active section via RNA, so it can fail properly. */
bScreen *screen = CTX_wm_screen(C);
PointerRNA space_ptr = RNA_pointer_create(
&screen->id, &RNA_SpaceProjectSettings, space_project);
PropertyRNA *active_section_prop = RNA_struct_find_property(&space_ptr, "active_section");
RNA_property_enum_set(&space_ptr, active_section_prop, RNA_property_enum_get(op->ptr, prop));
RNA_property_update(C, &space_ptr, active_section_prop);
}
return OPERATOR_FINISHED;
}
static void SCREEN_OT_project_settings_show(struct wmOperatorType *ot)
{
/* identifiers */
ot->name = "Open Project Settings...";
ot->description = "Edit configuration for the active Blender project";
ot->idname = "SCREEN_OT_project_settings_show";
/* api callbacks */
ot->exec = project_settings_show_exec;
ot->poll = ED_operator_screenactive_nobackground; /* Not in background as this opens a window. */
PropertyRNA *prop;
prop = RNA_def_enum(ot->srna,
"section",
rna_enum_project_settings_section_items,
0,
"",
"Section to activate in the project settings");
RNA_def_property_flag(prop, PROP_HIDDEN);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Show Drivers Editor Operator
* \{ */
@ -5885,6 +5957,7 @@ void ED_operatortypes_screen()
WM_operatortype_append(SCREEN_OT_screenshot);
WM_operatortype_append(SCREEN_OT_screenshot_area);
WM_operatortype_append(SCREEN_OT_userpref_show);
WM_operatortype_append(SCREEN_OT_project_settings_show);
WM_operatortype_append(SCREEN_OT_drivers_editor_show);
WM_operatortype_append(SCREEN_OT_info_log_show);
WM_operatortype_append(SCREEN_OT_region_blend);

View File

@ -24,6 +24,7 @@ set(LIB
PRIVATE bf::blenlib
PRIVATE bf::dna
bf_editor_geometry
bf_editor_project
bf_editor_space_action
bf_editor_space_buttons
bf_editor_space_clip
@ -35,6 +36,7 @@ set(LIB
bf_editor_space_nla
bf_editor_space_node
bf_editor_space_outliner
bf_editor_space_project_settings
bf_editor_space_script
bf_editor_space_sequencer
bf_editor_space_spreadsheet

View File

@ -45,6 +45,7 @@
#include "ED_object.hh"
#include "ED_paint.hh"
#include "ED_physics.hh"
#include "ED_project.hh"
#include "ED_render.hh"
#include "ED_scene.hh"
#include "ED_screen.hh"
@ -84,9 +85,11 @@ void ED_spacetypes_init()
ED_spacetype_statusbar();
ED_spacetype_topbar();
ED_spacetype_spreadsheet();
ED_spacetype_project_settings();
/* Register operator types for screen and all spaces. */
ED_operatortypes_userpref();
ED_operatortypes_project();
ED_operatortypes_workspace();
ED_operatortypes_scene();
ED_operatortypes_screen();

View File

@ -1216,13 +1216,17 @@ static void file_draw_invalid_asset_library_hint(const bContext *C,
{
UI_icon_draw(sx, sy - UI_UNIT_Y, ICON_INFO);
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
const char *suggestion = TIP_(
"Asset Libraries are local directories that can contain .blend files with assets inside.\n"
"Manage Asset Libraries from the File Paths section in Preferences");
"Manage Asset Libraries from the File Paths section in the Preferences or in the Project "
"Settings.");
file_draw_string_multiline(
sx + UI_UNIT_X, sy, suggestion, width - UI_UNIT_X, line_height, text_col, nullptr, &sy);
uiBlock *block = UI_block_begin(C, region, __func__, UI_EMBOSS);
const short but_offset_y = line_height + UI_UNIT_Y * 1.2f;
const short but_width = UI_UNIT_X * 8;
uiBut *but = uiDefIconTextButO(block,
UI_BTYPE_BUT,
"SCREEN_OT_userpref_show",
@ -1230,13 +1234,26 @@ static void file_draw_invalid_asset_library_hint(const bContext *C,
ICON_PREFERENCES,
nullptr,
sx + UI_UNIT_X,
sy - line_height - UI_UNIT_Y * 1.2f,
UI_UNIT_X * 8,
sy - but_offset_y,
but_width,
UI_UNIT_Y,
nullptr);
PointerRNA *but_opptr = UI_but_operator_ptr_get(but);
RNA_enum_set(but_opptr, "section", USER_SECTION_FILE_PATHS);
but = uiDefButO(block,
UI_BTYPE_BUT,
"SCREEN_OT_project_settings_show",
WM_OP_INVOKE_DEFAULT,
nullptr,
sx + UI_UNIT_X + but_width + UI_UNIT_X,
sy - but_offset_y,
but_width,
UI_UNIT_Y,
nullptr);
but_opptr = UI_but_operator_ptr_get(but);
RNA_enum_set(but_opptr, "section", PROJECT_SETTINGS_SECTION_ASSET_LIBRARIES);
UI_block_end(C, block);
UI_block_draw(C, block);
}

View File

@ -49,6 +49,7 @@
#endif
#include "BKE_asset.hh"
#include "BKE_blender_project.hh"
#include "BKE_blendfile.h"
#include "BKE_context.hh"
#include "BKE_global.h"
@ -59,10 +60,12 @@
#include "BKE_main_idmap.hh"
#include "BKE_preferences.h"
#include "BKE_preview_image.hh"
#include "BLO_readfile.h"
#include "DNA_asset_types.h"
#include "DNA_space_types.h"
#include "ED_asset_library.h"
#include "ED_datafiles.h"
#include "ED_fileselect.hh"
#include "ED_screen.hh"
@ -1061,12 +1064,18 @@ static bool filelist_compare_asset_libraries(const AssetLibraryReference *librar
if (library_a->type != library_b->type) {
return false;
}
if (library_a->type == ASSET_LIBRARY_CUSTOM) {
const bool is_custom_library = ELEM(
library_a->type, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, ASSET_LIBRARY_CUSTOM_FROM_PROJECT);
if (is_custom_library) {
if (library_a->custom_library_index != library_b->custom_library_index) {
return false;
}
/* Don't only check the index, also check that it's valid. */
bUserAssetLibrary *library_ptr_a = BKE_preferences_asset_library_find_index(
&U, library_a->custom_library_index);
return (library_ptr_a != nullptr) &&
(library_a->custom_library_index == library_b->custom_library_index);
if (!ED_asset_library_find_custom_library_from_reference(library_a)) {
return false;
}
}
return true;
@ -1219,6 +1228,10 @@ static int filelist_geticon_ex(const FileList *filelist,
{
const eFileSel_File_Types typeflag = (eFileSel_File_Types)file->typeflag;
if ((typeflag & FILE_TYPE_DIR) && (typeflag & FILE_TYPE_BLENDER_PROJECT)) {
return ICON_FUND;
}
if ((typeflag & FILE_TYPE_DIR) &&
!(ignore_libdir && (typeflag & (FILE_TYPE_BLENDERLIB | FILE_TYPE_BLENDER))))
{
@ -3133,6 +3146,12 @@ static int filelist_readjob_list_dir(FileListReadJob *job_params,
}
}
if ((entry->typeflag & FILE_TYPE_DIR) && !(entry->typeflag & FILE_TYPE_BLENDER)) {
if (blender::bke::BlenderProject::path_is_project_root(target)) {
entry->typeflag |= FILE_TYPE_BLENDER_PROJECT;
}
}
#ifndef WIN32
/* Set linux-style dot files hidden too. */
if (is_hidden_dot_filename(entry->relpath, entry)) {

View File

@ -24,6 +24,7 @@
# include <unistd.h>
#endif
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "DNA_screen_types.h"
@ -42,13 +43,15 @@
#include "BLT_translation.h"
#include "BKE_appdir.h"
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.hh"
#include "BKE_context.hh"
#include "BKE_idtype.h"
#include "BKE_main.hh"
#include "BKE_preferences.h"
#include "BLF_api.h"
#include "ED_asset_library.h"
#include "ED_fileselect.hh"
#include "ED_screen.hh"
@ -418,34 +421,29 @@ static void fileselect_refresh_asset_params(FileAssetSelectParams *asset_params)
{
AssetLibraryReference *library = &asset_params->asset_library_ref;
FileSelectParams *base_params = &asset_params->base_params;
bUserAssetLibrary *user_library = nullptr;
/* Ensure valid repository, or fall-back to local one. */
if (library->type == ASSET_LIBRARY_CUSTOM) {
BLI_assert(library->custom_library_index >= 0);
user_library = BKE_preferences_asset_library_find_index(&U, library->custom_library_index);
if (!user_library) {
library->type = ASSET_LIBRARY_ALL;
}
/* Ensure valid asset library, or fall-back to local one. */
if (!ED_asset_library_find_custom_library_from_reference(library)) {
library->type = ASSET_LIBRARY_ALL;
}
std::string root_path = AS_asset_library_root_path_from_library_ref(*library);
STRNCPY(base_params->dir, root_path.c_str());
switch (eAssetLibraryType(library->type)) {
case ASSET_LIBRARY_ESSENTIALS:
STRNCPY(base_params->dir, blender::asset_system::essentials_directory_path().c_str());
base_params->type = FILE_ASSET_LIBRARY;
break;
case ASSET_LIBRARY_ALL:
base_params->dir[0] = '\0';
base_params->type = FILE_ASSET_LIBRARY_ALL;
break;
case ASSET_LIBRARY_LOCAL:
base_params->dir[0] = '\0';
base_params->type = FILE_MAIN_ASSET;
break;
case ASSET_LIBRARY_CUSTOM:
BLI_assert(user_library);
STRNCPY(base_params->dir, user_library->dirpath);
case ASSET_LIBRARY_CUSTOM_PATH:
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT:
base_params->type = FILE_ASSET_LIBRARY;
break;
}

View File

@ -258,10 +258,11 @@ static void gather_search_link_ops_for_all_assets(const bContext &C,
Vector<SocketLinkOperation> &search_link_ops)
{
int i;
LISTBASE_FOREACH_INDEX (const bUserAssetLibrary *, asset_library, &U.asset_libraries, i) {
LISTBASE_FOREACH_INDEX (
const CustomAssetLibraryDefinition *, asset_library, &U.asset_libraries, i) {
AssetLibraryReference library_ref{};
library_ref.custom_library_index = i;
library_ref.type = ASSET_LIBRARY_CUSTOM;
library_ref.type = ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES;
/* Skip local assets to avoid duplicates when the asset is part of the local file library. */
gather_search_link_ops_for_asset_library(
C, node_tree, socket, library_ref, true, search_link_ops);

View File

@ -0,0 +1,35 @@
# SPDX-FileCopyrightText: 2023 Blender Authors
#
# SPDX-License-Identifier: GPL-2.0-or-later
set(INC
../include
../../blenfont
../../blenkernel
../../blenlib
../../blenloader
../../blentranslation
../../depsgraph
../../makesdna
../../makesrna
../../windowmanager
../../../../intern/guardedalloc
# dna_type_offsets.h
${CMAKE_CURRENT_BINARY_DIR}/../../makesdna/intern
# RNA_prototypes.h
${CMAKE_BINARY_DIR}/source/blender/makesrna
)
set(SRC
space_project_settings.cc
)
set(LIB
)
blender_add_lib(bf_editor_space_project_settings "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
# RNA_prototypes.h dna_type_offsets.h
add_dependencies(bf_editor_space_project_settings bf_rna)
add_dependencies(bf_editor_space_project_settings bf_dna)

View File

@ -0,0 +1,253 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_screen.hh"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLO_read_write.hh"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "ED_screen.hh"
#include "ED_space_api.hh"
#include "MEM_guardedalloc.h"
#include "RNA_access.hh"
#include "RNA_enum_types.hh"
#include "UI_interface_c.hh"
using namespace blender;
static SpaceLink *project_settings_create(const ScrArea *area, const Scene * /*scene*/)
{
SpaceProjectSettings *project_settings_space = MEM_cnew<SpaceProjectSettings>(
"project settings space");
project_settings_space->spacetype = SPACE_PROJECT_SETTINGS;
{
/* Header. */
ARegion *region = MEM_cnew<ARegion>("project settings header");
BLI_addtail(&project_settings_space->regionbase, region);
region->regiontype = RGN_TYPE_HEADER;
/* Ignore preference "USER_HEADER_BOTTOM" here (always show bottom for new types). */
region->alignment = RGN_ALIGN_BOTTOM;
}
{
/* navigation region */
ARegion *region = MEM_cnew<ARegion>("project settings navigation region");
BLI_addtail(&project_settings_space->regionbase, region);
region->regiontype = RGN_TYPE_NAV_BAR;
region->alignment = RGN_ALIGN_LEFT;
/* Use smaller size when opened in area like properties editor (same as preferences do). */
if (area->winx && area->winx < 3.0f * UI_NAVIGATION_REGION_WIDTH * UI_SCALE_FAC) {
region->sizex = UI_NARROW_NAVIGATION_REGION_WIDTH;
}
}
{
/* execution region */
ARegion *region = MEM_cnew<ARegion>("project settings execution region");
BLI_addtail(&project_settings_space->regionbase, region);
region->regiontype = RGN_TYPE_EXECUTE;
region->alignment = RGN_ALIGN_BOTTOM | RGN_SPLIT_PREV;
region->flag |= RGN_FLAG_DYNAMIC_SIZE | RGN_FLAG_NO_USER_RESIZE;
}
{
/* Main window. */
ARegion *region = MEM_cnew<ARegion>("project settings main region");
BLI_addtail(&project_settings_space->regionbase, region);
region->regiontype = RGN_TYPE_WINDOW;
}
return reinterpret_cast<SpaceLink *>(project_settings_space);
}
static void project_settings_free(SpaceLink * /*sl*/) {}
static void project_settings_init(wmWindowManager * /*wm*/, ScrArea * /*area*/) {}
static SpaceLink *project_settings_duplicate(SpaceLink *sl)
{
const SpaceProjectSettings *sproject_settings_old = reinterpret_cast<SpaceProjectSettings *>(sl);
SpaceProjectSettings *sproject_settings_new = reinterpret_cast<SpaceProjectSettings *>(
MEM_dupallocN(sproject_settings_old));
return reinterpret_cast<SpaceLink *>(sproject_settings_new);
}
static void project_settings_listener(const wmSpaceTypeListenerParams *params)
{
const wmNotifier *wmn = params->notifier;
ScrArea *area = params->area;
switch (wmn->category) {
case NC_PROJECT:
ED_area_tag_redraw(area);
break;
}
}
static void project_settings_operatortypes() {}
static void project_settings_keymap(struct wmKeyConfig * /*keyconf*/) {}
static void project_settings_blend_write(BlendWriter *writer, SpaceLink *sl)
{
BLO_write_struct(writer, SpaceProjectSettings, sl);
}
/* add handlers, stuff you only do once or on area/region changes */
static void project_settings_main_region_init(wmWindowManager *wm, ARegion *region)
{
/* do not use here, the properties changed in user-preferences do a system-wide refresh,
* then scroller jumps back */
// region->v2d.flag &= ~V2D_IS_INIT;
region->v2d.scroll = V2D_SCROLL_RIGHT | V2D_SCROLL_VERTICAL_HIDE;
ED_region_panels_init(wm, region);
}
static void project_settings_main_region_layout(const bContext *C, ARegion *region)
{
SpaceProjectSettings *sproject_settings = CTX_wm_space_project_settings(C);
char id_lower[64];
const char *contexts[2] = {id_lower, nullptr};
if (!CTX_wm_project()) {
/* Special context for when there is no project. UI can draw a special panel then. */
STRNCPY(id_lower, "no_project");
}
else {
/* Avoid duplicating identifiers, use existing RNA enum. */
const EnumPropertyItem *items = rna_enum_project_settings_section_items;
int i = RNA_enum_from_value(items, sproject_settings->active_section);
/* Enum value not found: File is from the future. */
if (i == -1) {
i = 0;
}
StringRefNull id = items[i].identifier;
BLI_assert(id.size() < (int64_t)sizeof(id_lower));
STRNCPY(id_lower, id.c_str());
BLI_str_tolower_ascii(id_lower, strlen(id_lower));
}
ED_region_panels_layout_ex(C, region, &region->type->paneltypes, contexts, nullptr);
}
static void project_settings_main_region_listener(const wmRegionListenerParams * /*params*/) {}
static void project_settings_header_region_init(wmWindowManager * /*wm*/, ARegion *region)
{
ED_region_header_init(region);
}
static void project_settings_header_region_listener(const wmRegionListenerParams * /*params*/) {}
/* add handlers, stuff you only do once or on area/region changes */
static void project_settings_navigation_region_init(wmWindowManager *wm, ARegion *region)
{
region->v2d.scroll = V2D_SCROLL_RIGHT | V2D_SCROLL_VERTICAL_HIDE;
ED_region_panels_init(wm, region);
}
static void project_settings_navigation_region_draw(const bContext *C, ARegion *region)
{
ED_region_panels(C, region);
}
static void project_settings_navigation_region_listener(const wmRegionListenerParams * /*params*/)
{
}
/* add handlers, stuff you only do once or on area/region changes */
static void project_settings_execute_region_init(wmWindowManager *wm, ARegion *region)
{
ED_region_panels_init(wm, region);
region->v2d.keepzoom |= V2D_LOCKZOOM_X | V2D_LOCKZOOM_Y;
}
static bool project_settings_execute_region_poll(const RegionPollParams *params)
{
const ARegion *region_header = BKE_area_find_region_type(params->area, RGN_TYPE_HEADER);
return !region_header->visible;
}
static void project_settings_execute_region_listener(const wmRegionListenerParams * /*params*/) {}
void ED_spacetype_project_settings()
{
SpaceType *st = MEM_cnew<SpaceType>("spacetype project settings");
st->spaceid = SPACE_PROJECT_SETTINGS;
STRNCPY(st->name, "Project Settings");
st->create = project_settings_create;
st->free = project_settings_free;
st->init = project_settings_init;
st->duplicate = project_settings_duplicate;
st->listener = project_settings_listener;
st->operatortypes = project_settings_operatortypes;
st->keymap = project_settings_keymap;
st->blend_write = project_settings_blend_write;
ARegionType *art;
/* regions: main window */
art = MEM_cnew<ARegionType>("spacetype project settings region");
art->regionid = RGN_TYPE_WINDOW;
art->keymapflag = ED_KEYMAP_UI;
art->init = project_settings_main_region_init;
art->layout = project_settings_main_region_layout;
art->draw = ED_region_panels_draw;
art->listener = project_settings_main_region_listener;
BLI_addhead(&st->regiontypes, art);
/* regions: header */
art = MEM_cnew<ARegionType>("spacetype project settings header region");
art->regionid = RGN_TYPE_HEADER;
art->prefsizey = HEADERY;
art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_VIEW2D | ED_KEYMAP_HEADER;
art->listener = project_settings_header_region_listener;
art->init = project_settings_header_region_init;
art->draw = ED_region_header;
BLI_addhead(&st->regiontypes, art);
/* regions: navigation window */
art = MEM_cnew<ARegionType>("spacetype project settings navigation region");
art->regionid = RGN_TYPE_NAV_BAR;
art->prefsizex = UI_NAVIGATION_REGION_WIDTH;
art->keymapflag = ED_KEYMAP_UI | ED_KEYMAP_NAVBAR;
art->init = project_settings_navigation_region_init;
art->draw = project_settings_navigation_region_draw;
art->listener = project_settings_navigation_region_listener;
BLI_addhead(&st->regiontypes, art);
/* regions: execution window */
art = MEM_cnew<ARegionType>("spacetype project settings execute region");
art->regionid = RGN_TYPE_EXECUTE;
art->prefsizey = HEADERY;
art->keymapflag = ED_KEYMAP_UI;
art->init = project_settings_execute_region_init;
art->poll = project_settings_execute_region_poll;
art->layout = ED_region_panels_layout;
art->draw = ED_region_panels_draw;
art->listener = project_settings_execute_region_listener;
BLI_addhead(&st->regiontypes, art);
BKE_spacetype_register(st);
}

View File

@ -17,6 +17,7 @@
#endif
#include "BLI_path_util.h"
#include "BKE_asset_library_custom.h"
#include "BKE_callbacks.h"
#include "BKE_context.hh"
#include "BKE_main.hh"
@ -137,7 +138,8 @@ static int preferences_asset_library_add_exec(bContext * /*C*/, wmOperator *op)
BLI_path_split_file_part(path, dirname, sizeof(dirname));
/* nullptr is a valid directory path here. A library without path will be created then. */
const bUserAssetLibrary *new_library = BKE_preferences_asset_library_add(&U, dirname, path);
CustomAssetLibraryDefinition *new_library = BKE_asset_library_custom_add(
&U.asset_libraries, dirname, path);
/* Activate new library in the UI for further setup. */
U.active_asset_library = BLI_findindex(&U.asset_libraries, new_library);
U.runtime.is_dirty = true;
@ -161,6 +163,7 @@ static int preferences_asset_library_add_invoke(bContext *C,
return preferences_asset_library_add_exec(C, op);
}
/* Similar to #PROJECT_OT_custom_asset_library_add. */
static void PREFERENCES_OT_asset_library_add(wmOperatorType *ot)
{
ot->name = "Add Asset Library";
@ -199,24 +202,24 @@ static bool preferences_asset_library_remove_poll(bContext *C)
static int preferences_asset_library_remove_exec(bContext * /*C*/, wmOperator *op)
{
const int index = RNA_int_get(op->ptr, "index");
bUserAssetLibrary *library = static_cast<bUserAssetLibrary *>(
CustomAssetLibraryDefinition *library = static_cast<CustomAssetLibraryDefinition *>(
BLI_findlink(&U.asset_libraries, index));
if (!library) {
return OPERATOR_CANCELLED;
}
BKE_preferences_asset_library_remove(&U, library);
BKE_asset_library_custom_remove(&U.asset_libraries, library);
const int count_remaining = BLI_listbase_count(&U.asset_libraries);
/* Update active library index to be in range. */
CLAMP(U.active_asset_library, 0, count_remaining - 1);
U.runtime.is_dirty = true;
/* Trigger refresh for the Asset Browser. */
WM_main_add_notifier(NC_SPACE | ND_SPACE_ASSET_PARAMS, nullptr);
return OPERATOR_FINISHED;
}
/* Similar to #PROJECT_OT_custom_asset_library_add. */
static void PREFERENCES_OT_asset_library_remove(wmOperatorType *ot)
{
ot->name = "Remove Asset Library";

View File

@ -70,6 +70,7 @@ set(SRC
../include/ED_paint.hh
../include/ED_particle.hh
../include/ED_physics.hh
../include/ED_project.hh
../include/ED_render.hh
../include/ED_scene.hh
../include/ED_screen.hh

View File

@ -29,4 +29,16 @@
/** \} */
/* -------------------------------------------------------------------- */
/** \name CustomAssetLibraryDefinition Struct
* \{ */
#define _DNA_DEFAULT_CustomAssetLibraryDefinition \
{ \
.import_method = ASSET_IMPORT_APPEND_REUSE, \
.flag = ASSET_LIBRARY_RELATIVE_PATH, \
}
/** \} */
/* clang-format on */

View File

@ -4,6 +4,9 @@
/** \file
* \ingroup DNA
*
* Only contains types that need writing to files or that are accessed directly via RNA (as opposed
* to being opaque types accessed via an API).
*/
#pragma once
@ -104,13 +107,17 @@ typedef enum eAssetLibraryType {
ASSET_LIBRARY_ALL = 2,
/** Display assets bundled with Blender by default. */
ASSET_LIBRARY_ESSENTIALS = 3,
/** Not exposed to users or BPY. Just an internal type, mostly for unit testing the asset system
* independently of preferences or project settings. */
ASSET_LIBRARY_CUSTOM_PATH = 4,
/** Display assets from custom asset libraries, as defined in the preferences
* (#bUserAssetLibrary). The name will be taken from #FileSelectParams.asset_library_ref.idname
* then.
* In RNA, we add the index of the custom library to this to identify it by index. So keep
* this last! */
ASSET_LIBRARY_CUSTOM = 100,
* (#CustomAssetLibraryDefinition). In RNA, we add the index of the custom library to this to
* identify it by index. */
ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES = 100,
/** Same as #ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, but the library is defined for a specific
project only. */
ASSET_LIBRARY_CUSTOM_FROM_PROJECT = 500,
} eAssetLibraryType;
typedef enum eAssetImportMethod {
@ -129,23 +136,37 @@ typedef enum eAssetLibrary_Flag {
} eAssetLibrary_Flag;
/**
* Information to identify an asset library. May be either one of the predefined types (current
* 'Main', builtin library, project library), or a custom type as defined in the Preferences.
*
* If the type is set to #ASSET_LIBRARY_CUSTOM, `custom_library_index` must be set to identify the
* custom library. Otherwise it is not used.
* Information to identify an asset library. May be either one of the predefined types ("Current
* File" builtin library), or a custom type as defined in the Preferences/Project.
*/
typedef struct AssetLibraryReference {
/* If set to #ASSET_LIBRARY_CUSTOM_XXX, `custom_library_index` must be set to identify
* the custom library. Otherwise it is not used. */
short type; /* eAssetLibraryType */
char _pad1[2];
/**
* If showing a custom asset library (#ASSET_LIBRARY_CUSTOM), this is the index of the
* #bUserAssetLibrary within #UserDef.asset_libraries.
* If showing a custom asset library (#ASSET_LIBRARY_CUSTOM_XXX), this is the index of the
* #CustomAssetLibraryDefinition within its owner (preferences or project settings).
* Should be ignored otherwise (but better set to -1 then, for sanity and debugging).
*/
int custom_library_index;
} AssetLibraryReference;
/**
* Custom asset libraries can be registered in the preferences or project settings. This is the
* container to hold these settings.
*/
typedef struct CustomAssetLibraryDefinition {
struct CustomAssetLibraryDefinition *next, *prev;
char name[64]; /* MAX_NAME */
char dirpath[1024]; /* FILE_MAX */
short import_method; /* eAssetImportMethod */
short flag; /* eAssetLibrary_Flag */
char _pad[4];
} CustomAssetLibraryDefinition;
/**
* Information to refer to an asset (may be stored in files) on a "best effort" basis. It should
* work well enough for many common cases, but can break. For example when the location of the

View File

@ -1101,6 +1101,8 @@ typedef enum eFileSel_File_Types {
FILE_TYPE_VOLUME = (1 << 19),
FILE_TYPE_ASSET = (1 << 28),
/** Directory is a Blender project root directory. */
FILE_TYPE_BLENDER_PROJECT = (1 << 29),
/** An FS directory (i.e. S_ISDIR on its path is true). */
FILE_TYPE_DIR = (1 << 30),
FILE_TYPE_BLENDERLIB = (1u << 31),
@ -1767,6 +1769,30 @@ typedef struct SpaceUserPref {
/** \} */
/* -------------------------------------------------------------------- */
/** \name Project Settings
* \{ */
typedef struct SpaceProjectSettings {
SpaceLink *next, *prev;
/** Storage of regions for inactive spaces. */
ListBase regionbase;
char spacetype;
char link_flag;
char _pad0[6];
/* End 'SpaceLink' header. */
char active_section; /* eSpaceProjectSettings_Section */
char _pad1[7];
} SpaceProjectSettings;
typedef enum eSpaceProjectSettings_Section {
PROJECT_SETTINGS_SECTION_GENERAL = 0,
PROJECT_SETTINGS_SECTION_ASSET_LIBRARIES = 1,
} eSpaceProjectSettings_Section;
/** \} */
/* -------------------------------------------------------------------- */
/** \name Motion Tracking
* \{ */
@ -2117,9 +2143,10 @@ typedef enum eSpace_Type {
SPACE_CLIP = 20,
SPACE_TOPBAR = 21,
SPACE_STATUSBAR = 22,
SPACE_SPREADSHEET = 23
SPACE_SPREADSHEET = 23,
SPACE_PROJECT_SETTINGS = 24
#define SPACE_TYPE_NUM (SPACE_SPREADSHEET + 1)
#define SPACE_TYPE_NUM (SPACE_PROJECT_SETTINGS + 1)
} eSpace_Type;
/* use for function args */

View File

@ -8,23 +8,9 @@
#pragma once
#include "DNA_asset_types.h"
/* Struct members on own line. */
/* clang-format off */
/* -------------------------------------------------------------------- */
/** \name bUserAssetLibrary Struct
* \{ */
#define _DNA_DEFAULT_bUserAssetLibrary \
{ \
.import_method = ASSET_IMPORT_APPEND_REUSE, \
.flag = ASSET_LIBRARY_RELATIVE_PATH, \
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name bUserExtensionRepo Struct
* \{ */

View File

@ -511,6 +511,7 @@ typedef struct bTheme {
ThemeSpace space_outliner;
ThemeSpace space_node;
ThemeSpace space_preferences;
ThemeSpace space_project_settings;
ThemeSpace space_console;
ThemeSpace space_clip;
ThemeSpace space_topbar;
@ -599,17 +600,6 @@ enum {
USER_MENU_TYPE_PROP = 4,
};
typedef struct bUserAssetLibrary {
struct bUserAssetLibrary *next, *prev;
char name[64]; /* MAX_NAME */
char dirpath[1024]; /* FILE_MAX */
short import_method; /* eAssetImportMethod */
short flag; /* eAssetLibrary_Flag */
char _pad0[4];
} bUserAssetLibrary;
typedef struct bUserExtensionRepo {
struct bUserExtensionRepo *next, *prev;
/**
@ -711,8 +701,9 @@ typedef struct UserDef_Experimental {
char use_new_volume_nodes;
char use_shader_node_previews;
char use_extension_repos;
char use_blender_projects;
char _pad[3];
char _pad[2];
/** `makesdna` does not allow empty structs. */
} UserDef_Experimental;
@ -848,7 +839,7 @@ typedef struct UserDef {
ListBase script_directories; /* #bUserScriptDirectory */
/** #bUserMenu. */
struct ListBase user_menus;
/** #bUserAssetLibrary */
/** #CustomAssetLibraryDefinition */
struct ListBase asset_libraries;
/** #bUserExtensionRepo */
struct ListBase extension_repos;

View File

@ -142,6 +142,7 @@
/* DNA_asset_defaults.h */
SDNA_DEFAULT_DECL_STRUCT(AssetMetaData);
SDNA_DEFAULT_DECL_STRUCT(AssetLibraryReference);
SDNA_DEFAULT_DECL_STRUCT(CustomAssetLibraryDefinition);
/* DNA_armature_defaults.h */
SDNA_DEFAULT_DECL_STRUCT(bArmature);
@ -222,7 +223,6 @@ SDNA_DEFAULT_DECL_STRUCT(Speaker);
SDNA_DEFAULT_DECL_STRUCT(Tex);
/* DNA_userdef_types.h */
SDNA_DEFAULT_DECL_STRUCT(bUserAssetLibrary);
SDNA_DEFAULT_DECL_STRUCT(bUserExtensionRepo);
/* DNA_view3d_defaults.h */
@ -354,6 +354,7 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
/* DNA_asset_defaults.h */
SDNA_DEFAULT_DECL(AssetMetaData),
SDNA_DEFAULT_DECL(AssetLibraryReference),
SDNA_DEFAULT_DECL(CustomAssetLibraryDefinition),
/* DNA_armature_defaults.h */
SDNA_DEFAULT_DECL(bArmature),
@ -463,7 +464,6 @@ const void *DNA_default_table[SDNA_TYPE_MAX] = {
SDNA_DEFAULT_DECL_EX(UserDef_SpaceData, UserDef.space_data),
SDNA_DEFAULT_DECL_EX(UserDef_FileSpaceData, UserDef.file_space_data),
SDNA_DEFAULT_DECL_EX(WalkNavigation, UserDef.walk_navigation),
SDNA_DEFAULT_DECL(bUserAssetLibrary),
SDNA_DEFAULT_DECL(bUserExtensionRepo),
/* DNA_view3d_defaults.h */

View File

@ -47,6 +47,7 @@ DNA_STRUCT_RENAME(SeqRetimingHandle, SeqRetimingKey)
DNA_STRUCT_RENAME(SpaceButs, SpaceProperties)
DNA_STRUCT_RENAME(SpaceIpo, SpaceGraph)
DNA_STRUCT_RENAME(SpaceOops, SpaceOutliner)
DNA_STRUCT_RENAME(bUserAssetLibrary, CustomAssetLibraryDefinition)
DNA_STRUCT_RENAME_ELEM(BPoint, alfa, tilt)
DNA_STRUCT_RENAME_ELEM(BezTriple, alfa, tilt)
DNA_STRUCT_RENAME_ELEM(Bone, curveInX, curve_in_x)
@ -213,7 +214,7 @@ DNA_STRUCT_RENAME_ELEM(bTheme, tstatusbar, space_statusbar)
DNA_STRUCT_RENAME_ELEM(bTheme, ttopbar, space_topbar)
DNA_STRUCT_RENAME_ELEM(bTheme, tuserpref, space_preferences)
DNA_STRUCT_RENAME_ELEM(bTheme, tv3d, space_view3d)
DNA_STRUCT_RENAME_ELEM(bUserAssetLibrary, path, dirpath)
DNA_STRUCT_RENAME_ELEM(CustomAssetLibraryDefinition, path, dirpath)
/* NOTE: Keep sorted! */
/* Write with a different name, old Blender versions crash loading files with non-NULL

View File

@ -219,6 +219,8 @@ DEF_ENUM(rna_enum_context_mode_items)
DEF_ENUM(rna_enum_preference_section_items)
DEF_ENUM(rna_enum_project_settings_section_items)
DEF_ENUM(rna_enum_attribute_type_items)
DEF_ENUM(rna_enum_color_attribute_type_items)
DEF_ENUM(rna_enum_attribute_type_with_auto_items)

View File

@ -30,6 +30,7 @@ set(DEFSRC
rna_armature.cc
rna_asset.cc
rna_attribute.cc
rna_blender_project.cc
rna_boid.cc
rna_brush.cc
rna_cachefile.cc

View File

@ -4732,6 +4732,7 @@ static RNAProcessItem PROCESS_ITEMS[] = {
{"rna_armature.cc", "rna_armature_api.cc", RNA_def_armature},
{"rna_attribute.cc", nullptr, RNA_def_attribute},
{"rna_asset.cc", nullptr, RNA_def_asset},
{"rna_blender_project.cc", NULL, RNA_def_blender_project},
{"rna_boid.cc", nullptr, RNA_def_boid},
{"rna_brush.cc", nullptr, RNA_def_brush},
{"rna_cachefile.cc", nullptr, RNA_def_cachefile},

View File

@ -31,11 +31,16 @@ const EnumPropertyItem rna_enum_asset_library_type_items[] = {
0,
"Essentials",
"Show the basic building blocks and utilities coming with Blender"},
{ASSET_LIBRARY_CUSTOM,
{ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES,
"CUSTOM_FROM_PREFERENCES",
0,
"Custom from Preferences",
"Show assets from the asset libraries configured in the Preferences"},
{ASSET_LIBRARY_CUSTOM_FROM_PROJECT,
"CUSTOM",
0,
"Custom",
"Show assets from the asset libraries configured in the Preferences"},
"Custom from Project",
"Show assets from the asset libraries configured in the Project Settings"},
{0, nullptr, 0, nullptr, nullptr},
};
@ -45,6 +50,7 @@ const EnumPropertyItem rna_enum_asset_library_type_items[] = {
# include "AS_asset_representation.hh"
# include "BKE_asset.hh"
# include "BKE_asset_library_custom.h"
# include "BKE_context.hh"
# include "BKE_idprop.h"
@ -54,6 +60,9 @@ const EnumPropertyItem rna_enum_asset_library_type_items[] = {
# include "ED_asset.hh"
# include "ED_fileselect.hh"
# include "WM_api.hh"
# include "WM_types.hh"
# include "RNA_access.hh"
using namespace blender::asset_system;
@ -363,6 +372,33 @@ void rna_AssetMetaData_catalog_id_update(bContext *C, PointerRNA *ptr)
AS_asset_library_refresh_catalog_simplename(asset_library, asset_data);
}
static void rna_CustomAssetLibraryDefinition_name_set(PointerRNA *ptr, const char *value)
{
CustomAssetLibraryDefinition *library = (CustomAssetLibraryDefinition *)ptr->data;
/* We can't cleanly access the owning listbase here, but reconstructing the list from the link is
* fine. */
ListBase asset_libraries = BLI_listbase_from_link((Link *)library);
BKE_asset_library_custom_name_set(&asset_libraries, library, value);
}
static void rna_CustomAssetLibraryDefinition_path_set(PointerRNA *ptr, const char *value)
{
CustomAssetLibraryDefinition *library = (CustomAssetLibraryDefinition *)ptr->data;
char dirpath[FILE_MAX];
BLI_strncpy(dirpath, value, sizeof(dirpath));
if (BLI_is_file(dirpath)) {
BLI_path_parent_dir(dirpath);
}
BKE_asset_library_custom_path_set(library, dirpath);
}
void rna_AssetLibrary_settings_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA * /*ptr*/)
{
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIBRARY, NULL);
}
static PointerRNA rna_AssetHandle_file_data_get(PointerRNA *ptr)
{
AssetHandle *asset_handle = static_cast<AssetHandle *>(ptr->data);
@ -708,6 +744,61 @@ static void rna_def_asset_library_reference(BlenderRNA *brna)
srna, "Asset Library Reference", "Identifier to refer to the asset library");
}
static void rna_def_asset_library_reference_custom(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "CustomAssetLibraryDefinition", NULL);
RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
RNA_def_struct_ui_text(
srna, "Asset Library", "Settings to define a reusable library for Asset Browsers to use");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(
prop, "Name", "Identifier (not necessarily unique) for the asset library");
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_CustomAssetLibraryDefinition_name_set");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_update(prop, 0, "rna_AssetLibrary_settings_update");
prop = RNA_def_property(srna, "path", PROP_STRING, PROP_DIRPATH);
RNA_def_property_string_sdna(prop, NULL, "dirpath");
RNA_def_property_ui_text(
prop, "Path", "Path to a directory with .blend files to use as an asset library");
RNA_def_property_string_funcs(prop, NULL, NULL, "rna_CustomAssetLibraryDefinition_path_set");
RNA_def_property_update(prop, 0, "rna_AssetLibrary_settings_update");
static const EnumPropertyItem import_method_items[] = {
{ASSET_IMPORT_LINK, "LINK", 0, "Link", "Import the assets as linked data-block"},
{ASSET_IMPORT_APPEND,
"APPEND",
0,
"Append",
"Import the assets as copied data-block, with no link to the original asset data-block"},
{ASSET_IMPORT_APPEND_REUSE,
"APPEND_REUSE",
0,
"Append (Reuse Data)",
"Import the assets as copied data-block while avoiding multiple copies of nested, "
"typically heavy data. For example the textures of a material asset, or the mesh of an "
"object asset, don't have to be copied every time this asset is imported. The instances of "
"the asset share the data instead"},
{0, NULL, 0, NULL, NULL},
};
prop = RNA_def_property(srna, "import_method", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, import_method_items);
RNA_def_property_ui_text(
prop,
"Default Import Method",
"Determine how the asset will be imported, unless overridden by the Asset Browser");
RNA_def_property_update(prop, 0, "rna_AssetLibrary_settings_update");
prop = RNA_def_property(srna, "use_relative_path", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "flag", ASSET_LIBRARY_RELATIVE_PATH);
RNA_def_property_ui_text(
prop, "Relative Path", "Use relative path when linking assets from this asset library");
}
PropertyRNA *rna_def_asset_library_reference_common(StructRNA *srna,
const char *get,
const char *set)
@ -745,6 +836,7 @@ void RNA_def_asset(BlenderRNA *brna)
rna_def_asset_tag(brna);
rna_def_asset_data(brna);
rna_def_asset_library_reference(brna);
rna_def_asset_library_reference_custom(brna);
rna_def_asset_handle(brna);
rna_def_asset_representation(brna);
rna_def_asset_catalog_path(brna);

View File

@ -0,0 +1,162 @@
/* SPDX-FileCopyrightText: 2023 Blender Authors
*
* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup RNA
*/
#include "RNA_define.hh"
#include "rna_internal.h"
#ifdef RNA_RUNTIME
# include "BLI_string_ref.hh"
# include "BKE_asset_library_custom.h"
# include "BKE_blender_project.hh"
# include "BLT_translation.h"
# include "WM_api.hh"
using namespace blender;
static void rna_BlenderProject_update(Main * /*bmain*/, Scene * /*scene*/, PointerRNA * /*ptr*/)
{
/* TODO evaluate which props should send which notifiers. */
/* Force full redraw of all windows. */
WM_main_add_notifier(NC_WINDOW, nullptr);
}
static void rna_BlenderProject_name_get(PointerRNA *ptr, char *value)
{
const bke::BlenderProject *project = static_cast<bke::BlenderProject *>(ptr->data);
if (!project) {
value[0] = '\0';
return;
}
strcpy(value, project->project_name().c_str());
}
static int rna_BlenderProject_name_length(PointerRNA *ptr)
{
const bke::BlenderProject *project = static_cast<bke::BlenderProject *>(ptr->data);
if (!project) {
return 0;
}
return project->project_name().size();
}
static void rna_BlenderProject_name_set(PointerRNA *ptr, const char *value)
{
bke::BlenderProject *project = static_cast<bke::BlenderProject *>(ptr->data);
if (!project) {
return;
}
project->set_project_name(value);
}
static void rna_BlenderProject_root_path_get(PointerRNA *ptr, char *value)
{
const bke::BlenderProject *project = static_cast<bke::BlenderProject *>(ptr->data);
if (!project) {
value[0] = '\0';
return;
}
strcpy(value, project->root_path().c_str());
}
static int rna_BlenderProject_root_path_length(PointerRNA *ptr)
{
const bke::BlenderProject *project = static_cast<bke::BlenderProject *>(ptr->data);
if (!project) {
return 0;
}
return project->root_path().size();
}
static void rna_BlenderProject_root_path_set(PointerRNA * /*ptr*/, const char * /*value*/)
{
/* Property is not editable, see #rna_BlenderProject_root_path_editable(). */
BLI_assert_unreachable();
}
static int rna_BlenderProject_root_path_editable(PointerRNA * /*ptr*/, const char **r_info)
{
/* Path is never editable (setting up a project is an operation), but return a nicer disabled
* hint. */
*r_info = N_("Project location cannot be changed, displayed for informal purposes only");
return 0;
}
static void rna_BlenderProject_asset_libraries_begin(CollectionPropertyIterator *iter,
PointerRNA *ptr)
{
bke::BlenderProject *project = static_cast<bke::BlenderProject *>(ptr->data);
ListBase &asset_libraries = project->asset_library_definitions();
rna_iterator_listbase_begin(iter, &asset_libraries, nullptr);
}
static bool rna_BlenderProject_is_dirty_get(PointerRNA *ptr)
{
const bke::BlenderProject *project = static_cast<bke::BlenderProject *>(ptr->data);
return project->has_unsaved_changes();
}
#else
void RNA_def_blender_project(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "BlenderProject", nullptr);
RNA_def_struct_ui_text(srna, "Blender Project", "");
PropertyRNA *prop;
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_string_funcs(prop,
"rna_BlenderProject_name_get",
"rna_BlenderProject_name_length",
"rna_BlenderProject_name_set");
RNA_def_property_ui_text(prop, "Name", "The identifier for the project");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_update(prop, 0, "rna_BlenderProject_update");
prop = RNA_def_property(srna, "root_path", PROP_STRING, PROP_NONE);
RNA_def_property_string_funcs(prop,
"rna_BlenderProject_root_path_get",
"rna_BlenderProject_root_path_length",
"rna_BlenderProject_root_path_set");
RNA_def_property_editable_func(prop, "rna_BlenderProject_root_path_editable");
RNA_def_property_ui_text(prop, "Location", "The location of the project on disk");
prop = RNA_def_property(srna, "asset_libraries", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "CustomAssetLibraryDefinition");
RNA_def_property_collection_funcs(prop,
"rna_BlenderProject_asset_libraries_begin",
"rna_iterator_listbase_next",
"rna_iterator_listbase_end",
"rna_iterator_listbase_get",
nullptr,
nullptr,
nullptr,
nullptr);
RNA_def_property_ui_text(prop, "Asset Libraries", "");
prop = RNA_def_property(srna, "is_dirty", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_funcs(prop, "rna_BlenderProject_is_dirty_get", nullptr);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop,
"Dirty",
"Project settings have changed since read from disk. Save the settings to keep them");
}
#endif

View File

@ -192,6 +192,12 @@ static PointerRNA rna_Context_preferences_get(PointerRNA * /*ptr*/)
return newptr;
}
static PointerRNA rna_Context_project_get(PointerRNA * /*ptr*/)
{
blender::bke::BlenderProject *project = CTX_wm_project();
return RNA_pointer_create(nullptr, &RNA_BlenderProject, project);
}
static int rna_Context_mode_get(PointerRNA *ptr)
{
bContext *C = (bContext *)ptr->data;
@ -328,6 +334,11 @@ void RNA_def_context(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "Preferences");
RNA_def_property_pointer_funcs(prop, "rna_Context_preferences_get", nullptr, nullptr, nullptr);
prop = RNA_def_property(srna, "project", PROP_POINTER, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_struct_type(prop, "BlenderProject");
RNA_def_property_pointer_funcs(prop, "rna_Context_project_get", NULL, NULL, NULL);
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_context_mode_items);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);

View File

@ -144,6 +144,7 @@ void RNA_def_animviz(struct BlenderRNA *brna);
void RNA_def_armature(struct BlenderRNA *brna);
void RNA_def_attribute(struct BlenderRNA *brna);
void RNA_def_asset(struct BlenderRNA *brna);
void RNA_def_blender_project(struct BlenderRNA *brna);
void RNA_def_boid(struct BlenderRNA *brna);
void RNA_def_brush(struct BlenderRNA *brna);
void RNA_def_cachefile(struct BlenderRNA *brna);

View File

@ -199,6 +199,10 @@ static const EnumPropertyItem *rna_Area_ui_type_itemf(bContext *C,
continue;
}
if (!U.experimental.use_blender_projects && (item_from->value == SPACE_PROJECT_SETTINGS)) {
continue;
}
SpaceType *st = item_from->identifier[0] ? BKE_spacetype_from_id(item_from->value) : nullptr;
int totitem_prev = totitem;
if (st && st->space_subtype_item_extend != nullptr) {

View File

@ -180,11 +180,16 @@ const EnumPropertyItem rna_enum_space_type_items[] = {
ICON_SPREADSHEET,
"Spreadsheet",
"Explore geometry data in a table"},
{SPACE_PROJECT_SETTINGS,
"PROJECT_SETTINGS",
ICON_PREFERENCES,
"Project Settings",
"Edit persistent configuration settings for the active project"},
{SPACE_USERPREF,
"PREFERENCES",
ICON_PREFERENCES,
"Preferences",
"Edit persistent configuration settings"},
"Edit persistent configuration settings for personal use"},
{0, nullptr, 0, nullptr, nullptr},
};
@ -536,6 +541,12 @@ static const EnumPropertyItem rna_enum_curve_display_handle_items[] = {
{0, nullptr, 0, nullptr, nullptr},
};
const EnumPropertyItem rna_enum_project_settings_section_items[] = {
{PROJECT_SETTINGS_SECTION_GENERAL, "GENERAL", 0, "General", ""},
{PROJECT_SETTINGS_SECTION_ASSET_LIBRARIES, "ASSET_LIBRARIES", 0, "Asset Libraries", ""},
{0, NULL, 0, NULL, NULL},
};
#ifdef RNA_RUNTIME
# include "AS_asset_representation.hh"
@ -559,7 +570,6 @@ static const EnumPropertyItem rna_enum_curve_display_handle_items[] = {
# include "BKE_layer.h"
# include "BKE_nla.h"
# include "BKE_paint.hh"
# include "BKE_preferences.h"
# include "BKE_scene.h"
# include "BKE_screen.hh"
# include "BKE_workspace.h"
@ -619,6 +629,8 @@ static StructRNA *rna_Space_refine(PointerRNA *ptr)
return &RNA_SpaceConsole;
case SPACE_USERPREF:
return &RNA_SpacePreferences;
case SPACE_PROJECT_SETTINGS:
return &RNA_SpaceProjectSettings;
case SPACE_CLIP:
return &RNA_SpaceClipEditor;
case SPACE_SPREADSHEET:
@ -7287,6 +7299,20 @@ static void rna_def_space_userpref(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Filter", "Search term for filtering in the UI");
}
static void rna_def_space_project_settings(BlenderRNA *brna)
{
StructRNA *srna;
srna = RNA_def_struct(brna, "SpaceProjectSettings", "Space");
RNA_def_struct_ui_text(srna, "Space Project Settings", "Blender project space data");
PropertyRNA *prop;
prop = RNA_def_property(srna, "active_section", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_project_settings_section_items);
RNA_def_property_ui_text(prop, "Active Section", "Choose the category of options to display");
}
static void rna_def_node_tree_path(BlenderRNA *brna)
{
StructRNA *srna;
@ -8321,6 +8347,7 @@ void RNA_def_space(BlenderRNA *brna)
rna_def_console_line(brna);
rna_def_space_info(brna);
rna_def_space_userpref(brna);
rna_def_space_project_settings(brna);
rna_def_node_tree_path(brna);
rna_def_space_node(brna);
rna_def_space_clip(brna);

View File

@ -30,6 +30,7 @@
#include "BKE_addon.h"
#include "BKE_appdir.h"
#include "BKE_callbacks.h"
#include "BKE_preferences.h"
#include "BKE_sound.h"
#include "BKE_studiolight.h"
@ -181,7 +182,6 @@ static const EnumPropertyItem rna_enum_preference_gpu_backend_items[] = {
# include "BKE_mesh_runtime.hh"
# include "BKE_object.hh"
# include "BKE_paint.hh"
# include "BKE_preferences.h"
# include "BKE_screen.hh"
# include "DEG_depsgraph.hh"
@ -338,18 +338,6 @@ static void rna_userdef_language_update(Main * /*bmain*/, Scene * /*scene*/, Poi
USERDEF_TAG_DIRTY;
}
static void rna_userdef_asset_library_name_set(PointerRNA *ptr, const char *value)
{
bUserAssetLibrary *library = (bUserAssetLibrary *)ptr->data;
BKE_preferences_asset_library_name_set(&U, library, value);
}
static void rna_userdef_asset_library_path_set(PointerRNA *ptr, const char *value)
{
bUserAssetLibrary *library = (bUserAssetLibrary *)ptr->data;
BKE_preferences_asset_library_path_set(library, value);
}
static void rna_userdef_extension_repo_name_set(PointerRNA *ptr, const char *value)
{
bUserExtensionRepo *repo = (bUserExtensionRepo *)ptr->data;
@ -2801,6 +2789,21 @@ static void rna_def_userdef_theme_space_userpref(BlenderRNA *brna)
rna_def_userdef_theme_spaces_main(srna);
}
static void rna_def_userdef_theme_space_project_settings(BlenderRNA *brna)
{
StructRNA *srna;
/* space_userpref */
srna = RNA_def_struct(brna, "ThemeProjectSettings", NULL);
RNA_def_struct_sdna(srna, "ThemeSpace");
RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
RNA_def_struct_ui_text(
srna, "Theme Project Settings", "Theme settings for the Blender project settings editor");
rna_def_userdef_theme_spaces_main(srna);
}
static void rna_def_userdef_theme_space_console(BlenderRNA *brna)
{
StructRNA *srna;
@ -4222,6 +4225,8 @@ static void rna_def_userdef_themes(BlenderRNA *brna)
{5, "NLA_EDITOR", ICON_NLA, "Nonlinear Animation", ""},
{12, "OUTLINER", ICON_OUTLINER, "Outliner", ""},
{14, "PREFERENCES", ICON_PREFERENCES, "Preferences", ""},
/* TODO icon */
{24, "PROJECT_SETTINGS", ICON_PREFERENCES, "Project Settings", ""},
{11, "PROPERTIES", ICON_PROPERTIES, "Properties", ""},
{17, "CONSOLE", ICON_CONSOLE, "Python Console", ""},
{23, "SPREADSHEET", ICON_SPREADSHEET, "Spreadsheet"},
@ -4335,6 +4340,12 @@ static void rna_def_userdef_themes(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "ThemePreferences");
RNA_def_property_ui_text(prop, "Preferences", "");
prop = RNA_def_property(srna, "project_settings", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);
RNA_def_property_pointer_sdna(prop, NULL, "space_project_settings");
RNA_def_property_struct_type(prop, "ThemeProjectSettings");
RNA_def_property_ui_text(prop, "Project Settings", "");
prop = RNA_def_property(srna, "console", PROP_POINTER, PROP_NONE);
RNA_def_property_flag(prop, PROP_NEVER_NULL);
RNA_def_property_pointer_sdna(prop, nullptr, "space_console");
@ -4626,6 +4637,7 @@ static void rna_def_userdef_dothemes(BlenderRNA *brna)
rna_def_userdef_theme_space_outliner(brna);
rna_def_userdef_theme_space_info(brna);
rna_def_userdef_theme_space_userpref(brna);
rna_def_userdef_theme_space_project_settings(brna);
rna_def_userdef_theme_space_console(brna);
rna_def_userdef_theme_space_clip(brna);
rna_def_userdef_theme_space_topbar(brna);
@ -6526,63 +6538,6 @@ static void rna_def_userdef_keymap(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Key Config", "The name of the active key configuration");
}
static void rna_def_userdef_filepaths_asset_library(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "UserAssetLibrary", nullptr);
RNA_def_struct_sdna(srna, "bUserAssetLibrary");
RNA_def_struct_clear_flag(srna, STRUCT_UNDO);
RNA_def_struct_ui_text(
srna, "Asset Library", "Settings to define a reusable library for Asset Browsers to use");
prop = RNA_def_property(srna, "name", PROP_STRING, PROP_NONE);
RNA_def_property_ui_text(
prop, "Name", "Identifier (not necessarily unique) for the asset library");
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_userdef_asset_library_name_set");
RNA_def_struct_name_property(srna, prop);
RNA_def_property_update(prop, 0, "rna_userdef_update");
prop = RNA_def_property(srna, "path", PROP_STRING, PROP_DIRPATH);
RNA_def_property_string_sdna(prop, nullptr, "dirpath");
RNA_def_property_ui_text(
prop, "Path", "Path to a directory with .blend files to use as an asset library");
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_EDITOR_FILEBROWSER);
RNA_def_property_string_funcs(prop, nullptr, nullptr, "rna_userdef_asset_library_path_set");
RNA_def_property_update(prop, 0, "rna_userdef_update");
static const EnumPropertyItem import_method_items[] = {
{ASSET_IMPORT_LINK, "LINK", 0, "Link", "Import the assets as linked data-block"},
{ASSET_IMPORT_APPEND,
"APPEND",
0,
"Append",
"Import the assets as copied data-block, with no link to the original asset data-block"},
{ASSET_IMPORT_APPEND_REUSE,
"APPEND_REUSE",
0,
"Append (Reuse Data)",
"Import the assets as copied data-block while avoiding multiple copies of nested, "
"typically heavy data. For example the textures of a material asset, or the mesh of an "
"object asset, don't have to be copied every time this asset is imported. The instances of "
"the asset share the data instead"},
{0, nullptr, 0, nullptr, nullptr},
};
prop = RNA_def_property(srna, "import_method", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, import_method_items);
RNA_def_property_ui_text(
prop,
"Default Import Method",
"Determine how the asset will be imported, unless overridden by the Asset Browser");
RNA_def_property_update(prop, 0, "rna_userdef_update");
prop = RNA_def_property(srna, "use_relative_path", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", ASSET_LIBRARY_RELATIVE_PATH);
RNA_def_property_ui_text(
prop, "Relative Path", "Use relative path when linking assets from this asset library");
}
static void rna_def_userdef_filepaths_extension_repo(BlenderRNA *brna)
{
StructRNA *srna;
@ -6932,10 +6887,8 @@ static void rna_def_userdef_filepaths(BlenderRNA *brna)
RNA_def_property_enum_items(prop, preview_type_items);
RNA_def_property_ui_text(prop, "File Preview Type", "What type of blend preview to create");
rna_def_userdef_filepaths_asset_library(brna);
prop = RNA_def_property(srna, "asset_libraries", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "UserAssetLibrary");
RNA_def_property_struct_type(prop, "CustomAssetLibraryDefinition");
RNA_def_property_ui_text(prop, "Asset Libraries", "");
prop = RNA_def_property(srna, "active_asset_library", PROP_INT, PROP_NONE);
@ -7086,6 +7039,12 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
RNA_def_property_flag(prop, PROP_CONTEXT_UPDATE);
RNA_def_property_update(prop, 0, "rna_userdef_keyconfig_reload_update");
prop = RNA_def_property(srna, "use_blender_projects", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_ui_text(prop,
"Blender Projects",
"Enable support for Blender project directories, consisting out of "
"multiple .blend files and dedicated project settings");
prop = RNA_def_property(srna, "use_viewport_debug", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "use_viewport_debug", 1);
RNA_def_property_ui_text(prop,

View File

@ -238,6 +238,9 @@ static eSpace_Type rna_Space_refine_reverse(StructRNA *srna)
if (srna == &RNA_SpacePreferences) {
return SPACE_USERPREF;
}
if (srna == &RNA_SpaceProjectSettings) {
return SPACE_PROJECT_SETTINGS;
}
if (srna == &RNA_SpaceClipEditor) {
return SPACE_CLIP;
}

View File

@ -363,6 +363,8 @@ struct wmNotifier {
#define NC_ASSET (27 << 24)
/* Changes to the active viewer path. */
#define NC_VIEWER_PATH (28 << 24)
/* Changes related to the active project. */
#define NC_PROJECT (29 << 24)
/* data type, 256 entries is enough, it can overlap */
#define NOTE_DATA 0x00FF0000
@ -510,6 +512,9 @@ struct wmNotifier {
* reloading of asset libraries & their catalogs should happen. That only happens on explicit user
* action. */
#define ND_ASSET_CATALOGS (4 << 16)
/* Some settings of an asset library were changed, and UIs showing asset library information should
* redraw. */
#define ND_ASSET_LIBRARY (5 << 16)
/* subtype, 256 entries too */
#define NOTE_SUBTYPE 0x0000FF00

View File

@ -587,6 +587,7 @@ static const char *wm_area_name(ScrArea *area)
SPACE_NAME(SPACE_CLIP);
SPACE_NAME(SPACE_TOPBAR);
SPACE_NAME(SPACE_STATUSBAR);
SPACE_NAME(SPACE_PROJECT_SETTINGS);
default:
return "Unknown Space";
}
@ -1544,6 +1545,9 @@ void wm_draw_update(bContext *C)
BKE_image_free_unused_gpu_textures();
/* Subscribe to messages when drawing, #ED_region_do_draw() does the same on region level. */
wm_messages_subscribe(wm);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
#ifdef WIN32
GHOST_TWindowState state = GHOST_GetWindowState(

View File

@ -65,6 +65,7 @@
#include "BKE_appdir.h"
#include "BKE_autoexec.h"
#include "BKE_blender.h"
#include "BKE_blender_project.hh"
#include "BKE_blender_version.h"
#include "BKE_blendfile.h"
#include "BKE_callbacks.h"
@ -147,6 +148,8 @@ static void wm_test_autorun_revert_action_exec(bContext *C);
static CLG_LogRef LOG = {"wm.files"};
using namespace blender;
/* -------------------------------------------------------------------- */
/** \name Misc Utility Functions
* \{ */
@ -164,7 +167,8 @@ void WM_file_tag_modified()
bool wm_file_or_session_data_has_unsaved_changes(const Main *bmain, const wmWindowManager *wm)
{
return !wm->file_saved || ED_image_should_save_modified(bmain) ||
AS_asset_library_has_any_unsaved_catalogs();
AS_asset_library_has_any_unsaved_catalogs() ||
bke::BlenderProject::has_unsaved_changes(CTX_wm_project());
}
/** \} */
@ -2435,7 +2439,7 @@ static int wm_userpref_read_exec(bContext *C, wmOperator *op)
BKE_callback_exec_null(bmain, BKE_CB_EVT_EXTENSION_REPOS_UPDATE_PRE);
UserDef U_backup = blender::dna::shallow_copy(U);
UserDef U_backup = dna::shallow_copy(U);
wmHomeFileRead_Params read_homefile_params{};
read_homefile_params.use_data = use_data;
@ -2543,7 +2547,7 @@ static int wm_homefile_read_exec(bContext *C, wmOperator *op)
bool use_userdef = false;
char filepath_buf[FILE_MAX];
const char *filepath = nullptr;
UserDef U_backup = blender::dna::shallow_copy(U);
UserDef U_backup = dna::shallow_copy(U);
if (!use_factory_settings) {
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "filepath");
@ -3343,6 +3347,16 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op)
}
}
const bke::BlenderProject *active_project = CTX_wm_project();
if (active_project && !bke::BlenderProject::path_is_within_project(filepath)) {
BKE_reportf(
op->reports,
RPT_WARNING,
"File saved outside of the active project's path (\"%s\"). Active project changed.",
active_project->root_path().c_str());
/* Don't cancel. Otherwise there's no way to save files outside of the active project. */
}
const int fileflags_orig = G.fileflags;
int fileflags = G.fileflags;
@ -4011,6 +4025,7 @@ void wm_save_file_forwardcompat_dialog(bContext *C, wmOperator *op)
* \{ */
static char save_images_when_file_is_closed = true;
static char save_project_settings_when_file_is_closed = true;
static void wm_block_file_close_cancel(bContext *C, void *arg_block, void * /*arg_data*/)
{
@ -4054,6 +4069,11 @@ static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_dat
}
}
bke::BlenderProject *project = CTX_wm_project();
if (project && project->has_unsaved_changes() && save_project_settings_when_file_is_closed) {
project->save_settings();
}
bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0';
if (file_has_been_saved_before) {
@ -4252,6 +4272,30 @@ static uiBlock *block_create__close_file_dialog(bContext *C, ARegion *region, vo
has_extra_checkboxes = true;
}
if (bke::BlenderProject::has_unsaved_changes(CTX_wm_project())) {
/* Only the first checkbox should get extra separation. */
if (!has_extra_checkboxes) {
uiItemS(layout);
}
uiDefButBitC(block,
UI_BTYPE_CHECKBOX,
1,
0,
"Save modified project settings",
0,
0,
0,
UI_UNIT_Y,
&save_project_settings_when_file_is_closed,
0,
0,
0,
0,
"");
has_extra_checkboxes = true;
}
if (AS_asset_library_has_any_unsaved_catalogs()) {
static char save_catalogs_when_file_is_closed;

View File

@ -30,6 +30,7 @@
#include "BLT_translation.h"
#include "BKE_blender_project.hh"
#include "BKE_blender_version.h"
#include "BKE_context.hh"
#include "BKE_global.h"
@ -45,6 +46,7 @@
#include "RNA_enum_types.hh"
#include "WM_api.hh"
#include "WM_message.hh"
#include "WM_types.hh"
#include "wm.hh"
#include "wm_draw.hh"
@ -480,6 +482,8 @@ void wm_window_close(bContext *C, wmWindowManager *wm, wmWindow *win)
void wm_window_title(wmWindowManager *wm, wmWindow *win)
{
#define MAX_PROJECT_NAME_HINT (MAX_NAME + 4)
if (win->ghostwin == nullptr) {
return;
}
@ -505,6 +509,14 @@ void wm_window_title(wmWindowManager *wm, wmWindow *win)
}
if (has_filepath) {
const blender::bke::BlenderProject *project = CTX_wm_project();
if (project) {
blender::StringRefNull name = project->project_name();
str += "";
str += name.is_empty() ? IFACE_("Unnamed project") : name;
str += " - ";
}
const size_t filename_no_ext_len = BLI_path_extension_or_end(filename) - filename;
str.append(filename, filename_no_ext_len);
}
@ -1766,6 +1778,35 @@ void wm_window_events_process(const bContext *C)
/** \} */
/* -------------------------------------------------------------------- */
/** \name WM level message bus subscribers
* \{ */
static void wm_msg_windows_title_update_fn(bContext *C,
wmMsgSubscribeKey * /*msg_key*/,
wmMsgSubscribeValue * /*msg_val*/)
{
wmWindowManager *wm = CTX_wm_manager(C);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
wm_window_title(wm, win);
}
}
void wm_messages_subscribe(wmWindowManager *wm)
{
WM_msgbus_clear_by_owner(wm->message_bus, wm);
wmMsgSubscribeValue msg_sub_value_update_win_titles = {0};
msg_sub_value_update_win_titles.owner = wm;
msg_sub_value_update_win_titles.notify = wm_msg_windows_title_update_fn;
/* Update window titles on project name change. */
WM_msg_subscribe_rna_anon_prop(
wm->message_bus, BlenderProject, name, &msg_sub_value_update_win_titles);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Ghost Init/Exit
* \{ */

View File

@ -30,6 +30,8 @@ void wm_ghost_exit();
void wm_clipboard_free();
void wm_messages_subscribe(wmWindowManager *wm);
/**
* This one should correctly check for apple top header...
* done for Cocoa: returns window contents (and not frame) max size.