diff --git a/scripts/modules/bpy_types.py b/scripts/modules/bpy_types.py index d87261bed05..1b4808d731d 100644 --- a/scripts/modules/bpy_types.py +++ b/scripts/modules/bpy_types.py @@ -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) ) diff --git a/scripts/startup/bl_operators/presets.py b/scripts/startup/bl_operators/presets.py index 49929cf5b42..4df8e6e4a53 100644 --- a/scripts/startup/bl_operators/presets.py +++ b/scripts/startup/bl_operators/presets.py @@ -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, ) diff --git a/scripts/startup/bl_ui/properties_collection.py b/scripts/startup/bl_ui/properties_collection.py index 80278781799..c5f7b4c7ab2 100644 --- a/scripts/startup/bl_ui/properties_collection.py +++ b/scripts/startup/bl_ui/properties_collection.py @@ -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. diff --git a/scripts/startup/bl_ui/space_topbar.py b/scripts/startup/bl_ui/space_topbar.py index ea82fd692ce..7b6ee9b2f8b 100644 --- a/scripts/startup/bl_ui/space_topbar.py +++ b/scripts/startup/bl_ui/space_topbar.py @@ -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() diff --git a/source/blender/blenkernel/BKE_collection.hh b/source/blender/blenkernel/BKE_collection.hh index 761b279095a..2166a5d7fc6 100644 --- a/source/blender/blenkernel/BKE_collection.hh +++ b/source/blender/blenkernel/BKE_collection.hh @@ -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. diff --git a/source/blender/blenkernel/BKE_file_handler.hh b/source/blender/blenkernel/BKE_file_handler.hh index a66818c12b0..7020ad26fa2 100644 --- a/source/blender/blenkernel/BKE_file_handler.hh +++ b/source/blender/blenkernel/BKE_file_handler.hh @@ -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 filter_supported_paths(const blender::Span paths) const; + + /** + * Generate a default file name for use with this file handler. + */ + std::string get_default_filename(const StringRefNull name); }; /** diff --git a/source/blender/blenkernel/intern/collection.cc b/source/blender/blenkernel/intern/collection.cc index 3d05bcad2f3..cd7eaac1c12 100644 --- a/source/blender/blenkernel/intern/collection.cc +++ b/source/blender/blenkernel/intern/collection.cc @@ -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"); + 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) { diff --git a/source/blender/blenkernel/intern/file_handler.cc b/source/blender/blenkernel/intern/file_handler.cc index c4771ab7861..97d021d3606 100644 --- a/source/blender/blenkernel/intern/file_handler.cc +++ b/source/blender/blenkernel/intern/file_handler.cc @@ -123,4 +123,9 @@ blender::Vector 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 diff --git a/source/blender/blenkernel/intern/layer.cc b/source/blender/blenkernel/intern/layer.cc index ea67d4c32c5..cdcf90498ac 100644 --- a/source/blender/blenkernel/intern/layer.cc +++ b/source/blender/blenkernel/intern/layer.cc @@ -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; + } } /* Replace layer collection list with new one. */ @@ -1356,6 +1360,9 @@ void BKE_layer_collection_sync(const Scene *scene, ViewLayer *view_layer) static_cast(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; diff --git a/source/blender/editors/include/UI_interface_c.hh b/source/blender/editors/include/UI_interface_c.hh index 48493f03db2..d4cde172123 100644 --- a/source/blender/editors/include/UI_interface_c.hh +++ b/source/blender/editors/include/UI_interface_c.hh @@ -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. diff --git a/source/blender/editors/interface/templates/interface_templates.cc b/source/blender/editors/interface/templates/interface_templates.cc index 2047a435dfc..c0adf21945d 100644 --- a/source/blender/editors/interface/templates/interface_templates.cc +++ b/source/blender/editors/interface/templates/interface_templates.cc @@ -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(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("wmOperatorPtrRNA"); + op->properties = static_cast(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)); + } + } +} + /** \} */ /* -------------------------------------------------------------------- */ diff --git a/source/blender/editors/io/io_obj.cc b/source/blender/editors/io/io_obj.cc index 5c211c6d53d..dd5bf6f30fe 100644 --- a/source/blender/editors/io/io_obj.cc +++ b/source/blender/editors/io/io_obj.cc @@ -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(); 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; diff --git a/source/blender/editors/io/io_usd.cc b/source/blender/editors/io/io_usd.cc index 0c7ea023691..a002973b929 100644 --- a/source/blender/editors/io/io_usd.cc +++ b/source/blender/editors/io/io_usd.cc @@ -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, ¶ms, 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(); 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; diff --git a/source/blender/editors/object/object_collection.cc b/source/blender/editors/object/object_collection.cc index 96ddd0737a0..ad550c8ce61 100644 --- a/source/blender/editors/object/object_collection.cc +++ b/source/blender/editors/object/object_collection.cc @@ -8,6 +8,8 @@ #include +#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; +} + +static int collection_exporter_add_exec(bContext *C, wmOperator *op) +{ + using namespace blender; + Collection *collection = CTX_data_collection(C); + 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; + } + + 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"); + STRNCPY(data->fh_idname, fh->idname); + + IDPropertyTemplate val{}; + data->export_properties = IDP_New(IDP_GROUP, &val, "export_properties"); + data->flag |= IO_HANDLER_PANEL_OPEN; + + BLI_addtail(exporters, data); + + BKE_view_layer_need_resync_tag(CTX_data_view_layer(C)); + DEG_id_tag_update(&collection->id, ID_RECALC_SYNC_TO_EVAL); + + 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(BLI_findlink(exporters, index)); + if (!data) { + return OPERATOR_CANCELLED; + } + + BLI_remlink(exporters, data); + BKE_collection_exporter_free_data(data); + + 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); + + 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. */ + char filepath[FILE_MAX]; + RNA_string_get(&properties, "filepath", filepath); + if (!filepath[0]) { + BLI_path_join( + 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)); + 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(BLI_findlink(exporters, index)); + if (!data) { + return OPERATOR_CANCELLED; + } + + return collection_exporter_export(C, op, data, collection); +} + +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"; + 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(__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*/) diff --git a/source/blender/editors/object/object_intern.hh b/source/blender/editors/object/object_intern.hh index a5cdaca57da..0abd7cf7e84 100644 --- a/source/blender/editors/object/object_intern.hh +++ b/source/blender/editors/object/object_intern.hh @@ -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 diff --git a/source/blender/editors/object/object_ops.cc b/source/blender/editors/object/object_ops.cc index 71b67bb0192..588c79046c2 100644 --- a/source/blender/editors/object/object_ops.cc +++ b/source/blender/editors/object/object_ops.cc @@ -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() diff --git a/source/blender/editors/space_outliner/outliner_draw.cc b/source/blender/editors/space_outliner/outliner_draw.cc index 52ae3632c41..a6a5bbda52b 100644 --- a/source/blender/editors/space_outliner/outliner_draw.cc +++ b/source/blender/editors/space_outliner/outliner_draw.cc @@ -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); diff --git a/source/blender/io/usd/intern/usd_capi_export.cc b/source/blender/io/usd/intern/usd_capi_export.cc index c3fc350a609..4f916c04626 100644 --- a/source/blender/io/usd/intern/usd_capi_export.cc +++ b/source/blender/io/usd/intern/usd_capi_export.cc @@ -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( + 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 { diff --git a/source/blender/io/usd/usd.hh b/source/blender/io/usd/usd.hh index e6228079a63..d668a1a74eb 100644 --- a/source/blender/io/usd/usd.hh +++ b/source/blender/io/usd/usd.hh @@ -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. */ diff --git a/source/blender/io/wavefront_obj/IO_wavefront_obj.hh b/source/blender/io/wavefront_obj/IO_wavefront_obj.hh index 367e4513ee2..ab74efc0142 100644 --- a/source/blender/io/wavefront_obj/IO_wavefront_obj.hh +++ b/source/blender/io/wavefront_obj/IO_wavefront_obj.hh @@ -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; diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc index 11ecde162fe..f24ef0fc92f 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.cc +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.cc @@ -11,6 +11,7 @@ #include #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( + 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; diff --git a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh index 4180f29a254..768c3f1f716 100644 --- a/source/blender/io/wavefront_obj/exporter/obj_exporter.hh +++ b/source/blender/io/wavefront_obj/exporter/obj_exporter.hh @@ -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(); diff --git a/source/blender/makesdna/DNA_collection_types.h b/source/blender/makesdna/DNA_collection_types.h index 41743d588b9..68c2cc91534 100644 --- a/source/blender/makesdna/DNA_collection_types.h +++ b/source/blender/makesdna/DNA_collection_types.h @@ -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; diff --git a/source/blender/makesdna/DNA_layer_types.h b/source/blender/makesdna/DNA_layer_types.h index 661a7fde678..6d60be6af30 100644 --- a/source/blender/makesdna/DNA_layer_types.h +++ b/source/blender/makesdna/DNA_layer_types.h @@ -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), }; diff --git a/source/blender/makesrna/intern/rna_collection.cc b/source/blender/makesrna/intern/rna_collection.cc index 6322e42e022..b745881cdb5 100644 --- a/source/blender/makesrna/intern/rna_collection.cc +++ b/source/blender/makesrna/intern/rna_collection.cc @@ -8,6 +8,8 @@ #include +#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(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 diff --git a/source/blender/makesrna/intern/rna_layer.cc b/source/blender/makesrna/intern/rna_layer.cc index b9548d61ee7..accd8f43a3d 100644 --- a/source/blender/makesrna/intern/rna_layer.cc +++ b/source/blender/makesrna/intern/rna_layer.cc @@ -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"); diff --git a/source/blender/makesrna/intern/rna_ui.cc b/source/blender/makesrna/intern/rna_ui.cc index e9bf59c62c7..b10048ec2aa 100644 --- a/source/blender/makesrna/intern/rna_ui.cc +++ b/source/blender/makesrna/intern/rna_ui.cc @@ -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"); diff --git a/source/blender/makesrna/intern/rna_ui_api.cc b/source/blender/makesrna/intern/rna_ui_api.cc index 27a8f56bbf7..63e16456c07 100644 --- a/source/blender/makesrna/intern/rna_ui_api.cc +++ b/source/blender/makesrna/intern/rna_ui_api.cc @@ -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");