Compare commits

...

61 Commits

Author SHA1 Message Date
576af00029 Merge branch 'main', update to changes 2023-02-17 15:37:16 +01:00
b999b79d08 Support project asset libraries in new asset library loading 2023-01-12 14:37:39 +01:00
e63671c21d Support & use new asset library path query from master 2023-01-10 16:45:39 +01:00
3ff321bc98 Merge branch 'master' into blender-projects-basics 2023-01-10 16:37:29 +01:00
f0eba2fe6c Merge branch 'master' into blender-projects-basics 2023-01-10 15:54:27 +01:00
01f9ac24ed Adapt to changes in master, fixes all compile errors 2023-01-06 17:20:03 +01:00
58d32e8185 Merge branch 'master' into blender-projects-basics 2023-01-06 15:26:21 +01:00
7158e0eb4c Merge branch 'master' into blender-projects-basics 2022-11-02 11:58:03 +01:00
75794f95c0 Fix incorrect operator name in UI script 2022-10-18 19:00:15 +02:00
7234c363db Hide UI behind experimental option 2022-10-18 18:26:50 +02:00
c475201378 Use operator poll instead of cancelling; Minor cleanup 2022-10-18 17:32:10 +02:00
907a7aba1e Confirmation prompt for closing file with unsaved project settings
The regular confirmation prompt is shown then with a "Save modified
project settings" checkbox.
2022-10-18 16:44:43 +02:00
e1d90eccc5 Fix error when changing asset library name or path
Coded the RNA functions to only work with the preferences for the
start. But we actually don't have to care here if the libraries are
defined in the preferences or project.
2022-10-18 16:23:53 +02:00
42091addb0 Add comment on asset library definition 2022-10-18 15:45:20 +02:00
7aba0015d0 Cleanup: Remove unused headers 2022-10-18 15:30:19 +02:00
eafc9cb446 Refactor BKE project interface & implementation
Mostly this is about more cleanly separating between `BlenderProject`
and `ProjectSettings`. Overall this is a nice improvement I think, helps
readability of interfaces and implementation a lot.
2022-10-18 15:24:56 +02:00
30b08fbec5 Merge branch 'master' into blender-projects-basics 2022-10-18 15:18:36 +02:00
55512dd2a8 Update to path changes in master 2022-10-18 11:49:50 +02:00
dacbcde446 Merge branch 'master' into blender-projects-basics 2022-10-17 22:36:47 +02:00
7987608fae Add missing break 2022-10-17 15:10:18 +02:00
3dfe0421f2 Attempt to fix build error on clean builds 2022-10-14 19:14:38 +02:00
80a4c388f9 Cleanup: Move project settings code to own file
Will probably reorganize things even further, but step by step.
2022-10-14 19:13:06 +02:00
8ea0551d36 Cleanup: Move operators & operator logic to editors/project 2022-10-14 15:17:23 +02:00
6177df9dce Support displaying project asset libraries in asset browser
The hint in the asset browser for when the selected library path doesn't
exists is updated to mention the project settings too, and there is a
button to open the project settings in the asset library section.
2022-10-13 21:31:20 +02:00
97f667058c Set default project name & add default asset library
Default project name will just be the directory name. The default asset
library will be called "Project Library" and point to an `assets/`
directory inside the project root directory.
2022-10-13 19:37:03 +02:00
1f819df7df Add UI for setting up project asset libraries
Looks just like the UI for setting up custom asset libraries in the
Preferences. However project asset libraries use paths relative to the
project root directory.

Had to do some changes to project data storage to avoid memory issues.
2022-10-13 16:43:54 +02:00
f22cc99944 Merge branch 'master' into blender-projects-basics 2022-10-13 12:06:12 +02:00
97e21ed248 Support writing asset library definitions to the project settings
Unit tested, there is no UI for this yet.
2022-10-13 11:57:24 +02:00
378e0d96fc Make custom asset libraries independent of Preferences
Projects are supposed to support custom asset libraries too. So the
custom asset library types should be generalized and not be preferences
specific.
2022-10-12 16:29:30 +02:00
416a486c00 Swap order of Show Preferences and Show Project Settings menu entires
Looks a bit less nice, but doesn't mess with muscle memory as much,
since the Preferences used to be last in the menu. I for one kept
opening the wrong window :)
2022-10-12 15:57:54 +02:00
9dc2bcf166 Indicate project folders with special icon in File Browser
Using the dev-fund heart icon temporarily :)
2022-10-07 15:58:14 +02:00
c8daddf7db Rename File->New Project to Set up Project, open settings after 2022-10-07 15:37:54 +02:00
b2b00cb8ea Improve message in Project Settings when there's no project + cleanup
Also show a button to set up a project.
2022-10-07 11:55:23 +02:00
0cba4a12a3 Merge branch 'master' into blender-projects-basics 2022-10-07 11:12:23 +02:00
4441e3b791 Fix incorrect setting of initial file browser location 2022-10-06 19:36:23 +02:00
3013cf0b37 Add menu with operator to delete project configuration
Deletes the active project's .blender_project directory, but leaves all
actual project files (.blends and such) untouched.
2022-10-06 19:32:06 +02:00
3002bd6e88 Show warning popup when creating project not containing the current file 2022-10-06 17:42:49 +02:00
3ad6715265 Cleanup: Move project operators closer together 2022-10-06 17:39:53 +02:00
2578c0ba92 Show warning when saving files outside the active project 2022-10-06 17:35:05 +02:00
525f24f1ba Disable buttons if no project is loaded, always show hint then
Disable the navigation and "Save Settings" buttons when there is no
active project.

The message saying that no project is loaded always shows up now, even
if the UI somehow displays a section that is not the "General" one.
2022-10-06 17:00:04 +02:00
871bdb5d1f Keep track of unsaved changes, indicate in "Save Settings" button 2022-10-06 16:42:59 +02:00
818a3d3743 Merge branch 'master' into blender-projects-basics 2022-10-06 16:03:40 +02:00
483a70eeb5 Support changing project name in Project Settings UI + show location
Adds basic access to the active project and its properties to BPY
(`context.project`).
2022-10-06 15:53:04 +02:00
6fde8ab8db Cleanup: variable names, memory debug names 2022-10-06 15:52:22 +02:00
cd15fbbed6 Support saving project settings to .blender_project/settings.json 2022-10-06 15:51:10 +02:00
3ab935c8a9 Project Settings: Add sections support and display some text for testing 2022-10-04 02:15:48 +02:00
420cc8a093 Add operator to open project settings window (like preferences) 2022-10-03 22:40:44 +02:00
c8583415e5 Add theme options for new project settings editor 2022-10-03 21:45:10 +02:00
5cc304b9a7 Add most bolierplate code & some buttons for Project Settings editor 2022-10-03 21:34:17 +02:00
c2fce60bea Don't display save file name & red-alert on "New Project..." 2022-10-03 16:15:45 +02:00
a2863b8891 Merge branch 'master' into blender-projects-basics 2022-10-03 10:42:58 +02:00
d5ae369efa Initial support for reading project name from settings.json
Adds very basic json deserializing for reading a project name from a
`.blender_project/settings.json` file. The name is displayed in the
window title.
2022-09-29 16:58:15 +02:00
c32b7bfe54 Add "New Project" operator to select a directory to use as project root
Name and exact design of this is pending still, they are not optimal.
2022-09-29 15:47:41 +02:00
24cb209915 Show hint in window title when editing file inside project
Currently only says "Has Project", but should be the project name once
that's supported.
2022-09-29 15:46:32 +02:00
5fb4b2d6ce Fix project not updated correctly on file save and write 2022-09-29 15:46:01 +02:00
1e105e99b0 Merge branch 'master' into blender-projects-basics 2022-09-29 14:38:07 +02:00
ce9949f7fa Support active project, set when loading or writing a file (+ tests)
The active project is determined via the path of the .blend file. So if
that changes (on write) or when a new .blend file is opend, the active
project is updated.

Some of the added tests require a latest checkout of the libraries SVN
repository (see rBL63043).
2022-09-29 14:29:00 +02:00
994c33ae71 Merge branch 'master' into blender-projects-basics 2022-09-29 00:17:53 +02:00
939f20490d Handle and test both Unix and Windows style slashes
When unit testing on Unix with Windows style slashes, the project
directories would have the backslash in the name, rather than
recognizing it as nested directory. We could just expect native paths
only like most BLI functions, but it's not a big problem to just support
any format and just convert it internally. The most important part is
that the API defines well how it deals with the different formats, and
that this is unit tested. Ideally we'd have some path object type that
abstracts away the difference.
2022-09-29 00:17:20 +02:00
6d7a301b04 Test project paths with unicode characters & trailing slashes 2022-09-28 16:08:50 +02:00
39987a890a Add create, dummy load function and tests for project settings directory 2022-09-27 15:30:02 +02:00
73 changed files with 3427 additions and 420 deletions

View File

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

View File

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

View File

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

View File

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

View File

@@ -278,6 +278,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'
@@ -629,6 +633,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')
@@ -735,7 +741,7 @@ class TOPBAR_MT_help(Menu):
class TOPBAR_MT_file_context_menu(Menu):
bl_label = "File Context Menu"
def draw(self, _context):
def draw(self, context):
layout = self.layout
layout.operator_context = 'INVOKE_AREA'
@@ -754,6 +760,8 @@ class TOPBAR_MT_file_context_menu(Menu):
layout.separator()
if context.preferences.experimental.use_blender_projects:
layout.operator("screen.project_settings_show", text="Project Settings...")
layout.operator("screen.userpref_show",
text="Preferences...", icon='PREFERENCES')

View File

@@ -10,6 +10,7 @@ from bpy.app.translations import (
pgettext_iface as iface_,
pgettext_tip as tip_,
)
from bl_ui.utils import CenterAlignMixIn
# -----------------------------------------------------------------------------
@@ -147,40 +148,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
@@ -1290,6 +1257,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), {
@@ -2348,6 +2319,7 @@ class USERPREF_PT_experimental_prototypes(ExperimentalPanel, Panel):
({"property": "use_full_frame_compositor"}, ("blender/blender/issues/88150", "#88150")),
({"property": "enable_eevee_next"}, ("blender/blender/issues/93220", "#93220")),
({"property": "enable_workbench_next"}, ("blender/blender/issues/101619", "#101619")),
({"property": "use_blender_projects"}, None),
),
)

View File

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

View File

@@ -12,8 +12,8 @@
#include "AS_asset_library.hh"
#include "AS_asset_representation.hh"
#include "BKE_asset_library_custom.h"
#include "BKE_main.h"
#include "BKE_preferences.h"
#include "BLI_fileops.h"
#include "BLI_path_util.h"
@@ -74,8 +74,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->path;
}
@@ -267,15 +267,17 @@ 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->path)) {
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);
}
/* TODO project libraries */
AssetLibraryReference library_ref{};
library_ref.custom_library_index = -1;

View File

@@ -4,9 +4,14 @@
* \ingroup asset_system
*/
#include "BKE_blender.h"
#include "BKE_preferences.h"
#include <memory>
#include <string>
#include "BKE_asset_library_custom.h"
#include "BKE_blender.h"
#include "BKE_blender_project.h"
#include "BLI_path_util.h"
#include "BLI_string_ref.hh"
#include "DNA_asset_types.h"
@@ -82,8 +87,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;
@@ -199,29 +205,81 @@ AssetLibrary *AssetLibraryService::get_asset_library_all(const Main *bmain)
return all_library_.get();
}
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_from_index(&U, library_reference.custom_library_index);
switch (eAssetLibraryType(library_reference.type)) {
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES: {
return BKE_asset_library_custom_find_from_index(&U.asset_libraries,
library_reference.custom_library_index);
}
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
BlenderProject *project = BKE_project_active_get();
if (!project) {
return NULL;
}
ListBase *project_libraries = BKE_project_custom_asset_libraries_get(project);
return BKE_asset_library_custom_find_from_index(project_libraries,
library_reference.custom_library_index);
}
case ASSET_LIBRARY_ALL:
case ASSET_LIBRARY_LOCAL:
case ASSET_LIBRARY_ESSENTIALS:
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 "";
switch (eAssetLibraryType(library_reference.type)) {
case ASSET_LIBRARY_ALL:
case ASSET_LIBRARY_LOCAL:
case ASSET_LIBRARY_ESSENTIALS:
return "";
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES: {
CustomAssetLibraryDefinition *user_library = find_custom_asset_library_from_library_ref(
library_reference);
if (!user_library) {
return "";
}
return user_library->path;
}
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->path)) {
const BlenderProject *project = BKE_project_active_get();
const char *project_root_path = BKE_project_root_path_get(project);
char path[1024]; /* FILE_MAX */
BLI_path_join(path, sizeof(path), project_root_path, project_library->path);
return path;
}
return project_library->path;
}
default:
BLI_assert_unreachable();
break;
}
bUserAssetLibrary *custom_library = find_custom_asset_library_from_library_ref(
library_reference);
if (!custom_library || !custom_library->path[0]) {
return "";
}
return custom_library->path;
return "";
}
void AssetLibraryService::allocate_service_instance()

View File

@@ -14,7 +14,6 @@
#include <memory>
struct AssetLibraryReference;
struct bUserAssetLibrary;
namespace blender::asset_system {
@@ -59,7 +58,7 @@ class AssetLibraryService {
static void destroy();
static std::string root_path_from_library_ref(const AssetLibraryReference &library_reference);
static bUserAssetLibrary *find_custom_asset_library_from_library_ref(
static CustomAssetLibraryDefinition *find_custom_asset_library_from_library_ref(
const AssetLibraryReference &library_reference);
AssetLibrary *get_asset_library(const Main *bmain,

View File

@@ -4,7 +4,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"
@@ -93,8 +93,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()));
@@ -145,7 +145,7 @@ class AssetCatalogTest : public AssetLibraryTestBase {
/* Test that the "red herring" CDF has not been touched. */
EXPECT_EQ(0, BLI_file_size(cdf_in_subdir.c_str()));
BKE_preferences_asset_library_remove(&U, asset_lib_pref);
BKE_asset_library_custom_remove(&U.asset_libraries, asset_lib_pref);
}
};

View File

@@ -0,0 +1,69 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*
* API to manage a list of #CustomAssetLibraryDefinition items.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "BLI_compiler_attrs.h"
#include "BLI_utildefines.h"
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_from_index(
const struct ListBase *custom_libraries, int index) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
struct CustomAssetLibraryDefinition *BKE_asset_library_custom_find_from_name(
const struct ListBase *custom_libraries, const char *name)
ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
/**
* Return the #CustomAssetLibraryDefinition that contains the given file/directory path. The given
* path can be the library's top-level directory, or any path inside that directory.
*
* When more than one asset libraries match, the first matching one is returned (no smartness when
* there nested asset libraries).
*
* Return NULL when no such asset library is found. */
struct CustomAssetLibraryDefinition *BKE_asset_library_custom_containing_path(
const struct ListBase *custom_libraries, const char *path)
ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
int BKE_asset_library_custom_get_index(
const struct ListBase /*#CustomAssetLibraryDefinition*/ *custom_libraries,
const struct CustomAssetLibraryDefinition *library) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,75 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#pragma once
#include "DNA_space_types.h"
#include "BLI_compiler_attrs.h"
#ifdef __cplusplus
extern "C" {
#endif
/* C-handle for #bke::BlenderProject. */
typedef struct BlenderProject BlenderProject;
/** See #bke::ProjectSettings::create_settings_directory(). */
bool BKE_project_create_settings_directory(const char *project_root_path) ATTR_NONNULL();
/** See #bke::ProjectSettings::delete_settings_directory(). */
bool BKE_project_delete_settings_directory(BlenderProject *project) ATTR_NONNULL();
BlenderProject *BKE_project_active_get(void) ATTR_WARN_UNUSED_RESULT;
/**
* \note: When unsetting an active project, the previously active one will be destroyed, so
* pointers may dangle.
*/
void BKE_project_active_unset(void);
/**
* Check if \a path references a project root directory. Will return false for paths pointing into
* the project root directory.
*/
bool BKE_project_is_path_project_root(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* 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).
*/
bool BKE_project_contains_path(const char *path) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
/**
* Attempt to load and activate a project based on the given path. If the path doesn't lead
* 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.
*/
BlenderProject *BKE_project_active_load_from_path(const char *path) ATTR_NONNULL();
bool BKE_project_settings_save(const BlenderProject *project) ATTR_NONNULL();
const char *BKE_project_root_path_get(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
/**
* \param name The new name to set, expected to be 0 terminated.
*/
void BKE_project_name_set(const BlenderProject *project_handle, const char *name) ATTR_NONNULL();
const char *BKE_project_name_get(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT
ATTR_NONNULL();
ListBase *BKE_project_custom_asset_libraries_get(const BlenderProject *project)
ATTR_WARN_UNUSED_RESULT ATTR_NONNULL();
void BKE_project_tag_has_unsaved_changes(const BlenderProject *project) ATTR_NONNULL();
/**
* Check if the project is marked as having unsaved changes. For convenience this allows passing
* null as the project (returns false then), so a call like
* `BKE_project_has_unsaved_changes(CTX_wm_project())` can be done without having to null-check the
* project first.
*/
bool BKE_project_has_unsaved_changes(const BlenderProject *project) ATTR_WARN_UNUSED_RESULT;
#ifdef __cplusplus
}
#endif

View File

@@ -0,0 +1,204 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#pragma once
#include <memory>
#include "BLI_listbase.h"
#include "BLI_string_ref.hh"
#include "BLI_utility_mixins.hh"
struct BlenderProject;
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:
static auto get_active [[nodiscard]] () -> BlenderProject *;
static auto set_active(std::unique_ptr<BlenderProject> settings) -> BlenderProject *;
/**
* 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 auto load_from_path(StringRef project_path) -> std::unique_ptr<BlenderProject>;
/**
* 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 auto create_settings_directory(StringRef project_root_path) -> bool;
/**
* 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 auto delete_settings_directory(StringRef project_root_path) -> bool;
/**
* Check if the directory given by \a path contains a .blender_project directory and should thus
* be considered a project root directory.
*/
static auto path_is_project_root(StringRef path) -> bool;
/**
* 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.
*/
static auto project_root_path_find_from_path [[nodiscard]] (StringRef path) -> StringRef;
/* --- Non-static member functions. --- */
BlenderProject(StringRef project_root_path, std::unique_ptr<ProjectSettings> 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.
*/
auto delete_settings_directory() -> bool;
auto root_path [[nodiscard]] () const -> StringRefNull;
auto get_settings [[nodiscard]] () const -> ProjectSettings &;
private:
static auto active_project_ptr() -> std::unique_ptr<BlenderProject> &;
/**
* 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.
*/
static auto project_path_to_native_project_root_path(StringRef project_path) -> std::string;
/**
* 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.
*/
static auto project_root_path_to_settings_path(StringRef project_root_path) -> std::string;
/**
* Returns the path with native slashes.
* Assumes the path already ends with a native trailing slash.
*/
static auto project_root_path_to_settings_filepath(StringRef project_root_path) -> std::string;
};
/**
* 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.
*/
static auto load_from_disk [[nodiscard]] (StringRef project_path)
-> std::unique_ptr<ProjectSettings>;
/**
* 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.
*/
static auto load_from_path [[nodiscard]] (StringRef path) -> std::unique_ptr<ProjectSettings>;
/** 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.
*/
auto save_to_disk(StringRef project_path) -> bool;
void project_name(StringRef new_name);
auto project_name [[nodiscard]] () const -> StringRefNull;
auto asset_library_definitions() const -> const ListBase &;
auto asset_library_definitions() -> ListBase &;
/**
* 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 settings 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 settings 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().
*/
auto has_unsaved_changes [[nodiscard]] () const -> bool;
private:
auto to_dictionary() const -> std::unique_ptr<io::serialize::DictionaryValue>;
};
} // namespace blender::bke
inline ::BlenderProject *BKE_project_c_handle(blender::bke::BlenderProject *project)
{
return reinterpret_cast<::BlenderProject *>(project);
}

View File

@@ -27,6 +27,7 @@ extern "C" {
struct ARegion;
struct Base;
struct BlenderProject;
struct CacheFile;
struct Collection;
struct Depsgraph;
@@ -186,6 +187,7 @@ struct ARegion *CTX_wm_menu(const bContext *C);
struct wmGizmoGroup *CTX_wm_gizmo_group(const bContext *C);
struct wmMsgBus *CTX_wm_message_bus(const bContext *C);
struct ReportList *CTX_wm_reports(const bContext *C);
struct BlenderProject *CTX_wm_project(void);
struct View3D *CTX_wm_view3d(const bContext *C);
struct RegionView3D *CTX_wm_region_view3d(const bContext *C);
@@ -205,6 +207,7 @@ struct SpaceUserPref *CTX_wm_space_userpref(const bContext *C);
struct SpaceClip *CTX_wm_space_clip(const bContext *C);
struct SpaceTopBar *CTX_wm_space_topbar(const bContext *C);
struct SpaceSpreadsheet *CTX_wm_space_spreadsheet(const bContext *C);
struct SpaceProjectSettings *CTX_wm_space_project_settings(const bContext *C);
void CTX_wm_manager_set(bContext *C, struct wmWindowManager *wm);
void CTX_wm_window_set(bContext *C, struct wmWindow *win);

View File

@@ -13,55 +13,11 @@ extern "C" {
#include "BLI_compiler_attrs.h"
struct UserDef;
struct bUserAssetLibrary;
/** 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 *path) 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_from_index(
const struct UserDef *userdef, int index) ATTR_NONNULL() ATTR_WARN_UNUSED_RESULT;
struct bUserAssetLibrary *BKE_preferences_asset_library_find_from_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();
#ifdef __cplusplus
}

View File

@@ -72,12 +72,15 @@ set(SRC
intern/armature_selection.cc
intern/armature_update.c
intern/asset.cc
intern/asset_library_custom.cc
intern/attribute.cc
intern/attribute_access.cc
intern/attribute_math.cc
intern/autoexec.c
intern/blender.c
intern/blender_copybuffer.c
intern/blender_project.cc
intern/blender_project_settings.cc
intern/blender_undo.cc
intern/blender_user_menu.c
intern/blendfile.cc
@@ -321,12 +324,15 @@ set(SRC
BKE_armature.h
BKE_armature.hh
BKE_asset.h
BKE_asset_library_custom.h
BKE_attribute.h
BKE_attribute.hh
BKE_attribute_math.hh
BKE_autoexec.h
BKE_blender.h
BKE_blender_copybuffer.h
BKE_blender_project.h
BKE_blender_project.hh
BKE_blender_undo.h
BKE_blender_user_menu.h
BKE_blender_version.h
@@ -824,7 +830,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
@@ -842,6 +848,7 @@ if(WITH_GTESTS)
)
set(TEST_INC
../editors/include
../blenloader/tests
)
include(GTestTesting)
blender_add_test_lib(bf_blenkernel_tests "${TEST_SRC}" "${INC};${TEST_INC}" "${INC_SYS}" "${LIB}")

View File

@@ -0,0 +1,113 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include <string.h>
#include "MEM_guardedalloc.h"
#include "BLI_fileops.h"
#include "BLI_listbase.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
#include "BKE_appdir.h"
#include "BLT_translation.h"
#include "DNA_asset_types.h"
#include "DNA_userdef_types.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 *path)
{
CustomAssetLibraryDefinition *library = MEM_cnew<CustomAssetLibraryDefinition>(
"CustomAssetLibraryDefinition");
BLI_addtail(custom_libraries, library);
if (name) {
BKE_asset_library_custom_name_set(custom_libraries, library, name);
}
if (path) {
BLI_strncpy(library->path, path, sizeof(library->path));
}
library->import_method = ASSET_IMPORT_APPEND_REUSE;
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)
{
BLI_strncpy_utf8(library->name, name, sizeof(library->name));
BLI_uniquename(custom_libraries,
library,
name,
'.',
offsetof(CustomAssetLibraryDefinition, name),
sizeof(library->name));
}
void BKE_asset_library_custom_path_set(CustomAssetLibraryDefinition *library, const char *path)
{
BLI_strncpy(library->path, path, sizeof(library->path));
}
CustomAssetLibraryDefinition *BKE_asset_library_custom_find_from_index(
const ListBase *custom_libraries, int index)
{
return static_cast<CustomAssetLibraryDefinition *>(BLI_findlink(custom_libraries, index));
}
CustomAssetLibraryDefinition *BKE_asset_library_custom_find_from_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 *path)
{
LISTBASE_FOREACH (CustomAssetLibraryDefinition *, asset_lib_pref, custom_libraries) {
if (BLI_path_contains(asset_lib_pref->path, path)) {
return asset_lib_pref;
}
}
return NULL;
}
int BKE_asset_library_custom_get_index(const ListBase *custom_libraries,
const CustomAssetLibraryDefinition *library)
{
return BLI_findindex(custom_libraries, library);
}
/** \} */

View File

@@ -0,0 +1,311 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup bke
*/
#include "BKE_asset_library_custom.h"
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_fileops.h"
#include "BKE_blender_project.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));
}
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::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::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_;
}
/** \} */
/* ---------------------------------------------------------------------- */
/** \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());
}
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
/* ---------------------------------------------------------------------- */
/** \name C-API
* \{ */
using namespace blender;
bool BKE_project_create_settings_directory(const char *project_root_path)
{
return bke::BlenderProject::create_settings_directory(project_root_path);
}
bool BKE_project_delete_settings_directory(BlenderProject *project_handle)
{
bke::BlenderProject *project = reinterpret_cast<bke::BlenderProject *>(project_handle);
return project->delete_settings_directory();
}
BlenderProject *BKE_project_active_get(void)
{
return reinterpret_cast<BlenderProject *>(bke::BlenderProject::get_active());
}
void BKE_project_active_unset(void)
{
bke::BlenderProject::set_active(nullptr);
}
bool BKE_project_is_path_project_root(const char *path)
{
return bke::BlenderProject::path_is_project_root(path);
}
bool BKE_project_contains_path(const char *path)
{
const StringRef found_root_path = bke::BlenderProject::project_root_path_find_from_path(path);
return !found_root_path.is_empty();
}
BlenderProject *BKE_project_active_load_from_path(const char *path)
{
/* Project should be unset if the path doesn't contain a project root. Unset in the beginning so
* early exiting behaves correctly. */
BKE_project_active_unset();
std::unique_ptr<bke::BlenderProject> project = bke::BlenderProject::load_from_path(path);
return reinterpret_cast<::BlenderProject *>(bke::BlenderProject::set_active(std::move(project)));
}
bool BKE_project_settings_save(const BlenderProject *project_handle)
{
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
project_handle);
bke::ProjectSettings &settings = project->get_settings();
return settings.save_to_disk(project->root_path());
}
const char *BKE_project_root_path_get(const BlenderProject *project_handle)
{
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
project_handle);
return project->root_path().c_str();
}
void BKE_project_name_set(const BlenderProject *project_handle, const char *name)
{
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
project_handle);
project->get_settings().project_name(name);
}
const char *BKE_project_name_get(const BlenderProject *project_handle)
{
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
project_handle);
return project->get_settings().project_name().c_str();
}
ListBase *BKE_project_custom_asset_libraries_get(const BlenderProject *project_handle)
{
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
project_handle);
bke::ProjectSettings &settings = project->get_settings();
return &settings.asset_library_definitions();
}
void BKE_project_tag_has_unsaved_changes(const BlenderProject *project_handle)
{
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
project_handle);
bke::ProjectSettings &settings = project->get_settings();
settings.tag_has_unsaved_changes();
}
bool BKE_project_has_unsaved_changes(const BlenderProject *project_handle)
{
if (!project_handle) {
return false;
}
const bke::BlenderProject *project = reinterpret_cast<const bke::BlenderProject *>(
project_handle);
const bke::ProjectSettings &settings = project->get_settings();
return settings.has_unsaved_changes();
}
/** \} */

View File

@@ -0,0 +1,318 @@
/* 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_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 &&);
~CustomAssetLibraries();
auto operator=(CustomAssetLibraries &&) -> CustomAssetLibraries &;
};
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->path));
asset_libs_elements.append_as(std::move(library_dict));
}
root_attributes.append_as("asset_libraries", std::move(asset_libs_array));
}
}
return root;
}
static void write_settings_file(StringRef settings_filepath,
std::unique_ptr<serialize::DictionaryValue> dictionary)
{
using namespace serialize;
JsonFormatter formatter;
std::ofstream os;
os.open(settings_filepath, std::ios::out | std::ios::trunc);
formatter.serialize(os, *dictionary);
os.close();
}
/** \} */
/* ---------------------------------------------------------------------- */
std::unique_ptr<ProjectSettings> ProjectSettings::load_from_disk(StringRef project_path)
{
const std::string project_root_path = BlenderProject::project_path_to_native_project_root_path(
project_path);
if (!BLI_exists(project_root_path.c_str())) {
return nullptr;
}
if (!BlenderProject::path_is_project_root(project_root_path.c_str())) {
return nullptr;
}
const std::string settings_filepath = BlenderProject::project_root_path_to_settings_filepath(
project_root_path);
std::unique_ptr<serialize::Value> values = read_settings_file(settings_filepath);
std::unique_ptr<ExtractedSettings> extracted_settings = nullptr;
if (values) {
BLI_assert(values->as_dictionary_value() != nullptr);
extracted_settings = extract_settings(*values->as_dictionary_value());
}
std::unique_ptr loaded_settings = std::make_unique<ProjectSettings>();
if (extracted_settings) {
loaded_settings->project_name_ = extracted_settings->project_name;
/* Moves ownership. */
loaded_settings->asset_libraries_ = std::make_unique<CustomAssetLibraries>(
extracted_settings->asset_libraries);
}
return loaded_settings;
}
std::unique_ptr<ProjectSettings> ProjectSettings::load_from_path(StringRef path)
{
StringRef project_root = BlenderProject::project_root_path_find_from_path(path);
if (project_root.is_empty()) {
return nullptr;
}
return bke::ProjectSettings::load_from_disk(project_root);
}
bool ProjectSettings::save_to_disk(StringRef project_path)
{
const std::string project_root_path = BlenderProject::project_path_to_native_project_root_path(
project_path);
if (!BLI_exists(project_root_path.c_str())) {
return false;
}
if (!BlenderProject::path_is_project_root(project_root_path.c_str())) {
return false;
}
const std::string settings_filepath = BlenderProject::project_root_path_to_settings_filepath(
project_root_path);
std::unique_ptr settings_as_dict = to_dictionary();
write_settings_file(settings_filepath, std::move(settings_as_dict));
has_unsaved_changes_ = false;
return true;
}
/* ---------------------------------------------------------------------- */
ProjectSettings::ProjectSettings() : asset_libraries_(std::make_unique<CustomAssetLibraries>())
{
}
ProjectSettings::~ProjectSettings() = default;
void ProjectSettings::project_name(StringRef new_name)
{
project_name_ = new_name;
has_unsaved_changes_ = true;
}
StringRefNull ProjectSettings::project_name() const
{
return project_name_;
}
const ListBase &ProjectSettings::asset_library_definitions() const
{
return asset_libraries_->asset_libraries;
}
ListBase &ProjectSettings::asset_library_definitions()
{
return asset_libraries_->asset_libraries;
}
void ProjectSettings::tag_has_unsaved_changes()
{
has_unsaved_changes_ = true;
}
bool ProjectSettings::has_unsaved_changes() const
{
return has_unsaved_changes_;
}
} // namespace blender::bke

View File

@@ -0,0 +1,325 @@
/* SPDX-License-Identifier: GPL-2.0-or-later
* Copyright 2022 Blender Foundation. All rights reserved. */
#include "BKE_appdir.h"
#include "BKE_blender_project.h"
#include "BKE_blender_project.hh"
#include "BKE_main.h"
#include "BLI_fileops.h"
#include "BLI_function_ref.hh"
#include "BLI_path_util.h"
#include "BLI_vector.hh"
#include "BLO_readfile.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_project_active_unset();
}
};
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->path, "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->path, "новый\\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_project_active_get(), nullptr);
if (!blendfile_load("blender_project/the_project/some_project_file.blend")) {
FAIL();
}
::BlenderProject *svn_project = BKE_project_active_load_from_path(bfile->main->filepath);
EXPECT_NE(svn_project, nullptr);
EXPECT_EQ(BKE_project_active_get(), svn_project);
EXPECT_STREQ("Ružena", BKE_project_name_get(svn_project));
/* Note: The project above will be freed once a different active project is set. So get the path
* for future comparisons. */
std::string svn_project_path = BKE_project_root_path_get(svn_project);
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();
}
::BlenderProject *svn_project_from_nested = BKE_project_active_load_from_path(
bfile->main->filepath);
EXPECT_NE(svn_project_from_nested, nullptr);
EXPECT_EQ(BKE_project_active_get(), svn_project_from_nested);
EXPECT_STREQ(svn_project_path.c_str(), BKE_project_root_path_get(svn_project_from_nested));
EXPECT_STREQ("Ružena", BKE_project_name_get(svn_project_from_nested));
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_project_active_load_from_path(bfile->main->filepath);
EXPECT_EQ(BKE_project_active_get(), 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);
::BlenderProject *project = BKE_project_active_load_from_path(project_path.c_str());
EXPECT_NE(project, nullptr);
EXPECT_FALSE(BKE_project_has_unsaved_changes(project));
std::string project_settings_dir = project_path_native + SEP_STR +
BlenderProject::SETTINGS_DIRNAME;
EXPECT_TRUE(BLI_exists(project_settings_dir.c_str()));
if (!BKE_project_delete_settings_directory(project)) {
FAIL();
}
/* Runtime project should still exist, but with unsaved changes. */
EXPECT_NE(project, nullptr);
EXPECT_TRUE(BKE_project_has_unsaved_changes(project));
EXPECT_FALSE(BLI_exists(project_settings_dir.c_str()));
});
}
} // namespace blender::bke::tests

View File

@@ -30,6 +30,7 @@
#include "BKE_addon.h"
#include "BKE_appdir.h"
#include "BKE_blender.h"
#include "BKE_blender_project.h"
#include "BKE_blender_version.h"
#include "BKE_blendfile.h"
#include "BKE_bpath.h"
@@ -455,6 +456,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) {
BKE_project_active_load_from_path(bfd->main->filepath);
}
}
static void handle_subversion_warning(Main *main, BlendFileReadReport *reports)
{
if (main->minversionfile > BLENDER_FILE_VERSION ||
@@ -481,6 +492,7 @@ void BKE_blendfile_read_setup_ex(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, reports);
BLO_blendfiledata_free(bfd);
}
@@ -695,7 +707,7 @@ UserDef *BKE_blendfile_userdef_from_defaults(void)
/* Default studio light. */
BKE_studiolight_default(userdef->light_param, userdef->light_ambient);
BKE_preferences_asset_library_default_add(userdef);
BKE_preferences_custom_asset_library_default_add(userdef);
return userdef;
}

View File

@@ -30,6 +30,7 @@
#include "BLT_translation.h"
#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BKE_layer.h"
#include "BKE_main.h"
@@ -763,6 +764,11 @@ ReportList *CTX_wm_reports(const bContext *C)
return nullptr;
}
BlenderProject *CTX_wm_project(void)
{
return BKE_project_active_get();
}
View3D *CTX_wm_view3d(const bContext *C)
{
ScrArea *area = CTX_wm_area(C);
@@ -929,6 +935,15 @@ SpaceSpreadsheet *CTX_wm_space_spreadsheet(const bContext *C)
return nullptr;
}
SpaceProjectSettings *CTX_wm_space_project_settings(const bContext *C)
{
ScrArea *area = CTX_wm_area(C);
if (area && area->spacetype == SPACE_PROJECT_SETTINGS) {
return static_cast<SpaceProjectSettings *>(area->spacedata.first);
}
return nullptr;
}
void CTX_wm_manager_set(bContext *C, wmWindowManager *wm)
{
C->wm.manager = wm;

View File

@@ -2,28 +2,17 @@
/** \file
* \ingroup bke
*
* User defined asset library API.
*/
#include <string.h>
#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"
#include "BLI_string_utf8.h"
#include "BLI_string_utils.h"
#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_userdef_types.h"
#define U BLI_STATIC_ASSERT(false, "Global 'U' not allowed, only use arguments passed in!")
@@ -32,80 +21,7 @@
/** \name Asset Libraries
* \{ */
bUserAssetLibrary *BKE_preferences_asset_library_add(UserDef *userdef,
const char *name,
const char *path)
{
bUserAssetLibrary *library = MEM_callocN(sizeof(*library), "bUserAssetLibrary");
BLI_addtail(&userdef->asset_libraries, library);
if (name) {
BKE_preferences_asset_library_name_set(userdef, library, name);
}
if (path) {
BLI_strncpy(library->path, path, sizeof(library->path));
}
library->import_method = ASSET_IMPORT_APPEND_REUSE;
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)
{
BLI_strncpy_utf8(library->name, name, sizeof(library->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)
{
BLI_strncpy(library->path, path, sizeof(library->path));
if (BLI_is_file(library->path)) {
BLI_path_parent_dir(library->path);
}
}
bUserAssetLibrary *BKE_preferences_asset_library_find_from_index(const UserDef *userdef, int index)
{
return BLI_findlink(&userdef->asset_libraries, index);
}
bUserAssetLibrary *BKE_preferences_asset_library_find_from_name(const UserDef *userdef,
const char *name)
{
return 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->path, path)) {
return asset_lib_pref;
}
}
return NULL;
}
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];
@@ -114,8 +30,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), NULL);
CustomAssetLibraryDefinition *library = BKE_asset_library_custom_add(
&userdef->asset_libraries, DATA_(BKE_PREFS_ASSET_LIBRARY_DEFAULT_NAME), NULL);
/* Add new "Default" library under '[doc_path]/Blender/Assets'. */
BLI_path_join(library->path, sizeof(library->path), documents_path, N_("Blender"), N_("Assets"));

View File

@@ -2675,6 +2675,7 @@ static void lib_link_workspace_layout_restore(IDNameLib_Map *id_map,
case SPACE_USERPREF:
case SPACE_TOPBAR:
case SPACE_STATUSBAR:
case SPACE_PROJECT_SETTINGS:
case SPACE_EMPTY:
/* Nothing to do here. */
break;

View File

@@ -22,6 +22,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"
@@ -100,6 +101,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
@@ -680,7 +682,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);
}
}
@@ -733,11 +735,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);
}
}
}
@@ -774,7 +776,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;
}
}

View File

@@ -94,6 +94,8 @@
#include "BLI_threads.h"
#include "MEM_guardedalloc.h" /* MEM_freeN */
#include "BKE_blender_project.h"
#include "BKE_blender_project.hh"
#include "BKE_blender_version.h"
#include "BKE_bpath.h"
#include "BKE_global.h" /* for G */
@@ -922,8 +924,9 @@ static void write_userdef(BlendWriter *writer, const UserDef *userdef)
BLO_write_struct(writer, bPathCompare, path_cmp);
}
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 uiStyle *, style, &userdef->uistyles) {
@@ -1506,6 +1509,11 @@ bool BLO_write_file(Main *mainvar,
}
}
/* Update active project information based on the new file location. */
if (U.experimental.use_blender_projects) {
BKE_project_active_load_from_path(filepath);
}
/* actual file writing */
const bool err = write_file_handle(
mainvar, &ww, nullptr, nullptr, write_flags, use_userdef, thumb);

View File

@@ -21,6 +21,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)
@@ -37,6 +38,7 @@ if(WITH_BLENDER)
add_subdirectory(space_nla)
add_subdirectory(space_node)
add_subdirectory(space_outliner)
add_subdirectory(space_project_settings)
add_subdirectory(space_script)
add_subdirectory(space_sequencer)
add_subdirectory(space_spreadsheet)

View File

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

View File

@@ -4,8 +4,15 @@
* \ingroup edasset
*/
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "DNA_userdef_types.h"
#include "BLI_hash.hh"
#include "ED_asset_library.h"
#include "asset_library_reference.hh"
namespace blender::ed::asset {
@@ -18,14 +25,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;
}
@@ -34,3 +42,25 @@ uint64_t AssetLibraryReferenceWrapper::hash() const
}
} // namespace blender::ed::asset
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_from_index(&U.asset_libraries,
library_ref->custom_library_index);
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT: {
BlenderProject *project = CTX_wm_project();
if (project) {
return BKE_asset_library_custom_find_from_index(
BKE_project_custom_asset_libraries_get(project), library_ref->custom_library_index);
}
break;
}
case ASSET_LIBRARY_LOCAL:
return nullptr;
}
return nullptr;
}

View File

@@ -11,7 +11,9 @@
#include "BLI_listbase.h"
#include "BKE_preferences.h"
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "DNA_userdef_types.h"
@@ -23,17 +25,15 @@
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_from_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;
@@ -44,32 +44,63 @@ 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_from_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->path[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->path[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->path[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->path};
RNA_enum_item_add(items, totitem, &tmp);
}
}
const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(const bool include_generated)
{
EnumPropertyItem *item = nullptr;
@@ -97,29 +128,21 @@ const EnumPropertyItem *ED_asset_library_reference_to_rna_enum_itemf(const bool
RNA_enum_items_add(&item, &totitem, generated_items);
}
/* Add separator if needed. */
if (!BLI_listbase_is_empty(&U.asset_libraries)) {
BlenderProject *project = CTX_wm_project();
if (project && !BLI_listbase_is_empty(BKE_project_custom_asset_libraries_get(project))) {
RNA_enum_item_add_separator(&item, &totitem);
add_custom_asset_library_enum_items(BKE_project_custom_asset_libraries_get(project),
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->path[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->path};
RNA_enum_item_add(&item, &totitem, &tmp);
add_custom_asset_library_enum_items(
&U.asset_libraries, ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES, &item, &totitem);
}
RNA_enum_item_end(&item, &totitem);

View File

@@ -14,15 +14,15 @@
#include "AS_asset_library.hh"
#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BLI_map.hh"
#include "BLI_path_util.h"
#include "BLI_utility_mixins.hh"
#include "DNA_space_types.h"
#include "BKE_preferences.h"
#include "WM_api.h"
/* XXX uses private header of file-space. */
@@ -31,6 +31,7 @@
#include "ED_asset_handle.h"
#include "ED_asset_indexer.h"
#include "ED_asset_library.h"
#include "ED_asset_list.h"
#include "ED_asset_list.hh"
#include "asset_library_reference.hh"
@@ -373,7 +374,8 @@ 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:
return FILE_ASSET_LIBRARY;
case ASSET_LIBRARY_LOCAL:
return FILE_MAIN_ASSET;

View File

@@ -7,11 +7,11 @@
#include "AS_asset_library.h"
#include "AS_asset_library.hh"
#include "BKE_asset_library_custom.h"
#include "BKE_bpath.h"
#include "BKE_context.h"
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_preferences.h"
#include "BKE_report.h"
#include "BLI_fileops.h" /* MSVC needs this for `PATH_MAX` */
@@ -679,7 +679,7 @@ static void ASSET_OT_catalogs_save(struct wmOperatorType *ot)
/* -------------------------------------------------------------------- */
static bool could_be_asset_bundle(const Main *bmain);
static const bUserAssetLibrary *selected_asset_library(struct wmOperator *op);
static const CustomAssetLibraryDefinition *selected_asset_library(struct wmOperator *op);
static bool is_contained_in_selected_asset_library(struct wmOperator *op, const char *filepath);
static bool set_filepath_for_asset_lib(const Main *bmain, struct wmOperator *op);
static bool has_external_files(Main *bmain, struct ReportList *reports);
@@ -701,8 +701,8 @@ static bool asset_bundle_install_poll(bContext *C)
}
/* Check whether this file is already located inside any asset library. */
const struct bUserAssetLibrary *asset_lib = BKE_preferences_asset_library_containing_path(
&U, bmain->filepath);
const struct CustomAssetLibraryDefinition *asset_lib = BKE_asset_library_custom_containing_path(
&U.asset_libraries, bmain->filepath);
if (asset_lib) {
return false;
}
@@ -773,7 +773,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,
@@ -833,18 +833,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(struct wmOperator *op)
static const CustomAssetLibraryDefinition *selected_asset_library(struct wmOperator *op)
{
const int enum_value = RNA_enum_get(op->ptr, "asset_library_ref");
const AssetLibraryReference lib_ref = ED_asset_library_reference_from_enum_value(enum_value);
const bUserAssetLibrary *lib = BKE_preferences_asset_library_find_from_index(
&U, lib_ref.custom_library_index);
const CustomAssetLibraryDefinition *lib = BKE_asset_library_custom_find_from_index(
&U.asset_libraries, lib_ref.custom_library_index);
return lib;
}
static bool is_contained_in_selected_asset_library(struct wmOperator *op, const char *filepath)
{
const bUserAssetLibrary *lib = selected_asset_library(op);
const CustomAssetLibraryDefinition *lib = selected_asset_library(op);
if (!lib) {
return false;
}
@@ -858,7 +858,7 @@ static bool is_contained_in_selected_asset_library(struct wmOperator *op, const
static bool set_filepath_for_asset_lib(const Main *bmain, struct wmOperator *op)
{
/* Find the directory path of the selected asset library. */
const bUserAssetLibrary *lib = selected_asset_library(op);
const CustomAssetLibraryDefinition *lib = selected_asset_library(op);
if (lib == nullptr) {
return false;
}

View File

@@ -0,0 +1,40 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup editors
*
* Editor level functions for Blender projects.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
struct BlenderProject;
struct Main;
struct ReportList;
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(struct 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);
#ifdef __cplusplus
}
#endif

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,84 @@
/* 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.h"
#include "BKE_blender_project.hh"
#include "BKE_context.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_project.h"
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(BlenderProject *project)
{
char project_root_dir[FILE_MAXDIR];
BLI_strncpy(project_root_dir, BKE_project_root_path_get(project), sizeof(project_root_dir));
/* Set directory name as default project name. */
char dirname[FILE_MAXFILE];
BLI_path_slash_rstrip(project_root_dir);
BLI_split_file_part(project_root_dir, dirname, sizeof(dirname));
BKE_project_name_set(project, dirname);
ListBase *libraries = BKE_project_custom_asset_libraries_get(project);
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_project_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) {
BlenderProject *loaded_project_c = BKE_project_c_handle(loaded_project.get());
ED_project_set_defaults(loaded_project_c);
/* Write defaults to the hard drive. */
BKE_project_settings_save(loaded_project_c);
}
BKE_reportf(reports, RPT_INFO, "Project created and loaded successfully");
const char *blend_path = BKE_main_blendfile_path(bmain);
const bool blend_is_in_project = blend_path[0] &&
BLI_path_contains(project_root_dir, blend_path);
if (blend_is_in_project) {
bke::BlenderProject::set_active(std::move(loaded_project));
}
else {
BKE_reportf(reports,
RPT_WARNING,
"The current file is not located inside of the new project. This means the new "
"project is not active");
}
return true;
}

View File

@@ -0,0 +1,298 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup edproject
*/
#include <climits>
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BLI_path_util.h"
#include "BLT_translation.h"
#include "DNA_space_types.h"
#include "RNA_access.h"
#include "RNA_define.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_project.h"
#include "ED_screen.h"
using namespace blender;
static bool has_active_project_poll(bContext *C)
{
const BlenderProject *active_project = CTX_wm_project();
CTX_wm_operator_poll_msg_set(C, TIP_("No active project loaded"));
return active_project != NULL;
}
/* -------------------------------------------------------------------- */
/** \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, NULL);
/* Update the window title. */
WM_main_add_notifier(NC_WM | ND_DATACHANGED, NULL);
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*/)
{
BlenderProject *active_project = CTX_wm_project();
if (!BKE_project_settings_save(active_project)) {
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)
{
if (!BKE_project_delete_settings_directory(CTX_wm_project())) {
BKE_report(op->reports,
RPT_ERROR,
"Failed to delete project settings. Is the project directory read-only?");
return OPERATOR_CANCELLED;
}
BKE_project_active_unset();
WM_main_add_notifier(NC_PROJECT, NULL);
/* Update the window title. */
WM_event_add_notifier_ex(CTX_wm_manager(C), CTX_wm_window(C), NC_WM | ND_DATACHANGED, NULL);
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)
{
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, BKE_project_root_path_get(project));
BLI_split_file_part(path, dirname, sizeof(dirname));
ListBase *asset_libraries = BKE_project_custom_asset_libraries_get(project);
/* NULL is a valid directory path here. A library without path will be created then. */
BKE_asset_library_custom_add(asset_libraries, dirname, path);
BKE_project_tag_has_unsaved_changes(project);
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIBRARY, NULL);
WM_main_add_notifier(NC_PROJECT, NULL);
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");
BlenderProject *project = CTX_wm_project();
ListBase *asset_libraries = BKE_project_custom_asset_libraries_get(project);
CustomAssetLibraryDefinition *library = BKE_asset_library_custom_find_from_index(asset_libraries,
index);
BKE_asset_library_custom_remove(asset_libraries, library);
BKE_project_tag_has_unsaved_changes(project);
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIBRARY, NULL);
WM_main_add_notifier(NC_PROJECT, NULL);
return OPERATOR_FINISHED;
}
/* Similar to #PREFERENCES_OT_asset_library_remove. */
static void PROJECT_OT_custom_asset_library_remove(wmOperatorType *ot)
{
ot->name = "Remove Asset Library";
ot->idname = "PROJECT_OT_custom_asset_library_remove";
ot->description =
"Unregister a path to a .blend file, so the Asset Browser will not attempt to show it "
"anymore";
ot->exec = custom_asset_library_remove_exec;
ot->poll = has_active_project_poll;
ot->flag = OPTYPE_INTERNAL;
RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "", 0, 1000);
}
/** \} */
void ED_operatortypes_project()
{
WM_operatortype_append(PROJECT_OT_new);
WM_operatortype_append(PROJECT_OT_save_settings);
WM_operatortype_append(PROJECT_OT_delete_setup);
WM_operatortype_append(PROJECT_OT_custom_asset_library_add);
WM_operatortype_append(PROJECT_OT_custom_asset_library_remove);
}

View File

@@ -5039,7 +5039,14 @@ static void SCREEN_OT_back_to_previous(struct 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(). */
@@ -5047,6 +5054,43 @@ static int userpref_show_exec(bContext *C, wmOperator *op)
int sizex = (500 + UI_NAVIGATION_REGION_WIDTH) * UI_DPI_FAC;
int sizey = 520 * UI_DPI_FAC;
/* changes context! */
if (WM_window_open(C,
window_title,
event->xy[0],
event->xy[1],
sizex,
sizey,
space_type,
false,
false,
true,
WIN_ALIGN_LOCATION_CENTER) != NULL) {
/* The header only contains the editor switcher and looks empty.
* So hiding in the temp window makes sense. */
ScrArea *area = CTX_wm_area(C);
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HEADER);
if (region) {
region->flag |= RGN_FLAG_HIDDEN;
ED_region_visibility_change_update(C, area, region);
}
/* And also show the region with "Load & Save" buttons. */
region = BKE_area_find_region_type(area, RGN_TYPE_EXECUTE);
if (region) {
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. */
@@ -5059,34 +5103,10 @@ static int userpref_show_exec(bContext *C, wmOperator *op)
RNA_property_update(C, &pref_ptr, active_section_prop);
}
/* changes context! */
if (WM_window_open(C,
IFACE_("Blender Preferences"),
event->xy[0],
event->xy[1],
sizex,
sizey,
SPACE_USERPREF,
false,
false,
true,
WIN_ALIGN_LOCATION_CENTER) != NULL) {
/* The header only contains the editor switcher and looks empty.
* So hiding in the temp window makes sense. */
ScrArea *area = CTX_wm_area(C);
ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_HEADER);
region->flag |= RGN_FLAG_HIDDEN;
ED_region_visibility_change_update(C, area, region);
/* And also show the region with "Load & Save" buttons. */
region = BKE_area_find_region_type(area, RGN_TYPE_EXECUTE);
region->flag &= ~RGN_FLAG_HIDDEN;
ED_region_visibility_change_update(C, area, region);
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;
}
@@ -5114,6 +5134,62 @@ static void SCREEN_OT_userpref_show(struct 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, &space_ptr);
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
* \{ */
@@ -5702,6 +5778,7 @@ void ED_operatortypes_screen(void)
WM_operatortype_append(SCREEN_OT_screenshot);
WM_operatortype_append(SCREEN_OT_screenshot_area);
WM_operatortype_append(SCREEN_OT_userpref_show);
WM_operatortype_append(SCREEN_OT_project_settings_show);
WM_operatortype_append(SCREEN_OT_drivers_editor_show);
WM_operatortype_append(SCREEN_OT_info_log_show);
WM_operatortype_append(SCREEN_OT_region_blend);

View File

@@ -22,6 +22,7 @@ set(SRC
set(LIB
bf_editor_geometry
bf_editor_project
bf_editor_space_action
bf_editor_space_buttons
bf_editor_space_clip
@@ -33,6 +34,7 @@ set(LIB
bf_editor_space_nla
bf_editor_space_node
bf_editor_space_outliner
bf_editor_space_project_settings
bf_editor_space_script
bf_editor_space_sequencer
bf_editor_space_spreadsheet

View File

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

View File

@@ -1157,13 +1157,17 @@ static void file_draw_invalid_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, NULL, &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",
@@ -1171,13 +1175,26 @@ static void file_draw_invalid_library_hint(const bContext *C,
ICON_PREFERENCES,
NULL,
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,
NULL);
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,
NULL,
sx + UI_UNIT_X + but_width + UI_UNIT_X,
sy - but_offset_y,
but_width,
UI_UNIT_Y,
NULL);
but_opptr = UI_but_operator_ptr_get(but);
RNA_enum_set(but_opptr, "section", PROJECT_SETTINGS_SECTION_ASSET_LIBRARIES);
UI_block_end(C, block);
UI_block_draw(C, block);
}

View File

@@ -48,6 +48,7 @@
#endif
#include "BKE_asset.h"
#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_icons.h"
@@ -55,12 +56,12 @@
#include "BKE_lib_id.h"
#include "BKE_main.h"
#include "BKE_main_idmap.h"
#include "BKE_preferences.h"
#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.h"
#include "ED_screen.h"
@@ -1052,12 +1053,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_from_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;
@@ -1198,6 +1205,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)))) {
if (FILENAME_IS_PARENT(file->relpath)) {
@@ -3045,6 +3056,12 @@ static int filelist_readjob_list_dir(FileListReadJob *job_params,
}
}
if ((entry->typeflag & FILE_TYPE_DIR) && !(entry->typeflag & FILE_TYPE_BLENDER)) {
if (BKE_project_is_path_project_root(target)) {
entry->typeflag |= FILE_TYPE_BLENDER_PROJECT;
}
}
#ifndef WIN32
/* Set linux-style dot files hidden too. */
if (is_hidden_dot_filename(entry->relpath, entry)) {

View File

@@ -24,6 +24,7 @@
#endif
#include "AS_asset_representation.hh"
#include "AS_asset_library.hh"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
@@ -41,13 +42,15 @@
#include "BLT_translation.h"
#include "BKE_appdir.h"
#include "BKE_asset_library_custom.h"
#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BKE_idtype.h"
#include "BKE_main.h"
#include "BKE_preferences.h"
#include "BLF_api.h"
#include "ED_asset_library.h"
#include "ED_fileselect.h"
#include "ED_screen.h"
@@ -416,37 +419,30 @@ 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_from_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);
BLI_strncpy(base_params->dir, root_path.c_str(), sizeof(base_params->dir));
switch (eAssetLibraryType(library->type)) {
case ASSET_LIBRARY_ESSENTIALS:
case ASSET_LIBRARY_ESSENTIALS:
BLI_strncpy(base_params->dir,
blender::asset_system::essentials_directory_path().c_str(),
sizeof(base_params->dir));
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);
BLI_strncpy(base_params->dir, user_library->path, sizeof(base_params->dir));
case ASSET_LIBRARY_CUSTOM_FROM_PREFERENCES:
case ASSET_LIBRARY_CUSTOM_FROM_PROJECT:
base_params->type = FILE_ASSET_LIBRARY;
break;
}

View File

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

View File

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

View File

@@ -0,0 +1,258 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
#include "BKE_screen.h"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLO_read_write.h"
#include "DNA_screen_types.h"
#include "DNA_space_types.h"
#include "ED_screen.h"
#include "ED_space_api.h"
#include "MEM_guardedalloc.h"
#include "RNA_access.h"
#include "RNA_enum_types.h"
#include "UI_interface.h"
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_DPI_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_HIDDEN;
}
{
/* 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(void)
{
}
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, NULL};
if (!CTX_wm_project()) {
/* Special context for when there is no project. UI can draw a special panel then. */
STRNCPY(id_lower, "no_project");
}
else {
/* Avoid duplicating identifiers, use existing RNA enum. */
const EnumPropertyItem *items = rna_enum_project_settings_section_items;
int i = RNA_enum_from_value(items, sproject_settings->active_section);
/* Enum value not found: File is from the future. */
if (i == -1) {
i = 0;
}
StringRefNull id = items[i].identifier;
BLI_assert(id.size() < (int64_t)sizeof(id_lower));
STRNCPY(id_lower, id.c_str());
BLI_str_tolower_ascii(id_lower, strlen(id_lower));
}
ED_region_panels_layout_ex(C, region, &region->type->paneltypes, contexts, NULL);
}
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 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->layout = ED_region_panels_layout;
art->draw = ED_region_panels_draw;
art->listener = project_settings_execute_region_listener;
BLI_addhead(&st->regiontypes, art);
BKE_spacetype_register(st);
}

View File

@@ -16,9 +16,9 @@
#endif
#include "BLI_path_util.h"
#include "BKE_asset_library_custom.h"
#include "BKE_context.h"
#include "BKE_main.h"
#include "BKE_preferences.h"
#include "BKE_report.h"
@@ -134,7 +134,8 @@ static int preferences_asset_library_add_exec(bContext *UNUSED(C), wmOperator *o
BLI_split_file_part(path, dirname, sizeof(dirname));
/* NULL 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;
@@ -158,6 +159,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";
@@ -196,23 +198,23 @@ static bool preferences_asset_library_remove_poll(bContext *C)
static int preferences_asset_library_remove_exec(bContext *UNUSED(C), wmOperator *op)
{
const int index = RNA_int_get(op->ptr, "index");
bUserAssetLibrary *library = BLI_findlink(&U.asset_libraries, index);
CustomAssetLibraryDefinition *library = 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, NULL);
return OPERATOR_FINISHED;
}
/* Similar to #PROJECT_OT_custom_asset_library_add. */
static void PREFERENCES_OT_asset_library_remove(wmOperatorType *ot)
{
ot->name = "Remove Asset Library";

View File

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

View File

@@ -2,6 +2,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
@@ -92,11 +95,12 @@ typedef enum eAssetLibraryType {
ASSET_LIBRARY_ESSENTIALS = 3,
/** 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 {
@@ -111,23 +115,36 @@ typedef enum eAssetImportMethod {
} eAssetImportMethod;
/**
* 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 path[1024]; /* FILE_MAX */
short import_method; /* eAssetImportMethod */
char _pad[6];
} CustomAssetLibraryDefinition;
/**
* To be replaced by #AssetRepresentation!
*

View File

@@ -1091,6 +1091,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),
@@ -1732,6 +1734,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
* \{ */
@@ -2076,9 +2102,10 @@ typedef enum eSpace_Type {
SPACE_CLIP = 20,
SPACE_TOPBAR = 21,
SPACE_STATUSBAR = 22,
SPACE_SPREADSHEET = 23
SPACE_SPREADSHEET = 23,
SPACE_PROJECT_SETTINGS = 24
#define SPACE_TYPE_NUM (SPACE_SPREADSHEET + 1)
#define SPACE_TYPE_NUM (SPACE_PROJECT_SETTINGS + 1)
} eSpace_Type;
/* use for function args */

View File

@@ -488,6 +488,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;
@@ -572,16 +573,6 @@ enum {
USER_MENU_TYPE_PROP = 4,
};
typedef struct bUserAssetLibrary {
struct bUserAssetLibrary *next, *prev;
char name[64]; /* MAX_NAME */
char path[1024]; /* FILE_MAX */
short import_method; /* eAssetImportMethod */
char _pad0[6];
} bUserAssetLibrary;
typedef struct SolidLight {
int flag;
float smooth;
@@ -658,7 +649,8 @@ typedef struct UserDef_Experimental {
char use_sculpt_texture_paint;
char enable_workbench_next;
char use_new_volume_nodes;
char _pad[6];
char use_blender_projects;
char _pad[5];
/** `makesdna` does not allow empty structs. */
} UserDef_Experimental;
@@ -778,7 +770,7 @@ typedef struct UserDef {
struct ListBase autoexec_paths;
/** #bUserMenu. */
struct ListBase user_menus;
/** #bUserAssetLibrary */
/** #CustomAssetLibraryDefinition */
struct ListBase asset_libraries;
char keyconfigstr[64];

View File

@@ -41,6 +41,7 @@ DNA_STRUCT_RENAME(Lamp, Light)
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)

View File

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

View File

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

View File

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

View File

@@ -20,6 +20,7 @@
# include "AS_asset_library.h"
# include "BKE_asset.h"
# include "BKE_asset_library_custom.h"
# include "BKE_context.h"
# include "BKE_idprop.h"
@@ -29,6 +30,9 @@
# include "ED_asset.h"
# include "ED_fileselect.h"
# include "WM_api.h"
# include "WM_types.h"
# include "RNA_access.h"
static char *rna_AssetMetaData_path(const PointerRNA *UNUSED(ptr))
@@ -266,6 +270,35 @@ void rna_AssetMetaData_catalog_id_update(struct bContext *C, struct PointerRNA *
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 *UNUSED(bmain),
Scene *UNUSED(scene),
PointerRNA *UNUSED(ptr))
{
WM_main_add_notifier(NC_ASSET | ND_ASSET_LIBRARY, NULL);
}
static PointerRNA rna_AssetHandle_file_data_get(PointerRNA *ptr)
{
AssetHandle *asset_handle = ptr->data;
@@ -497,6 +530,55 @@ 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_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");
}
PropertyRNA *rna_def_asset_library_reference_common(struct StructRNA *srna,
const char *get,
const char *set)
@@ -515,6 +597,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_catalog_path(brna);

View File

@@ -0,0 +1,158 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/** \file
* \ingroup RNA
*/
#include "RNA_define.h"
#include "rna_internal.h"
#ifdef RNA_RUNTIME
# include "BKE_asset_library_custom.h"
# include "BKE_blender_project.h"
# include "BLT_translation.h"
# include "WM_api.h"
static void rna_BlenderProject_update(Main *UNUSED(bmain),
Scene *UNUSED(scene),
PointerRNA *UNUSED(ptr))
{
/* TODO evaluate which props should send which notifiers. */
/* Force full redraw of all windows. */
WM_main_add_notifier(NC_WINDOW, NULL);
}
static void rna_BlenderProject_name_get(PointerRNA *ptr, char *value)
{
BlenderProject *project = ptr->data;
if (!project) {
value[0] = '\0';
return;
}
strcpy(value, BKE_project_name_get(project));
}
static int rna_BlenderProject_name_length(PointerRNA *ptr)
{
BlenderProject *project = ptr->data;
if (!project) {
return 0;
}
return strlen(BKE_project_name_get(project));
}
static void rna_BlenderProject_name_set(PointerRNA *ptr, const char *value)
{
BlenderProject *project = ptr->data;
if (!project) {
return;
}
BKE_project_name_set(project, value);
}
static void rna_BlenderProject_root_path_get(PointerRNA *ptr, char *value)
{
BlenderProject *project = ptr->data;
if (!project) {
value[0] = '\0';
return;
}
strcpy(value, BKE_project_root_path_get(project));
}
static int rna_BlenderProject_root_path_length(PointerRNA *ptr)
{
BlenderProject *project = ptr->data;
if (!project) {
return 0;
}
return strlen(BKE_project_root_path_get(project));
}
static void rna_BlenderProject_root_path_set(PointerRNA *UNUSED(ptr), const char *UNUSED(value))
{
/* Property is not editable, see #rna_BlenderProject_root_path_editable(). */
BLI_assert_unreachable();
}
static int rna_BlenderProject_root_path_editable(PointerRNA *UNUSED(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)
{
BlenderProject *project = ptr->data;
ListBase *asset_libraries = BKE_project_custom_asset_libraries_get(project);
rna_iterator_listbase_begin(iter, asset_libraries, NULL);
}
static bool rna_BlenderProject_is_dirty_get(PointerRNA *ptr)
{
const BlenderProject *project = ptr->data;
return BKE_project_has_unsaved_changes(project);
}
#else
void RNA_def_blender_project(BlenderRNA *brna)
{
StructRNA *srna = RNA_def_struct(brna, "BlenderProject", NULL);
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",
NULL,
NULL,
NULL,
NULL);
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", NULL);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_ui_text(
prop,
"Dirty",
"Project settings have changed since read from disk. Save the settings to keep them");
}
#endif

View File

@@ -203,6 +203,15 @@ static PointerRNA rna_Context_preferences_get(PointerRNA *UNUSED(ptr))
return newptr;
}
static PointerRNA rna_Context_project_get(PointerRNA *UNUSED(ptr))
{
struct BlenderProject *project = CTX_wm_project();
PointerRNA newptr;
RNA_pointer_create(NULL, &RNA_BlenderProject, project, &newptr);
return newptr;
}
static int rna_Context_mode_get(PointerRNA *ptr)
{
bContext *C = (bContext *)ptr->data;
@@ -338,6 +347,11 @@ void RNA_def_context(BlenderRNA *brna)
RNA_def_property_struct_type(prop, "Preferences");
RNA_def_property_pointer_funcs(prop, "rna_Context_preferences_get", NULL, NULL, NULL);
prop = RNA_def_property(srna, "project", PROP_POINTER, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_struct_type(prop, "BlenderProject");
RNA_def_property_pointer_funcs(prop, "rna_Context_project_get", NULL, NULL, NULL);
prop = RNA_def_property(srna, "mode", PROP_ENUM, PROP_NONE);
RNA_def_property_enum_items(prop, rna_enum_context_mode_items);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);

View File

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

View File

@@ -186,6 +186,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) : NULL;
int totitem_prev = totitem;
if (st && st->space_subtype_item_extend != NULL) {

View File

@@ -171,11 +171,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, NULL, 0, NULL, NULL},
};
@@ -528,6 +533,12 @@ static const EnumPropertyItem rna_enum_curve_display_handle_items[] = {
{0, NULL, 0, NULL, NULL},
};
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.h"
@@ -551,7 +562,6 @@ static const EnumPropertyItem rna_enum_curve_display_handle_items[] = {
# include "BKE_layer.h"
# include "BKE_nla.h"
# include "BKE_paint.h"
# include "BKE_preferences.h"
# include "BKE_scene.h"
# include "BKE_screen.h"
# include "BKE_workspace.h"
@@ -611,6 +621,8 @@ static StructRNA *rna_Space_refine(struct 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:
@@ -7246,6 +7258,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;
@@ -8176,6 +8202,7 @@ void RNA_def_space(BlenderRNA *brna)
rna_def_console_line(brna);
rna_def_space_info(brna);
rna_def_space_userpref(brna);
rna_def_space_project_settings(brna);
rna_def_node_tree_path(brna);
rna_def_space_node(brna);
rna_def_space_clip(brna);

View File

@@ -163,7 +163,6 @@ static const EnumPropertyItem rna_enum_preference_gpu_backend_items[] = {
# include "BKE_object.h"
# include "BKE_paint.h"
# include "BKE_pbvh.h"
# include "BKE_preferences.h"
# include "BKE_screen.h"
# include "DEG_depsgraph.h"
@@ -317,18 +316,6 @@ static void rna_userdef_language_update(Main *UNUSED(bmain),
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_script_autoexec_update(Main *UNUSED(bmain),
Scene *UNUSED(scene),
PointerRNA *ptr)
@@ -2562,6 +2549,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;
@@ -3902,6 +3904,8 @@ static void rna_def_userdef_themes(BlenderRNA *brna)
{11, "PROPERTIES", ICON_PROPERTIES, "Properties", ""},
{12, "OUTLINER", ICON_OUTLINER, "Outliner", ""},
{14, "PREFERENCES", ICON_PREFERENCES, "Preferences", ""},
/* TODO icon */
{24, "PROJECT_SETTINGS", ICON_PREFERENCES, "Project Settings", ""},
{15, "INFO", ICON_INFO, "Info", ""},
{16, "FILE_BROWSER", ICON_FILEBROWSER, "File Browser", ""},
{17, "CONSOLE", ICON_CONSOLE, "Python Console", ""},
@@ -4014,6 +4018,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, NULL, "space_console");
@@ -4300,6 +4310,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);
@@ -6111,56 +6122,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", NULL);
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, NULL, NULL, "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_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_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, 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_userdef_update");
}
static void rna_def_userdef_filepaths(BlenderRNA *brna)
{
PropertyRNA *prop;
@@ -6341,10 +6302,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);
@@ -6468,6 +6427,12 @@ static void rna_def_userdef_experimental(BlenderRNA *brna)
RNA_def_property_ui_text(
prop, "Override Templates", "Enable library override template in the python API");
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, "enable_eevee_next", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, NULL, "enable_eevee_next", 1);
RNA_def_property_ui_text(prop, "EEVEE Next", "Enable the new EEVEE codebase, requires restart");

View File

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

View File

@@ -355,6 +355,8 @@ typedef 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
@@ -496,6 +498,9 @@ typedef struct wmNotifier {
* reloading of asset libraries & their catalogs should happen. That only happens on explicit user
* action. */
#define ND_ASSET_CATALOGS (4 << 16)
/* Some settings of an asset library were changed, and UIs showing asset library information should
* redraw. */
#define ND_ASSET_LIBRARY (5 << 16)
/* subtype, 256 entries too */
#define NOTE_SUBTYPE 0x0000FF00

View File

@@ -567,6 +567,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";
}
@@ -1357,6 +1358,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(win->ghostwin);

View File

@@ -61,6 +61,7 @@
#include "BKE_appdir.h"
#include "BKE_autoexec.h"
#include "BKE_blender.h"
#include "BKE_blender_project.h"
#include "BKE_blendfile.h"
#include "BKE_callbacks.h"
#include "BKE_context.h"
@@ -160,7 +161,8 @@ void WM_file_tag_modified(void)
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_project_has_unsaved_changes(CTX_wm_project());
}
/** \} */
@@ -3187,6 +3189,16 @@ static int wm_save_as_mainfile_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
const BlenderProject *active_project = CTX_wm_project();
if (active_project && !BKE_project_contains_path(path)) {
BKE_reportf(
op->reports,
RPT_WARNING,
"File saved outside of the active project's path (\"%s\"). Active project changed.",
BKE_project_root_path_get(active_project));
/* 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;
@@ -3597,6 +3609,7 @@ void wm_test_autorun_warning(bContext *C)
* \{ */
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*/)
{
@@ -3639,6 +3652,12 @@ static void wm_block_file_close_save(bContext *C, void *arg_block, void *arg_dat
}
}
BlenderProject *project = CTX_wm_project();
if (project && BKE_project_has_unsaved_changes(project) &&
save_project_settings_when_file_is_closed) {
BKE_project_settings_save(project);
}
bool file_has_been_saved_before = BKE_main_blendfile_path(bmain)[0] != '\0';
if (file_has_been_saved_before) {
@@ -3776,6 +3795,30 @@ static uiBlock *block_create__close_file_dialog(struct bContext *C,
has_extra_checkboxes = true;
}
if (BKE_project_has_unsaved_changes(CTX_wm_project())) {
/* Only the first checkbox should get extra separation. */
if (!has_extra_checkboxes) {
uiItemS(layout);
}
uiDefButBitC(block,
UI_BTYPE_CHECKBOX,
1,
0,
"Save modified project settings",
0,
0,
0,
UI_UNIT_Y,
&save_project_settings_when_file_is_closed,
0,
0,
0,
0,
"");
has_extra_checkboxes = true;
}
if (AS_asset_library_has_any_unsaved_catalogs()) {
static char save_catalogs_when_file_is_closed;

View File

@@ -28,6 +28,7 @@
#include "BLT_translation.h"
#include "BKE_blender_project.h"
#include "BKE_context.h"
#include "BKE_global.h"
#include "BKE_icons.h"
@@ -42,6 +43,7 @@
#include "RNA_enum_types.h"
#include "WM_api.h"
#include "WM_message.h"
#include "WM_types.h"
#include "wm.h"
#include "wm_draw.h"
@@ -464,6 +466,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 (WM_window_is_temp_screen(win)) {
/* Nothing to do for 'temp' windows,
* because #WM_window_open always sets window title. */
@@ -472,11 +476,23 @@ void wm_window_title(wmWindowManager *wm, wmWindow *win)
/* this is set to 1 if you don't have startup.blend open */
const char *blendfile_path = BKE_main_blendfile_path_from_global();
if (blendfile_path[0] != '\0') {
char str[sizeof(((Main *)NULL)->filepath) + 24];
char project_name_hint[MAX_PROJECT_NAME_HINT] = "";
char str[sizeof(((Main *)NULL)->filepath) + sizeof(project_name_hint) + 24];
struct BlenderProject *project = CTX_wm_project();
if (project) {
const char *name = BKE_project_name_get(project);
BLI_snprintf(project_name_hint,
sizeof(project_name_hint),
"%s - ",
(name && name[0]) ? name : IFACE_("Unnamed project"));
}
BLI_snprintf(str,
sizeof(str),
"Blender%s [%s%s]",
"Blender%s [%s%s%s]",
wm->file_saved ? "" : "*",
project_name_hint,
blendfile_path,
G_MAIN->recovered ? " (Recovered)" : "");
GHOST_SetTitle(win->ghostwin, str);
@@ -490,6 +506,8 @@ void wm_window_title(wmWindowManager *wm, wmWindow *win)
* terminate request (e.g. OS Shortcut Alt+F4, Command+Q, (...), or session end). */
GHOST_SetWindowModifiedState(win->ghostwin, (bool)!wm->file_saved);
}
#undef MAX_PROJECT_NAME_HINT
}
void WM_window_set_dpi(const wmWindow *win)
@@ -1596,6 +1614,35 @@ void wm_window_process_events(const bContext *C)
}
}
/* -------------------------------------------------------------------- */
/** \name WM level message bus subscribers
* \{ */
static void wm_msg_windows_title_update_fn(bContext *C,
wmMsgSubscribeKey *UNUSED(msg_key),
wmMsgSubscribeValue *UNUSED(msg_val))
{
wmWindowManager *wm = CTX_wm_manager(C);
LISTBASE_FOREACH (wmWindow *, win, &wm->windows) {
wm_window_title(wm, win);
}
}
void wm_messages_subscribe(wmWindowManager *wm)
{
WM_msgbus_clear_by_owner(wm->message_bus, wm);
wmMsgSubscribeValue msg_sub_value_update_win_titles = {0};
msg_sub_value_update_win_titles.owner = wm;
msg_sub_value_update_win_titles.notify = wm_msg_windows_title_update_fn;
/* Update window titles on project name change. */
WM_msg_subscribe_rna_anon_prop(
wm->message_bus, BlenderProject, name, &msg_sub_value_update_win_titles);
}
/** \} */
/* -------------------------------------------------------------------- */
/** \name Ghost Init/Exit
* \{ */

View File

@@ -23,6 +23,8 @@ void wm_ghost_init(bContext *C);
void wm_ghost_init_background(void);
void wm_ghost_exit(void);
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.