Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'source/blender/editors/io')
-rw-r--r--source/blender/editors/io/CMakeLists.txt5
-rw-r--r--source/blender/editors/io/SConscript5
-rw-r--r--source/blender/editors/io/io_cache_library.c961
-rw-r--r--source/blender/editors/io/io_cache_library.h56
-rw-r--r--source/blender/editors/io/io_cache_shapekey.c417
-rw-r--r--source/blender/editors/io/io_ops.c19
6 files changed, 1461 insertions, 2 deletions
diff --git a/source/blender/editors/io/CMakeLists.txt b/source/blender/editors/io/CMakeLists.txt
index f331dea5c5c..f8e4738ab38 100644
--- a/source/blender/editors/io/CMakeLists.txt
+++ b/source/blender/editors/io/CMakeLists.txt
@@ -26,8 +26,10 @@ set(INC
../../bmesh
../../makesdna
../../makesrna
+ ../../pointcache
../../windowmanager
../../collada
+ ../../../../intern/guardedalloc
)
set(INC_SYS
@@ -35,9 +37,12 @@ set(INC_SYS
)
set(SRC
+ io_cache_library.c
+ io_cache_shapekey.c
io_collada.c
io_ops.c
+ io_cache_library.h
io_collada.h
io_ops.h
)
diff --git a/source/blender/editors/io/SConscript b/source/blender/editors/io/SConscript
index 0facb24e2c3..d1826784547 100644
--- a/source/blender/editors/io/SConscript
+++ b/source/blender/editors/io/SConscript
@@ -36,10 +36,13 @@ incs = [
'../../blenfont',
'../../blenkernel',
'../../blenlib',
+ '../../bmesh',
'../../collada',
+ '../../makesdna',
'../../makesrna',
+ '../../pointcache',
'../../windowmanager',
- '../../bmesh../../makesdna',
+ '../../../../intern/guardedalloc',
]
if env['WITH_BF_COLLADA']:
diff --git a/source/blender/editors/io/io_cache_library.c b/source/blender/editors/io/io_cache_library.c
new file mode 100644
index 00000000000..f7076ae0cae
--- /dev/null
+++ b/source/blender/editors/io/io_cache_library.c
@@ -0,0 +1,961 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2015 Blender Foundation.
+ * All rights reserved.
+ *
+ * Contributor(s): Blender Foundation
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/io/io_cache_library.c
+ * \ingroup editor/io
+ */
+
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLF_translation.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_fileops.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_path_util.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_cache_library_types.h"
+#include "DNA_group_types.h"
+#include "DNA_listBase.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_force.h"
+#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
+
+#include "BKE_anim.h"
+#include "BKE_blender.h"
+#include "BKE_depsgraph.h"
+#include "BKE_cache_library.h"
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_idprop.h"
+#include "BKE_library.h"
+#include "BKE_main.h"
+#include "BKE_report.h"
+#include "BKE_scene.h"
+#include "BKE_screen.h"
+
+#include "ED_screen.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+#include "RNA_enum_types.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "io_cache_library.h"
+
+#include "PTC_api.h"
+
+static int ED_cache_library_active_object_poll(bContext *C)
+{
+ Object *ob = CTX_data_active_object(C);
+ if (!(ob && (ob->transflag & OB_DUPLIGROUP) && ob->dup_group && ob->cache_library))
+ return false;
+
+ return true;
+}
+
+static int ED_cache_modifier_poll(bContext *C)
+{
+ if (!ED_cache_library_active_object_poll(C))
+ return false;
+ if (!CTX_data_pointer_get_type(C, "cache_modifier", &RNA_CacheLibraryModifier).data)
+ return false;
+
+ return true;
+}
+
+/********************** new cache library operator *********************/
+
+static int new_cachelib_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Object *ob = CTX_data_active_object(C);
+ CacheLibrary *cachelib = ob->cache_library;
+ Main *bmain = CTX_data_main(C);
+ PointerRNA ptr, idptr;
+ PropertyRNA *prop;
+
+ /* add or copy material */
+ if (cachelib) {
+ cachelib = BKE_cache_library_copy(cachelib);
+ }
+ else {
+ cachelib = BKE_cache_library_add(bmain, DATA_("CacheLibrary"));
+ }
+
+ /* enable fake user by default */
+ cachelib->id.flag |= LIB_FAKEUSER;
+
+ /* hook into UI */
+ UI_context_active_but_prop_get_templateID(C, &ptr, &prop);
+
+ if (prop) {
+ /* when creating new ID blocks, use is already 1, but RNA
+ * pointer se also increases user, so this compensates it */
+ cachelib->id.us--;
+
+ RNA_id_pointer_create(&cachelib->id, &idptr);
+ RNA_property_pointer_set(&ptr, prop, idptr);
+ RNA_property_update(C, &ptr, prop);
+ }
+
+ WM_event_add_notifier(C, NC_SCENE, cachelib);
+
+ return OPERATOR_FINISHED;
+}
+
+void CACHELIBRARY_OT_new(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "New Cache Library";
+ ot->idname = "CACHELIBRARY_OT_new";
+ ot->description = "Add a new cache library";
+
+ /* api callbacks */
+ ot->poll = ED_operator_object_active;
+ ot->exec = new_cachelib_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO | OPTYPE_INTERNAL;
+}
+
+/********************** delete cache library operator *********************/
+
+static int cache_library_delete_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ Main *bmain = CTX_data_main(C);
+ Object *ob = CTX_data_active_object(C);
+ CacheLibrary *cachelib = ob->cache_library;
+
+ BKE_cache_library_unlink(cachelib);
+ BKE_libblock_free(bmain, cachelib);
+
+ WM_event_add_notifier(C, NC_SCENE, cachelib);
+
+ return OPERATOR_FINISHED;
+}
+
+void CACHELIBRARY_OT_delete(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Delete Cache Library";
+ ot->idname = "CACHELIBRARY_OT_delete";
+ ot->description = "Delete a cache library data block";
+
+ /* api callbacks */
+ ot->exec = cache_library_delete_exec;
+ ot->invoke = WM_operator_confirm;
+ ot->poll = ED_cache_library_active_object_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_UNDO;
+}
+
+/********************** bake cache operator *********************/
+
+typedef enum eCacheLibraryBake_EvalMode {
+ CACHELIBRARY_BAKE_PREVIEW = (1 << 0), /* evaluate data with preview settings */
+ CACHELIBRARY_BAKE_RENDER = (1 << 1), /* evaluate data with render settings */
+} eCacheLibraryBake_EvalMode;
+
+static int cache_library_bake_poll(bContext *C)
+{
+ Object *ob = CTX_data_active_object(C);
+
+ if (!ob || !(ob->transflag & OB_DUPLIGROUP) || !ob->dup_group || !ob->cache_library)
+ return false;
+ /* disable when the result is not displayed, just to avoid confusing situations */
+ if (ob->cache_library->display_mode != CACHE_LIBRARY_DISPLAY_RESULT)
+ return false;
+
+ return true;
+}
+
+typedef struct CacheLibraryBakeJob {
+ short *stop, *do_update;
+ float *progress;
+
+ struct Main *bmain;
+ struct Scene *scene;
+ struct CacheLibrary *cachelib;
+ int lay;
+ float mat[4][4];
+ struct Group *group;
+
+ eCacheLibraryBake_EvalMode eval_mode;
+ EvaluationContext eval_ctx;
+
+ struct PTCWriterArchive *archive;
+ struct PTCWriter *writer;
+
+ int start_frame, end_frame;
+ int origfra; /* original frame to reset scene after export */
+ float origframelen; /* original frame length to reset scene after export */
+} CacheLibraryBakeJob;
+
+static bool cache_library_bake_stop(CacheLibraryBakeJob *data)
+{
+ return (*data->stop) || G.is_break;
+}
+
+static void cache_library_bake_set_progress(CacheLibraryBakeJob *data, float progress)
+{
+ *data->do_update = 1;
+ *data->progress = progress;
+}
+
+static void cache_library_bake_set_particle_baking(Main *bmain, bool baking)
+{
+ /* XXX would be nicer to just loop over scene->base here,
+ * but this does not catch all objects included in dupli groups ...
+ */
+ Object *ob;
+ for (ob = bmain->object.first; ob; ob = ob->id.next) {
+ ParticleSystem *psys;
+
+ for (psys = ob->particlesystem.first; psys; psys = psys->next) {
+ if (baking)
+ psys->pointcache->flag |= PTCACHE_BAKING;
+ else
+ psys->pointcache->flag &= ~PTCACHE_BAKING;
+ }
+ }
+}
+
+static void cache_library_bake_do(CacheLibraryBakeJob *data, bool use_render)
+{
+ Scene *scene = data->scene;
+ int frame, frame_prev, start_frame, end_frame;
+ CacheProcessData process_data;
+
+ if (cache_library_bake_stop(data))
+ return;
+
+ /* === prepare === */
+
+ process_data.lay = data->lay;
+ copy_m4_m4(process_data.mat, data->mat);
+ process_data.dupcache = BKE_dupli_cache_new();
+
+ switch (data->cachelib->source_mode) {
+ case CACHE_LIBRARY_SOURCE_SCENE:
+ data->writer = PTC_writer_dupligroup(data->group->id.name, &data->eval_ctx, scene, data->group, data->cachelib);
+ break;
+ case CACHE_LIBRARY_SOURCE_CACHE:
+ data->writer = PTC_writer_duplicache(data->group->id.name, data->group, process_data.dupcache, data->cachelib->data_types, G.debug & G_DEBUG_SIMDATA);
+ break;
+ }
+ if (!data->writer) {
+ BKE_dupli_cache_free(process_data.dupcache);
+ return;
+ }
+
+ data->cachelib->flag |= CACHE_LIBRARY_BAKING;
+
+ PTC_writer_init(data->writer, data->archive);
+
+ start_frame = data->start_frame;
+ end_frame = data->end_frame;
+
+ /* === frame loop === */
+
+ cache_library_bake_set_progress(data, 0.0f);
+ for (frame = frame_prev = start_frame; frame <= end_frame; frame_prev = frame++) {
+
+ const bool init_strands = (frame == start_frame);
+
+ printf("Bake Cache '%s' | Frame %d\n", data->group->id.name+2, frame);
+
+ /* XXX Ugly, but necessary to avoid particle caching of paths when not needed.
+ * This takes a lot of time, but is only needed in the first frame.
+ */
+ cache_library_bake_set_particle_baking(data->bmain, !init_strands);
+
+ scene->r.cfra = frame;
+ BKE_scene_update_group_for_newframe(&data->eval_ctx, data->bmain, scene, data->group, scene->lay);
+
+ switch (data->cachelib->source_mode) {
+ case CACHE_LIBRARY_SOURCE_SCENE:
+ BKE_dupli_cache_from_group(scene, data->group, data->cachelib, process_data.dupcache, &data->eval_ctx, init_strands);
+ break;
+ case CACHE_LIBRARY_SOURCE_CACHE:
+ BKE_cache_read_dupli_cache(data->cachelib, process_data.dupcache, scene, data->group, frame, use_render, false);
+ break;
+ }
+
+ BKE_cache_process_dupli_cache(data->cachelib, &process_data, scene, data->group, frame_prev, frame, true, false, true);
+
+ PTC_write_sample(data->writer);
+
+ cache_library_bake_set_progress(data, (float)(frame - start_frame + 1) / (float)(end_frame - start_frame + 1));
+ if (cache_library_bake_stop(data))
+ break;
+ }
+
+ /* === cleanup === */
+
+ if (data->writer) {
+ PTC_writer_free(data->writer);
+ data->writer = NULL;
+ }
+
+ data->cachelib->flag &= ~CACHE_LIBRARY_BAKING;
+ cache_library_bake_set_particle_baking(data->bmain, false);
+
+ BKE_dupli_cache_free(process_data.dupcache);
+}
+
+/* Warning! Deletes existing files if possible, operator should show confirm dialog! */
+static bool cache_library_bake_ensure_file_target(const char *filename)
+{
+ if (BLI_exists(filename)) {
+ if (BLI_is_dir(filename)) {
+ return false;
+ }
+ else if (BLI_is_file(filename)) {
+ if (BLI_file_is_writable(filename)) {
+ /* returns 0 on success */
+ return (BLI_delete(filename, false, false) == 0);
+ }
+ else {
+ return false;
+ }
+ }
+ else {
+ return false;
+ }
+ }
+ return true;
+}
+
+static void cache_library_bake_start(void *customdata, short *stop, short *do_update, float *progress)
+{
+ CacheLibraryBakeJob *data = (CacheLibraryBakeJob *)customdata;
+ const bool do_preview = data->eval_mode & CACHELIBRARY_BAKE_PREVIEW;
+ const bool do_render = data->eval_mode & CACHELIBRARY_BAKE_RENDER;
+ const PTCArchiveResolution archive_res = (do_preview ? PTC_RESOLUTION_PREVIEW : 0) | (do_render ? PTC_RESOLUTION_RENDER : 0);
+ Scene *scene = data->scene;
+ char filename[FILE_MAX];
+ char app_name[MAX_NAME];
+ IDProperty *metadata;
+
+ data->stop = stop;
+ data->do_update = do_update;
+ data->progress = progress;
+
+ data->origfra = scene->r.cfra;
+ data->origframelen = scene->r.framelen;
+ scene->r.framelen = 1.0f;
+
+ BKE_cache_archive_output_path(data->cachelib, filename, sizeof(filename));
+ BLI_snprintf(app_name, sizeof(app_name), "Blender %s", versionstr);
+
+ metadata = BKE_cache_library_get_output_metadata(data->cachelib, false);
+
+ data->archive = PTC_open_writer_archive(FPS, data->start_frame, filename, archive_res, app_name, data->cachelib->description, NULL, metadata);
+
+ if (data->archive) {
+
+ G.is_break = false;
+
+ if (do_preview) {
+ data->eval_ctx.mode = DAG_EVAL_VIEWPORT;
+ PTC_writer_archive_use_render(data->archive, false);
+ cache_library_bake_do(data, false);
+ }
+
+ if (do_render) {
+ data->eval_ctx.mode = DAG_EVAL_RENDER;
+ PTC_writer_archive_use_render(data->archive, true);
+ cache_library_bake_do(data, true);
+ }
+
+ }
+
+ *do_update = true;
+ *stop = 0;
+}
+
+static void cache_library_bake_end(void *customdata)
+{
+ CacheLibraryBakeJob *data = (CacheLibraryBakeJob *)customdata;
+ Scene *scene = data->scene;
+
+ G.is_rendering = false;
+ BKE_spacedata_draw_locks(false);
+
+ if (data->writer)
+ PTC_writer_free(data->writer);
+ if (data->archive)
+ PTC_close_writer_archive(data->archive);
+
+ /* reset scene frame */
+ scene->r.cfra = data->origfra;
+ scene->r.framelen = data->origframelen;
+ BKE_scene_update_for_newframe(&data->eval_ctx, data->bmain, scene, scene->lay);
+}
+
+static void cache_library_bake_init(CacheLibraryBakeJob *data, bContext *C, wmOperator *op)
+{
+ Object *ob = CTX_data_active_object(C);
+ CacheLibrary *cachelib = ob->cache_library;
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+
+ /* make sure we can write */
+ char filename[FILE_MAX];
+ BKE_cache_archive_output_path(cachelib, filename, sizeof(filename));
+ cache_library_bake_ensure_file_target(filename);
+
+ /* XXX annoying hack: needed to prevent data corruption when changing
+ * scene frame in separate threads
+ */
+ G.is_rendering = true;
+
+ BKE_spacedata_draw_locks(true);
+
+ /* setup data */
+ data->bmain = bmain;
+ data->scene = scene;
+ data->cachelib = cachelib;
+ data->lay = ob->lay;
+ copy_m4_m4(data->mat, ob->obmat);
+ data->group = ob->dup_group;
+
+ data->eval_mode = RNA_enum_get(op->ptr, "eval_mode");
+
+ if (RNA_struct_property_is_set(op->ptr, "start_frame"))
+ data->start_frame = RNA_int_get(op->ptr, "start_frame");
+ else
+ data->start_frame = scene->r.sfra;
+ if (RNA_struct_property_is_set(op->ptr, "end_frame"))
+ data->end_frame = RNA_int_get(op->ptr, "end_frame");
+ else
+ data->end_frame = scene->r.efra;
+}
+
+static void cache_library_bake_freejob(void *customdata)
+{
+ CacheLibraryBakeJob *data= (CacheLibraryBakeJob *)customdata;
+ MEM_freeN(data);
+}
+
+static int cache_library_bake_exec(bContext *C, wmOperator *op)
+{
+ const bool use_job = RNA_boolean_get(op->ptr, "use_job");
+
+ if (use_job) {
+ /* when running through invoke, run as a job */
+ CacheLibraryBakeJob *data;
+ wmJob *wm_job;
+
+ /* XXX set WM_JOB_EXCL_RENDER to prevent conflicts with render jobs,
+ * since we need to set G.is_rendering
+ */
+ wm_job = WM_jobs_get(CTX_wm_manager(C), CTX_wm_window(C), CTX_data_scene(C), "Cache Library Bake",
+ WM_JOB_PROGRESS | WM_JOB_EXCL_RENDER, WM_JOB_TYPE_CACHELIBRARY_BAKE);
+
+ /* setup data */
+ data = MEM_callocN(sizeof(CacheLibraryBakeJob), "Cache Library Bake Job");
+ cache_library_bake_init(data, C, op);
+
+ WM_jobs_customdata_set(wm_job, data, cache_library_bake_freejob);
+ WM_jobs_timer(wm_job, 0.1, NC_SCENE|ND_FRAME, NC_SCENE|ND_FRAME);
+ WM_jobs_callbacks(wm_job, cache_library_bake_start, NULL, NULL, cache_library_bake_end);
+
+ WM_jobs_start(CTX_wm_manager(C), wm_job);
+ WM_cursor_wait(0);
+
+ /* add modal handler for ESC */
+ WM_event_add_modal_handler(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+ }
+ else {
+ /* in direct execution mode we run this operator blocking instead of using a job */
+ CacheLibraryBakeJob data;
+ short stop = false, do_update = false;
+ float progress = 0.0f;
+
+ cache_library_bake_init(&data, C, op);
+
+ cache_library_bake_start(&data, &stop, &do_update, &progress);
+ cache_library_bake_end(&data);
+
+ return OPERATOR_FINISHED;
+ }
+}
+
+static int cache_library_bake_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ Object *ob = CTX_data_active_object(C);
+ CacheLibrary *cachelib = ob->cache_library;
+
+ char filename[FILE_MAX];
+
+ if (!cachelib)
+ return OPERATOR_CANCELLED;
+
+ /* make sure we run a job when exec is called after confirm popup */
+ RNA_boolean_set(op->ptr, "use_job", true);
+
+ BKE_cache_archive_output_path(cachelib, filename, sizeof(filename));
+
+ if (!BKE_cache_archive_path_test(cachelib, cachelib->output_filepath)) {
+ BKE_reportf(op->reports, RPT_ERROR, "Cannot create file path for cache library %200s", cachelib->id.name+2);
+ return OPERATOR_CANCELLED;
+ }
+
+ if (BLI_exists(filename)) {
+ if (BLI_is_dir(filename)) {
+ BKE_reportf(op->reports, RPT_ERROR, "Cache Library target is a directory: %200s", filename);
+ return OPERATOR_CANCELLED;
+ }
+ else if (BLI_is_file(filename)) {
+ if (BLI_file_is_writable(filename)) {
+ return WM_operator_confirm_message(C, op, "Overwrite?");
+ }
+ else {
+ BKE_reportf(op->reports, RPT_ERROR, "Cannot overwrite Cache Library target: %200s", filename);
+ return OPERATOR_CANCELLED;
+ }
+
+ }
+ else {
+ BKE_reportf(op->reports, RPT_ERROR, "Invalid Cache Library target: %200s", filename);
+ return OPERATOR_CANCELLED;
+ }
+ }
+ else {
+ return cache_library_bake_exec(C, op);
+ }
+}
+
+/* catch esc */
+static int cache_library_bake_modal(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
+{
+ /* no running job, remove handler and pass through */
+ if (!WM_jobs_test(CTX_wm_manager(C), CTX_data_scene(C), WM_JOB_TYPE_CACHELIBRARY_BAKE))
+ return OPERATOR_FINISHED | OPERATOR_PASS_THROUGH;
+
+ /* running bake */
+ switch (event->type) {
+ case ESCKEY:
+ return OPERATOR_RUNNING_MODAL;
+ }
+ return OPERATOR_PASS_THROUGH;
+}
+
+void CACHELIBRARY_OT_bake(wmOperatorType *ot)
+{
+ PropertyRNA *prop;
+
+ static EnumPropertyItem eval_mode_items[] = {
+ {CACHELIBRARY_BAKE_PREVIEW, "PREVIEW", ICON_RESTRICT_VIEW_OFF, "Preview", "Evaluate data with preview settings"},
+ {CACHELIBRARY_BAKE_RENDER, "RENDER", ICON_RESTRICT_RENDER_OFF, "Render", "Evaluate data with render settings"},
+ {0, NULL, 0, NULL, NULL}
+ };
+
+ /* identifiers */
+ ot->name = "Bake";
+ ot->description = "Bake cache library";
+ ot->idname = "CACHELIBRARY_OT_bake";
+
+ /* api callbacks */
+ ot->invoke = cache_library_bake_invoke;
+ ot->exec = cache_library_bake_exec;
+ ot->modal = cache_library_bake_modal;
+ ot->poll = cache_library_bake_poll;
+
+ /* flags */
+ /* no undo for this operator, cannot restore old cache files anyway */
+ ot->flag = OPTYPE_REGISTER;
+
+ prop = RNA_def_boolean(ot->srna, "use_job", false, "Use Job", "Run operator as a job");
+ /* This is in internal property set by the invoke function.
+ * It allows the exec function to be called from both the confirm popup
+ * as well as a direct exec call for running a blocking operator in background mode.
+ */
+ RNA_def_property_flag(prop, PROP_HIDDEN);
+
+ prop = RNA_def_enum(ot->srna, "eval_mode", eval_mode_items, CACHELIBRARY_BAKE_RENDER, "Evaluation Mode", "Mode to use when evaluating data");
+ RNA_def_property_flag(prop, PROP_ENUM_FLAG);
+
+ RNA_def_int(ot->srna, "start_frame", 0, INT_MIN, INT_MAX, "Start Frame", "First frame to be cached", INT_MIN, INT_MAX);
+ RNA_def_int(ot->srna, "end_frame", 0, INT_MIN, INT_MAX, "End Frame", "Last frame to be cached", INT_MIN, INT_MAX);
+}
+
+/* ========================================================================= */
+
+static int cache_library_archive_slice_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = CTX_data_active_object(C);
+ CacheLibrary *cachelib = ob->cache_library;
+ Scene *scene = CTX_data_scene(C);
+
+ const int start_frame = RNA_int_get(op->ptr, "start_frame");
+ const int end_frame = RNA_int_get(op->ptr, "end_frame");
+
+ char input_filepath[FILE_MAX], input_filename[FILE_MAX];
+ char output_filepath[FILE_MAX], output_filename[FILE_MAX];
+ struct PTCReaderArchive *input_archive;
+ struct PTCWriterArchive *output_archive;
+ PTCArchiveResolution archive_res;
+ CacheArchiveInfo info;
+ IDProperty *metadata;
+
+ RNA_string_get(op->ptr, "input_filepath", input_filepath);
+ if (input_filepath[0] == '\0')
+ return OPERATOR_CANCELLED;
+ RNA_string_get(op->ptr, "output_filepath", output_filepath);
+ if (output_filepath[0] == '\0')
+ return OPERATOR_CANCELLED;
+
+ BKE_cache_archive_path_ex(input_filepath, cachelib->id.lib, NULL, input_filename, sizeof(input_filename));
+ BKE_cache_archive_path_ex(output_filepath, cachelib->id.lib, NULL, output_filename, sizeof(output_filename));
+
+ /* make sure we can write */
+ cache_library_bake_ensure_file_target(output_filename);
+
+ input_archive = PTC_open_reader_archive(scene, input_filename);
+ if (!input_archive) {
+ BKE_reportf(op->reports, RPT_ERROR, "Cannot open cache file at '%s'", input_filepath);
+ return OPERATOR_CANCELLED;
+ }
+
+ archive_res = PTC_reader_archive_get_resolutions(input_archive);
+ {
+ IDPropertyTemplate val;
+ val.i = 0;
+ metadata = IDP_New(IDP_GROUP, &val, "cache input metadata");
+ }
+ PTC_get_archive_info(input_archive, &info, metadata);
+
+ output_archive = PTC_open_writer_archive(FPS, start_frame, output_filename, archive_res, info.app_name, info.description, NULL, metadata);
+
+ IDP_FreeProperty(metadata);
+ MEM_freeN(metadata);
+
+ if (!output_archive) {
+ BKE_reportf(op->reports, RPT_ERROR, "Cannot write to cache file at '%s'", output_filepath);
+ return OPERATOR_CANCELLED;
+ }
+
+ PTC_archive_slice(input_archive, output_archive, start_frame, end_frame);
+
+ PTC_close_reader_archive(input_archive);
+ PTC_close_writer_archive(output_archive);
+
+ return OPERATOR_FINISHED;
+}
+
+static int cache_library_archive_slice_invoke(bContext *C, wmOperator *op, const wmEvent *event)
+{
+ return WM_operator_props_popup_confirm(C, op, event);
+
+#if 0
+ Object *ob = CTX_data_active_object(C);
+ CacheLibrary *cachelib = ob->cache_library;
+
+ char output_filename[FILE_MAX];
+
+ if (!cachelib)
+ return OPERATOR_CANCELLED;
+
+ /* make sure we run a job when exec is called after confirm popup */
+ RNA_boolean_set(op->ptr, "use_job", true);
+
+ RNA_string_get(op->ptr, "output_filepath", output_filepath);
+ if (output_filepath[0] == '\0')
+ return OPERATOR_CANCELLED;
+ BKE_cache_archive_output_path(cachelib, output_filename, sizeof(output_filename));
+
+ if (!BKE_cache_archive_path_test(cachelib, output_filepath)) {
+ BKE_reportf(op->reports, RPT_ERROR, "Cannot create file path for cache library %200s", cachelib->id.name+2);
+ return OPERATOR_CANCELLED;
+ }
+
+ if (BLI_exists(output_filename)) {
+ if (BLI_is_dir(output_filename)) {
+ BKE_reportf(op->reports, RPT_ERROR, "Cache Library target is a directory: %200s", output_filename);
+ return OPERATOR_CANCELLED;
+ }
+ else if (BLI_is_file(output_filename)) {
+ if (BLI_file_is_writable(output_filename)) {
+ return WM_operator_confirm_message(C, op, "Overwrite?");
+ }
+ else {
+ BKE_reportf(op->reports, RPT_ERROR, "Cannot overwrite Cache Library target: %200s", output_filename);
+ return OPERATOR_CANCELLED;
+ }
+
+ }
+ else {
+ BKE_reportf(op->reports, RPT_ERROR, "Invalid Cache Library target: %200s", output_filename);
+ return OPERATOR_CANCELLED;
+ }
+ }
+ else {
+ return cache_library_bake_exec(C, op);
+ }
+#endif
+}
+
+void CACHELIBRARY_OT_archive_slice(wmOperatorType *ot)
+{
+ PropertyRNA *prop;
+
+ /* identifiers */
+ ot->name = "Archive Slice";
+ ot->description = "Copy a range of frames to a new cache archive";
+ ot->idname = "CACHELIBRARY_OT_archive_slice";
+
+ /* api callbacks */
+ ot->exec = cache_library_archive_slice_exec;
+ ot->invoke = cache_library_archive_slice_invoke;
+ ot->poll = ED_cache_library_active_object_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ prop = RNA_def_boolean(ot->srna, "use_job", false, "Use Job", "Run operator as a job");
+ /* This is in internal property set by the invoke function.
+ * It allows the exec function to be called from both the confirm popup
+ * as well as a direct exec call for running a blocking operator in background mode.
+ */
+ RNA_def_property_flag(prop, PROP_HIDDEN);
+
+ prop = RNA_def_string(ot->srna, "input_filepath", NULL, FILE_MAX, "Input File Path", "Path to the source cache archive");
+ RNA_def_property_subtype(prop, PROP_FILEPATH);
+ prop = RNA_def_string(ot->srna, "output_filepath", NULL, FILE_MAX, "Output File Path", "Path to the target cache archive");
+ RNA_def_property_subtype(prop, PROP_FILEPATH);
+ RNA_def_int(ot->srna, "start_frame", 1, INT_MIN, INT_MAX, "Start Frame", "First frame to copy", 1, 10000);
+ RNA_def_int(ot->srna, "end_frame", 250, INT_MIN, INT_MAX, "End Frame", "Last frame to copy", 1, 10000);
+}
+
+/* ========================================================================= */
+
+#if 0
+static void ui_item_nlabel(uiLayout *layout, const char *s, size_t len)
+{
+ char buf[256];
+
+ BLI_strncpy(buf, s, sizeof(buf)-1);
+ buf[min_ii(len, sizeof(buf)-1)] = '\0';
+
+ uiItemL(layout, buf, ICON_NONE);
+}
+
+static void archive_info_labels(uiLayout *layout, const char *info)
+{
+ const char delim[] = {'\n', '\0'};
+ const char *cur = info;
+ size_t linelen;
+ char *sep, *suf;
+
+ linelen = BLI_str_partition(cur, delim, &sep, &suf);
+ while (sep) {
+ ui_item_nlabel(layout, cur, linelen);
+ cur = suf;
+
+ linelen = BLI_str_partition(cur, delim, &sep, &suf);
+ }
+ ui_item_nlabel(layout, cur, linelen);
+}
+
+static uiBlock *archive_info_popup_create(bContext *C, ARegion *ar, void *arg)
+{
+ const char *info = arg;
+ uiBlock *block;
+ uiLayout *layout;
+
+ block = UI_block_begin(C, ar, "_popup", UI_EMBOSS);
+ UI_block_flag_disable(block, UI_BLOCK_LOOP);
+ UI_block_flag_enable(block, UI_BLOCK_KEEP_OPEN | UI_BLOCK_MOVEMOUSE_QUIT);
+
+ layout = UI_block_layout(block, UI_LAYOUT_VERTICAL, UI_LAYOUT_PANEL, 0, 0, UI_UNIT_X * 20, UI_UNIT_Y, 0, UI_style_get());
+
+ archive_info_labels(layout, info);
+
+ UI_block_bounds_set_centered(block, 0);
+ UI_block_direction_set(block, UI_DIR_DOWN);
+
+ return block;
+}
+#endif
+
+static void print_stream(void *UNUSED(userdata), const char *s)
+{
+ printf("%s", s);
+}
+
+static int cache_library_archive_info_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = CTX_data_active_object(C);
+ CacheLibrary *cachelib = ob->cache_library;
+ Scene *scene = CTX_data_scene(C);
+
+ const bool use_cache_info = RNA_boolean_get(op->ptr, "use_cache_info");
+ const bool calc_bytes_size = RNA_boolean_get(op->ptr, "calc_bytes_size");
+ const bool use_stdout = RNA_boolean_get(op->ptr, "use_stdout");
+ const bool use_popup = RNA_boolean_get(op->ptr, "use_popup");
+ const bool use_clipboard = RNA_boolean_get(op->ptr, "use_clipboard");
+
+ char filepath[FILE_MAX], filename[FILE_MAX];
+ struct PTCReaderArchive *archive;
+
+ RNA_string_get(op->ptr, "filepath", filepath);
+ if (filepath[0] == '\0')
+ return OPERATOR_CANCELLED;
+
+ BKE_cache_archive_path_ex(filepath, cachelib->id.lib, NULL, filename, sizeof(filename));
+ archive = PTC_open_reader_archive(scene, filename);
+ if (!archive) {
+ BKE_reportf(op->reports, RPT_ERROR, "Cannot open cache file at '%s'", filepath);
+ return OPERATOR_CANCELLED;
+ }
+
+ if (use_cache_info) {
+ if (cachelib->archive_info)
+ BKE_cache_archive_info_clear(cachelib->archive_info);
+ else
+ cachelib->archive_info = BKE_cache_archive_info_new();
+
+ BLI_strncpy(cachelib->archive_info->filepath, filename, sizeof(cachelib->archive_info->filepath));
+
+ PTC_get_archive_info_nodes(archive, cachelib->archive_info, calc_bytes_size);
+ }
+
+ if (use_stdout) {
+ PTC_get_archive_info_stream(archive, print_stream, NULL);
+ }
+
+ if (use_popup) {
+// UI_popup_block_invoke(C, archive_info_popup_create, info);
+ }
+
+ if (use_clipboard) {
+// WM_clipboard_text_set(info, false);
+ }
+
+ PTC_close_reader_archive(archive);
+
+ return OPERATOR_FINISHED;
+}
+
+void CACHELIBRARY_OT_archive_info(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Archive Info";
+ ot->description = "Get archive details from a cache library archive";
+ ot->idname = "CACHELIBRARY_OT_archive_info";
+
+ /* api callbacks */
+ ot->exec = cache_library_archive_info_exec;
+ ot->poll = ED_cache_library_active_object_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_string(ot->srna, "filepath", NULL, FILE_MAX, "File Path", "Path to the cache archive");
+ RNA_def_boolean(ot->srna, "use_cache_info", false, "Use Cache Library Info", "Store info in the cache library");
+ RNA_def_boolean(ot->srna, "calc_bytes_size", false, "Calculate Size", "Calculate overall size of nodes in bytes (can take a while)");
+ RNA_def_boolean(ot->srna, "use_stdout", false, "Use stdout", "Print info in standard output");
+ RNA_def_boolean(ot->srna, "use_popup", false, "Show Popup", "Display archive info in a popup");
+ RNA_def_boolean(ot->srna, "use_clipboard", false, "Copy to Clipboard", "Copy archive info to the clipboard");
+}
+
+/* ------------------------------------------------------------------------- */
+/* Cache Modifiers */
+
+static int cache_library_add_modifier_exec(bContext *C, wmOperator *op)
+{
+ Object *ob = CTX_data_active_object(C);
+ CacheLibrary *cachelib = ob->cache_library;
+
+ eCacheModifier_Type type = RNA_enum_get(op->ptr, "type");
+ if (type == eCacheModifierType_None) {
+ return OPERATOR_CANCELLED;
+ }
+
+ BKE_cache_modifier_add(cachelib, NULL, type);
+
+ return OPERATOR_FINISHED;
+}
+
+void CACHELIBRARY_OT_add_modifier(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add Cache Modifier";
+ ot->description = "Add a cache modifier";
+ ot->idname = "CACHELIBRARY_OT_add_modifier";
+
+ /* api callbacks */
+ ot->exec = cache_library_add_modifier_exec;
+ ot->poll = ED_cache_library_active_object_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_enum(ot->srna, "type", cache_modifier_type_items, eCacheModifierType_None, "Type", "Type of modifier to add");
+}
+
+static int cache_library_remove_modifier_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ PointerRNA md_ptr = CTX_data_pointer_get_type(C, "cache_modifier", &RNA_CacheLibraryModifier);
+ CacheModifier *md = md_ptr.data;
+ CacheLibrary *cachelib = md_ptr.id.data;
+
+ if (!md)
+ return OPERATOR_CANCELLED;
+
+ BKE_cache_modifier_remove(cachelib, md);
+
+ return OPERATOR_FINISHED;
+}
+
+void CACHELIBRARY_OT_remove_modifier(struct wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Cache Modifier";
+ ot->description = "Remove a cache modifier";
+ ot->idname = "CACHELIBRARY_OT_remove_modifier";
+
+ /* api callbacks */
+ ot->exec = cache_library_remove_modifier_exec;
+ ot->poll = ED_cache_modifier_poll;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
diff --git a/source/blender/editors/io/io_cache_library.h b/source/blender/editors/io/io_cache_library.h
new file mode 100644
index 00000000000..78959e6900c
--- /dev/null
+++ b/source/blender/editors/io/io_cache_library.h
@@ -0,0 +1,56 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2015 Blender Foundation.
+ * All rights reserved.
+ *
+ * Contributor(s): Blender Foundation
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/io/io_cache_library.h
+ * \ingroup editor/io
+ */
+
+#ifndef __IO_CACHE_LIBRARY_H__
+#define __IO_CACHE_LIBRARY_H__
+
+struct wmOperatorType;
+
+void CACHELIBRARY_OT_new(struct wmOperatorType *ot);
+void CACHELIBRARY_OT_delete(struct wmOperatorType *ot);
+
+void CACHELIBRARY_OT_bake(struct wmOperatorType *ot);
+
+void CACHELIBRARY_OT_archive_info(struct wmOperatorType *ot);
+void CACHELIBRARY_OT_archive_slice(struct wmOperatorType *ot);
+
+/* ------------------------------------------------------------------------- */
+/* Cache Modifiers */
+
+void CACHELIBRARY_OT_add_modifier(struct wmOperatorType *ot);
+void CACHELIBRARY_OT_remove_modifier(struct wmOperatorType *ot);
+
+/* cache_shapekey.c */
+void CACHELIBRARY_OT_shape_key_add(struct wmOperatorType *ot);
+void CACHELIBRARY_OT_shape_key_remove(struct wmOperatorType *ot);
+void CACHELIBRARY_OT_shape_key_clear(struct wmOperatorType *ot);
+void CACHELIBRARY_OT_shape_key_retime(struct wmOperatorType *ot);
+void CACHELIBRARY_OT_shape_key_move(struct wmOperatorType *ot);
+
+#endif
diff --git a/source/blender/editors/io/io_cache_shapekey.c b/source/blender/editors/io/io_cache_shapekey.c
new file mode 100644
index 00000000000..e28a16b8e55
--- /dev/null
+++ b/source/blender/editors/io/io_cache_shapekey.c
@@ -0,0 +1,417 @@
+/*
+ * ***** BEGIN GPL LICENSE BLOCK *****
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2015 Blender Foundation.
+ * All rights reserved.
+ *
+ * Contributor(s): Blender Foundation
+ *
+ * ***** END GPL LICENSE BLOCK *****
+ */
+
+/** \file blender/editors/io/io_cache_shapekey.c
+ * \ingroup editor/io
+ */
+
+
+#include <math.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_blenlib.h"
+#include "BLI_math.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_cache_library_types.h"
+#include "DNA_key_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_strands_types.h"
+
+#include "BKE_cache_library.h"
+#include "BKE_context.h"
+#include "BKE_depsgraph.h"
+#include "BKE_key.h"
+#include "BKE_library.h"
+#include "BKE_main.h"
+#include "BKE_object.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "io_cache_library.h"
+
+/*********************** add shape key ***********************/
+
+static void ED_cache_shape_key_add(bContext *C, StrandsKeyCacheModifier *skmd, Strands *strands, const bool from_mix)
+{
+ KeyBlock *kb;
+ if ((kb = BKE_cache_modifier_strands_key_insert_key(skmd, strands, NULL, from_mix))) {
+ Key *key = skmd->key;
+ /* for absolute shape keys, new keys may not be added last */
+ skmd->shapenr = BLI_findindex(&key->block, kb) + 1;
+
+ WM_event_add_notifier(C, NC_WINDOW, NULL);
+ }
+}
+
+/*********************** remove shape key ***********************/
+
+static void keyblock_free(StrandsKeyCacheModifier *skmd, KeyBlock *kb)
+{
+ Key *key = skmd->key;
+
+ BLI_remlink(&key->block, kb);
+ key->totkey--;
+
+ if (kb->data) MEM_freeN(kb->data);
+ MEM_freeN(kb);
+}
+
+static bool ED_cache_shape_key_remove_all(StrandsKeyCacheModifier *skmd, Strands *UNUSED(strands))
+{
+ Key *key = skmd->key;
+ KeyBlock *kb, *kb_next;
+
+ if (key == NULL)
+ return false;
+
+ for (kb = key->block.first; kb; kb = kb_next) {
+ kb_next = kb->next;
+
+ keyblock_free(skmd, kb);
+ }
+
+ key->refkey = NULL;
+ skmd->shapenr = 0;
+
+ return true;
+}
+
+static bool ED_cache_shape_key_remove(StrandsKeyCacheModifier *skmd, Strands *strands)
+{
+ Key *key = skmd->key;
+ KeyBlock *kb, *rkb;
+
+ if (key == NULL)
+ return false;
+
+ kb = BLI_findlink(&key->block, skmd->shapenr - 1);
+ if (kb) {
+ for (rkb = key->block.first; rkb; rkb = rkb->next) {
+ if (rkb->relative == skmd->shapenr - 1) {
+ /* remap to the 'Basis' */
+ rkb->relative = 0;
+ }
+ else if (rkb->relative >= skmd->shapenr) {
+ /* Fix positional shift of the keys when kb is deleted from the list */
+ rkb->relative -= 1;
+ }
+ }
+
+ keyblock_free(skmd, kb);
+
+ if (key->refkey == kb) {
+ key->refkey = key->block.first;
+
+ if (key->refkey) {
+ /* apply new basis key on original data */
+ BKE_keyblock_convert_to_strands(key->refkey, strands, skmd->flag & eStrandsKeyCacheModifier_Flag_UseMotionState);
+ }
+ }
+
+ if (skmd->shapenr > 1) {
+ skmd->shapenr--;
+ }
+ }
+
+ return true;
+}
+
+/********************** shape key operators *********************/
+
+static bool shape_key_get_context(bContext *C, CacheLibrary **r_cachelib, StrandsKeyCacheModifier **r_skmd, Strands **r_strands)
+{
+ CacheLibrary *cachelib = CTX_data_pointer_get_type(C, "cache_library", &RNA_CacheLibrary).data;
+ CacheModifier *md = CTX_data_pointer_get_type(C, "cache_modifier", &RNA_CacheLibraryModifier).data;
+ StrandsKeyCacheModifier *skmd;
+ Object *ob = CTX_data_active_object(C);
+ Strands *strands;
+
+ if (!(cachelib && !cachelib->id.lib && md && md->type == eCacheModifierType_StrandsKey))
+ return false;
+ skmd = (StrandsKeyCacheModifier *)md;
+
+ if (!(ob && ob->dup_cache && (ob->transflag & OB_DUPLIGROUP) && ob->dup_group))
+ return false;
+ if (!BKE_cache_modifier_find_strands(ob->dup_cache, skmd->object, skmd->hair_system, NULL, &strands, NULL, NULL))
+ return false;
+
+ if (r_cachelib) *r_cachelib = cachelib;
+ if (r_skmd) *r_skmd = skmd;
+ if (r_strands) *r_strands = strands;
+ return true;
+}
+
+static int shape_key_poll(bContext *C)
+{
+ return shape_key_get_context(C, NULL, NULL, NULL);
+}
+
+static int shape_key_exists_poll(bContext *C)
+{
+ StrandsKeyCacheModifier *skmd;
+
+ if (!shape_key_get_context(C, NULL, &skmd, NULL))
+ return false;
+
+ return (skmd->key && skmd->shapenr >= 0 && skmd->shapenr < skmd->key->totkey);
+}
+
+static int shape_key_move_poll(bContext *C)
+{
+ StrandsKeyCacheModifier *skmd;
+
+ if (!shape_key_get_context(C, NULL, &skmd, NULL))
+ return false;
+
+ return (skmd->key != NULL && skmd->key->totkey > 1);
+}
+
+static int shape_key_add_exec(bContext *C, wmOperator *op)
+{
+ const bool from_mix = RNA_boolean_get(op->ptr, "from_mix");
+ CacheLibrary *cachelib;
+ StrandsKeyCacheModifier *skmd;
+ Strands *strands;
+
+ shape_key_get_context(C, &cachelib, &skmd, &strands);
+
+ ED_cache_shape_key_add(C, skmd, strands, from_mix);
+
+ DAG_id_tag_update(&cachelib->id, OB_RECALC_DATA);
+
+ return OPERATOR_FINISHED;
+}
+
+void CACHELIBRARY_OT_shape_key_add(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Add Shape Key";
+ ot->idname = "CACHELIBRARY_OT_shape_key_add";
+ ot->description = "Add shape key to the object";
+
+ /* api callbacks */
+ ot->poll = shape_key_poll;
+ ot->exec = shape_key_add_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_boolean(ot->srna, "from_mix", true, "From Mix", "Create the new shape key from the existing mix of keys");
+}
+
+static int shape_key_remove_exec(bContext *C, wmOperator *op)
+{
+ CacheLibrary *cachelib;
+ StrandsKeyCacheModifier *skmd;
+ Strands *strands;
+ bool changed = false;
+
+ shape_key_get_context(C, &cachelib, &skmd, &strands);
+
+ if (RNA_boolean_get(op->ptr, "all")) {
+ changed = ED_cache_shape_key_remove_all(skmd, strands);
+ }
+ else {
+ changed = ED_cache_shape_key_remove(skmd, strands);
+ }
+
+ if (changed) {
+ DAG_id_tag_update(&cachelib->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_WINDOW, NULL);
+
+ return OPERATOR_FINISHED;
+ }
+ else {
+ return OPERATOR_CANCELLED;
+ }
+}
+
+void CACHELIBRARY_OT_shape_key_remove(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Remove Shape Key";
+ ot->idname = "CACHELIBRARY_OT_shape_key_remove";
+ ot->description = "Remove shape key from the object";
+
+ /* api callbacks */
+ ot->poll = shape_key_exists_poll;
+ ot->exec = shape_key_remove_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ /* properties */
+ RNA_def_boolean(ot->srna, "all", 0, "All", "Remove all shape keys");
+}
+
+static int shape_key_clear_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ CacheLibrary *cachelib;
+ StrandsKeyCacheModifier *skmd;
+ Strands *strands;
+ KeyBlock *kb;
+
+ shape_key_get_context(C, &cachelib, &skmd, &strands);
+
+ for (kb = skmd->key->block.first; kb; kb = kb->next)
+ kb->curval = 0.0f;
+
+ DAG_id_tag_update(&cachelib->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_WINDOW, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+void CACHELIBRARY_OT_shape_key_clear(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Clear Shape Keys";
+ ot->description = "Clear weights for all shape keys";
+ ot->idname = "CACHELIBRARY_OT_shape_key_clear";
+
+ /* api callbacks */
+ ot->poll = shape_key_poll;
+ ot->exec = shape_key_clear_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+/* starting point and step size could be optional */
+static int shape_key_retime_exec(bContext *C, wmOperator *UNUSED(op))
+{
+ CacheLibrary *cachelib;
+ StrandsKeyCacheModifier *skmd;
+ Strands *strands;
+ KeyBlock *kb;
+ float cfra = 0.0f;
+
+ shape_key_get_context(C, &cachelib, &skmd, &strands);
+
+ for (kb = skmd->key->block.first; kb; kb = kb->next)
+ kb->pos = (cfra += 0.1f);
+
+ DAG_id_tag_update(&cachelib->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_WINDOW, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+void CACHELIBRARY_OT_shape_key_retime(wmOperatorType *ot)
+{
+ /* identifiers */
+ ot->name = "Re-Time Shape Keys";
+ ot->description = "Resets the timing for absolute shape keys";
+ ot->idname = "CACHELIBRARY_OT_shape_key_retime";
+
+ /* api callbacks */
+ ot->poll = shape_key_poll;
+ ot->exec = shape_key_retime_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+}
+
+
+enum {
+ KB_MOVE_TOP = -2,
+ KB_MOVE_UP = -1,
+ KB_MOVE_DOWN = 1,
+ KB_MOVE_BOTTOM = 2,
+};
+
+static int shape_key_move_exec(bContext *C, wmOperator *op)
+{
+ const int type = RNA_enum_get(op->ptr, "type");
+ CacheLibrary *cachelib;
+ StrandsKeyCacheModifier *skmd;
+ Strands *strands;
+ Key *key;
+ int totkey, act_index, new_index;
+
+ shape_key_get_context(C, &cachelib, &skmd, &strands);
+ key = skmd->key;
+ totkey = key->totkey;
+ act_index = skmd->shapenr - 1;
+
+ switch (type) {
+ case KB_MOVE_TOP:
+ /* Replace the ref key only if we're at the top already (only for relative keys) */
+ new_index = (ELEM(act_index, 0, 1) || key->type == KEY_NORMAL) ? 0 : 1;
+ break;
+ case KB_MOVE_BOTTOM:
+ new_index = totkey - 1;
+ break;
+ case KB_MOVE_UP:
+ case KB_MOVE_DOWN:
+ default:
+ new_index = (totkey + act_index + type) % totkey;
+ break;
+ }
+
+ if (!BKE_keyblock_move_ex(key, &skmd->shapenr, act_index, new_index)) {
+ return OPERATOR_CANCELLED;
+ }
+
+ DAG_id_tag_update(&cachelib->id, OB_RECALC_DATA);
+ WM_event_add_notifier(C, NC_WINDOW, NULL);
+
+ return OPERATOR_FINISHED;
+}
+
+void CACHELIBRARY_OT_shape_key_move(wmOperatorType *ot)
+{
+ static EnumPropertyItem slot_move[] = {
+ {KB_MOVE_TOP, "TOP", 0, "Top", "Top of the list"},
+ {KB_MOVE_UP, "UP", 0, "Up", ""},
+ {KB_MOVE_DOWN, "DOWN", 0, "Down", ""},
+ {KB_MOVE_BOTTOM, "BOTTOM", 0, "Bottom", "Bottom of the list"},
+ { 0, NULL, 0, NULL, NULL }
+ };
+
+ /* identifiers */
+ ot->name = "Move Shape Key";
+ ot->idname = "CACHELIBRARY_OT_shape_key_move";
+ ot->description = "Move the active shape key up/down in the list";
+
+ /* api callbacks */
+ ot->poll = shape_key_move_poll;
+ ot->exec = shape_key_move_exec;
+
+ /* flags */
+ ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
+
+ RNA_def_enum(ot->srna, "type", slot_move, 0, "Type", "");
+}
+
diff --git a/source/blender/editors/io/io_ops.c b/source/blender/editors/io/io_ops.c
index a70a51a60be..49b1553ee78 100644
--- a/source/blender/editors/io/io_ops.c
+++ b/source/blender/editors/io/io_ops.c
@@ -30,13 +30,30 @@
#include "io_ops.h" /* own include */
+#include "io_cache_library.h"
#ifdef WITH_COLLADA
# include "io_collada.h"
-# include "WM_api.h"
#endif
+#include "WM_api.h"
+
void ED_operatortypes_io(void)
{
+ WM_operatortype_append(CACHELIBRARY_OT_new);
+ WM_operatortype_append(CACHELIBRARY_OT_delete);
+ WM_operatortype_append(CACHELIBRARY_OT_bake);
+ WM_operatortype_append(CACHELIBRARY_OT_archive_info);
+ WM_operatortype_append(CACHELIBRARY_OT_archive_slice);
+
+ WM_operatortype_append(CACHELIBRARY_OT_add_modifier);
+ WM_operatortype_append(CACHELIBRARY_OT_remove_modifier);
+
+ WM_operatortype_append(CACHELIBRARY_OT_shape_key_add);
+ WM_operatortype_append(CACHELIBRARY_OT_shape_key_remove);
+ WM_operatortype_append(CACHELIBRARY_OT_shape_key_clear);
+ WM_operatortype_append(CACHELIBRARY_OT_shape_key_retime);
+ WM_operatortype_append(CACHELIBRARY_OT_shape_key_move);
+
#ifdef WITH_COLLADA
/* Collada operators: */
WM_operatortype_append(WM_OT_collada_export);