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
5 changed files with 165 additions and 97 deletions
Showing only changes of commit de2b529da8 - Show all commits

View File

@ -280,6 +280,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 = any(len(coll.io_handlers) > 0 for coll in bpy.data.collections)
layout.separator()

View File

@ -108,6 +108,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_io_handler_copy(Collection *collection, IOHandlerData *data);
/** \} */
@ -159,8 +160,6 @@ static void collection_copy_data(Main *bmain, ID *id_dst, const ID *id_src, cons
BLI_listbase_clear(&collection_dst->runtime.parents);
collection_dst->runtime.gobject_hash = nullptr;
/* TODO: Copy over io_handlers */
LISTBASE_FOREACH (CollectionChild *, child, &collection_src->children) {
collection_child_add(
bmain, collection_dst, child->collection, &child->light_linking, flag, false);
@ -168,6 +167,9 @@ static void collection_copy_data(Main *bmain, ID *id_dst, const ID *id_src, cons
LISTBASE_FOREACH (CollectionObject *, cob, &collection_src->gobject) {
collection_object_add(bmain, collection_dst, cob->ob, &cob->light_linking, flag, false);
}
LISTBASE_FOREACH (IOHandlerData *, data, &collection_src->io_handlers) {
collection_io_handler_copy(collection_dst, data);
}
}
static void collection_free_data(ID *id)
@ -1375,6 +1377,22 @@ static bool collection_object_remove(
return true;
}
static void collection_io_handler_copy(Collection *collection, IOHandlerData *data)
{
IOHandlerData *new_data = MEM_cnew<IOHandlerData>("IOHandlerData");
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_ClearProperty(filepath);
}
BLI_addtail(&collection->io_handlers, new_data);
}
bool BKE_collection_object_add_notest(Main *bmain, Collection *collection, Object *ob)
{
if (ob == nullptr) {

View File

@ -2337,86 +2337,6 @@ void uiTemplateModifiers(uiLayout * /*layout*/, bContext *C)
}
}
#ifdef _MSC_VER
# pragma optimize("", off)
#endif
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 minimal_operator_free(wmOperator *op)
{
MEM_freeN(op->ptr);
MEM_freeN(op);
}
static void draw_export_controls(uiLayout *layout, const char *label, int index)
{
uiItemL(layout, label, ICON_NONE);
uiItemIntO(layout, "", ICON_EXPORT, "COLLECTION_OT_io_handler_export", "index", index);
uiItemIntO(layout, "", ICON_X, "COLLECTION_OT_io_handler_remove", "index", index);
}
static void draw_export_properties(
bContext *C, uiLayout *layout, ID *id, wmOperatorType *ot, IOHandlerData *data)
{
PointerRNA properties = RNA_pointer_create(id, ot->srna, data->export_properties);
uiLayout *box = uiLayoutBox(layout);
uiItemR(box, &properties, "filepath", UI_ITEM_NONE, nullptr, ICON_NONE);
wmOperator *op = minimal_operator_create(ot, &properties);
op->layout = layout;
op->type->ui(C, op);
op->layout = nullptr;
minimal_operator_free(op);
}
void uiTemplateCollectionExporters(uiLayout *layout, bContext *C)
{
Collection *collection = CTX_data_collection(C);
ListBase *io_handlers = &collection->io_handlers;
/* Draw all the IO handlers. */
int index = 0;
LISTBASE_FOREACH_INDEX (IOHandlerData *, data, io_handlers, index) {
using namespace blender;
bke::FileHandlerType *fh = bke::file_handler_find(data->fh_idname);
if (!fh) {
continue;
}
wmOperatorType *ot = WM_operatortype_find(fh->export_operator, false);
if (!ot) {
continue;
}
PointerRNA io_handler_ptr = RNA_pointer_create(&collection->id, &RNA_IOHandlerData, data);
PanelLayout panel = uiLayoutPanelProp(C, layout, &io_handler_ptr, "is_open");
draw_export_controls(panel.header, fh->label, index);
if (panel.body) {
draw_export_properties(C, panel.body, &collection->id, ot, data);
}
}
}
#ifdef _MSC_VER
# pragma optimize("", on)
#endif
/** \} */
/* -------------------------------------------------------------------- */
@ -3039,6 +2959,86 @@ 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 minimal_operator_free(wmOperator *op)
{
MEM_freeN(op->ptr);
MEM_freeN(op);
}
static void draw_export_controls(uiLayout *layout, const std::string &label, int index, bool valid)
{
uiItemL(layout, label.c_str(), ICON_NONE);
if (valid) {
uiItemIntO(layout, "", ICON_EXPORT, "COLLECTION_OT_io_handler_export", "index", index);
uiItemIntO(layout, "", ICON_X, "COLLECTION_OT_io_handler_remove", "index", index);
}
}
static void draw_export_properties(
bContext *C, uiLayout *layout, ID *id, wmOperatorType *ot, IOHandlerData *data)
{
PointerRNA properties = RNA_pointer_create(id, ot->srna, data->export_properties);
uiLayout *box = uiLayoutBox(layout);
uiItemR(box, &properties, "filepath", UI_ITEM_NONE, nullptr, ICON_NONE);
wmOperator *op = minimal_operator_create(ot, &properties);
op->type->flag &= ~OPTYPE_PRESET; /* TODO: Presets will not work currently. */
template_operator_property_buts_draw_single(C, op, layout, UI_BUT_LABEL_ALIGN_NONE, 0);
minimal_operator_free(op);
}
void uiTemplateCollectionExporters(uiLayout *layout, bContext *C)
{
Collection *collection = CTX_data_collection(C);
ListBase *io_handlers = &collection->io_handlers;
/* Draw all the IO handlers. */
int index = 0;
LISTBASE_FOREACH_INDEX (IOHandlerData *, data, io_handlers, index) {
using namespace blender;
PointerRNA io_handler_ptr = RNA_pointer_create(&collection->id, &RNA_IOHandlerData, data);
PanelLayout panel = uiLayoutPanelProp(C, layout, &io_handler_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(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(panel.header, label, index, false);
continue;
}
std::string label(fh->label);
draw_export_controls(panel.header, label, index, true);
if (panel.body) {
draw_export_properties(C, panel.body, &collection->id, ot, data);
}
}
}
/** \} */
/* -------------------------------------------------------------------- */

View File

@ -197,8 +197,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);
}
/**

View File

@ -464,7 +464,7 @@ void COLLECTION_OT_io_handler_add(wmOperatorType *ot)
/* api callbacks */
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.
ot->exec = collection_io_handler_add_exec;
ot->poll = ED_operator_objectmode;
ot->poll = WM_operator_winactive;
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.
/* flags */
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`.
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -502,7 +502,7 @@ void COLLECTION_OT_io_handler_remove(wmOperatorType *ot)
/* api callbacks */
ot->exec = collection_io_handler_remove_exec;
deadpin marked this conversation as resolved Outdated

COLLECTION_OT_exporter_remove

`COLLECTION_OT_exporter_remove`
ot->poll = ED_operator_objectmode;
ot->poll = WM_operator_winactive;
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?
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
deadpin marked this conversation as resolved Outdated

Same comment about poll function.

Same comment about poll function.
@ -555,7 +555,7 @@ void COLLECTION_OT_io_handler_export(wmOperatorType *ot)
/* api callbacks */
ot->exec = collection_io_handler_export_exec;
ot->poll = ED_operator_objectmode;
ot->poll = WM_operator_winactive;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -563,31 +563,78 @@ void COLLECTION_OT_io_handler_export(wmOperatorType *ot)
RNA_def_int(ot->srna, "index", 0, 0, INT_MAX, "Index", "IO Handler index", 0, INT_MAX);
}
static int collection_io_export_all_exec(bContext *C, wmOperator * /*op*/)
static int collection_export(bContext *C, Collection *collection)
{
Collection *collection = CTX_data_collection(C);
ListBase *io_handlers = &collection->io_handlers;
LISTBASE_FOREACH (IOHandlerData *, data, io_handlers) {
io_handler_export(C, data, collection);
/* TODO: Should we continue calling operators if one fails? */
/* TODO: What's the best way to surface the problem? Reports? */
if (io_handler_export(C, data, collection) != OPERATOR_FINISHED) {
deadpin marked this conversation as resolved Outdated

COLLECTION_OT_export perhaps.

`COLLECTION_OT_export` perhaps.
/* Do not continue calling exporters if we encounter one that fails. */
return OPERATOR_CANCELLED;
}
}
deadpin marked this conversation as resolved Outdated

Same comment about poll.

Same comment about poll.
return OPERATOR_FINISHED;
}
static int collection_io_export_all_exec(bContext *C, wmOperator * /*op*/)
deadpin marked this conversation as resolved Outdated

IO Handler index -> Exporter index

IO Handler index -> Exporter index
{
Collection *collection = CTX_data_collection(C);
return collection_export(C, collection);
}
void COLLECTION_OT_io_export_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Export All";
ot->description = "Export all configured IO Handlers";
ot->description = "Invoke all configured exporters on this collection";
ot->idname = "COLLECTION_OT_io_export_all";
/* api callbacks */
ot->exec = collection_io_export_all_exec;
ot->poll = ED_operator_objectmode;
ot->poll = WM_operator_winactive;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
static int collection_export_recursive(bContext *C, LayerCollection *layer_collection)
{
if (collection_export(C, layer_collection->collection) != OPERATOR_FINISHED) {
return OPERATOR_CANCELLED;
}
LISTBASE_FOREACH (LayerCollection *, child, &layer_collection->layer_collections) {
if (collection_export_recursive(C, child) != OPERATOR_FINISHED) {
deadpin marked this conversation as resolved Outdated

COLLECTION_OT_export_all

`COLLECTION_OT_export_all`
return OPERATOR_CANCELLED;
}
}
deadpin marked this conversation as resolved Outdated

Same comment about poll.

Same comment about poll.
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, 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 = WM_operator_winactive;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
@ -609,7 +656,7 @@ static void collection_io_handler_menu_draw(const bContext * /*C*/, Menu *menu)
}
if (!at_least_one) {
uiItemL(layout, "No file handlers available", ICON_NONE);
uiItemL(layout, IFACE_("No file handlers available"), ICON_NONE);
}
}
@ -619,7 +666,7 @@ void collection_io_handler_register()
{
MenuType *mt = MEM_cnew<MenuType>(__func__);
STRNCPY(mt->idname, "COLLECTION_MT_io_handler_add");
STRNCPY(mt->label, N_("Add IO Handler"));
STRNCPY(mt->label, N_("Add Exporter"));
mt->draw = collection_io_handler_menu_draw;
WM_menutype_add(mt);
@ -627,6 +674,7 @@ void collection_io_handler_register()
WM_operatortype_append(COLLECTION_OT_io_handler_remove);
WM_operatortype_append(COLLECTION_OT_io_handler_export);
WM_operatortype_append(COLLECTION_OT_io_export_all);
WM_operatortype_append(WM_OT_collection_export_all);
}
} // namespace blender::ed::object