Collection IO: Enable file exporters to be specified on Collections #116646

Merged
Jesse Yurkovich merged 56 commits from deadpin/blender:collection-io into main 2024-04-08 22:10:52 +02:00
28 changed files with 712 additions and 10 deletions

View File

@ -1099,7 +1099,7 @@ class Menu(StructRNA, _GenericUI, metaclass=RNAMeta):
def path_menu(self, searchpaths, operator, *,
props_default=None, prop_filepath="filepath",
filter_ext=None, filter_path=None, display_name=None,
add_operator=None):
add_operator=None, add_operator_props=None):
"""
Populate a menu from a list of paths.
@ -1176,6 +1176,9 @@ class Menu(StructRNA, _GenericUI, metaclass=RNAMeta):
props = row.operator(add_operator, text="", icon='REMOVE')
props.name = name
props.remove_name = True
if add_operator_props is not None:
for attr, value in add_operator_props.items():
setattr(props, attr, value)
if add_operator:
wm = bpy.data.window_managers[0]
@ -1189,6 +1192,9 @@ class Menu(StructRNA, _GenericUI, metaclass=RNAMeta):
props = row.operator(add_operator, text="", icon='ADD')
props.name = wm.preset_name
if add_operator_props is not None:
for attr, value in add_operator_props.items():
setattr(props, attr, value)
def draw_preset(self, _context):
"""
@ -1205,12 +1211,14 @@ class Menu(StructRNA, _GenericUI, metaclass=RNAMeta):
ext_valid = getattr(self, "preset_extensions", {".py", ".xml"})
props_default = getattr(self, "preset_operator_defaults", None)
add_operator = getattr(self, "preset_add_operator", None)
add_operator_props = getattr(self, "preset_add_operator_properties", None)
self.path_menu(
bpy.utils.preset_paths(self.preset_subdir),
self.preset_operator,
props_default=props_default,
filter_ext=lambda ext: ext.lower() in ext_valid,
add_operator=add_operator,
add_operator_props=add_operator_props,
display_name=lambda name: bpy.path.display_name(name, title_case=False)
)

View File

@ -7,6 +7,7 @@ from bpy.types import (
Menu,
Operator,
OperatorFileListElement,
Panel,
WindowManager,
)
from bpy.props import (
@ -18,6 +19,7 @@ from bpy.app.translations import (
pgettext_rpt as rpt_,
pgettext_data as data_,
)
from bl_ui.utils import PresetPanel
# For preset popover menu
@ -750,6 +752,24 @@ class WM_MT_operator_presets(Menu):
preset_operator = "script.execute_preset"
class WM_PT_operator_presets(PresetPanel, Panel):
bl_label = "Operator Presets"
preset_add_operator = "wm.operator_preset_add"
preset_operator = "script.execute_preset"
@property
def preset_subdir(self):
return AddPresetOperator.operator_path(self.operator)
@property
def preset_add_operator_properties(self):
return {"operator": self.operator}
def draw(self, context):
self.operator = context.active_operator.bl_idname
PresetPanel.draw(self, context)
class WM_OT_operator_presets_cleanup(Operator):
"""Remove outdated operator properties from presets that may cause problems"""
@ -921,5 +941,6 @@ classes = (
AddPresetEEVEERaytracing,
ExecutePreset,
WM_MT_operator_presets,
WM_PT_operator_presets,
WM_OT_operator_presets_cleanup,
)

View File

@ -49,6 +49,23 @@ class COLLECTION_PT_collection_flags(CollectionButtonsPanel, Panel):
col.prop(vlc, "indirect_only", toggle=False)
class COLLECTION_PT_exporters(CollectionButtonsPanel, Panel):
bl_label = "Exporters"
def draw(self, context):
layout = self.layout
collection = context.collection
row = layout.row()
col = row.column()
col.operator("wm.call_menu", text="Add", icon='ADD').name = "COLLECTION_MT_exporter_add"
col = row.column()
col.operator("COLLECTION_OT_export_all", icon='EXPORT')
col.enabled = len(collection.exporters) > 0
layout.template_collection_exporters()
class COLLECTION_MT_context_menu_instance_offset(Menu):
bl_label = "Instance Offset"
@ -114,6 +131,7 @@ classes = (
COLLECTION_PT_instancing,
COLLECTION_PT_lineart_collection,
COLLECTION_PT_collection_custom_props,
COLLECTION_PT_exporters,
)
if __name__ == "__main__": # only for live edit.

View File

@ -262,6 +262,9 @@ class TOPBAR_MT_file(Menu):
layout.menu("TOPBAR_MT_file_import", icon='IMPORT')
layout.menu("TOPBAR_MT_file_export", icon='EXPORT')
row = layout.row()
row.operator("wm.collection_export_all")
row.enabled = context.view_layer.has_export_collections
layout.separator()

View File

@ -23,6 +23,7 @@ struct BlendDataReader;
struct BlendWriter;
struct Collection;
struct ID;
struct CollectionExport;
struct Main;
struct Object;
struct Scene;
@ -64,6 +65,12 @@ void BKE_collection_add_from_collection(Main *bmain,
* Free (or release) any data used by this collection (does not free the collection itself).
*/
void BKE_collection_free_data(Collection *collection);
/**
* Free any data used by the IO handler (does not free the IO handler itself).
*/
void BKE_collection_exporter_free_data(CollectionExport *data);
/**
* Remove a collection, optionally removing its child objects or moving
* them to parent collections.

View File

@ -23,6 +23,8 @@ struct FileHandlerType {
char label[OP_MAX_TYPENAME];
/** Import operator name. */
char import_operator[OP_MAX_TYPENAME];
/** Export operator name. */
char export_operator[OP_MAX_TYPENAME];
/** Formatted string of file extensions supported by the file handler, each extension should
* start with a `.` and be separated by `;`. For Example: `".blend;.ble"`. */
char file_extensions_str[FH_MAX_FILE_EXTENSIONS_STR];
@ -40,6 +42,11 @@ struct FileHandlerType {
* Return a vector of indices in #paths of file paths supported by the file handler.
*/
blender::Vector<int64_t> filter_supported_paths(const blender::Span<std::string> paths) const;
/**
* Generate a default file name for use with this file handler.
*/
std::string get_default_filename(const StringRefNull name);
};
/**

View File

@ -32,6 +32,7 @@
#include "BKE_main.hh"
#include "BKE_object.hh"
#include "BKE_preview_image.hh"
#include "BKE_report.hh"
#include "BKE_rigidbody.h"
#include "BKE_scene.hh"
@ -108,6 +109,7 @@ static void collection_gobject_hash_ensure(Collection *collection);
static void collection_gobject_hash_update_object(Collection *collection,
Object *ob_old,
CollectionObject *cob);
static void collection_exporter_copy(Collection *collection, CollectionExport *data);
/** \} */
@ -159,6 +161,7 @@ static void collection_copy_data(Main *bmain,
BLI_listbase_clear(&collection_dst->gobject);
BLI_listbase_clear(&collection_dst->children);
BLI_listbase_clear(&collection_dst->exporters);
BLI_listbase_clear(&collection_dst->runtime.parents);
collection_dst->runtime.gobject_hash = nullptr;
@ -169,6 +172,9 @@ static void collection_copy_data(Main *bmain,
LISTBASE_FOREACH (CollectionObject *, cob, &collection_src->gobject) {
collection_object_add(bmain, collection_dst, cob->ob, &cob->light_linking, flag, false);
}
LISTBASE_FOREACH (CollectionExport *, data, &collection_src->exporters) {
collection_exporter_copy(collection_dst, data);
}
}
static void collection_free_data(ID *id)
@ -187,6 +193,11 @@ static void collection_free_data(ID *id)
BLI_freelistN(&collection->children);
BLI_freelistN(&collection->runtime.parents);
LISTBASE_FOREACH (CollectionExport *, data, &collection->exporters) {
BKE_collection_exporter_free_data(data);
}
BLI_freelistN(&collection->exporters);
/* No need for depsgraph tagging here, since the data is being deleted. */
collection_object_cache_free(nullptr, collection, LIB_ID_CREATE_NO_DEG_TAG, 0);
}
@ -274,6 +285,13 @@ void BKE_collection_blend_write_nolib(BlendWriter *writer, Collection *collectio
LISTBASE_FOREACH (CollectionChild *, child, &collection->children) {
BLO_write_struct(writer, CollectionChild, child);
}
LISTBASE_FOREACH (CollectionExport *, data, &collection->exporters) {
BLO_write_struct(writer, CollectionExport, data);
if (data->export_properties) {
IDP_BlendWrite(writer, data->export_properties);
}
}
}
static void collection_blend_write(BlendWriter *writer, ID *id, const void *id_address)
@ -324,6 +342,12 @@ void BKE_collection_blend_read_data(BlendDataReader *reader, Collection *collect
BLO_read_list(reader, &collection->gobject);
BLO_read_list(reader, &collection->children);
BLO_read_list(reader, &collection->exporters);
LISTBASE_FOREACH (CollectionExport *, data, &collection->exporters) {
BLO_read_data_address(reader, &data->export_properties);
IDP_BlendDataRead(reader, &data->export_properties);
}
BLO_read_data_address(reader, &collection->preview);
BKE_previewimg_blend_read(reader, collection->preview);
}
@ -490,6 +514,13 @@ void BKE_collection_free_data(Collection *collection)
collection_free_data(&collection->id);
}
void BKE_collection_exporter_free_data(struct CollectionExport *data)
{
if (data->export_properties) {
IDP_FreeProperty(data->export_properties);
}
}
bool BKE_collection_delete(Main *bmain, Collection *collection, bool hierarchy)
{
/* Master collection is not real datablock, can't be removed. */
@ -1353,6 +1384,22 @@ static bool collection_object_remove(
return true;
}
static void collection_exporter_copy(Collection *collection, CollectionExport *data)
{
CollectionExport *new_data = MEM_cnew<CollectionExport>("CollectionExport");
STRNCPY(new_data->fh_idname, data->fh_idname);
new_data->export_properties = IDP_CopyProperty(data->export_properties);
new_data->flag = data->flag;
/* Clear the `filepath` property. */
IDProperty *filepath = IDP_GetPropertyFromGroup(new_data->export_properties, "filepath");
if (filepath) {
IDP_AssignString(filepath, "");
}
BLI_addtail(&collection->exporters, new_data);
}
bool BKE_collection_object_add_notest(Main *bmain, Collection *collection, Object *ob)
{
if (ob == nullptr) {

View File

@ -123,4 +123,9 @@ blender::Vector<int64_t> FileHandlerType::filter_supported_paths(
return indices;
}
std::string FileHandlerType::get_default_filename(const StringRefNull name)
{
return name + (file_extensions.is_empty() ? "" : file_extensions.first());
}
} // namespace blender::bke

View File

@ -1219,6 +1219,10 @@ static void layer_collection_sync(ViewLayer *view_layer,
{
child_layer->runtime_flag |= LAYER_COLLECTION_VISIBLE_VIEW_LAYER;
}
if (!BLI_listbase_is_empty(&child_collection->exporters)) {
view_layer->flag |= VIEW_LAYER_HAS_EXPORT_COLLECTIONS;
}
deadpin marked this conversation as resolved
Review

Just to be sure, it is intended that excluded collections are skipped from this test (see the continue; statement a few lines above)?

Because afaict, below in export operators code, there is no check to skip such collection...

Just to be sure, it is intended that excluded collections are skipped from this test (see the `continue;` statement a few lines above)? Because afaict, below in export operators code, there is no check to skip such collection...
Review

You're right. I do think they should be skipped when Excluded but I'll have to account for this in the export all operator.

You're right. I do think they should be skipped when Excluded but I'll have to account for this in the export all operator.
}
/* Replace layer collection list with new one. */
@ -1356,6 +1360,9 @@ void BKE_layer_collection_sync(const Scene *scene, ViewLayer *view_layer)
static_cast<LayerCollection *>(view_layer->layer_collections.first),
layer_resync_mempool);
/* Clear the cached flag indicating if the view layer has a collection exporter set. */
view_layer->flag &= ~VIEW_LAYER_HAS_EXPORT_COLLECTIONS;
/* Generate new layer connections and object bases when collections changed. */
ListBase new_object_bases{};
const short parent_exclude = 0, parent_restrict = 0, parent_layer_restrict = 0;

View File

@ -2709,6 +2709,8 @@ void uiTemplateNodeTreeInterface(uiLayout *layout, PointerRNA *ptr);
*/
void uiTemplateNodeInputs(uiLayout *layout, bContext *C, PointerRNA *ptr);
void uiTemplateCollectionExporters(uiLayout *layout, bContext *C);
/**
* \return: True if the list item with unfiltered, unordered index \a item_idx is visible given the
* current filter settings.

View File

@ -35,6 +35,7 @@
#include "BLI_path_util.h"
#include "BLI_rect.h"
#include "BLI_string.h"
#include "BLI_string_ref.hh"
#include "BLI_string_utils.hh"
#include "BLI_time.h"
#include "BLI_timecode.h"
@ -50,6 +51,7 @@
#include "BKE_constraint.h"
#include "BKE_context.hh"
#include "BKE_curveprofile.h"
#include "BKE_file_handler.hh"
#include "BKE_global.hh"
#include "BKE_gpencil_modifier_legacy.h"
#include "BKE_idprop.hh"
@ -2962,6 +2964,89 @@ void uiTemplateOperatorRedoProperties(uiLayout *layout, const bContext *C)
}
}
static wmOperator *minimal_operator_create(wmOperatorType *ot, PointerRNA *properties)
{
/* Copied from #wm_operator_create.
* Create a slimmed down operator suitable only for UI drawing. */
wmOperator *op = MEM_cnew<wmOperator>(ot->idname);
STRNCPY(op->idname, ot->idname);
op->type = ot;
/* Initialize properties but do not assume ownership of them.
* This "minimal" operator owns nothing. */
op->ptr = MEM_cnew<PointerRNA>("wmOperatorPtrRNA");
op->properties = static_cast<IDProperty *>(properties->data);
*op->ptr = *properties;
return op;
}
static void draw_export_controls(
bContext *C, uiLayout *layout, const std::string &label, int index, bool valid)
{
uiItemL(layout, label.c_str(), ICON_NONE);
if (valid) {
uiItemPopoverPanel(layout, C, "WM_PT_operator_presets", "", ICON_PRESET);
uiItemIntO(layout, "", ICON_EXPORT, "COLLECTION_OT_exporter_export", "index", index);
uiItemIntO(layout, "", ICON_X, "COLLECTION_OT_exporter_remove", "index", index);
}
}
static void draw_export_properties(bContext *C,
uiLayout *layout,
wmOperator *op,
const std::string &filename)
{
uiLayout *box = uiLayoutBox(layout);
PropertyRNA *prop = RNA_struct_find_property(op->ptr, "filepath");
std::string placeholder = "//" + filename;
uiItemFullR(
box, op->ptr, prop, RNA_NO_INDEX, 0, UI_ITEM_NONE, nullptr, ICON_NONE, placeholder.c_str());
template_operator_property_buts_draw_single(C, op, layout, UI_BUT_LABEL_ALIGN_NONE, 0);
}
void uiTemplateCollectionExporters(uiLayout *layout, bContext *C)
{
Collection *collection = CTX_data_collection(C);
ListBase *exporters = &collection->exporters;
/* Draw all the IO handlers. */
int index = 0;
LISTBASE_FOREACH_INDEX (CollectionExport *, data, exporters, index) {
using namespace blender;
PointerRNA exporter_ptr = RNA_pointer_create(&collection->id, &RNA_CollectionExport, data);
PanelLayout panel = uiLayoutPanelProp(C, layout, &exporter_ptr, "is_open");
bke::FileHandlerType *fh = bke::file_handler_find(data->fh_idname);
if (!fh) {
std::string label = std::string(IFACE_("Undefined")) + " " + data->fh_idname;
draw_export_controls(C, panel.header, label, index, false);
continue;
}
wmOperatorType *ot = WM_operatortype_find(fh->export_operator, false);
if (!ot) {
std::string label = std::string(IFACE_("Undefined")) + " " + fh->export_operator;
draw_export_controls(C, panel.header, label, index, false);
continue;
}
/* Assign temporary operator to uiBlock, which takes ownership. */
PointerRNA properties = RNA_pointer_create(&collection->id, ot->srna, data->export_properties);
wmOperator *op = minimal_operator_create(ot, &properties);
UI_block_set_active_operator(uiLayoutGetBlock(panel.header), op, true);
/* Draw panel header and contents. */
std::string label(fh->label);
draw_export_controls(C, panel.header, label, index, true);
if (panel.body) {
draw_export_properties(C, panel.body, op, fh->get_default_filename(collection->id.name + 2));
}
}
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -108,6 +108,8 @@ static int wm_obj_export_exec(bContext *C, wmOperator *op)
export_params.reports = op->reports;
RNA_string_get(op->ptr, "collection", export_params.collection);
OBJ_export(C, &export_params);
return OPERATOR_FINISHED;
@ -197,8 +199,7 @@ static void ui_obj_export_settings(uiLayout *layout, PointerRNA *imfptr)
static void wm_obj_export_draw(bContext * /*C*/, wmOperator *op)
{
PointerRNA ptr = RNA_pointer_create(nullptr, op->type->srna, op->properties);
ui_obj_export_settings(op->layout, &ptr);
ui_obj_export_settings(op->layout, op->ptr);
}
/**
@ -387,6 +388,9 @@ void WM_OT_obj_export(wmOperatorType *ot)
/* Only show .obj or .mtl files by default. */
prop = RNA_def_string(ot->srna, "filter_glob", "*.obj;*.mtl", 0, "Extension Filter", "");
RNA_def_property_flag(prop, PROP_HIDDEN);
prop = RNA_def_string(ot->srna, "collection", nullptr, MAX_IDPROP_NAME, "Collection", nullptr);
RNA_def_property_flag(prop, PROP_HIDDEN);
}
static int wm_obj_import_exec(bContext *C, wmOperator *op)
@ -550,6 +554,7 @@ void obj_file_handler_add()
auto fh = std::make_unique<blender::bke::FileHandlerType>();
STRNCPY(fh->idname, "IO_FH_obj");
STRNCPY(fh->import_operator, "WM_OT_obj_import");
STRNCPY(fh->export_operator, "WM_OT_obj_export");
STRNCPY(fh->label, "Wavefront OBJ");
STRNCPY(fh->file_extensions_str, ".obj");
fh->poll_drop = poll_file_object_drop;

View File

@ -215,6 +215,7 @@ static int wm_usd_export_exec(bContext *C, wmOperator *op)
};
STRNCPY(params.root_prim_path, root_prim_path);
RNA_string_get(op->ptr, "collection", params.collection);
bool ok = USD_export(C, filepath, &params, as_background_job, op->reports);
@ -348,6 +349,9 @@ void WM_OT_usd_export(wmOperatorType *ot)
"Only export visible objects. Invisible parents of exported objects are "
"exported as empty transforms");
prop = RNA_def_string(ot->srna, "collection", nullptr, MAX_IDPROP_NAME, "Collection", nullptr);
RNA_def_property_flag(prop, PROP_HIDDEN);
RNA_def_boolean(
ot->srna,
"export_animation",
@ -832,6 +836,7 @@ void usd_file_handler_add()
auto fh = std::make_unique<blender::bke::FileHandlerType>();
STRNCPY(fh->idname, "IO_FH_usd");
STRNCPY(fh->import_operator, "WM_OT_usd_import");
STRNCPY(fh->export_operator, "WM_OT_usd_export");
STRNCPY(fh->label, "Universal Scene Description");
STRNCPY(fh->file_extensions_str, ".usd;.usda;.usdc;.usdz");
fh->poll_drop = poll_file_object_drop;

View File

@ -8,6 +8,8 @@
#include <cstring>
#include "BLI_path_util.h"
#include "BLI_string.h"
#include "BLI_utildefines.h"
#include "DNA_collection_types.h"
@ -16,11 +18,16 @@
#include "BKE_collection.hh"
#include "BKE_context.hh"
#include "BKE_file_handler.hh"
#include "BKE_idprop.hh"
#include "BKE_layer.hh"
#include "BKE_lib_id.hh"
#include "BKE_main.hh"
#include "BKE_object.hh"
#include "BKE_report.hh"
#include "BKE_screen.hh"
#include "BLT_translation.hh"
#include "DEG_depsgraph.hh"
#include "DEG_depsgraph_build.hh"
@ -36,6 +43,7 @@
#include "RNA_enum_types.hh"
#include "RNA_prototypes.h"
#include "UI_interface.hh"
#include "UI_interface_icons.hh"
#include "object_intern.hh"
@ -416,6 +424,329 @@ void COLLECTION_OT_create(wmOperatorType *ot)
ot->srna, "name", "Collection", MAX_ID_NAME - 2, "Name", "Name of the new collection");
}
static bool collection_exporter_poll(bContext *C)
{
return CTX_data_collection(C) != nullptr;
}
static bool collection_export_all_poll(bContext *C)
{
return CTX_data_view_layer(C) != nullptr;
}
deadpin marked this conversation as resolved Outdated

Add BKE_report(op->reports, RPT_ERROR, "File handler '%s' not found'", name)

Add `BKE_report(op->reports, RPT_ERROR, "File handler '%s' not found'", name)`
static int collection_exporter_add_exec(bContext *C, wmOperator *op)
{
using namespace blender;
Collection *collection = CTX_data_collection(C);
deadpin marked this conversation as resolved Outdated

Add BKE_report(op->reports, RPT_ERROR, "File handler operator '%s' not found'", fh->export_operator)

Add `BKE_report(op->reports, RPT_ERROR, "File handler operator '%s' not found'", fh->export_operator)`
ListBase *exporters = &collection->exporters;
char name[MAX_ID_NAME - 2]; /* id name */
RNA_string_get(op->ptr, "name", name);
bke::FileHandlerType *fh = bke::file_handler_find(name);
if (!fh) {
BKE_reportf(op->reports, RPT_ERROR, "File handler '%s' not found", name);
return OPERATOR_CANCELLED;
deadpin marked this conversation as resolved Outdated

io_wmOpItemProp is a bit of a strange name. Any reason it's not export_properties?

`io_wmOpItemProp` is a bit of a strange name. Any reason it's not `export_properties`?
}
if (!WM_operatortype_find(fh->export_operator, true)) {
BKE_reportf(
op->reports, RPT_ERROR, "File handler operator '%s' not found", fh->export_operator);
return OPERATOR_CANCELLED;
}
/* Add a new #CollectionExport item to our handler list and fill it with #FileHandlerType
* information. Also load in the operator's properties now as well. */
CollectionExport *data = MEM_cnew<CollectionExport>("CollectionExport");
STRNCPY(data->fh_idname, fh->idname);
IDPropertyTemplate val{};
data->export_properties = IDP_New(IDP_GROUP, &val, "export_properties");
data->flag |= IO_HANDLER_PANEL_OPEN;
deadpin marked this conversation as resolved Outdated

Rename COLLECTION_OT_io_handler_add COLLECTION_OT_exporter_add. I don't really see a reason to deviate from the user interface name here.

Rename `COLLECTION_OT_io_handler_add` `COLLECTION_OT_exporter_add`. I don't really see a reason to deviate from the user interface name here.
BLI_addtail(exporters, data);
deadpin marked this conversation as resolved
Review

Missing deg tagging of the modified collection (likely just using ID_RECALC_SYNC_TO_EVAL?).

Same below in remove code.

Missing deg tagging of the modified collection (likely just using `ID_RECALC_SYNC_TO_EVAL`?). Same below in remove code.
BKE_view_layer_need_resync_tag(CTX_data_view_layer(C));
deadpin marked this conversation as resolved Outdated

This needs a poll function that checks CTX_data_collection(C) != nullptr.

This needs a poll function that checks `CTX_data_collection(C) != nullptr`.
DEG_id_tag_update(&collection->id, ID_RECALC_SYNC_TO_EVAL);
deadpin marked this conversation as resolved Outdated

This should not be called here (same for the remove op code below). Use instead BKE_view_layer_need_resync_tag, code that needs to get a valid viewlayer data is in charge of ensuring it itself (via BKE_view_layer_synced_ensure).

Also, this should be called before deg tagging and notifiers are sent.

This should not be called here (same for the remove op code below). Use instead `BKE_view_layer_need_resync_tag`, code that needs to get a valid viewlayer data is in charge of ensuring it itself (via `BKE_view_layer_synced_ensure`). Also, this should be called before deg tagging and notifiers are sent.
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_PROPERTIES, nullptr);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, nullptr);
return OPERATOR_FINISHED;
}
void COLLECTION_OT_exporter_add(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Add Exporter";
ot->description = "Add Exporter";
ot->idname = "COLLECTION_OT_exporter_add";
/* api callbacks */
ot->exec = collection_exporter_add_exec;
ot->poll = collection_exporter_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_string(ot->srna, "name", nullptr, MAX_ID_NAME - 2, "Name", "FileHandler idname");
}
static int collection_exporter_remove_exec(bContext *C, wmOperator *op)
{
Collection *collection = CTX_data_collection(C);
ListBase *exporters = &collection->exporters;
int index = RNA_int_get(op->ptr, "index");
CollectionExport *data = static_cast<CollectionExport *>(BLI_findlink(exporters, index));
if (!data) {
return OPERATOR_CANCELLED;
}
deadpin marked this conversation as resolved Outdated

COLLECTION_OT_exporter_remove

`COLLECTION_OT_exporter_remove`
BLI_remlink(exporters, data);
deadpin marked this conversation as resolved Outdated

Most likely have no consequences here, but would rather remove the link from the listbase before starting to free its data?

Most likely have no consequences here, but would rather remove the link from the listbase before starting to free its data?
BKE_collection_exporter_free_data(data);
deadpin marked this conversation as resolved Outdated

Same comment about poll function.

Same comment about poll function.
MEM_freeN(data);
BKE_view_layer_need_resync_tag(CTX_data_view_layer(C));
DEG_id_tag_update(&collection->id, ID_RECALC_SYNC_TO_EVAL);
deadpin marked this conversation as resolved Outdated

IO Handler index -> Exporter index

IO Handler index -> Exporter index
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_PROPERTIES, nullptr);
WM_event_add_notifier(C, NC_SPACE | ND_SPACE_OUTLINER, nullptr);
return OPERATOR_FINISHED;
}
static int collection_exporter_remove_invoke(bContext *C,
wmOperator *op,
const wmEvent * /*event*/)
{
return WM_operator_confirm_ex(
C, op, IFACE_("Remove exporter?"), nullptr, IFACE_("Delete"), ALERT_ICON_NONE, false);
}
void COLLECTION_OT_exporter_remove(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Remove Exporter";
ot->description = "Remove Exporter";
ot->idname = "COLLECTION_OT_exporter_remove";
/* api callbacks */
ot->invoke = collection_exporter_remove_invoke;
ot->exec = collection_exporter_remove_exec;
ot->poll = collection_exporter_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "Exporter index", 0, INT_MAX);
}
static int collection_exporter_export(bContext *C,
wmOperator *op,
CollectionExport *data,
Collection *collection)
{
using namespace blender;
bke::FileHandlerType *fh = bke::file_handler_find(data->fh_idname);
if (!fh) {
BKE_reportf(op->reports, RPT_ERROR, "File handler '%s' not found", data->fh_idname);
return OPERATOR_CANCELLED;
}
wmOperatorType *ot = WM_operatortype_find(fh->export_operator, false);
if (!ot) {
BKE_reportf(
op->reports, RPT_ERROR, "File handler operator '%s' not found", fh->export_operator);
return OPERATOR_CANCELLED;
}
/* Execute operator with our stored properties. */
/* TODO: Cascade settings down from parent collections(?) */
IDProperty *op_props = IDP_CopyProperty(data->export_properties);
PointerRNA properties = RNA_pointer_create(nullptr, ot->srna, op_props);
const char *collection_name = collection->id.name + 2;
/* Ensure we have a valid filepath set. Create one if the user has not specified anything yet. */
deadpin marked this conversation as resolved Outdated

COLLECTION_OT_export perhaps.

`COLLECTION_OT_export` perhaps.
char filepath[FILE_MAX];
RNA_string_get(&properties, "filepath", filepath);
if (!filepath[0]) {
BLI_path_join(
deadpin marked this conversation as resolved Outdated

Same comment about poll.

Same comment about poll.
filepath, sizeof(filepath), "//", fh->get_default_filename(collection_name).c_str());
}
else {
char filename[FILENAME_MAX];
BLI_path_split_file_part(filepath, filename, sizeof(filename));
deadpin marked this conversation as resolved Outdated

IO Handler index -> Exporter index

IO Handler index -> Exporter index
if (!filename[0] || !BLI_path_extension(filename)) {
BKE_reportf(op->reports, RPT_ERROR, "File path '%s' is not a valid file", filepath);
IDP_FreeProperty(op_props);
return OPERATOR_CANCELLED;
}
}
const Main *bmain = CTX_data_main(C);
BLI_path_abs(filepath, BKE_main_blendfile_path(bmain));
RNA_string_set(&properties, "filepath", filepath);
RNA_string_set(&properties, "collection", collection_name);
int op_result = WM_operator_name_call_ptr(C, ot, WM_OP_EXEC_DEFAULT, &properties, nullptr);
IDP_FreeProperty(op_props);
return op_result;
}
static int collection_exporter_export_exec(bContext *C, wmOperator *op)
{
Collection *collection = CTX_data_collection(C);
ListBase *exporters = &collection->exporters;
int index = RNA_int_get(op->ptr, "index");
CollectionExport *data = static_cast<CollectionExport *>(BLI_findlink(exporters, index));
if (!data) {
return OPERATOR_CANCELLED;
deadpin marked this conversation as resolved Outdated

COLLECTION_OT_export_all

`COLLECTION_OT_export_all`
}
return collection_exporter_export(C, op, data, collection);
}
deadpin marked this conversation as resolved Outdated

Same comment about poll.

Same comment about poll.
void COLLECTION_OT_exporter_export(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Export";
ot->description = "Invoke the export operation";
ot->idname = "COLLECTION_OT_exporter_export";
/* api callbacks */
ot->exec = collection_exporter_export_exec;
ot->poll = collection_exporter_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "Exporter index", 0, INT_MAX);
}
static int collection_export(bContext *C, wmOperator *op, Collection *collection)
{
ListBase *exporters = &collection->exporters;
LISTBASE_FOREACH (CollectionExport *, data, exporters) {
if (collection_exporter_export(C, op, data, collection) != OPERATOR_FINISHED) {
/* Do not continue calling exporters if we encounter one that fails. */
return OPERATOR_CANCELLED;
}
}
return OPERATOR_FINISHED;
}
static int collection_io_export_all_exec(bContext *C, wmOperator *op)
{
Collection *collection = CTX_data_collection(C);
return collection_export(C, op, collection);
}
void COLLECTION_OT_export_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Export All";
deadpin marked this conversation as resolved Outdated

Same comment about poll.

Same comment about poll.
ot->description = "Invoke all configured exporters on this collection";
ot->idname = "COLLECTION_OT_export_all";
/* api callbacks */
ot->exec = collection_io_export_all_exec;
ot->poll = collection_exporter_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static int collection_export_recursive(bContext *C,
wmOperator *op,
LayerCollection *layer_collection)
{
/* Skip collections which have been Excluded in the View Layer. */
if (layer_collection->flag & LAYER_COLLECTION_EXCLUDE) {
return OPERATOR_FINISHED;
}
if (collection_export(C, op, layer_collection->collection) != OPERATOR_FINISHED) {
return OPERATOR_CANCELLED;
}
LISTBASE_FOREACH (LayerCollection *, child, &layer_collection->layer_collections) {
if (collection_export_recursive(C, op, child) != OPERATOR_FINISHED) {
return OPERATOR_CANCELLED;
}
}
return OPERATOR_FINISHED;
}
static int wm_collection_export_all_exec(bContext *C, wmOperator *op)
{
ViewLayer *view_layer = CTX_data_view_layer(C);
LISTBASE_FOREACH (LayerCollection *, layer_collection, &view_layer->layer_collections) {
if (collection_export_recursive(C, op, layer_collection) != OPERATOR_FINISHED) {
return OPERATOR_CANCELLED;
}
}
return OPERATOR_FINISHED;
}
void WM_OT_collection_export_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Export All Collections";
ot->description = "Invoke all configured exporters for all collections";
ot->idname = "WM_OT_collection_export_all";
/* api callbacks */
ot->exec = wm_collection_export_all_exec;
ot->poll = collection_export_all_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static void collection_exporter_menu_draw(const bContext * /*C*/, Menu *menu)
{
using namespace blender;
uiLayout *layout = menu->layout;
/* Add all file handlers capable of being exported to the menu. */
bool at_least_one = false;
for (const auto &fh : bke::file_handlers()) {
if (WM_operatortype_find(fh->export_operator, true)) {
uiItemStringO(
layout, fh->label, ICON_NONE, "COLLECTION_OT_exporter_add", "name", fh->idname);
at_least_one = true;
}
}
if (!at_least_one) {
uiItemL(layout, IFACE_("No file handlers available"), ICON_NONE);
}
}
void collection_exporter_register()
{
MenuType *mt = MEM_cnew<MenuType>(__func__);
STRNCPY(mt->idname, "COLLECTION_MT_exporter_add");
STRNCPY(mt->label, N_("Add Exporter"));
mt->draw = collection_exporter_menu_draw;
WM_menutype_add(mt);
WM_operatortype_append(COLLECTION_OT_exporter_add);
WM_operatortype_append(COLLECTION_OT_exporter_remove);
WM_operatortype_append(COLLECTION_OT_exporter_export);
WM_operatortype_append(COLLECTION_OT_export_all);
WM_operatortype_append(WM_OT_collection_export_all);
}
/****************** properties window operators *********************/
static int collection_add_exec(bContext *C, wmOperator * /*op*/)

View File

@ -377,4 +377,6 @@ void OBJECT_OT_datalayout_transfer(wmOperatorType *ot);
void object_modifier_add_asset_register();
void collection_exporter_register();
} // namespace blender::ed::object

View File

@ -303,6 +303,7 @@ void operatortypes_object()
WM_operatortype_append(OBJECT_OT_light_linking_unlink_from_collection);
object_modifier_add_asset_register();
collection_exporter_register();
}
void operatormacros_object()

View File

@ -3442,6 +3442,15 @@ static void outliner_draw_tree_element(bContext *C,
float(startx) + offsx + 2 * ufac, float(*starty) + 2 * ufac, lib_icon, alpha_fac);
offsx += UI_UNIT_X + 4 * ufac;
}
if (tselem->type == TSE_LAYER_COLLECTION) {
const Collection *collection = (Collection *)tselem->id;
if (!BLI_listbase_is_empty(&collection->exporters)) {
UI_icon_draw_alpha(
float(startx) + offsx + 2 * ufac, float(*starty) + 2 * ufac, ICON_EXPORT, alpha_fac);
offsx += UI_UNIT_X + 4 * ufac;
}
}
}
GPU_blend(GPU_BLEND_NONE);

View File

@ -25,12 +25,14 @@
#include "DEG_depsgraph_build.hh"
#include "DEG_depsgraph_query.hh"
#include "DNA_collection_types.h"
#include "DNA_scene_types.h"
#include "BKE_appdir.hh"
#include "BKE_blender_version.h"
#include "BKE_context.hh"
#include "BKE_global.hh"
#include "BKE_lib_id.hh"
#include "BKE_report.hh"
#include "BKE_scene.hh"
@ -483,7 +485,20 @@ bool USD_export(bContext *C,
*
* Has to be done from main thread currently, as it may affect Main original data (e.g. when
* doing deferred update of the view-layers, see #112534 for details). */
if (job->params.visible_objects_only) {
if (strlen(job->params.collection) > 0) {
Collection *collection = reinterpret_cast<Collection *>(
BKE_libblock_find_name(job->bmain, ID_GR, job->params.collection));
if (!collection) {
BKE_reportf(job->params.worker_status->reports,
RPT_ERROR,
"USD Export: Unable to find collection %s",
job->params.collection);
return false;
}
DEG_graph_build_from_collection(job->depsgraph, collection);
}
else if (job->params.visible_objects_only) {
DEG_graph_build_from_view_layer(job->depsgraph);
}
else {

View File

@ -85,6 +85,7 @@ struct USDExportParams {
bool overwrite_textures = true;
bool relative_paths = true;
char root_prim_path[1024] = ""; /* FILE_MAX */
char collection[MAX_IDPROP_NAME] = "";
/** Communication structure between the wmJob management code and the worker code. Currently used
* to generate safely reports from the worker thread. */

View File

@ -23,6 +23,7 @@ struct OBJExportParams {
char filepath[FILE_MAX];
/** Pretend that destination file folder is this, if non-empty. Used only for tests. */
char file_base_for_tests[FILE_MAX];
char collection[MAX_IDPROP_NAME] = "";
/** Full path to current blender file (used for comments in output). */
const char *blen_filepath;

View File

@ -11,6 +11,7 @@
#include <system_error>
#include "BKE_context.hh"
#include "BKE_lib_id.hh"
#include "BKE_report.hh"
#include "BKE_scene.hh"
@ -21,6 +22,7 @@
#include "DEG_depsgraph_query.hh"
#include "DNA_collection_types.h"
#include "DNA_scene_types.h"
#include "ED_object.hh"
@ -33,13 +35,23 @@
namespace blender::io::obj {
OBJDepsgraph::OBJDepsgraph(const bContext *C, const eEvaluationMode eval_mode)
OBJDepsgraph::OBJDepsgraph(const bContext *C,
const eEvaluationMode eval_mode,
Collection *collection)
{
Scene *scene = CTX_data_scene(C);
Main *bmain = CTX_data_main(C);
ViewLayer *view_layer = CTX_data_view_layer(C);
if (eval_mode == DAG_EVAL_RENDER) {
depsgraph_ = DEG_graph_new(bmain, scene, view_layer, DAG_EVAL_RENDER);
/* If a collection was provided, use it. */
if (collection) {
depsgraph_ = DEG_graph_new(bmain, scene, view_layer, eval_mode);
needs_free_ = true;
DEG_graph_build_from_collection(depsgraph_, collection);
BKE_scene_graph_evaluated_ensure(depsgraph_, bmain);
}
else if (eval_mode == DAG_EVAL_RENDER) {
depsgraph_ = DEG_graph_new(bmain, scene, view_layer, eval_mode);
needs_free_ = true;
DEG_graph_build_for_all_objects(depsgraph_);
BKE_scene_graph_evaluated_ensure(depsgraph_, bmain);
@ -318,7 +330,22 @@ bool append_frame_to_filename(const char *filepath, const int frame, char *r_fil
void exporter_main(bContext *C, const OBJExportParams &export_params)
{
ed::object::mode_set(C, OB_MODE_OBJECT);
OBJDepsgraph obj_depsgraph(C, export_params.export_eval_mode);
Collection *collection = nullptr;
if (strlen(export_params.collection) > 0) {
Main *bmain = CTX_data_main(C);
collection = reinterpret_cast<Collection *>(
BKE_libblock_find_name(bmain, ID_GR, export_params.collection));
if (!collection) {
BKE_reportf(export_params.reports,
RPT_ERROR,
"OBJ Export: Unable to find collection %s",
export_params.collection);
return;
}
}
OBJDepsgraph obj_depsgraph(C, export_params.export_eval_mode, collection);
Scene *scene = DEG_get_input_scene(obj_depsgraph.get());
const char *filepath = export_params.filepath;

View File

@ -14,6 +14,9 @@
#include "IO_wavefront_obj.hh"
struct bContext;
struct Collection;
namespace blender::io::obj {
/**
@ -26,7 +29,7 @@ class OBJDepsgraph : NonMovable, NonCopyable {
bool needs_free_ = false;
public:
OBJDepsgraph(const bContext *C, eEvaluationMode eval_mode);
OBJDepsgraph(const bContext *C, eEvaluationMode eval_mode, Collection *collection);
~OBJDepsgraph();
Depsgraph *get();

View File

@ -62,6 +62,23 @@ typedef struct CollectionChild {
int _pad;
} CollectionChild;
/* Collection IO property storage and access. */
typedef struct CollectionExport {
struct CollectionExport *next, *prev;
/** Identifier that matches the #FileHandlerType.idname. */
char fh_idname[64];
IDProperty *export_properties;
uint32_t flag;
uint32_t _pad0;
} CollectionExport;
typedef enum IOHandlerPanelFlag {
IO_HANDLER_PANEL_OPEN = 1 << 0,
} IOHandlerPanelFlag;
/* Light linking state of object or collection: defines how they react to the emitters in the
* scene. See the comment for the link_state in the CollectionLightLinking for the details. */
typedef enum eCollectionLightLinkingState {
@ -116,6 +133,8 @@ typedef struct Collection {
/** CollectionChild. */
ListBase children;
ListBase exporters;
struct PreviewImage *preview;
unsigned int layer DNA_DEPRECATED;

View File

@ -275,4 +275,5 @@ enum {
/* VIEW_LAYER_DEPRECATED = (1 << 1), */
VIEW_LAYER_FREESTYLE = (1 << 2),
VIEW_LAYER_OUT_OF_SYNC = (1 << 3),
VIEW_LAYER_HAS_EXPORT_COLLECTIONS = (1 << 4),
};

View File

@ -8,6 +8,8 @@
#include <cstdlib>
#include "BKE_file_handler.hh"
#include "DNA_collection_types.h"
#include "DNA_lineart_types.h"
@ -446,6 +448,25 @@ static void rna_CollectionLightLinking_update(Main *bmain, Scene * /*scene*/, Po
DEG_relations_tag_update(bmain);
}
static PointerRNA rna_CollectionExport_export_properties_get(PointerRNA *ptr)
{
const CollectionExport *data = reinterpret_cast<CollectionExport *>(ptr->data);
/* If the File Handler or Operator is missing, we allow the data to be accessible
* as generic ID properties. */
blender::bke::FileHandlerType *fh = blender::bke::file_handler_find(data->fh_idname);
if (!fh) {
return RNA_pointer_create(ptr->owner_id, &RNA_IDPropertyWrapPtr, data->export_properties);
}
wmOperatorType *ot = WM_operatortype_find(fh->export_operator, false);
if (!ot) {
return RNA_pointer_create(ptr->owner_id, &RNA_IDPropertyWrapPtr, data->export_properties);
}
return RNA_pointer_create(ptr->owner_id, ot->srna, data->export_properties);
}
#else
/* collection.objects */
@ -566,6 +587,29 @@ static void rna_def_collection_child(BlenderRNA *brna)
prop, "Light Linking", "Light linking settings of the collection object");
}
static void rna_def_collection_exporter_data(BlenderRNA *brna)
{
StructRNA *srna;
PropertyRNA *prop;
srna = RNA_def_struct(brna, "CollectionExport", nullptr);
RNA_def_struct_sdna(srna, "CollectionExport");
RNA_def_struct_ui_text(srna, "Collection Export Data", "Exporter configured for the collection");
prop = RNA_def_property(srna, "is_open", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", IO_HANDLER_PANEL_OPEN);
RNA_def_property_ui_text(prop, "Is Open", "Whether the panel is expanded or closed");
RNA_def_property_flag(prop, PROP_NO_DEG_UPDATE);
RNA_def_property_update(prop, NC_SPACE | ND_SPACE_PROPERTIES, nullptr);
prop = RNA_def_property(srna, "export_properties", PROP_POINTER, PROP_NONE);
RNA_def_property_struct_type(prop, "PropertyGroup");
RNA_def_property_ui_text(
prop, "Export Properties", "Properties associated with the configured exporter");
RNA_def_property_pointer_funcs(
prop, "rna_CollectionExport_export_properties_get", nullptr, nullptr, nullptr);
}
void RNA_def_collections(BlenderRNA *brna)
{
StructRNA *srna;
@ -651,6 +695,14 @@ void RNA_def_collections(BlenderRNA *brna)
RNA_def_property_ui_text(prop,
"Collection Children",
"Children collections their parent-collection-specific settings");
/* Export Handlers. */
prop = RNA_def_property(srna, "exporters", PROP_COLLECTION, PROP_NONE);
RNA_def_property_struct_type(prop, "CollectionExport");
RNA_def_property_collection_sdna(prop, nullptr, "exporters", nullptr);
RNA_def_property_ui_text(
prop, "Collection Export Handlers", "Export Handlers configured for the collection");
/* TODO(sergey): Functions to link and unlink collections. */
/* Flags */
@ -753,6 +805,7 @@ void RNA_def_collections(BlenderRNA *brna)
rna_def_collection_light_linking(brna);
rna_def_collection_object(brna);
rna_def_collection_child(brna);
rna_def_collection_exporter_data(brna);
}
#endif

View File

@ -659,6 +659,14 @@ void RNA_def_view_layer(BlenderRNA *brna)
RNA_def_property_ui_text(prop, "Enabled", "Enable or disable rendering of this View Layer");
RNA_def_property_update(prop, NC_SCENE | ND_LAYER, nullptr);
/* Cached flag indicating if any Collection in this ViewLayer has an Exporter set. */
prop = RNA_def_property(srna, "has_export_collections", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_clear_flag(prop, PROP_EDITABLE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", VIEW_LAYER_HAS_EXPORT_COLLECTIONS);
RNA_def_property_ui_text(prop,
"Has export collections",
"At least one Collection in this View Layer has an exporter");
prop = RNA_def_property(srna, "use_freestyle", PROP_BOOLEAN, PROP_NONE);
RNA_def_property_boolean_sdna(prop, nullptr, "flag", VIEW_LAYER_FREESTYLE);
RNA_def_property_ui_text(prop, "Freestyle", "Render stylized strokes in this Layer");

View File

@ -2392,7 +2392,14 @@ static void rna_def_file_handler(BlenderRNA *brna)
RNA_def_property_ui_text(
prop,
"Operator",
"Operator that can handle import files with the extensions given in bl_file_extensions");
"Operator that can handle import for files with the extensions given in bl_file_extensions");
prop = RNA_def_property(srna, "bl_export_operator", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, nullptr, "type->export_operator");
RNA_def_property_flag(prop, PROP_REGISTER_OPTIONAL);
RNA_def_property_ui_text(
prop,
"Operator",
"Operator that can handle export for files with the extensions given in bl_file_extensions");
prop = RNA_def_property(srna, "bl_label", PROP_STRING, PROP_NONE);
RNA_def_property_string_sdna(prop, nullptr, "type->label");

View File

@ -1699,6 +1699,10 @@ void RNA_api_ui_layout(StructRNA *srna)
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Generates the UI layout for the modifier stack");
func = RNA_def_function(srna, "template_collection_exporters", "uiTemplateCollectionExporters");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Generates the UI layout for collection exporters");
func = RNA_def_function(srna, "template_constraints", "uiTemplateConstraints");
RNA_def_function_flag(func, FUNC_USE_CONTEXT);
RNA_def_function_ui_description(func, "Generates the panels for the constraint stack");