WIP: Basic Blender Project Support (experimental feature) #107655
|
@ -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),
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -78,6 +78,7 @@ _modules = [
|
|||
"space_nla",
|
||||
"space_node",
|
||||
"space_outliner",
|
||||
"space_project_settings",
|
||||
"space_properties",
|
||||
"space_sequencer",
|
||||
"space_spreadsheet",
|
||||
|
|
|
@ -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)
|
|
@ -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')
|
||||
|
||||
|
|
|
@ -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),
|
||||
),
|
||||
)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -6,6 +6,7 @@ set(INC
|
|||
.
|
||||
intern
|
||||
../blenkernel
|
||||
../makesrna
|
||||
)
|
||||
|
||||
set(INC_SYS
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
/** \} */
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
|
||||
/** \} */
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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" :
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}")
|
|
@ -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;
|
||||
}
|
|
@ -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);
|
||||
}
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
|
@ -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, ®ion->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);
|
||||
}
|
|
@ -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";
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
* \{ */
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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},
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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
|
||||
* \{ */
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue