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:
authorSybren A. Stüvel <sybren@blender.org>2020-03-06 18:19:35 +0300
committerSybren A. Stüvel <sybren@blender.org>2020-03-06 18:19:45 +0300
commiteb522af4fec58876ac1b0a73ad9bcdae2d82d33f (patch)
tree485c6a1fb23b5be256757375e2157378d3a5c61b /source/blender/io
parentff60dd8b18ed00902e5bdfd36882072db7af8735 (diff)
Cleanup: move Alembic, AVI, Collada, and USD to `source/blender/io`
This moves the `alembic`, `avi`, `collada`, and `usd` modules into a common `io` directory. This also cleans up some `#include "../../{somedir}/{somefile}.h"` by adding `../../io/{somedir}` to `CMakeLists.txt` and then just using `#include "{somefile}.h"`. No functional changes.
Diffstat (limited to 'source/blender/io')
-rw-r--r--source/blender/io/CMakeLists.txt35
-rw-r--r--source/blender/io/alembic/ABC_alembic.h141
-rw-r--r--source/blender/io/alembic/CMakeLists.txt110
-rw-r--r--source/blender/io/alembic/intern/abc_customdata.cc484
-rw-r--r--source/blender/io/alembic/intern/abc_customdata.h104
-rw-r--r--source/blender/io/alembic/intern/abc_exporter.cc677
-rw-r--r--source/blender/io/alembic/intern/abc_exporter.h128
-rw-r--r--source/blender/io/alembic/intern/abc_reader_archive.cc140
-rw-r--r--source/blender/io/alembic/intern/abc_reader_archive.h67
-rw-r--r--source/blender/io/alembic/intern/abc_reader_camera.cc113
-rw-r--r--source/blender/io/alembic/intern/abc_reader_camera.h40
-rw-r--r--source/blender/io/alembic/intern/abc_reader_curves.cc354
-rw-r--r--source/blender/io/alembic/intern/abc_reader_curves.h56
-rw-r--r--source/blender/io/alembic/intern/abc_reader_mesh.cc889
-rw-r--r--source/blender/io/alembic/intern/abc_reader_mesh.h86
-rw-r--r--source/blender/io/alembic/intern/abc_reader_nurbs.cc225
-rw-r--r--source/blender/io/alembic/intern/abc_reader_nurbs.h40
-rw-r--r--source/blender/io/alembic/intern/abc_reader_object.cc333
-rw-r--r--source/blender/io/alembic/intern/abc_reader_object.h171
-rw-r--r--source/blender/io/alembic/intern/abc_reader_points.cc157
-rw-r--r--source/blender/io/alembic/intern/abc_reader_points.h54
-rw-r--r--source/blender/io/alembic/intern/abc_reader_transform.cc76
-rw-r--r--source/blender/io/alembic/intern/abc_reader_transform.h42
-rw-r--r--source/blender/io/alembic/intern/abc_util.cc393
-rw-r--r--source/blender/io/alembic/intern/abc_util.h236
-rw-r--r--source/blender/io/alembic/intern/abc_writer_archive.cc114
-rw-r--r--source/blender/io/alembic/intern/abc_writer_archive.h58
-rw-r--r--source/blender/io/alembic/intern/abc_writer_camera.cc81
-rw-r--r--source/blender/io/alembic/intern/abc_writer_camera.h45
-rw-r--r--source/blender/io/alembic/intern/abc_writer_curves.cc189
-rw-r--r--source/blender/io/alembic/intern/abc_writer_curves.h55
-rw-r--r--source/blender/io/alembic/intern/abc_writer_hair.cc292
-rw-r--r--source/blender/io/alembic/intern/abc_writer_hair.h62
-rw-r--r--source/blender/io/alembic/intern/abc_writer_mball.cc97
-rw-r--r--source/blender/io/alembic/intern/abc_writer_mball.h56
-rw-r--r--source/blender/io/alembic/intern/abc_writer_mesh.cc592
-rw-r--r--source/blender/io/alembic/intern/abc_writer_mesh.h91
-rw-r--r--source/blender/io/alembic/intern/abc_writer_nurbs.cc172
-rw-r--r--source/blender/io/alembic/intern/abc_writer_nurbs.h42
-rw-r--r--source/blender/io/alembic/intern/abc_writer_object.cc79
-rw-r--r--source/blender/io/alembic/intern/abc_writer_object.h71
-rw-r--r--source/blender/io/alembic/intern/abc_writer_points.cc123
-rw-r--r--source/blender/io/alembic/intern/abc_writer_points.h49
-rw-r--r--source/blender/io/alembic/intern/abc_writer_transform.cc121
-rw-r--r--source/blender/io/alembic/intern/abc_writer_transform.h60
-rw-r--r--source/blender/io/alembic/intern/alembic_capi.cc1052
-rw-r--r--source/blender/io/avi/AVI_avi.h299
-rw-r--r--source/blender/io/avi/CMakeLists.txt53
-rw-r--r--source/blender/io/avi/intern/avi.c1056
-rw-r--r--source/blender/io/avi/intern/avi_codecs.c138
-rw-r--r--source/blender/io/avi/intern/avi_endian.c203
-rw-r--r--source/blender/io/avi/intern/avi_endian.h40
-rw-r--r--source/blender/io/avi/intern/avi_intern.h65
-rw-r--r--source/blender/io/avi/intern/avi_mjpeg.c547
-rw-r--r--source/blender/io/avi/intern/avi_mjpeg.h30
-rw-r--r--source/blender/io/avi/intern/avi_options.c148
-rw-r--r--source/blender/io/avi/intern/avi_rgb.c154
-rw-r--r--source/blender/io/avi/intern/avi_rgb.h30
-rw-r--r--source/blender/io/avi/intern/avi_rgb32.c94
-rw-r--r--source/blender/io/avi/intern/avi_rgb32.h30
-rw-r--r--source/blender/io/collada/AnimationClipExporter.cpp50
-rw-r--r--source/blender/io/collada/AnimationClipExporter.h51
-rw-r--r--source/blender/io/collada/AnimationExporter.cpp877
-rw-r--r--source/blender/io/collada/AnimationExporter.h266
-rw-r--r--source/blender/io/collada/AnimationImporter.cpp2232
-rw-r--r--source/blender/io/collada/AnimationImporter.h254
-rw-r--r--source/blender/io/collada/ArmatureExporter.cpp328
-rw-r--r--source/blender/io/collada/ArmatureExporter.h107
-rw-r--r--source/blender/io/collada/ArmatureImporter.cpp1115
-rw-r--r--source/blender/io/collada/ArmatureImporter.h187
-rw-r--r--source/blender/io/collada/BCAnimationCurve.cpp691
-rw-r--r--source/blender/io/collada/BCAnimationCurve.h152
-rw-r--r--source/blender/io/collada/BCAnimationSampler.cpp662
-rw-r--r--source/blender/io/collada/BCAnimationSampler.h194
-rw-r--r--source/blender/io/collada/BCMath.cpp244
-rw-r--r--source/blender/io/collada/BCMath.h110
-rw-r--r--source/blender/io/collada/BCSampleData.cpp97
-rw-r--r--source/blender/io/collada/BCSampleData.h66
-rw-r--r--source/blender/io/collada/BlenderContext.cpp156
-rw-r--r--source/blender/io/collada/BlenderContext.h73
-rw-r--r--source/blender/io/collada/BlenderTypes.h48
-rw-r--r--source/blender/io/collada/CMakeLists.txt147
-rw-r--r--source/blender/io/collada/CameraExporter.cpp98
-rw-r--r--source/blender/io/collada/CameraExporter.h46
-rw-r--r--source/blender/io/collada/ControllerExporter.cpp649
-rw-r--r--source/blender/io/collada/ControllerExporter.h137
-rw-r--r--source/blender/io/collada/DocumentExporter.cpp346
-rw-r--r--source/blender/io/collada/DocumentExporter.h44
-rw-r--r--source/blender/io/collada/DocumentImporter.cpp1265
-rw-r--r--source/blender/io/collada/DocumentImporter.h172
-rw-r--r--source/blender/io/collada/EffectExporter.cpp312
-rw-r--r--source/blender/io/collada/EffectExporter.h89
-rw-r--r--source/blender/io/collada/ErrorHandler.cpp118
-rw-r--r--source/blender/io/collada/ErrorHandler.h57
-rw-r--r--source/blender/io/collada/ExportSettings.cpp21
-rw-r--r--source/blender/io/collada/ExportSettings.h295
-rw-r--r--source/blender/io/collada/ExtraHandler.cpp93
-rw-r--r--source/blender/io/collada/ExtraHandler.h83
-rw-r--r--source/blender/io/collada/ExtraTags.cpp126
-rw-r--r--source/blender/io/collada/ExtraTags.h77
-rw-r--r--source/blender/io/collada/GeometryExporter.cpp718
-rw-r--r--source/blender/io/collada/GeometryExporter.h140
-rw-r--r--source/blender/io/collada/ImageExporter.cpp169
-rw-r--r--source/blender/io/collada/ImageExporter.h51
-rw-r--r--source/blender/io/collada/ImportSettings.cpp21
-rw-r--r--source/blender/io/collada/ImportSettings.h34
-rw-r--r--source/blender/io/collada/InstanceWriter.cpp70
-rw-r--r--source/blender/io/collada/InstanceWriter.h35
-rw-r--r--source/blender/io/collada/LightExporter.cpp156
-rw-r--r--source/blender/io/collada/LightExporter.h44
-rw-r--r--source/blender/io/collada/MaterialExporter.cpp75
-rw-r--r--source/blender/io/collada/MaterialExporter.h98
-rw-r--r--source/blender/io/collada/Materials.cpp396
-rw-r--r--source/blender/io/collada/Materials.h76
-rw-r--r--source/blender/io/collada/MeshImporter.cpp1208
-rw-r--r--source/blender/io/collada/MeshImporter.h182
-rw-r--r--source/blender/io/collada/SceneExporter.cpp242
-rw-r--r--source/blender/io/collada/SceneExporter.h117
-rw-r--r--source/blender/io/collada/SkinInfo.cpp357
-rw-r--r--source/blender/io/collada/SkinInfo.h130
-rw-r--r--source/blender/io/collada/TransformReader.cpp151
-rw-r--r--source/blender/io/collada/TransformReader.h72
-rw-r--r--source/blender/io/collada/TransformWriter.cpp141
-rw-r--r--source/blender/io/collada/TransformWriter.h48
-rw-r--r--source/blender/io/collada/collada.cpp117
-rw-r--r--source/blender/io/collada/collada.h50
-rw-r--r--source/blender/io/collada/collada_internal.cpp340
-rw-r--r--source/blender/io/collada/collada_internal.h98
-rw-r--r--source/blender/io/collada/collada_utils.cpp1458
-rw-r--r--source/blender/io/collada/collada_utils.h399
-rw-r--r--source/blender/io/collada/version.conf1
-rw-r--r--source/blender/io/usd/CMakeLists.txt111
-rw-r--r--source/blender/io/usd/intern/abstract_hierarchy_iterator.cc595
-rw-r--r--source/blender/io/usd/intern/abstract_hierarchy_iterator.h251
-rw-r--r--source/blender/io/usd/intern/usd_capi.cc233
-rw-r--r--source/blender/io/usd/intern/usd_exporter_context.h44
-rw-r--r--source/blender/io/usd/intern/usd_hierarchy_iterator.cc150
-rw-r--r--source/blender/io/usd/intern/usd_hierarchy_iterator.h71
-rw-r--r--source/blender/io/usd/intern/usd_writer_abstract.cc147
-rw-r--r--source/blender/io/usd/intern/usd_writer_abstract.h77
-rw-r--r--source/blender/io/usd/intern/usd_writer_camera.cc111
-rw-r--r--source/blender/io/usd/intern/usd_writer_camera.h38
-rw-r--r--source/blender/io/usd/intern/usd_writer_hair.cc90
-rw-r--r--source/blender/io/usd/intern/usd_writer_hair.h38
-rw-r--r--source/blender/io/usd/intern/usd_writer_light.cc112
-rw-r--r--source/blender/io/usd/intern/usd_writer_light.h37
-rw-r--r--source/blender/io/usd/intern/usd_writer_mesh.cc489
-rw-r--r--source/blender/io/usd/intern/usd_writer_mesh.h66
-rw-r--r--source/blender/io/usd/intern/usd_writer_metaball.cc81
-rw-r--r--source/blender/io/usd/intern/usd_writer_metaball.h42
-rw-r--r--source/blender/io/usd/intern/usd_writer_transform.cc64
-rw-r--r--source/blender/io/usd/intern/usd_writer_transform.h42
-rw-r--r--source/blender/io/usd/usd.h63
153 files changed, 33820 insertions, 0 deletions
diff --git a/source/blender/io/CMakeLists.txt b/source/blender/io/CMakeLists.txt
new file mode 100644
index 00000000000..bc2f8d628e2
--- /dev/null
+++ b/source/blender/io/CMakeLists.txt
@@ -0,0 +1,35 @@
+# ***** 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) 2020, Blender Foundation
+# All rights reserved.
+# ***** END GPL LICENSE BLOCK *****
+
+if(WITH_ALEMBIC)
+ add_subdirectory(alembic)
+endif()
+
+if(WITH_CODEC_AVI)
+ add_subdirectory(avi)
+endif()
+
+if(WITH_OPENCOLLADA)
+ add_subdirectory(collada)
+endif()
+
+if(WITH_USD)
+ add_subdirectory(usd)
+endif()
diff --git a/source/blender/io/alembic/ABC_alembic.h b/source/blender/io/alembic/ABC_alembic.h
new file mode 100644
index 00000000000..878dbfc2a53
--- /dev/null
+++ b/source/blender/io/alembic/ABC_alembic.h
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_ALEMBIC_H__
+#define __ABC_ALEMBIC_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct CacheReader;
+struct ListBase;
+struct Main;
+struct Mesh;
+struct Object;
+struct Scene;
+struct bContext;
+
+typedef struct AbcArchiveHandle AbcArchiveHandle;
+
+enum {
+ ABC_ARCHIVE_OGAWA = 0,
+ ABC_ARCHIVE_HDF5 = 1,
+};
+
+int ABC_get_version(void);
+
+struct AlembicExportParams {
+ double frame_start;
+ double frame_end;
+
+ unsigned int frame_samples_xform;
+ unsigned int frame_samples_shape;
+
+ double shutter_open;
+ double shutter_close;
+
+ bool selected_only;
+ bool uvs;
+ bool normals;
+ bool vcolors;
+ bool apply_subdiv;
+ bool curves_as_mesh;
+ bool flatten_hierarchy;
+ bool visible_objects_only;
+ bool renderable_only;
+ bool face_sets;
+ bool use_subdiv_schema;
+ bool packuv;
+ bool triangulate;
+ bool export_hair;
+ bool export_particles;
+
+ unsigned int compression_type : 1;
+
+ /* See MOD_TRIANGULATE_NGON_xxx and MOD_TRIANGULATE_QUAD_xxx
+ * in DNA_modifier_types.h */
+ int quad_method;
+ int ngon_method;
+
+ float global_scale;
+};
+
+/* The ABC_export and ABC_import functions both take a as_background_job
+ * parameter, and return a boolean.
+ *
+ * When as_background_job=true, returns false immediately after scheduling
+ * a background job.
+ *
+ * When as_background_job=false, performs the export synchronously, and returns
+ * true when the export was ok, and false if there were any errors.
+ */
+
+bool ABC_export(struct Scene *scene,
+ struct bContext *C,
+ const char *filepath,
+ const struct AlembicExportParams *params,
+ bool as_background_job);
+
+bool ABC_import(struct bContext *C,
+ const char *filepath,
+ float scale,
+ bool is_sequence,
+ bool set_frame_range,
+ int sequence_len,
+ int offset,
+ bool validate_meshes,
+ bool as_background_job);
+
+AbcArchiveHandle *ABC_create_handle(struct Main *bmain,
+ const char *filename,
+ struct ListBase *object_paths);
+
+void ABC_free_handle(AbcArchiveHandle *handle);
+
+void ABC_get_transform(struct CacheReader *reader, float r_mat[4][4], float time, float scale);
+
+/* Either modifies current_mesh in-place or constructs a new mesh. */
+struct Mesh *ABC_read_mesh(struct CacheReader *reader,
+ struct Object *ob,
+ struct Mesh *current_mesh,
+ const float time,
+ const char **err_str,
+ int flags);
+
+bool ABC_mesh_topology_changed(struct CacheReader *reader,
+ struct Object *ob,
+ struct Mesh *existing_mesh,
+ const float time,
+ const char **err_str);
+
+void CacheReader_incref(struct CacheReader *reader);
+void CacheReader_free(struct CacheReader *reader);
+
+struct CacheReader *CacheReader_open_alembic_object(struct AbcArchiveHandle *handle,
+ struct CacheReader *reader,
+ struct Object *object,
+ const char *object_path);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __ABC_ALEMBIC_H__ */
diff --git a/source/blender/io/alembic/CMakeLists.txt b/source/blender/io/alembic/CMakeLists.txt
new file mode 100644
index 00000000000..cbcdfaf4b77
--- /dev/null
+++ b/source/blender/io/alembic/CMakeLists.txt
@@ -0,0 +1,110 @@
+# ***** 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) 2006, Blender Foundation
+# All rights reserved.
+# ***** END GPL LICENSE BLOCK *****
+
+set(INC
+ .
+ ../../blenkernel
+ ../../blenlib
+ ../../blenloader
+ ../../bmesh
+ ../../depsgraph
+ ../../editors/include
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/guardedalloc
+ ../../../../intern/utfconv
+)
+
+set(INC_SYS
+ ${ALEMBIC_INCLUDE_DIRS}
+ ${BOOST_INCLUDE_DIR}
+ ${HDF5_INCLUDE_DIRS}
+ ${OPENEXR_INCLUDE_DIRS}
+)
+
+set(SRC
+ intern/abc_customdata.cc
+ intern/abc_exporter.cc
+ intern/abc_reader_archive.cc
+ intern/abc_reader_camera.cc
+ intern/abc_reader_curves.cc
+ intern/abc_reader_mesh.cc
+ intern/abc_reader_nurbs.cc
+ intern/abc_reader_object.cc
+ intern/abc_reader_points.cc
+ intern/abc_reader_transform.cc
+ intern/abc_util.cc
+ intern/abc_writer_archive.cc
+ intern/abc_writer_camera.cc
+ intern/abc_writer_curves.cc
+ intern/abc_writer_hair.cc
+ intern/abc_writer_mball.cc
+ intern/abc_writer_mesh.cc
+ intern/abc_writer_nurbs.cc
+ intern/abc_writer_object.cc
+ intern/abc_writer_points.cc
+ intern/abc_writer_transform.cc
+ intern/alembic_capi.cc
+
+ ABC_alembic.h
+ intern/abc_customdata.h
+ intern/abc_exporter.h
+ intern/abc_reader_archive.h
+ intern/abc_reader_camera.h
+ intern/abc_reader_curves.h
+ intern/abc_reader_mesh.h
+ intern/abc_reader_nurbs.h
+ intern/abc_reader_object.h
+ intern/abc_reader_points.h
+ intern/abc_reader_transform.h
+ intern/abc_util.h
+ intern/abc_writer_archive.h
+ intern/abc_writer_camera.h
+ intern/abc_writer_curves.h
+ intern/abc_writer_hair.h
+ intern/abc_writer_mball.h
+ intern/abc_writer_mesh.h
+ intern/abc_writer_nurbs.h
+ intern/abc_writer_object.h
+ intern/abc_writer_points.h
+ intern/abc_writer_transform.h
+)
+
+set(LIB
+ bf_blenkernel
+ bf_blenlib
+
+ ${ALEMBIC_LIBRARIES}
+ ${OPENEXR_LIBRARIES}
+)
+
+if(WITH_ALEMBIC_HDF5)
+ add_definitions(-DWITH_ALEMBIC_HDF5)
+ list(APPEND LIB
+ ${HDF5_LIBRARIES}
+ )
+endif()
+
+list(APPEND LIB
+ ${BOOST_LIBRARIES}
+)
+
+blender_add_lib(bf_alembic "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/io/alembic/intern/abc_customdata.cc b/source/blender/io/alembic/intern/abc_customdata.cc
new file mode 100644
index 00000000000..c5f60ac3e29
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_customdata.cc
@@ -0,0 +1,484 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_customdata.h"
+
+#include <Alembic/AbcGeom/All.h>
+#include <algorithm>
+#include <unordered_map>
+
+extern "C" {
+#include "DNA_customdata_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BLI_math_base.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_customdata.h"
+}
+
+/* NOTE: for now only UVs and Vertex Colors are supported for streaming.
+ * Although Alembic only allows for a single UV layer per {I|O}Schema, and does
+ * not have a vertex color concept, there is a convention between DCCs to write
+ * such data in a way that lets other DCC know what they are for. See comments
+ * in the write code for the conventions. */
+
+using Alembic::AbcGeom::kFacevaryingScope;
+using Alembic::AbcGeom::kVertexScope;
+
+using Alembic::Abc::C4fArraySample;
+using Alembic::Abc::UInt32ArraySample;
+using Alembic::Abc::V2fArraySample;
+
+using Alembic::AbcGeom::OC4fGeomParam;
+using Alembic::AbcGeom::OV2fGeomParam;
+
+static void get_uvs(const CDStreamConfig &config,
+ std::vector<Imath::V2f> &uvs,
+ std::vector<uint32_t> &uvidx,
+ void *cd_data)
+{
+ MLoopUV *mloopuv_array = static_cast<MLoopUV *>(cd_data);
+
+ if (!mloopuv_array) {
+ return;
+ }
+
+ const int num_poly = config.totpoly;
+ MPoly *polygons = config.mpoly;
+ MLoop *mloop = config.mloop;
+
+ if (!config.pack_uvs) {
+ int cnt = 0;
+ uvidx.resize(config.totloop);
+ uvs.resize(config.totloop);
+
+ /* Iterate in reverse order to match exported polygons. */
+ for (int i = 0; i < num_poly; i++) {
+ MPoly &current_poly = polygons[i];
+ MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop;
+
+ for (int j = 0; j < current_poly.totloop; j++, cnt++) {
+ loopuv--;
+
+ uvidx[cnt] = cnt;
+ uvs[cnt][0] = loopuv->uv[0];
+ uvs[cnt][1] = loopuv->uv[1];
+ }
+ }
+ }
+ else {
+ /* Mapping for indexed UVs, deduplicating UV coordinates at vertices. */
+ std::vector<std::vector<uint32_t>> idx_map(config.totvert);
+ int idx_count = 0;
+
+ for (int i = 0; i < num_poly; i++) {
+ MPoly &current_poly = polygons[i];
+ MLoop *looppoly = mloop + current_poly.loopstart + current_poly.totloop;
+ MLoopUV *loopuv = mloopuv_array + current_poly.loopstart + current_poly.totloop;
+
+ for (int j = 0; j < current_poly.totloop; j++) {
+ looppoly--;
+ loopuv--;
+
+ Imath::V2f uv(loopuv->uv[0], loopuv->uv[1]);
+ bool found_same = false;
+
+ /* Find UV already in uvs array. */
+ for (uint32_t uv_idx : idx_map[looppoly->v]) {
+ if (uvs[uv_idx] == uv) {
+ found_same = true;
+ uvidx.push_back(uv_idx);
+ break;
+ }
+ }
+
+ /* UV doesn't exists for this vertex, add it. */
+ if (!found_same) {
+ uint32_t uv_idx = idx_count++;
+ idx_map[looppoly->v].push_back(uv_idx);
+ uvidx.push_back(uv_idx);
+ uvs.push_back(uv);
+ }
+ }
+ }
+ }
+}
+
+const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data)
+{
+ const int active_uvlayer = CustomData_get_active_layer(data, CD_MLOOPUV);
+
+ if (active_uvlayer < 0) {
+ return "";
+ }
+
+ void *cd_data = CustomData_get_layer_n(data, CD_MLOOPUV, active_uvlayer);
+
+ get_uvs(config, sample.uvs, sample.indices, cd_data);
+
+ return CustomData_get_layer_name(data, CD_MLOOPUV, active_uvlayer);
+}
+
+/* Convention to write UVs:
+ * - V2fGeomParam on the arbGeomParam
+ * - set scope as face varying
+ * - (optional due to its behavior) tag as UV using Alembic::AbcGeom::SetIsUV
+ */
+static void write_uv(const OCompoundProperty &prop,
+ const CDStreamConfig &config,
+ void *data,
+ const char *name)
+{
+ std::vector<uint32_t> indices;
+ std::vector<Imath::V2f> uvs;
+
+ get_uvs(config, uvs, indices, data);
+
+ if (indices.empty() || uvs.empty()) {
+ return;
+ }
+
+ OV2fGeomParam param(prop, name, true, kFacevaryingScope, 1);
+
+ OV2fGeomParam::Sample sample(V2fArraySample(&uvs.front(), uvs.size()),
+ UInt32ArraySample(&indices.front(), indices.size()),
+ kFacevaryingScope);
+
+ param.set(sample);
+}
+
+/* Convention to write Vertex Colors:
+ * - C3fGeomParam/C4fGeomParam on the arbGeomParam
+ * - set scope as vertex varying
+ */
+static void write_mcol(const OCompoundProperty &prop,
+ const CDStreamConfig &config,
+ void *data,
+ const char *name)
+{
+ const float cscale = 1.0f / 255.0f;
+ MPoly *polys = config.mpoly;
+ MLoop *mloops = config.mloop;
+ MCol *cfaces = static_cast<MCol *>(data);
+
+ std::vector<Imath::C4f> buffer;
+ std::vector<uint32_t> indices;
+
+ buffer.reserve(config.totvert);
+ indices.reserve(config.totvert);
+
+ Imath::C4f col;
+
+ for (int i = 0; i < config.totpoly; i++) {
+ MPoly *p = &polys[i];
+ MCol *cface = &cfaces[p->loopstart + p->totloop];
+ MLoop *mloop = &mloops[p->loopstart + p->totloop];
+
+ for (int j = 0; j < p->totloop; j++) {
+ cface--;
+ mloop--;
+
+ col[0] = cface->a * cscale;
+ col[1] = cface->r * cscale;
+ col[2] = cface->g * cscale;
+ col[3] = cface->b * cscale;
+
+ buffer.push_back(col);
+ indices.push_back(buffer.size() - 1);
+ }
+ }
+
+ OC4fGeomParam param(prop, name, true, kFacevaryingScope, 1);
+
+ OC4fGeomParam::Sample sample(C4fArraySample(&buffer.front(), buffer.size()),
+ UInt32ArraySample(&indices.front(), indices.size()),
+ kVertexScope);
+
+ param.set(sample);
+}
+
+void write_custom_data(const OCompoundProperty &prop,
+ const CDStreamConfig &config,
+ CustomData *data,
+ int data_type)
+{
+ CustomDataType cd_data_type = static_cast<CustomDataType>(data_type);
+
+ if (!CustomData_has_layer(data, cd_data_type)) {
+ return;
+ }
+
+ const int active_layer = CustomData_get_active_layer(data, cd_data_type);
+ const int tot_layers = CustomData_number_of_layers(data, cd_data_type);
+
+ for (int i = 0; i < tot_layers; i++) {
+ void *cd_data = CustomData_get_layer_n(data, cd_data_type, i);
+ const char *name = CustomData_get_layer_name(data, cd_data_type, i);
+
+ if (cd_data_type == CD_MLOOPUV) {
+ /* Already exported. */
+ if (i == active_layer) {
+ continue;
+ }
+
+ write_uv(prop, config, cd_data, name);
+ }
+ else if (cd_data_type == CD_MLOOPCOL) {
+ write_mcol(prop, config, cd_data, name);
+ }
+ }
+}
+
+/* ************************************************************************** */
+
+using Alembic::Abc::C3fArraySamplePtr;
+using Alembic::Abc::C4fArraySamplePtr;
+using Alembic::Abc::PropertyHeader;
+
+using Alembic::AbcGeom::IC3fGeomParam;
+using Alembic::AbcGeom::IC4fGeomParam;
+using Alembic::AbcGeom::IV2fGeomParam;
+
+static void read_uvs(const CDStreamConfig &config,
+ void *data,
+ const Alembic::AbcGeom::V2fArraySamplePtr &uvs,
+ const Alembic::AbcGeom::UInt32ArraySamplePtr &indices)
+{
+ MPoly *mpolys = config.mpoly;
+ MLoopUV *mloopuvs = static_cast<MLoopUV *>(data);
+
+ unsigned int uv_index, loop_index, rev_loop_index;
+
+ for (int i = 0; i < config.totpoly; i++) {
+ MPoly &poly = mpolys[i];
+ unsigned int rev_loop_offset = poly.loopstart + poly.totloop - 1;
+
+ for (int f = 0; f < poly.totloop; f++) {
+ loop_index = poly.loopstart + f;
+ rev_loop_index = rev_loop_offset - f;
+ uv_index = (*indices)[loop_index];
+ const Imath::V2f &uv = (*uvs)[uv_index];
+
+ MLoopUV &loopuv = mloopuvs[rev_loop_index];
+ loopuv.uv[0] = uv[0];
+ loopuv.uv[1] = uv[1];
+ }
+ }
+}
+
+static size_t mcols_out_of_bounds_check(const size_t color_index,
+ const size_t array_size,
+ const std::string &iobject_full_name,
+ const PropertyHeader &prop_header,
+ bool &r_is_out_of_bounds,
+ bool &r_bounds_warning_given)
+{
+ if (color_index < array_size) {
+ return color_index;
+ }
+
+ if (!r_bounds_warning_given) {
+ std::cerr << "Alembic: color index out of bounds "
+ "reading face colors for object "
+ << iobject_full_name << ", property " << prop_header.getName() << std::endl;
+ r_bounds_warning_given = true;
+ }
+ r_is_out_of_bounds = true;
+ return 0;
+}
+
+static void read_custom_data_mcols(const std::string &iobject_full_name,
+ const ICompoundProperty &arbGeomParams,
+ const PropertyHeader &prop_header,
+ const CDStreamConfig &config,
+ const Alembic::Abc::ISampleSelector &iss)
+{
+ C3fArraySamplePtr c3f_ptr = C3fArraySamplePtr();
+ C4fArraySamplePtr c4f_ptr = C4fArraySamplePtr();
+ Alembic::Abc::UInt32ArraySamplePtr indices;
+ bool use_c3f_ptr;
+ bool is_facevarying;
+
+ /* Find the correct interpretation of the data */
+ if (IC3fGeomParam::matches(prop_header)) {
+ IC3fGeomParam color_param(arbGeomParams, prop_header.getName());
+ IC3fGeomParam::Sample sample;
+ BLI_assert(!strcmp("rgb", color_param.getInterpretation()));
+
+ color_param.getIndexed(sample, iss);
+ is_facevarying = sample.getScope() == kFacevaryingScope &&
+ config.totloop == sample.getIndices()->size();
+
+ c3f_ptr = sample.getVals();
+ indices = sample.getIndices();
+ use_c3f_ptr = true;
+ }
+ else if (IC4fGeomParam::matches(prop_header)) {
+ IC4fGeomParam color_param(arbGeomParams, prop_header.getName());
+ IC4fGeomParam::Sample sample;
+ BLI_assert(!strcmp("rgba", color_param.getInterpretation()));
+
+ color_param.getIndexed(sample, iss);
+ is_facevarying = sample.getScope() == kFacevaryingScope &&
+ config.totloop == sample.getIndices()->size();
+
+ c4f_ptr = sample.getVals();
+ indices = sample.getIndices();
+ use_c3f_ptr = false;
+ }
+ else {
+ /* this won't happen due to the checks in read_custom_data() */
+ return;
+ }
+ BLI_assert(c3f_ptr || c4f_ptr);
+
+ /* Read the vertex colors */
+ void *cd_data = config.add_customdata_cb(
+ config.mesh, prop_header.getName().c_str(), CD_MLOOPCOL);
+ MCol *cfaces = static_cast<MCol *>(cd_data);
+ MPoly *mpolys = config.mpoly;
+ MLoop *mloops = config.mloop;
+
+ size_t face_index = 0;
+ size_t color_index;
+ bool bounds_warning_given = false;
+
+ /* The colors can go through two layers of indexing. Often the 'indices'
+ * array doesn't do anything (i.e. indices[n] = n), but when it does, it's
+ * important. Blender 2.79 writes indices incorrectly (see T53745), which
+ * is why we have to check for indices->size() > 0 */
+ bool use_dual_indexing = is_facevarying && indices->size() > 0;
+
+ for (int i = 0; i < config.totpoly; i++) {
+ MPoly *poly = &mpolys[i];
+ MCol *cface = &cfaces[poly->loopstart + poly->totloop];
+ MLoop *mloop = &mloops[poly->loopstart + poly->totloop];
+
+ for (int j = 0; j < poly->totloop; j++, face_index++) {
+ cface--;
+ mloop--;
+
+ color_index = is_facevarying ? face_index : mloop->v;
+ if (use_dual_indexing) {
+ color_index = (*indices)[color_index];
+ }
+ if (use_c3f_ptr) {
+ bool is_mcols_out_of_bounds = false;
+ color_index = mcols_out_of_bounds_check(color_index,
+ c3f_ptr->size(),
+ iobject_full_name,
+ prop_header,
+ is_mcols_out_of_bounds,
+ bounds_warning_given);
+ if (is_mcols_out_of_bounds) {
+ continue;
+ }
+ const Imath::C3f &color = (*c3f_ptr)[color_index];
+ cface->a = unit_float_to_uchar_clamp(color[0]);
+ cface->r = unit_float_to_uchar_clamp(color[1]);
+ cface->g = unit_float_to_uchar_clamp(color[2]);
+ cface->b = 255;
+ }
+ else {
+ bool is_mcols_out_of_bounds = false;
+ color_index = mcols_out_of_bounds_check(color_index,
+ c4f_ptr->size(),
+ iobject_full_name,
+ prop_header,
+ is_mcols_out_of_bounds,
+ bounds_warning_given);
+ if (is_mcols_out_of_bounds) {
+ continue;
+ }
+ const Imath::C4f &color = (*c4f_ptr)[color_index];
+ cface->a = unit_float_to_uchar_clamp(color[0]);
+ cface->r = unit_float_to_uchar_clamp(color[1]);
+ cface->g = unit_float_to_uchar_clamp(color[2]);
+ cface->b = unit_float_to_uchar_clamp(color[3]);
+ }
+ }
+ }
+}
+
+static void read_custom_data_uvs(const ICompoundProperty &prop,
+ const PropertyHeader &prop_header,
+ const CDStreamConfig &config,
+ const Alembic::Abc::ISampleSelector &iss)
+{
+ IV2fGeomParam uv_param(prop, prop_header.getName());
+
+ if (!uv_param.isIndexed()) {
+ return;
+ }
+
+ IV2fGeomParam::Sample sample;
+ uv_param.getIndexed(sample, iss);
+
+ if (uv_param.getScope() != kFacevaryingScope) {
+ return;
+ }
+
+ void *cd_data = config.add_customdata_cb(config.mesh, prop_header.getName().c_str(), CD_MLOOPUV);
+
+ read_uvs(config, cd_data, sample.getVals(), sample.getIndices());
+}
+
+void read_custom_data(const std::string &iobject_full_name,
+ const ICompoundProperty &prop,
+ const CDStreamConfig &config,
+ const Alembic::Abc::ISampleSelector &iss)
+{
+ if (!prop.valid()) {
+ return;
+ }
+
+ int num_uvs = 0;
+ int num_colors = 0;
+
+ const size_t num_props = prop.getNumProperties();
+
+ for (size_t i = 0; i < num_props; i++) {
+ const Alembic::Abc::PropertyHeader &prop_header = prop.getPropertyHeader(i);
+
+ /* Read UVs according to convention. */
+ if (IV2fGeomParam::matches(prop_header) && Alembic::AbcGeom::isUV(prop_header)) {
+ if (++num_uvs > MAX_MTFACE) {
+ continue;
+ }
+
+ read_custom_data_uvs(prop, prop_header, config, iss);
+ continue;
+ }
+
+ /* Read vertex colors according to convention. */
+ if (IC3fGeomParam::matches(prop_header) || IC4fGeomParam::matches(prop_header)) {
+ if (++num_colors > MAX_MCOL) {
+ continue;
+ }
+
+ read_custom_data_mcols(iobject_full_name, prop, prop_header, config, iss);
+ continue;
+ }
+ }
+}
diff --git a/source/blender/io/alembic/intern/abc_customdata.h b/source/blender/io/alembic/intern/abc_customdata.h
new file mode 100644
index 00000000000..6107e230627
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_customdata.h
@@ -0,0 +1,104 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_CUSTOMDATA_H__
+#define __ABC_CUSTOMDATA_H__
+
+#include <Alembic/Abc/All.h>
+#include <Alembic/AbcGeom/All.h>
+
+struct CustomData;
+struct MLoop;
+struct MLoopUV;
+struct MPoly;
+struct MVert;
+struct Mesh;
+
+using Alembic::Abc::ICompoundProperty;
+using Alembic::Abc::OCompoundProperty;
+
+struct UVSample {
+ std::vector<Imath::V2f> uvs;
+ std::vector<uint32_t> indices;
+};
+
+struct CDStreamConfig {
+ MLoop *mloop;
+ int totloop;
+
+ MPoly *mpoly;
+ int totpoly;
+
+ MVert *mvert;
+ int totvert;
+
+ MLoopUV *mloopuv;
+
+ CustomData *loopdata;
+
+ bool pack_uvs;
+
+ /* TODO(kevin): might need a better way to handle adding and/or updating
+ * custom data such that it updates the custom data holder and its pointers properly. */
+ Mesh *mesh;
+ void *(*add_customdata_cb)(Mesh *mesh, const char *name, int data_type);
+
+ float weight;
+ float time;
+ Alembic::AbcGeom::index_t index;
+ Alembic::AbcGeom::index_t ceil_index;
+
+ CDStreamConfig()
+ : mloop(NULL),
+ totloop(0),
+ mpoly(NULL),
+ totpoly(0),
+ totvert(0),
+ pack_uvs(false),
+ mesh(NULL),
+ add_customdata_cb(NULL),
+ weight(0.0f),
+ time(0.0f),
+ index(0),
+ ceil_index(0)
+ {
+ }
+};
+
+/* Get the UVs for the main UV property on a OSchema.
+ * Returns the name of the UV layer.
+ *
+ * For now the active layer is used, maybe needs a better way to choose this. */
+const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, CustomData *data);
+
+void write_custom_data(const OCompoundProperty &prop,
+ const CDStreamConfig &config,
+ CustomData *data,
+ int data_type);
+
+void read_custom_data(const std::string &iobject_full_name,
+ const ICompoundProperty &prop,
+ const CDStreamConfig &config,
+ const Alembic::Abc::ISampleSelector &iss);
+
+#endif /* __ABC_CUSTOMDATA_H__ */
diff --git a/source/blender/io/alembic/intern/abc_exporter.cc b/source/blender/io/alembic/intern/abc_exporter.cc
new file mode 100644
index 00000000000..a58b0a29e5e
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_exporter.cc
@@ -0,0 +1,677 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_exporter.h"
+
+#include <cmath>
+
+#include "abc_writer_archive.h"
+#include "abc_writer_camera.h"
+#include "abc_writer_curves.h"
+#include "abc_writer_hair.h"
+#include "abc_writer_mball.h"
+#include "abc_writer_mesh.h"
+#include "abc_writer_nurbs.h"
+#include "abc_writer_points.h"
+#include "abc_writer_transform.h"
+#include "abc_util.h"
+
+extern "C" {
+#include "DNA_camera_types.h"
+#include "DNA_curve_types.h"
+#include "DNA_meta_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_space_types.h" /* for FILE_MAX */
+#include "DNA_fluid_types.h"
+
+#include "BLI_string.h"
+
+#ifdef WIN32
+/* needed for MSCV because of snprintf from BLI_string */
+# include "BLI_winstuff.h"
+#endif
+
+#include "BKE_anim.h"
+#include "BKE_global.h"
+#include "BKE_idprop.h"
+#include "BKE_layer.h"
+#include "BKE_main.h"
+#include "BKE_mball.h"
+#include "BKE_modifier.h"
+#include "BKE_particle.h"
+#include "BKE_scene.h"
+
+#include "DEG_depsgraph_query.h"
+}
+
+using Alembic::Abc::OBox3dProperty;
+using Alembic::Abc::TimeSamplingPtr;
+
+/* ************************************************************************** */
+
+ExportSettings::ExportSettings()
+ : scene(NULL),
+ view_layer(NULL),
+ depsgraph(NULL),
+ logger(),
+ selected_only(false),
+ visible_objects_only(false),
+ renderable_only(false),
+ frame_start(1),
+ frame_end(1),
+ frame_samples_xform(1),
+ frame_samples_shape(1),
+ shutter_open(0.0),
+ shutter_close(1.0),
+ global_scale(1.0f),
+ flatten_hierarchy(false),
+ export_normals(false),
+ export_uvs(false),
+ export_vcols(false),
+ export_face_sets(false),
+ export_vweigths(false),
+ export_hair(true),
+ export_particles(true),
+ apply_subdiv(false),
+ use_subdiv_schema(false),
+ export_child_hairs(true),
+ export_ogawa(true),
+ pack_uv(false),
+ triangulate(false),
+ quad_method(0),
+ ngon_method(0)
+{
+}
+
+static bool object_is_smoke_sim(Object *ob)
+{
+ ModifierData *md = modifiers_findByType(ob, eModifierType_Fluid);
+
+ if (md) {
+ FluidModifierData *smd = reinterpret_cast<FluidModifierData *>(md);
+ return (smd->type == MOD_FLUID_TYPE_DOMAIN && smd->domain &&
+ smd->domain->type == FLUID_DOMAIN_TYPE_GAS);
+ }
+
+ return false;
+}
+
+static bool object_type_is_exportable(Scene *scene, Object *ob)
+{
+ switch (ob->type) {
+ case OB_MESH:
+ if (object_is_smoke_sim(ob)) {
+ return false;
+ }
+
+ return true;
+ case OB_EMPTY:
+ case OB_CURVE:
+ case OB_SURF:
+ case OB_CAMERA:
+ return true;
+ case OB_MBALL:
+ return AbcMBallWriter::isBasisBall(scene, ob);
+ default:
+ return false;
+ }
+}
+
+/**
+ * Returns whether this object should be exported into the Alembic file.
+ *
+ * \param settings: export settings, used for options like 'selected only'.
+ * \param ob: the object's base in question.
+ * \param is_duplicated: Normally false; true when the object is instanced
+ * into the scene by a dupli-object (e.g. part of a dupligroup).
+ * This ignores selection and layer visibility,
+ * and assumes that the dupli-object itself (e.g. the group-instantiating empty) is exported.
+ */
+static bool export_object(const ExportSettings *const settings,
+ const Base *const base,
+ bool is_duplicated)
+{
+ if (!is_duplicated) {
+ View3D *v3d = NULL;
+
+ /* These two tests only make sense when the object isn't being instanced
+ * into the scene. When it is, its exportability is determined by
+ * its dupli-object and the DupliObject::no_draw property. */
+ if (settings->selected_only && !BASE_SELECTED(v3d, base)) {
+ return false;
+ }
+ // FIXME Sybren: handle these cleanly (maybe just remove code),
+ // now using active scene layer instead.
+ if (settings->visible_objects_only && !BASE_VISIBLE(v3d, base)) {
+ return false;
+ }
+ }
+
+ Object *ob_eval = DEG_get_evaluated_object(settings->depsgraph, base->object);
+ if ((ob_eval->id.tag & LIB_TAG_COPIED_ON_WRITE) == 0) {
+ /* XXX fix after 2.80: the object was not part of the depsgraph, and thus we cannot get the
+ * evaluated copy to export. This will be handled more elegantly in the new
+ * AbstractHierarchyIterator that Sybren is working on. This condition is temporary, and avoids
+ * a BLI_assert() failure getting the evaluated mesh of this object. */
+ return false;
+ }
+
+ // if (settings->renderable_only && (ob->restrictflag & OB_RESTRICT_RENDER)) {
+ // return false;
+ // }
+
+ return true;
+}
+
+/* ************************************************************************** */
+
+AbcExporter::AbcExporter(Main *bmain, const char *filename, ExportSettings &settings)
+ : m_bmain(bmain),
+ m_settings(settings),
+ m_filename(filename),
+ m_trans_sampling_index(0),
+ m_shape_sampling_index(0),
+ m_writer(NULL)
+{
+}
+
+AbcExporter::~AbcExporter()
+{
+ /* Free xforms map */
+ m_xforms_type::iterator it_x, e_x;
+ for (it_x = m_xforms.begin(), e_x = m_xforms.end(); it_x != e_x; ++it_x) {
+ delete it_x->second;
+ }
+
+ /* Free shapes vector */
+ for (int i = 0, e = m_shapes.size(); i != e; i++) {
+ delete m_shapes[i];
+ }
+
+ delete m_writer;
+}
+
+void AbcExporter::getShutterSamples(unsigned int nr_of_samples,
+ bool time_relative,
+ std::vector<double> &samples)
+{
+ Scene *scene = m_settings.scene; /* for use in the FPS macro */
+ samples.clear();
+
+ unsigned int frame_offset = time_relative ? m_settings.frame_start : 0;
+ double time_factor = time_relative ? FPS : 1.0;
+ double shutter_open = m_settings.shutter_open;
+ double shutter_close = m_settings.shutter_close;
+ double time_inc = (shutter_close - shutter_open) / nr_of_samples;
+
+ /* sample between shutter open & close */
+ for (int sample = 0; sample < nr_of_samples; sample++) {
+ double sample_time = shutter_open + time_inc * sample;
+ double time = (frame_offset + sample_time) / time_factor;
+
+ samples.push_back(time);
+ }
+}
+
+Alembic::Abc::TimeSamplingPtr AbcExporter::createTimeSampling(double step)
+{
+ std::vector<double> samples;
+
+ if (m_settings.frame_start == m_settings.frame_end) {
+ return TimeSamplingPtr(new Alembic::Abc::TimeSampling());
+ }
+
+ getShutterSamples(step, true, samples);
+
+ /* TODO(Sybren): shouldn't we use the FPS macro here? */
+ Alembic::Abc::TimeSamplingType ts(static_cast<uint32_t>(samples.size()),
+ 1.0 / m_settings.scene->r.frs_sec);
+
+ return TimeSamplingPtr(new Alembic::Abc::TimeSampling(ts, samples));
+}
+
+void AbcExporter::getFrameSet(unsigned int nr_of_samples, std::set<double> &frames)
+{
+ frames.clear();
+
+ std::vector<double> shutter_samples;
+
+ getShutterSamples(nr_of_samples, false, shutter_samples);
+
+ for (double frame = m_settings.frame_start; frame <= m_settings.frame_end; frame += 1.0) {
+ for (size_t j = 0; j < nr_of_samples; j++) {
+ frames.insert(frame + shutter_samples[j]);
+ }
+ }
+}
+
+void AbcExporter::operator()(short *do_update, float *progress, bool *was_canceled)
+{
+ std::string abc_scene_name;
+
+ if (m_bmain->name[0] != '\0') {
+ char scene_file_name[FILE_MAX];
+ BLI_strncpy(scene_file_name, m_bmain->name, FILE_MAX);
+ abc_scene_name = scene_file_name;
+ }
+ else {
+ abc_scene_name = "untitled";
+ }
+
+ m_writer = new ArchiveWriter(
+ m_filename, abc_scene_name, m_settings.scene, m_settings.export_ogawa);
+
+ /* Create time samplings for transforms and shapes. */
+
+ TimeSamplingPtr trans_time = createTimeSampling(m_settings.frame_samples_xform);
+
+ m_trans_sampling_index = m_writer->archive().addTimeSampling(*trans_time);
+
+ TimeSamplingPtr shape_time;
+
+ if ((m_settings.frame_samples_shape == m_settings.frame_samples_xform) ||
+ (m_settings.frame_start == m_settings.frame_end)) {
+ shape_time = trans_time;
+ m_shape_sampling_index = m_trans_sampling_index;
+ }
+ else {
+ shape_time = createTimeSampling(m_settings.frame_samples_shape);
+ m_shape_sampling_index = m_writer->archive().addTimeSampling(*shape_time);
+ }
+
+ OBox3dProperty archive_bounds_prop = Alembic::AbcGeom::CreateOArchiveBounds(
+ m_writer->archive(), m_trans_sampling_index);
+
+ createTransformWritersHierarchy();
+ createShapeWriters();
+
+ /* Make a list of frames to export. */
+
+ std::set<double> xform_frames;
+ getFrameSet(m_settings.frame_samples_xform, xform_frames);
+
+ std::set<double> shape_frames;
+ getFrameSet(m_settings.frame_samples_shape, shape_frames);
+
+ /* Merge all frames needed. */
+ std::set<double> frames(xform_frames);
+ frames.insert(shape_frames.begin(), shape_frames.end());
+
+ /* Export all frames. */
+
+ std::set<double>::const_iterator begin = frames.begin();
+ std::set<double>::const_iterator end = frames.end();
+
+ const float size = static_cast<float>(frames.size());
+ size_t i = 0;
+
+ for (; begin != end; ++begin) {
+ *progress = (++i / size);
+ *do_update = 1;
+
+ if (G.is_break) {
+ *was_canceled = true;
+ break;
+ }
+
+ const double frame = *begin;
+
+ /* 'frame' is offset by start frame, so need to cancel the offset. */
+ setCurrentFrame(m_bmain, frame);
+
+ if (shape_frames.count(frame) != 0) {
+ for (int i = 0, e = m_shapes.size(); i != e; i++) {
+ m_shapes[i]->write();
+ }
+ }
+
+ if (xform_frames.count(frame) == 0) {
+ continue;
+ }
+
+ m_xforms_type::iterator xit, xe;
+ for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) {
+ xit->second->write();
+ }
+
+ /* Save the archive 's bounding box. */
+ Imath::Box3d bounds;
+
+ for (xit = m_xforms.begin(), xe = m_xforms.end(); xit != xe; ++xit) {
+ Imath::Box3d box = xit->second->bounds();
+ bounds.extendBy(box);
+ }
+
+ archive_bounds_prop.set(bounds);
+ }
+}
+
+void AbcExporter::createTransformWritersHierarchy()
+{
+ for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base;
+ base = base->next) {
+ Object *ob = base->object;
+
+ if (export_object(&m_settings, base, false)) {
+ switch (ob->type) {
+ case OB_LAMP:
+ case OB_LATTICE:
+ case OB_SPEAKER:
+ /* We do not export transforms for objects of these classes. */
+ break;
+ default:
+ exploreTransform(base, ob, ob->parent, NULL);
+ }
+ }
+ }
+}
+
+void AbcExporter::exploreTransform(Base *base,
+ Object *object,
+ Object *parent,
+ Object *dupliObParent)
+{
+ /* If an object isn't exported itself, its duplilist shouldn't be
+ * exported either. */
+ if (!export_object(&m_settings, base, dupliObParent != NULL)) {
+ return;
+ }
+
+ Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object);
+ if (object_type_is_exportable(m_settings.scene, ob)) {
+ createTransformWriter(ob, parent, dupliObParent);
+ }
+
+ ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob);
+
+ if (lb) {
+ DupliObject *link = static_cast<DupliObject *>(lb->first);
+ Object *dupli_ob = NULL;
+ Object *dupli_parent = NULL;
+
+ for (; link; link = link->next) {
+ /* This skips things like custom bone shapes. */
+ if (m_settings.renderable_only && link->no_draw) {
+ continue;
+ }
+
+ if (link->type == OB_DUPLICOLLECTION) {
+ dupli_ob = link->ob;
+ dupli_parent = (dupli_ob->parent) ? dupli_ob->parent : ob;
+
+ exploreTransform(base, dupli_ob, dupli_parent, ob);
+ }
+ }
+
+ free_object_duplilist(lb);
+ }
+}
+
+AbcTransformWriter *AbcExporter::createTransformWriter(Object *ob,
+ Object *parent,
+ Object *dupliObParent)
+{
+ /* An object should not be its own parent, or we'll get infinite loops. */
+ BLI_assert(ob != parent);
+ BLI_assert(ob != dupliObParent);
+
+ std::string name;
+ if (m_settings.flatten_hierarchy) {
+ name = get_id_name(ob);
+ }
+ else {
+ name = get_object_dag_path_name(ob, dupliObParent);
+ }
+
+ /* check if we have already created a transform writer for this object */
+ AbcTransformWriter *my_writer = getXForm(name);
+ if (my_writer != NULL) {
+ return my_writer;
+ }
+
+ AbcTransformWriter *parent_writer = NULL;
+ Alembic::Abc::OObject alembic_parent;
+
+ if (m_settings.flatten_hierarchy || parent == NULL) {
+ /* Parentless objects still have the "top object" as parent
+ * in Alembic. */
+ alembic_parent = m_writer->archive().getTop();
+ }
+ else {
+ /* Since there are so many different ways to find parents (as evident
+ * in the number of conditions below), we can't really look up the
+ * parent by name. We'll just call createTransformWriter(), which will
+ * return the parent's AbcTransformWriter pointer. */
+ if (parent->parent) {
+ if (parent == dupliObParent) {
+ parent_writer = createTransformWriter(parent, parent->parent, NULL);
+ }
+ else {
+ parent_writer = createTransformWriter(parent, parent->parent, dupliObParent);
+ }
+ }
+ else if (parent == dupliObParent) {
+ if (dupliObParent->parent == NULL) {
+ parent_writer = createTransformWriter(parent, NULL, NULL);
+ }
+ else {
+ parent_writer = createTransformWriter(
+ parent, dupliObParent->parent, dupliObParent->parent);
+ }
+ }
+ else {
+ parent_writer = createTransformWriter(parent, dupliObParent, dupliObParent);
+ }
+
+ BLI_assert(parent_writer);
+ alembic_parent = parent_writer->alembicXform();
+ }
+
+ my_writer = new AbcTransformWriter(
+ ob, alembic_parent, parent_writer, m_trans_sampling_index, m_settings);
+
+ /* When flattening, the matrix of the dupliobject has to be added. */
+ if (m_settings.flatten_hierarchy && dupliObParent) {
+ my_writer->m_proxy_from = dupliObParent;
+ }
+
+ m_xforms[name] = my_writer;
+ return my_writer;
+}
+
+void AbcExporter::createShapeWriters()
+{
+ for (Base *base = static_cast<Base *>(m_settings.view_layer->object_bases.first); base;
+ base = base->next) {
+ exploreObject(base, base->object, NULL);
+ }
+}
+
+void AbcExporter::exploreObject(Base *base, Object *object, Object *dupliObParent)
+{
+ /* If an object isn't exported itself, its duplilist shouldn't be
+ * exported either. */
+ if (!export_object(&m_settings, base, dupliObParent != NULL)) {
+ return;
+ }
+
+ Object *ob = DEG_get_evaluated_object(m_settings.depsgraph, object);
+ createShapeWriter(ob, dupliObParent);
+
+ ListBase *lb = object_duplilist(m_settings.depsgraph, m_settings.scene, ob);
+
+ if (lb) {
+ DupliObject *link = static_cast<DupliObject *>(lb->first);
+
+ for (; link; link = link->next) {
+ /* This skips things like custom bone shapes. */
+ if (m_settings.renderable_only && link->no_draw) {
+ continue;
+ }
+ if (link->type == OB_DUPLICOLLECTION) {
+ exploreObject(base, link->ob, ob);
+ }
+ }
+
+ free_object_duplilist(lb);
+ }
+}
+
+void AbcExporter::createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform)
+{
+ if (!m_settings.export_hair && !m_settings.export_particles) {
+ return;
+ }
+
+ ParticleSystem *psys = static_cast<ParticleSystem *>(ob->particlesystem.first);
+
+ for (; psys; psys = psys->next) {
+ if (!psys_check_enabled(ob, psys, G.is_rendering) || !psys->part) {
+ continue;
+ }
+
+ if (m_settings.export_hair && psys->part->type == PART_HAIR) {
+ m_settings.export_child_hairs = true;
+ m_shapes.push_back(new AbcHairWriter(ob, xform, m_shape_sampling_index, m_settings, psys));
+ }
+ else if (m_settings.export_particles &&
+ (psys->part->type == PART_EMITTER || psys->part->type == PART_FLUID_FLIP ||
+ psys->part->type == PART_FLUID_SPRAY || psys->part->type == PART_FLUID_BUBBLE ||
+ psys->part->type == PART_FLUID_FOAM || psys->part->type == PART_FLUID_TRACER ||
+ psys->part->type == PART_FLUID_SPRAYFOAM ||
+ psys->part->type == PART_FLUID_SPRAYBUBBLE ||
+ psys->part->type == PART_FLUID_FOAMBUBBLE ||
+ psys->part->type == PART_FLUID_SPRAYFOAMBUBBLE)) {
+ m_shapes.push_back(new AbcPointsWriter(ob, xform, m_shape_sampling_index, m_settings, psys));
+ }
+ }
+}
+
+void AbcExporter::createShapeWriter(Object *ob, Object *dupliObParent)
+{
+ if (!object_type_is_exportable(m_settings.scene, ob)) {
+ return;
+ }
+
+ std::string name;
+
+ if (m_settings.flatten_hierarchy) {
+ name = get_id_name(ob);
+ }
+ else {
+ name = get_object_dag_path_name(ob, dupliObParent);
+ }
+
+ AbcTransformWriter *xform = getXForm(name);
+
+ if (!xform) {
+ ABC_LOG(m_settings.logger) << __func__ << ": xform " << name << " is NULL\n";
+ return;
+ }
+
+ createParticleSystemsWriters(ob, xform);
+
+ switch (ob->type) {
+ case OB_MESH: {
+ Mesh *me = static_cast<Mesh *>(ob->data);
+
+ if (!me) {
+ return;
+ }
+
+ m_shapes.push_back(new AbcMeshWriter(ob, xform, m_shape_sampling_index, m_settings));
+ break;
+ }
+ case OB_SURF: {
+ Curve *cu = static_cast<Curve *>(ob->data);
+
+ if (!cu) {
+ return;
+ }
+
+ AbcObjectWriter *writer;
+ if (m_settings.curves_as_mesh) {
+ writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings);
+ }
+ else {
+ writer = new AbcNurbsWriter(ob, xform, m_shape_sampling_index, m_settings);
+ }
+ m_shapes.push_back(writer);
+ break;
+ }
+ case OB_CURVE: {
+ Curve *cu = static_cast<Curve *>(ob->data);
+
+ if (!cu) {
+ return;
+ }
+
+ AbcObjectWriter *writer;
+ if (m_settings.curves_as_mesh) {
+ writer = new AbcCurveMeshWriter(ob, xform, m_shape_sampling_index, m_settings);
+ }
+ else {
+ writer = new AbcCurveWriter(ob, xform, m_shape_sampling_index, m_settings);
+ }
+ m_shapes.push_back(writer);
+ break;
+ }
+ case OB_CAMERA: {
+ Camera *cam = static_cast<Camera *>(ob->data);
+
+ if (cam->type == CAM_PERSP) {
+ m_shapes.push_back(new AbcCameraWriter(ob, xform, m_shape_sampling_index, m_settings));
+ }
+
+ break;
+ }
+ case OB_MBALL: {
+ MetaBall *mball = static_cast<MetaBall *>(ob->data);
+ if (!mball) {
+ return;
+ }
+
+ m_shapes.push_back(
+ new AbcMBallWriter(m_bmain, ob, xform, m_shape_sampling_index, m_settings));
+ break;
+ }
+ }
+}
+
+AbcTransformWriter *AbcExporter::getXForm(const std::string &name)
+{
+ std::map<std::string, AbcTransformWriter *>::iterator it = m_xforms.find(name);
+
+ if (it == m_xforms.end()) {
+ return NULL;
+ }
+
+ return it->second;
+}
+
+void AbcExporter::setCurrentFrame(Main *bmain, double t)
+{
+ m_settings.scene->r.cfra = static_cast<int>(t);
+ m_settings.scene->r.subframe = static_cast<float>(t) - m_settings.scene->r.cfra;
+ BKE_scene_graph_update_for_newframe(m_settings.depsgraph, bmain);
+}
diff --git a/source/blender/io/alembic/intern/abc_exporter.h b/source/blender/io/alembic/intern/abc_exporter.h
new file mode 100644
index 00000000000..398004d2ec5
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_exporter.h
@@ -0,0 +1,128 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_EXPORTER_H__
+#define __ABC_EXPORTER_H__
+
+#include <Alembic/Abc/All.h>
+#include <map>
+#include <set>
+#include <vector>
+
+#include "abc_util.h"
+
+class AbcObjectWriter;
+class AbcTransformWriter;
+class ArchiveWriter;
+
+struct Base;
+struct Depsgraph;
+struct Main;
+struct Object;
+struct Scene;
+struct ViewLayer;
+
+struct ExportSettings {
+ ExportSettings();
+
+ Scene *scene;
+ /** Scene layer to export; all its objects will be exported, unless selected_only=true. */
+ ViewLayer *view_layer;
+ Depsgraph *depsgraph;
+ SimpleLogger logger;
+
+ bool selected_only;
+ bool visible_objects_only;
+ bool renderable_only;
+
+ double frame_start, frame_end;
+ double frame_samples_xform;
+ double frame_samples_shape;
+ double shutter_open;
+ double shutter_close;
+ float global_scale;
+
+ bool flatten_hierarchy;
+
+ bool export_normals;
+ bool export_uvs;
+ bool export_vcols;
+ bool export_face_sets;
+ bool export_vweigths;
+ bool export_hair;
+ bool export_particles;
+
+ bool apply_subdiv;
+ bool curves_as_mesh;
+ bool use_subdiv_schema;
+ bool export_child_hairs;
+ bool export_ogawa;
+ bool pack_uv;
+ bool triangulate;
+
+ int quad_method;
+ int ngon_method;
+};
+
+class AbcExporter {
+ Main *m_bmain;
+ ExportSettings &m_settings;
+
+ const char *m_filename;
+
+ unsigned int m_trans_sampling_index, m_shape_sampling_index;
+
+ ArchiveWriter *m_writer;
+
+ /* mapping from name to transform writer */
+ typedef std::map<std::string, AbcTransformWriter *> m_xforms_type;
+ m_xforms_type m_xforms;
+
+ std::vector<AbcObjectWriter *> m_shapes;
+
+ public:
+ AbcExporter(Main *bmain, const char *filename, ExportSettings &settings);
+ ~AbcExporter();
+
+ void operator()(short *do_update, float *progress, bool *was_canceled);
+
+ protected:
+ void getShutterSamples(unsigned int nr_of_samples,
+ bool time_relative,
+ std::vector<double> &samples);
+ void getFrameSet(unsigned int nr_of_samples, std::set<double> &frames);
+
+ private:
+ Alembic::Abc::TimeSamplingPtr createTimeSampling(double step);
+
+ void createTransformWritersHierarchy();
+ AbcTransformWriter *createTransformWriter(Object *ob, Object *parent, Object *dupliObParent);
+ void exploreTransform(Base *base, Object *object, Object *parent, Object *dupliObParent);
+ void exploreObject(Base *base, Object *object, Object *dupliObParent);
+ void createShapeWriters();
+ void createShapeWriter(Object *ob, Object *dupliObParent);
+ void createParticleSystemsWriters(Object *ob, AbcTransformWriter *xform);
+
+ AbcTransformWriter *getXForm(const std::string &name);
+
+ void setCurrentFrame(Main *bmain, double t);
+};
+
+#endif /* __ABC_EXPORTER_H__ */
diff --git a/source/blender/io/alembic/intern/abc_reader_archive.cc b/source/blender/io/alembic/intern/abc_reader_archive.cc
new file mode 100644
index 00000000000..6ad44553701
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_archive.cc
@@ -0,0 +1,140 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_reader_archive.h"
+
+extern "C" {
+#include "BKE_main.h"
+
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+}
+
+#ifdef WIN32
+# include "utfconv.h"
+#endif
+
+#include <fstream>
+
+using Alembic::Abc::ErrorHandler;
+using Alembic::Abc::Exception;
+using Alembic::Abc::IArchive;
+using Alembic::Abc::kWrapExisting;
+
+static IArchive open_archive(const std::string &filename,
+ const std::vector<std::istream *> &input_streams,
+ bool &is_hdf5)
+{
+ is_hdf5 = false;
+
+ try {
+ Alembic::AbcCoreOgawa::ReadArchive archive_reader(input_streams);
+
+ return IArchive(archive_reader(filename), kWrapExisting, ErrorHandler::kThrowPolicy);
+ }
+ catch (const Exception &e) {
+ std::cerr << e.what() << '\n';
+
+#ifdef WITH_ALEMBIC_HDF5
+ try {
+ is_hdf5 = true;
+ Alembic::AbcCoreAbstract::ReadArraySampleCachePtr cache_ptr;
+
+ return IArchive(Alembic::AbcCoreHDF5::ReadArchive(),
+ filename.c_str(),
+ ErrorHandler::kThrowPolicy,
+ cache_ptr);
+ }
+ catch (const Exception &) {
+ std::cerr << e.what() << '\n';
+ return IArchive();
+ }
+#else
+ /* Inspect the file to see whether it's really a HDF5 file. */
+ char header[4]; /* char(0x89) + "HDF" */
+ std::ifstream the_file(filename.c_str(), std::ios::in | std::ios::binary);
+ if (!the_file) {
+ std::cerr << "Unable to open " << filename << std::endl;
+ }
+ else if (!the_file.read(header, sizeof(header))) {
+ std::cerr << "Unable to read from " << filename << std::endl;
+ }
+ else if (strncmp(header + 1, "HDF", 3)) {
+ std::cerr << filename << " has an unknown file format, unable to read." << std::endl;
+ }
+ else {
+ is_hdf5 = true;
+ std::cerr << filename << " is in the obsolete HDF5 format, unable to read." << std::endl;
+ }
+
+ if (the_file.is_open()) {
+ the_file.close();
+ }
+
+ return IArchive();
+#endif
+ }
+
+ return IArchive();
+}
+
+ArchiveReader::ArchiveReader(struct Main *bmain, const char *filename)
+{
+ char abs_filename[FILE_MAX];
+ BLI_strncpy(abs_filename, filename, FILE_MAX);
+ BLI_path_abs(abs_filename, BKE_main_blendfile_path(bmain));
+
+#ifdef WIN32
+ UTF16_ENCODE(abs_filename);
+ std::wstring wstr(abs_filename_16);
+ m_infile.open(wstr.c_str(), std::ios::in | std::ios::binary);
+ UTF16_UN_ENCODE(abs_filename);
+#else
+ m_infile.open(abs_filename, std::ios::in | std::ios::binary);
+#endif
+
+ m_streams.push_back(&m_infile);
+
+ m_archive = open_archive(abs_filename, m_streams, m_is_hdf5);
+
+ /* We can't open an HDF5 file from a stream, so close it. */
+ if (m_is_hdf5) {
+ m_infile.close();
+ m_streams.clear();
+ }
+}
+
+bool ArchiveReader::is_hdf5() const
+{
+ return m_is_hdf5;
+}
+
+bool ArchiveReader::valid() const
+{
+ return m_archive.valid();
+}
+
+Alembic::Abc::IObject ArchiveReader::getTop()
+{
+ return m_archive.getTop();
+}
diff --git a/source/blender/io/alembic/intern/abc_reader_archive.h b/source/blender/io/alembic/intern/abc_reader_archive.h
new file mode 100644
index 00000000000..bdb53bd0b8c
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_archive.h
@@ -0,0 +1,67 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_READER_ARCHIVE_H__
+#define __ABC_READER_ARCHIVE_H__
+
+#include <Alembic/Abc/All.h>
+
+#ifdef WITH_ALEMBIC_HDF5
+# include <Alembic/AbcCoreHDF5/All.h>
+#endif
+
+#include <Alembic/AbcCoreOgawa/All.h>
+
+#include <fstream>
+
+struct Main;
+struct Scene;
+
+/* Wrappers around input and output archives. The goal is to be able to use
+ * streams so that unicode paths work on Windows (T49112), and to make sure that
+ * the stream objects remain valid as long as the archives are open.
+ */
+
+class ArchiveReader {
+ Alembic::Abc::IArchive m_archive;
+ std::ifstream m_infile;
+ std::vector<std::istream *> m_streams;
+ bool m_is_hdf5;
+
+ public:
+ ArchiveReader(struct Main *bmain, const char *filename);
+
+ bool valid() const;
+
+ /**
+ * Returns true when either Blender is compiled with HDF5 support and
+ * the archive was successfully opened (valid() will also return true),
+ * or when Blender was built without HDF5 support but a HDF5 file was
+ * detected (valid() will return false).
+ */
+ bool is_hdf5() const;
+
+ Alembic::Abc::IObject getTop();
+};
+
+#endif /* __ABC_READER_ARCHIVE_H__ */
diff --git a/source/blender/io/alembic/intern/abc_reader_camera.cc b/source/blender/io/alembic/intern/abc_reader_camera.cc
new file mode 100644
index 00000000000..ab506f32cbe
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_camera.cc
@@ -0,0 +1,113 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_reader_camera.h"
+#include "abc_reader_transform.h"
+#include "abc_util.h"
+
+extern "C" {
+#include "DNA_camera_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_camera.h"
+#include "BKE_object.h"
+
+#include "BLI_math.h"
+}
+
+using Alembic::AbcGeom::CameraSample;
+using Alembic::AbcGeom::ICamera;
+using Alembic::AbcGeom::ICompoundProperty;
+using Alembic::AbcGeom::IFloatProperty;
+using Alembic::AbcGeom::ISampleSelector;
+using Alembic::AbcGeom::kWrapExisting;
+
+AbcCameraReader::AbcCameraReader(const Alembic::Abc::IObject &object, ImportSettings &settings)
+ : AbcObjectReader(object, settings)
+{
+ ICamera abc_cam(m_iobject, kWrapExisting);
+ m_schema = abc_cam.getSchema();
+
+ get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time);
+}
+
+bool AbcCameraReader::valid() const
+{
+ return m_schema.valid();
+}
+
+bool AbcCameraReader::accepts_object_type(
+ const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const
+{
+ if (!Alembic::AbcGeom::ICamera::matches(alembic_header)) {
+ *err_str =
+ "Object type mismatch, Alembic object path pointed to Camera when importing, but not any "
+ "more.";
+ return false;
+ }
+
+ if (ob->type != OB_CAMERA) {
+ *err_str = "Object type mismatch, Alembic object path points to Camera.";
+ return false;
+ }
+
+ return true;
+}
+
+void AbcCameraReader::readObjectData(Main *bmain, const ISampleSelector &sample_sel)
+{
+ Camera *bcam = static_cast<Camera *>(BKE_camera_add(bmain, m_data_name.c_str()));
+
+ CameraSample cam_sample;
+ m_schema.get(cam_sample, sample_sel);
+
+ ICompoundProperty customDataContainer = m_schema.getUserProperties();
+
+ if (customDataContainer.valid() && customDataContainer.getPropertyHeader("stereoDistance") &&
+ customDataContainer.getPropertyHeader("eyeSeparation")) {
+ IFloatProperty convergence_plane(customDataContainer, "stereoDistance");
+ IFloatProperty eye_separation(customDataContainer, "eyeSeparation");
+
+ bcam->stereo.interocular_distance = eye_separation.getValue(sample_sel);
+ bcam->stereo.convergence_distance = convergence_plane.getValue(sample_sel);
+ }
+
+ const float lens = static_cast<float>(cam_sample.getFocalLength());
+ const float apperture_x = static_cast<float>(cam_sample.getHorizontalAperture());
+ const float apperture_y = static_cast<float>(cam_sample.getVerticalAperture());
+ const float h_film_offset = static_cast<float>(cam_sample.getHorizontalFilmOffset());
+ const float v_film_offset = static_cast<float>(cam_sample.getVerticalFilmOffset());
+ const float film_aspect = apperture_x / apperture_y;
+
+ bcam->lens = lens;
+ bcam->sensor_x = apperture_x * 10;
+ bcam->sensor_y = apperture_y * 10;
+ bcam->shiftx = h_film_offset / apperture_x;
+ bcam->shifty = v_film_offset / apperture_y / film_aspect;
+ bcam->clip_start = max_ff(0.1f, static_cast<float>(cam_sample.getNearClippingPlane()));
+ bcam->clip_end = static_cast<float>(cam_sample.getFarClippingPlane());
+ bcam->dof.focus_distance = static_cast<float>(cam_sample.getFocusDistance());
+ bcam->dof.aperture_fstop = static_cast<float>(cam_sample.getFStop());
+
+ m_object = BKE_object_add_only_object(bmain, OB_CAMERA, m_object_name.c_str());
+ m_object->data = bcam;
+}
diff --git a/source/blender/io/alembic/intern/abc_reader_camera.h b/source/blender/io/alembic/intern/abc_reader_camera.h
new file mode 100644
index 00000000000..1d9763b0454
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_camera.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_READER_CAMERA_H__
+#define __ABC_READER_CAMERA_H__
+
+#include "abc_reader_object.h"
+
+class AbcCameraReader : public AbcObjectReader {
+ Alembic::AbcGeom::ICameraSchema m_schema;
+
+ public:
+ AbcCameraReader(const Alembic::Abc::IObject &object, ImportSettings &settings);
+
+ bool valid() const;
+ bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const;
+
+ void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel);
+};
+
+#endif /* __ABC_READER_CAMERA_H__ */
diff --git a/source/blender/io/alembic/intern/abc_reader_curves.cc b/source/blender/io/alembic/intern/abc_reader_curves.cc
new file mode 100644
index 00000000000..1be164c7c94
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_curves.cc
@@ -0,0 +1,354 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_reader_curves.h"
+#include "abc_reader_transform.h"
+#include "abc_util.h"
+
+#include <cstdio>
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "DNA_curve_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_listbase.h"
+
+#include "BKE_curve.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+}
+
+using Alembic::Abc::FloatArraySamplePtr;
+using Alembic::Abc::Int32ArraySamplePtr;
+using Alembic::Abc::P3fArraySamplePtr;
+using Alembic::Abc::PropertyHeader;
+using Alembic::Abc::UcharArraySamplePtr;
+
+using Alembic::AbcGeom::CurvePeriodicity;
+using Alembic::AbcGeom::ICompoundProperty;
+using Alembic::AbcGeom::ICurves;
+using Alembic::AbcGeom::ICurvesSchema;
+using Alembic::AbcGeom::IFloatGeomParam;
+using Alembic::AbcGeom::IInt16Property;
+using Alembic::AbcGeom::ISampleSelector;
+using Alembic::AbcGeom::kWrapExisting;
+
+AbcCurveReader::AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings)
+ : AbcObjectReader(object, settings)
+{
+ ICurves abc_curves(object, kWrapExisting);
+ m_curves_schema = abc_curves.getSchema();
+
+ get_min_max_time(m_iobject, m_curves_schema, m_min_time, m_max_time);
+}
+
+bool AbcCurveReader::valid() const
+{
+ return m_curves_schema.valid();
+}
+
+bool AbcCurveReader::accepts_object_type(
+ const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const
+{
+ if (!Alembic::AbcGeom::ICurves::matches(alembic_header)) {
+ *err_str =
+ "Object type mismatch, Alembic object path pointed to Curves when importing, but not any "
+ "more.";
+ return false;
+ }
+
+ if (ob->type != OB_CURVE) {
+ *err_str = "Object type mismatch, Alembic object path points to Curves.";
+ return false;
+ }
+
+ return true;
+}
+
+void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
+{
+ Curve *cu = BKE_curve_add(bmain, m_data_name.c_str(), OB_CURVE);
+
+ cu->flag |= CU_DEFORM_FILL | CU_3D;
+ cu->actvert = CU_ACT_NONE;
+ cu->resolu = 1;
+
+ ICompoundProperty user_props = m_curves_schema.getUserProperties();
+ if (user_props) {
+ const PropertyHeader *header = user_props.getPropertyHeader(ABC_CURVE_RESOLUTION_U_PROPNAME);
+ if (header != NULL && header->isScalar() && IInt16Property::matches(*header)) {
+ IInt16Property resolu(user_props, header->getName());
+ cu->resolu = resolu.getValue(sample_sel);
+ }
+ }
+
+ m_object = BKE_object_add_only_object(bmain, OB_CURVE, m_object_name.c_str());
+ m_object->data = cu;
+
+ read_curve_sample(cu, m_curves_schema, sample_sel);
+
+ if (has_animations(m_curves_schema, m_settings)) {
+ addCacheModifier();
+ }
+}
+
+void AbcCurveReader::read_curve_sample(Curve *cu,
+ const ICurvesSchema &schema,
+ const ISampleSelector &sample_sel)
+{
+ ICurvesSchema::Sample smp;
+ try {
+ smp = schema.getValue(sample_sel);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ printf("Alembic: error reading curve sample for '%s/%s' at time %f: %s\n",
+ m_iobject.getFullName().c_str(),
+ schema.getName().c_str(),
+ sample_sel.getRequestedTime(),
+ ex.what());
+ return;
+ }
+
+ const Int32ArraySamplePtr num_vertices = smp.getCurvesNumVertices();
+ const P3fArraySamplePtr positions = smp.getPositions();
+ const FloatArraySamplePtr weights = smp.getPositionWeights();
+ const FloatArraySamplePtr knots = smp.getKnots();
+ const CurvePeriodicity periodicity = smp.getWrap();
+ const UcharArraySamplePtr orders = smp.getOrders();
+
+ const IFloatGeomParam widths_param = schema.getWidthsParam();
+ FloatArraySamplePtr radiuses;
+
+ if (widths_param.valid()) {
+ IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel);
+ radiuses = wsample.getVals();
+ }
+
+ int knot_offset = 0;
+
+ size_t idx = 0;
+ for (size_t i = 0; i < num_vertices->size(); i++) {
+ const int num_verts = (*num_vertices)[i];
+
+ Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), "abc_getnurb"));
+ nu->resolu = cu->resolu;
+ nu->resolv = cu->resolv;
+ nu->pntsu = num_verts;
+ nu->pntsv = 1;
+ nu->flag |= CU_SMOOTH;
+
+ switch (smp.getType()) {
+ case Alembic::AbcGeom::kCubic:
+ nu->orderu = 4;
+ break;
+ case Alembic::AbcGeom::kVariableOrder:
+ if (orders && orders->size() > i) {
+ nu->orderu = static_cast<short>((*orders)[i]);
+ break;
+ }
+ ATTR_FALLTHROUGH;
+ case Alembic::AbcGeom::kLinear:
+ default:
+ nu->orderu = 2;
+ }
+
+ if (periodicity == Alembic::AbcGeom::kNonPeriodic) {
+ nu->flagu |= CU_NURB_ENDPOINT;
+ }
+ else if (periodicity == Alembic::AbcGeom::kPeriodic) {
+ nu->flagu |= CU_NURB_CYCLIC;
+
+ /* Check the number of points which overlap, we don't have
+ * overlapping points in Blender, but other software do use them to
+ * indicate that a curve is actually cyclic. Usually the number of
+ * overlapping points is equal to the order/degree of the curve.
+ */
+
+ const int start = idx;
+ const int end = idx + num_verts;
+ int overlap = 0;
+
+ for (int j = start, k = end - nu->orderu; j < nu->orderu; j++, k++) {
+ const Imath::V3f &p1 = (*positions)[j];
+ const Imath::V3f &p2 = (*positions)[k];
+
+ if (p1 != p2) {
+ break;
+ }
+
+ overlap++;
+ }
+
+ /* TODO: Special case, need to figure out how it coincides with knots. */
+ if (overlap == 0 && num_verts > 2 && (*positions)[start] == (*positions)[end - 1]) {
+ overlap = 1;
+ }
+
+ /* There is no real cycles. */
+ if (overlap == 0) {
+ nu->flagu &= ~CU_NURB_CYCLIC;
+ nu->flagu |= CU_NURB_ENDPOINT;
+ }
+
+ nu->pntsu -= overlap;
+ }
+
+ const bool do_weights = (weights != NULL) && (weights->size() > 1);
+ float weight = 1.0f;
+
+ const bool do_radius = (radiuses != NULL) && (radiuses->size() > 1);
+ float radius = (radiuses && radiuses->size() == 1) ? (*radiuses)[0] : 1.0f;
+
+ nu->type = CU_NURBS;
+
+ nu->bp = static_cast<BPoint *>(MEM_callocN(sizeof(BPoint) * nu->pntsu, "abc_getnurb"));
+ BPoint *bp = nu->bp;
+
+ for (int j = 0; j < nu->pntsu; j++, bp++, idx++) {
+ const Imath::V3f &pos = (*positions)[idx];
+
+ if (do_radius) {
+ radius = (*radiuses)[idx];
+ }
+
+ if (do_weights) {
+ weight = (*weights)[idx];
+ }
+
+ copy_zup_from_yup(bp->vec, pos.getValue());
+ bp->vec[3] = weight;
+ bp->f1 = SELECT;
+ bp->radius = radius;
+ bp->weight = 1.0f;
+ }
+
+ if (knots && knots->size() != 0) {
+ nu->knotsu = static_cast<float *>(
+ MEM_callocN(KNOTSU(nu) * sizeof(float), "abc_setsplineknotsu"));
+
+ /* TODO: second check is temporary, for until the check for cycles is rock solid. */
+ if (periodicity == Alembic::AbcGeom::kPeriodic && (KNOTSU(nu) == knots->size() - 2)) {
+ /* Skip first and last knots. */
+ for (size_t i = 1; i < knots->size() - 1; i++) {
+ nu->knotsu[i - 1] = (*knots)[knot_offset + i];
+ }
+ }
+ else {
+ /* TODO: figure out how to use the knots array from other
+ * software in this case. */
+ BKE_nurb_knot_calc_u(nu);
+ }
+
+ knot_offset += knots->size();
+ }
+ else {
+ BKE_nurb_knot_calc_u(nu);
+ }
+
+ BLI_addtail(BKE_curve_nurbs_get(cu), nu);
+ }
+}
+
+/* NOTE: Alembic only stores data about control points, but the Mesh
+ * passed from the cache modifier contains the displist, which has more data
+ * than the control points, so to avoid corrupting the displist we modify the
+ * object directly and create a new Mesh from that. Also we might need to
+ * create new or delete existing NURBS in the curve.
+ */
+Mesh *AbcCurveReader::read_mesh(Mesh *existing_mesh,
+ const ISampleSelector &sample_sel,
+ int /*read_flag*/,
+ const char **err_str)
+{
+ ICurvesSchema::Sample sample;
+
+ try {
+ sample = m_curves_schema.getValue(sample_sel);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ *err_str = "Error reading curve sample; more detail on the console";
+ printf("Alembic: error reading curve sample for '%s/%s' at time %f: %s\n",
+ m_iobject.getFullName().c_str(),
+ m_curves_schema.getName().c_str(),
+ sample_sel.getRequestedTime(),
+ ex.what());
+ return existing_mesh;
+ }
+
+ const P3fArraySamplePtr &positions = sample.getPositions();
+ const Int32ArraySamplePtr num_vertices = sample.getCurvesNumVertices();
+
+ int vertex_idx = 0;
+ int curve_idx;
+ Curve *curve = static_cast<Curve *>(m_object->data);
+
+ const int curve_count = BLI_listbase_count(&curve->nurb);
+ bool same_topology = curve_count == num_vertices->size();
+
+ if (same_topology) {
+ Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first);
+ for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) {
+ const int num_in_alembic = (*num_vertices)[curve_idx];
+ const int num_in_blender = nurbs->pntsu;
+
+ if (num_in_alembic != num_in_blender) {
+ same_topology = false;
+ break;
+ }
+ }
+ }
+
+ if (!same_topology) {
+ BKE_nurbList_free(&curve->nurb);
+ read_curve_sample(curve, m_curves_schema, sample_sel);
+ }
+ else {
+ Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first);
+ for (curve_idx = 0; nurbs; nurbs = nurbs->next, curve_idx++) {
+ const int totpoint = (*num_vertices)[curve_idx];
+
+ if (nurbs->bp) {
+ BPoint *point = nurbs->bp;
+
+ for (int i = 0; i < totpoint; i++, point++, vertex_idx++) {
+ const Imath::V3f &pos = (*positions)[vertex_idx];
+ copy_zup_from_yup(point->vec, pos.getValue());
+ }
+ }
+ else if (nurbs->bezt) {
+ BezTriple *bezier = nurbs->bezt;
+
+ for (int i = 0; i < totpoint; i++, bezier++, vertex_idx++) {
+ const Imath::V3f &pos = (*positions)[vertex_idx];
+ copy_zup_from_yup(bezier->vec[1], pos.getValue());
+ }
+ }
+ }
+ }
+
+ return BKE_mesh_new_nomain_from_curve(m_object);
+}
diff --git a/source/blender/io/alembic/intern/abc_reader_curves.h b/source/blender/io/alembic/intern/abc_reader_curves.h
new file mode 100644
index 00000000000..1e4f28edc51
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_curves.h
@@ -0,0 +1,56 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_READER_CURVES_H__
+#define __ABC_READER_CURVES_H__
+
+#include "abc_reader_object.h"
+#include "abc_reader_mesh.h"
+
+struct Curve;
+
+#define ABC_CURVE_RESOLUTION_U_PROPNAME "blender:resolution"
+
+class AbcCurveReader : public AbcObjectReader {
+ Alembic::AbcGeom::ICurvesSchema m_curves_schema;
+
+ public:
+ AbcCurveReader(const Alembic::Abc::IObject &object, ImportSettings &settings);
+
+ bool valid() const;
+ bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const;
+
+ void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel);
+ struct Mesh *read_mesh(struct Mesh *existing_mesh,
+ const Alembic::Abc::ISampleSelector &sample_sel,
+ int read_flag,
+ const char **err_str);
+
+ void read_curve_sample(Curve *cu,
+ const Alembic::AbcGeom::ICurvesSchema &schema,
+ const Alembic::Abc::ISampleSelector &sample_selector);
+};
+
+#endif /* __ABC_READER_CURVES_H__ */
diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.cc b/source/blender/io/alembic/intern/abc_reader_mesh.cc
new file mode 100644
index 00000000000..a4e412695c3
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_mesh.cc
@@ -0,0 +1,889 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_reader_mesh.h"
+#include "abc_reader_transform.h"
+#include "abc_util.h"
+
+#include <algorithm>
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "DNA_material_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_math_geom.h"
+
+#include "BKE_main.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_modifier.h"
+#include "BKE_object.h"
+}
+
+using Alembic::Abc::Int32ArraySamplePtr;
+using Alembic::Abc::P3fArraySamplePtr;
+
+using Alembic::AbcGeom::IFaceSet;
+using Alembic::AbcGeom::IFaceSetSchema;
+using Alembic::AbcGeom::IN3fGeomParam;
+using Alembic::AbcGeom::IObject;
+using Alembic::AbcGeom::IPolyMesh;
+using Alembic::AbcGeom::IPolyMeshSchema;
+using Alembic::AbcGeom::ISampleSelector;
+using Alembic::AbcGeom::ISubD;
+using Alembic::AbcGeom::ISubDSchema;
+using Alembic::AbcGeom::IV2fGeomParam;
+using Alembic::AbcGeom::kWrapExisting;
+using Alembic::AbcGeom::N3fArraySample;
+using Alembic::AbcGeom::N3fArraySamplePtr;
+using Alembic::AbcGeom::UInt32ArraySamplePtr;
+using Alembic::AbcGeom::V2fArraySamplePtr;
+
+/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */
+
+/* Some helpers for mesh generation */
+namespace utils {
+
+static void build_mat_map(const Main *bmain, std::map<std::string, Material *> &mat_map)
+{
+ Material *material = static_cast<Material *>(bmain->materials.first);
+
+ for (; material; material = static_cast<Material *>(material->id.next)) {
+ mat_map[material->id.name + 2] = material;
+ }
+}
+
+static void assign_materials(Main *bmain,
+ Object *ob,
+ const std::map<std::string, int> &mat_index_map)
+{
+ bool can_assign = true;
+ std::map<std::string, int>::const_iterator it = mat_index_map.begin();
+
+ int matcount = 0;
+ for (; it != mat_index_map.end(); ++it, matcount++) {
+ if (!BKE_object_material_slot_add(bmain, ob)) {
+ can_assign = false;
+ break;
+ }
+ }
+
+ /* TODO(kevin): use global map? */
+ std::map<std::string, Material *> mat_map;
+ build_mat_map(bmain, mat_map);
+
+ std::map<std::string, Material *>::iterator mat_iter;
+
+ if (can_assign) {
+ it = mat_index_map.begin();
+
+ for (; it != mat_index_map.end(); ++it) {
+ std::string mat_name = it->first;
+ mat_iter = mat_map.find(mat_name.c_str());
+
+ Material *assigned_mat;
+
+ if (mat_iter == mat_map.end()) {
+ assigned_mat = BKE_material_add(bmain, mat_name.c_str());
+ mat_map[mat_name] = assigned_mat;
+ }
+ else {
+ assigned_mat = mat_iter->second;
+ }
+
+ BKE_object_material_assign(bmain, ob, assigned_mat, it->second, BKE_MAT_ASSIGN_OBDATA);
+ }
+ }
+}
+
+} /* namespace utils */
+
+struct AbcMeshData {
+ Int32ArraySamplePtr face_indices;
+ Int32ArraySamplePtr face_counts;
+
+ P3fArraySamplePtr positions;
+ P3fArraySamplePtr ceil_positions;
+
+ V2fArraySamplePtr uvs;
+ UInt32ArraySamplePtr uvs_indices;
+};
+
+static void read_mverts_interp(MVert *mverts,
+ const P3fArraySamplePtr &positions,
+ const P3fArraySamplePtr &ceil_positions,
+ const float weight)
+{
+ float tmp[3];
+ for (int i = 0; i < positions->size(); i++) {
+ MVert &mvert = mverts[i];
+ const Imath::V3f &floor_pos = (*positions)[i];
+ const Imath::V3f &ceil_pos = (*ceil_positions)[i];
+
+ interp_v3_v3v3(tmp, floor_pos.getValue(), ceil_pos.getValue(), weight);
+ copy_zup_from_yup(mvert.co, tmp);
+
+ mvert.bweight = 0;
+ }
+}
+
+static void read_mverts(CDStreamConfig &config, const AbcMeshData &mesh_data)
+{
+ MVert *mverts = config.mvert;
+ const P3fArraySamplePtr &positions = mesh_data.positions;
+
+ if (config.weight != 0.0f && mesh_data.ceil_positions != NULL &&
+ mesh_data.ceil_positions->size() == positions->size()) {
+ read_mverts_interp(mverts, positions, mesh_data.ceil_positions, config.weight);
+ return;
+ }
+
+ read_mverts(mverts, positions, nullptr);
+}
+
+void read_mverts(MVert *mverts, const P3fArraySamplePtr positions, const N3fArraySamplePtr normals)
+{
+ for (int i = 0; i < positions->size(); i++) {
+ MVert &mvert = mverts[i];
+ Imath::V3f pos_in = (*positions)[i];
+
+ copy_zup_from_yup(mvert.co, pos_in.getValue());
+
+ mvert.bweight = 0;
+
+ if (normals) {
+ Imath::V3f nor_in = (*normals)[i];
+
+ short no[3];
+ normal_float_to_short_v3(no, nor_in.getValue());
+
+ copy_zup_from_yup(mvert.no, no);
+ }
+ }
+}
+
+static void read_mpolys(CDStreamConfig &config, const AbcMeshData &mesh_data)
+{
+ MPoly *mpolys = config.mpoly;
+ MLoop *mloops = config.mloop;
+ MLoopUV *mloopuvs = config.mloopuv;
+
+ const Int32ArraySamplePtr &face_indices = mesh_data.face_indices;
+ const Int32ArraySamplePtr &face_counts = mesh_data.face_counts;
+ const V2fArraySamplePtr &uvs = mesh_data.uvs;
+ const size_t uvs_size = uvs == nullptr ? 0 : uvs->size();
+
+ const UInt32ArraySamplePtr &uvs_indices = mesh_data.uvs_indices;
+
+ const bool do_uvs = (mloopuvs && uvs && uvs_indices) &&
+ (uvs_indices->size() == face_indices->size());
+ unsigned int loop_index = 0;
+ unsigned int rev_loop_index = 0;
+ unsigned int uv_index = 0;
+
+ for (int i = 0; i < face_counts->size(); i++) {
+ const int face_size = (*face_counts)[i];
+
+ MPoly &poly = mpolys[i];
+ poly.loopstart = loop_index;
+ poly.totloop = face_size;
+
+ /* Polygons are always assumed to be smooth-shaded. If the Alembic mesh should be flat-shaded,
+ * this is encoded in custom loop normals. See T71246. */
+ poly.flag |= ME_SMOOTH;
+
+ /* NOTE: Alembic data is stored in the reverse order. */
+ rev_loop_index = loop_index + (face_size - 1);
+
+ for (int f = 0; f < face_size; f++, loop_index++, rev_loop_index--) {
+ MLoop &loop = mloops[rev_loop_index];
+ loop.v = (*face_indices)[loop_index];
+
+ if (do_uvs) {
+ MLoopUV &loopuv = mloopuvs[rev_loop_index];
+
+ uv_index = (*uvs_indices)[loop_index];
+
+ /* Some Alembic files are broken (or at least export UVs in a way we don't expect). */
+ if (uv_index >= uvs_size) {
+ continue;
+ }
+
+ loopuv.uv[0] = (*uvs)[uv_index][0];
+ loopuv.uv[1] = (*uvs)[uv_index][1];
+ }
+ }
+ }
+
+ BKE_mesh_calc_edges(config.mesh, false, false);
+}
+
+static void process_no_normals(CDStreamConfig &config)
+{
+ /* Absense of normals in the Alembic mesh is interpreted as 'smooth'. */
+ BKE_mesh_calc_normals(config.mesh);
+}
+
+static void process_loop_normals(CDStreamConfig &config, const N3fArraySamplePtr loop_normals_ptr)
+{
+ size_t loop_count = loop_normals_ptr->size();
+
+ if (loop_count == 0) {
+ process_no_normals(config);
+ return;
+ }
+
+ float(*lnors)[3] = static_cast<float(*)[3]>(
+ MEM_malloc_arrayN(loop_count, sizeof(float[3]), "ABC::FaceNormals"));
+
+ Mesh *mesh = config.mesh;
+ MPoly *mpoly = mesh->mpoly;
+ const N3fArraySample &loop_normals = *loop_normals_ptr;
+ int abc_index = 0;
+ for (int i = 0, e = mesh->totpoly; i < e; i++, mpoly++) {
+ /* As usual, ABC orders the loops in reverse. */
+ for (int j = mpoly->totloop - 1; j >= 0; j--, abc_index++) {
+ int blender_index = mpoly->loopstart + j;
+ copy_zup_from_yup(lnors[blender_index], loop_normals[abc_index].getValue());
+ }
+ }
+
+ mesh->flag |= ME_AUTOSMOOTH;
+ BKE_mesh_set_custom_normals(mesh, lnors);
+
+ MEM_freeN(lnors);
+}
+
+static void process_vertex_normals(CDStreamConfig &config,
+ const N3fArraySamplePtr vertex_normals_ptr)
+{
+ size_t normals_count = vertex_normals_ptr->size();
+ if (normals_count == 0) {
+ process_no_normals(config);
+ return;
+ }
+
+ float(*vnors)[3] = static_cast<float(*)[3]>(
+ MEM_malloc_arrayN(normals_count, sizeof(float[3]), "ABC::VertexNormals"));
+
+ const N3fArraySample &vertex_normals = *vertex_normals_ptr;
+ for (int index = 0; index < normals_count; index++) {
+ copy_zup_from_yup(vnors[index], vertex_normals[index].getValue());
+ }
+
+ config.mesh->flag |= ME_AUTOSMOOTH;
+ BKE_mesh_set_custom_normals_from_vertices(config.mesh, vnors);
+ MEM_freeN(vnors);
+}
+
+static void process_normals(CDStreamConfig &config,
+ const IN3fGeomParam &normals,
+ const ISampleSelector &selector)
+{
+ if (!normals.valid()) {
+ process_no_normals(config);
+ return;
+ }
+
+ IN3fGeomParam::Sample normsamp = normals.getExpandedValue(selector);
+ Alembic::AbcGeom::GeometryScope scope = normals.getScope();
+
+ switch (scope) {
+ case Alembic::AbcGeom::kFacevaryingScope: // 'Vertex Normals' in Houdini.
+ process_loop_normals(config, normsamp.getVals());
+ break;
+ case Alembic::AbcGeom::kVertexScope:
+ case Alembic::AbcGeom::kVaryingScope: // 'Point Normals' in Houdini.
+ process_vertex_normals(config, normsamp.getVals());
+ break;
+ case Alembic::AbcGeom::kConstantScope:
+ case Alembic::AbcGeom::kUniformScope:
+ case Alembic::AbcGeom::kUnknownScope:
+ process_no_normals(config);
+ break;
+ }
+}
+
+ABC_INLINE void read_uvs_params(CDStreamConfig &config,
+ AbcMeshData &abc_data,
+ const IV2fGeomParam &uv,
+ const ISampleSelector &selector)
+{
+ if (!uv.valid()) {
+ return;
+ }
+
+ IV2fGeomParam::Sample uvsamp;
+ uv.getIndexed(uvsamp, selector);
+
+ abc_data.uvs = uvsamp.getVals();
+ abc_data.uvs_indices = uvsamp.getIndices();
+
+ if (abc_data.uvs_indices->size() == config.totloop) {
+ std::string name = Alembic::Abc::GetSourceName(uv.getMetaData());
+
+ /* According to the convention, primary UVs should have had their name
+ * set using Alembic::Abc::SetSourceName, but you can't expect everyone
+ * to follow it! :) */
+ if (name.empty()) {
+ name = uv.getName();
+ }
+
+ void *cd_ptr = config.add_customdata_cb(config.mesh, name.c_str(), CD_MLOOPUV);
+ config.mloopuv = static_cast<MLoopUV *>(cd_ptr);
+ }
+}
+
+static void *add_customdata_cb(Mesh *mesh, const char *name, int data_type)
+{
+ CustomDataType cd_data_type = static_cast<CustomDataType>(data_type);
+ void *cd_ptr;
+ CustomData *loopdata;
+ int numloops;
+
+ /* unsupported custom data type -- don't do anything. */
+ if (!ELEM(cd_data_type, CD_MLOOPUV, CD_MLOOPCOL)) {
+ return NULL;
+ }
+
+ loopdata = &mesh->ldata;
+ cd_ptr = CustomData_get_layer_named(loopdata, cd_data_type, name);
+ if (cd_ptr != NULL) {
+ /* layer already exists, so just return it. */
+ return cd_ptr;
+ }
+
+ /* Create a new layer. */
+ numloops = mesh->totloop;
+ cd_ptr = CustomData_add_layer_named(loopdata, cd_data_type, CD_DEFAULT, NULL, numloops, name);
+ return cd_ptr;
+}
+
+static void get_weight_and_index(CDStreamConfig &config,
+ Alembic::AbcCoreAbstract::TimeSamplingPtr time_sampling,
+ size_t samples_number)
+{
+ Alembic::AbcGeom::index_t i0, i1;
+
+ config.weight = get_weight_and_index(config.time, time_sampling, samples_number, i0, i1);
+
+ config.index = i0;
+ config.ceil_index = i1;
+}
+
+static void read_mesh_sample(const std::string &iobject_full_name,
+ ImportSettings *settings,
+ const IPolyMeshSchema &schema,
+ const ISampleSelector &selector,
+ CDStreamConfig &config)
+{
+ const IPolyMeshSchema::Sample sample = schema.getValue(selector);
+
+ AbcMeshData abc_mesh_data;
+ abc_mesh_data.face_counts = sample.getFaceCounts();
+ abc_mesh_data.face_indices = sample.getFaceIndices();
+ abc_mesh_data.positions = sample.getPositions();
+
+ get_weight_and_index(config, schema.getTimeSampling(), schema.getNumSamples());
+
+ if (config.weight != 0.0f) {
+ Alembic::AbcGeom::IPolyMeshSchema::Sample ceil_sample;
+ schema.get(ceil_sample, Alembic::Abc::ISampleSelector(config.ceil_index));
+ abc_mesh_data.ceil_positions = ceil_sample.getPositions();
+ }
+
+ if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
+ read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector);
+ }
+
+ if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) {
+ read_mverts(config, abc_mesh_data);
+ }
+
+ if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) {
+ read_mpolys(config, abc_mesh_data);
+ process_normals(config, schema.getNormalsParam(), selector);
+ }
+
+ if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) {
+ read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector);
+ }
+}
+
+CDStreamConfig get_config(Mesh *mesh)
+{
+ CDStreamConfig config;
+
+ BLI_assert(mesh->mvert || mesh->totvert == 0);
+
+ config.mesh = mesh;
+ config.mvert = mesh->mvert;
+ config.mloop = mesh->mloop;
+ config.mpoly = mesh->mpoly;
+ config.totloop = mesh->totloop;
+ config.totpoly = mesh->totpoly;
+ config.loopdata = &mesh->ldata;
+ config.add_customdata_cb = add_customdata_cb;
+
+ return config;
+}
+
+/* ************************************************************************** */
+
+AbcMeshReader::AbcMeshReader(const IObject &object, ImportSettings &settings)
+ : AbcObjectReader(object, settings)
+{
+ m_settings->read_flag |= MOD_MESHSEQ_READ_ALL;
+
+ IPolyMesh ipoly_mesh(m_iobject, kWrapExisting);
+ m_schema = ipoly_mesh.getSchema();
+
+ get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time);
+}
+
+bool AbcMeshReader::valid() const
+{
+ return m_schema.valid();
+}
+
+void AbcMeshReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
+{
+ Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str());
+
+ m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str());
+ m_object->data = mesh;
+
+ Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, NULL);
+ if (read_mesh != mesh) {
+ /* XXX fixme after 2.80; mesh->flag isn't copied by BKE_mesh_nomain_to_mesh() */
+ /* read_mesh can be freed by BKE_mesh_nomain_to_mesh(), so get the flag before that happens. */
+ short autosmooth = (read_mesh->flag & ME_AUTOSMOOTH);
+ BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true);
+ mesh->flag |= autosmooth;
+ }
+
+ if (m_settings->validate_meshes) {
+ BKE_mesh_validate(mesh, false, false);
+ }
+
+ readFaceSetsSample(bmain, mesh, sample_sel);
+
+ if (has_animations(m_schema, m_settings)) {
+ addCacheModifier();
+ }
+}
+
+bool AbcMeshReader::accepts_object_type(
+ const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const
+{
+ if (!Alembic::AbcGeom::IPolyMesh::matches(alembic_header)) {
+ *err_str =
+ "Object type mismatch, Alembic object path pointed to PolyMesh when importing, but not "
+ "any more.";
+ return false;
+ }
+
+ if (ob->type != OB_MESH) {
+ *err_str = "Object type mismatch, Alembic object path points to PolyMesh.";
+ return false;
+ }
+
+ return true;
+}
+
+bool AbcMeshReader::topology_changed(Mesh *existing_mesh, const ISampleSelector &sample_sel)
+{
+ IPolyMeshSchema::Sample sample;
+ try {
+ sample = m_schema.getValue(sample_sel);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n",
+ m_iobject.getFullName().c_str(),
+ m_schema.getName().c_str(),
+ sample_sel.getRequestedTime(),
+ ex.what());
+ // A similar error in read_mesh() would just return existing_mesh.
+ return false;
+ }
+
+ const P3fArraySamplePtr &positions = sample.getPositions();
+ const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices();
+ const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts();
+
+ return positions->size() != existing_mesh->totvert ||
+ face_counts->size() != existing_mesh->totpoly ||
+ face_indices->size() != existing_mesh->totloop;
+}
+
+Mesh *AbcMeshReader::read_mesh(Mesh *existing_mesh,
+ const ISampleSelector &sample_sel,
+ int read_flag,
+ const char **err_str)
+{
+ IPolyMeshSchema::Sample sample;
+ try {
+ sample = m_schema.getValue(sample_sel);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ if (err_str != nullptr) {
+ *err_str = "Error reading mesh sample; more detail on the console";
+ }
+ printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n",
+ m_iobject.getFullName().c_str(),
+ m_schema.getName().c_str(),
+ sample_sel.getRequestedTime(),
+ ex.what());
+ return existing_mesh;
+ }
+
+ const P3fArraySamplePtr &positions = sample.getPositions();
+ const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices();
+ const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts();
+
+ Mesh *new_mesh = NULL;
+
+ /* Only read point data when streaming meshes, unless we need to create new ones. */
+ ImportSettings settings;
+ settings.read_flag |= read_flag;
+
+ if (topology_changed(existing_mesh, sample_sel)) {
+ new_mesh = BKE_mesh_new_nomain_from_template(
+ existing_mesh, positions->size(), 0, 0, face_indices->size(), face_counts->size());
+
+ settings.read_flag |= MOD_MESHSEQ_READ_ALL;
+ }
+ else {
+ /* If the face count changed (e.g. by triangulation), only read points.
+ * This prevents crash from T49813.
+ * TODO(kevin): perhaps find a better way to do this? */
+ if (face_counts->size() != existing_mesh->totpoly ||
+ face_indices->size() != existing_mesh->totloop) {
+ settings.read_flag = MOD_MESHSEQ_READ_VERT;
+
+ if (err_str) {
+ *err_str =
+ "Topology has changed, perhaps by triangulating the"
+ " mesh. Only vertices will be read!";
+ }
+ }
+ }
+
+ CDStreamConfig config = get_config(new_mesh ? new_mesh : existing_mesh);
+ config.time = sample_sel.getRequestedTime();
+
+ read_mesh_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config);
+
+ if (new_mesh) {
+ /* Here we assume that the number of materials doesn't change, i.e. that
+ * the material slots that were created when the object was loaded from
+ * Alembic are still valid now. */
+ size_t num_polys = new_mesh->totpoly;
+ if (num_polys > 0) {
+ std::map<std::string, int> mat_map;
+ assign_facesets_to_mpoly(sample_sel, new_mesh->mpoly, num_polys, mat_map);
+ }
+
+ return new_mesh;
+ }
+
+ return existing_mesh;
+}
+
+void AbcMeshReader::assign_facesets_to_mpoly(const ISampleSelector &sample_sel,
+ MPoly *mpoly,
+ int totpoly,
+ std::map<std::string, int> &r_mat_map)
+{
+ std::vector<std::string> face_sets;
+ m_schema.getFaceSetNames(face_sets);
+
+ if (face_sets.empty()) {
+ return;
+ }
+
+ int current_mat = 0;
+
+ for (int i = 0; i < face_sets.size(); i++) {
+ const std::string &grp_name = face_sets[i];
+
+ if (r_mat_map.find(grp_name) == r_mat_map.end()) {
+ r_mat_map[grp_name] = 1 + current_mat++;
+ }
+
+ const int assigned_mat = r_mat_map[grp_name];
+
+ const IFaceSet faceset = m_schema.getFaceSet(grp_name);
+
+ if (!faceset.valid()) {
+ std::cerr << " Face set " << grp_name << " invalid for " << m_object_name << "\n";
+ continue;
+ }
+
+ const IFaceSetSchema face_schem = faceset.getSchema();
+ const IFaceSetSchema::Sample face_sample = face_schem.getValue(sample_sel);
+ const Int32ArraySamplePtr group_faces = face_sample.getFaces();
+ const size_t num_group_faces = group_faces->size();
+
+ for (size_t l = 0; l < num_group_faces; l++) {
+ size_t pos = (*group_faces)[l];
+
+ if (pos >= totpoly) {
+ std::cerr << "Faceset overflow on " << faceset.getName() << '\n';
+ break;
+ }
+
+ MPoly &poly = mpoly[pos];
+ poly.mat_nr = assigned_mat - 1;
+ }
+ }
+}
+
+void AbcMeshReader::readFaceSetsSample(Main *bmain, Mesh *mesh, const ISampleSelector &sample_sel)
+{
+ std::map<std::string, int> mat_map;
+ assign_facesets_to_mpoly(sample_sel, mesh->mpoly, mesh->totpoly, mat_map);
+ utils::assign_materials(bmain, m_object, mat_map);
+}
+
+/* ************************************************************************** */
+
+ABC_INLINE MEdge *find_edge(MEdge *edges, int totedge, int v1, int v2)
+{
+ for (int i = 0, e = totedge; i < e; i++) {
+ MEdge &edge = edges[i];
+
+ if (edge.v1 == v1 && edge.v2 == v2) {
+ return &edge;
+ }
+ }
+
+ return NULL;
+}
+
+static void read_subd_sample(const std::string &iobject_full_name,
+ ImportSettings *settings,
+ const ISubDSchema &schema,
+ const ISampleSelector &selector,
+ CDStreamConfig &config)
+{
+ const ISubDSchema::Sample sample = schema.getValue(selector);
+
+ AbcMeshData abc_mesh_data;
+ abc_mesh_data.face_counts = sample.getFaceCounts();
+ abc_mesh_data.face_indices = sample.getFaceIndices();
+ abc_mesh_data.positions = sample.getPositions();
+
+ get_weight_and_index(config, schema.getTimeSampling(), schema.getNumSamples());
+
+ if (config.weight != 0.0f) {
+ Alembic::AbcGeom::ISubDSchema::Sample ceil_sample;
+ schema.get(ceil_sample, Alembic::Abc::ISampleSelector(config.ceil_index));
+ abc_mesh_data.ceil_positions = ceil_sample.getPositions();
+ }
+
+ if ((settings->read_flag & MOD_MESHSEQ_READ_UV) != 0) {
+ read_uvs_params(config, abc_mesh_data, schema.getUVsParam(), selector);
+ }
+
+ if ((settings->read_flag & MOD_MESHSEQ_READ_VERT) != 0) {
+ read_mverts(config, abc_mesh_data);
+ }
+
+ if ((settings->read_flag & MOD_MESHSEQ_READ_POLY) != 0) {
+ /* Alembic's 'SubD' scheme is used to store subdivision surfaces, i.e. the pre-subdivision
+ * mesh. Currently we don't add a subdivision modifier when we load such data. This code is
+ * assuming that the subdivided surface should be smooth. */
+ read_mpolys(config, abc_mesh_data);
+ process_no_normals(config);
+ }
+
+ if ((settings->read_flag & (MOD_MESHSEQ_READ_UV | MOD_MESHSEQ_READ_COLOR)) != 0) {
+ read_custom_data(iobject_full_name, schema.getArbGeomParams(), config, selector);
+ }
+}
+
+/* ************************************************************************** */
+
+AbcSubDReader::AbcSubDReader(const IObject &object, ImportSettings &settings)
+ : AbcObjectReader(object, settings)
+{
+ m_settings->read_flag |= MOD_MESHSEQ_READ_ALL;
+
+ ISubD isubd_mesh(m_iobject, kWrapExisting);
+ m_schema = isubd_mesh.getSchema();
+
+ get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time);
+}
+
+bool AbcSubDReader::valid() const
+{
+ return m_schema.valid();
+}
+
+bool AbcSubDReader::accepts_object_type(
+ const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const
+{
+ if (!Alembic::AbcGeom::ISubD::matches(alembic_header)) {
+ *err_str =
+ "Object type mismatch, Alembic object path pointed to SubD when importing, but not any "
+ "more.";
+ return false;
+ }
+
+ if (ob->type != OB_MESH) {
+ *err_str = "Object type mismatch, Alembic object path points to SubD.";
+ return false;
+ }
+
+ return true;
+}
+
+void AbcSubDReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
+{
+ Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str());
+
+ m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str());
+ m_object->data = mesh;
+
+ Mesh *read_mesh = this->read_mesh(mesh, sample_sel, MOD_MESHSEQ_READ_ALL, NULL);
+ if (read_mesh != mesh) {
+ BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true);
+ }
+
+ ISubDSchema::Sample sample;
+ try {
+ sample = m_schema.getValue(sample_sel);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n",
+ m_iobject.getFullName().c_str(),
+ m_schema.getName().c_str(),
+ sample_sel.getRequestedTime(),
+ ex.what());
+ return;
+ }
+
+ Int32ArraySamplePtr indices = sample.getCreaseIndices();
+ Alembic::Abc::FloatArraySamplePtr sharpnesses = sample.getCreaseSharpnesses();
+
+ if (indices && sharpnesses) {
+ MEdge *edges = mesh->medge;
+ int totedge = mesh->totedge;
+
+ for (int i = 0, s = 0, e = indices->size(); i < e; i += 2, s++) {
+ int v1 = (*indices)[i];
+ int v2 = (*indices)[i + 1];
+
+ if (v2 < v1) {
+ /* It appears to be common to store edges with the smallest index first, in which case this
+ * prevents us from doing the second search below. */
+ std::swap(v1, v2);
+ }
+
+ MEdge *edge = find_edge(edges, totedge, v1, v2);
+ if (edge == NULL) {
+ edge = find_edge(edges, totedge, v2, v1);
+ }
+
+ if (edge) {
+ edge->crease = unit_float_to_uchar_clamp((*sharpnesses)[s]);
+ }
+ }
+
+ mesh->cd_flag |= ME_CDFLAG_EDGE_CREASE;
+ }
+
+ if (m_settings->validate_meshes) {
+ BKE_mesh_validate(mesh, false, false);
+ }
+
+ if (has_animations(m_schema, m_settings)) {
+ addCacheModifier();
+ }
+}
+
+Mesh *AbcSubDReader::read_mesh(Mesh *existing_mesh,
+ const ISampleSelector &sample_sel,
+ int read_flag,
+ const char **err_str)
+{
+ ISubDSchema::Sample sample;
+ try {
+ sample = m_schema.getValue(sample_sel);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ if (err_str != nullptr) {
+ *err_str = "Error reading mesh sample; more detail on the console";
+ }
+ printf("Alembic: error reading mesh sample for '%s/%s' at time %f: %s\n",
+ m_iobject.getFullName().c_str(),
+ m_schema.getName().c_str(),
+ sample_sel.getRequestedTime(),
+ ex.what());
+ return existing_mesh;
+ }
+
+ const P3fArraySamplePtr &positions = sample.getPositions();
+ const Alembic::Abc::Int32ArraySamplePtr &face_indices = sample.getFaceIndices();
+ const Alembic::Abc::Int32ArraySamplePtr &face_counts = sample.getFaceCounts();
+
+ Mesh *new_mesh = NULL;
+
+ ImportSettings settings;
+ settings.read_flag |= read_flag;
+
+ if (existing_mesh->totvert != positions->size()) {
+ new_mesh = BKE_mesh_new_nomain_from_template(
+ existing_mesh, positions->size(), 0, 0, face_indices->size(), face_counts->size());
+
+ settings.read_flag |= MOD_MESHSEQ_READ_ALL;
+ }
+ else {
+ /* If the face count changed (e.g. by triangulation), only read points.
+ * This prevents crash from T49813.
+ * TODO(kevin): perhaps find a better way to do this? */
+ if (face_counts->size() != existing_mesh->totpoly ||
+ face_indices->size() != existing_mesh->totloop) {
+ settings.read_flag = MOD_MESHSEQ_READ_VERT;
+
+ if (err_str) {
+ *err_str =
+ "Topology has changed, perhaps by triangulating the"
+ " mesh. Only vertices will be read!";
+ }
+ }
+ }
+
+ /* Only read point data when streaming meshes, unless we need to create new ones. */
+ CDStreamConfig config = get_config(new_mesh ? new_mesh : existing_mesh);
+ config.time = sample_sel.getRequestedTime();
+ read_subd_sample(m_iobject.getFullName(), &settings, m_schema, sample_sel, config);
+
+ return config.mesh;
+}
diff --git a/source/blender/io/alembic/intern/abc_reader_mesh.h b/source/blender/io/alembic/intern/abc_reader_mesh.h
new file mode 100644
index 00000000000..bc95c7ec134
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_mesh.h
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_READER_MESH_H__
+#define __ABC_READER_MESH_H__
+
+#include "abc_customdata.h"
+#include "abc_reader_object.h"
+
+struct Mesh;
+
+class AbcMeshReader : public AbcObjectReader {
+ Alembic::AbcGeom::IPolyMeshSchema m_schema;
+
+ CDStreamConfig m_mesh_data;
+
+ public:
+ AbcMeshReader(const Alembic::Abc::IObject &object, ImportSettings &settings);
+
+ bool valid() const override;
+ bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const override;
+ void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
+
+ struct Mesh *read_mesh(struct Mesh *existing_mesh,
+ const Alembic::Abc::ISampleSelector &sample_sel,
+ int read_flag,
+ const char **err_str) override;
+ bool topology_changed(Mesh *existing_mesh,
+ const Alembic::Abc::ISampleSelector &sample_sel) override;
+
+ private:
+ void readFaceSetsSample(Main *bmain,
+ Mesh *mesh,
+ const Alembic::AbcGeom::ISampleSelector &sample_sel);
+
+ void assign_facesets_to_mpoly(const Alembic::Abc::ISampleSelector &sample_sel,
+ MPoly *mpoly,
+ int totpoly,
+ std::map<std::string, int> &r_mat_map);
+};
+
+class AbcSubDReader : public AbcObjectReader {
+ Alembic::AbcGeom::ISubDSchema m_schema;
+
+ CDStreamConfig m_mesh_data;
+
+ public:
+ AbcSubDReader(const Alembic::Abc::IObject &object, ImportSettings &settings);
+
+ bool valid() const;
+ bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const;
+ void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel);
+ struct Mesh *read_mesh(struct Mesh *existing_mesh,
+ const Alembic::Abc::ISampleSelector &sample_sel,
+ int read_flag,
+ const char **err_str);
+};
+
+void read_mverts(MVert *mverts,
+ const Alembic::AbcGeom::P3fArraySamplePtr positions,
+ const Alembic::AbcGeom::N3fArraySamplePtr normals);
+
+CDStreamConfig get_config(struct Mesh *mesh);
+
+#endif /* __ABC_READER_MESH_H__ */
diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.cc b/source/blender/io/alembic/intern/abc_reader_nurbs.cc
new file mode 100644
index 00000000000..0ada10baba5
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_nurbs.cc
@@ -0,0 +1,225 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_reader_nurbs.h"
+#include "abc_reader_transform.h"
+#include "abc_util.h"
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "DNA_curve_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_listbase.h"
+#include "BLI_string.h"
+
+#include "BKE_curve.h"
+#include "BKE_object.h"
+}
+
+using Alembic::AbcGeom::FloatArraySamplePtr;
+using Alembic::AbcGeom::kWrapExisting;
+using Alembic::AbcGeom::MetaData;
+using Alembic::AbcGeom::P3fArraySamplePtr;
+
+using Alembic::AbcGeom::ICompoundProperty;
+using Alembic::AbcGeom::INuPatch;
+using Alembic::AbcGeom::INuPatchSchema;
+using Alembic::AbcGeom::IObject;
+
+AbcNurbsReader::AbcNurbsReader(const IObject &object, ImportSettings &settings)
+ : AbcObjectReader(object, settings)
+{
+ getNurbsPatches(m_iobject);
+ get_min_max_time(m_iobject, m_schemas[0].first, m_min_time, m_max_time);
+}
+
+bool AbcNurbsReader::valid() const
+{
+ if (m_schemas.empty()) {
+ return false;
+ }
+
+ std::vector<std::pair<INuPatchSchema, IObject>>::const_iterator it;
+ for (it = m_schemas.begin(); it != m_schemas.end(); ++it) {
+ const INuPatchSchema &schema = it->first;
+
+ if (!schema.valid()) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static bool set_knots(const FloatArraySamplePtr &knots, float *&nu_knots)
+{
+ if (!knots || knots->size() == 0) {
+ return false;
+ }
+
+ /* Skip first and last knots, as they are used for padding. */
+ const size_t num_knots = knots->size() - 2;
+ nu_knots = static_cast<float *>(MEM_callocN(num_knots * sizeof(float), "abc_setsplineknotsu"));
+
+ for (size_t i = 0; i < num_knots; i++) {
+ nu_knots[i] = (*knots)[i + 1];
+ }
+
+ return true;
+}
+
+void AbcNurbsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
+{
+ Curve *cu = static_cast<Curve *>(BKE_curve_add(bmain, "abc_curve", OB_SURF));
+ cu->actvert = CU_ACT_NONE;
+
+ std::vector<std::pair<INuPatchSchema, IObject>>::iterator it;
+
+ for (it = m_schemas.begin(); it != m_schemas.end(); ++it) {
+ Nurb *nu = static_cast<Nurb *>(MEM_callocN(sizeof(Nurb), "abc_getnurb"));
+ nu->flag = CU_SMOOTH;
+ nu->type = CU_NURBS;
+ nu->resolu = cu->resolu;
+ nu->resolv = cu->resolv;
+
+ const INuPatchSchema &schema = it->first;
+ INuPatchSchema::Sample smp;
+ try {
+ smp = schema.getValue(sample_sel);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ printf("Alembic: error reading nurbs sample for '%s/%s' at time %f: %s\n",
+ m_iobject.getFullName().c_str(),
+ schema.getName().c_str(),
+ sample_sel.getRequestedTime(),
+ ex.what());
+ return;
+ }
+
+ nu->orderu = smp.getUOrder() - 1;
+ nu->orderv = smp.getVOrder() - 1;
+ nu->pntsu = smp.getNumU();
+ nu->pntsv = smp.getNumV();
+
+ /* Read positions and weights. */
+
+ const P3fArraySamplePtr positions = smp.getPositions();
+ const FloatArraySamplePtr weights = smp.getPositionWeights();
+
+ const size_t num_points = positions->size();
+
+ nu->bp = static_cast<BPoint *>(MEM_callocN(num_points * sizeof(BPoint), "abc_setsplinetype"));
+
+ BPoint *bp = nu->bp;
+ float posw_in = 1.0f;
+
+ for (int i = 0; i < num_points; i++, bp++) {
+ const Imath::V3f &pos_in = (*positions)[i];
+
+ if (weights) {
+ posw_in = (*weights)[i];
+ }
+
+ copy_zup_from_yup(bp->vec, pos_in.getValue());
+ bp->vec[3] = posw_in;
+ bp->f1 = SELECT;
+ bp->radius = 1.0f;
+ bp->weight = 1.0f;
+ }
+
+ /* Read knots. */
+
+ if (!set_knots(smp.getUKnot(), nu->knotsu)) {
+ BKE_nurb_knot_calc_u(nu);
+ }
+
+ if (!set_knots(smp.getVKnot(), nu->knotsv)) {
+ BKE_nurb_knot_calc_v(nu);
+ }
+
+ /* Read flags. */
+
+ ICompoundProperty user_props = schema.getUserProperties();
+
+ if (has_property(user_props, "enpoint_u")) {
+ nu->flagu |= CU_NURB_ENDPOINT;
+ }
+
+ if (has_property(user_props, "enpoint_v")) {
+ nu->flagv |= CU_NURB_ENDPOINT;
+ }
+
+ if (has_property(user_props, "cyclic_u")) {
+ nu->flagu |= CU_NURB_CYCLIC;
+ }
+
+ if (has_property(user_props, "cyclic_v")) {
+ nu->flagv |= CU_NURB_CYCLIC;
+ }
+
+ BLI_addtail(BKE_curve_nurbs_get(cu), nu);
+ }
+
+ BLI_strncpy(cu->id.name + 2, m_data_name.c_str(), m_data_name.size() + 1);
+
+ m_object = BKE_object_add_only_object(bmain, OB_SURF, m_object_name.c_str());
+ m_object->data = cu;
+}
+
+void AbcNurbsReader::getNurbsPatches(const IObject &obj)
+{
+ if (!obj.valid()) {
+ return;
+ }
+
+ const int num_children = obj.getNumChildren();
+
+ if (num_children == 0) {
+ INuPatch abc_nurb(obj, kWrapExisting);
+ INuPatchSchema schem = abc_nurb.getSchema();
+ m_schemas.push_back(std::pair<INuPatchSchema, IObject>(schem, obj));
+ return;
+ }
+
+ for (int i = 0; i < num_children; i++) {
+ bool ok = true;
+ IObject child(obj, obj.getChildHeader(i).getName());
+
+ if (!m_name.empty() && child.valid() && !begins_with(child.getFullName(), m_name)) {
+ ok = false;
+ }
+
+ if (!child.valid()) {
+ continue;
+ }
+
+ const MetaData &md = child.getMetaData();
+
+ if (INuPatch::matches(md) && ok) {
+ INuPatch abc_nurb(child, kWrapExisting);
+ INuPatchSchema schem = abc_nurb.getSchema();
+ m_schemas.push_back(std::pair<INuPatchSchema, IObject>(schem, child));
+ }
+
+ getNurbsPatches(child);
+ }
+}
diff --git a/source/blender/io/alembic/intern/abc_reader_nurbs.h b/source/blender/io/alembic/intern/abc_reader_nurbs.h
new file mode 100644
index 00000000000..f4284c136fb
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_nurbs.h
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_READER_NURBS_H__
+#define __ABC_READER_NURBS_H__
+
+#include "abc_reader_object.h"
+
+class AbcNurbsReader : public AbcObjectReader {
+ std::vector<std::pair<Alembic::AbcGeom::INuPatchSchema, Alembic::Abc::IObject>> m_schemas;
+
+ public:
+ AbcNurbsReader(const Alembic::Abc::IObject &object, ImportSettings &settings);
+
+ bool valid() const;
+
+ void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel);
+
+ private:
+ void getNurbsPatches(const Alembic::Abc::IObject &obj);
+};
+
+#endif /* __ABC_READER_NURBS_H__ */
diff --git a/source/blender/io/alembic/intern/abc_reader_object.cc b/source/blender/io/alembic/intern/abc_reader_object.cc
new file mode 100644
index 00000000000..3e7f87d78cc
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_object.cc
@@ -0,0 +1,333 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_reader_object.h"
+#include "abc_util.h"
+
+extern "C" {
+#include "DNA_cachefile_types.h"
+#include "DNA_constraint_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_space_types.h" /* for FILE_MAX */
+
+#include "BKE_constraint.h"
+#include "BKE_lib_id.h"
+#include "BKE_modifier.h"
+#include "BKE_object.h"
+
+#include "BLI_utildefines.h"
+#include "BLI_listbase.h"
+#include "BLI_math_geom.h"
+#include "BLI_string.h"
+}
+
+using Alembic::AbcGeom::IObject;
+using Alembic::AbcGeom::IXform;
+using Alembic::AbcGeom::IXformSchema;
+
+AbcObjectReader::AbcObjectReader(const IObject &object, ImportSettings &settings)
+ : m_name(""),
+ m_object_name(""),
+ m_data_name(""),
+ m_object(NULL),
+ m_iobject(object),
+ m_settings(&settings),
+ m_min_time(std::numeric_limits<chrono_t>::max()),
+ m_max_time(std::numeric_limits<chrono_t>::min()),
+ m_refcount(0),
+ parent_reader(NULL)
+{
+ m_name = object.getFullName();
+ std::vector<std::string> parts;
+ split(m_name, '/', parts);
+
+ if (parts.size() >= 2) {
+ m_object_name = parts[parts.size() - 2];
+ m_data_name = parts[parts.size() - 1];
+ }
+ else {
+ m_object_name = m_data_name = parts[parts.size() - 1];
+ }
+
+ determine_inherits_xform();
+}
+
+/* Determine whether we can inherit our parent's XForm */
+void AbcObjectReader::determine_inherits_xform()
+{
+ m_inherits_xform = false;
+
+ IXform ixform = xform();
+ if (!ixform) {
+ return;
+ }
+
+ const IXformSchema &schema(ixform.getSchema());
+ if (!schema.valid()) {
+ std::cerr << "Alembic object " << ixform.getFullName() << " has an invalid schema."
+ << std::endl;
+ return;
+ }
+
+ m_inherits_xform = schema.getInheritsXforms();
+
+ IObject ixform_parent = ixform.getParent();
+ if (!ixform_parent.getParent()) {
+ /* The archive top object certainly is not a transform itself, so handle
+ * it as "no parent". */
+ m_inherits_xform = false;
+ }
+ else {
+ m_inherits_xform = ixform_parent && m_inherits_xform;
+ }
+}
+
+AbcObjectReader::~AbcObjectReader()
+{
+}
+
+const IObject &AbcObjectReader::iobject() const
+{
+ return m_iobject;
+}
+
+Object *AbcObjectReader::object() const
+{
+ return m_object;
+}
+
+void AbcObjectReader::object(Object *ob)
+{
+ m_object = ob;
+}
+
+static Imath::M44d blend_matrices(const Imath::M44d &m0, const Imath::M44d &m1, const float weight)
+{
+ float mat0[4][4], mat1[4][4], ret[4][4];
+
+ /* Cannot use Imath::M44d::getValue() since this returns a pointer to
+ * doubles and interp_m4_m4m4 expects pointers to floats. So need to convert
+ * the matrices manually.
+ */
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ mat0[i][j] = static_cast<float>(m0[i][j]);
+ }
+ }
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ mat1[i][j] = static_cast<float>(m1[i][j]);
+ }
+ }
+
+ interp_m4_m4m4(ret, mat0, mat1, weight);
+
+ Imath::M44d m;
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ m[i][j] = ret[i][j];
+ }
+ }
+
+ return m;
+}
+
+Imath::M44d get_matrix(const IXformSchema &schema, const float time)
+{
+ Alembic::AbcGeom::index_t i0, i1;
+ Alembic::AbcGeom::XformSample s0, s1;
+
+ const float weight = get_weight_and_index(
+ time, schema.getTimeSampling(), schema.getNumSamples(), i0, i1);
+
+ schema.get(s0, Alembic::AbcGeom::ISampleSelector(i0));
+
+ if (i0 != i1) {
+ schema.get(s1, Alembic::AbcGeom::ISampleSelector(i1));
+ return blend_matrices(s0.getMatrix(), s1.getMatrix(), weight);
+ }
+
+ return s0.getMatrix();
+}
+
+struct Mesh *AbcObjectReader::read_mesh(struct Mesh *existing_mesh,
+ const Alembic::Abc::ISampleSelector &UNUSED(sample_sel),
+ int UNUSED(read_flag),
+ const char **UNUSED(err_str))
+{
+ return existing_mesh;
+}
+
+bool AbcObjectReader::topology_changed(Mesh * /*existing_mesh*/,
+ const Alembic::Abc::ISampleSelector & /*sample_sel*/)
+{
+ /* The default implementation of read_mesh() just returns the original mesh, so never changes the
+ * topology. */
+ return false;
+}
+
+void AbcObjectReader::setupObjectTransform(const float time)
+{
+ bool is_constant = false;
+ float transform_from_alembic[4][4];
+
+ /* If the parent is a camera, apply the inverse rotation to make up for the from-Maya rotation.
+ * This assumes that the parent object also was imported from Alembic. */
+ if (m_object->parent != nullptr && m_object->parent->type == OB_CAMERA) {
+ axis_angle_to_mat4_single(m_object->parentinv, 'X', -M_PI_2);
+ }
+
+ this->read_matrix(transform_from_alembic, time, m_settings->scale, is_constant);
+
+ /* Apply the matrix to the object. */
+ BKE_object_apply_mat4(m_object, transform_from_alembic, true, false);
+ BKE_object_to_mat4(m_object, m_object->obmat);
+
+ if (!is_constant) {
+ bConstraint *con = BKE_constraint_add_for_object(
+ m_object, NULL, CONSTRAINT_TYPE_TRANSFORM_CACHE);
+ bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data);
+ BLI_strncpy(data->object_path, m_iobject.getFullName().c_str(), FILE_MAX);
+
+ data->cache_file = m_settings->cache_file;
+ id_us_plus(&data->cache_file->id);
+ }
+}
+
+Alembic::AbcGeom::IXform AbcObjectReader::xform()
+{
+ /* Check that we have an empty object (locator, bone head/tail...). */
+ if (IXform::matches(m_iobject.getMetaData())) {
+ try {
+ return IXform(m_iobject, Alembic::AbcGeom::kWrapExisting);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ printf("Alembic: error reading object transform for '%s': %s\n",
+ m_iobject.getFullName().c_str(),
+ ex.what());
+ return IXform();
+ }
+ }
+
+ /* Check that we have an object with actual data, in which case the
+ * parent Alembic object should contain the transform. */
+ IObject abc_parent = m_iobject.getParent();
+
+ /* The archive's top object can be recognised by not having a parent. */
+ if (abc_parent.getParent() && IXform::matches(abc_parent.getMetaData())) {
+ try {
+ return IXform(abc_parent, Alembic::AbcGeom::kWrapExisting);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ printf("Alembic: error reading object transform for '%s': %s\n",
+ abc_parent.getFullName().c_str(),
+ ex.what());
+ return IXform();
+ }
+ }
+
+ /* This can happen in certain cases. For example, MeshLab exports
+ * point clouds without parent XForm. */
+ return IXform();
+}
+
+void AbcObjectReader::read_matrix(float r_mat[4][4] /* local matrix */,
+ const float time,
+ const float scale,
+ bool &is_constant)
+{
+ IXform ixform = xform();
+ if (!ixform) {
+ unit_m4(r_mat);
+ is_constant = true;
+ return;
+ }
+
+ const IXformSchema &schema(ixform.getSchema());
+ if (!schema.valid()) {
+ std::cerr << "Alembic object " << ixform.getFullName() << " has an invalid schema."
+ << std::endl;
+ return;
+ }
+
+ const Imath::M44d matrix = get_matrix(schema, time);
+ convert_matrix_datatype(matrix, r_mat);
+ copy_m44_axis_swap(r_mat, r_mat, ABC_ZUP_FROM_YUP);
+
+ /* Convert from Maya to Blender camera orientation. Children of this camera
+ * will have the opposite transform as their Parent Inverse matrix.
+ * See AbcObjectReader::setupObjectTransform(). */
+ if (m_object->type == OB_CAMERA) {
+ float camera_rotation[4][4];
+ axis_angle_to_mat4_single(camera_rotation, 'X', M_PI_2);
+ mul_m4_m4m4(r_mat, r_mat, camera_rotation);
+ }
+
+ if (!m_inherits_xform) {
+ /* Only apply scaling to root objects, parenting will propagate it. */
+ float scale_mat[4][4];
+ scale_m4_fl(scale_mat, scale);
+ mul_m4_m4m4(r_mat, scale_mat, r_mat);
+ }
+
+ is_constant = schema.isConstant();
+}
+
+void AbcObjectReader::addCacheModifier()
+{
+ ModifierData *md = modifier_new(eModifierType_MeshSequenceCache);
+ BLI_addtail(&m_object->modifiers, md);
+
+ MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md);
+
+ mcmd->cache_file = m_settings->cache_file;
+ id_us_plus(&mcmd->cache_file->id);
+
+ BLI_strncpy(mcmd->object_path, m_iobject.getFullName().c_str(), FILE_MAX);
+}
+
+chrono_t AbcObjectReader::minTime() const
+{
+ return m_min_time;
+}
+
+chrono_t AbcObjectReader::maxTime() const
+{
+ return m_max_time;
+}
+
+int AbcObjectReader::refcount() const
+{
+ return m_refcount;
+}
+
+void AbcObjectReader::incref()
+{
+ m_refcount++;
+}
+
+void AbcObjectReader::decref()
+{
+ m_refcount--;
+ BLI_assert(m_refcount >= 0);
+}
diff --git a/source/blender/io/alembic/intern/abc_reader_object.h b/source/blender/io/alembic/intern/abc_reader_object.h
new file mode 100644
index 00000000000..94923df2df9
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_object.h
@@ -0,0 +1,171 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_READER_OBJECT_H__
+#define __ABC_READER_OBJECT_H__
+
+#include <Alembic/Abc/All.h>
+#include <Alembic/AbcGeom/All.h>
+
+extern "C" {
+#include "DNA_ID.h"
+}
+
+struct CacheFile;
+struct Main;
+struct Mesh;
+struct Object;
+
+using Alembic::AbcCoreAbstract::chrono_t;
+
+struct ImportSettings {
+ bool do_convert_mat;
+ float conversion_mat[4][4];
+
+ int from_up;
+ int from_forward;
+ float scale;
+ bool is_sequence;
+ bool set_frame_range;
+
+ /* Length and frame offset of file sequences. */
+ int sequence_len;
+ int sequence_offset;
+
+ /* From MeshSeqCacheModifierData.read_flag */
+ int read_flag;
+
+ bool validate_meshes;
+
+ CacheFile *cache_file;
+
+ ImportSettings()
+ : do_convert_mat(false),
+ from_up(0),
+ from_forward(0),
+ scale(1.0f),
+ is_sequence(false),
+ set_frame_range(false),
+ sequence_len(1),
+ sequence_offset(0),
+ read_flag(0),
+ validate_meshes(false),
+ cache_file(NULL)
+ {
+ }
+};
+
+template<typename Schema> static bool has_animations(Schema &schema, ImportSettings *settings)
+{
+ return settings->is_sequence || !schema.isConstant();
+}
+
+class AbcObjectReader {
+ protected:
+ std::string m_name;
+ std::string m_object_name;
+ std::string m_data_name;
+ Object *m_object;
+ Alembic::Abc::IObject m_iobject;
+
+ ImportSettings *m_settings;
+
+ chrono_t m_min_time;
+ chrono_t m_max_time;
+
+ /* Use reference counting since the same reader may be used by multiple
+ * modifiers and/or constraints. */
+ int m_refcount;
+
+ bool m_inherits_xform;
+
+ public:
+ AbcObjectReader *parent_reader;
+
+ public:
+ explicit AbcObjectReader(const Alembic::Abc::IObject &object, ImportSettings &settings);
+
+ virtual ~AbcObjectReader();
+
+ const Alembic::Abc::IObject &iobject() const;
+
+ typedef std::vector<AbcObjectReader *> ptr_vector;
+
+ /**
+ * Returns the transform of this object. This can be the Alembic object
+ * itself (in case of an Empty) or it can be the parent Alembic object.
+ */
+ virtual Alembic::AbcGeom::IXform xform();
+
+ Object *object() const;
+ void object(Object *ob);
+
+ const std::string &name() const
+ {
+ return m_name;
+ }
+ const std::string &object_name() const
+ {
+ return m_object_name;
+ }
+ const std::string &data_name() const
+ {
+ return m_data_name;
+ }
+ bool inherits_xform() const
+ {
+ return m_inherits_xform;
+ }
+
+ virtual bool valid() const = 0;
+ virtual bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const = 0;
+
+ virtual void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) = 0;
+
+ virtual struct Mesh *read_mesh(struct Mesh *mesh,
+ const Alembic::Abc::ISampleSelector &sample_sel,
+ int read_flag,
+ const char **err_str);
+ virtual bool topology_changed(Mesh *existing_mesh,
+ const Alembic::Abc::ISampleSelector &sample_sel);
+
+ /** Reads the object matrix and sets up an object transform if animated. */
+ void setupObjectTransform(const float time);
+
+ void addCacheModifier();
+
+ chrono_t minTime() const;
+ chrono_t maxTime() const;
+
+ int refcount() const;
+ void incref();
+ void decref();
+
+ void read_matrix(float r_mat[4][4], const float time, const float scale, bool &is_constant);
+
+ protected:
+ void determine_inherits_xform();
+};
+
+Imath::M44d get_matrix(const Alembic::AbcGeom::IXformSchema &schema, const float time);
+
+#endif /* __ABC_READER_OBJECT_H__ */
diff --git a/source/blender/io/alembic/intern/abc_reader_points.cc b/source/blender/io/alembic/intern/abc_reader_points.cc
new file mode 100644
index 00000000000..e4dc345f868
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_points.cc
@@ -0,0 +1,157 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_reader_points.h"
+#include "abc_reader_mesh.h"
+#include "abc_reader_transform.h"
+#include "abc_util.h"
+
+extern "C" {
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_customdata.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+}
+
+using Alembic::AbcGeom::kWrapExisting;
+using Alembic::AbcGeom::N3fArraySamplePtr;
+using Alembic::AbcGeom::P3fArraySamplePtr;
+
+using Alembic::AbcGeom::ICompoundProperty;
+using Alembic::AbcGeom::IN3fArrayProperty;
+using Alembic::AbcGeom::IPoints;
+using Alembic::AbcGeom::IPointsSchema;
+using Alembic::AbcGeom::ISampleSelector;
+
+AbcPointsReader::AbcPointsReader(const Alembic::Abc::IObject &object, ImportSettings &settings)
+ : AbcObjectReader(object, settings)
+{
+ IPoints ipoints(m_iobject, kWrapExisting);
+ m_schema = ipoints.getSchema();
+ get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time);
+}
+
+bool AbcPointsReader::valid() const
+{
+ return m_schema.valid();
+}
+
+bool AbcPointsReader::accepts_object_type(
+ const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const
+{
+ if (!Alembic::AbcGeom::IPoints::matches(alembic_header)) {
+ *err_str =
+ "Object type mismatch, Alembic object path pointed to Points when importing, but not any "
+ "more.";
+ return false;
+ }
+
+ if (ob->type != OB_MESH) {
+ *err_str = "Object type mismatch, Alembic object path points to Points.";
+ return false;
+ }
+
+ return true;
+}
+
+void AbcPointsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
+{
+ Mesh *mesh = BKE_mesh_add(bmain, m_data_name.c_str());
+ Mesh *read_mesh = this->read_mesh(mesh, sample_sel, 0, NULL);
+
+ if (read_mesh != mesh) {
+ BKE_mesh_nomain_to_mesh(read_mesh, mesh, m_object, &CD_MASK_MESH, true);
+ }
+
+ if (m_settings->validate_meshes) {
+ BKE_mesh_validate(mesh, false, false);
+ }
+
+ m_object = BKE_object_add_only_object(bmain, OB_MESH, m_object_name.c_str());
+ m_object->data = mesh;
+
+ if (has_animations(m_schema, m_settings)) {
+ addCacheModifier();
+ }
+}
+
+void read_points_sample(const IPointsSchema &schema,
+ const ISampleSelector &selector,
+ CDStreamConfig &config)
+{
+ Alembic::AbcGeom::IPointsSchema::Sample sample = schema.getValue(selector);
+
+ const P3fArraySamplePtr &positions = sample.getPositions();
+
+ ICompoundProperty prop = schema.getArbGeomParams();
+ N3fArraySamplePtr vnormals;
+
+ if (has_property(prop, "N")) {
+ const Alembic::Util::uint32_t itime = static_cast<Alembic::Util::uint32_t>(
+ selector.getRequestedTime());
+ const IN3fArrayProperty &normals_prop = IN3fArrayProperty(prop, "N", itime);
+
+ if (normals_prop) {
+ vnormals = normals_prop.getValue(selector);
+ }
+ }
+
+ read_mverts(config.mvert, positions, vnormals);
+}
+
+struct Mesh *AbcPointsReader::read_mesh(struct Mesh *existing_mesh,
+ const ISampleSelector &sample_sel,
+ int /*read_flag*/,
+ const char **err_str)
+{
+ IPointsSchema::Sample sample;
+ try {
+ sample = m_schema.getValue(sample_sel);
+ }
+ catch (Alembic::Util::Exception &ex) {
+ *err_str = "Error reading points sample; more detail on the console";
+ printf("Alembic: error reading points sample for '%s/%s' at time %f: %s\n",
+ m_iobject.getFullName().c_str(),
+ m_schema.getName().c_str(),
+ sample_sel.getRequestedTime(),
+ ex.what());
+ return existing_mesh;
+ }
+
+ const P3fArraySamplePtr &positions = sample.getPositions();
+
+ Mesh *new_mesh = NULL;
+
+ if (existing_mesh->totvert != positions->size()) {
+ new_mesh = BKE_mesh_new_nomain(positions->size(), 0, 0, 0, 0);
+ }
+
+ CDStreamConfig config = get_config(new_mesh ? new_mesh : existing_mesh);
+ read_points_sample(m_schema, sample_sel, config);
+
+ return new_mesh ? new_mesh : existing_mesh;
+}
diff --git a/source/blender/io/alembic/intern/abc_reader_points.h b/source/blender/io/alembic/intern/abc_reader_points.h
new file mode 100644
index 00000000000..31ad6c4589b
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_points.h
@@ -0,0 +1,54 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_READER_POINTS_H__
+#define __ABC_READER_POINTS_H__
+
+#include "abc_reader_object.h"
+#include "abc_customdata.h"
+
+class AbcPointsReader : public AbcObjectReader {
+ Alembic::AbcGeom::IPointsSchema m_schema;
+ Alembic::AbcGeom::IPointsSchema::Sample m_sample;
+
+ public:
+ AbcPointsReader(const Alembic::Abc::IObject &object, ImportSettings &settings);
+
+ bool valid() const;
+ bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const;
+
+ void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel);
+
+ struct Mesh *read_mesh(struct Mesh *existing_mesh,
+ const Alembic::Abc::ISampleSelector &sample_sel,
+ int read_flag,
+ const char **err_str);
+};
+
+void read_points_sample(const Alembic::AbcGeom::IPointsSchema &schema,
+ const Alembic::AbcGeom::ISampleSelector &selector,
+ CDStreamConfig &config);
+
+#endif /* __ABC_READER_POINTS_H__ */
diff --git a/source/blender/io/alembic/intern/abc_reader_transform.cc b/source/blender/io/alembic/intern/abc_reader_transform.cc
new file mode 100644
index 00000000000..ce569a9ccb5
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_transform.cc
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_reader_transform.h"
+#include "abc_util.h"
+
+extern "C" {
+#include "DNA_object_types.h"
+
+#include "BLI_utildefines.h"
+
+#include "BKE_object.h"
+}
+
+using Alembic::Abc::ISampleSelector;
+
+AbcEmptyReader::AbcEmptyReader(const Alembic::Abc::IObject &object, ImportSettings &settings)
+ : AbcObjectReader(object, settings)
+{
+ /* Empties have no data. It makes the import of Alembic files easier to
+ * understand when we name the empty after its name in Alembic. */
+ m_object_name = object.getName();
+
+ Alembic::AbcGeom::IXform xform(object, Alembic::AbcGeom::kWrapExisting);
+ m_schema = xform.getSchema();
+
+ get_min_max_time(m_iobject, m_schema, m_min_time, m_max_time);
+}
+
+bool AbcEmptyReader::valid() const
+{
+ return m_schema.valid();
+}
+
+bool AbcEmptyReader::accepts_object_type(
+ const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const
+{
+ if (!Alembic::AbcGeom::IXform::matches(alembic_header)) {
+ *err_str =
+ "Object type mismatch, Alembic object path pointed to XForm when importing, but not any "
+ "more.";
+ return false;
+ }
+
+ if (ob->type != OB_EMPTY) {
+ *err_str = "Object type mismatch, Alembic object path points to XForm.";
+ return false;
+ }
+
+ return true;
+}
+
+void AbcEmptyReader::readObjectData(Main *bmain, const ISampleSelector &UNUSED(sample_sel))
+{
+ m_object = BKE_object_add_only_object(bmain, OB_EMPTY, m_object_name.c_str());
+ m_object->data = NULL;
+}
diff --git a/source/blender/io/alembic/intern/abc_reader_transform.h b/source/blender/io/alembic/intern/abc_reader_transform.h
new file mode 100644
index 00000000000..6b4d23c1884
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_reader_transform.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_READER_TRANSFORM_H__
+#define __ABC_READER_TRANSFORM_H__
+
+#include "abc_reader_object.h"
+
+#include <Alembic/AbcGeom/All.h>
+
+class AbcEmptyReader : public AbcObjectReader {
+ Alembic::AbcGeom::IXformSchema m_schema;
+
+ public:
+ AbcEmptyReader(const Alembic::Abc::IObject &object, ImportSettings &settings);
+
+ bool valid() const;
+ bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
+ const Object *const ob,
+ const char **err_str) const;
+
+ void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel);
+};
+
+#endif /* __ABC_READER_TRANSFORM_H__ */
diff --git a/source/blender/io/alembic/intern/abc_util.cc b/source/blender/io/alembic/intern/abc_util.cc
new file mode 100644
index 00000000000..b26ef8b3b76
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_util.cc
@@ -0,0 +1,393 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_util.h"
+
+#include "abc_reader_camera.h"
+#include "abc_reader_curves.h"
+#include "abc_reader_mesh.h"
+#include "abc_reader_nurbs.h"
+#include "abc_reader_points.h"
+#include "abc_reader_transform.h"
+
+#include <Alembic/AbcMaterial/IMaterial.h>
+
+#include <algorithm>
+
+extern "C" {
+#include "DNA_object_types.h"
+
+#include "BLI_math_geom.h"
+
+#include "PIL_time.h"
+}
+
+std::string get_id_name(const Object *const ob)
+{
+ if (!ob) {
+ return "";
+ }
+
+ return get_id_name(&ob->id);
+}
+
+std::string get_id_name(const ID *const id)
+{
+ std::string name(id->name + 2);
+ std::replace(name.begin(), name.end(), ' ', '_');
+ std::replace(name.begin(), name.end(), '.', '_');
+ std::replace(name.begin(), name.end(), ':', '_');
+
+ return name;
+}
+
+/**
+ * \brief get_object_dag_path_name returns the name under which the object
+ * will be exported in the Alembic file. It is of the form
+ * "[../grandparent/]parent/object" if dupli_parent is NULL, or
+ * "dupli_parent/[../grandparent/]parent/object" otherwise.
+ * \param ob:
+ * \param dupli_parent:
+ * \return
+ */
+std::string get_object_dag_path_name(const Object *const ob, Object *dupli_parent)
+{
+ std::string name = get_id_name(ob);
+
+ Object *p = ob->parent;
+
+ while (p) {
+ name = get_id_name(p) + "/" + name;
+ p = p->parent;
+ }
+
+ if (dupli_parent && (ob != dupli_parent)) {
+ name = get_id_name(dupli_parent) + "/" + name;
+ }
+
+ return name;
+}
+
+Imath::M44d convert_matrix_datatype(float mat[4][4])
+{
+ Imath::M44d m;
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ m[i][j] = mat[i][j];
+ }
+ }
+
+ return m;
+}
+
+void convert_matrix_datatype(const Imath::M44d &xform, float r_mat[4][4])
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ r_mat[i][j] = static_cast<float>(xform[i][j]);
+ }
+ }
+}
+
+void split(const std::string &s, const char delim, std::vector<std::string> &tokens)
+{
+ tokens.clear();
+
+ std::stringstream ss(s);
+ std::string item;
+
+ while (std::getline(ss, item, delim)) {
+ if (!item.empty()) {
+ tokens.push_back(item);
+ }
+ }
+}
+
+void create_swapped_rotation_matrix(float rot_x_mat[3][3],
+ float rot_y_mat[3][3],
+ float rot_z_mat[3][3],
+ const float euler[3],
+ AbcAxisSwapMode mode)
+{
+ const float rx = euler[0];
+ float ry;
+ float rz;
+
+ /* Apply transformation */
+ switch (mode) {
+ case ABC_ZUP_FROM_YUP:
+ ry = -euler[2];
+ rz = euler[1];
+ break;
+ case ABC_YUP_FROM_ZUP:
+ ry = euler[2];
+ rz = -euler[1];
+ break;
+ default:
+ ry = 0.0f;
+ rz = 0.0f;
+ BLI_assert(false);
+ break;
+ }
+
+ unit_m3(rot_x_mat);
+ unit_m3(rot_y_mat);
+ unit_m3(rot_z_mat);
+
+ rot_x_mat[1][1] = cos(rx);
+ rot_x_mat[2][1] = -sin(rx);
+ rot_x_mat[1][2] = sin(rx);
+ rot_x_mat[2][2] = cos(rx);
+
+ rot_y_mat[2][2] = cos(ry);
+ rot_y_mat[0][2] = -sin(ry);
+ rot_y_mat[2][0] = sin(ry);
+ rot_y_mat[0][0] = cos(ry);
+
+ rot_z_mat[0][0] = cos(rz);
+ rot_z_mat[1][0] = -sin(rz);
+ rot_z_mat[0][1] = sin(rz);
+ rot_z_mat[1][1] = cos(rz);
+}
+
+/* Convert matrix from Z=up to Y=up or vice versa.
+ * Use yup_mat = zup_mat for in-place conversion. */
+void copy_m44_axis_swap(float dst_mat[4][4], float src_mat[4][4], AbcAxisSwapMode mode)
+{
+ float dst_rot[3][3], src_rot[3][3], dst_scale_mat[4][4];
+ float rot_x_mat[3][3], rot_y_mat[3][3], rot_z_mat[3][3];
+ float src_trans[3], dst_scale[3], src_scale[3], euler[3];
+
+ zero_v3(src_trans);
+ zero_v3(dst_scale);
+ zero_v3(src_scale);
+ zero_v3(euler);
+ unit_m3(src_rot);
+ unit_m3(dst_rot);
+ unit_m4(dst_scale_mat);
+
+ /* TODO(Sybren): This code assumes there is no sheer component and no
+ * homogeneous scaling component, which is not always true when writing
+ * non-hierarchical (e.g. flat) objects (e.g. when parent has non-uniform
+ * scale and the child rotates). This is currently not taken into account
+ * when axis-swapping. */
+
+ /* Extract translation, rotation, and scale form matrix. */
+ mat4_to_loc_rot_size(src_trans, src_rot, src_scale, src_mat);
+
+ /* Get euler angles from rotation matrix. */
+ mat3_to_eulO(euler, ROT_MODE_XZY, src_rot);
+
+ /* Create X, Y, Z rotation matrices from euler angles. */
+ create_swapped_rotation_matrix(rot_x_mat, rot_y_mat, rot_z_mat, euler, mode);
+
+ /* Concatenate rotation matrices. */
+ mul_m3_m3m3(dst_rot, dst_rot, rot_z_mat);
+ mul_m3_m3m3(dst_rot, dst_rot, rot_y_mat);
+ mul_m3_m3m3(dst_rot, dst_rot, rot_x_mat);
+
+ mat3_to_eulO(euler, ROT_MODE_XZY, dst_rot);
+
+ /* Start construction of dst_mat from rotation matrix */
+ unit_m4(dst_mat);
+ copy_m4_m3(dst_mat, dst_rot);
+
+ /* Apply translation */
+ switch (mode) {
+ case ABC_ZUP_FROM_YUP:
+ copy_zup_from_yup(dst_mat[3], src_trans);
+ break;
+ case ABC_YUP_FROM_ZUP:
+ copy_yup_from_zup(dst_mat[3], src_trans);
+ break;
+ default:
+ BLI_assert(false);
+ }
+
+ /* Apply scale matrix. Swaps y and z, but does not
+ * negate like translation does. */
+ dst_scale[0] = src_scale[0];
+ dst_scale[1] = src_scale[2];
+ dst_scale[2] = src_scale[1];
+
+ size_to_mat4(dst_scale_mat, dst_scale);
+ mul_m4_m4m4(dst_mat, dst_mat, dst_scale_mat);
+}
+
+/* Recompute transform matrix of object in new coordinate system
+ * (from Z-Up to Y-Up). */
+void create_transform_matrix(Object *obj,
+ float r_yup_mat[4][4],
+ AbcMatrixMode mode,
+ Object *proxy_from)
+{
+ float zup_mat[4][4];
+
+ /* get local or world matrix. */
+ if (mode == ABC_MATRIX_LOCAL && obj->parent) {
+ /* Note that this produces another matrix than the local matrix, due to
+ * constraints and modifiers as well as the obj->parentinv matrix. */
+ invert_m4_m4(obj->parent->imat, obj->parent->obmat);
+ mul_m4_m4m4(zup_mat, obj->parent->imat, obj->obmat);
+ }
+ else {
+ copy_m4_m4(zup_mat, obj->obmat);
+ }
+
+ if (proxy_from) {
+ mul_m4_m4m4(zup_mat, proxy_from->obmat, zup_mat);
+ }
+
+ copy_m44_axis_swap(r_yup_mat, zup_mat, ABC_YUP_FROM_ZUP);
+}
+
+bool has_property(const Alembic::Abc::ICompoundProperty &prop, const std::string &name)
+{
+ if (!prop.valid()) {
+ return false;
+ }
+
+ return prop.getPropertyHeader(name) != NULL;
+}
+
+typedef std::pair<Alembic::AbcCoreAbstract::index_t, float> index_time_pair_t;
+
+float get_weight_and_index(float time,
+ const Alembic::AbcCoreAbstract::TimeSamplingPtr &time_sampling,
+ int samples_number,
+ Alembic::AbcGeom::index_t &i0,
+ Alembic::AbcGeom::index_t &i1)
+{
+ samples_number = std::max(samples_number, 1);
+
+ index_time_pair_t t0 = time_sampling->getFloorIndex(time, samples_number);
+ i0 = i1 = t0.first;
+
+ if (samples_number == 1 || (fabs(time - t0.second) < 0.0001f)) {
+ return 0.0f;
+ }
+
+ index_time_pair_t t1 = time_sampling->getCeilIndex(time, samples_number);
+ i1 = t1.first;
+
+ if (i0 == i1) {
+ return 0.0f;
+ }
+
+ const float bias = (time - t0.second) / (t1.second - t0.second);
+
+ if (fabs(1.0f - bias) < 0.0001f) {
+ i0 = i1;
+ return 0.0f;
+ }
+
+ return bias;
+}
+
+//#define USE_NURBS
+
+AbcObjectReader *create_reader(const Alembic::AbcGeom::IObject &object, ImportSettings &settings)
+{
+ AbcObjectReader *reader = NULL;
+
+ const Alembic::AbcGeom::MetaData &md = object.getMetaData();
+
+ if (Alembic::AbcGeom::IXform::matches(md)) {
+ reader = new AbcEmptyReader(object, settings);
+ }
+ else if (Alembic::AbcGeom::IPolyMesh::matches(md)) {
+ reader = new AbcMeshReader(object, settings);
+ }
+ else if (Alembic::AbcGeom::ISubD::matches(md)) {
+ reader = new AbcSubDReader(object, settings);
+ }
+ else if (Alembic::AbcGeom::INuPatch::matches(md)) {
+#ifdef USE_NURBS
+ /* TODO(kevin): importing cyclic NURBS from other software crashes
+ * at the moment. This is due to the fact that NURBS in other
+ * software have duplicated points which causes buffer overflows in
+ * Blender. Need to figure out exactly how these points are
+ * duplicated, in all cases (cyclic U, cyclic V, and cyclic UV).
+ * Until this is fixed, disabling NURBS reading. */
+ reader = new AbcNurbsReader(child, settings);
+#endif
+ }
+ else if (Alembic::AbcGeom::ICamera::matches(md)) {
+ reader = new AbcCameraReader(object, settings);
+ }
+ else if (Alembic::AbcGeom::IPoints::matches(md)) {
+ reader = new AbcPointsReader(object, settings);
+ }
+ else if (Alembic::AbcMaterial::IMaterial::matches(md)) {
+ /* Pass for now. */
+ }
+ else if (Alembic::AbcGeom::ILight::matches(md)) {
+ /* Pass for now. */
+ }
+ else if (Alembic::AbcGeom::IFaceSet::matches(md)) {
+ /* Pass, those are handled in the mesh reader. */
+ }
+ else if (Alembic::AbcGeom::ICurves::matches(md)) {
+ reader = new AbcCurveReader(object, settings);
+ }
+ else {
+ std::cerr << "Alembic: unknown how to handle objects of schema '" << md.get("schemaObjTitle")
+ << "', skipping object '" << object.getFullName() << "'" << std::endl;
+ }
+
+ return reader;
+}
+
+/* ********************** */
+
+ScopeTimer::ScopeTimer(const char *message)
+ : m_message(message), m_start(PIL_check_seconds_timer())
+{
+}
+
+ScopeTimer::~ScopeTimer()
+{
+ fprintf(stderr, "%s: %fs\n", m_message, PIL_check_seconds_timer() - m_start);
+}
+
+/* ********************** */
+
+std::string SimpleLogger::str() const
+{
+ return m_stream.str();
+}
+
+void SimpleLogger::clear()
+{
+ m_stream.clear();
+ m_stream.str("");
+}
+
+std::ostringstream &SimpleLogger::stream()
+{
+ return m_stream;
+}
+
+std::ostream &operator<<(std::ostream &os, const SimpleLogger &logger)
+{
+ os << logger.str();
+ return os;
+}
diff --git a/source/blender/io/alembic/intern/abc_util.h b/source/blender/io/alembic/intern/abc_util.h
new file mode 100644
index 00000000000..0b3462c2132
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_util.h
@@ -0,0 +1,236 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_UTIL_H__
+#define __ABC_UTIL_H__
+
+#include <Alembic/Abc/All.h>
+#include <Alembic/AbcGeom/All.h>
+
+#ifdef _MSC_VER
+# define ABC_INLINE static __forceinline
+#else
+# define ABC_INLINE static inline
+#endif
+
+/**
+ * \brief The CacheReader struct is only used for anonymous pointers,
+ * to interface between C and C++ code. This library only creates
+ * pointers to AbcObjectReader (or subclasses thereof).
+ */
+struct CacheReader {
+ int unused;
+};
+
+using Alembic::Abc::chrono_t;
+
+class AbcObjectReader;
+struct ImportSettings;
+
+struct ID;
+struct Object;
+
+std::string get_id_name(const ID *const id);
+std::string get_id_name(const Object *const ob);
+std::string get_object_dag_path_name(const Object *const ob, Object *dupli_parent);
+
+/* Convert from float to Alembic matrix representations. Does NOT convert from Z-up to Y-up. */
+Imath::M44d convert_matrix_datatype(float mat[4][4]);
+/* Convert from Alembic to float matrix representations. Does NOT convert from Y-up to Z-up. */
+void convert_matrix_datatype(const Imath::M44d &xform, float r_mat[4][4]);
+
+typedef enum {
+ ABC_MATRIX_WORLD = 1,
+ ABC_MATRIX_LOCAL = 2,
+} AbcMatrixMode;
+void create_transform_matrix(Object *obj,
+ float r_transform_mat[4][4],
+ AbcMatrixMode mode,
+ Object *proxy_from);
+
+void split(const std::string &s, const char delim, std::vector<std::string> &tokens);
+
+template<class TContainer> bool begins_with(const TContainer &input, const TContainer &match)
+{
+ return input.size() >= match.size() && std::equal(match.begin(), match.end(), input.begin());
+}
+
+template<typename Schema>
+void get_min_max_time_ex(const Schema &schema, chrono_t &min, chrono_t &max)
+{
+ const Alembic::Abc::TimeSamplingPtr &time_samp = schema.getTimeSampling();
+
+ if (!schema.isConstant()) {
+ const size_t num_samps = schema.getNumSamples();
+
+ if (num_samps > 0) {
+ const chrono_t min_time = time_samp->getSampleTime(0);
+ min = std::min(min, min_time);
+
+ const chrono_t max_time = time_samp->getSampleTime(num_samps - 1);
+ max = std::max(max, max_time);
+ }
+ }
+}
+
+template<typename Schema>
+void get_min_max_time(const Alembic::AbcGeom::IObject &object,
+ const Schema &schema,
+ chrono_t &min,
+ chrono_t &max)
+{
+ get_min_max_time_ex(schema, min, max);
+
+ const Alembic::AbcGeom::IObject &parent = object.getParent();
+ if (parent.valid() && Alembic::AbcGeom::IXform::matches(parent.getMetaData())) {
+ Alembic::AbcGeom::IXform xform(parent, Alembic::AbcGeom::kWrapExisting);
+ get_min_max_time_ex(xform.getSchema(), min, max);
+ }
+}
+
+bool has_property(const Alembic::Abc::ICompoundProperty &prop, const std::string &name);
+
+float get_weight_and_index(float time,
+ const Alembic::AbcCoreAbstract::TimeSamplingPtr &time_sampling,
+ int samples_number,
+ Alembic::AbcGeom::index_t &i0,
+ Alembic::AbcGeom::index_t &i1);
+
+AbcObjectReader *create_reader(const Alembic::AbcGeom::IObject &object, ImportSettings &settings);
+
+/* ************************** */
+
+/* TODO(kevin): for now keeping these transformations hardcoded to make sure
+ * everything works properly, and also because Alembic is almost exclusively
+ * used in Y-up software, but eventually they'll be set by the user in the UI
+ * like other importers/exporters do, to support other axis. */
+
+/* Copy from Y-up to Z-up. */
+
+ABC_INLINE void copy_zup_from_yup(float zup[3], const float yup[3])
+{
+ const float old_yup1 = yup[1]; /* in case zup == yup */
+ zup[0] = yup[0];
+ zup[1] = -yup[2];
+ zup[2] = old_yup1;
+}
+
+ABC_INLINE void copy_zup_from_yup(short zup[3], const short yup[3])
+{
+ const short old_yup1 = yup[1]; /* in case zup == yup */
+ zup[0] = yup[0];
+ zup[1] = -yup[2];
+ zup[2] = old_yup1;
+}
+
+/* Copy from Z-up to Y-up. */
+
+ABC_INLINE void copy_yup_from_zup(float yup[3], const float zup[3])
+{
+ const float old_zup1 = zup[1]; /* in case yup == zup */
+ yup[0] = zup[0];
+ yup[1] = zup[2];
+ yup[2] = -old_zup1;
+}
+
+ABC_INLINE void copy_yup_from_zup(short yup[3], const short zup[3])
+{
+ const short old_zup1 = zup[1]; /* in case yup == zup */
+ yup[0] = zup[0];
+ yup[1] = zup[2];
+ yup[2] = -old_zup1;
+}
+
+/* Names are given in (dst, src) order, just like
+ * the parameters of copy_m44_axis_swap() */
+typedef enum {
+ ABC_ZUP_FROM_YUP = 1,
+ ABC_YUP_FROM_ZUP = 2,
+} AbcAxisSwapMode;
+
+/* Create a rotation matrix for each axis from euler angles.
+ * Euler angles are swapped to change coordinate system. */
+void create_swapped_rotation_matrix(float rot_x_mat[3][3],
+ float rot_y_mat[3][3],
+ float rot_z_mat[3][3],
+ const float euler[3],
+ AbcAxisSwapMode mode);
+
+void copy_m44_axis_swap(float dst_mat[4][4], float src_mat[4][4], AbcAxisSwapMode mode);
+
+/* *************************** */
+
+#undef ABC_DEBUG_TIME
+
+class ScopeTimer {
+ const char *m_message;
+ double m_start;
+
+ public:
+ ScopeTimer(const char *message);
+ ~ScopeTimer();
+};
+
+#ifdef ABC_DEBUG_TIME
+# define SCOPE_TIMER(message) ScopeTimer prof(message)
+#else
+# define SCOPE_TIMER(message)
+#endif
+
+/* *************************** */
+
+/**
+ * Utility class whose purpose is to more easily log related information. An
+ * instance of the SimpleLogger can be created in any context, and will hold a
+ * copy of all the strings passed to its output stream.
+ *
+ * Different instances of the class may be accessed from different threads,
+ * although accessing the same instance from different threads will lead to race
+ * conditions.
+ */
+class SimpleLogger {
+ std::ostringstream m_stream;
+
+ public:
+ /**
+ * Return a copy of the string contained in the SimpleLogger's stream.
+ */
+ std::string str() const;
+
+ /**
+ * Remove the bits set on the SimpleLogger's stream and clear its string.
+ */
+ void clear();
+
+ /**
+ * Return a reference to the SimpleLogger's stream, in order to e.g. push
+ * content into it.
+ */
+ std::ostringstream &stream();
+};
+
+#define ABC_LOG(logger) logger.stream()
+
+/**
+ * Pass the content of the logger's stream to the specified std::ostream.
+ */
+std::ostream &operator<<(std::ostream &os, const SimpleLogger &logger);
+
+#endif /* __ABC_UTIL_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_archive.cc b/source/blender/io/alembic/intern/abc_writer_archive.cc
new file mode 100644
index 00000000000..af18d480a18
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_archive.cc
@@ -0,0 +1,114 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_archive.h"
+extern "C" {
+#include "BKE_blender_version.h"
+
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "DNA_scene_types.h"
+}
+
+#ifdef WIN32
+# include "utfconv.h"
+#endif
+
+#include <fstream>
+
+using Alembic::Abc::ErrorHandler;
+using Alembic::Abc::kWrapExisting;
+using Alembic::Abc::OArchive;
+
+/* This kinda duplicates CreateArchiveWithInfo, but Alembic does not seem to
+ * have a version supporting streams. */
+static OArchive create_archive(std::ostream *ostream,
+ const std::string &filename,
+ const std::string &scene_name,
+ double scene_fps,
+ bool ogawa)
+{
+ Alembic::Abc::MetaData abc_metadata;
+
+ abc_metadata.set(Alembic::Abc::kApplicationNameKey, "Blender");
+ abc_metadata.set(Alembic::Abc::kUserDescriptionKey, scene_name);
+ abc_metadata.set("blender_version", versionstr);
+ abc_metadata.set("FramesPerTimeUnit", std::to_string(scene_fps));
+
+ time_t raw_time;
+ time(&raw_time);
+ char buffer[128];
+
+#if defined _WIN32 || defined _WIN64
+ ctime_s(buffer, 128, &raw_time);
+#else
+ ctime_r(&raw_time, buffer);
+#endif
+
+ const std::size_t buffer_len = strlen(buffer);
+ if (buffer_len > 0 && buffer[buffer_len - 1] == '\n') {
+ buffer[buffer_len - 1] = '\0';
+ }
+
+ abc_metadata.set(Alembic::Abc::kDateWrittenKey, buffer);
+
+ ErrorHandler::Policy policy = ErrorHandler::kThrowPolicy;
+
+#ifdef WITH_ALEMBIC_HDF5
+ if (!ogawa) {
+ return OArchive(Alembic::AbcCoreHDF5::WriteArchive(), filename, abc_metadata, policy);
+ }
+#else
+ static_cast<void>(filename);
+ static_cast<void>(ogawa);
+#endif
+
+ Alembic::AbcCoreOgawa::WriteArchive archive_writer;
+ return OArchive(archive_writer(ostream, abc_metadata), kWrapExisting, policy);
+}
+
+ArchiveWriter::ArchiveWriter(const char *filename,
+ const std::string &abc_scene_name,
+ const Scene *scene,
+ bool do_ogawa)
+{
+ /* Use stream to support unicode character paths on Windows. */
+ if (do_ogawa) {
+#ifdef WIN32
+ UTF16_ENCODE(filename);
+ std::wstring wstr(filename_16);
+ m_outfile.open(wstr.c_str(), std::ios::out | std::ios::binary);
+ UTF16_UN_ENCODE(filename);
+#else
+ m_outfile.open(filename, std::ios::out | std::ios::binary);
+#endif
+ }
+
+ m_archive = create_archive(&m_outfile, filename, abc_scene_name, FPS, do_ogawa);
+}
+
+OArchive &ArchiveWriter::archive()
+{
+ return m_archive;
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_archive.h b/source/blender/io/alembic/intern/abc_writer_archive.h
new file mode 100644
index 00000000000..e261e60990a
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_archive.h
@@ -0,0 +1,58 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_ARCHIVE_H__
+#define __ABC_WRITER_ARCHIVE_H__
+
+#include <Alembic/Abc/All.h>
+
+#ifdef WITH_ALEMBIC_HDF5
+# include <Alembic/AbcCoreHDF5/All.h>
+#endif
+
+#include <Alembic/AbcCoreOgawa/All.h>
+
+#include <fstream>
+
+struct Main;
+struct Scene;
+
+/* Wrappers around input and output archives. The goal is to be able to use
+ * streams so that unicode paths work on Windows (T49112), and to make sure that
+ * the stream objects remain valid as long as the archives are open.
+ */
+
+class ArchiveWriter {
+ std::ofstream m_outfile;
+ Alembic::Abc::OArchive m_archive;
+
+ public:
+ ArchiveWriter(const char *filename,
+ const std::string &abc_scene_name,
+ const Scene *scene,
+ bool do_ogawa);
+
+ Alembic::Abc::OArchive &archive();
+};
+
+#endif /* __ABC_WRITER_ARCHIVE_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_camera.cc b/source/blender/io/alembic/intern/abc_writer_camera.cc
new file mode 100644
index 00000000000..e705e5ba911
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_camera.cc
@@ -0,0 +1,81 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_camera.h"
+#include "abc_writer_transform.h"
+
+extern "C" {
+#include "DNA_camera_types.h"
+#include "DNA_object_types.h"
+}
+
+using Alembic::AbcGeom::OCamera;
+using Alembic::AbcGeom::OFloatProperty;
+
+AbcCameraWriter::AbcCameraWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings)
+ : AbcObjectWriter(ob, time_sampling, settings, parent)
+{
+ OCamera camera(parent->alembicXform(), m_name, m_time_sampling);
+ m_camera_schema = camera.getSchema();
+
+ m_custom_data_container = m_camera_schema.getUserProperties();
+ m_stereo_distance = OFloatProperty(m_custom_data_container, "stereoDistance", m_time_sampling);
+ m_eye_separation = OFloatProperty(m_custom_data_container, "eyeSeparation", m_time_sampling);
+}
+
+void AbcCameraWriter::do_write()
+{
+ Camera *cam = static_cast<Camera *>(m_object->data);
+
+ m_stereo_distance.set(cam->stereo.convergence_distance);
+ m_eye_separation.set(cam->stereo.interocular_distance);
+
+ const double apperture_x = cam->sensor_x / 10.0;
+ const double apperture_y = cam->sensor_y / 10.0;
+ const double film_aspect = apperture_x / apperture_y;
+
+ m_camera_sample.setFocalLength(cam->lens);
+ m_camera_sample.setHorizontalAperture(apperture_x);
+ m_camera_sample.setVerticalAperture(apperture_y);
+ m_camera_sample.setHorizontalFilmOffset(apperture_x * cam->shiftx);
+ m_camera_sample.setVerticalFilmOffset(apperture_y * cam->shifty * film_aspect);
+ m_camera_sample.setNearClippingPlane(cam->clip_start);
+ m_camera_sample.setFarClippingPlane(cam->clip_end);
+
+ if (cam->dof.focus_object) {
+ Imath::V3f v(m_object->loc[0] - cam->dof.focus_object->loc[0],
+ m_object->loc[1] - cam->dof.focus_object->loc[1],
+ m_object->loc[2] - cam->dof.focus_object->loc[2]);
+ m_camera_sample.setFocusDistance(v.length());
+ }
+ else {
+ m_camera_sample.setFocusDistance(cam->dof.focus_distance);
+ }
+
+ /* Blender camera does not have an fstop param, so try to find a custom prop
+ * instead. */
+ m_camera_sample.setFStop(cam->dof.aperture_fstop);
+
+ m_camera_sample.setLensSqueezeRatio(1.0);
+ m_camera_schema.set(m_camera_sample);
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_camera.h b/source/blender/io/alembic/intern/abc_writer_camera.h
new file mode 100644
index 00000000000..3b515911a48
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_camera.h
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_CAMERA_H__
+#define __ABC_WRITER_CAMERA_H__
+
+#include "abc_writer_object.h"
+
+/* ************************************************************************** */
+
+class AbcCameraWriter : public AbcObjectWriter {
+ Alembic::AbcGeom::OCameraSchema m_camera_schema;
+ Alembic::AbcGeom::CameraSample m_camera_sample;
+ Alembic::AbcGeom::OCompoundProperty m_custom_data_container;
+ Alembic::AbcGeom::OFloatProperty m_stereo_distance;
+ Alembic::AbcGeom::OFloatProperty m_eye_separation;
+
+ public:
+ AbcCameraWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ private:
+ virtual void do_write();
+};
+
+#endif /* __ABC_WRITER_CAMERA_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_curves.cc b/source/blender/io/alembic/intern/abc_writer_curves.cc
new file mode 100644
index 00000000000..3ab9b365a72
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_curves.cc
@@ -0,0 +1,189 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_curves.h"
+#include "abc_reader_curves.h"
+#include "abc_writer_transform.h"
+
+extern "C" {
+#include "DNA_curve_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_curve.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+}
+
+using Alembic::AbcGeom::OCompoundProperty;
+using Alembic::AbcGeom::OCurves;
+using Alembic::AbcGeom::OCurvesSchema;
+using Alembic::AbcGeom::OInt16Property;
+using Alembic::AbcGeom::ON3fGeomParam;
+using Alembic::AbcGeom::OV2fGeomParam;
+
+AbcCurveWriter::AbcCurveWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings)
+ : AbcObjectWriter(ob, time_sampling, settings, parent)
+{
+ OCurves curves(parent->alembicXform(), m_name, m_time_sampling);
+ m_schema = curves.getSchema();
+
+ Curve *cu = static_cast<Curve *>(m_object->data);
+ OCompoundProperty user_props = m_schema.getUserProperties();
+ OInt16Property user_prop_resolu(user_props, ABC_CURVE_RESOLUTION_U_PROPNAME);
+ user_prop_resolu.set(cu->resolu);
+}
+
+void AbcCurveWriter::do_write()
+{
+ Curve *curve = static_cast<Curve *>(m_object->data);
+
+ std::vector<Imath::V3f> verts;
+ std::vector<int32_t> vert_counts;
+ std::vector<float> widths;
+ std::vector<float> weights;
+ std::vector<float> knots;
+ std::vector<uint8_t> orders;
+ Imath::V3f temp_vert;
+
+ Alembic::AbcGeom::BasisType curve_basis;
+ Alembic::AbcGeom::CurveType curve_type;
+ Alembic::AbcGeom::CurvePeriodicity periodicity;
+
+ Nurb *nurbs = static_cast<Nurb *>(curve->nurb.first);
+ for (; nurbs; nurbs = nurbs->next) {
+ if (nurbs->bp) {
+ curve_basis = Alembic::AbcGeom::kNoBasis;
+ curve_type = Alembic::AbcGeom::kVariableOrder;
+
+ const int totpoint = nurbs->pntsu * nurbs->pntsv;
+
+ const BPoint *point = nurbs->bp;
+
+ for (int i = 0; i < totpoint; i++, point++) {
+ copy_yup_from_zup(temp_vert.getValue(), point->vec);
+ verts.push_back(temp_vert);
+ weights.push_back(point->vec[3]);
+ widths.push_back(point->radius);
+ }
+ }
+ else if (nurbs->bezt) {
+ curve_basis = Alembic::AbcGeom::kBezierBasis;
+ curve_type = Alembic::AbcGeom::kCubic;
+
+ const int totpoint = nurbs->pntsu;
+
+ const BezTriple *bezier = nurbs->bezt;
+
+ /* TODO(kevin): store info about handles, Alembic doesn't have this. */
+ for (int i = 0; i < totpoint; i++, bezier++) {
+ copy_yup_from_zup(temp_vert.getValue(), bezier->vec[1]);
+ verts.push_back(temp_vert);
+ widths.push_back(bezier->radius);
+ }
+ }
+
+ if ((nurbs->flagu & CU_NURB_ENDPOINT) != 0) {
+ periodicity = Alembic::AbcGeom::kNonPeriodic;
+ }
+ else if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) {
+ periodicity = Alembic::AbcGeom::kPeriodic;
+
+ /* Duplicate the start points to indicate that the curve is actually
+ * cyclic since other software need those.
+ */
+
+ for (int i = 0; i < nurbs->orderu; i++) {
+ verts.push_back(verts[i]);
+ }
+ }
+
+ if (nurbs->knotsu != NULL) {
+ const size_t num_knots = KNOTSU(nurbs);
+
+ /* Add an extra knot at the beginning and end of the array since most apps
+ * require/expect them. */
+ knots.resize(num_knots + 2);
+
+ for (int i = 0; i < num_knots; i++) {
+ knots[i + 1] = nurbs->knotsu[i];
+ }
+
+ if ((nurbs->flagu & CU_NURB_CYCLIC) != 0) {
+ knots[0] = nurbs->knotsu[0];
+ knots[num_knots - 1] = nurbs->knotsu[num_knots - 1];
+ }
+ else {
+ knots[0] = (2.0f * nurbs->knotsu[0] - nurbs->knotsu[1]);
+ knots[num_knots - 1] = (2.0f * nurbs->knotsu[num_knots - 1] -
+ nurbs->knotsu[num_knots - 2]);
+ }
+ }
+
+ orders.push_back(nurbs->orderu);
+ vert_counts.push_back(verts.size());
+ }
+
+ Alembic::AbcGeom::OFloatGeomParam::Sample width_sample;
+ width_sample.setVals(widths);
+
+ m_sample = OCurvesSchema::Sample(verts,
+ vert_counts,
+ curve_type,
+ periodicity,
+ width_sample,
+ OV2fGeomParam::Sample(), /* UVs */
+ ON3fGeomParam::Sample(), /* normals */
+ curve_basis,
+ weights,
+ orders,
+ knots);
+
+ m_sample.setSelfBounds(bounds());
+ m_schema.set(m_sample);
+}
+
+AbcCurveMeshWriter::AbcCurveMeshWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings)
+ : AbcGenericMeshWriter(ob, parent, time_sampling, settings)
+{
+}
+
+Mesh *AbcCurveMeshWriter::getEvaluatedMesh(Scene * /*scene_eval*/,
+ Object *ob_eval,
+ bool &r_needsfree)
+{
+ Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
+ if (mesh_eval != NULL) {
+ /* Mesh_eval only exists when generative modifiers are in use. */
+ r_needsfree = false;
+ return mesh_eval;
+ }
+
+ r_needsfree = true;
+ return BKE_mesh_new_nomain_from_curve(ob_eval);
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_curves.h b/source/blender/io/alembic/intern/abc_writer_curves.h
new file mode 100644
index 00000000000..d6d8c0a7f11
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_curves.h
@@ -0,0 +1,55 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_CURVES_H__
+#define __ABC_WRITER_CURVES_H__
+
+#include "abc_writer_object.h"
+#include "abc_writer_mesh.h"
+
+class AbcCurveWriter : public AbcObjectWriter {
+ Alembic::AbcGeom::OCurvesSchema m_schema;
+ Alembic::AbcGeom::OCurvesSchema::Sample m_sample;
+
+ public:
+ AbcCurveWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ protected:
+ void do_write();
+};
+
+class AbcCurveMeshWriter : public AbcGenericMeshWriter {
+ public:
+ AbcCurveMeshWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ protected:
+ Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree);
+};
+
+#endif /* __ABC_WRITER_CURVES_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_hair.cc b/source/blender/io/alembic/intern/abc_writer_hair.cc
new file mode 100644
index 00000000000..bbba03ed7f4
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_hair.cc
@@ -0,0 +1,292 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_hair.h"
+#include "abc_writer_transform.h"
+#include "abc_util.h"
+
+#include <cstdio>
+
+extern "C" {
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_math_geom.h"
+
+#include "BKE_mesh.h"
+#include "BKE_mesh_runtime.h"
+#include "BKE_particle.h"
+}
+
+using Alembic::Abc::P3fArraySamplePtr;
+
+using Alembic::AbcGeom::OCurves;
+using Alembic::AbcGeom::OCurvesSchema;
+using Alembic::AbcGeom::ON3fGeomParam;
+using Alembic::AbcGeom::OV2fGeomParam;
+
+/* ************************************************************************** */
+
+AbcHairWriter::AbcHairWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings,
+ ParticleSystem *psys)
+ : AbcObjectWriter(ob, time_sampling, settings, parent), m_uv_warning_shown(false)
+{
+ m_psys = psys;
+
+ OCurves curves(parent->alembicXform(), psys->name, m_time_sampling);
+ m_schema = curves.getSchema();
+}
+
+void AbcHairWriter::do_write()
+{
+ if (!m_psys) {
+ return;
+ }
+ Mesh *mesh = mesh_get_eval_final(
+ m_settings.depsgraph, m_settings.scene, m_object, &CD_MASK_MESH);
+ BKE_mesh_tessface_ensure(mesh);
+
+ std::vector<Imath::V3f> verts;
+ std::vector<int32_t> hvertices;
+ std::vector<Imath::V2f> uv_values;
+ std::vector<Imath::V3f> norm_values;
+
+ if (m_psys->pathcache) {
+ ParticleSettings *part = m_psys->part;
+ bool export_children = m_settings.export_child_hairs && m_psys->childcache &&
+ part->childtype != 0;
+
+ if (!export_children || part->draw & PART_DRAW_PARENT) {
+ write_hair_sample(mesh, part, verts, norm_values, uv_values, hvertices);
+ }
+
+ if (export_children) {
+ write_hair_child_sample(mesh, part, verts, norm_values, uv_values, hvertices);
+ }
+ }
+
+ Alembic::Abc::P3fArraySample iPos(verts);
+ m_sample = OCurvesSchema::Sample(iPos, hvertices);
+ m_sample.setBasis(Alembic::AbcGeom::kNoBasis);
+ m_sample.setType(Alembic::AbcGeom::kLinear);
+ m_sample.setWrap(Alembic::AbcGeom::kNonPeriodic);
+
+ if (!uv_values.empty()) {
+ OV2fGeomParam::Sample uv_smp;
+ uv_smp.setVals(uv_values);
+ m_sample.setUVs(uv_smp);
+ }
+
+ if (!norm_values.empty()) {
+ ON3fGeomParam::Sample norm_smp;
+ norm_smp.setVals(norm_values);
+ m_sample.setNormals(norm_smp);
+ }
+
+ m_sample.setSelfBounds(bounds());
+ m_schema.set(m_sample);
+}
+
+void AbcHairWriter::write_hair_sample(Mesh *mesh,
+ ParticleSettings *part,
+ std::vector<Imath::V3f> &verts,
+ std::vector<Imath::V3f> &norm_values,
+ std::vector<Imath::V2f> &uv_values,
+ std::vector<int32_t> &hvertices)
+{
+ /* Get untransformed vertices, there's a xform under the hair. */
+ float inv_mat[4][4];
+ invert_m4_m4_safe(inv_mat, m_object->obmat);
+
+ MTFace *mtface = mesh->mtface;
+ MFace *mface = mesh->mface;
+ MVert *mverts = mesh->mvert;
+
+ if ((!mtface || !mface) && !m_uv_warning_shown) {
+ std::fprintf(stderr,
+ "Warning, no UV set found for underlying geometry of %s.\n",
+ m_object->id.name + 2);
+ m_uv_warning_shown = true;
+ }
+
+ ParticleData *pa = m_psys->particles;
+ int k;
+
+ ParticleCacheKey **cache = m_psys->pathcache;
+ ParticleCacheKey *path;
+ float normal[3];
+ Imath::V3f tmp_nor;
+
+ for (int p = 0; p < m_psys->totpart; p++, pa++) {
+ /* underlying info for faces-only emission */
+ path = cache[p];
+
+ /* Write UV and normal vectors */
+ if (part->from == PART_FROM_FACE && mtface) {
+ const int num = pa->num_dmcache >= 0 ? pa->num_dmcache : pa->num;
+
+ if (num < mesh->totface) {
+ /* TODO(Sybren): check whether the NULL check here and if(mface) are actually required */
+ MFace *face = mface == NULL ? NULL : &mface[num];
+ MTFace *tface = mtface + num;
+
+ if (mface) {
+ float r_uv[2], mapfw[4], vec[3];
+
+ psys_interpolate_uvs(tface, face->v4, pa->fuv, r_uv);
+ uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1]));
+
+ psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, normal, NULL, NULL, NULL);
+
+ copy_yup_from_zup(tmp_nor.getValue(), normal);
+ norm_values.push_back(tmp_nor);
+ }
+ }
+ else {
+ std::fprintf(stderr, "Particle to faces overflow (%d/%d)\n", num, mesh->totface);
+ }
+ }
+ else if (part->from == PART_FROM_VERT && mtface) {
+ /* vertex id */
+ const int num = (pa->num_dmcache >= 0) ? pa->num_dmcache : pa->num;
+
+ /* iterate over all faces to find a corresponding underlying UV */
+ for (int n = 0; n < mesh->totface; n++) {
+ MFace *face = &mface[n];
+ MTFace *tface = mtface + n;
+ unsigned int vtx[4];
+ vtx[0] = face->v1;
+ vtx[1] = face->v2;
+ vtx[2] = face->v3;
+ vtx[3] = face->v4;
+ bool found = false;
+
+ for (int o = 0; o < 4; o++) {
+ if (o > 2 && vtx[o] == 0) {
+ break;
+ }
+
+ if (vtx[o] == num) {
+ uv_values.push_back(Imath::V2f(tface->uv[o][0], tface->uv[o][1]));
+
+ MVert *mv = mverts + vtx[o];
+
+ normal_short_to_float_v3(normal, mv->no);
+ copy_yup_from_zup(tmp_nor.getValue(), normal);
+ norm_values.push_back(tmp_nor);
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ break;
+ }
+ }
+ }
+
+ int steps = path->segments + 1;
+ hvertices.push_back(steps);
+
+ for (k = 0; k < steps; k++, path++) {
+ float vert[3];
+ copy_v3_v3(vert, path->co);
+ mul_m4_v3(inv_mat, vert);
+
+ /* Convert Z-up to Y-up. */
+ verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1]));
+ }
+ }
+}
+
+void AbcHairWriter::write_hair_child_sample(Mesh *mesh,
+ ParticleSettings *part,
+ std::vector<Imath::V3f> &verts,
+ std::vector<Imath::V3f> &norm_values,
+ std::vector<Imath::V2f> &uv_values,
+ std::vector<int32_t> &hvertices)
+{
+ /* Get untransformed vertices, there's a xform under the hair. */
+ float inv_mat[4][4];
+ invert_m4_m4_safe(inv_mat, m_object->obmat);
+
+ MTFace *mtface = mesh->mtface;
+ MVert *mverts = mesh->mvert;
+
+ ParticleCacheKey **cache = m_psys->childcache;
+ ParticleCacheKey *path;
+
+ ChildParticle *pc = m_psys->child;
+
+ for (int p = 0; p < m_psys->totchild; p++, pc++) {
+ path = cache[p];
+
+ if (part->from == PART_FROM_FACE && part->childtype != PART_CHILD_PARTICLES && mtface) {
+ const int num = pc->num;
+ if (num < 0) {
+ ABC_LOG(m_settings.logger)
+ << "Warning, child particle of hair system " << m_psys->name
+ << " has unknown face index of geometry of " << (m_object->id.name + 2)
+ << ", skipping child hair." << std::endl;
+ continue;
+ }
+
+ MFace *face = &mesh->mface[num];
+ MTFace *tface = mtface + num;
+
+ float r_uv[2], tmpnor[3], mapfw[4], vec[3];
+
+ psys_interpolate_uvs(tface, face->v4, pc->fuv, r_uv);
+ uv_values.push_back(Imath::V2f(r_uv[0], r_uv[1]));
+
+ psys_interpolate_face(mverts, face, tface, NULL, mapfw, vec, tmpnor, NULL, NULL, NULL);
+
+ /* Convert Z-up to Y-up. */
+ norm_values.push_back(Imath::V3f(tmpnor[0], tmpnor[2], -tmpnor[1]));
+ }
+ else {
+ if (uv_values.size()) {
+ uv_values.push_back(uv_values[pc->parent]);
+ }
+ if (norm_values.size()) {
+ norm_values.push_back(norm_values[pc->parent]);
+ }
+ }
+
+ int steps = path->segments + 1;
+ hvertices.push_back(steps);
+
+ for (int k = 0; k < steps; k++) {
+ float vert[3];
+ copy_v3_v3(vert, path->co);
+ mul_m4_v3(inv_mat, vert);
+
+ /* Convert Z-up to Y-up. */
+ verts.push_back(Imath::V3f(vert[0], vert[2], -vert[1]));
+
+ path++;
+ }
+ }
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_hair.h b/source/blender/io/alembic/intern/abc_writer_hair.h
new file mode 100644
index 00000000000..67d1b7b3d23
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_hair.h
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_HAIR_H__
+#define __ABC_WRITER_HAIR_H__
+
+#include "abc_writer_object.h"
+
+struct ParticleSettings;
+struct ParticleSystem;
+
+class AbcHairWriter : public AbcObjectWriter {
+ ParticleSystem *m_psys;
+
+ Alembic::AbcGeom::OCurvesSchema m_schema;
+ Alembic::AbcGeom::OCurvesSchema::Sample m_sample;
+
+ bool m_uv_warning_shown;
+
+ public:
+ AbcHairWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings,
+ ParticleSystem *psys);
+
+ private:
+ virtual void do_write();
+
+ void write_hair_sample(struct Mesh *mesh,
+ ParticleSettings *part,
+ std::vector<Imath::V3f> &verts,
+ std::vector<Imath::V3f> &norm_values,
+ std::vector<Imath::V2f> &uv_values,
+ std::vector<int32_t> &hvertices);
+
+ void write_hair_child_sample(struct Mesh *mesh,
+ ParticleSettings *part,
+ std::vector<Imath::V3f> &verts,
+ std::vector<Imath::V3f> &norm_values,
+ std::vector<Imath::V2f> &uv_values,
+ std::vector<int32_t> &hvertices);
+};
+
+#endif /* __ABC_WRITER_HAIR_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_mball.cc b/source/blender/io/alembic/intern/abc_writer_mball.cc
new file mode 100644
index 00000000000..cc0775bd537
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_mball.cc
@@ -0,0 +1,97 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_mball.h"
+#include "abc_writer_mesh.h"
+
+extern "C" {
+#include "DNA_meta_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+
+#include "BKE_displist.h"
+#include "BKE_lib_id.h"
+#include "BKE_mball.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+
+#include "BLI_utildefines.h"
+}
+
+AbcMBallWriter::AbcMBallWriter(Main *bmain,
+ Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings)
+ : AbcGenericMeshWriter(ob, parent, time_sampling, settings), m_bmain(bmain)
+{
+ m_is_animated = isAnimated();
+}
+
+AbcMBallWriter::~AbcMBallWriter()
+{
+}
+
+bool AbcMBallWriter::isAnimated() const
+{
+ return true;
+}
+
+Mesh *AbcMBallWriter::getEvaluatedMesh(Scene * /*scene_eval*/, Object *ob_eval, bool &r_needsfree)
+{
+ Mesh *mesh_eval = BKE_object_get_evaluated_mesh(ob_eval);
+ if (mesh_eval != NULL) {
+ /* Mesh_eval only exists when generative modifiers are in use. */
+ r_needsfree = false;
+ return mesh_eval;
+ }
+ r_needsfree = true;
+
+ /* The approach below is copied from BKE_mesh_new_from_object() */
+ Mesh *tmpmesh = BKE_mesh_add(m_bmain, ((ID *)m_object->data)->name + 2);
+ BLI_assert(tmpmesh != NULL);
+
+ /* BKE_mesh_add gives us a user count we don't need */
+ id_us_min(&tmpmesh->id);
+
+ ListBase disp = {NULL, NULL};
+ /* TODO(sergey): This is gonna to work for until Depsgraph
+ * only contains for_render flag. As soon as CoW is
+ * implemented, this is to be rethought.
+ */
+ BKE_displist_make_mball_forRender(m_settings.depsgraph, m_settings.scene, m_object, &disp);
+ BKE_mesh_from_metaball(&disp, tmpmesh);
+ BKE_displist_free(&disp);
+
+ BKE_mesh_texspace_copy_from_object(tmpmesh, m_object);
+
+ return tmpmesh;
+}
+
+void AbcMBallWriter::freeEvaluatedMesh(struct Mesh *mesh)
+{
+ BKE_id_free(m_bmain, mesh);
+}
+
+bool AbcMBallWriter::isBasisBall(Scene *scene, Object *ob)
+{
+ Object *basis_ob = BKE_mball_basis_find(scene, ob);
+ return ob == basis_ob;
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_mball.h b/source/blender/io/alembic/intern/abc_writer_mball.h
new file mode 100644
index 00000000000..c752472c86d
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_mball.h
@@ -0,0 +1,56 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_MBALL_H__
+#define __ABC_WRITER_MBALL_H__
+
+#include "abc_writer_object.h"
+#include "abc_writer_mesh.h"
+
+struct Main;
+struct Object;
+
+/* AbcMBallWriter converts the metaballs to meshes at every frame,
+ * and defers to AbcGenericMeshWriter to perform the writing
+ * to the Alembic file. Only the basis balls are exported, as this
+ * results in the entire shape as one mesh. */
+class AbcMBallWriter : public AbcGenericMeshWriter {
+ Main *m_bmain;
+
+ public:
+ explicit AbcMBallWriter(Main *bmain,
+ Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ ~AbcMBallWriter();
+
+ static bool isBasisBall(Scene *scene, Object *ob);
+
+ protected:
+ Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override;
+ void freeEvaluatedMesh(struct Mesh *mesh) override;
+
+ private:
+ bool isAnimated() const override;
+};
+
+#endif /* __ABC_WRITER_MBALL_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_mesh.cc b/source/blender/io/alembic/intern/abc_writer_mesh.cc
new file mode 100644
index 00000000000..b55d2473f99
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_mesh.cc
@@ -0,0 +1,592 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_mesh.h"
+#include "abc_writer_transform.h"
+#include "abc_util.h"
+
+extern "C" {
+#include "DNA_material_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_fluidsim_types.h"
+
+#include "BKE_animsys.h"
+#include "BKE_key.h"
+#include "BKE_lib_id.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_runtime.h"
+#include "BKE_modifier.h"
+
+#include "bmesh.h"
+#include "bmesh_tools.h"
+
+#include "DEG_depsgraph_query.h"
+}
+
+using Alembic::Abc::FloatArraySample;
+using Alembic::Abc::Int32ArraySample;
+using Alembic::Abc::V2fArraySample;
+using Alembic::Abc::V3fArraySample;
+
+using Alembic::AbcGeom::kFacevaryingScope;
+using Alembic::AbcGeom::OBoolProperty;
+using Alembic::AbcGeom::OCompoundProperty;
+using Alembic::AbcGeom::OFaceSet;
+using Alembic::AbcGeom::OFaceSetSchema;
+using Alembic::AbcGeom::ON3fGeomParam;
+using Alembic::AbcGeom::OPolyMesh;
+using Alembic::AbcGeom::OPolyMeshSchema;
+using Alembic::AbcGeom::OSubD;
+using Alembic::AbcGeom::OSubDSchema;
+using Alembic::AbcGeom::OV2fGeomParam;
+using Alembic::AbcGeom::UInt32ArraySample;
+
+/* NOTE: Alembic's polygon winding order is clockwise, to match with Renderman. */
+
+static void get_vertices(struct Mesh *mesh, std::vector<Imath::V3f> &points)
+{
+ points.clear();
+ points.resize(mesh->totvert);
+
+ MVert *verts = mesh->mvert;
+
+ for (int i = 0, e = mesh->totvert; i < e; i++) {
+ copy_yup_from_zup(points[i].getValue(), verts[i].co);
+ }
+}
+
+static void get_topology(struct Mesh *mesh,
+ std::vector<int32_t> &poly_verts,
+ std::vector<int32_t> &loop_counts,
+ bool &r_has_flat_shaded_poly)
+{
+ const int num_poly = mesh->totpoly;
+ const int num_loops = mesh->totloop;
+ MLoop *mloop = mesh->mloop;
+ MPoly *mpoly = mesh->mpoly;
+ r_has_flat_shaded_poly = false;
+
+ poly_verts.clear();
+ loop_counts.clear();
+ poly_verts.reserve(num_loops);
+ loop_counts.reserve(num_poly);
+
+ /* NOTE: data needs to be written in the reverse order. */
+ for (int i = 0; i < num_poly; i++) {
+ MPoly &poly = mpoly[i];
+ loop_counts.push_back(poly.totloop);
+
+ r_has_flat_shaded_poly |= (poly.flag & ME_SMOOTH) == 0;
+
+ MLoop *loop = mloop + poly.loopstart + (poly.totloop - 1);
+
+ for (int j = 0; j < poly.totloop; j++, loop--) {
+ poly_verts.push_back(loop->v);
+ }
+ }
+}
+
+static void get_creases(struct Mesh *mesh,
+ std::vector<int32_t> &indices,
+ std::vector<int32_t> &lengths,
+ std::vector<float> &sharpnesses)
+{
+ const float factor = 1.0f / 255.0f;
+
+ indices.clear();
+ lengths.clear();
+ sharpnesses.clear();
+
+ MEdge *edge = mesh->medge;
+
+ for (int i = 0, e = mesh->totedge; i < e; i++) {
+ const float sharpness = static_cast<float>(edge[i].crease) * factor;
+
+ if (sharpness != 0.0f) {
+ indices.push_back(edge[i].v1);
+ indices.push_back(edge[i].v2);
+ sharpnesses.push_back(sharpness);
+ }
+ }
+
+ lengths.resize(sharpnesses.size(), 2);
+}
+
+static void get_loop_normals(struct Mesh *mesh,
+ std::vector<Imath::V3f> &normals,
+ bool has_flat_shaded_poly)
+{
+ normals.clear();
+
+ /* If all polygons are smooth shaded, and there are no custom normals, we don't need to export
+ * normals at all. This is also done by other software, see T71246. */
+ if (!has_flat_shaded_poly && !CustomData_has_layer(&mesh->ldata, CD_CUSTOMLOOPNORMAL)) {
+ return;
+ }
+
+ BKE_mesh_calc_normals_split(mesh);
+ const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL));
+ BLI_assert(lnors != NULL || !"BKE_mesh_calc_normals_split() should have computed CD_NORMAL");
+
+ normals.resize(mesh->totloop);
+
+ /* NOTE: data needs to be written in the reverse order. */
+ int abc_index = 0;
+ MPoly *mp = mesh->mpoly;
+ for (int i = 0, e = mesh->totpoly; i < e; i++, mp++) {
+ for (int j = mp->totloop - 1; j >= 0; j--, abc_index++) {
+ int blender_index = mp->loopstart + j;
+ copy_yup_from_zup(normals[abc_index].getValue(), lnors[blender_index]);
+ }
+ }
+}
+
+/* *************** Modifiers *************** */
+
+/* check if the mesh is a subsurf, ignoring disabled modifiers and
+ * displace if it's after subsurf. */
+static ModifierData *get_subsurf_modifier(Scene *scene, Object *ob)
+{
+ ModifierData *md = static_cast<ModifierData *>(ob->modifiers.last);
+
+ for (; md; md = md->prev) {
+ if (!modifier_isEnabled(scene, md, eModifierMode_Render)) {
+ continue;
+ }
+
+ if (md->type == eModifierType_Subsurf) {
+ SubsurfModifierData *smd = reinterpret_cast<SubsurfModifierData *>(md);
+
+ if (smd->subdivType == ME_CC_SUBSURF) {
+ return md;
+ }
+ }
+
+ /* mesh is not a subsurf. break */
+ if ((md->type != eModifierType_Displace) && (md->type != eModifierType_ParticleSystem)) {
+ return NULL;
+ }
+ }
+
+ return NULL;
+}
+
+static ModifierData *get_liquid_sim_modifier(Scene *scene, Object *ob)
+{
+ ModifierData *md = modifiers_findByType(ob, eModifierType_Fluidsim);
+
+ if (md && (modifier_isEnabled(scene, md, eModifierMode_Render))) {
+ FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md);
+
+ if (fsmd->fss && fsmd->fss->type == OB_FLUIDSIM_DOMAIN) {
+ return md;
+ }
+ }
+
+ return NULL;
+}
+
+/* ************************************************************************** */
+
+AbcGenericMeshWriter::AbcGenericMeshWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings)
+ : AbcObjectWriter(ob, time_sampling, settings, parent)
+{
+ m_is_animated = isAnimated();
+ m_subsurf_mod = NULL;
+ m_is_subd = false;
+
+ /* If the object is static, use the default static time sampling. */
+ if (!m_is_animated) {
+ time_sampling = 0;
+ }
+
+ if (!m_settings.apply_subdiv) {
+ m_subsurf_mod = get_subsurf_modifier(m_settings.scene, m_object);
+ m_is_subd = (m_subsurf_mod != NULL);
+ }
+
+ m_is_liquid = (get_liquid_sim_modifier(m_settings.scene, m_object) != NULL);
+
+ while (parent->alembicXform().getChildHeader(m_name)) {
+ m_name.append("_");
+ }
+
+ if (m_settings.use_subdiv_schema && m_is_subd) {
+ OSubD subd(parent->alembicXform(), m_name, m_time_sampling);
+ m_subdiv_schema = subd.getSchema();
+ }
+ else {
+ OPolyMesh mesh(parent->alembicXform(), m_name, m_time_sampling);
+ m_mesh_schema = mesh.getSchema();
+
+ OCompoundProperty typeContainer = m_mesh_schema.getUserProperties();
+ OBoolProperty type(typeContainer, "meshtype");
+ type.set(m_is_subd);
+ }
+}
+
+AbcGenericMeshWriter::~AbcGenericMeshWriter()
+{
+ if (m_subsurf_mod) {
+ m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary;
+ }
+}
+
+bool AbcGenericMeshWriter::isAnimated() const
+{
+ if (BKE_animdata_id_is_animated(static_cast<ID *>(m_object->data))) {
+ return true;
+ }
+ if (BKE_key_from_object(m_object) != NULL) {
+ return true;
+ }
+
+ /* Test modifiers. */
+ ModifierData *md = static_cast<ModifierData *>(m_object->modifiers.first);
+ while (md) {
+
+ if (md->type != eModifierType_Subsurf) {
+ return true;
+ }
+
+ md = md->next;
+ }
+
+ return false;
+}
+
+void AbcGenericMeshWriter::setIsAnimated(bool is_animated)
+{
+ m_is_animated = is_animated;
+}
+
+void AbcGenericMeshWriter::do_write()
+{
+ /* We have already stored a sample for this object. */
+ if (!m_first_frame && !m_is_animated) {
+ return;
+ }
+
+ bool needsfree;
+ struct Mesh *mesh = getFinalMesh(needsfree);
+
+ try {
+ if (m_settings.use_subdiv_schema && m_subdiv_schema.valid()) {
+ writeSubD(mesh);
+ }
+ else {
+ writeMesh(mesh);
+ }
+
+ if (needsfree) {
+ freeEvaluatedMesh(mesh);
+ }
+ }
+ catch (...) {
+ if (needsfree) {
+ freeEvaluatedMesh(mesh);
+ }
+ throw;
+ }
+}
+
+void AbcGenericMeshWriter::freeEvaluatedMesh(struct Mesh *mesh)
+{
+ BKE_id_free(NULL, mesh);
+}
+
+void AbcGenericMeshWriter::writeMesh(struct Mesh *mesh)
+{
+ std::vector<Imath::V3f> points, normals;
+ std::vector<int32_t> poly_verts, loop_counts;
+ std::vector<Imath::V3f> velocities;
+ bool has_flat_shaded_poly = false;
+
+ get_vertices(mesh, points);
+ get_topology(mesh, poly_verts, loop_counts, has_flat_shaded_poly);
+
+ if (m_first_frame && m_settings.export_face_sets) {
+ writeFaceSets(mesh, m_mesh_schema);
+ }
+
+ m_mesh_sample = OPolyMeshSchema::Sample(
+ V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts));
+
+ UVSample sample;
+ if (m_first_frame && m_settings.export_uvs) {
+ const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata);
+
+ if (!sample.indices.empty() && !sample.uvs.empty()) {
+ OV2fGeomParam::Sample uv_sample;
+ uv_sample.setVals(V2fArraySample(sample.uvs));
+ uv_sample.setIndices(UInt32ArraySample(sample.indices));
+ uv_sample.setScope(kFacevaryingScope);
+
+ m_mesh_schema.setUVSourceName(name);
+ m_mesh_sample.setUVs(uv_sample);
+ }
+
+ write_custom_data(
+ m_mesh_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV);
+ }
+
+ if (m_settings.export_normals) {
+ get_loop_normals(mesh, normals, has_flat_shaded_poly);
+
+ ON3fGeomParam::Sample normals_sample;
+ if (!normals.empty()) {
+ normals_sample.setScope(kFacevaryingScope);
+ normals_sample.setVals(V3fArraySample(normals));
+ }
+
+ m_mesh_sample.setNormals(normals_sample);
+ }
+
+ if (m_is_liquid) {
+ getVelocities(mesh, velocities);
+ m_mesh_sample.setVelocities(V3fArraySample(velocities));
+ }
+
+ m_mesh_sample.setSelfBounds(bounds());
+
+ m_mesh_schema.set(m_mesh_sample);
+
+ writeArbGeoParams(mesh);
+}
+
+void AbcGenericMeshWriter::writeSubD(struct Mesh *mesh)
+{
+ std::vector<float> crease_sharpness;
+ std::vector<Imath::V3f> points;
+ std::vector<int32_t> poly_verts, loop_counts;
+ std::vector<int32_t> crease_indices, crease_lengths;
+ bool has_flat_poly = false;
+
+ get_vertices(mesh, points);
+ get_topology(mesh, poly_verts, loop_counts, has_flat_poly);
+ get_creases(mesh, crease_indices, crease_lengths, crease_sharpness);
+
+ if (m_first_frame && m_settings.export_face_sets) {
+ writeFaceSets(mesh, m_subdiv_schema);
+ }
+
+ m_subdiv_sample = OSubDSchema::Sample(
+ V3fArraySample(points), Int32ArraySample(poly_verts), Int32ArraySample(loop_counts));
+
+ UVSample sample;
+ if (m_first_frame && m_settings.export_uvs) {
+ const char *name = get_uv_sample(sample, m_custom_data_config, &mesh->ldata);
+
+ if (!sample.indices.empty() && !sample.uvs.empty()) {
+ OV2fGeomParam::Sample uv_sample;
+ uv_sample.setVals(V2fArraySample(sample.uvs));
+ uv_sample.setIndices(UInt32ArraySample(sample.indices));
+ uv_sample.setScope(kFacevaryingScope);
+
+ m_subdiv_schema.setUVSourceName(name);
+ m_subdiv_sample.setUVs(uv_sample);
+ }
+
+ write_custom_data(
+ m_subdiv_schema.getArbGeomParams(), m_custom_data_config, &mesh->ldata, CD_MLOOPUV);
+ }
+
+ if (!crease_indices.empty()) {
+ m_subdiv_sample.setCreaseIndices(Int32ArraySample(crease_indices));
+ m_subdiv_sample.setCreaseLengths(Int32ArraySample(crease_lengths));
+ m_subdiv_sample.setCreaseSharpnesses(FloatArraySample(crease_sharpness));
+ }
+
+ m_subdiv_sample.setSelfBounds(bounds());
+ m_subdiv_schema.set(m_subdiv_sample);
+
+ writeArbGeoParams(mesh);
+}
+
+template<typename Schema> void AbcGenericMeshWriter::writeFaceSets(struct Mesh *me, Schema &schema)
+{
+ std::map<std::string, std::vector<int32_t>> geo_groups;
+ getGeoGroups(me, geo_groups);
+
+ std::map<std::string, std::vector<int32_t>>::iterator it;
+ for (it = geo_groups.begin(); it != geo_groups.end(); ++it) {
+ OFaceSet face_set = schema.createFaceSet(it->first);
+ OFaceSetSchema::Sample samp;
+ samp.setFaces(Int32ArraySample(it->second));
+ face_set.getSchema().set(samp);
+ }
+}
+
+Mesh *AbcGenericMeshWriter::getFinalMesh(bool &r_needsfree)
+{
+ /* We don't want subdivided mesh data */
+ if (m_subsurf_mod) {
+ m_subsurf_mod->mode |= eModifierMode_DisableTemporary;
+ }
+
+ r_needsfree = false;
+
+ Scene *scene = DEG_get_evaluated_scene(m_settings.depsgraph);
+ Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object);
+ struct Mesh *mesh = getEvaluatedMesh(scene, ob_eval, r_needsfree);
+
+ if (m_subsurf_mod) {
+ m_subsurf_mod->mode &= ~eModifierMode_DisableTemporary;
+ }
+
+ if (m_settings.triangulate) {
+ const bool tag_only = false;
+ const int quad_method = m_settings.quad_method;
+ const int ngon_method = m_settings.ngon_method;
+
+ struct BMeshCreateParams bmcp = {false};
+ struct BMeshFromMeshParams bmfmp = {true, false, false, 0};
+ BMesh *bm = BKE_mesh_to_bmesh_ex(mesh, &bmcp, &bmfmp);
+
+ BM_mesh_triangulate(bm, quad_method, ngon_method, 4, tag_only, NULL, NULL, NULL);
+
+ Mesh *result = BKE_mesh_from_bmesh_for_eval_nomain(bm, NULL, mesh);
+ BM_mesh_free(bm);
+
+ if (r_needsfree) {
+ BKE_id_free(NULL, mesh);
+ }
+
+ mesh = result;
+ r_needsfree = true;
+ }
+
+ m_custom_data_config.pack_uvs = m_settings.pack_uv;
+ m_custom_data_config.mpoly = mesh->mpoly;
+ m_custom_data_config.mloop = mesh->mloop;
+ m_custom_data_config.totpoly = mesh->totpoly;
+ m_custom_data_config.totloop = mesh->totloop;
+ m_custom_data_config.totvert = mesh->totvert;
+
+ return mesh;
+}
+
+void AbcGenericMeshWriter::writeArbGeoParams(struct Mesh *me)
+{
+ if (m_is_liquid) {
+ /* We don't need anything more for liquid meshes. */
+ return;
+ }
+
+ if (m_first_frame && m_settings.export_vcols) {
+ if (m_subdiv_schema.valid()) {
+ write_custom_data(
+ m_subdiv_schema.getArbGeomParams(), m_custom_data_config, &me->ldata, CD_MLOOPCOL);
+ }
+ else {
+ write_custom_data(
+ m_mesh_schema.getArbGeomParams(), m_custom_data_config, &me->ldata, CD_MLOOPCOL);
+ }
+ }
+}
+
+void AbcGenericMeshWriter::getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels)
+{
+ const int totverts = mesh->totvert;
+
+ vels.clear();
+ vels.resize(totverts);
+
+ ModifierData *md = get_liquid_sim_modifier(m_settings.scene, m_object);
+ FluidsimModifierData *fmd = reinterpret_cast<FluidsimModifierData *>(md);
+ FluidsimSettings *fss = fmd->fss;
+
+ if (fss->meshVelocities) {
+ float *mesh_vels = reinterpret_cast<float *>(fss->meshVelocities);
+
+ for (int i = 0; i < totverts; i++) {
+ copy_yup_from_zup(vels[i].getValue(), mesh_vels);
+ mesh_vels += 3;
+ }
+ }
+ else {
+ std::fill(vels.begin(), vels.end(), Imath::V3f(0.0f));
+ }
+}
+
+void AbcGenericMeshWriter::getGeoGroups(struct Mesh *mesh,
+ std::map<std::string, std::vector<int32_t>> &geo_groups)
+{
+ const int num_poly = mesh->totpoly;
+ MPoly *polygons = mesh->mpoly;
+
+ for (int i = 0; i < num_poly; i++) {
+ MPoly &current_poly = polygons[i];
+ short mnr = current_poly.mat_nr;
+
+ Material *mat = BKE_object_material_get(m_object, mnr + 1);
+
+ if (!mat) {
+ continue;
+ }
+
+ std::string name = get_id_name(&mat->id);
+
+ if (geo_groups.find(name) == geo_groups.end()) {
+ std::vector<int32_t> faceArray;
+ geo_groups[name] = faceArray;
+ }
+
+ geo_groups[name].push_back(i);
+ }
+
+ if (geo_groups.size() == 0) {
+ Material *mat = BKE_object_material_get(m_object, 1);
+
+ std::string name = (mat) ? get_id_name(&mat->id) : "default";
+
+ std::vector<int32_t> faceArray;
+
+ for (int i = 0, e = mesh->totface; i < e; i++) {
+ faceArray.push_back(i);
+ }
+
+ geo_groups[name] = faceArray;
+ }
+}
+
+AbcMeshWriter::AbcMeshWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings)
+ : AbcGenericMeshWriter(ob, parent, time_sampling, settings)
+{
+}
+
+AbcMeshWriter::~AbcMeshWriter()
+{
+}
+
+Mesh *AbcMeshWriter::getEvaluatedMesh(Scene *scene_eval,
+ Object *ob_eval,
+ bool &UNUSED(r_needsfree))
+{
+ return mesh_get_eval_final(m_settings.depsgraph, scene_eval, ob_eval, &CD_MASK_MESH);
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_mesh.h b/source/blender/io/alembic/intern/abc_writer_mesh.h
new file mode 100644
index 00000000000..9152a370e4f
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_mesh.h
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_MESH_H__
+#define __ABC_WRITER_MESH_H__
+
+#include "abc_customdata.h"
+#include "abc_writer_object.h"
+
+struct Mesh;
+struct ModifierData;
+
+/* Writer for Alembic meshes. Does not assume the object is a mesh object. */
+class AbcGenericMeshWriter : public AbcObjectWriter {
+ protected:
+ Alembic::AbcGeom::OPolyMeshSchema m_mesh_schema;
+ Alembic::AbcGeom::OPolyMeshSchema::Sample m_mesh_sample;
+
+ Alembic::AbcGeom::OSubDSchema m_subdiv_schema;
+ Alembic::AbcGeom::OSubDSchema::Sample m_subdiv_sample;
+
+ Alembic::Abc::OArrayProperty m_mat_indices;
+
+ bool m_is_animated;
+ ModifierData *m_subsurf_mod;
+
+ CDStreamConfig m_custom_data_config;
+
+ bool m_is_liquid;
+ bool m_is_subd;
+
+ public:
+ AbcGenericMeshWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ ~AbcGenericMeshWriter();
+ void setIsAnimated(bool is_animated);
+
+ protected:
+ virtual void do_write();
+ virtual bool isAnimated() const;
+ virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) = 0;
+ virtual void freeEvaluatedMesh(struct Mesh *mesh);
+
+ Mesh *getFinalMesh(bool &r_needsfree);
+
+ void writeMesh(struct Mesh *mesh);
+ void writeSubD(struct Mesh *mesh);
+
+ void writeArbGeoParams(struct Mesh *mesh);
+ void getGeoGroups(struct Mesh *mesh, std::map<std::string, std::vector<int32_t>> &geoGroups);
+
+ /* fluid surfaces support */
+ void getVelocities(struct Mesh *mesh, std::vector<Imath::V3f> &vels);
+
+ template<typename Schema> void writeFaceSets(struct Mesh *mesh, Schema &schema);
+};
+
+class AbcMeshWriter : public AbcGenericMeshWriter {
+ public:
+ AbcMeshWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ ~AbcMeshWriter();
+
+ protected:
+ virtual Mesh *getEvaluatedMesh(Scene *scene_eval, Object *ob_eval, bool &r_needsfree) override;
+};
+
+#endif /* __ABC_WRITER_MESH_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_nurbs.cc b/source/blender/io/alembic/intern/abc_writer_nurbs.cc
new file mode 100644
index 00000000000..9796eaf54c3
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_nurbs.cc
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_nurbs.h"
+#include "abc_writer_transform.h"
+#include "abc_util.h"
+
+extern "C" {
+#include "DNA_curve_types.h"
+#include "DNA_object_types.h"
+
+#include "BLI_listbase.h"
+
+#include "BKE_curve.h"
+}
+
+using Alembic::AbcGeom::FloatArraySample;
+using Alembic::AbcGeom::OBoolProperty;
+using Alembic::AbcGeom::OCompoundProperty;
+using Alembic::AbcGeom::ONuPatch;
+using Alembic::AbcGeom::ONuPatchSchema;
+
+AbcNurbsWriter::AbcNurbsWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings)
+ : AbcObjectWriter(ob, time_sampling, settings, parent)
+{
+ m_is_animated = isAnimated();
+
+ /* if the object is static, use the default static time sampling */
+ if (!m_is_animated) {
+ m_time_sampling = 0;
+ }
+
+ Curve *curve = static_cast<Curve *>(m_object->data);
+ size_t numNurbs = BLI_listbase_count(&curve->nurb);
+
+ for (size_t i = 0; i < numNurbs; i++) {
+ std::stringstream str;
+ str << m_name << '_' << i;
+
+ while (parent->alembicXform().getChildHeader(str.str())) {
+ str << "_";
+ }
+
+ ONuPatch nurbs(parent->alembicXform(), str.str().c_str(), m_time_sampling);
+ m_nurbs_schema.push_back(nurbs.getSchema());
+ }
+}
+
+bool AbcNurbsWriter::isAnimated() const
+{
+ /* check if object has shape keys */
+ Curve *cu = static_cast<Curve *>(m_object->data);
+ return (cu->key != NULL);
+}
+
+static void get_knots(std::vector<float> &knots, const int num_knots, float *nu_knots)
+{
+ if (num_knots <= 1) {
+ return;
+ }
+
+ /* Add an extra knot at the beginning and end of the array since most apps
+ * require/expect them. */
+ knots.reserve(num_knots + 2);
+
+ knots.push_back(0.0f);
+
+ for (int i = 0; i < num_knots; i++) {
+ knots.push_back(nu_knots[i]);
+ }
+
+ knots[0] = 2.0f * knots[1] - knots[2];
+ knots.push_back(2.0f * knots[num_knots] - knots[num_knots - 1]);
+}
+
+void AbcNurbsWriter::do_write()
+{
+ /* we have already stored a sample for this object. */
+ if (!m_first_frame && !m_is_animated) {
+ return;
+ }
+
+ if (!ELEM(m_object->type, OB_SURF, OB_CURVE)) {
+ return;
+ }
+
+ Curve *curve = static_cast<Curve *>(m_object->data);
+ ListBase *nulb;
+
+ if (m_object->runtime.curve_cache->deformed_nurbs.first != NULL) {
+ nulb = &m_object->runtime.curve_cache->deformed_nurbs;
+ }
+ else {
+ nulb = BKE_curve_nurbs_get(curve);
+ }
+
+ size_t count = 0;
+ for (Nurb *nu = static_cast<Nurb *>(nulb->first); nu; nu = nu->next, count++) {
+ std::vector<float> knotsU;
+ get_knots(knotsU, KNOTSU(nu), nu->knotsu);
+
+ std::vector<float> knotsV;
+ get_knots(knotsV, KNOTSV(nu), nu->knotsv);
+
+ const int size = nu->pntsu * nu->pntsv;
+ std::vector<Imath::V3f> positions(size);
+ std::vector<float> weights(size);
+
+ const BPoint *bp = nu->bp;
+
+ for (int i = 0; i < size; i++, bp++) {
+ copy_yup_from_zup(positions[i].getValue(), bp->vec);
+ weights[i] = bp->vec[3];
+ }
+
+ ONuPatchSchema::Sample sample;
+ sample.setUOrder(nu->orderu + 1);
+ sample.setVOrder(nu->orderv + 1);
+ sample.setPositions(positions);
+ sample.setPositionWeights(weights);
+ sample.setUKnot(FloatArraySample(knotsU));
+ sample.setVKnot(FloatArraySample(knotsV));
+ sample.setNu(nu->pntsu);
+ sample.setNv(nu->pntsv);
+
+ /* TODO(kevin): to accommodate other software we should duplicate control
+ * points to indicate that a NURBS is cyclic. */
+ OCompoundProperty user_props = m_nurbs_schema[count].getUserProperties();
+
+ if ((nu->flagu & CU_NURB_ENDPOINT) != 0) {
+ OBoolProperty prop(user_props, "endpoint_u");
+ prop.set(true);
+ }
+
+ if ((nu->flagv & CU_NURB_ENDPOINT) != 0) {
+ OBoolProperty prop(user_props, "endpoint_v");
+ prop.set(true);
+ }
+
+ if ((nu->flagu & CU_NURB_CYCLIC) != 0) {
+ OBoolProperty prop(user_props, "cyclic_u");
+ prop.set(true);
+ }
+
+ if ((nu->flagv & CU_NURB_CYCLIC) != 0) {
+ OBoolProperty prop(user_props, "cyclic_v");
+ prop.set(true);
+ }
+
+ m_nurbs_schema[count].set(sample);
+ }
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_nurbs.h b/source/blender/io/alembic/intern/abc_writer_nurbs.h
new file mode 100644
index 00000000000..c6a3c399b66
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_nurbs.h
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_NURBS_H__
+#define __ABC_WRITER_NURBS_H__
+
+#include "abc_writer_object.h"
+
+class AbcNurbsWriter : public AbcObjectWriter {
+ std::vector<Alembic::AbcGeom::ONuPatchSchema> m_nurbs_schema;
+ bool m_is_animated;
+
+ public:
+ AbcNurbsWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings);
+
+ private:
+ virtual void do_write();
+
+ bool isAnimated() const;
+};
+
+#endif /* __ABC_WRITER_NURBS_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_object.cc b/source/blender/io/alembic/intern/abc_writer_object.cc
new file mode 100644
index 00000000000..75dc93bd08e
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_object.cc
@@ -0,0 +1,79 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_object.h"
+
+extern "C" {
+#include "DNA_object_types.h"
+
+#include "BKE_object.h"
+}
+
+AbcObjectWriter::AbcObjectWriter(Object *ob,
+ uint32_t time_sampling,
+ ExportSettings &settings,
+ AbcObjectWriter *parent)
+ : m_object(ob), m_settings(settings), m_time_sampling(time_sampling), m_first_frame(true)
+{
+ m_name = get_id_name(m_object) + "Shape";
+
+ if (parent) {
+ parent->addChild(this);
+ }
+}
+
+AbcObjectWriter::~AbcObjectWriter()
+{
+}
+
+void AbcObjectWriter::addChild(AbcObjectWriter *child)
+{
+ m_children.push_back(child);
+}
+
+Imath::Box3d AbcObjectWriter::bounds()
+{
+ BoundBox *bb = BKE_object_boundbox_get(this->m_object);
+
+ if (!bb) {
+ if (this->m_object->type != OB_CAMERA) {
+ ABC_LOG(m_settings.logger) << "Bounding box is null!\n";
+ }
+
+ return Imath::Box3d();
+ }
+
+ /* Convert Z-up to Y-up. This also changes which vector goes into which min/max property. */
+ this->m_bounds.min.x = bb->vec[0][0];
+ this->m_bounds.min.y = bb->vec[0][2];
+ this->m_bounds.min.z = -bb->vec[6][1];
+
+ this->m_bounds.max.x = bb->vec[6][0];
+ this->m_bounds.max.y = bb->vec[6][2];
+ this->m_bounds.max.z = -bb->vec[0][1];
+
+ return this->m_bounds;
+}
+
+void AbcObjectWriter::write()
+{
+ do_write();
+ m_first_frame = false;
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_object.h b/source/blender/io/alembic/intern/abc_writer_object.h
new file mode 100644
index 00000000000..c3511566372
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_object.h
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_OBJECT_H__
+#define __ABC_WRITER_OBJECT_H__
+
+#include <Alembic/Abc/All.h>
+#include <Alembic/AbcGeom/All.h>
+
+#include "abc_exporter.h"
+
+extern "C" {
+#include "DNA_ID.h"
+}
+
+class AbcTransformWriter;
+
+struct Main;
+struct Object;
+
+class AbcObjectWriter {
+ protected:
+ Object *m_object;
+ ExportSettings &m_settings;
+
+ uint32_t m_time_sampling;
+
+ Imath::Box3d m_bounds;
+ std::vector<AbcObjectWriter *> m_children;
+
+ std::vector<std::pair<std::string, IDProperty *>> m_props;
+
+ bool m_first_frame;
+ std::string m_name;
+
+ public:
+ AbcObjectWriter(Object *ob,
+ uint32_t time_sampling,
+ ExportSettings &settings,
+ AbcObjectWriter *parent = NULL);
+
+ virtual ~AbcObjectWriter();
+
+ void addChild(AbcObjectWriter *child);
+
+ virtual Imath::Box3d bounds();
+
+ void write();
+
+ private:
+ virtual void do_write() = 0;
+};
+
+#endif /* __ABC_WRITER_OBJECT_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_points.cc b/source/blender/io/alembic/intern/abc_writer_points.cc
new file mode 100644
index 00000000000..cc4abe8ec4b
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_points.cc
@@ -0,0 +1,123 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_points.h"
+#include "abc_writer_mesh.h"
+#include "abc_writer_transform.h"
+#include "abc_util.h"
+
+extern "C" {
+#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
+
+#include "BKE_lattice.h"
+#include "BKE_particle.h"
+
+#include "BLI_math.h"
+
+#include "DEG_depsgraph_query.h"
+}
+
+using Alembic::AbcGeom::kVertexScope;
+using Alembic::AbcGeom::OPoints;
+using Alembic::AbcGeom::OPointsSchema;
+
+/* ************************************************************************** */
+
+AbcPointsWriter::AbcPointsWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings,
+ ParticleSystem *psys)
+ : AbcObjectWriter(ob, time_sampling, settings, parent)
+{
+ m_psys = psys;
+
+ OPoints points(parent->alembicXform(), psys->name, m_time_sampling);
+ m_schema = points.getSchema();
+}
+
+void AbcPointsWriter::do_write()
+{
+ if (!m_psys) {
+ return;
+ }
+
+ std::vector<Imath::V3f> points;
+ std::vector<Imath::V3f> velocities;
+ std::vector<float> widths;
+ std::vector<uint64_t> ids;
+
+ ParticleKey state;
+
+ ParticleSimulationData sim;
+ sim.depsgraph = m_settings.depsgraph;
+ sim.scene = m_settings.scene;
+ sim.ob = m_object;
+ sim.psys = m_psys;
+
+ m_psys->lattice_deform_data = psys_create_lattice_deform_data(&sim);
+
+ uint64_t index = 0;
+ for (int p = 0; p < m_psys->totpart; p++) {
+ float pos[3], vel[3];
+
+ if (m_psys->particles[p].flag & (PARS_NO_DISP | PARS_UNEXIST)) {
+ continue;
+ }
+
+ state.time = DEG_get_ctime(m_settings.depsgraph);
+
+ if (psys_get_particle_state(&sim, p, &state, 0) == 0) {
+ continue;
+ }
+
+ /* location */
+ mul_v3_m4v3(pos, m_object->imat, state.co);
+
+ /* velocity */
+ sub_v3_v3v3(vel, state.co, m_psys->particles[p].prev_state.co);
+
+ /* Convert Z-up to Y-up. */
+ points.push_back(Imath::V3f(pos[0], pos[2], -pos[1]));
+ velocities.push_back(Imath::V3f(vel[0], vel[2], -vel[1]));
+ widths.push_back(m_psys->particles[p].size);
+ ids.push_back(index++);
+ }
+
+ if (m_psys->lattice_deform_data) {
+ end_latt_deform(m_psys->lattice_deform_data);
+ m_psys->lattice_deform_data = NULL;
+ }
+
+ Alembic::Abc::P3fArraySample psample(points);
+ Alembic::Abc::UInt64ArraySample idsample(ids);
+ Alembic::Abc::V3fArraySample vsample(velocities);
+ Alembic::Abc::FloatArraySample wsample_array(widths);
+ Alembic::AbcGeom::OFloatGeomParam::Sample wsample(wsample_array, kVertexScope);
+
+ m_sample = OPointsSchema::Sample(psample, idsample, vsample, wsample);
+ m_sample.setSelfBounds(bounds());
+
+ m_schema.set(m_sample);
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_points.h b/source/blender/io/alembic/intern/abc_writer_points.h
new file mode 100644
index 00000000000..77dd10c4b26
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_points.h
@@ -0,0 +1,49 @@
+/*
+ * 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) 2016 Kévin Dietrich.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_POINTS_H__
+#define __ABC_WRITER_POINTS_H__
+
+#include "abc_writer_object.h"
+#include "abc_customdata.h"
+
+struct ParticleSystem;
+
+/* ************************************************************************** */
+
+class AbcPointsWriter : public AbcObjectWriter {
+ Alembic::AbcGeom::OPointsSchema m_schema;
+ Alembic::AbcGeom::OPointsSchema::Sample m_sample;
+ ParticleSystem *m_psys;
+
+ public:
+ AbcPointsWriter(Object *ob,
+ AbcTransformWriter *parent,
+ uint32_t time_sampling,
+ ExportSettings &settings,
+ ParticleSystem *psys);
+
+ void do_write();
+};
+
+#endif /* __ABC_WRITER_POINTS_H__ */
diff --git a/source/blender/io/alembic/intern/abc_writer_transform.cc b/source/blender/io/alembic/intern/abc_writer_transform.cc
new file mode 100644
index 00000000000..d7bcc46d96f
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_transform.cc
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "abc_writer_transform.h"
+#include "abc_util.h"
+
+#include <OpenEXR/ImathBoxAlgo.h>
+
+extern "C" {
+#include "DNA_object_types.h"
+
+#include "BLI_math.h"
+
+#include "DEG_depsgraph_query.h"
+}
+
+using Alembic::AbcGeom::OObject;
+using Alembic::AbcGeom::OXform;
+
+AbcTransformWriter::AbcTransformWriter(Object *ob,
+ const OObject &abc_parent,
+ AbcTransformWriter *parent,
+ unsigned int time_sampling,
+ ExportSettings &settings)
+ : AbcObjectWriter(ob, time_sampling, settings, parent), m_proxy_from(NULL)
+{
+ m_is_animated = hasAnimation(m_object);
+
+ if (!m_is_animated) {
+ time_sampling = 0;
+ }
+
+ m_xform = OXform(abc_parent, get_id_name(m_object), time_sampling);
+ m_schema = m_xform.getSchema();
+
+ /* Blender objects can't have a parent without inheriting the transform. */
+ m_inherits_xform = parent != NULL;
+}
+
+void AbcTransformWriter::do_write()
+{
+ Object *ob_eval = DEG_get_evaluated_object(m_settings.depsgraph, m_object);
+
+ if (m_first_frame) {
+ m_visibility = Alembic::AbcGeom::CreateVisibilityProperty(
+ m_xform, m_xform.getSchema().getTimeSampling());
+ }
+
+ m_visibility.set(!(ob_eval->restrictflag & OB_RESTRICT_VIEWPORT));
+
+ if (!m_first_frame && !m_is_animated) {
+ return;
+ }
+
+ float yup_mat[4][4];
+ create_transform_matrix(
+ ob_eval, yup_mat, m_inherits_xform ? ABC_MATRIX_LOCAL : ABC_MATRIX_WORLD, m_proxy_from);
+
+ /* If the parent is a camera, undo its to-Maya rotation (see below). */
+ bool is_root_object = !m_inherits_xform || ob_eval->parent == nullptr;
+ if (!is_root_object && ob_eval->parent->type == OB_CAMERA) {
+ float rot_mat[4][4];
+ axis_angle_to_mat4_single(rot_mat, 'X', M_PI_2);
+ mul_m4_m4m4(yup_mat, rot_mat, yup_mat);
+ }
+
+ /* If the object is a camera, apply an extra rotation to Maya camera orientation. */
+ if (ob_eval->type == OB_CAMERA) {
+ float rot_mat[4][4];
+ axis_angle_to_mat4_single(rot_mat, 'X', -M_PI_2);
+ mul_m4_m4m4(yup_mat, yup_mat, rot_mat);
+ }
+
+ if (is_root_object) {
+ /* Only apply scaling to root objects, parenting will propagate it. */
+ float scale_mat[4][4];
+ scale_m4_fl(scale_mat, m_settings.global_scale);
+ scale_mat[3][3] = m_settings.global_scale; /* also scale translation */
+ mul_m4_m4m4(yup_mat, yup_mat, scale_mat);
+ yup_mat[3][3] /= m_settings.global_scale; /* normalise the homogeneous component */
+ }
+
+ m_matrix = convert_matrix_datatype(yup_mat);
+ m_sample.setMatrix(m_matrix);
+ m_sample.setInheritsXforms(m_inherits_xform);
+ m_schema.set(m_sample);
+}
+
+Imath::Box3d AbcTransformWriter::bounds()
+{
+ Imath::Box3d bounds;
+
+ for (int i = 0; i < m_children.size(); i++) {
+ Imath::Box3d box(m_children[i]->bounds());
+ bounds.extendBy(box);
+ }
+
+ return Imath::transform(bounds, m_matrix);
+}
+
+bool AbcTransformWriter::hasAnimation(Object * /*ob*/) const
+{
+ return true;
+}
diff --git a/source/blender/io/alembic/intern/abc_writer_transform.h b/source/blender/io/alembic/intern/abc_writer_transform.h
new file mode 100644
index 00000000000..4397b220761
--- /dev/null
+++ b/source/blender/io/alembic/intern/abc_writer_transform.h
@@ -0,0 +1,60 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#ifndef __ABC_WRITER_TRANSFORM_H__
+#define __ABC_WRITER_TRANSFORM_H__
+
+#include "abc_writer_object.h"
+
+#include <Alembic/AbcGeom/All.h>
+
+class AbcTransformWriter : public AbcObjectWriter {
+ Alembic::AbcGeom::OXform m_xform;
+ Alembic::AbcGeom::OXformSchema m_schema;
+ Alembic::AbcGeom::XformSample m_sample;
+ Alembic::AbcGeom::OVisibilityProperty m_visibility;
+ Alembic::Abc::M44d m_matrix;
+
+ bool m_is_animated;
+ bool m_inherits_xform;
+
+ public:
+ Object *m_proxy_from;
+
+ public:
+ AbcTransformWriter(Object *ob,
+ const Alembic::AbcGeom::OObject &abc_parent,
+ AbcTransformWriter *parent,
+ unsigned int time_sampling,
+ ExportSettings &settings);
+
+ Alembic::AbcGeom::OXform &alembicXform()
+ {
+ return m_xform;
+ }
+ virtual Imath::Box3d bounds();
+
+ private:
+ virtual void do_write();
+
+ bool hasAnimation(Object *ob) const;
+};
+
+#endif /* __ABC_WRITER_TRANSFORM_H__ */
diff --git a/source/blender/io/alembic/intern/alembic_capi.cc b/source/blender/io/alembic/intern/alembic_capi.cc
new file mode 100644
index 00000000000..c6f9e284d53
--- /dev/null
+++ b/source/blender/io/alembic/intern/alembic_capi.cc
@@ -0,0 +1,1052 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup balembic
+ */
+
+#include "../ABC_alembic.h"
+
+#include <Alembic/AbcMaterial/IMaterial.h>
+
+#include "abc_reader_archive.h"
+#include "abc_reader_camera.h"
+#include "abc_reader_curves.h"
+#include "abc_reader_mesh.h"
+#include "abc_reader_nurbs.h"
+#include "abc_reader_points.h"
+#include "abc_reader_transform.h"
+#include "abc_util.h"
+#include "abc_writer_camera.h"
+#include "abc_writer_curves.h"
+#include "abc_writer_hair.h"
+#include "abc_writer_mesh.h"
+#include "abc_writer_nurbs.h"
+#include "abc_writer_points.h"
+#include "abc_writer_transform.h"
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "DNA_cachefile_types.h"
+#include "DNA_curve_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_cachefile.h"
+#include "BKE_context.h"
+#include "BKE_curve.h"
+#include "BKE_global.h"
+#include "BKE_layer.h"
+#include "BKE_lib_id.h"
+#include "BKE_scene.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+
+/* SpaceType struct has a member called 'new' which obviously conflicts with C++
+ * so temporarily redefining the new keyword to make it compile. */
+#define new extern_new
+#include "BKE_screen.h"
+#undef new
+
+#include "BLI_fileops.h"
+#include "BLI_ghash.h"
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+}
+
+using Alembic::Abc::Int32ArraySamplePtr;
+using Alembic::Abc::ObjectHeader;
+
+using Alembic::AbcGeom::kWrapExisting;
+using Alembic::AbcGeom::MetaData;
+using Alembic::AbcGeom::P3fArraySamplePtr;
+
+using Alembic::AbcGeom::ICamera;
+using Alembic::AbcGeom::ICompoundProperty;
+using Alembic::AbcGeom::ICurves;
+using Alembic::AbcGeom::ICurvesSchema;
+using Alembic::AbcGeom::IFaceSet;
+using Alembic::AbcGeom::ILight;
+using Alembic::AbcGeom::IN3fArrayProperty;
+using Alembic::AbcGeom::IN3fGeomParam;
+using Alembic::AbcGeom::INuPatch;
+using Alembic::AbcGeom::IObject;
+using Alembic::AbcGeom::IPoints;
+using Alembic::AbcGeom::IPointsSchema;
+using Alembic::AbcGeom::IPolyMesh;
+using Alembic::AbcGeom::IPolyMeshSchema;
+using Alembic::AbcGeom::ISampleSelector;
+using Alembic::AbcGeom::ISubD;
+using Alembic::AbcGeom::IV2fGeomParam;
+using Alembic::AbcGeom::IXform;
+using Alembic::AbcGeom::IXformSchema;
+using Alembic::AbcGeom::N3fArraySamplePtr;
+using Alembic::AbcGeom::V3fArraySamplePtr;
+using Alembic::AbcGeom::XformSample;
+
+using Alembic::AbcMaterial::IMaterial;
+
+struct AbcArchiveHandle {
+ int unused;
+};
+
+ABC_INLINE ArchiveReader *archive_from_handle(AbcArchiveHandle *handle)
+{
+ return reinterpret_cast<ArchiveReader *>(handle);
+}
+
+ABC_INLINE AbcArchiveHandle *handle_from_archive(ArchiveReader *archive)
+{
+ return reinterpret_cast<AbcArchiveHandle *>(archive);
+}
+
+//#define USE_NURBS
+
+/* NOTE: this function is similar to visit_objects below, need to keep them in
+ * sync. */
+static bool gather_objects_paths(const IObject &object, ListBase *object_paths)
+{
+ if (!object.valid()) {
+ return false;
+ }
+
+ size_t children_claiming_this_object = 0;
+ size_t num_children = object.getNumChildren();
+
+ for (size_t i = 0; i < num_children; i++) {
+ bool child_claims_this_object = gather_objects_paths(object.getChild(i), object_paths);
+ children_claiming_this_object += child_claims_this_object ? 1 : 0;
+ }
+
+ const MetaData &md = object.getMetaData();
+ bool get_path = false;
+ bool parent_is_part_of_this_object = false;
+
+ if (!object.getParent()) {
+ /* The root itself is not an object we should import. */
+ }
+ else if (IXform::matches(md)) {
+ if (has_property(object.getProperties(), "locator")) {
+ get_path = true;
+ }
+ else {
+ get_path = children_claiming_this_object == 0;
+ }
+
+ /* Transforms are never "data" for their parent. */
+ parent_is_part_of_this_object = false;
+ }
+ else {
+ /* These types are "data" for their parent. */
+ get_path = IPolyMesh::matches(md) || ISubD::matches(md) ||
+#ifdef USE_NURBS
+ INuPatch::matches(md) ||
+#endif
+ ICamera::matches(md) || IPoints::matches(md) || ICurves::matches(md);
+ parent_is_part_of_this_object = get_path;
+ }
+
+ if (get_path) {
+ void *abc_path_void = MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath");
+ AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>(abc_path_void);
+
+ BLI_strncpy(abc_path->path, object.getFullName().c_str(), sizeof(abc_path->path));
+ BLI_addtail(object_paths, abc_path);
+ }
+
+ return parent_is_part_of_this_object;
+}
+
+AbcArchiveHandle *ABC_create_handle(struct Main *bmain,
+ const char *filename,
+ ListBase *object_paths)
+{
+ ArchiveReader *archive = new ArchiveReader(bmain, filename);
+
+ if (!archive->valid()) {
+ delete archive;
+ return NULL;
+ }
+
+ if (object_paths) {
+ gather_objects_paths(archive->getTop(), object_paths);
+ }
+
+ return handle_from_archive(archive);
+}
+
+void ABC_free_handle(AbcArchiveHandle *handle)
+{
+ delete archive_from_handle(handle);
+}
+
+int ABC_get_version()
+{
+ return ALEMBIC_LIBRARY_VERSION;
+}
+
+static void find_iobject(const IObject &object, IObject &ret, const std::string &path)
+{
+ if (!object.valid()) {
+ return;
+ }
+
+ std::vector<std::string> tokens;
+ split(path, '/', tokens);
+
+ IObject tmp = object;
+
+ std::vector<std::string>::iterator iter;
+ for (iter = tokens.begin(); iter != tokens.end(); ++iter) {
+ IObject child = tmp.getChild(*iter);
+ tmp = child;
+ }
+
+ ret = tmp;
+}
+
+struct ExportJobData {
+ ViewLayer *view_layer;
+ Main *bmain;
+ wmWindowManager *wm;
+
+ char filename[1024];
+ ExportSettings settings;
+
+ short *stop;
+ short *do_update;
+ float *progress;
+
+ bool was_canceled;
+ bool export_ok;
+};
+
+static void export_startjob(void *customdata, short *stop, short *do_update, float *progress)
+{
+ ExportJobData *data = static_cast<ExportJobData *>(customdata);
+
+ data->stop = stop;
+ data->do_update = do_update;
+ data->progress = progress;
+
+ /* XXX annoying hack: needed to prevent data corruption when changing
+ * scene frame in separate threads
+ */
+ G.is_rendering = true;
+ WM_set_locked_interface(data->wm, true);
+ G.is_break = false;
+
+ DEG_graph_build_from_view_layer(
+ data->settings.depsgraph, data->bmain, data->settings.scene, data->view_layer);
+ BKE_scene_graph_update_tagged(data->settings.depsgraph, data->bmain);
+
+ try {
+ AbcExporter exporter(data->bmain, data->filename, data->settings);
+
+ Scene *scene = data->settings.scene; /* for the CFRA macro */
+ const int orig_frame = CFRA;
+
+ data->was_canceled = false;
+ exporter(do_update, progress, &data->was_canceled);
+
+ if (CFRA != orig_frame) {
+ CFRA = orig_frame;
+
+ BKE_scene_graph_update_for_newframe(data->settings.depsgraph, data->bmain);
+ }
+
+ data->export_ok = !data->was_canceled;
+ }
+ catch (const std::exception &e) {
+ ABC_LOG(data->settings.logger) << "Abc Export error: " << e.what() << '\n';
+ }
+ catch (...) {
+ ABC_LOG(data->settings.logger) << "Abc Export: unknown error...\n";
+ }
+}
+
+static void export_endjob(void *customdata)
+{
+ ExportJobData *data = static_cast<ExportJobData *>(customdata);
+
+ DEG_graph_free(data->settings.depsgraph);
+
+ if (data->was_canceled && BLI_exists(data->filename)) {
+ BLI_delete(data->filename, false, false);
+ }
+
+ std::string log = data->settings.logger.str();
+ if (!log.empty()) {
+ std::cerr << log;
+ WM_report(RPT_ERROR, "Errors occurred during the export, look in the console to know more...");
+ }
+
+ G.is_rendering = false;
+ WM_set_locked_interface(data->wm, false);
+}
+
+bool ABC_export(Scene *scene,
+ bContext *C,
+ const char *filepath,
+ const struct AlembicExportParams *params,
+ bool as_background_job)
+{
+ ExportJobData *job = static_cast<ExportJobData *>(
+ MEM_mallocN(sizeof(ExportJobData), "ExportJobData"));
+
+ job->view_layer = CTX_data_view_layer(C);
+ job->bmain = CTX_data_main(C);
+ job->wm = CTX_wm_manager(C);
+ job->export_ok = false;
+ BLI_strncpy(job->filename, filepath, 1024);
+
+ /* Alright, alright, alright....
+ *
+ * ExportJobData contains an ExportSettings containing a SimpleLogger.
+ *
+ * Since ExportJobData is a C-style struct dynamically allocated with
+ * MEM_mallocN (see above), its constructor is never called, therefore the
+ * ExportSettings constructor is not called which implies that the
+ * SimpleLogger one is not called either. SimpleLogger in turn does not call
+ * the constructor of its data members which ultimately means that its
+ * std::ostringstream member has a NULL pointer. To be able to properly use
+ * the stream's operator<<, the pointer needs to be set, therefore we have
+ * to properly construct everything. And this is done using the placement
+ * new operator as here below. It seems hackish, but I'm too lazy to
+ * do bigger refactor and maybe there is a better way which does not involve
+ * hardcore refactoring. */
+ new (&job->settings) ExportSettings();
+ job->settings.scene = scene;
+ job->settings.depsgraph = DEG_graph_new(job->bmain, scene, job->view_layer, DAG_EVAL_RENDER);
+
+ /* TODO(Sybren): for now we only export the active scene layer.
+ * Later in the 2.8 development process this may be replaced by using
+ * a specific collection for Alembic I/O, which can then be toggled
+ * between "real" objects and cached Alembic files. */
+ job->settings.view_layer = job->view_layer;
+
+ job->settings.frame_start = params->frame_start;
+ job->settings.frame_end = params->frame_end;
+ job->settings.frame_samples_xform = params->frame_samples_xform;
+ job->settings.frame_samples_shape = params->frame_samples_shape;
+ job->settings.shutter_open = params->shutter_open;
+ job->settings.shutter_close = params->shutter_close;
+
+ /* TODO(Sybren): For now this is ignored, until we can get selection
+ * detection working through Base pointers (instead of ob->flags). */
+ job->settings.selected_only = params->selected_only;
+
+ job->settings.export_face_sets = params->face_sets;
+ job->settings.export_normals = params->normals;
+ job->settings.export_uvs = params->uvs;
+ job->settings.export_vcols = params->vcolors;
+ job->settings.export_hair = params->export_hair;
+ job->settings.export_particles = params->export_particles;
+ job->settings.apply_subdiv = params->apply_subdiv;
+ job->settings.curves_as_mesh = params->curves_as_mesh;
+ job->settings.flatten_hierarchy = params->flatten_hierarchy;
+
+ /* TODO(Sybren): visible_layer & renderable only is ignored for now,
+ * to be replaced with collections later in the 2.8 dev process
+ * (also see note above). */
+ job->settings.visible_objects_only = params->visible_objects_only;
+ job->settings.renderable_only = params->renderable_only;
+
+ job->settings.use_subdiv_schema = params->use_subdiv_schema;
+ job->settings.export_ogawa = (params->compression_type == ABC_ARCHIVE_OGAWA);
+ job->settings.pack_uv = params->packuv;
+ job->settings.global_scale = params->global_scale;
+ job->settings.triangulate = params->triangulate;
+ job->settings.quad_method = params->quad_method;
+ job->settings.ngon_method = params->ngon_method;
+
+ if (job->settings.frame_start > job->settings.frame_end) {
+ std::swap(job->settings.frame_start, job->settings.frame_end);
+ }
+
+ bool export_ok = false;
+ if (as_background_job) {
+ wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C),
+ CTX_wm_window(C),
+ job->settings.scene,
+ "Alembic Export",
+ WM_JOB_PROGRESS,
+ WM_JOB_TYPE_ALEMBIC);
+
+ /* setup job */
+ WM_jobs_customdata_set(wm_job, job, MEM_freeN);
+ WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME);
+ WM_jobs_callbacks(wm_job, export_startjob, NULL, NULL, export_endjob);
+
+ WM_jobs_start(CTX_wm_manager(C), wm_job);
+ }
+ else {
+ /* Fake a job context, so that we don't need NULL pointer checks while exporting. */
+ short stop = 0, do_update = 0;
+ float progress = 0.f;
+
+ export_startjob(job, &stop, &do_update, &progress);
+ export_endjob(job);
+ export_ok = job->export_ok;
+
+ MEM_freeN(job);
+ }
+
+ return export_ok;
+}
+
+/* ********************** Import file ********************** */
+
+/**
+ * Generates an AbcObjectReader for this Alembic object and its children.
+ *
+ * \param object: The Alembic IObject to visit.
+ * \param readers: The created AbcObjectReader * will be appended to this vector.
+ * \param settings: Import settings, not used directly but passed to the
+ * AbcObjectReader subclass constructors.
+ * \param r_assign_as_parent: Return parameter, contains a list of reader
+ * pointers, whose parent pointer should still be set.
+ * This is filled when this call to visit_object() didn't create
+ * a reader that should be the parent.
+ * \return A pair of boolean and reader pointer. The boolean indicates whether
+ * this IObject claims its parent as part of the same object
+ * (for example an IPolyMesh object would claim its parent, as the mesh
+ * is interpreted as the object's data, and the parent IXform as its
+ * Blender object). The pointer is the AbcObjectReader that represents
+ * the IObject parameter.
+ *
+ * NOTE: this function is similar to gather_object_paths above, need to keep
+ * them in sync. */
+static std::pair<bool, AbcObjectReader *> visit_object(
+ const IObject &object,
+ AbcObjectReader::ptr_vector &readers,
+ ImportSettings &settings,
+ AbcObjectReader::ptr_vector &r_assign_as_parent)
+{
+ const std::string &full_name = object.getFullName();
+
+ if (!object.valid()) {
+ std::cerr << " - " << full_name << ": object is invalid, skipping it and all its children.\n";
+ return std::make_pair(false, static_cast<AbcObjectReader *>(NULL));
+ }
+
+ /* The interpretation of data by the children determine the role of this
+ * object. This is especially important for Xform objects, as they can be
+ * either part of a Blender object or a Blender object (Empty) themselves.
+ */
+ size_t children_claiming_this_object = 0;
+ size_t num_children = object.getNumChildren();
+ AbcObjectReader::ptr_vector claiming_child_readers;
+ AbcObjectReader::ptr_vector nonclaiming_child_readers;
+ AbcObjectReader::ptr_vector assign_as_parent;
+ for (size_t i = 0; i < num_children; i++) {
+ const IObject ichild = object.getChild(i);
+
+ /* TODO: When we only support C++11, use std::tie() instead. */
+ std::pair<bool, AbcObjectReader *> child_result;
+ child_result = visit_object(ichild, readers, settings, assign_as_parent);
+
+ bool child_claims_this_object = child_result.first;
+ AbcObjectReader *child_reader = child_result.second;
+
+ if (child_reader == NULL) {
+ BLI_assert(!child_claims_this_object);
+ }
+ else {
+ if (child_claims_this_object) {
+ claiming_child_readers.push_back(child_reader);
+ }
+ else {
+ nonclaiming_child_readers.push_back(child_reader);
+ }
+ }
+
+ children_claiming_this_object += child_claims_this_object ? 1 : 0;
+ }
+ BLI_assert(children_claiming_this_object == claiming_child_readers.size());
+
+ AbcObjectReader *reader = NULL;
+ const MetaData &md = object.getMetaData();
+ bool parent_is_part_of_this_object = false;
+
+ if (!object.getParent()) {
+ /* The root itself is not an object we should import. */
+ }
+ else if (IXform::matches(md)) {
+ bool create_empty;
+
+ /* An xform can either be a Blender Object (if it contains a mesh, for
+ * example), but it can also be an Empty. Its correct translation to
+ * Blender's data model depends on its children. */
+
+ /* Check whether or not this object is a Maya locator, which is
+ * similar to empties used as parent object in Blender. */
+ if (has_property(object.getProperties(), "locator")) {
+ create_empty = true;
+ }
+ else {
+ create_empty = claiming_child_readers.empty();
+ }
+
+ if (create_empty) {
+ reader = new AbcEmptyReader(object, settings);
+ }
+ }
+ else if (IPolyMesh::matches(md)) {
+ reader = new AbcMeshReader(object, settings);
+ parent_is_part_of_this_object = true;
+ }
+ else if (ISubD::matches(md)) {
+ reader = new AbcSubDReader(object, settings);
+ parent_is_part_of_this_object = true;
+ }
+ else if (INuPatch::matches(md)) {
+#ifdef USE_NURBS
+ /* TODO(kevin): importing cyclic NURBS from other software crashes
+ * at the moment. This is due to the fact that NURBS in other
+ * software have duplicated points which causes buffer overflows in
+ * Blender. Need to figure out exactly how these points are
+ * duplicated, in all cases (cyclic U, cyclic V, and cyclic UV).
+ * Until this is fixed, disabling NURBS reading. */
+ reader = new AbcNurbsReader(object, settings);
+ parent_is_part_of_this_object = true;
+#endif
+ }
+ else if (ICamera::matches(md)) {
+ reader = new AbcCameraReader(object, settings);
+ parent_is_part_of_this_object = true;
+ }
+ else if (IPoints::matches(md)) {
+ reader = new AbcPointsReader(object, settings);
+ parent_is_part_of_this_object = true;
+ }
+ else if (IMaterial::matches(md)) {
+ /* Pass for now. */
+ }
+ else if (ILight::matches(md)) {
+ /* Pass for now. */
+ }
+ else if (IFaceSet::matches(md)) {
+ /* Pass, those are handled in the mesh reader. */
+ }
+ else if (ICurves::matches(md)) {
+ reader = new AbcCurveReader(object, settings);
+ parent_is_part_of_this_object = true;
+ }
+ else {
+ std::cerr << "Alembic object " << full_name << " is of unsupported schema type '"
+ << object.getMetaData().get("schemaObjTitle") << "'" << std::endl;
+ }
+
+ if (reader) {
+ /* We have created a reader, which should imply that this object is
+ * not claimed as part of any child Alembic object. */
+ BLI_assert(claiming_child_readers.empty());
+
+ readers.push_back(reader);
+ reader->incref();
+
+ AlembicObjectPath *abc_path = static_cast<AlembicObjectPath *>(
+ MEM_callocN(sizeof(AlembicObjectPath), "AlembicObjectPath"));
+ BLI_strncpy(abc_path->path, full_name.c_str(), sizeof(abc_path->path));
+ BLI_addtail(&settings.cache_file->object_paths, abc_path);
+
+ /* We can now assign this reader as parent for our children. */
+ if (nonclaiming_child_readers.size() + assign_as_parent.size() > 0) {
+ for (AbcObjectReader *child_reader : nonclaiming_child_readers) {
+ child_reader->parent_reader = reader;
+ }
+ for (AbcObjectReader *child_reader : assign_as_parent) {
+ child_reader->parent_reader = reader;
+ }
+ }
+ }
+ else if (object.getParent()) {
+ if (claiming_child_readers.size() > 0) {
+ /* The first claiming child will serve just fine as parent to
+ * our non-claiming children. Since all claiming children share
+ * the same XForm, it doesn't really matter which one we pick. */
+ AbcObjectReader *claiming_child = claiming_child_readers[0];
+ for (AbcObjectReader *child_reader : nonclaiming_child_readers) {
+ child_reader->parent_reader = claiming_child;
+ }
+ for (AbcObjectReader *child_reader : assign_as_parent) {
+ child_reader->parent_reader = claiming_child;
+ }
+ /* Claiming children should have our parent set as their parent. */
+ for (AbcObjectReader *child_reader : claiming_child_readers) {
+ r_assign_as_parent.push_back(child_reader);
+ }
+ }
+ else {
+ /* This object isn't claimed by any child, and didn't produce
+ * a reader. Odd situation, could be the top Alembic object, or
+ * an unsupported Alembic schema. Delegate to our parent. */
+ for (AbcObjectReader *child_reader : claiming_child_readers) {
+ r_assign_as_parent.push_back(child_reader);
+ }
+ for (AbcObjectReader *child_reader : nonclaiming_child_readers) {
+ r_assign_as_parent.push_back(child_reader);
+ }
+ for (AbcObjectReader *child_reader : assign_as_parent) {
+ r_assign_as_parent.push_back(child_reader);
+ }
+ }
+ }
+
+ return std::make_pair(parent_is_part_of_this_object, reader);
+}
+
+enum {
+ ABC_NO_ERROR = 0,
+ ABC_ARCHIVE_FAIL,
+ ABC_UNSUPPORTED_HDF5,
+};
+
+struct ImportJobData {
+ Main *bmain;
+ Scene *scene;
+ ViewLayer *view_layer;
+ wmWindowManager *wm;
+
+ char filename[1024];
+ ImportSettings settings;
+
+ ArchiveReader *archive;
+ std::vector<AbcObjectReader *> readers;
+
+ short *stop;
+ short *do_update;
+ float *progress;
+
+ char error_code;
+ bool was_cancelled;
+ bool import_ok;
+};
+
+static void import_startjob(void *user_data, short *stop, short *do_update, float *progress)
+{
+ SCOPE_TIMER("Alembic import, objects reading and creation");
+
+ ImportJobData *data = static_cast<ImportJobData *>(user_data);
+
+ data->stop = stop;
+ data->do_update = do_update;
+ data->progress = progress;
+
+ WM_set_locked_interface(data->wm, true);
+
+ ArchiveReader *archive = new ArchiveReader(data->bmain, data->filename);
+
+ if (!archive->valid()) {
+#ifndef WITH_ALEMBIC_HDF5
+ data->error_code = archive->is_hdf5() ? ABC_UNSUPPORTED_HDF5 : ABC_ARCHIVE_FAIL;
+#else
+ data->error_code = ABC_ARCHIVE_FAIL;
+#endif
+ delete archive;
+ return;
+ }
+
+ CacheFile *cache_file = static_cast<CacheFile *>(
+ BKE_cachefile_add(data->bmain, BLI_path_basename(data->filename)));
+
+ /* Decrement the ID ref-count because it is going to be incremented for each
+ * modifier and constraint that it will be attached to, so since currently
+ * it is not used by anyone, its use count will off by one. */
+ id_us_min(&cache_file->id);
+
+ cache_file->is_sequence = data->settings.is_sequence;
+ cache_file->scale = data->settings.scale;
+ STRNCPY(cache_file->filepath, data->filename);
+
+ data->archive = archive;
+ data->settings.cache_file = cache_file;
+
+ *data->do_update = true;
+ *data->progress = 0.05f;
+
+ /* Parse Alembic Archive. */
+ AbcObjectReader::ptr_vector assign_as_parent;
+ visit_object(archive->getTop(), data->readers, data->settings, assign_as_parent);
+
+ /* There shouldn't be any orphans. */
+ BLI_assert(assign_as_parent.size() == 0);
+
+ if (G.is_break) {
+ data->was_cancelled = true;
+ return;
+ }
+
+ *data->do_update = true;
+ *data->progress = 0.1f;
+
+ /* Create objects and set scene frame range. */
+
+ const float size = static_cast<float>(data->readers.size());
+ size_t i = 0;
+
+ chrono_t min_time = std::numeric_limits<chrono_t>::max();
+ chrono_t max_time = std::numeric_limits<chrono_t>::min();
+
+ ISampleSelector sample_sel(0.0f);
+ std::vector<AbcObjectReader *>::iterator iter;
+ for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) {
+ AbcObjectReader *reader = *iter;
+
+ if (reader->valid()) {
+ reader->readObjectData(data->bmain, sample_sel);
+
+ min_time = std::min(min_time, reader->minTime());
+ max_time = std::max(max_time, reader->maxTime());
+ }
+ else {
+ std::cerr << "Object " << reader->name() << " in Alembic file " << data->filename
+ << " is invalid.\n";
+ }
+
+ *data->progress = 0.1f + 0.3f * (++i / size);
+ *data->do_update = true;
+
+ if (G.is_break) {
+ data->was_cancelled = true;
+ return;
+ }
+ }
+
+ if (data->settings.set_frame_range) {
+ Scene *scene = data->scene;
+
+ if (data->settings.is_sequence) {
+ SFRA = data->settings.sequence_offset;
+ EFRA = SFRA + (data->settings.sequence_len - 1);
+ CFRA = SFRA;
+ }
+ else if (min_time < max_time) {
+ SFRA = static_cast<int>(round(min_time * FPS));
+ EFRA = static_cast<int>(round(max_time * FPS));
+ CFRA = SFRA;
+ }
+ }
+
+ /* Setup parenthood. */
+ for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) {
+ const AbcObjectReader *reader = *iter;
+ const AbcObjectReader *parent_reader = reader->parent_reader;
+ Object *ob = reader->object();
+
+ if (parent_reader == NULL || !reader->inherits_xform()) {
+ ob->parent = NULL;
+ }
+ else {
+ ob->parent = parent_reader->object();
+ }
+ }
+
+ /* Setup transformations and constraints. */
+ i = 0;
+ for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) {
+ AbcObjectReader *reader = *iter;
+ reader->setupObjectTransform(0.0f);
+
+ *data->progress = 0.7f + 0.3f * (++i / size);
+ *data->do_update = true;
+
+ if (G.is_break) {
+ data->was_cancelled = true;
+ return;
+ }
+ }
+}
+
+static void import_endjob(void *user_data)
+{
+ SCOPE_TIMER("Alembic import, cleanup");
+
+ ImportJobData *data = static_cast<ImportJobData *>(user_data);
+
+ std::vector<AbcObjectReader *>::iterator iter;
+
+ /* Delete objects on cancellation. */
+ if (data->was_cancelled) {
+ for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) {
+ Object *ob = (*iter)->object();
+
+ /* It's possible that cancellation occurred between the creation of
+ * the reader and the creation of the Blender object. */
+ if (ob == NULL) {
+ continue;
+ }
+
+ BKE_id_free_us(data->bmain, ob);
+ }
+ }
+ else {
+ /* Add object to scene. */
+ Base *base;
+ LayerCollection *lc;
+ ViewLayer *view_layer = data->view_layer;
+
+ BKE_view_layer_base_deselect_all(view_layer);
+
+ lc = BKE_layer_collection_get_active(view_layer);
+
+ for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) {
+ Object *ob = (*iter)->object();
+
+ BKE_collection_object_add(data->bmain, lc->collection, ob);
+
+ base = BKE_view_layer_base_find(view_layer, ob);
+ /* TODO: is setting active needed? */
+ BKE_view_layer_base_select_and_set_active(view_layer, base);
+
+ DEG_id_tag_update(&lc->collection->id, ID_RECALC_COPY_ON_WRITE);
+ DEG_id_tag_update_ex(data->bmain,
+ &ob->id,
+ ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION |
+ ID_RECALC_BASE_FLAGS);
+ }
+
+ DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS);
+ DEG_relations_tag_update(data->bmain);
+ }
+
+ for (iter = data->readers.begin(); iter != data->readers.end(); ++iter) {
+ AbcObjectReader *reader = *iter;
+ reader->decref();
+
+ if (reader->refcount() == 0) {
+ delete reader;
+ }
+ }
+
+ WM_set_locked_interface(data->wm, false);
+
+ switch (data->error_code) {
+ default:
+ case ABC_NO_ERROR:
+ data->import_ok = !data->was_cancelled;
+ break;
+ case ABC_ARCHIVE_FAIL:
+ WM_report(RPT_ERROR, "Could not open Alembic archive for reading! See console for detail.");
+ break;
+ case ABC_UNSUPPORTED_HDF5:
+ WM_report(RPT_ERROR, "Alembic archive in obsolete HDF5 format is not supported.");
+ break;
+ }
+
+ WM_main_add_notifier(NC_SCENE | ND_FRAME, data->scene);
+}
+
+static void import_freejob(void *user_data)
+{
+ ImportJobData *data = static_cast<ImportJobData *>(user_data);
+ delete data->archive;
+ delete data;
+}
+
+bool ABC_import(bContext *C,
+ const char *filepath,
+ float scale,
+ bool is_sequence,
+ bool set_frame_range,
+ int sequence_len,
+ int offset,
+ bool validate_meshes,
+ bool as_background_job)
+{
+ /* Using new here since MEM_* funcs do not call ctor to properly initialize
+ * data. */
+ ImportJobData *job = new ImportJobData();
+ job->bmain = CTX_data_main(C);
+ job->scene = CTX_data_scene(C);
+ job->view_layer = CTX_data_view_layer(C);
+ job->wm = CTX_wm_manager(C);
+ job->import_ok = false;
+ BLI_strncpy(job->filename, filepath, 1024);
+
+ job->settings.scale = scale;
+ job->settings.is_sequence = is_sequence;
+ job->settings.set_frame_range = set_frame_range;
+ job->settings.sequence_len = sequence_len;
+ job->settings.sequence_offset = offset;
+ job->settings.validate_meshes = validate_meshes;
+ job->error_code = ABC_NO_ERROR;
+ job->was_cancelled = false;
+ job->archive = NULL;
+
+ G.is_break = false;
+
+ bool import_ok = false;
+ if (as_background_job) {
+ wmJob *wm_job = WM_jobs_get(CTX_wm_manager(C),
+ CTX_wm_window(C),
+ job->scene,
+ "Alembic Import",
+ WM_JOB_PROGRESS,
+ WM_JOB_TYPE_ALEMBIC);
+
+ /* setup job */
+ WM_jobs_customdata_set(wm_job, job, import_freejob);
+ WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME);
+ WM_jobs_callbacks(wm_job, import_startjob, NULL, NULL, import_endjob);
+
+ WM_jobs_start(CTX_wm_manager(C), wm_job);
+ }
+ else {
+ /* Fake a job context, so that we don't need NULL pointer checks while importing. */
+ short stop = 0, do_update = 0;
+ float progress = 0.f;
+
+ import_startjob(job, &stop, &do_update, &progress);
+ import_endjob(job);
+ import_ok = job->import_ok;
+
+ import_freejob(job);
+ }
+
+ return import_ok;
+}
+
+/* ************************************************************************** */
+
+void ABC_get_transform(CacheReader *reader, float r_mat[4][4], float time, float scale)
+{
+ if (!reader) {
+ return;
+ }
+
+ AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
+
+ bool is_constant = false;
+ abc_reader->read_matrix(r_mat, time, scale, is_constant);
+}
+
+/* ************************************************************************** */
+
+static AbcObjectReader *get_abc_reader(CacheReader *reader, Object *ob, const char **err_str)
+{
+ AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
+ IObject iobject = abc_reader->iobject();
+
+ if (!iobject.valid()) {
+ *err_str = "Invalid object: verify object path";
+ return NULL;
+ }
+
+ const ObjectHeader &header = iobject.getHeader();
+ if (!abc_reader->accepts_object_type(header, ob, err_str)) {
+ /* err_str is set by acceptsObjectType() */
+ return NULL;
+ }
+
+ return abc_reader;
+}
+
+static ISampleSelector sample_selector_for_time(float time)
+{
+ /* kFloorIndex is used to be compatible with non-interpolating
+ * properties; they use the floor. */
+ return ISampleSelector(time, ISampleSelector::kFloorIndex);
+}
+
+Mesh *ABC_read_mesh(CacheReader *reader,
+ Object *ob,
+ Mesh *existing_mesh,
+ const float time,
+ const char **err_str,
+ int read_flag)
+{
+ AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str);
+ if (abc_reader == NULL) {
+ return NULL;
+ }
+
+ ISampleSelector sample_sel = sample_selector_for_time(time);
+ return abc_reader->read_mesh(existing_mesh, sample_sel, read_flag, err_str);
+}
+
+bool ABC_mesh_topology_changed(
+ CacheReader *reader, Object *ob, Mesh *existing_mesh, const float time, const char **err_str)
+{
+ AbcObjectReader *abc_reader = get_abc_reader(reader, ob, err_str);
+ if (abc_reader == NULL) {
+ return false;
+ }
+
+ ISampleSelector sample_sel = sample_selector_for_time(time);
+ return abc_reader->topology_changed(existing_mesh, sample_sel);
+}
+
+/* ************************************************************************** */
+
+void CacheReader_free(CacheReader *reader)
+{
+ AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
+ abc_reader->decref();
+
+ if (abc_reader->refcount() == 0) {
+ delete abc_reader;
+ }
+}
+
+void CacheReader_incref(CacheReader *reader)
+{
+ AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
+ abc_reader->incref();
+}
+
+CacheReader *CacheReader_open_alembic_object(AbcArchiveHandle *handle,
+ CacheReader *reader,
+ Object *object,
+ const char *object_path)
+{
+ if (object_path[0] == '\0') {
+ return reader;
+ }
+
+ ArchiveReader *archive = archive_from_handle(handle);
+
+ if (!archive || !archive->valid()) {
+ return reader;
+ }
+
+ IObject iobject;
+ find_iobject(archive->getTop(), iobject, object_path);
+
+ if (reader) {
+ CacheReader_free(reader);
+ }
+
+ ImportSettings settings;
+ AbcObjectReader *abc_reader = create_reader(iobject, settings);
+ if (abc_reader == NULL) {
+ /* This object is not supported */
+ return NULL;
+ }
+ abc_reader->object(object);
+ abc_reader->incref();
+
+ return reinterpret_cast<CacheReader *>(abc_reader);
+}
diff --git a/source/blender/io/avi/AVI_avi.h b/source/blender/io/avi/AVI_avi.h
new file mode 100644
index 00000000000..4f3aa720da3
--- /dev/null
+++ b/source/blender/io/avi/AVI_avi.h
@@ -0,0 +1,299 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ *
+ * \section avi_about About the AVI module
+ *
+ * This is external code. It provides avi file import/export and
+ * conversions. It has been adapted to make use of Blender memory
+ * management functions, and because of this it needs module
+ * blenlib. You need to provide this lib when linking with libavi.a .
+ *
+ * \subsection avi_issues Known issues with AVI
+ *
+ * - avi uses #MEM_mallocN, #MEM_freeN from blenlib.
+ * - Not all functions that are used externally are properly
+ * prototyped.
+ *
+ * This header has not been split, since it interleaves type defines
+ * and functions. You would need the types to be able to include the
+ * function headers anyway. And, after all, it is someone else's
+ * code. So we keep it like this.
+ */
+
+#ifndef __AVI_AVI_H__
+#define __AVI_AVI_H__
+
+#include "BLI_sys_types.h"
+#include <stdio.h> /* for FILE */
+
+typedef struct _AviChunk {
+ int fcc;
+ int size;
+} AviChunk;
+
+typedef struct _AviList {
+ int fcc;
+ int size;
+ int ids;
+} AviList;
+
+typedef struct _AviMainHeader {
+ int fcc;
+ int size;
+ int MicroSecPerFrame; /* MicroSecPerFrame - timing between frames */
+ int MaxBytesPerSec; /* MaxBytesPerSec - approx bps system must handle */
+ int PaddingGranularity;
+ int Flags;
+
+ /** had idx1 chunk */
+#define AVIF_HASINDEX 0x00000010
+ /** must use idx1 chunk to determine order */
+#define AVIF_MUSTUSEINDEX 0x00000020
+ /** AVI file is interleaved */
+#define AVIF_ISINTERLEAVED 0x00000100
+#define AVIF_TRUSTCKTYPE 0x00000800
+ /** specially allocated used for capturing real time video */
+#define AVIF_WASCAPTUREFILE 0x00010000
+ /** contains copyrighted data */
+#define AVIF_COPYRIGHTED 0x00020000
+
+ int TotalFrames;
+ int InitialFrames; /* InitialFrames - initial frame before interleaving */
+ int Streams;
+ int SuggestedBufferSize;
+ int Width;
+ int Height;
+ int Reserved[4];
+} AviMainHeader;
+
+typedef struct _AviStreamHeader {
+ int fcc;
+ int size;
+ int Type;
+#define AVIST_VIDEO FCC("vids")
+#define AVIST_AUDIO FCC("auds")
+#define AVIST_MIDI FCC("mids")
+#define AVIST_TEXT FCC("txts")
+
+ int Handler;
+ int Flags;
+#define AVISF_DISABLED 0x00000001
+#define AVISF_VIDEO_PALCHANGES 0x00010000
+
+ short Priority;
+ short Language;
+ int InitialFrames;
+ int Scale;
+ int Rate;
+ int Start;
+ int Length;
+ int SuggestedBufferSize;
+ int Quality;
+ int SampleSize;
+ short left;
+ short top;
+ short right;
+ short bottom;
+} AviStreamHeader;
+
+typedef struct _AviBitmapInfoHeader {
+ int fcc;
+ int size;
+ int Size;
+ int Width;
+ int Height;
+ short Planes;
+ short BitCount;
+ int Compression;
+ int SizeImage;
+ int XPelsPerMeter;
+ int YPelsPerMeter;
+ int ClrUsed;
+ int ClrImportant;
+} AviBitmapInfoHeader;
+
+typedef struct _AviMJPEGUnknown {
+ int a;
+ int b;
+ int c;
+ int d;
+ int e;
+ int f;
+ int g;
+} AviMJPEGUnknown;
+
+typedef struct _AviIndexEntry {
+ int ChunkId;
+ int Flags;
+#define AVIIF_LIST 0x00000001
+#define AVIIF_KEYFRAME 0x00000010
+#define AVIIF_NO_TIME 0x00000100
+#define AVIIF_COMPRESSOR 0x0FFF0000
+ int Offset;
+ int Size;
+} AviIndexEntry;
+
+typedef struct _AviIndex {
+ int fcc;
+ int size;
+ AviIndexEntry *entrys;
+} AviIndex;
+
+typedef enum {
+ /** The most basic of forms, 3 bytes per pixel, 1 per r, g, b. */
+ AVI_FORMAT_RGB24,
+ /** The second most basic of forms, 4 bytes per pixel, 1 per r, g, b, alpha. */
+ AVI_FORMAT_RGB32,
+ /** Same as above, but is in the weird AVI order (bottom to top, left to right). */
+ AVI_FORMAT_AVI_RGB,
+ /** Motion-JPEG. */
+ AVI_FORMAT_MJPEG,
+} AviFormat;
+
+typedef struct _AviStreamRec {
+ AviStreamHeader sh;
+ void *sf;
+ int sf_size;
+ AviFormat format;
+} AviStreamRec;
+
+typedef struct _AviMovie {
+ FILE *fp;
+
+ int type;
+#define AVI_MOVIE_READ 0
+#define AVI_MOVIE_WRITE 1
+
+ int64_t size;
+
+ AviMainHeader *header;
+ AviStreamRec *streams;
+ AviIndexEntry *entries;
+ int index_entries;
+
+ int64_t movi_offset;
+ int64_t read_offset;
+ int64_t *offset_table;
+
+ /* Local data goes here */
+ int interlace;
+ int odd_fields;
+} AviMovie;
+
+typedef enum {
+ AVI_ERROR_NONE = 0,
+ AVI_ERROR_COMPRESSION,
+ AVI_ERROR_OPEN,
+ AVI_ERROR_READING,
+ AVI_ERROR_WRITING,
+ AVI_ERROR_FORMAT,
+ AVI_ERROR_ALLOC,
+ AVI_ERROR_FOUND,
+ AVI_ERROR_OPTION,
+} AviError;
+
+/* belongs to the option-setting function. */
+typedef enum {
+ AVI_OPTION_WIDTH = 0,
+ AVI_OPTION_HEIGHT,
+ AVI_OPTION_QUALITY,
+ AVI_OPTION_FRAMERATE,
+} AviOption;
+
+/* The offsets that will always stay the same in AVI files we
+ * write... used to seek around to the places where we need to write
+ * the sizes */
+
+#define AVI_RIFF_SOFF 4L
+#define AVI_HDRL_SOFF 16L
+
+/**
+ * This is a sort of MAKE_ID thing. Used in imbuf :( It is used
+ * through options in the AVI header (AviStreamHeader). */
+#define FCC(ch4) (ch4[0] | ch4[1] << 8 | ch4[2] << 16 | ch4[3] << 24)
+
+/**
+ * Test whether this is an avi-format.
+ */
+bool AVI_is_avi(const char *name);
+
+/**
+ * Open a compressed file, decompress it into memory.
+ */
+AviError AVI_open_compress(char *name, AviMovie *movie, int streams, ...);
+
+/**
+ * Finalize a compressed output stream.
+ */
+AviError AVI_close_compress(AviMovie *movie);
+
+/**
+ * Choose a compression option for \<movie\>. Possible options are
+ * AVI_OPTION_TYPE_MAIN, AVI_OPTION_TYPE_STRH, AVI_OPTION_TYPE_STRF
+ */
+AviError AVI_set_compress_option(
+ AviMovie *movie, int option_type, int stream, AviOption option, void *opt_data);
+/* Hmmm... there should be some explanation about what these mean */
+/**
+ * Compression option, for use in avi_set_compress_option
+ */
+#define AVI_OPTION_TYPE_MAIN 0
+/**
+ * Compression option, for use in avi_set_compress_option
+ */
+#define AVI_OPTION_TYPE_STRH 1
+/**
+ * Compression option, for use in avi_set_compress_option
+ */
+#define AVI_OPTION_TYPE_STRF 2
+
+/**
+ * Direct the streams \<avist_type\> to \<movie\>. Redirect \<stream_num\>
+ * streams.
+ */
+int AVI_get_stream(AviMovie *movie, int avist_type, int stream_num);
+
+/**
+ * Open a movie stream from file.
+ */
+AviError AVI_open_movie(const char *name, AviMovie *movie);
+
+/**
+ * Read a frame from a movie stream.
+ */
+void *AVI_read_frame(AviMovie *movie, AviFormat format, int frame, int stream);
+/**
+ * Close an open movie stream.
+ */
+AviError AVI_close(AviMovie *movie);
+
+/**
+ * Write frames to a movie stream.
+ */
+AviError AVI_write_frame(AviMovie *movie, int frame_num, ...);
+
+/**
+ * Unused but still external
+ */
+AviError AVI_print_error(AviError error);
+
+#endif /* __AVI_AVI_H__ */
diff --git a/source/blender/io/avi/CMakeLists.txt b/source/blender/io/avi/CMakeLists.txt
new file mode 100644
index 00000000000..76c90353673
--- /dev/null
+++ b/source/blender/io/avi/CMakeLists.txt
@@ -0,0 +1,53 @@
+# ***** 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) 2006, Blender Foundation
+# All rights reserved.
+# ***** END GPL LICENSE BLOCK *****
+
+set(INC
+ .
+ ../../blenlib
+ ../../imbuf
+ ../../../../intern/guardedalloc
+)
+
+set(INC_SYS
+ ${JPEG_INCLUDE_DIR}
+)
+
+set(SRC
+ intern/avi.c
+ intern/avi_codecs.c
+ intern/avi_endian.c
+ intern/avi_mjpeg.c
+ intern/avi_options.c
+ intern/avi_rgb.c
+ intern/avi_rgb32.c
+
+ AVI_avi.h
+ intern/avi_endian.h
+ intern/avi_intern.h
+ intern/avi_mjpeg.h
+ intern/avi_rgb.h
+ intern/avi_rgb32.h
+)
+
+set(LIB
+ ${JPEG_LIBRARIES}
+)
+
+blender_add_lib(bf_avi "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/io/avi/intern/avi.c b/source/blender/io/avi/intern/avi.c
new file mode 100644
index 00000000000..22eb0be0cc0
--- /dev/null
+++ b/source/blender/io/avi/intern/avi.c
@@ -0,0 +1,1056 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ *
+ * This is external code.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#ifdef WIN32
+# include "BLI_winstuff.h"
+#endif
+
+#include "MEM_guardedalloc.h"
+
+#include "BLI_sys_types.h"
+#include "BLI_utildefines.h"
+#include "BLI_fileops.h"
+
+#include "AVI_avi.h"
+#include "avi_intern.h"
+
+#include "avi_endian.h"
+
+static int AVI_DEBUG = 0;
+static char DEBUG_FCC[4];
+
+#define DEBUG_PRINT(x) \
+ if (AVI_DEBUG) { \
+ printf("AVI DEBUG: " x); \
+ } \
+ (void)0
+
+/* local functions */
+char *fcc_to_char(unsigned int fcc);
+char *tcc_to_char(unsigned int tcc);
+
+/* implementation */
+
+unsigned int GET_FCC(FILE *fp)
+{
+ unsigned char tmp[4];
+
+ tmp[0] = getc(fp);
+ tmp[1] = getc(fp);
+ tmp[2] = getc(fp);
+ tmp[3] = getc(fp);
+
+ return FCC(tmp);
+}
+
+unsigned int GET_TCC(FILE *fp)
+{
+ char tmp[5];
+
+ tmp[0] = getc(fp);
+ tmp[1] = getc(fp);
+ tmp[2] = 0;
+ tmp[3] = 0;
+
+ return FCC(tmp);
+}
+
+char *fcc_to_char(unsigned int fcc)
+{
+ DEBUG_FCC[0] = (fcc)&127;
+ DEBUG_FCC[1] = (fcc >> 8) & 127;
+ DEBUG_FCC[2] = (fcc >> 16) & 127;
+ DEBUG_FCC[3] = (fcc >> 24) & 127;
+
+ return DEBUG_FCC;
+}
+
+char *tcc_to_char(unsigned int tcc)
+{
+ DEBUG_FCC[0] = (tcc)&127;
+ DEBUG_FCC[1] = (tcc >> 8) & 127;
+ DEBUG_FCC[2] = 0;
+ DEBUG_FCC[3] = 0;
+
+ return DEBUG_FCC;
+}
+
+int AVI_get_stream(AviMovie *movie, int avist_type, int stream_num)
+{
+ int cur_stream;
+
+ if (movie == NULL) {
+ return -AVI_ERROR_OPTION;
+ }
+
+ for (cur_stream = 0; cur_stream < movie->header->Streams; cur_stream++) {
+ if (movie->streams[cur_stream].sh.Type == avist_type) {
+ if (stream_num == 0) {
+ return cur_stream;
+ }
+ else {
+ stream_num--;
+ }
+ }
+ }
+
+ return -AVI_ERROR_FOUND;
+}
+
+static int fcc_get_stream(int fcc)
+{
+ char fccs[4];
+
+ fccs[0] = fcc;
+ fccs[1] = fcc >> 8;
+ fccs[2] = fcc >> 16;
+ fccs[3] = fcc >> 24;
+
+ return 10 * (fccs[0] - '0') + (fccs[1] - '0');
+}
+
+static bool fcc_is_data(int fcc)
+{
+ char fccs[4];
+
+ fccs[0] = fcc;
+ fccs[1] = fcc >> 8;
+ fccs[2] = fcc >> 16;
+ fccs[3] = fcc >> 24;
+
+ if (!isdigit(fccs[0]) || !isdigit(fccs[1]) || (fccs[2] != 'd' && fccs[2] != 'w')) {
+ return 0;
+ }
+ if (fccs[3] != 'b' && fccs[3] != 'c') {
+ return 0;
+ }
+
+ return 1;
+}
+
+AviError AVI_print_error(AviError in_error)
+{
+ int error;
+
+ if ((int)in_error < 0) {
+ error = -in_error;
+ }
+ else {
+ error = in_error;
+ }
+
+ switch (error) {
+ case AVI_ERROR_NONE:
+ break;
+ case AVI_ERROR_COMPRESSION:
+ printf("AVI ERROR: compressed in an unsupported format\n");
+ break;
+ case AVI_ERROR_OPEN:
+ printf("AVI ERROR: could not open file\n");
+ break;
+ case AVI_ERROR_READING:
+ printf("AVI ERROR: could not read from file\n");
+ break;
+ case AVI_ERROR_WRITING:
+ printf("AVI ERROR: could not write to file\n");
+ break;
+ case AVI_ERROR_FORMAT:
+ printf("AVI ERROR: file is in an illegal or unrecognized format\n");
+ break;
+ case AVI_ERROR_ALLOC:
+ printf("AVI ERROR: error encountered while allocating memory\n");
+ break;
+ case AVI_ERROR_OPTION:
+ printf("AVI ERROR: program made illegal request\n");
+ break;
+ case AVI_ERROR_FOUND:
+ printf("AVI ERROR: movie did not contain expected item\n");
+ break;
+ default:
+ break;
+ }
+
+ return in_error;
+}
+
+bool AVI_is_avi(const char *name)
+{
+ int temp, fcca, j;
+ AviMovie movie = {NULL};
+ AviMainHeader header;
+ AviBitmapInfoHeader bheader;
+ int movie_tracks = 0;
+
+ DEBUG_PRINT("opening movie\n");
+
+ movie.type = AVI_MOVIE_READ;
+ movie.fp = BLI_fopen(name, "rb");
+ movie.offset_table = NULL;
+
+ if (movie.fp == NULL) {
+ return 0;
+ }
+
+ if (GET_FCC(movie.fp) != FCC("RIFF") || !(movie.size = GET_FCC(movie.fp))) {
+ fclose(movie.fp);
+ return 0;
+ }
+
+ movie.header = &header;
+
+ if (GET_FCC(movie.fp) != FCC("AVI ") || GET_FCC(movie.fp) != FCC("LIST") || !GET_FCC(movie.fp) ||
+ GET_FCC(movie.fp) != FCC("hdrl") || (movie.header->fcc = GET_FCC(movie.fp)) != FCC("avih") ||
+ !(movie.header->size = GET_FCC(movie.fp))) {
+ DEBUG_PRINT("bad initial header info\n");
+ fclose(movie.fp);
+ return 0;
+ }
+
+ movie.header->MicroSecPerFrame = GET_FCC(movie.fp);
+ movie.header->MaxBytesPerSec = GET_FCC(movie.fp);
+ movie.header->PaddingGranularity = GET_FCC(movie.fp);
+ movie.header->Flags = GET_FCC(movie.fp);
+ movie.header->TotalFrames = GET_FCC(movie.fp);
+ movie.header->InitialFrames = GET_FCC(movie.fp);
+ movie.header->Streams = GET_FCC(movie.fp);
+ movie.header->SuggestedBufferSize = GET_FCC(movie.fp);
+ movie.header->Width = GET_FCC(movie.fp);
+ movie.header->Height = GET_FCC(movie.fp);
+ movie.header->Reserved[0] = GET_FCC(movie.fp);
+ movie.header->Reserved[1] = GET_FCC(movie.fp);
+ movie.header->Reserved[2] = GET_FCC(movie.fp);
+ movie.header->Reserved[3] = GET_FCC(movie.fp);
+
+ fseek(movie.fp, movie.header->size - 14 * 4, SEEK_CUR);
+
+ /* Limit number of streams to some reasonable amount to prevent
+ * buffer overflow vulnerabilities. */
+ if (movie.header->Streams < 1 || movie.header->Streams > 65536) {
+ DEBUG_PRINT("Number of streams should be in range 1-65536\n");
+ fclose(movie.fp);
+ return 0;
+ }
+
+ movie.streams = (AviStreamRec *)MEM_calloc_arrayN(
+ movie.header->Streams, sizeof(AviStreamRec), "moviestreams");
+
+ for (temp = 0; temp < movie.header->Streams; temp++) {
+
+ if (GET_FCC(movie.fp) != FCC("LIST") || !GET_FCC(movie.fp) ||
+ GET_FCC(movie.fp) != FCC("strl") ||
+ (movie.streams[temp].sh.fcc = GET_FCC(movie.fp)) != FCC("strh") ||
+ !(movie.streams[temp].sh.size = GET_FCC(movie.fp))) {
+ DEBUG_PRINT("bad stream header information\n");
+
+ MEM_freeN(movie.streams);
+ fclose(movie.fp);
+ return 0;
+ }
+
+ movie.streams[temp].sh.Type = GET_FCC(movie.fp);
+ movie.streams[temp].sh.Handler = GET_FCC(movie.fp);
+
+ fcca = movie.streams[temp].sh.Handler;
+
+ if (movie.streams[temp].sh.Type == FCC("vids")) {
+ if (fcca == FCC("DIB ") || fcca == FCC("RGB ") || fcca == FCC("rgb ") ||
+ fcca == FCC("RAW ") || fcca == 0) {
+ movie.streams[temp].format = AVI_FORMAT_AVI_RGB;
+ }
+ else if (fcca == FCC("mjpg") || fcca == FCC("MJPG")) {
+ movie.streams[temp].format = AVI_FORMAT_MJPEG;
+ }
+ else {
+ MEM_freeN(movie.streams);
+ fclose(movie.fp);
+ return 0;
+ }
+ movie_tracks++;
+ }
+
+ movie.streams[temp].sh.Flags = GET_FCC(movie.fp);
+ movie.streams[temp].sh.Priority = GET_TCC(movie.fp);
+ movie.streams[temp].sh.Language = GET_TCC(movie.fp);
+ movie.streams[temp].sh.InitialFrames = GET_FCC(movie.fp);
+ movie.streams[temp].sh.Scale = GET_FCC(movie.fp);
+ movie.streams[temp].sh.Rate = GET_FCC(movie.fp);
+ movie.streams[temp].sh.Start = GET_FCC(movie.fp);
+ movie.streams[temp].sh.Length = GET_FCC(movie.fp);
+ movie.streams[temp].sh.SuggestedBufferSize = GET_FCC(movie.fp);
+ movie.streams[temp].sh.Quality = GET_FCC(movie.fp);
+ movie.streams[temp].sh.SampleSize = GET_FCC(movie.fp);
+ movie.streams[temp].sh.left = GET_TCC(movie.fp);
+ movie.streams[temp].sh.top = GET_TCC(movie.fp);
+ movie.streams[temp].sh.right = GET_TCC(movie.fp);
+ movie.streams[temp].sh.bottom = GET_TCC(movie.fp);
+
+ fseek(movie.fp, movie.streams[temp].sh.size - 14 * 4, SEEK_CUR);
+
+ if (GET_FCC(movie.fp) != FCC("strf")) {
+ DEBUG_PRINT("no stream format information\n");
+ MEM_freeN(movie.streams);
+ fclose(movie.fp);
+ return 0;
+ }
+
+ movie.streams[temp].sf_size = GET_FCC(movie.fp);
+ if (movie.streams[temp].sh.Type == FCC("vids")) {
+ j = movie.streams[temp].sf_size - (sizeof(AviBitmapInfoHeader) - 8);
+ if (j >= 0) {
+ AviBitmapInfoHeader *bi;
+
+ movie.streams[temp].sf = &bheader;
+ bi = (AviBitmapInfoHeader *)movie.streams[temp].sf;
+
+ bi->fcc = FCC("strf");
+ bi->size = movie.streams[temp].sf_size;
+ bi->Size = GET_FCC(movie.fp);
+ bi->Width = GET_FCC(movie.fp);
+ bi->Height = GET_FCC(movie.fp);
+ bi->Planes = GET_TCC(movie.fp);
+ bi->BitCount = GET_TCC(movie.fp);
+ bi->Compression = GET_FCC(movie.fp);
+ bi->SizeImage = GET_FCC(movie.fp);
+ bi->XPelsPerMeter = GET_FCC(movie.fp);
+ bi->YPelsPerMeter = GET_FCC(movie.fp);
+ bi->ClrUsed = GET_FCC(movie.fp);
+ bi->ClrImportant = GET_FCC(movie.fp);
+
+ fcca = bi->Compression;
+
+ if (movie.streams[temp].format == AVI_FORMAT_AVI_RGB) {
+ if (fcca == FCC("DIB ") || fcca == FCC("RGB ") || fcca == FCC("rgb ") ||
+ fcca == FCC("RAW ") || fcca == 0) {
+ /* pass */
+ }
+ else if (fcca == FCC("mjpg") || fcca == FCC("MJPG")) {
+ movie.streams[temp].format = AVI_FORMAT_MJPEG;
+ }
+ else {
+ MEM_freeN(movie.streams);
+ fclose(movie.fp);
+ return 0;
+ }
+ }
+ }
+ if (j > 0) {
+ fseek(movie.fp, j, SEEK_CUR);
+ }
+ }
+ else {
+ fseek(movie.fp, movie.streams[temp].sf_size, SEEK_CUR);
+ }
+
+ /* Walk to the next LIST */
+ while (GET_FCC(movie.fp) != FCC("LIST")) {
+ temp = GET_FCC(movie.fp);
+ if (temp < 0 || ftell(movie.fp) > movie.size) {
+ DEBUG_PRINT("incorrect size in header or error in AVI\n");
+
+ MEM_freeN(movie.streams);
+ fclose(movie.fp);
+ return 0;
+ }
+ fseek(movie.fp, temp, SEEK_CUR);
+ }
+
+ fseek(movie.fp, -4L, SEEK_CUR);
+ }
+
+ MEM_freeN(movie.streams);
+ fclose(movie.fp);
+
+ /* at least one video track is needed */
+ return (movie_tracks != 0);
+}
+
+AviError AVI_open_movie(const char *name, AviMovie *movie)
+{
+ int temp, fcca, size, j;
+
+ DEBUG_PRINT("opening movie\n");
+
+ memset(movie, 0, sizeof(AviMovie));
+
+ movie->type = AVI_MOVIE_READ;
+ movie->fp = BLI_fopen(name, "rb");
+ movie->offset_table = NULL;
+
+ if (movie->fp == NULL) {
+ return AVI_ERROR_OPEN;
+ }
+
+ if (GET_FCC(movie->fp) != FCC("RIFF") || !(movie->size = GET_FCC(movie->fp))) {
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->header = (AviMainHeader *)MEM_mallocN(sizeof(AviMainHeader), "movieheader");
+
+ if (GET_FCC(movie->fp) != FCC("AVI ") || GET_FCC(movie->fp) != FCC("LIST") ||
+ !GET_FCC(movie->fp) || GET_FCC(movie->fp) != FCC("hdrl") ||
+ (movie->header->fcc = GET_FCC(movie->fp)) != FCC("avih") ||
+ !(movie->header->size = GET_FCC(movie->fp))) {
+ DEBUG_PRINT("bad initial header info\n");
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->header->MicroSecPerFrame = GET_FCC(movie->fp);
+ movie->header->MaxBytesPerSec = GET_FCC(movie->fp);
+ movie->header->PaddingGranularity = GET_FCC(movie->fp);
+ movie->header->Flags = GET_FCC(movie->fp);
+ movie->header->TotalFrames = GET_FCC(movie->fp);
+ movie->header->InitialFrames = GET_FCC(movie->fp);
+ movie->header->Streams = GET_FCC(movie->fp);
+ movie->header->SuggestedBufferSize = GET_FCC(movie->fp);
+ movie->header->Width = GET_FCC(movie->fp);
+ movie->header->Height = GET_FCC(movie->fp);
+ movie->header->Reserved[0] = GET_FCC(movie->fp);
+ movie->header->Reserved[1] = GET_FCC(movie->fp);
+ movie->header->Reserved[2] = GET_FCC(movie->fp);
+ movie->header->Reserved[3] = GET_FCC(movie->fp);
+
+ fseek(movie->fp, movie->header->size - 14 * 4, SEEK_CUR);
+
+ /* Limit number of streams to some reasonable amount to prevent
+ * buffer overflow vulnerabilities. */
+ if (movie->header->Streams < 1 || movie->header->Streams > 65536) {
+ DEBUG_PRINT("Number of streams should be in range 1-65536\n");
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->streams = (AviStreamRec *)MEM_calloc_arrayN(
+ movie->header->Streams, sizeof(AviStreamRec), "moviestreams");
+
+ for (temp = 0; temp < movie->header->Streams; temp++) {
+
+ if (GET_FCC(movie->fp) != FCC("LIST") || !GET_FCC(movie->fp) ||
+ GET_FCC(movie->fp) != FCC("strl") ||
+ (movie->streams[temp].sh.fcc = GET_FCC(movie->fp)) != FCC("strh") ||
+ !(movie->streams[temp].sh.size = GET_FCC(movie->fp))) {
+ DEBUG_PRINT("bad stream header information\n");
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->streams[temp].sh.Type = GET_FCC(movie->fp);
+ movie->streams[temp].sh.Handler = GET_FCC(movie->fp);
+
+ fcca = movie->streams[temp].sh.Handler;
+
+ if (movie->streams[temp].sh.Type == FCC("vids")) {
+ if (fcca == FCC("DIB ") || fcca == FCC("RGB ") || fcca == FCC("rgb ") ||
+ fcca == FCC("RAW ") || fcca == 0) {
+ movie->streams[temp].format = AVI_FORMAT_AVI_RGB;
+ }
+ else if (fcca == FCC("mjpg") || fcca == FCC("MJPG")) {
+ movie->streams[temp].format = AVI_FORMAT_MJPEG;
+ }
+ else {
+ return AVI_ERROR_COMPRESSION;
+ }
+ }
+
+ movie->streams[temp].sh.Flags = GET_FCC(movie->fp);
+ movie->streams[temp].sh.Priority = GET_TCC(movie->fp);
+ movie->streams[temp].sh.Language = GET_TCC(movie->fp);
+ movie->streams[temp].sh.InitialFrames = GET_FCC(movie->fp);
+ movie->streams[temp].sh.Scale = GET_FCC(movie->fp);
+ movie->streams[temp].sh.Rate = GET_FCC(movie->fp);
+ movie->streams[temp].sh.Start = GET_FCC(movie->fp);
+ movie->streams[temp].sh.Length = GET_FCC(movie->fp);
+ movie->streams[temp].sh.SuggestedBufferSize = GET_FCC(movie->fp);
+ movie->streams[temp].sh.Quality = GET_FCC(movie->fp);
+ movie->streams[temp].sh.SampleSize = GET_FCC(movie->fp);
+ movie->streams[temp].sh.left = GET_TCC(movie->fp);
+ movie->streams[temp].sh.top = GET_TCC(movie->fp);
+ movie->streams[temp].sh.right = GET_TCC(movie->fp);
+ movie->streams[temp].sh.bottom = GET_TCC(movie->fp);
+
+ fseek(movie->fp, movie->streams[temp].sh.size - 14 * 4, SEEK_CUR);
+
+ if (GET_FCC(movie->fp) != FCC("strf")) {
+ DEBUG_PRINT("no stream format information\n");
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->streams[temp].sf_size = GET_FCC(movie->fp);
+ if (movie->streams[temp].sh.Type == FCC("vids")) {
+ j = movie->streams[temp].sf_size - (sizeof(AviBitmapInfoHeader) - 8);
+ if (j >= 0) {
+ AviBitmapInfoHeader *bi;
+
+ movie->streams[temp].sf = MEM_mallocN(sizeof(AviBitmapInfoHeader), "streamformat");
+
+ bi = (AviBitmapInfoHeader *)movie->streams[temp].sf;
+
+ bi->fcc = FCC("strf");
+ bi->size = movie->streams[temp].sf_size;
+ bi->Size = GET_FCC(movie->fp);
+ bi->Width = GET_FCC(movie->fp);
+ bi->Height = GET_FCC(movie->fp);
+ bi->Planes = GET_TCC(movie->fp);
+ bi->BitCount = GET_TCC(movie->fp);
+ bi->Compression = GET_FCC(movie->fp);
+ bi->SizeImage = GET_FCC(movie->fp);
+ bi->XPelsPerMeter = GET_FCC(movie->fp);
+ bi->YPelsPerMeter = GET_FCC(movie->fp);
+ bi->ClrUsed = GET_FCC(movie->fp);
+ bi->ClrImportant = GET_FCC(movie->fp);
+
+ fcca = bi->Compression;
+
+ if (movie->streams[temp].format == AVI_FORMAT_AVI_RGB) {
+ if (fcca == FCC("DIB ") || fcca == FCC("RGB ") || fcca == FCC("rgb ") ||
+ fcca == FCC("RAW ") || fcca == 0) {
+ /* pass */
+ }
+ else if (fcca == FCC("mjpg") || fcca == FCC("MJPG")) {
+ movie->streams[temp].format = AVI_FORMAT_MJPEG;
+ }
+ else {
+ return AVI_ERROR_COMPRESSION;
+ }
+ }
+ }
+ if (j > 0) {
+ fseek(movie->fp, j, SEEK_CUR);
+ }
+ }
+ else {
+ fseek(movie->fp, movie->streams[temp].sf_size, SEEK_CUR);
+ }
+
+ /* Walk to the next LIST */
+ while (GET_FCC(movie->fp) != FCC("LIST")) {
+ temp = GET_FCC(movie->fp);
+ if (temp < 0 || ftell(movie->fp) > movie->size) {
+ DEBUG_PRINT("incorrect size in header or error in AVI\n");
+ return AVI_ERROR_FORMAT;
+ }
+ fseek(movie->fp, temp, SEEK_CUR);
+ }
+
+ fseek(movie->fp, -4L, SEEK_CUR);
+ }
+
+ while (1) {
+ temp = GET_FCC(movie->fp);
+ size = GET_FCC(movie->fp);
+
+ if (size == 0) {
+ break;
+ }
+
+ if (temp == FCC("LIST")) {
+ if (GET_FCC(movie->fp) == FCC("movi")) {
+ break;
+ }
+ else {
+ fseek(movie->fp, size - 4, SEEK_CUR);
+ }
+ }
+ else {
+ fseek(movie->fp, size, SEEK_CUR);
+ }
+ if (ftell(movie->fp) > movie->size) {
+ DEBUG_PRINT("incorrect size in header or error in AVI\n");
+ return AVI_ERROR_FORMAT;
+ }
+ }
+
+ movie->movi_offset = ftell(movie->fp);
+ movie->read_offset = movie->movi_offset;
+
+ /* Read in the index if the file has one, otherwise create one */
+ if (movie->header->Flags & AVIF_HASINDEX) {
+ fseek(movie->fp, size - 4, SEEK_CUR);
+
+ if (GET_FCC(movie->fp) != FCC("idx1")) {
+ DEBUG_PRINT("bad index informatio\n");
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->index_entries = GET_FCC(movie->fp) / sizeof(AviIndexEntry);
+ if (movie->index_entries == 0) {
+ DEBUG_PRINT("no index entries\n");
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->entries = (AviIndexEntry *)MEM_mallocN(movie->index_entries * sizeof(AviIndexEntry),
+ "movieentries");
+
+ for (temp = 0; temp < movie->index_entries; temp++) {
+ movie->entries[temp].ChunkId = GET_FCC(movie->fp);
+ movie->entries[temp].Flags = GET_FCC(movie->fp);
+ movie->entries[temp].Offset = GET_FCC(movie->fp);
+ movie->entries[temp].Size = GET_FCC(movie->fp);
+
+ if (AVI_DEBUG) {
+ printf("Index entry %04d: ChunkId:%s Flags:%d Offset:%d Size:%d\n",
+ temp,
+ fcc_to_char(movie->entries[temp].ChunkId),
+ movie->entries[temp].Flags,
+ movie->entries[temp].Offset,
+ movie->entries[temp].Size);
+ }
+ }
+
+ /* Some AVI's have offset entries in absolute coordinates
+ * instead of an offset from the movie beginning... this is...
+ * wacky, but we need to handle it. The wacky offset always
+ * starts at movi_offset it seems... so we'll check that.
+ * Note the offset needs an extra 4 bytes for some
+ * undetermined reason */
+
+ if (movie->entries[0].Offset == movie->movi_offset) {
+ movie->read_offset = 4;
+ }
+ }
+
+ DEBUG_PRINT("movie successfully opened\n");
+ return AVI_ERROR_NONE;
+}
+
+void *AVI_read_frame(AviMovie *movie, AviFormat format, int frame, int stream)
+{
+ int cur_frame = -1, i = 0, rewind = 1;
+ void *buffer;
+
+ /* Retrieve the record number of the desired frame in the index
+ * If a chunk has Size 0 we need to rewind to previous frame */
+ while (rewind && frame > -1) {
+ i = 0;
+ cur_frame = -1;
+ rewind = 0;
+
+ while (cur_frame < frame && i < movie->index_entries) {
+ if (fcc_is_data(movie->entries[i].ChunkId) &&
+ fcc_get_stream(movie->entries[i].ChunkId) == stream) {
+ if ((cur_frame == frame - 1) && (movie->entries[i].Size == 0)) {
+ rewind = 1;
+ frame = frame - 1;
+ }
+ else {
+ cur_frame++;
+ }
+ }
+ i++;
+ }
+ }
+
+ if (cur_frame != frame) {
+ return NULL;
+ }
+
+ fseek(movie->fp, movie->read_offset + movie->entries[i - 1].Offset, SEEK_SET);
+
+ size_t size = GET_FCC(movie->fp);
+ buffer = MEM_mallocN(size, "readbuffer");
+
+ if (fread(buffer, 1, size, movie->fp) != size) {
+ MEM_freeN(buffer);
+
+ return NULL;
+ }
+
+ buffer = avi_format_convert(movie, stream, buffer, movie->streams[stream].format, format, &size);
+
+ return buffer;
+}
+
+AviError AVI_close(AviMovie *movie)
+{
+ int i;
+
+ fclose(movie->fp);
+
+ for (i = 0; i < movie->header->Streams; i++) {
+ if (movie->streams[i].sf != NULL) {
+ MEM_freeN(movie->streams[i].sf);
+ }
+ }
+
+ MEM_freeN(movie->header);
+ MEM_freeN(movie->streams);
+
+ if (movie->entries != NULL) {
+ MEM_freeN(movie->entries);
+ }
+ if (movie->offset_table != NULL) {
+ MEM_freeN(movie->offset_table);
+ }
+
+ return AVI_ERROR_NONE;
+}
+
+AviError AVI_open_compress(char *name, AviMovie *movie, int streams, ...)
+{
+ va_list ap;
+ AviList list;
+ AviChunk chunk;
+ int i;
+ int64_t header_pos1, header_pos2;
+ int64_t stream_pos1, stream_pos2;
+ int64_t junk_pos;
+
+ movie->type = AVI_MOVIE_WRITE;
+ movie->fp = BLI_fopen(name, "wb");
+
+ movie->index_entries = 0;
+
+ if (movie->fp == NULL) {
+ return AVI_ERROR_OPEN;
+ }
+
+ movie->offset_table = (int64_t *)MEM_mallocN((1 + streams * 2) * sizeof(int64_t), "offsettable");
+
+ for (i = 0; i < 1 + streams * 2; i++) {
+ movie->offset_table[i] = -1L;
+ }
+
+ movie->entries = NULL;
+
+ movie->header = (AviMainHeader *)MEM_mallocN(sizeof(AviMainHeader), "movieheader");
+
+ movie->header->fcc = FCC("avih");
+ movie->header->size = 56;
+ movie->header->MicroSecPerFrame = 66667;
+ movie->header->MaxBytesPerSec = 0;
+ movie->header->PaddingGranularity = 0;
+ movie->header->Flags = AVIF_HASINDEX | AVIF_MUSTUSEINDEX;
+ movie->header->TotalFrames = 0;
+ movie->header->InitialFrames = 0;
+ movie->header->Streams = streams;
+ movie->header->SuggestedBufferSize = 0;
+ movie->header->Width = 0;
+ movie->header->Height = 0;
+ movie->header->Reserved[0] = 0;
+ movie->header->Reserved[1] = 0;
+ movie->header->Reserved[2] = 0;
+ movie->header->Reserved[3] = 0;
+
+ /* Limit number of streams to some reasonable amount to prevent
+ * buffer overflow vulnerabilities. */
+ if (movie->header->Streams < 0 || movie->header->Streams > 65536) {
+ DEBUG_PRINT("Number of streams should be in range 0-65536\n");
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->streams = (AviStreamRec *)MEM_mallocN(sizeof(AviStreamRec) * movie->header->Streams,
+ "moviestreams");
+
+ va_start(ap, streams);
+
+ for (i = 0; i < movie->header->Streams; i++) {
+ movie->streams[i].format = va_arg(ap, AviFormat);
+
+ movie->streams[i].sh.fcc = FCC("strh");
+ movie->streams[i].sh.size = 56;
+ movie->streams[i].sh.Type = avi_get_format_type(movie->streams[i].format);
+ if (movie->streams[i].sh.Type == 0) {
+ va_end(ap);
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->streams[i].sh.Handler = avi_get_format_fcc(movie->streams[i].format);
+ if (movie->streams[i].sh.Handler == 0) {
+ va_end(ap);
+ return AVI_ERROR_FORMAT;
+ }
+
+ movie->streams[i].sh.Flags = 0;
+ movie->streams[i].sh.Priority = 0;
+ movie->streams[i].sh.Language = 0;
+ movie->streams[i].sh.InitialFrames = 0;
+ movie->streams[i].sh.Scale = 66667;
+ movie->streams[i].sh.Rate = 1000000;
+ movie->streams[i].sh.Start = 0;
+ movie->streams[i].sh.Length = 0;
+ movie->streams[i].sh.SuggestedBufferSize = 0;
+ movie->streams[i].sh.Quality = 10000;
+ movie->streams[i].sh.SampleSize = 0;
+ movie->streams[i].sh.left = 0;
+ movie->streams[i].sh.top = 0;
+ movie->streams[i].sh.right = 0;
+ movie->streams[i].sh.bottom = 0;
+
+ if (movie->streams[i].sh.Type == FCC("vids")) {
+ movie->streams[i].sf = MEM_mallocN(sizeof(AviBitmapInfoHeader), "moviestreamformatS");
+ movie->streams[i].sf_size = sizeof(AviBitmapInfoHeader);
+
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->fcc = FCC("strf");
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->size = movie->streams[i].sf_size - 8;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->Size = movie->streams[i].sf_size - 8;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->Width = 0;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->Height = 0;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->Planes = 1;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->BitCount = 24;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->Compression = avi_get_format_compression(
+ movie->streams[i].format);
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->SizeImage = 0;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->XPelsPerMeter = 0;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->YPelsPerMeter = 0;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->ClrUsed = 0;
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->ClrImportant = 0;
+ }
+ }
+
+ list.fcc = FCC("RIFF");
+ list.size = 0;
+ list.ids = FCC("AVI ");
+
+ awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST);
+
+ list.fcc = FCC("LIST");
+ list.size = 0;
+ list.ids = FCC("hdrl");
+
+ awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST);
+
+ header_pos1 = ftell(movie->fp);
+
+ movie->offset_table[0] = ftell(movie->fp);
+
+ awrite(movie, movie->header, 1, sizeof(AviMainHeader), movie->fp, AVI_MAINH);
+
+ for (i = 0; i < movie->header->Streams; i++) {
+ list.fcc = FCC("LIST");
+ list.size = 0;
+ list.ids = FCC("strl");
+
+ awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST);
+
+ stream_pos1 = ftell(movie->fp);
+
+ movie->offset_table[1 + i * 2] = ftell(movie->fp);
+ awrite(movie, &movie->streams[i].sh, 1, sizeof(AviStreamHeader), movie->fp, AVI_STREAMH);
+
+ movie->offset_table[1 + i * 2 + 1] = ftell(movie->fp);
+ awrite(movie, movie->streams[i].sf, 1, movie->streams[i].sf_size, movie->fp, AVI_BITMAPH);
+
+ stream_pos2 = ftell(movie->fp);
+
+ fseek(movie->fp, stream_pos1 - 8, SEEK_SET);
+
+ PUT_FCCN((stream_pos2 - stream_pos1 + 4L), movie->fp);
+
+ fseek(movie->fp, stream_pos2, SEEK_SET);
+ }
+
+ junk_pos = ftell(movie->fp);
+
+ if (junk_pos < 2024 - 8) {
+ chunk.fcc = FCC("JUNK");
+ chunk.size = 2024 - 8 - (int)junk_pos;
+
+ awrite(movie, &chunk, 1, sizeof(AviChunk), movie->fp, AVI_CHUNK);
+
+ for (i = 0; i < chunk.size; i++) {
+ putc(0, movie->fp);
+ }
+ }
+
+ header_pos2 = ftell(movie->fp);
+
+ list.fcc = FCC("LIST");
+ list.size = 0;
+ list.ids = FCC("movi");
+
+ awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST);
+
+ movie->movi_offset = ftell(movie->fp) - 8L;
+
+ fseek(movie->fp, AVI_HDRL_SOFF, SEEK_SET);
+
+ PUT_FCCN((header_pos2 - header_pos1 + 4L), movie->fp);
+
+ va_end(ap);
+
+ return AVI_ERROR_NONE;
+}
+
+AviError AVI_write_frame(AviMovie *movie, int frame_num, ...)
+{
+ AviList list;
+ AviChunk chunk;
+ va_list ap;
+ int stream;
+ int64_t rec_off;
+ AviFormat format;
+ void *buffer;
+
+ if (frame_num < 0) {
+ return AVI_ERROR_OPTION;
+ }
+
+ /* Allocate the new memory for the index entry */
+
+ if (frame_num >= movie->index_entries) {
+ const size_t entry_size = (movie->header->Streams + 1) * sizeof(AviIndexEntry);
+ movie->entries = (AviIndexEntry *)MEM_recallocN(movie->entries, (frame_num + 1) * entry_size);
+ movie->index_entries = frame_num + 1;
+ }
+
+ /* Slap a new record entry onto the end of the file */
+
+ fseek(movie->fp, 0L, SEEK_END);
+
+ list.fcc = FCC("LIST");
+ list.size = 0;
+ list.ids = FCC("rec ");
+
+ awrite(movie, &list, 1, sizeof(AviList), movie->fp, AVI_LIST);
+
+ rec_off = ftell(movie->fp) - 8L;
+
+ /* Write a frame for every stream */
+
+ va_start(ap, frame_num);
+
+ for (stream = 0; stream < movie->header->Streams; stream++) {
+ unsigned int tbuf = 0;
+
+ format = va_arg(ap, AviFormat);
+ buffer = va_arg(ap, void *);
+ size_t size = va_arg(ap, int);
+
+ /* Convert the buffer into the output format */
+ buffer = avi_format_convert(
+ movie, stream, buffer, format, movie->streams[stream].format, &size);
+
+ /* Write the header info for this data chunk */
+
+ fseek(movie->fp, 0L, SEEK_END);
+
+ chunk.fcc = avi_get_data_id(format, stream);
+ chunk.size = size;
+
+ if (size % 4) {
+ chunk.size += 4 - size % 4;
+ }
+
+ awrite(movie, &chunk, 1, sizeof(AviChunk), movie->fp, AVI_CHUNK);
+
+ /* Write the index entry for this data chunk */
+
+ movie->entries[frame_num * (movie->header->Streams + 1) + stream + 1].ChunkId = chunk.fcc;
+ movie->entries[frame_num * (movie->header->Streams + 1) + stream + 1].Flags = AVIIF_KEYFRAME;
+ movie->entries[frame_num * (movie->header->Streams + 1) + stream + 1].Offset =
+ (int)(ftell(movie->fp) - 12L - movie->movi_offset);
+ movie->entries[frame_num * (movie->header->Streams + 1) + stream + 1].Size = chunk.size;
+
+ /* Write the chunk */
+ awrite(movie, buffer, 1, size, movie->fp, AVI_RAW);
+ MEM_freeN(buffer);
+
+ if (size % 4) {
+ awrite(movie, &tbuf, 1, 4 - size % 4, movie->fp, AVI_RAW);
+ }
+
+ /* Update the stream headers length field */
+ movie->streams[stream].sh.Length++;
+ fseek(movie->fp, movie->offset_table[1 + stream * 2], SEEK_SET);
+ awrite(movie, &movie->streams[stream].sh, 1, sizeof(AviStreamHeader), movie->fp, AVI_STREAMH);
+ }
+ va_end(ap);
+
+ /* Record the entry for the new record */
+
+ fseek(movie->fp, 0L, SEEK_END);
+
+ movie->entries[frame_num * (movie->header->Streams + 1)].ChunkId = FCC("rec ");
+ movie->entries[frame_num * (movie->header->Streams + 1)].Flags = AVIIF_LIST;
+ movie->entries[frame_num * (movie->header->Streams + 1)].Offset = (int)(rec_off - 8L -
+ movie->movi_offset);
+ movie->entries[frame_num * (movie->header->Streams + 1)].Size = (int)(ftell(movie->fp) -
+ (rec_off + 4L));
+
+ /* Update the record size */
+ fseek(movie->fp, rec_off, SEEK_SET);
+ PUT_FCCN(movie->entries[frame_num * (movie->header->Streams + 1)].Size, movie->fp);
+
+ /* Update the main header information in the file */
+ movie->header->TotalFrames++;
+ fseek(movie->fp, movie->offset_table[0], SEEK_SET);
+ awrite(movie, movie->header, 1, sizeof(AviMainHeader), movie->fp, AVI_MAINH);
+
+ return AVI_ERROR_NONE;
+}
+
+AviError AVI_close_compress(AviMovie *movie)
+{
+ int temp, movi_size, i;
+
+ if (movie->fp == NULL) {
+ /* none of the allocations below were done if the file failed to open */
+ return AVI_ERROR_FOUND;
+ }
+
+ fseek(movie->fp, 0L, SEEK_END);
+ movi_size = (int)ftell(movie->fp);
+
+ PUT_FCC("idx1", movie->fp);
+ PUT_FCCN((movie->index_entries * (movie->header->Streams + 1) * 16), movie->fp);
+
+ for (temp = 0; temp < movie->index_entries * (movie->header->Streams + 1); temp++) {
+ awrite(movie, &movie->entries[temp], 1, sizeof(AviIndexEntry), movie->fp, AVI_INDEXE);
+ }
+
+ temp = (int)ftell(movie->fp);
+
+ fseek(movie->fp, AVI_RIFF_SOFF, SEEK_SET);
+
+ PUT_FCCN((temp - 8L), movie->fp);
+
+ fseek(movie->fp, movie->movi_offset, SEEK_SET);
+
+ PUT_FCCN((movi_size - (movie->movi_offset + 4L)), movie->fp);
+
+ fclose(movie->fp);
+
+ for (i = 0; i < movie->header->Streams; i++) {
+ if (movie->streams && (movie->streams[i].sf != NULL)) {
+ MEM_freeN(movie->streams[i].sf);
+ }
+ }
+
+ MEM_freeN(movie->header);
+
+ if (movie->entries != NULL) {
+ MEM_freeN(movie->entries);
+ }
+ if (movie->streams != NULL) {
+ MEM_freeN(movie->streams);
+ }
+ if (movie->offset_table != NULL) {
+ MEM_freeN(movie->offset_table);
+ }
+ return AVI_ERROR_NONE;
+}
diff --git a/source/blender/io/avi/intern/avi_codecs.c b/source/blender/io/avi/intern/avi_codecs.c
new file mode 100644
index 00000000000..15f498ac653
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_codecs.c
@@ -0,0 +1,138 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ *
+ * This is external code. Identify and convert different avi-files.
+ */
+
+#include "AVI_avi.h"
+#include "avi_intern.h"
+
+#include "avi_rgb.h"
+#include "avi_mjpeg.h"
+#include "avi_rgb32.h"
+
+void *avi_format_convert(
+ AviMovie *movie, int stream, void *buffer, AviFormat from, AviFormat to, size_t *size)
+{
+ if (from == to) {
+ return buffer;
+ }
+
+ if (from != AVI_FORMAT_RGB24 && to != AVI_FORMAT_RGB24) {
+ return avi_format_convert(
+ movie,
+ stream,
+ avi_format_convert(movie, stream, buffer, from, AVI_FORMAT_RGB24, size),
+ AVI_FORMAT_RGB24,
+ to,
+ size);
+ }
+
+ switch (to) {
+ case AVI_FORMAT_RGB24:
+ switch (from) {
+ case AVI_FORMAT_AVI_RGB:
+ buffer = avi_converter_from_avi_rgb(movie, stream, buffer, size);
+ break;
+ case AVI_FORMAT_MJPEG:
+ buffer = avi_converter_from_mjpeg(movie, stream, buffer, size);
+ break;
+ case AVI_FORMAT_RGB32:
+ buffer = avi_converter_from_rgb32(movie, stream, buffer, size);
+ break;
+ default:
+ break;
+ }
+ break;
+ case AVI_FORMAT_AVI_RGB:
+ buffer = avi_converter_to_avi_rgb(movie, stream, buffer, size);
+ break;
+ case AVI_FORMAT_MJPEG:
+ buffer = avi_converter_to_mjpeg(movie, stream, buffer, size);
+ break;
+ case AVI_FORMAT_RGB32:
+ buffer = avi_converter_to_rgb32(movie, stream, buffer, size);
+ break;
+ default:
+ break;
+ }
+
+ return buffer;
+}
+
+int avi_get_data_id(AviFormat format, int stream)
+{
+ char fcc[5];
+
+ if (avi_get_format_type(format) == FCC("vids")) {
+ sprintf(fcc, "%2.2ddc", stream);
+ }
+ else if (avi_get_format_type(format) == FCC("auds")) {
+ sprintf(fcc, "%2.2ddc", stream);
+ }
+ else {
+ return 0;
+ }
+
+ return FCC(fcc);
+}
+
+int avi_get_format_type(AviFormat format)
+{
+ switch (format) {
+ case AVI_FORMAT_RGB24:
+ case AVI_FORMAT_RGB32:
+ case AVI_FORMAT_AVI_RGB:
+ case AVI_FORMAT_MJPEG:
+ return FCC("vids");
+ default:
+ return 0;
+ }
+}
+
+int avi_get_format_fcc(AviFormat format)
+{
+ switch (format) {
+ case AVI_FORMAT_RGB24:
+ case AVI_FORMAT_RGB32:
+ case AVI_FORMAT_AVI_RGB:
+ return FCC("DIB ");
+ case AVI_FORMAT_MJPEG:
+ return FCC("MJPG");
+ default:
+ return 0;
+ }
+}
+
+int avi_get_format_compression(AviFormat format)
+{
+ switch (format) {
+ case AVI_FORMAT_RGB24:
+ case AVI_FORMAT_RGB32:
+ case AVI_FORMAT_AVI_RGB:
+ return 0;
+ case AVI_FORMAT_MJPEG:
+ return FCC("MJPG");
+ default:
+ return 0;
+ }
+}
diff --git a/source/blender/io/avi/intern/avi_endian.c b/source/blender/io/avi/intern/avi_endian.c
new file mode 100644
index 00000000000..56474e9e329
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_endian.c
@@ -0,0 +1,203 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ *
+ * This is external code. Streams bytes to output depending on the
+ * endianness of the system.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "AVI_avi.h"
+#include "avi_endian.h"
+#include "avi_intern.h"
+
+#ifdef __BIG_ENDIAN__
+# include "MEM_guardedalloc.h"
+#endif
+
+#ifdef __BIG_ENDIAN__
+
+/* copied from BLI_endian_switch_inline.h */
+static void invert(int *val)
+{
+ int tval = *val;
+ *val = ((tval >> 24)) | ((tval << 8) & 0x00ff0000) | ((tval >> 8) & 0x0000ff00) | ((tval << 24));
+}
+
+static void sinvert(short int *val)
+{
+ short tval = *val;
+ *val = (tval >> 8) | (tval << 8);
+}
+
+static void Ichunk(AviChunk *chunk)
+{
+ invert(&chunk->fcc);
+ invert(&chunk->size);
+}
+#endif
+
+#ifdef __BIG_ENDIAN__
+static void Ilist(AviList *list)
+{
+ invert(&list->fcc);
+ invert(&list->size);
+ invert(&list->ids);
+}
+
+static void Imainh(AviMainHeader *mainh)
+{
+ invert(&mainh->fcc);
+ invert(&mainh->size);
+ invert(&mainh->MicroSecPerFrame);
+ invert(&mainh->MaxBytesPerSec);
+ invert(&mainh->PaddingGranularity);
+ invert(&mainh->Flags);
+ invert(&mainh->TotalFrames);
+ invert(&mainh->InitialFrames);
+ invert(&mainh->Streams);
+ invert(&mainh->SuggestedBufferSize);
+ invert(&mainh->Width);
+ invert(&mainh->Height);
+ invert(&mainh->Reserved[0]);
+ invert(&mainh->Reserved[1]);
+ invert(&mainh->Reserved[2]);
+ invert(&mainh->Reserved[3]);
+}
+
+static void Istreamh(AviStreamHeader *streamh)
+{
+ invert(&streamh->fcc);
+ invert(&streamh->size);
+ invert(&streamh->Type);
+ invert(&streamh->Handler);
+ invert(&streamh->Flags);
+ sinvert(&streamh->Priority);
+ sinvert(&streamh->Language);
+ invert(&streamh->InitialFrames);
+ invert(&streamh->Scale);
+ invert(&streamh->Rate);
+ invert(&streamh->Start);
+ invert(&streamh->Length);
+ invert(&streamh->SuggestedBufferSize);
+ invert(&streamh->Quality);
+ invert(&streamh->SampleSize);
+ sinvert(&streamh->left);
+ sinvert(&streamh->right);
+ sinvert(&streamh->top);
+ sinvert(&streamh->bottom);
+}
+
+static void Ibitmaph(AviBitmapInfoHeader *bitmaph)
+{
+ invert(&bitmaph->fcc);
+ invert(&bitmaph->size);
+ invert(&bitmaph->Size);
+ invert(&bitmaph->Width);
+ invert(&bitmaph->Height);
+ sinvert(&bitmaph->Planes);
+ sinvert(&bitmaph->BitCount);
+ invert(&bitmaph->Compression);
+ invert(&bitmaph->SizeImage);
+ invert(&bitmaph->XPelsPerMeter);
+ invert(&bitmaph->YPelsPerMeter);
+ invert(&bitmaph->ClrUsed);
+ invert(&bitmaph->ClrImportant);
+}
+
+static void Imjpegu(AviMJPEGUnknown *mjpgu)
+{
+ invert(&mjpgu->a);
+ invert(&mjpgu->b);
+ invert(&mjpgu->c);
+ invert(&mjpgu->d);
+ invert(&mjpgu->e);
+ invert(&mjpgu->f);
+ invert(&mjpgu->g);
+}
+
+static void Iindexe(AviIndexEntry *indexe)
+{
+ invert(&indexe->ChunkId);
+ invert(&indexe->Flags);
+ invert(&indexe->Offset);
+ invert(&indexe->Size);
+}
+#endif /* __BIG_ENDIAN__ */
+
+void awrite(AviMovie *movie, void *datain, int block, int size, FILE *fp, int type)
+{
+#ifdef __BIG_ENDIAN__
+ void *data;
+
+ data = MEM_mallocN(size, "avi endian");
+
+ memcpy(data, datain, size);
+
+ switch (type) {
+ case AVI_RAW:
+ fwrite(data, block, size, fp);
+ break;
+ case AVI_CHUNK:
+ Ichunk((AviChunk *)data);
+ fwrite(data, block, size, fp);
+ break;
+ case AVI_LIST:
+ Ilist((AviList *)data);
+ fwrite(data, block, size, fp);
+ break;
+ case AVI_MAINH:
+ Imainh((AviMainHeader *)data);
+ fwrite(data, block, size, fp);
+ break;
+ case AVI_STREAMH:
+ Istreamh((AviStreamHeader *)data);
+ fwrite(data, block, size, fp);
+ break;
+ case AVI_BITMAPH:
+ Ibitmaph((AviBitmapInfoHeader *)data);
+ if (size == sizeof(AviBitmapInfoHeader) + sizeof(AviMJPEGUnknown)) {
+ Imjpegu((AviMJPEGUnknown *)((char *)data + sizeof(AviBitmapInfoHeader)));
+ }
+ fwrite(data, block, size, fp);
+ break;
+ case AVI_MJPEGU:
+ Imjpegu((AviMJPEGUnknown *)data);
+ fwrite(data, block, size, fp);
+ break;
+ case AVI_INDEXE:
+ Iindexe((AviIndexEntry *)data);
+ fwrite(data, block, size, fp);
+ break;
+ default:
+ break;
+ }
+
+ MEM_freeN(data);
+#else /* __BIG_ENDIAN__ */
+ (void)movie; /* unused */
+ (void)type; /* unused */
+ fwrite(datain, block, size, fp);
+#endif /* __BIG_ENDIAN__ */
+}
diff --git a/source/blender/io/avi/intern/avi_endian.h b/source/blender/io/avi/intern/avi_endian.h
new file mode 100644
index 00000000000..d1253f488e7
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_endian.h
@@ -0,0 +1,40 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ *
+ * This is external code.
+ */
+
+#ifndef __AVI_ENDIAN_H__
+#define __AVI_ENDIAN_H__
+
+#define AVI_RAW 0
+#define AVI_CHUNK 1
+#define AVI_LIST 2
+#define AVI_MAINH 3
+#define AVI_STREAMH 4
+#define AVI_BITMAPH 5
+#define AVI_INDEXE 6
+#define AVI_MJPEGU 7
+
+void awrite(AviMovie *movie, void *datain, int block, int size, FILE *fp, int type);
+
+#endif /* __AVI_ENDIAN_H__ */
diff --git a/source/blender/io/avi/intern/avi_intern.h b/source/blender/io/avi/intern/avi_intern.h
new file mode 100644
index 00000000000..6ce91ce7f70
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_intern.h
@@ -0,0 +1,65 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ */
+
+#ifndef __AVI_INTERN_H__
+#define __AVI_INTERN_H__
+
+#include <stdio.h> /* for FILE */
+
+unsigned int GET_FCC(FILE *fp);
+unsigned int GET_TCC(FILE *fp);
+
+#define PUT_FCC(ch4, fp) \
+ { \
+ putc(ch4[0], fp); \
+ putc(ch4[1], fp); \
+ putc(ch4[2], fp); \
+ putc(ch4[3], fp); \
+ } \
+ (void)0
+
+#define PUT_FCCN(num, fp) \
+ { \
+ putc((num >> 0) & 0377, fp); \
+ putc((num >> 8) & 0377, fp); \
+ putc((num >> 16) & 0377, fp); \
+ putc((num >> 24) & 0377, fp); \
+ } \
+ (void)0
+
+#define PUT_TCC(ch2, fp) \
+ { \
+ putc(ch2[0], fp); \
+ putc(ch2[1], fp); \
+ } \
+ (void)0
+
+void *avi_format_convert(
+ AviMovie *movie, int stream, void *buffer, AviFormat from, AviFormat to, size_t *size);
+
+int avi_get_data_id(AviFormat format, int stream);
+int avi_get_format_type(AviFormat format);
+int avi_get_format_fcc(AviFormat format);
+int avi_get_format_compression(AviFormat format);
+
+#endif
diff --git a/source/blender/io/avi/intern/avi_mjpeg.c b/source/blender/io/avi/intern/avi_mjpeg.c
new file mode 100644
index 00000000000..d4c7378964e
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_mjpeg.c
@@ -0,0 +1,547 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ *
+ * This is external code. Converts between avi and mpeg/jpeg.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "AVI_avi.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "IMB_imbuf.h"
+
+#include "jpeglib.h"
+#include "jerror.h"
+
+#include "avi_mjpeg.h"
+
+static void jpegmemdestmgr_build(j_compress_ptr cinfo, unsigned char *buffer, size_t bufsize);
+static void jpegmemsrcmgr_build(j_decompress_ptr dinfo, unsigned char *buffer, size_t bufsize);
+
+static size_t numbytes;
+
+static void add_huff_table(j_decompress_ptr dinfo,
+ JHUFF_TBL **htblptr,
+ const UINT8 *bits,
+ const UINT8 *val)
+{
+ if (*htblptr == NULL) {
+ *htblptr = jpeg_alloc_huff_table((j_common_ptr)dinfo);
+ }
+
+ memcpy((*htblptr)->bits, bits, sizeof((*htblptr)->bits));
+ memcpy((*htblptr)->huffval, val, sizeof((*htblptr)->huffval));
+
+ /* Initialize sent_table false so table will be written to JPEG file. */
+ (*htblptr)->sent_table = false;
+}
+
+/* Set up the standard Huffman tables (cf. JPEG standard section K.3) */
+/* IMPORTANT: these are only valid for 8-bit data precision! */
+
+static void std_huff_tables(j_decompress_ptr dinfo)
+{
+ static const UINT8 bits_dc_luminance[17] = {
+ /* 0-base */
+ 0,
+ 0,
+ 1,
+ 5,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ };
+ static const UINT8 val_dc_luminance[] = {
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ };
+
+ static const UINT8 bits_dc_chrominance[17] = {
+ /* 0-base */
+ 0,
+ 0,
+ 3,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 1,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ };
+ static const UINT8 val_dc_chrominance[] = {
+ 0,
+ 1,
+ 2,
+ 3,
+ 4,
+ 5,
+ 6,
+ 7,
+ 8,
+ 9,
+ 10,
+ 11,
+ };
+
+ static const UINT8 bits_ac_luminance[17] = {
+ /* 0-base */
+ 0,
+ 0,
+ 2,
+ 1,
+ 3,
+ 3,
+ 2,
+ 4,
+ 3,
+ 5,
+ 5,
+ 4,
+ 4,
+ 0,
+ 0,
+ 1,
+ 0x7d,
+ };
+ static const UINT8 val_ac_luminance[] = {
+ 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61,
+ 0x07, 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52,
+ 0xd1, 0xf0, 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25,
+ 0x26, 0x27, 0x28, 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45,
+ 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64,
+ 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83,
+ 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
+ 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
+ 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3,
+ 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8,
+ 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa,
+ };
+ static const UINT8 bits_ac_chrominance[17] = {
+ /* 0-base */
+ 0,
+ 0,
+ 2,
+ 1,
+ 2,
+ 4,
+ 4,
+ 3,
+ 4,
+ 7,
+ 5,
+ 4,
+ 4,
+ 0,
+ 1,
+ 2,
+ 0x77,
+ };
+ static const UINT8 val_ac_chrominance[] = {
+ 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61,
+ 0x71, 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33,
+ 0x52, 0xf0, 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18,
+ 0x19, 0x1a, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44,
+ 0x45, 0x46, 0x47, 0x48, 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63,
+ 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a,
+ 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97,
+ 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4,
+ 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca,
+ 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7,
+ 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 0xf9, 0xfa,
+ };
+
+ add_huff_table(dinfo, &dinfo->dc_huff_tbl_ptrs[0], bits_dc_luminance, val_dc_luminance);
+ add_huff_table(dinfo, &dinfo->ac_huff_tbl_ptrs[0], bits_ac_luminance, val_ac_luminance);
+ add_huff_table(dinfo, &dinfo->dc_huff_tbl_ptrs[1], bits_dc_chrominance, val_dc_chrominance);
+ add_huff_table(dinfo, &dinfo->ac_huff_tbl_ptrs[1], bits_ac_chrominance, val_ac_chrominance);
+}
+
+static int Decode_JPEG(unsigned char *inBuffer,
+ unsigned char *outBuffer,
+ unsigned int width,
+ unsigned int height,
+ size_t bufsize)
+{
+ struct jpeg_decompress_struct dinfo;
+ struct jpeg_error_mgr jerr;
+
+ (void)width; /* unused */
+
+ numbytes = 0;
+
+ dinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&dinfo);
+ jpegmemsrcmgr_build(&dinfo, inBuffer, bufsize);
+ jpeg_read_header(&dinfo, true);
+ if (dinfo.dc_huff_tbl_ptrs[0] == NULL) {
+ std_huff_tables(&dinfo);
+ }
+ dinfo.out_color_space = JCS_RGB;
+ dinfo.dct_method = JDCT_IFAST;
+
+ jpeg_start_decompress(&dinfo);
+
+ size_t rowstride = dinfo.output_width * dinfo.output_components;
+ for (size_t y = 0; y < dinfo.output_height; y++) {
+ jpeg_read_scanlines(&dinfo, (JSAMPARRAY)&outBuffer, 1);
+ outBuffer += rowstride;
+ }
+ jpeg_finish_decompress(&dinfo);
+
+ if (dinfo.output_height >= height) {
+ return 0;
+ }
+
+ inBuffer += numbytes;
+ jpegmemsrcmgr_build(&dinfo, inBuffer, bufsize - numbytes);
+
+ numbytes = 0;
+ jpeg_read_header(&dinfo, true);
+ if (dinfo.dc_huff_tbl_ptrs[0] == NULL) {
+ std_huff_tables(&dinfo);
+ }
+
+ jpeg_start_decompress(&dinfo);
+ rowstride = dinfo.output_width * dinfo.output_components;
+ for (size_t y = 0; y < dinfo.output_height; y++) {
+ jpeg_read_scanlines(&dinfo, (JSAMPARRAY)&outBuffer, 1);
+ outBuffer += rowstride;
+ }
+ jpeg_finish_decompress(&dinfo);
+ jpeg_destroy_decompress(&dinfo);
+
+ return 1;
+}
+
+static void Compress_JPEG(int quality,
+ unsigned char *outbuffer,
+ const unsigned char *inBuffer,
+ int width,
+ int height,
+ size_t bufsize)
+{
+ struct jpeg_compress_struct cinfo;
+ struct jpeg_error_mgr jerr;
+ unsigned char marker[60];
+
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_compress(&cinfo);
+ jpegmemdestmgr_build(&cinfo, outbuffer, bufsize);
+
+ cinfo.image_width = width;
+ cinfo.image_height = height;
+ cinfo.input_components = 3;
+ cinfo.in_color_space = JCS_RGB;
+
+ jpeg_set_defaults(&cinfo);
+ jpeg_set_colorspace(&cinfo, JCS_YCbCr);
+
+ jpeg_set_quality(&cinfo, quality, true);
+
+ cinfo.dc_huff_tbl_ptrs[0]->sent_table = true;
+ cinfo.dc_huff_tbl_ptrs[1]->sent_table = true;
+ cinfo.ac_huff_tbl_ptrs[0]->sent_table = true;
+ cinfo.ac_huff_tbl_ptrs[1]->sent_table = true;
+
+ cinfo.comp_info[0].component_id = 0;
+ cinfo.comp_info[0].v_samp_factor = 1;
+ cinfo.comp_info[1].component_id = 1;
+ cinfo.comp_info[2].component_id = 2;
+
+ cinfo.write_JFIF_header = false;
+
+ jpeg_start_compress(&cinfo, false);
+
+ int i = 0;
+ marker[i++] = 'A';
+ marker[i++] = 'V';
+ marker[i++] = 'I';
+ marker[i++] = '1';
+ marker[i++] = 0;
+ while (i < 60) {
+ marker[i++] = 32;
+ }
+
+ jpeg_write_marker(&cinfo, JPEG_APP0, marker, 60);
+
+ i = 0;
+ while (i < 60) {
+ marker[i++] = 0;
+ }
+
+ jpeg_write_marker(&cinfo, JPEG_COM, marker, 60);
+
+ size_t rowstride = cinfo.image_width * cinfo.input_components;
+ for (size_t y = 0; y < cinfo.image_height; y++) {
+ jpeg_write_scanlines(&cinfo, (JSAMPARRAY)&inBuffer, 1);
+ inBuffer += rowstride;
+ }
+ jpeg_finish_compress(&cinfo);
+ jpeg_destroy_compress(&cinfo);
+}
+
+static void interlace(unsigned char *to, unsigned char *from, int width, int height)
+{
+ size_t i, rowstride = width * 3;
+
+ for (i = 0; i < height; i++) {
+ if (i & 1) {
+ memcpy(&to[i * rowstride], &from[(i / 2 + height / 2) * rowstride], rowstride);
+ }
+ else {
+ memcpy(&to[i * rowstride], &from[(i / 2) * rowstride], rowstride);
+ }
+ }
+}
+
+static void deinterlace(int odd, unsigned char *to, unsigned char *from, int width, int height)
+{
+ size_t i, rowstride = width * 3;
+
+ for (i = 0; i < height; i++) {
+ if ((i & 1) == odd) {
+ memcpy(&to[(i / 2 + height / 2) * rowstride], &from[i * rowstride], rowstride);
+ }
+ else {
+ memcpy(&to[(i / 2) * rowstride], &from[i * rowstride], rowstride);
+ }
+ }
+}
+
+void *avi_converter_from_mjpeg(AviMovie *movie, int stream, unsigned char *buffer, size_t *size)
+{
+ int deint;
+ unsigned char *buf;
+
+ (void)stream; /* unused */
+
+ buf = imb_alloc_pixels(movie->header->Height,
+ movie->header->Width,
+ 3,
+ sizeof(unsigned char),
+ "avi.avi_converter_from_mjpeg 1");
+ if (!buf) {
+ return NULL;
+ }
+
+ deint = Decode_JPEG(buffer, buf, movie->header->Width, movie->header->Height, *size);
+
+ MEM_freeN(buffer);
+
+ if (deint) {
+ buffer = imb_alloc_pixels(movie->header->Height,
+ movie->header->Width,
+ 3,
+ sizeof(unsigned char),
+ "avi.avi_converter_from_mjpeg 2");
+ if (buffer) {
+ interlace(buffer, buf, movie->header->Width, movie->header->Height);
+ }
+ MEM_freeN(buf);
+
+ buf = buffer;
+ }
+
+ return buf;
+}
+
+void *avi_converter_to_mjpeg(AviMovie *movie, int stream, unsigned char *buffer, size_t *size)
+{
+ unsigned char *buf;
+ size_t bufsize = *size;
+
+ numbytes = 0;
+ *size = 0;
+
+ buf = imb_alloc_pixels(movie->header->Height,
+ movie->header->Width,
+ 3,
+ sizeof(unsigned char),
+ "avi.avi_converter_to_mjpeg 1");
+ if (!buf) {
+ return NULL;
+ }
+
+ if (!movie->interlace) {
+ Compress_JPEG(movie->streams[stream].sh.Quality / 100,
+ buf,
+ buffer,
+ movie->header->Width,
+ movie->header->Height,
+ bufsize);
+ *size += numbytes;
+ }
+ else {
+ deinterlace(movie->odd_fields, buf, buffer, movie->header->Width, movie->header->Height);
+ MEM_freeN(buffer);
+
+ buffer = buf;
+ buf = imb_alloc_pixels(movie->header->Height,
+ movie->header->Width,
+ 3,
+ sizeof(unsigned char),
+ "avi.avi_converter_to_mjpeg 1");
+
+ if (buf) {
+ Compress_JPEG(movie->streams[stream].sh.Quality / 100,
+ buf,
+ buffer,
+ movie->header->Width,
+ movie->header->Height / 2,
+ bufsize / 2);
+ *size += numbytes;
+ numbytes = 0;
+ Compress_JPEG(movie->streams[stream].sh.Quality / 100,
+ buf + *size,
+ buffer +
+ (size_t)(movie->header->Height / 2) * (size_t)movie->header->Width * 3,
+ movie->header->Width,
+ movie->header->Height / 2,
+ bufsize / 2);
+ *size += numbytes;
+ }
+ }
+
+ MEM_freeN(buffer);
+ return buf;
+}
+
+/* Compression from memory */
+
+static void jpegmemdestmgr_init_destination(j_compress_ptr cinfo)
+{
+ (void)cinfo; /* unused */
+}
+
+static boolean jpegmemdestmgr_empty_output_buffer(j_compress_ptr cinfo)
+{
+ (void)cinfo; /* unused */
+ return true;
+}
+
+static void jpegmemdestmgr_term_destination(j_compress_ptr cinfo)
+{
+ numbytes -= cinfo->dest->free_in_buffer;
+
+ MEM_freeN(cinfo->dest);
+}
+
+static void jpegmemdestmgr_build(j_compress_ptr cinfo, unsigned char *buffer, size_t bufsize)
+{
+ cinfo->dest = MEM_mallocN(sizeof(*(cinfo->dest)), "avi.jpegmemdestmgr_build");
+
+ cinfo->dest->init_destination = jpegmemdestmgr_init_destination;
+ cinfo->dest->empty_output_buffer = jpegmemdestmgr_empty_output_buffer;
+ cinfo->dest->term_destination = jpegmemdestmgr_term_destination;
+
+ cinfo->dest->next_output_byte = buffer;
+ cinfo->dest->free_in_buffer = bufsize;
+
+ numbytes = bufsize;
+}
+
+/* Decompression from memory */
+
+static void jpegmemsrcmgr_init_source(j_decompress_ptr dinfo)
+{
+ (void)dinfo;
+}
+
+static boolean jpegmemsrcmgr_fill_input_buffer(j_decompress_ptr dinfo)
+{
+ unsigned char *buf = (unsigned char *)dinfo->src->next_input_byte - 2;
+
+ /* if we get called, must have run out of data */
+ WARNMS(dinfo, JWRN_JPEG_EOF);
+
+ buf[0] = (JOCTET)0xFF;
+ buf[1] = (JOCTET)JPEG_EOI;
+
+ dinfo->src->next_input_byte = buf;
+ dinfo->src->bytes_in_buffer = 2;
+
+ return true;
+}
+
+static void jpegmemsrcmgr_skip_input_data(j_decompress_ptr dinfo, long skipcnt)
+{
+ if (dinfo->src->bytes_in_buffer < skipcnt) {
+ skipcnt = dinfo->src->bytes_in_buffer;
+ }
+
+ dinfo->src->next_input_byte += skipcnt;
+ dinfo->src->bytes_in_buffer -= skipcnt;
+}
+
+static void jpegmemsrcmgr_term_source(j_decompress_ptr dinfo)
+{
+ numbytes -= dinfo->src->bytes_in_buffer;
+
+ MEM_freeN(dinfo->src);
+}
+
+static void jpegmemsrcmgr_build(j_decompress_ptr dinfo, unsigned char *buffer, size_t bufsize)
+{
+ dinfo->src = MEM_mallocN(sizeof(*(dinfo->src)), "avi.jpegmemsrcmgr_build");
+
+ dinfo->src->init_source = jpegmemsrcmgr_init_source;
+ dinfo->src->fill_input_buffer = jpegmemsrcmgr_fill_input_buffer;
+ dinfo->src->skip_input_data = jpegmemsrcmgr_skip_input_data;
+ dinfo->src->resync_to_restart = jpeg_resync_to_restart;
+ dinfo->src->term_source = jpegmemsrcmgr_term_source;
+
+ dinfo->src->bytes_in_buffer = bufsize;
+ dinfo->src->next_input_byte = buffer;
+
+ numbytes = bufsize;
+}
diff --git a/source/blender/io/avi/intern/avi_mjpeg.h b/source/blender/io/avi/intern/avi_mjpeg.h
new file mode 100644
index 00000000000..30e46bf1d0c
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_mjpeg.h
@@ -0,0 +1,30 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ */
+
+#ifndef __AVI_MJPEG_H__
+#define __AVI_MJPEG_H__
+
+void *avi_converter_from_mjpeg(AviMovie *movie, int stream, unsigned char *buffer, size_t *size);
+void *avi_converter_to_mjpeg(AviMovie *movie, int stream, unsigned char *buffer, size_t *size);
+
+#endif /* __AVI_MJPEG_H__ */
diff --git a/source/blender/io/avi/intern/avi_options.c b/source/blender/io/avi/intern/avi_options.c
new file mode 100644
index 00000000000..65db8c19397
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_options.c
@@ -0,0 +1,148 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ *
+ * This is external code. Sets some compression related options
+ * (width, height quality, framerate).
+ */
+
+#include "AVI_avi.h"
+#include "avi_intern.h"
+#include "avi_endian.h"
+
+#ifdef WIN32
+# include "BLI_winstuff.h"
+#endif
+
+/* avi_set_compress_options gets its own file... now don't WE feel important? */
+
+AviError AVI_set_compress_option(
+ AviMovie *movie, int option_type, int stream, AviOption option, void *opt_data)
+{
+ int i;
+ int useconds;
+
+ (void)stream; /* unused */
+
+ if (movie->header->TotalFrames != 0) {
+ /* Can't change params after we have already started writing frames. */
+ return AVI_ERROR_OPTION;
+ }
+
+ switch (option_type) {
+ case AVI_OPTION_TYPE_MAIN:
+ switch (option) {
+ case AVI_OPTION_WIDTH:
+ movie->header->Width = *((int *)opt_data);
+ movie->header->SuggestedBufferSize = movie->header->Width * movie->header->Height * 3;
+
+ for (i = 0; i < movie->header->Streams; i++) {
+ if (avi_get_format_type(movie->streams[i].format) == FCC("vids")) {
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->Width = *((int *)opt_data);
+ movie->streams[i].sh.SuggestedBufferSize = movie->header->SuggestedBufferSize;
+ movie->streams[i].sh.right = *((int *)opt_data);
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->SizeImage =
+ movie->header->SuggestedBufferSize;
+ fseek(movie->fp, movie->offset_table[1 + i * 2 + 1], SEEK_SET);
+ awrite(movie,
+ movie->streams[i].sf,
+ 1,
+ movie->streams[i].sf_size,
+ movie->fp,
+ AVI_BITMAPH);
+ }
+ }
+
+ break;
+
+ case AVI_OPTION_HEIGHT:
+ movie->header->Height = *((int *)opt_data);
+ movie->header->SuggestedBufferSize = movie->header->Width * movie->header->Height * 3;
+
+ for (i = 0; i < movie->header->Streams; i++) {
+ if (avi_get_format_type(movie->streams[i].format) == FCC("vids")) {
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->Height = *((int *)opt_data);
+ movie->streams[i].sh.SuggestedBufferSize = movie->header->SuggestedBufferSize;
+ movie->streams[i].sh.bottom = *((int *)opt_data);
+ ((AviBitmapInfoHeader *)movie->streams[i].sf)->SizeImage =
+ movie->header->SuggestedBufferSize;
+ fseek(movie->fp, movie->offset_table[1 + i * 2 + 1], SEEK_SET);
+ awrite(movie,
+ movie->streams[i].sf,
+ 1,
+ movie->streams[i].sf_size,
+ movie->fp,
+ AVI_BITMAPH);
+ }
+ }
+
+ break;
+
+ case AVI_OPTION_QUALITY:
+ for (i = 0; i < movie->header->Streams; i++) {
+ if (avi_get_format_type(movie->streams[i].format) == FCC("vids")) {
+ movie->streams[i].sh.Quality = (*((int *)opt_data)) * 100;
+ fseek(movie->fp, movie->offset_table[1 + i * 2 + 1], SEEK_SET);
+ awrite(movie,
+ movie->streams[i].sf,
+ 1,
+ movie->streams[i].sf_size,
+ movie->fp,
+ AVI_BITMAPH);
+ }
+ }
+ break;
+
+ case AVI_OPTION_FRAMERATE:
+ useconds = (int)(1000000 / (*((double *)opt_data)));
+ if (useconds) {
+ movie->header->MicroSecPerFrame = useconds;
+ }
+
+ for (i = 0; i < movie->header->Streams; i++) {
+ if (avi_get_format_type(movie->streams[i].format) == FCC("vids")) {
+ movie->streams[i].sh.Scale = movie->header->MicroSecPerFrame;
+ fseek(movie->fp, movie->offset_table[1 + i * 2 + 1], SEEK_SET);
+ awrite(movie,
+ movie->streams[i].sf,
+ 1,
+ movie->streams[i].sf_size,
+ movie->fp,
+ AVI_BITMAPH);
+ }
+ }
+ break;
+ }
+
+ fseek(movie->fp, movie->offset_table[0], SEEK_SET);
+ awrite(movie, movie->header, 1, sizeof(AviMainHeader), movie->fp, AVI_MAINH);
+
+ break;
+ case AVI_OPTION_TYPE_STRH:
+ break;
+ case AVI_OPTION_TYPE_STRF:
+ break;
+ default:
+ return AVI_ERROR_OPTION;
+ }
+
+ return AVI_ERROR_NONE;
+}
diff --git a/source/blender/io/avi/intern/avi_rgb.c b/source/blender/io/avi/intern/avi_rgb.c
new file mode 100644
index 00000000000..d449556e79b
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_rgb.c
@@ -0,0 +1,154 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ *
+ * This is external code. Converts rgb-type avi-s.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "AVI_avi.h"
+#include "avi_rgb.h"
+
+#include "IMB_imbuf.h"
+
+/* implementation */
+
+void *avi_converter_from_avi_rgb(AviMovie *movie, int stream, unsigned char *buffer, size_t *size)
+{
+ unsigned char *buf;
+ AviBitmapInfoHeader *bi;
+ short bits = 32;
+
+ (void)size; /* unused */
+
+ bi = (AviBitmapInfoHeader *)movie->streams[stream].sf;
+ if (bi) {
+ bits = bi->BitCount;
+ }
+
+ if (bits == 16) {
+ unsigned short *pxl;
+ unsigned char *to;
+#ifdef __BIG_ENDIAN__
+ unsigned char *pxla;
+#endif
+
+ buf = imb_alloc_pixels(
+ movie->header->Height, movie->header->Width, 3, sizeof(unsigned char), "fromavirgbbuf");
+
+ if (buf) {
+ size_t y = movie->header->Height;
+ to = buf;
+
+ while (y--) {
+ pxl = (unsigned short *)(buffer + y * movie->header->Width * 2);
+
+#ifdef __BIG_ENDIAN__
+ pxla = (unsigned char *)pxl;
+#endif
+
+ size_t x = movie->header->Width;
+ while (x--) {
+#ifdef __BIG_ENDIAN__
+ int i = pxla[0];
+ pxla[0] = pxla[1];
+ pxla[1] = i;
+
+ pxla += 2;
+#endif
+
+ *(to++) = ((*pxl >> 10) & 0x1f) * 8;
+ *(to++) = ((*pxl >> 5) & 0x1f) * 8;
+ *(to++) = (*pxl & 0x1f) * 8;
+ pxl++;
+ }
+ }
+ }
+
+ MEM_freeN(buffer);
+
+ return buf;
+ }
+ else {
+ buf = imb_alloc_pixels(
+ movie->header->Height, movie->header->Width, 3, sizeof(unsigned char), "fromavirgbbuf");
+
+ if (buf) {
+ size_t rowstride = movie->header->Width * 3;
+ if ((bits != 16) && (movie->header->Width % 2)) {
+ rowstride++;
+ }
+
+ for (size_t y = 0; y < movie->header->Height; y++) {
+ memcpy(&buf[y * movie->header->Width * 3],
+ &buffer[((movie->header->Height - 1) - y) * rowstride],
+ movie->header->Width * 3);
+ }
+
+ for (size_t y = 0; y < (size_t)movie->header->Height * (size_t)movie->header->Width * 3;
+ y += 3) {
+ int i = buf[y];
+ buf[y] = buf[y + 2];
+ buf[y + 2] = i;
+ }
+ }
+
+ MEM_freeN(buffer);
+
+ return buf;
+ }
+}
+
+void *avi_converter_to_avi_rgb(AviMovie *movie, int stream, unsigned char *buffer, size_t *size)
+{
+ unsigned char *buf;
+
+ (void)stream; /* unused */
+
+ size_t rowstride = movie->header->Width * 3;
+ /* AVI files has uncompressed lines 4-byte aligned */
+ rowstride = (rowstride + 3) & ~3;
+
+ *size = movie->header->Height * rowstride;
+ buf = MEM_mallocN(*size, "toavirgbbuf");
+
+ for (size_t y = 0; y < movie->header->Height; y++) {
+ memcpy(&buf[y * rowstride],
+ &buffer[((movie->header->Height - 1) - y) * movie->header->Width * 3],
+ movie->header->Width * 3);
+ }
+
+ for (size_t y = 0; y < movie->header->Height; y++) {
+ for (size_t x = 0; x < movie->header->Width * 3; x += 3) {
+ int i = buf[y * rowstride + x];
+ buf[y * rowstride + x] = buf[y * rowstride + x + 2];
+ buf[y * rowstride + x + 2] = i;
+ }
+ }
+
+ MEM_freeN(buffer);
+
+ return buf;
+}
diff --git a/source/blender/io/avi/intern/avi_rgb.h b/source/blender/io/avi/intern/avi_rgb.h
new file mode 100644
index 00000000000..7c8ce590d27
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_rgb.h
@@ -0,0 +1,30 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ */
+
+#ifndef __AVI_RGB_H__
+#define __AVI_RGB_H__
+
+void *avi_converter_from_avi_rgb(AviMovie *movie, int stream, unsigned char *buffer, size_t *size);
+void *avi_converter_to_avi_rgb(AviMovie *movie, int stream, unsigned char *buffer, size_t *size);
+
+#endif /* __AVI_RGB_H__ */
diff --git a/source/blender/io/avi/intern/avi_rgb32.c b/source/blender/io/avi/intern/avi_rgb32.c
new file mode 100644
index 00000000000..3efa4814c70
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_rgb32.c
@@ -0,0 +1,94 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ *
+ * This is external code. Converts between rgb32 and avi.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+
+#include "MEM_guardedalloc.h"
+
+#include "IMB_imbuf.h"
+
+#include "AVI_avi.h"
+#include "avi_rgb32.h"
+
+void *avi_converter_from_rgb32(AviMovie *movie, int stream, unsigned char *buffer, size_t *size)
+{
+ unsigned char *buf;
+
+ (void)stream; /* unused */
+
+ *size = (size_t)movie->header->Height * (size_t)movie->header->Width * 3;
+ buf = imb_alloc_pixels(
+ movie->header->Height, movie->header->Width, 3, sizeof(unsigned char), "fromrgb32buf");
+ if (!buf) {
+ return NULL;
+ }
+
+ size_t rowstridea = movie->header->Width * 3;
+ size_t rowstrideb = movie->header->Width * 4;
+
+ for (size_t y = 0; y < movie->header->Height; y++) {
+ for (size_t x = 0; x < movie->header->Width; x++) {
+ buf[y * rowstridea + x * 3 + 0] = buffer[y * rowstrideb + x * 4 + 3];
+ buf[y * rowstridea + x * 3 + 1] = buffer[y * rowstrideb + x * 4 + 2];
+ buf[y * rowstridea + x * 3 + 2] = buffer[y * rowstrideb + x * 4 + 1];
+ }
+ }
+
+ MEM_freeN(buffer);
+
+ return buf;
+}
+
+void *avi_converter_to_rgb32(AviMovie *movie, int stream, unsigned char *buffer, size_t *size)
+{
+ unsigned char *buf;
+ unsigned char *to, *from;
+
+ (void)stream; /* unused */
+
+ *size = (size_t)movie->header->Height * (size_t)movie->header->Width * 4;
+ buf = imb_alloc_pixels(
+ movie->header->Height, movie->header->Width, 4, sizeof(unsigned char), "torgb32buf");
+ if (!buf) {
+ return NULL;
+ }
+
+ memset(buf, 255, *size);
+
+ to = buf;
+ from = buffer;
+ size_t i = (size_t)movie->header->Height * (size_t)movie->header->Width;
+
+ while (i--) {
+ memcpy(to, from, 3);
+ to += 4;
+ from += 3;
+ }
+
+ MEM_freeN(buffer);
+
+ return buf;
+}
diff --git a/source/blender/io/avi/intern/avi_rgb32.h b/source/blender/io/avi/intern/avi_rgb32.h
new file mode 100644
index 00000000000..eb4b9ca4e21
--- /dev/null
+++ b/source/blender/io/avi/intern/avi_rgb32.h
@@ -0,0 +1,30 @@
+/*
+ * 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) 2001-2002 by NaN Holding BV.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup avi
+ */
+
+#ifndef __AVI_RGB32_H__
+#define __AVI_RGB32_H__
+
+void *avi_converter_from_rgb32(AviMovie *movie, int stream, unsigned char *buffer, size_t *size);
+void *avi_converter_to_rgb32(AviMovie *movie, int stream, unsigned char *buffer, size_t *size);
+
+#endif /* __AVI_RGB32_H__ */
diff --git a/source/blender/io/collada/AnimationClipExporter.cpp b/source/blender/io/collada/AnimationClipExporter.cpp
new file mode 100644
index 00000000000..5868c24e6cd
--- /dev/null
+++ b/source/blender/io/collada/AnimationClipExporter.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+#include "GeometryExporter.h"
+#include "AnimationClipExporter.h"
+#include "MaterialExporter.h"
+
+void AnimationClipExporter::exportAnimationClips(Scene *sce)
+{
+ openLibrary();
+ std::map<std::string, COLLADASW::ColladaAnimationClip *> clips;
+
+ std::vector<std::vector<std::string>>::iterator anim_meta_entry;
+ for (anim_meta_entry = anim_meta.begin(); anim_meta_entry != anim_meta.end();
+ ++anim_meta_entry) {
+ std::vector<std::string> entry = *anim_meta_entry;
+ std::string action_id = entry[0];
+ std::string action_name = entry[1];
+
+ std::map<std::string, COLLADASW::ColladaAnimationClip *>::iterator it = clips.find(
+ action_name);
+ if (it == clips.end()) {
+ COLLADASW::ColladaAnimationClip *clip = new COLLADASW::ColladaAnimationClip(action_name);
+ clips[action_name] = clip;
+ }
+ COLLADASW::ColladaAnimationClip *clip = clips[action_name];
+ clip->setInstancedAnimation(action_id);
+ }
+
+ std::map<std::string, COLLADASW::ColladaAnimationClip *>::iterator clips_it;
+ for (clips_it = clips.begin(); clips_it != clips.end(); clips_it++) {
+ COLLADASW::ColladaAnimationClip *clip = (COLLADASW::ColladaAnimationClip *)clips_it->second;
+ addAnimationClip(*clip);
+ }
+
+ closeLibrary();
+}
diff --git a/source/blender/io/collada/AnimationClipExporter.h b/source/blender/io/collada/AnimationClipExporter.h
new file mode 100644
index 00000000000..25c69fe6b93
--- /dev/null
+++ b/source/blender/io/collada/AnimationClipExporter.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+#ifndef __ANIMATIONCLIPEXPORTER_H__
+#define __ANIMATIONCLIPEXPORTER_H__
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "COLLADASWLibraryAnimationClips.h"
+
+class AnimationClipExporter : COLLADASW::LibraryAnimationClips {
+ private:
+ Depsgraph *depsgraph;
+ Scene *scene;
+ COLLADASW::StreamWriter *sw;
+ BCExportSettings &export_settings;
+ std::vector<std::vector<std::string>> anim_meta;
+
+ public:
+ AnimationClipExporter(Depsgraph *depsgraph,
+ COLLADASW::StreamWriter *sw,
+ BCExportSettings &export_settings,
+ std::vector<std::vector<std::string>> anim_meta)
+ : COLLADASW::LibraryAnimationClips(sw),
+ depsgraph(depsgraph),
+ scene(nullptr),
+ sw(sw),
+ export_settings(export_settings),
+ anim_meta(anim_meta)
+ {
+ }
+
+ void exportAnimationClips(Scene *sce);
+};
+
+#endif /* __ANIMATIONCLIPEXPORTER_H__ */
diff --git a/source/blender/io/collada/AnimationExporter.cpp b/source/blender/io/collada/AnimationExporter.cpp
new file mode 100644
index 00000000000..cd4319e3101
--- /dev/null
+++ b/source/blender/io/collada/AnimationExporter.cpp
@@ -0,0 +1,877 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include "GeometryExporter.h"
+#include "AnimationExporter.h"
+#include "AnimationClipExporter.h"
+#include "BCAnimationSampler.h"
+#include "MaterialExporter.h"
+#include "collada_utils.h"
+
+std::string EMPTY_STRING;
+
+std::string AnimationExporter::get_axis_name(std::string channel_type, int id)
+{
+
+ static std::map<std::string, std::vector<std::string>> BC_COLLADA_AXIS_FROM_TYPE = {
+ {"color", {"R", "G", "B"}},
+ {"specular_color", {"R", "G", "B"}},
+ {"diffuse_color", {"R", "G", "B"}},
+ {"alpha", {"R", "G", "B"}},
+ {"scale", {"X", "Y", "Z"}},
+ {"location", {"X", "Y", "Z"}},
+ {"rotation_euler", {"X", "Y", "Z"}}};
+
+ std::map<std::string, std::vector<std::string>>::const_iterator it;
+
+ it = BC_COLLADA_AXIS_FROM_TYPE.find(channel_type);
+ if (it == BC_COLLADA_AXIS_FROM_TYPE.end()) {
+ return "";
+ }
+
+ const std::vector<std::string> &subchannel = it->second;
+ if (id >= subchannel.size()) {
+ return "";
+ }
+ return subchannel[id];
+}
+
+bool AnimationExporter::open_animation_container(bool has_container, Object *ob)
+{
+ if (!has_container) {
+ char anim_id[200];
+ sprintf(anim_id, "action_container-%s", translate_id(id_name(ob)).c_str());
+ openAnimation(anim_id, encode_xml(id_name(ob)));
+ }
+ return true;
+}
+
+void AnimationExporter::openAnimationWithClip(std::string action_id, std::string action_name)
+{
+ std::vector<std::string> anim_meta_entry;
+ anim_meta_entry.push_back(translate_id(action_id));
+ anim_meta_entry.push_back(action_name);
+ anim_meta.push_back(anim_meta_entry);
+
+ openAnimation(translate_id(action_id), action_name);
+}
+
+void AnimationExporter::close_animation_container(bool has_container)
+{
+ if (has_container) {
+ closeAnimation();
+ }
+}
+
+bool AnimationExporter::exportAnimations()
+{
+ Scene *sce = export_settings.get_scene();
+
+ LinkNode *export_set = this->export_settings.get_export_set();
+ bool has_anim_data = bc_has_animations(sce, export_set);
+ int animation_count = 0;
+ if (has_anim_data) {
+
+ BCObjectSet animated_subset;
+ BCAnimationSampler::get_animated_from_export_set(animated_subset, *export_set);
+ animation_count = animated_subset.size();
+ BCAnimationSampler animation_sampler(export_settings, animated_subset);
+
+ try {
+ animation_sampler.sample_scene(export_settings, /*keyframe_at_end = */ true);
+
+ openLibrary();
+
+ BCObjectSet::iterator it;
+ for (it = animated_subset.begin(); it != animated_subset.end(); ++it) {
+ Object *ob = *it;
+ exportAnimation(ob, animation_sampler);
+ }
+ }
+ catch (std::invalid_argument &iae) {
+ fprintf(stderr, "Animation export interrupted");
+ fprintf(stderr, "Exception was: %s", iae.what());
+ }
+
+ closeLibrary();
+
+#if 0
+ /* TODO: If all actions shall be exported, we need to call the
+ * AnimationClipExporter which will figure out which actions
+ * need to be exported for which objects
+ */
+ if (this->export_settings->include_all_actions) {
+ AnimationClipExporter ace(eval_ctx, sw, export_settings, anim_meta);
+ ace.exportAnimationClips(sce);
+ }
+#endif
+ }
+ return animation_count;
+}
+
+/* called for each exported object */
+void AnimationExporter::exportAnimation(Object *ob, BCAnimationSampler &sampler)
+{
+ bool container_is_open = false;
+
+ /* Transform animations (trans, rot, scale). */
+ container_is_open = open_animation_container(container_is_open, ob);
+
+ /* Now take care of the Object Animations
+ * Note: For Armatures the skeletal animation has already been exported (see above)
+ * However Armatures also can have Object animation.
+ */
+ bool export_as_matrix = this->export_settings.get_animation_transformation_type() ==
+ BC_TRANSFORMATION_TYPE_MATRIX;
+
+ if (export_as_matrix) {
+ /* export all transform_curves as one single matrix animation */
+ export_matrix_animation(ob, sampler);
+ }
+
+ export_curve_animation_set(ob, sampler, export_as_matrix);
+
+ if (ob->type == OB_ARMATURE && export_as_matrix) {
+
+#ifdef WITH_MORPH_ANIMATION
+ /* TODO: This needs to be handled by extra profiles, postponed for now */
+ export_morph_animation(ob);
+#endif
+
+ /* Export skeletal animation (if any) */
+ bArmature *arm = (bArmature *)ob->data;
+ for (Bone *root_bone = (Bone *)arm->bonebase.first; root_bone; root_bone = root_bone->next) {
+ export_bone_animations_recursive(ob, root_bone, sampler);
+ }
+ }
+
+ close_animation_container(container_is_open);
+}
+
+/*
+ * Export all animation FCurves of an Object.
+ *
+ * Note: This uses the keyframes as sample points,
+ * and exports "baked keyframes" while keeping the tangent information
+ * of the FCurves intact. This works for simple cases, but breaks
+ * especially when negative scales are involved in the animation.
+ * And when parent inverse matrices are involved (when exporting
+ * object hierarchies)
+ */
+void AnimationExporter::export_curve_animation_set(Object *ob,
+ BCAnimationSampler &sampler,
+ bool export_as_matrix)
+{
+ BCAnimationCurveMap *curves = sampler.get_curves(ob);
+ bool keep_flat_curves = this->export_settings.get_keep_flat_curves();
+
+ BCAnimationCurveMap::iterator it;
+ for (it = curves->begin(); it != curves->end(); ++it) {
+ BCAnimationCurve &curve = *it->second;
+ std::string channel_type = curve.get_channel_type();
+ if (channel_type == "rotation_quaternion") {
+ /* Can not export Quaternion animation in Collada as far as i know)
+ * Maybe automatically convert to euler rotation?
+ * Discard for now. */
+ continue;
+ }
+
+ if (export_as_matrix && curve.is_transform_curve()) {
+ /* All Transform curves will be exported within a single matrix animation,
+ * see export_matrix_animation()
+ * No need to export the curves here again.
+ */
+ continue;
+ }
+
+ if (!keep_flat_curves && !curve.is_animated()) {
+ continue;
+ }
+
+ BCAnimationCurve *mcurve = get_modified_export_curve(ob, curve, *curves);
+ if (mcurve) {
+ export_curve_animation(ob, *mcurve);
+ delete mcurve;
+ }
+ else {
+ export_curve_animation(ob, curve);
+ }
+ }
+}
+
+void AnimationExporter::export_matrix_animation(Object *ob, BCAnimationSampler &sampler)
+{
+ bool keep_flat_curves = this->export_settings.get_keep_flat_curves();
+
+ std::vector<float> frames;
+ sampler.get_object_frames(frames, ob);
+ if (frames.size() > 0) {
+ BCMatrixSampleMap samples;
+ bool is_animated = sampler.get_object_samples(samples, ob);
+ if (keep_flat_curves || is_animated) {
+ bAction *action = bc_getSceneObjectAction(ob);
+ std::string name = encode_xml(id_name(ob));
+ std::string action_name = (action == NULL) ? name + "-action" : id_name(action);
+ std::string channel_type = "transform";
+ std::string axis = "";
+ std::string id = bc_get_action_id(action_name, name, channel_type, axis);
+
+ std::string target = translate_id(name) + '/' + channel_type;
+
+ BC_global_rotation_type global_rotation_type = get_global_rotation_type(ob);
+ export_collada_matrix_animation(
+ id, name, target, frames, samples, global_rotation_type, ob->parentinv);
+ }
+ }
+}
+
+BC_global_rotation_type AnimationExporter::get_global_rotation_type(Object *ob)
+{
+ bool is_export_root = this->export_settings.is_export_root(ob);
+ if (!is_export_root) {
+ return BC_NO_ROTATION;
+ }
+
+ bool apply_global_rotation = this->export_settings.get_apply_global_orientation();
+
+ return (apply_global_rotation) ? BC_DATA_ROTATION : BC_OBJECT_ROTATION;
+}
+
+/* Write bone animations in transform matrix sources. */
+void AnimationExporter::export_bone_animations_recursive(Object *ob,
+ Bone *bone,
+ BCAnimationSampler &sampler)
+{
+ bool keep_flat_curves = this->export_settings.get_keep_flat_curves();
+
+ std::vector<float> frames;
+ sampler.get_bone_frames(frames, ob, bone);
+
+ if (frames.size()) {
+ BCMatrixSampleMap samples;
+ bool is_animated = sampler.get_bone_samples(samples, ob, bone);
+ if (keep_flat_curves || is_animated) {
+ export_bone_animation(ob, bone, frames, samples);
+ }
+ }
+
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ export_bone_animations_recursive(ob, child, sampler);
+ }
+}
+
+/**
+ * In some special cases the exported Curve needs to be replaced
+ * by a modified curve (for collada purposes)
+ * This method checks if a conversion is necessary and if applicable
+ * returns a pointer to the modified BCAnimationCurve.
+ * IMPORTANT: the modified curve must be deleted by the caller when no longer needed
+ * if no conversion is needed this method returns a NULL;
+ */
+BCAnimationCurve *AnimationExporter::get_modified_export_curve(Object *ob,
+ BCAnimationCurve &curve,
+ BCAnimationCurveMap &curves)
+{
+ std::string channel_type = curve.get_channel_type();
+ BCAnimationCurve *mcurve = NULL;
+ if (channel_type == "lens") {
+
+ /* Create an xfov curve */
+
+ BCCurveKey key(BC_ANIMATION_TYPE_CAMERA, "xfov", 0);
+ mcurve = new BCAnimationCurve(key, ob);
+
+ /* now tricky part: transform the fcurve */
+ BCValueMap lens_values;
+ curve.get_value_map(lens_values);
+
+ BCAnimationCurve *sensor_curve = NULL;
+ BCCurveKey sensor_key(BC_ANIMATION_TYPE_CAMERA, "sensor_width", 0);
+ BCAnimationCurveMap::iterator cit = curves.find(sensor_key);
+ if (cit != curves.end()) {
+ sensor_curve = cit->second;
+ }
+
+ BCValueMap::const_iterator vit;
+ for (vit = lens_values.begin(); vit != lens_values.end(); ++vit) {
+ int frame = vit->first;
+ float lens_value = vit->second;
+
+ float sensor_value;
+ if (sensor_curve) {
+ sensor_value = sensor_curve->get_value(frame);
+ }
+ else {
+ sensor_value = ((Camera *)ob->data)->sensor_x;
+ }
+ float value = RAD2DEGF(focallength_to_fov(lens_value, sensor_value));
+ mcurve->add_value(value, frame);
+ }
+ /* to reset the handles */
+ mcurve->clean_handles();
+ }
+ return mcurve;
+}
+
+void AnimationExporter::export_curve_animation(Object *ob, BCAnimationCurve &curve)
+{
+ std::string channel_target = curve.get_channel_target();
+
+ /*
+ * Some curves can not be exported as is and need some conversion
+ * For more information see implementation of get_modified_export_curve()
+ * note: if mcurve is not NULL then it must be deleted at end of this method;
+ */
+
+ int channel_index = curve.get_channel_index();
+ /* RGB or XYZ or "" */
+ std::string channel_type = curve.get_channel_type();
+ std::string axis = get_axis_name(channel_type, channel_index);
+
+ std::string action_name;
+ bAction *action = bc_getSceneObjectAction(ob);
+ action_name = (action) ? id_name(action) : "constraint_anim";
+
+ const std::string curve_name = encode_xml(curve.get_animation_name(ob));
+ std::string id = bc_get_action_id(action_name, curve_name, channel_target, axis, ".");
+
+ std::string collada_target = translate_id(curve_name);
+
+ if (curve.is_of_animation_type(BC_ANIMATION_TYPE_MATERIAL)) {
+ int material_index = curve.get_subindex();
+ Material *ma = BKE_object_material_get(ob, material_index + 1);
+ if (ma) {
+ collada_target = translate_id(id_name(ma)) + "-effect/common/" +
+ get_collada_sid(curve, axis);
+ }
+ }
+ else {
+ collada_target += "/" + get_collada_sid(curve, axis);
+ }
+
+ BC_global_rotation_type global_rotation_type = get_global_rotation_type(ob);
+ export_collada_curve_animation(
+ id, curve_name, collada_target, axis, curve, global_rotation_type);
+}
+
+void AnimationExporter::export_bone_animation(Object *ob,
+ Bone *bone,
+ BCFrames &frames,
+ BCMatrixSampleMap &samples)
+{
+ bAction *action = bc_getSceneObjectAction(ob);
+ std::string bone_name(bone->name);
+ std::string name = encode_xml(id_name(ob));
+ std::string id = bc_get_action_id(id_name(action), name, bone_name, "pose_matrix");
+ std::string target = translate_id(id_name(ob) + "_" + bone_name) + "/transform";
+
+ BC_global_rotation_type global_rotation_type = get_global_rotation_type(ob);
+ export_collada_matrix_animation(
+ id, name, target, frames, samples, global_rotation_type, ob->parentinv);
+}
+
+bool AnimationExporter::is_bone_deform_group(Bone *bone)
+{
+ bool is_def;
+ /* Check if current bone is deform */
+ if ((bone->flag & BONE_NO_DEFORM) == 0) {
+ return true;
+ }
+ /* Check child bones */
+ else {
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ /* loop through all the children until deform bone is found, and then return */
+ is_def = is_bone_deform_group(child);
+ if (is_def) {
+ return true;
+ }
+ }
+ }
+ /* no deform bone found in children also */
+ return false;
+}
+
+void AnimationExporter::export_collada_curve_animation(
+ std::string id,
+ std::string name,
+ std::string collada_target,
+ std::string axis,
+ BCAnimationCurve &curve,
+ BC_global_rotation_type global_rotation_type)
+{
+ BCFrames frames;
+ BCValues values;
+ curve.get_frames(frames);
+ curve.get_values(values);
+ std::string channel_target = curve.get_channel_target();
+
+ fprintf(
+ stdout, "Export animation curve %s (%d control points)\n", id.c_str(), int(frames.size()));
+ openAnimation(id, name);
+ BC_animation_source_type source_type = (curve.is_rotation_curve()) ? BC_SOURCE_TYPE_ANGLE :
+ BC_SOURCE_TYPE_VALUE;
+
+ std::string input_id = collada_source_from_values(
+ BC_SOURCE_TYPE_TIMEFRAME, COLLADASW::InputSemantic::INPUT, frames, id, axis);
+ std::string output_id = collada_source_from_values(
+ source_type, COLLADASW::InputSemantic::OUTPUT, values, id, axis);
+
+ bool has_tangents = false;
+ std::string interpolation_id;
+ if (this->export_settings.get_keep_smooth_curves()) {
+ interpolation_id = collada_interpolation_source(curve, id, axis, &has_tangents);
+ }
+ else {
+ interpolation_id = collada_linear_interpolation_source(frames.size(), id);
+ }
+
+ std::string intangent_id;
+ std::string outtangent_id;
+ if (has_tangents) {
+ intangent_id = collada_tangent_from_curve(
+ COLLADASW::InputSemantic::IN_TANGENT, curve, id, axis);
+ outtangent_id = collada_tangent_from_curve(
+ COLLADASW::InputSemantic::OUT_TANGENT, curve, id, axis);
+ }
+
+ std::string sampler_id = std::string(id) + SAMPLER_ID_SUFFIX;
+
+ COLLADASW::LibraryAnimations::Sampler sampler(sw, sampler_id);
+
+ sampler.addInput(COLLADASW::InputSemantic::INPUT, COLLADABU::URI(EMPTY_STRING, input_id));
+ sampler.addInput(COLLADASW::InputSemantic::OUTPUT, COLLADABU::URI(EMPTY_STRING, output_id));
+ sampler.addInput(COLLADASW::InputSemantic::INTERPOLATION,
+ COLLADABU::URI(EMPTY_STRING, interpolation_id));
+
+ if (has_tangents) {
+ sampler.addInput(COLLADASW::InputSemantic::IN_TANGENT,
+ COLLADABU::URI(EMPTY_STRING, intangent_id));
+ sampler.addInput(COLLADASW::InputSemantic::OUT_TANGENT,
+ COLLADABU::URI(EMPTY_STRING, outtangent_id));
+ }
+
+ addSampler(sampler);
+ addChannel(COLLADABU::URI(EMPTY_STRING, sampler_id), collada_target);
+
+ closeAnimation();
+}
+
+void AnimationExporter::export_collada_matrix_animation(
+ std::string id,
+ std::string name,
+ std::string target,
+ BCFrames &frames,
+ BCMatrixSampleMap &samples,
+ BC_global_rotation_type global_rotation_type,
+ Matrix &parentinv)
+{
+ fprintf(
+ stdout, "Export animation matrix %s (%d control points)\n", id.c_str(), int(frames.size()));
+
+ openAnimationWithClip(id, name);
+
+ std::string input_id = collada_source_from_values(
+ BC_SOURCE_TYPE_TIMEFRAME, COLLADASW::InputSemantic::INPUT, frames, id, "");
+ std::string output_id = collada_source_from_values(samples, id, global_rotation_type, parentinv);
+ std::string interpolation_id = collada_linear_interpolation_source(frames.size(), id);
+
+ std::string sampler_id = std::string(id) + SAMPLER_ID_SUFFIX;
+ COLLADASW::LibraryAnimations::Sampler sampler(sw, sampler_id);
+
+ sampler.addInput(COLLADASW::InputSemantic::INPUT, COLLADABU::URI(EMPTY_STRING, input_id));
+ sampler.addInput(COLLADASW::InputSemantic::OUTPUT, COLLADABU::URI(EMPTY_STRING, output_id));
+ sampler.addInput(COLLADASW::InputSemantic::INTERPOLATION,
+ COLLADABU::URI(EMPTY_STRING, interpolation_id));
+
+ /* Matrix animation has no tangents */
+
+ addSampler(sampler);
+ addChannel(COLLADABU::URI(EMPTY_STRING, sampler_id), target);
+
+ closeAnimation();
+}
+
+std::string AnimationExporter::get_semantic_suffix(COLLADASW::InputSemantic::Semantics semantic)
+{
+ switch (semantic) {
+ case COLLADASW::InputSemantic::INPUT:
+ return INPUT_SOURCE_ID_SUFFIX;
+ case COLLADASW::InputSemantic::OUTPUT:
+ return OUTPUT_SOURCE_ID_SUFFIX;
+ case COLLADASW::InputSemantic::INTERPOLATION:
+ return INTERPOLATION_SOURCE_ID_SUFFIX;
+ case COLLADASW::InputSemantic::IN_TANGENT:
+ return INTANGENT_SOURCE_ID_SUFFIX;
+ case COLLADASW::InputSemantic::OUT_TANGENT:
+ return OUTTANGENT_SOURCE_ID_SUFFIX;
+ default:
+ break;
+ }
+ return "";
+}
+
+void AnimationExporter::add_source_parameters(COLLADASW::SourceBase::ParameterNameList &param,
+ COLLADASW::InputSemantic::Semantics semantic,
+ bool is_rot,
+ const std::string axis,
+ bool transform)
+{
+ switch (semantic) {
+ case COLLADASW::InputSemantic::INPUT:
+ param.push_back("TIME");
+ break;
+ case COLLADASW::InputSemantic::OUTPUT:
+ if (is_rot) {
+ param.push_back("ANGLE");
+ }
+ else {
+ if (axis != "") {
+ param.push_back(axis);
+ }
+ else if (transform) {
+ param.push_back("TRANSFORM");
+ }
+ else {
+ /* assumes if axis isn't specified all axises are added */
+ param.push_back("X");
+ param.push_back("Y");
+ param.push_back("Z");
+ }
+ }
+ break;
+ case COLLADASW::InputSemantic::IN_TANGENT:
+ case COLLADASW::InputSemantic::OUT_TANGENT:
+ param.push_back("X");
+ param.push_back("Y");
+ break;
+ default:
+ break;
+ }
+}
+
+std::string AnimationExporter::collada_tangent_from_curve(
+ COLLADASW::InputSemantic::Semantics semantic,
+ BCAnimationCurve &curve,
+ const std::string &anim_id,
+ std::string axis_name)
+{
+ Scene *scene = this->export_settings.get_scene();
+
+ std::string channel = curve.get_channel_target();
+
+ const std::string source_id = anim_id + get_semantic_suffix(semantic);
+
+ bool is_angle = (bc_startswith(channel, "rotation") || channel == "spot_size");
+
+ COLLADASW::FloatSourceF source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(curve.sample_count());
+ source.setAccessorStride(2);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ add_source_parameters(param, semantic, is_angle, axis_name, false);
+
+ source.prepareToAppendValues();
+
+ const FCurve *fcu = curve.get_fcurve();
+ int tangent = (semantic == COLLADASW::InputSemantic::IN_TANGENT) ? 0 : 2;
+
+ for (int i = 0; i < fcu->totvert; i++) {
+ BezTriple &bezt = fcu->bezt[i];
+
+ float sampled_time = bezt.vec[tangent][0];
+ float sampled_val = bezt.vec[tangent][1];
+
+ if (is_angle) {
+ sampled_val = RAD2DEGF(sampled_val);
+ }
+
+ source.appendValues(FRA2TIME(sampled_time));
+ source.appendValues(sampled_val);
+ }
+ source.finish();
+ return source_id;
+}
+
+std::string AnimationExporter::collada_source_from_values(
+ BC_animation_source_type source_type,
+ COLLADASW::InputSemantic::Semantics semantic,
+ std::vector<float> &values,
+ const std::string &anim_id,
+ const std::string axis_name)
+{
+ BlenderContext &blender_context = this->export_settings.get_blender_context();
+ Scene *scene = blender_context.get_scene();
+ /* T can be float, int or double */
+
+ int stride = 1;
+ int entry_count = values.size() / stride;
+ std::string source_id = anim_id + get_semantic_suffix(semantic);
+
+ COLLADASW::FloatSourceF source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(entry_count);
+ source.setAccessorStride(stride);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ add_source_parameters(param, semantic, source_type == BC_SOURCE_TYPE_ANGLE, axis_name, false);
+
+ source.prepareToAppendValues();
+
+ for (int i = 0; i < entry_count; i++) {
+ float val = values[i];
+ switch (source_type) {
+ case BC_SOURCE_TYPE_TIMEFRAME:
+ val = FRA2TIME(val);
+ break;
+ case BC_SOURCE_TYPE_ANGLE:
+ val = RAD2DEGF(val);
+ break;
+ default:
+ break;
+ }
+ source.appendValues(val);
+ }
+
+ source.finish();
+
+ return source_id;
+}
+
+/*
+ * Create a collada matrix source for a set of samples
+ */
+std::string AnimationExporter::collada_source_from_values(
+ BCMatrixSampleMap &samples,
+ const std::string &anim_id,
+ BC_global_rotation_type global_rotation_type,
+ Matrix &parentinv)
+{
+ COLLADASW::InputSemantic::Semantics semantic = COLLADASW::InputSemantic::OUTPUT;
+ std::string source_id = anim_id + get_semantic_suffix(semantic);
+
+ COLLADASW::Float4x4Source source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(samples.size());
+ source.setAccessorStride(16);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ add_source_parameters(param, semantic, false, "", true);
+
+ source.prepareToAppendValues();
+
+ BCMatrixSampleMap::iterator it;
+ /* could be made configurable */
+ int precision = (this->export_settings.get_limit_precision()) ? 6 : -1;
+ for (it = samples.begin(); it != samples.end(); it++) {
+ BCMatrix sample = BCMatrix(*it->second);
+ BCMatrix global_transform = this->export_settings.get_global_transform();
+ DMatrix daemat;
+ if (this->export_settings.get_apply_global_orientation()) {
+ sample.apply_transform(global_transform);
+ }
+ else {
+ sample.add_transform(global_transform);
+ }
+ sample.get_matrix(daemat, true, precision);
+ source.appendValues(daemat);
+ }
+
+ source.finish();
+ return source_id;
+}
+
+std::string AnimationExporter::collada_interpolation_source(const BCAnimationCurve &curve,
+ const std::string &anim_id,
+ const std::string axis,
+ bool *has_tangents)
+{
+ std::string source_id = anim_id + get_semantic_suffix(COLLADASW::InputSemantic::INTERPOLATION);
+
+ COLLADASW::NameSource source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(curve.sample_count());
+ source.setAccessorStride(1);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("INTERPOLATION");
+
+ source.prepareToAppendValues();
+
+ *has_tangents = false;
+
+ std::vector<float> frames;
+ curve.get_frames(frames);
+
+ for (unsigned int i = 0; i < curve.sample_count(); i++) {
+ float frame = frames[i];
+ int ipo = curve.get_interpolation_type(frame);
+ if (ipo == BEZT_IPO_BEZ) {
+ source.appendValues(BEZIER_NAME);
+ *has_tangents = true;
+ }
+ else if (ipo == BEZT_IPO_CONST) {
+ source.appendValues(STEP_NAME);
+ }
+ else {
+ /* BEZT_IPO_LIN */
+ source.appendValues(LINEAR_NAME);
+ }
+ }
+ /* unsupported? -- HERMITE, CARDINAL, BSPLINE, NURBS */
+
+ source.finish();
+
+ return source_id;
+}
+
+std::string AnimationExporter::collada_linear_interpolation_source(int tot,
+ const std::string &anim_id)
+{
+ std::string source_id = anim_id + get_semantic_suffix(COLLADASW::InputSemantic::INTERPOLATION);
+
+ COLLADASW::NameSource source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(tot);
+ source.setAccessorStride(1);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("INTERPOLATION");
+
+ source.prepareToAppendValues();
+
+ for (int i = 0; i < tot; i++) {
+ source.appendValues(LINEAR_NAME);
+ }
+
+ source.finish();
+
+ return source_id;
+}
+
+const std::string AnimationExporter::get_collada_name(std::string channel_type) const
+{
+ /*
+ * Translation table to map FCurve animation types to Collada animation.
+ * Todo: Maybe we can keep the names from the fcurves here instead of
+ * mapping. However this is what i found in the old code. So keep
+ * this map for now.
+ */
+ static std::map<std::string, std::string> BC_CHANNEL_BLENDER_TO_COLLADA = {
+ {"rotation", "rotation"},
+ {"rotation_euler", "rotation"},
+ {"rotation_quaternion", "rotation"},
+ {"scale", "scale"},
+ {"location", "location"},
+
+ /* Materials */
+ {"specular_color", "specular"},
+ {"diffuse_color", "diffuse"},
+ {"ior", "index_of_refraction"},
+ {"specular_hardness", "specular_hardness"},
+ {"alpha", "alpha"},
+
+ /* Lights */
+ {"color", "color"},
+ {"fall_off_angle", "falloff_angle"},
+ {"spot_size", "falloff_angle"},
+ {"fall_off_exponent", "falloff_exponent"},
+ {"spot_blend", "falloff_exponent"},
+ /* Special blender profile (todo: make this more elegant). */
+ {"blender/blender_dist", "blender/blender_dist"},
+ /* Special blender profile (todo: make this more elegant). */
+ {"distance", "blender/blender_dist"},
+
+ /* Cameras */
+ {"lens", "xfov"},
+ {"xfov", "xfov"},
+ {"xmag", "xmag"},
+ {"zfar", "zfar"},
+ {"znear", "znear"},
+ {"ortho_scale", "xmag"},
+ {"clip_end", "zfar"},
+ {"clip_start", "znear"}};
+
+ std::map<std::string, std::string>::iterator name_it = BC_CHANNEL_BLENDER_TO_COLLADA.find(
+ channel_type);
+ if (name_it == BC_CHANNEL_BLENDER_TO_COLLADA.end()) {
+ return "";
+ }
+ std::string tm_name = name_it->second;
+ return tm_name;
+}
+
+/*
+ * Assign sid of the animated parameter or transform for rotation,
+ * axis name is always appended and the value of append_axis is ignored
+ */
+std::string AnimationExporter::get_collada_sid(const BCAnimationCurve &curve,
+ const std::string axis_name)
+{
+ std::string channel_target = curve.get_channel_target();
+ std::string channel_type = curve.get_channel_type();
+ std::string tm_name = get_collada_name(channel_type);
+
+ bool is_angle = curve.is_rotation_curve();
+
+ if (tm_name.size()) {
+ if (is_angle) {
+ return tm_name + std::string(axis_name) + ".ANGLE";
+ }
+ else if (axis_name != "") {
+ return tm_name + "." + std::string(axis_name);
+ }
+ else {
+ return tm_name;
+ }
+ }
+
+ return tm_name;
+}
+
+#ifdef WITH_MORPH_ANIMATION
+/* TODO: This function needs to be implemented similar to the material animation export
+ * So we have to update BCSample for this to work. */
+void AnimationExporter::export_morph_animation(Object *ob, BCAnimationSampler &sampler)
+{
+ FCurve *fcu;
+ Key *key = BKE_key_from_object(ob);
+ if (!key) {
+ return;
+ }
+
+ if (key->adt && key->adt->action) {
+ fcu = (FCurve *)key->adt->action->curves.first;
+
+ while (fcu) {
+ BC_animation_transform_type tm_type = get_transform_type(fcu->rna_path);
+
+ create_keyframed_animation(ob, fcu, tm_type, true, sampler);
+
+ fcu = fcu->next;
+ }
+ }
+}
+#endif
diff --git a/source/blender/io/collada/AnimationExporter.h b/source/blender/io/collada/AnimationExporter.h
new file mode 100644
index 00000000000..64751ec5327
--- /dev/null
+++ b/source/blender/io/collada/AnimationExporter.h
@@ -0,0 +1,266 @@
+/*
+ * 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.
+ */
+
+#ifndef __ANIMATIONEXPORTER_H__
+#define __ANIMATIONEXPORTER_H__
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+#include "BCAnimationCurve.h"
+
+extern "C" {
+#include "DNA_scene_types.h"
+#include "DNA_object_types.h"
+#include "DNA_anim_types.h"
+#include "DNA_action_types.h"
+#include "DNA_curve_types.h"
+#include "DNA_light_types.h"
+#include "DNA_camera_types.h"
+#include "DNA_armature_types.h"
+#include "DNA_material_types.h"
+#include "DNA_constraint_types.h"
+#include "DNA_scene_types.h"
+
+#include "BLI_math.h"
+#include "BLI_string.h"
+#include "BLI_listbase.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_fcurve.h"
+#include "BKE_animsys.h"
+#include "BKE_scene.h"
+#include "BKE_action.h" // pose functions
+#include "BKE_armature.h"
+#include "BKE_object.h"
+#include "BKE_constraint.h"
+#include "BIK_api.h"
+#include "ED_object.h"
+}
+
+#include "MEM_guardedalloc.h"
+
+#include "RNA_access.h"
+
+#include "COLLADASWSource.h"
+#include "COLLADASWInstanceGeometry.h"
+#include "COLLADASWInputList.h"
+#include "COLLADASWPrimitves.h"
+#include "COLLADASWVertices.h"
+#include "COLLADASWLibraryAnimations.h"
+#include "COLLADASWParamTemplate.h"
+#include "COLLADASWParamBase.h"
+#include "COLLADASWSampler.h"
+#include "COLLADASWConstants.h"
+#include "COLLADASWBaseInputElement.h"
+
+#include "EffectExporter.h"
+#include "BCAnimationSampler.h"
+#include "collada_internal.h"
+
+#include "IK_solver.h"
+
+#include <vector>
+#include <map>
+#include <algorithm> // std::find
+
+typedef enum BC_animation_source_type {
+ BC_SOURCE_TYPE_VALUE,
+ BC_SOURCE_TYPE_ANGLE,
+ BC_SOURCE_TYPE_TIMEFRAME,
+} BC_animation_source_type;
+
+typedef enum BC_global_rotation_type {
+ BC_NO_ROTATION,
+ BC_OBJECT_ROTATION,
+ BC_DATA_ROTATION
+} BC_global_rotation_type;
+
+class AnimationExporter : COLLADASW::LibraryAnimations {
+ private:
+ COLLADASW::StreamWriter *sw;
+ BCExportSettings &export_settings;
+
+ BC_global_rotation_type get_global_rotation_type(Object *ob);
+
+ public:
+ AnimationExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings)
+ : COLLADASW::LibraryAnimations(sw), sw(sw), export_settings(export_settings)
+ {
+ }
+
+ bool exportAnimations();
+
+ // called for each exported object
+ void operator()(Object *ob);
+
+ protected:
+ void export_object_constraint_animation(Object *ob);
+
+ void export_morph_animation(Object *ob);
+
+ void write_bone_animation_matrix(Object *ob_arm, Bone *bone);
+
+ void write_bone_animation(Object *ob_arm, Bone *bone);
+
+ void sample_and_write_bone_animation(Object *ob_arm, Bone *bone, int transform_type);
+
+ void sample_and_write_bone_animation_matrix(Object *ob_arm, Bone *bone);
+
+ void sample_animation(float *v,
+ std::vector<float> &frames,
+ int type,
+ Bone *bone,
+ Object *ob_arm,
+ bPoseChannel *pChan);
+
+ void sample_animation(std::vector<float[4][4]> &mats,
+ std::vector<float> &frames,
+ Bone *bone,
+ Object *ob_arm,
+ bPoseChannel *pChan);
+
+ // dae_bone_animation -> add_bone_animation
+ // (blend this into dae_bone_animation)
+ void dae_bone_animation(std::vector<float> &fra,
+ float *v,
+ int tm_type,
+ int axis,
+ std::string ob_name,
+ std::string bone_name);
+
+ void dae_baked_animation(std::vector<float> &fra, Object *ob_arm, Bone *bone);
+
+ void dae_baked_object_animation(std::vector<float> &fra, Object *ob);
+
+ float convert_time(float frame);
+
+ float convert_angle(float angle);
+
+ std::vector<std::vector<std::string>> anim_meta;
+
+ /* Main entry point into Animation export (called for each exported object) */
+ void exportAnimation(Object *ob, BCAnimationSampler &sampler);
+
+ /* export animation as separate trans/rot/scale curves */
+ void export_curve_animation_set(Object *ob, BCAnimationSampler &sampler, bool export_tm_curves);
+
+ /* export one single curve */
+ void export_curve_animation(Object *ob, BCAnimationCurve &curve);
+
+ /* export animation as matrix data */
+ void export_matrix_animation(Object *ob, BCAnimationSampler &sampler);
+
+ /* step through the bone hierarchy */
+ void export_bone_animations_recursive(Object *ob_arm, Bone *bone, BCAnimationSampler &sampler);
+
+ /* Export for one bone */
+ void export_bone_animation(Object *ob, Bone *bone, BCFrames &frames, BCMatrixSampleMap &outmats);
+
+ /* call to the low level collada exporter */
+ void export_collada_curve_animation(std::string id,
+ std::string name,
+ std::string target,
+ std::string axis,
+ BCAnimationCurve &curve,
+ BC_global_rotation_type global_rotation_type);
+
+ /* call to the low level collada exporter */
+ void export_collada_matrix_animation(std::string id,
+ std::string name,
+ std::string target,
+ BCFrames &frames,
+ BCMatrixSampleMap &outmats,
+ BC_global_rotation_type global_rotation_type,
+ Matrix &parentinv);
+
+ BCAnimationCurve *get_modified_export_curve(Object *ob,
+ BCAnimationCurve &curve,
+ BCAnimationCurveMap &curves);
+
+ /* Helper functions */
+ void openAnimationWithClip(std::string id, std::string name);
+ bool open_animation_container(bool has_container, Object *ob);
+ void close_animation_container(bool has_container);
+
+ /* Input and Output sources (single valued) */
+ std::string collada_source_from_values(BC_animation_source_type tm_channel,
+ COLLADASW::InputSemantic::Semantics semantic,
+ std::vector<float> &values,
+ const std::string &anim_id,
+ const std::string axis_name);
+
+ /* Output sources (matrix data) */
+ std::string collada_source_from_values(BCMatrixSampleMap &samples,
+ const std::string &anim_id,
+ BC_global_rotation_type global_rotation_type,
+ Matrix &parentinv);
+
+ /* Interpolation sources */
+ std::string collada_linear_interpolation_source(int tot, const std::string &anim_id);
+
+ /* source ID = animation_name + semantic_suffix */
+
+ std::string get_semantic_suffix(COLLADASW::InputSemantic::Semantics semantic);
+
+ void add_source_parameters(COLLADASW::SourceBase::ParameterNameList &param,
+ COLLADASW::InputSemantic::Semantics semantic,
+ bool is_rot,
+ const std::string axis,
+ bool transform);
+
+ int get_point_in_curve(BCBezTriple &bezt,
+ COLLADASW::InputSemantic::Semantics semantic,
+ bool is_angle,
+ float *values);
+ int get_point_in_curve(const BCAnimationCurve &curve,
+ float sample_frame,
+ COLLADASW::InputSemantic::Semantics semantic,
+ bool is_angle,
+ float *values);
+
+ std::string collada_tangent_from_curve(COLLADASW::InputSemantic::Semantics semantic,
+ BCAnimationCurve &curve,
+ const std::string &anim_id,
+ const std::string axis_name);
+
+ std::string collada_interpolation_source(const BCAnimationCurve &curve,
+ const std::string &anim_id,
+ std::string axis_name,
+ bool *has_tangents);
+
+ std::string get_axis_name(std::string channel, int id);
+ const std::string get_collada_name(std::string channel_target) const;
+ std::string get_collada_sid(const BCAnimationCurve &curve, const std::string axis_name);
+
+ /* ===================================== */
+ /* Currently unused or not (yet?) needed */
+ /* ===================================== */
+
+ bool is_bone_deform_group(Bone *bone);
+
+#if 0
+ BC_animation_transform_type _get_transform_type(const std::string path);
+ void get_eul_source_for_quat(std::vector<float> &cache, Object *ob);
+#endif
+
+#ifdef WITH_MORPH_ANIMATION
+ void export_morph_animation(Object *ob, BCAnimationSampler &sampler);
+#endif
+};
+
+#endif /* __ANIMATIONEXPORTER_H__ */
diff --git a/source/blender/io/collada/AnimationImporter.cpp b/source/blender/io/collada/AnimationImporter.cpp
new file mode 100644
index 00000000000..715cd9e1a12
--- /dev/null
+++ b/source/blender/io/collada/AnimationImporter.cpp
@@ -0,0 +1,2232 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <stddef.h>
+
+/* COLLADABU_ASSERT, may be able to remove later */
+#include "COLLADABUPlatform.h"
+
+#include "DNA_armature_types.h"
+
+#include "ED_keyframing.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_string.h"
+#include "BLI_string_utils.h"
+
+#include "BLT_translation.h"
+
+#include "BKE_action.h"
+#include "BKE_armature.h"
+#include "BKE_fcurve.h"
+#include "BKE_object.h"
+
+#include "MEM_guardedalloc.h"
+
+#include "collada_utils.h"
+#include "AnimationImporter.h"
+#include "ArmatureImporter.h"
+#include "MaterialExporter.h"
+
+#include <algorithm>
+
+/* first try node name, if not available (since is optional), fall back to original id */
+template<class T> static const char *bc_get_joint_name(T *node)
+{
+ const std::string &id = node->getName();
+ return id.size() ? id.c_str() : node->getOriginalId().c_str();
+}
+
+FCurve *AnimationImporter::create_fcurve(int array_index, const char *rna_path)
+{
+ FCurve *fcu = (FCurve *)MEM_callocN(sizeof(FCurve), "FCurve");
+ fcu->flag = (FCURVE_VISIBLE | FCURVE_AUTO_HANDLES | FCURVE_SELECTED);
+ fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path));
+ fcu->array_index = array_index;
+ return fcu;
+}
+
+void AnimationImporter::add_bezt(FCurve *fcu,
+ float frame,
+ float value,
+ eBezTriple_Interpolation ipo)
+{
+ // float fps = (float)FPS;
+ BezTriple bez;
+ memset(&bez, 0, sizeof(BezTriple));
+ bez.vec[1][0] = frame;
+ bez.vec[1][1] = value;
+ bez.ipo = ipo; /* use default interpolation mode here... */
+ bez.f1 = bez.f2 = bez.f3 = SELECT;
+ bez.h1 = bez.h2 = HD_AUTO;
+ insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS);
+ calchandles_fcurve(fcu);
+}
+
+/* create one or several fcurves depending on the number of parameters being animated */
+void AnimationImporter::animation_to_fcurves(COLLADAFW::AnimationCurve *curve)
+{
+ COLLADAFW::FloatOrDoubleArray &input = curve->getInputValues();
+ COLLADAFW::FloatOrDoubleArray &output = curve->getOutputValues();
+
+ float fps = (float)FPS;
+ size_t dim = curve->getOutDimension();
+ unsigned int i;
+
+ std::vector<FCurve *> &fcurves = curve_map[curve->getUniqueId()];
+
+ switch (dim) {
+ case 1: /* X, Y, Z or angle */
+ case 3: /* XYZ */
+ case 4:
+ case 16: /* matrix */
+ {
+ for (i = 0; i < dim; i++) {
+ FCurve *fcu = (FCurve *)MEM_callocN(sizeof(FCurve), "FCurve");
+
+ fcu->flag = (FCURVE_VISIBLE | FCURVE_AUTO_HANDLES | FCURVE_SELECTED);
+ fcu->array_index = 0;
+ fcu->auto_smoothing = U.auto_smoothing_new;
+
+ for (unsigned int j = 0; j < curve->getKeyCount(); j++) {
+ BezTriple bez;
+ memset(&bez, 0, sizeof(BezTriple));
+
+ /* input, output */
+ bez.vec[1][0] = bc_get_float_value(input, j) * fps;
+ bez.vec[1][1] = bc_get_float_value(output, j * dim + i);
+ bez.h1 = bez.h2 = HD_AUTO;
+
+ if (curve->getInterpolationType() == COLLADAFW::AnimationCurve::INTERPOLATION_BEZIER ||
+ curve->getInterpolationType() == COLLADAFW::AnimationCurve::INTERPOLATION_STEP) {
+ COLLADAFW::FloatOrDoubleArray &intan = curve->getInTangentValues();
+ COLLADAFW::FloatOrDoubleArray &outtan = curve->getOutTangentValues();
+
+ /* intangent */
+ unsigned int index = 2 * (j * dim + i);
+ bez.vec[0][0] = bc_get_float_value(intan, index) * fps;
+ bez.vec[0][1] = bc_get_float_value(intan, index + 1);
+
+ /* outtangent */
+ bez.vec[2][0] = bc_get_float_value(outtan, index) * fps;
+ bez.vec[2][1] = bc_get_float_value(outtan, index + 1);
+ if (curve->getInterpolationType() == COLLADAFW::AnimationCurve::INTERPOLATION_BEZIER) {
+ bez.ipo = BEZT_IPO_BEZ;
+ bez.h1 = bez.h2 = HD_AUTO_ANIM;
+ }
+ else {
+ bez.ipo = BEZT_IPO_CONST;
+ }
+ }
+ else {
+ bez.ipo = BEZT_IPO_LIN;
+ }
+#if 0
+ bez.ipo = U.ipo_new; /* use default interpolation mode here... */
+#endif
+ bez.f1 = bez.f2 = bez.f3 = SELECT;
+
+ insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS);
+ }
+
+ calchandles_fcurve(fcu);
+
+ fcurves.push_back(fcu);
+ unused_curves.push_back(fcu);
+ }
+ } break;
+ default:
+ fprintf(stderr,
+ "Output dimension of %d is not yet supported (animation id = %s)\n",
+ (int)dim,
+ curve->getOriginalId().c_str());
+ }
+}
+
+void AnimationImporter::fcurve_deg_to_rad(FCurve *cu)
+{
+ for (unsigned int i = 0; i < cu->totvert; i++) {
+ /* TODO convert handles too */
+ cu->bezt[i].vec[1][1] *= DEG2RADF(1.0f);
+ cu->bezt[i].vec[0][1] *= DEG2RADF(1.0f);
+ cu->bezt[i].vec[2][1] *= DEG2RADF(1.0f);
+ }
+}
+
+void AnimationImporter::fcurve_scale(FCurve *cu, int scale)
+{
+ for (unsigned int i = 0; i < cu->totvert; i++) {
+ /* TODO convert handles too */
+ cu->bezt[i].vec[1][1] *= scale;
+ cu->bezt[i].vec[0][1] *= scale;
+ cu->bezt[i].vec[2][1] *= scale;
+ }
+}
+
+void AnimationImporter::fcurve_is_used(FCurve *fcu)
+{
+ unused_curves.erase(std::remove(unused_curves.begin(), unused_curves.end(), fcu),
+ unused_curves.end());
+}
+
+void AnimationImporter::add_fcurves_to_object(Main *bmain,
+ Object *ob,
+ std::vector<FCurve *> &curves,
+ char *rna_path,
+ int array_index,
+ Animation *animated)
+{
+ bAction *act;
+
+ if (!ob->adt || !ob->adt->action) {
+ act = ED_id_action_ensure(bmain, (ID *)&ob->id);
+ }
+ else {
+ act = ob->adt->action;
+ }
+
+ std::vector<FCurve *>::iterator it;
+ int i;
+
+#if 0
+ char *p = strstr(rna_path, "rotation_euler");
+ bool is_rotation = p && *(p + strlen("rotation_euler")) == '\0';
+
+ /* convert degrees to radians for rotation */
+ if (is_rotation) {
+ fcurve_deg_to_rad(fcu);
+ }
+#endif
+
+ for (it = curves.begin(), i = 0; it != curves.end(); it++, i++) {
+ FCurve *fcu = *it;
+ fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path));
+
+ if (array_index == -1) {
+ fcu->array_index = i;
+ }
+ else {
+ fcu->array_index = array_index;
+ }
+
+ if (ob->type == OB_ARMATURE) {
+ bActionGroup *grp = NULL;
+ const char *bone_name = bc_get_joint_name(animated->node);
+
+ if (bone_name) {
+ /* try to find group */
+ grp = BKE_action_group_find_name(act, bone_name);
+
+ /* no matching groups, so add one */
+ if (grp == NULL) {
+ /* Add a new group, and make it active */
+ grp = (bActionGroup *)MEM_callocN(sizeof(bActionGroup), "bActionGroup");
+
+ grp->flag = AGRP_SELECTED;
+ BLI_strncpy(grp->name, bone_name, sizeof(grp->name));
+
+ BLI_addtail(&act->groups, grp);
+ BLI_uniquename(&act->groups,
+ grp,
+ CTX_DATA_(BLT_I18NCONTEXT_ID_ACTION, "Group"),
+ '.',
+ offsetof(bActionGroup, name),
+ 64);
+ }
+
+ /* add F-Curve to group */
+ action_groups_add_channel(act, grp, fcu);
+ fcurve_is_used(fcu);
+ }
+#if 0
+ if (is_rotation) {
+ fcurves_actionGroup_map[grp].push_back(fcu);
+ }
+#endif
+ }
+ else {
+ BLI_addtail(&act->curves, fcu);
+ fcurve_is_used(fcu);
+ }
+ }
+}
+
+AnimationImporter::~AnimationImporter()
+{
+ /* free unused FCurves */
+ for (std::vector<FCurve *>::iterator it = unused_curves.begin(); it != unused_curves.end();
+ it++) {
+ free_fcurve(*it);
+ }
+
+ if (unused_curves.size()) {
+ fprintf(stderr, "removed %d unused curves\n", (int)unused_curves.size());
+ }
+}
+
+bool AnimationImporter::write_animation(const COLLADAFW::Animation *anim)
+{
+ if (anim->getAnimationType() == COLLADAFW::Animation::ANIMATION_CURVE) {
+ COLLADAFW::AnimationCurve *curve = (COLLADAFW::AnimationCurve *)anim;
+
+ /* XXX Don't know if it's necessary
+ * Should we check outPhysicalDimension? */
+ if (curve->getInPhysicalDimension() != COLLADAFW::PHYSICAL_DIMENSION_TIME) {
+ fprintf(stderr, "Inputs physical dimension is not time.\n");
+ return true;
+ }
+
+ /* a curve can have mixed interpolation type,
+ * in this case curve->getInterpolationTypes returns a list of interpolation types per key */
+ COLLADAFW::AnimationCurve::InterpolationType interp = curve->getInterpolationType();
+
+ if (interp != COLLADAFW::AnimationCurve::INTERPOLATION_MIXED) {
+ switch (interp) {
+ case COLLADAFW::AnimationCurve::INTERPOLATION_LINEAR:
+ case COLLADAFW::AnimationCurve::INTERPOLATION_BEZIER:
+ case COLLADAFW::AnimationCurve::INTERPOLATION_STEP:
+ animation_to_fcurves(curve);
+ break;
+ default:
+ /* TODO there're also CARDINAL, HERMITE, BSPLINE and STEP types */
+ fprintf(stderr,
+ "CARDINAL, HERMITE and BSPLINE anim interpolation types not supported yet.\n");
+ break;
+ }
+ }
+ else {
+ /* not supported yet */
+ fprintf(stderr, "MIXED anim interpolation type is not supported yet.\n");
+ }
+ }
+ else {
+ fprintf(stderr, "FORMULA animation type is not supported yet.\n");
+ }
+
+ return true;
+}
+
+/* called on post-process stage after writeVisualScenes */
+bool AnimationImporter::write_animation_list(const COLLADAFW::AnimationList *animlist)
+{
+ const COLLADAFW::UniqueId &animlist_id = animlist->getUniqueId();
+ animlist_map[animlist_id] = animlist;
+
+#if 0
+
+ /* should not happen */
+ if (uid_animated_map.find(animlist_id) == uid_animated_map.end()) {
+ return true;
+ }
+
+ /* for bones rna_path is like: pose.bones["bone-name"].rotation */
+
+#endif
+
+ return true;
+}
+
+/* \todo refactor read_node_transform to not automatically apply anything,
+ * but rather return the transform matrix, so caller can do with it what is
+ * necessary. Same for \ref get_node_mat */
+void AnimationImporter::read_node_transform(COLLADAFW::Node *node, Object *ob)
+{
+ float mat[4][4];
+ TransformReader::get_node_mat(mat, node, &uid_animated_map, ob);
+ if (ob) {
+ copy_m4_m4(ob->obmat, mat);
+ BKE_object_apply_mat4(ob, ob->obmat, 0, 0);
+ }
+}
+
+#if 0
+virtual void AnimationImporter::change_eul_to_quat(Object *ob, bAction *act)
+{
+ bActionGroup *grp;
+ int i;
+
+ for (grp = (bActionGroup *)act->groups.first; grp; grp = grp->next) {
+
+ FCurve *eulcu[3] = {NULL, NULL, NULL};
+
+ if (fcurves_actionGroup_map.find(grp) == fcurves_actionGroup_map.end()) {
+ continue;
+ }
+
+ std::vector<FCurve *> &rot_fcurves = fcurves_actionGroup_map[grp];
+
+ if (rot_fcurves.size() > 3) {
+ continue;
+ }
+
+ for (i = 0; i < rot_fcurves.size(); i++) {
+ eulcu[rot_fcurves[i]->array_index] = rot_fcurves[i];
+ }
+
+ char joint_path[100];
+ char rna_path[100];
+
+ BLI_snprintf(joint_path, sizeof(joint_path), "pose.bones[\"%s\"]", grp->name);
+ BLI_snprintf(rna_path, sizeof(rna_path), "%s.rotation_quaternion", joint_path);
+
+ FCurve *quatcu[4] = {
+ create_fcurve(0, rna_path),
+ create_fcurve(1, rna_path),
+ create_fcurve(2, rna_path),
+ create_fcurve(3, rna_path),
+ };
+
+ bPoseChannel *chan = BKE_pose_channel_find_name(ob->pose, grp->name);
+
+ float m4[4][4], irest[3][3];
+ invert_m4_m4(m4, chan->bone->arm_mat);
+ copy_m3_m4(irest, m4);
+
+ for (i = 0; i < 3; i++) {
+
+ FCurve *cu = eulcu[i];
+
+ if (!cu) {
+ continue;
+ }
+
+ for (int j = 0; j < cu->totvert; j++) {
+ float frame = cu->bezt[j].vec[1][0];
+
+ float eul[3] = {
+ eulcu[0] ? evaluate_fcurve(eulcu[0], frame) : 0.0f,
+ eulcu[1] ? evaluate_fcurve(eulcu[1], frame) : 0.0f,
+ eulcu[2] ? evaluate_fcurve(eulcu[2], frame) : 0.0f,
+ };
+
+ /* make eul relative to bone rest pose */
+ float rot[3][3], rel[3][3], quat[4];
+
+# if 0
+ eul_to_mat3(rot, eul);
+ mul_m3_m3m3(rel, irest, rot);
+ mat3_to_quat(quat, rel);
+# endif
+
+ eul_to_quat(quat, eul);
+
+ for (int k = 0; k < 4; k++) {
+ create_bezt(quatcu[k], frame, quat[k], U.ipo_new);
+ }
+ }
+ }
+
+ /* now replace old Euler curves */
+
+ for (i = 0; i < 3; i++) {
+ if (!eulcu[i]) {
+ continue;
+ }
+
+ action_groups_remove_channel(act, eulcu[i]);
+ free_fcurve(eulcu[i]);
+ }
+
+ chan->rotmode = ROT_MODE_QUAT;
+
+ for (i = 0; i < 4; i++) {
+ action_groups_add_channel(act, grp, quatcu[i]);
+ }
+ }
+
+ bPoseChannel *pchan;
+ for (pchan = (bPoseChannel *)ob->pose->chanbase.first; pchan; pchan = pchan->next) {
+ pchan->rotmode = ROT_MODE_QUAT;
+ }
+}
+#endif
+
+/* sets the rna_path and array index to curve */
+void AnimationImporter::modify_fcurve(std::vector<FCurve *> *curves,
+ const char *rna_path,
+ int array_index,
+ int scale)
+{
+ std::vector<FCurve *>::iterator it;
+ int i;
+ for (it = curves->begin(), i = 0; it != curves->end(); it++, i++) {
+ FCurve *fcu = *it;
+ fcu->rna_path = BLI_strdup(rna_path);
+
+ if (array_index == -1) {
+ fcu->array_index = i;
+ }
+ else {
+ fcu->array_index = array_index;
+ }
+
+ if (scale != 1) {
+ fcurve_scale(fcu, scale);
+ }
+
+ fcurve_is_used(fcu);
+ }
+}
+
+void AnimationImporter::unused_fcurve(std::vector<FCurve *> *curves)
+{
+ /* when an error happens and we can't actually use curve remove it from unused_curves */
+ std::vector<FCurve *>::iterator it;
+ for (it = curves->begin(); it != curves->end(); it++) {
+ FCurve *fcu = *it;
+ fcurve_is_used(fcu);
+ }
+}
+
+void AnimationImporter::find_frames(std::vector<float> *frames, std::vector<FCurve *> *curves)
+{
+ std::vector<FCurve *>::iterator iter;
+ for (iter = curves->begin(); iter != curves->end(); iter++) {
+ FCurve *fcu = *iter;
+
+ for (unsigned int k = 0; k < fcu->totvert; k++) {
+ /* get frame value from bezTriple */
+ float fra = fcu->bezt[k].vec[1][0];
+ /* if frame already not added add frame to frames */
+ if (std::find(frames->begin(), frames->end(), fra) == frames->end()) {
+ frames->push_back(fra);
+ }
+ }
+ }
+}
+
+static int get_animation_axis_index(const COLLADABU::Math::Vector3 &axis)
+{
+ int index;
+ if (COLLADABU::Math::Vector3::UNIT_X == axis) {
+ index = 0;
+ }
+ else if (COLLADABU::Math::Vector3::UNIT_Y == axis) {
+ index = 1;
+ }
+ else if (COLLADABU::Math::Vector3::UNIT_Z == axis) {
+ index = 2;
+ }
+ else {
+ index = -1;
+ }
+ return index;
+}
+
+/* creates the rna_paths and array indices of fcurves from animations using transformation and
+ * bound animation class of each animation. */
+void AnimationImporter::Assign_transform_animations(
+ COLLADAFW::Transformation *transform,
+ const COLLADAFW::AnimationList::AnimationBinding *binding,
+ std::vector<FCurve *> *curves,
+ bool is_joint,
+ char *joint_path)
+{
+ COLLADAFW::Transformation::TransformationType tm_type = transform->getTransformationType();
+ bool is_matrix = tm_type == COLLADAFW::Transformation::MATRIX;
+ bool is_rotation = tm_type == COLLADAFW::Transformation::ROTATE;
+
+ /* to check if the no of curves are valid */
+ bool xyz = ((tm_type == COLLADAFW::Transformation::TRANSLATE ||
+ tm_type == COLLADAFW::Transformation::SCALE) &&
+ binding->animationClass == COLLADAFW::AnimationList::POSITION_XYZ);
+
+ if (!((!xyz && curves->size() == 1) || (xyz && curves->size() == 3) || is_matrix)) {
+ fprintf(stderr, "expected %d curves, got %d\n", xyz ? 3 : 1, (int)curves->size());
+ return;
+ }
+
+ char rna_path[100];
+
+ switch (tm_type) {
+ case COLLADAFW::Transformation::TRANSLATE:
+ case COLLADAFW::Transformation::SCALE: {
+ bool loc = tm_type == COLLADAFW::Transformation::TRANSLATE;
+ if (is_joint) {
+ BLI_snprintf(rna_path, sizeof(rna_path), "%s.%s", joint_path, loc ? "location" : "scale");
+ }
+ else {
+ BLI_strncpy(rna_path, loc ? "location" : "scale", sizeof(rna_path));
+ }
+
+ switch (binding->animationClass) {
+ case COLLADAFW::AnimationList::POSITION_X:
+ modify_fcurve(curves, rna_path, 0);
+ break;
+ case COLLADAFW::AnimationList::POSITION_Y:
+ modify_fcurve(curves, rna_path, 1);
+ break;
+ case COLLADAFW::AnimationList::POSITION_Z:
+ modify_fcurve(curves, rna_path, 2);
+ break;
+ case COLLADAFW::AnimationList::POSITION_XYZ:
+ modify_fcurve(curves, rna_path, -1);
+ break;
+ default:
+ unused_fcurve(curves);
+ fprintf(stderr,
+ "AnimationClass %d is not supported for %s.\n",
+ binding->animationClass,
+ loc ? "TRANSLATE" : "SCALE");
+ }
+ break;
+ }
+
+ case COLLADAFW::Transformation::ROTATE: {
+ if (is_joint) {
+ BLI_snprintf(rna_path, sizeof(rna_path), "%s.rotation_euler", joint_path);
+ }
+ else {
+ BLI_strncpy(rna_path, "rotation_euler", sizeof(rna_path));
+ }
+ std::vector<FCurve *>::iterator iter;
+ for (iter = curves->begin(); iter != curves->end(); iter++) {
+ FCurve *fcu = *iter;
+
+ /* if transform is rotation the fcurves values must be turned in to radian. */
+ if (is_rotation) {
+ fcurve_deg_to_rad(fcu);
+ }
+ }
+ COLLADAFW::Rotate *rot = (COLLADAFW::Rotate *)transform;
+ COLLADABU::Math::Vector3 &axis = rot->getRotationAxis();
+
+ switch (binding->animationClass) {
+ case COLLADAFW::AnimationList::ANGLE: {
+ int axis_index = get_animation_axis_index(axis);
+ if (axis_index >= 0) {
+ modify_fcurve(curves, rna_path, axis_index);
+ }
+ else {
+ unused_fcurve(curves);
+ }
+ } break;
+ case COLLADAFW::AnimationList::AXISANGLE:
+ /* TODO convert axis-angle to quat? or XYZ? */
+ default:
+ unused_fcurve(curves);
+ fprintf(stderr,
+ "AnimationClass %d is not supported for ROTATE transformation.\n",
+ binding->animationClass);
+ }
+ break;
+ }
+
+ case COLLADAFW::Transformation::MATRIX:
+#if 0
+ {
+ COLLADAFW::Matrix *mat = (COLLADAFW::Matrix *)transform;
+ COLLADABU::Math::Matrix4 mat4 = mat->getMatrix();
+ switch (binding->animationClass) {
+ case COLLADAFW::AnimationList::TRANSFORM:
+ }
+ }
+#endif
+ unused_fcurve(curves);
+ break;
+ case COLLADAFW::Transformation::SKEW:
+ case COLLADAFW::Transformation::LOOKAT:
+ unused_fcurve(curves);
+ fprintf(stderr, "Animation of SKEW and LOOKAT transformations is not supported yet.\n");
+ break;
+ }
+}
+
+/* creates the rna_paths and array indices of fcurves from animations using color and bound
+ * animation class of each animation. */
+void AnimationImporter::Assign_color_animations(const COLLADAFW::UniqueId &listid,
+ ListBase *AnimCurves,
+ const char *anim_type)
+{
+ char rna_path[100];
+ BLI_strncpy(rna_path, anim_type, sizeof(rna_path));
+
+ const COLLADAFW::AnimationList *animlist = animlist_map[listid];
+ if (animlist == NULL) {
+ fprintf(stderr,
+ "Collada: No animlist found for ID: %s of type %s\n",
+ listid.toAscii().c_str(),
+ anim_type);
+ return;
+ }
+
+ const COLLADAFW::AnimationList::AnimationBindings &bindings = animlist->getAnimationBindings();
+ /* all the curves belonging to the current binding */
+ std::vector<FCurve *> animcurves;
+ for (unsigned int j = 0; j < bindings.getCount(); j++) {
+ animcurves = curve_map[bindings[j].animation];
+
+ switch (bindings[j].animationClass) {
+ case COLLADAFW::AnimationList::COLOR_R:
+ modify_fcurve(&animcurves, rna_path, 0);
+ break;
+ case COLLADAFW::AnimationList::COLOR_G:
+ modify_fcurve(&animcurves, rna_path, 1);
+ break;
+ case COLLADAFW::AnimationList::COLOR_B:
+ modify_fcurve(&animcurves, rna_path, 2);
+ break;
+ case COLLADAFW::AnimationList::COLOR_RGB:
+ case COLLADAFW::AnimationList::COLOR_RGBA: /* to do-> set intensity */
+ modify_fcurve(&animcurves, rna_path, -1);
+ break;
+
+ default:
+ unused_fcurve(&animcurves);
+ fprintf(stderr,
+ "AnimationClass %d is not supported for %s.\n",
+ bindings[j].animationClass,
+ "COLOR");
+ }
+
+ std::vector<FCurve *>::iterator iter;
+ /* Add the curves of the current animation to the object */
+ for (iter = animcurves.begin(); iter != animcurves.end(); iter++) {
+ FCurve *fcu = *iter;
+ BLI_addtail(AnimCurves, fcu);
+ fcurve_is_used(fcu);
+ }
+ }
+}
+
+void AnimationImporter::Assign_float_animations(const COLLADAFW::UniqueId &listid,
+ ListBase *AnimCurves,
+ const char *anim_type)
+{
+ char rna_path[100];
+ if (animlist_map.find(listid) == animlist_map.end()) {
+ return;
+ }
+ else {
+ /* anim_type has animations */
+ const COLLADAFW::AnimationList *animlist = animlist_map[listid];
+ const COLLADAFW::AnimationList::AnimationBindings &bindings = animlist->getAnimationBindings();
+ /* all the curves belonging to the current binding */
+ std::vector<FCurve *> animcurves;
+ for (unsigned int j = 0; j < bindings.getCount(); j++) {
+ animcurves = curve_map[bindings[j].animation];
+
+ BLI_strncpy(rna_path, anim_type, sizeof(rna_path));
+ modify_fcurve(&animcurves, rna_path, 0);
+ std::vector<FCurve *>::iterator iter;
+ /* Add the curves of the current animation to the object */
+ for (iter = animcurves.begin(); iter != animcurves.end(); iter++) {
+ FCurve *fcu = *iter;
+ /* All anim_types whose values are to be converted from Degree to Radians can be ORed here
+ */
+ if (STREQ("spot_size", anim_type)) {
+ /* NOTE: Do NOT convert if imported file was made by blender <= 2.69.10
+ * Reason: old blender versions stored spot_size in radians (was a bug)
+ */
+ if (this->import_from_version == "" ||
+ BLI_strcasecmp_natural(this->import_from_version.c_str(), "2.69.10") != -1) {
+ fcurve_deg_to_rad(fcu);
+ }
+ }
+ /** XXX What About animtype "rotation" ? */
+
+ BLI_addtail(AnimCurves, fcu);
+ fcurve_is_used(fcu);
+ }
+ }
+ }
+}
+
+float AnimationImporter::convert_to_focal_length(float in_xfov,
+ int fov_type,
+ float aspect,
+ float sensorx)
+{
+ /* NOTE: Needs more testing (As we currently have no official test data for this) */
+ float xfov = (fov_type == CAMERA_YFOV) ?
+ (2.0f * atanf(aspect * tanf(DEG2RADF(in_xfov) * 0.5f))) :
+ DEG2RADF(in_xfov);
+ return fov_to_focallength(xfov, sensorx);
+}
+
+/*
+ * Lens animations must be stored in COLLADA by using FOV,
+ * while blender internally uses focal length.
+ * The imported animation curves must be converted appropriately.
+ */
+void AnimationImporter::Assign_lens_animations(const COLLADAFW::UniqueId &listid,
+ ListBase *AnimCurves,
+ const double aspect,
+ Camera *cam,
+ const char *anim_type,
+ int fov_type)
+{
+ char rna_path[100];
+ if (animlist_map.find(listid) == animlist_map.end()) {
+ return;
+ }
+ else {
+ /* anim_type has animations */
+ const COLLADAFW::AnimationList *animlist = animlist_map[listid];
+ const COLLADAFW::AnimationList::AnimationBindings &bindings = animlist->getAnimationBindings();
+ /* all the curves belonging to the current binding */
+ std::vector<FCurve *> animcurves;
+ for (unsigned int j = 0; j < bindings.getCount(); j++) {
+ animcurves = curve_map[bindings[j].animation];
+
+ BLI_strncpy(rna_path, anim_type, sizeof(rna_path));
+
+ modify_fcurve(&animcurves, rna_path, 0);
+ std::vector<FCurve *>::iterator iter;
+ /* Add the curves of the current animation to the object */
+ for (iter = animcurves.begin(); iter != animcurves.end(); iter++) {
+ FCurve *fcu = *iter;
+
+ for (unsigned int i = 0; i < fcu->totvert; i++) {
+ fcu->bezt[i].vec[0][1] = convert_to_focal_length(
+ fcu->bezt[i].vec[0][1], fov_type, aspect, cam->sensor_x);
+ fcu->bezt[i].vec[1][1] = convert_to_focal_length(
+ fcu->bezt[i].vec[1][1], fov_type, aspect, cam->sensor_x);
+ fcu->bezt[i].vec[2][1] = convert_to_focal_length(
+ fcu->bezt[i].vec[2][1], fov_type, aspect, cam->sensor_x);
+ }
+
+ BLI_addtail(AnimCurves, fcu);
+ fcurve_is_used(fcu);
+ }
+ }
+ }
+}
+
+void AnimationImporter::apply_matrix_curves(Object *ob,
+ std::vector<FCurve *> &animcurves,
+ COLLADAFW::Node *root,
+ COLLADAFW::Node *node,
+ COLLADAFW::Transformation *tm)
+{
+ bool is_joint = node->getType() == COLLADAFW::Node::JOINT;
+ const char *bone_name = is_joint ? bc_get_joint_name(node) : NULL;
+ char joint_path[200];
+ if (is_joint) {
+ armature_importer->get_rna_path_for_joint(node, joint_path, sizeof(joint_path));
+ }
+
+ std::vector<float> frames;
+ find_frames(&frames, &animcurves);
+
+ float irest_dae[4][4];
+ float rest[4][4], irest[4][4];
+
+ if (is_joint) {
+ get_joint_rest_mat(irest_dae, root, node);
+ invert_m4(irest_dae);
+
+ Bone *bone = BKE_armature_find_bone_name((bArmature *)ob->data, bone_name);
+ if (!bone) {
+ fprintf(stderr, "cannot find bone \"%s\"\n", bone_name);
+ return;
+ }
+
+ unit_m4(rest);
+ copy_m4_m4(rest, bone->arm_mat);
+ invert_m4_m4(irest, rest);
+ }
+ /* new curves to assign matrix transform animation */
+ FCurve *newcu[10]; /* if tm_type is matrix, then create 10 curves: 4 rot, 3 loc, 3 scale */
+ unsigned int totcu = 10;
+ const char *tm_str = NULL;
+ char rna_path[200];
+ for (int i = 0; i < totcu; i++) {
+
+ int axis = i;
+
+ if (i < 4) {
+ tm_str = "rotation_quaternion";
+ axis = i;
+ }
+ else if (i < 7) {
+ tm_str = "location";
+ axis = i - 4;
+ }
+ else {
+ tm_str = "scale";
+ axis = i - 7;
+ }
+
+ if (is_joint) {
+ BLI_snprintf(rna_path, sizeof(rna_path), "%s.%s", joint_path, tm_str);
+ }
+ else {
+ BLI_strncpy(rna_path, tm_str, sizeof(rna_path));
+ }
+ newcu[i] = create_fcurve(axis, rna_path);
+ newcu[i]->totvert = frames.size();
+ }
+
+ if (frames.size() == 0) {
+ return;
+ }
+
+ std::sort(frames.begin(), frames.end());
+
+ std::vector<float>::iterator it;
+
+#if 0
+ float qref[4];
+ unit_qt(qref);
+#endif
+
+ /* sample values at each frame */
+ for (it = frames.begin(); it != frames.end(); it++) {
+ float fra = *it;
+
+ float mat[4][4];
+ float matfra[4][4];
+
+ unit_m4(matfra);
+
+ /* calc object-space mat */
+ evaluate_transform_at_frame(matfra, node, fra);
+
+ /* for joints, we need a special matrix */
+ if (is_joint) {
+ /* special matrix: iR * M * iR_dae * R
+ * where R, iR are bone rest and inverse rest mats in world space (Blender bones),
+ * iR_dae is joint inverse rest matrix (DAE)
+ * and M is an evaluated joint world-space matrix (DAE) */
+ float temp[4][4], par[4][4];
+
+ /* calc M */
+ calc_joint_parent_mat_rest(par, NULL, root, node);
+ mul_m4_m4m4(temp, par, matfra);
+
+#if 0
+ evaluate_joint_world_transform_at_frame(temp, NULL, node, fra);
+#endif
+
+ /* calc special matrix */
+ mul_m4_series(mat, irest, temp, irest_dae, rest);
+ }
+ else {
+ copy_m4_m4(mat, matfra);
+ }
+
+ float rot[4], loc[3], scale[3];
+ mat4_decompose(loc, rot, scale, mat);
+
+ /* add keys */
+ for (int i = 0; i < totcu; i++) {
+ if (i < 4) {
+ add_bezt(newcu[i], fra, rot[i]);
+ }
+ else if (i < 7) {
+ add_bezt(newcu[i], fra, loc[i - 4]);
+ }
+ else {
+ add_bezt(newcu[i], fra, scale[i - 7]);
+ }
+ }
+ }
+ Main *bmain = CTX_data_main(mContext);
+ ED_id_action_ensure(bmain, (ID *)&ob->id);
+
+ ListBase *curves = &ob->adt->action->curves;
+
+ /* add curves */
+ for (int i = 0; i < totcu; i++) {
+ if (is_joint) {
+ add_bone_fcurve(ob, node, newcu[i]);
+ }
+ else {
+ BLI_addtail(curves, newcu[i]);
+ }
+#if 0
+ fcurve_is_used(newcu[i]); /* never added to unused */
+#endif
+ }
+
+ if (is_joint) {
+ bPoseChannel *chan = BKE_pose_channel_find_name(ob->pose, bone_name);
+ chan->rotmode = ROT_MODE_QUAT;
+ }
+ else {
+ ob->rotmode = ROT_MODE_QUAT;
+ }
+
+ return;
+}
+
+/*
+ * This function returns the aspect ration from the Collada camera.
+ *
+ * Note:COLLADA allows to specify either XFov, or YFov alone.
+ * In that case the aspect ratio can be determined from
+ * the viewport aspect ratio (which is 1:1 ?)
+ * XXX: check this: its probably wrong!
+ * If both values are specified, then the aspect ration is simply xfov/yfov
+ * and if aspect ratio is efined, then .. well then its that one.
+ */
+static const double get_aspect_ratio(const COLLADAFW::Camera *camera)
+{
+ double aspect = camera->getAspectRatio().getValue();
+
+ if (aspect == 0) {
+ const double yfov = camera->getYFov().getValue();
+
+ if (yfov == 0) {
+ aspect = 1; /* assume yfov and xfov are equal */
+ }
+ else {
+ const double xfov = camera->getXFov().getValue();
+ if (xfov == 0) {
+ aspect = 1;
+ }
+ else {
+ aspect = xfov / yfov;
+ }
+ }
+ }
+ return aspect;
+}
+
+static ListBase &get_animation_curves(Main *bmain, Material *ma)
+{
+ bAction *act;
+ if (!ma->adt || !ma->adt->action) {
+ act = ED_id_action_ensure(bmain, (ID *)&ma->id);
+ }
+ else {
+ act = ma->adt->action;
+ }
+
+ return act->curves;
+}
+
+void AnimationImporter::translate_Animations(
+ COLLADAFW::Node *node,
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &root_map,
+ std::multimap<COLLADAFW::UniqueId, Object *> &object_map,
+ std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map,
+ std::map<COLLADAFW::UniqueId, Material *> uid_material_map)
+{
+ bool is_joint = node->getType() == COLLADAFW::Node::JOINT;
+ COLLADAFW::UniqueId uid = node->getUniqueId();
+ COLLADAFW::Node *root = root_map.find(uid) == root_map.end() ? node : root_map[uid];
+
+ Object *ob;
+ if (is_joint) {
+ ob = armature_importer->get_armature_for_joint(root);
+ }
+ else {
+ ob = object_map.find(uid) == object_map.end() ? NULL : object_map.find(uid)->second;
+ }
+
+ if (!ob) {
+ fprintf(stderr, "cannot find Object for Node with id=\"%s\"\n", node->getOriginalId().c_str());
+ return;
+ }
+
+ AnimationImporter::AnimMix *animType = get_animation_type(node, FW_object_map);
+ bAction *act;
+ Main *bmain = CTX_data_main(mContext);
+
+ if ((animType->transform) != 0) {
+ /* const char *bone_name = is_joint ? bc_get_joint_name(node) : NULL; */ /* UNUSED */
+ char joint_path[200];
+
+ if (is_joint) {
+ armature_importer->get_rna_path_for_joint(node, joint_path, sizeof(joint_path));
+ }
+
+ if (!ob->adt || !ob->adt->action) {
+ act = ED_id_action_ensure(bmain, (ID *)&ob->id);
+ }
+ else {
+ act = ob->adt->action;
+ }
+
+ /* Get the list of animation curves of the object */
+ ListBase *AnimCurves = &(act->curves);
+
+ const COLLADAFW::TransformationPointerArray &nodeTransforms = node->getTransformations();
+
+ /* for each transformation in node */
+ for (unsigned int i = 0; i < nodeTransforms.getCount(); i++) {
+ COLLADAFW::Transformation *transform = nodeTransforms[i];
+ COLLADAFW::Transformation::TransformationType tm_type = transform->getTransformationType();
+
+ bool is_rotation = tm_type == COLLADAFW::Transformation::ROTATE;
+ bool is_matrix = tm_type == COLLADAFW::Transformation::MATRIX;
+
+ const COLLADAFW::UniqueId &listid = transform->getAnimationList();
+
+ /* check if transformation has animations */
+ if (animlist_map.find(listid) == animlist_map.end()) {
+ continue;
+ }
+ else {
+ /* transformation has animations */
+ const COLLADAFW::AnimationList *animlist = animlist_map[listid];
+ const COLLADAFW::AnimationList::AnimationBindings &bindings =
+ animlist->getAnimationBindings();
+ /* all the curves belonging to the current binding */
+ std::vector<FCurve *> animcurves;
+ for (unsigned int j = 0; j < bindings.getCount(); j++) {
+ animcurves = curve_map[bindings[j].animation];
+ if (is_matrix) {
+ apply_matrix_curves(ob, animcurves, root, node, transform);
+ }
+ else {
+ /* calculate rnapaths and array index of fcurves according to transformation and
+ * animation class */
+ Assign_transform_animations(
+ transform, &bindings[j], &animcurves, is_joint, joint_path);
+
+ std::vector<FCurve *>::iterator iter;
+ /* Add the curves of the current animation to the object */
+ for (iter = animcurves.begin(); iter != animcurves.end(); iter++) {
+ FCurve *fcu = *iter;
+
+ BLI_addtail(AnimCurves, fcu);
+ fcurve_is_used(fcu);
+ }
+ }
+ }
+ }
+ if (is_rotation && !(is_joint || is_matrix)) {
+ ob->rotmode = ROT_MODE_EUL;
+ }
+ }
+ }
+
+ if ((animType->light) != 0) {
+ Light *lamp = (Light *)ob->data;
+ if (!lamp->adt || !lamp->adt->action) {
+ act = ED_id_action_ensure(bmain, (ID *)&lamp->id);
+ }
+ else {
+ act = lamp->adt->action;
+ }
+
+ ListBase *AnimCurves = &(act->curves);
+ const COLLADAFW::InstanceLightPointerArray &nodeLights = node->getInstanceLights();
+
+ for (unsigned int i = 0; i < nodeLights.getCount(); i++) {
+ const COLLADAFW::Light *light = (COLLADAFW::Light *)
+ FW_object_map[nodeLights[i]->getInstanciatedObjectId()];
+
+ if ((animType->light & LIGHT_COLOR) != 0) {
+ const COLLADAFW::Color *col = &(light->getColor());
+ const COLLADAFW::UniqueId &listid = col->getAnimationList();
+
+ Assign_color_animations(listid, AnimCurves, "color");
+ }
+ if ((animType->light & LIGHT_FOA) != 0) {
+ const COLLADAFW::AnimatableFloat *foa = &(light->getFallOffAngle());
+ const COLLADAFW::UniqueId &listid = foa->getAnimationList();
+
+ Assign_float_animations(listid, AnimCurves, "spot_size");
+ }
+ if ((animType->light & LIGHT_FOE) != 0) {
+ const COLLADAFW::AnimatableFloat *foe = &(light->getFallOffExponent());
+ const COLLADAFW::UniqueId &listid = foe->getAnimationList();
+
+ Assign_float_animations(listid, AnimCurves, "spot_blend");
+ }
+ }
+ }
+
+ if (animType->camera != 0) {
+
+ Camera *cam = (Camera *)ob->data;
+ if (!cam->adt || !cam->adt->action) {
+ act = ED_id_action_ensure(bmain, (ID *)&cam->id);
+ }
+ else {
+ act = cam->adt->action;
+ }
+
+ ListBase *AnimCurves = &(act->curves);
+ const COLLADAFW::InstanceCameraPointerArray &nodeCameras = node->getInstanceCameras();
+
+ for (unsigned int i = 0; i < nodeCameras.getCount(); i++) {
+ const COLLADAFW::Camera *camera = (COLLADAFW::Camera *)
+ FW_object_map[nodeCameras[i]->getInstanciatedObjectId()];
+
+ if ((animType->camera & CAMERA_XFOV) != 0) {
+ const COLLADAFW::AnimatableFloat *xfov = &(camera->getXFov());
+ const COLLADAFW::UniqueId &listid = xfov->getAnimationList();
+ double aspect = get_aspect_ratio(camera);
+ Assign_lens_animations(listid, AnimCurves, aspect, cam, "lens", CAMERA_XFOV);
+ }
+
+ else if ((animType->camera & CAMERA_YFOV) != 0) {
+ const COLLADAFW::AnimatableFloat *yfov = &(camera->getYFov());
+ const COLLADAFW::UniqueId &listid = yfov->getAnimationList();
+ double aspect = get_aspect_ratio(camera);
+ Assign_lens_animations(listid, AnimCurves, aspect, cam, "lens", CAMERA_YFOV);
+ }
+
+ else if ((animType->camera & CAMERA_XMAG) != 0) {
+ const COLLADAFW::AnimatableFloat *xmag = &(camera->getXMag());
+ const COLLADAFW::UniqueId &listid = xmag->getAnimationList();
+ Assign_float_animations(listid, AnimCurves, "ortho_scale");
+ }
+
+ else if ((animType->camera & CAMERA_YMAG) != 0) {
+ const COLLADAFW::AnimatableFloat *ymag = &(camera->getYMag());
+ const COLLADAFW::UniqueId &listid = ymag->getAnimationList();
+ Assign_float_animations(listid, AnimCurves, "ortho_scale");
+ }
+
+ if ((animType->camera & CAMERA_ZFAR) != 0) {
+ const COLLADAFW::AnimatableFloat *zfar = &(camera->getFarClippingPlane());
+ const COLLADAFW::UniqueId &listid = zfar->getAnimationList();
+ Assign_float_animations(listid, AnimCurves, "clip_end");
+ }
+
+ if ((animType->camera & CAMERA_ZNEAR) != 0) {
+ const COLLADAFW::AnimatableFloat *znear = &(camera->getNearClippingPlane());
+ const COLLADAFW::UniqueId &listid = znear->getAnimationList();
+ Assign_float_animations(listid, AnimCurves, "clip_start");
+ }
+ }
+ }
+ if (animType->material != 0) {
+
+ Material *ma = BKE_object_material_get(ob, 1);
+ if (!ma->adt || !ma->adt->action) {
+ act = ED_id_action_ensure(bmain, (ID *)&ma->id);
+ }
+ else {
+ act = ma->adt->action;
+ }
+
+ const COLLADAFW::InstanceGeometryPointerArray &nodeGeoms = node->getInstanceGeometries();
+ for (unsigned int i = 0; i < nodeGeoms.getCount(); i++) {
+ const COLLADAFW::MaterialBindingArray &matBinds = nodeGeoms[i]->getMaterialBindings();
+ for (unsigned int j = 0; j < matBinds.getCount(); j++) {
+ const COLLADAFW::UniqueId &matuid = matBinds[j].getReferencedMaterial();
+ const COLLADAFW::Effect *ef = (COLLADAFW::Effect *)(FW_object_map[matuid]);
+ if (ef != NULL) { /* can be NULL [#28909] */
+ Material *ma = uid_material_map[matuid];
+ if (!ma) {
+ fprintf(stderr,
+ "Collada: Node %s refers to undefined material\n",
+ node->getName().c_str());
+ continue;
+ }
+ ListBase &AnimCurves = get_animation_curves(bmain, ma);
+ const COLLADAFW::CommonEffectPointerArray &commonEffects = ef->getCommonEffects();
+ COLLADAFW::EffectCommon *efc = commonEffects[0];
+ if ((animType->material & MATERIAL_SHININESS) != 0) {
+ const COLLADAFW::FloatOrParam *shin = &(efc->getShininess());
+ const COLLADAFW::UniqueId &listid = shin->getAnimationList();
+ Assign_float_animations(listid, &AnimCurves, "specular_hardness");
+ }
+
+ if ((animType->material & MATERIAL_IOR) != 0) {
+ const COLLADAFW::FloatOrParam *ior = &(efc->getIndexOfRefraction());
+ const COLLADAFW::UniqueId &listid = ior->getAnimationList();
+ Assign_float_animations(listid, &AnimCurves, "raytrace_transparency.ior");
+ }
+
+ if ((animType->material & MATERIAL_SPEC_COLOR) != 0) {
+ const COLLADAFW::ColorOrTexture *cot = &(efc->getSpecular());
+ const COLLADAFW::UniqueId &listid = cot->getColor().getAnimationList();
+ Assign_color_animations(listid, &AnimCurves, "specular_color");
+ }
+
+ if ((animType->material & MATERIAL_DIFF_COLOR) != 0) {
+ const COLLADAFW::ColorOrTexture *cot = &(efc->getDiffuse());
+ const COLLADAFW::UniqueId &listid = cot->getColor().getAnimationList();
+ Assign_color_animations(listid, &AnimCurves, "diffuse_color");
+ }
+ }
+ }
+ }
+ }
+
+ delete animType;
+}
+
+void AnimationImporter::add_bone_animation_sampled(Object *ob,
+ std::vector<FCurve *> &animcurves,
+ COLLADAFW::Node *root,
+ COLLADAFW::Node *node,
+ COLLADAFW::Transformation *tm)
+{
+ const char *bone_name = bc_get_joint_name(node);
+ char joint_path[200];
+ armature_importer->get_rna_path_for_joint(node, joint_path, sizeof(joint_path));
+
+ std::vector<float> frames;
+ find_frames(&frames, &animcurves);
+
+ /* convert degrees to radians */
+ if (tm->getTransformationType() == COLLADAFW::Transformation::ROTATE) {
+
+ std::vector<FCurve *>::iterator iter;
+ for (iter = animcurves.begin(); iter != animcurves.end(); iter++) {
+ FCurve *fcu = *iter;
+
+ fcurve_deg_to_rad(fcu);
+ }
+ }
+
+ float irest_dae[4][4];
+ float rest[4][4], irest[4][4];
+
+ get_joint_rest_mat(irest_dae, root, node);
+ invert_m4(irest_dae);
+
+ Bone *bone = BKE_armature_find_bone_name((bArmature *)ob->data, bone_name);
+ if (!bone) {
+ fprintf(stderr, "cannot find bone \"%s\"\n", bone_name);
+ return;
+ }
+
+ unit_m4(rest);
+ copy_m4_m4(rest, bone->arm_mat);
+ invert_m4_m4(irest, rest);
+
+ /* new curves to assign matrix transform animation */
+ FCurve *newcu[10]; /* if tm_type is matrix, then create 10 curves: 4 rot, 3 loc, 3 scale. */
+ unsigned int totcu = 10;
+ const char *tm_str = NULL;
+ char rna_path[200];
+ for (int i = 0; i < totcu; i++) {
+
+ int axis = i;
+
+ if (i < 4) {
+ tm_str = "rotation_quaternion";
+ axis = i;
+ }
+ else if (i < 7) {
+ tm_str = "location";
+ axis = i - 4;
+ }
+ else {
+ tm_str = "scale";
+ axis = i - 7;
+ }
+
+ BLI_snprintf(rna_path, sizeof(rna_path), "%s.%s", joint_path, tm_str);
+
+ newcu[i] = create_fcurve(axis, rna_path);
+ newcu[i]->totvert = frames.size();
+ }
+
+ if (frames.size() == 0) {
+ return;
+ }
+
+ std::sort(frames.begin(), frames.end());
+
+ BCQuat qref;
+
+ std::vector<float>::iterator it;
+
+ /* sample values at each frame */
+ for (it = frames.begin(); it != frames.end(); it++) {
+ float fra = *it;
+
+ Matrix mat;
+ Matrix matfra;
+
+ unit_m4(matfra);
+
+ /* calc object-space mat */
+ evaluate_transform_at_frame(matfra, node, fra);
+
+ /* for joints, we need a special matrix
+ * special matrix: iR * M * iR_dae * R
+ * where R, iR are bone rest and inverse rest mats in world space (Blender bones),
+ * iR_dae is joint inverse rest matrix (DAE)
+ * and M is an evaluated joint world-space matrix (DAE). */
+ Matrix temp, par;
+
+ /* calc M */
+ calc_joint_parent_mat_rest(par, NULL, root, node);
+ mul_m4_m4m4(temp, par, matfra);
+
+ /* evaluate_joint_world_transform_at_frame(temp, NULL, node, fra); */
+
+ /* calc special matrix */
+ mul_m4_series(mat, irest, temp, irest_dae, rest);
+
+ Vector loc, scale;
+
+ qref.rotate_to(mat);
+
+ copy_v3_v3(loc, mat[3]);
+ mat4_to_size(scale, mat);
+
+ /* add keys */
+ for (int i = 0; i < totcu; i++) {
+ if (i < 4) {
+ add_bezt(newcu[i], fra, qref.quat()[i]);
+ }
+ else if (i < 7) {
+ add_bezt(newcu[i], fra, loc[i - 4]);
+ }
+ else {
+ add_bezt(newcu[i], fra, scale[i - 7]);
+ }
+ }
+ }
+ Main *bmain = CTX_data_main(mContext);
+ ED_id_action_ensure(bmain, (ID *)&ob->id);
+
+ /* add curves */
+ for (int i = 0; i < totcu; i++) {
+ add_bone_fcurve(ob, node, newcu[i]);
+#if 0
+ fcurve_is_used(newcu[i]); /* never added to unused */
+#endif
+ }
+
+ bPoseChannel *chan = BKE_pose_channel_find_name(ob->pose, bone_name);
+ chan->rotmode = ROT_MODE_QUAT;
+}
+
+/* Check if object is animated by checking if animlist_map
+ * holds the animlist_id of node transforms */
+AnimationImporter::AnimMix *AnimationImporter::get_animation_type(
+ const COLLADAFW::Node *node,
+ std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map)
+{
+ AnimMix *types = new AnimMix();
+
+ const COLLADAFW::TransformationPointerArray &nodeTransforms = node->getTransformations();
+
+ /* for each transformation in node */
+ for (unsigned int i = 0; i < nodeTransforms.getCount(); i++) {
+ COLLADAFW::Transformation *transform = nodeTransforms[i];
+ const COLLADAFW::UniqueId &listid = transform->getAnimationList();
+
+ /* check if transformation has animations */
+ if (animlist_map.find(listid) == animlist_map.end()) {
+ continue;
+ }
+ else {
+ types->transform = types->transform | BC_NODE_TRANSFORM;
+ break;
+ }
+ }
+ const COLLADAFW::InstanceLightPointerArray &nodeLights = node->getInstanceLights();
+
+ for (unsigned int i = 0; i < nodeLights.getCount(); i++) {
+ const COLLADAFW::Light *light = (COLLADAFW::Light *)
+ FW_object_map[nodeLights[i]->getInstanciatedObjectId()];
+ types->light = setAnimType(&(light->getColor()), (types->light), LIGHT_COLOR);
+ types->light = setAnimType(&(light->getFallOffAngle()), (types->light), LIGHT_FOA);
+ types->light = setAnimType(&(light->getFallOffExponent()), (types->light), LIGHT_FOE);
+
+ if (types->light != 0) {
+ break;
+ }
+ }
+
+ const COLLADAFW::InstanceCameraPointerArray &nodeCameras = node->getInstanceCameras();
+ for (unsigned int i = 0; i < nodeCameras.getCount(); i++) {
+ const COLLADAFW::Camera *camera = (COLLADAFW::Camera *)
+ FW_object_map[nodeCameras[i]->getInstanciatedObjectId()];
+ if (camera == NULL) {
+ /* Can happen if the node refers to an unknown camera. */
+ continue;
+ }
+
+ const bool is_perspective_type = camera->getCameraType() == COLLADAFW::Camera::PERSPECTIVE;
+
+ int addition;
+ const COLLADAFW::Animatable *mag;
+ const COLLADAFW::UniqueId listid = camera->getYMag().getAnimationList();
+ if (animlist_map.find(listid) != animlist_map.end()) {
+ mag = &(camera->getYMag());
+ addition = (is_perspective_type) ? CAMERA_YFOV : CAMERA_YMAG;
+ }
+ else {
+ mag = &(camera->getXMag());
+ addition = (is_perspective_type) ? CAMERA_XFOV : CAMERA_XMAG;
+ }
+ types->camera = setAnimType(mag, (types->camera), addition);
+
+ types->camera = setAnimType(&(camera->getFarClippingPlane()), (types->camera), CAMERA_ZFAR);
+ types->camera = setAnimType(&(camera->getNearClippingPlane()), (types->camera), CAMERA_ZNEAR);
+
+ if (types->camera != 0) {
+ break;
+ }
+ }
+
+ const COLLADAFW::InstanceGeometryPointerArray &nodeGeoms = node->getInstanceGeometries();
+ for (unsigned int i = 0; i < nodeGeoms.getCount(); i++) {
+ const COLLADAFW::MaterialBindingArray &matBinds = nodeGeoms[i]->getMaterialBindings();
+ for (unsigned int j = 0; j < matBinds.getCount(); j++) {
+ const COLLADAFW::UniqueId &matuid = matBinds[j].getReferencedMaterial();
+ const COLLADAFW::Effect *ef = (COLLADAFW::Effect *)(FW_object_map[matuid]);
+ if (ef != NULL) { /* can be NULL [#28909] */
+ const COLLADAFW::CommonEffectPointerArray &commonEffects = ef->getCommonEffects();
+ if (!commonEffects.empty()) {
+ COLLADAFW::EffectCommon *efc = commonEffects[0];
+ types->material = setAnimType(
+ &(efc->getShininess()), (types->material), MATERIAL_SHININESS);
+ types->material = setAnimType(
+ &(efc->getSpecular().getColor()), (types->material), MATERIAL_SPEC_COLOR);
+ types->material = setAnimType(
+ &(efc->getDiffuse().getColor()), (types->material), MATERIAL_DIFF_COLOR);
+#if 0
+ types->material = setAnimType(&(efc->get()), (types->material), MATERIAL_TRANSPARENCY);
+#endif
+ types->material = setAnimType(
+ &(efc->getIndexOfRefraction()), (types->material), MATERIAL_IOR);
+ }
+ }
+ }
+ }
+ return types;
+}
+
+int AnimationImporter::setAnimType(const COLLADAFW::Animatable *prop, int types, int addition)
+{
+ int anim_type;
+ const COLLADAFW::UniqueId &listid = prop->getAnimationList();
+ if (animlist_map.find(listid) != animlist_map.end()) {
+ anim_type = types | addition;
+ }
+ else {
+ anim_type = types;
+ }
+
+ return anim_type;
+}
+
+/* Is not used anymore. */
+void AnimationImporter::find_frames_old(std::vector<float> *frames,
+ COLLADAFW::Node *node,
+ COLLADAFW::Transformation::TransformationType tm_type)
+{
+ bool is_matrix = tm_type == COLLADAFW::Transformation::MATRIX;
+ bool is_rotation = tm_type == COLLADAFW::Transformation::ROTATE;
+ /* for each <rotate>, <translate>, etc. there is a separate Transformation */
+ const COLLADAFW::TransformationPointerArray &nodeTransforms = node->getTransformations();
+
+ unsigned int i;
+ /* find frames at which to sample plus convert all rotation keys to radians */
+ for (i = 0; i < nodeTransforms.getCount(); i++) {
+ COLLADAFW::Transformation *transform = nodeTransforms[i];
+ COLLADAFW::Transformation::TransformationType nodeTmType = transform->getTransformationType();
+
+ if (nodeTmType == tm_type) {
+ /* get animation bindings for the current transformation */
+ const COLLADAFW::UniqueId &listid = transform->getAnimationList();
+ /* if transform is animated its animlist must exist. */
+ if (animlist_map.find(listid) != animlist_map.end()) {
+
+ const COLLADAFW::AnimationList *animlist = animlist_map[listid];
+ const COLLADAFW::AnimationList::AnimationBindings &bindings =
+ animlist->getAnimationBindings();
+
+ if (bindings.getCount()) {
+ /* for each AnimationBinding get the fcurves which animate the transform */
+ for (unsigned int j = 0; j < bindings.getCount(); j++) {
+ std::vector<FCurve *> &curves = curve_map[bindings[j].animation];
+ bool xyz = ((nodeTmType == COLLADAFW::Transformation::TRANSLATE ||
+ nodeTmType == COLLADAFW::Transformation::SCALE) &&
+ bindings[j].animationClass == COLLADAFW::AnimationList::POSITION_XYZ);
+
+ if ((!xyz && curves.size() == 1) || (xyz && curves.size() == 3) || is_matrix) {
+ std::vector<FCurve *>::iterator iter;
+
+ for (iter = curves.begin(); iter != curves.end(); iter++) {
+ FCurve *fcu = *iter;
+
+ /* if transform is rotation the fcurves values must be turned in to radian. */
+ if (is_rotation) {
+ fcurve_deg_to_rad(fcu);
+ }
+
+ for (unsigned int k = 0; k < fcu->totvert; k++) {
+ /* get frame value from bezTriple */
+ float fra = fcu->bezt[k].vec[1][0];
+ /* if frame already not added add frame to frames */
+ if (std::find(frames->begin(), frames->end(), fra) == frames->end()) {
+ frames->push_back(fra);
+ }
+ }
+ }
+ }
+ else {
+ fprintf(stderr, "expected %d curves, got %d\n", xyz ? 3 : 1, (int)curves.size());
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/* prerequisites:
+ * animlist_map - map animlist id -> animlist
+ * curve_map - map anim id -> curve(s) */
+Object *AnimationImporter::translate_animation_OLD(
+ COLLADAFW::Node *node,
+ std::map<COLLADAFW::UniqueId, Object *> &object_map,
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &root_map,
+ COLLADAFW::Transformation::TransformationType tm_type,
+ Object *par_job)
+{
+
+ bool is_rotation = tm_type == COLLADAFW::Transformation::ROTATE;
+ bool is_matrix = tm_type == COLLADAFW::Transformation::MATRIX;
+ bool is_joint = node->getType() == COLLADAFW::Node::JOINT;
+
+ COLLADAFW::Node *root = root_map.find(node->getUniqueId()) == root_map.end() ?
+ node :
+ root_map[node->getUniqueId()];
+ Object *ob = is_joint ? armature_importer->get_armature_for_joint(node) :
+ object_map[node->getUniqueId()];
+ const char *bone_name = is_joint ? bc_get_joint_name(node) : NULL;
+ if (!ob) {
+ fprintf(stderr, "cannot find Object for Node with id=\"%s\"\n", node->getOriginalId().c_str());
+ return NULL;
+ }
+
+ /* frames at which to sample */
+ std::vector<float> frames;
+
+ find_frames_old(&frames, node, tm_type);
+
+ unsigned int i;
+
+ float irest_dae[4][4];
+ float rest[4][4], irest[4][4];
+
+ if (is_joint) {
+ get_joint_rest_mat(irest_dae, root, node);
+ invert_m4(irest_dae);
+
+ Bone *bone = BKE_armature_find_bone_name((bArmature *)ob->data, bone_name);
+ if (!bone) {
+ fprintf(stderr, "cannot find bone \"%s\"\n", bone_name);
+ return NULL;
+ }
+
+ unit_m4(rest);
+ copy_m4_m4(rest, bone->arm_mat);
+ invert_m4_m4(irest, rest);
+ }
+
+ Object *job = NULL;
+
+#ifdef ARMATURE_TEST
+ FCurve *job_curves[10];
+ job = get_joint_object(root, node, par_job);
+#endif
+
+ if (frames.size() == 0) {
+ return job;
+ }
+
+ std::sort(frames.begin(), frames.end());
+
+ const char *tm_str = NULL;
+ switch (tm_type) {
+ case COLLADAFW::Transformation::ROTATE:
+ tm_str = "rotation_quaternion";
+ break;
+ case COLLADAFW::Transformation::SCALE:
+ tm_str = "scale";
+ break;
+ case COLLADAFW::Transformation::TRANSLATE:
+ tm_str = "location";
+ break;
+ case COLLADAFW::Transformation::MATRIX:
+ break;
+ default:
+ return job;
+ }
+
+ char rna_path[200];
+ char joint_path[200];
+
+ if (is_joint) {
+ armature_importer->get_rna_path_for_joint(node, joint_path, sizeof(joint_path));
+ }
+
+ /* new curves */
+ FCurve *newcu[10]; /* if tm_type is matrix, then create 10 curves: 4 rot, 3 loc, 3 scale */
+ unsigned int totcu = is_matrix ? 10 : (is_rotation ? 4 : 3);
+
+ for (i = 0; i < totcu; i++) {
+
+ int axis = i;
+
+ if (is_matrix) {
+ if (i < 4) {
+ tm_str = "rotation_quaternion";
+ axis = i;
+ }
+ else if (i < 7) {
+ tm_str = "location";
+ axis = i - 4;
+ }
+ else {
+ tm_str = "scale";
+ axis = i - 7;
+ }
+ }
+
+ if (is_joint) {
+ BLI_snprintf(rna_path, sizeof(rna_path), "%s.%s", joint_path, tm_str);
+ }
+ else {
+ BLI_strncpy(rna_path, tm_str, sizeof(rna_path));
+ }
+ newcu[i] = create_fcurve(axis, rna_path);
+
+#ifdef ARMATURE_TEST
+ if (is_joint) {
+ job_curves[i] = create_fcurve(axis, tm_str);
+ }
+#endif
+ }
+
+ std::vector<float>::iterator it;
+
+ /* sample values at each frame */
+ for (it = frames.begin(); it != frames.end(); it++) {
+ float fra = *it;
+
+ float mat[4][4];
+ float matfra[4][4];
+
+ unit_m4(matfra);
+
+ /* calc object-space mat */
+ evaluate_transform_at_frame(matfra, node, fra);
+
+ /* for joints, we need a special matrix */
+ if (is_joint) {
+ /* special matrix: iR * M * iR_dae * R
+ * where R, iR are bone rest and inverse rest mats in world space (Blender bones),
+ * iR_dae is joint inverse rest matrix (DAE)
+ * and M is an evaluated joint world-space matrix (DAE). */
+ float temp[4][4], par[4][4];
+
+ /* calc M */
+ calc_joint_parent_mat_rest(par, NULL, root, node);
+ mul_m4_m4m4(temp, par, matfra);
+
+ /* evaluate_joint_world_transform_at_frame(temp, NULL, node, fra); */
+
+ /* calc special matrix */
+ mul_m4_series(mat, irest, temp, irest_dae, rest);
+ }
+ else {
+ copy_m4_m4(mat, matfra);
+ }
+
+ float val[4] = {};
+ float rot[4], loc[3], scale[3];
+
+ switch (tm_type) {
+ case COLLADAFW::Transformation::ROTATE:
+ mat4_to_quat(val, mat);
+ break;
+ case COLLADAFW::Transformation::SCALE:
+ mat4_to_size(val, mat);
+ break;
+ case COLLADAFW::Transformation::TRANSLATE:
+ copy_v3_v3(val, mat[3]);
+ break;
+ case COLLADAFW::Transformation::MATRIX:
+ mat4_to_quat(rot, mat);
+ copy_v3_v3(loc, mat[3]);
+ mat4_to_size(scale, mat);
+ break;
+ default:
+ break;
+ }
+
+ /* add keys */
+ for (i = 0; i < totcu; i++) {
+ if (is_matrix) {
+ if (i < 4) {
+ add_bezt(newcu[i], fra, rot[i]);
+ }
+ else if (i < 7) {
+ add_bezt(newcu[i], fra, loc[i - 4]);
+ }
+ else {
+ add_bezt(newcu[i], fra, scale[i - 7]);
+ }
+ }
+ else {
+ add_bezt(newcu[i], fra, val[i]);
+ }
+ }
+
+#ifdef ARMATURE_TEST
+ if (is_joint) {
+ switch (tm_type) {
+ case COLLADAFW::Transformation::ROTATE:
+ mat4_to_quat(val, matfra);
+ break;
+ case COLLADAFW::Transformation::SCALE:
+ mat4_to_size(val, matfra);
+ break;
+ case COLLADAFW::Transformation::TRANSLATE:
+ copy_v3_v3(val, matfra[3]);
+ break;
+ case MATRIX:
+ mat4_to_quat(rot, matfra);
+ copy_v3_v3(loc, matfra[3]);
+ mat4_to_size(scale, matfra);
+ break;
+ default:
+ break;
+ }
+
+ for (i = 0; i < totcu; i++) {
+ if (is_matrix) {
+ if (i < 4) {
+ add_bezt(job_curves[i], fra, rot[i]);
+ }
+ else if (i < 7) {
+ add_bezt(job_curves[i], fra, loc[i - 4]);
+ }
+ else {
+ add_bezt(job_curves[i], fra, scale[i - 7]);
+ }
+ }
+ else {
+ add_bezt(job_curves[i], fra, val[i]);
+ }
+ }
+ }
+#endif
+ }
+ Main *bmain = CTX_data_main(mContext);
+ ED_id_action_ensure(bmain, (ID *)&ob->id);
+
+ ListBase *curves = &ob->adt->action->curves;
+
+ /* add curves */
+ for (i = 0; i < totcu; i++) {
+ if (is_joint) {
+ add_bone_fcurve(ob, node, newcu[i]);
+ }
+ else {
+ BLI_addtail(curves, newcu[i]);
+ }
+
+#ifdef ARMATURE_TEST
+ if (is_joint) {
+ BLI_addtail(&job->adt->action->curves, job_curves[i]);
+ }
+#endif
+ }
+
+ if (is_rotation || is_matrix) {
+ if (is_joint) {
+ bPoseChannel *chan = BKE_pose_channel_find_name(ob->pose, bone_name);
+ chan->rotmode = (is_matrix) ? ROT_MODE_QUAT : ROT_MODE_EUL;
+ }
+ else {
+ ob->rotmode = (is_matrix) ? ROT_MODE_QUAT : ROT_MODE_EUL;
+ }
+ }
+
+ return job;
+}
+
+/* internal, better make it private
+ * warning: evaluates only rotation and only assigns matrix transforms now
+ * prerequisites: animlist_map, curve_map */
+void AnimationImporter::evaluate_transform_at_frame(float mat[4][4],
+ COLLADAFW::Node *node,
+ float fra)
+{
+ const COLLADAFW::TransformationPointerArray &tms = node->getTransformations();
+
+ unit_m4(mat);
+
+ for (unsigned int i = 0; i < tms.getCount(); i++) {
+ COLLADAFW::Transformation *tm = tms[i];
+ COLLADAFW::Transformation::TransformationType type = tm->getTransformationType();
+ float m[4][4];
+
+ unit_m4(m);
+
+ std::string nodename = node->getName().size() ? node->getName() : node->getOriginalId();
+ if (!evaluate_animation(tm, m, fra, nodename.c_str())) {
+ switch (type) {
+ case COLLADAFW::Transformation::ROTATE:
+ dae_rotate_to_mat4(tm, m);
+ break;
+ case COLLADAFW::Transformation::TRANSLATE:
+ dae_translate_to_mat4(tm, m);
+ break;
+ case COLLADAFW::Transformation::SCALE:
+ dae_scale_to_mat4(tm, m);
+ break;
+ case COLLADAFW::Transformation::MATRIX:
+ dae_matrix_to_mat4(tm, m);
+ break;
+ default:
+ fprintf(stderr, "unsupported transformation type %d\n", type);
+ }
+ }
+
+ float temp[4][4];
+ copy_m4_m4(temp, mat);
+
+ mul_m4_m4m4(mat, temp, m);
+ }
+}
+
+static void report_class_type_unsupported(const char *path,
+ const COLLADAFW::AnimationList::AnimationClass animclass,
+ const COLLADAFW::Transformation::TransformationType type)
+{
+ if (animclass == COLLADAFW::AnimationList::UNKNOWN_CLASS) {
+ fprintf(stderr, "%s: UNKNOWN animation class\n", path);
+ }
+ else {
+ fprintf(stderr,
+ "%s: animation class %d is not supported yet for transformation type %d\n",
+ path,
+ animclass,
+ type);
+ }
+}
+
+/* return true to indicate that mat contains a sane value */
+bool AnimationImporter::evaluate_animation(COLLADAFW::Transformation *tm,
+ float mat[4][4],
+ float fra,
+ const char *node_id)
+{
+ const COLLADAFW::UniqueId &listid = tm->getAnimationList();
+ COLLADAFW::Transformation::TransformationType type = tm->getTransformationType();
+
+ if (type != COLLADAFW::Transformation::ROTATE && type != COLLADAFW::Transformation::SCALE &&
+ type != COLLADAFW::Transformation::TRANSLATE && type != COLLADAFW::Transformation::MATRIX) {
+ fprintf(stderr, "animation of transformation %d is not supported yet\n", type);
+ return false;
+ }
+
+ if (animlist_map.find(listid) == animlist_map.end()) {
+ return false;
+ }
+
+ const COLLADAFW::AnimationList *animlist = animlist_map[listid];
+ const COLLADAFW::AnimationList::AnimationBindings &bindings = animlist->getAnimationBindings();
+
+ if (bindings.getCount()) {
+ float vec[3];
+
+ bool is_scale = (type == COLLADAFW::Transformation::SCALE);
+ bool is_translate = (type == COLLADAFW::Transformation::TRANSLATE);
+
+ if (is_scale) {
+ dae_scale_to_v3(tm, vec);
+ }
+ else if (is_translate) {
+ dae_translate_to_v3(tm, vec);
+ }
+
+ for (unsigned int index = 0; index < bindings.getCount(); index++) {
+ const COLLADAFW::AnimationList::AnimationBinding &binding = bindings[index];
+ std::vector<FCurve *> &curves = curve_map[binding.animation];
+ COLLADAFW::AnimationList::AnimationClass animclass = binding.animationClass;
+ char path[100];
+
+ switch (type) {
+ case COLLADAFW::Transformation::ROTATE:
+ BLI_snprintf(path, sizeof(path), "%s.rotate (binding %u)", node_id, index);
+ break;
+ case COLLADAFW::Transformation::SCALE:
+ BLI_snprintf(path, sizeof(path), "%s.scale (binding %u)", node_id, index);
+ break;
+ case COLLADAFW::Transformation::TRANSLATE:
+ BLI_snprintf(path, sizeof(path), "%s.translate (binding %u)", node_id, index);
+ break;
+ case COLLADAFW::Transformation::MATRIX:
+ BLI_snprintf(path, sizeof(path), "%s.matrix (binding %u)", node_id, index);
+ break;
+ default:
+ break;
+ }
+
+ if (type == COLLADAFW::Transformation::ROTATE) {
+ if (curves.size() != 1) {
+ fprintf(stderr, "expected 1 curve, got %d\n", (int)curves.size());
+ return false;
+ }
+
+ /* TODO support other animclasses */
+ if (animclass != COLLADAFW::AnimationList::ANGLE) {
+ report_class_type_unsupported(path, animclass, type);
+ return false;
+ }
+
+ COLLADABU::Math::Vector3 &axis = ((COLLADAFW::Rotate *)tm)->getRotationAxis();
+
+ float ax[3] = {(float)axis[0], (float)axis[1], (float)axis[2]};
+ float angle = evaluate_fcurve(curves[0], fra);
+ axis_angle_to_mat4(mat, ax, angle);
+
+ return true;
+ }
+ else if (is_scale || is_translate) {
+ bool is_xyz = animclass == COLLADAFW::AnimationList::POSITION_XYZ;
+
+ if ((!is_xyz && curves.size() != 1) || (is_xyz && curves.size() != 3)) {
+ if (is_xyz) {
+ fprintf(stderr, "%s: expected 3 curves, got %d\n", path, (int)curves.size());
+ }
+ else {
+ fprintf(stderr, "%s: expected 1 curve, got %d\n", path, (int)curves.size());
+ }
+ return false;
+ }
+
+ switch (animclass) {
+ case COLLADAFW::AnimationList::POSITION_X:
+ vec[0] = evaluate_fcurve(curves[0], fra);
+ break;
+ case COLLADAFW::AnimationList::POSITION_Y:
+ vec[1] = evaluate_fcurve(curves[0], fra);
+ break;
+ case COLLADAFW::AnimationList::POSITION_Z:
+ vec[2] = evaluate_fcurve(curves[0], fra);
+ break;
+ case COLLADAFW::AnimationList::POSITION_XYZ:
+ vec[0] = evaluate_fcurve(curves[0], fra);
+ vec[1] = evaluate_fcurve(curves[1], fra);
+ vec[2] = evaluate_fcurve(curves[2], fra);
+ break;
+ default:
+ report_class_type_unsupported(path, animclass, type);
+ break;
+ }
+ }
+ else if (type == COLLADAFW::Transformation::MATRIX) {
+ /* for now, of matrix animation,
+ * support only the case when all values are packed into one animation */
+ if (curves.size() != 16) {
+ fprintf(stderr, "%s: expected 16 curves, got %d\n", path, (int)curves.size());
+ return false;
+ }
+
+ COLLADABU::Math::Matrix4 matrix;
+ int mi = 0, mj = 0;
+
+ for (std::vector<FCurve *>::iterator it = curves.begin(); it != curves.end(); it++) {
+ matrix.setElement(mi, mj, evaluate_fcurve(*it, fra));
+ mj++;
+ if (mj == 4) {
+ mi++;
+ mj = 0;
+ }
+ }
+ unit_converter->dae_matrix_to_mat4_(mat, matrix);
+ return true;
+ }
+ }
+
+ if (is_scale) {
+ size_to_mat4(mat, vec);
+ }
+ else {
+ copy_v3_v3(mat[3], vec);
+ }
+
+ return is_scale || is_translate;
+ }
+
+ return false;
+}
+
+/* gives a world-space mat of joint at rest position */
+void AnimationImporter::get_joint_rest_mat(float mat[4][4],
+ COLLADAFW::Node *root,
+ COLLADAFW::Node *node)
+{
+ /* if bind mat is not available,
+ * use "current" node transform, i.e. all those tms listed inside <node> */
+ if (!armature_importer->get_joint_bind_mat(mat, node)) {
+ float par[4][4], m[4][4];
+
+ calc_joint_parent_mat_rest(par, NULL, root, node);
+ get_node_mat(m, node, NULL, NULL);
+ mul_m4_m4m4(mat, par, m);
+ }
+}
+
+/* gives a world-space mat, end's mat not included */
+bool AnimationImporter::calc_joint_parent_mat_rest(float mat[4][4],
+ float par[4][4],
+ COLLADAFW::Node *node,
+ COLLADAFW::Node *end)
+{
+ float m[4][4];
+
+ if (node == end) {
+ par ? copy_m4_m4(mat, par) : unit_m4(mat);
+ return true;
+ }
+
+ /* use bind matrix if available or calc "current" world mat */
+ if (!armature_importer->get_joint_bind_mat(m, node)) {
+ if (par) {
+ float temp[4][4];
+ get_node_mat(temp, node, NULL, NULL);
+ mul_m4_m4m4(m, par, temp);
+ }
+ else {
+ get_node_mat(m, node, NULL, NULL);
+ }
+ }
+
+ COLLADAFW::NodePointerArray &children = node->getChildNodes();
+ for (unsigned int i = 0; i < children.getCount(); i++) {
+ if (calc_joint_parent_mat_rest(mat, m, children[i], end)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+#ifdef ARMATURE_TEST
+Object *AnimationImporter::get_joint_object(COLLADAFW::Node *root,
+ COLLADAFW::Node *node,
+ Object *par_job)
+{
+ if (joint_objects.find(node->getUniqueId()) == joint_objects.end()) {
+ Object *job = bc_add_object(scene, OB_EMPTY, (char *)get_joint_name(node));
+
+ job->lay = BKE_scene_base_find(scene, job)->lay = 2;
+
+ mul_v3_fl(job->scale, 0.5f);
+ DEG_id_tag_update(&job->id, ID_RECALC_TRANSFORM);
+
+ ED_id_action_ensure((ID *)&job->id);
+
+ job->rotmode = ROT_MODE_QUAT;
+
+ float mat[4][4];
+ get_joint_rest_mat(mat, root, node);
+
+ if (par_job) {
+ float temp[4][4], ipar[4][4];
+ invert_m4_m4(ipar, par_job->obmat);
+ copy_m4_m4(temp, mat);
+ mul_m4_m4m4(mat, ipar, temp);
+ }
+
+ bc_decompose(mat, job->loc, NULL, job->quat, job->scale);
+
+ if (par_job) {
+ job->parent = par_job;
+
+ DEG_id_tag_update(&par_job->id, ID_RECALC_TRANSFORM);
+ job->parsubstr[0] = 0;
+ }
+
+ BKE_object_where_is_calc(scene, job);
+
+ /* after parenting and layer change */
+ DEG_relations_tag_update(CTX_data_main(C));
+
+ joint_objects[node->getUniqueId()] = job;
+ }
+
+ return joint_objects[node->getUniqueId()];
+}
+#endif
+
+#if 0
+/* recursively evaluates joint tree until end is found,
+ * mat then is world-space matrix of end mat must be identity on enter, node must be root. */
+bool AnimationImporter::evaluate_joint_world_transform_at_frame(
+ float mat[4][4], float par[4][4], COLLADAFW::Node *node, COLLADAFW::Node *end, float fra)
+{
+ float m[4][4];
+ if (par) {
+ float temp[4][4];
+ evaluate_transform_at_frame(temp, node, node == end ? fra : 0.0f);
+ mul_m4_m4m4(m, par, temp);
+ }
+ else {
+ evaluate_transform_at_frame(m, node, node == end ? fra : 0.0f);
+ }
+
+ if (node == end) {
+ copy_m4_m4(mat, m);
+ return true;
+ }
+ else {
+ COLLADAFW::NodePointerArray &children = node->getChildNodes();
+ for (int i = 0; i < children.getCount(); i++) {
+ if (evaluate_joint_world_transform_at_frame(mat, m, children[i], end, fra)) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+#endif
+
+void AnimationImporter::add_bone_fcurve(Object *ob, COLLADAFW::Node *node, FCurve *fcu)
+{
+ const char *bone_name = bc_get_joint_name(node);
+ bAction *act = ob->adt->action;
+
+ /* try to find group */
+ bActionGroup *grp = BKE_action_group_find_name(act, bone_name);
+
+ /* no matching groups, so add one */
+ if (grp == NULL) {
+ /* Add a new group, and make it active */
+ grp = (bActionGroup *)MEM_callocN(sizeof(bActionGroup), "bActionGroup");
+
+ grp->flag = AGRP_SELECTED;
+ BLI_strncpy(grp->name, bone_name, sizeof(grp->name));
+
+ BLI_addtail(&act->groups, grp);
+ BLI_uniquename(&act->groups,
+ grp,
+ CTX_DATA_(BLT_I18NCONTEXT_ID_ACTION, "Group"),
+ '.',
+ offsetof(bActionGroup, name),
+ 64);
+ }
+
+ /* add F-Curve to group */
+ action_groups_add_channel(act, grp, fcu);
+}
+
+void AnimationImporter::set_import_from_version(std::string import_from_version)
+{
+ this->import_from_version = import_from_version;
+}
diff --git a/source/blender/io/collada/AnimationImporter.h b/source/blender/io/collada/AnimationImporter.h
new file mode 100644
index 00000000000..0043dad7116
--- /dev/null
+++ b/source/blender/io/collada/AnimationImporter.h
@@ -0,0 +1,254 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __ANIMATIONIMPORTER_H__
+#define __ANIMATIONIMPORTER_H__
+
+#include <map>
+#include <vector>
+
+#include "COLLADAFWAnimation.h"
+#include "COLLADAFWAnimationCurve.h"
+#include "COLLADAFWAnimationList.h"
+#include "COLLADAFWNode.h"
+#include "COLLADAFWUniqueId.h"
+#include "COLLADAFWLight.h"
+#include "COLLADAFWCamera.h"
+#include "COLLADAFWMaterial.h"
+#include "COLLADAFWEffect.h"
+#include "COLLADAFWInstanceGeometry.h"
+
+extern "C" {
+#include "BKE_context.h"
+#include "DNA_anim_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_light_types.h"
+#include "DNA_camera_types.h"
+}
+
+//#include "ArmatureImporter.h"
+#include "TransformReader.h"
+
+#include "collada_internal.h"
+
+class ArmatureImporter;
+
+class AnimationImporterBase {
+ public:
+ // virtual void change_eul_to_quat(Object *ob, bAction *act) = 0;
+};
+
+class AnimationImporter : private TransformReader, public AnimationImporterBase {
+ private:
+ bContext *mContext;
+ ArmatureImporter *armature_importer;
+ Scene *scene;
+
+ std::map<COLLADAFW::UniqueId, std::vector<FCurve *>> curve_map;
+ std::map<COLLADAFW::UniqueId, TransformReader::Animation> uid_animated_map;
+ // std::map<bActionGroup*, std::vector<FCurve*> > fcurves_actionGroup_map;
+ std::map<COLLADAFW::UniqueId, const COLLADAFW::AnimationList *> animlist_map;
+ std::vector<FCurve *> unused_curves;
+ std::map<COLLADAFW::UniqueId, Object *> joint_objects;
+
+ FCurve *create_fcurve(int array_index, const char *rna_path);
+
+ void add_bezt(FCurve *fcu,
+ float frame,
+ float value,
+ eBezTriple_Interpolation ipo = BEZT_IPO_LIN);
+
+ // create one or several fcurves depending on the number of parameters being animated
+ void animation_to_fcurves(COLLADAFW::AnimationCurve *curve);
+
+ void fcurve_deg_to_rad(FCurve *cu);
+ void fcurve_scale(FCurve *cu, int scale);
+
+ void fcurve_is_used(FCurve *fcu);
+
+ void add_fcurves_to_object(Main *bmain,
+ Object *ob,
+ std::vector<FCurve *> &curves,
+ char *rna_path,
+ int array_index,
+ Animation *animated);
+
+ int typeFlag;
+
+ std::string import_from_version;
+
+ enum lightAnim {
+ // INANIMATE = 0,
+ LIGHT_COLOR = 2,
+ LIGHT_FOA = 4,
+ LIGHT_FOE = 8,
+ };
+
+ enum cameraAnim {
+ // INANIMATE = 0,
+ CAMERA_XFOV = 2,
+ CAMERA_XMAG = 4,
+ CAMERA_YFOV = 8,
+ CAMERA_YMAG = 16,
+ CAMERA_ZFAR = 32,
+ CAMERA_ZNEAR = 64,
+ };
+
+ enum matAnim {
+ MATERIAL_SHININESS = 2,
+ MATERIAL_SPEC_COLOR = 4,
+ MATERIAL_DIFF_COLOR = 1 << 3,
+ MATERIAL_TRANSPARENCY = 1 << 4,
+ MATERIAL_IOR = 1 << 5,
+ };
+
+ enum AnimationType {
+ BC_INANIMATE = 0,
+ BC_NODE_TRANSFORM = 1,
+ };
+
+ struct AnimMix {
+ int transform;
+ int light;
+ int camera;
+ int material;
+ int texture;
+ };
+
+ public:
+ AnimationImporter(bContext *C, UnitConverter *conv, ArmatureImporter *arm, Scene *scene)
+ : TransformReader(conv), mContext(C), armature_importer(arm), scene(scene)
+ {
+ }
+
+ ~AnimationImporter();
+
+ void set_import_from_version(std::string import_from_version);
+ bool write_animation(const COLLADAFW::Animation *anim);
+
+ // called on post-process stage after writeVisualScenes
+ bool write_animation_list(const COLLADAFW::AnimationList *animlist);
+
+ void read_node_transform(COLLADAFW::Node *node, Object *ob);
+#if 0
+ virtual void change_eul_to_quat(Object *ob, bAction *act);
+#endif
+
+ void translate_Animations(COLLADAFW::Node *Node,
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &root_map,
+ std::multimap<COLLADAFW::UniqueId, Object *> &object_map,
+ std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map,
+ std::map<COLLADAFW::UniqueId, Material *> uid_material_map);
+
+ AnimMix *get_animation_type(
+ const COLLADAFW::Node *node,
+ std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map);
+
+ void apply_matrix_curves(Object *ob,
+ std::vector<FCurve *> &animcurves,
+ COLLADAFW::Node *root,
+ COLLADAFW::Node *node,
+ COLLADAFW::Transformation *tm);
+
+ void add_bone_animation_sampled(Object *ob,
+ std::vector<FCurve *> &animcurves,
+ COLLADAFW::Node *root,
+ COLLADAFW::Node *node,
+ COLLADAFW::Transformation *tm);
+
+ void Assign_transform_animations(COLLADAFW::Transformation *transform,
+ const COLLADAFW::AnimationList::AnimationBinding *binding,
+ std::vector<FCurve *> *curves,
+ bool is_joint,
+ char *joint_path);
+
+ void Assign_color_animations(const COLLADAFW::UniqueId &listid,
+ ListBase *AnimCurves,
+ const char *anim_type);
+ void Assign_float_animations(const COLLADAFW::UniqueId &listid,
+ ListBase *AnimCurves,
+ const char *anim_type);
+ void Assign_lens_animations(const COLLADAFW::UniqueId &listid,
+ ListBase *AnimCurves,
+ const double aspect,
+ Camera *cam,
+ const char *anim_type,
+ int fov_type);
+
+ int setAnimType(const COLLADAFW::Animatable *prop, int type, int addition);
+
+ void modify_fcurve(std::vector<FCurve *> *curves,
+ const char *rna_path,
+ int array_index,
+ int scale = 1);
+ void unused_fcurve(std::vector<FCurve *> *curves);
+ // prerequisites:
+ // animlist_map - map animlist id -> animlist
+ // curve_map - map anim id -> curve(s)
+ Object *translate_animation_OLD(COLLADAFW::Node *node,
+ std::map<COLLADAFW::UniqueId, Object *> &object_map,
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &root_map,
+ COLLADAFW::Transformation::TransformationType tm_type,
+ Object *par_job = NULL);
+
+ void find_frames(std::vector<float> *frames, std::vector<FCurve *> *curves);
+ void find_frames_old(std::vector<float> *frames,
+ COLLADAFW::Node *node,
+ COLLADAFW::Transformation::TransformationType tm_type);
+ // internal, better make it private
+ // warning: evaluates only rotation
+ // prerequisites: animlist_map, curve_map
+ void evaluate_transform_at_frame(float mat[4][4], COLLADAFW::Node *node, float fra);
+
+ // return true to indicate that mat contains a sane value
+ bool evaluate_animation(COLLADAFW::Transformation *tm,
+ float mat[4][4],
+ float fra,
+ const char *node_id);
+
+ // gives a world-space mat of joint at rest position
+ void get_joint_rest_mat(float mat[4][4], COLLADAFW::Node *root, COLLADAFW::Node *node);
+
+ // gives a world-space mat, end's mat not included
+ bool calc_joint_parent_mat_rest(float mat[4][4],
+ float par[4][4],
+ COLLADAFW::Node *node,
+ COLLADAFW::Node *end);
+
+ float convert_to_focal_length(float in_xfov, int fov_type, float aspect, float sensorx);
+
+#ifdef ARMATURE_TEST
+ Object *get_joint_object(COLLADAFW::Node *root, COLLADAFW::Node *node, Object *par_job);
+#endif
+
+#if 0
+ // recursively evaluates joint tree until end is found, mat then is world-space matrix of end
+ // mat must be identity on enter, node must be root
+ bool evaluate_joint_world_transform_at_frame(
+ float mat[4][4], float par[4][4], COLLADAFW::Node *node, COLLADAFW::Node *end, float fra);
+#endif
+
+ void add_bone_fcurve(Object *ob, COLLADAFW::Node *node, FCurve *fcu);
+
+ void extra_data_importer(std::string elementName);
+};
+
+#endif
diff --git a/source/blender/io/collada/ArmatureExporter.cpp b/source/blender/io/collada/ArmatureExporter.cpp
new file mode 100644
index 00000000000..84979cc4ca4
--- /dev/null
+++ b/source/blender/io/collada/ArmatureExporter.cpp
@@ -0,0 +1,328 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include "COLLADASWBaseInputElement.h"
+#include "COLLADASWInstanceController.h"
+#include "COLLADASWPrimitves.h"
+#include "COLLADASWSource.h"
+
+#include "DNA_action_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+
+#include "BKE_action.h"
+#include "BKE_armature.h"
+
+extern "C" {
+#include "BKE_global.h"
+#include "BKE_mesh.h"
+}
+
+#include "ED_armature.h"
+
+#include "BLI_listbase.h"
+
+#include "GeometryExporter.h"
+#include "ArmatureExporter.h"
+#include "SceneExporter.h"
+
+// write bone nodes
+void ArmatureExporter::add_armature_bones(Object *ob_arm,
+ ViewLayer *view_layer,
+ SceneExporter *se,
+ std::vector<Object *> &child_objects)
+
+{
+ // write bone nodes
+
+ bArmature *armature = (bArmature *)ob_arm->data;
+ bool is_edited = armature->edbo != NULL;
+
+ if (!is_edited) {
+ ED_armature_to_edit(armature);
+ }
+
+ for (Bone *bone = (Bone *)armature->bonebase.first; bone; bone = bone->next) {
+ add_bone_node(bone, ob_arm, se, child_objects);
+ }
+
+ if (!is_edited) {
+ ED_armature_edit_free(armature);
+ }
+}
+
+void ArmatureExporter::write_bone_URLs(COLLADASW::InstanceController &ins,
+ Object *ob_arm,
+ Bone *bone)
+{
+ if (bc_is_root_bone(bone, this->export_settings.get_deform_bones_only())) {
+ std::string joint_id = translate_id(id_name(ob_arm) + "_" + bone->name);
+ ins.addSkeleton(COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, joint_id));
+ }
+ else {
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ write_bone_URLs(ins, ob_arm, child);
+ }
+ }
+}
+
+bool ArmatureExporter::add_instance_controller(Object *ob)
+{
+ Object *ob_arm = bc_get_assigned_armature(ob);
+ bArmature *arm = (bArmature *)ob_arm->data;
+
+ const std::string &controller_id = get_controller_id(ob_arm, ob);
+
+ COLLADASW::InstanceController ins(mSW);
+ ins.setUrl(COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, controller_id));
+
+ Mesh *me = (Mesh *)ob->data;
+ if (!me->dvert) {
+ return false;
+ }
+
+ // write root bone URLs
+ Bone *bone;
+ for (bone = (Bone *)arm->bonebase.first; bone; bone = bone->next) {
+ write_bone_URLs(ins, ob_arm, bone);
+ }
+
+ InstanceWriter::add_material_bindings(
+ ins.getBindMaterial(), ob, this->export_settings.get_active_uv_only());
+
+ ins.add();
+ return true;
+}
+
+#if 0
+void ArmatureExporter::operator()(Object *ob)
+{
+ Object *ob_arm = bc_get_assigned_armature(ob);
+}
+
+bool ArmatureExporter::already_written(Object *ob_arm)
+{
+ return std::find(written_armatures.begin(), written_armatures.end(), ob_arm) !=
+ written_armatures.end();
+}
+
+void ArmatureExporter::wrote(Object *ob_arm)
+{
+ written_armatures.push_back(ob_arm);
+}
+
+void ArmatureExporter::find_objects_using_armature(Object *ob_arm,
+ std::vector<Object *> &objects,
+ Scene *sce)
+{
+ objects.clear();
+
+ Base *base = (Base *)sce->base.first;
+ while (base) {
+ Object *ob = base->object;
+
+ if (ob->type == OB_MESH && get_assigned_armature(ob) == ob_arm) {
+ objects.push_back(ob);
+ }
+
+ base = base->next;
+ }
+}
+#endif
+
+// parent_mat is armature-space
+void ArmatureExporter::add_bone_node(Bone *bone,
+ Object *ob_arm,
+ SceneExporter *se,
+ std::vector<Object *> &child_objects)
+{
+ if (can_export(bone)) {
+ std::string node_id = translate_id(id_name(ob_arm) + "_" + bone->name);
+ std::string node_name = std::string(bone->name);
+ std::string node_sid = get_joint_sid(bone);
+
+ COLLADASW::Node node(mSW);
+
+ node.setType(COLLADASW::Node::JOINT);
+ node.setNodeId(node_id);
+ node.setNodeName(node_name);
+ node.setNodeSid(node_sid);
+
+ if (this->export_settings.get_use_blender_profile()) {
+ if (!is_export_root(bone)) {
+ if (bone->flag & BONE_CONNECTED) {
+ node.addExtraTechniqueParameter("blender", "connect", true);
+ }
+ }
+ std::string layers = BoneExtended::get_bone_layers(bone->layer);
+ node.addExtraTechniqueParameter("blender", "layer", layers);
+
+ bArmature *armature = (bArmature *)ob_arm->data;
+ EditBone *ebone = bc_get_edit_bone(armature, bone->name);
+ if (ebone && ebone->roll != 0) {
+ node.addExtraTechniqueParameter("blender", "roll", ebone->roll);
+ }
+ if (bc_is_leaf_bone(bone)) {
+ Vector head, tail;
+ const BCMatrix &global_transform = this->export_settings.get_global_transform();
+ if (this->export_settings.get_apply_global_orientation()) {
+ bc_add_global_transform(head, bone->arm_head, global_transform);
+ bc_add_global_transform(tail, bone->arm_tail, global_transform);
+ }
+ else {
+ copy_v3_v3(head, bone->arm_head);
+ copy_v3_v3(tail, bone->arm_tail);
+ }
+ node.addExtraTechniqueParameter("blender", "tip_x", tail[0] - head[0]);
+ node.addExtraTechniqueParameter("blender", "tip_y", tail[1] - head[1]);
+ node.addExtraTechniqueParameter("blender", "tip_z", tail[2] - head[2]);
+ }
+ }
+
+ node.start();
+
+ add_bone_transform(ob_arm, bone, node);
+
+ // Write nodes of childobjects, remove written objects from list
+ std::vector<Object *>::iterator iter = child_objects.begin();
+
+ while (iter != child_objects.end()) {
+ Object *ob = *iter;
+ if (ob->partype == PARBONE && STREQ(ob->parsubstr, bone->name)) {
+ float backup_parinv[4][4];
+ copy_m4_m4(backup_parinv, ob->parentinv);
+
+ // crude, temporary change to parentinv
+ // so transform gets exported correctly.
+
+ // Add bone tail- translation... don't know why
+ // bone parenting is against the tail of a bone
+ // and not it's head, seems arbitrary.
+ ob->parentinv[3][1] += bone->length;
+
+ // OPEN_SIM_COMPATIBILITY
+ // TODO: when such objects are animated as
+ // single matrix the tweak must be applied
+ // to the result.
+ if (export_settings.get_open_sim()) {
+ // tweak objects parentinverse to match compatibility
+ float temp[4][4];
+
+ copy_m4_m4(temp, bone->arm_mat);
+ temp[3][0] = temp[3][1] = temp[3][2] = 0.0f;
+
+ mul_m4_m4m4(ob->parentinv, temp, ob->parentinv);
+ }
+
+ se->writeNode(ob);
+ copy_m4_m4(ob->parentinv, backup_parinv);
+ iter = child_objects.erase(iter);
+ }
+ else {
+ iter++;
+ }
+ }
+
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ add_bone_node(child, ob_arm, se, child_objects);
+ }
+ node.end();
+ }
+ else {
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ add_bone_node(child, ob_arm, se, child_objects);
+ }
+ }
+}
+
+bool ArmatureExporter::is_export_root(Bone *bone)
+{
+ Bone *entry = bone->parent;
+ while (entry) {
+ if (can_export(entry)) {
+ return false;
+ }
+ entry = entry->parent;
+ }
+ return can_export(bone);
+}
+
+void ArmatureExporter::add_bone_transform(Object *ob_arm, Bone *bone, COLLADASW::Node &node)
+{
+ // bPoseChannel *pchan = BKE_pose_channel_find_name(ob_arm->pose, bone->name);
+
+ float mat[4][4];
+ float bone_rest_mat[4][4]; /* derived from bone->arm_mat */
+ float parent_rest_mat[4][4]; /* derived from bone->parent->arm_mat */
+
+ bool has_restmat = bc_get_property_matrix(bone, "rest_mat", mat);
+
+ if (!has_restmat) {
+
+ /* Have no restpose matrix stored, try old style <= Blender 2.78 */
+
+ bc_create_restpose_mat(this->export_settings, bone, bone_rest_mat, bone->arm_mat, true);
+
+ if (is_export_root(bone)) {
+ copy_m4_m4(mat, bone_rest_mat);
+ }
+ else {
+ Matrix parent_inverse;
+ bc_create_restpose_mat(
+ this->export_settings, bone->parent, parent_rest_mat, bone->parent->arm_mat, true);
+
+ invert_m4_m4(parent_inverse, parent_rest_mat);
+ mul_m4_m4m4(mat, parent_inverse, bone_rest_mat);
+ }
+
+ // OPEN_SIM_COMPATIBILITY
+
+ if (export_settings.get_open_sim()) {
+ // Remove rotations vs armature from transform
+ // parent_rest_rot * mat * irest_rot
+ Matrix workmat;
+ copy_m4_m4(workmat, bone_rest_mat);
+
+ workmat[3][0] = workmat[3][1] = workmat[3][2] = 0.0f;
+ invert_m4(workmat);
+
+ mul_m4_m4m4(mat, mat, workmat);
+
+ if (!is_export_root(bone)) {
+ copy_m4_m4(workmat, parent_rest_mat);
+ workmat[3][0] = workmat[3][1] = workmat[3][2] = 0.0f;
+
+ mul_m4_m4m4(mat, workmat, mat);
+ }
+ }
+ }
+
+ if (this->export_settings.get_limit_precision()) {
+ BCMatrix::sanitize(mat, LIMITTED_PRECISION);
+ }
+
+ TransformWriter::add_joint_transform(node, mat, NULL, this->export_settings, has_restmat);
+}
+
+std::string ArmatureExporter::get_controller_id(Object *ob_arm, Object *ob)
+{
+ return translate_id(id_name(ob_arm)) + "_" + translate_id(id_name(ob)) +
+ SKIN_CONTROLLER_ID_SUFFIX;
+}
diff --git a/source/blender/io/collada/ArmatureExporter.h b/source/blender/io/collada/ArmatureExporter.h
new file mode 100644
index 00000000000..da6d6f79ef5
--- /dev/null
+++ b/source/blender/io/collada/ArmatureExporter.h
@@ -0,0 +1,107 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __ARMATUREEXPORTER_H__
+#define __ARMATUREEXPORTER_H__
+
+#include <list>
+#include <string>
+//#include <vector>
+
+#include "COLLADASWStreamWriter.h"
+#include "COLLADASWLibraryControllers.h"
+#include "COLLADASWInputList.h"
+#include "COLLADASWNode.h"
+
+#include "DNA_armature_types.h"
+#include "DNA_listBase.h"
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+#include "DNA_constraint_types.h"
+#include "DNA_scene_types.h"
+
+#include "TransformWriter.h"
+#include "InstanceWriter.h"
+
+#include "ExportSettings.h"
+
+class SceneExporter;
+
+// XXX exporter writes wrong data for shared armatures. A separate
+// controller should be written for each armature-mesh binding how do
+// we make controller ids then?
+class ArmatureExporter : public COLLADASW::LibraryControllers,
+ protected TransformWriter,
+ protected InstanceWriter {
+ public:
+ // XXX exporter writes wrong data for shared armatures. A separate
+ // controller should be written for each armature-mesh binding how do
+ // we make controller ids then?
+ ArmatureExporter(BlenderContext &blender_context,
+ COLLADASW::StreamWriter *sw,
+ BCExportSettings &export_settings)
+ : COLLADASW::LibraryControllers(sw),
+ blender_context(blender_context),
+ export_settings(export_settings)
+ {
+ }
+
+ void add_armature_bones(Object *ob_arm,
+ ViewLayer *view_layer,
+ SceneExporter *se,
+ std::vector<Object *> &child_objects);
+
+ bool add_instance_controller(Object *ob);
+
+ private:
+ BlenderContext &blender_context;
+ BCExportSettings &export_settings;
+
+#if 0
+ std::vector<Object *> written_armatures;
+
+ bool already_written(Object *ob_arm);
+
+ void wrote(Object *ob_arm);
+
+ void find_objects_using_armature(Object *ob_arm, std::vector<Object *> &objects, Scene *sce);
+#endif
+
+ // Scene, SceneExporter and the list of child_objects
+ // are required for writing bone parented objects
+ void add_bone_node(Bone *bone,
+ Object *ob_arm,
+ SceneExporter *se,
+ std::vector<Object *> &child_objects);
+
+ inline bool can_export(Bone *bone)
+ {
+ return !(export_settings.get_deform_bones_only() && bone->flag & BONE_NO_DEFORM);
+ }
+
+ bool is_export_root(Bone *bone);
+ void add_bone_transform(Object *ob_arm, Bone *bone, COLLADASW::Node &node);
+
+ std::string get_controller_id(Object *ob_arm, Object *ob);
+
+ void write_bone_URLs(COLLADASW::InstanceController &ins, Object *ob_arm, Bone *bone);
+};
+
+#endif
diff --git a/source/blender/io/collada/ArmatureImporter.cpp b/source/blender/io/collada/ArmatureImporter.cpp
new file mode 100644
index 00000000000..b8c534f97dd
--- /dev/null
+++ b/source/blender/io/collada/ArmatureImporter.cpp
@@ -0,0 +1,1115 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+/* COLLADABU_ASSERT, may be able to remove later */
+#include "COLLADABUPlatform.h"
+
+#include <algorithm>
+
+#include "COLLADAFWUniqueId.h"
+
+extern "C" {
+#include "BKE_action.h"
+#include "BKE_object.h"
+#include "BKE_armature.h"
+#include "BLI_string.h"
+#include "BLI_listbase.h"
+#include "ED_armature.h"
+}
+
+#include "DEG_depsgraph.h"
+
+#include "collada_utils.h"
+#include "ArmatureImporter.h"
+
+/* use node name, or fall back to original id if not present (name is optional) */
+template<class T> static const char *bc_get_joint_name(T *node)
+{
+ const std::string &id = node->getName();
+ return id.size() ? id.c_str() : node->getOriginalId().c_str();
+}
+
+ArmatureImporter::ArmatureImporter(UnitConverter *conv,
+ MeshImporterBase *mesh,
+ Main *bmain,
+ Scene *sce,
+ ViewLayer *view_layer,
+ const ImportSettings *import_settings)
+ : TransformReader(conv),
+ m_bmain(bmain),
+ scene(sce),
+ view_layer(view_layer),
+ unit_converter(conv),
+ import_settings(import_settings),
+ empty(NULL),
+ mesh_importer(mesh)
+{
+}
+
+ArmatureImporter::~ArmatureImporter()
+{
+ /* free skin controller data if we forget to do this earlier */
+ std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it;
+ for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) {
+ it->second.free();
+ }
+}
+
+#if 0
+JointData *ArmatureImporter::get_joint_data(COLLADAFW::Node *node);
+{
+ const COLLADAFW::UniqueId &joint_id = node->getUniqueId();
+
+ if (joint_id_to_joint_index_map.find(joint_id) == joint_id_to_joint_index_map.end()) {
+ fprintf(
+ stderr, "Cannot find a joint index by joint id for %s.\n", node->getOriginalId().c_str());
+ return NULL;
+ }
+
+ int joint_index = joint_id_to_joint_index_map[joint_id];
+
+ return &joint_index_to_joint_info_map[joint_index];
+}
+#endif
+
+int ArmatureImporter::create_bone(SkinInfo *skin,
+ COLLADAFW::Node *node,
+ EditBone *parent,
+ int totchild,
+ float parent_mat[4][4],
+ bArmature *arm,
+ std::vector<std::string> &layer_labels)
+{
+ float mat[4][4];
+ float joint_inv_bind_mat[4][4];
+ float joint_bind_mat[4][4];
+ int chain_length = 0;
+
+ /* Checking if bone is already made. */
+ std::vector<COLLADAFW::Node *>::iterator it;
+ it = std::find(finished_joints.begin(), finished_joints.end(), node);
+ if (it != finished_joints.end()) {
+ return chain_length;
+ }
+
+ EditBone *bone = ED_armature_ebone_add(arm, bc_get_joint_name(node));
+ totbone++;
+
+ /*
+ * We use the inv_bind_shape matrix to apply the armature bind pose as its rest pose.
+ */
+
+ std::map<COLLADAFW::UniqueId, SkinInfo>::iterator skin_it;
+ bool bone_is_skinned = false;
+ for (skin_it = skin_by_data_uid.begin(); skin_it != skin_by_data_uid.end(); skin_it++) {
+
+ SkinInfo *b = &skin_it->second;
+ if (b->get_joint_inv_bind_matrix(joint_inv_bind_mat, node)) {
+
+ /* get original world-space matrix */
+ invert_m4_m4(mat, joint_inv_bind_mat);
+ copy_m4_m4(joint_bind_mat, mat);
+ /* And make local to armature */
+ Object *ob_arm = skin->BKE_armature_from_object();
+ if (ob_arm) {
+ float invmat[4][4];
+ invert_m4_m4(invmat, ob_arm->obmat);
+ mul_m4_m4m4(mat, invmat, mat);
+ }
+
+ bone_is_skinned = true;
+ break;
+ }
+ }
+
+ /* create a bone even if there's no joint data for it (i.e. it has no influence) */
+ if (!bone_is_skinned) {
+ get_node_mat(mat, node, NULL, NULL, parent_mat);
+ }
+
+ if (parent) {
+ bone->parent = parent;
+ }
+
+ float loc[3], size[3], rot[3][3];
+ BoneExtensionMap &extended_bones = bone_extension_manager.getExtensionMap(arm);
+ BoneExtended &be = add_bone_extended(bone, node, totchild, layer_labels, extended_bones);
+ int layer = be.get_bone_layers();
+ if (layer) {
+ bone->layer = layer;
+ }
+ arm->layer |= layer; // ensure that all populated bone layers are visible after import
+
+ float *tail = be.get_tail();
+ int use_connect = be.get_use_connect();
+
+ switch (use_connect) {
+ case 1:
+ bone->flag |= BONE_CONNECTED;
+ break;
+ case -1: /* Connect type not specified */
+ case 0:
+ bone->flag &= ~BONE_CONNECTED;
+ break;
+ }
+
+ if (be.has_roll()) {
+ bone->roll = be.get_roll();
+ }
+ else {
+ float angle;
+ mat4_to_loc_rot_size(loc, rot, size, mat);
+ mat3_to_vec_roll(rot, NULL, &angle);
+ bone->roll = angle;
+ }
+ copy_v3_v3(bone->head, mat[3]);
+
+ if (bone_is_skinned && this->import_settings->keep_bind_info) {
+ float rest_mat[4][4];
+ get_node_mat(rest_mat, node, NULL, NULL, NULL);
+ bc_set_IDPropertyMatrix(bone, "bind_mat", joint_bind_mat);
+ bc_set_IDPropertyMatrix(bone, "rest_mat", rest_mat);
+ }
+
+ add_v3_v3v3(bone->tail, bone->head, tail); /* tail must be non zero */
+
+ /* find smallest bone length in armature (used later for leaf bone length) */
+ if (parent) {
+
+ if (use_connect == 1) {
+ copy_v3_v3(parent->tail, bone->head);
+ }
+
+ /* guess reasonable leaf bone length */
+ float length = len_v3v3(parent->head, bone->head);
+ if ((length < leaf_bone_length || totbone == 0) && length > MINIMUM_BONE_LENGTH) {
+ leaf_bone_length = length;
+ }
+ }
+
+ COLLADAFW::NodePointerArray &children = node->getChildNodes();
+
+ for (unsigned int i = 0; i < children.getCount(); i++) {
+ int cl = create_bone(skin, children[i], bone, children.getCount(), mat, arm, layer_labels);
+ if (cl > chain_length) {
+ chain_length = cl;
+ }
+ }
+
+ bone->length = len_v3v3(bone->head, bone->tail);
+ joint_by_uid[node->getUniqueId()] = node;
+ finished_joints.push_back(node);
+
+ be.set_chain_length(chain_length + 1);
+
+ return chain_length + 1;
+}
+
+/**
+ * Collada only knows Joints, hence bones at the end of a bone chain
+ * don't have a defined length. This function guesses reasonable
+ * tail locations for the affected bones (nodes which don't have any connected child)
+ * Hint: The extended_bones set gets populated in ArmatureImporter::create_bone
+ */
+void ArmatureImporter::fix_leaf_bone_hierarchy(bArmature *armature,
+ Bone *bone,
+ bool fix_orientation)
+{
+ if (bone == NULL) {
+ return;
+ }
+
+ if (bc_is_leaf_bone(bone)) {
+ BoneExtensionMap &extended_bones = bone_extension_manager.getExtensionMap(armature);
+ BoneExtended *be = extended_bones[bone->name];
+ EditBone *ebone = bc_get_edit_bone(armature, bone->name);
+ fix_leaf_bone(armature, ebone, be, fix_orientation);
+ }
+
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ fix_leaf_bone_hierarchy(armature, child, fix_orientation);
+ }
+}
+
+void ArmatureImporter::fix_leaf_bone(bArmature *armature,
+ EditBone *ebone,
+ BoneExtended *be,
+ bool fix_orientation)
+{
+ if (be == NULL || !be->has_tail()) {
+
+ /* Collada only knows Joints, Here we guess a reasonable leaf bone length */
+ float leaf_length = (leaf_bone_length == FLT_MAX) ? 1.0 : leaf_bone_length;
+
+ float vec[3];
+
+ if (fix_orientation && ebone->parent != NULL) {
+ EditBone *parent = ebone->parent;
+ sub_v3_v3v3(vec, ebone->head, parent->head);
+ if (len_squared_v3(vec) < MINIMUM_BONE_LENGTH) {
+ sub_v3_v3v3(vec, parent->tail, parent->head);
+ }
+ }
+ else {
+ vec[2] = 0.1f;
+ sub_v3_v3v3(vec, ebone->tail, ebone->head);
+ }
+
+ normalize_v3_v3(vec, vec);
+ mul_v3_fl(vec, leaf_length);
+ add_v3_v3v3(ebone->tail, ebone->head, vec);
+ }
+}
+
+void ArmatureImporter::fix_parent_connect(bArmature *armature, Bone *bone)
+{
+ /* armature has no bones */
+ if (bone == NULL) {
+ return;
+ }
+
+ if (bone->parent && bone->flag & BONE_CONNECTED) {
+ copy_v3_v3(bone->parent->tail, bone->head);
+ }
+
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ fix_parent_connect(armature, child);
+ }
+}
+
+void ArmatureImporter::connect_bone_chains(bArmature *armature, Bone *parentbone, int clip)
+{
+ BoneExtensionMap &extended_bones = bone_extension_manager.getExtensionMap(armature);
+ BoneExtended *dominant_child = NULL;
+ int maxlen = 0;
+
+ if (parentbone == NULL) {
+ return;
+ }
+
+ Bone *child = (Bone *)parentbone->childbase.first;
+ if (child && (import_settings->find_chains || child->next == NULL)) {
+ for (; child; child = child->next) {
+ BoneExtended *be = extended_bones[child->name];
+ if (be != NULL) {
+ int chain_len = be->get_chain_length();
+ if (chain_len <= clip) {
+ if (chain_len > maxlen) {
+ dominant_child = be;
+ maxlen = chain_len;
+ }
+ else if (chain_len == maxlen) {
+ dominant_child = NULL;
+ }
+ }
+ }
+ }
+ }
+
+ BoneExtended *pbe = extended_bones[parentbone->name];
+ if (dominant_child != NULL) {
+ /* Found a valid chain. Now connect current bone with that chain.*/
+ EditBone *pebone = bc_get_edit_bone(armature, parentbone->name);
+ EditBone *cebone = bc_get_edit_bone(armature, dominant_child->get_name());
+ if (pebone && !(cebone->flag & BONE_CONNECTED)) {
+ float vec[3];
+ sub_v3_v3v3(vec, cebone->head, pebone->head);
+
+ /*
+ * It is possible that the child's head is located on the parents head.
+ * When this happens, then moving the parent's tail to the child's head
+ * would result in a zero sized bone and Blender would silently remove the bone.
+ * So we move the tail only when the resulting bone has a minimum length:
+ */
+
+ if (len_squared_v3(vec) > MINIMUM_BONE_LENGTH) {
+ copy_v3_v3(pebone->tail, cebone->head);
+ pbe->set_tail(pebone->tail); /* to make fix_leafbone happy ...*/
+ if (pbe && pbe->get_chain_length() >= this->import_settings->min_chain_length) {
+
+ BoneExtended *cbe = extended_bones[cebone->name];
+ cbe->set_use_connect(true);
+
+ cebone->flag |= BONE_CONNECTED;
+ pbe->set_leaf_bone(false);
+ printf("Connect Bone chain: parent (%s --> %s) child)\n", pebone->name, cebone->name);
+ }
+ }
+ }
+ for (Bone *ch = (Bone *)parentbone->childbase.first; ch; ch = ch->next) {
+ ArmatureImporter::connect_bone_chains(armature, ch, UNLIMITED_CHAIN_MAX);
+ }
+ }
+ else if (maxlen > 1 && maxlen > this->import_settings->min_chain_length) {
+ /* Try again with smaller chain length */
+ ArmatureImporter::connect_bone_chains(armature, parentbone, maxlen - 1);
+ }
+ else {
+ /* can't connect this Bone. Proceed with children ... */
+ if (pbe) {
+ pbe->set_leaf_bone(true);
+ }
+ for (Bone *ch = (Bone *)parentbone->childbase.first; ch; ch = ch->next) {
+ ArmatureImporter::connect_bone_chains(armature, ch, UNLIMITED_CHAIN_MAX);
+ }
+ }
+}
+
+#if 0
+void ArmatureImporter::set_leaf_bone_shapes(Object *ob_arm)
+{
+ bPose *pose = ob_arm->pose;
+
+ std::vector<LeafBone>::iterator it;
+ for (it = leaf_bones.begin(); it != leaf_bones.end(); it++) {
+ LeafBone &leaf = *it;
+
+ bPoseChannel *pchan = BKE_pose_channel_find_name(pose, leaf.name);
+ if (pchan) {
+ pchan->custom = get_empty_for_leaves();
+ }
+ else {
+ fprintf(stderr, "Cannot find a pose channel for leaf bone %s\n", leaf.name);
+ }
+ }
+}
+
+void ArmatureImporter::set_euler_rotmode()
+{
+ /* just set rotmode = ROT_MODE_EUL on pose channel for each joint */
+
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *>::iterator it;
+
+ for (it = joint_by_uid.begin(); it != joint_by_uid.end(); it++) {
+
+ COLLADAFW::Node *joint = it->second;
+
+ std::map<COLLADAFW::UniqueId, SkinInfo>::iterator sit;
+
+ for (sit = skin_by_data_uid.begin(); sit != skin_by_data_uid.end(); sit++) {
+ SkinInfo &skin = sit->second;
+
+ if (skin.uses_joint_or_descendant(joint)) {
+ bPoseChannel *pchan = skin.get_pose_channel_from_node(joint);
+
+ if (pchan) {
+ pchan->rotmode = ROT_MODE_EUL;
+ }
+ else {
+ fprintf(stderr, "Cannot find pose channel for %s.\n", get_joint_name(joint));
+ }
+
+ break;
+ }
+ }
+ }
+}
+#endif
+
+Object *ArmatureImporter::get_empty_for_leaves()
+{
+ if (empty) {
+ return empty;
+ }
+
+ empty = bc_add_object(m_bmain, scene, view_layer, OB_EMPTY, NULL);
+ empty->empty_drawtype = OB_EMPTY_SPHERE;
+
+ return empty;
+}
+
+#if 0
+Object *ArmatureImporter::find_armature(COLLADAFW::Node *node)
+{
+ JointData *jd = get_joint_data(node);
+ if (jd) {
+ return jd->ob_arm;
+ }
+
+ COLLADAFW::NodePointerArray &children = node->getChildNodes();
+ for (int i = 0; i < children.getCount(); i++) {
+ Object *ob_arm = find_armature(children[i]);
+ if (ob_arm) {
+ return ob_arm;
+ }
+ }
+
+ return NULL;
+}
+
+ArmatureJoints &ArmatureImporter::get_armature_joints(Object *ob_arm)
+{
+ /* try finding it */
+ std::vector<ArmatureJoints>::iterator it;
+ for (it = armature_joints.begin(); it != armature_joints.end(); it++) {
+ if ((*it).ob_arm == ob_arm) {
+ return *it;
+ }
+ }
+
+ /* not found, create one */
+ ArmatureJoints aj;
+ aj.ob_arm = ob_arm;
+ armature_joints.push_back(aj);
+
+ return armature_joints.back();
+}
+#endif
+void ArmatureImporter::create_armature_bones(Main *bmain, std::vector<Object *> &ob_arms)
+{
+ std::vector<COLLADAFW::Node *>::iterator ri;
+ std::vector<std::string> layer_labels;
+
+ /* if there is an armature created for root_joint next root_joint */
+ for (ri = root_joints.begin(); ri != root_joints.end(); ri++) {
+ COLLADAFW::Node *node = *ri;
+ if (get_armature_for_joint(node) != NULL) {
+ continue;
+ }
+
+ Object *ob_arm = joint_parent_map[node->getUniqueId()];
+ if (!ob_arm) {
+ continue;
+ }
+
+ bArmature *armature = (bArmature *)ob_arm->data;
+ if (!armature) {
+ continue;
+ }
+
+ char *bone_name = (char *)bc_get_joint_name(node);
+ Bone *bone = BKE_armature_find_bone_name(armature, bone_name);
+ if (bone) {
+ fprintf(stderr,
+ "Reuse of child bone [%s] as root bone in same Armature is not supported.\n",
+ bone_name);
+ continue;
+ }
+
+ ED_armature_to_edit(armature);
+ armature->layer = 0; // layer is set according to imported bone set in create_bone()
+
+ create_bone(NULL, node, NULL, node->getChildNodes().getCount(), NULL, armature, layer_labels);
+ if (this->import_settings->find_chains) {
+ connect_bone_chains(armature, (Bone *)armature->bonebase.first, UNLIMITED_CHAIN_MAX);
+ }
+
+ /* exit armature edit mode to populate the Armature object */
+ ED_armature_from_edit(bmain, armature);
+ ED_armature_edit_free(armature);
+ ED_armature_to_edit(armature);
+
+ fix_leaf_bone_hierarchy(
+ armature, (Bone *)armature->bonebase.first, this->import_settings->fix_orientation);
+ unskinned_armature_map[node->getUniqueId()] = ob_arm;
+
+ ED_armature_from_edit(bmain, armature);
+ ED_armature_edit_free(armature);
+
+ set_bone_transformation_type(node, ob_arm);
+
+ int index = std::find(ob_arms.begin(), ob_arms.end(), ob_arm) - ob_arms.begin();
+ if (index == 0) {
+ ob_arms.push_back(ob_arm);
+ }
+
+ DEG_id_tag_update(&ob_arm->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+ }
+}
+
+Object *ArmatureImporter::create_armature_bones(Main *bmain, SkinInfo &skin)
+{
+ /* just do like so:
+ * - get armature
+ * - enter editmode
+ * - add edit bones and head/tail properties using matrices and parent-child info
+ * - exit edit mode
+ * - set a sphere shape to leaf bones */
+
+ Object *ob_arm = NULL;
+
+ /*
+ * find if there's another skin sharing at least one bone with this skin
+ * if so, use that skin's armature
+ */
+
+ /**
+ * Pseudocode:
+ *
+ * find_node_in_tree(node, root_joint)
+ *
+ * skin::find_root_joints(root_joints):
+ * std::vector root_joints;
+ * for each root in root_joints:
+ * for each joint in joints:
+ * if find_node_in_tree(joint, root):
+ * if (std::find(root_joints.begin(), root_joints.end(), root) ==
+ * root_joints.end()) root_joints.push_back(root);
+ *
+ * for (each skin B with armature) {
+ * find all root joints for skin B
+ *
+ * for each joint X in skin A:
+ * for each root joint R in skin B:
+ * if (find_node_in_tree(X, R)) {
+ * shared = 1;
+ * goto endloop;
+ * }
+ * }
+ *
+ * endloop:
+ */
+
+ SkinInfo *a = &skin;
+ Object *shared = NULL;
+ std::vector<COLLADAFW::Node *> skin_root_joints;
+ std::vector<std::string> layer_labels;
+
+ std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it;
+ for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) {
+ SkinInfo *b = &it->second;
+ if (b == a || b->BKE_armature_from_object() == NULL) {
+ continue;
+ }
+
+ skin_root_joints.clear();
+
+ b->find_root_joints(root_joints, joint_by_uid, skin_root_joints);
+
+ std::vector<COLLADAFW::Node *>::iterator ri;
+ for (ri = skin_root_joints.begin(); ri != skin_root_joints.end(); ri++) {
+ COLLADAFW::Node *node = *ri;
+ if (a->uses_joint_or_descendant(node)) {
+ shared = b->BKE_armature_from_object();
+ break;
+ }
+ }
+
+ if (shared != NULL) {
+ break;
+ }
+ }
+
+ if (!shared && this->joint_parent_map.size() > 0) {
+ /* All armatures have been created while creating the Node tree.
+ * The Collada exporter currently does not create a
+ * strict relationship between geometries and armatures
+ * So when we reimport a Blender collada file, then we have
+ * to guess what is meant.
+ * XXX This is not safe when we have more than one armatures
+ * in the import. */
+ shared = this->joint_parent_map.begin()->second;
+ }
+
+ if (shared) {
+ ob_arm = skin.set_armature(shared);
+ }
+ else {
+ ob_arm = skin.create_armature(m_bmain, scene, view_layer); // once for every armature
+ }
+
+ /* enter armature edit mode */
+ bArmature *armature = (bArmature *)ob_arm->data;
+ ED_armature_to_edit(armature);
+
+ totbone = 0;
+ // bone_direction_row = 1; // TODO: don't default to Y but use asset and based on it decide on
+ /* default row */
+
+ /* create bones */
+ /* TODO:
+ * check if bones have already been created for a given joint */
+
+ std::vector<COLLADAFW::Node *>::iterator ri;
+ for (ri = root_joints.begin(); ri != root_joints.end(); ri++) {
+ COLLADAFW::Node *node = *ri;
+ /* for shared armature check if bone tree is already created */
+ if (shared && std::find(skin_root_joints.begin(), skin_root_joints.end(), node) !=
+ skin_root_joints.end()) {
+ continue;
+ }
+
+ /* since root_joints may contain joints for multiple controllers, we need to filter */
+ if (skin.uses_joint_or_descendant(node)) {
+
+ create_bone(
+ &skin, node, NULL, node->getChildNodes().getCount(), NULL, armature, layer_labels);
+
+ if (joint_parent_map.find(node->getUniqueId()) != joint_parent_map.end() &&
+ !skin.get_parent()) {
+ skin.set_parent(joint_parent_map[node->getUniqueId()]);
+ }
+ }
+ }
+
+ /* exit armature edit mode to populate the Armature object */
+ ED_armature_from_edit(bmain, armature);
+ ED_armature_edit_free(armature);
+
+ for (ri = root_joints.begin(); ri != root_joints.end(); ri++) {
+ COLLADAFW::Node *node = *ri;
+ set_bone_transformation_type(node, ob_arm);
+ }
+
+ ED_armature_to_edit(armature);
+ if (this->import_settings->find_chains) {
+ connect_bone_chains(armature, (Bone *)armature->bonebase.first, UNLIMITED_CHAIN_MAX);
+ }
+ fix_leaf_bone_hierarchy(
+ armature, (Bone *)armature->bonebase.first, this->import_settings->fix_orientation);
+ ED_armature_from_edit(bmain, armature);
+ ED_armature_edit_free(armature);
+
+ DEG_id_tag_update(&ob_arm->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+
+ return ob_arm;
+}
+
+void ArmatureImporter::set_bone_transformation_type(const COLLADAFW::Node *node, Object *ob_arm)
+{
+ bPoseChannel *pchan = BKE_pose_channel_find_name(ob_arm->pose, bc_get_joint_name(node));
+ if (pchan) {
+ pchan->rotmode = (node_is_decomposed(node)) ? ROT_MODE_EUL : ROT_MODE_QUAT;
+ }
+
+ COLLADAFW::NodePointerArray childnodes = node->getChildNodes();
+ for (int index = 0; index < childnodes.getCount(); index++) {
+ node = childnodes[index];
+ set_bone_transformation_type(node, ob_arm);
+ }
+}
+
+void ArmatureImporter::set_pose(Object *ob_arm,
+ COLLADAFW::Node *root_node,
+ const char *parentname,
+ float parent_mat[4][4])
+{
+ const char *bone_name = bc_get_joint_name(root_node);
+ float mat[4][4];
+ float obmat[4][4];
+
+ /* object-space */
+ get_node_mat(obmat, root_node, NULL, NULL);
+ bool is_decomposed = node_is_decomposed(root_node);
+
+ // if (*edbone)
+ bPoseChannel *pchan = BKE_pose_channel_find_name(ob_arm->pose, bone_name);
+ pchan->rotmode = (is_decomposed) ? ROT_MODE_EUL : ROT_MODE_QUAT;
+
+ // else fprintf ( "",
+
+ /* get world-space */
+ if (parentname) {
+ mul_m4_m4m4(mat, parent_mat, obmat);
+ bPoseChannel *parchan = BKE_pose_channel_find_name(ob_arm->pose, parentname);
+
+ mul_m4_m4m4(pchan->pose_mat, parchan->pose_mat, mat);
+ }
+ else {
+
+ copy_m4_m4(mat, obmat);
+ float invObmat[4][4];
+ invert_m4_m4(invObmat, ob_arm->obmat);
+ mul_m4_m4m4(pchan->pose_mat, invObmat, mat);
+ }
+
+#if 0
+ float angle = 0.0f;
+ mat4_to_axis_angle(ax, &angle, mat);
+ pchan->bone->roll = angle;
+#endif
+
+ COLLADAFW::NodePointerArray &children = root_node->getChildNodes();
+ for (unsigned int i = 0; i < children.getCount(); i++) {
+ set_pose(ob_arm, children[i], bone_name, mat);
+ }
+}
+
+bool ArmatureImporter::node_is_decomposed(const COLLADAFW::Node *node)
+{
+ const COLLADAFW::TransformationPointerArray &nodeTransforms = node->getTransformations();
+ for (unsigned int i = 0; i < nodeTransforms.getCount(); i++) {
+ COLLADAFW::Transformation *transform = nodeTransforms[i];
+ COLLADAFW::Transformation::TransformationType tm_type = transform->getTransformationType();
+ if (tm_type == COLLADAFW::Transformation::MATRIX) {
+ return false;
+ }
+ }
+ return true;
+}
+
+/**
+ * root - if this joint is the top joint in hierarchy, if a joint
+ * is a child of a node (not joint), root should be true since
+ * this is where we build armature bones from
+ */
+void ArmatureImporter::add_root_joint(COLLADAFW::Node *node, Object *parent)
+{
+ root_joints.push_back(node);
+ if (parent) {
+ joint_parent_map[node->getUniqueId()] = parent;
+ }
+}
+
+#if 0
+void ArmatureImporter::add_root_joint(COLLADAFW::Node *node)
+{
+ // root_joints.push_back(node);
+ Object *ob_arm = find_armature(node);
+ if (ob_arm) {
+ get_armature_joints(ob_arm).root_joints.push_back(node);
+ }
+# ifdef COLLADA_DEBUG
+ else {
+ fprintf(stderr, "%s cannot be added to armature.\n", get_joint_name(node));
+ }
+# endif
+}
+#endif
+
+/* here we add bones to armatures, having armatures previously created in write_controller */
+void ArmatureImporter::make_armatures(bContext *C, std::vector<Object *> &objects_to_scale)
+{
+ Main *bmain = CTX_data_main(C);
+ std::vector<Object *> ob_arms;
+ std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it;
+
+ /* TODO: Make this work for more than one armature in the import file. */
+ leaf_bone_length = FLT_MAX;
+
+ for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) {
+
+ SkinInfo &skin = it->second;
+
+ Object *ob_arm = create_armature_bones(bmain, skin);
+
+ /* link armature with a mesh object */
+ const COLLADAFW::UniqueId &uid = skin.get_controller_uid();
+ const COLLADAFW::UniqueId *guid = get_geometry_uid(uid);
+ if (guid != NULL) {
+ Object *ob = mesh_importer->get_object_by_geom_uid(*guid);
+ if (ob) {
+ skin.link_armature(C, ob, joint_by_uid, this);
+
+ std::vector<Object *>::iterator ob_it = std::find(
+ objects_to_scale.begin(), objects_to_scale.end(), ob);
+
+ if (ob_it != objects_to_scale.end()) {
+ int index = ob_it - objects_to_scale.begin();
+ objects_to_scale.erase(objects_to_scale.begin() + index);
+ }
+
+ if (std::find(objects_to_scale.begin(), objects_to_scale.end(), ob_arm) ==
+ objects_to_scale.end()) {
+ objects_to_scale.push_back(ob_arm);
+ }
+
+ if (std::find(ob_arms.begin(), ob_arms.end(), ob_arm) == ob_arms.end()) {
+ ob_arms.push_back(ob_arm);
+ }
+ }
+ else {
+ fprintf(stderr, "Cannot find object to link armature with.\n");
+ }
+ }
+ else {
+ fprintf(stderr, "Cannot find geometry to link armature with.\n");
+ }
+
+ /* set armature parent if any */
+ Object *par = skin.get_parent();
+ if (par) {
+ bc_set_parent(skin.BKE_armature_from_object(), par, C, false);
+ }
+
+ /* free memory stolen from SkinControllerData */
+ skin.free();
+ }
+
+ /* for bones without skins */
+ create_armature_bones(bmain, ob_arms);
+
+ /* Fix bone relations */
+ std::vector<Object *>::iterator ob_arm_it;
+ for (ob_arm_it = ob_arms.begin(); ob_arm_it != ob_arms.end(); ob_arm_it++) {
+
+ Object *ob_arm = *ob_arm_it;
+ bArmature *armature = (bArmature *)ob_arm->data;
+
+ /* and step back to edit mode to fix the leaf nodes */
+ ED_armature_to_edit(armature);
+
+ fix_parent_connect(armature, (Bone *)armature->bonebase.first);
+
+ ED_armature_from_edit(bmain, armature);
+ ED_armature_edit_free(armature);
+ }
+}
+
+#if 0
+/* link with meshes, create vertex groups, assign weights */
+void ArmatureImporter::link_armature(Object *ob_arm,
+ const COLLADAFW::UniqueId &geom_id,
+ const COLLADAFW::UniqueId &controller_data_id)
+{
+ Object *ob = mesh_importer->get_object_by_geom_uid(geom_id);
+
+ if (!ob) {
+ fprintf(stderr, "Cannot find object by geometry UID.\n");
+ return;
+ }
+
+ if (skin_by_data_uid.find(controller_data_id) == skin_by_data_uid.end()) {
+ fprintf(stderr, "Cannot find skin info by controller data UID.\n");
+ return;
+ }
+
+ SkinInfo &skin = skin_by_data_uid[conroller_data_id];
+
+ /* create vertex groups */
+}
+#endif
+
+bool ArmatureImporter::write_skin_controller_data(const COLLADAFW::SkinControllerData *data)
+{
+ /* at this stage we get vertex influence info that should go into me->verts and ob->defbase
+ * there's no info to which object this should be long so we associate it with
+ * skin controller data UID. */
+
+ /* don't forget to call BKE_object_defgroup_unique_name before we copy */
+
+ /* controller data uid -> [armature] -> joint data,
+ * [mesh object] */
+
+ SkinInfo skin(unit_converter);
+ skin.borrow_skin_controller_data(data);
+
+ /* store join inv bind matrix to use it later in armature construction */
+ const COLLADAFW::Matrix4Array &inv_bind_mats = data->getInverseBindMatrices();
+ for (unsigned int i = 0; i < data->getJointsCount(); i++) {
+ skin.add_joint(inv_bind_mats[i]);
+ }
+
+ skin_by_data_uid[data->getUniqueId()] = skin;
+
+ return true;
+}
+
+bool ArmatureImporter::write_controller(const COLLADAFW::Controller *controller)
+{
+ /* - create and store armature object */
+ const COLLADAFW::UniqueId &con_id = controller->getUniqueId();
+
+ if (controller->getControllerType() == COLLADAFW::Controller::CONTROLLER_TYPE_SKIN) {
+ COLLADAFW::SkinController *co = (COLLADAFW::SkinController *)controller;
+ /* to be able to find geom id by controller id */
+ geom_uid_by_controller_uid[con_id] = co->getSource();
+
+ const COLLADAFW::UniqueId &data_uid = co->getSkinControllerData();
+ if (skin_by_data_uid.find(data_uid) == skin_by_data_uid.end()) {
+ fprintf(stderr, "Cannot find skin by controller data UID.\n");
+ return true;
+ }
+
+ skin_by_data_uid[data_uid].set_controller(co);
+ }
+ /* morph controller */
+ else if (controller->getControllerType() == COLLADAFW::Controller::CONTROLLER_TYPE_MORPH) {
+ COLLADAFW::MorphController *co = (COLLADAFW::MorphController *)controller;
+ /* to be able to find geom id by controller id */
+ geom_uid_by_controller_uid[con_id] = co->getSource();
+ /* Shape keys are applied in DocumentImporter->finish() */
+ morph_controllers.push_back(co);
+ }
+
+ return true;
+}
+
+void ArmatureImporter::make_shape_keys(bContext *C)
+{
+ Main *bmain = CTX_data_main(C);
+ std::vector<COLLADAFW::MorphController *>::iterator mc;
+ float weight;
+
+ for (mc = morph_controllers.begin(); mc != morph_controllers.end(); mc++) {
+ /* Controller data */
+ COLLADAFW::UniqueIdArray &morphTargetIds = (*mc)->getMorphTargets();
+ COLLADAFW::FloatOrDoubleArray &morphWeights = (*mc)->getMorphWeights();
+
+ /* Prereq: all the geometries must be imported and mesh objects must be made */
+ Object *source_ob = this->mesh_importer->get_object_by_geom_uid((*mc)->getSource());
+
+ if (source_ob) {
+
+ Mesh *source_me = (Mesh *)source_ob->data;
+ /* insert key to source mesh */
+ Key *key = source_me->key = BKE_key_add(bmain, (ID *)source_me);
+ key->type = KEY_RELATIVE;
+ KeyBlock *kb;
+
+ /* insert basis key */
+ kb = BKE_keyblock_add_ctime(key, "Basis", false);
+ BKE_keyblock_convert_from_mesh(source_me, key, kb);
+
+ /* insert other shape keys */
+ for (int i = 0; i < morphTargetIds.getCount(); i++) {
+ /* better to have a separate map of morph objects,
+ * This'll do for now since only mesh morphing is imported */
+
+ Mesh *me = this->mesh_importer->get_mesh_by_geom_uid(morphTargetIds[i]);
+
+ if (me) {
+ me->key = key;
+ std::string morph_name = *this->mesh_importer->get_geometry_name(me->id.name);
+
+ kb = BKE_keyblock_add_ctime(key, morph_name.c_str(), false);
+ BKE_keyblock_convert_from_mesh(me, key, kb);
+
+ /* apply weights */
+ weight = morphWeights.getFloatValues()->getData()[i];
+ kb->curval = weight;
+ }
+ else {
+ fprintf(stderr, "Morph target geometry not found.\n");
+ }
+ }
+ }
+ else {
+ fprintf(stderr, "Morph target object not found.\n");
+ }
+ }
+}
+
+COLLADAFW::UniqueId *ArmatureImporter::get_geometry_uid(const COLLADAFW::UniqueId &controller_uid)
+{
+ if (geom_uid_by_controller_uid.find(controller_uid) == geom_uid_by_controller_uid.end()) {
+ return NULL;
+ }
+
+ return &geom_uid_by_controller_uid[controller_uid];
+}
+
+Object *ArmatureImporter::get_armature_for_joint(COLLADAFW::Node *node)
+{
+ std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it;
+ for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) {
+ SkinInfo &skin = it->second;
+
+ if (skin.uses_joint_or_descendant(node)) {
+ return skin.BKE_armature_from_object();
+ }
+ }
+
+ std::map<COLLADAFW::UniqueId, Object *>::iterator arm;
+ for (arm = unskinned_armature_map.begin(); arm != unskinned_armature_map.end(); arm++) {
+ if (arm->first == node->getUniqueId()) {
+ return arm->second;
+ }
+ }
+ return NULL;
+}
+
+void ArmatureImporter::set_tags_map(TagsMap &tagsMap)
+{
+ this->uid_tags_map = tagsMap;
+}
+
+void ArmatureImporter::get_rna_path_for_joint(COLLADAFW::Node *node,
+ char *joint_path,
+ size_t count)
+{
+ BLI_snprintf(joint_path, count, "pose.bones[\"%s\"]", bc_get_joint_name(node));
+}
+
+/* gives a world-space mat */
+bool ArmatureImporter::get_joint_bind_mat(float m[4][4], COLLADAFW::Node *joint)
+{
+ std::map<COLLADAFW::UniqueId, SkinInfo>::iterator it;
+ bool found = false;
+ for (it = skin_by_data_uid.begin(); it != skin_by_data_uid.end(); it++) {
+ SkinInfo &skin = it->second;
+ if ((found = skin.get_joint_inv_bind_matrix(m, joint))) {
+ invert_m4(m);
+ break;
+ }
+ }
+
+ return found;
+}
+
+BoneExtended &ArmatureImporter::add_bone_extended(EditBone *bone,
+ COLLADAFW::Node *node,
+ int sibcount,
+ std::vector<std::string> &layer_labels,
+ BoneExtensionMap &extended_bones)
+{
+ BoneExtended *be = new BoneExtended(bone);
+ extended_bones[bone->name] = be;
+
+ TagsMap::iterator etit;
+ ExtraTags *et = 0;
+ etit = uid_tags_map.find(node->getUniqueId().toAscii());
+
+ bool has_connect = false;
+ int connect_type = -1;
+
+ if (etit != uid_tags_map.end()) {
+
+ float tail[3] = {FLT_MAX, FLT_MAX, FLT_MAX};
+ float roll = 0;
+ std::string layers;
+
+ et = etit->second;
+
+ bool has_tail = false;
+ has_tail |= et->setData("tip_x", &tail[0]);
+ has_tail |= et->setData("tip_y", &tail[1]);
+ has_tail |= et->setData("tip_z", &tail[2]);
+
+ has_connect = et->setData("connect", &connect_type);
+ bool has_roll = et->setData("roll", &roll);
+
+ layers = et->setData("layer", layers);
+
+ if (has_tail && !has_connect) {
+ /* got a bone tail definition but no connect info -> bone is not connected */
+ has_connect = true;
+ connect_type = 0;
+ }
+
+ be->set_bone_layers(layers, layer_labels);
+ if (has_tail) {
+ be->set_tail(tail);
+ }
+ if (has_roll) {
+ be->set_roll(roll);
+ }
+ }
+
+ if (!has_connect && this->import_settings->auto_connect) {
+ /* auto connect only whyen parent has exactly one child*/
+ connect_type = sibcount == 1;
+ }
+
+ be->set_use_connect(connect_type);
+ be->set_leaf_bone(true);
+
+ return *be;
+}
diff --git a/source/blender/io/collada/ArmatureImporter.h b/source/blender/io/collada/ArmatureImporter.h
new file mode 100644
index 00000000000..da92c04e5dc
--- /dev/null
+++ b/source/blender/io/collada/ArmatureImporter.h
@@ -0,0 +1,187 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __ARMATUREIMPORTER_H__
+#define __ARMATUREIMPORTER_H__
+
+#include "COLLADAFWNode.h"
+#include "COLLADAFWUniqueId.h"
+#include "COLLADAFWMorphController.h"
+
+extern "C" {
+#include "BKE_context.h"
+#include "BKE_key.h"
+
+#include "DNA_armature_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_key_types.h"
+
+#include "ED_armature.h"
+}
+
+#include "AnimationImporter.h"
+#include "MeshImporter.h"
+#include "SkinInfo.h"
+#include "TransformReader.h"
+#include "ExtraTags.h"
+
+#include <map>
+#include <vector>
+
+#include "collada_internal.h"
+#include "collada_utils.h"
+#include "ImportSettings.h"
+
+#define UNLIMITED_CHAIN_MAX INT_MAX
+#define MINIMUM_BONE_LENGTH 0.000001f
+
+class ArmatureImporter : private TransformReader {
+ private:
+ Main *m_bmain;
+ Scene *scene;
+ ViewLayer *view_layer;
+ UnitConverter *unit_converter;
+ const ImportSettings *import_settings;
+
+ // std::map<int, JointData> joint_index_to_joint_info_map;
+ // std::map<COLLADAFW::UniqueId, int> joint_id_to_joint_index_map;
+ BoneExtensionManager bone_extension_manager;
+ // int bone_direction_row; // XXX not used
+ float leaf_bone_length;
+ int totbone;
+ // XXX not used
+ // float min_angle; // minimum angle between bone head-tail and a row of bone matrix
+
+#if 0
+ struct ArmatureJoints {
+ Object *ob_arm;
+ std::vector<COLLADAFW::Node *> root_joints;
+ };
+ std::vector<ArmatureJoints> armature_joints;
+#endif
+
+ Object *empty; // empty for leaf bones
+
+ std::map<COLLADAFW::UniqueId, COLLADAFW::UniqueId> geom_uid_by_controller_uid;
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> joint_by_uid; // contains all joints
+ std::vector<COLLADAFW::Node *> root_joints;
+ std::vector<COLLADAFW::Node *> finished_joints;
+ std::vector<COLLADAFW::MorphController *> morph_controllers;
+ std::map<COLLADAFW::UniqueId, Object *> joint_parent_map;
+ std::map<COLLADAFW::UniqueId, Object *> unskinned_armature_map;
+
+ MeshImporterBase *mesh_importer;
+
+ // This is used to store data passed in write_controller_data.
+ // Arrays from COLLADAFW::SkinControllerData lose ownership, so do this class members
+ // so that arrays don't get freed until we free them explicitly.
+
+ std::map<COLLADAFW::UniqueId, SkinInfo> skin_by_data_uid; // data UID = skin controller data UID
+#if 0
+ JointData *get_joint_data(COLLADAFW::Node *node);
+#endif
+
+ int create_bone(SkinInfo *skin,
+ COLLADAFW::Node *node,
+ EditBone *parent,
+ int totchild,
+ float parent_mat[4][4],
+ bArmature *arm,
+ std::vector<std::string> &layer_labels);
+
+ BoneExtended &add_bone_extended(EditBone *bone,
+ COLLADAFW::Node *node,
+ int sibcount,
+ std::vector<std::string> &layer_labels,
+ BoneExtensionMap &extended_bones);
+
+ void fix_leaf_bone_hierarchy(bArmature *armature, Bone *bone, bool fix_orientation);
+ void fix_leaf_bone(bArmature *armature, EditBone *ebone, BoneExtended *be, bool fix_orientation);
+ void fix_parent_connect(bArmature *armature, Bone *bone);
+ void connect_bone_chains(bArmature *armature, Bone *bone, const int max_chain_length);
+
+ void set_pose(Object *ob_arm,
+ COLLADAFW::Node *root_node,
+ const char *parentname,
+ float parent_mat[4][4]);
+
+ void set_bone_transformation_type(const COLLADAFW::Node *node, Object *ob_arm);
+ bool node_is_decomposed(const COLLADAFW::Node *node);
+#if 0
+ void set_leaf_bone_shapes(Object *ob_arm);
+ void set_euler_rotmode();
+#endif
+
+ Object *get_empty_for_leaves();
+
+#if 0
+ Object *find_armature(COLLADAFW::Node *node);
+
+ ArmatureJoints &get_armature_joints(Object *ob_arm);
+#endif
+
+ Object *create_armature_bones(Main *bmain, SkinInfo &skin);
+ void create_armature_bones(Main *bmain, std::vector<Object *> &arm_objs);
+
+ /** TagsMap typedef for uid_tags_map. */
+ typedef std::map<std::string, ExtraTags *> TagsMap;
+ TagsMap uid_tags_map;
+
+ public:
+ ArmatureImporter(UnitConverter *conv,
+ MeshImporterBase *mesh,
+ Main *bmain,
+ Scene *sce,
+ ViewLayer *view_layer,
+ const ImportSettings *import_settings);
+ ~ArmatureImporter();
+
+ void add_root_joint(COLLADAFW::Node *node, Object *parent);
+
+ // here we add bones to armatures, having armatures previously created in write_controller
+ void make_armatures(bContext *C, std::vector<Object *> &objects_to_scale);
+
+ void make_shape_keys(bContext *C);
+
+#if 0
+ // link with meshes, create vertex groups, assign weights
+ void link_armature(Object *ob_arm,
+ const COLLADAFW::UniqueId &geom_id,
+ const COLLADAFW::UniqueId &controller_data_id);
+#endif
+
+ bool write_skin_controller_data(const COLLADAFW::SkinControllerData *data);
+
+ bool write_controller(const COLLADAFW::Controller *controller);
+
+ COLLADAFW::UniqueId *get_geometry_uid(const COLLADAFW::UniqueId &controller_uid);
+
+ Object *get_armature_for_joint(COLLADAFW::Node *node);
+
+ void get_rna_path_for_joint(COLLADAFW::Node *node, char *joint_path, size_t count);
+
+ // gives a world-space mat
+ bool get_joint_bind_mat(float m[4][4], COLLADAFW::Node *joint);
+
+ void set_tags_map(TagsMap &tags_map);
+};
+
+#endif
diff --git a/source/blender/io/collada/BCAnimationCurve.cpp b/source/blender/io/collada/BCAnimationCurve.cpp
new file mode 100644
index 00000000000..36800d611d2
--- /dev/null
+++ b/source/blender/io/collada/BCAnimationCurve.cpp
@@ -0,0 +1,691 @@
+/*
+ * 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) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+#include "BCAnimationCurve.h"
+
+BCAnimationCurve::BCAnimationCurve()
+{
+ this->curve_key.set_object_type(BC_ANIMATION_TYPE_OBJECT);
+ this->fcurve = NULL;
+ this->curve_is_local_copy = false;
+}
+
+BCAnimationCurve::BCAnimationCurve(const BCAnimationCurve &other)
+{
+ this->min = other.min;
+ this->max = other.max;
+ this->fcurve = other.fcurve;
+ this->curve_key = other.curve_key;
+ this->curve_is_local_copy = false;
+ this->id_ptr = other.id_ptr;
+
+ /* The fcurve of the new instance is a copy and can be modified */
+
+ get_edit_fcurve();
+}
+
+BCAnimationCurve::BCAnimationCurve(BCCurveKey key, Object *ob, FCurve *fcu)
+{
+ this->min = 0;
+ this->max = 0;
+ this->curve_key = key;
+ this->fcurve = fcu;
+ this->curve_is_local_copy = false;
+ init_pointer_rna(ob);
+}
+
+BCAnimationCurve::BCAnimationCurve(const BCCurveKey &key, Object *ob)
+{
+ this->curve_key = key;
+ this->fcurve = NULL;
+ this->curve_is_local_copy = false;
+ init_pointer_rna(ob);
+}
+
+void BCAnimationCurve::init_pointer_rna(Object *ob)
+{
+ switch (this->curve_key.get_animation_type()) {
+ case BC_ANIMATION_TYPE_BONE: {
+ bArmature *arm = (bArmature *)ob->data;
+ RNA_id_pointer_create(&arm->id, &id_ptr);
+ } break;
+ case BC_ANIMATION_TYPE_OBJECT: {
+ RNA_id_pointer_create(&ob->id, &id_ptr);
+ } break;
+ case BC_ANIMATION_TYPE_MATERIAL: {
+ Material *ma = BKE_object_material_get(ob, curve_key.get_subindex() + 1);
+ RNA_id_pointer_create(&ma->id, &id_ptr);
+ } break;
+ case BC_ANIMATION_TYPE_CAMERA: {
+ Camera *camera = (Camera *)ob->data;
+ RNA_id_pointer_create(&camera->id, &id_ptr);
+ } break;
+ case BC_ANIMATION_TYPE_LIGHT: {
+ Light *lamp = (Light *)ob->data;
+ RNA_id_pointer_create(&lamp->id, &id_ptr);
+ } break;
+ default:
+ fprintf(
+ stderr, "BC_animation_curve_type %d not supported", this->curve_key.get_array_index());
+ break;
+ }
+}
+
+void BCAnimationCurve::delete_fcurve(FCurve *fcu)
+{
+ free_fcurve(fcu);
+}
+
+FCurve *BCAnimationCurve::create_fcurve(int array_index, const char *rna_path)
+{
+ FCurve *fcu = (FCurve *)MEM_callocN(sizeof(FCurve), "FCurve");
+ fcu->flag = (FCURVE_VISIBLE | FCURVE_AUTO_HANDLES | FCURVE_SELECTED);
+ fcu->rna_path = BLI_strdupn(rna_path, strlen(rna_path));
+ fcu->array_index = array_index;
+ return fcu;
+}
+
+void BCAnimationCurve::create_bezt(float frame, float output)
+{
+ FCurve *fcu = get_edit_fcurve();
+ BezTriple bez;
+ memset(&bez, 0, sizeof(BezTriple));
+ bez.vec[1][0] = frame;
+ bez.vec[1][1] = output;
+ bez.ipo = U.ipo_new; /* use default interpolation mode here... */
+ bez.f1 = bez.f2 = bez.f3 = SELECT;
+ bez.h1 = bez.h2 = HD_AUTO;
+ insert_bezt_fcurve(fcu, &bez, INSERTKEY_NOFLAGS);
+ calchandles_fcurve(fcu);
+}
+
+BCAnimationCurve::~BCAnimationCurve()
+{
+ if (curve_is_local_copy && fcurve) {
+ // fprintf(stderr, "removed fcurve %s\n", fcurve->rna_path);
+ delete_fcurve(fcurve);
+ this->fcurve = NULL;
+ }
+}
+
+const bool BCAnimationCurve::is_of_animation_type(BC_animation_type type) const
+{
+ return curve_key.get_animation_type() == type;
+}
+
+const std::string BCAnimationCurve::get_channel_target() const
+{
+ const std::string path = curve_key.get_path();
+
+ if (bc_startswith(path, "pose.bones")) {
+ return bc_string_after(path, "pose.bones");
+ }
+ return bc_string_after(path, ".");
+}
+
+const std::string BCAnimationCurve::get_channel_type() const
+{
+ const std::string channel = get_channel_target();
+ return bc_string_after(channel, ".");
+}
+
+const std::string BCAnimationCurve::get_channel_posebone() const
+{
+ const std::string channel = get_channel_target();
+ std::string pose_bone_name = bc_string_before(channel, ".");
+ if (pose_bone_name == channel) {
+ pose_bone_name = "";
+ }
+ else {
+ pose_bone_name = bc_string_after(pose_bone_name, "\"[");
+ pose_bone_name = bc_string_before(pose_bone_name, "]\"");
+ }
+ return pose_bone_name;
+}
+
+const std::string BCAnimationCurve::get_animation_name(Object *ob) const
+{
+ std::string name;
+
+ switch (curve_key.get_animation_type()) {
+ case BC_ANIMATION_TYPE_OBJECT: {
+ name = id_name(ob);
+ } break;
+
+ case BC_ANIMATION_TYPE_BONE: {
+ if (fcurve == NULL || fcurve->rna_path == NULL) {
+ name = "";
+ }
+ else {
+ const char *boneName = BLI_str_quoted_substrN(fcurve->rna_path, "pose.bones[");
+ name = (boneName) ? id_name(ob) + "_" + std::string(boneName) : "";
+ }
+ } break;
+
+ case BC_ANIMATION_TYPE_CAMERA: {
+ Camera *camera = (Camera *)ob->data;
+ name = id_name(ob) + "-" + id_name(camera) + "-camera";
+ } break;
+
+ case BC_ANIMATION_TYPE_LIGHT: {
+ Light *lamp = (Light *)ob->data;
+ name = id_name(ob) + "-" + id_name(lamp) + "-light";
+ } break;
+
+ case BC_ANIMATION_TYPE_MATERIAL: {
+ Material *ma = BKE_object_material_get(ob, this->curve_key.get_subindex() + 1);
+ name = id_name(ob) + "-" + id_name(ma) + "-material";
+ } break;
+
+ default: {
+ name = "";
+ }
+ }
+
+ return name;
+}
+
+const int BCAnimationCurve::get_channel_index() const
+{
+ return curve_key.get_array_index();
+}
+
+const int BCAnimationCurve::get_subindex() const
+{
+ return curve_key.get_subindex();
+}
+
+const std::string BCAnimationCurve::get_rna_path() const
+{
+ return curve_key.get_path();
+}
+
+const int BCAnimationCurve::sample_count() const
+{
+ if (fcurve == NULL) {
+ return 0;
+ }
+ return fcurve->totvert;
+}
+
+const int BCAnimationCurve::closest_index_above(const float sample_frame, const int start_at) const
+{
+ if (fcurve == NULL) {
+ return -1;
+ }
+
+ const int cframe = fcurve->bezt[start_at].vec[1][0]; // inacurate!
+
+ if (fabs(cframe - sample_frame) < 0.00001) {
+ return start_at;
+ }
+ return (fcurve->totvert > start_at + 1) ? start_at + 1 : start_at;
+}
+
+const int BCAnimationCurve::closest_index_below(const float sample_frame) const
+{
+ if (fcurve == NULL) {
+ return -1;
+ }
+
+ float lower_frame = sample_frame;
+ float upper_frame = sample_frame;
+ int lower_index = 0;
+ int upper_index = 0;
+
+ for (int fcu_index = 0; fcu_index < fcurve->totvert; fcu_index++) {
+ upper_index = fcu_index;
+
+ const int cframe = fcurve->bezt[fcu_index].vec[1][0]; // inacurate!
+ if (cframe <= sample_frame) {
+ lower_frame = cframe;
+ lower_index = fcu_index;
+ }
+ if (cframe >= sample_frame) {
+ upper_frame = cframe;
+ break;
+ }
+ }
+
+ if (lower_index == upper_index) {
+ return lower_index;
+ }
+
+ const float fraction = float(sample_frame - lower_frame) / (upper_frame - lower_frame);
+ return (fraction < 0.5) ? lower_index : upper_index;
+}
+
+const int BCAnimationCurve::get_interpolation_type(float sample_frame) const
+{
+ const int index = closest_index_below(sample_frame);
+ if (index < 0) {
+ return BEZT_IPO_BEZ;
+ }
+ return fcurve->bezt[index].ipo;
+}
+
+const FCurve *BCAnimationCurve::get_fcurve() const
+{
+ return fcurve;
+}
+
+FCurve *BCAnimationCurve::get_edit_fcurve()
+{
+ if (!curve_is_local_copy) {
+ const int index = curve_key.get_array_index();
+ const std::string &path = curve_key.get_path();
+ fcurve = create_fcurve(index, path.c_str());
+
+ /* Caution here:
+ * Replacing the pointer here is OK only because the original value
+ * of FCurve was a const pointer into Blender territory. We do not
+ * touch that! We use the local copy to prepare data for export. */
+
+ curve_is_local_copy = true;
+ }
+ return fcurve;
+}
+
+void BCAnimationCurve::clean_handles()
+{
+ if (fcurve == NULL) {
+ fcurve = get_edit_fcurve();
+ }
+
+ /* Keep old bezt data for copy)*/
+ BezTriple *old_bezts = fcurve->bezt;
+ int totvert = fcurve->totvert;
+ fcurve->bezt = NULL;
+ fcurve->totvert = 0;
+
+ for (int i = 0; i < totvert; i++) {
+ BezTriple *bezt = &old_bezts[i];
+ float x = bezt->vec[1][0];
+ float y = bezt->vec[1][1];
+ insert_vert_fcurve(fcurve, x, y, (eBezTriple_KeyframeType)BEZKEYTYPE(bezt), INSERTKEY_NOFLAGS);
+ BezTriple *lastb = fcurve->bezt + (fcurve->totvert - 1);
+ lastb->f1 = lastb->f2 = lastb->f3 = 0;
+ }
+
+ /* now free the memory used by the old BezTriples */
+ if (old_bezts) {
+ MEM_freeN(old_bezts);
+ }
+}
+
+const bool BCAnimationCurve::is_transform_curve() const
+{
+ std::string channel_type = this->get_channel_type();
+ return (is_rotation_curve() || channel_type == "scale" || channel_type == "location");
+}
+
+const bool BCAnimationCurve::is_rotation_curve() const
+{
+ std::string channel_type = this->get_channel_type();
+ return (channel_type == "rotation" || channel_type == "rotation_euler" ||
+ channel_type == "rotation_quaternion");
+}
+
+const float BCAnimationCurve::get_value(const float frame)
+{
+ if (fcurve) {
+ return evaluate_fcurve(fcurve, frame);
+ }
+ return 0; // TODO: handle case where neither sample nor fcu exist
+}
+
+void BCAnimationCurve::update_range(float val)
+{
+ if (val < min) {
+ min = val;
+ }
+ if (val > max) {
+ max = val;
+ }
+}
+
+void BCAnimationCurve::init_range(float val)
+{
+ min = max = val;
+}
+
+void BCAnimationCurve::adjust_range(const int frame_index)
+{
+ if (fcurve && fcurve->totvert > 1) {
+ const float eval = evaluate_fcurve(fcurve, frame_index);
+
+ int first_frame = fcurve->bezt[0].vec[1][0];
+ if (first_frame == frame_index) {
+ init_range(eval);
+ }
+ else {
+ update_range(eval);
+ }
+ }
+}
+
+void BCAnimationCurve::add_value(const float val, const int frame_index)
+{
+ FCurve *fcu = get_edit_fcurve();
+ fcu->auto_smoothing = U.auto_smoothing_new;
+ insert_vert_fcurve(fcu, frame_index, val, BEZT_KEYTYPE_KEYFRAME, INSERTKEY_NOFLAGS);
+
+ if (fcu->totvert == 1) {
+ init_range(val);
+ }
+ else {
+ update_range(val);
+ }
+}
+
+bool BCAnimationCurve::add_value_from_matrix(const BCSample &sample, const int frame_index)
+{
+ int array_index = curve_key.get_array_index();
+
+ /* transformation curves are fed directly from the transformation matrix
+ * to resolve parent inverse matrix issues with object hierarchies.
+ * Maybe this can be unified with the
+ */
+ const std::string channel_target = get_channel_target();
+ float val = 0;
+ /* Pick the value from the sample according to the definition of the FCurve */
+ bool good = sample.get_value(channel_target, array_index, &val);
+ if (good) {
+ add_value(val, frame_index);
+ }
+ return good;
+}
+
+bool BCAnimationCurve::add_value_from_rna(const int frame_index)
+{
+ PointerRNA ptr;
+ PropertyRNA *prop;
+ float value = 0.0f;
+ int array_index = curve_key.get_array_index();
+ const std::string full_path = curve_key.get_full_path();
+
+ /* get property to read from, and get value as appropriate */
+ bool path_resolved = RNA_path_resolve_full(
+ &id_ptr, full_path.c_str(), &ptr, &prop, &array_index);
+ if (!path_resolved && array_index == 0) {
+ const std::string rna_path = curve_key.get_path();
+ path_resolved = RNA_path_resolve_full(&id_ptr, rna_path.c_str(), &ptr, &prop, &array_index);
+ }
+
+ if (path_resolved) {
+ bool is_array = RNA_property_array_check(prop);
+ if (is_array) {
+ /* array */
+ if ((array_index >= 0) && (array_index < RNA_property_array_length(&ptr, prop))) {
+ switch (RNA_property_type(prop)) {
+ case PROP_BOOLEAN:
+ value = (float)RNA_property_boolean_get_index(&ptr, prop, array_index);
+ break;
+ case PROP_INT:
+ value = (float)RNA_property_int_get_index(&ptr, prop, array_index);
+ break;
+ case PROP_FLOAT:
+ value = RNA_property_float_get_index(&ptr, prop, array_index);
+ break;
+ default:
+ break;
+ }
+ }
+ else {
+ fprintf(stderr,
+ "Out of Bounds while reading data for Curve %s\n",
+ curve_key.get_full_path().c_str());
+ return false;
+ }
+ }
+ else {
+ /* not an array */
+ switch (RNA_property_type(prop)) {
+ case PROP_BOOLEAN:
+ value = (float)RNA_property_boolean_get(&ptr, prop);
+ break;
+ case PROP_INT:
+ value = (float)RNA_property_int_get(&ptr, prop);
+ break;
+ case PROP_FLOAT:
+ value = RNA_property_float_get(&ptr, prop);
+ break;
+ case PROP_ENUM:
+ value = (float)RNA_property_enum_get(&ptr, prop);
+ break;
+ default:
+ fprintf(stderr,
+ "property type %d not supported for Curve %s\n",
+ RNA_property_type(prop),
+ curve_key.get_full_path().c_str());
+ return false;
+ break;
+ }
+ }
+ }
+ else {
+ /* path couldn't be resolved */
+ fprintf(stderr, "Path not recognized for Curve %s\n", curve_key.get_full_path().c_str());
+ return false;
+ }
+
+ add_value(value, frame_index);
+ return true;
+}
+
+void BCAnimationCurve::get_value_map(BCValueMap &value_map)
+{
+ value_map.clear();
+ if (fcurve == NULL) {
+ return;
+ }
+
+ for (int i = 0; i < fcurve->totvert; i++) {
+ const float frame = fcurve->bezt[i].vec[1][0];
+ const float val = fcurve->bezt[i].vec[1][1];
+ value_map[frame] = val;
+ }
+}
+
+void BCAnimationCurve::get_frames(BCFrames &frames) const
+{
+ frames.clear();
+ if (fcurve) {
+ for (int i = 0; i < fcurve->totvert; i++) {
+ const float val = fcurve->bezt[i].vec[1][0];
+ frames.push_back(val);
+ }
+ }
+}
+
+void BCAnimationCurve::get_values(BCValues &values) const
+{
+ values.clear();
+ if (fcurve) {
+ for (int i = 0; i < fcurve->totvert; i++) {
+ const float val = fcurve->bezt[i].vec[1][1];
+ values.push_back(val);
+ }
+ }
+}
+
+bool BCAnimationCurve::is_animated()
+{
+ static float MIN_DISTANCE = 0.00001;
+ return fabs(max - min) > MIN_DISTANCE;
+}
+
+bool BCAnimationCurve::is_keyframe(int frame)
+{
+ if (this->fcurve == NULL) {
+ return false;
+ }
+
+ for (int i = 0; i < fcurve->totvert; i++) {
+ const int cframe = nearbyint(fcurve->bezt[i].vec[1][0]);
+ if (cframe == frame) {
+ return true;
+ }
+ if (cframe > frame) {
+ break;
+ }
+ }
+ return false;
+}
+
+/* Needed for adding a BCAnimationCurve into a BCAnimationCurveSet */
+inline bool operator<(const BCAnimationCurve &lhs, const BCAnimationCurve &rhs)
+{
+ std::string lhtgt = lhs.get_channel_target();
+ std::string rhtgt = rhs.get_channel_target();
+ if (lhtgt == rhtgt) {
+ const int lha = lhs.get_channel_index();
+ const int rha = rhs.get_channel_index();
+ return lha < rha;
+ }
+ else {
+ return lhtgt < rhtgt;
+ }
+}
+
+BCCurveKey::BCCurveKey()
+{
+ this->key_type = BC_ANIMATION_TYPE_OBJECT;
+ this->rna_path = "";
+ this->curve_array_index = 0;
+ this->curve_subindex = -1;
+}
+
+BCCurveKey::BCCurveKey(const BC_animation_type type,
+ const std::string path,
+ const int array_index,
+ const int subindex)
+{
+ this->key_type = type;
+ this->rna_path = path;
+ this->curve_array_index = array_index;
+ this->curve_subindex = subindex;
+}
+
+void BCCurveKey::operator=(const BCCurveKey &other)
+{
+ this->key_type = other.key_type;
+ this->rna_path = other.rna_path;
+ this->curve_array_index = other.curve_array_index;
+ this->curve_subindex = other.curve_subindex;
+}
+
+const std::string BCCurveKey::get_full_path() const
+{
+ return this->rna_path + '[' + std::to_string(this->curve_array_index) + ']';
+}
+
+const std::string BCCurveKey::get_path() const
+{
+ return this->rna_path;
+}
+
+const int BCCurveKey::get_array_index() const
+{
+ return this->curve_array_index;
+}
+
+const int BCCurveKey::get_subindex() const
+{
+ return this->curve_subindex;
+}
+
+void BCCurveKey::set_object_type(BC_animation_type object_type)
+{
+ this->key_type = object_type;
+}
+
+const BC_animation_type BCCurveKey::get_animation_type() const
+{
+ return this->key_type;
+}
+
+const bool BCCurveKey::operator<(const BCCurveKey &other) const
+{
+ /* needed for using this class as key in maps and sets */
+ if (this->key_type != other.key_type) {
+ return this->key_type < other.key_type;
+ }
+
+ if (this->curve_subindex != other.curve_subindex) {
+ return this->curve_subindex < other.curve_subindex;
+ }
+
+ if (this->rna_path != other.rna_path) {
+ return this->rna_path < other.rna_path;
+ }
+
+ return this->curve_array_index < other.curve_array_index;
+}
+
+BCBezTriple::BCBezTriple(BezTriple &bezt) : bezt(bezt)
+{
+}
+
+const float BCBezTriple::get_frame() const
+{
+ return bezt.vec[1][0];
+}
+
+const float BCBezTriple::get_time(Scene *scene) const
+{
+ return FRA2TIME(bezt.vec[1][0]);
+}
+
+const float BCBezTriple::get_value() const
+{
+ return bezt.vec[1][1];
+}
+
+const float BCBezTriple::get_angle() const
+{
+ return RAD2DEGF(get_value());
+}
+
+void BCBezTriple::get_in_tangent(Scene *scene, float point[2], bool as_angle) const
+{
+ get_tangent(scene, point, as_angle, 0);
+}
+
+void BCBezTriple::get_out_tangent(Scene *scene, float point[2], bool as_angle) const
+{
+ get_tangent(scene, point, as_angle, 2);
+}
+
+void BCBezTriple::get_tangent(Scene *scene, float point[2], bool as_angle, int index) const
+{
+ point[0] = FRA2TIME(bezt.vec[index][0]);
+ if (bezt.ipo != BEZT_IPO_BEZ) {
+ /* We're in a mixed interpolation scenario, set zero as it's irrelevant but value might contain
+ * unused data */
+ point[0] = 0;
+ point[1] = 0;
+ }
+ else if (as_angle) {
+ point[1] = RAD2DEGF(bezt.vec[index][1]);
+ }
+ else {
+ point[1] = bezt.vec[index][1];
+ }
+}
diff --git a/source/blender/io/collada/BCAnimationCurve.h b/source/blender/io/collada/BCAnimationCurve.h
new file mode 100644
index 00000000000..7b523ac53ca
--- /dev/null
+++ b/source/blender/io/collada/BCAnimationCurve.h
@@ -0,0 +1,152 @@
+/*
+ * 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) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+#ifndef __BCANIMATIONCURVE_H__
+#define __BCANIMATIONCURVE_H__
+
+#include "collada_utils.h"
+#include "BCSampleData.h"
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "BKE_fcurve.h"
+#include "BKE_armature.h"
+#include "BKE_material.h"
+#include "ED_anim_api.h"
+#include "ED_keyframing.h"
+#include "ED_keyframes_edit.h"
+}
+
+typedef float(TangentPoint)[2];
+
+typedef std::set<float> BCFrameSet;
+typedef std::vector<float> BCFrames;
+typedef std::vector<float> BCValues;
+typedef std::vector<float> BCTimes;
+typedef std::map<int, float> BCValueMap;
+
+typedef enum BC_animation_type {
+ BC_ANIMATION_TYPE_OBJECT,
+ BC_ANIMATION_TYPE_BONE,
+ BC_ANIMATION_TYPE_CAMERA,
+ BC_ANIMATION_TYPE_MATERIAL,
+ BC_ANIMATION_TYPE_LIGHT,
+} BC_animation_type;
+
+class BCCurveKey {
+ private:
+ BC_animation_type key_type;
+ std::string rna_path;
+ int curve_array_index;
+ int curve_subindex; /* only needed for materials */
+
+ public:
+ BCCurveKey();
+ BCCurveKey(const BC_animation_type type,
+ const std::string path,
+ const int array_index,
+ const int subindex = -1);
+ void operator=(const BCCurveKey &other);
+ const std::string get_full_path() const;
+ const std::string get_path() const;
+ const int get_array_index() const;
+ const int get_subindex() const;
+ void set_object_type(BC_animation_type object_type);
+ const BC_animation_type get_animation_type() const;
+ const bool operator<(const BCCurveKey &other) const;
+};
+
+class BCBezTriple {
+ public:
+ BezTriple &bezt;
+
+ BCBezTriple(BezTriple &bezt);
+ const float get_frame() const;
+ const float get_time(Scene *scene) const;
+ const float get_value() const;
+ const float get_angle() const;
+ void get_in_tangent(Scene *scene, float point[2], bool as_angle) const;
+ void get_out_tangent(Scene *scene, float point[2], bool as_angle) const;
+ void get_tangent(Scene *scene, float point[2], bool as_angle, int index) const;
+};
+
+class BCAnimationCurve {
+ private:
+ BCCurveKey curve_key;
+ float min = 0;
+ float max = 0;
+
+ bool curve_is_local_copy = false;
+ FCurve *fcurve;
+ PointerRNA id_ptr;
+ void init_pointer_rna(Object *ob);
+ void delete_fcurve(FCurve *fcu);
+ FCurve *create_fcurve(int array_index, const char *rna_path);
+ void create_bezt(float frame, float output);
+ void update_range(float val);
+ void init_range(float val);
+
+ public:
+ BCAnimationCurve();
+ BCAnimationCurve(const BCAnimationCurve &other);
+ BCAnimationCurve(const BCCurveKey &key, Object *ob);
+ BCAnimationCurve(BCCurveKey key, Object *ob, FCurve *fcu);
+ ~BCAnimationCurve();
+
+ const bool is_of_animation_type(BC_animation_type type) const;
+ const int get_interpolation_type(float sample_frame) const;
+ bool is_animated();
+ const bool is_transform_curve() const;
+ const bool is_rotation_curve() const;
+ bool is_keyframe(int frame);
+ void adjust_range(int frame);
+
+ const std::string get_animation_name(Object *ob) const; /* xxx: this is collada specific */
+ const std::string get_channel_target() const;
+ const std::string get_channel_type() const;
+ const std::string get_channel_posebone() const; // returns "" if channel is not a bone channel
+
+ const int get_channel_index() const;
+ const int get_subindex() const;
+ const std::string get_rna_path() const;
+ const FCurve *get_fcurve() const;
+ const int sample_count() const;
+
+ const float get_value(const float frame);
+ void get_values(BCValues &values) const;
+ void get_value_map(BCValueMap &value_map);
+
+ void get_frames(BCFrames &frames) const;
+
+ /* Curve edit functions create a copy of the underlaying FCurve */
+ FCurve *get_edit_fcurve();
+ bool add_value_from_rna(const int frame);
+ bool add_value_from_matrix(const BCSample &sample, const int frame);
+ void add_value(const float val, const int frame);
+ void clean_handles();
+
+ /* experimental stuff */
+ const int closest_index_above(const float sample_frame, const int start_at) const;
+ const int closest_index_below(const float sample_frame) const;
+};
+
+typedef std::map<BCCurveKey, BCAnimationCurve *> BCAnimationCurveMap;
+
+#endif /* __BCANIMATIONCURVE_H__ */
diff --git a/source/blender/io/collada/BCAnimationSampler.cpp b/source/blender/io/collada/BCAnimationSampler.cpp
new file mode 100644
index 00000000000..e6996e95a5b
--- /dev/null
+++ b/source/blender/io/collada/BCAnimationSampler.cpp
@@ -0,0 +1,662 @@
+/*
+ * 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) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+#include <vector>
+#include <map>
+#include <algorithm> // std::find
+
+#include "ExportSettings.h"
+#include "BCAnimationCurve.h"
+#include "BCAnimationSampler.h"
+#include "collada_utils.h"
+#include "BCMath.h"
+
+extern "C" {
+#include "BKE_action.h"
+#include "BKE_constraint.h"
+#include "BKE_key.h"
+#include "BKE_main.h"
+#include "BKE_lib_id.h"
+#include "BKE_material.h"
+#include "BLI_listbase.h"
+#include "DNA_anim_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_key_types.h"
+#include "DNA_constraint_types.h"
+#include "ED_object.h"
+}
+
+static std::string EMPTY_STRING;
+static BCAnimationCurveMap BCEmptyAnimationCurves;
+
+BCAnimationSampler::BCAnimationSampler(BCExportSettings &export_settings, BCObjectSet &object_set)
+ : export_settings(export_settings)
+{
+ BCObjectSet::iterator it;
+ for (it = object_set.begin(); it != object_set.end(); ++it) {
+ Object *ob = *it;
+ add_object(ob);
+ }
+}
+
+BCAnimationSampler::~BCAnimationSampler()
+{
+ BCAnimationObjectMap::iterator it;
+ for (it = objects.begin(); it != objects.end(); ++it) {
+ BCAnimation *animation = it->second;
+ delete animation;
+ }
+}
+
+void BCAnimationSampler::add_object(Object *ob)
+{
+ BlenderContext blender_context = export_settings.get_blender_context();
+ BCAnimation *animation = new BCAnimation(blender_context.get_context(), ob);
+ objects[ob] = animation;
+
+ initialize_keyframes(animation->frame_set, ob);
+ initialize_curves(animation->curve_map, ob);
+}
+
+BCAnimationCurveMap *BCAnimationSampler::get_curves(Object *ob)
+{
+ BCAnimation &animation = *objects[ob];
+ if (animation.curve_map.size() == 0) {
+ initialize_curves(animation.curve_map, ob);
+ }
+ return &animation.curve_map;
+}
+
+static void get_sample_frames(BCFrameSet &sample_frames,
+ int sampling_rate,
+ bool keyframe_at_end,
+ Scene *scene)
+{
+ sample_frames.clear();
+
+ if (sampling_rate < 1) {
+ return; // no sample frames in this case
+ }
+
+ float sfra = scene->r.sfra;
+ float efra = scene->r.efra;
+
+ int frame_index;
+ for (frame_index = nearbyint(sfra); frame_index < efra; frame_index += sampling_rate) {
+ sample_frames.insert(frame_index);
+ }
+
+ if (frame_index >= efra && keyframe_at_end) {
+ sample_frames.insert(efra);
+ }
+}
+
+static bool is_object_keyframe(Object *ob, int frame_index)
+{
+ return false;
+}
+
+static void add_keyframes_from(bAction *action, BCFrameSet &frameset)
+{
+ if (action) {
+ FCurve *fcu = NULL;
+ for (fcu = (FCurve *)action->curves.first; fcu; fcu = fcu->next) {
+ BezTriple *bezt = fcu->bezt;
+ for (int i = 0; i < fcu->totvert; bezt++, i++) {
+ int frame_index = nearbyint(bezt->vec[1][0]);
+ frameset.insert(frame_index);
+ }
+ }
+ }
+}
+
+void BCAnimationSampler::check_property_is_animated(
+ BCAnimation &animation, float *ref, float *val, std::string data_path, int length)
+{
+ for (int array_index = 0; array_index < length; array_index++) {
+ if (!bc_in_range(ref[length], val[length], 0.00001)) {
+ BCCurveKey key(BC_ANIMATION_TYPE_OBJECT, data_path, array_index);
+ BCAnimationCurveMap::iterator it = animation.curve_map.find(key);
+ if (it == animation.curve_map.end()) {
+ animation.curve_map[key] = new BCAnimationCurve(key, animation.get_reference());
+ }
+ }
+ }
+}
+
+void BCAnimationSampler::update_animation_curves(BCAnimation &animation,
+ BCSample &sample,
+ Object *ob,
+ int frame)
+{
+ BCAnimationCurveMap::iterator it;
+ for (it = animation.curve_map.begin(); it != animation.curve_map.end(); ++it) {
+ BCAnimationCurve *curve = it->second;
+ if (curve->is_transform_curve()) {
+ curve->add_value_from_matrix(sample, frame);
+ }
+ else {
+ curve->add_value_from_rna(frame);
+ }
+ }
+}
+
+BCSample &BCAnimationSampler::sample_object(Object *ob, int frame_index, bool for_opensim)
+{
+ BCSample &ob_sample = sample_data.add(ob, frame_index);
+ // if (export_settings.get_apply_global_orientation()) {
+ // const BCMatrix &global_transform = export_settings.get_global_transform();
+ // ob_sample.get_matrix(global_transform);
+ //}
+
+ if (ob->type == OB_ARMATURE) {
+ bPoseChannel *pchan;
+ for (pchan = (bPoseChannel *)ob->pose->chanbase.first; pchan; pchan = pchan->next) {
+ Bone *bone = pchan->bone;
+ Matrix bmat;
+ if (bc_bone_matrix_local_get(ob, bone, bmat, for_opensim)) {
+
+ ob_sample.add_bone_matrix(bone, bmat);
+ }
+ }
+ }
+ return ob_sample;
+}
+
+void BCAnimationSampler::sample_scene(BCExportSettings &export_settings, bool keyframe_at_end)
+{
+ BlenderContext blender_context = export_settings.get_blender_context();
+ int sampling_rate = export_settings.get_sampling_rate();
+ bool for_opensim = export_settings.get_open_sim();
+ bool keep_keyframes = export_settings.get_keep_keyframes();
+ BC_export_animation_type export_animation_type = export_settings.get_export_animation_type();
+
+ Scene *scene = blender_context.get_scene();
+ BCFrameSet scene_sample_frames;
+ get_sample_frames(scene_sample_frames, sampling_rate, keyframe_at_end, scene);
+ BCFrameSet::iterator it;
+
+ int startframe = scene->r.sfra;
+ int endframe = scene->r.efra;
+
+ for (int frame_index = startframe; frame_index <= endframe; frame_index++) {
+ /* Loop over all frames and decide for each frame if sampling is necessary */
+ bool is_scene_sample_frame = false;
+ bool needs_update = true;
+ if (scene_sample_frames.find(frame_index) != scene_sample_frames.end()) {
+ bc_update_scene(blender_context, frame_index);
+ needs_update = false;
+ is_scene_sample_frame = true;
+ }
+
+ bool needs_sampling = is_scene_sample_frame || keep_keyframes ||
+ export_animation_type == BC_ANIMATION_EXPORT_KEYS;
+ if (!needs_sampling) {
+ continue;
+ }
+
+ BCAnimationObjectMap::iterator obit;
+ for (obit = objects.begin(); obit != objects.end(); ++obit) {
+ Object *ob = obit->first;
+ BCAnimation *animation = obit->second;
+ BCFrameSet &object_keyframes = animation->frame_set;
+ if (is_scene_sample_frame || object_keyframes.find(frame_index) != object_keyframes.end()) {
+
+ if (needs_update) {
+ bc_update_scene(blender_context, frame_index);
+ needs_update = false;
+ }
+
+ BCSample &sample = sample_object(ob, frame_index, for_opensim);
+ update_animation_curves(*animation, sample, ob, frame_index);
+ }
+ }
+ }
+}
+
+bool BCAnimationSampler::is_animated_by_constraint(Object *ob,
+ ListBase *conlist,
+ std::set<Object *> &animated_objects)
+{
+ bConstraint *con;
+ for (con = (bConstraint *)conlist->first; con; con = con->next) {
+ ListBase targets = {NULL, NULL};
+
+ const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
+
+ if (!bc_validateConstraints(con)) {
+ continue;
+ }
+
+ if (cti && cti->get_constraint_targets) {
+ bConstraintTarget *ct;
+ Object *obtar;
+ cti->get_constraint_targets(con, &targets);
+ for (ct = (bConstraintTarget *)targets.first; ct; ct = ct->next) {
+ obtar = ct->tar;
+ if (obtar) {
+ if (animated_objects.find(obtar) != animated_objects.end()) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+void BCAnimationSampler::find_depending_animated(std::set<Object *> &animated_objects,
+ std::set<Object *> &candidates)
+{
+ bool found_more;
+ do {
+ found_more = false;
+ std::set<Object *>::iterator it;
+ for (it = candidates.begin(); it != candidates.end(); ++it) {
+ Object *cob = *it;
+ ListBase *conlist = get_active_constraints(cob);
+ if (is_animated_by_constraint(cob, conlist, animated_objects)) {
+ animated_objects.insert(cob);
+ candidates.erase(cob);
+ found_more = true;
+ break;
+ }
+ }
+ } while (found_more && candidates.size() > 0);
+}
+
+void BCAnimationSampler::get_animated_from_export_set(std::set<Object *> &animated_objects,
+ LinkNode &export_set)
+{
+ /* Check if this object is animated. That is: Check if it has its own action, or:
+ *
+ * - Check if it has constraints to other objects.
+ * - at least one of the other objects is animated as well.
+ */
+
+ animated_objects.clear();
+ std::set<Object *> static_objects;
+ std::set<Object *> candidates;
+
+ LinkNode *node;
+ for (node = &export_set; node; node = node->next) {
+ Object *cob = (Object *)node->link;
+ if (bc_has_animations(cob)) {
+ animated_objects.insert(cob);
+ }
+ else {
+ ListBase conlist = cob->constraints;
+ if (conlist.first) {
+ candidates.insert(cob);
+ }
+ }
+ }
+ find_depending_animated(animated_objects, candidates);
+}
+
+void BCAnimationSampler::get_object_frames(BCFrames &frames, Object *ob)
+{
+ sample_data.get_frames(ob, frames);
+}
+
+void BCAnimationSampler::get_bone_frames(BCFrames &frames, Object *ob, Bone *bone)
+{
+ sample_data.get_frames(ob, bone, frames);
+}
+
+bool BCAnimationSampler::get_bone_samples(BCMatrixSampleMap &samples, Object *ob, Bone *bone)
+{
+ sample_data.get_matrices(ob, bone, samples);
+ return bc_is_animated(samples);
+}
+
+bool BCAnimationSampler::get_object_samples(BCMatrixSampleMap &samples, Object *ob)
+{
+ sample_data.get_matrices(ob, samples);
+ return bc_is_animated(samples);
+}
+
+#if 0
+/**
+ * Add sampled values to #FCurve
+ * If no #FCurve exists, create a temporary #FCurve;
+ * \note The temporary #FCurve will later be removed when the
+ * #BCAnimationSampler is removed (by its destructor).
+ *
+ * \param curve: The curve to which the data is added.
+ * \param matrices: The set of matrix values from where the data is taken.
+ * \param animation_type:
+ * - #BC_ANIMATION_EXPORT_SAMPLES: Use all matrix data.
+ * - #BC_ANIMATION_EXPORT_KEYS: Only take data from matrices for keyframes.
+ */
+void BCAnimationSampler::add_value_set(BCAnimationCurve &curve,
+ BCFrameSampleMap &samples,
+ BC_export_animation_type animation_type)
+{
+ int array_index = curve.get_array_index();
+ const BC_animation_transform_type tm_type = curve.get_transform_type();
+
+ BCFrameSampleMap::iterator it;
+ for (it = samples.begin(); it != samples.end(); ++it) {
+ const int frame_index = nearbyint(it->first);
+ if (animation_type == BC_ANIMATION_EXPORT_SAMPLES || curve.is_keyframe(frame_index)) {
+
+ const BCSample *sample = it->second;
+ float val = 0;
+
+ int subindex = curve.get_subindex();
+ bool good;
+ if (subindex == -1) {
+ good = sample->get_value(tm_type, array_index, &val);
+ }
+ else {
+ good = sample->get_value(tm_type, array_index, &val, subindex);
+ }
+
+ if (good) {
+ curve.add_value(val, frame_index);
+ }
+ }
+ }
+ curve.remove_unused_keyframes();
+ curve.calchandles();
+}
+#endif
+
+void BCAnimationSampler::generate_transform(Object *ob,
+ const BCCurveKey &key,
+ BCAnimationCurveMap &curves)
+{
+ BCAnimationCurveMap::const_iterator it = curves.find(key);
+ if (it == curves.end()) {
+ curves[key] = new BCAnimationCurve(key, ob);
+ }
+}
+
+void BCAnimationSampler::generate_transforms(Object *ob,
+ const std::string prep,
+ const BC_animation_type type,
+ BCAnimationCurveMap &curves)
+{
+ generate_transform(ob, BCCurveKey(type, prep + "location", 0), curves);
+ generate_transform(ob, BCCurveKey(type, prep + "location", 1), curves);
+ generate_transform(ob, BCCurveKey(type, prep + "location", 2), curves);
+ generate_transform(ob, BCCurveKey(type, prep + "rotation_euler", 0), curves);
+ generate_transform(ob, BCCurveKey(type, prep + "rotation_euler", 1), curves);
+ generate_transform(ob, BCCurveKey(type, prep + "rotation_euler", 2), curves);
+ generate_transform(ob, BCCurveKey(type, prep + "scale", 0), curves);
+ generate_transform(ob, BCCurveKey(type, prep + "scale", 1), curves);
+ generate_transform(ob, BCCurveKey(type, prep + "scale", 2), curves);
+}
+
+void BCAnimationSampler::generate_transforms(Object *ob, Bone *bone, BCAnimationCurveMap &curves)
+{
+ std::string prep = "pose.bones[\"" + std::string(bone->name) + "\"].";
+ generate_transforms(ob, prep, BC_ANIMATION_TYPE_BONE, curves);
+
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ generate_transforms(ob, child, curves);
+ }
+}
+
+/**
+ * Collect all keyframes from all animation curves related to the object.
+ * The bc_get... functions check for NULL and correct object type.
+ * The #add_keyframes_from() function checks for NULL.
+ */
+void BCAnimationSampler::initialize_keyframes(BCFrameSet &frameset, Object *ob)
+{
+ frameset.clear();
+ add_keyframes_from(bc_getSceneObjectAction(ob), frameset);
+ add_keyframes_from(bc_getSceneCameraAction(ob), frameset);
+ add_keyframes_from(bc_getSceneLightAction(ob), frameset);
+
+ for (int a = 0; a < ob->totcol; a++) {
+ Material *ma = BKE_object_material_get(ob, a + 1);
+ add_keyframes_from(bc_getSceneMaterialAction(ma), frameset);
+ }
+}
+
+void BCAnimationSampler::initialize_curves(BCAnimationCurveMap &curves, Object *ob)
+{
+ BC_animation_type object_type = BC_ANIMATION_TYPE_OBJECT;
+
+ bAction *action = bc_getSceneObjectAction(ob);
+ if (action) {
+ FCurve *fcu = (FCurve *)action->curves.first;
+
+ for (; fcu; fcu = fcu->next) {
+ object_type = BC_ANIMATION_TYPE_OBJECT;
+ if (ob->type == OB_ARMATURE) {
+ char *boneName = BLI_str_quoted_substrN(fcu->rna_path, "pose.bones[");
+ if (boneName) {
+ object_type = BC_ANIMATION_TYPE_BONE;
+ }
+ }
+
+ /* Adding action curves on object */
+ BCCurveKey key(object_type, fcu->rna_path, fcu->array_index);
+ curves[key] = new BCAnimationCurve(key, ob, fcu);
+ }
+ }
+
+ /* Add missing curves */
+ object_type = BC_ANIMATION_TYPE_OBJECT;
+ generate_transforms(ob, EMPTY_STRING, object_type, curves);
+ if (ob->type == OB_ARMATURE) {
+ bArmature *arm = (bArmature *)ob->data;
+ for (Bone *root_bone = (Bone *)arm->bonebase.first; root_bone; root_bone = root_bone->next) {
+ generate_transforms(ob, root_bone, curves);
+ }
+ }
+
+ /* Add curves on Object->data actions */
+ action = NULL;
+ if (ob->type == OB_CAMERA) {
+ action = bc_getSceneCameraAction(ob);
+ object_type = BC_ANIMATION_TYPE_CAMERA;
+ }
+ else if (ob->type == OB_LAMP) {
+ action = bc_getSceneLightAction(ob);
+ object_type = BC_ANIMATION_TYPE_LIGHT;
+ }
+
+ if (action) {
+ /* Add light action or Camera action */
+ FCurve *fcu = (FCurve *)action->curves.first;
+ for (; fcu; fcu = fcu->next) {
+ BCCurveKey key(object_type, fcu->rna_path, fcu->array_index);
+ curves[key] = new BCAnimationCurve(key, ob, fcu);
+ }
+ }
+
+ /* Add curves on Object->material actions*/
+ object_type = BC_ANIMATION_TYPE_MATERIAL;
+ for (int a = 0; a < ob->totcol; a++) {
+ /* Export Material parameter animations. */
+ Material *ma = BKE_object_material_get(ob, a + 1);
+ if (ma) {
+ action = bc_getSceneMaterialAction(ma);
+ if (action) {
+ /* isMatAnim = true; */
+ FCurve *fcu = (FCurve *)action->curves.first;
+ for (; fcu; fcu = fcu->next) {
+ BCCurveKey key(object_type, fcu->rna_path, fcu->array_index, a);
+ curves[key] = new BCAnimationCurve(key, ob, fcu);
+ }
+ }
+ }
+ }
+}
+
+/* ==================================================================== */
+
+BCSample &BCSampleFrame::add(Object *ob)
+{
+ BCSample *sample = new BCSample(ob);
+ sampleMap[ob] = sample;
+ return *sample;
+}
+
+/* Get the matrix for the given key, returns Unity when the key does not exist */
+const BCSample *BCSampleFrame::get_sample(Object *ob) const
+{
+ BCSampleMap::const_iterator it = sampleMap.find(ob);
+ if (it == sampleMap.end()) {
+ return NULL;
+ }
+ return it->second;
+}
+
+const BCMatrix *BCSampleFrame::get_sample_matrix(Object *ob) const
+{
+ BCSampleMap::const_iterator it = sampleMap.find(ob);
+ if (it == sampleMap.end()) {
+ return NULL;
+ }
+ BCSample *sample = it->second;
+ return &sample->get_matrix();
+}
+
+/* Get the matrix for the given Bone, returns Unity when the Objewct is not sampled */
+const BCMatrix *BCSampleFrame::get_sample_matrix(Object *ob, Bone *bone) const
+{
+ BCSampleMap::const_iterator it = sampleMap.find(ob);
+ if (it == sampleMap.end()) {
+ return NULL;
+ }
+
+ BCSample *sample = it->second;
+ const BCMatrix *bc_bone = sample->get_matrix(bone);
+ return bc_bone;
+}
+
+/* Check if the key is in this BCSampleFrame */
+const bool BCSampleFrame::has_sample_for(Object *ob) const
+{
+ return sampleMap.find(ob) != sampleMap.end();
+}
+
+/* Check if the Bone is in this BCSampleFrame */
+const bool BCSampleFrame::has_sample_for(Object *ob, Bone *bone) const
+{
+ const BCMatrix *bc_bone = get_sample_matrix(ob, bone);
+ return (bc_bone);
+}
+
+/* ==================================================================== */
+
+BCSample &BCSampleFrameContainer::add(Object *ob, int frame_index)
+{
+ BCSampleFrame &frame = sample_frames[frame_index];
+ return frame.add(ob);
+}
+
+/* ====================================================== */
+/* Below are the getters which we need to export the data */
+/* ====================================================== */
+
+/* Return either the BCSampleFrame or NULL if frame does not exist*/
+BCSampleFrame *BCSampleFrameContainer::get_frame(int frame_index)
+{
+ BCSampleFrameMap::iterator it = sample_frames.find(frame_index);
+ BCSampleFrame *frame = (it == sample_frames.end()) ? NULL : &it->second;
+ return frame;
+}
+
+/* Return a list of all frames that need to be sampled */
+const int BCSampleFrameContainer::get_frames(std::vector<int> &frames) const
+{
+ frames.clear(); // safety;
+ BCSampleFrameMap::const_iterator it;
+ for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
+ frames.push_back(it->first);
+ }
+ return frames.size();
+}
+
+const int BCSampleFrameContainer::get_frames(Object *ob, BCFrames &frames) const
+{
+ frames.clear(); // safety;
+ BCSampleFrameMap::const_iterator it;
+ for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
+ const BCSampleFrame &frame = it->second;
+ if (frame.has_sample_for(ob)) {
+ frames.push_back(it->first);
+ }
+ }
+ return frames.size();
+}
+
+const int BCSampleFrameContainer::get_frames(Object *ob, Bone *bone, BCFrames &frames) const
+{
+ frames.clear(); // safety;
+ BCSampleFrameMap::const_iterator it;
+ for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
+ const BCSampleFrame &frame = it->second;
+ if (frame.has_sample_for(ob, bone)) {
+ frames.push_back(it->first);
+ }
+ }
+ return frames.size();
+}
+
+const int BCSampleFrameContainer::get_samples(Object *ob, BCFrameSampleMap &samples) const
+{
+ samples.clear(); // safety;
+ BCSampleFrameMap::const_iterator it;
+ for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
+ const BCSampleFrame &frame = it->second;
+ const BCSample *sample = frame.get_sample(ob);
+ if (sample) {
+ samples[it->first] = sample;
+ }
+ }
+ return samples.size();
+}
+
+const int BCSampleFrameContainer::get_matrices(Object *ob, BCMatrixSampleMap &samples) const
+{
+ samples.clear(); // safety;
+ BCSampleFrameMap::const_iterator it;
+ for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
+ const BCSampleFrame &frame = it->second;
+ const BCMatrix *matrix = frame.get_sample_matrix(ob);
+ if (matrix) {
+ samples[it->first] = matrix;
+ }
+ }
+ return samples.size();
+}
+
+const int BCSampleFrameContainer::get_matrices(Object *ob,
+ Bone *bone,
+ BCMatrixSampleMap &samples) const
+{
+ samples.clear(); // safety;
+ BCSampleFrameMap::const_iterator it;
+ for (it = sample_frames.begin(); it != sample_frames.end(); ++it) {
+ const BCSampleFrame &frame = it->second;
+ const BCMatrix *sample = frame.get_sample_matrix(ob, bone);
+ if (sample) {
+ samples[it->first] = sample;
+ }
+ }
+ return samples.size();
+}
diff --git a/source/blender/io/collada/BCAnimationSampler.h b/source/blender/io/collada/BCAnimationSampler.h
new file mode 100644
index 00000000000..96138d0cbca
--- /dev/null
+++ b/source/blender/io/collada/BCAnimationSampler.h
@@ -0,0 +1,194 @@
+/*
+ * 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.
+ */
+
+#ifndef __BCANIMATIONSAMPLER_H__
+#define __BCANIMATIONSAMPLER_H__
+
+#include "BCAnimationCurve.h"
+#include "BCSampleData.h"
+#include "collada_utils.h"
+
+extern "C" {
+#include "BKE_action.h"
+#include "BKE_lib_id.h"
+#include "BLI_math_rotation.h"
+#include "DNA_action_types.h"
+}
+
+/* Collection of animation curves */
+class BCAnimation {
+ private:
+ Object *reference = NULL;
+ bContext *mContext;
+
+ public:
+ BCFrameSet frame_set;
+ BCAnimationCurveMap curve_map;
+
+ BCAnimation(bContext *C, Object *ob) : mContext(C)
+ {
+ Main *bmain = CTX_data_main(mContext);
+ reference = BKE_object_copy(bmain, ob);
+ }
+
+ ~BCAnimation()
+ {
+ BCAnimationCurveMap::iterator it;
+ for (it = curve_map.begin(); it != curve_map.end(); ++it) {
+ delete it->second;
+ }
+
+ if (reference && reference->id.us == 0) {
+ Main *bmain = CTX_data_main(mContext);
+ BKE_id_delete(bmain, &reference->id);
+ }
+ curve_map.clear();
+ }
+
+ Object *get_reference()
+ {
+ return reference;
+ }
+};
+
+typedef std::map<Object *, BCAnimation *> BCAnimationObjectMap;
+
+class BCSampleFrame {
+
+ /* Each frame on the timeline that needs to be sampled will have
+ * one BCSampleFrame where we collect sample information about all objects
+ * that need to be sampled for that frame. */
+
+ private:
+ BCSampleMap sampleMap;
+
+ public:
+ ~BCSampleFrame()
+ {
+ BCSampleMap::iterator it;
+ for (it = sampleMap.begin(); it != sampleMap.end(); ++it) {
+ BCSample *sample = it->second;
+ delete sample;
+ }
+ sampleMap.clear();
+ }
+
+ BCSample &add(Object *ob);
+
+ /* Following methods return NULL if object is not in the sampleMap*/
+ const BCSample *get_sample(Object *ob) const;
+ const BCMatrix *get_sample_matrix(Object *ob) const;
+ const BCMatrix *get_sample_matrix(Object *ob, Bone *bone) const;
+
+ const bool has_sample_for(Object *ob) const;
+ const bool has_sample_for(Object *ob, Bone *bone) const;
+};
+
+typedef std::map<int, BCSampleFrame> BCSampleFrameMap;
+
+class BCSampleFrameContainer {
+
+ /*
+ * The BCSampleFrameContainer stores a map of BCSampleFrame objects
+ * with the timeline frame as key.
+ *
+ * Some details on the purpose:
+ * An Animation is made of multiple FCurves where each FCurve can
+ * have multiple keyframes. When we want to export the animation we
+ * also can decide whether we want to export the keyframes or a set
+ * of sample frames at equidistant locations (sample period).
+ * In any case we must resample first need to resample it fully
+ * to resolve things like:
+ *
+ * - animations by constraints
+ * - animations by drivers
+ *
+ * For this purpose we need to step through the entire animation and
+ * then sample each frame that contains at least one keyFrame or
+ * sampleFrame. Then for each frame we have to store the transform
+ * information for all exported objects in a BCSampleframe
+ *
+ * The entire set of BCSampleframes is finally collected into
+ * a BCSampleframneContainer
+ */
+
+ private:
+ BCSampleFrameMap sample_frames;
+
+ public:
+ ~BCSampleFrameContainer()
+ {
+ }
+
+ BCSample &add(Object *ob, int frame_index);
+ BCSampleFrame *get_frame(int frame_index); // returns NULL if frame does not exist
+
+ const int get_frames(std::vector<int> &frames) const;
+ const int get_frames(Object *ob, BCFrames &frames) const;
+ const int get_frames(Object *ob, Bone *bone, BCFrames &frames) const;
+
+ const int get_samples(Object *ob, BCFrameSampleMap &samples) const;
+ const int get_matrices(Object *ob, BCMatrixSampleMap &matrices) const;
+ const int get_matrices(Object *ob, Bone *bone, BCMatrixSampleMap &bones) const;
+};
+
+class BCAnimationSampler {
+ private:
+ BCExportSettings &export_settings;
+ BCSampleFrameContainer sample_data;
+ BCAnimationObjectMap objects;
+
+ void generate_transform(Object *ob, const BCCurveKey &key, BCAnimationCurveMap &curves);
+ void generate_transforms(Object *ob,
+ const std::string prep,
+ const BC_animation_type type,
+ BCAnimationCurveMap &curves);
+ void generate_transforms(Object *ob, Bone *bone, BCAnimationCurveMap &curves);
+
+ void initialize_curves(BCAnimationCurveMap &curves, Object *ob);
+ void initialize_keyframes(BCFrameSet &frameset, Object *ob);
+ BCSample &sample_object(Object *ob, int frame_index, bool for_opensim);
+ void update_animation_curves(BCAnimation &animation,
+ BCSample &sample,
+ Object *ob,
+ int frame_index);
+ void check_property_is_animated(
+ BCAnimation &animation, float *ref, float *val, std::string data_path, int length);
+
+ public:
+ BCAnimationSampler(BCExportSettings &export_settings, BCObjectSet &animated_subset);
+ ~BCAnimationSampler();
+
+ void add_object(Object *ob);
+
+ void sample_scene(BCExportSettings &export_settings, bool keyframe_at_end);
+
+ BCAnimationCurveMap *get_curves(Object *ob);
+ void get_object_frames(BCFrames &frames, Object *ob);
+ bool get_object_samples(BCMatrixSampleMap &samples, Object *ob);
+ void get_bone_frames(BCFrames &frames, Object *ob, Bone *bone);
+ bool get_bone_samples(BCMatrixSampleMap &samples, Object *ob, Bone *bone);
+
+ static void get_animated_from_export_set(std::set<Object *> &animated_objects,
+ LinkNode &export_set);
+ static void find_depending_animated(std::set<Object *> &animated_objects,
+ std::set<Object *> &candidates);
+ static bool is_animated_by_constraint(Object *ob,
+ ListBase *conlist,
+ std::set<Object *> &animated_objects);
+};
+
+#endif /* __BCANIMATIONSAMPLER_H__ */
diff --git a/source/blender/io/collada/BCMath.cpp b/source/blender/io/collada/BCMath.cpp
new file mode 100644
index 00000000000..ec9977c1469
--- /dev/null
+++ b/source/blender/io/collada/BCMath.cpp
@@ -0,0 +1,244 @@
+/*
+ * 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) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+#include "BCMath.h"
+#include "BlenderContext.h"
+
+void BCQuat::rotate_to(Matrix &mat_to)
+{
+ Quat qd;
+ Matrix matd;
+ Matrix mati;
+ Matrix mat_from;
+
+ quat_to_mat4(mat_from, q);
+
+ /* Calculate the difference matrix matd between mat_from and mat_to */
+ invert_m4_m4(mati, mat_from);
+ mul_m4_m4m4(matd, mati, mat_to);
+
+ mat4_to_quat(qd, matd);
+
+ mul_qt_qtqt(q, qd, q); /* rotate to the final rotation to mat_to */
+}
+
+BCMatrix::BCMatrix(const BCMatrix &mat)
+{
+ set_transform(mat.matrix);
+}
+
+BCMatrix::BCMatrix(Matrix &mat)
+{
+ set_transform(mat);
+}
+
+BCMatrix::BCMatrix(Object *ob)
+{
+ set_transform(ob);
+}
+
+BCMatrix::BCMatrix()
+{
+ unit();
+}
+
+BCMatrix::BCMatrix(BC_global_forward_axis global_forward_axis, BC_global_up_axis global_up_axis)
+{
+ float mrot[3][3];
+ float mat[4][4];
+ mat3_from_axis_conversion(
+ BC_DEFAULT_FORWARD, BC_DEFAULT_UP, global_forward_axis, global_up_axis, mrot);
+
+ transpose_m3(mrot); // TODO: Verify that mat3_from_axis_conversion() returns a transposed matrix
+ copy_m4_m3(mat, mrot);
+ set_transform(mat);
+}
+
+void BCMatrix::add_transform(const Matrix &mat, bool inverse)
+{
+ add_transform(this->matrix, mat, this->matrix, inverse);
+}
+
+void BCMatrix::add_transform(const BCMatrix &mat, bool inverse)
+{
+ add_transform(this->matrix, mat.matrix, this->matrix, inverse);
+}
+
+void BCMatrix::apply_transform(const BCMatrix &mat, bool inverse)
+{
+ apply_transform(this->matrix, mat.matrix, this->matrix, inverse);
+}
+
+void BCMatrix::add_transform(Matrix &to, const Matrix &transform, const Matrix &from, bool inverse)
+{
+ if (inverse) {
+ Matrix globinv;
+ invert_m4_m4(globinv, transform);
+ add_transform(to, globinv, from, /*inverse=*/false);
+ }
+ else {
+ mul_m4_m4m4(to, transform, from);
+ }
+}
+
+void BCMatrix::apply_transform(Matrix &to,
+ const Matrix &transform,
+ const Matrix &from,
+ bool inverse)
+{
+ Matrix globinv;
+ invert_m4_m4(globinv, transform);
+ if (inverse) {
+ add_transform(to, globinv, from, /*inverse=*/false);
+ }
+ else {
+ mul_m4_m4m4(to, transform, from);
+ mul_m4_m4m4(to, to, globinv);
+ }
+}
+
+void BCMatrix::add_inverted_transform(Matrix &to, const Matrix &transform, const Matrix &from)
+{
+ Matrix workmat;
+ invert_m4_m4(workmat, transform);
+ mul_m4_m4m4(to, workmat, from);
+}
+
+void BCMatrix::set_transform(Object *ob)
+{
+ Matrix lmat;
+
+ BKE_object_matrix_local_get(ob, lmat);
+ copy_m4_m4(matrix, lmat);
+
+ mat4_decompose(this->loc, this->q, this->size, lmat);
+ quat_to_compatible_eul(this->rot, ob->rot, this->q);
+}
+
+void BCMatrix::set_transform(Matrix &mat)
+{
+ copy_m4_m4(matrix, mat);
+ mat4_decompose(this->loc, this->q, this->size, mat);
+ quat_to_eul(this->rot, this->q);
+}
+
+void BCMatrix::copy(Matrix &out, Matrix &in)
+{
+ /* destination comes first: */
+ memcpy(out, in, sizeof(Matrix));
+}
+
+void BCMatrix::transpose(Matrix &mat)
+{
+ transpose_m4(mat);
+}
+
+void BCMatrix::sanitize(Matrix &mat, int precision)
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ double val = (double)mat[i][j];
+ val = double_round(val, precision);
+ mat[i][j] = (float)val;
+ }
+ }
+}
+
+void BCMatrix::sanitize(DMatrix &mat, int precision)
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ mat[i][j] = double_round(mat[i][j], precision);
+ }
+ }
+}
+
+void BCMatrix::unit()
+{
+ unit_m4(this->matrix);
+ mat4_decompose(this->loc, this->q, this->size, this->matrix);
+ quat_to_eul(this->rot, this->q);
+}
+
+/* We need double here because the OpenCollada API needs it.
+ * precision = -1 indicates to not limit the precision. */
+void BCMatrix::get_matrix(DMatrix &mat, const bool transposed, const int precision) const
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ float val = (transposed) ? matrix[j][i] : matrix[i][j];
+ if (precision >= 0) {
+ val = floor((val * pow(10, precision) + 0.5)) / pow(10, precision);
+ }
+ mat[i][j] = val;
+ }
+ }
+}
+
+void BCMatrix::get_matrix(Matrix &mat,
+ const bool transposed,
+ const int precision,
+ const bool inverted) const
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ float val = (transposed) ? matrix[j][i] : matrix[i][j];
+ if (precision >= 0) {
+ val = floor((val * pow(10, precision) + 0.5)) / pow(10, precision);
+ }
+ mat[i][j] = val;
+ }
+ }
+
+ if (inverted) {
+ invert_m4(mat);
+ }
+}
+
+const bool BCMatrix::in_range(const BCMatrix &other, float distance) const
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ if (fabs(other.matrix[i][j] - matrix[i][j]) > distance) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+float (&BCMatrix::location() const)[3]
+{
+ return loc;
+}
+
+float (&BCMatrix::rotation() const)[3]
+{
+ return rot;
+}
+
+float (&BCMatrix::scale() const)[3]
+{
+ return size;
+}
+
+float (&BCMatrix::quat() const)[4]
+{
+ return q;
+}
diff --git a/source/blender/io/collada/BCMath.h b/source/blender/io/collada/BCMath.h
new file mode 100644
index 00000000000..9ecea85b08c
--- /dev/null
+++ b/source/blender/io/collada/BCMath.h
@@ -0,0 +1,110 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __BCMATH_H__
+#define __BCMATH_H__
+
+#include "BlenderTypes.h"
+
+extern "C" {
+#include "BKE_object.h"
+#include "BLI_math.h"
+}
+
+class BCQuat {
+ private:
+ mutable Quat q;
+
+ public:
+ BCQuat(const BCQuat &other)
+ {
+ copy_v4_v4(q, other.q);
+ }
+
+ BCQuat(Quat &other)
+ {
+ copy_v4_v4(q, other);
+ }
+
+ BCQuat()
+ {
+ unit_qt(q);
+ }
+
+ Quat &quat()
+ {
+ return q;
+ }
+
+ void rotate_to(Matrix &mat_to);
+};
+
+class BCMatrix {
+
+ private:
+ mutable float matrix[4][4];
+ mutable float size[3];
+ mutable float rot[3];
+ mutable float loc[3];
+ mutable float q[4];
+
+ void unit();
+ void copy(Matrix &r, Matrix &a);
+
+ public:
+ float (&location() const)[3];
+ float (&rotation() const)[3];
+ float (&scale() const)[3];
+ float (&quat() const)[4];
+
+ BCMatrix(BC_global_forward_axis global_forward_axis, BC_global_up_axis global_up_axis);
+ BCMatrix(const BCMatrix &mat);
+ BCMatrix(Matrix &mat);
+ BCMatrix(Object *ob);
+ BCMatrix();
+
+ void get_matrix(DMatrix &matrix, const bool transposed = false, const int precision = -1) const;
+ void get_matrix(Matrix &matrix,
+ const bool transposed = false,
+ const int precision = -1,
+ const bool inverted = false) const;
+ void set_transform(Object *ob);
+ void set_transform(Matrix &mat);
+ void add_transform(Matrix &to,
+ const Matrix &transform,
+ const Matrix &from,
+ const bool inverted = false);
+ void apply_transform(Matrix &to,
+ const Matrix &transform,
+ const Matrix &from,
+ const bool inverted = false);
+ void add_inverted_transform(Matrix &to, const Matrix &transform, const Matrix &from);
+ void add_transform(const Matrix &matrix, const bool inverted = false);
+ void add_transform(const BCMatrix &matrix, const bool inverted = false);
+ void apply_transform(const BCMatrix &matrix, const bool inverted = false);
+
+ const bool in_range(const BCMatrix &other, float distance) const;
+
+ static void sanitize(Matrix &matrix, int precision);
+ static void sanitize(DMatrix &matrix, int precision);
+ static void transpose(Matrix &matrix);
+};
+
+#endif /* __BCMATH_H__ */
diff --git a/source/blender/io/collada/BCSampleData.cpp b/source/blender/io/collada/BCSampleData.cpp
new file mode 100644
index 00000000000..7e23a2de00f
--- /dev/null
+++ b/source/blender/io/collada/BCSampleData.cpp
@@ -0,0 +1,97 @@
+/*
+ * 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) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+#include "BCSampleData.h"
+#include "collada_utils.h"
+
+BCSample::~BCSample()
+{
+ BCBoneMatrixMap::iterator it;
+ for (it = bonemats.begin(); it != bonemats.end(); ++it) {
+ delete it->second;
+ }
+}
+
+void BCSample::add_bone_matrix(Bone *bone, Matrix &mat)
+{
+ BCMatrix *matrix;
+ BCBoneMatrixMap::const_iterator it = bonemats.find(bone);
+ if (it != bonemats.end()) {
+ throw std::invalid_argument("bone " + std::string(bone->name) + " already defined before");
+ }
+ matrix = new BCMatrix(mat);
+ bonemats[bone] = matrix;
+}
+
+/* Get channel value */
+const bool BCSample::get_value(std::string channel_target, const int array_index, float *val) const
+{
+ std::string bname = bc_string_before(channel_target, ".");
+ std::string channel_type = bc_string_after(channel_target, ".");
+
+ const BCMatrix *matrix = &obmat;
+ if (bname != channel_target) {
+ bname = bname.substr(2);
+ bname = bc_string_before(bname, "\"");
+ BCBoneMatrixMap::const_iterator it;
+ for (it = bonemats.begin(); it != bonemats.end(); ++it) {
+ Bone *bone = it->first;
+ if (bname == bone->name) {
+ matrix = it->second;
+ break;
+ }
+ }
+ }
+ else {
+ matrix = &obmat;
+ }
+
+ if (channel_type == "location") {
+ *val = matrix->location()[array_index];
+ }
+ else if (channel_type == "scale") {
+ *val = matrix->scale()[array_index];
+ }
+ else if (channel_type == "rotation" || channel_type == "rotation_euler") {
+ *val = matrix->rotation()[array_index];
+ }
+ else if (channel_type == "rotation_quaternion") {
+ *val = matrix->quat()[array_index];
+ }
+ else {
+ *val = 0;
+ return false;
+ }
+
+ return true;
+}
+
+const BCMatrix *BCSample::get_matrix(Bone *bone) const
+{
+ BCBoneMatrixMap::const_iterator it = bonemats.find(bone);
+ if (it == bonemats.end()) {
+ return NULL;
+ }
+ return it->second;
+}
+
+const BCMatrix &BCSample::get_matrix() const
+{
+ return obmat;
+}
diff --git a/source/blender/io/collada/BCSampleData.h b/source/blender/io/collada/BCSampleData.h
new file mode 100644
index 00000000000..07ecb544c71
--- /dev/null
+++ b/source/blender/io/collada/BCSampleData.h
@@ -0,0 +1,66 @@
+/*
+ * 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) 2008 Blender Foundation.
+ * All rights reserved.
+ */
+
+#ifndef __BCSAMPLEDATA_H__
+#define __BCSAMPLEDATA_H__
+
+#include <string>
+#include <map>
+#include <algorithm>
+
+#include "ExportSettings.h"
+#include "BCSampleData.h"
+#include "BCMath.h"
+
+extern "C" {
+#include "BKE_object.h"
+#include "BLI_math_rotation.h"
+#include "DNA_object_types.h"
+#include "DNA_armature_types.h"
+#include "DNA_material_types.h"
+#include "DNA_light_types.h"
+#include "DNA_camera_types.h"
+}
+
+typedef std::map<Bone *, BCMatrix *> BCBoneMatrixMap;
+
+class BCSample {
+ private:
+ BCMatrix obmat;
+ BCBoneMatrixMap bonemats; /* For Armature animation */
+
+ public:
+ BCSample(Object *ob) : obmat(ob)
+ {
+ }
+
+ ~BCSample();
+
+ void add_bone_matrix(Bone *bone, Matrix &mat);
+
+ const bool get_value(std::string channel_target, const int array_index, float *val) const;
+ const BCMatrix &get_matrix() const;
+ const BCMatrix *get_matrix(Bone *bone) const; // returns NULL if bone is not animated
+};
+
+typedef std::map<Object *, BCSample *> BCSampleMap;
+typedef std::map<int, const BCSample *> BCFrameSampleMap;
+typedef std::map<int, const BCMatrix *> BCMatrixSampleMap;
+
+#endif /* __BCSAMPLEDATA_H__ */
diff --git a/source/blender/io/collada/BlenderContext.cpp b/source/blender/io/collada/BlenderContext.cpp
new file mode 100644
index 00000000000..a9783a9b9c4
--- /dev/null
+++ b/source/blender/io/collada/BlenderContext.cpp
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <vector>
+
+#include "BlenderContext.h"
+#include "ExportSettings.h"
+
+#include "BKE_scene.h"
+
+bool bc_is_base_node(LinkNode *export_set, Object *ob, ViewLayer *view_layer)
+{
+ Object *root = bc_get_highest_exported_ancestor_or_self(export_set, ob, view_layer);
+ return (root == ob);
+}
+
+/**
+ * Returns the highest selected ancestor
+ * returns NULL if no ancestor is selected
+ * IMPORTANT: This function expects that all exported objects have set:
+ * ob->id.tag & LIB_TAG_DOIT
+ */
+Object *bc_get_highest_exported_ancestor_or_self(LinkNode *export_set,
+ Object *ob,
+ ViewLayer *view_layer)
+{
+ Object *ancestor = ob;
+ while (ob->parent) {
+ if (bc_is_in_Export_set(export_set, ob->parent, view_layer)) {
+ ancestor = ob->parent;
+ }
+ ob = ob->parent;
+ }
+ return ancestor;
+}
+
+void bc_get_children(std::vector<Object *> &child_set, Object *ob, ViewLayer *view_layer)
+{
+ Base *base;
+ for (base = (Base *)view_layer->object_bases.first; base; base = base->next) {
+ Object *cob = base->object;
+ if (cob->parent == ob) {
+ switch (ob->type) {
+ case OB_MESH:
+ case OB_CAMERA:
+ case OB_LAMP:
+ case OB_EMPTY:
+ case OB_ARMATURE:
+ child_set.push_back(cob);
+ default:
+ break;
+ }
+ }
+ }
+}
+
+bool bc_is_in_Export_set(LinkNode *export_set, Object *ob, ViewLayer *view_layer)
+{
+ bool to_export = (BLI_linklist_index(export_set, ob) != -1);
+
+ if (!to_export) {
+ /* Mark this object as to_export even if it is not in the
+ export list, but it contains children to export */
+
+ std::vector<Object *> children;
+ bc_get_children(children, ob, view_layer);
+ for (int i = 0; i < children.size(); i++) {
+ if (bc_is_in_Export_set(export_set, children[i], view_layer)) {
+ to_export = true;
+ break;
+ }
+ }
+ }
+ return to_export;
+}
+
+int bc_is_marked(Object *ob)
+{
+ return ob && (ob->id.tag & LIB_TAG_DOIT);
+}
+
+void bc_remove_mark(Object *ob)
+{
+ ob->id.tag &= ~LIB_TAG_DOIT;
+}
+
+void bc_set_mark(Object *ob)
+{
+ ob->id.tag |= LIB_TAG_DOIT;
+}
+
+BlenderContext::BlenderContext(bContext *C)
+{
+ context = C;
+ main = CTX_data_main(C);
+ scene = CTX_data_scene(C);
+ view_layer = CTX_data_view_layer(C);
+ depsgraph = nullptr; // create only when needed
+}
+
+bContext *BlenderContext::get_context()
+{
+ return context;
+}
+
+Depsgraph *BlenderContext::get_depsgraph()
+{
+ if (!depsgraph) {
+ depsgraph = BKE_scene_get_depsgraph(main, scene, view_layer, true);
+ }
+ return depsgraph;
+}
+
+Scene *BlenderContext::get_scene()
+{
+ return scene;
+}
+
+Scene *BlenderContext::get_evaluated_scene()
+{
+ Scene *scene_eval = DEG_get_evaluated_scene(get_depsgraph());
+ return scene_eval;
+}
+
+Object *BlenderContext::get_evaluated_object(Object *ob)
+{
+ Object *ob_eval = DEG_get_evaluated_object(depsgraph, ob);
+ return ob_eval;
+}
+
+ViewLayer *BlenderContext::get_view_layer()
+{
+ return view_layer;
+}
+
+Main *BlenderContext::get_main()
+{
+ return main;
+}
diff --git a/source/blender/io/collada/BlenderContext.h b/source/blender/io/collada/BlenderContext.h
new file mode 100644
index 00000000000..50781e8eede
--- /dev/null
+++ b/source/blender/io/collada/BlenderContext.h
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __BLENDERCONTEXT_H__
+#define __BLENDERCONTEXT_H__
+
+#ifdef __cplusplus
+
+extern "C" {
+#endif
+
+#include "DNA_object_types.h"
+#include "BLI_linklist.h"
+#include "BKE_context.h"
+#include "BKE_main.h"
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+#include "DNA_layer_types.h"
+#include "BlenderTypes.h"
+
+static const BC_global_forward_axis BC_DEFAULT_FORWARD = BC_GLOBAL_FORWARD_Y;
+static const BC_global_up_axis BC_DEFAULT_UP = BC_GLOBAL_UP_Z;
+
+bool bc_is_in_Export_set(LinkNode *export_set, Object *ob, ViewLayer *view_layer);
+bool bc_is_base_node(LinkNode *export_set, Object *ob, ViewLayer *view_layer);
+Object *bc_get_highest_exported_ancestor_or_self(LinkNode *export_set,
+ Object *ob,
+ ViewLayer *view_layer);
+int bc_is_marked(Object *ob);
+void bc_remove_mark(Object *ob);
+void bc_set_mark(Object *ob);
+
+#ifdef __cplusplus
+}
+
+class BlenderContext {
+ private:
+ bContext *context;
+ Depsgraph *depsgraph;
+ Scene *scene;
+ ViewLayer *view_layer;
+ Main *main;
+
+ public:
+ BlenderContext(bContext *C);
+ bContext *get_context();
+ Depsgraph *get_depsgraph();
+ Scene *get_scene();
+ Scene *get_evaluated_scene();
+ Object *get_evaluated_object(Object *ob);
+ ViewLayer *get_view_layer();
+ Main *get_main();
+};
+#endif
+
+#endif
diff --git a/source/blender/io/collada/BlenderTypes.h b/source/blender/io/collada/BlenderTypes.h
new file mode 100644
index 00000000000..0e024be2374
--- /dev/null
+++ b/source/blender/io/collada/BlenderTypes.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __BLENDERTYPES_H__
+#define __BLENDERTYPES_H__
+
+typedef float(Vector)[3];
+typedef float(Quat)[4];
+typedef float(Color)[4];
+typedef float(Matrix)[4][4];
+typedef double(DMatrix)[4][4];
+
+typedef enum BC_global_forward_axis {
+ BC_GLOBAL_FORWARD_X = 0,
+ BC_GLOBAL_FORWARD_Y = 1,
+ BC_GLOBAL_FORWARD_Z = 2,
+ BC_GLOBAL_FORWARD_MINUS_X = 3,
+ BC_GLOBAL_FORWARD_MINUS_Y = 4,
+ BC_GLOBAL_FORWARD_MINUS_Z = 5
+} BC_global_forward_axis;
+
+typedef enum BC_global_up_axis {
+ BC_GLOBAL_UP_X = 0,
+ BC_GLOBAL_UP_Y = 1,
+ BC_GLOBAL_UP_Z = 2,
+ BC_GLOBAL_UP_MINUS_X = 3,
+ BC_GLOBAL_UP_MINUS_Y = 4,
+ BC_GLOBAL_UP_MINUS_Z = 5
+} BC_global_up_axis;
+
+#endif
diff --git a/source/blender/io/collada/CMakeLists.txt b/source/blender/io/collada/CMakeLists.txt
new file mode 100644
index 00000000000..8ffce9e3e7e
--- /dev/null
+++ b/source/blender/io/collada/CMakeLists.txt
@@ -0,0 +1,147 @@
+# ***** 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) 2006, Blender Foundation
+# All rights reserved.
+# ***** END GPL LICENSE BLOCK *****
+
+remove_strict_flags()
+FIND_FILE(OPENCOLLADA_ANIMATION_CLIP
+ NAMES
+ COLLADAFWAnimationClip.h
+ PATHS
+ ${OPENCOLLADA_INCLUDE_DIRS}
+ NO_DEFAULT_PATH
+)
+
+if(OPENCOLLADA_ANIMATION_CLIP)
+ add_definitions(-DWITH_OPENCOLLADA_ANIMATION_CLIP)
+endif()
+
+set(INC
+ .
+ ../../blenkernel
+ ../../blenlib
+ ../../blentranslation
+ ../../depsgraph
+ ../../editors/include
+ ../../imbuf
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/guardedalloc
+ ../../ikplugin
+ ../../../../intern/iksolver/extern
+ ../../bmesh
+)
+
+set(INC_SYS
+ ${OPENCOLLADA_INCLUDE_DIRS}
+)
+
+set(SRC
+ AnimationClipExporter.cpp
+ AnimationExporter.cpp
+ AnimationImporter.cpp
+ ArmatureExporter.cpp
+ ArmatureImporter.cpp
+ BCAnimationCurve.cpp
+ BCAnimationSampler.cpp
+ BCMath.cpp
+ BCSampleData.cpp
+ BlenderContext.cpp
+ CameraExporter.cpp
+ ControllerExporter.cpp
+ DocumentExporter.cpp
+ DocumentImporter.cpp
+ EffectExporter.cpp
+ ErrorHandler.cpp
+ ExportSettings.cpp
+ ExtraHandler.cpp
+ ExtraTags.cpp
+ GeometryExporter.cpp
+ ImageExporter.cpp
+ ImportSettings.cpp
+ InstanceWriter.cpp
+ LightExporter.cpp
+ MaterialExporter.cpp
+ Materials.cpp
+ MeshImporter.cpp
+ SceneExporter.cpp
+ SkinInfo.cpp
+ TransformReader.cpp
+ TransformWriter.cpp
+ collada.cpp
+ collada_internal.cpp
+ collada_utils.cpp
+
+ AnimationClipExporter.h
+ AnimationExporter.h
+ AnimationImporter.h
+ ArmatureExporter.h
+ ArmatureImporter.h
+ BCAnimationCurve.h
+ BCAnimationSampler.h
+ BCMath.h
+ BCSampleData.h
+ BlenderContext.h
+ BlenderTypes.h
+ CameraExporter.h
+ ControllerExporter.h
+ DocumentExporter.h
+ DocumentImporter.h
+ EffectExporter.h
+ ErrorHandler.h
+ ExportSettings.h
+ ExtraHandler.h
+ ExtraTags.h
+ GeometryExporter.h
+ ImageExporter.h
+ ImportSettings.h
+ InstanceWriter.h
+ LightExporter.h
+ MaterialExporter.h
+ Materials.h
+ MeshImporter.h
+ SceneExporter.h
+ SkinInfo.h
+ TransformReader.h
+ TransformWriter.h
+ collada.h
+ collada_internal.h
+ collada_utils.h
+)
+
+set(LIB
+ ${OPENCOLLADA_LIBRARIES}
+ ${PCRE_LIBRARIES}
+ ${XML2_LIBRARIES}
+)
+
+if(WITH_BUILDINFO)
+ add_definitions(-DWITH_BUILDINFO)
+endif()
+
+if(WITH_INTERNATIONAL)
+ add_definitions(-DWITH_INTERNATIONAL)
+endif()
+
+if(CMAKE_COMPILER_IS_GNUCXX)
+ # COLLADAFWArray.h gives error with gcc 4.5
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive")
+endif()
+
+blender_add_lib(bf_collada "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/io/collada/CameraExporter.cpp b/source/blender/io/collada/CameraExporter.cpp
new file mode 100644
index 00000000000..74862c44270
--- /dev/null
+++ b/source/blender/io/collada/CameraExporter.cpp
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <string>
+
+#include "COLLADASWCamera.h"
+
+extern "C" {
+#include "DNA_camera_types.h"
+}
+#include "CameraExporter.h"
+
+#include "collada_internal.h"
+
+CamerasExporter::CamerasExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings)
+ : COLLADASW::LibraryCameras(sw), export_settings(export_settings)
+{
+}
+
+template<class Functor>
+void forEachCameraObjectInExportSet(Scene *sce, Functor &f, LinkNode *export_set)
+{
+ LinkNode *node;
+ for (node = export_set; node; node = node->next) {
+ Object *ob = (Object *)node->link;
+
+ if (ob->type == OB_CAMERA && ob->data) {
+ f(ob, sce);
+ }
+ }
+}
+
+void CamerasExporter::exportCameras(Scene *sce)
+{
+ openLibrary();
+
+ forEachCameraObjectInExportSet(sce, *this, this->export_settings.get_export_set());
+
+ closeLibrary();
+}
+void CamerasExporter::operator()(Object *ob, Scene *sce)
+{
+ Camera *cam = (Camera *)ob->data;
+ std::string cam_id(get_camera_id(ob));
+ std::string cam_name(id_name(cam));
+
+ switch (cam->type) {
+ case CAM_PANO:
+ case CAM_PERSP: {
+ COLLADASW::PerspectiveOptic persp(mSW);
+ persp.setXFov(RAD2DEGF(focallength_to_fov(cam->lens, cam->sensor_x)), "xfov");
+ persp.setAspectRatio((float)(sce->r.xsch) / (float)(sce->r.ysch), false, "aspect_ratio");
+ persp.setZFar(cam->clip_end, false, "zfar");
+ persp.setZNear(cam->clip_start, false, "znear");
+ COLLADASW::Camera ccam(mSW, &persp, cam_id, cam_name);
+ exportBlenderProfile(ccam, cam);
+ addCamera(ccam);
+
+ break;
+ }
+ case CAM_ORTHO:
+ default: {
+ COLLADASW::OrthographicOptic ortho(mSW);
+ ortho.setXMag(cam->ortho_scale / 2, "xmag");
+ ortho.setAspectRatio((float)(sce->r.xsch) / (float)(sce->r.ysch), false, "aspect_ratio");
+ ortho.setZFar(cam->clip_end, false, "zfar");
+ ortho.setZNear(cam->clip_start, false, "znear");
+ COLLADASW::Camera ccam(mSW, &ortho, cam_id, cam_name);
+ exportBlenderProfile(ccam, cam);
+ addCamera(ccam);
+ break;
+ }
+ }
+}
+bool CamerasExporter::exportBlenderProfile(COLLADASW::Camera &cm, Camera *cam)
+{
+ cm.addExtraTechniqueParameter("blender", "shiftx", cam->shiftx);
+ cm.addExtraTechniqueParameter("blender", "shifty", cam->shifty);
+ cm.addExtraTechniqueParameter("blender", "dof_distance", cam->dof.focus_distance);
+ return true;
+}
diff --git a/source/blender/io/collada/CameraExporter.h b/source/blender/io/collada/CameraExporter.h
new file mode 100644
index 00000000000..04bcc4a5dad
--- /dev/null
+++ b/source/blender/io/collada/CameraExporter.h
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __CAMERAEXPORTER_H__
+#define __CAMERAEXPORTER_H__
+
+#include "COLLADASWStreamWriter.h"
+#include "COLLADASWLibraryCameras.h"
+
+extern "C" {
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+}
+
+#include "ExportSettings.h"
+#include "DNA_camera_types.h"
+
+class CamerasExporter : COLLADASW::LibraryCameras {
+ public:
+ CamerasExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings);
+ void exportCameras(Scene *sce);
+ void operator()(Object *ob, Scene *sce);
+
+ private:
+ bool exportBlenderProfile(COLLADASW::Camera &cla, Camera *cam);
+ BCExportSettings &export_settings;
+};
+
+#endif
diff --git a/source/blender/io/collada/ControllerExporter.cpp b/source/blender/io/collada/ControllerExporter.cpp
new file mode 100644
index 00000000000..0119aba7dfd
--- /dev/null
+++ b/source/blender/io/collada/ControllerExporter.cpp
@@ -0,0 +1,649 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include "COLLADASWBaseInputElement.h"
+#include "COLLADASWInstanceController.h"
+#include "COLLADASWPrimitves.h"
+#include "COLLADASWSource.h"
+
+#include "DNA_action_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+
+#include "BKE_action.h"
+#include "BKE_armature.h"
+
+extern "C" {
+#include "BKE_global.h"
+#include "BKE_idprop.h"
+#include "BKE_lib_id.h"
+#include "BKE_mesh.h"
+}
+
+#include "ED_armature.h"
+
+#include "BLI_listbase.h"
+
+#include "GeometryExporter.h"
+#include "ArmatureExporter.h"
+#include "ControllerExporter.h"
+#include "SceneExporter.h"
+
+#include "collada_utils.h"
+
+bool ControllerExporter::is_skinned_mesh(Object *ob)
+{
+ return bc_get_assigned_armature(ob) != NULL;
+}
+
+void ControllerExporter::write_bone_URLs(COLLADASW::InstanceController &ins,
+ Object *ob_arm,
+ Bone *bone)
+{
+ if (bc_is_root_bone(bone, this->export_settings.get_deform_bones_only())) {
+ std::string node_id = translate_id(id_name(ob_arm) + "_" + bone->name);
+ ins.addSkeleton(COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, node_id));
+ }
+ else {
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ write_bone_URLs(ins, ob_arm, child);
+ }
+ }
+}
+
+bool ControllerExporter::add_instance_controller(Object *ob)
+{
+ Object *ob_arm = bc_get_assigned_armature(ob);
+ bArmature *arm = (bArmature *)ob_arm->data;
+
+ const std::string &controller_id = get_controller_id(ob_arm, ob);
+
+ COLLADASW::InstanceController ins(mSW);
+ ins.setUrl(COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, controller_id));
+
+ Mesh *me = (Mesh *)ob->data;
+ if (!me->dvert) {
+ return false;
+ }
+
+ /* write root bone URLs */
+ Bone *bone;
+ for (bone = (Bone *)arm->bonebase.first; bone; bone = bone->next) {
+ write_bone_URLs(ins, ob_arm, bone);
+ }
+
+ InstanceWriter::add_material_bindings(
+ ins.getBindMaterial(), ob, this->export_settings.get_active_uv_only());
+
+ ins.add();
+ return true;
+}
+
+void ControllerExporter::export_controllers()
+{
+ Scene *sce = blender_context.get_scene();
+ openLibrary();
+
+ GeometryFunctor gf;
+ gf.forEachMeshObjectInExportSet<ControllerExporter>(
+ sce, *this, this->export_settings.get_export_set());
+
+ closeLibrary();
+}
+
+void ControllerExporter::operator()(Object *ob)
+{
+ Object *ob_arm = bc_get_assigned_armature(ob);
+ Key *key = BKE_key_from_object(ob);
+
+ if (ob_arm) {
+ export_skin_controller(ob, ob_arm);
+ }
+ if (key && this->export_settings.get_include_shapekeys()) {
+ export_morph_controller(ob, key);
+ }
+}
+#if 0
+
+bool ArmatureExporter::already_written(Object *ob_arm)
+{
+ return std::find(written_armatures.begin(), written_armatures.end(), ob_arm) !=
+ written_armatures.end();
+}
+
+void ArmatureExporter::wrote(Object *ob_arm)
+{
+ written_armatures.push_back(ob_arm);
+}
+
+void ArmatureExporter::find_objects_using_armature(Object *ob_arm,
+ std::vector<Object *> &objects,
+ Scene *sce)
+{
+ objects.clear();
+
+ Base *base = (Base *)sce->base.first;
+ while (base) {
+ Object *ob = base->object;
+
+ if (ob->type == OB_MESH && get_assigned_armature(ob) == ob_arm) {
+ objects.push_back(ob);
+ }
+
+ base = base->next;
+ }
+}
+#endif
+
+std::string ControllerExporter::get_controller_id(Object *ob_arm, Object *ob)
+{
+ return translate_id(id_name(ob_arm)) + "_" + translate_id(id_name(ob)) +
+ SKIN_CONTROLLER_ID_SUFFIX;
+}
+
+std::string ControllerExporter::get_controller_id(Key *key, Object *ob)
+{
+ return translate_id(id_name(ob)) + MORPH_CONTROLLER_ID_SUFFIX;
+}
+
+/* ob should be of type OB_MESH
+ * both args are required */
+void ControllerExporter::export_skin_controller(Object *ob, Object *ob_arm)
+{
+ /* joint names
+ * joint inverse bind matrices
+ * vertex weights */
+
+ /* input:
+ * joint names: ob -> vertex group names
+ * vertex group weights: me->dvert -> groups -> index, weight */
+
+ bool use_instantiation = this->export_settings.get_use_object_instantiation();
+ Mesh *me;
+
+ if (((Mesh *)ob->data)->dvert == NULL) {
+ return;
+ }
+
+ me = bc_get_mesh_copy(blender_context,
+ ob,
+ this->export_settings.get_export_mesh_type(),
+ this->export_settings.get_apply_modifiers(),
+ this->export_settings.get_triangulate());
+
+ std::string controller_name = id_name(ob_arm);
+ std::string controller_id = get_controller_id(ob_arm, ob);
+
+ openSkin(controller_id,
+ controller_name,
+ COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, get_geometry_id(ob, use_instantiation)));
+
+ add_bind_shape_mat(ob);
+
+ std::string joints_source_id = add_joints_source(ob_arm, &ob->defbase, controller_id);
+ std::string inv_bind_mat_source_id = add_inv_bind_mats_source(
+ ob_arm, &ob->defbase, controller_id);
+
+ std::list<int> vcounts;
+ std::list<int> joints;
+ std::list<float> weights;
+
+ {
+ int i, j;
+
+ /* def group index -> joint index */
+ std::vector<int> joint_index_by_def_index;
+ bDeformGroup *def;
+
+ for (def = (bDeformGroup *)ob->defbase.first, i = 0, j = 0; def; def = def->next, i++) {
+ if (is_bone_defgroup(ob_arm, def)) {
+ joint_index_by_def_index.push_back(j++);
+ }
+ else {
+ joint_index_by_def_index.push_back(-1);
+ }
+ }
+
+ int oob_counter = 0;
+ for (i = 0; i < me->totvert; i++) {
+ MDeformVert *vert = &me->dvert[i];
+ std::map<int, float> jw;
+
+ /* We're normalizing the weights later */
+ float sumw = 0.0f;
+
+ for (j = 0; j < vert->totweight; j++) {
+ uint idx = vert->dw[j].def_nr;
+ if (idx >= joint_index_by_def_index.size()) {
+ /* XXX: Maybe better find out where and
+ * why the Out Of Bound indexes get created ? */
+ oob_counter += 1;
+ }
+ else {
+ int joint_index = joint_index_by_def_index[idx];
+ if (joint_index != -1 && vert->dw[j].weight > 0.0f) {
+ jw[joint_index] += vert->dw[j].weight;
+ sumw += vert->dw[j].weight;
+ }
+ }
+ }
+
+ if (sumw > 0.0f) {
+ float invsumw = 1.0f / sumw;
+ vcounts.push_back(jw.size());
+ for (std::map<int, float>::iterator m = jw.begin(); m != jw.end(); ++m) {
+ joints.push_back((*m).first);
+ weights.push_back(invsumw * (*m).second);
+ }
+ }
+ else {
+ vcounts.push_back(0);
+#if 0
+ vcounts.push_back(1);
+ joints.push_back(-1);
+ weights.push_back(1.0f);
+#endif
+ }
+ }
+
+ if (oob_counter > 0) {
+ fprintf(stderr,
+ "Ignored %d Vertex weights which use index to non existing VGroup %zu.\n",
+ oob_counter,
+ joint_index_by_def_index.size());
+ }
+ }
+
+ std::string weights_source_id = add_weights_source(me, controller_id, weights);
+ add_joints_element(&ob->defbase, joints_source_id, inv_bind_mat_source_id);
+ add_vertex_weights_element(weights_source_id, joints_source_id, vcounts, joints);
+
+ BKE_id_free(NULL, me);
+
+ closeSkin();
+ closeController();
+}
+
+void ControllerExporter::export_morph_controller(Object *ob, Key *key)
+{
+ bool use_instantiation = this->export_settings.get_use_object_instantiation();
+ Mesh *me;
+
+ me = bc_get_mesh_copy(blender_context,
+ ob,
+ this->export_settings.get_export_mesh_type(),
+ this->export_settings.get_apply_modifiers(),
+ this->export_settings.get_triangulate());
+
+ std::string controller_name = id_name(ob) + "-morph";
+ std::string controller_id = get_controller_id(key, ob);
+
+ openMorph(
+ controller_id,
+ controller_name,
+ COLLADABU::URI(COLLADABU::Utils::EMPTY_STRING, get_geometry_id(ob, use_instantiation)));
+
+ std::string targets_id = add_morph_targets(key, ob);
+ std::string morph_weights_id = add_morph_weights(key, ob);
+
+ COLLADASW::TargetsElement targets(mSW);
+
+ COLLADASW::InputList &input = targets.getInputList();
+
+ input.push_back(COLLADASW::Input(
+ COLLADASW::InputSemantic::MORPH_TARGET, // constant declared in COLLADASWInputList.h
+ COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, targets_id)));
+ input.push_back(
+ COLLADASW::Input(COLLADASW::InputSemantic::MORPH_WEIGHT,
+ COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, morph_weights_id)));
+ targets.add();
+
+ BKE_id_free(NULL, me);
+
+ /* support for animations
+ * can also try the base element and param alternative */
+ add_weight_extras(key);
+ closeMorph();
+ closeController();
+}
+
+std::string ControllerExporter::add_morph_targets(Key *key, Object *ob)
+{
+ std::string source_id = translate_id(id_name(ob)) + TARGETS_SOURCE_ID_SUFFIX;
+
+ COLLADASW::IdRefSource source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(key->totkey - 1);
+ source.setAccessorStride(1);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("IDREF");
+
+ source.prepareToAppendValues();
+
+ KeyBlock *kb = (KeyBlock *)key->block.first;
+ /* skip the basis */
+ kb = kb->next;
+ for (; kb; kb = kb->next) {
+ std::string geom_id = get_geometry_id(ob, false) + "_morph_" + translate_id(kb->name);
+ source.appendValues(geom_id);
+ }
+
+ source.finish();
+
+ return source_id;
+}
+
+std::string ControllerExporter::add_morph_weights(Key *key, Object *ob)
+{
+ std::string source_id = translate_id(id_name(ob)) + WEIGHTS_SOURCE_ID_SUFFIX;
+
+ COLLADASW::FloatSourceF source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(key->totkey - 1);
+ source.setAccessorStride(1);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("MORPH_WEIGHT");
+
+ source.prepareToAppendValues();
+
+ KeyBlock *kb = (KeyBlock *)key->block.first;
+ /* skip the basis */
+ kb = kb->next;
+ for (; kb; kb = kb->next) {
+ float weight = kb->curval;
+ source.appendValues(weight);
+ }
+ source.finish();
+
+ return source_id;
+}
+
+/* Added to implement support for animations. */
+void ControllerExporter::add_weight_extras(Key *key)
+{
+ /* can also try the base element and param alternative */
+ COLLADASW::BaseExtraTechnique extra;
+
+ KeyBlock *kb = (KeyBlock *)key->block.first;
+ /* skip the basis */
+ kb = kb->next;
+ for (; kb; kb = kb->next) {
+ /* XXX why is the weight not used here and set to 0.0?
+ * float weight = kb->curval; */
+ extra.addExtraTechniqueParameter("KHR", "morph_weights", 0.000, "MORPH_WEIGHT_TO_TARGET");
+ }
+}
+
+void ControllerExporter::add_joints_element(ListBase *defbase,
+ const std::string &joints_source_id,
+ const std::string &inv_bind_mat_source_id)
+{
+ COLLADASW::JointsElement joints(mSW);
+ COLLADASW::InputList &input = joints.getInputList();
+
+ input.push_back(COLLADASW::Input(
+ COLLADASW::InputSemantic::JOINT, // constant declared in COLLADASWInputList.h
+ COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, joints_source_id)));
+ input.push_back(
+ COLLADASW::Input(COLLADASW::InputSemantic::BINDMATRIX,
+ COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, inv_bind_mat_source_id)));
+ joints.add();
+}
+
+void ControllerExporter::add_bind_shape_mat(Object *ob)
+{
+ double bind_mat[4][4];
+ float f_obmat[4][4];
+ BKE_object_matrix_local_get(ob, f_obmat);
+
+ if (export_settings.get_apply_global_orientation()) {
+ // do nothing, rotation is going to be applied to the Data
+ }
+ else {
+ bc_add_global_transform(f_obmat, export_settings.get_global_transform());
+ }
+
+ // UnitConverter::mat4_to_dae_double(bind_mat, ob->obmat);
+ UnitConverter::mat4_to_dae_double(bind_mat, f_obmat);
+ if (this->export_settings.get_limit_precision()) {
+ BCMatrix::sanitize(bind_mat, LIMITTED_PRECISION);
+ }
+
+ addBindShapeTransform(bind_mat);
+}
+
+std::string ControllerExporter::add_joints_source(Object *ob_arm,
+ ListBase *defbase,
+ const std::string &controller_id)
+{
+ std::string source_id = controller_id + JOINTS_SOURCE_ID_SUFFIX;
+
+ int totjoint = 0;
+ bDeformGroup *def;
+ for (def = (bDeformGroup *)defbase->first; def; def = def->next) {
+ if (is_bone_defgroup(ob_arm, def)) {
+ totjoint++;
+ }
+ }
+
+ COLLADASW::NameSource source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(totjoint);
+ source.setAccessorStride(1);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("JOINT");
+
+ source.prepareToAppendValues();
+
+ for (def = (bDeformGroup *)defbase->first; def; def = def->next) {
+ Bone *bone = get_bone_from_defgroup(ob_arm, def);
+ if (bone) {
+ source.appendValues(get_joint_sid(bone));
+ }
+ }
+
+ source.finish();
+
+ return source_id;
+}
+
+std::string ControllerExporter::add_inv_bind_mats_source(Object *ob_arm,
+ ListBase *defbase,
+ const std::string &controller_id)
+{
+ std::string source_id = controller_id + BIND_POSES_SOURCE_ID_SUFFIX;
+
+ int totjoint = 0;
+ for (bDeformGroup *def = (bDeformGroup *)defbase->first; def; def = def->next) {
+ if (is_bone_defgroup(ob_arm, def)) {
+ totjoint++;
+ }
+ }
+
+ COLLADASW::FloatSourceF source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(totjoint); // BLI_listbase_count(defbase));
+ source.setAccessorStride(16);
+
+ source.setParameterTypeName(&COLLADASW::CSWC::CSW_VALUE_TYPE_FLOAT4x4);
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("TRANSFORM");
+
+ source.prepareToAppendValues();
+
+ bPose *pose = ob_arm->pose;
+ bArmature *arm = (bArmature *)ob_arm->data;
+
+ int flag = arm->flag;
+
+ /* put armature in rest position */
+ if (!(arm->flag & ARM_RESTPOS)) {
+ Depsgraph *depsgraph = blender_context.get_depsgraph();
+ Scene *scene = blender_context.get_scene();
+
+ arm->flag |= ARM_RESTPOS;
+ BKE_pose_where_is(depsgraph, scene, ob_arm);
+ }
+
+ for (bDeformGroup *def = (bDeformGroup *)defbase->first; def; def = def->next) {
+ if (is_bone_defgroup(ob_arm, def)) {
+ bPoseChannel *pchan = BKE_pose_channel_find_name(pose, def->name);
+
+ float mat[4][4];
+ float world[4][4];
+ float inv_bind_mat[4][4];
+
+ float bind_mat[4][4]; /* derived from bone->arm_mat */
+
+ bool has_bindmat = bc_get_property_matrix(pchan->bone, "bind_mat", bind_mat);
+
+ if (!has_bindmat) {
+
+ /* Have no bind matrix stored, try old style <= Blender 2.78 */
+
+ bc_create_restpose_mat(
+ this->export_settings, pchan->bone, bind_mat, pchan->bone->arm_mat, true);
+
+ /* SL/OPEN_SIM COMPATIBILITY */
+ if (export_settings.get_open_sim()) {
+ float loc[3];
+ float rot[3] = {0, 0, 0};
+ float scale[3];
+ bc_decompose(bind_mat, loc, NULL, NULL, scale);
+
+ /* Only translations, no rotation vs armature */
+ loc_eulO_size_to_mat4(bind_mat, loc, rot, scale, 6);
+ }
+ }
+
+ /* make world-space matrix (bind_mat is armature-space) */
+ mul_m4_m4m4(world, ob_arm->obmat, bind_mat);
+
+ if (!has_bindmat) {
+ if (export_settings.get_apply_global_orientation()) {
+ bc_apply_global_transform(world, export_settings.get_global_transform());
+ }
+ }
+
+ invert_m4_m4(mat, world);
+ UnitConverter::mat4_to_dae(inv_bind_mat, mat);
+ if (this->export_settings.get_limit_precision()) {
+ BCMatrix::sanitize(inv_bind_mat, LIMITTED_PRECISION);
+ }
+ source.appendValues(inv_bind_mat);
+ }
+ }
+
+ /* back from rest position */
+ if (!(flag & ARM_RESTPOS)) {
+ Depsgraph *depsgraph = blender_context.get_depsgraph();
+ Scene *scene = blender_context.get_scene();
+ arm->flag = flag;
+ BKE_pose_where_is(depsgraph, scene, ob_arm);
+ }
+
+ source.finish();
+
+ return source_id;
+}
+
+Bone *ControllerExporter::get_bone_from_defgroup(Object *ob_arm, bDeformGroup *def)
+{
+ bPoseChannel *pchan = BKE_pose_channel_find_name(ob_arm->pose, def->name);
+ return pchan ? pchan->bone : NULL;
+}
+
+bool ControllerExporter::is_bone_defgroup(Object *ob_arm, bDeformGroup *def)
+{
+ return get_bone_from_defgroup(ob_arm, def) != NULL;
+}
+
+std::string ControllerExporter::add_weights_source(Mesh *me,
+ const std::string &controller_id,
+ const std::list<float> &weights)
+{
+ std::string source_id = controller_id + WEIGHTS_SOURCE_ID_SUFFIX;
+
+ COLLADASW::FloatSourceF source(mSW);
+ source.setId(source_id);
+ source.setArrayId(source_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(weights.size());
+ source.setAccessorStride(1);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("WEIGHT");
+
+ source.prepareToAppendValues();
+
+ for (std::list<float>::const_iterator i = weights.begin(); i != weights.end(); ++i) {
+ source.appendValues(*i);
+ }
+
+ source.finish();
+
+ return source_id;
+}
+
+void ControllerExporter::add_vertex_weights_element(const std::string &weights_source_id,
+ const std::string &joints_source_id,
+ const std::list<int> &vcounts,
+ const std::list<int> &joints)
+{
+ COLLADASW::VertexWeightsElement weightselem(mSW);
+ COLLADASW::InputList &input = weightselem.getInputList();
+
+ int offset = 0;
+ input.push_back(COLLADASW::Input(
+ COLLADASW::InputSemantic::JOINT, // constant declared in COLLADASWInputList.h
+ COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, joints_source_id),
+ offset++));
+ input.push_back(
+ COLLADASW::Input(COLLADASW::InputSemantic::WEIGHT,
+ COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, weights_source_id),
+ offset++));
+
+ weightselem.setCount(vcounts.size());
+
+ /* write number of deformers per vertex */
+ COLLADASW::PrimitivesBase::VCountList vcountlist;
+
+ vcountlist.resize(vcounts.size());
+ std::copy(vcounts.begin(), vcounts.end(), vcountlist.begin());
+
+ weightselem.prepareToAppendVCountValues();
+ weightselem.appendVertexCount(vcountlist);
+
+ weightselem.CloseVCountAndOpenVElement();
+
+ /* write deformer index - weight index pairs */
+ int weight_index = 0;
+ for (std::list<int>::const_iterator i = joints.begin(); i != joints.end(); ++i) {
+ weightselem.appendValues(*i, weight_index++);
+ }
+
+ weightselem.finish();
+}
diff --git a/source/blender/io/collada/ControllerExporter.h b/source/blender/io/collada/ControllerExporter.h
new file mode 100644
index 00000000000..ce2ed9fe453
--- /dev/null
+++ b/source/blender/io/collada/ControllerExporter.h
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __CONTROLLEREXPORTER_H__
+#define __CONTROLLEREXPORTER_H__
+
+#include <list>
+#include <string>
+//#include <vector>
+
+#include "COLLADASWStreamWriter.h"
+#include "COLLADASWLibraryControllers.h"
+#include "COLLADASWInstanceController.h"
+#include "COLLADASWInputList.h"
+#include "COLLADASWNode.h"
+#include "COLLADASWExtraTechnique.h"
+
+#include "DNA_armature_types.h"
+#include "DNA_listBase.h"
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+#include "DNA_constraint_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_key_types.h"
+
+#include "TransformWriter.h"
+#include "InstanceWriter.h"
+
+#include "ExportSettings.h"
+
+#include "BKE_key.h"
+
+class SceneExporter;
+
+class ControllerExporter : public COLLADASW::LibraryControllers,
+ protected TransformWriter,
+ protected InstanceWriter {
+ private:
+ BlenderContext &blender_context;
+ BCExportSettings export_settings;
+
+ public:
+ // XXX exporter writes wrong data for shared armatures. A separate
+ // controller should be written for each armature-mesh binding how do
+ // we make controller ids then?
+ ControllerExporter(BlenderContext &blender_context,
+ COLLADASW::StreamWriter *sw,
+ BCExportSettings &export_settings)
+ : COLLADASW::LibraryControllers(sw),
+ blender_context(blender_context),
+ export_settings(export_settings)
+ {
+ }
+
+ bool is_skinned_mesh(Object *ob);
+
+ bool add_instance_controller(Object *ob);
+
+ void export_controllers();
+
+ void operator()(Object *ob);
+
+ private:
+#if 0
+ std::vector<Object *> written_armatures;
+
+ bool already_written(Object *ob_arm);
+
+ void wrote(Object *ob_arm);
+
+ void find_objects_using_armature(Object *ob_arm, std::vector<Object *> &objects, Scene *sce);
+#endif
+
+ std::string get_controller_id(Object *ob_arm, Object *ob);
+
+ std::string get_controller_id(Key *key, Object *ob);
+
+ // ob should be of type OB_MESH
+ // both args are required
+ void export_skin_controller(Object *ob, Object *ob_arm);
+
+ void export_morph_controller(Object *ob, Key *key);
+
+ void add_joints_element(ListBase *defbase,
+ const std::string &joints_source_id,
+ const std::string &inv_bind_mat_source_id);
+
+ void add_bind_shape_mat(Object *ob);
+
+ std::string add_morph_targets(Key *key, Object *ob);
+
+ std::string add_morph_weights(Key *key, Object *ob);
+
+ void add_weight_extras(Key *key);
+
+ std::string add_joints_source(Object *ob_arm,
+ ListBase *defbase,
+ const std::string &controller_id);
+
+ std::string add_inv_bind_mats_source(Object *ob_arm,
+ ListBase *defbase,
+ const std::string &controller_id);
+
+ Bone *get_bone_from_defgroup(Object *ob_arm, bDeformGroup *def);
+
+ bool is_bone_defgroup(Object *ob_arm, bDeformGroup *def);
+
+ std::string add_weights_source(Mesh *me,
+ const std::string &controller_id,
+ const std::list<float> &weights);
+
+ void add_vertex_weights_element(const std::string &weights_source_id,
+ const std::string &joints_source_id,
+ const std::list<int> &vcount,
+ const std::list<int> &joints);
+
+ void write_bone_URLs(COLLADASW::InstanceController &ins, Object *ob_arm, Bone *bone);
+};
+
+#endif
diff --git a/source/blender/io/collada/DocumentExporter.cpp b/source/blender/io/collada/DocumentExporter.cpp
new file mode 100644
index 00000000000..24a960ab287
--- /dev/null
+++ b/source/blender/io/collada/DocumentExporter.cpp
@@ -0,0 +1,346 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+#include <vector>
+#include <algorithm> // std::find
+
+#include "COLLADASWCamera.h"
+#include "COLLADASWAsset.h"
+#include "COLLADASWLibraryVisualScenes.h"
+#include "COLLADASWNode.h"
+#include "COLLADASWSource.h"
+#include "COLLADASWInstanceGeometry.h"
+#include "COLLADASWInputList.h"
+#include "COLLADASWPrimitves.h"
+#include "COLLADASWVertices.h"
+#include "COLLADASWLibraryAnimations.h"
+#include "COLLADASWLibraryImages.h"
+#include "COLLADASWLibraryEffects.h"
+#include "COLLADASWImage.h"
+#include "COLLADASWEffectProfile.h"
+#include "COLLADASWColorOrTexture.h"
+#include "COLLADASWParamTemplate.h"
+#include "COLLADASWParamBase.h"
+#include "COLLADASWSurfaceInitOption.h"
+#include "COLLADASWSampler.h"
+#include "COLLADASWScene.h"
+#include "COLLADASWTechnique.h"
+#include "COLLADASWTexture.h"
+#include "COLLADASWLibraryMaterials.h"
+#include "COLLADASWBindMaterial.h"
+#include "COLLADASWInstanceCamera.h"
+#include "COLLADASWInstanceLight.h"
+#include "COLLADASWConstants.h"
+#include "COLLADASWLibraryControllers.h"
+#include "COLLADASWInstanceController.h"
+#include "COLLADASWInstanceNode.h"
+#include "COLLADASWBaseInputElement.h"
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "DNA_scene_types.h"
+#include "DNA_object_types.h"
+#include "DNA_collection_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_image_types.h"
+#include "DNA_material_types.h"
+#include "DNA_anim_types.h"
+#include "DNA_action_types.h"
+#include "DNA_curve_types.h"
+#include "DNA_armature_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_userdef_types.h"
+
+#include "BLI_path_util.h"
+#include "BLI_fileops.h"
+#include "BLI_math.h"
+#include "BLI_string.h"
+#include "BLI_listbase.h"
+#include "BLI_utildefines.h"
+
+#include "BKE_action.h" // pose functions
+#include "BKE_animsys.h"
+#include "BKE_armature.h"
+#include "BKE_blender_version.h"
+#include "BKE_customdata.h"
+#include "BKE_fcurve.h"
+#include "BKE_global.h"
+#include "BKE_image.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+#include "BKE_object.h"
+#include "BKE_scene.h"
+#include "BKE_appdir.h"
+
+#include "ED_keyframing.h"
+#ifdef WITH_BUILDINFO
+extern char build_commit_date[];
+extern char build_commit_time[];
+extern char build_hash[];
+#endif
+
+#include "RNA_access.h"
+}
+
+#include "collada_internal.h"
+#include "collada_utils.h"
+#include "DocumentExporter.h"
+
+extern bool bc_has_object_type(LinkNode *export_set, short obtype);
+
+// can probably go after refactor is complete
+#include "InstanceWriter.h"
+#include "TransformWriter.h"
+
+#include "SceneExporter.h"
+#include "ArmatureExporter.h"
+#include "AnimationExporter.h"
+#include "CameraExporter.h"
+#include "ControllerExporter.h"
+#include "EffectExporter.h"
+#include "GeometryExporter.h"
+#include "ImageExporter.h"
+#include "LightExporter.h"
+#include "MaterialExporter.h"
+
+#include <errno.h>
+
+char *bc_CustomData_get_layer_name(const struct CustomData *data, int type, int n)
+{
+ int layer_index = CustomData_get_layer_index(data, type);
+ if (layer_index < 0) {
+ return NULL;
+ }
+
+ return data->layers[layer_index + n].name;
+}
+
+char *bc_CustomData_get_active_layer_name(const CustomData *data, int type)
+{
+ /* get the layer index of the active layer of type */
+ int layer_index = CustomData_get_active_layer_index(data, type);
+ if (layer_index < 0) {
+ return NULL;
+ }
+
+ return data->layers[layer_index].name;
+}
+
+DocumentExporter::DocumentExporter(BlenderContext &blender_context, ExportSettings *exportSettings)
+ : blender_context(blender_context),
+ export_settings(BCExportSettings(exportSettings, blender_context))
+{
+}
+
+static COLLADABU::NativeString make_temp_filepath(const char *name, const char *extension)
+{
+ char tempfile[FILE_MAX];
+ const char *tempdir = BKE_tempdir_session();
+
+ if (name == NULL) {
+ name = "untitled";
+ }
+
+ BLI_make_file_string(NULL, tempfile, tempdir, name);
+
+ if (extension) {
+ BLI_path_extension_ensure(tempfile, FILE_MAX, extension);
+ }
+
+ COLLADABU::NativeString native_filename = COLLADABU::NativeString(
+ tempfile, COLLADABU::NativeString::ENCODING_UTF8);
+ return native_filename;
+}
+
+// TODO: it would be better to instantiate animations rather than create a new one per object
+// COLLADA allows this through multiple <channel>s in <animation>.
+// For this to work, we need to know objects that use a certain action.
+
+int DocumentExporter::exportCurrentScene()
+{
+ Scene *sce = blender_context.get_scene();
+ bContext *C = blender_context.get_context();
+
+ PointerRNA sceneptr, unit_settings;
+ PropertyRNA *system; /* unused , *scale; */
+
+ clear_global_id_map();
+
+ COLLADABU::NativeString native_filename = make_temp_filepath(NULL, ".dae");
+ COLLADASW::StreamWriter *writer = new COLLADASW::StreamWriter(native_filename);
+
+ // open <collada>
+ writer->startDocument();
+
+ // <asset>
+ COLLADASW::Asset asset(writer);
+
+ RNA_id_pointer_create(&(sce->id), &sceneptr);
+ unit_settings = RNA_pointer_get(&sceneptr, "unit_settings");
+ system = RNA_struct_find_property(&unit_settings, "system");
+ // scale = RNA_struct_find_property(&unit_settings, "scale_length");
+
+ std::string unitname = "meter";
+ float linearmeasure = RNA_float_get(&unit_settings, "scale_length");
+
+ switch (RNA_property_enum_get(&unit_settings, system)) {
+ case USER_UNIT_NONE:
+ case USER_UNIT_METRIC:
+ if (linearmeasure == 0.001f) {
+ unitname = "millimeter";
+ }
+ else if (linearmeasure == 0.01f) {
+ unitname = "centimeter";
+ }
+ else if (linearmeasure == 0.1f) {
+ unitname = "decimeter";
+ }
+ else if (linearmeasure == 1.0f) {
+ unitname = "meter";
+ }
+ else if (linearmeasure == 1000.0f) {
+ unitname = "kilometer";
+ }
+ break;
+ case USER_UNIT_IMPERIAL:
+ if (linearmeasure == 0.0254f) {
+ unitname = "inch";
+ }
+ else if (linearmeasure == 0.3048f) {
+ unitname = "foot";
+ }
+ else if (linearmeasure == 0.9144f) {
+ unitname = "yard";
+ }
+ break;
+ default:
+ break;
+ }
+
+ asset.setUnit(unitname, linearmeasure);
+ asset.setUpAxisType(COLLADASW::Asset::Z_UP);
+ asset.getContributor().mAuthor = "Blender User";
+ char version_buf[128];
+#ifdef WITH_BUILDINFO
+ BLI_snprintf(version_buf,
+ sizeof(version_buf),
+ "Blender %d.%02d.%d commit date:%s, commit time:%s, hash:%s",
+ BLENDER_VERSION / 100,
+ BLENDER_VERSION % 100,
+ BLENDER_SUBVERSION,
+ build_commit_date,
+ build_commit_time,
+ build_hash);
+#else
+ BLI_snprintf(version_buf,
+ sizeof(version_buf),
+ "Blender %d.%02d.%d",
+ BLENDER_VERSION / 100,
+ BLENDER_VERSION % 100,
+ BLENDER_SUBVERSION);
+#endif
+ asset.getContributor().mAuthoringTool = version_buf;
+ asset.add();
+
+ LinkNode *export_set = this->export_settings.get_export_set();
+ // <library_cameras>
+ if (bc_has_object_type(export_set, OB_CAMERA)) {
+ CamerasExporter ce(writer, this->export_settings);
+ ce.exportCameras(sce);
+ }
+
+ // <library_lights>
+ if (bc_has_object_type(export_set, OB_LAMP)) {
+ LightsExporter le(writer, this->export_settings);
+ le.exportLights(sce);
+ }
+
+ // <library_effects>
+ EffectsExporter ee(writer, this->export_settings, key_image_map);
+ ee.exportEffects(C, sce);
+
+ // <library_images>
+ ImagesExporter ie(writer, this->export_settings, key_image_map);
+ ie.exportImages(sce);
+
+ // <library_materials>
+ MaterialsExporter me(writer, this->export_settings);
+ me.exportMaterials(sce);
+
+ // <library_geometries>
+ if (bc_has_object_type(export_set, OB_MESH)) {
+ GeometryExporter ge(blender_context, writer, this->export_settings);
+ ge.exportGeom();
+ }
+
+ // <library_controllers>
+ ArmatureExporter arm_exporter(blender_context, writer, this->export_settings);
+ ControllerExporter controller_exporter(blender_context, writer, this->export_settings);
+ if (bc_has_object_type(export_set, OB_ARMATURE) ||
+ this->export_settings.get_include_shapekeys()) {
+ controller_exporter.export_controllers();
+ }
+
+ // <library_visual_scenes>
+
+ SceneExporter se(blender_context, writer, &arm_exporter, this->export_settings);
+
+ if (this->export_settings.get_include_animations()) {
+ // <library_animations>
+ AnimationExporter ae(writer, this->export_settings);
+ ae.exportAnimations();
+ }
+
+ se.exportScene();
+
+ // <scene>
+ std::string scene_name(translate_id(id_name(sce)));
+ COLLADASW::Scene scene(writer, COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, scene_name));
+ scene.add();
+
+ // close <Collada>
+ writer->endDocument();
+ delete writer;
+
+ // Finally move the created document into place
+ fprintf(stdout, "Collada export to: %s\n", this->export_settings.get_filepath());
+ int status = BLI_rename(native_filename.c_str(), this->export_settings.get_filepath());
+ if (status != 0) {
+ status = BLI_copy(native_filename.c_str(), this->export_settings.get_filepath());
+ BLI_delete(native_filename.c_str(), false, false);
+ }
+ return status;
+}
+
+void DocumentExporter::exportScenes(const char *filename)
+{
+}
+
+/*
+ * NOTES:
+ *
+ * AnimationExporter::sample_animation enables all curves on armature, this is undesirable for a
+ * user
+ */
diff --git a/source/blender/io/collada/DocumentExporter.h b/source/blender/io/collada/DocumentExporter.h
new file mode 100644
index 00000000000..70722ae601e
--- /dev/null
+++ b/source/blender/io/collada/DocumentExporter.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __DOCUMENTEXPORTER_H__
+#define __DOCUMENTEXPORTER_H__
+
+#include "collada.h"
+#include "collada_utils.h"
+#include "BlenderContext.h"
+
+extern "C" {
+#include "DNA_customdata_types.h"
+}
+
+class DocumentExporter {
+ public:
+ DocumentExporter(BlenderContext &blender_context, ExportSettings *export_settings);
+ int exportCurrentScene();
+ void exportScenes(const char *filename);
+
+ private:
+ BlenderContext &blender_context;
+ BCExportSettings export_settings;
+ KeyImageMap key_image_map;
+};
+
+#endif
diff --git a/source/blender/io/collada/DocumentImporter.cpp b/source/blender/io/collada/DocumentImporter.cpp
new file mode 100644
index 00000000000..9b66ff429e1
--- /dev/null
+++ b/source/blender/io/collada/DocumentImporter.cpp
@@ -0,0 +1,1265 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+/* TODO:
+ * * name imported objects
+ * * import object rotation as euler */
+
+#include <string>
+#include <map>
+#include <algorithm> // sort()
+
+#include "COLLADAFWRoot.h"
+#include "COLLADAFWStableHeaders.h"
+#include "COLLADAFWColorOrTexture.h"
+#include "COLLADAFWIndexList.h"
+#include "COLLADAFWMeshPrimitiveWithFaceVertexCount.h"
+#include "COLLADAFWPolygons.h"
+#include "COLLADAFWSampler.h"
+#include "COLLADAFWTypes.h"
+#include "COLLADAFWVisualScene.h"
+#include "COLLADAFWArrayPrimitiveType.h"
+#include "COLLADAFWLibraryNodes.h"
+#include "COLLADAFWCamera.h"
+#include "COLLADAFWLight.h"
+
+#include "COLLADASaxFWLLoader.h"
+#include "COLLADASaxFWLIExtraDataCallbackHandler.h"
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_string.h"
+#include "BLI_utildefines.h"
+#include "BLI_fileops.h"
+
+#include "BKE_camera.h"
+#include "BKE_collection.h"
+#include "BKE_fcurve.h"
+#include "BKE_global.h"
+#include "BKE_image.h"
+#include "BKE_layer.h"
+#include "BKE_light.h"
+#include "BKE_lib_id.h"
+#include "BKE_material.h"
+#include "BKE_scene.h"
+
+#include "BLI_path_util.h"
+
+#include "DNA_camera_types.h"
+#include "DNA_light_types.h"
+
+#include "RNA_access.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+}
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+
+#include "ExtraHandler.h"
+#include "ErrorHandler.h"
+#include "DocumentImporter.h"
+#include "TransformReader.h"
+
+#include "collada_internal.h"
+#include "collada_utils.h"
+#include "Materials.h"
+
+/*
+ * COLLADA Importer limitations:
+ * - no multiple scene import, all objects are added to active scene
+ */
+
+// #define COLLADA_DEBUG
+// creates empties for each imported bone on layer 2, for debugging
+// #define ARMATURE_TEST
+
+DocumentImporter::DocumentImporter(bContext *C, const ImportSettings *import_settings)
+ : import_settings(import_settings),
+ mImportStage(Fetching_Scene_data),
+ mContext(C),
+ view_layer(CTX_data_view_layer(mContext)),
+ armature_importer(&unit_converter,
+ &mesh_importer,
+ CTX_data_main(C),
+ CTX_data_scene(C),
+ view_layer,
+ import_settings),
+ mesh_importer(
+ &unit_converter, &armature_importer, CTX_data_main(C), CTX_data_scene(C), view_layer),
+ anim_importer(C, &unit_converter, &armature_importer, CTX_data_scene(C))
+{
+}
+
+DocumentImporter::~DocumentImporter()
+{
+ TagsMap::iterator etit;
+ etit = uid_tags_map.begin();
+ while (etit != uid_tags_map.end()) {
+ delete etit->second;
+ etit++;
+ }
+}
+
+bool DocumentImporter::import()
+{
+ ErrorHandler errorHandler;
+ COLLADASaxFWL::Loader loader(&errorHandler);
+ COLLADAFW::Root root(&loader, this);
+ ExtraHandler *ehandler = new ExtraHandler(this, &(this->anim_importer));
+
+ loader.registerExtraDataCallbackHandler(ehandler);
+
+ /* deselect all to select new objects */
+ BKE_view_layer_base_deselect_all(view_layer);
+
+ std::string mFilename = std::string(this->import_settings->filepath);
+ const std::string encodedFilename = bc_url_encode(mFilename);
+ if (!root.loadDocument(encodedFilename)) {
+ fprintf(stderr, "COLLADAFW::Root::loadDocument() returned false on 1st pass\n");
+ delete ehandler;
+ return false;
+ }
+
+ if (errorHandler.hasError()) {
+ delete ehandler;
+ return false;
+ }
+
+ /** TODO set up scene graph and such here */
+ mImportStage = Fetching_Controller_data;
+ COLLADASaxFWL::Loader loader2;
+ COLLADAFW::Root root2(&loader2, this);
+
+ if (!root2.loadDocument(encodedFilename)) {
+ fprintf(stderr, "COLLADAFW::Root::loadDocument() returned false on 2nd pass\n");
+ delete ehandler;
+ return false;
+ }
+
+ delete ehandler;
+
+ return true;
+}
+
+void DocumentImporter::cancel(const COLLADAFW::String &errorMessage)
+{
+ /* TODO: if possible show error info
+ *
+ * Should we get rid of invisible Meshes that were created so far
+ * or maybe create objects at coordinate space origin?
+ *
+ * The latter sounds better. */
+}
+
+void DocumentImporter::start()
+{
+}
+
+void DocumentImporter::finish()
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return;
+ }
+
+ Main *bmain = CTX_data_main(mContext);
+ /* TODO: create a new scene except the selected <visual_scene> -
+ * use current blender scene for it */
+ Scene *sce = CTX_data_scene(mContext);
+ unit_converter.calculate_scale(*sce);
+
+ std::vector<Object *> *objects_to_scale = new std::vector<Object *>();
+
+ /** TODO Break up and put into 2-pass parsing of DAE */
+ std::vector<const COLLADAFW::VisualScene *>::iterator sit;
+ for (sit = vscenes.begin(); sit != vscenes.end(); sit++) {
+ PointerRNA sceneptr, unit_settings;
+ PropertyRNA *system, *scale;
+
+ /* for scene unit settings: system, scale_length */
+
+ RNA_id_pointer_create(&sce->id, &sceneptr);
+ unit_settings = RNA_pointer_get(&sceneptr, "unit_settings");
+ system = RNA_struct_find_property(&unit_settings, "system");
+ scale = RNA_struct_find_property(&unit_settings, "scale_length");
+
+ if (this->import_settings->import_units) {
+
+ switch (unit_converter.isMetricSystem()) {
+ case UnitConverter::Metric:
+ RNA_property_enum_set(&unit_settings, system, USER_UNIT_METRIC);
+ break;
+ case UnitConverter::Imperial:
+ RNA_property_enum_set(&unit_settings, system, USER_UNIT_IMPERIAL);
+ break;
+ default:
+ RNA_property_enum_set(&unit_settings, system, USER_UNIT_NONE);
+ break;
+ }
+ float unit_factor = unit_converter.getLinearMeter();
+ RNA_property_float_set(&unit_settings, scale, unit_factor);
+ fprintf(stdout, "Collada: Adjusting Blender units to Importset units: %f.\n", unit_factor);
+ }
+
+ /* Write nodes to scene */
+ fprintf(stderr, "+-- Import Scene --------\n");
+ const COLLADAFW::NodePointerArray &roots = (*sit)->getRootNodes();
+ for (unsigned int i = 0; i < roots.getCount(); i++) {
+ std::vector<Object *> *objects_done = write_node(roots[i], NULL, sce, NULL, false);
+ objects_to_scale->insert(
+ objects_to_scale->end(), objects_done->begin(), objects_done->end());
+ delete objects_done;
+ }
+ }
+
+ mesh_importer.optimize_material_assignements();
+
+ armature_importer.set_tags_map(this->uid_tags_map);
+ armature_importer.make_armatures(mContext, *objects_to_scale);
+ armature_importer.make_shape_keys(mContext);
+
+#if 0
+ armature_importer.fix_animation();
+#endif
+
+ for (std::vector<const COLLADAFW::VisualScene *>::iterator vsit = vscenes.begin();
+ vsit != vscenes.end();
+ vsit++) {
+ const COLLADAFW::NodePointerArray &roots = (*vsit)->getRootNodes();
+
+ for (unsigned int i = 0; i < roots.getCount(); i++) {
+ translate_anim_recursive(roots[i], NULL, NULL);
+ }
+ }
+
+ if (libnode_ob.size()) {
+
+ fprintf(stderr, "| Cleanup: free %d library nodes\n", (int)libnode_ob.size());
+ /* free all library_nodes */
+ std::vector<Object *>::iterator it;
+ for (it = libnode_ob.begin(); it != libnode_ob.end(); it++) {
+ Object *ob = *it;
+ BKE_scene_collections_object_remove(bmain, sce, ob, true);
+ }
+ libnode_ob.clear();
+ }
+
+ bc_match_scale(objects_to_scale, unit_converter, !this->import_settings->import_units);
+
+ delete objects_to_scale;
+
+ /* update scene */
+ DEG_id_tag_update(&sce->id, ID_RECALC_COPY_ON_WRITE);
+ DEG_relations_tag_update(bmain);
+ WM_event_add_notifier(mContext, NC_OBJECT | ND_TRANSFORM, NULL);
+}
+
+void DocumentImporter::translate_anim_recursive(COLLADAFW::Node *node,
+ COLLADAFW::Node *par = NULL,
+ Object *parob = NULL)
+{
+ /* The split in #29246, rootmap must point at actual root when
+ * calculating bones in apply_curves_as_matrix. - actual root is the root node.
+ * This has to do with inverse bind poses being world space
+ * (the sources for skinned bones' restposes) and the way
+ * non-skinning nodes have their "restpose" recursively calculated.
+ * XXX TODO: design issue, how to support unrelated joints taking
+ * part in skinning. */
+ if (par) { // && par->getType() == COLLADAFW::Node::JOINT) {
+ /* par is root if there's no corresp. key in root_map */
+ if (root_map.find(par->getUniqueId()) == root_map.end()) {
+ root_map[node->getUniqueId()] = node;
+ }
+ else {
+ root_map[node->getUniqueId()] = root_map[par->getUniqueId()];
+ }
+ }
+
+#if 0
+ COLLADAFW::Transformation::TransformationType types[] = {
+ COLLADAFW::Transformation::ROTATE,
+ COLLADAFW::Transformation::SCALE,
+ COLLADAFW::Transformation::TRANSLATE,
+ COLLADAFW::Transformation::MATRIX,
+ };
+
+ Object *ob;
+#endif
+ unsigned int i;
+
+ if (node->getType() == COLLADAFW::Node::JOINT && par == NULL) {
+ /* For Skeletons without root node we have to simulate the
+ * root node here and recursively enter the same function
+ * XXX: maybe this can be made more elegant. */
+ translate_anim_recursive(node, node, parob);
+ }
+ else {
+ anim_importer.translate_Animations(
+ node, root_map, object_map, FW_object_map, uid_material_map);
+ COLLADAFW::NodePointerArray &children = node->getChildNodes();
+ for (i = 0; i < children.getCount(); i++) {
+ translate_anim_recursive(children[i], node, NULL);
+ }
+ }
+}
+
+/**
+ * If the imported file was made with Blender, return the Blender version used,
+ * otherwise return an empty std::string
+ */
+std::string DocumentImporter::get_import_version(const COLLADAFW::FileInfo *asset)
+{
+ const char AUTORING_TOOL[] = "authoring_tool";
+ const std::string BLENDER("Blender ");
+ const COLLADAFW::FileInfo::ValuePairPointerArray &valuePairs = asset->getValuePairArray();
+ for (size_t i = 0, count = valuePairs.getCount(); i < count; i++) {
+ const COLLADAFW::FileInfo::ValuePair *valuePair = valuePairs[i];
+ const COLLADAFW::String &key = valuePair->first;
+ const COLLADAFW::String &value = valuePair->second;
+ if (key == AUTORING_TOOL) {
+ if (value.compare(0, BLENDER.length(), BLENDER) == 0) {
+ /* Was made with Blender, now get version string */
+ std::string v = value.substr(BLENDER.length());
+ std::string::size_type n = v.find(" ");
+ if (n > 0) {
+ return v.substr(0, n);
+ }
+ }
+ }
+ }
+ return "";
+}
+
+/**
+ * When this method is called, the writer must write the global document asset.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeGlobalAsset(const COLLADAFW::FileInfo *asset)
+{
+ unit_converter.read_asset(asset);
+ import_from_version = get_import_version(asset);
+ anim_importer.set_import_from_version(import_from_version);
+ return true;
+}
+
+/**
+ * When this method is called, the writer must write the scene.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeScene(const COLLADAFW::Scene *scene)
+{
+ /* XXX could store the scene id, but do nothing for now */
+ return true;
+}
+Object *DocumentImporter::create_camera_object(COLLADAFW::InstanceCamera *camera, Scene *sce)
+{
+ const COLLADAFW::UniqueId &cam_uid = camera->getInstanciatedObjectId();
+ if (uid_camera_map.find(cam_uid) == uid_camera_map.end()) {
+ // fprintf(stderr, "Couldn't find camera by UID.\n");
+ return NULL;
+ }
+
+ Main *bmain = CTX_data_main(mContext);
+ Object *ob = bc_add_object(bmain, sce, view_layer, OB_CAMERA, NULL);
+ Camera *cam = uid_camera_map[cam_uid];
+ Camera *old_cam = (Camera *)ob->data;
+ ob->data = cam;
+ BKE_id_free_us(bmain, old_cam);
+ return ob;
+}
+
+Object *DocumentImporter::create_light_object(COLLADAFW::InstanceLight *lamp, Scene *sce)
+{
+ const COLLADAFW::UniqueId &lamp_uid = lamp->getInstanciatedObjectId();
+ if (uid_light_map.find(lamp_uid) == uid_light_map.end()) {
+ fprintf(stderr, "Couldn't find light by UID.\n");
+ return NULL;
+ }
+
+ Main *bmain = CTX_data_main(mContext);
+ Object *ob = bc_add_object(bmain, sce, view_layer, OB_LAMP, NULL);
+ Light *la = uid_light_map[lamp_uid];
+ Light *old_light = (Light *)ob->data;
+ ob->data = la;
+ BKE_id_free_us(bmain, old_light);
+ return ob;
+}
+
+Object *DocumentImporter::create_instance_node(Object *source_ob,
+ COLLADAFW::Node *source_node,
+ COLLADAFW::Node *instance_node,
+ Scene *sce,
+ bool is_library_node)
+{
+ // fprintf(stderr, "create <instance_node> under node id=%s from node id=%s\n", instance_node ?
+ // instance_node->getOriginalId().c_str() : NULL, source_node ?
+ // source_node->getOriginalId().c_str() : NULL);
+
+ Main *bmain = CTX_data_main(mContext);
+ Object *obn = BKE_object_copy(bmain, source_ob);
+ DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
+ BKE_collection_object_add_from(bmain, sce, source_ob, obn);
+
+ if (instance_node) {
+ anim_importer.read_node_transform(instance_node, obn);
+ /* if we also have a source_node (always ;), take its
+ * transformation matrix and apply it to the newly instantiated
+ * object to account for node hierarchy transforms in
+ * .dae */
+ if (source_node) {
+ COLLADABU::Math::Matrix4 mat4 = source_node->getTransformationMatrix();
+ COLLADABU::Math::Matrix4 bmat4 =
+ mat4.transpose(); // transpose to get blender row-major order
+ float mat[4][4];
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ mat[i][j] = bmat4[i][j];
+ }
+ }
+ /* calc new matrix and apply */
+ mul_m4_m4m4(obn->obmat, obn->obmat, mat);
+ BKE_object_apply_mat4(obn, obn->obmat, 0, 0);
+ }
+ }
+ else {
+ anim_importer.read_node_transform(source_node, obn);
+ }
+
+ /*DAG_relations_tag_update(CTX_data_main(mContext));*/
+
+ COLLADAFW::NodePointerArray &children = source_node->getChildNodes();
+ if (children.getCount()) {
+ for (unsigned int i = 0; i < children.getCount(); i++) {
+ COLLADAFW::Node *child_node = children[i];
+ const COLLADAFW::UniqueId &child_id = child_node->getUniqueId();
+ if (object_map.find(child_id) == object_map.end()) {
+ continue;
+ }
+ COLLADAFW::InstanceNodePointerArray &inodes = child_node->getInstanceNodes();
+ Object *new_child = NULL;
+ if (inodes.getCount()) { // \todo loop through instance nodes
+ const COLLADAFW::UniqueId &id = inodes[0]->getInstanciatedObjectId();
+ fprintf(stderr, "Doing %d child nodes\n", (int)node_map.count(id));
+ new_child = create_instance_node(
+ object_map.find(id)->second, node_map[id], child_node, sce, is_library_node);
+ }
+ else {
+ new_child = create_instance_node(
+ object_map.find(child_id)->second, child_node, NULL, sce, is_library_node);
+ }
+ bc_set_parent(new_child, obn, mContext, true);
+
+ if (is_library_node) {
+ libnode_ob.push_back(new_child);
+ }
+ }
+ }
+
+ return obn;
+}
+
+/* to create constraints off node <extra> tags. Assumes only constraint data in
+ * current <extra> with blender profile. */
+void DocumentImporter::create_constraints(ExtraTags *et, Object *ob)
+{
+ if (et && et->isProfile("blender")) {
+ std::string name;
+ short type = 0;
+ et->setData("type", &type);
+ BKE_constraint_add_for_object(ob, "Test_con", type);
+ }
+}
+
+void DocumentImporter::report_unknown_reference(const COLLADAFW::Node &node,
+ const std::string object_type)
+{
+ std::string id = node.getOriginalId();
+ std::string name = node.getName();
+ fprintf(stderr,
+ "error: node id=\"%s\", name=\"%s\" refers to an undefined %s.\n",
+ id.c_str(),
+ name.c_str(),
+ object_type.c_str());
+}
+
+std::vector<Object *> *DocumentImporter::write_node(COLLADAFW::Node *node,
+ COLLADAFW::Node *parent_node,
+ Scene *sce,
+ Object *par,
+ bool is_library_node)
+{
+ Main *bmain = CTX_data_main(mContext);
+ Object *ob = NULL;
+ bool is_joint = node->getType() == COLLADAFW::Node::JOINT;
+ bool read_transform = true;
+ std::string id = node->getOriginalId();
+ std::string name = node->getName();
+
+ /* if node has child nodes write them */
+ COLLADAFW::NodePointerArray &child_nodes = node->getChildNodes();
+
+ std::vector<Object *> *objects_done = new std::vector<Object *>();
+ std::vector<Object *> *root_objects = new std::vector<Object *>();
+
+ fprintf(
+ stderr, "| %s id='%s', name='%s'\n", is_joint ? "JOINT" : "NODE ", id.c_str(), name.c_str());
+
+ if (is_joint) {
+ if (parent_node == NULL && !is_library_node) {
+ /* A Joint on root level is a skeleton without root node.
+ * Here we add the armature "on the fly": */
+ par = bc_add_object(bmain, sce, view_layer, OB_ARMATURE, std::string("Armature").c_str());
+ objects_done->push_back(par);
+ root_objects->push_back(par);
+ object_map.insert(std::pair<COLLADAFW::UniqueId, Object *>(node->getUniqueId(), par));
+ node_map[node->getUniqueId()] = node;
+ }
+ if (parent_node == NULL || parent_node->getType() != COLLADAFW::Node::JOINT) {
+ armature_importer.add_root_joint(node, par);
+ }
+
+ if (parent_node == NULL) {
+ /* for skeletons without root node all has been done above.
+ * Skeletons with root node are handled further down. */
+ goto finally;
+ }
+ }
+ else {
+ COLLADAFW::InstanceGeometryPointerArray &geom = node->getInstanceGeometries();
+ COLLADAFW::InstanceCameraPointerArray &camera = node->getInstanceCameras();
+ COLLADAFW::InstanceLightPointerArray &lamp = node->getInstanceLights();
+ COLLADAFW::InstanceControllerPointerArray &controller = node->getInstanceControllers();
+ COLLADAFW::InstanceNodePointerArray &inst_node = node->getInstanceNodes();
+ size_t geom_done = 0;
+ size_t camera_done = 0;
+ size_t lamp_done = 0;
+ size_t controller_done = 0;
+ size_t inst_done = 0;
+
+ /* XXX linking object with the first <instance_geometry>, though a node may have more of
+ * them... maybe join multiple <instance_...> meshes into 1, and link object with it? not
+ * sure... <instance_geometry> */
+ while (geom_done < geom.getCount()) {
+ ob = mesh_importer.create_mesh_object(node, geom[geom_done], false, uid_material_map);
+ if (ob == NULL) {
+ report_unknown_reference(*node, "instance_mesh");
+ }
+ else {
+ objects_done->push_back(ob);
+ if (parent_node == NULL) {
+ root_objects->push_back(ob);
+ }
+ }
+ geom_done++;
+ }
+ while (camera_done < camera.getCount()) {
+ ob = create_camera_object(camera[camera_done], sce);
+ if (ob == NULL) {
+ report_unknown_reference(*node, "instance_camera");
+ }
+ else {
+ objects_done->push_back(ob);
+ if (parent_node == NULL) {
+ root_objects->push_back(ob);
+ }
+ }
+ camera_done++;
+ }
+ while (lamp_done < lamp.getCount()) {
+ ob = create_light_object(lamp[lamp_done], sce);
+ if (ob == NULL) {
+ report_unknown_reference(*node, "instance_light");
+ }
+ else {
+ objects_done->push_back(ob);
+ if (parent_node == NULL) {
+ root_objects->push_back(ob);
+ }
+ }
+ lamp_done++;
+ }
+ while (controller_done < controller.getCount()) {
+ COLLADAFW::InstanceGeometry *geometry = (COLLADAFW::InstanceGeometry *)
+ controller[controller_done];
+ ob = mesh_importer.create_mesh_object(node, geometry, true, uid_material_map);
+ if (ob == NULL) {
+ report_unknown_reference(*node, "instance_controller");
+ }
+ else {
+ objects_done->push_back(ob);
+ if (parent_node == NULL) {
+ root_objects->push_back(ob);
+ }
+ }
+ controller_done++;
+ }
+ /* XXX instance_node is not supported yet */
+ while (inst_done < inst_node.getCount()) {
+ const COLLADAFW::UniqueId &node_id = inst_node[inst_done]->getInstanciatedObjectId();
+ if (object_map.find(node_id) == object_map.end()) {
+ fprintf(stderr,
+ "Cannot find object for node referenced by <instance_node name=\"%s\">.\n",
+ inst_node[inst_done]->getName().c_str());
+ ob = NULL;
+ }
+ else {
+ std::pair<std::multimap<COLLADAFW::UniqueId, Object *>::iterator,
+ std::multimap<COLLADAFW::UniqueId, Object *>::iterator>
+ pair_iter = object_map.equal_range(node_id);
+ for (std::multimap<COLLADAFW::UniqueId, Object *>::iterator it2 = pair_iter.first;
+ it2 != pair_iter.second;
+ it2++) {
+ Object *source_ob = (Object *)it2->second;
+ COLLADAFW::Node *source_node = node_map[node_id];
+ ob = create_instance_node(source_ob, source_node, node, sce, is_library_node);
+ objects_done->push_back(ob);
+ if (parent_node == NULL) {
+ root_objects->push_back(ob);
+ }
+ }
+ }
+ inst_done++;
+
+ read_transform = false;
+ }
+
+ /* if node is empty - create empty object
+ * XXX empty node may not mean it is empty object, not sure about this */
+ if ((geom_done + camera_done + lamp_done + controller_done + inst_done) < 1) {
+ /* Check if Object is armature, by checking if immediate child is a JOINT node. */
+ if (is_armature(node)) {
+ ob = bc_add_object(bmain, sce, view_layer, OB_ARMATURE, name.c_str());
+ }
+ else {
+ ob = bc_add_object(bmain, sce, view_layer, OB_EMPTY, NULL);
+ }
+ objects_done->push_back(ob);
+ if (parent_node == NULL) {
+ root_objects->push_back(ob);
+ }
+ }
+
+ /* XXX: if there're multiple instances, only one is stored */
+
+ if (!ob) {
+ goto finally;
+ }
+
+ for (std::vector<Object *>::iterator it = objects_done->begin(); it != objects_done->end();
+ ++it) {
+ ob = *it;
+ std::string nodename = node->getName().size() ? node->getName() : node->getOriginalId();
+ BKE_libblock_rename(bmain, &ob->id, (char *)nodename.c_str());
+ object_map.insert(std::pair<COLLADAFW::UniqueId, Object *>(node->getUniqueId(), ob));
+ node_map[node->getUniqueId()] = node;
+
+ if (is_library_node) {
+ libnode_ob.push_back(ob);
+ }
+ }
+
+ // create_constraints(et,ob);
+ }
+
+ for (std::vector<Object *>::iterator it = objects_done->begin(); it != objects_done->end();
+ ++it) {
+ ob = *it;
+
+ if (read_transform) {
+ anim_importer.read_node_transform(node, ob); // overwrites location set earlier
+ }
+
+ if (!is_joint) {
+ if (par && ob) {
+ ob->parent = par;
+ ob->partype = PAROBJECT;
+ ob->parsubstr[0] = 0;
+
+ // bc_set_parent(ob, par, mContext, false);
+ }
+ }
+ }
+
+ if (objects_done->size() > 0) {
+ ob = *objects_done->begin();
+ }
+ else {
+ ob = NULL;
+ }
+
+ for (unsigned int i = 0; i < child_nodes.getCount(); i++) {
+ std::vector<Object *> *child_objects;
+ child_objects = write_node(child_nodes[i], node, sce, ob, is_library_node);
+ delete child_objects;
+ }
+
+finally:
+ delete objects_done;
+
+ return root_objects;
+}
+
+/**
+ * When this method is called, the writer must write the entire visual scene.
+ * Return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeVisualScene(const COLLADAFW::VisualScene *visualScene)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ /* This method called on post process after writeGeometry, writeMaterial, etc. */
+
+ /* For each <node> in <visual_scene>:
+ * create an Object
+ * if Mesh (previously created in writeGeometry) to which <node> corresponds exists,
+ * link Object with that mesh.
+ *
+ * Update: since we cannot link a Mesh with Object in
+ * writeGeometry because <geometry> does not reference <node>,
+ * we link Objects with Meshes here.
+ */
+ vscenes.push_back(visualScene);
+
+ return true;
+}
+
+/**
+ * When this method is called, the writer must handle all nodes contained in the
+ * library nodes.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeLibraryNodes(const COLLADAFW::LibraryNodes *libraryNodes)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ Scene *sce = CTX_data_scene(mContext);
+
+ const COLLADAFW::NodePointerArray &nodes = libraryNodes->getNodes();
+
+ fprintf(stderr, "+-- Read Library nodes ----------\n");
+ for (unsigned int i = 0; i < nodes.getCount(); i++) {
+ std::vector<Object *> *child_objects;
+ child_objects = write_node(nodes[i], NULL, sce, NULL, true);
+ delete child_objects;
+ }
+ return true;
+}
+
+/**
+ * When this method is called, the writer must write the geometry.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeGeometry(const COLLADAFW::Geometry *geom)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ return mesh_importer.write_geometry(geom);
+}
+
+/**
+ * When this method is called, the writer must write the material.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeMaterial(const COLLADAFW::Material *cmat)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ Main *bmain = CTX_data_main(mContext);
+ const std::string &str_mat_id = cmat->getName().size() ? cmat->getName() : cmat->getOriginalId();
+ Material *ma = BKE_material_add(bmain, (char *)str_mat_id.c_str());
+
+ this->uid_effect_map[cmat->getInstantiatedEffect()] = ma;
+ this->uid_material_map[cmat->getUniqueId()] = ma;
+
+ return true;
+}
+
+void DocumentImporter::write_profile_COMMON(COLLADAFW::EffectCommon *ef, Material *ma)
+{
+ MaterialNode matNode = MaterialNode(mContext, ef, ma, uid_image_map);
+
+ /* Direct mapping to principled BSDF Shader */
+ matNode.set_diffuse(ef->getDiffuse());
+ matNode.set_emission(ef->getEmission());
+ matNode.set_ior(ef->getIndexOfRefraction());
+ matNode.set_alpha(ef->getOpaqueMode(), ef->getTransparent(), ef->getTransparency());
+
+ /* following mapping still needs to be verified */
+#if 0
+ // needs rework to be done for 2.81
+ matNode.set_shininess(ef->getShininess());
+#endif
+ matNode.set_reflectivity(ef->getReflectivity());
+
+ /* not supported by principled BSDF */
+ matNode.set_ambient(ef->getAmbient());
+ matNode.set_specular(ef->getSpecular());
+ matNode.set_reflective(ef->getReflective());
+}
+
+/**
+ * When this method is called, the writer must write the effect.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeEffect(const COLLADAFW::Effect *effect)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ const COLLADAFW::UniqueId &uid = effect->getUniqueId();
+
+ if (uid_effect_map.find(uid) == uid_effect_map.end()) {
+ fprintf(stderr, "Couldn't find a material by UID.\n");
+ return true;
+ }
+
+ Material *ma = uid_effect_map[uid];
+ std::map<COLLADAFW::UniqueId, Material *>::iterator iter;
+ for (iter = uid_material_map.begin(); iter != uid_material_map.end(); iter++) {
+ if (iter->second == ma) {
+ this->FW_object_map[iter->first] = effect;
+ break;
+ }
+ }
+ COLLADAFW::CommonEffectPointerArray common_efs = effect->getCommonEffects();
+ if (common_efs.getCount() < 1) {
+ fprintf(stderr, "Couldn't find <profile_COMMON>.\n");
+ return true;
+ }
+ /* XXX TODO: Take all <profile_common>s
+ * Currently only first <profile_common> is supported */
+ COLLADAFW::EffectCommon *ef = common_efs[0];
+ write_profile_COMMON(ef, ma);
+ this->FW_object_map[effect->getUniqueId()] = effect;
+
+ return true;
+}
+
+/**
+ * When this method is called, the writer must write the camera.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeCamera(const COLLADAFW::Camera *camera)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ Main *bmain = CTX_data_main(mContext);
+ Camera *cam = NULL;
+ std::string cam_id, cam_name;
+
+ ExtraTags *et = getExtraTags(camera->getUniqueId());
+ cam_id = camera->getOriginalId();
+ cam_name = camera->getName();
+ if (cam_name.size()) {
+ cam = (Camera *)BKE_camera_add(bmain, (char *)cam_name.c_str());
+ }
+ else {
+ cam = (Camera *)BKE_camera_add(bmain, (char *)cam_id.c_str());
+ }
+
+ if (!cam) {
+ fprintf(stderr, "Cannot create camera.\n");
+ return true;
+ }
+
+ if (et && et->isProfile("blender")) {
+ et->setData("shiftx", &(cam->shiftx));
+ et->setData("shifty", &(cam->shifty));
+ et->setData("dof_distance", &(cam->dof.focus_distance));
+ }
+ cam->clip_start = camera->getNearClippingPlane().getValue();
+ cam->clip_end = camera->getFarClippingPlane().getValue();
+
+ COLLADAFW::Camera::CameraType type = camera->getCameraType();
+ switch (type) {
+ case COLLADAFW::Camera::ORTHOGRAPHIC: {
+ cam->type = CAM_ORTHO;
+ } break;
+ case COLLADAFW::Camera::PERSPECTIVE: {
+ cam->type = CAM_PERSP;
+ } break;
+ case COLLADAFW::Camera::UNDEFINED_CAMERATYPE: {
+ fprintf(stderr, "Current camera type is not supported.\n");
+ cam->type = CAM_PERSP;
+ } break;
+ }
+
+ switch (camera->getDescriptionType()) {
+ case COLLADAFW::Camera::ASPECTRATIO_AND_Y: {
+ switch (cam->type) {
+ case CAM_ORTHO: {
+ double ymag = 2 * camera->getYMag().getValue();
+ double aspect = camera->getAspectRatio().getValue();
+ double xmag = aspect * ymag;
+ cam->ortho_scale = (float)xmag;
+ } break;
+ case CAM_PERSP:
+ default: {
+ double yfov = camera->getYFov().getValue();
+ double aspect = camera->getAspectRatio().getValue();
+
+ /* NOTE: Needs more testing (As we currently have no official test data for this) */
+
+ double xfov = 2.0f * atanf(aspect * tanf(DEG2RADF(yfov) * 0.5f));
+ cam->lens = fov_to_focallength(xfov, cam->sensor_x);
+ } break;
+ }
+ } break;
+ /* XXX correct way to do following four is probably to get also render
+ * size and determine proper settings from that somehow */
+ case COLLADAFW::Camera::ASPECTRATIO_AND_X:
+ case COLLADAFW::Camera::SINGLE_X:
+ case COLLADAFW::Camera::X_AND_Y: {
+ switch (cam->type) {
+ case CAM_ORTHO:
+ cam->ortho_scale = (float)camera->getXMag().getValue() * 2;
+ break;
+ case CAM_PERSP:
+ default: {
+ double x = camera->getXFov().getValue();
+ /* x is in degrees, cam->lens is in millimiters */
+ cam->lens = fov_to_focallength(DEG2RADF(x), cam->sensor_x);
+ } break;
+ }
+ } break;
+ case COLLADAFW::Camera::SINGLE_Y: {
+ switch (cam->type) {
+ case CAM_ORTHO:
+ cam->ortho_scale = (float)camera->getYMag().getValue();
+ break;
+ case CAM_PERSP:
+ default: {
+ double yfov = camera->getYFov().getValue();
+ /* yfov is in degrees, cam->lens is in millimiters */
+ cam->lens = fov_to_focallength(DEG2RADF(yfov), cam->sensor_x);
+ } break;
+ }
+ } break;
+ case COLLADAFW::Camera::UNDEFINED:
+ /* read nothing, use blender defaults. */
+ break;
+ }
+
+ this->uid_camera_map[camera->getUniqueId()] = cam;
+ this->FW_object_map[camera->getUniqueId()] = camera;
+ /* XXX import camera options */
+ return true;
+}
+
+/**
+ * When this method is called, the writer must write the image.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeImage(const COLLADAFW::Image *image)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ const std::string &imagepath = image->getImageURI().toNativePath();
+
+ char dir[FILE_MAX];
+ char absolute_path[FILE_MAX];
+ const char *workpath;
+
+ BLI_split_dir_part(this->import_settings->filepath, dir, sizeof(dir));
+ BLI_join_dirfile(absolute_path, sizeof(absolute_path), dir, imagepath.c_str());
+ if (BLI_exists(absolute_path)) {
+ workpath = absolute_path;
+ }
+ else {
+ /* Maybe imagepath was already absolute ? */
+ if (!BLI_exists(imagepath.c_str())) {
+ fprintf(stderr, "|! Image not found: %s\n", imagepath.c_str());
+ return true;
+ }
+ workpath = imagepath.c_str();
+ }
+
+ Image *ima = BKE_image_load_exists(CTX_data_main(mContext), workpath);
+ if (!ima) {
+ fprintf(stderr, "|! Cannot create image: %s\n", workpath);
+ return true;
+ }
+ this->uid_image_map[image->getUniqueId()] = ima;
+ fprintf(stderr, "| import Image: %s\n", workpath);
+ return true;
+}
+
+/**
+ * When this method is called, the writer must write the light.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeLight(const COLLADAFW::Light *light)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ Main *bmain = CTX_data_main(mContext);
+ Light *lamp = NULL;
+ std::string la_id, la_name;
+
+ ExtraTags *et = getExtraTags(light->getUniqueId());
+#if 0
+ TagsMap::iterator etit;
+ ExtraTags *et = 0;
+ etit = uid_tags_map.find(light->getUniqueId().toAscii());
+ if (etit != uid_tags_map.end()) {
+ et = etit->second;
+ }
+#endif
+
+ la_id = light->getOriginalId();
+ la_name = light->getName();
+ if (la_name.size()) {
+ lamp = (Light *)BKE_light_add(bmain, (char *)la_name.c_str());
+ }
+ else {
+ lamp = (Light *)BKE_light_add(bmain, (char *)la_id.c_str());
+ }
+
+ if (!lamp) {
+ fprintf(stderr, "Cannot create light.\n");
+ return true;
+ }
+
+ /* if we find an ExtraTags for this, use that instead. */
+ if (et && et->isProfile("blender")) {
+ et->setData("type", &(lamp->type));
+ et->setData("flag", &(lamp->flag));
+ et->setData("mode", &(lamp->mode));
+ et->setData("gamma", &(lamp->k));
+ et->setData("red", &(lamp->r));
+ et->setData("green", &(lamp->g));
+ et->setData("blue", &(lamp->b));
+ et->setData("shadow_r", &(lamp->shdwr));
+ et->setData("shadow_g", &(lamp->shdwg));
+ et->setData("shadow_b", &(lamp->shdwb));
+ et->setData("energy", &(lamp->energy));
+ et->setData("dist", &(lamp->dist));
+ et->setData("spotsize", &(lamp->spotsize));
+ lamp->spotsize = DEG2RADF(lamp->spotsize);
+ et->setData("spotblend", &(lamp->spotblend));
+ et->setData("att1", &(lamp->att1));
+ et->setData("att2", &(lamp->att2));
+ et->setData("falloff_type", &(lamp->falloff_type));
+ et->setData("clipsta", &(lamp->clipsta));
+ et->setData("clipend", &(lamp->clipend));
+ et->setData("bias", &(lamp->bias));
+ et->setData("soft", &(lamp->soft));
+ et->setData("bufsize", &(lamp->bufsize));
+ et->setData("buffers", &(lamp->buffers));
+ et->setData("area_shape", &(lamp->area_shape));
+ et->setData("area_size", &(lamp->area_size));
+ et->setData("area_sizey", &(lamp->area_sizey));
+ et->setData("area_sizez", &(lamp->area_sizez));
+ }
+ else {
+ float constatt = light->getConstantAttenuation().getValue();
+ float linatt = light->getLinearAttenuation().getValue();
+ float quadatt = light->getQuadraticAttenuation().getValue();
+ float d = 25.0f;
+ float att1 = 0.0f;
+ float att2 = 0.0f;
+ float e = 1.0f;
+
+ if (light->getColor().isValid()) {
+ COLLADAFW::Color col = light->getColor();
+ lamp->r = col.getRed();
+ lamp->g = col.getGreen();
+ lamp->b = col.getBlue();
+ }
+
+ if (IS_EQ(linatt, 0.0f) && quadatt > 0.0f) {
+ att2 = quadatt;
+ d = sqrt(1.0f / quadatt);
+ }
+ /* linear light */
+ else if (IS_EQ(quadatt, 0.0f) && linatt > 0.0f) {
+ att1 = linatt;
+ d = (1.0f / linatt);
+ }
+ else if (IS_EQ(constatt, 1.0f)) {
+ att1 = 1.0f;
+ }
+ else {
+ /* assuming point light (const att = 1.0); */
+ att1 = 1.0f;
+ }
+
+ d *= (1.0f / unit_converter.getLinearMeter());
+
+ lamp->energy = e;
+ lamp->dist = d;
+
+ switch (light->getLightType()) {
+ case COLLADAFW::Light::AMBIENT_LIGHT: {
+ lamp->type = LA_SUN; // TODO needs more thoughts
+ } break;
+ case COLLADAFW::Light::SPOT_LIGHT: {
+ lamp->type = LA_SPOT;
+ lamp->att1 = att1;
+ lamp->att2 = att2;
+ if (IS_EQ(att1, 0.0f) && att2 > 0) {
+ lamp->falloff_type = LA_FALLOFF_INVSQUARE;
+ }
+ if (IS_EQ(att2, 0.0f) && att1 > 0) {
+ lamp->falloff_type = LA_FALLOFF_INVLINEAR;
+ }
+ lamp->spotsize = DEG2RADF(light->getFallOffAngle().getValue());
+ lamp->spotblend = light->getFallOffExponent().getValue();
+ } break;
+ case COLLADAFW::Light::DIRECTIONAL_LIGHT: {
+ /* our sun is very strong, so pick a smaller energy level */
+ lamp->type = LA_SUN;
+ } break;
+ case COLLADAFW::Light::POINT_LIGHT: {
+ lamp->type = LA_LOCAL;
+ lamp->att1 = att1;
+ lamp->att2 = att2;
+ if (IS_EQ(att1, 0.0f) && att2 > 0) {
+ lamp->falloff_type = LA_FALLOFF_INVSQUARE;
+ }
+ if (IS_EQ(att2, 0.0f) && att1 > 0) {
+ lamp->falloff_type = LA_FALLOFF_INVLINEAR;
+ }
+ } break;
+ case COLLADAFW::Light::UNDEFINED: {
+ fprintf(stderr, "Current light type is not supported.\n");
+ lamp->type = LA_LOCAL;
+ } break;
+ }
+ }
+
+ this->uid_light_map[light->getUniqueId()] = lamp;
+ this->FW_object_map[light->getUniqueId()] = light;
+ return true;
+}
+
+/* this function is called only for animations that pass COLLADAFW::validate */
+bool DocumentImporter::writeAnimation(const COLLADAFW::Animation *anim)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ return anim_importer.write_animation(anim);
+}
+
+/* called on post-process stage after writeVisualScenes */
+bool DocumentImporter::writeAnimationList(const COLLADAFW::AnimationList *animationList)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ /* return true; */
+ return anim_importer.write_animation_list(animationList);
+}
+
+#if WITH_OPENCOLLADA_ANIMATION_CLIP
+/* Since opencollada 1.6.68
+ * called on post-process stage after writeVisualScenes */
+bool DocumentImporter::writeAnimationClip(const COLLADAFW::AnimationClip *animationClip)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ return true;
+ /* TODO: implement import of AnimationClips */
+ // return animation_clip_importer.write_animation_clip(animationClip);
+}
+#endif
+
+/**
+ * When this method is called, the writer must write the skin controller data.
+ * \return The writer should return true, if writing succeeded, false otherwise.
+ */
+bool DocumentImporter::writeSkinControllerData(const COLLADAFW::SkinControllerData *skin)
+{
+ return armature_importer.write_skin_controller_data(skin);
+}
+
+/* this is called on postprocess, before writeVisualScenes */
+bool DocumentImporter::writeController(const COLLADAFW::Controller *controller)
+{
+ if (mImportStage == Fetching_Controller_data) {
+ return true;
+ }
+
+ return armature_importer.write_controller(controller);
+}
+
+bool DocumentImporter::writeFormulas(const COLLADAFW::Formulas *formulas)
+{
+ return true;
+}
+
+bool DocumentImporter::writeKinematicsScene(const COLLADAFW::KinematicsScene *kinematicsScene)
+{
+ return true;
+}
+
+ExtraTags *DocumentImporter::getExtraTags(const COLLADAFW::UniqueId &uid)
+{
+ if (uid_tags_map.find(uid.toAscii()) == uid_tags_map.end()) {
+ return NULL;
+ }
+ return uid_tags_map[uid.toAscii()];
+}
+
+bool DocumentImporter::addExtraTags(const COLLADAFW::UniqueId &uid, ExtraTags *extra_tags)
+{
+ uid_tags_map[uid.toAscii()] = extra_tags;
+ return true;
+}
+
+bool DocumentImporter::is_armature(COLLADAFW::Node *node)
+{
+ COLLADAFW::NodePointerArray &child_nodes = node->getChildNodes();
+ for (unsigned int i = 0; i < child_nodes.getCount(); i++) {
+ if (child_nodes[i]->getType() == COLLADAFW::Node::JOINT) {
+ return true;
+ }
+ else {
+ continue;
+ }
+ }
+
+ /* no child is JOINT */
+ return false;
+}
diff --git a/source/blender/io/collada/DocumentImporter.h b/source/blender/io/collada/DocumentImporter.h
new file mode 100644
index 00000000000..e47c844f7c6
--- /dev/null
+++ b/source/blender/io/collada/DocumentImporter.h
@@ -0,0 +1,172 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __DOCUMENTIMPORTER_H__
+#define __DOCUMENTIMPORTER_H__
+
+#include "COLLADAFWIWriter.h"
+#include "COLLADAFWMaterial.h"
+#include "COLLADAFWEffect.h"
+#include "COLLADAFWColor.h"
+#include "COLLADAFWImage.h"
+#include "COLLADAFWInstanceGeometry.h"
+#include "COLLADAFWController.h"
+#include "COLLADAFWMorphController.h"
+#include "COLLADAFWSkinController.h"
+#include "COLLADAFWEffectCommon.h"
+
+#include "BKE_object.h"
+#include "BKE_constraint.h"
+
+#include "TransformReader.h"
+#include "AnimationImporter.h"
+#include "ArmatureImporter.h"
+#include "ControllerExporter.h"
+#include "MeshImporter.h"
+#include "ImportSettings.h"
+
+struct bContext;
+
+/** Importer class. */
+class DocumentImporter : COLLADAFW::IWriter {
+ public:
+ //! Enumeration to denote the stage of import
+ enum ImportStage {
+ Fetching_Scene_data, /* First pass to collect all data except controller */
+ Fetching_Controller_data, /* Second pass to collect controller data */
+ };
+ /** Constructor */
+ DocumentImporter(bContext *C, const ImportSettings *import_settings);
+
+ /** Destructor */
+ ~DocumentImporter();
+
+ /** Function called by blender UI */
+ bool import();
+
+ /** these should not be here */
+ Object *create_camera_object(COLLADAFW::InstanceCamera *, Scene *);
+ Object *create_light_object(COLLADAFW::InstanceLight *, Scene *);
+ Object *create_instance_node(Object *, COLLADAFW::Node *, COLLADAFW::Node *, Scene *, bool);
+ void create_constraints(ExtraTags *et, Object *ob);
+ std::vector<Object *> *write_node(COLLADAFW::Node *, COLLADAFW::Node *, Scene *, Object *, bool);
+ void write_profile_COMMON(COLLADAFW::EffectCommon *, Material *);
+
+ void translate_anim_recursive(COLLADAFW::Node *, COLLADAFW::Node *, Object *);
+
+ /**
+ * This method will be called if an error in the loading process occurred and the loader cannot
+ * continue to load. The writer should undo all operations that have been performed.
+ * \param errorMessage: A message containing information about the error that occurred.
+ */
+ void cancel(const COLLADAFW::String &errorMessage);
+
+ /** This is the method called. The writer hast to prepare to receive data.*/
+ void start();
+
+ /** This method is called after the last write* method. No other methods will be called after
+ * this.*/
+ void finish();
+
+ bool writeGlobalAsset(const COLLADAFW::FileInfo *);
+ std::string get_import_version(const COLLADAFW::FileInfo *asset);
+
+ bool writeScene(const COLLADAFW::Scene *);
+
+ bool writeVisualScene(const COLLADAFW::VisualScene *);
+
+ bool writeLibraryNodes(const COLLADAFW::LibraryNodes *);
+
+ bool writeAnimation(const COLLADAFW::Animation *);
+
+ bool writeAnimationList(const COLLADAFW::AnimationList *);
+
+#if WITH_OPENCOLLADA_ANIMATION_CLIP
+ // Please enable this when building with Collada 1.6.65 or newer (also in DocumentImporter.cpp)
+ bool writeAnimationClip(const COLLADAFW::AnimationClip *animationClip);
+#endif
+
+ bool writeGeometry(const COLLADAFW::Geometry *);
+
+ bool writeMaterial(const COLLADAFW::Material *);
+
+ bool writeEffect(const COLLADAFW::Effect *);
+
+ bool writeCamera(const COLLADAFW::Camera *);
+
+ bool writeImage(const COLLADAFW::Image *);
+
+ bool writeLight(const COLLADAFW::Light *);
+
+ bool writeSkinControllerData(const COLLADAFW::SkinControllerData *);
+
+ bool writeController(const COLLADAFW::Controller *);
+
+ bool writeFormulas(const COLLADAFW::Formulas *);
+
+ bool writeKinematicsScene(const COLLADAFW::KinematicsScene *);
+
+ /** Add element and data for UniqueId */
+ bool addExtraTags(const COLLADAFW::UniqueId &uid, ExtraTags *extra_tags);
+ /** Get an extisting ExtraTags for uid */
+ ExtraTags *getExtraTags(const COLLADAFW::UniqueId &uid);
+
+ bool is_armature(COLLADAFW::Node *node);
+
+ private:
+ const ImportSettings *import_settings;
+
+ /** Current import stage we're in. */
+ ImportStage mImportStage;
+
+ bContext *mContext;
+ ViewLayer *view_layer;
+
+ UnitConverter unit_converter;
+ ArmatureImporter armature_importer;
+ MeshImporter mesh_importer;
+ AnimationImporter anim_importer;
+
+ /** TagsMap typedef for uid_tags_map. */
+ typedef std::map<std::string, ExtraTags *> TagsMap;
+ /** Tags map of unique id as a string and ExtraTags instance. */
+ TagsMap uid_tags_map;
+
+ UidImageMap uid_image_map;
+ std::map<COLLADAFW::UniqueId, Material *> uid_material_map;
+ std::map<COLLADAFW::UniqueId, Material *> uid_effect_map;
+ std::map<COLLADAFW::UniqueId, Camera *> uid_camera_map;
+ std::map<COLLADAFW::UniqueId, Light *> uid_light_map;
+ std::map<Material *, TexIndexTextureArrayMap> material_texture_mapping_map;
+ std::multimap<COLLADAFW::UniqueId, Object *> object_map;
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> node_map;
+ std::vector<const COLLADAFW::VisualScene *> vscenes;
+ std::vector<Object *> libnode_ob;
+
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *>
+ root_map; // find root joint by child joint uid, for bone tree evaluation during resampling
+ std::map<COLLADAFW::UniqueId, const COLLADAFW::Object *> FW_object_map;
+
+ std::string import_from_version;
+
+ void report_unknown_reference(const COLLADAFW::Node &node, const std::string object_type);
+};
+
+#endif
diff --git a/source/blender/io/collada/EffectExporter.cpp b/source/blender/io/collada/EffectExporter.cpp
new file mode 100644
index 00000000000..a1174fdff56
--- /dev/null
+++ b/source/blender/io/collada/EffectExporter.cpp
@@ -0,0 +1,312 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <map>
+#include <set>
+
+#include "COLLADASWEffectProfile.h"
+#include "COLLADAFWColorOrTexture.h"
+
+#include "EffectExporter.h"
+#include "DocumentExporter.h"
+#include "MaterialExporter.h"
+
+#include "collada_internal.h"
+#include "collada_utils.h"
+
+extern "C" {
+#include "DNA_mesh_types.h"
+#include "DNA_world_types.h"
+
+#include "BKE_collection.h"
+#include "BKE_customdata.h"
+#include "BKE_mesh.h"
+#include "BKE_material.h"
+}
+
+static std::string getActiveUVLayerName(Object *ob)
+{
+ Mesh *me = (Mesh *)ob->data;
+
+ int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV);
+ if (num_layers) {
+ return std::string(bc_CustomData_get_active_layer_name(&me->ldata, CD_MLOOPUV));
+ }
+
+ return "";
+}
+
+EffectsExporter::EffectsExporter(COLLADASW::StreamWriter *sw,
+ BCExportSettings &export_settings,
+ KeyImageMap &key_image_map)
+ : COLLADASW::LibraryEffects(sw), export_settings(export_settings), key_image_map(key_image_map)
+{
+}
+
+bool EffectsExporter::hasEffects(Scene *sce)
+{
+ FOREACH_SCENE_OBJECT_BEGIN (sce, ob) {
+ int a;
+ for (a = 0; a < ob->totcol; a++) {
+ Material *ma = BKE_object_material_get(ob, a + 1);
+
+ // no material, but check all of the slots
+ if (!ma) {
+ continue;
+ }
+
+ return true;
+ }
+ }
+ FOREACH_SCENE_OBJECT_END;
+ return false;
+}
+
+void EffectsExporter::exportEffects(bContext *C, Scene *sce)
+{
+ if (hasEffects(sce)) {
+ this->mContext = C;
+ this->scene = sce;
+ openLibrary();
+ MaterialFunctor mf;
+ mf.forEachMaterialInExportSet<EffectsExporter>(
+ sce, *this, this->export_settings.get_export_set());
+
+ closeLibrary();
+ }
+}
+
+void EffectsExporter::set_shader_type(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ /* XXX check if BLINN and PHONG can be supported as well */
+ ep.setShaderType(COLLADASW::EffectProfile::LAMBERT);
+}
+
+void EffectsExporter::set_transparency(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ double alpha = bc_get_alpha(ma);
+ if (alpha < 1) {
+ // workaround use <transparent> to avoid wrong handling of <transparency> by other tools
+ COLLADASW::ColorOrTexture cot = bc_get_cot(0, 0, 0, alpha);
+ ep.setTransparent(cot, false, "alpha");
+ ep.setOpaque(COLLADASW::EffectProfile::A_ONE);
+ }
+}
+
+void EffectsExporter::set_diffuse_color(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ COLLADASW::ColorOrTexture cot = bc_get_base_color(ma);
+ ep.setDiffuse(cot, false, "diffuse");
+}
+
+void EffectsExporter::set_ambient(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ COLLADASW::ColorOrTexture cot = bc_get_ambient(ma);
+ ep.setAmbient(cot, false, "ambient");
+}
+void EffectsExporter::set_specular(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ COLLADASW::ColorOrTexture cot = bc_get_specular(ma);
+ ep.setSpecular(cot, false, "specular");
+}
+void EffectsExporter::set_reflective(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ COLLADASW::ColorOrTexture cot = bc_get_reflective(ma);
+ ep.setReflective(cot, false, "reflective");
+}
+
+void EffectsExporter::set_reflectivity(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ double reflectivity = bc_get_reflectivity(ma);
+ if (reflectivity > 0.0) {
+ ep.setReflectivity(reflectivity, false, "specular");
+ }
+}
+
+void EffectsExporter::set_emission(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ COLLADASW::ColorOrTexture cot = bc_get_emission(ma);
+ ep.setEmission(cot, false, "emission");
+}
+
+void EffectsExporter::set_ior(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ double alpha = bc_get_ior(ma);
+ ep.setIndexOfRefraction(alpha, false, "ior");
+}
+
+void EffectsExporter::set_shininess(COLLADASW::EffectProfile &ep, Material *ma)
+{
+ double shininess = bc_get_shininess(ma);
+ ep.setShininess(shininess, false, "shininess");
+}
+
+void EffectsExporter::get_images(Material *ma, KeyImageMap &material_image_map)
+{
+ if (!ma->use_nodes) {
+ return;
+ }
+
+ MaterialNode material = MaterialNode(mContext, ma, key_image_map);
+ Image *image = material.get_diffuse_image();
+ if (image == nullptr) {
+ return;
+ }
+
+ std::string uid(id_name(image));
+ std::string key = translate_id(uid);
+
+ if (material_image_map.find(key) == material_image_map.end()) {
+ material_image_map[key] = image;
+ key_image_map[key] = image;
+ }
+}
+
+void EffectsExporter::create_image_samplers(COLLADASW::EffectProfile &ep,
+ KeyImageMap &material_image_map,
+ std::string &active_uv)
+{
+ KeyImageMap::iterator iter;
+
+ for (iter = material_image_map.begin(); iter != material_image_map.end(); iter++) {
+
+ Image *image = iter->second;
+ std::string uid(id_name(image));
+ std::string key = translate_id(uid);
+
+ COLLADASW::Sampler *sampler = new COLLADASW::Sampler(
+ COLLADASW::Sampler::SAMPLER_TYPE_2D,
+ key + COLLADASW::Sampler::SAMPLER_SID_SUFFIX,
+ key + COLLADASW::Sampler::SURFACE_SID_SUFFIX);
+
+ sampler->setImageId(key);
+
+ ep.setDiffuse(createTexture(image, active_uv, sampler), false, "diffuse");
+ }
+}
+
+void EffectsExporter::operator()(Material *ma, Object *ob)
+{
+ KeyImageMap material_image_map;
+
+ openEffect(get_effect_id(ma));
+
+ COLLADASW::EffectProfile ep(mSW);
+ ep.setProfileType(COLLADASW::EffectProfile::COMMON);
+ ep.openProfile();
+ set_shader_type(ep, ma); // creates a Lambert Shader for now
+
+ COLLADASW::ColorOrTexture cot;
+
+ set_diffuse_color(ep, ma);
+ set_emission(ep, ma);
+ set_ior(ep, ma);
+ set_reflectivity(ep, ma);
+ set_transparency(ep, ma);
+
+ /* TODO: */
+ // set_shininess(ep, ma); shininess not supported for lambert
+ // set_ambient(ep, ma);
+ // set_specular(ep, ma);
+
+ get_images(ma, material_image_map);
+ std::string active_uv(getActiveUVLayerName(ob));
+ create_image_samplers(ep, material_image_map, active_uv);
+
+#if 0
+ unsigned int a, b;
+ for (a = 0, b = 0; a < tex_indices.size(); a++) {
+ MTex *t = ma->mtex[tex_indices[a]];
+ Image *ima = t->tex->ima;
+
+ // Image not set for texture
+ if (!ima) {
+ continue;
+ }
+
+ std::string key(id_name(ima));
+ key = translate_id(key);
+
+ // create only one <sampler>/<surface> pair for each unique image
+ if (im_samp_map.find(key) == im_samp_map.end()) {
+ //<newparam> <sampler> <source>
+ COLLADASW::Sampler sampler(COLLADASW::Sampler::SAMPLER_TYPE_2D,
+ key + COLLADASW::Sampler::SAMPLER_SID_SUFFIX,
+ key + COLLADASW::Sampler::SURFACE_SID_SUFFIX);
+ sampler.setImageId(key);
+ // copy values to arrays since they will live longer
+ samplers[a] = sampler;
+
+ // store pointers so they can be used later when we create <texture>s
+ samp_surf[b] = &samplers[a];
+ //samp_surf[b][1] = &surfaces[a];
+
+ im_samp_map[key] = b;
+ b++;
+ }
+ }
+
+ for (a = 0; a < tex_indices.size(); a++) {
+ MTex *t = ma->mtex[tex_indices[a]];
+ Image *ima = t->tex->ima;
+
+ if (!ima) {
+ continue;
+ }
+
+ std::string key(id_name(ima));
+ key = translate_id(key);
+ int i = im_samp_map[key];
+ std::string uvname = strlen(t->uvname) ? t->uvname : active_uv;
+ COLLADASW::Sampler *sampler = (COLLADASW::Sampler *)
+ samp_surf[i]; // possibly uninitialized memory ...
+ writeTextures(ep, key, sampler, t, ima, uvname);
+ }
+#endif
+
+ // performs the actual writing
+ ep.addProfileElements();
+ ep.addExtraTechniques(mSW);
+
+ ep.closeProfile();
+ closeEffect();
+}
+
+COLLADASW::ColorOrTexture EffectsExporter::createTexture(Image *ima,
+ std::string &uv_layer_name,
+ COLLADASW::Sampler *sampler
+ /*COLLADASW::Surface *surface*/)
+{
+
+ COLLADASW::Texture texture(translate_id(id_name(ima)));
+ texture.setTexcoord(uv_layer_name);
+ // texture.setSurface(*surface);
+ texture.setSampler(*sampler);
+
+ COLLADASW::ColorOrTexture cot(texture);
+ return cot;
+}
+
+COLLADASW::ColorOrTexture EffectsExporter::getcol(float r, float g, float b, float a)
+{
+ COLLADASW::Color color(r, g, b, a);
+ COLLADASW::ColorOrTexture cot(color);
+ return cot;
+}
diff --git a/source/blender/io/collada/EffectExporter.h b/source/blender/io/collada/EffectExporter.h
new file mode 100644
index 00000000000..57df844233c
--- /dev/null
+++ b/source/blender/io/collada/EffectExporter.h
@@ -0,0 +1,89 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __EFFECTEXPORTER_H__
+#define __EFFECTEXPORTER_H__
+
+#include <string>
+#include <vector>
+
+#include "COLLADASWColorOrTexture.h"
+#include "COLLADASWStreamWriter.h"
+#include "COLLADASWSampler.h"
+#include "COLLADASWLibraryEffects.h"
+
+#include "DNA_image_types.h"
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "ExportSettings.h"
+#include "collada_utils.h"
+
+class EffectsExporter : COLLADASW::LibraryEffects {
+ public:
+ EffectsExporter(COLLADASW::StreamWriter *sw,
+ BCExportSettings &export_settings,
+ KeyImageMap &key_image_map);
+ void exportEffects(bContext *C, Scene *sce);
+
+ void operator()(Material *ma, Object *ob);
+
+ COLLADASW::ColorOrTexture createTexture(Image *ima,
+ std::string &uv_layer_name,
+ COLLADASW::Sampler *sampler
+ /*COLLADASW::Surface *surface*/);
+
+ COLLADASW::ColorOrTexture getcol(float r, float g, float b, float a);
+
+ private:
+ void set_shader_type(COLLADASW::EffectProfile &ep, Material *ma);
+
+ void set_diffuse_color(COLLADASW::EffectProfile &ep, Material *ma);
+ void set_emission(COLLADASW::EffectProfile &ep, Material *ma);
+ void set_ior(COLLADASW::EffectProfile &ep, Material *ma);
+ void set_shininess(COLLADASW::EffectProfile &ep, Material *ma);
+ void set_reflectivity(COLLADASW::EffectProfile &ep, Material *ma);
+ void set_transparency(COLLADASW::EffectProfile &ep, Material *ma);
+ void set_ambient(COLLADASW::EffectProfile &ep, Material *ma);
+ void set_specular(COLLADASW::EffectProfile &ep, Material *ma);
+ void set_reflective(COLLADASW::EffectProfile &ep, Material *ma);
+
+ void get_images(Material *ma, KeyImageMap &uid_image_map);
+ void create_image_samplers(COLLADASW::EffectProfile &ep,
+ KeyImageMap &uid_image_map,
+ std::string &active_uv);
+
+ void writeTextures(COLLADASW::EffectProfile &ep,
+ std::string &key,
+ COLLADASW::Sampler *sampler,
+ MTex *t,
+ Image *ima,
+ std::string &uvname);
+
+ bool hasEffects(Scene *sce);
+
+ BCExportSettings &export_settings;
+ KeyImageMap &key_image_map;
+ Scene *scene;
+ bContext *mContext;
+};
+
+#endif
diff --git a/source/blender/io/collada/ErrorHandler.cpp b/source/blender/io/collada/ErrorHandler.cpp
new file mode 100644
index 00000000000..286bcbfb759
--- /dev/null
+++ b/source/blender/io/collada/ErrorHandler.cpp
@@ -0,0 +1,118 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+#include "ErrorHandler.h"
+#include <iostream>
+
+#include "COLLADASaxFWLIError.h"
+#include "COLLADASaxFWLSaxParserError.h"
+#include "COLLADASaxFWLSaxFWLError.h"
+
+#include "GeneratedSaxParserParserError.h"
+
+#include <string.h>
+
+#include "BLI_utildefines.h"
+
+//--------------------------------------------------------------------
+ErrorHandler::ErrorHandler() : mError(false)
+{
+}
+
+//--------------------------------------------------------------------
+ErrorHandler::~ErrorHandler()
+{
+}
+
+//--------------------------------------------------------------------
+bool ErrorHandler::handleError(const COLLADASaxFWL::IError *error)
+{
+ /* This method must return false when Collada should continue.
+ * See https://github.com/KhronosGroup/OpenCOLLADA/issues/442
+ */
+ bool isError = true;
+ std::string error_context;
+ std::string error_message;
+
+ if (error->getErrorClass() == COLLADASaxFWL::IError::ERROR_SAXPARSER) {
+ error_context = "Schema validation";
+
+ COLLADASaxFWL::SaxParserError *saxParserError = (COLLADASaxFWL::SaxParserError *)error;
+ const GeneratedSaxParser::ParserError &parserError = saxParserError->getError();
+ error_message = parserError.getErrorMessage();
+
+ if (parserError.getErrorType() ==
+ GeneratedSaxParser::ParserError::ERROR_VALIDATION_MIN_OCCURS_UNMATCHED) {
+ if (STREQ(parserError.getElement(), "effect")) {
+ isError = false;
+ }
+ }
+
+ else if (parserError.getErrorType() ==
+ GeneratedSaxParser::ParserError::
+ ERROR_VALIDATION_SEQUENCE_PREVIOUS_SIBLING_NOT_PRESENT) {
+ if (!(STREQ(parserError.getElement(), "extra") &&
+ STREQ(parserError.getAdditionalText().c_str(), "sibling: fx_profile_abstract"))) {
+ isError = false;
+ }
+ }
+
+ else if (parserError.getErrorType() ==
+ GeneratedSaxParser::ParserError::ERROR_COULD_NOT_OPEN_FILE) {
+ isError = true;
+ error_context = "File access";
+ }
+
+ else if (parserError.getErrorType() ==
+ GeneratedSaxParser::ParserError::ERROR_REQUIRED_ATTRIBUTE_MISSING) {
+ isError = true;
+ }
+
+ else {
+ isError = (parserError.getSeverity() !=
+ GeneratedSaxParser::ParserError::Severity::SEVERITY_ERROR_NONCRITICAL);
+ }
+ }
+ else if (error->getErrorClass() == COLLADASaxFWL::IError::ERROR_SAXFWL) {
+ error_context = "Sax FWL";
+ COLLADASaxFWL::SaxFWLError *saxFWLError = (COLLADASaxFWL::SaxFWLError *)error;
+ error_message = saxFWLError->getErrorMessage();
+
+ /*
+ * Accept non critical errors as warnings (i.e. texture not found)
+ * This makes the importer more graceful, so it now imports what makes sense.
+ */
+
+ isError = (saxFWLError->getSeverity() != COLLADASaxFWL::IError::SEVERITY_ERROR_NONCRITICAL);
+ }
+ else {
+ error_context = "OpenCollada";
+ error_message = error->getFullErrorMessage();
+ isError = true;
+ }
+
+ std::string severity = (isError) ? "Error" : "Warning";
+ std::cout << error_context << " (" << severity << "): " << error_message << std::endl;
+ if (isError) {
+ std::cout << "The Collada import has been forced to stop." << std::endl;
+ std::cout << "Please fix the reported error and then try again.";
+ mError = true;
+ }
+ return isError;
+}
diff --git a/source/blender/io/collada/ErrorHandler.h b/source/blender/io/collada/ErrorHandler.h
new file mode 100644
index 00000000000..f040855244d
--- /dev/null
+++ b/source/blender/io/collada/ErrorHandler.h
@@ -0,0 +1,57 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __ERRORHANDLER_H__
+#define __ERRORHANDLER_H__
+
+#include <string>
+#include <map>
+#include <vector>
+#include <algorithm> // sort()
+
+#include "COLLADASaxFWLIErrorHandler.h"
+
+/** \brief Handler class for parser errors
+ */
+class ErrorHandler : public COLLADASaxFWL::IErrorHandler {
+ public:
+ /** Constructor. */
+ ErrorHandler();
+
+ /** Destructor. */
+ virtual ~ErrorHandler();
+ /** handle any error thrown by the parser. */
+ bool virtual handleError(const COLLADASaxFWL::IError *error);
+ /** True if there was an error during parsing. */
+ bool hasError()
+ {
+ return mError;
+ }
+
+ private:
+ /** Disable default copy ctor. */
+ ErrorHandler(const ErrorHandler &pre);
+ /** Disable default assignment operator. */
+ const ErrorHandler &operator=(const ErrorHandler &pre);
+ /** Hold error status. */
+ bool mError;
+};
+
+#endif /* __ERRORHANDLER_H__ */
diff --git a/source/blender/io/collada/ExportSettings.cpp b/source/blender/io/collada/ExportSettings.cpp
new file mode 100644
index 00000000000..da3c0de0fdf
--- /dev/null
+++ b/source/blender/io/collada/ExportSettings.cpp
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include "ExportSettings.h"
diff --git a/source/blender/io/collada/ExportSettings.h b/source/blender/io/collada/ExportSettings.h
new file mode 100644
index 00000000000..1e158418120
--- /dev/null
+++ b/source/blender/io/collada/ExportSettings.h
@@ -0,0 +1,295 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __EXPORTSETTINGS_H__
+#define __EXPORTSETTINGS_H__
+
+#ifdef __cplusplus
+# include <vector>
+# include "BCMath.h"
+
+extern "C" {
+#endif
+
+#include "BLI_linklist.h"
+#include "BlenderContext.h"
+
+typedef enum BC_export_mesh_type {
+ BC_MESH_TYPE_VIEW,
+ BC_MESH_TYPE_RENDER,
+} BC_export_mesh_type;
+
+typedef enum BC_export_transformation_type {
+ BC_TRANSFORMATION_TYPE_MATRIX,
+ BC_TRANSFORMATION_TYPE_DECOMPOSED,
+} BC_export_transformation_type;
+
+typedef enum BC_export_animation_type {
+ BC_ANIMATION_EXPORT_SAMPLES,
+ BC_ANIMATION_EXPORT_KEYS,
+} BC_export_animation_type;
+
+typedef enum BC_ui_export_section {
+ BC_UI_SECTION_MAIN,
+ BC_UI_SECTION_GEOMETRY,
+ BC_UI_SECTION_ARMATURE,
+ BC_UI_SECTION_ANIMATION,
+ BC_UI_SECTION_COLLADA,
+} BC_ui_export_section;
+
+typedef struct ExportSettings {
+ bool apply_modifiers;
+ BC_global_forward_axis global_forward;
+ BC_global_up_axis global_up;
+ bool apply_global_orientation;
+
+ BC_export_mesh_type export_mesh_type;
+
+ bool selected;
+ bool include_children;
+ bool include_armatures;
+ bool include_shapekeys;
+ bool deform_bones_only;
+ bool include_animations;
+ bool include_all_actions;
+ int sampling_rate;
+ bool keep_smooth_curves;
+ bool keep_keyframes;
+ bool keep_flat_curves;
+
+ bool active_uv_only;
+ BC_export_animation_type export_animation_type;
+ bool use_texture_copies;
+
+ bool triangulate;
+ bool use_object_instantiation;
+ bool use_blender_profile;
+ bool sort_by_name;
+ BC_export_transformation_type object_transformation_type;
+ BC_export_transformation_type animation_transformation_type;
+
+ bool open_sim;
+ bool limit_precision;
+ bool keep_bind_info;
+
+ char *filepath;
+ LinkNode *export_set;
+} ExportSettings;
+
+#ifdef __cplusplus
+}
+
+void bc_get_children(std::vector<Object *> &child_set, Object *ob, ViewLayer *view_layer);
+
+class BCExportSettings {
+
+ private:
+ const ExportSettings &export_settings;
+ BlenderContext &blender_context;
+ const BCMatrix global_transform;
+
+ public:
+ BCExportSettings(ExportSettings *exportSettings, BlenderContext &blenderContext)
+ : export_settings(*exportSettings),
+ blender_context(blenderContext),
+ global_transform(BCMatrix(exportSettings->global_forward, exportSettings->global_up))
+
+ {
+ }
+
+ const BCMatrix &get_global_transform()
+ {
+ return global_transform;
+ }
+
+ bool get_apply_modifiers()
+ {
+ return export_settings.apply_modifiers;
+ }
+
+ BC_global_forward_axis get_global_forward()
+ {
+ return export_settings.global_forward;
+ }
+
+ BC_global_up_axis get_global_up()
+ {
+ return export_settings.global_up;
+ }
+
+ bool get_apply_global_orientation()
+ {
+ return export_settings.apply_global_orientation;
+ }
+
+ BC_export_mesh_type get_export_mesh_type()
+ {
+ return export_settings.export_mesh_type;
+ }
+
+ bool get_selected()
+ {
+ return export_settings.selected;
+ }
+
+ bool get_include_children()
+ {
+ return export_settings.include_children;
+ }
+
+ bool get_include_armatures()
+ {
+ return export_settings.include_armatures;
+ }
+
+ bool get_include_shapekeys()
+ {
+ return export_settings.include_shapekeys;
+ }
+
+ bool get_deform_bones_only()
+ {
+ return export_settings.deform_bones_only;
+ }
+
+ bool get_include_animations()
+ {
+ return export_settings.include_animations;
+ }
+
+ bool get_include_all_actions()
+ {
+ return export_settings.include_all_actions;
+ }
+
+ int get_sampling_rate()
+ {
+ return export_settings.sampling_rate;
+ }
+
+ bool get_keep_smooth_curves()
+ {
+ return export_settings.keep_smooth_curves;
+ }
+
+ bool get_keep_keyframes()
+ {
+ return export_settings.keep_keyframes;
+ }
+
+ bool get_keep_flat_curves()
+ {
+ return export_settings.keep_flat_curves;
+ }
+
+ bool get_active_uv_only()
+ {
+ return export_settings.active_uv_only;
+ }
+
+ BC_export_animation_type get_export_animation_type()
+ {
+ return export_settings.export_animation_type;
+ }
+
+ bool get_use_texture_copies()
+ {
+ return export_settings.use_texture_copies;
+ }
+
+ bool get_triangulate()
+ {
+ return export_settings.triangulate;
+ }
+
+ bool get_use_object_instantiation()
+ {
+ return export_settings.use_object_instantiation;
+ }
+
+ bool get_use_blender_profile()
+ {
+ return export_settings.use_blender_profile;
+ }
+
+ bool get_sort_by_name()
+ {
+ return export_settings.sort_by_name;
+ }
+
+ BC_export_transformation_type get_object_transformation_type()
+ {
+ return export_settings.object_transformation_type;
+ }
+
+ BC_export_transformation_type get_animation_transformation_type()
+ {
+ return export_settings.animation_transformation_type;
+ }
+
+ bool get_open_sim()
+ {
+ return export_settings.open_sim;
+ }
+
+ bool get_limit_precision()
+ {
+ return export_settings.limit_precision;
+ }
+
+ bool get_keep_bind_info()
+ {
+ return export_settings.keep_bind_info;
+ }
+
+ char *get_filepath()
+ {
+ return export_settings.filepath;
+ }
+
+ LinkNode *get_export_set()
+ {
+ return export_settings.export_set;
+ }
+
+ BlenderContext &get_blender_context()
+ {
+ return blender_context;
+ }
+
+ Scene *get_scene()
+ {
+ return blender_context.get_scene();
+ }
+
+ ViewLayer *get_view_layer()
+ {
+ return blender_context.get_view_layer();
+ }
+
+ bool is_export_root(Object *ob)
+ {
+ return bc_is_base_node(get_export_set(), ob, get_view_layer());
+ }
+};
+
+#endif
+
+#endif
diff --git a/source/blender/io/collada/ExtraHandler.cpp b/source/blender/io/collada/ExtraHandler.cpp
new file mode 100644
index 00000000000..4875ee72b0f
--- /dev/null
+++ b/source/blender/io/collada/ExtraHandler.cpp
@@ -0,0 +1,93 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <stddef.h>
+#include "BLI_string.h"
+
+#include "ExtraHandler.h"
+
+ExtraHandler::ExtraHandler(DocumentImporter *dimp, AnimationImporter *aimp) : currentExtraTags(0)
+{
+ this->dimp = dimp;
+ this->aimp = aimp;
+}
+
+ExtraHandler::~ExtraHandler()
+{
+}
+
+bool ExtraHandler::elementBegin(const char *elementName, const char **attributes)
+{
+ /* \todo attribute handling for profile tags */
+ currentElement = std::string(elementName);
+ // addToSidTree(attributes[0], attributes[1]);
+ return true;
+}
+
+bool ExtraHandler::elementEnd(const char *elementName)
+{
+ return true;
+}
+
+bool ExtraHandler::textData(const char *text, size_t textLength)
+{
+ char buf[1024];
+
+ if (currentElement.length() == 0 || currentExtraTags == 0) {
+ return false;
+ }
+
+ BLI_strncpy(buf, text, textLength + 1);
+ currentExtraTags->addTag(currentElement, std::string(buf));
+ return true;
+}
+
+bool ExtraHandler::parseElement(const char *profileName,
+ const unsigned long &elementHash,
+ const COLLADAFW::UniqueId &uniqueId)
+{
+ /* implement for backwards compatibility, new version added object parameter */
+ return parseElement(profileName, elementHash, uniqueId, NULL);
+}
+
+bool ExtraHandler::parseElement(const char *profileName,
+ const unsigned long &elementHash,
+ const COLLADAFW::UniqueId &uniqueId,
+ COLLADAFW::Object *object)
+{
+ if (BLI_strcaseeq(profileName, "blender")) {
+#if 0
+ printf("In parseElement for supported profile %s for id %s\n",
+ profileName,
+ uniqueId.toAscii().c_str());
+#endif
+ currentUid = uniqueId;
+ ExtraTags *et = dimp->getExtraTags(uniqueId);
+ if (!et) {
+ et = new ExtraTags(std::string(profileName));
+ dimp->addExtraTags(uniqueId, et);
+ }
+ currentExtraTags = et;
+ return true;
+ }
+ // printf("In parseElement for unsupported profile %s for id %s\n", profileName,
+ // uniqueId.toAscii().c_str());
+ return false;
+}
diff --git a/source/blender/io/collada/ExtraHandler.h b/source/blender/io/collada/ExtraHandler.h
new file mode 100644
index 00000000000..021eb8e9663
--- /dev/null
+++ b/source/blender/io/collada/ExtraHandler.h
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __EXTRAHANDLER_H__
+#define __EXTRAHANDLER_H__
+
+#include <string>
+#include <map>
+#include <vector>
+#include <algorithm> // sort()
+
+#include "COLLADASaxFWLIExtraDataCallbackHandler.h"
+#include "COLLADASaxFWLFilePartLoader.h"
+#include "COLLADASWInstanceController.h"
+
+#include "DocumentImporter.h"
+#include "AnimationImporter.h"
+
+/** \brief Handler class for \<extra\> data, through which different
+ * profiles can be handled
+ */
+class ExtraHandler : public COLLADASaxFWL::IExtraDataCallbackHandler {
+ public:
+ /** Constructor. */
+ ExtraHandler(DocumentImporter *dimp, AnimationImporter *aimp);
+
+ /** Destructor. */
+ virtual ~ExtraHandler();
+
+ /** Handle the beginning of an element. */
+ bool elementBegin(const char *elementName, const char **attributes);
+
+ /** Handle the end of an element. */
+ bool elementEnd(const char *elementName);
+
+ /** Receive the data in text format. */
+ bool textData(const char *text, size_t textLength);
+
+ /** Method to ask, if the current callback handler want to read the data of the given extra
+ * element. */
+ bool parseElement(const char *profileName,
+ const unsigned long &elementHash,
+ const COLLADAFW::UniqueId &uniqueId,
+ COLLADAFW::Object *object);
+
+ /** For backwards compatibility with older OpenCollada, new version added object parameter */
+ bool parseElement(const char *profileName,
+ const unsigned long &elementHash,
+ const COLLADAFW::UniqueId &uniqueId);
+
+ private:
+ /** Disable default copy constructor. */
+ ExtraHandler(const ExtraHandler &pre);
+ /** Disable default assignment operator. */
+ const ExtraHandler &operator=(const ExtraHandler &pre);
+
+ /** Handle to DocumentImporter for interface to extra element data saving. */
+ DocumentImporter *dimp;
+ AnimationImporter *aimp;
+ /** Holds Id of element for which <extra> XML elements are handled. */
+ COLLADAFW::UniqueId currentUid;
+ ExtraTags *currentExtraTags;
+ std::string currentElement;
+};
+
+#endif /* __EXTRAHANDLER_H__ */
diff --git a/source/blender/io/collada/ExtraTags.cpp b/source/blender/io/collada/ExtraTags.cpp
new file mode 100644
index 00000000000..496ba3891f7
--- /dev/null
+++ b/source/blender/io/collada/ExtraTags.cpp
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <stddef.h>
+#include <stdlib.h>
+#include "BLI_string.h"
+
+#include <iostream>
+
+#include "ExtraTags.h"
+
+ExtraTags::ExtraTags(std::string profile)
+{
+ this->profile = profile;
+ this->tags = std::map<std::string, std::string>();
+}
+
+ExtraTags::~ExtraTags()
+{
+}
+
+bool ExtraTags::isProfile(std::string profile)
+{
+ return this->profile == profile;
+}
+
+bool ExtraTags::addTag(std::string tag, std::string data)
+{
+ tags[tag] = data;
+
+ return true;
+}
+
+int ExtraTags::asInt(std::string tag, bool *ok)
+{
+ if (tags.find(tag) == tags.end()) {
+ *ok = false;
+ return -1;
+ }
+ *ok = true;
+ return atoi(tags[tag].c_str());
+}
+
+float ExtraTags::asFloat(std::string tag, bool *ok)
+{
+ if (tags.find(tag) == tags.end()) {
+ *ok = false;
+ return -1.0f;
+ }
+ *ok = true;
+ return (float)atof(tags[tag].c_str());
+}
+
+std::string ExtraTags::asString(std::string tag, bool *ok)
+{
+ if (tags.find(tag) == tags.end()) {
+ *ok = false;
+ return "";
+ }
+ *ok = true;
+ return tags[tag];
+}
+
+bool ExtraTags::setData(std::string tag, short *data)
+{
+ bool ok = false;
+ int tmp = asInt(tag, &ok);
+ if (ok) {
+ *data = (short)tmp;
+ }
+ return ok;
+}
+
+bool ExtraTags::setData(std::string tag, int *data)
+{
+ bool ok = false;
+ int tmp = asInt(tag, &ok);
+ if (ok) {
+ *data = tmp;
+ }
+ return ok;
+}
+
+bool ExtraTags::setData(std::string tag, float *data)
+{
+ bool ok = false;
+ float tmp = asFloat(tag, &ok);
+ if (ok) {
+ *data = tmp;
+ }
+ return ok;
+}
+
+bool ExtraTags::setData(std::string tag, char *data)
+{
+ bool ok = false;
+ int tmp = asInt(tag, &ok);
+ if (ok) {
+ *data = (char)tmp;
+ }
+ return ok;
+}
+
+std::string ExtraTags::setData(std::string tag, std::string &data)
+{
+ bool ok = false;
+ std::string tmp = asString(tag, &ok);
+ return (ok) ? tmp : data;
+}
diff --git a/source/blender/io/collada/ExtraTags.h b/source/blender/io/collada/ExtraTags.h
new file mode 100644
index 00000000000..9191182c757
--- /dev/null
+++ b/source/blender/io/collada/ExtraTags.h
@@ -0,0 +1,77 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __EXTRATAGS_H__
+#define __EXTRATAGS_H__
+
+#include <string>
+#include <map>
+#include <vector>
+
+/** \brief Class for saving \<extra\> tags for a specific UniqueId.
+ */
+class ExtraTags {
+ public:
+ /** Constructor. */
+ ExtraTags(const std::string profile);
+
+ /** Destructor. */
+ virtual ~ExtraTags();
+
+ /** Handle the beginning of an element. */
+ bool addTag(std::string tag, std::string data);
+
+ /** Set given short pointer to value of tag, if it exists. */
+ bool setData(std::string tag, short *data);
+
+ /** Set given int pointer to value of tag, if it exists. */
+ bool setData(std::string tag, int *data);
+
+ /** Set given float pointer to value of tag, if it exists. */
+ bool setData(std::string tag, float *data);
+
+ /** Set given char pointer to value of tag, if it exists. */
+ bool setData(std::string tag, char *data);
+ std::string setData(std::string tag, std::string &data);
+
+ /** Return true if the extra tags is for specified profile. */
+ bool isProfile(std::string profile);
+
+ private:
+ /** Disable default copy constructor. */
+ ExtraTags(const ExtraTags &pre);
+ /** Disable default assignment operator. */
+ const ExtraTags &operator=(const ExtraTags &pre);
+
+ /** The profile for which the tags are. */
+ std::string profile;
+
+ /** Map of tag and text pairs. */
+ std::map<std::string, std::string> tags;
+
+ /** Get text data for tag as an int. */
+ int asInt(std::string tag, bool *ok);
+ /** Get text data for tag as a float. */
+ float asFloat(std::string tag, bool *ok);
+ /** Get text data for tag as a string. */
+ std::string asString(std::string tag, bool *ok);
+};
+
+#endif /* __EXTRATAGS_H__ */
diff --git a/source/blender/io/collada/GeometryExporter.cpp b/source/blender/io/collada/GeometryExporter.cpp
new file mode 100644
index 00000000000..640bf3c0633
--- /dev/null
+++ b/source/blender/io/collada/GeometryExporter.cpp
@@ -0,0 +1,718 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <sstream>
+
+#include "COLLADASWPrimitves.h"
+#include "COLLADASWSource.h"
+#include "COLLADASWVertices.h"
+#include "COLLADABUUtils.h"
+
+#include "GeometryExporter.h"
+
+#include "DNA_meshdata_types.h"
+
+extern "C" {
+#include "BLI_utildefines.h"
+
+#include "BKE_customdata.h"
+#include "BKE_global.h"
+#include "BKE_lib_id.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+}
+
+#include "collada_internal.h"
+#include "collada_utils.h"
+
+void GeometryExporter::exportGeom()
+{
+ Scene *sce = blender_context.get_scene();
+ openLibrary();
+
+ GeometryFunctor gf;
+ gf.forEachMeshObjectInExportSet<GeometryExporter>(
+ sce, *this, this->export_settings.get_export_set());
+
+ closeLibrary();
+}
+
+void GeometryExporter::operator()(Object *ob)
+{
+ bool use_instantiation = this->export_settings.get_use_object_instantiation();
+ Mesh *me = bc_get_mesh_copy(blender_context,
+ ob,
+ this->export_settings.get_export_mesh_type(),
+ this->export_settings.get_apply_modifiers(),
+ this->export_settings.get_triangulate());
+
+ std::string geom_id = get_geometry_id(ob, use_instantiation);
+ std::vector<Normal> nor;
+ std::vector<BCPolygonNormalsIndices> norind;
+
+ /* Skip if linked geometry was already exported from another reference */
+ if (use_instantiation && exportedGeometry.find(geom_id) != exportedGeometry.end()) {
+ return;
+ }
+
+ std::string geom_name = (use_instantiation) ? id_name(ob->data) : id_name(ob);
+ geom_name = encode_xml(geom_name);
+
+ exportedGeometry.insert(geom_id);
+
+ bool has_color = (bool)CustomData_has_layer(&me->fdata, CD_MCOL);
+
+ create_normals(nor, norind, me);
+
+ /* openMesh(geoId, geoName, meshId) */
+ openMesh(geom_id, geom_name);
+
+ /* writes <source> for vertex coords */
+ createVertsSource(geom_id, me);
+
+ /* writes <source> for normal coords */
+ createNormalsSource(geom_id, me, nor);
+
+ bool has_uvs = (bool)CustomData_has_layer(&me->ldata, CD_MLOOPUV);
+
+ /* writes <source> for uv coords if mesh has uv coords */
+ if (has_uvs) {
+ createTexcoordsSource(geom_id, me);
+ }
+
+ if (has_color) {
+ createVertexColorSource(geom_id, me);
+ }
+ /* <vertices> */
+
+ COLLADASW::Vertices verts(mSW);
+ verts.setId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::VERTEX));
+ COLLADASW::InputList &input_list = verts.getInputList();
+ COLLADASW::Input input(COLLADASW::InputSemantic::POSITION,
+ getUrlBySemantics(geom_id, COLLADASW::InputSemantic::POSITION));
+ input_list.push_back(input);
+ verts.add();
+
+ createLooseEdgeList(ob, me, geom_id);
+
+ /* Only create Polylists if number of faces > 0 */
+ if (me->totface > 0) {
+ /* XXX slow */
+ if (ob->totcol) {
+ for (int a = 0; a < ob->totcol; a++) {
+ create_mesh_primitive_list(a, has_uvs, has_color, ob, me, geom_id, norind);
+ }
+ }
+ else {
+ create_mesh_primitive_list(0, has_uvs, has_color, ob, me, geom_id, norind);
+ }
+ }
+
+ closeMesh();
+
+ closeGeometry();
+
+ if (this->export_settings.get_include_shapekeys()) {
+ Key *key = BKE_key_from_object(ob);
+ if (key) {
+ KeyBlock *kb = (KeyBlock *)key->block.first;
+ /* skip the basis */
+ kb = kb->next;
+ for (; kb; kb = kb->next) {
+ BKE_keyblock_convert_to_mesh(kb, me);
+ export_key_mesh(ob, me, kb);
+ }
+ }
+ }
+
+ BKE_id_free(NULL, me);
+}
+
+void GeometryExporter::export_key_mesh(Object *ob, Mesh *me, KeyBlock *kb)
+{
+ std::string geom_id = get_geometry_id(ob, false) + "_morph_" + translate_id(kb->name);
+ std::vector<Normal> nor;
+ std::vector<BCPolygonNormalsIndices> norind;
+
+ if (exportedGeometry.find(geom_id) != exportedGeometry.end()) {
+ return;
+ }
+
+ std::string geom_name = kb->name;
+
+ exportedGeometry.insert(geom_id);
+
+ bool has_color = (bool)CustomData_has_layer(&me->fdata, CD_MCOL);
+
+ create_normals(nor, norind, me);
+
+ // openMesh(geoId, geoName, meshId)
+ openMesh(geom_id, geom_name);
+
+ /* writes <source> for vertex coords */
+ createVertsSource(geom_id, me);
+
+ /* writes <source> for normal coords */
+ createNormalsSource(geom_id, me, nor);
+
+ bool has_uvs = (bool)CustomData_has_layer(&me->ldata, CD_MLOOPUV);
+
+ /* writes <source> for uv coords if mesh has uv coords */
+ if (has_uvs) {
+ createTexcoordsSource(geom_id, me);
+ }
+
+ if (has_color) {
+ createVertexColorSource(geom_id, me);
+ }
+
+ /* <vertices> */
+
+ COLLADASW::Vertices verts(mSW);
+ verts.setId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::VERTEX));
+ COLLADASW::InputList &input_list = verts.getInputList();
+ COLLADASW::Input input(COLLADASW::InputSemantic::POSITION,
+ getUrlBySemantics(geom_id, COLLADASW::InputSemantic::POSITION));
+ input_list.push_back(input);
+ verts.add();
+
+ // createLooseEdgeList(ob, me, geom_id, norind);
+
+ /* XXX slow */
+ if (ob->totcol) {
+ for (int a = 0; a < ob->totcol; a++) {
+ create_mesh_primitive_list(a, has_uvs, has_color, ob, me, geom_id, norind);
+ }
+ }
+ else {
+ create_mesh_primitive_list(0, has_uvs, has_color, ob, me, geom_id, norind);
+ }
+
+ closeMesh();
+
+ closeGeometry();
+}
+
+void GeometryExporter::createLooseEdgeList(Object *ob, Mesh *me, std::string &geom_id)
+{
+
+ MEdge *medges = me->medge;
+ int totedges = me->totedge;
+ int edges_in_linelist = 0;
+ std::vector<unsigned int> edge_list;
+ int index;
+
+ /* Find all loose edges in Mesh
+ * and save vertex indices in edge_list */
+ for (index = 0; index < totedges; index++) {
+ MEdge *edge = &medges[index];
+
+ if (edge->flag & ME_LOOSEEDGE) {
+ edges_in_linelist += 1;
+ edge_list.push_back(edge->v1);
+ edge_list.push_back(edge->v2);
+ }
+ }
+
+ if (edges_in_linelist > 0) {
+ /* Create the list of loose edges */
+ COLLADASW::Lines lines(mSW);
+
+ lines.setCount(edges_in_linelist);
+
+ COLLADASW::InputList &til = lines.getInputList();
+
+ /* creates <input> in <lines> for vertices */
+ COLLADASW::Input input1(COLLADASW::InputSemantic::VERTEX,
+ getUrlBySemantics(geom_id, COLLADASW::InputSemantic::VERTEX),
+ 0);
+ til.push_back(input1);
+
+ lines.prepareToAppendValues();
+
+ for (index = 0; index < edges_in_linelist; index++) {
+ lines.appendValues(edge_list[2 * index + 1]);
+ lines.appendValues(edge_list[2 * index]);
+ }
+ lines.finish();
+ }
+}
+
+static void prepareToAppendValues(bool is_triangulated,
+ COLLADASW::PrimitivesBase &primitive_list,
+ std::vector<unsigned long> &vcount_list)
+{
+ /* performs the actual writing */
+ if (is_triangulated) {
+ ((COLLADASW::Triangles &)primitive_list).prepareToAppendValues();
+ }
+ else {
+ /* sets <vcount> */
+ primitive_list.setVCountList(vcount_list);
+ ((COLLADASW::Polylist &)primitive_list).prepareToAppendValues();
+ }
+}
+
+static void finish_and_delete_primitive_List(bool is_triangulated,
+ COLLADASW::PrimitivesBase *primitive_list)
+{
+ if (is_triangulated) {
+ ((COLLADASW::Triangles *)primitive_list)->finish();
+ }
+ else {
+ ((COLLADASW::Polylist *)primitive_list)->finish();
+ }
+ delete primitive_list;
+}
+
+static COLLADASW::PrimitivesBase *create_primitive_list(bool is_triangulated,
+ COLLADASW::StreamWriter *mSW)
+{
+ COLLADASW::PrimitivesBase *primitive_list;
+
+ if (is_triangulated) {
+ primitive_list = new COLLADASW::Triangles(mSW);
+ }
+ else {
+ primitive_list = new COLLADASW::Polylist(mSW);
+ }
+ return primitive_list;
+}
+
+static bool collect_vertex_counts_per_poly(Mesh *me,
+ int material_index,
+ std::vector<unsigned long> &vcount_list)
+{
+ MPoly *mpolys = me->mpoly;
+ int totpolys = me->totpoly;
+ bool is_triangulated = true;
+
+ int i;
+ /* Expecting that p->mat_nr is always 0 if the mesh has no materials assigned */
+ for (i = 0; i < totpolys; i++) {
+ MPoly *p = &mpolys[i];
+ if (p->mat_nr == material_index) {
+ int vertex_count = p->totloop;
+ vcount_list.push_back(vertex_count);
+ if (vertex_count != 3) {
+ is_triangulated = false;
+ }
+ }
+ }
+ return is_triangulated;
+}
+
+std::string GeometryExporter::makeVertexColorSourceId(std::string &geom_id, char *layer_name)
+{
+ std::string result = getIdBySemantics(geom_id, COLLADASW::InputSemantic::COLOR) + "-" +
+ layer_name;
+ return result;
+}
+
+/* powerful because it handles both cases when there is material and when there's not */
+void GeometryExporter::create_mesh_primitive_list(short material_index,
+ bool has_uvs,
+ bool has_color,
+ Object *ob,
+ Mesh *me,
+ std::string &geom_id,
+ std::vector<BCPolygonNormalsIndices> &norind)
+{
+
+ MPoly *mpolys = me->mpoly;
+ MLoop *mloops = me->mloop;
+ int totpolys = me->totpoly;
+
+ std::vector<unsigned long> vcount_list;
+
+ bool is_triangulated = collect_vertex_counts_per_poly(me, material_index, vcount_list);
+ int polygon_count = vcount_list.size();
+
+ /* no faces using this material */
+ if (polygon_count == 0) {
+ fprintf(
+ stderr, "%s: material with index %d is not used.\n", id_name(ob).c_str(), material_index);
+ return;
+ }
+
+ Material *ma = ob->totcol ? BKE_object_material_get(ob, material_index + 1) : NULL;
+ COLLADASW::PrimitivesBase *primitive_list = create_primitive_list(is_triangulated, mSW);
+
+ /* sets count attribute in <polylist> */
+ primitive_list->setCount(polygon_count);
+
+ /* sets material name */
+ if (ma) {
+ std::string material_id = get_material_id(ma);
+ std::ostringstream ostr;
+ ostr << translate_id(material_id);
+ primitive_list->setMaterial(ostr.str());
+ }
+
+ COLLADASW::Input vertex_input(COLLADASW::InputSemantic::VERTEX,
+ getUrlBySemantics(geom_id, COLLADASW::InputSemantic::VERTEX),
+ 0);
+ COLLADASW::Input normals_input(COLLADASW::InputSemantic::NORMAL,
+ getUrlBySemantics(geom_id, COLLADASW::InputSemantic::NORMAL),
+ 1);
+
+ COLLADASW::InputList &til = primitive_list->getInputList();
+ til.push_back(vertex_input);
+ til.push_back(normals_input);
+
+ /* if mesh has uv coords writes <input> for TEXCOORD */
+ int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV);
+ int active_uv_index = CustomData_get_active_layer_index(&me->ldata, CD_MLOOPUV);
+ for (int i = 0; i < num_layers; i++) {
+ int layer_index = CustomData_get_layer_index_n(&me->ldata, CD_MLOOPUV, i);
+ if (!this->export_settings.get_active_uv_only() || layer_index == active_uv_index) {
+
+ // char *name = CustomData_get_layer_name(&me->ldata, CD_MLOOPUV, i);
+ COLLADASW::Input texcoord_input(
+ COLLADASW::InputSemantic::TEXCOORD,
+ makeUrl(makeTexcoordSourceId(geom_id, i, this->export_settings.get_active_uv_only())),
+ 2, // this is only until we have optimized UV sets
+ (this->export_settings.get_active_uv_only()) ? 0 : layer_index - 1 /* set (0,1,2,...) */
+ );
+ til.push_back(texcoord_input);
+ }
+ }
+
+ int totlayer_mcol = CustomData_number_of_layers(&me->ldata, CD_MLOOPCOL);
+ if (totlayer_mcol > 0) {
+ int map_index = 0;
+
+ for (int a = 0; a < totlayer_mcol; a++) {
+ char *layer_name = bc_CustomData_get_layer_name(&me->ldata, CD_MLOOPCOL, a);
+ COLLADASW::Input input4(COLLADASW::InputSemantic::COLOR,
+ makeUrl(makeVertexColorSourceId(geom_id, layer_name)),
+ (has_uvs) ? 3 : 2, // all color layers have same index order
+ map_index // set number equals color map index
+ );
+ til.push_back(input4);
+ map_index++;
+ }
+ }
+
+ /* performs the actual writing */
+ prepareToAppendValues(is_triangulated, *primitive_list, vcount_list);
+
+ /* <p> */
+ int texindex = 0;
+ for (int i = 0; i < totpolys; i++) {
+ MPoly *p = &mpolys[i];
+ int loop_count = p->totloop;
+
+ if (p->mat_nr == material_index) {
+ MLoop *l = &mloops[p->loopstart];
+ BCPolygonNormalsIndices normal_indices = norind[i];
+
+ for (int j = 0; j < loop_count; j++) {
+ primitive_list->appendValues(l[j].v);
+ primitive_list->appendValues(normal_indices[j]);
+ if (has_uvs) {
+ primitive_list->appendValues(texindex + j);
+ }
+
+ if (has_color) {
+ primitive_list->appendValues(texindex + j);
+ }
+ }
+ }
+
+ texindex += loop_count;
+ }
+
+ finish_and_delete_primitive_List(is_triangulated, primitive_list);
+}
+
+/* creates <source> for positions */
+void GeometryExporter::createVertsSource(std::string geom_id, Mesh *me)
+{
+#if 0
+ int totverts = dm->getNumVerts(dm);
+ MVert *verts = dm->getVertArray(dm);
+#endif
+ int totverts = me->totvert;
+ MVert *verts = me->mvert;
+
+ COLLADASW::FloatSourceF source(mSW);
+ source.setId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::POSITION));
+ source.setArrayId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::POSITION) +
+ ARRAY_ID_SUFFIX);
+ source.setAccessorCount(totverts);
+ source.setAccessorStride(3);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("X");
+ param.push_back("Y");
+ param.push_back("Z");
+ /* main function, it creates <source id = "">, <float_array id = ""
+ * count = ""> */
+ source.prepareToAppendValues();
+ /* appends data to <float_array> */
+ int i = 0;
+ for (i = 0; i < totverts; i++) {
+ Vector co;
+ if (export_settings.get_apply_global_orientation()) {
+ bc_add_global_transform(co, verts[i].co, export_settings.get_global_transform());
+ }
+ else {
+ copy_v3_v3(co, verts[i].co);
+ }
+ source.appendValues(co[0], co[1], co[2]);
+ }
+
+ source.finish();
+}
+
+void GeometryExporter::createVertexColorSource(std::string geom_id, Mesh *me)
+{
+ /* Find number of vertex color layers */
+ int totlayer_mcol = CustomData_number_of_layers(&me->ldata, CD_MLOOPCOL);
+ if (totlayer_mcol == 0) {
+ return;
+ }
+
+ int map_index = 0;
+ for (int a = 0; a < totlayer_mcol; a++) {
+
+ map_index++;
+ MLoopCol *mloopcol = (MLoopCol *)CustomData_get_layer_n(&me->ldata, CD_MLOOPCOL, a);
+
+ COLLADASW::FloatSourceF source(mSW);
+
+ char *layer_name = bc_CustomData_get_layer_name(&me->ldata, CD_MLOOPCOL, a);
+ std::string layer_id = makeVertexColorSourceId(geom_id, layer_name);
+ source.setId(layer_id);
+
+ source.setNodeName(layer_name);
+
+ source.setArrayId(layer_id + ARRAY_ID_SUFFIX);
+ source.setAccessorCount(me->totloop);
+ source.setAccessorStride(4);
+
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("R");
+ param.push_back("G");
+ param.push_back("B");
+ param.push_back("A");
+
+ source.prepareToAppendValues();
+
+ MPoly *mpoly;
+ int i;
+ for (i = 0, mpoly = me->mpoly; i < me->totpoly; i++, mpoly++) {
+ MLoopCol *mlc = mloopcol + mpoly->loopstart;
+ for (int j = 0; j < mpoly->totloop; j++, mlc++) {
+ source.appendValues(mlc->r / 255.0f, mlc->g / 255.0f, mlc->b / 255.0f, mlc->a / 255.0f);
+ }
+ }
+
+ source.finish();
+ }
+}
+
+std::string GeometryExporter::makeTexcoordSourceId(std::string &geom_id,
+ int layer_index,
+ bool is_single_layer)
+{
+ char suffix[20];
+ if (is_single_layer) {
+ suffix[0] = '\0';
+ }
+ else {
+ sprintf(suffix, "-%d", layer_index);
+ }
+ return getIdBySemantics(geom_id, COLLADASW::InputSemantic::TEXCOORD) + suffix;
+}
+
+/* creates <source> for texcoords */
+void GeometryExporter::createTexcoordsSource(std::string geom_id, Mesh *me)
+{
+
+ int totpoly = me->totpoly;
+ int totuv = me->totloop;
+ MPoly *mpolys = me->mpoly;
+
+ int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV);
+
+ /* write <source> for each layer
+ * each <source> will get id like meshName + "map-channel-1" */
+ int active_uv_index = CustomData_get_active_layer_index(&me->ldata, CD_MLOOPUV);
+ for (int a = 0; a < num_layers; a++) {
+ int layer_index = CustomData_get_layer_index_n(&me->ldata, CD_MLOOPUV, a);
+ if (!this->export_settings.get_active_uv_only() || layer_index == active_uv_index) {
+ MLoopUV *mloops = (MLoopUV *)CustomData_get_layer_n(&me->ldata, CD_MLOOPUV, a);
+
+ COLLADASW::FloatSourceF source(mSW);
+ std::string layer_id = makeTexcoordSourceId(
+ geom_id, a, this->export_settings.get_active_uv_only());
+ source.setId(layer_id);
+ source.setArrayId(layer_id + ARRAY_ID_SUFFIX);
+
+ source.setAccessorCount(totuv);
+ source.setAccessorStride(2);
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("S");
+ param.push_back("T");
+
+ source.prepareToAppendValues();
+
+ for (int index = 0; index < totpoly; index++) {
+ MPoly *mpoly = mpolys + index;
+ MLoopUV *mloop = mloops + mpoly->loopstart;
+ for (int j = 0; j < mpoly->totloop; j++) {
+ source.appendValues(mloop[j].uv[0], mloop[j].uv[1]);
+ }
+ }
+
+ source.finish();
+ }
+ }
+}
+
+bool operator<(const Normal &a, const Normal &b)
+{
+ /* only needed to sort normal vectors and find() them later in a map.*/
+ return a.x < b.x || (a.x == b.x && (a.y < b.y || (a.y == b.y && a.z < b.z)));
+}
+
+/* creates <source> for normals */
+void GeometryExporter::createNormalsSource(std::string geom_id, Mesh *me, std::vector<Normal> &nor)
+{
+#if 0
+ int totverts = dm->getNumVerts(dm);
+ MVert *verts = dm->getVertArray(dm);
+#endif
+
+ COLLADASW::FloatSourceF source(mSW);
+ source.setId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::NORMAL));
+ source.setArrayId(getIdBySemantics(geom_id, COLLADASW::InputSemantic::NORMAL) + ARRAY_ID_SUFFIX);
+ source.setAccessorCount((unsigned long)nor.size());
+ source.setAccessorStride(3);
+ COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
+ param.push_back("X");
+ param.push_back("Y");
+ param.push_back("Z");
+
+ source.prepareToAppendValues();
+
+ std::vector<Normal>::iterator it;
+ for (it = nor.begin(); it != nor.end(); it++) {
+ Normal &n = *it;
+
+ Vector no{n.x, n.y, n.z};
+ if (export_settings.get_apply_global_orientation()) {
+ bc_add_global_transform(no, export_settings.get_global_transform());
+ }
+ source.appendValues(no[0], no[1], no[2]);
+ }
+
+ source.finish();
+}
+
+void GeometryExporter::create_normals(std::vector<Normal> &normals,
+ std::vector<BCPolygonNormalsIndices> &polygons_normals,
+ Mesh *me)
+{
+ std::map<Normal, unsigned int> shared_normal_indices;
+ int last_normal_index = -1;
+
+ MVert *verts = me->mvert;
+ MLoop *mloops = me->mloop;
+ float(*lnors)[3] = NULL;
+ bool use_custom_normals = false;
+
+ BKE_mesh_calc_normals_split(me);
+ if (CustomData_has_layer(&me->ldata, CD_NORMAL)) {
+ lnors = (float(*)[3])CustomData_get_layer(&me->ldata, CD_NORMAL);
+ use_custom_normals = true;
+ }
+
+ for (int poly_index = 0; poly_index < me->totpoly; poly_index++) {
+ MPoly *mpoly = &me->mpoly[poly_index];
+ bool use_vertex_normals = use_custom_normals || mpoly->flag & ME_SMOOTH;
+
+ if (!use_vertex_normals) {
+ /* For flat faces use face normal as vertex normal: */
+
+ float vector[3];
+ BKE_mesh_calc_poly_normal(mpoly, mloops + mpoly->loopstart, verts, vector);
+
+ Normal n = {vector[0], vector[1], vector[2]};
+ normals.push_back(n);
+ last_normal_index++;
+ }
+
+ BCPolygonNormalsIndices poly_indices;
+ for (int loop_index = 0; loop_index < mpoly->totloop; loop_index++) {
+ unsigned int loop_idx = mpoly->loopstart + loop_index;
+ if (use_vertex_normals) {
+ float normalized[3];
+
+ if (use_custom_normals) {
+ normalize_v3_v3(normalized, lnors[loop_idx]);
+ }
+ else {
+ normal_short_to_float_v3(normalized, verts[mloops[loop_index].v].no);
+ normalize_v3(normalized);
+ }
+ Normal n = {normalized[0], normalized[1], normalized[2]};
+
+ if (shared_normal_indices.find(n) != shared_normal_indices.end()) {
+ poly_indices.add_index(shared_normal_indices[n]);
+ }
+ else {
+ last_normal_index++;
+ poly_indices.add_index(last_normal_index);
+ shared_normal_indices[n] = last_normal_index;
+ normals.push_back(n);
+ }
+ }
+ else {
+ poly_indices.add_index(last_normal_index);
+ }
+ }
+
+ polygons_normals.push_back(poly_indices);
+ }
+}
+
+std::string GeometryExporter::getIdBySemantics(std::string geom_id,
+ COLLADASW::InputSemantic::Semantics type,
+ std::string other_suffix)
+{
+ return geom_id + getSuffixBySemantic(type) + other_suffix;
+}
+
+COLLADASW::URI GeometryExporter::getUrlBySemantics(std::string geom_id,
+ COLLADASW::InputSemantic::Semantics type,
+ std::string other_suffix)
+{
+
+ std::string id(getIdBySemantics(geom_id, type, other_suffix));
+ return COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, id);
+}
+
+COLLADASW::URI GeometryExporter::makeUrl(std::string id)
+{
+ return COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, id);
+}
diff --git a/source/blender/io/collada/GeometryExporter.h b/source/blender/io/collada/GeometryExporter.h
new file mode 100644
index 00000000000..8c7a38fc407
--- /dev/null
+++ b/source/blender/io/collada/GeometryExporter.h
@@ -0,0 +1,140 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __GEOMETRYEXPORTER_H__
+#define __GEOMETRYEXPORTER_H__
+
+#include <string>
+#include <vector>
+#include <set>
+
+#include "COLLADASWStreamWriter.h"
+#include "COLLADASWLibraryGeometries.h"
+#include "COLLADASWInputList.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_key_types.h"
+
+#include "ExportSettings.h"
+#include "collada_utils.h"
+#include "BlenderContext.h"
+#include "BKE_key.h"
+
+class Normal {
+ public:
+ float x;
+ float y;
+ float z;
+
+ friend bool operator<(const Normal &, const Normal &);
+};
+
+bool operator<(const Normal &, const Normal &);
+
+/* TODO: optimize UV sets by making indexed list with duplicates removed */
+class GeometryExporter : COLLADASW::LibraryGeometries {
+ struct Face {
+ unsigned int v1, v2, v3, v4;
+ };
+
+ public:
+ /* TODO: optimize UV sets by making indexed list with duplicates removed */
+ GeometryExporter(BlenderContext &blender_context,
+ COLLADASW::StreamWriter *sw,
+ BCExportSettings &export_settings)
+ : COLLADASW::LibraryGeometries(sw),
+ blender_context(blender_context),
+ export_settings(export_settings)
+ {
+ }
+
+ void exportGeom();
+
+ void operator()(Object *ob);
+
+ void createLooseEdgeList(Object *ob, Mesh *me, std::string &geom_id);
+
+ /* powerful because it handles both cases when there is material and when there's not */
+ void create_mesh_primitive_list(short material_index,
+ bool has_uvs,
+ bool has_color,
+ Object *ob,
+ Mesh *me,
+ std::string &geom_id,
+ std::vector<BCPolygonNormalsIndices> &norind);
+
+ /* creates <source> for positions */
+ void createVertsSource(std::string geom_id, Mesh *me);
+
+ void createVertexColorSource(std::string geom_id, Mesh *me);
+
+ std::string makeTexcoordSourceId(std::string &geom_id, int layer_index, bool is_single_layer);
+
+ /* creates <source> for texcoords */
+ void createTexcoordsSource(std::string geom_id, Mesh *me);
+ void createTesselatedTexcoordsSource(std::string geom_id, Mesh *me);
+
+ /* creates <source> for normals */
+ void createNormalsSource(std::string geom_id, Mesh *me, std::vector<Normal> &nor);
+
+ void create_normals(std::vector<Normal> &nor,
+ std::vector<BCPolygonNormalsIndices> &ind,
+ Mesh *me);
+
+ std::string getIdBySemantics(std::string geom_id,
+ COLLADASW::InputSemantic::Semantics type,
+ std::string other_suffix = "");
+ std::string makeVertexColorSourceId(std::string &geom_id, char *layer_name);
+
+ COLLADASW::URI getUrlBySemantics(std::string geom_id,
+ COLLADASW::InputSemantic::Semantics type,
+ std::string other_suffix = "");
+
+ COLLADASW::URI makeUrl(std::string id);
+
+ void export_key_mesh(Object *ob, Mesh *me, KeyBlock *kb);
+
+ private:
+ std::set<std::string> exportedGeometry;
+ BlenderContext &blender_context;
+ BCExportSettings &export_settings;
+
+ Mesh *get_mesh(Scene *sce, Object *ob, int apply_modifiers);
+};
+
+struct GeometryFunctor {
+ /* f should have
+ * void operator()(Object *ob) */
+ template<class Functor>
+ void forEachMeshObjectInExportSet(Scene *sce, Functor &f, LinkNode *export_set)
+ {
+ LinkNode *node;
+ for (node = export_set; node; node = node->next) {
+ Object *ob = (Object *)node->link;
+ if (ob->type == OB_MESH) {
+ f(ob);
+ }
+ }
+ }
+};
+
+#endif
diff --git a/source/blender/io/collada/ImageExporter.cpp b/source/blender/io/collada/ImageExporter.cpp
new file mode 100644
index 00000000000..6e31e17fb26
--- /dev/null
+++ b/source/blender/io/collada/ImageExporter.cpp
@@ -0,0 +1,169 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include "COLLADABUURI.h"
+#include "COLLADASWImage.h"
+
+extern "C" {
+#include "DNA_texture_types.h"
+#include "DNA_image_types.h"
+#include "DNA_meshdata_types.h"
+
+#include "BKE_customdata.h"
+#include "BKE_global.h"
+#include "BKE_image.h"
+#include "BKE_main.h"
+#include "BKE_mesh.h"
+
+#include "BLI_fileops.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "IMB_imbuf_types.h"
+}
+
+#include "ImageExporter.h"
+#include "MaterialExporter.h"
+
+ImagesExporter::ImagesExporter(COLLADASW::StreamWriter *sw,
+ BCExportSettings &export_settings,
+ KeyImageMap &key_image_map)
+ : COLLADASW::LibraryImages(sw), export_settings(export_settings), key_image_map(key_image_map)
+{
+ /* pass */
+}
+
+void ImagesExporter::export_UV_Image(Image *image, bool use_copies)
+{
+ std::string name(id_name(image));
+ std::string translated_name(translate_id(name));
+
+ ImBuf *imbuf = BKE_image_acquire_ibuf(image, NULL, NULL);
+ if (!imbuf) {
+ fprintf(stderr, "Collada export: image does not exist:\n%s\n", image->name);
+ return;
+ }
+
+ bool is_dirty = BKE_image_is_dirty(image);
+
+ ImageFormatData imageFormat;
+ BKE_imbuf_to_image_format(&imageFormat, imbuf);
+
+ short image_source = image->source;
+ bool is_generated = image_source == IMA_SRC_GENERATED;
+ bool is_packed = BKE_image_has_packedfile(image);
+
+ char export_path[FILE_MAX];
+ char source_path[FILE_MAX];
+ char export_dir[FILE_MAX];
+ char export_file[FILE_MAX];
+
+ /* Destination folder for exported assets */
+ BLI_split_dir_part(this->export_settings.get_filepath(), export_dir, sizeof(export_dir));
+
+ if (is_generated || is_dirty || use_copies || is_packed) {
+
+ /* make absolute destination path */
+
+ BLI_strncpy(export_file, name.c_str(), sizeof(export_file));
+ BKE_image_path_ensure_ext_from_imformat(export_file, &imageFormat);
+
+ BLI_join_dirfile(export_path, sizeof(export_path), export_dir, export_file);
+
+ /* make dest directory if it doesn't exist */
+ BLI_make_existing_file(export_path);
+ }
+
+ if (is_generated || is_dirty || is_packed) {
+
+ /* This image in its current state only exists in Blender memory.
+ * So we have to export it. The export will keep the image state intact,
+ * so the exported file will not be associated with the image. */
+
+ if (BKE_imbuf_write_as(imbuf, export_path, &imageFormat, true) == 0) {
+ fprintf(stderr, "Collada export: Cannot export image to:\n%s\n", export_path);
+ return;
+ }
+ BLI_strncpy(export_path, export_file, sizeof(export_path));
+ }
+ else {
+
+ /* make absolute source path */
+ BLI_strncpy(source_path, image->name, sizeof(source_path));
+ BLI_path_abs(source_path, ID_BLEND_PATH_FROM_GLOBAL(&image->id));
+ BLI_cleanup_path(NULL, source_path);
+
+ if (use_copies) {
+
+ /* This image is already located on the file system.
+ * But we want to create copies here.
+ * To move images into the same export directory.
+ * Note: If an image is already located in the export folder,
+ * then skip the copy (as it would result in a file copy error). */
+
+ if (BLI_path_cmp(source_path, export_path) != 0) {
+ if (BLI_copy(source_path, export_path) != 0) {
+ fprintf(stderr,
+ "Collada export: Cannot copy image:\n source:%s\ndest :%s\n",
+ source_path,
+ export_path);
+ return;
+ }
+ }
+
+ BLI_strncpy(export_path, export_file, sizeof(export_path));
+ }
+ else {
+
+ /* Do not make any copies, but use the source path directly as reference
+ * to the original image */
+
+ BLI_strncpy(export_path, source_path, sizeof(export_path));
+ }
+ }
+
+ /* Set name also to mNameNC.
+ * This helps other viewers import files exported from Blender better. */
+ COLLADASW::Image img(COLLADABU::URI(COLLADABU::URI::nativePathToUri(export_path)),
+ translated_name,
+ translated_name);
+ img.add(mSW);
+ fprintf(stdout, "Collada export: Added image: %s\n", export_file);
+
+ BKE_image_release_ibuf(image, imbuf, NULL);
+}
+
+void ImagesExporter::exportImages(Scene *sce)
+{
+ bool use_texture_copies = this->export_settings.get_use_texture_copies();
+ openLibrary();
+
+ KeyImageMap::iterator iter;
+ for (iter = key_image_map.begin(); iter != key_image_map.end(); iter++) {
+
+ Image *image = iter->second;
+ std::string uid(id_name(image));
+ std::string key = translate_id(uid);
+
+ export_UV_Image(image, use_texture_copies);
+ }
+
+ closeLibrary();
+}
diff --git a/source/blender/io/collada/ImageExporter.h b/source/blender/io/collada/ImageExporter.h
new file mode 100644
index 00000000000..b72d2709382
--- /dev/null
+++ b/source/blender/io/collada/ImageExporter.h
@@ -0,0 +1,51 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __IMAGEEXPORTER_H__
+#define __IMAGEEXPORTER_H__
+
+#include <vector>
+#include <string>
+
+#include "COLLADASWStreamWriter.h"
+#include "COLLADASWLibraryImages.h"
+
+#include "DNA_material_types.h"
+#include "DNA_image_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "ExportSettings.h"
+#include "collada_utils.h"
+
+class ImagesExporter : COLLADASW::LibraryImages {
+ public:
+ ImagesExporter(COLLADASW::StreamWriter *sw,
+ BCExportSettings &export_settings,
+ KeyImageMap &key_image_map);
+ void exportImages(Scene *sce);
+
+ private:
+ BCExportSettings &export_settings;
+ KeyImageMap &key_image_map;
+ void export_UV_Image(Image *image, bool use_texture_copies);
+};
+
+#endif
diff --git a/source/blender/io/collada/ImportSettings.cpp b/source/blender/io/collada/ImportSettings.cpp
new file mode 100644
index 00000000000..049ee1d0975
--- /dev/null
+++ b/source/blender/io/collada/ImportSettings.cpp
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include "ImportSettings.h"
diff --git a/source/blender/io/collada/ImportSettings.h b/source/blender/io/collada/ImportSettings.h
new file mode 100644
index 00000000000..608d8bff882
--- /dev/null
+++ b/source/blender/io/collada/ImportSettings.h
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __IMPORTSETTINGS_H__
+#define __IMPORTSETTINGS_H__
+
+typedef struct ImportSettings {
+ bool import_units;
+ bool find_chains;
+ bool auto_connect;
+ bool fix_orientation;
+ int min_chain_length;
+ char *filepath;
+ bool keep_bind_info;
+} ImportSettings;
+
+#endif
diff --git a/source/blender/io/collada/InstanceWriter.cpp b/source/blender/io/collada/InstanceWriter.cpp
new file mode 100644
index 00000000000..c9390d23fe7
--- /dev/null
+++ b/source/blender/io/collada/InstanceWriter.cpp
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <string>
+#include <sstream>
+
+#include "COLLADASWInstanceMaterial.h"
+
+extern "C" {
+#include "BKE_customdata.h"
+#include "BKE_material.h"
+#include "DNA_mesh_types.h"
+}
+
+#include "InstanceWriter.h"
+#include "collada_internal.h"
+#include "collada_utils.h"
+
+void InstanceWriter::add_material_bindings(COLLADASW::BindMaterial &bind_material,
+ Object *ob,
+ bool active_uv_only)
+{
+ for (int a = 0; a < ob->totcol; a++) {
+ Material *ma = BKE_object_material_get(ob, a + 1);
+
+ COLLADASW::InstanceMaterialList &iml = bind_material.getInstanceMaterialList();
+
+ if (ma) {
+ std::string matid(get_material_id(ma));
+ matid = translate_id(matid);
+ std::ostringstream ostr;
+ ostr << matid;
+ COLLADASW::InstanceMaterial im(ostr.str(),
+ COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, matid));
+
+ // create <bind_vertex_input> for each uv map
+ Mesh *me = (Mesh *)ob->data;
+
+ int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV);
+
+ int map_index = 0;
+ int active_uv_index = CustomData_get_active_layer_index(&me->ldata, CD_MLOOPUV);
+ for (int b = 0; b < num_layers; b++) {
+ if (!active_uv_only || b == active_uv_index) {
+ char *name = bc_CustomData_get_layer_name(&me->ldata, CD_MLOOPUV, b);
+ im.push_back(COLLADASW::BindVertexInput(name, "TEXCOORD", map_index++));
+ }
+ }
+
+ iml.push_back(im);
+ }
+ }
+}
diff --git a/source/blender/io/collada/InstanceWriter.h b/source/blender/io/collada/InstanceWriter.h
new file mode 100644
index 00000000000..cfec1cf7006
--- /dev/null
+++ b/source/blender/io/collada/InstanceWriter.h
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __INSTANCEWRITER_H__
+#define __INSTANCEWRITER_H__
+
+#include "COLLADASWBindMaterial.h"
+
+#include "DNA_object_types.h"
+
+class InstanceWriter {
+ protected:
+ void add_material_bindings(COLLADASW::BindMaterial &bind_material,
+ Object *ob,
+ bool active_uv_only);
+};
+
+#endif
diff --git a/source/blender/io/collada/LightExporter.cpp b/source/blender/io/collada/LightExporter.cpp
new file mode 100644
index 00000000000..463981ceefa
--- /dev/null
+++ b/source/blender/io/collada/LightExporter.cpp
@@ -0,0 +1,156 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <string>
+
+#include "COLLADASWColor.h"
+#include "COLLADASWLight.h"
+
+#include "BLI_math.h"
+
+#include "LightExporter.h"
+#include "collada_internal.h"
+
+template<class Functor>
+void forEachLightObjectInExportSet(Scene *sce, Functor &f, LinkNode *export_set)
+{
+ LinkNode *node;
+ for (node = export_set; node; node = node->next) {
+ Object *ob = (Object *)node->link;
+
+ if (ob->type == OB_LAMP && ob->data) {
+ f(ob);
+ }
+ }
+}
+
+LightsExporter::LightsExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings)
+ : COLLADASW::LibraryLights(sw), export_settings(export_settings)
+{
+}
+
+void LightsExporter::exportLights(Scene *sce)
+{
+ openLibrary();
+
+ forEachLightObjectInExportSet(sce, *this, this->export_settings.get_export_set());
+
+ closeLibrary();
+}
+
+void LightsExporter::operator()(Object *ob)
+{
+ Light *la = (Light *)ob->data;
+ std::string la_id(get_light_id(ob));
+ std::string la_name(id_name(la));
+ COLLADASW::Color col(la->r * la->energy, la->g * la->energy, la->b * la->energy);
+ float d, constatt, linatt, quadatt;
+
+ d = la->dist;
+
+ constatt = 1.0f;
+
+ if (la->falloff_type == LA_FALLOFF_INVLINEAR) {
+ linatt = 1.0f / d;
+ quadatt = 0.0f;
+ }
+ else {
+ linatt = 0.0f;
+ quadatt = 1.0f / (d * d);
+ }
+
+ // sun
+ if (la->type == LA_SUN) {
+ COLLADASW::DirectionalLight cla(mSW, la_id, la_name);
+ cla.setColor(col, false, "color");
+ cla.setConstantAttenuation(constatt);
+ exportBlenderProfile(cla, la);
+ addLight(cla);
+ }
+
+ // spot
+ else if (la->type == LA_SPOT) {
+ COLLADASW::SpotLight cla(mSW, la_id, la_name);
+ cla.setColor(col, false, "color");
+ cla.setFallOffAngle(RAD2DEGF(la->spotsize), false, "fall_off_angle");
+ cla.setFallOffExponent(la->spotblend, false, "fall_off_exponent");
+ cla.setConstantAttenuation(constatt);
+ cla.setLinearAttenuation(linatt);
+ cla.setQuadraticAttenuation(quadatt);
+ exportBlenderProfile(cla, la);
+ addLight(cla);
+ }
+ // lamp
+ else if (la->type == LA_LOCAL) {
+ COLLADASW::PointLight cla(mSW, la_id, la_name);
+ cla.setColor(col, false, "color");
+ cla.setConstantAttenuation(constatt);
+ cla.setLinearAttenuation(linatt);
+ cla.setQuadraticAttenuation(quadatt);
+ exportBlenderProfile(cla, la);
+ addLight(cla);
+ }
+ // area light is not supported
+ // it will be exported as a local lamp
+ else {
+ COLLADASW::PointLight cla(mSW, la_id, la_name);
+ cla.setColor(col, false, "color");
+ cla.setConstantAttenuation(constatt);
+ cla.setLinearAttenuation(linatt);
+ cla.setQuadraticAttenuation(quadatt);
+ exportBlenderProfile(cla, la);
+ addLight(cla);
+ }
+}
+
+bool LightsExporter::exportBlenderProfile(COLLADASW::Light &cla, Light *la)
+{
+ cla.addExtraTechniqueParameter("blender", "type", la->type);
+ cla.addExtraTechniqueParameter("blender", "flag", la->flag);
+ cla.addExtraTechniqueParameter("blender", "mode", la->mode);
+ cla.addExtraTechniqueParameter("blender", "gamma", la->k, "blender_gamma");
+ cla.addExtraTechniqueParameter("blender", "red", la->r);
+ cla.addExtraTechniqueParameter("blender", "green", la->g);
+ cla.addExtraTechniqueParameter("blender", "blue", la->b);
+ cla.addExtraTechniqueParameter("blender", "shadow_r", la->shdwr, "blender_shadow_r");
+ cla.addExtraTechniqueParameter("blender", "shadow_g", la->shdwg, "blender_shadow_g");
+ cla.addExtraTechniqueParameter("blender", "shadow_b", la->shdwb, "blender_shadow_b");
+ cla.addExtraTechniqueParameter("blender", "energy", la->energy, "blender_energy");
+ cla.addExtraTechniqueParameter("blender", "dist", la->dist, "blender_dist");
+ cla.addExtraTechniqueParameter("blender", "spotsize", RAD2DEGF(la->spotsize));
+ cla.addExtraTechniqueParameter("blender", "spotblend", la->spotblend);
+ cla.addExtraTechniqueParameter("blender", "att1", la->att1);
+ cla.addExtraTechniqueParameter("blender", "att2", la->att2);
+ // \todo figure out how we can have falloff curve supported here
+ cla.addExtraTechniqueParameter("blender", "falloff_type", la->falloff_type);
+ cla.addExtraTechniqueParameter("blender", "clipsta", la->clipsta);
+ cla.addExtraTechniqueParameter("blender", "clipend", la->clipend);
+ cla.addExtraTechniqueParameter("blender", "bias", la->bias);
+ cla.addExtraTechniqueParameter("blender", "soft", la->soft);
+ cla.addExtraTechniqueParameter("blender", "bufsize", la->bufsize);
+ cla.addExtraTechniqueParameter("blender", "samp", la->samp);
+ cla.addExtraTechniqueParameter("blender", "buffers", la->buffers);
+ cla.addExtraTechniqueParameter("blender", "area_shape", la->area_shape);
+ cla.addExtraTechniqueParameter("blender", "area_size", la->area_size);
+ cla.addExtraTechniqueParameter("blender", "area_sizey", la->area_sizey);
+ cla.addExtraTechniqueParameter("blender", "area_sizez", la->area_sizez);
+
+ return true;
+}
diff --git a/source/blender/io/collada/LightExporter.h b/source/blender/io/collada/LightExporter.h
new file mode 100644
index 00000000000..045ccfe1ce8
--- /dev/null
+++ b/source/blender/io/collada/LightExporter.h
@@ -0,0 +1,44 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __LIGHTEXPORTER_H__
+#define __LIGHTEXPORTER_H__
+
+#include "COLLADASWStreamWriter.h"
+#include "COLLADASWLibraryLights.h"
+
+#include "DNA_light_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "ExportSettings.h"
+
+class LightsExporter : COLLADASW::LibraryLights {
+ public:
+ LightsExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings);
+ void exportLights(Scene *sce);
+ void operator()(Object *ob);
+
+ private:
+ bool exportBlenderProfile(COLLADASW::Light &cla, Light *la);
+ BCExportSettings &export_settings;
+};
+
+#endif
diff --git a/source/blender/io/collada/MaterialExporter.cpp b/source/blender/io/collada/MaterialExporter.cpp
new file mode 100644
index 00000000000..488d1833e48
--- /dev/null
+++ b/source/blender/io/collada/MaterialExporter.cpp
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include "MaterialExporter.h"
+#include "COLLADABUUtils.h"
+#include "collada_internal.h"
+
+MaterialsExporter::MaterialsExporter(COLLADASW::StreamWriter *sw,
+ BCExportSettings &export_settings)
+ : COLLADASW::LibraryMaterials(sw), export_settings(export_settings)
+{
+ /* pass */
+}
+
+void MaterialsExporter::exportMaterials(Scene *sce)
+{
+ if (hasMaterials(sce)) {
+ openLibrary();
+
+ MaterialFunctor mf;
+ mf.forEachMaterialInExportSet<MaterialsExporter>(
+ sce, *this, this->export_settings.get_export_set());
+
+ closeLibrary();
+ }
+}
+
+bool MaterialsExporter::hasMaterials(Scene *sce)
+{
+ LinkNode *node;
+ for (node = this->export_settings.get_export_set(); node; node = node->next) {
+ Object *ob = (Object *)node->link;
+ int a;
+ for (a = 0; a < ob->totcol; a++) {
+ Material *ma = BKE_object_material_get(ob, a + 1);
+
+ // no material, but check all of the slots
+ if (!ma) {
+ continue;
+ }
+
+ return true;
+ }
+ }
+ return false;
+}
+
+void MaterialsExporter::operator()(Material *ma, Object *ob)
+{
+ std::string mat_name = encode_xml(id_name(ma));
+ std::string mat_id = get_material_id(ma);
+ std::string eff_id = get_effect_id(ma);
+
+ openMaterial(mat_id, mat_name);
+ addInstanceEffect(COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, eff_id));
+
+ closeMaterial();
+}
diff --git a/source/blender/io/collada/MaterialExporter.h b/source/blender/io/collada/MaterialExporter.h
new file mode 100644
index 00000000000..be0d939b68a
--- /dev/null
+++ b/source/blender/io/collada/MaterialExporter.h
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __MATERIALEXPORTER_H__
+#define __MATERIALEXPORTER_H__
+
+#include <string>
+#include <vector>
+
+#include "COLLADASWLibraryMaterials.h"
+#include "COLLADASWStreamWriter.h"
+
+extern "C" {
+#include "BKE_material.h"
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+}
+
+#include "GeometryExporter.h"
+#include "collada_internal.h"
+#include "ExportSettings.h"
+#include "Materials.h"
+
+class MaterialsExporter : COLLADASW::LibraryMaterials {
+ public:
+ MaterialsExporter(COLLADASW::StreamWriter *sw, BCExportSettings &export_settings);
+ void exportMaterials(Scene *sce);
+ void operator()(Material *ma, Object *ob);
+
+ private:
+ bool hasMaterials(Scene *sce);
+ BCExportSettings &export_settings;
+};
+
+// used in forEachMaterialInScene
+template<class Functor> class ForEachMaterialFunctor {
+ std::vector<std::string>
+ mMat; // contains list of material names, to avoid duplicate calling of f
+ Functor *f;
+
+ public:
+ ForEachMaterialFunctor(Functor *f) : f(f)
+ {
+ }
+
+ void operator()(Object *ob)
+ {
+ int a;
+ for (a = 0; a < ob->totcol; a++) {
+
+ Material *ma = BKE_object_material_get(ob, a + 1);
+
+ if (!ma) {
+ continue;
+ }
+
+ std::string translated_id = translate_id(id_name(ma));
+ if (find(mMat.begin(), mMat.end(), translated_id) == mMat.end()) {
+ (*this->f)(ma, ob);
+
+ mMat.push_back(translated_id);
+ }
+ }
+ }
+};
+
+struct MaterialFunctor {
+ // calls f for each unique material linked to each object in sce
+ // f should have
+ // void operator()(Material *ma)
+ template<class Functor>
+ void forEachMaterialInExportSet(Scene *sce, Functor &f, LinkNode *export_set)
+ {
+ ForEachMaterialFunctor<Functor> matfunc(&f);
+ GeometryFunctor gf;
+ gf.forEachMeshObjectInExportSet<ForEachMaterialFunctor<Functor>>(sce, matfunc, export_set);
+ }
+};
+
+#endif
diff --git a/source/blender/io/collada/Materials.cpp b/source/blender/io/collada/Materials.cpp
new file mode 100644
index 00000000000..06f54884668
--- /dev/null
+++ b/source/blender/io/collada/Materials.cpp
@@ -0,0 +1,396 @@
+/*
+ * 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.
+ */
+
+#include "Materials.h"
+
+MaterialNode::MaterialNode(bContext *C, Material *ma, KeyImageMap &key_image_map)
+ : mContext(C), material(ma), effect(nullptr), key_image_map(&key_image_map)
+{
+ bNodeTree *new_ntree = prepare_material_nodetree();
+ setShaderType();
+ if (new_ntree) {
+ shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, "");
+ output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, "");
+ add_link(shader_node, 0, output_node, 0);
+ }
+}
+
+MaterialNode::MaterialNode(bContext *C,
+ COLLADAFW::EffectCommon *ef,
+ Material *ma,
+ UidImageMap &uid_image_map)
+ : mContext(C), material(ma), effect(ef), uid_image_map(&uid_image_map)
+{
+ prepare_material_nodetree();
+ setShaderType();
+
+ std::map<std::string, bNode *> nmap;
+#if 0
+ nmap["main"] = add_node(C, ntree, SH_NODE_BSDF_PRINCIPLED, -300, 300);
+ nmap["emission"] = add_node(C, ntree, SH_NODE_EMISSION, -300, 500, "emission");
+ nmap["add"] = add_node(C, ntree, SH_NODE_ADD_SHADER, 100, 400);
+ nmap["transparent"] = add_node(C, ntree, SH_NODE_BSDF_TRANSPARENT, 100, 200);
+ nmap["mix"] = add_node(C, ntree, SH_NODE_MIX_SHADER, 400, 300, "transparency");
+ nmap["out"] = add_node(C, ntree, SH_NODE_OUTPUT_MATERIAL, 600, 300);
+ nmap["out"]->flag &= ~NODE_SELECT;
+
+ add_link(ntree, nmap["emission"], 0, nmap["add"], 0);
+ add_link(ntree, nmap["main"], 0, nmap["add"], 1);
+ add_link(ntree, nmap["add"], 0, nmap["mix"], 1);
+ add_link(ntree, nmap["transparent"], 0, nmap["mix"], 2);
+
+ add_link(ntree, nmap["mix"], 0, nmap["out"], 0);
+ // experimental, probably not used.
+ make_group(C, ntree, nmap);
+#else
+ shader_node = add_node(SH_NODE_BSDF_PRINCIPLED, 0, 300, "");
+ output_node = add_node(SH_NODE_OUTPUT_MATERIAL, 300, 300, "");
+ add_link(shader_node, 0, output_node, 0);
+#endif
+}
+
+void MaterialNode::setShaderType()
+{
+#if 0
+ COLLADAFW::EffectCommon::ShaderType shader = ef->getShaderType();
+ // Currently we only support PBR based shaders
+ // TODO: simulate the effects with PBR
+
+ // blinn
+ if (shader == COLLADAFW::EffectCommon::SHADER_BLINN) {
+ ma->spec_shader = MA_SPEC_BLINN;
+ ma->spec = ef->getShininess().getFloatValue();
+ }
+ // phong
+ else if (shader == COLLADAFW::EffectCommon::SHADER_PHONG) {
+ ma->spec_shader = MA_SPEC_PHONG;
+ ma->har = ef->getShininess().getFloatValue();
+ }
+ // lambert
+ else if (shader == COLLADAFW::EffectCommon::SHADER_LAMBERT) {
+ ma->diff_shader = MA_DIFF_LAMBERT;
+ }
+ // default - lambert
+ else {
+ ma->diff_shader = MA_DIFF_LAMBERT;
+ fprintf(stderr, "Current shader type is not supported, default to lambert.\n");
+ }
+#endif
+}
+
+// returns null if material already has a node tree
+bNodeTree *MaterialNode::prepare_material_nodetree()
+{
+ if (material->nodetree) {
+ ntree = material->nodetree;
+ return NULL;
+ }
+
+ material->nodetree = ntreeAddTree(NULL, "Shader Nodetree", "ShaderNodeTree");
+ material->use_nodes = true;
+ ntree = material->nodetree;
+ return ntree;
+}
+
+bNode *MaterialNode::add_node(int node_type, int locx, int locy, std::string label)
+{
+ bNode *node = nodeAddStaticNode(mContext, ntree, node_type);
+ if (node) {
+ if (label.length() > 0) {
+ strcpy(node->label, label.c_str());
+ }
+ node->locx = locx;
+ node->locy = locy;
+ node->flag |= NODE_SELECT;
+ }
+ node_map[label] = node;
+ return node;
+}
+
+void MaterialNode::add_link(bNode *from_node, int from_index, bNode *to_node, int to_index)
+{
+ bNodeSocket *from_socket = (bNodeSocket *)BLI_findlink(&from_node->outputs, from_index);
+ bNodeSocket *to_socket = (bNodeSocket *)BLI_findlink(&to_node->inputs, to_index);
+
+ nodeAddLink(ntree, from_node, from_socket, to_node, to_socket);
+}
+
+void MaterialNode::set_reflectivity(COLLADAFW::FloatOrParam &val)
+{
+ float reflectivity = val.getFloatValue();
+ if (reflectivity >= 0) {
+ bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Metallic");
+ ((bNodeSocketValueFloat *)socket->default_value)->value = reflectivity;
+ material->metallic = reflectivity;
+ }
+}
+
+#if 0
+// needs rework to be done for 2.81
+void MaterialNode::set_shininess(COLLADAFW::FloatOrParam &val)
+{
+ float roughness = val.getFloatValue();
+ if (roughness >= 0) {
+ bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Roughness");
+ ((bNodeSocketValueFloat *)socket->default_value)->value = roughness;
+ }
+}
+#endif
+
+void MaterialNode::set_ior(COLLADAFW::FloatOrParam &val)
+{
+ float ior = val.getFloatValue();
+ if (ior < 0) {
+ fprintf(stderr,
+ "IOR of negative value is not allowed for materials (using Blender default value "
+ "instead)");
+ return;
+ }
+
+ bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "IOR");
+ ((bNodeSocketValueFloat *)socket->default_value)->value = ior;
+}
+
+void MaterialNode::set_alpha(COLLADAFW::EffectCommon::OpaqueMode mode,
+ COLLADAFW::ColorOrTexture &cot,
+ COLLADAFW::FloatOrParam &val)
+{
+ /* Handling the alpha value according to the Collada 1.4 reference guide
+ * see page 7-5 Determining Transparency (Opacity)
+ */
+
+ if (effect == nullptr) {
+ return;
+ }
+
+ if (cot.isColor() || !cot.isValid()) {
+ // transparent_cot is either a color or not defined
+
+ float transparent_alpha;
+ if (cot.isValid()) {
+ COLLADAFW::Color col = cot.getColor();
+ transparent_alpha = col.getAlpha();
+ }
+ else {
+ // no transparent color defined
+ transparent_alpha = 1;
+ }
+
+ float transparency_alpha = val.getFloatValue();
+ if (transparency_alpha < 0) {
+ // transparency is not defined
+ transparency_alpha = 1; // set to opaque
+ }
+
+ float alpha = transparent_alpha * transparency_alpha;
+ if (mode == COLLADAFW::EffectCommon::RGB_ZERO) {
+ alpha = 1 - alpha;
+ }
+
+ bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Alpha");
+ ((bNodeSocketValueFloat *)socket->default_value)->value = alpha;
+ material->a = alpha;
+ }
+ else if (cot.isTexture()) {
+ int locy = -300 * (node_map.size() - 2);
+ add_texture_node(cot, -300, locy, "Alpha");
+ }
+}
+
+void MaterialNode::set_diffuse(COLLADAFW::ColorOrTexture &cot)
+{
+ int locy = -300 * (node_map.size() - 2);
+ if (cot.isColor()) {
+ COLLADAFW::Color col = cot.getColor();
+ bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Base Color");
+ float *fcol = (float *)socket->default_value;
+
+ fcol[0] = material->r = col.getRed();
+ fcol[1] = material->g = col.getGreen();
+ fcol[2] = material->b = col.getBlue();
+ fcol[3] = material->a = col.getAlpha();
+ }
+ else if (cot.isTexture()) {
+ bNode *texture_node = add_texture_node(cot, -300, locy, "Base Color");
+ if (texture_node != NULL) {
+ add_link(texture_node, 0, shader_node, 0);
+ }
+ }
+}
+
+Image *MaterialNode::get_diffuse_image()
+{
+ bNode *shader = ntreeFindType(ntree, SH_NODE_BSDF_PRINCIPLED);
+ if (shader == nullptr) {
+ return nullptr;
+ }
+
+ bNodeSocket *in_socket = nodeFindSocket(shader, SOCK_IN, "Base Color");
+ if (in_socket == nullptr) {
+ return nullptr;
+ }
+
+ bNodeLink *link = in_socket->link;
+ if (link == nullptr) {
+ return nullptr;
+ }
+
+ bNode *texture = link->fromnode;
+ if (texture == nullptr) {
+ return nullptr;
+ }
+
+ if (texture->type != SH_NODE_TEX_IMAGE) {
+ return nullptr;
+ }
+
+ Image *image = (Image *)texture->id;
+ return image;
+}
+
+static bNodeSocket *set_color(bNode *node, COLLADAFW::Color col)
+{
+ bNodeSocket *socket = (bNodeSocket *)BLI_findlink(&node->outputs, 0);
+ float *fcol = (float *)socket->default_value;
+ fcol[0] = col.getRed();
+ fcol[1] = col.getGreen();
+ fcol[2] = col.getBlue();
+
+ return socket;
+}
+
+void MaterialNode::set_ambient(COLLADAFW::ColorOrTexture &cot)
+{
+ int locy = -300 * (node_map.size() - 2);
+ if (cot.isColor()) {
+ COLLADAFW::Color col = cot.getColor();
+ bNode *node = add_node(SH_NODE_RGB, -300, locy, "Ambient");
+ set_color(node, col);
+ // TODO: Connect node
+ }
+ // texture
+ else if (cot.isTexture()) {
+ add_texture_node(cot, -300, locy, "Ambient");
+ // TODO: Connect node
+ }
+}
+
+void MaterialNode::set_reflective(COLLADAFW::ColorOrTexture &cot)
+{
+ int locy = -300 * (node_map.size() - 2);
+ if (cot.isColor()) {
+ COLLADAFW::Color col = cot.getColor();
+ bNode *node = add_node(SH_NODE_RGB, -300, locy, "Reflective");
+ set_color(node, col);
+ // TODO: Connect node
+ }
+ // texture
+ else if (cot.isTexture()) {
+ add_texture_node(cot, -300, locy, "Reflective");
+ // TODO: Connect node
+ }
+}
+
+void MaterialNode::set_emission(COLLADAFW::ColorOrTexture &cot)
+{
+ int locy = -300 * (node_map.size() - 2);
+ if (cot.isColor()) {
+ COLLADAFW::Color col = cot.getColor();
+ bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Emission");
+ float *fcol = (float *)socket->default_value;
+
+ fcol[0] = col.getRed();
+ fcol[1] = col.getGreen();
+ fcol[2] = col.getBlue();
+ fcol[3] = col.getAlpha();
+ }
+ else if (cot.isTexture()) {
+ bNode *texture_node = add_texture_node(cot, -300, locy, "Emission");
+ if (texture_node != NULL) {
+ add_link(texture_node, 0, shader_node, 0);
+ }
+ }
+}
+
+void MaterialNode::set_opacity(COLLADAFW::ColorOrTexture &cot)
+{
+ if (effect == nullptr) {
+ return;
+ }
+
+ int locy = -300 * (node_map.size() - 2);
+ if (cot.isColor()) {
+ COLLADAFW::Color col = effect->getTransparent().getColor();
+ float alpha = effect->getTransparency().getFloatValue();
+
+ if (col.isValid()) {
+ alpha *= col.getAlpha(); // Assuming A_ONE opaque mode
+ }
+
+ bNodeSocket *socket = nodeFindSocket(shader_node, SOCK_IN, "Alpha");
+ ((bNodeSocketValueFloat *)socket->default_value)->value = alpha;
+ }
+ // texture
+ else if (cot.isTexture()) {
+ add_texture_node(cot, -300, locy, "Alpha");
+ // TODO: Connect node
+ }
+}
+
+void MaterialNode::set_specular(COLLADAFW::ColorOrTexture &cot)
+{
+ int locy = -300 * (node_map.size() - 2);
+ if (cot.isColor()) {
+ COLLADAFW::Color col = cot.getColor();
+ bNode *node = add_node(SH_NODE_RGB, -300, locy, "Specular");
+ set_color(node, col);
+ // TODO: Connect node
+ }
+ // texture
+ else if (cot.isTexture()) {
+ add_texture_node(cot, -300, locy, "Specular");
+ // TODO: Connect node
+ }
+}
+
+bNode *MaterialNode::add_texture_node(COLLADAFW::ColorOrTexture &cot,
+ int locx,
+ int locy,
+ std::string label)
+{
+ if (effect == nullptr) {
+ return nullptr;
+ }
+
+ UidImageMap &image_map = *uid_image_map;
+
+ COLLADAFW::Texture ctex = cot.getTexture();
+
+ COLLADAFW::SamplerPointerArray &samp_array = effect->getSamplerPointerArray();
+ COLLADAFW::Sampler *sampler = samp_array[ctex.getSamplerId()];
+
+ const COLLADAFW::UniqueId &ima_uid = sampler->getSourceImage();
+
+ if (image_map.find(ima_uid) == image_map.end()) {
+ fprintf(stderr, "Couldn't find an image by UID.\n");
+ return NULL;
+ }
+
+ Image *ima = image_map[ima_uid];
+ bNode *texture_node = add_node(SH_NODE_TEX_IMAGE, locx, locy, label);
+ texture_node->id = &ima->id;
+ return texture_node;
+}
diff --git a/source/blender/io/collada/Materials.h b/source/blender/io/collada/Materials.h
new file mode 100644
index 00000000000..0a4f2ee61a5
--- /dev/null
+++ b/source/blender/io/collada/Materials.h
@@ -0,0 +1,76 @@
+/*
+ * 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.
+ */
+
+#ifndef __MATERIALS_H__
+#define __MATERIALS_H__
+
+#include <map>
+#include <string>
+
+extern "C" {
+#include "BKE_context.h"
+#include "BKE_node.h"
+#include "BLI_listbase.h"
+#include "DNA_material_types.h"
+#include "DNA_node_types.h"
+}
+
+#include "collada_utils.h"
+#include "COLLADAFWEffectCommon.h"
+
+typedef std::map<std::string, bNode *> NodeMap;
+
+class MaterialNode {
+
+ private:
+ bContext *mContext;
+ Material *material;
+ COLLADAFW::EffectCommon *effect;
+ UidImageMap *uid_image_map = nullptr;
+ KeyImageMap *key_image_map = nullptr;
+
+ NodeMap node_map;
+ bNodeTree *ntree;
+
+ bNode *shader_node;
+ bNode *output_node;
+
+ bNodeTree *prepare_material_nodetree();
+ bNode *add_node(int node_type, int locx, int locy, std::string label);
+ void add_link(bNode *from_node, int from_index, bNode *to_node, int to_index);
+ bNode *add_texture_node(COLLADAFW::ColorOrTexture &cot, int locx, int locy, std::string label);
+ void setShaderType();
+
+ public:
+ MaterialNode(bContext *C, COLLADAFW::EffectCommon *ef, Material *ma, UidImageMap &uid_image_map);
+ MaterialNode(bContext *C, Material *ma, KeyImageMap &key_image_map);
+ Image *get_diffuse_image();
+
+ void set_diffuse(COLLADAFW::ColorOrTexture &cot);
+ void set_specular(COLLADAFW::ColorOrTexture &cot);
+ void set_ambient(COLLADAFW::ColorOrTexture &cot);
+ void set_reflective(COLLADAFW::ColorOrTexture &cot);
+ void set_emission(COLLADAFW::ColorOrTexture &cot);
+ void set_opacity(COLLADAFW::ColorOrTexture &cot);
+ void set_reflectivity(COLLADAFW::FloatOrParam &val);
+ void set_shininess(COLLADAFW::FloatOrParam &val);
+ void set_ior(COLLADAFW::FloatOrParam &val);
+ void set_alpha(COLLADAFW::EffectCommon::OpaqueMode mode,
+ COLLADAFW::ColorOrTexture &cot,
+ COLLADAFW::FloatOrParam &val);
+};
+
+#endif /* __MATERIALS_H__ */
diff --git a/source/blender/io/collada/MeshImporter.cpp b/source/blender/io/collada/MeshImporter.cpp
new file mode 100644
index 00000000000..bc6dd4202b1
--- /dev/null
+++ b/source/blender/io/collada/MeshImporter.cpp
@@ -0,0 +1,1208 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <algorithm>
+#include <iostream>
+
+/* COLLADABU_ASSERT, may be able to remove later */
+#include "COLLADABUPlatform.h"
+
+#include "COLLADAFWMeshPrimitive.h"
+#include "COLLADAFWMeshVertexData.h"
+#include "COLLADAFWPolygons.h"
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "BKE_customdata.h"
+#include "BKE_displist.h"
+#include "BKE_global.h"
+#include "BKE_lib_id.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_string.h"
+#include "BLI_edgehash.h"
+}
+
+#include "ArmatureImporter.h"
+#include "MeshImporter.h"
+#include "collada_utils.h"
+
+// get node name, or fall back to original id if not present (name is optional)
+template<class T> static const std::string bc_get_dae_name(T *node)
+{
+ return node->getName().size() ? node->getName() : node->getOriginalId();
+}
+
+static const char *bc_primTypeToStr(COLLADAFW::MeshPrimitive::PrimitiveType type)
+{
+ switch (type) {
+ case COLLADAFW::MeshPrimitive::LINES:
+ return "LINES";
+ case COLLADAFW::MeshPrimitive::LINE_STRIPS:
+ return "LINESTRIPS";
+ case COLLADAFW::MeshPrimitive::POLYGONS:
+ return "POLYGONS";
+ case COLLADAFW::MeshPrimitive::POLYLIST:
+ return "POLYLIST";
+ case COLLADAFW::MeshPrimitive::TRIANGLES:
+ return "TRIANGLES";
+ case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
+ return "TRIANGLE_FANS";
+ case COLLADAFW::MeshPrimitive::TRIANGLE_STRIPS:
+ return "TRIANGLE_STRIPS";
+ case COLLADAFW::MeshPrimitive::POINTS:
+ return "POINTS";
+ case COLLADAFW::MeshPrimitive::UNDEFINED_PRIMITIVE_TYPE:
+ return "UNDEFINED_PRIMITIVE_TYPE";
+ }
+ return "UNKNOWN";
+}
+
+static const char *bc_geomTypeToStr(COLLADAFW::Geometry::GeometryType type)
+{
+ switch (type) {
+ case COLLADAFW::Geometry::GEO_TYPE_MESH:
+ return "MESH";
+ case COLLADAFW::Geometry::GEO_TYPE_SPLINE:
+ return "SPLINE";
+ case COLLADAFW::Geometry::GEO_TYPE_CONVEX_MESH:
+ return "CONVEX_MESH";
+ case COLLADAFW::Geometry::GEO_TYPE_UNKNOWN:
+ default:
+ return "UNKNOWN";
+ }
+}
+
+UVDataWrapper::UVDataWrapper(COLLADAFW::MeshVertexData &vdata) : mVData(&vdata)
+{
+}
+
+#ifdef COLLADA_DEBUG
+void WVDataWrapper::print()
+{
+ fprintf(stderr, "UVs:\n");
+ switch (mVData->getType()) {
+ case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: {
+ COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues();
+ if (values->getCount()) {
+ for (int i = 0; i < values->getCount(); i += 2) {
+ fprintf(stderr, "%.1f, %.1f\n", (*values)[i], (*values)[i + 1]);
+ }
+ }
+ } break;
+ case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: {
+ COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues();
+ if (values->getCount()) {
+ for (int i = 0; i < values->getCount(); i += 2) {
+ fprintf(stderr, "%.1f, %.1f\n", (float)(*values)[i], (float)(*values)[i + 1]);
+ }
+ }
+ } break;
+ }
+ fprintf(stderr, "\n");
+}
+#endif
+
+void UVDataWrapper::getUV(int uv_index, float *uv)
+{
+ int stride = mVData->getStride(0);
+ if (stride == 0) {
+ stride = 2;
+ }
+
+ switch (mVData->getType()) {
+ case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: {
+ COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues();
+ if (values->empty()) {
+ return;
+ }
+ uv[0] = (*values)[uv_index * stride];
+ uv[1] = (*values)[uv_index * stride + 1];
+
+ } break;
+ case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: {
+ COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues();
+ if (values->empty()) {
+ return;
+ }
+ uv[0] = (float)(*values)[uv_index * stride];
+ uv[1] = (float)(*values)[uv_index * stride + 1];
+
+ } break;
+ case COLLADAFW::MeshVertexData::DATA_TYPE_UNKNOWN:
+ default:
+ fprintf(stderr, "MeshImporter.getUV(): unknown data type\n");
+ }
+}
+
+VCOLDataWrapper::VCOLDataWrapper(COLLADAFW::MeshVertexData &vdata) : mVData(&vdata)
+{
+}
+
+void VCOLDataWrapper::get_vcol(int v_index, MLoopCol *mloopcol)
+{
+ int stride = mVData->getStride(0);
+ if (stride == 0) {
+ stride = 3;
+ }
+
+ switch (mVData->getType()) {
+ case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: {
+ COLLADAFW::ArrayPrimitiveType<float> *values = mVData->getFloatValues();
+ if (values->empty() || values->getCount() <= (v_index * stride + 2)) {
+ return; // xxx need to create an error instead
+ }
+
+ mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]);
+ mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]);
+ mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]);
+ } break;
+
+ case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: {
+ COLLADAFW::ArrayPrimitiveType<double> *values = mVData->getDoubleValues();
+ if (values->empty() || values->getCount() <= (v_index * stride + 2)) {
+ return; // xxx need to create an error instead
+ }
+
+ mloopcol->r = unit_float_to_uchar_clamp((*values)[v_index * stride]);
+ mloopcol->g = unit_float_to_uchar_clamp((*values)[v_index * stride + 1]);
+ mloopcol->b = unit_float_to_uchar_clamp((*values)[v_index * stride + 2]);
+ } break;
+ default:
+ fprintf(stderr, "VCOLDataWrapper.getvcol(): unknown data type\n");
+ }
+}
+
+MeshImporter::MeshImporter(
+ UnitConverter *unitconv, ArmatureImporter *arm, Main *bmain, Scene *sce, ViewLayer *view_layer)
+ : unitconverter(unitconv),
+ m_bmain(bmain),
+ scene(sce),
+ view_layer(view_layer),
+ armature_importer(arm)
+{
+ /* pass */
+}
+
+bool MeshImporter::set_poly_indices(
+ MPoly *mpoly, MLoop *mloop, int loop_index, unsigned int *indices, int loop_count)
+{
+ mpoly->loopstart = loop_index;
+ mpoly->totloop = loop_count;
+ bool broken_loop = false;
+ for (int index = 0; index < loop_count; index++) {
+
+ /* Test if loop defines a hole */
+ if (!broken_loop) {
+ for (int i = 0; i < index; i++) {
+ if (indices[i] == indices[index]) {
+ // duplicate index -> not good
+ broken_loop = true;
+ }
+ }
+ }
+
+ mloop->v = indices[index];
+ mloop++;
+ }
+ return broken_loop;
+}
+
+void MeshImporter::set_vcol(MLoopCol *mlc,
+ VCOLDataWrapper &vob,
+ int loop_index,
+ COLLADAFW::IndexList &index_list,
+ int count)
+{
+ int index;
+ for (index = 0; index < count; index++, mlc++) {
+ int v_index = index_list.getIndex(index + loop_index);
+ vob.get_vcol(v_index, mlc);
+ }
+}
+
+void MeshImporter::set_face_uv(MLoopUV *mloopuv,
+ UVDataWrapper &uvs,
+ int start_index,
+ COLLADAFW::IndexList &index_list,
+ int count)
+{
+ // per face vertex indices, this means for quad we have 4 indices, not 8
+ COLLADAFW::UIntValuesArray &indices = index_list.getIndices();
+
+ for (int index = 0; index < count; index++) {
+ int uv_index = indices[index + start_index];
+ uvs.getUV(uv_index, mloopuv[index].uv);
+ }
+}
+
+#ifdef COLLADA_DEBUG
+void MeshImporter::print_index_list(COLLADAFW::IndexList &index_list)
+{
+ fprintf(stderr, "Index list for \"%s\":\n", index_list.getName().c_str());
+ for (int i = 0; i < index_list.getIndicesCount(); i += 2) {
+ fprintf(stderr, "%u, %u\n", index_list.getIndex(i), index_list.getIndex(i + 1));
+ }
+ fprintf(stderr, "\n");
+}
+#endif
+
+/* checks if mesh has supported primitive types: lines, polylist, triangles, triangle_fans */
+bool MeshImporter::is_nice_mesh(COLLADAFW::Mesh *mesh)
+{
+ COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives();
+
+ const std::string &name = bc_get_dae_name(mesh);
+
+ for (unsigned int i = 0; i < prim_arr.getCount(); i++) {
+
+ COLLADAFW::MeshPrimitive *mp = prim_arr[i];
+ COLLADAFW::MeshPrimitive::PrimitiveType type = mp->getPrimitiveType();
+
+ const char *type_str = bc_primTypeToStr(type);
+
+ // OpenCollada passes POLYGONS type for <polylist>
+ if (type == COLLADAFW::MeshPrimitive::POLYLIST || type == COLLADAFW::MeshPrimitive::POLYGONS) {
+
+ COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp;
+ COLLADAFW::Polygons::VertexCountArray &vca = mpvc->getGroupedVerticesVertexCountArray();
+
+ int hole_count = 0;
+ int nonface_count = 0;
+
+ for (unsigned int j = 0; j < vca.getCount(); j++) {
+ int count = vca[j];
+ if (abs(count) < 3) {
+ nonface_count++;
+ }
+
+ if (count < 0) {
+ hole_count++;
+ }
+ }
+
+ if (hole_count > 0) {
+ fprintf(stderr,
+ "WARNING: Primitive %s in %s: %d holes not imported (unsupported)\n",
+ type_str,
+ name.c_str(),
+ hole_count);
+ }
+
+ if (nonface_count > 0) {
+ fprintf(stderr,
+ "WARNING: Primitive %s in %s: %d faces with vertex count < 3 (rejected)\n",
+ type_str,
+ name.c_str(),
+ nonface_count);
+ }
+ }
+
+ else if (type == COLLADAFW::MeshPrimitive::LINES) {
+ // TODO: Add Checker for line syntax here
+ }
+
+ else if (type != COLLADAFW::MeshPrimitive::TRIANGLES &&
+ type != COLLADAFW::MeshPrimitive::TRIANGLE_FANS) {
+ fprintf(stderr, "ERROR: Primitive type %s is not supported.\n", type_str);
+ return false;
+ }
+ }
+
+ return true;
+}
+
+void MeshImporter::read_vertices(COLLADAFW::Mesh *mesh, Mesh *me)
+{
+ // vertices
+ COLLADAFW::MeshVertexData &pos = mesh->getPositions();
+ if (pos.empty()) {
+ return;
+ }
+
+ int stride = pos.getStride(0);
+ if (stride == 0) {
+ stride = 3;
+ }
+
+ me->totvert = pos.getFloatValues()->getCount() / stride;
+ me->mvert = (MVert *)CustomData_add_layer(&me->vdata, CD_MVERT, CD_CALLOC, NULL, me->totvert);
+
+ MVert *mvert;
+ int i;
+
+ for (i = 0, mvert = me->mvert; i < me->totvert; i++, mvert++) {
+ get_vector(mvert->co, pos, i, stride);
+ }
+}
+
+// =====================================================================
+// condition 1: The Primitive has normals
+// condition 2: The number of normals equals the number of faces.
+// return true if both conditions apply.
+// return false otherwise.
+// =====================================================================
+bool MeshImporter::primitive_has_useable_normals(COLLADAFW::MeshPrimitive *mp)
+{
+
+ bool has_useable_normals = false;
+
+ int normals_count = mp->getNormalIndices().getCount();
+ if (normals_count > 0) {
+ int index_count = mp->getPositionIndices().getCount();
+ if (index_count == normals_count) {
+ has_useable_normals = true;
+ }
+ else {
+ fprintf(stderr,
+ "Warning: Number of normals %d is different from the number of vertices %d, "
+ "skipping normals\n",
+ normals_count,
+ index_count);
+ }
+ }
+
+ return has_useable_normals;
+}
+
+// =====================================================================
+// Assume that only TRIANGLES, TRIANGLE_FANS, POLYLIST and POLYGONS
+// have faces. (to be verified)
+// =====================================================================
+bool MeshImporter::primitive_has_faces(COLLADAFW::MeshPrimitive *mp)
+{
+
+ bool has_faces = false;
+ int type = mp->getPrimitiveType();
+ switch (type) {
+ case COLLADAFW::MeshPrimitive::TRIANGLES:
+ case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
+ case COLLADAFW::MeshPrimitive::POLYLIST:
+ case COLLADAFW::MeshPrimitive::POLYGONS: {
+ has_faces = true;
+ break;
+ }
+ default: {
+ has_faces = false;
+ break;
+ }
+ }
+ return has_faces;
+}
+
+static std::string extract_vcolname(const COLLADAFW::String &collada_id)
+{
+ std::string colname = collada_id;
+ int spos = colname.find("-mesh-colors-");
+ if (spos != std::string::npos) {
+ colname = colname.substr(spos + 13);
+ }
+ return colname;
+}
+
+// =================================================================
+// Return the number of faces by summing up
+// the facecounts of the parts.
+// hint: This is done because mesh->getFacesCount() does
+// count loose edges as extra faces, which is not what we want here.
+// =================================================================
+void MeshImporter::allocate_poly_data(COLLADAFW::Mesh *collada_mesh, Mesh *me)
+{
+ COLLADAFW::MeshPrimitiveArray &prim_arr = collada_mesh->getMeshPrimitives();
+ int total_poly_count = 0;
+ int total_loop_count = 0;
+
+ // collect edge_count and face_count from all parts
+ for (int i = 0; i < prim_arr.getCount(); i++) {
+ COLLADAFW::MeshPrimitive *mp = prim_arr[i];
+ int type = mp->getPrimitiveType();
+ switch (type) {
+ case COLLADAFW::MeshPrimitive::TRIANGLES:
+ case COLLADAFW::MeshPrimitive::TRIANGLE_FANS:
+ case COLLADAFW::MeshPrimitive::POLYLIST:
+ case COLLADAFW::MeshPrimitive::POLYGONS: {
+ COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp;
+ size_t prim_poly_count = mpvc->getFaceCount();
+
+ size_t prim_loop_count = 0;
+ for (int index = 0; index < prim_poly_count; index++) {
+ int vcount = get_vertex_count(mpvc, index);
+ if (vcount > 0) {
+ prim_loop_count += vcount;
+ total_poly_count++;
+ }
+ else {
+ // TODO: this is a hole and not another polygon!
+ }
+ }
+
+ total_loop_count += prim_loop_count;
+
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ // Add the data containers
+ if (total_poly_count > 0) {
+ me->totpoly = total_poly_count;
+ me->totloop = total_loop_count;
+ me->mpoly = (MPoly *)CustomData_add_layer(&me->pdata, CD_MPOLY, CD_CALLOC, NULL, me->totpoly);
+ me->mloop = (MLoop *)CustomData_add_layer(&me->ldata, CD_MLOOP, CD_CALLOC, NULL, me->totloop);
+
+ unsigned int totuvset = collada_mesh->getUVCoords().getInputInfosArray().getCount();
+ for (int i = 0; i < totuvset; i++) {
+ if (collada_mesh->getUVCoords().getLength(i) == 0) {
+ totuvset = 0;
+ break;
+ }
+ }
+
+ if (totuvset > 0) {
+ for (int i = 0; i < totuvset; i++) {
+ COLLADAFW::MeshVertexData::InputInfos *info =
+ collada_mesh->getUVCoords().getInputInfosArray()[i];
+ COLLADAFW::String &uvname = info->mName;
+ // Allocate space for UV_data
+ CustomData_add_layer_named(
+ &me->ldata, CD_MLOOPUV, CD_DEFAULT, NULL, me->totloop, uvname.c_str());
+ }
+ // activate the first uv map
+ me->mloopuv = (MLoopUV *)CustomData_get_layer_n(&me->ldata, CD_MLOOPUV, 0);
+ }
+
+ int totcolset = collada_mesh->getColors().getInputInfosArray().getCount();
+ if (totcolset > 0) {
+ for (int i = 0; i < totcolset; i++) {
+ COLLADAFW::MeshVertexData::InputInfos *info =
+ collada_mesh->getColors().getInputInfosArray()[i];
+ COLLADAFW::String colname = extract_vcolname(info->mName);
+ CustomData_add_layer_named(
+ &me->ldata, CD_MLOOPCOL, CD_DEFAULT, NULL, me->totloop, colname.c_str());
+ }
+ me->mloopcol = (MLoopCol *)CustomData_get_layer_n(&me->ldata, CD_MLOOPCOL, 0);
+ }
+ }
+}
+
+unsigned int MeshImporter::get_vertex_count(COLLADAFW::Polygons *mp, int index)
+{
+ int type = mp->getPrimitiveType();
+ int result;
+ switch (type) {
+ case COLLADAFW::MeshPrimitive::TRIANGLES:
+ case COLLADAFW::MeshPrimitive::TRIANGLE_FANS: {
+ result = 3;
+ break;
+ }
+ case COLLADAFW::MeshPrimitive::POLYLIST:
+ case COLLADAFW::MeshPrimitive::POLYGONS: {
+ result = mp->getGroupedVerticesVertexCountArray()[index];
+ break;
+ }
+ default: {
+ result = -1;
+ break;
+ }
+ }
+ return result;
+}
+
+unsigned int MeshImporter::get_loose_edge_count(COLLADAFW::Mesh *mesh)
+{
+ COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives();
+ int loose_edge_count = 0;
+
+ // collect edge_count and face_count from all parts
+ for (int i = 0; i < prim_arr.getCount(); i++) {
+ COLLADAFW::MeshPrimitive *mp = prim_arr[i];
+ int type = mp->getPrimitiveType();
+ switch (type) {
+ case COLLADAFW::MeshPrimitive::LINES: {
+ size_t prim_totface = mp->getFaceCount();
+ loose_edge_count += prim_totface;
+ break;
+ }
+ default:
+ break;
+ }
+ }
+ return loose_edge_count;
+}
+
+// =================================================================
+// This function is copied from source/blender/editors/mesh/mesh_data.c
+//
+// TODO: (As discussed with sergey-) :
+// Maybe move this function to blenderkernel/intern/mesh.c
+// and add definition to BKE_mesh.c
+// =================================================================
+void MeshImporter::mesh_add_edges(Mesh *mesh, int len)
+{
+ CustomData edata;
+ MEdge *medge;
+ int totedge;
+
+ if (len == 0) {
+ return;
+ }
+
+ totedge = mesh->totedge + len;
+
+ /* update customdata */
+ CustomData_copy(&mesh->edata, &edata, CD_MASK_MESH.emask, CD_DEFAULT, totedge);
+ CustomData_copy_data(&mesh->edata, &edata, 0, 0, mesh->totedge);
+
+ if (!CustomData_has_layer(&edata, CD_MEDGE)) {
+ CustomData_add_layer(&edata, CD_MEDGE, CD_CALLOC, NULL, totedge);
+ }
+
+ CustomData_free(&mesh->edata, mesh->totedge);
+ mesh->edata = edata;
+ BKE_mesh_update_customdata_pointers(mesh, false); /* new edges don't change tessellation */
+
+ /* set default flags */
+ medge = &mesh->medge[mesh->totedge];
+ for (int i = 0; i < len; i++, medge++) {
+ medge->flag = ME_EDGEDRAW | ME_EDGERENDER | SELECT;
+ }
+
+ mesh->totedge = totedge;
+}
+
+// =================================================================
+// Read all loose edges.
+// Important: This function assumes that all edges from existing
+// faces have already been generated and added to me->medge
+// So this function MUST be called after read_faces() (see below)
+// =================================================================
+void MeshImporter::read_lines(COLLADAFW::Mesh *mesh, Mesh *me)
+{
+ unsigned int loose_edge_count = get_loose_edge_count(mesh);
+ if (loose_edge_count > 0) {
+
+ unsigned int face_edge_count = me->totedge;
+ /* unsigned int total_edge_count = loose_edge_count + face_edge_count; */ /* UNUSED */
+
+ mesh_add_edges(me, loose_edge_count);
+ MEdge *med = me->medge + face_edge_count;
+
+ COLLADAFW::MeshPrimitiveArray &prim_arr = mesh->getMeshPrimitives();
+
+ for (int index = 0; index < prim_arr.getCount(); index++) {
+ COLLADAFW::MeshPrimitive *mp = prim_arr[index];
+
+ int type = mp->getPrimitiveType();
+ if (type == COLLADAFW::MeshPrimitive::LINES) {
+ unsigned int edge_count = mp->getFaceCount();
+ unsigned int *indices = mp->getPositionIndices().getData();
+
+ for (int j = 0; j < edge_count; j++, med++) {
+ med->bweight = 0;
+ med->crease = 0;
+ med->flag |= ME_LOOSEEDGE;
+ med->v1 = indices[2 * j];
+ med->v2 = indices[2 * j + 1];
+ }
+ }
+ }
+ }
+}
+
+// =======================================================================
+// Read all faces from TRIANGLES, TRIANGLE_FANS, POLYLIST, POLYGON
+// Important: This function MUST be called before read_lines()
+// Otherwise we will loose all edges from faces (see read_lines() above)
+//
+// TODO: import uv set names
+// ========================================================================
+void MeshImporter::read_polys(COLLADAFW::Mesh *collada_mesh, Mesh *me)
+{
+ unsigned int i;
+
+ allocate_poly_data(collada_mesh, me);
+
+ UVDataWrapper uvs(collada_mesh->getUVCoords());
+ VCOLDataWrapper vcol(collada_mesh->getColors());
+
+ MPoly *mpoly = me->mpoly;
+ MLoop *mloop = me->mloop;
+ int loop_index = 0;
+
+ MaterialIdPrimitiveArrayMap mat_prim_map;
+
+ COLLADAFW::MeshPrimitiveArray &prim_arr = collada_mesh->getMeshPrimitives();
+ COLLADAFW::MeshVertexData &nor = collada_mesh->getNormals();
+
+ for (i = 0; i < prim_arr.getCount(); i++) {
+
+ COLLADAFW::MeshPrimitive *mp = prim_arr[i];
+
+ // faces
+ size_t prim_totpoly = mp->getFaceCount();
+ unsigned int *position_indices = mp->getPositionIndices().getData();
+ unsigned int *normal_indices = mp->getNormalIndices().getData();
+
+ bool mp_has_normals = primitive_has_useable_normals(mp);
+ bool mp_has_faces = primitive_has_faces(mp);
+
+ int collada_meshtype = mp->getPrimitiveType();
+
+ // since we cannot set mpoly->mat_nr here, we store a portion of me->mpoly in Primitive
+ Primitive prim = {mpoly, 0};
+
+ // If MeshPrimitive is TRIANGLE_FANS we split it into triangles
+ // The first trifan vertex will be the first vertex in every triangle
+ // XXX The proper function of TRIANGLE_FANS is not tested!!!
+ // XXX In particular the handling of the normal_indices looks very wrong to me
+ if (collada_meshtype == COLLADAFW::MeshPrimitive::TRIANGLE_FANS) {
+ unsigned int grouped_vertex_count = mp->getGroupedVertexElementsCount();
+ for (unsigned int group_index = 0; group_index < grouped_vertex_count; group_index++) {
+ unsigned int first_vertex = position_indices[0]; // Store first trifan vertex
+ unsigned int first_normal = normal_indices[0]; // Store first trifan vertex normal
+ unsigned int vertex_count = mp->getGroupedVerticesVertexCount(group_index);
+
+ for (unsigned int vertex_index = 0; vertex_index < vertex_count - 2; vertex_index++) {
+ // For each triangle store indices of its 3 vertices
+ unsigned int triangle_vertex_indices[3] = {
+ first_vertex, position_indices[1], position_indices[2]};
+ set_poly_indices(mpoly, mloop, loop_index, triangle_vertex_indices, 3);
+
+ if (mp_has_normals) { // vertex normals, same implementation as for the triangles
+ // the same for vertces normals
+ unsigned int vertex_normal_indices[3] = {
+ first_normal, normal_indices[1], normal_indices[2]};
+ if (!is_flat_face(vertex_normal_indices, nor, 3)) {
+ mpoly->flag |= ME_SMOOTH;
+ }
+ normal_indices++;
+ }
+
+ mpoly++;
+ mloop += 3;
+ loop_index += 3;
+ prim.totpoly++;
+ }
+
+ // Moving cursor to the next triangle fan.
+ if (mp_has_normals) {
+ normal_indices += 2;
+ }
+
+ position_indices += 2;
+ }
+ }
+
+ if (collada_meshtype == COLLADAFW::MeshPrimitive::POLYLIST ||
+ collada_meshtype == COLLADAFW::MeshPrimitive::POLYGONS ||
+ collada_meshtype == COLLADAFW::MeshPrimitive::TRIANGLES) {
+ COLLADAFW::Polygons *mpvc = (COLLADAFW::Polygons *)mp;
+ unsigned int start_index = 0;
+
+ COLLADAFW::IndexListArray &index_list_array_uvcoord = mp->getUVCoordIndicesArray();
+ COLLADAFW::IndexListArray &index_list_array_vcolor = mp->getColorIndicesArray();
+
+ int invalid_loop_holes = 0;
+ for (unsigned int j = 0; j < prim_totpoly; j++) {
+
+ // Vertices in polygon:
+ int vcount = get_vertex_count(mpvc, j);
+ if (vcount < 0) {
+ continue; // TODO: add support for holes
+ }
+
+ bool broken_loop = set_poly_indices(mpoly, mloop, loop_index, position_indices, vcount);
+ if (broken_loop) {
+ invalid_loop_holes += 1;
+ }
+
+ for (unsigned int uvset_index = 0; uvset_index < index_list_array_uvcoord.getCount();
+ uvset_index++) {
+ // get mtface by face index and uv set index
+ COLLADAFW::IndexList &index_list = *index_list_array_uvcoord[uvset_index];
+ MLoopUV *mloopuv = (MLoopUV *)CustomData_get_layer_named(
+ &me->ldata, CD_MLOOPUV, index_list.getName().c_str());
+ if (mloopuv == NULL) {
+ fprintf(stderr,
+ "Collada import: Mesh [%s] : Unknown reference to TEXCOORD [#%s].\n",
+ me->id.name,
+ index_list.getName().c_str());
+ }
+ else {
+ set_face_uv(mloopuv + loop_index,
+ uvs,
+ start_index,
+ *index_list_array_uvcoord[uvset_index],
+ vcount);
+ }
+ }
+
+ if (mp_has_normals) {
+ if (!is_flat_face(normal_indices, nor, vcount)) {
+ mpoly->flag |= ME_SMOOTH;
+ }
+ }
+
+ if (mp->hasColorIndices()) {
+ int vcolor_count = index_list_array_vcolor.getCount();
+
+ for (unsigned int vcolor_index = 0; vcolor_index < vcolor_count; vcolor_index++) {
+
+ COLLADAFW::IndexList &color_index_list = *mp->getColorIndices(vcolor_index);
+ COLLADAFW::String colname = extract_vcolname(color_index_list.getName());
+ MLoopCol *mloopcol = (MLoopCol *)CustomData_get_layer_named(
+ &me->ldata, CD_MLOOPCOL, colname.c_str());
+ if (mloopcol == NULL) {
+ fprintf(stderr,
+ "Collada import: Mesh [%s] : Unknown reference to VCOLOR [#%s].\n",
+ me->id.name,
+ color_index_list.getName().c_str());
+ }
+ else {
+ set_vcol(mloopcol + loop_index, vcol, start_index, color_index_list, vcount);
+ }
+ }
+ }
+
+ mpoly++;
+ mloop += vcount;
+ loop_index += vcount;
+ start_index += vcount;
+ prim.totpoly++;
+
+ if (mp_has_normals) {
+ normal_indices += vcount;
+ }
+
+ position_indices += vcount;
+ }
+
+ if (invalid_loop_holes > 0) {
+ fprintf(stderr,
+ "Collada import: Mesh [%s] : contains %d unsupported loops (holes).\n",
+ me->id.name,
+ invalid_loop_holes);
+ }
+ }
+
+ else if (collada_meshtype == COLLADAFW::MeshPrimitive::LINES) {
+ continue; // read the lines later after all the rest is done
+ }
+
+ if (mp_has_faces) {
+ mat_prim_map[mp->getMaterialId()].push_back(prim);
+ }
+ }
+
+ geom_uid_mat_mapping_map[collada_mesh->getUniqueId()] = mat_prim_map;
+}
+
+void MeshImporter::get_vector(float v[3], COLLADAFW::MeshVertexData &arr, int i, int stride)
+{
+ i *= stride;
+
+ switch (arr.getType()) {
+ case COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT: {
+ COLLADAFW::ArrayPrimitiveType<float> *values = arr.getFloatValues();
+ if (values->empty()) {
+ return;
+ }
+
+ v[0] = (*values)[i++];
+ v[1] = (*values)[i++];
+ if (stride >= 3) {
+ v[2] = (*values)[i];
+ }
+ else {
+ v[2] = 0.0f;
+ }
+
+ } break;
+ case COLLADAFW::MeshVertexData::DATA_TYPE_DOUBLE: {
+ COLLADAFW::ArrayPrimitiveType<double> *values = arr.getDoubleValues();
+ if (values->empty()) {
+ return;
+ }
+
+ v[0] = (float)(*values)[i++];
+ v[1] = (float)(*values)[i++];
+ if (stride >= 3) {
+ v[2] = (float)(*values)[i];
+ }
+ else {
+ v[2] = 0.0f;
+ }
+ } break;
+ default:
+ break;
+ }
+}
+
+bool MeshImporter::is_flat_face(unsigned int *nind, COLLADAFW::MeshVertexData &nor, int count)
+{
+ float a[3], b[3];
+
+ get_vector(a, nor, *nind, 3);
+ normalize_v3(a);
+
+ nind++;
+
+ for (int i = 1; i < count; i++, nind++) {
+ get_vector(b, nor, *nind, 3);
+ normalize_v3(b);
+
+ float dp = dot_v3v3(a, b);
+
+ if (dp < 0.99999f || dp > 1.00001f) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+Object *MeshImporter::get_object_by_geom_uid(const COLLADAFW::UniqueId &geom_uid)
+{
+ if (uid_object_map.find(geom_uid) != uid_object_map.end()) {
+ return uid_object_map[geom_uid];
+ }
+ return NULL;
+}
+
+Mesh *MeshImporter::get_mesh_by_geom_uid(const COLLADAFW::UniqueId &mesh_uid)
+{
+ if (uid_mesh_map.find(mesh_uid) != uid_mesh_map.end()) {
+ return uid_mesh_map[mesh_uid];
+ }
+ return NULL;
+}
+
+std::string *MeshImporter::get_geometry_name(const std::string &mesh_name)
+{
+ if (this->mesh_geom_map.find(mesh_name) != this->mesh_geom_map.end()) {
+ return &this->mesh_geom_map[mesh_name];
+ }
+ return NULL;
+}
+
+/**
+ * this function checks if both objects have the same
+ * materials assigned to Object (in the same order)
+ * returns true if condition matches, otherwise false;
+ */
+static bool bc_has_same_material_configuration(Object *ob1, Object *ob2)
+{
+ if (ob1->totcol != ob2->totcol) {
+ return false; // not same number of materials
+ }
+ if (ob1->totcol == 0) {
+ return false; // no material at all
+ }
+
+ for (int index = 0; index < ob1->totcol; index++) {
+ if (ob1->matbits[index] != ob2->matbits[index]) {
+ return false; // shouldn't happen
+ }
+ if (ob1->matbits[index] == 0) {
+ return false; // shouldn't happen
+ }
+ if (ob1->mat[index] != ob2->mat[index]) {
+ return false; // different material assignment
+ }
+ }
+ return true;
+}
+
+/**
+ *
+ * Caution here: This code assumes that all materials are assigned to Object
+ * and no material is assigned to Data.
+ * That is true right after the objects have been imported.
+ *
+ */
+static void bc_copy_materials_to_data(Object *ob, Mesh *me)
+{
+ for (int index = 0; index < ob->totcol; index++) {
+ ob->matbits[index] = 0;
+ me->mat[index] = ob->mat[index];
+ }
+}
+
+/**
+ *
+ * Remove all references to materials from the object
+ *
+ */
+static void bc_remove_materials_from_object(Object *ob, Mesh *me)
+{
+ for (int index = 0; index < ob->totcol; index++) {
+ ob->matbits[index] = 0;
+ ob->mat[index] = NULL;
+ }
+}
+
+/**
+ * Returns the list of Users of the given Mesh object.
+ * Note: This function uses the object user flag to control
+ * which objects have already been processed.
+ */
+std::vector<Object *> MeshImporter::get_all_users_of(Mesh *reference_mesh)
+{
+ std::vector<Object *> mesh_users;
+ for (std::vector<Object *>::iterator it = imported_objects.begin(); it != imported_objects.end();
+ ++it) {
+ Object *ob = (*it);
+ if (bc_is_marked(ob)) {
+ bc_remove_mark(ob);
+ Mesh *me = (Mesh *)ob->data;
+ if (me == reference_mesh) {
+ mesh_users.push_back(ob);
+ }
+ }
+ }
+ return mesh_users;
+}
+
+/**
+ *
+ * During import all materials have been assigned to Object.
+ * Now we iterate over the imported objects and optimize
+ * the assignments as follows:
+ *
+ * for each imported geometry:
+ * if number of users is 1:
+ * get the user (object)
+ * move the materials from Object to Data
+ * else:
+ * determine which materials are assigned to the first user
+ * check if all other users have the same materials in the same order
+ * if the check is positive:
+ * Add the materials of the first user to the geometry
+ * adjust all other users accordingly.
+ *
+ */
+void MeshImporter::optimize_material_assignements()
+{
+ for (std::vector<Object *>::iterator it = imported_objects.begin(); it != imported_objects.end();
+ ++it) {
+ Object *ob = (*it);
+ Mesh *me = (Mesh *)ob->data;
+ if (ID_REAL_USERS(&me->id) == 1) {
+ bc_copy_materials_to_data(ob, me);
+ bc_remove_materials_from_object(ob, me);
+ bc_remove_mark(ob);
+ }
+ else if (ID_REAL_USERS(&me->id) > 1) {
+ bool can_move = true;
+ std::vector<Object *> mesh_users = get_all_users_of(me);
+ if (mesh_users.size() > 1) {
+ Object *ref_ob = mesh_users[0];
+ for (int index = 1; index < mesh_users.size(); index++) {
+ if (!bc_has_same_material_configuration(ref_ob, mesh_users[index])) {
+ can_move = false;
+ break;
+ }
+ }
+ if (can_move) {
+ bc_copy_materials_to_data(ref_ob, me);
+ for (int index = 0; index < mesh_users.size(); index++) {
+ Object *object = mesh_users[index];
+ bc_remove_materials_from_object(object, me);
+ bc_remove_mark(object);
+ }
+ }
+ }
+ }
+ }
+}
+
+/**
+ * We do not know in advance which objects will share geometries.
+ * And we do not know either if the objects which share geometries
+ * come along with different materials. So we first create the objects
+ * and assign the materials to Object, then in a later cleanup we decide
+ * which materials shall be moved to the created geometries. Also see
+ * optimize_material_assignements() above.
+ */
+void MeshImporter::assign_material_to_geom(
+ COLLADAFW::MaterialBinding cmaterial,
+ std::map<COLLADAFW::UniqueId, Material *> &uid_material_map,
+ Object *ob,
+ const COLLADAFW::UniqueId *geom_uid,
+ short mat_index)
+{
+ const COLLADAFW::UniqueId &ma_uid = cmaterial.getReferencedMaterial();
+
+ // do we know this material?
+ if (uid_material_map.find(ma_uid) == uid_material_map.end()) {
+
+ fprintf(stderr, "Cannot find material by UID.\n");
+ return;
+ }
+
+ // first time we get geom_uid, ma_uid pair. Save for later check.
+ materials_mapped_to_geom.insert(
+ std::pair<COLLADAFW::UniqueId, COLLADAFW::UniqueId>(*geom_uid, ma_uid));
+
+ Material *ma = uid_material_map[ma_uid];
+
+ // Attention! This temporarily assigns material to object on purpose!
+ // See note above.
+ ob->actcol = 0;
+ BKE_object_material_assign(m_bmain, ob, ma, mat_index + 1, BKE_MAT_ASSIGN_OBJECT);
+
+ MaterialIdPrimitiveArrayMap &mat_prim_map = geom_uid_mat_mapping_map[*geom_uid];
+ COLLADAFW::MaterialId mat_id = cmaterial.getMaterialId();
+
+ // assign material indices to mesh faces
+ if (mat_prim_map.find(mat_id) != mat_prim_map.end()) {
+
+ std::vector<Primitive> &prims = mat_prim_map[mat_id];
+
+ std::vector<Primitive>::iterator it;
+
+ for (it = prims.begin(); it != prims.end(); it++) {
+ Primitive &prim = *it;
+ MPoly *mpoly = prim.mpoly;
+
+ for (int i = 0; i < prim.totpoly; i++, mpoly++) {
+ mpoly->mat_nr = mat_index;
+ }
+ }
+ }
+}
+
+Object *MeshImporter::create_mesh_object(
+ COLLADAFW::Node *node,
+ COLLADAFW::InstanceGeometry *geom,
+ bool isController,
+ std::map<COLLADAFW::UniqueId, Material *> &uid_material_map)
+{
+ const COLLADAFW::UniqueId *geom_uid = &geom->getInstanciatedObjectId();
+
+ // check if node instantiates controller or geometry
+ if (isController) {
+
+ geom_uid = armature_importer->get_geometry_uid(*geom_uid);
+
+ if (!geom_uid) {
+ fprintf(stderr, "Couldn't find a mesh UID by controller's UID.\n");
+ return NULL;
+ }
+ }
+ else {
+
+ if (uid_mesh_map.find(*geom_uid) == uid_mesh_map.end()) {
+ // this could happen if a mesh was not created
+ // (e.g. if it contains unsupported geometry)
+ fprintf(stderr, "Couldn't find a mesh by UID.\n");
+ return NULL;
+ }
+ }
+ if (!uid_mesh_map[*geom_uid]) {
+ return NULL;
+ }
+
+ // name Object
+ const std::string &id = node->getName().size() ? node->getName() : node->getOriginalId();
+ const char *name = (id.length()) ? id.c_str() : NULL;
+
+ // add object
+ Object *ob = bc_add_object(m_bmain, scene, view_layer, OB_MESH, name);
+ bc_set_mark(ob); // used later for material assignment optimization
+
+ // store object pointer for ArmatureImporter
+ uid_object_map[*geom_uid] = ob;
+ imported_objects.push_back(ob);
+
+ // replace ob->data freeing the old one
+ Mesh *old_mesh = (Mesh *)ob->data;
+ Mesh *new_mesh = uid_mesh_map[*geom_uid];
+
+ BKE_mesh_assign_object(m_bmain, ob, new_mesh);
+ BKE_mesh_calc_normals(new_mesh);
+
+ /* Because BKE_mesh_assign_object would have already decreased it... */
+ id_us_plus(&old_mesh->id);
+
+ BKE_id_free_us(m_bmain, old_mesh);
+
+ COLLADAFW::MaterialBindingArray &mat_array = geom->getMaterialBindings();
+
+ // loop through geom's materials
+ for (unsigned int i = 0; i < mat_array.getCount(); i++) {
+
+ if (mat_array[i].getReferencedMaterial().isValid()) {
+ assign_material_to_geom(mat_array[i], uid_material_map, ob, geom_uid, i);
+ }
+ else {
+ fprintf(stderr, "invalid referenced material for %s\n", mat_array[i].getName().c_str());
+ }
+ }
+
+ // clean up the mesh
+ BKE_mesh_validate((Mesh *)ob->data, false, false);
+
+ return ob;
+}
+
+// create a mesh storing a pointer in a map so it can be retrieved later by geometry UID
+bool MeshImporter::write_geometry(const COLLADAFW::Geometry *geom)
+{
+
+ if (geom->getType() != COLLADAFW::Geometry::GEO_TYPE_MESH) {
+ // TODO: report warning
+ fprintf(stderr, "Mesh type %s is not supported\n", bc_geomTypeToStr(geom->getType()));
+ return true;
+ }
+
+ COLLADAFW::Mesh *mesh = (COLLADAFW::Mesh *)geom;
+
+ if (!is_nice_mesh(mesh)) {
+ fprintf(stderr, "Ignoring mesh %s\n", bc_get_dae_name(mesh).c_str());
+ return true;
+ }
+
+ const std::string &str_geom_id = mesh->getName().size() ? mesh->getName() :
+ mesh->getOriginalId();
+ Mesh *me = BKE_mesh_add(m_bmain, (char *)str_geom_id.c_str());
+ id_us_min(&me->id); // is already 1 here, but will be set later in BKE_mesh_assign_object
+
+ // store the Mesh pointer to link it later with an Object
+ // mesh_geom_map needed to map mesh to its geometry name (for shape key naming)
+ this->uid_mesh_map[mesh->getUniqueId()] = me;
+ this->mesh_geom_map[std::string(me->id.name)] = str_geom_id;
+
+ read_vertices(mesh, me);
+ read_polys(mesh, me);
+ BKE_mesh_calc_edges(me, false, false);
+ // read_lines() must be called after the face edges have been generated.
+ // Otherwise the loose edges will be silently deleted again.
+ read_lines(mesh, me);
+
+ return true;
+}
diff --git a/source/blender/io/collada/MeshImporter.h b/source/blender/io/collada/MeshImporter.h
new file mode 100644
index 00000000000..9517587013d
--- /dev/null
+++ b/source/blender/io/collada/MeshImporter.h
@@ -0,0 +1,182 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __MESHIMPORTER_H__
+#define __MESHIMPORTER_H__
+
+#include <map>
+#include <vector>
+
+#include "COLLADAFWIndexList.h"
+#include "COLLADAFWPolygons.h"
+#include "COLLADAFWInstanceGeometry.h"
+#include "COLLADAFWMaterialBinding.h"
+#include "COLLADAFWMesh.h"
+#include "COLLADAFWMeshVertexData.h"
+#include "COLLADAFWNode.h"
+#include "COLLADAFWTextureCoordinateBinding.h"
+#include "COLLADAFWTypes.h"
+#include "COLLADAFWUniqueId.h"
+
+#include "ArmatureImporter.h"
+#include "collada_utils.h"
+
+extern "C" {
+#include "BLI_edgehash.h"
+#include "DNA_material_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+}
+
+/* only for ArmatureImporter to "see" MeshImporter::get_object_by_geom_uid */
+class MeshImporterBase {
+ public:
+ virtual Object *get_object_by_geom_uid(const COLLADAFW::UniqueId &geom_uid) = 0;
+ virtual Mesh *get_mesh_by_geom_uid(const COLLADAFW::UniqueId &mesh_uid) = 0;
+ virtual std::string *get_geometry_name(const std::string &mesh_name) = 0;
+};
+
+class UVDataWrapper {
+ COLLADAFW::MeshVertexData *mVData;
+
+ public:
+ UVDataWrapper(COLLADAFW::MeshVertexData &vdata);
+
+#ifdef COLLADA_DEBUG
+ void print();
+#endif
+
+ void getUV(int uv_index, float *uv);
+};
+
+class VCOLDataWrapper {
+ COLLADAFW::MeshVertexData *mVData;
+
+ public:
+ VCOLDataWrapper(COLLADAFW::MeshVertexData &vdata);
+ void get_vcol(int v_index, MLoopCol *mloopcol);
+};
+
+class MeshImporter : public MeshImporterBase {
+ private:
+ UnitConverter *unitconverter;
+
+ Main *m_bmain;
+ Scene *scene;
+ ViewLayer *view_layer;
+
+ ArmatureImporter *armature_importer;
+
+ std::map<std::string, std::string> mesh_geom_map; /* needed for correct shape key naming */
+ std::map<COLLADAFW::UniqueId, Mesh *> uid_mesh_map; /* geometry unique id-to-mesh map */
+ std::map<COLLADAFW::UniqueId, Object *> uid_object_map; /* geom uid-to-object */
+ std::vector<Object *> imported_objects; /* list of imported objects */
+
+ /* this structure is used to assign material indices to polygons
+ * it holds a portion of Mesh faces and corresponds to a DAE primitive list
+ * (<triangles>, <polylist>, etc.) */
+ struct Primitive {
+ MPoly *mpoly;
+ unsigned int totpoly;
+ };
+ typedef std::map<COLLADAFW::MaterialId, std::vector<Primitive>> MaterialIdPrimitiveArrayMap;
+ /* crazy name! */
+ std::map<COLLADAFW::UniqueId, MaterialIdPrimitiveArrayMap> geom_uid_mat_mapping_map;
+ /* < materials that have already been mapped to a geometry.
+ * A pair/of geom uid and mat uid, one geometry can have several materials */
+ std::multimap<COLLADAFW::UniqueId, COLLADAFW::UniqueId> materials_mapped_to_geom;
+
+ bool set_poly_indices(
+ MPoly *mpoly, MLoop *mloop, int loop_index, unsigned int *indices, int loop_count);
+
+ void set_face_uv(MLoopUV *mloopuv,
+ UVDataWrapper &uvs,
+ int loop_index,
+ COLLADAFW::IndexList &index_list,
+ int count);
+
+ void set_vcol(MLoopCol *mloopcol,
+ VCOLDataWrapper &vob,
+ int loop_index,
+ COLLADAFW::IndexList &index_list,
+ int count);
+
+#ifdef COLLADA_DEBUG
+ void print_index_list(COLLADAFW::IndexList &index_list);
+#endif
+
+ bool is_nice_mesh(COLLADAFW::Mesh *mesh);
+
+ void read_vertices(COLLADAFW::Mesh *mesh, Mesh *me);
+
+ bool primitive_has_useable_normals(COLLADAFW::MeshPrimitive *mp);
+ bool primitive_has_faces(COLLADAFW::MeshPrimitive *mp);
+
+ static void mesh_add_edges(Mesh *mesh, int len);
+
+ unsigned int get_loose_edge_count(COLLADAFW::Mesh *mesh);
+
+ CustomData create_edge_custom_data(EdgeHash *eh);
+
+ void allocate_poly_data(COLLADAFW::Mesh *collada_mesh, Mesh *me);
+
+ /* TODO: import uv set names */
+ void read_polys(COLLADAFW::Mesh *mesh, Mesh *me);
+ void read_lines(COLLADAFW::Mesh *mesh, Mesh *me);
+ unsigned int get_vertex_count(COLLADAFW::Polygons *mp, int index);
+
+ void get_vector(float v[3], COLLADAFW::MeshVertexData &arr, int i, int stride);
+
+ bool is_flat_face(unsigned int *nind, COLLADAFW::MeshVertexData &nor, int count);
+
+ std::vector<Object *> get_all_users_of(Mesh *reference_mesh);
+
+ public:
+ MeshImporter(UnitConverter *unitconv,
+ ArmatureImporter *arm,
+ Main *bmain,
+ Scene *sce,
+ ViewLayer *view_layer);
+
+ virtual Object *get_object_by_geom_uid(const COLLADAFW::UniqueId &geom_uid);
+
+ virtual Mesh *get_mesh_by_geom_uid(const COLLADAFW::UniqueId &geom_uid);
+
+ void optimize_material_assignements();
+
+ void assign_material_to_geom(COLLADAFW::MaterialBinding cmaterial,
+ std::map<COLLADAFW::UniqueId, Material *> &uid_material_map,
+ Object *ob,
+ const COLLADAFW::UniqueId *geom_uid,
+ short mat_index);
+
+ Object *create_mesh_object(COLLADAFW::Node *node,
+ COLLADAFW::InstanceGeometry *geom,
+ bool isController,
+ std::map<COLLADAFW::UniqueId, Material *> &uid_material_map);
+
+ /* create a mesh storing a pointer in a map so it can be retrieved later by geometry UID */
+ bool write_geometry(const COLLADAFW::Geometry *geom);
+ std::string *get_geometry_name(const std::string &mesh_name);
+};
+
+#endif
diff --git a/source/blender/io/collada/SceneExporter.cpp b/source/blender/io/collada/SceneExporter.cpp
new file mode 100644
index 00000000000..42901bd2a4a
--- /dev/null
+++ b/source/blender/io/collada/SceneExporter.cpp
@@ -0,0 +1,242 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+extern "C" {
+#include "BLI_utildefines.h"
+#include "BKE_collection.h"
+#include "BKE_object.h"
+#include "BLI_listbase.h"
+#include "BKE_lib_id.h"
+}
+
+#include "SceneExporter.h"
+#include "collada_utils.h"
+#include "BCSampleData.h"
+
+void SceneExporter::exportScene()
+{
+ Scene *scene = blender_context.get_scene();
+
+ /* <library_visual_scenes> <visual_scene> */
+ std::string name = id_name(scene);
+ openVisualScene(translate_id(name), encode_xml(name));
+ exportHierarchy();
+ closeVisualScene();
+ closeLibrary();
+}
+
+void SceneExporter::exportHierarchy()
+{
+ LinkNode *node;
+ ColladaBaseNodes base_objects;
+
+ /* Ensure all objects in the export_set are marked */
+ for (node = this->export_settings.get_export_set(); node; node = node->next) {
+ Object *ob = (Object *)node->link;
+ ob->id.tag |= LIB_TAG_DOIT;
+ }
+
+ /* Now find all exportable base objects (highest in export hierarchy) */
+ for (node = this->export_settings.get_export_set(); node; node = node->next) {
+ Object *ob = (Object *)node->link;
+ if (this->export_settings.is_export_root(ob)) {
+ switch (ob->type) {
+ case OB_MESH:
+ case OB_CAMERA:
+ case OB_LAMP:
+ case OB_EMPTY:
+ case OB_GPENCIL:
+ case OB_ARMATURE:
+ base_objects.add(ob);
+ break;
+ }
+ }
+ }
+
+ /* And now export the base objects: */
+ for (int index = 0; index < base_objects.size(); index++) {
+ Object *ob = base_objects.get(index);
+ writeNode(ob);
+ if (bc_is_marked(ob)) {
+ bc_remove_mark(ob);
+ }
+ }
+}
+
+void SceneExporter::writeNodeList(std::vector<Object *> &child_objects, Object *parent)
+{
+ /* TODO: Handle the case where a parent is not exported
+ * Actually i am not even sure if this can be done at all
+ * in a good way.
+ * I really prefer to enforce the export of hidden
+ * elements in an object hierarchy. When the children of
+ * the hidden elements are exported as well. */
+ for (int i = 0; i < child_objects.size(); i++) {
+ Object *child = child_objects[i];
+ writeNode(child);
+ if (bc_is_marked(child)) {
+ bc_remove_mark(child);
+ }
+ }
+}
+
+void SceneExporter::writeNode(Object *ob)
+{
+ ViewLayer *view_layer = blender_context.get_view_layer();
+
+ std::vector<Object *> child_objects;
+ bc_get_children(child_objects, ob, view_layer);
+ bool can_export = bc_is_in_Export_set(this->export_settings.get_export_set(), ob, view_layer);
+
+ /* Add associated armature first if available */
+ bool armature_exported = false;
+ Object *ob_arm = bc_get_assigned_armature(ob);
+
+ if (ob_arm != NULL) {
+ armature_exported = bc_is_in_Export_set(
+ this->export_settings.get_export_set(), ob_arm, view_layer);
+ if (armature_exported && bc_is_marked(ob_arm)) {
+ writeNode(ob_arm);
+ bc_remove_mark(ob_arm);
+ armature_exported = true;
+ }
+ }
+
+ if (can_export) {
+ COLLADASW::Node colladaNode(mSW);
+ colladaNode.setNodeId(translate_id(id_name(ob)));
+ colladaNode.setNodeName(encode_xml(id_name(ob)));
+ colladaNode.setType(COLLADASW::Node::NODE);
+
+ colladaNode.start();
+ if (ob->type == OB_MESH && armature_exported) {
+ /* for skinned mesh we write obmat in <bind_shape_matrix> */
+ TransformWriter::add_node_transform_identity(colladaNode, this->export_settings);
+ }
+ else {
+ TransformWriter::add_node_transform_ob(colladaNode, ob, this->export_settings);
+ }
+
+ /* <instance_geometry> */
+ if (ob->type == OB_MESH) {
+ bool instance_controller_created = false;
+ if (armature_exported) {
+ instance_controller_created = arm_exporter->add_instance_controller(ob);
+ }
+ if (!instance_controller_created) {
+ COLLADASW::InstanceGeometry instGeom(mSW);
+ instGeom.setUrl(COLLADASW::URI(
+ COLLADABU::Utils::EMPTY_STRING,
+ get_geometry_id(ob, this->export_settings.get_use_object_instantiation())));
+ instGeom.setName(encode_xml(id_name(ob)));
+ InstanceWriter::add_material_bindings(
+ instGeom.getBindMaterial(), ob, this->export_settings.get_active_uv_only());
+ instGeom.add();
+ }
+ }
+
+ /* <instance_controller> */
+ else if (ob->type == OB_ARMATURE) {
+ arm_exporter->add_armature_bones(ob, view_layer, this, child_objects);
+ }
+
+ /* <instance_camera> */
+ else if (ob->type == OB_CAMERA) {
+ COLLADASW::InstanceCamera instCam(
+ mSW, COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, get_camera_id(ob)));
+ instCam.add();
+ }
+
+ /* <instance_light> */
+ else if (ob->type == OB_LAMP) {
+ COLLADASW::InstanceLight instLa(
+ mSW, COLLADASW::URI(COLLADABU::Utils::EMPTY_STRING, get_light_id(ob)));
+ instLa.add();
+ }
+
+ /* empty object */
+ else if (ob->type == OB_EMPTY) { /* TODO: handle groups (OB_DUPLICOLLECTION */
+ if ((ob->transflag & OB_DUPLICOLLECTION) == OB_DUPLICOLLECTION && ob->instance_collection) {
+ Collection *collection = ob->instance_collection;
+ /* printf("group detected '%s'\n", group->id.name + 2); */
+ FOREACH_COLLECTION_OBJECT_RECURSIVE_BEGIN (collection, object) {
+ printf("\t%s\n", object->id.name);
+ }
+ FOREACH_COLLECTION_OBJECT_RECURSIVE_END;
+ }
+
+ if (BLI_listbase_is_empty(&ob->constraints) == false) {
+ bConstraint *con = (bConstraint *)ob->constraints.first;
+ while (con) {
+ std::string con_name(encode_xml(con->name));
+ std::string con_tag = con_name + "_constraint";
+ printf("%s\n", con_name.c_str());
+ printf("%s\n\n", con_tag.c_str());
+ colladaNode.addExtraTechniqueChildParameter("blender", con_tag, "type", con->type);
+ colladaNode.addExtraTechniqueChildParameter("blender", con_tag, "enforce", con->enforce);
+ colladaNode.addExtraTechniqueChildParameter("blender", con_tag, "flag", con->flag);
+ colladaNode.addExtraTechniqueChildParameter(
+ "blender", con_tag, "headtail", con->headtail);
+ colladaNode.addExtraTechniqueChildParameter(
+ "blender", con_tag, "lin_error", con->lin_error);
+ colladaNode.addExtraTechniqueChildParameter(
+ "blender", con_tag, "own_space", con->ownspace);
+ colladaNode.addExtraTechniqueChildParameter(
+ "blender", con_tag, "rot_error", con->rot_error);
+ colladaNode.addExtraTechniqueChildParameter(
+ "blender", con_tag, "tar_space", con->tarspace);
+ colladaNode.addExtraTechniqueChildParameter(
+ "blender", con_tag, "lin_error", con->lin_error);
+
+ /* not ideal: add the target object name as another parameter.
+ * No real mapping in the .dae
+ * Need support for multiple target objects also. */
+ const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
+ ListBase targets = {NULL, NULL};
+ if (cti && cti->get_constraint_targets) {
+
+ bConstraintTarget *ct;
+ Object *obtar;
+
+ cti->get_constraint_targets(con, &targets);
+
+ for (ct = (bConstraintTarget *)targets.first; ct; ct = ct->next) {
+ obtar = ct->tar;
+ std::string tar_id((obtar) ? id_name(obtar) : "");
+ colladaNode.addExtraTechniqueChildParameter("blender", con_tag, "target_id", tar_id);
+ }
+
+ if (cti->flush_constraint_targets) {
+ cti->flush_constraint_targets(con, &targets, 1);
+ }
+ }
+
+ con = con->next;
+ }
+ }
+ }
+ bc_remove_mark(ob);
+ writeNodeList(child_objects, ob);
+ colladaNode.end();
+ }
+ else {
+ writeNodeList(child_objects, ob);
+ }
+}
diff --git a/source/blender/io/collada/SceneExporter.h b/source/blender/io/collada/SceneExporter.h
new file mode 100644
index 00000000000..a61d045ad5d
--- /dev/null
+++ b/source/blender/io/collada/SceneExporter.h
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __SCENEEXPORTER_H__
+#define __SCENEEXPORTER_H__
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <math.h>
+
+extern "C" {
+#include "DNA_scene_types.h"
+#include "DNA_object_types.h"
+#include "DNA_collection_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_image_types.h"
+#include "DNA_material_types.h"
+#include "DNA_texture_types.h"
+#include "DNA_anim_types.h"
+#include "DNA_action_types.h"
+#include "DNA_curve_types.h"
+#include "DNA_constraint_types.h"
+#include "DNA_armature_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_userdef_types.h"
+
+#include "BKE_fcurve.h"
+#include "BKE_animsys.h"
+#include "BLI_path_util.h"
+#include "BKE_constraint.h"
+#include "BLI_fileops.h"
+#include "ED_keyframing.h"
+}
+
+#include "COLLADASWAsset.h"
+#include "COLLADASWLibraryVisualScenes.h"
+#include "COLLADASWNode.h"
+#include "COLLADASWSource.h"
+#include "COLLADASWInstanceGeometry.h"
+#include "COLLADASWInputList.h"
+#include "COLLADASWPrimitves.h"
+#include "COLLADASWVertices.h"
+#include "COLLADASWLibraryAnimations.h"
+#include "COLLADASWLibraryImages.h"
+#include "COLLADASWLibraryEffects.h"
+#include "COLLADASWImage.h"
+#include "COLLADASWEffectProfile.h"
+#include "COLLADASWColorOrTexture.h"
+#include "COLLADASWParamTemplate.h"
+#include "COLLADASWParamBase.h"
+#include "COLLADASWSurfaceInitOption.h"
+#include "COLLADASWSampler.h"
+#include "COLLADASWScene.h"
+#include "COLLADASWTechnique.h"
+#include "COLLADASWTexture.h"
+#include "COLLADASWLibraryMaterials.h"
+#include "COLLADASWBindMaterial.h"
+#include "COLLADASWInstanceCamera.h"
+#include "COLLADASWInstanceLight.h"
+#include "COLLADASWConstants.h"
+#include "COLLADASWLibraryControllers.h"
+#include "COLLADASWInstanceController.h"
+#include "COLLADASWInstanceNode.h"
+#include "COLLADASWBaseInputElement.h"
+
+#include "ArmatureExporter.h"
+#include "ExportSettings.h"
+
+extern void bc_get_children(std::vector<Object *> &child_set, Object *ob, ViewLayer *view_layer);
+
+class SceneExporter : COLLADASW::LibraryVisualScenes,
+ protected TransformWriter,
+ protected InstanceWriter {
+ public:
+ SceneExporter(BlenderContext &blender_context,
+ COLLADASW::StreamWriter *sw,
+ ArmatureExporter *arm,
+ BCExportSettings &export_settings)
+ : COLLADASW::LibraryVisualScenes(sw),
+ blender_context(blender_context),
+ arm_exporter(arm),
+ export_settings(export_settings)
+ {
+ }
+
+ void exportScene();
+
+ private:
+ BlenderContext &blender_context;
+ friend class ArmatureExporter;
+ ArmatureExporter *arm_exporter;
+ BCExportSettings &export_settings;
+
+ void exportHierarchy();
+ void writeNodeList(std::vector<Object *> &child_objects, Object *parent);
+ void writeNode(Object *ob);
+};
+
+#endif
diff --git a/source/blender/io/collada/SkinInfo.cpp b/source/blender/io/collada/SkinInfo.cpp
new file mode 100644
index 00000000000..d8804a1e831
--- /dev/null
+++ b/source/blender/io/collada/SkinInfo.cpp
@@ -0,0 +1,357 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include <algorithm>
+
+#if !defined(WIN32)
+# include <stdint.h>
+#endif
+
+/* COLLADABU_ASSERT, may be able to remove later */
+#include "COLLADABUPlatform.h"
+
+#include "BLI_listbase.h"
+#include "BLI_math.h"
+#include "BLI_compiler_attrs.h"
+
+#include "DNA_armature_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_action.h"
+#include "BKE_object.h"
+#include "BKE_object_deform.h"
+
+#include "ED_mesh.h"
+#include "ED_object.h"
+
+#include "SkinInfo.h"
+#include "collada_utils.h"
+
+/* use name, or fall back to original id if name not present (name is optional) */
+template<class T> static const char *bc_get_joint_name(T *node)
+{
+ const std::string &id = node->getName();
+ return id.size() ? id.c_str() : node->getOriginalId().c_str();
+}
+
+/* This is used to store data passed in write_controller_data.
+ * Arrays from COLLADAFW::SkinControllerData lose ownership, so do this class members
+ * so that arrays don't get freed until we free them explicitly. */
+SkinInfo::SkinInfo()
+{
+ /* pass */
+}
+
+SkinInfo::SkinInfo(const SkinInfo &skin)
+ : weights(skin.weights),
+ joint_data(skin.joint_data),
+ unit_converter(skin.unit_converter),
+ ob_arm(skin.ob_arm),
+ controller_uid(skin.controller_uid),
+ parent(skin.parent)
+{
+ copy_m4_m4(bind_shape_matrix, (float(*)[4])skin.bind_shape_matrix);
+
+ transfer_uint_array_data_const(skin.joints_per_vertex, joints_per_vertex);
+ transfer_uint_array_data_const(skin.weight_indices, weight_indices);
+ transfer_int_array_data_const(skin.joint_indices, joint_indices);
+}
+
+SkinInfo::SkinInfo(UnitConverter *conv) : unit_converter(conv), ob_arm(NULL), parent(NULL)
+{
+}
+
+/* nobody owns the data after this, so it should be freed manually with releaseMemory */
+template<class T> void SkinInfo::transfer_array_data(T &src, T &dest)
+{
+ dest.setData(src.getData(), src.getCount());
+ src.yieldOwnerShip();
+ dest.yieldOwnerShip();
+}
+
+/* when src is const we cannot src.yieldOwnerShip, this is used by copy constructor */
+void SkinInfo::transfer_int_array_data_const(const COLLADAFW::IntValuesArray &src,
+ COLLADAFW::IntValuesArray &dest)
+{
+ dest.setData((int *)src.getData(), src.getCount());
+ dest.yieldOwnerShip();
+}
+
+void SkinInfo::transfer_uint_array_data_const(const COLLADAFW::UIntValuesArray &src,
+ COLLADAFW::UIntValuesArray &dest)
+{
+ dest.setData((unsigned int *)src.getData(), src.getCount());
+ dest.yieldOwnerShip();
+}
+
+void SkinInfo::borrow_skin_controller_data(const COLLADAFW::SkinControllerData *skin)
+{
+ transfer_array_data((COLLADAFW::UIntValuesArray &)skin->getJointsPerVertex(), joints_per_vertex);
+ transfer_array_data((COLLADAFW::UIntValuesArray &)skin->getWeightIndices(), weight_indices);
+ transfer_array_data((COLLADAFW::IntValuesArray &)skin->getJointIndices(), joint_indices);
+ // transfer_array_data(skin->getWeights(), weights);
+
+ /* cannot transfer data for FloatOrDoubleArray, copy values manually */
+ const COLLADAFW::FloatOrDoubleArray &weight = skin->getWeights();
+ for (unsigned int i = 0; i < weight.getValuesCount(); i++) {
+ weights.push_back(bc_get_float_value(weight, i));
+ }
+
+ unit_converter->dae_matrix_to_mat4_(bind_shape_matrix, skin->getBindShapeMatrix());
+}
+
+void SkinInfo::free()
+{
+ joints_per_vertex.releaseMemory();
+ weight_indices.releaseMemory();
+ joint_indices.releaseMemory();
+ // weights.releaseMemory();
+}
+
+/* using inverse bind matrices to construct armature
+ * it is safe to invert them to get the original matrices
+ * because if they are inverse matrices, they can be inverted */
+void SkinInfo::add_joint(const COLLADABU::Math::Matrix4 &matrix)
+{
+ JointData jd;
+ unit_converter->dae_matrix_to_mat4_(jd.inv_bind_mat, matrix);
+ joint_data.push_back(jd);
+}
+
+void SkinInfo::set_controller(const COLLADAFW::SkinController *co)
+{
+ controller_uid = co->getUniqueId();
+
+ /* fill in joint UIDs */
+ const COLLADAFW::UniqueIdArray &joint_uids = co->getJoints();
+ for (unsigned int i = 0; i < joint_uids.getCount(); i++) {
+ joint_data[i].joint_uid = joint_uids[i];
+
+ /* store armature pointer */
+ // JointData& jd = joint_index_to_joint_info_map[i];
+ // jd.ob_arm = ob_arm;
+
+ /* now we'll be able to get inv bind matrix from joint id */
+ // joint_id_to_joint_index_map[joint_ids[i]] = i;
+ }
+}
+
+/* called from write_controller */
+Object *SkinInfo::create_armature(Main *bmain, Scene *scene, ViewLayer *view_layer)
+{
+ ob_arm = bc_add_object(bmain, scene, view_layer, OB_ARMATURE, NULL);
+ return ob_arm;
+}
+
+Object *SkinInfo::set_armature(Object *ob_arm)
+{
+ if (this->ob_arm) {
+ return this->ob_arm;
+ }
+
+ this->ob_arm = ob_arm;
+ return ob_arm;
+}
+
+bool SkinInfo::get_joint_inv_bind_matrix(float inv_bind_mat[4][4], COLLADAFW::Node *node)
+{
+ const COLLADAFW::UniqueId &uid = node->getUniqueId();
+ std::vector<JointData>::iterator it;
+ for (it = joint_data.begin(); it != joint_data.end(); it++) {
+ if ((*it).joint_uid == uid) {
+ copy_m4_m4(inv_bind_mat, (*it).inv_bind_mat);
+ return true;
+ }
+ }
+
+ return false;
+}
+
+Object *SkinInfo::BKE_armature_from_object()
+{
+ return ob_arm;
+}
+
+const COLLADAFW::UniqueId &SkinInfo::get_controller_uid()
+{
+ return controller_uid;
+}
+
+/* check if this skin controller references a joint or any descendant of it
+ *
+ * some nodes may not be referenced by SkinController,
+ * in this case to determine if the node belongs to this armature,
+ * we need to search down the tree */
+bool SkinInfo::uses_joint_or_descendant(COLLADAFW::Node *node)
+{
+ const COLLADAFW::UniqueId &uid = node->getUniqueId();
+ std::vector<JointData>::iterator it;
+ for (it = joint_data.begin(); it != joint_data.end(); it++) {
+ if ((*it).joint_uid == uid) {
+ return true;
+ }
+ }
+
+ COLLADAFW::NodePointerArray &children = node->getChildNodes();
+ for (unsigned int i = 0; i < children.getCount(); i++) {
+ if (uses_joint_or_descendant(children[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void SkinInfo::link_armature(bContext *C,
+ Object *ob,
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &joint_by_uid,
+ TransformReader *tm)
+{
+ Main *bmain = CTX_data_main(C);
+ Scene *scene = CTX_data_scene(C);
+
+ ModifierData *md = ED_object_modifier_add(NULL, bmain, scene, ob, NULL, eModifierType_Armature);
+ ArmatureModifierData *amd = (ArmatureModifierData *)md;
+ amd->object = ob_arm;
+
+#if 1
+ /* XXX Why do we enforce objects to be children of Armatures if they weren't so before ?*/
+ if (!BKE_object_is_child_recursive(ob_arm, ob)) {
+ bc_set_parent(ob, ob_arm, C);
+ }
+#else
+ Object workob;
+ ob->parent = ob_arm;
+ ob->partype = PAROBJECT;
+
+ BKE_object_workob_calc_parent(scene, ob, &workob);
+ invert_m4_m4(ob->parentinv, workob.obmat);
+
+ DEG_id_tag_update(&obn->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY);
+#endif
+ copy_m4_m4(ob->obmat, bind_shape_matrix);
+ BKE_object_apply_mat4(ob, ob->obmat, 0, 0);
+
+ amd->deformflag = ARM_DEF_VGROUP;
+
+ /* create all vertex groups */
+ std::vector<JointData>::iterator it;
+ int joint_index;
+ for (it = joint_data.begin(), joint_index = 0; it != joint_data.end(); it++, joint_index++) {
+ const char *name = "Group";
+
+ /* skip joints that have invalid UID */
+ if ((*it).joint_uid == COLLADAFW::UniqueId::INVALID) {
+ continue;
+ }
+
+ /* name group by joint node name */
+
+ if (joint_by_uid.find((*it).joint_uid) != joint_by_uid.end()) {
+ name = bc_get_joint_name(joint_by_uid[(*it).joint_uid]);
+ }
+
+ BKE_object_defgroup_add_name(ob, name);
+ }
+
+ /* <vcount> - number of joints per vertex - joints_per_vertex
+ * <v> - [[bone index, weight index] * joints per vertex] * vertices - weight indices
+ * ^ bone index can be -1 meaning weight toward bind shape, how to express this in Blender?
+ *
+ * for each vertex in weight indices
+ * for each bone index in vertex
+ * add vertex to group at group index
+ * treat group index -1 specially
+ *
+ * get def group by index with BLI_findlink */
+
+ for (unsigned int vertex = 0, weight = 0; vertex < joints_per_vertex.getCount(); vertex++) {
+
+ unsigned int limit = weight + joints_per_vertex[vertex];
+ for (; weight < limit; weight++) {
+ int joint = joint_indices[weight], joint_weight = weight_indices[weight];
+
+ /* -1 means "weight towards the bind shape", we just don't assign it to any group */
+ if (joint != -1) {
+ bDeformGroup *def = (bDeformGroup *)BLI_findlink(&ob->defbase, joint);
+
+ ED_vgroup_vert_add(ob, def, vertex, weights[joint_weight], WEIGHT_REPLACE);
+ }
+ }
+ }
+}
+
+bPoseChannel *SkinInfo::get_pose_channel_from_node(COLLADAFW::Node *node)
+{
+ return BKE_pose_channel_find_name(ob_arm->pose, bc_get_joint_name(node));
+}
+
+void SkinInfo::set_parent(Object *_parent)
+{
+ parent = _parent;
+}
+
+Object *SkinInfo::get_parent()
+{
+ return parent;
+}
+
+void SkinInfo::find_root_joints(const std::vector<COLLADAFW::Node *> &root_joints,
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &joint_by_uid,
+ std::vector<COLLADAFW::Node *> &result)
+{
+ std::vector<COLLADAFW::Node *>::const_iterator it;
+ /* for each root_joint */
+ for (it = root_joints.begin(); it != root_joints.end(); it++) {
+ COLLADAFW::Node *root = *it;
+ std::vector<JointData>::iterator ji;
+ /* for each joint_data in this skin */
+ for (ji = joint_data.begin(); ji != joint_data.end(); ji++) {
+ if (joint_by_uid.find((*ji).joint_uid) != joint_by_uid.end()) {
+ /* get joint node from joint map */
+ COLLADAFW::Node *joint = joint_by_uid[(*ji).joint_uid];
+
+ /* find if joint node is in the tree belonging to the root_joint */
+ if (find_node_in_tree(joint, root)) {
+ if (std::find(result.begin(), result.end(), root) == result.end()) {
+ result.push_back(root);
+ }
+ }
+ }
+ }
+ }
+}
+
+bool SkinInfo::find_node_in_tree(COLLADAFW::Node *node, COLLADAFW::Node *tree_root)
+{
+ if (node == tree_root) {
+ return true;
+ }
+
+ COLLADAFW::NodePointerArray &children = tree_root->getChildNodes();
+ for (unsigned int i = 0; i < children.getCount(); i++) {
+ if (find_node_in_tree(node, children[i])) {
+ return true;
+ }
+ }
+
+ return false;
+}
diff --git a/source/blender/io/collada/SkinInfo.h b/source/blender/io/collada/SkinInfo.h
new file mode 100644
index 00000000000..255d6d9b1f3
--- /dev/null
+++ b/source/blender/io/collada/SkinInfo.h
@@ -0,0 +1,130 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __SKININFO_H__
+#define __SKININFO_H__
+
+#include <map>
+#include <vector>
+
+#include "COLLADAFWUniqueId.h"
+#include "COLLADAFWTypes.h"
+#include "COLLADAFWNode.h"
+#include "COLLADAFWSkinController.h"
+#include "COLLADAFWSkinControllerData.h"
+
+#include "DNA_object_types.h"
+#include "BKE_context.h"
+
+#include "TransformReader.h"
+#include "collada_internal.h"
+
+// This is used to store data passed in write_controller_data.
+// Arrays from COLLADAFW::SkinControllerData lose ownership, so do this class members
+// so that arrays don't get freed until we free them explicitly.
+class SkinInfo {
+ private:
+ // to build armature bones from inverse bind matrices
+ struct JointData {
+ float inv_bind_mat[4][4]; // joint inverse bind matrix
+ COLLADAFW::UniqueId joint_uid; // joint node UID
+ // Object *ob_arm; // armature object
+ };
+
+ float bind_shape_matrix[4][4];
+
+ // data from COLLADAFW::SkinControllerData, each array should be freed
+ COLLADAFW::UIntValuesArray joints_per_vertex;
+ COLLADAFW::UIntValuesArray weight_indices;
+ COLLADAFW::IntValuesArray joint_indices;
+ // COLLADAFW::FloatOrDoubleArray weights;
+ std::vector<float> weights;
+
+ std::vector<JointData> joint_data; // index to this vector is joint index
+
+ UnitConverter *unit_converter;
+
+ Object *ob_arm;
+ COLLADAFW::UniqueId controller_uid;
+ Object *parent;
+
+ public:
+ SkinInfo();
+ SkinInfo(const SkinInfo &skin);
+ SkinInfo(UnitConverter *conv);
+
+ // nobody owns the data after this, so it should be freed manually with releaseMemory
+ template<typename T> void transfer_array_data(T &src, T &dest);
+
+ // when src is const we cannot src.yieldOwnerShip, this is used by copy constructor
+ void transfer_int_array_data_const(const COLLADAFW::IntValuesArray &src,
+ COLLADAFW::IntValuesArray &dest);
+
+ void transfer_uint_array_data_const(const COLLADAFW::UIntValuesArray &src,
+ COLLADAFW::UIntValuesArray &dest);
+
+ void borrow_skin_controller_data(const COLLADAFW::SkinControllerData *skin);
+
+ void free();
+
+ // using inverse bind matrices to construct armature
+ // it is safe to invert them to get the original matrices
+ // because if they are inverse matrices, they can be inverted
+ void add_joint(const COLLADABU::Math::Matrix4 &matrix);
+
+ void set_controller(const COLLADAFW::SkinController *co);
+
+ // called from write_controller
+ Object *create_armature(Main *bmain, Scene *scene, ViewLayer *view_layer);
+
+ Object *set_armature(Object *ob_arm);
+
+ bool get_joint_inv_bind_matrix(float inv_bind_mat[4][4], COLLADAFW::Node *node);
+
+ Object *BKE_armature_from_object();
+
+ const COLLADAFW::UniqueId &get_controller_uid();
+
+ // check if this skin controller references a joint or any descendant of it
+ //
+ // some nodes may not be referenced by SkinController,
+ // in this case to determine if the node belongs to this armature,
+ // we need to search down the tree
+ bool uses_joint_or_descendant(COLLADAFW::Node *node);
+
+ void link_armature(bContext *C,
+ Object *ob,
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &joint_by_uid,
+ TransformReader *tm);
+
+ bPoseChannel *get_pose_channel_from_node(COLLADAFW::Node *node);
+
+ void set_parent(Object *_parent);
+
+ Object *get_parent();
+
+ void find_root_joints(const std::vector<COLLADAFW::Node *> &root_joints,
+ std::map<COLLADAFW::UniqueId, COLLADAFW::Node *> &joint_by_uid,
+ std::vector<COLLADAFW::Node *> &result);
+
+ bool find_node_in_tree(COLLADAFW::Node *node, COLLADAFW::Node *tree_root);
+};
+
+#endif
diff --git a/source/blender/io/collada/TransformReader.cpp b/source/blender/io/collada/TransformReader.cpp
new file mode 100644
index 00000000000..8ee31f80405
--- /dev/null
+++ b/source/blender/io/collada/TransformReader.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+/* COLLADABU_ASSERT, may be able to remove later */
+#include "COLLADABUPlatform.h"
+
+#include "TransformReader.h"
+
+TransformReader::TransformReader(UnitConverter *conv) : unit_converter(conv)
+{
+ /* pass */
+}
+
+void TransformReader::get_node_mat(float mat[4][4],
+ COLLADAFW::Node *node,
+ std::map<COLLADAFW::UniqueId, Animation> *animation_map,
+ Object *ob)
+{
+ get_node_mat(mat, node, animation_map, ob, NULL);
+}
+
+void TransformReader::get_node_mat(float mat[4][4],
+ COLLADAFW::Node *node,
+ std::map<COLLADAFW::UniqueId, Animation> *animation_map,
+ Object *ob,
+ float parent_mat[4][4])
+{
+ float cur[4][4];
+ float copy[4][4];
+
+ unit_m4(mat);
+
+ for (unsigned int i = 0; i < node->getTransformations().getCount(); i++) {
+
+ COLLADAFW::Transformation *tm = node->getTransformations()[i];
+ COLLADAFW::Transformation::TransformationType type = tm->getTransformationType();
+
+ switch (type) {
+ case COLLADAFW::Transformation::MATRIX:
+ // When matrix AND Trans/Rot/Scale are defined for a node,
+ // then this is considered as redundant information.
+ // So if we find a Matrix we use that and return.
+ dae_matrix_to_mat4(tm, mat);
+ if (parent_mat) {
+ mul_m4_m4m4(mat, parent_mat, mat);
+ }
+ return;
+ case COLLADAFW::Transformation::TRANSLATE:
+ dae_translate_to_mat4(tm, cur);
+ break;
+ case COLLADAFW::Transformation::ROTATE:
+ dae_rotate_to_mat4(tm, cur);
+ break;
+ case COLLADAFW::Transformation::SCALE:
+ dae_scale_to_mat4(tm, cur);
+ break;
+ case COLLADAFW::Transformation::LOOKAT:
+ fprintf(stderr, "|! LOOKAT transformations are not supported yet.\n");
+ break;
+ case COLLADAFW::Transformation::SKEW:
+ fprintf(stderr, "|! SKEW transformations are not supported yet.\n");
+ break;
+ }
+
+ copy_m4_m4(copy, mat);
+ mul_m4_m4m4(mat, copy, cur);
+
+ if (animation_map) {
+ // AnimationList that drives this Transformation
+ const COLLADAFW::UniqueId &anim_list_id = tm->getAnimationList();
+
+ // store this so later we can link animation data with ob
+ Animation anim = {ob, node, tm};
+ (*animation_map)[anim_list_id] = anim;
+ }
+ }
+
+ if (parent_mat) {
+ mul_m4_m4m4(mat, parent_mat, mat);
+ }
+}
+
+void TransformReader::dae_rotate_to_mat4(COLLADAFW::Transformation *tm, float m[4][4])
+{
+ COLLADAFW::Rotate *ro = (COLLADAFW::Rotate *)tm;
+ COLLADABU::Math::Vector3 &axis = ro->getRotationAxis();
+ const float angle = (float)DEG2RAD(ro->getRotationAngle());
+ const float ax[] = {(float)axis[0], (float)axis[1], (float)axis[2]};
+ // float quat[4];
+ // axis_angle_to_quat(quat, axis, angle);
+ // quat_to_mat4(m, quat);
+ axis_angle_to_mat4(m, ax, angle);
+}
+
+void TransformReader::dae_translate_to_mat4(COLLADAFW::Transformation *tm, float m[4][4])
+{
+ COLLADAFW::Translate *tra = (COLLADAFW::Translate *)tm;
+ COLLADABU::Math::Vector3 &t = tra->getTranslation();
+
+ unit_m4(m);
+
+ m[3][0] = (float)t[0];
+ m[3][1] = (float)t[1];
+ m[3][2] = (float)t[2];
+}
+
+void TransformReader::dae_scale_to_mat4(COLLADAFW::Transformation *tm, float m[4][4])
+{
+ COLLADABU::Math::Vector3 &s = ((COLLADAFW::Scale *)tm)->getScale();
+ float size[3] = {(float)s[0], (float)s[1], (float)s[2]};
+ size_to_mat4(m, size);
+}
+
+void TransformReader::dae_matrix_to_mat4(COLLADAFW::Transformation *tm, float m[4][4])
+{
+ unit_converter->dae_matrix_to_mat4_(m, ((COLLADAFW::Matrix *)tm)->getMatrix());
+}
+
+void TransformReader::dae_translate_to_v3(COLLADAFW::Transformation *tm, float v[3])
+{
+ dae_vector3_to_v3(((COLLADAFW::Translate *)tm)->getTranslation(), v);
+}
+
+void TransformReader::dae_scale_to_v3(COLLADAFW::Transformation *tm, float v[3])
+{
+ dae_vector3_to_v3(((COLLADAFW::Scale *)tm)->getScale(), v);
+}
+
+void TransformReader::dae_vector3_to_v3(const COLLADABU::Math::Vector3 &v3, float v[3])
+{
+ v[0] = v3.x;
+ v[1] = v3.y;
+ v[2] = v3.z;
+}
diff --git a/source/blender/io/collada/TransformReader.h b/source/blender/io/collada/TransformReader.h
new file mode 100644
index 00000000000..2cf3ee795ae
--- /dev/null
+++ b/source/blender/io/collada/TransformReader.h
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __TRANSFORMREADER_H__
+#define __TRANSFORMREADER_H__
+
+#include "COLLADAFWNode.h"
+#include "COLLADAFWTransformation.h"
+#include "COLLADAFWTranslate.h"
+#include "COLLADAFWRotate.h"
+#include "COLLADAFWScale.h"
+#include "COLLADAFWMatrix.h"
+#include "COLLADAFWUniqueId.h"
+#include "Math/COLLADABUMathVector3.h"
+
+#include "DNA_object_types.h"
+#include "BLI_math.h"
+
+#include "collada_internal.h"
+
+// struct Object;
+
+class TransformReader {
+ protected:
+ UnitConverter *unit_converter;
+
+ public:
+ struct Animation {
+ Object *ob;
+ COLLADAFW::Node *node;
+ COLLADAFW::Transformation *tm; // which transform is animated by an AnimationList->id
+ };
+
+ TransformReader(UnitConverter *conv);
+
+ void get_node_mat(float mat[4][4],
+ COLLADAFW::Node *node,
+ std::map<COLLADAFW::UniqueId, Animation> *animation_map,
+ Object *ob);
+ void get_node_mat(float mat[4][4],
+ COLLADAFW::Node *node,
+ std::map<COLLADAFW::UniqueId, Animation> *animation_map,
+ Object *ob,
+ float parent_mat[4][4]);
+
+ void dae_rotate_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]);
+ void dae_translate_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]);
+ void dae_scale_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]);
+ void dae_matrix_to_mat4(COLLADAFW::Transformation *tm, float m[4][4]);
+ void dae_translate_to_v3(COLLADAFW::Transformation *tm, float v[3]);
+ void dae_scale_to_v3(COLLADAFW::Transformation *tm, float v[3]);
+ void dae_vector3_to_v3(const COLLADABU::Math::Vector3 &v3, float v[3]);
+};
+
+#endif
diff --git a/source/blender/io/collada/TransformWriter.cpp b/source/blender/io/collada/TransformWriter.cpp
new file mode 100644
index 00000000000..0a66db72cb9
--- /dev/null
+++ b/source/blender/io/collada/TransformWriter.cpp
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#include "BLI_math.h"
+#include "BLI_sys_types.h"
+
+#include "BKE_object.h"
+
+#include "TransformWriter.h"
+
+void TransformWriter::add_joint_transform(COLLADASW::Node &node,
+ float mat[4][4],
+ float parent_mat[4][4],
+ BCExportSettings &export_settings,
+ bool has_restmat)
+{
+ float local[4][4];
+
+ if (parent_mat) {
+ float invpar[4][4];
+ invert_m4_m4(invpar, parent_mat);
+ mul_m4_m4m4(local, invpar, mat);
+ }
+ else {
+ copy_m4_m4(local, mat);
+ }
+
+ if (!has_restmat && export_settings.get_apply_global_orientation()) {
+ bc_apply_global_transform(local, export_settings.get_global_transform());
+ }
+
+ double dmat[4][4];
+ UnitConverter *converter = new UnitConverter();
+ converter->mat4_to_dae_double(dmat, local);
+ delete converter;
+
+ if (export_settings.get_object_transformation_type() == BC_TRANSFORMATION_TYPE_MATRIX) {
+ node.addMatrix("transform", dmat);
+ }
+ else {
+ float loc[3], rot[3], scale[3];
+ bc_decompose(local, loc, rot, NULL, scale);
+ add_transform(node, loc, rot, scale);
+ }
+}
+
+void TransformWriter::add_node_transform_ob(COLLADASW::Node &node,
+ Object *ob,
+ BCExportSettings &export_settings)
+{
+ bool limit_precision = export_settings.get_limit_precision();
+
+ /* Export the local Matrix (relative to the object parent,
+ * be it an object, bone or vertex(-tices)). */
+ Matrix f_obmat;
+ BKE_object_matrix_local_get(ob, f_obmat);
+
+ if (export_settings.get_apply_global_orientation()) {
+ bc_apply_global_transform(f_obmat, export_settings.get_global_transform());
+ }
+ else {
+ bc_add_global_transform(f_obmat, export_settings.get_global_transform());
+ }
+
+ switch (export_settings.get_object_transformation_type()) {
+ case BC_TRANSFORMATION_TYPE_MATRIX: {
+ UnitConverter converter;
+ double d_obmat[4][4];
+ converter.mat4_to_dae_double(d_obmat, f_obmat);
+
+ if (limit_precision) {
+ BCMatrix::sanitize(d_obmat, LIMITTED_PRECISION);
+ }
+ node.addMatrix("transform", d_obmat);
+ break;
+ }
+ case BC_TRANSFORMATION_TYPE_DECOMPOSED: {
+ float loc[3], rot[3], scale[3];
+ bc_decompose(f_obmat, loc, rot, NULL, scale);
+ if (limit_precision) {
+ bc_sanitize_v3(loc, LIMITTED_PRECISION);
+ bc_sanitize_v3(rot, LIMITTED_PRECISION);
+ bc_sanitize_v3(scale, LIMITTED_PRECISION);
+ }
+ add_transform(node, loc, rot, scale);
+ break;
+ }
+ }
+}
+
+void TransformWriter::add_node_transform_identity(COLLADASW::Node &node,
+ BCExportSettings &export_settings)
+{
+ BC_export_transformation_type transformation_type =
+ export_settings.get_object_transformation_type();
+ switch (transformation_type) {
+ case BC_TRANSFORMATION_TYPE_MATRIX: {
+ BCMatrix mat;
+ DMatrix d_obmat;
+ mat.get_matrix(d_obmat);
+ node.addMatrix("transform", d_obmat);
+ break;
+ }
+ default: {
+ float loc[3] = {0.0f, 0.0f, 0.0f};
+ float scale[3] = {1.0f, 1.0f, 1.0f};
+ float rot[3] = {0.0f, 0.0f, 0.0f};
+ add_transform(node, loc, rot, scale);
+ break;
+ }
+ }
+}
+
+void TransformWriter::add_transform(COLLADASW::Node &node,
+ float loc[3],
+ float rot[3],
+ float scale[3])
+{
+ node.addScale("scale", scale[0], scale[1], scale[2]);
+ node.addRotateZ("rotationZ", RAD2DEGF(rot[2]));
+ node.addRotateY("rotationY", RAD2DEGF(rot[1]));
+ node.addRotateX("rotationX", RAD2DEGF(rot[0]));
+ node.addTranslate("location", loc[0], loc[1], loc[2]);
+}
diff --git a/source/blender/io/collada/TransformWriter.h b/source/blender/io/collada/TransformWriter.h
new file mode 100644
index 00000000000..d2e4b369cdc
--- /dev/null
+++ b/source/blender/io/collada/TransformWriter.h
@@ -0,0 +1,48 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __TRANSFORMWRITER_H__
+#define __TRANSFORMWRITER_H__
+
+#include "COLLADASWNode.h"
+
+#include "DNA_object_types.h"
+
+#include "collada_internal.h"
+#include "collada_utils.h"
+#include "collada.h"
+
+class TransformWriter {
+ protected:
+ void add_joint_transform(COLLADASW::Node &node,
+ float mat[4][4],
+ float parent_mat[4][4],
+ BCExportSettings &export_settings,
+ bool has_restmat);
+
+ void add_node_transform_ob(COLLADASW::Node &node, Object *ob, BCExportSettings &export_settings);
+
+ void add_node_transform_identity(COLLADASW::Node &node, BCExportSettings &export_settings);
+
+ private:
+ void add_transform(COLLADASW::Node &node, float loc[3], float rot[3], float scale[3]);
+};
+
+#endif
diff --git a/source/blender/io/collada/collada.cpp b/source/blender/io/collada/collada.cpp
new file mode 100644
index 00000000000..ea5600aa850
--- /dev/null
+++ b/source/blender/io/collada/collada.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+/* COLLADABU_ASSERT, may be able to remove later */
+#include "COLLADABUPlatform.h"
+
+#include "DocumentExporter.h"
+#include "DocumentImporter.h"
+#include "ExportSettings.h"
+#include "ImportSettings.h"
+#include "collada.h"
+
+extern "C" {
+#include "BKE_scene.h"
+#include "BKE_context.h"
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+/* make dummy file */
+#include "BLI_fileops.h"
+#include "BLI_linklist.h"
+
+static void print_import_header(ImportSettings &import_settings)
+{
+ fprintf(stderr, "+-- Collada Import parameters------\n");
+ fprintf(stderr, "| input file : %s\n", import_settings.filepath);
+ fprintf(stderr, "| use units : %s\n", (import_settings.import_units) ? "yes" : "no");
+ fprintf(stderr, "| autoconnect : %s\n", (import_settings.auto_connect) ? "yes" : "no");
+ fprintf(stderr, "+-- Armature Import parameters ----\n");
+ fprintf(stderr, "| find bone chains: %s\n", (import_settings.find_chains) ? "yes" : "no");
+ fprintf(stderr, "| min chain len : %d\n", import_settings.min_chain_length);
+ fprintf(stderr, "| fix orientation : %s\n", (import_settings.fix_orientation) ? "yes" : "no");
+ fprintf(stderr, "| keep bind info : %s\n", (import_settings.keep_bind_info) ? "yes" : "no");
+}
+
+static void print_import_footer(int status)
+{
+ fprintf(stderr, "+----------------------------------\n");
+ fprintf(stderr, "| Collada Import : %s\n", (status) ? "OK" : "FAIL");
+ fprintf(stderr, "+----------------------------------\n");
+}
+
+int collada_import(bContext *C, ImportSettings *import_settings)
+{
+ print_import_header(*import_settings);
+ DocumentImporter imp(C, import_settings);
+ int status = imp.import() ? 1 : 0;
+ print_import_footer(status);
+
+ return status;
+}
+
+int collada_export(bContext *C, ExportSettings *export_settings)
+{
+ BlenderContext blender_context(C);
+ ViewLayer *view_layer = blender_context.get_view_layer();
+
+ int includeFilter = OB_REL_NONE;
+ if (export_settings->include_armatures) {
+ includeFilter |= OB_REL_MOD_ARMATURE;
+ }
+ if (export_settings->include_children) {
+ includeFilter |= OB_REL_CHILDREN_RECURSIVE;
+ }
+
+ /* Fetch the complete set of exported objects
+ * ATTENTION: Invisible objects will not be exported
+ */
+ eObjectSet objectSet = (export_settings->selected) ? OB_SET_SELECTED : OB_SET_ALL;
+ export_settings->export_set = BKE_object_relational_superset(
+ view_layer, objectSet, (eObRelationTypes)includeFilter);
+
+ int export_count = BLI_linklist_count(export_settings->export_set);
+
+ if (export_count == 0) {
+ if (export_settings->selected) {
+ fprintf(stderr,
+ "Collada: Found no objects to export.\nPlease ensure that all objects which shall "
+ "be exported are also visible in the 3D Viewport.\n");
+ }
+ else {
+ fprintf(stderr, "Collada: Your scene seems to be empty. No Objects will be exported.\n");
+ }
+ }
+ else {
+ if (export_settings->sort_by_name) {
+ bc_bubble_sort_by_Object_name(export_settings->export_set);
+ }
+ }
+
+ DocumentExporter exporter(blender_context, export_settings);
+ int status = exporter.exportCurrentScene();
+
+ BLI_linklist_free(export_settings->export_set, NULL);
+
+ return (status) ? -1 : export_count;
+}
+
+/* end extern C */
+}
diff --git a/source/blender/io/collada/collada.h b/source/blender/io/collada/collada.h
new file mode 100644
index 00000000000..72753e170a3
--- /dev/null
+++ b/source/blender/io/collada/collada.h
@@ -0,0 +1,50 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __COLLADA_H__
+#define __COLLADA_H__
+
+#include <stdlib.h>
+
+#include "ImportSettings.h"
+#include "ExportSettings.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "BLI_linklist.h"
+#include "BLI_path_util.h"
+#include "RNA_types.h"
+
+struct bContext;
+
+/*
+ * both return 1 on success, 0 on error
+ */
+int collada_import(struct bContext *C, ImportSettings *import_settings);
+
+int collada_export(struct bContext *C, ExportSettings *export_settings);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/source/blender/io/collada/collada_internal.cpp b/source/blender/io/collada/collada_internal.cpp
new file mode 100644
index 00000000000..7e834045795
--- /dev/null
+++ b/source/blender/io/collada/collada_internal.cpp
@@ -0,0 +1,340 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+/* COLLADABU_ASSERT, may be able to remove later */
+#include "COLLADABUPlatform.h"
+#include "collada_utils.h"
+
+#include "BLI_linklist.h"
+#include "ED_armature.h"
+
+UnitConverter::UnitConverter() : unit(), up_axis(COLLADAFW::FileInfo::Z_UP)
+{
+ axis_angle_to_mat4_single(x_up_mat4, 'Y', -0.5 * M_PI);
+ axis_angle_to_mat4_single(y_up_mat4, 'X', 0.5 * M_PI);
+
+ unit_m4(z_up_mat4);
+ unit_m4(scale_mat4);
+}
+
+void UnitConverter::read_asset(const COLLADAFW::FileInfo *asset)
+{
+ unit = asset->getUnit();
+ up_axis = asset->getUpAxisType();
+}
+
+UnitConverter::UnitSystem UnitConverter::isMetricSystem()
+{
+ switch (unit.getLinearUnitUnit()) {
+ case COLLADAFW::FileInfo::Unit::MILLIMETER:
+ case COLLADAFW::FileInfo::Unit::CENTIMETER:
+ case COLLADAFW::FileInfo::Unit::DECIMETER:
+ case COLLADAFW::FileInfo::Unit::METER:
+ case COLLADAFW::FileInfo::Unit::KILOMETER:
+ return UnitConverter::Metric;
+ case COLLADAFW::FileInfo::Unit::INCH:
+ case COLLADAFW::FileInfo::Unit::FOOT:
+ case COLLADAFW::FileInfo::Unit::YARD:
+ return UnitConverter::Imperial;
+ default:
+ return UnitConverter::None;
+ }
+}
+
+float UnitConverter::getLinearMeter()
+{
+ return (float)unit.getLinearUnitMeter();
+}
+
+void UnitConverter::convertVector3(COLLADABU::Math::Vector3 &vec, float *v)
+{
+ v[0] = vec.x;
+ v[1] = vec.y;
+ v[2] = vec.z;
+}
+
+// TODO need also for angle conversion, time conversion...
+
+void UnitConverter::dae_matrix_to_mat4_(float out[4][4], const COLLADABU::Math::Matrix4 &in)
+{
+ // in DAE, matrices use columns vectors, (see comments in COLLADABUMathMatrix4.h)
+ // so here, to make a blender matrix, we swap columns and rows
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ out[i][j] = in[j][i];
+ }
+ }
+}
+
+void UnitConverter::mat4_to_dae(float out[4][4], float in[4][4])
+{
+ transpose_m4_m4(out, in);
+}
+
+void UnitConverter::mat4_to_dae_double(double out[4][4], float in[4][4])
+{
+ float mat[4][4];
+
+ mat4_to_dae(mat, in);
+
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ out[i][j] = mat[i][j];
+ }
+ }
+}
+
+float (&UnitConverter::get_rotation())[4][4]
+{
+ switch (up_axis) {
+ case COLLADAFW::FileInfo::X_UP:
+ return x_up_mat4;
+ break;
+ case COLLADAFW::FileInfo::Y_UP:
+ return y_up_mat4;
+ break;
+ default:
+ return z_up_mat4;
+ break;
+ }
+}
+
+float (&UnitConverter::get_scale())[4][4]
+{
+ return scale_mat4;
+}
+
+void UnitConverter::calculate_scale(Scene &sce)
+{
+ PointerRNA scene_ptr, unit_settings;
+ PropertyRNA *system_ptr, *scale_ptr;
+ RNA_id_pointer_create(&sce.id, &scene_ptr);
+
+ unit_settings = RNA_pointer_get(&scene_ptr, "unit_settings");
+ system_ptr = RNA_struct_find_property(&unit_settings, "system");
+ scale_ptr = RNA_struct_find_property(&unit_settings, "scale_length");
+
+ int type = RNA_property_enum_get(&unit_settings, system_ptr);
+
+ float bl_scale;
+
+ switch (type) {
+ case USER_UNIT_NONE:
+ bl_scale = 1.0; // map 1 Blender unit to 1 Meter
+ break;
+
+ case USER_UNIT_METRIC:
+ bl_scale = RNA_property_float_get(&unit_settings, scale_ptr);
+ break;
+
+ default:
+ bl_scale = RNA_property_float_get(&unit_settings, scale_ptr);
+ // it looks like the conversion to Imperial is done implicitly.
+ // So nothing to do here.
+ break;
+ }
+
+ float rescale[3];
+ rescale[0] = rescale[1] = rescale[2] = getLinearMeter() / bl_scale;
+
+ size_to_mat4(scale_mat4, rescale);
+}
+
+/**
+ * Translation map.
+ * Used to translate every COLLADA id to a valid id, no matter what "wrong" letters may be
+ * included. Look at the IDREF XSD declaration for more.
+ * Follows strictly the COLLADA XSD declaration which explicitly allows non-english chars,
+ * like special chars (e.g. micro sign), umlauts and so on.
+ * The COLLADA spec also allows additional chars for member access ('.'), these
+ * must obviously be removed too, otherwise they would be heavily misinterpreted.
+ */
+const unsigned char translate_start_name_map[256] = {
+
+ 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 95, 95, 95, 95, 95, 95, 95, 95, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 95, 95, 95, 95,
+ 95, 95, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
+ 114, 115, 116, 117, 118, 119, 120, 121, 122, 95, 95, 95, 95, 95,
+
+ 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
+ 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165,
+ 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184,
+ 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203,
+ 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222,
+ 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
+ 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
+};
+
+const unsigned char translate_name_map[256] = {
+
+ 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95, 95,
+ 95, 95, 95, 95, 95, 95, 95, 45, 95, 95, 48, 49, 50, 51, 52, 53, 54, 55, 56,
+ 57, 95, 95, 95, 95, 95, 95, 95, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75,
+ 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 95, 95, 95, 95,
+ 95, 95, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113,
+ 114, 115, 116, 117, 118, 119, 120, 121, 122, 95, 95, 95, 95, 95,
+
+ 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146,
+ 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, 163, 164, 165,
+ 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, 177, 178, 179, 180, 181, 182, 183, 184,
+ 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203,
+ 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222,
+ 223, 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241,
+ 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255,
+};
+
+typedef std::map<std::string, std::vector<std::string>> map_string_list;
+map_string_list global_id_map;
+
+void clear_global_id_map()
+{
+ global_id_map.clear();
+}
+
+/** Look at documentation of translate_map */
+std::string translate_id(const char *idString)
+{
+ std::string id = std::string(idString);
+ return translate_id(id);
+}
+
+std::string translate_id(const std::string &id)
+{
+ if (id.size() == 0) {
+ return id;
+ }
+
+ std::string id_translated = id;
+ id_translated[0] = translate_start_name_map[(unsigned int)id_translated[0]];
+ for (unsigned int i = 1; i < id_translated.size(); i++) {
+ id_translated[i] = translate_name_map[(unsigned int)id_translated[i]];
+ }
+ // It's so much workload now, the if () should speed up things.
+ if (id_translated != id) {
+ // Search duplicates
+ map_string_list::iterator iter = global_id_map.find(id_translated);
+ if (iter != global_id_map.end()) {
+ unsigned int i = 0;
+ bool found = false;
+ for (i = 0; i < iter->second.size(); i++) {
+ if (id == iter->second[i]) {
+ found = true;
+ break;
+ }
+ }
+ bool convert = false;
+ if (found) {
+ if (i > 0) {
+ convert = true;
+ }
+ }
+ else {
+ convert = true;
+ global_id_map[id_translated].push_back(id);
+ }
+ if (convert) {
+ std::stringstream out;
+ out << ++i;
+ id_translated += out.str();
+ }
+ }
+ else {
+ global_id_map[id_translated].push_back(id);
+ }
+ }
+ return id_translated;
+}
+
+std::string id_name(void *id)
+{
+ return ((ID *)id)->name + 2;
+}
+
+std::string encode_xml(std::string xml)
+{
+ const std::map<char, std::string> escape{
+ {'<', "&lt;"}, {'>', "&gt;"}, {'"', "&quot;"}, {'\'', "&apos;"}, {'&', "&amp;"}};
+
+ std::map<char, std::string>::const_iterator it;
+ std::string encoded_xml = "";
+
+ for (unsigned int i = 0; i < xml.size(); i++) {
+ char c = xml.at(i);
+ it = escape.find(c);
+
+ if (it == escape.end()) {
+ encoded_xml += c;
+ }
+ else {
+ encoded_xml += it->second;
+ }
+ }
+ return encoded_xml;
+}
+
+std::string get_geometry_id(Object *ob)
+{
+ return translate_id(id_name(ob->data)) + "-mesh";
+}
+
+std::string get_geometry_id(Object *ob, bool use_instantiation)
+{
+ std::string geom_name = (use_instantiation) ? id_name(ob->data) : id_name(ob);
+
+ return translate_id(geom_name) + "-mesh";
+}
+
+std::string get_light_id(Object *ob)
+{
+ return translate_id(id_name(ob)) + "-light";
+}
+
+std::string get_joint_sid(Bone *bone)
+{
+ return translate_id(bone->name);
+}
+static std::string get_joint_sid(EditBone *bone)
+{
+ return translate_id(bone->name);
+}
+
+std::string get_camera_id(Object *ob)
+{
+ return translate_id(id_name(ob)) + "-camera";
+}
+
+std::string get_effect_id(Material *mat)
+{
+ return translate_id(id_name(mat)) + "-effect";
+}
+
+std::string get_material_id(Material *mat)
+{
+ return translate_id(id_name(mat)) + "-material";
+}
+
+std::string get_morph_id(Object *ob)
+{
+ return translate_id(id_name(ob)) + "-morph";
+}
diff --git a/source/blender/io/collada/collada_internal.h b/source/blender/io/collada/collada_internal.h
new file mode 100644
index 00000000000..297ea9c0bbb
--- /dev/null
+++ b/source/blender/io/collada/collada_internal.h
@@ -0,0 +1,98 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __COLLADA_INTERNAL_H__
+#define __COLLADA_INTERNAL_H__
+
+#include <string>
+#include <vector>
+#include <map>
+
+#include "COLLADAFWFileInfo.h"
+#include "Math/COLLADABUMathMatrix4.h"
+
+#include "DNA_armature_types.h"
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "BLI_math.h"
+#include "BLI_linklist.h"
+
+class UnitConverter {
+ private:
+ COLLADAFW::FileInfo::Unit unit;
+ COLLADAFW::FileInfo::UpAxisType up_axis;
+
+ float x_up_mat4[4][4];
+ float y_up_mat4[4][4];
+ float z_up_mat4[4][4];
+ float scale_mat4[4][4];
+
+ public:
+ enum UnitSystem {
+ None,
+ Metric,
+ Imperial,
+ };
+
+ // Initialize with Z_UP, since Blender uses right-handed, z-up
+ UnitConverter();
+
+ void read_asset(const COLLADAFW::FileInfo *asset);
+
+ void convertVector3(COLLADABU::Math::Vector3 &vec, float *v);
+
+ UnitConverter::UnitSystem isMetricSystem(void);
+
+ float getLinearMeter(void);
+
+ // TODO need also for angle conversion, time conversion...
+
+ static void dae_matrix_to_mat4_(float out[4][4], const COLLADABU::Math::Matrix4 &in);
+ static void mat4_to_dae(float out[4][4], float in[4][4]);
+ static void mat4_to_dae_double(double out[4][4], float in[4][4]);
+
+ float (&get_rotation())[4][4];
+ float (&get_scale())[4][4];
+ void calculate_scale(Scene &sce);
+};
+
+extern void clear_global_id_map();
+/** Look at documentation of translate_map */
+extern std::string translate_id(const std::string &id);
+extern std::string translate_id(const char *idString);
+
+extern std::string id_name(void *id);
+extern std::string encode_xml(std::string xml);
+
+extern std::string get_geometry_id(Object *ob);
+extern std::string get_geometry_id(Object *ob, bool use_instantiation);
+
+extern std::string get_light_id(Object *ob);
+
+extern std::string get_joint_sid(Bone *bone);
+
+extern std::string get_camera_id(Object *ob);
+extern std::string get_morph_id(Object *ob);
+
+extern std::string get_effect_id(Material *mat);
+extern std::string get_material_id(Material *mat);
+
+#endif /* __COLLADA_INTERNAL_H__ */
diff --git a/source/blender/io/collada/collada_utils.cpp b/source/blender/io/collada/collada_utils.cpp
new file mode 100644
index 00000000000..26b392af0a1
--- /dev/null
+++ b/source/blender/io/collada/collada_utils.cpp
@@ -0,0 +1,1458 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+/* COLLADABU_ASSERT, may be able to remove later */
+#include "COLLADABUPlatform.h"
+
+#include "COLLADAFWGeometry.h"
+#include "COLLADAFWMeshPrimitive.h"
+#include "COLLADAFWMeshVertexData.h"
+
+#include <set>
+#include <string>
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "DNA_modifier_types.h"
+#include "DNA_customdata_types.h"
+#include "DNA_key_types.h"
+#include "DNA_object_types.h"
+#include "DNA_constraint_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_armature_types.h"
+
+#include "BLI_math.h"
+#include "BLI_linklist.h"
+#include "BLI_listbase.h"
+
+#include "BKE_action.h"
+#include "BKE_context.h"
+#include "BKE_customdata.h"
+#include "BKE_constraint.h"
+#include "BKE_key.h"
+#include "BKE_material.h"
+#include "BKE_node.h"
+#include "BKE_object.h"
+#include "BKE_global.h"
+#include "BKE_layer.h"
+#include "BKE_lib_id.h"
+#include "BKE_mesh.h"
+#include "BKE_mesh_runtime.h"
+#include "BKE_object.h"
+#include "BKE_scene.h"
+
+#include "ED_armature.h"
+#include "ED_screen.h"
+#include "ED_node.h"
+#include "ED_object.h"
+
+#include "WM_api.h" /* XXX hrm, see if we can do without this */
+#include "WM_types.h"
+
+#include "bmesh.h"
+#include "bmesh_tools.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+#if 0
+# include "NOD_common.h"
+#endif
+}
+
+#include "collada_utils.h"
+#include "ExportSettings.h"
+#include "BlenderContext.h"
+
+float bc_get_float_value(const COLLADAFW::FloatOrDoubleArray &array, unsigned int index)
+{
+ if (index >= array.getValuesCount()) {
+ return 0.0f;
+ }
+
+ if (array.getType() == COLLADAFW::MeshVertexData::DATA_TYPE_FLOAT) {
+ return array.getFloatValues()->getData()[index];
+ }
+ else {
+ return array.getDoubleValues()->getData()[index];
+ }
+}
+
+/* copied from /editors/object/object_relations.c */
+int bc_test_parent_loop(Object *par, Object *ob)
+{
+ /* test if 'ob' is a parent somewhere in par's parents */
+
+ if (par == NULL) {
+ return 0;
+ }
+ if (ob == par) {
+ return 1;
+ }
+
+ return bc_test_parent_loop(par->parent, ob);
+}
+
+bool bc_validateConstraints(bConstraint *con)
+{
+ const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
+
+ /* these we can skip completely (invalid constraints...) */
+ if (cti == NULL) {
+ return false;
+ }
+ if (con->flag & (CONSTRAINT_DISABLE | CONSTRAINT_OFF)) {
+ return false;
+ }
+
+ /* these constraints can't be evaluated anyway */
+ if (cti->evaluate_constraint == NULL) {
+ return false;
+ }
+
+ /* influence == 0 should be ignored */
+ if (con->enforce == 0.0f) {
+ return false;
+ }
+
+ /* validation passed */
+ return true;
+}
+
+bool bc_set_parent(Object *ob, Object *par, bContext *C, bool is_parent_space)
+{
+ Scene *scene = CTX_data_scene(C);
+ int partype = PAR_OBJECT;
+ const bool xmirror = false;
+ const bool keep_transform = false;
+
+ if (par && is_parent_space) {
+ mul_m4_m4m4(ob->obmat, par->obmat, ob->obmat);
+ }
+
+ bool ok = ED_object_parent_set(NULL, C, scene, ob, par, partype, xmirror, keep_transform, NULL);
+ return ok;
+}
+
+std::vector<bAction *> bc_getSceneActions(const bContext *C, Object *ob, bool all_actions)
+{
+ std::vector<bAction *> actions;
+ if (all_actions) {
+ Main *bmain = CTX_data_main(C);
+ ID *id;
+
+ for (id = (ID *)bmain->actions.first; id; id = (ID *)(id->next)) {
+ bAction *act = (bAction *)id;
+ /* XXX This currently creates too many actions.
+ * TODO Need to check if the action is compatible to the given object. */
+ actions.push_back(act);
+ }
+ }
+ else {
+ bAction *action = bc_getSceneObjectAction(ob);
+ actions.push_back(action);
+ }
+
+ return actions;
+}
+
+std::string bc_get_action_id(std::string action_name,
+ std::string ob_name,
+ std::string channel_type,
+ std::string axis_name,
+ std::string axis_separator)
+{
+ std::string result = action_name + "_" + channel_type;
+ if (ob_name.length() > 0) {
+ result = ob_name + "_" + result;
+ }
+ if (axis_name.length() > 0) {
+ result += axis_separator + axis_name;
+ }
+ return translate_id(result);
+}
+
+void bc_update_scene(BlenderContext &blender_context, float ctime)
+{
+ Main *bmain = blender_context.get_main();
+ Scene *scene = blender_context.get_scene();
+ Depsgraph *depsgraph = blender_context.get_depsgraph();
+
+ /* See remark in physics_fluid.c lines 395...) */
+ // BKE_scene_update_for_newframe(ev_context, bmain, scene, scene->lay);
+ BKE_scene_frame_set(scene, ctime);
+ ED_update_for_newframe(bmain, depsgraph);
+}
+
+Object *bc_add_object(Main *bmain, Scene *scene, ViewLayer *view_layer, int type, const char *name)
+{
+ Object *ob = BKE_object_add_only_object(bmain, type, name);
+
+ ob->data = BKE_object_obdata_add_from_type(bmain, type, name);
+ DEG_id_tag_update(&ob->id, ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION);
+
+ LayerCollection *layer_collection = BKE_layer_collection_get_active(view_layer);
+ BKE_collection_object_add(bmain, layer_collection->collection, ob);
+
+ Base *base = BKE_view_layer_base_find(view_layer, ob);
+ /* TODO: is setting active needed? */
+ BKE_view_layer_base_select_and_set_active(view_layer, base);
+
+ return ob;
+}
+
+Mesh *bc_get_mesh_copy(BlenderContext &blender_context,
+ Object *ob,
+ BC_export_mesh_type export_mesh_type,
+ bool apply_modifiers,
+ bool triangulate)
+{
+ CustomData_MeshMasks mask = CD_MASK_MESH;
+ Mesh *tmpmesh = NULL;
+ if (apply_modifiers) {
+#if 0 /* Not supported by new system currently... */
+ switch (export_mesh_type) {
+ case BC_MESH_TYPE_VIEW: {
+ dm = mesh_create_derived_view(depsgraph, scene, ob, &mask);
+ break;
+ }
+ case BC_MESH_TYPE_RENDER: {
+ dm = mesh_create_derived_render(depsgraph, scene, ob, &mask);
+ break;
+ }
+ }
+#else
+ Depsgraph *depsgraph = blender_context.get_depsgraph();
+ Scene *scene_eval = blender_context.get_evaluated_scene();
+ Object *ob_eval = blender_context.get_evaluated_object(ob);
+ tmpmesh = mesh_get_eval_final(depsgraph, scene_eval, ob_eval, &mask);
+#endif
+ }
+ else {
+ tmpmesh = (Mesh *)ob->data;
+ }
+
+ BKE_id_copy_ex(NULL, &tmpmesh->id, (ID **)&tmpmesh, LIB_ID_COPY_LOCALIZE);
+
+ if (triangulate) {
+ bc_triangulate_mesh(tmpmesh);
+ }
+ BKE_mesh_tessface_ensure(tmpmesh);
+ return tmpmesh;
+}
+
+Object *bc_get_assigned_armature(Object *ob)
+{
+ Object *ob_arm = NULL;
+
+ if (ob->parent && ob->partype == PARSKEL && ob->parent->type == OB_ARMATURE) {
+ ob_arm = ob->parent;
+ }
+ else {
+ ModifierData *mod;
+ for (mod = (ModifierData *)ob->modifiers.first; mod; mod = mod->next) {
+ if (mod->type == eModifierType_Armature) {
+ ob_arm = ((ArmatureModifierData *)mod)->object;
+ }
+ }
+ }
+
+ return ob_arm;
+}
+
+bool bc_has_object_type(LinkNode *export_set, short obtype)
+{
+ LinkNode *node;
+
+ for (node = export_set; node; node = node->next) {
+ Object *ob = (Object *)node->link;
+ /* XXX - why is this checking for ob->data? - we could be looking for empties */
+ if (ob->type == obtype && ob->data) {
+ return true;
+ }
+ }
+ return false;
+}
+
+/* Use bubble sort algorithm for sorting the export set */
+void bc_bubble_sort_by_Object_name(LinkNode *export_set)
+{
+ bool sorted = false;
+ LinkNode *node;
+ for (node = export_set; node->next && !sorted; node = node->next) {
+
+ sorted = true;
+
+ LinkNode *current;
+ for (current = export_set; current->next; current = current->next) {
+ Object *a = (Object *)current->link;
+ Object *b = (Object *)current->next->link;
+
+ if (strcmp(a->id.name, b->id.name) > 0) {
+ current->link = b;
+ current->next->link = a;
+ sorted = false;
+ }
+ }
+ }
+}
+
+/* Check if a bone is the top most exportable bone in the bone hierarchy.
+ * When deform_bones_only == false, then only bones with NO parent
+ * can be root bones. Otherwise the top most deform bones in the hierarchy
+ * are root bones.
+ */
+bool bc_is_root_bone(Bone *aBone, bool deform_bones_only)
+{
+ if (deform_bones_only) {
+ Bone *root = NULL;
+ Bone *bone = aBone;
+ while (bone) {
+ if (!(bone->flag & BONE_NO_DEFORM)) {
+ root = bone;
+ }
+ bone = bone->parent;
+ }
+ return (aBone == root);
+ }
+ else {
+ return !(aBone->parent);
+ }
+}
+
+int bc_get_active_UVLayer(Object *ob)
+{
+ Mesh *me = (Mesh *)ob->data;
+ return CustomData_get_active_layer_index(&me->ldata, CD_MLOOPUV);
+}
+
+std::string bc_url_encode(std::string data)
+{
+ /* XXX We probably do not need to do a full encoding.
+ * But in case that is necessary,then it can be added here.
+ */
+ return bc_replace_string(data, "#", "%23");
+}
+
+std::string bc_replace_string(std::string data,
+ const std::string &pattern,
+ const std::string &replacement)
+{
+ size_t pos = 0;
+ while ((pos = data.find(pattern, pos)) != std::string::npos) {
+ data.replace(pos, pattern.length(), replacement);
+ pos += replacement.length();
+ }
+ return data;
+}
+
+/**
+ * Calculate a rescale factor such that the imported scene's scale
+ * is preserved. I.e. 1 meter in the import will also be
+ * 1 meter in the current scene.
+ */
+
+void bc_match_scale(Object *ob, UnitConverter &bc_unit, bool scale_to_scene)
+{
+ if (scale_to_scene) {
+ mul_m4_m4m4(ob->obmat, bc_unit.get_scale(), ob->obmat);
+ }
+ mul_m4_m4m4(ob->obmat, bc_unit.get_rotation(), ob->obmat);
+ BKE_object_apply_mat4(ob, ob->obmat, 0, 0);
+}
+
+void bc_match_scale(std::vector<Object *> *objects_done,
+ UnitConverter &bc_unit,
+ bool scale_to_scene)
+{
+ for (std::vector<Object *>::iterator it = objects_done->begin(); it != objects_done->end();
+ ++it) {
+ Object *ob = *it;
+ if (ob->parent == NULL) {
+ bc_match_scale(*it, bc_unit, scale_to_scene);
+ }
+ }
+}
+
+/*
+ * Convenience function to get only the needed components of a matrix
+ */
+void bc_decompose(float mat[4][4], float *loc, float eul[3], float quat[4], float *size)
+{
+ if (size) {
+ mat4_to_size(size, mat);
+ }
+
+ if (eul) {
+ mat4_to_eul(eul, mat);
+ }
+
+ if (quat) {
+ mat4_to_quat(quat, mat);
+ }
+
+ if (loc) {
+ copy_v3_v3(loc, mat[3]);
+ }
+}
+
+/*
+ * Create rotation_quaternion from a delta rotation and a reference quat
+ *
+ * Input:
+ * mat_from: The rotation matrix before rotation
+ * mat_to : The rotation matrix after rotation
+ * qref : the quat corresponding to mat_from
+ *
+ * Output:
+ * rot : the calculated result (quaternion)
+ */
+void bc_rotate_from_reference_quat(float quat_to[4], float quat_from[4], float mat_to[4][4])
+{
+ float qd[4];
+ float matd[4][4];
+ float mati[4][4];
+ float mat_from[4][4];
+ quat_to_mat4(mat_from, quat_from);
+
+ /* Calculate the difference matrix matd between mat_from and mat_to */
+ invert_m4_m4(mati, mat_from);
+ mul_m4_m4m4(matd, mati, mat_to);
+
+ mat4_to_quat(qd, matd);
+
+ mul_qt_qtqt(quat_to, qd, quat_from); /* rot is the final rotation corresponding to mat_to */
+}
+
+void bc_triangulate_mesh(Mesh *me)
+{
+ bool use_beauty = false;
+ bool tag_only = false;
+
+ /* XXX: The triangulation method selection could be offered in the UI. */
+ int quad_method = MOD_TRIANGULATE_QUAD_SHORTEDGE;
+
+ const struct BMeshCreateParams bm_create_params = {0};
+ BMesh *bm = BM_mesh_create(&bm_mesh_allocsize_default, &bm_create_params);
+ BMeshFromMeshParams bm_from_me_params = {0};
+ bm_from_me_params.calc_face_normal = true;
+ BM_mesh_bm_from_me(bm, me, &bm_from_me_params);
+ BM_mesh_triangulate(bm, quad_method, use_beauty, 4, tag_only, NULL, NULL, NULL);
+
+ BMeshToMeshParams bm_to_me_params = {0};
+ bm_to_me_params.calc_object_remap = false;
+ BM_mesh_bm_to_me(NULL, bm, me, &bm_to_me_params);
+ BM_mesh_free(bm);
+}
+
+/*
+ * A bone is a leaf when it has no children or all children are not connected.
+ */
+bool bc_is_leaf_bone(Bone *bone)
+{
+ for (Bone *child = (Bone *)bone->childbase.first; child; child = child->next) {
+ if (child->flag & BONE_CONNECTED) {
+ return false;
+ }
+ }
+ return true;
+}
+
+EditBone *bc_get_edit_bone(bArmature *armature, char *name)
+{
+ EditBone *eBone;
+
+ for (eBone = (EditBone *)armature->edbo->first; eBone; eBone = eBone->next) {
+ if (STREQ(name, eBone->name)) {
+ return eBone;
+ }
+ }
+
+ return NULL;
+}
+int bc_set_layer(int bitfield, int layer)
+{
+ return bc_set_layer(bitfield, layer, true); /* enable */
+}
+
+int bc_set_layer(int bitfield, int layer, bool enable)
+{
+ int bit = 1u << layer;
+
+ if (enable) {
+ bitfield |= bit;
+ }
+ else {
+ bitfield &= ~bit;
+ }
+
+ return bitfield;
+}
+
+/**
+ * This method creates a new extension map when needed.
+ * \note The ~BoneExtensionManager destructor takes care
+ * to delete the created maps when the manager is removed.
+ */
+BoneExtensionMap &BoneExtensionManager::getExtensionMap(bArmature *armature)
+{
+ std::string key = armature->id.name;
+ BoneExtensionMap *result = extended_bone_maps[key];
+ if (result == NULL) {
+ result = new BoneExtensionMap();
+ extended_bone_maps[key] = result;
+ }
+ return *result;
+}
+
+BoneExtensionManager::~BoneExtensionManager()
+{
+ std::map<std::string, BoneExtensionMap *>::iterator map_it;
+ for (map_it = extended_bone_maps.begin(); map_it != extended_bone_maps.end(); ++map_it) {
+ BoneExtensionMap *extended_bones = map_it->second;
+ for (BoneExtensionMap::iterator ext_it = extended_bones->begin();
+ ext_it != extended_bones->end();
+ ++ext_it) {
+ if (ext_it->second != NULL) {
+ delete ext_it->second;
+ }
+ }
+ extended_bones->clear();
+ delete extended_bones;
+ }
+}
+
+/**
+ * BoneExtended is a helper class needed for the Bone chain finder
+ * See ArmatureImporter::fix_leaf_bones()
+ * and ArmatureImporter::connect_bone_chains()
+ */
+
+BoneExtended::BoneExtended(EditBone *aBone)
+{
+ this->set_name(aBone->name);
+ this->chain_length = 0;
+ this->is_leaf = false;
+ this->tail[0] = 0.0f;
+ this->tail[1] = 0.5f;
+ this->tail[2] = 0.0f;
+ this->use_connect = -1;
+ this->roll = 0;
+ this->bone_layers = 0;
+
+ this->has_custom_tail = false;
+ this->has_custom_roll = false;
+}
+
+char *BoneExtended::get_name()
+{
+ return name;
+}
+
+void BoneExtended::set_name(char *aName)
+{
+ BLI_strncpy(name, aName, MAXBONENAME);
+}
+
+int BoneExtended::get_chain_length()
+{
+ return chain_length;
+}
+
+void BoneExtended::set_chain_length(const int aLength)
+{
+ chain_length = aLength;
+}
+
+void BoneExtended::set_leaf_bone(bool state)
+{
+ is_leaf = state;
+}
+
+bool BoneExtended::is_leaf_bone()
+{
+ return is_leaf;
+}
+
+void BoneExtended::set_roll(float roll)
+{
+ this->roll = roll;
+ this->has_custom_roll = true;
+}
+
+bool BoneExtended::has_roll()
+{
+ return this->has_custom_roll;
+}
+
+float BoneExtended::get_roll()
+{
+ return this->roll;
+}
+
+void BoneExtended::set_tail(float vec[])
+{
+ this->tail[0] = vec[0];
+ this->tail[1] = vec[1];
+ this->tail[2] = vec[2];
+ this->has_custom_tail = true;
+}
+
+bool BoneExtended::has_tail()
+{
+ return this->has_custom_tail;
+}
+
+float *BoneExtended::get_tail()
+{
+ return this->tail;
+}
+
+inline bool isInteger(const std::string &s)
+{
+ if (s.empty() || ((!isdigit(s[0])) && (s[0] != '-') && (s[0] != '+'))) {
+ return false;
+ }
+
+ char *p;
+ strtol(s.c_str(), &p, 10);
+
+ return (*p == 0);
+}
+
+void BoneExtended::set_bone_layers(std::string layerString, std::vector<std::string> &layer_labels)
+{
+ std::stringstream ss(layerString);
+ std::string layer;
+ int pos;
+
+ while (ss >> layer) {
+
+ /* Blender uses numbers to specify layers*/
+ if (isInteger(layer)) {
+ pos = atoi(layer.c_str());
+ if (pos >= 0 && pos < 32) {
+ this->bone_layers = bc_set_layer(this->bone_layers, pos);
+ continue;
+ }
+ }
+
+ /* layer uses labels (not supported by blender). Map to layer numbers:*/
+ pos = find(layer_labels.begin(), layer_labels.end(), layer) - layer_labels.begin();
+ if (pos >= layer_labels.size()) {
+ layer_labels.push_back(layer); /* remember layer number for future usage*/
+ }
+
+ if (pos > 31) {
+ fprintf(stderr,
+ "Too many layers in Import. Layer %s mapped to Blender layer 31\n",
+ layer.c_str());
+ pos = 31;
+ }
+
+ /* If numeric layers and labeled layers are used in parallel (unlikely),
+ * we get a potential mixup. Just leave as is for now.
+ */
+ this->bone_layers = bc_set_layer(this->bone_layers, pos);
+ }
+}
+
+std::string BoneExtended::get_bone_layers(int bitfield)
+{
+ std::string result = "";
+ std::string sep = "";
+ int bit = 1u;
+
+ std::ostringstream ss;
+ for (int i = 0; i < 32; i++) {
+ if (bit & bitfield) {
+ ss << sep << i;
+ sep = " ";
+ }
+ bit = bit << 1;
+ }
+ return ss.str();
+}
+
+int BoneExtended::get_bone_layers()
+{
+ /* ensure that the bone is in at least one bone layer! */
+ return (bone_layers == 0) ? 1 : bone_layers;
+}
+
+void BoneExtended::set_use_connect(int use_connect)
+{
+ this->use_connect = use_connect;
+}
+
+int BoneExtended::get_use_connect()
+{
+ return this->use_connect;
+}
+
+/**
+ * Stores a 4*4 matrix as a custom bone property array of size 16
+ */
+void bc_set_IDPropertyMatrix(EditBone *ebone, const char *key, float mat[4][4])
+{
+ IDProperty *idgroup = (IDProperty *)ebone->prop;
+ if (idgroup == NULL) {
+ IDPropertyTemplate val = {0};
+ idgroup = IDP_New(IDP_GROUP, &val, "RNA_EditBone ID properties");
+ ebone->prop = idgroup;
+ }
+
+ IDPropertyTemplate val = {0};
+ val.array.len = 16;
+ val.array.type = IDP_FLOAT;
+
+ IDProperty *data = IDP_New(IDP_ARRAY, &val, key);
+ float *array = (float *)IDP_Array(data);
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ array[4 * i + j] = mat[i][j];
+ }
+ }
+
+ IDP_AddToGroup(idgroup, data);
+}
+
+#if 0
+/**
+ * Stores a Float value as a custom bone property
+ *
+ * Note: This function is currently not needed. Keep for future usage
+ */
+static void bc_set_IDProperty(EditBone *ebone, const char *key, float value)
+{
+ if (ebone->prop == NULL) {
+ IDPropertyTemplate val = {0};
+ ebone->prop = IDP_New(IDP_GROUP, &val, "RNA_EditBone ID properties");
+ }
+
+ IDProperty *pgroup = (IDProperty *)ebone->prop;
+ IDPropertyTemplate val = {0};
+ IDProperty *prop = IDP_New(IDP_FLOAT, &val, key);
+ IDP_Float(prop) = value;
+ IDP_AddToGroup(pgroup, prop);
+}
+#endif
+
+/**
+ * Get a custom property when it exists.
+ * This function is also used to check if a property exists.
+ */
+IDProperty *bc_get_IDProperty(Bone *bone, std::string key)
+{
+ return (bone->prop == NULL) ? NULL : IDP_GetPropertyFromGroup(bone->prop, key.c_str());
+}
+
+/**
+ * Read a custom bone property and convert to float
+ * Return def if the property does not exist.
+ */
+float bc_get_property(Bone *bone, std::string key, float def)
+{
+ float result = def;
+ IDProperty *property = bc_get_IDProperty(bone, key);
+ if (property) {
+ switch (property->type) {
+ case IDP_INT:
+ result = (float)(IDP_Int(property));
+ break;
+ case IDP_FLOAT:
+ result = (float)(IDP_Float(property));
+ break;
+ case IDP_DOUBLE:
+ result = (float)(IDP_Double(property));
+ break;
+ default:
+ result = def;
+ }
+ }
+ return result;
+}
+
+/**
+ * Read a custom bone property and convert to matrix
+ * Return true if conversion was successful
+ *
+ * Return false if:
+ * - the property does not exist
+ * - is not an array of size 16
+ */
+bool bc_get_property_matrix(Bone *bone, std::string key, float mat[4][4])
+{
+ IDProperty *property = bc_get_IDProperty(bone, key);
+ if (property && property->type == IDP_ARRAY && property->len == 16) {
+ float *array = (float *)IDP_Array(property);
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ mat[i][j] = array[4 * i + j];
+ }
+ }
+ return true;
+ }
+ return false;
+}
+
+/**
+ * get a vector that is stored in 3 custom properties (used in Blender <= 2.78)
+ */
+void bc_get_property_vector(Bone *bone, std::string key, float val[3], const float def[3])
+{
+ val[0] = bc_get_property(bone, key + "_x", def[0]);
+ val[1] = bc_get_property(bone, key + "_y", def[1]);
+ val[2] = bc_get_property(bone, key + "_z", def[2]);
+}
+
+/**
+ * Check if vector exist stored in 3 custom properties (used in Blender <= 2.78)
+ */
+static bool has_custom_props(Bone *bone, bool enabled, std::string key)
+{
+ if (!enabled) {
+ return false;
+ }
+
+ return (bc_get_IDProperty(bone, key + "_x") || bc_get_IDProperty(bone, key + "_y") ||
+ bc_get_IDProperty(bone, key + "_z"));
+}
+
+void bc_enable_fcurves(bAction *act, char *bone_name)
+{
+ FCurve *fcu;
+ char prefix[200];
+
+ if (bone_name) {
+ BLI_snprintf(prefix, sizeof(prefix), "pose.bones[\"%s\"]", bone_name);
+ }
+
+ for (fcu = (FCurve *)act->curves.first; fcu; fcu = fcu->next) {
+ if (bone_name) {
+ if (STREQLEN(fcu->rna_path, prefix, strlen(prefix))) {
+ fcu->flag &= ~FCURVE_DISABLED;
+ }
+ else {
+ fcu->flag |= FCURVE_DISABLED;
+ }
+ }
+ else {
+ fcu->flag &= ~FCURVE_DISABLED;
+ }
+ }
+}
+
+bool bc_bone_matrix_local_get(Object *ob, Bone *bone, Matrix &mat, bool for_opensim)
+{
+
+ /* Ok, lets be super cautious and check if the bone exists */
+ bPose *pose = ob->pose;
+ bPoseChannel *pchan = BKE_pose_channel_find_name(pose, bone->name);
+ if (!pchan) {
+ return false;
+ }
+
+ bAction *action = bc_getSceneObjectAction(ob);
+ bPoseChannel *parchan = pchan->parent;
+
+ bc_enable_fcurves(action, bone->name);
+ float ipar[4][4];
+
+ if (bone->parent) {
+ invert_m4_m4(ipar, parchan->pose_mat);
+ mul_m4_m4m4(mat, ipar, pchan->pose_mat);
+ }
+ else {
+ copy_m4_m4(mat, pchan->pose_mat);
+ }
+
+ /* OPEN_SIM_COMPATIBILITY
+ * AFAIK animation to second life is via BVH, but no
+ * reason to not have the collada-animation be correct */
+ if (for_opensim) {
+ float temp[4][4];
+ copy_m4_m4(temp, bone->arm_mat);
+ temp[3][0] = temp[3][1] = temp[3][2] = 0.0f;
+ invert_m4(temp);
+
+ mul_m4_m4m4(mat, mat, temp);
+
+ if (bone->parent) {
+ copy_m4_m4(temp, bone->parent->arm_mat);
+ temp[3][0] = temp[3][1] = temp[3][2] = 0.0f;
+
+ mul_m4_m4m4(mat, temp, mat);
+ }
+ }
+ bc_enable_fcurves(action, NULL);
+ return true;
+}
+
+bool bc_is_animated(BCMatrixSampleMap &values)
+{
+ static float MIN_DISTANCE = 0.00001;
+
+ if (values.size() < 2) {
+ return false; /* need at least 2 entries to be not flat */
+ }
+
+ BCMatrixSampleMap::iterator it;
+ const BCMatrix *refmat = NULL;
+ for (it = values.begin(); it != values.end(); ++it) {
+ const BCMatrix *matrix = it->second;
+
+ if (refmat == NULL) {
+ refmat = matrix;
+ continue;
+ }
+
+ if (!matrix->in_range(*refmat, MIN_DISTANCE)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool bc_has_animations(Object *ob)
+{
+ /* Check for object, light and camera transform animations */
+ if ((bc_getSceneObjectAction(ob) && bc_getSceneObjectAction(ob)->curves.first) ||
+ (bc_getSceneLightAction(ob) && bc_getSceneLightAction(ob)->curves.first) ||
+ (bc_getSceneCameraAction(ob) && bc_getSceneCameraAction(ob)->curves.first)) {
+ return true;
+ }
+
+ /* Check Material Effect parameter animations. */
+ for (int a = 0; a < ob->totcol; a++) {
+ Material *ma = BKE_object_material_get(ob, a + 1);
+ if (!ma) {
+ continue;
+ }
+ if (ma->adt && ma->adt->action && ma->adt->action->curves.first) {
+ return true;
+ }
+ }
+
+ Key *key = BKE_key_from_object(ob);
+ if ((key && key->adt && key->adt->action) && key->adt->action->curves.first) {
+ return true;
+ }
+
+ return false;
+}
+
+bool bc_has_animations(Scene *sce, LinkNode *export_set)
+{
+ LinkNode *node;
+ if (export_set) {
+ for (node = export_set; node; node = node->next) {
+ Object *ob = (Object *)node->link;
+
+ if (bc_has_animations(ob)) {
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+void bc_add_global_transform(Matrix &to_mat,
+ const Matrix &from_mat,
+ const BCMatrix &global_transform,
+ const bool invert)
+{
+ copy_m4_m4(to_mat, from_mat);
+ bc_add_global_transform(to_mat, global_transform, invert);
+}
+
+void bc_add_global_transform(Vector &to_vec,
+ const Vector &from_vec,
+ const BCMatrix &global_transform,
+ const bool invert)
+{
+ copy_v3_v3(to_vec, from_vec);
+ bc_add_global_transform(to_vec, global_transform, invert);
+}
+
+void bc_add_global_transform(Matrix &to_mat, const BCMatrix &global_transform, const bool invert)
+{
+ BCMatrix mat(to_mat);
+ mat.add_transform(global_transform, invert);
+ mat.get_matrix(to_mat);
+}
+
+void bc_add_global_transform(Vector &to_vec, const BCMatrix &global_transform, const bool invert)
+{
+ Matrix mat;
+ Vector from_vec;
+ copy_v3_v3(from_vec, to_vec);
+ global_transform.get_matrix(mat, false, 6, invert);
+ mul_v3_m4v3(to_vec, mat, from_vec);
+}
+
+void bc_apply_global_transform(Matrix &to_mat, const BCMatrix &global_transform, const bool invert)
+{
+ BCMatrix mat(to_mat);
+ mat.apply_transform(global_transform, invert);
+ mat.get_matrix(to_mat);
+}
+
+void bc_apply_global_transform(Vector &to_vec, const BCMatrix &global_transform, const bool invert)
+{
+ Matrix transform;
+ global_transform.get_matrix(transform);
+ mul_v3_m4v3(to_vec, transform, to_vec);
+}
+
+/**
+ * Check if custom information about bind matrix exists and modify the from_mat
+ * accordingly.
+ *
+ * Note: This is old style for Blender <= 2.78 only kept for compatibility
+ */
+void bc_create_restpose_mat(BCExportSettings &export_settings,
+ Bone *bone,
+ float to_mat[4][4],
+ float from_mat[4][4],
+ bool use_local_space)
+{
+ float loc[3];
+ float rot[3];
+ float scale[3];
+ static const float V0[3] = {0, 0, 0};
+
+ if (!has_custom_props(bone, export_settings.get_keep_bind_info(), "restpose_loc") &&
+ !has_custom_props(bone, export_settings.get_keep_bind_info(), "restpose_rot") &&
+ !has_custom_props(bone, export_settings.get_keep_bind_info(), "restpose_scale")) {
+ /* No need */
+ copy_m4_m4(to_mat, from_mat);
+ return;
+ }
+
+ bc_decompose(from_mat, loc, rot, NULL, scale);
+ loc_eulO_size_to_mat4(to_mat, loc, rot, scale, 6);
+
+ if (export_settings.get_keep_bind_info()) {
+ bc_get_property_vector(bone, "restpose_loc", loc, loc);
+
+ if (use_local_space && bone->parent) {
+ Bone *b = bone;
+ while (b->parent) {
+ b = b->parent;
+ float ploc[3];
+ bc_get_property_vector(b, "restpose_loc", ploc, V0);
+ loc[0] += ploc[0];
+ loc[1] += ploc[1];
+ loc[2] += ploc[2];
+ }
+ }
+ }
+
+ if (export_settings.get_keep_bind_info()) {
+ if (bc_get_IDProperty(bone, "restpose_rot_x")) {
+ rot[0] = DEG2RADF(bc_get_property(bone, "restpose_rot_x", 0));
+ }
+ if (bc_get_IDProperty(bone, "restpose_rot_y")) {
+ rot[1] = DEG2RADF(bc_get_property(bone, "restpose_rot_y", 0));
+ }
+ if (bc_get_IDProperty(bone, "restpose_rot_z")) {
+ rot[2] = DEG2RADF(bc_get_property(bone, "restpose_rot_z", 0));
+ }
+ }
+
+ if (export_settings.get_keep_bind_info()) {
+ bc_get_property_vector(bone, "restpose_scale", scale, scale);
+ }
+
+ loc_eulO_size_to_mat4(to_mat, loc, rot, scale, 6);
+}
+
+void bc_sanitize_v3(float v[3], int precision)
+{
+ for (int i = 0; i < 3; i++) {
+ double val = (double)v[i];
+ val = double_round(val, precision);
+ v[i] = (float)val;
+ }
+}
+
+void bc_sanitize_v3(double v[3], int precision)
+{
+ for (int i = 0; i < 3; i++) {
+ v[i] = double_round(v[i], precision);
+ }
+}
+
+void bc_copy_m4_farray(float r[4][4], float *a)
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ r[i][j] = *a++;
+ }
+ }
+}
+
+void bc_copy_farray_m4(float *r, float a[4][4])
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ *r++ = a[i][j];
+ }
+ }
+}
+
+void bc_copy_darray_m4d(double *r, double a[4][4])
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ *r++ = a[i][j];
+ }
+ }
+}
+
+void bc_copy_v44_m4d(std::vector<std::vector<double>> &r, double (&a)[4][4])
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ r[i][j] = a[i][j];
+ }
+ }
+}
+
+void bc_copy_m4d_v44(double (&r)[4][4], std::vector<std::vector<double>> &a)
+{
+ for (int i = 0; i < 4; i++) {
+ for (int j = 0; j < 4; j++) {
+ r[i][j] = a[i][j];
+ }
+ }
+}
+
+/**
+ * Returns name of Active UV Layer or empty String if no active UV Layer defined
+ */
+static std::string bc_get_active_uvlayer_name(Mesh *me)
+{
+ int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV);
+ if (num_layers) {
+ char *layer_name = bc_CustomData_get_active_layer_name(&me->ldata, CD_MLOOPUV);
+ if (layer_name) {
+ return std::string(layer_name);
+ }
+ }
+ return "";
+}
+
+/**
+ * Returns name of Active UV Layer or empty String if no active UV Layer defined.
+ * Assuming the Object is of type MESH
+ */
+static std::string bc_get_active_uvlayer_name(Object *ob)
+{
+ Mesh *me = (Mesh *)ob->data;
+ return bc_get_active_uvlayer_name(me);
+}
+
+/**
+ * Returns UV Layer name or empty string if layer index is out of range
+ */
+static std::string bc_get_uvlayer_name(Mesh *me, int layer)
+{
+ int num_layers = CustomData_number_of_layers(&me->ldata, CD_MLOOPUV);
+ if (num_layers && layer < num_layers) {
+ char *layer_name = bc_CustomData_get_layer_name(&me->ldata, CD_MLOOPUV, layer);
+ if (layer_name) {
+ return std::string(layer_name);
+ }
+ }
+ return "";
+}
+
+std::string bc_find_bonename_in_path(std::string path, std::string probe)
+{
+ std::string result;
+ char *boneName = BLI_str_quoted_substrN(path.c_str(), probe.c_str());
+ if (boneName) {
+ result = std::string(boneName);
+ MEM_freeN(boneName);
+ }
+ return result;
+}
+
+static bNodeTree *prepare_material_nodetree(Material *ma)
+{
+ if (ma->nodetree == NULL) {
+ ma->nodetree = ntreeAddTree(NULL, "Shader Nodetree", "ShaderNodeTree");
+ ma->use_nodes = true;
+ }
+ return ma->nodetree;
+}
+
+static bNode *bc_add_node(
+ bContext *C, bNodeTree *ntree, int node_type, int locx, int locy, std::string label)
+{
+ bNode *node = nodeAddStaticNode(C, ntree, node_type);
+ if (node) {
+ if (label.length() > 0) {
+ strcpy(node->label, label.c_str());
+ }
+ node->locx = locx;
+ node->locy = locy;
+ node->flag |= NODE_SELECT;
+ }
+ return node;
+}
+
+static bNode *bc_add_node(bContext *C, bNodeTree *ntree, int node_type, int locx, int locy)
+{
+ return bc_add_node(C, ntree, node_type, locx, locy, "");
+}
+
+#if 0
+/* experimental, probably not used */
+static bNodeSocket *bc_group_add_input_socket(bNodeTree *ntree,
+ bNode *to_node,
+ int to_index,
+ std::string label)
+{
+ bNodeSocket *to_socket = (bNodeSocket *)BLI_findlink(&to_node->inputs, to_index);
+
+ //bNodeSocket *socket = ntreeAddSocketInterfaceFromSocket(ntree, to_node, to_socket);
+ //return socket;
+
+ bNodeSocket *gsock = ntreeAddSocketInterfaceFromSocket(ntree, to_node, to_socket);
+ bNode *inputGroup = ntreeFindType(ntree, NODE_GROUP_INPUT);
+ node_group_input_verify(ntree, inputGroup, (ID *)ntree);
+ bNodeSocket *newsock = node_group_input_find_socket(inputGroup, gsock->identifier);
+ nodeAddLink(ntree, inputGroup, newsock, to_node, to_socket);
+ strcpy(newsock->name, label.c_str());
+ return newsock;
+}
+
+static bNodeSocket *bc_group_add_output_socket(bNodeTree *ntree,
+ bNode *from_node,
+ int from_index,
+ std::string label)
+{
+ bNodeSocket *from_socket = (bNodeSocket *)BLI_findlink(&from_node->outputs, from_index);
+
+ //bNodeSocket *socket = ntreeAddSocketInterfaceFromSocket(ntree, to_node, to_socket);
+ //return socket;
+
+ bNodeSocket *gsock = ntreeAddSocketInterfaceFromSocket(ntree, from_node, from_socket);
+ bNode *outputGroup = ntreeFindType(ntree, NODE_GROUP_OUTPUT);
+ node_group_output_verify(ntree, outputGroup, (ID *)ntree);
+ bNodeSocket *newsock = node_group_output_find_socket(outputGroup, gsock->identifier);
+ nodeAddLink(ntree, from_node, from_socket, outputGroup, newsock);
+ strcpy(newsock->name, label.c_str());
+ return newsock;
+}
+
+void bc_make_group(bContext *C, bNodeTree *ntree, std::map<std::string, bNode *> nmap)
+{
+ bNode *gnode = node_group_make_from_selected(C, ntree, "ShaderNodeGroup", "ShaderNodeTree");
+ bNodeTree *gtree = (bNodeTree *)gnode->id;
+
+ bc_group_add_input_socket(gtree, nmap["main"], 0, "Diffuse");
+ bc_group_add_input_socket(gtree, nmap["emission"], 0, "Emission");
+ bc_group_add_input_socket(gtree, nmap["mix"], 0, "Transparency");
+ bc_group_add_input_socket(gtree, nmap["emission"], 1, "Emission");
+ bc_group_add_input_socket(gtree, nmap["main"], 4, "Metallic");
+ bc_group_add_input_socket(gtree, nmap["main"], 5, "Specular");
+
+ bc_group_add_output_socket(gtree, nmap["mix"], 0, "Shader");
+}
+#endif
+
+static void bc_node_add_link(
+ bNodeTree *ntree, bNode *from_node, int from_index, bNode *to_node, int to_index)
+{
+ bNodeSocket *from_socket = (bNodeSocket *)BLI_findlink(&from_node->outputs, from_index);
+ bNodeSocket *to_socket = (bNodeSocket *)BLI_findlink(&to_node->inputs, to_index);
+
+ nodeAddLink(ntree, from_node, from_socket, to_node, to_socket);
+}
+
+void bc_add_default_shader(bContext *C, Material *ma)
+{
+ bNodeTree *ntree = prepare_material_nodetree(ma);
+ std::map<std::string, bNode *> nmap;
+#if 0
+ nmap["main"] = bc_add_node(C, ntree, SH_NODE_BSDF_PRINCIPLED, -300, 300);
+ nmap["emission"] = bc_add_node(C, ntree, SH_NODE_EMISSION, -300, 500, "emission");
+ nmap["add"] = bc_add_node(C, ntree, SH_NODE_ADD_SHADER, 100, 400);
+ nmap["transparent"] = bc_add_node(C, ntree, SH_NODE_BSDF_TRANSPARENT, 100, 200);
+ nmap["mix"] = bc_add_node(C, ntree, SH_NODE_MIX_SHADER, 400, 300, "transparency");
+ nmap["out"] = bc_add_node(C, ntree, SH_NODE_OUTPUT_MATERIAL, 600, 300);
+ nmap["out"]->flag &= ~NODE_SELECT;
+
+ bc_node_add_link(ntree, nmap["emission"], 0, nmap["add"], 0);
+ bc_node_add_link(ntree, nmap["main"], 0, nmap["add"], 1);
+ bc_node_add_link(ntree, nmap["add"], 0, nmap["mix"], 1);
+ bc_node_add_link(ntree, nmap["transparent"], 0, nmap["mix"], 2);
+
+ bc_node_add_link(ntree, nmap["mix"], 0, nmap["out"], 0);
+ /* experimental, probably not used. */
+ bc_make_group(C, ntree, nmap);
+#else
+ nmap["main"] = bc_add_node(C, ntree, SH_NODE_BSDF_PRINCIPLED, 0, 300);
+ nmap["out"] = bc_add_node(C, ntree, SH_NODE_OUTPUT_MATERIAL, 300, 300);
+ bc_node_add_link(ntree, nmap["main"], 0, nmap["out"], 0);
+#endif
+}
+
+COLLADASW::ColorOrTexture bc_get_base_color(Material *ma)
+{
+ /* for alpha see bc_get_alpha() */
+ Color default_color = {ma->r, ma->g, ma->b, 1.0};
+ bNode *shader = bc_get_master_shader(ma);
+ if (ma->use_nodes && shader) {
+ return bc_get_cot_from_shader(shader, "Base Color", default_color, false);
+ }
+ else {
+ return bc_get_cot(default_color);
+ }
+}
+
+COLLADASW::ColorOrTexture bc_get_emission(Material *ma)
+{
+ Color default_color = {0, 0, 0, 1};
+ bNode *shader = bc_get_master_shader(ma);
+ if (ma->use_nodes && shader) {
+ return bc_get_cot_from_shader(shader, "Emission", default_color);
+ }
+ else {
+ return bc_get_cot(default_color); /* default black */
+ }
+}
+
+COLLADASW::ColorOrTexture bc_get_ambient(Material *ma)
+{
+ Color default_color = {0, 0, 0, 1.0};
+ return bc_get_cot(default_color);
+}
+
+COLLADASW::ColorOrTexture bc_get_specular(Material *ma)
+{
+ Color default_color = {0, 0, 0, 1.0};
+ return bc_get_cot(default_color);
+}
+
+COLLADASW::ColorOrTexture bc_get_reflective(Material *ma)
+{
+ Color default_color = {0, 0, 0, 1.0};
+ return bc_get_cot(default_color);
+}
+
+double bc_get_alpha(Material *ma)
+{
+ double alpha = ma->a; /* fallback if no socket found */
+ bNode *master_shader = bc_get_master_shader(ma);
+ if (ma->use_nodes && master_shader) {
+ bc_get_float_from_shader(master_shader, alpha, "Alpha");
+ }
+ return alpha;
+}
+
+double bc_get_ior(Material *ma)
+{
+ double ior = -1; /* fallback if no socket found */
+ bNode *master_shader = bc_get_master_shader(ma);
+ if (ma->use_nodes && master_shader) {
+ bc_get_float_from_shader(master_shader, ior, "IOR");
+ }
+ return ior;
+}
+
+double bc_get_shininess(Material *ma)
+{
+ double ior = -1; /* fallback if no socket found */
+ bNode *master_shader = bc_get_master_shader(ma);
+ if (ma->use_nodes && master_shader) {
+ bc_get_float_from_shader(master_shader, ior, "Roughness");
+ }
+ return ior;
+}
+
+double bc_get_reflectivity(Material *ma)
+{
+ double reflectivity = ma->spec; /* fallback if no socket found */
+ bNode *master_shader = bc_get_master_shader(ma);
+ if (ma->use_nodes && master_shader) {
+ bc_get_float_from_shader(master_shader, reflectivity, "Metallic");
+ }
+ return reflectivity;
+}
+
+double bc_get_float_from_shader(bNode *shader, double &val, std::string nodeid)
+{
+ bNodeSocket *socket = nodeFindSocket(shader, SOCK_IN, nodeid.c_str());
+ if (socket) {
+ bNodeSocketValueFloat *ref = (bNodeSocketValueFloat *)socket->default_value;
+ val = (double)ref->value;
+ return true;
+ }
+ return false;
+}
+
+COLLADASW::ColorOrTexture bc_get_cot_from_shader(bNode *shader,
+ std::string nodeid,
+ Color &default_color,
+ bool with_alpha)
+{
+ bNodeSocket *socket = nodeFindSocket(shader, SOCK_IN, nodeid.c_str());
+ if (socket) {
+ bNodeSocketValueRGBA *dcol = (bNodeSocketValueRGBA *)socket->default_value;
+ float *col = dcol->value;
+ return bc_get_cot(col, with_alpha);
+ }
+ else {
+ return bc_get_cot(default_color, with_alpha);
+ }
+}
+
+bNode *bc_get_master_shader(Material *ma)
+{
+ bNodeTree *nodetree = ma->nodetree;
+ if (nodetree) {
+ for (bNode *node = (bNode *)nodetree->nodes.first; node; node = node->next) {
+ if (node->typeinfo->type == SH_NODE_BSDF_PRINCIPLED) {
+ return node;
+ }
+ }
+ }
+ return NULL;
+}
+
+COLLADASW::ColorOrTexture bc_get_cot(float r, float g, float b, float a)
+{
+ COLLADASW::Color color(r, g, b, a);
+ COLLADASW::ColorOrTexture cot(color);
+ return cot;
+}
+
+COLLADASW::ColorOrTexture bc_get_cot(Color col, bool with_alpha)
+{
+ COLLADASW::Color color(col[0], col[1], col[2], (with_alpha) ? col[3] : 1.0);
+ COLLADASW::ColorOrTexture cot(color);
+ return cot;
+}
diff --git a/source/blender/io/collada/collada_utils.h b/source/blender/io/collada/collada_utils.h
new file mode 100644
index 00000000000..5c5e1415422
--- /dev/null
+++ b/source/blender/io/collada/collada_utils.h
@@ -0,0 +1,399 @@
+/*
+ * 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.
+ */
+
+/** \file
+ * \ingroup collada
+ */
+
+#ifndef __COLLADA_UTILS_H__
+#define __COLLADA_UTILS_H__
+
+#include "COLLADAFWMeshPrimitive.h"
+#include "COLLADAFWGeometry.h"
+#include "COLLADAFWFloatOrDoubleArray.h"
+#include "COLLADAFWTypes.h"
+#include "COLLADASWEffectProfile.h"
+#include "COLLADAFWColorOrTexture.h"
+
+#include <vector>
+#include <map>
+#include <set>
+#include <algorithm>
+
+extern "C" {
+#include "DNA_object_types.h"
+#include "DNA_anim_types.h"
+#include "DNA_constraint_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_light_types.h"
+#include "DNA_camera_types.h"
+
+#include "DNA_customdata_types.h"
+#include "DNA_texture_types.h"
+#include "DNA_scene_types.h"
+
+#include "RNA_access.h"
+
+#include "BLI_linklist.h"
+#include "BLI_utildefines.h"
+#include "BLI_string.h"
+
+#include "BKE_main.h"
+#include "BKE_context.h"
+#include "BKE_object.h"
+#include "BKE_scene.h"
+#include "BKE_idprop.h"
+#include "BKE_node.h"
+}
+
+#include "DEG_depsgraph_query.h"
+
+#include "ImportSettings.h"
+#include "ExportSettings.h"
+#include "collada_internal.h"
+#include "BCSampleData.h"
+#include "BlenderContext.h"
+
+constexpr int LIMITTED_PRECISION = 6;
+
+typedef std::map<COLLADAFW::UniqueId, Image *> UidImageMap;
+typedef std::map<std::string, Image *> KeyImageMap;
+typedef std::map<COLLADAFW::TextureMapId, std::vector<MTex *>> TexIndexTextureArrayMap;
+typedef std::set<Object *> BCObjectSet;
+
+extern void bc_update_scene(BlenderContext &blender_context, float ctime);
+
+/* Action helpers */
+
+std::vector<bAction *> bc_getSceneActions(const bContext *C, Object *ob, bool all_actions);
+
+/* Action helpers */
+
+inline bAction *bc_getSceneObjectAction(Object *ob)
+{
+ return (ob->adt && ob->adt->action) ? ob->adt->action : NULL;
+}
+
+/* Returns Light Action or NULL */
+inline bAction *bc_getSceneLightAction(Object *ob)
+{
+ if (ob->type != OB_LAMP) {
+ return NULL;
+ }
+
+ Light *lamp = (Light *)ob->data;
+ return (lamp->adt && lamp->adt->action) ? lamp->adt->action : NULL;
+}
+
+/* Return Camera Action or NULL */
+inline bAction *bc_getSceneCameraAction(Object *ob)
+{
+ if (ob->type != OB_CAMERA) {
+ return NULL;
+ }
+
+ Camera *camera = (Camera *)ob->data;
+ return (camera->adt && camera->adt->action) ? camera->adt->action : NULL;
+}
+
+/* returns material action or NULL */
+inline bAction *bc_getSceneMaterialAction(Material *ma)
+{
+ if (ma == NULL) {
+ return NULL;
+ }
+
+ return (ma->adt && ma->adt->action) ? ma->adt->action : NULL;
+}
+
+std::string bc_get_action_id(std::string action_name,
+ std::string ob_name,
+ std::string channel_type,
+ std::string axis_name,
+ std::string axis_separator = "_");
+
+extern float bc_get_float_value(const COLLADAFW::FloatOrDoubleArray &array, unsigned int index);
+extern int bc_test_parent_loop(Object *par, Object *ob);
+
+extern bool bc_validateConstraints(bConstraint *con);
+
+bool bc_set_parent(Object *ob, Object *par, bContext *C, bool is_parent_space = true);
+extern Object *bc_add_object(
+ Main *bmain, Scene *scene, ViewLayer *view_layer, int type, const char *name);
+extern Mesh *bc_get_mesh_copy(BlenderContext &blender_context,
+ Object *ob,
+ BC_export_mesh_type export_mesh_type,
+ bool apply_modifiers,
+ bool triangulate);
+
+extern Object *bc_get_assigned_armature(Object *ob);
+extern bool bc_has_object_type(LinkNode *export_set, short obtype);
+
+extern char *bc_CustomData_get_layer_name(const CustomData *data, int type, int n);
+extern char *bc_CustomData_get_active_layer_name(const CustomData *data, int type);
+
+extern void bc_bubble_sort_by_Object_name(LinkNode *export_set);
+extern bool bc_is_root_bone(Bone *aBone, bool deform_bones_only);
+extern int bc_get_active_UVLayer(Object *ob);
+
+std::string bc_find_bonename_in_path(std::string path, std::string probe);
+
+inline std::string bc_string_after(const std::string &s, const std::string probe)
+{
+ size_t i = s.rfind(probe);
+ if (i != std::string::npos) {
+ return (s.substr(i + probe.length(), s.length() - i));
+ }
+ return (s);
+}
+
+inline std::string bc_string_before(const std::string &s, const std::string probe)
+{
+ size_t i = s.find(probe);
+ if (i != std::string::npos) {
+ return s.substr(0, i);
+ }
+ return (s);
+}
+
+inline bool bc_startswith(std::string const &value, std::string const &starting)
+{
+ if (starting.size() > value.size()) {
+ return false;
+ }
+ return (value.substr(0, starting.size()) == starting);
+}
+
+inline bool bc_endswith(const std::string &value, const std::string &ending)
+{
+ if (ending.size() > value.size()) {
+ return false;
+ }
+
+ return value.compare(value.size() - ending.size(), ending.size(), ending) == 0;
+}
+
+#if 0 /* UNUSED */
+inline bool bc_endswith(std::string const &value, std::string const &ending)
+{
+ if (ending.size() > value.size()) {
+ return false;
+ }
+ return std::equal(ending.rbegin(), ending.rend(), value.rbegin());
+}
+#endif
+
+extern std::string bc_replace_string(std::string data,
+ const std::string &pattern,
+ const std::string &replacement);
+extern std::string bc_url_encode(std::string data);
+extern void bc_match_scale(Object *ob, UnitConverter &bc_unit, bool scale_to_scene);
+extern void bc_match_scale(std::vector<Object *> *objects_done,
+ UnitConverter &unit_converter,
+ bool scale_to_scene);
+
+extern void bc_decompose(float mat[4][4], float *loc, float eul[3], float quat[4], float *size);
+extern void bc_rotate_from_reference_quat(float quat_to[4],
+ float quat_from[4],
+ float mat_to[4][4]);
+
+extern void bc_triangulate_mesh(Mesh *me);
+extern bool bc_is_leaf_bone(Bone *bone);
+extern EditBone *bc_get_edit_bone(bArmature *armature, char *name);
+extern int bc_set_layer(int bitfield, int layer, bool enable);
+extern int bc_set_layer(int bitfield, int layer);
+
+inline bool bc_in_range(float a, float b, float range)
+{
+ return fabsf(a - b) < range;
+}
+void bc_copy_m4_farray(float r[4][4], float *a);
+void bc_copy_farray_m4(float *r, float a[4][4]);
+void bc_copy_darray_m4d(double *r, double a[4][4]);
+void bc_copy_m4d_v44(double (&r)[4][4], std::vector<std::vector<double>> &a);
+void bc_copy_v44_m4d(std::vector<std::vector<double>> &a, double (&r)[4][4]);
+
+void bc_sanitize_v3(double v[3], int precision);
+void bc_sanitize_v3(float v[3], int precision);
+
+extern IDProperty *bc_get_IDProperty(Bone *bone, std::string key);
+extern void bc_set_IDProperty(EditBone *ebone, const char *key, float value);
+extern void bc_set_IDPropertyMatrix(EditBone *ebone, const char *key, float mat[4][4]);
+
+extern float bc_get_property(Bone *bone, std::string key, float def);
+extern void bc_get_property_vector(Bone *bone, std::string key, float val[3], const float def[3]);
+extern bool bc_get_property_matrix(Bone *bone, std::string key, float mat[4][4]);
+
+extern void bc_enable_fcurves(bAction *act, char *bone_name);
+extern bool bc_bone_matrix_local_get(Object *ob, Bone *bone, Matrix &mat, bool for_opensim);
+extern bool bc_is_animated(BCMatrixSampleMap &values);
+extern bool bc_has_animations(Scene *sce, LinkNode *node);
+extern bool bc_has_animations(Object *ob);
+
+extern void bc_add_global_transform(Matrix &to_mat,
+ const Matrix &from_mat,
+ const BCMatrix &global_transform,
+ const bool invert = false);
+extern void bc_add_global_transform(Vector &to_vec,
+ const Vector &from_vec,
+ const BCMatrix &global_transform,
+ const bool invert = false);
+extern void bc_add_global_transform(Vector &to_vec,
+ const BCMatrix &global_transform,
+ const bool invert = false);
+extern void bc_add_global_transform(Matrix &to_mat,
+ const BCMatrix &global_transform,
+ const bool invert = false);
+extern void bc_apply_global_transform(Matrix &to_mat,
+ const BCMatrix &global_transform,
+ const bool invert = false);
+extern void bc_apply_global_transform(Vector &to_vec,
+ const BCMatrix &global_transform,
+ const bool invert = false);
+extern void bc_create_restpose_mat(BCExportSettings &export_settings,
+ Bone *bone,
+ float to_mat[4][4],
+ float from_mat[4][4],
+ bool use_local_space);
+
+class ColladaBaseNodes {
+ private:
+ std::vector<Object *> base_objects;
+
+ public:
+ void add(Object *ob)
+ {
+ base_objects.push_back(ob);
+ }
+
+ bool contains(Object *ob)
+ {
+ std::vector<Object *>::iterator it = std::find(base_objects.begin(), base_objects.end(), ob);
+ return (it != base_objects.end());
+ }
+
+ int size()
+ {
+ return base_objects.size();
+ }
+
+ Object *get(int index)
+ {
+ return base_objects[index];
+ }
+};
+
+class BCPolygonNormalsIndices {
+ std::vector<unsigned int> normal_indices;
+
+ public:
+ void add_index(unsigned int index)
+ {
+ normal_indices.push_back(index);
+ }
+
+ unsigned int operator[](unsigned int i)
+ {
+ return normal_indices[i];
+ }
+};
+
+class BoneExtended {
+
+ private:
+ char name[MAXBONENAME];
+ int chain_length;
+ bool is_leaf;
+ float tail[3];
+ float roll;
+
+ int bone_layers;
+ int use_connect;
+ bool has_custom_tail;
+ bool has_custom_roll;
+
+ public:
+ BoneExtended(EditBone *aBone);
+
+ void set_name(char *aName);
+ char *get_name();
+
+ void set_chain_length(const int aLength);
+ int get_chain_length();
+
+ void set_leaf_bone(bool state);
+ bool is_leaf_bone();
+
+ void set_bone_layers(std::string layers, std::vector<std::string> &layer_labels);
+ int get_bone_layers();
+ static std::string get_bone_layers(int bitfield);
+
+ void set_roll(float roll);
+ bool has_roll();
+ float get_roll();
+
+ void set_tail(float vec[]);
+ float *get_tail();
+ bool has_tail();
+
+ void set_use_connect(int use_connect);
+ int get_use_connect();
+};
+
+/* a map to store bone extension maps
+ * std:string : an armature name
+ * BoneExtended * : a map that contains extra data for bones
+ */
+typedef std::map<std::string, BoneExtended *> BoneExtensionMap;
+
+/*
+ * A class to organize bone extension data for multiple Armatures.
+ * this is needed for the case where a Collada file contains 2 or more
+ * separate armatures.
+ */
+class BoneExtensionManager {
+ private:
+ std::map<std::string, BoneExtensionMap *> extended_bone_maps;
+
+ public:
+ BoneExtensionMap &getExtensionMap(bArmature *armature);
+ ~BoneExtensionManager();
+};
+
+void bc_add_default_shader(bContext *C, Material *ma);
+bNode *bc_get_master_shader(Material *ma);
+
+COLLADASW::ColorOrTexture bc_get_base_color(Material *ma);
+COLLADASW::ColorOrTexture bc_get_emission(Material *ma);
+COLLADASW::ColorOrTexture bc_get_ambient(Material *ma);
+COLLADASW::ColorOrTexture bc_get_specular(Material *ma);
+COLLADASW::ColorOrTexture bc_get_reflective(Material *ma);
+
+double bc_get_reflectivity(Material *ma);
+double bc_get_alpha(Material *ma);
+double bc_get_ior(Material *ma);
+double bc_get_shininess(Material *ma);
+
+double bc_get_float_from_shader(bNode *shader, double &ior, std::string nodeid);
+COLLADASW::ColorOrTexture bc_get_cot_from_shader(bNode *shader,
+ std::string nodeid,
+ Color &default_color,
+ bool with_alpha = true);
+
+COLLADASW::ColorOrTexture bc_get_cot(float r, float g, float b, float a);
+COLLADASW::ColorOrTexture bc_get_cot(Color col, bool with_alpha = true);
+
+#endif
diff --git a/source/blender/io/collada/version.conf b/source/blender/io/collada/version.conf
new file mode 100644
index 00000000000..d39af7a53df
--- /dev/null
+++ b/source/blender/io/collada/version.conf
@@ -0,0 +1 @@
+463ba8a2ef5a021ce21df614dde29e0ee800e10b
diff --git a/source/blender/io/usd/CMakeLists.txt b/source/blender/io/usd/CMakeLists.txt
new file mode 100644
index 00000000000..732a638a255
--- /dev/null
+++ b/source/blender/io/usd/CMakeLists.txt
@@ -0,0 +1,111 @@
+# ***** 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) 2019, Blender Foundation
+# All rights reserved.
+# ***** END GPL LICENSE BLOCK *****
+
+# This suppresses the warning "This file includes at least one deprecated or antiquated
+# header which may be removed without further notice at a future date", which is caused
+# by the USD library including <ext/hash_set> on Linux. This has been reported at:
+# https://github.com/PixarAnimationStudios/USD/issues/1057.
+if(UNIX AND NOT APPLE)
+ add_definitions(-D_GLIBCXX_PERMIT_BACKWARD_HASH)
+endif()
+if(WIN32)
+ add_definitions(-DNOMINMAX -DWIN32_LEAN_AND_MEAN)
+endif()
+add_definitions(-DPXR_STATIC)
+
+set(INC
+ .
+ ../../blenkernel
+ ../../blenlib
+ ../../blenloader
+ ../../bmesh
+ ../../depsgraph
+ ../../editors/include
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/guardedalloc
+ ../../../../intern/utfconv
+)
+
+set(INC_SYS
+ ${USD_INCLUDE_DIRS}
+ ${BOOST_INCLUDE_DIR}
+ ${TBB_INCLUDE_DIR}
+)
+
+set(SRC
+ intern/abstract_hierarchy_iterator.cc
+ intern/usd_capi.cc
+ intern/usd_hierarchy_iterator.cc
+ intern/usd_writer_abstract.cc
+ intern/usd_writer_camera.cc
+ intern/usd_writer_hair.cc
+ intern/usd_writer_light.cc
+ intern/usd_writer_mesh.cc
+ intern/usd_writer_metaball.cc
+ intern/usd_writer_transform.cc
+
+ usd.h
+ intern/abstract_hierarchy_iterator.h
+ intern/usd_exporter_context.h
+ intern/usd_hierarchy_iterator.h
+ intern/usd_writer_abstract.h
+ intern/usd_writer_camera.h
+ intern/usd_writer_hair.h
+ intern/usd_writer_light.h
+ intern/usd_writer_mesh.h
+ intern/usd_writer_metaball.h
+ intern/usd_writer_transform.h
+)
+
+set(LIB
+ bf_blenkernel
+ bf_blenlib
+)
+
+list(APPEND LIB
+ ${BOOST_LIBRARIES}
+)
+
+list(APPEND LIB
+)
+
+blender_add_lib(bf_usd "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
+
+if(WIN32)
+ set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_DEBUG " /WHOLEARCHIVE:${USD_DEBUG_LIB}")
+ set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_RELEASE " /WHOLEARCHIVE:${USD_RELEASE_LIB}")
+ set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_RELWITHDEBINFO " /WHOLEARCHIVE:${USD_RELEASE_LIB}")
+ set_property(TARGET bf_usd APPEND_STRING PROPERTY LINK_FLAGS_MINSIZEREL " /WHOLEARCHIVE:${USD_RELEASE_LIB}")
+endif()
+
+# Source: https://github.com/PixarAnimationStudios/USD/blob/master/BUILDING.md#linking-whole-archives
+if(WIN32)
+ target_link_libraries(bf_usd INTERFACE ${USD_LIBRARIES})
+elseif(CMAKE_COMPILER_IS_GNUCXX)
+ target_link_libraries(bf_usd INTERFACE "-Wl,--whole-archive ${USD_LIBRARIES} -Wl,--no-whole-archive ${TBB_LIBRARIES}")
+elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
+ target_link_libraries(bf_usd INTERFACE -Wl,-force_load ${USD_LIBRARIES})
+else()
+ message(FATAL_ERROR "Unknown how to link USD with your compiler ${CMAKE_CXX_COMPILER_ID}")
+endif()
+
+target_link_libraries(bf_usd INTERFACE ${TBB_LIBRARIES})
diff --git a/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc b/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc
new file mode 100644
index 00000000000..a8ed2c5f2a5
--- /dev/null
+++ b/source/blender/io/usd/intern/abstract_hierarchy_iterator.cc
@@ -0,0 +1,595 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "abstract_hierarchy_iterator.h"
+
+#include <iostream>
+#include <limits.h>
+#include <sstream>
+#include <string>
+
+extern "C" {
+#include "BKE_anim.h"
+#include "BKE_particle.h"
+
+#include "BLI_assert.h"
+#include "BLI_listbase.h"
+#include "BLI_math_matrix.h"
+
+#include "DNA_ID.h"
+#include "DNA_layer_types.h"
+#include "DNA_object_types.h"
+#include "DNA_particle_types.h"
+
+#include "DEG_depsgraph_query.h"
+}
+
+namespace USD {
+
+const HierarchyContext *HierarchyContext::root()
+{
+ return nullptr;
+}
+
+bool HierarchyContext::operator<(const HierarchyContext &other) const
+{
+ if (object != other.object) {
+ return object < other.object;
+ }
+ if (duplicator != NULL && duplicator == other.duplicator) {
+ // Only resort to string comparisons when both objects are created by the same duplicator.
+ return export_name < other.export_name;
+ }
+
+ return export_parent < other.export_parent;
+}
+
+bool HierarchyContext::is_instance() const
+{
+ return !original_export_path.empty();
+}
+void HierarchyContext::mark_as_instance_of(const std::string &reference_export_path)
+{
+ original_export_path = reference_export_path;
+}
+void HierarchyContext::mark_as_not_instanced()
+{
+ original_export_path.clear();
+}
+
+AbstractHierarchyWriter::~AbstractHierarchyWriter()
+{
+}
+
+AbstractHierarchyIterator::AbstractHierarchyIterator(Depsgraph *depsgraph)
+ : depsgraph_(depsgraph), writers_()
+{
+}
+
+AbstractHierarchyIterator::~AbstractHierarchyIterator()
+{
+}
+
+void AbstractHierarchyIterator::iterate_and_write()
+{
+ export_graph_construct();
+ connect_loose_objects();
+ export_graph_prune();
+ determine_export_paths(HierarchyContext::root());
+ determine_duplication_references(HierarchyContext::root(), "");
+ make_writers(HierarchyContext::root());
+ export_graph_clear();
+}
+
+void AbstractHierarchyIterator::release_writers()
+{
+ for (WriterMap::value_type it : writers_) {
+ delete_object_writer(it.second);
+ }
+ writers_.clear();
+}
+
+std::string AbstractHierarchyIterator::make_valid_name(const std::string &name) const
+{
+ return name;
+}
+
+std::string AbstractHierarchyIterator::get_id_name(const ID *id) const
+{
+ if (id == nullptr) {
+ return "";
+ }
+
+ return make_valid_name(std::string(id->name + 2));
+}
+
+std::string AbstractHierarchyIterator::get_object_data_path(const HierarchyContext *context) const
+{
+ BLI_assert(!context->export_path.empty());
+ BLI_assert(context->object->data);
+
+ return path_concatenate(context->export_path, get_object_data_name(context->object));
+}
+
+void AbstractHierarchyIterator::debug_print_export_graph(const ExportGraph &graph) const
+{
+ size_t total_graph_size = 0;
+ for (const ExportGraph::value_type &map_iter : graph) {
+ const DupliAndDuplicator &parent_info = map_iter.first;
+ Object *const export_parent = parent_info.first;
+ Object *const duplicator = parent_info.second;
+
+ if (duplicator != nullptr) {
+ printf(" DU %s (as dupped by %s):\n",
+ export_parent == nullptr ? "-null-" : (export_parent->id.name + 2),
+ duplicator->id.name + 2);
+ }
+ else {
+ printf(" OB %s:\n", export_parent == nullptr ? "-null-" : (export_parent->id.name + 2));
+ }
+
+ total_graph_size += map_iter.second.size();
+ for (HierarchyContext *child_ctx : map_iter.second) {
+ if (child_ctx->duplicator == nullptr) {
+ printf(" - %s%s%s\n",
+ child_ctx->object->id.name + 2,
+ child_ctx->weak_export ? " (weak)" : "",
+ child_ctx->original_export_path.size() ?
+ (std::string("ref ") + child_ctx->original_export_path).c_str() :
+ "");
+ }
+ else {
+ printf(" - %s (dup by %s%s) %s\n",
+ child_ctx->object->id.name + 2,
+ child_ctx->duplicator->id.name + 2,
+ child_ctx->weak_export ? ", weak" : "",
+ child_ctx->original_export_path.size() ?
+ (std::string("ref ") + child_ctx->original_export_path).c_str() :
+ "");
+ }
+ }
+ }
+ printf(" (Total graph size: %lu objects\n", total_graph_size);
+}
+
+void AbstractHierarchyIterator::export_graph_construct()
+{
+ Scene *scene = DEG_get_evaluated_scene(depsgraph_);
+
+ DEG_OBJECT_ITER_BEGIN (depsgraph_,
+ object,
+ DEG_ITER_OBJECT_FLAG_LINKED_DIRECTLY |
+ DEG_ITER_OBJECT_FLAG_LINKED_VIA_SET) {
+ // Non-instanced objects always have their object-parent as export-parent.
+ const bool weak_export = mark_as_weak_export(object);
+ visit_object(object, object->parent, weak_export);
+
+ if (weak_export) {
+ // If a duplicator shouldn't be exported, its duplilist also shouldn't be.
+ continue;
+ }
+
+ // Export the duplicated objects instanced by this object.
+ ListBase *lb = object_duplilist(depsgraph_, scene, object);
+ if (lb) {
+ // Construct the set of duplicated objects, so that later we can determine whether a parent
+ // is also duplicated itself.
+ std::set<Object *> dupli_set;
+ LISTBASE_FOREACH (DupliObject *, dupli_object, lb) {
+ if (!should_visit_dupli_object(dupli_object)) {
+ continue;
+ }
+ dupli_set.insert(dupli_object->ob);
+ }
+
+ LISTBASE_FOREACH (DupliObject *, dupli_object, lb) {
+ if (!should_visit_dupli_object(dupli_object)) {
+ continue;
+ }
+
+ visit_dupli_object(dupli_object, object, dupli_set);
+ }
+ }
+
+ free_object_duplilist(lb);
+ }
+ DEG_OBJECT_ITER_END;
+}
+
+void AbstractHierarchyIterator::connect_loose_objects()
+{
+ // Find those objects whose parent is not part of the export graph; these
+ // objects would be skipped when traversing the graph as a hierarchy.
+ // These objects will have to be re-attached to some parent object in order to
+ // fit into the hierarchy.
+ ExportGraph loose_objects_graph = export_graph_;
+ for (const ExportGraph::value_type &map_iter : export_graph_) {
+ for (const HierarchyContext *child : map_iter.second) {
+ // An object that is marked as a child of another object is not considered 'loose'.
+ loose_objects_graph.erase(std::make_pair(child->object, child->duplicator));
+ }
+ }
+ // The root of the hierarchy is always found, so it's never considered 'loose'.
+ loose_objects_graph.erase(std::make_pair(nullptr, nullptr));
+
+ // Iterate over the loose objects and connect them to their export parent.
+ for (const ExportGraph::value_type &map_iter : loose_objects_graph) {
+ const DupliAndDuplicator &export_info = map_iter.first;
+ Object *object = export_info.first;
+ Object *export_parent = object->parent;
+
+ while (true) {
+ // Loose objects will all be real objects, as duplicated objects always have
+ // their duplicator or other exported duplicated object as ancestor.
+ ExportGraph::iterator found_parent_iter = export_graph_.find(
+ std::make_pair(export_parent, nullptr));
+
+ visit_object(object, export_parent, true);
+ if (found_parent_iter != export_graph_.end()) {
+ break;
+ }
+ // 'export_parent' will never be nullptr here, as the export graph contains the
+ // tuple <nullptr, nullptr> as root and thus will cause a break.
+ BLI_assert(export_parent != nullptr);
+
+ object = export_parent;
+ export_parent = export_parent->parent;
+ }
+ }
+}
+
+static bool remove_weak_subtrees(const HierarchyContext *context,
+ AbstractHierarchyIterator::ExportGraph &clean_graph,
+ const AbstractHierarchyIterator::ExportGraph &input_graph)
+{
+ bool all_is_weak = context != nullptr && context->weak_export;
+ Object *object = context != nullptr ? context->object : nullptr;
+ Object *duplicator = context != nullptr ? context->duplicator : nullptr;
+
+ const AbstractHierarchyIterator::DupliAndDuplicator map_key = std::make_pair(object, duplicator);
+ AbstractHierarchyIterator::ExportGraph::const_iterator child_iterator;
+
+ child_iterator = input_graph.find(map_key);
+ if (child_iterator != input_graph.end()) {
+ for (HierarchyContext *child_context : child_iterator->second) {
+ bool child_tree_is_weak = remove_weak_subtrees(child_context, clean_graph, input_graph);
+ all_is_weak &= child_tree_is_weak;
+
+ if (child_tree_is_weak) {
+ // This subtree is all weak, so we can remove it from the current object's children.
+ clean_graph[map_key].erase(child_context);
+ delete child_context;
+ }
+ }
+ }
+
+ if (all_is_weak) {
+ // This node and all its children are weak, so it can be removed from the export graph.
+ clean_graph.erase(map_key);
+ }
+
+ return all_is_weak;
+}
+
+void AbstractHierarchyIterator::export_graph_prune()
+{
+ // Take a copy of the map so that we can modify while recursing.
+ ExportGraph unpruned_export_graph = export_graph_;
+ remove_weak_subtrees(HierarchyContext::root(), export_graph_, unpruned_export_graph);
+}
+
+void AbstractHierarchyIterator::export_graph_clear()
+{
+ for (ExportGraph::iterator::value_type &it : export_graph_) {
+ for (HierarchyContext *context : it.second) {
+ delete context;
+ }
+ }
+ export_graph_.clear();
+}
+
+void AbstractHierarchyIterator::visit_object(Object *object,
+ Object *export_parent,
+ bool weak_export)
+{
+ HierarchyContext *context = new HierarchyContext();
+ context->object = object;
+ context->export_name = get_object_name(object);
+ context->export_parent = export_parent;
+ context->duplicator = nullptr;
+ context->weak_export = weak_export;
+ context->animation_check_include_parent = false;
+ context->export_path = "";
+ context->original_export_path = "";
+ copy_m4_m4(context->matrix_world, object->obmat);
+
+ export_graph_[std::make_pair(export_parent, nullptr)].insert(context);
+}
+
+void AbstractHierarchyIterator::visit_dupli_object(DupliObject *dupli_object,
+ Object *duplicator,
+ const std::set<Object *> &dupli_set)
+{
+ ExportGraph::key_type graph_index;
+ bool animation_check_include_parent = false;
+
+ HierarchyContext *context = new HierarchyContext();
+ context->object = dupli_object->ob;
+ context->duplicator = duplicator;
+ context->weak_export = false;
+ context->export_path = "";
+ context->original_export_path = "";
+
+ /* If the dupli-object's parent is also instanced by this object, use that as the
+ * export parent. Otherwise use the dupli-parent as export parent. */
+ Object *parent = dupli_object->ob->parent;
+ if (parent != nullptr && dupli_set.find(parent) != dupli_set.end()) {
+ // The parent object is part of the duplicated collection.
+ context->export_parent = parent;
+ graph_index = std::make_pair(parent, duplicator);
+ }
+ else {
+ /* The parent object is NOT part of the duplicated collection. This means that the world
+ * transform of this dupliobject can be influenced by objects that are not part of its
+ * export graph. */
+ animation_check_include_parent = true;
+ context->export_parent = duplicator;
+ graph_index = std::make_pair(duplicator, nullptr);
+ }
+
+ context->animation_check_include_parent = animation_check_include_parent;
+ copy_m4_m4(context->matrix_world, dupli_object->mat);
+
+ // Construct export name for the dupli-instance.
+ std::stringstream suffix_stream;
+ suffix_stream << std::hex;
+ for (int i = 0; i < MAX_DUPLI_RECUR && dupli_object->persistent_id[i] != INT_MAX; i++) {
+ suffix_stream << "-" << dupli_object->persistent_id[i];
+ }
+ context->export_name = make_valid_name(get_object_name(context->object) + suffix_stream.str());
+
+ export_graph_[graph_index].insert(context);
+}
+
+AbstractHierarchyIterator::ExportChildren &AbstractHierarchyIterator::graph_children(
+ const HierarchyContext *context)
+{
+ if (context == nullptr) {
+ return export_graph_[std::make_pair(nullptr, nullptr)];
+ }
+
+ return export_graph_[std::make_pair(context->object, context->duplicator)];
+}
+
+void AbstractHierarchyIterator::determine_export_paths(const HierarchyContext *parent_context)
+{
+ const std::string &parent_export_path = parent_context ? parent_context->export_path : "";
+
+ for (HierarchyContext *context : graph_children(parent_context)) {
+ context->export_path = path_concatenate(parent_export_path, context->export_name);
+
+ if (context->duplicator == nullptr) {
+ /* This is an original (i.e. non-instanced) object, so we should keep track of where it was
+ * exported to, just in case it gets instanced somewhere. */
+ ID *source_ob = &context->object->id;
+ duplisource_export_path_[source_ob] = context->export_path;
+
+ if (context->object->data != nullptr) {
+ ID *object_data = static_cast<ID *>(context->object->data);
+ ID *source_data = object_data;
+ duplisource_export_path_[source_data] = get_object_data_path(context);
+ }
+ }
+
+ determine_export_paths(context);
+ }
+}
+
+void AbstractHierarchyIterator::determine_duplication_references(
+ const HierarchyContext *parent_context, std::string indent)
+{
+ ExportChildren children = graph_children(parent_context);
+
+ for (HierarchyContext *context : children) {
+ if (context->duplicator != nullptr) {
+ ID *source_id = &context->object->id;
+ const ExportPathMap::const_iterator &it = duplisource_export_path_.find(source_id);
+
+ if (it == duplisource_export_path_.end()) {
+ // The original was not found, so mark this instance as "the original".
+ context->mark_as_not_instanced();
+ duplisource_export_path_[source_id] = context->export_path;
+ }
+ else {
+ context->mark_as_instance_of(it->second);
+ }
+
+ if (context->object->data) {
+ ID *source_data_id = (ID *)context->object->data;
+ const ExportPathMap::const_iterator &it = duplisource_export_path_.find(source_data_id);
+
+ if (it == duplisource_export_path_.end()) {
+ // The original was not found, so mark this instance as "original".
+ std::string data_path = get_object_data_path(context);
+ context->mark_as_not_instanced();
+ duplisource_export_path_[source_id] = context->export_path;
+ duplisource_export_path_[source_data_id] = data_path;
+ }
+ }
+ }
+
+ determine_duplication_references(context, indent + " ");
+ }
+}
+
+void AbstractHierarchyIterator::make_writers(const HierarchyContext *parent_context)
+{
+ AbstractHierarchyWriter *transform_writer = nullptr;
+ float parent_matrix_inv_world[4][4];
+
+ if (parent_context) {
+ invert_m4_m4(parent_matrix_inv_world, parent_context->matrix_world);
+ }
+ else {
+ unit_m4(parent_matrix_inv_world);
+ }
+
+ for (HierarchyContext *context : graph_children(parent_context)) {
+ copy_m4_m4(context->parent_matrix_inv_world, parent_matrix_inv_world);
+
+ // Get or create the transform writer.
+ transform_writer = ensure_writer(context, &AbstractHierarchyIterator::create_transform_writer);
+ if (transform_writer == nullptr) {
+ // Unable to export, so there is nothing to attach any children to; just abort this entire
+ // branch of the export hierarchy.
+ return;
+ }
+
+ BLI_assert(DEG_is_evaluated_object(context->object));
+ /* XXX This can lead to too many XForms being written. For example, a camera writer can refuse
+ * to write an orthographic camera. By the time that this is known, the XForm has already been
+ * written. */
+ transform_writer->write(*context);
+
+ if (!context->weak_export) {
+ make_writers_particle_systems(context);
+ make_writer_object_data(context);
+ }
+
+ // Recurse into this object's children.
+ make_writers(context);
+ }
+
+ // TODO(Sybren): iterate over all unused writers and call unused_during_iteration() or something.
+}
+
+void AbstractHierarchyIterator::make_writer_object_data(const HierarchyContext *context)
+{
+ if (context->object->data == nullptr) {
+ return;
+ }
+
+ HierarchyContext data_context = *context;
+ data_context.export_path = get_object_data_path(context);
+
+ /* data_context.original_export_path is just a copy from the context. It points to the object,
+ * but needs to point to the object data. */
+ if (data_context.is_instance()) {
+ ID *object_data = static_cast<ID *>(context->object->data);
+ data_context.original_export_path = duplisource_export_path_[object_data];
+
+ /* If the object is marked as an instance, so should the object data. */
+ BLI_assert(data_context.is_instance());
+ }
+
+ AbstractHierarchyWriter *data_writer;
+ data_writer = ensure_writer(&data_context, &AbstractHierarchyIterator::create_data_writer);
+ if (data_writer == nullptr) {
+ return;
+ }
+
+ data_writer->write(data_context);
+}
+
+void AbstractHierarchyIterator::make_writers_particle_systems(
+ const HierarchyContext *transform_context)
+{
+ Object *object = transform_context->object;
+ ParticleSystem *psys = static_cast<ParticleSystem *>(object->particlesystem.first);
+ for (; psys; psys = psys->next) {
+ if (!psys_check_enabled(object, psys, true)) {
+ continue;
+ }
+
+ HierarchyContext hair_context = *transform_context;
+ hair_context.export_path = path_concatenate(transform_context->export_path,
+ get_id_name(&psys->part->id));
+ hair_context.particle_system = psys;
+
+ AbstractHierarchyWriter *writer = nullptr;
+ switch (psys->part->type) {
+ case PART_HAIR:
+ writer = ensure_writer(&hair_context, &AbstractHierarchyIterator::create_hair_writer);
+ break;
+ case PART_EMITTER:
+ writer = ensure_writer(&hair_context, &AbstractHierarchyIterator::create_particle_writer);
+ break;
+ }
+
+ if (writer != nullptr) {
+ writer->write(hair_context);
+ }
+ }
+}
+
+std::string AbstractHierarchyIterator::get_object_name(const Object *object) const
+{
+ return get_id_name(&object->id);
+}
+
+std::string AbstractHierarchyIterator::get_object_data_name(const Object *object) const
+{
+ ID *object_data = static_cast<ID *>(object->data);
+ return get_id_name(object_data);
+}
+
+AbstractHierarchyWriter *AbstractHierarchyIterator::get_writer(const std::string &export_path)
+{
+ WriterMap::iterator it = writers_.find(export_path);
+
+ if (it == writers_.end()) {
+ return nullptr;
+ }
+ return it->second;
+}
+
+AbstractHierarchyWriter *AbstractHierarchyIterator::ensure_writer(
+ HierarchyContext *context, AbstractHierarchyIterator::create_writer_func create_func)
+{
+ AbstractHierarchyWriter *writer = get_writer(context->export_path);
+ if (writer != nullptr) {
+ return writer;
+ }
+
+ writer = (this->*create_func)(context);
+ if (writer == nullptr) {
+ return nullptr;
+ }
+
+ writers_[context->export_path] = writer;
+
+ return writer;
+}
+
+std::string AbstractHierarchyIterator::path_concatenate(const std::string &parent_path,
+ const std::string &child_path) const
+{
+ return parent_path + "/" + child_path;
+}
+
+bool AbstractHierarchyIterator::mark_as_weak_export(const Object * /*object*/) const
+{
+ return false;
+}
+bool AbstractHierarchyIterator::should_visit_dupli_object(const DupliObject *dupli_object) const
+{
+ // Removing dupli_object->no_draw hides things like custom bone shapes.
+ return !dupli_object->no_draw;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/abstract_hierarchy_iterator.h b/source/blender/io/usd/intern/abstract_hierarchy_iterator.h
new file mode 100644
index 00000000000..8bca2ddd447
--- /dev/null
+++ b/source/blender/io/usd/intern/abstract_hierarchy_iterator.h
@@ -0,0 +1,251 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+/*
+ * This file contains the AbstractHierarchyIterator. It is intended for exporters for file
+ * formats that concern an entire hierarchy of objects (rather than, for example, an OBJ file that
+ * contains only a single mesh). Examples are Universal Scene Description (USD) and Alembic.
+ * AbstractHierarchyIterator is intended to be subclassed to support concrete file formats.
+ *
+ * The AbstractHierarchyIterator makes a distinction between the actual object hierarchy and the
+ * export hierarchy. The former is the parent/child structure in Blender, which can have multiple
+ * parent-like objects. For example, a duplicated object can have both a duplicator and a parent,
+ * both determining the final transform. The export hierarchy is the hierarchy as written to the
+ * file, and every object has only one export-parent.
+ *
+ * Currently the AbstractHierarchyIterator does not make any decisions about *what* to export.
+ * Selections like "selected only" or "no hair systems" are left to concrete subclasses.
+ */
+
+#ifndef __ABSTRACT_HIERARCHY_ITERATOR_H__
+#define __ABSTRACT_HIERARCHY_ITERATOR_H__
+
+#include <map>
+#include <string>
+#include <set>
+
+struct Base;
+struct Depsgraph;
+struct DupliObject;
+struct ID;
+struct Object;
+struct ParticleSystem;
+struct ViewLayer;
+
+namespace USD {
+
+class AbstractHierarchyWriter;
+
+/* HierarchyContext structs are created by the AbstractHierarchyIterator. Each HierarchyContext
+ * struct contains everything necessary to export a single object to a file. */
+struct HierarchyContext {
+ /*********** Determined during hierarchy iteration: ***************/
+ Object *object;
+ Object *export_parent;
+ Object *duplicator;
+ float matrix_world[4][4];
+ std::string export_name;
+
+ /* When weak_export=true, the object will be exported only as transform, and only if is an
+ * ancestor of an object with weak_export=false.
+ *
+ * In other words: when weak_export=true but this object has no children, or all decendants also
+ * have weak_export=true, this object (and by recursive reasoning all its decendants) will be
+ * excluded from the export.
+ *
+ * The export hierarchy is kept as close to the the hierarchy in Blender as possible. As such, an
+ * object that serves as a parent for another object, but which should NOT be exported itself, is
+ * exported only as transform (i.e. as empty). This happens with objects that are part of a
+ * holdout collection (which prevents them from being exported) but also parent of an exported
+ * object. */
+ bool weak_export;
+
+ /* When true, this object should check its parents for animation data when determining whether
+ * it's animated. This is necessary when a parent object in Blender is not part of the export. */
+ bool animation_check_include_parent;
+
+ /*********** Determined during writer creation: ***************/
+ float parent_matrix_inv_world[4][4]; // Inverse of the parent's world matrix.
+ std::string export_path; // Hierarchical path, such as "/grandparent/parent/objectname".
+ ParticleSystem *particle_system; // Only set for particle/hair writers.
+
+ /* Hierarchical path of the object this object is duplicating; only set when this object should
+ * be stored as a reference to its original. It can happen that the original is not part of the
+ * exported objects, in which case this string is empty even though 'duplicator' is set. */
+ std::string original_export_path;
+
+ bool operator<(const HierarchyContext &other) const;
+
+ /* Return a HierarchyContext representing the root of the export hierarchy. */
+ static const HierarchyContext *root();
+
+ /* For handling instanced collections, instances created by particles, etc. */
+ bool is_instance() const;
+ void mark_as_instance_of(const std::string &reference_export_path);
+ void mark_as_not_instanced();
+};
+
+/* Abstract writer for objects. Create concrete subclasses to write to USD, Alembic, etc.
+ *
+ * Instantiated by the AbstractHierarchyIterator on the first frame an object exists. Generally
+ * that's the first frame to be exported, but can be later, for example when objects are
+ * instantiated by particles. The AbstractHierarchyWriter::write() function is called on every
+ * frame the object exists in the dependency graph and should be exported.
+ */
+class AbstractHierarchyWriter {
+ public:
+ virtual ~AbstractHierarchyWriter();
+ virtual void write(HierarchyContext &context) = 0;
+ // TODO(Sybren): add function like absent() that's called when a writer was previously created,
+ // but wasn't used while exporting the current frame (for example, a particle-instanced mesh of
+ // which the particle is no longer alive).
+};
+
+/* AbstractHierarchyIterator iterates over objects in a dependency graph, and constructs export
+ * writers. These writers are then called to perform the actual writing to a USD or Alembic file.
+ *
+ * Dealing with file- and scene-level data (for example, creating a USD scene, setting the frame
+ * rate, etc.) is not part of the AbstractHierarchyIterator class structure, and should be done
+ * in separate code.
+ */
+class AbstractHierarchyIterator {
+ public:
+ /* Mapping from export path to writer. */
+ typedef std::map<std::string, AbstractHierarchyWriter *> WriterMap;
+ /* Pair of a (potentially duplicated) object and its duplicator (or nullptr).
+ * This is typically used to store a pair of HierarchyContext::object and
+ * HierarchyContext::duplicator. */
+ typedef std::pair<Object *, Object *> DupliAndDuplicator;
+ /* All the children of some object, as per the export hierarchy. */
+ typedef std::set<HierarchyContext *> ExportChildren;
+ /* Mapping from an object and its duplicator to the object's export-children. */
+ typedef std::map<DupliAndDuplicator, ExportChildren> ExportGraph;
+ /* Mapping from ID to its export path. This is used for instancing; given an
+ * instanced datablock, the export path of the original can be looked up. */
+ typedef std::map<ID *, std::string> ExportPathMap;
+
+ protected:
+ ExportGraph export_graph_;
+ ExportPathMap duplisource_export_path_;
+ Depsgraph *depsgraph_;
+ WriterMap writers_;
+
+ public:
+ explicit AbstractHierarchyIterator(Depsgraph *depsgraph);
+ virtual ~AbstractHierarchyIterator();
+
+ /* Iterate over the depsgraph, create writers, and tell the writers to write.
+ * Main entry point for the AbstractHierarchyIterator, must be called for every to-be-exported
+ * frame. */
+ void iterate_and_write();
+
+ /* Release all writers. Call after all frames have been exported. */
+ void release_writers();
+
+ /* Convert the given name to something that is valid for the exported file format.
+ * This base implementation is a no-op; override in a concrete subclass. */
+ virtual std::string make_valid_name(const std::string &name) const;
+
+ /* Return the name of this ID datablock that is valid for the exported file format. Overriding is
+ * only necessary if make_valid_name(id->name+2) is not suitable for the exported file format.
+ * NULL-safe: when `id == nullptr` this returns an empty string. */
+ virtual std::string get_id_name(const ID *id) const;
+
+ /* Given a HierarchyContext of some Object *, return an export path that is valid for its
+ * object->data. Overriding is necessary when the exported format does NOT expect the object's
+ * data to be a child of the object. */
+ virtual std::string get_object_data_path(const HierarchyContext *context) const;
+
+ private:
+ void debug_print_export_graph(const ExportGraph &graph) const;
+
+ void export_graph_construct();
+ void connect_loose_objects();
+ void export_graph_prune();
+ void export_graph_clear();
+
+ void visit_object(Object *object, Object *export_parent, bool weak_export);
+ void visit_dupli_object(DupliObject *dupli_object,
+ Object *duplicator,
+ const std::set<Object *> &dupli_set);
+
+ ExportChildren &graph_children(const HierarchyContext *parent_context);
+
+ void determine_export_paths(const HierarchyContext *parent_context);
+ void determine_duplication_references(const HierarchyContext *parent_context,
+ std::string indent);
+
+ void make_writers(const HierarchyContext *parent_context);
+ void make_writer_object_data(const HierarchyContext *context);
+ void make_writers_particle_systems(const HierarchyContext *context);
+
+ /* Convenience wrappers around get_id_name(). */
+ std::string get_object_name(const Object *object) const;
+ std::string get_object_data_name(const Object *object) const;
+
+ AbstractHierarchyWriter *get_writer(const std::string &export_path);
+
+ typedef AbstractHierarchyWriter *(AbstractHierarchyIterator::*create_writer_func)(
+ const HierarchyContext *);
+ /* Ensure that a writer exists; if it doesn't, call create_func(context). The create_func
+ * function should be one of the create_XXXX_writer(context) functions declared below. */
+ AbstractHierarchyWriter *ensure_writer(HierarchyContext *context,
+ create_writer_func create_func);
+
+ protected:
+ /* Construct a valid path for the export file format. This class concatenates by using '/' as a
+ * path separator, which is valid for both Alembic and USD. */
+ virtual std::string path_concatenate(const std::string &parent_path,
+ const std::string &child_path) const;
+
+ /* Return whether this object should be marked as 'weak export' or not.
+ *
+ * When this returns false, writers for the transform and data are created,
+ * and dupli-objects dupli-object generated from this object will be passed to
+ * should_visit_dupli_object().
+ *
+ * When this returns true, only a transform writer is created and marked as
+ * 'weak export'. In this case, the transform writer will be removed before
+ * exporting starts, unless a decendant of this object is to be exported.
+ * Dupli-object generated from this object will also be skipped.
+ *
+ * See HierarchyContext::weak_export.
+ */
+ virtual bool mark_as_weak_export(const Object *object) const;
+
+ virtual bool should_visit_dupli_object(const DupliObject *dupli_object) const;
+
+ /* These functions should create an AbstractHierarchyWriter subclass instance, or return
+ * nullptr if the object or its data should not be exported. Returning a nullptr for
+ * data/hair/particle will NOT prevent the transform to be written.
+ *
+ * The returned writer is owned by the AbstractHierarchyWriter, and should be freed in
+ * delete_object_writer(). */
+ virtual AbstractHierarchyWriter *create_transform_writer(const HierarchyContext *context) = 0;
+ virtual AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) = 0;
+ virtual AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) = 0;
+ virtual AbstractHierarchyWriter *create_particle_writer(const HierarchyContext *context) = 0;
+
+ /* Called by release_writers() to free what the create_XXX_writer() functions allocated. */
+ virtual void delete_object_writer(AbstractHierarchyWriter *writer) = 0;
+};
+
+} // namespace USD
+
+#endif /* __ABSTRACT_HIERARCHY_ITERATOR_H__ */
diff --git a/source/blender/io/usd/intern/usd_capi.cc b/source/blender/io/usd/intern/usd_capi.cc
new file mode 100644
index 00000000000..83e11cd7bf3
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_capi.cc
@@ -0,0 +1,233 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+#include "usd.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/pxr.h>
+#include <pxr/usd/usd/stage.h>
+#include <pxr/usd/usdGeom/tokens.h>
+
+#include "MEM_guardedalloc.h"
+
+extern "C" {
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_build.h"
+#include "DEG_depsgraph_query.h"
+
+#include "DNA_scene_types.h"
+
+#include "BKE_blender_version.h"
+#include "BKE_context.h"
+#include "BKE_global.h"
+#include "BKE_scene.h"
+
+#include "BLI_fileops.h"
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+}
+
+namespace USD {
+
+struct ExportJobData {
+ ViewLayer *view_layer;
+ Main *bmain;
+ Depsgraph *depsgraph;
+ wmWindowManager *wm;
+
+ char filename[FILE_MAX];
+ USDExportParams params;
+
+ short *stop;
+ short *do_update;
+ float *progress;
+
+ bool was_canceled;
+ bool export_ok;
+};
+
+static void export_startjob(void *customdata, short *stop, short *do_update, float *progress)
+{
+ ExportJobData *data = static_cast<ExportJobData *>(customdata);
+
+ data->stop = stop;
+ data->do_update = do_update;
+ data->progress = progress;
+ data->was_canceled = false;
+
+ G.is_rendering = true;
+ WM_set_locked_interface(data->wm, true);
+ G.is_break = false;
+
+ // Construct the depsgraph for exporting.
+ Scene *scene = DEG_get_input_scene(data->depsgraph);
+ ViewLayer *view_layer = DEG_get_input_view_layer(data->depsgraph);
+ DEG_graph_build_from_view_layer(data->depsgraph, data->bmain, scene, view_layer);
+ BKE_scene_graph_update_tagged(data->depsgraph, data->bmain);
+
+ *progress = 0.0f;
+ *do_update = true;
+
+ // For restoring the current frame after exporting animation is done.
+ const int orig_frame = CFRA;
+
+ pxr::UsdStageRefPtr usd_stage = pxr::UsdStage::CreateNew(data->filename);
+ if (!usd_stage) {
+ /* This happens when the USD JSON files cannot be found. When that happens,
+ * the USD library doesn't know it has the functionality to write USDA and
+ * USDC files, and creating a new UsdStage fails. */
+ WM_reportf(
+ RPT_ERROR, "USD Export: unable to find suitable USD plugin to write %s", data->filename);
+ data->export_ok = false;
+ return;
+ }
+
+ usd_stage->SetMetadata(pxr::UsdGeomTokens->upAxis, pxr::VtValue(pxr::UsdGeomTokens->z));
+ usd_stage->SetMetadata(pxr::UsdGeomTokens->metersPerUnit,
+ pxr::VtValue(scene->unit.scale_length));
+ usd_stage->GetRootLayer()->SetDocumentation(std::string("Blender ") + versionstr);
+
+ // Set up the stage for animated data.
+ if (data->params.export_animation) {
+ usd_stage->SetTimeCodesPerSecond(FPS);
+ usd_stage->SetStartTimeCode(scene->r.sfra);
+ usd_stage->SetEndTimeCode(scene->r.efra);
+ }
+
+ USDHierarchyIterator iter(data->depsgraph, usd_stage, data->params);
+
+ if (data->params.export_animation) {
+ // Writing the animated frames is not 100% of the work, but it's our best guess.
+ float progress_per_frame = 1.0f / std::max(1, (scene->r.efra - scene->r.sfra + 1));
+
+ for (float frame = scene->r.sfra; frame <= scene->r.efra; frame++) {
+ if (G.is_break || (stop != nullptr && *stop)) {
+ break;
+ }
+
+ // Update the scene for the next frame to render.
+ scene->r.cfra = static_cast<int>(frame);
+ scene->r.subframe = frame - scene->r.cfra;
+ BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain);
+
+ iter.set_export_frame(frame);
+ iter.iterate_and_write();
+
+ *progress += progress_per_frame;
+ *do_update = true;
+ }
+ }
+ else {
+ // If we're not animating, a single iteration over all objects is enough.
+ iter.iterate_and_write();
+ }
+
+ iter.release_writers();
+ usd_stage->GetRootLayer()->Save();
+
+ // Finish up by going back to the keyframe that was current before we started.
+ if (CFRA != orig_frame) {
+ CFRA = orig_frame;
+ BKE_scene_graph_update_for_newframe(data->depsgraph, data->bmain);
+ }
+
+ data->export_ok = !data->was_canceled;
+
+ *progress = 1.0f;
+ *do_update = true;
+}
+
+static void export_endjob(void *customdata)
+{
+ ExportJobData *data = static_cast<ExportJobData *>(customdata);
+
+ DEG_graph_free(data->depsgraph);
+
+ if (data->was_canceled && BLI_exists(data->filename)) {
+ BLI_delete(data->filename, false, false);
+ }
+
+ G.is_rendering = false;
+ WM_set_locked_interface(data->wm, false);
+}
+
+} // namespace USD
+
+bool USD_export(bContext *C,
+ const char *filepath,
+ const USDExportParams *params,
+ bool as_background_job)
+{
+ ViewLayer *view_layer = CTX_data_view_layer(C);
+ Scene *scene = CTX_data_scene(C);
+
+ USD::ExportJobData *job = static_cast<USD::ExportJobData *>(
+ MEM_mallocN(sizeof(USD::ExportJobData), "ExportJobData"));
+
+ job->bmain = CTX_data_main(C);
+ job->wm = CTX_wm_manager(C);
+ job->export_ok = false;
+ BLI_strncpy(job->filename, filepath, sizeof(job->filename));
+
+ job->depsgraph = DEG_graph_new(job->bmain, scene, view_layer, params->evaluation_mode);
+ job->params = *params;
+
+ bool export_ok = false;
+ if (as_background_job) {
+ wmJob *wm_job = WM_jobs_get(
+ job->wm, CTX_wm_window(C), scene, "USD Export", WM_JOB_PROGRESS, WM_JOB_TYPE_ALEMBIC);
+
+ /* setup job */
+ WM_jobs_customdata_set(wm_job, job, MEM_freeN);
+ WM_jobs_timer(wm_job, 0.1, NC_SCENE | ND_FRAME, NC_SCENE | ND_FRAME);
+ WM_jobs_callbacks(wm_job, USD::export_startjob, NULL, NULL, USD::export_endjob);
+
+ WM_jobs_start(CTX_wm_manager(C), wm_job);
+ }
+ else {
+ /* Fake a job context, so that we don't need NULL pointer checks while exporting. */
+ short stop = 0, do_update = 0;
+ float progress = 0.f;
+
+ USD::export_startjob(job, &stop, &do_update, &progress);
+ USD::export_endjob(job);
+ export_ok = job->export_ok;
+
+ MEM_freeN(job);
+ }
+
+ return export_ok;
+}
+
+int USD_get_version(void)
+{
+ /* USD 19.11 defines:
+ *
+ * #define PXR_MAJOR_VERSION 0
+ * #define PXR_MINOR_VERSION 19
+ * #define PXR_PATCH_VERSION 11
+ * #define PXR_VERSION 1911
+ *
+ * So the major version is implicit/invisible in the public version number.
+ */
+ return PXR_VERSION;
+}
diff --git a/source/blender/io/usd/intern/usd_exporter_context.h b/source/blender/io/usd/intern/usd_exporter_context.h
new file mode 100644
index 00000000000..4ae415b3d34
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_exporter_context.h
@@ -0,0 +1,44 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_EXPORTER_CONTEXT_H__
+#define __USD_EXPORTER_CONTEXT_H__
+
+#include "usd.h"
+
+#include <pxr/usd/sdf/path.h>
+#include <pxr/usd/usd/common.h>
+
+struct Depsgraph;
+struct Object;
+
+namespace USD {
+
+class USDHierarchyIterator;
+
+struct USDExporterContext {
+ Depsgraph *depsgraph;
+ const pxr::UsdStageRefPtr stage;
+ const pxr::SdfPath usd_path;
+ const USDHierarchyIterator *hierarchy_iterator;
+ const USDExportParams &export_params;
+};
+
+} // namespace USD
+
+#endif /* __USD_EXPORTER_CONTEXT_H__ */
diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.cc b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc
new file mode 100644
index 00000000000..fd888f39adc
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.cc
@@ -0,0 +1,150 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd.h"
+
+#include "usd_hierarchy_iterator.h"
+#include "usd_writer_abstract.h"
+#include "usd_writer_camera.h"
+#include "usd_writer_hair.h"
+#include "usd_writer_light.h"
+#include "usd_writer_mesh.h"
+#include "usd_writer_metaball.h"
+#include "usd_writer_transform.h"
+
+#include <string>
+
+#include <pxr/base/tf/stringUtils.h>
+
+extern "C" {
+#include "BKE_anim.h"
+
+#include "BLI_assert.h"
+
+#include "DEG_depsgraph_query.h"
+
+#include "DNA_ID.h"
+#include "DNA_layer_types.h"
+#include "DNA_object_types.h"
+}
+
+namespace USD {
+
+USDHierarchyIterator::USDHierarchyIterator(Depsgraph *depsgraph,
+ pxr::UsdStageRefPtr stage,
+ const USDExportParams &params)
+ : AbstractHierarchyIterator(depsgraph), stage_(stage), params_(params)
+{
+}
+
+bool USDHierarchyIterator::mark_as_weak_export(const Object *object) const
+{
+ if (params_.selected_objects_only && (object->base_flag & BASE_SELECTED) == 0) {
+ return true;
+ }
+ return false;
+}
+
+void USDHierarchyIterator::delete_object_writer(AbstractHierarchyWriter *writer)
+{
+ delete static_cast<USDAbstractWriter *>(writer);
+}
+
+std::string USDHierarchyIterator::make_valid_name(const std::string &name) const
+{
+ return pxr::TfMakeValidIdentifier(name);
+}
+
+void USDHierarchyIterator::set_export_frame(float frame_nr)
+{
+ // The USD stage is already set up to have FPS timecodes per frame.
+ export_time_ = pxr::UsdTimeCode(frame_nr);
+}
+
+const pxr::UsdTimeCode &USDHierarchyIterator::get_export_time_code() const
+{
+ return export_time_;
+}
+
+USDExporterContext USDHierarchyIterator::create_usd_export_context(const HierarchyContext *context)
+{
+ return USDExporterContext{depsgraph_, stage_, pxr::SdfPath(context->export_path), this, params_};
+}
+
+AbstractHierarchyWriter *USDHierarchyIterator::create_transform_writer(
+ const HierarchyContext *context)
+{
+ return new USDTransformWriter(create_usd_export_context(context));
+}
+
+AbstractHierarchyWriter *USDHierarchyIterator::create_data_writer(const HierarchyContext *context)
+{
+ USDExporterContext usd_export_context = create_usd_export_context(context);
+ USDAbstractWriter *data_writer = nullptr;
+
+ switch (context->object->type) {
+ case OB_MESH:
+ data_writer = new USDMeshWriter(usd_export_context);
+ break;
+ case OB_CAMERA:
+ data_writer = new USDCameraWriter(usd_export_context);
+ break;
+ case OB_LAMP:
+ data_writer = new USDLightWriter(usd_export_context);
+ break;
+ case OB_MBALL:
+ data_writer = new USDMetaballWriter(usd_export_context);
+ break;
+
+ case OB_EMPTY:
+ case OB_CURVE:
+ case OB_SURF:
+ case OB_FONT:
+ case OB_SPEAKER:
+ case OB_LIGHTPROBE:
+ case OB_LATTICE:
+ case OB_ARMATURE:
+ case OB_GPENCIL:
+ return nullptr;
+ case OB_TYPE_MAX:
+ BLI_assert(!"OB_TYPE_MAX should not be used");
+ return nullptr;
+ }
+
+ if (!data_writer->is_supported(context)) {
+ delete data_writer;
+ return nullptr;
+ }
+
+ return data_writer;
+}
+
+AbstractHierarchyWriter *USDHierarchyIterator::create_hair_writer(const HierarchyContext *context)
+{
+ if (!params_.export_hair) {
+ return nullptr;
+ }
+ return new USDHairWriter(create_usd_export_context(context));
+}
+
+AbstractHierarchyWriter *USDHierarchyIterator::create_particle_writer(const HierarchyContext *)
+{
+ return nullptr;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_hierarchy_iterator.h b/source/blender/io/usd/intern/usd_hierarchy_iterator.h
new file mode 100644
index 00000000000..90c82c6e551
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_hierarchy_iterator.h
@@ -0,0 +1,71 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_HIERARCHY_ITERATOR_H__
+#define __USD_HIERARCHY_ITERATOR_H__
+
+#include "abstract_hierarchy_iterator.h"
+#include "usd_exporter_context.h"
+#include "usd.h"
+
+#include <string>
+
+#include <pxr/usd/usd/common.h>
+#include <pxr/usd/usd/timeCode.h>
+
+struct Depsgraph;
+struct ID;
+struct Object;
+
+namespace USD {
+
+class USDHierarchyIterator : public AbstractHierarchyIterator {
+ private:
+ const pxr::UsdStageRefPtr stage_;
+ pxr::UsdTimeCode export_time_;
+ const USDExportParams &params_;
+
+ public:
+ USDHierarchyIterator(Depsgraph *depsgraph,
+ pxr::UsdStageRefPtr stage,
+ const USDExportParams &params);
+
+ void set_export_frame(float frame_nr);
+ const pxr::UsdTimeCode &get_export_time_code() const;
+
+ virtual std::string make_valid_name(const std::string &name) const override;
+
+ protected:
+ virtual bool mark_as_weak_export(const Object *object) const override;
+
+ virtual AbstractHierarchyWriter *create_transform_writer(
+ const HierarchyContext *context) override;
+ virtual AbstractHierarchyWriter *create_data_writer(const HierarchyContext *context) override;
+ virtual AbstractHierarchyWriter *create_hair_writer(const HierarchyContext *context) override;
+ virtual AbstractHierarchyWriter *create_particle_writer(
+ const HierarchyContext *context) override;
+
+ virtual void delete_object_writer(AbstractHierarchyWriter *writer) override;
+
+ private:
+ USDExporterContext create_usd_export_context(const HierarchyContext *context);
+};
+
+} // namespace USD
+
+#endif /* __USD_HIERARCHY_ITERATOR_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_abstract.cc b/source/blender/io/usd/intern/usd_writer_abstract.cc
new file mode 100644
index 00000000000..4d0b4364fb5
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_abstract.cc
@@ -0,0 +1,147 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_abstract.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/base/tf/stringUtils.h>
+
+extern "C" {
+#include "BKE_animsys.h"
+#include "BKE_key.h"
+
+#include "DNA_modifier_types.h"
+}
+
+/* TfToken objects are not cheap to construct, so we do it once. */
+namespace usdtokens {
+// Materials
+static const pxr::TfToken diffuse_color("diffuseColor", pxr::TfToken::Immortal);
+static const pxr::TfToken metallic("metallic", pxr::TfToken::Immortal);
+static const pxr::TfToken preview_shader("previewShader", pxr::TfToken::Immortal);
+static const pxr::TfToken preview_surface("UsdPreviewSurface", pxr::TfToken::Immortal);
+static const pxr::TfToken roughness("roughness", pxr::TfToken::Immortal);
+static const pxr::TfToken surface("surface", pxr::TfToken::Immortal);
+} // namespace usdtokens
+
+namespace USD {
+
+USDAbstractWriter::USDAbstractWriter(const USDExporterContext &usd_export_context)
+ : usd_export_context_(usd_export_context),
+ usd_value_writer_(),
+ frame_has_been_written_(false),
+ is_animated_(false)
+{
+}
+
+USDAbstractWriter::~USDAbstractWriter()
+{
+}
+
+bool USDAbstractWriter::is_supported(const HierarchyContext * /*context*/) const
+{
+ return true;
+}
+
+pxr::UsdTimeCode USDAbstractWriter::get_export_time_code() const
+{
+ if (is_animated_) {
+ return usd_export_context_.hierarchy_iterator->get_export_time_code();
+ }
+ // By using the default timecode USD won't even write a single `timeSample` for non-animated
+ // data. Instead, it writes it as non-timesampled.
+ static pxr::UsdTimeCode default_timecode = pxr::UsdTimeCode::Default();
+ return default_timecode;
+}
+
+void USDAbstractWriter::write(HierarchyContext &context)
+{
+ if (!frame_has_been_written_) {
+ is_animated_ = usd_export_context_.export_params.export_animation &&
+ check_is_animated(context);
+ }
+ else if (!is_animated_) {
+ /* A frame has already been written, and without animation one frame is enough. */
+ return;
+ }
+
+ do_write(context);
+
+ frame_has_been_written_ = true;
+}
+
+bool USDAbstractWriter::check_is_animated(const HierarchyContext &context) const
+{
+ const Object *object = context.object;
+
+ if (BKE_animdata_id_is_animated(static_cast<ID *>(object->data))) {
+ return true;
+ }
+ if (BKE_key_from_object(object) != nullptr) {
+ return true;
+ }
+
+ /* Test modifiers. */
+ /* TODO(Sybren): replace this with a check on the depsgraph to properly check for dependency on
+ * time. */
+ ModifierData *md = static_cast<ModifierData *>(object->modifiers.first);
+ while (md) {
+ if (md->type != eModifierType_Subsurf) {
+ return true;
+ }
+ md = md->next;
+ }
+
+ return false;
+}
+
+const pxr::SdfPath &USDAbstractWriter::usd_path() const
+{
+ return usd_export_context_.usd_path;
+}
+
+pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material(Material *material)
+{
+ static pxr::SdfPath material_library_path("/_materials");
+ pxr::UsdStageRefPtr stage = usd_export_context_.stage;
+
+ // Construct the material.
+ pxr::TfToken material_name(usd_export_context_.hierarchy_iterator->get_id_name(&material->id));
+ pxr::SdfPath usd_path = material_library_path.AppendChild(material_name);
+ pxr::UsdShadeMaterial usd_material = pxr::UsdShadeMaterial::Get(stage, usd_path);
+ if (usd_material) {
+ return usd_material;
+ }
+ usd_material = pxr::UsdShadeMaterial::Define(stage, usd_path);
+
+ // Construct the shader.
+ pxr::SdfPath shader_path = usd_path.AppendChild(usdtokens::preview_shader);
+ pxr::UsdShadeShader shader = pxr::UsdShadeShader::Define(stage, shader_path);
+ shader.CreateIdAttr(pxr::VtValue(usdtokens::preview_surface));
+ shader.CreateInput(usdtokens::diffuse_color, pxr::SdfValueTypeNames->Color3f)
+ .Set(pxr::GfVec3f(material->r, material->g, material->b));
+ shader.CreateInput(usdtokens::roughness, pxr::SdfValueTypeNames->Float).Set(material->roughness);
+ shader.CreateInput(usdtokens::metallic, pxr::SdfValueTypeNames->Float).Set(material->metallic);
+
+ // Connect the shader and the material together.
+ usd_material.CreateSurfaceOutput().ConnectToSource(shader, usdtokens::surface);
+
+ return usd_material;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_abstract.h b/source/blender/io/usd/intern/usd_writer_abstract.h
new file mode 100644
index 00000000000..835d3a42c80
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_abstract.h
@@ -0,0 +1,77 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_ABSTRACT_H__
+#define __USD_WRITER_ABSTRACT_H__
+
+#include "usd_exporter_context.h"
+#include "abstract_hierarchy_iterator.h"
+
+#include <pxr/usd/sdf/path.h>
+#include <pxr/usd/usd/stage.h>
+#include <pxr/usd/usdShade/material.h>
+#include <pxr/usd/usdUtils/sparseValueWriter.h>
+
+#include <vector>
+
+extern "C" {
+#include "DEG_depsgraph_query.h"
+#include "DNA_material_types.h"
+}
+
+struct Material;
+struct Object;
+
+namespace USD {
+
+class USDAbstractWriter : public AbstractHierarchyWriter {
+ protected:
+ const USDExporterContext usd_export_context_;
+ pxr::UsdUtilsSparseValueWriter usd_value_writer_;
+
+ bool frame_has_been_written_;
+ bool is_animated_;
+
+ public:
+ USDAbstractWriter(const USDExporterContext &usd_export_context);
+ virtual ~USDAbstractWriter();
+
+ virtual void write(HierarchyContext &context) override;
+
+ /* Returns true if the data to be written is actually supported. This would, for example, allow a
+ * hypothetical camera writer accept a perspective camera but reject an orthogonal one.
+ *
+ * Returning false from a transform writer will prevent the object and all its decendants from
+ * being exported. Returning false from a data writer (object data, hair, or particles) will
+ * only prevent that data from being written (and thus cause the object to be exported as an
+ * Empty). */
+ virtual bool is_supported(const HierarchyContext *context) const;
+
+ const pxr::SdfPath &usd_path() const;
+
+ protected:
+ virtual void do_write(HierarchyContext &context) = 0;
+ virtual bool check_is_animated(const HierarchyContext &context) const;
+ pxr::UsdTimeCode get_export_time_code() const;
+
+ pxr::UsdShadeMaterial ensure_usd_material(Material *material);
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_ABSTRACT_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_camera.cc b/source/blender/io/usd/intern/usd_writer_camera.cc
new file mode 100644
index 00000000000..9b85d69559c
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_camera.cc
@@ -0,0 +1,111 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_camera.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdGeom/camera.h>
+#include <pxr/usd/usdGeom/tokens.h>
+
+extern "C" {
+#include "BKE_camera.h"
+#include "BLI_assert.h"
+
+#include "DNA_camera_types.h"
+#include "DNA_scene_types.h"
+}
+
+namespace USD {
+
+USDCameraWriter::USDCameraWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+bool USDCameraWriter::is_supported(const HierarchyContext *context) const
+{
+ Camera *camera = static_cast<Camera *>(context->object->data);
+ return camera->type == CAM_PERSP;
+}
+
+static void camera_sensor_size_for_render(const Camera *camera,
+ const struct RenderData *rd,
+ float *r_sensor_x,
+ float *r_sensor_y)
+{
+ /* Compute the final image size in pixels. */
+ float sizex = rd->xsch * rd->xasp;
+ float sizey = rd->ysch * rd->yasp;
+
+ int sensor_fit = BKE_camera_sensor_fit(camera->sensor_fit, sizex, sizey);
+
+ switch (sensor_fit) {
+ case CAMERA_SENSOR_FIT_HOR:
+ *r_sensor_x = camera->sensor_x;
+ *r_sensor_y = camera->sensor_x * sizey / sizex;
+ break;
+ case CAMERA_SENSOR_FIT_VERT:
+ *r_sensor_x = camera->sensor_y * sizex / sizey;
+ *r_sensor_y = camera->sensor_y;
+ break;
+ case CAMERA_SENSOR_FIT_AUTO:
+ BLI_assert(!"Camera fit should be either horizontal or vertical");
+ break;
+ }
+}
+
+void USDCameraWriter::do_write(HierarchyContext &context)
+{
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ pxr::UsdGeomCamera usd_camera = pxr::UsdGeomCamera::Define(usd_export_context_.stage,
+ usd_export_context_.usd_path);
+
+ Camera *camera = static_cast<Camera *>(context.object->data);
+ Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
+
+ usd_camera.CreateProjectionAttr().Set(pxr::UsdGeomTokens->perspective);
+
+ /* USD stores the focal length in "millimeters or tenths of world units", because at some point
+ * they decided world units might be centimeters. Quite confusing, as the USD Viewer shows the
+ * correct FoV when we write millimeters and not "tenths of world units".
+ */
+ usd_camera.CreateFocalLengthAttr().Set(camera->lens, timecode);
+
+ float aperture_x, aperture_y;
+ camera_sensor_size_for_render(camera, &scene->r, &aperture_x, &aperture_y);
+
+ float film_aspect = aperture_x / aperture_y;
+ usd_camera.CreateHorizontalApertureAttr().Set(aperture_x, timecode);
+ usd_camera.CreateVerticalApertureAttr().Set(aperture_y, timecode);
+ usd_camera.CreateHorizontalApertureOffsetAttr().Set(aperture_x * camera->shiftx, timecode);
+ usd_camera.CreateVerticalApertureOffsetAttr().Set(aperture_y * camera->shifty * film_aspect,
+ timecode);
+
+ usd_camera.CreateClippingRangeAttr().Set(
+ pxr::VtValue(pxr::GfVec2f(camera->clip_start, camera->clip_end)), timecode);
+
+ // Write DoF-related attributes.
+ if (camera->dof.flag & CAM_DOF_ENABLED) {
+ usd_camera.CreateFStopAttr().Set(camera->dof.aperture_fstop, timecode);
+
+ float focus_distance = scene->unit.scale_length *
+ BKE_camera_object_dof_distance(context.object);
+ usd_camera.CreateFocusDistanceAttr().Set(focus_distance, timecode);
+ }
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_camera.h b/source/blender/io/usd/intern/usd_writer_camera.h
new file mode 100644
index 00000000000..971264ef11e
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_camera.h
@@ -0,0 +1,38 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_CAMERA_H__
+#define __USD_WRITER_CAMERA_H__
+
+#include "usd_writer_abstract.h"
+
+namespace USD {
+
+/* Writer for writing camera data to UsdGeomCamera. */
+class USDCameraWriter : public USDAbstractWriter {
+ public:
+ USDCameraWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual bool is_supported(const HierarchyContext *context) const override;
+ virtual void do_write(HierarchyContext &context) override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_CAMERA_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_hair.cc b/source/blender/io/usd/intern/usd_writer_hair.cc
new file mode 100644
index 00000000000..9251425c0b8
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_hair.cc
@@ -0,0 +1,90 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_hair.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdGeom/basisCurves.h>
+#include <pxr/usd/usdGeom/tokens.h>
+
+extern "C" {
+#include "BKE_particle.h"
+
+#include "DNA_particle_types.h"
+}
+
+namespace USD {
+
+USDHairWriter::USDHairWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+void USDHairWriter::do_write(HierarchyContext &context)
+{
+ ParticleSystem *psys = context.particle_system;
+ ParticleCacheKey **cache = psys->pathcache;
+ if (cache == nullptr) {
+ return;
+ }
+
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ pxr::UsdGeomBasisCurves curves = pxr::UsdGeomBasisCurves::Define(usd_export_context_.stage,
+ usd_export_context_.usd_path);
+
+ // TODO(Sybren): deal with (psys->part->flag & PART_HAIR_BSPLINE)
+ curves.CreateBasisAttr(pxr::VtValue(pxr::UsdGeomTokens->bspline));
+ curves.CreateTypeAttr(pxr::VtValue(pxr::UsdGeomTokens->cubic));
+
+ pxr::VtArray<pxr::GfVec3f> points;
+ pxr::VtIntArray curve_point_counts;
+ curve_point_counts.reserve(psys->totpart);
+
+ ParticleCacheKey *strand;
+ for (int strand_index = 0; strand_index < psys->totpart; ++strand_index) {
+ strand = cache[strand_index];
+
+ int point_count = strand->segments + 1;
+ curve_point_counts.push_back(point_count);
+
+ for (int point_index = 0; point_index < point_count; ++point_index, ++strand) {
+ points.push_back(pxr::GfVec3f(strand->co));
+ }
+ }
+
+ pxr::UsdAttribute attr_points = curves.CreatePointsAttr(pxr::VtValue(), true);
+ pxr::UsdAttribute attr_vertex_counts = curves.CreateCurveVertexCountsAttr(pxr::VtValue(), true);
+ if (!attr_points.HasValue()) {
+ attr_points.Set(points, pxr::UsdTimeCode::Default());
+ attr_vertex_counts.Set(curve_point_counts, pxr::UsdTimeCode::Default());
+ }
+ usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(points), timecode);
+ usd_value_writer_.SetAttribute(attr_vertex_counts, pxr::VtValue(curve_point_counts), timecode);
+
+ if (psys->totpart > 0) {
+ pxr::VtArray<pxr::GfVec3f> colors;
+ colors.push_back(pxr::GfVec3f(cache[0]->col));
+ curves.CreateDisplayColorAttr(pxr::VtValue(colors));
+ }
+}
+
+bool USDHairWriter::check_is_animated(const HierarchyContext &) const
+{
+ return true;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_hair.h b/source/blender/io/usd/intern/usd_writer_hair.h
new file mode 100644
index 00000000000..1e882fa1654
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_hair.h
@@ -0,0 +1,38 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_HAIR_H__
+#define __USD_WRITER_HAIR_H__
+
+#include "usd_writer_abstract.h"
+
+namespace USD {
+
+/* Writer for writing hair particle data as USD curves. */
+class USDHairWriter : public USDAbstractWriter {
+ public:
+ USDHairWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual void do_write(HierarchyContext &context) override;
+ virtual bool check_is_animated(const HierarchyContext &context) const override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_HAIR_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_light.cc b/source/blender/io/usd/intern/usd_writer_light.cc
new file mode 100644
index 00000000000..e13e2c58a79
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_light.cc
@@ -0,0 +1,112 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_light.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdLux/diskLight.h>
+#include <pxr/usd/usdLux/distantLight.h>
+#include <pxr/usd/usdLux/rectLight.h>
+#include <pxr/usd/usdLux/sphereLight.h>
+
+extern "C" {
+#include "BLI_assert.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_light_types.h"
+#include "DNA_object_types.h"
+}
+
+namespace USD {
+
+USDLightWriter::USDLightWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+bool USDLightWriter::is_supported(const HierarchyContext *context) const
+{
+ Light *light = static_cast<Light *>(context->object->data);
+ return ELEM(light->type, LA_AREA, LA_LOCAL, LA_SUN);
+}
+
+void USDLightWriter::do_write(HierarchyContext &context)
+{
+ pxr::UsdStageRefPtr stage = usd_export_context_.stage;
+ const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
+ pxr::UsdTimeCode timecode = get_export_time_code();
+
+ Light *light = static_cast<Light *>(context.object->data);
+ pxr::UsdLuxLight usd_light;
+
+ switch (light->type) {
+ case LA_AREA:
+ switch (light->area_shape) {
+ case LA_AREA_DISK:
+ case LA_AREA_ELLIPSE: { /* An ellipse light will deteriorate into a disk light. */
+ pxr::UsdLuxDiskLight disk_light = pxr::UsdLuxDiskLight::Define(stage, usd_path);
+ disk_light.CreateRadiusAttr().Set(light->area_size, timecode);
+ usd_light = disk_light;
+ break;
+ }
+ case LA_AREA_RECT: {
+ pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path);
+ rect_light.CreateWidthAttr().Set(light->area_size, timecode);
+ rect_light.CreateHeightAttr().Set(light->area_sizey, timecode);
+ usd_light = rect_light;
+ break;
+ }
+ case LA_AREA_SQUARE: {
+ pxr::UsdLuxRectLight rect_light = pxr::UsdLuxRectLight::Define(stage, usd_path);
+ rect_light.CreateWidthAttr().Set(light->area_size, timecode);
+ rect_light.CreateHeightAttr().Set(light->area_size, timecode);
+ usd_light = rect_light;
+ break;
+ }
+ }
+ break;
+ case LA_LOCAL: {
+ pxr::UsdLuxSphereLight sphere_light = pxr::UsdLuxSphereLight::Define(stage, usd_path);
+ sphere_light.CreateRadiusAttr().Set(light->area_size, timecode);
+ usd_light = sphere_light;
+ break;
+ }
+ case LA_SUN:
+ usd_light = pxr::UsdLuxDistantLight::Define(stage, usd_path);
+ break;
+ default:
+ BLI_assert(!"is_supported() returned true for unsupported light type");
+ }
+
+ /* Scale factor to get to somewhat-similar illumination. Since the USDViewer had similar
+ * over-exposure as Blender Internal with the same values, this code applies the reverse of the
+ * versioning code in light_emission_unify(). */
+ float usd_intensity;
+ if (light->type == LA_SUN) {
+ /* Untested, as the Hydra GL viewport of USDViewer doesn't support distant lights. */
+ usd_intensity = light->energy;
+ }
+ else {
+ usd_intensity = light->energy / 100.f;
+ }
+ usd_light.CreateIntensityAttr().Set(usd_intensity, timecode);
+
+ usd_light.CreateColorAttr().Set(pxr::GfVec3f(light->r, light->g, light->b), timecode);
+ usd_light.CreateSpecularAttr().Set(light->spec_fac, timecode);
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_light.h b/source/blender/io/usd/intern/usd_writer_light.h
new file mode 100644
index 00000000000..349c034b6bc
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_light.h
@@ -0,0 +1,37 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_LIGHT_H__
+#define __USD_WRITER_LIGHT_H__
+
+#include "usd_writer_abstract.h"
+
+namespace USD {
+
+class USDLightWriter : public USDAbstractWriter {
+ public:
+ USDLightWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual bool is_supported(const HierarchyContext *context) const override;
+ virtual void do_write(HierarchyContext &context) override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_LIGHT_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_mesh.cc b/source/blender/io/usd/intern/usd_writer_mesh.cc
new file mode 100644
index 00000000000..74005afaf31
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_mesh.cc
@@ -0,0 +1,489 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_mesh.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdGeom/mesh.h>
+#include <pxr/usd/usdShade/material.h>
+#include <pxr/usd/usdShade/materialBindingAPI.h>
+
+extern "C" {
+#include "BLI_assert.h"
+#include "BLI_math_vector.h"
+
+#include "BKE_anim.h"
+#include "BKE_customdata.h"
+#include "BKE_lib_id.h"
+#include "BKE_material.h"
+#include "BKE_mesh.h"
+#include "BKE_modifier.h"
+#include "BKE_object.h"
+
+#include "DEG_depsgraph.h"
+
+#include "DNA_layer_types.h"
+#include "DNA_mesh_types.h"
+#include "DNA_meshdata_types.h"
+#include "DNA_modifier_types.h"
+#include "DNA_object_fluidsim_types.h"
+#include "DNA_particle_types.h"
+}
+
+namespace USD {
+
+USDGenericMeshWriter::USDGenericMeshWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+bool USDGenericMeshWriter::is_supported(const HierarchyContext *context) const
+{
+ Object *object = context->object;
+ bool is_dupli = context->duplicator != nullptr;
+ int base_flag;
+
+ if (is_dupli) {
+ /* Construct the object's base flags from its dupliparent, just like is done in
+ * deg_objects_dupli_iterator_next(). Without this, the visibility check below will fail. Doing
+ * this here, instead of a more suitable location in AbstractHierarchyIterator, prevents
+ * copying the Object for every dupli. */
+ base_flag = object->base_flag;
+ object->base_flag = context->duplicator->base_flag | BASE_FROM_DUPLI;
+ }
+
+ int visibility = BKE_object_visibility(object,
+ usd_export_context_.export_params.evaluation_mode);
+
+ if (is_dupli) {
+ object->base_flag = base_flag;
+ }
+
+ return (visibility & OB_VISIBLE_SELF) != 0;
+}
+
+void USDGenericMeshWriter::do_write(HierarchyContext &context)
+{
+ Object *object_eval = context.object;
+ bool needsfree = false;
+ Mesh *mesh = get_export_mesh(object_eval, needsfree);
+
+ if (mesh == NULL) {
+ return;
+ }
+
+ try {
+ write_mesh(context, mesh);
+
+ if (needsfree) {
+ free_export_mesh(mesh);
+ }
+ }
+ catch (...) {
+ if (needsfree) {
+ free_export_mesh(mesh);
+ }
+ throw;
+ }
+}
+
+void USDGenericMeshWriter::free_export_mesh(Mesh *mesh)
+{
+ BKE_id_free(NULL, mesh);
+}
+
+struct USDMeshData {
+ pxr::VtArray<pxr::GfVec3f> points;
+ pxr::VtIntArray face_vertex_counts;
+ pxr::VtIntArray face_indices;
+ std::map<short, pxr::VtIntArray> face_groups;
+
+ /* The length of this array specifies the number of creases on the surface. Each element gives
+ * the number of (must be adjacent) vertices in each crease, whose indices are linearly laid out
+ * in the 'creaseIndices' attribute. Since each crease must be at least one edge long, each
+ * element of this array should be greater than one. */
+ pxr::VtIntArray crease_lengths;
+ /* The indices of all vertices forming creased edges. The size of this array must be equal to the
+ * sum of all elements of the 'creaseLengths' attribute. */
+ pxr::VtIntArray crease_vertex_indices;
+ /* The per-crease or per-edge sharpness for all creases (Usd.Mesh.SHARPNESS_INFINITE for a
+ * perfectly sharp crease). Since 'creaseLengths' encodes the number of vertices in each crease,
+ * the number of elements in this array will be either len(creaseLengths) or the sum over all X
+ * of (creaseLengths[X] - 1). Note that while the RI spec allows each crease to have either a
+ * single sharpness or a value per-edge, USD will encode either a single sharpness per crease on
+ * a mesh, or sharpnesses for all edges making up the creases on a mesh. */
+ pxr::VtFloatArray crease_sharpnesses;
+};
+
+void USDGenericMeshWriter::write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
+{
+ pxr::UsdTimeCode timecode = get_export_time_code();
+
+ const CustomData *ldata = &mesh->ldata;
+ for (int layer_idx = 0; layer_idx < ldata->totlayer; layer_idx++) {
+ const CustomDataLayer *layer = &ldata->layers[layer_idx];
+ if (layer->type != CD_MLOOPUV) {
+ continue;
+ }
+
+ /* UV coordinates are stored in a Primvar on the Mesh, and can be referenced from materials.
+ * The primvar name is the same as the UV Map name. This is to allow the standard name "st"
+ * for texture coordinates by naming the UV Map as such, without having to guess which UV Map
+ * is the "standard" one. */
+ pxr::TfToken primvar_name(pxr::TfMakeValidIdentifier(layer->name));
+ pxr::UsdGeomPrimvar uv_coords_primvar = usd_mesh.CreatePrimvar(
+ primvar_name, pxr::SdfValueTypeNames->TexCoord2fArray, pxr::UsdGeomTokens->faceVarying);
+
+ MLoopUV *mloopuv = static_cast<MLoopUV *>(layer->data);
+ pxr::VtArray<pxr::GfVec2f> uv_coords;
+ for (int loop_idx = 0; loop_idx < mesh->totloop; loop_idx++) {
+ uv_coords.push_back(pxr::GfVec2f(mloopuv[loop_idx].uv));
+ }
+
+ if (!uv_coords_primvar.HasValue()) {
+ uv_coords_primvar.Set(uv_coords, pxr::UsdTimeCode::Default());
+ }
+ const pxr::UsdAttribute &uv_coords_attr = uv_coords_primvar.GetAttr();
+ usd_value_writer_.SetAttribute(uv_coords_attr, pxr::VtValue(uv_coords), timecode);
+ }
+}
+
+void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
+{
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ pxr::UsdTimeCode defaultTime = pxr::UsdTimeCode::Default();
+ pxr::UsdStageRefPtr stage = usd_export_context_.stage;
+ const pxr::SdfPath &usd_path = usd_export_context_.usd_path;
+
+ pxr::UsdGeomMesh usd_mesh = pxr::UsdGeomMesh::Define(stage, usd_path);
+ USDMeshData usd_mesh_data;
+ get_geometry_data(mesh, usd_mesh_data);
+
+ if (usd_export_context_.export_params.use_instancing && context.is_instance()) {
+ // This object data is instanced, just reference the original instead of writing a copy.
+ if (context.export_path == context.original_export_path) {
+ printf("USD ref error: export path is reference path: %s\n", context.export_path.c_str());
+ BLI_assert(!"USD reference error");
+ return;
+ }
+ pxr::SdfPath ref_path(context.original_export_path);
+ if (!usd_mesh.GetPrim().GetReferences().AddInternalReference(ref_path)) {
+ /* See this URL for a description fo why referencing may fail"
+ * https://graphics.pixar.com/usd/docs/api/class_usd_references.html#Usd_Failing_References
+ */
+ printf("USD Export warning: unable to add reference from %s to %s, not instancing object\n",
+ context.export_path.c_str(),
+ context.original_export_path.c_str());
+ return;
+ }
+ /* The material path will be of the form </_materials/{material name}>, which is outside the
+ subtree pointed to by ref_path. As a result, the referenced data is not allowed to point out
+ of its own subtree. It does work when we override the material with exactly the same path,
+ though.*/
+ if (usd_export_context_.export_params.export_materials) {
+ assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
+ }
+ return;
+ }
+
+ pxr::UsdAttribute attr_points = usd_mesh.CreatePointsAttr(pxr::VtValue(), true);
+ pxr::UsdAttribute attr_face_vertex_counts = usd_mesh.CreateFaceVertexCountsAttr(pxr::VtValue(),
+ true);
+ pxr::UsdAttribute attr_face_vertex_indices = usd_mesh.CreateFaceVertexIndicesAttr(pxr::VtValue(),
+ true);
+
+ if (!attr_points.HasValue()) {
+ // Provide the initial value as default. This makes USD write the value as constant if they
+ // don't change over time.
+ attr_points.Set(usd_mesh_data.points, defaultTime);
+ attr_face_vertex_counts.Set(usd_mesh_data.face_vertex_counts, defaultTime);
+ attr_face_vertex_indices.Set(usd_mesh_data.face_indices, defaultTime);
+ }
+
+ usd_value_writer_.SetAttribute(attr_points, pxr::VtValue(usd_mesh_data.points), timecode);
+ usd_value_writer_.SetAttribute(
+ attr_face_vertex_counts, pxr::VtValue(usd_mesh_data.face_vertex_counts), timecode);
+ usd_value_writer_.SetAttribute(
+ attr_face_vertex_indices, pxr::VtValue(usd_mesh_data.face_indices), timecode);
+
+ if (!usd_mesh_data.crease_lengths.empty()) {
+ pxr::UsdAttribute attr_crease_lengths = usd_mesh.CreateCreaseLengthsAttr(pxr::VtValue(), true);
+ pxr::UsdAttribute attr_crease_indices = usd_mesh.CreateCreaseIndicesAttr(pxr::VtValue(), true);
+ pxr::UsdAttribute attr_crease_sharpness = usd_mesh.CreateCreaseSharpnessesAttr(pxr::VtValue(),
+ true);
+
+ if (!attr_crease_lengths.HasValue()) {
+ attr_crease_lengths.Set(usd_mesh_data.crease_lengths, defaultTime);
+ attr_crease_indices.Set(usd_mesh_data.crease_vertex_indices, defaultTime);
+ attr_crease_sharpness.Set(usd_mesh_data.crease_sharpnesses, defaultTime);
+ }
+
+ usd_value_writer_.SetAttribute(
+ attr_crease_lengths, pxr::VtValue(usd_mesh_data.crease_lengths), timecode);
+ usd_value_writer_.SetAttribute(
+ attr_crease_indices, pxr::VtValue(usd_mesh_data.crease_vertex_indices), timecode);
+ usd_value_writer_.SetAttribute(
+ attr_crease_sharpness, pxr::VtValue(usd_mesh_data.crease_sharpnesses), timecode);
+ }
+
+ if (usd_export_context_.export_params.export_uvmaps) {
+ write_uv_maps(mesh, usd_mesh);
+ }
+ if (usd_export_context_.export_params.export_normals) {
+ write_normals(mesh, usd_mesh);
+ }
+ write_surface_velocity(context.object, mesh, usd_mesh);
+
+ // TODO(Sybren): figure out what happens when the face groups change.
+ if (frame_has_been_written_) {
+ return;
+ }
+
+ usd_mesh.CreateSubdivisionSchemeAttr().Set(pxr::UsdGeomTokens->none);
+
+ if (usd_export_context_.export_params.export_materials) {
+ assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
+ }
+}
+
+static void get_vertices(const Mesh *mesh, USDMeshData &usd_mesh_data)
+{
+ usd_mesh_data.points.reserve(mesh->totvert);
+
+ const MVert *verts = mesh->mvert;
+ for (int i = 0; i < mesh->totvert; ++i) {
+ usd_mesh_data.points.push_back(pxr::GfVec3f(verts[i].co));
+ }
+}
+
+static void get_loops_polys(const Mesh *mesh, USDMeshData &usd_mesh_data)
+{
+ /* Only construct face groups (a.k.a. geometry subsets) when we need them for material
+ * assignments. */
+ bool construct_face_groups = mesh->totcol > 1;
+
+ usd_mesh_data.face_vertex_counts.reserve(mesh->totpoly);
+ usd_mesh_data.face_indices.reserve(mesh->totloop);
+
+ MLoop *mloop = mesh->mloop;
+ MPoly *mpoly = mesh->mpoly;
+ for (int i = 0; i < mesh->totpoly; ++i, ++mpoly) {
+ MLoop *loop = mloop + mpoly->loopstart;
+ usd_mesh_data.face_vertex_counts.push_back(mpoly->totloop);
+ for (int j = 0; j < mpoly->totloop; ++j, ++loop) {
+ usd_mesh_data.face_indices.push_back(loop->v);
+ }
+
+ if (construct_face_groups) {
+ usd_mesh_data.face_groups[mpoly->mat_nr].push_back(i);
+ }
+ }
+}
+
+static void get_creases(const Mesh *mesh, USDMeshData &usd_mesh_data)
+{
+ const float factor = 1.0f / 255.0f;
+
+ MEdge *edge = mesh->medge;
+ float sharpness;
+ for (int edge_idx = 0, totedge = mesh->totedge; edge_idx < totedge; ++edge_idx, ++edge) {
+ if (edge->crease == 0) {
+ continue;
+ }
+
+ if (edge->crease == 255) {
+ sharpness = pxr::UsdGeomMesh::SHARPNESS_INFINITE;
+ }
+ else {
+ sharpness = static_cast<float>(edge->crease) * factor;
+ }
+
+ usd_mesh_data.crease_vertex_indices.push_back(edge->v1);
+ usd_mesh_data.crease_vertex_indices.push_back(edge->v2);
+ usd_mesh_data.crease_lengths.push_back(2);
+ usd_mesh_data.crease_sharpnesses.push_back(sharpness);
+ }
+}
+
+void USDGenericMeshWriter::get_geometry_data(const Mesh *mesh, USDMeshData &usd_mesh_data)
+{
+ get_vertices(mesh, usd_mesh_data);
+ get_loops_polys(mesh, usd_mesh_data);
+ get_creases(mesh, usd_mesh_data);
+}
+
+void USDGenericMeshWriter::assign_materials(const HierarchyContext &context,
+ pxr::UsdGeomMesh usd_mesh,
+ const MaterialFaceGroups &usd_face_groups)
+{
+ if (context.object->totcol == 0) {
+ return;
+ }
+
+ /* Binding a material to a geometry subset isn't supported by the Hydra GL viewport yet,
+ * which is why we always bind the first material to the entire mesh. See
+ * https://github.com/PixarAnimationStudios/USD/issues/542 for more info. */
+ bool mesh_material_bound = false;
+ for (short mat_num = 0; mat_num < context.object->totcol; mat_num++) {
+ Material *material = BKE_object_material_get(context.object, mat_num + 1);
+ if (material == nullptr) {
+ continue;
+ }
+
+ pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
+ usd_material.Bind(usd_mesh.GetPrim());
+
+ /* USD seems to support neither per-material nor per-face-group double-sidedness, so we just
+ * use the flag from the first non-empty material slot. */
+ usd_mesh.CreateDoubleSidedAttr(
+ pxr::VtValue((material->blend_flag & MA_BL_CULL_BACKFACE) == 0));
+
+ mesh_material_bound = true;
+ break;
+ }
+
+ if (!mesh_material_bound) {
+ /* Blender defaults to double-sided, but USD to single-sided. */
+ usd_mesh.CreateDoubleSidedAttr(pxr::VtValue(true));
+ }
+
+ if (!mesh_material_bound || usd_face_groups.size() < 2) {
+ /* Either all material slots were empty or there is only one material in use. As geometry
+ * subsets are only written when actually used to assign a material, and the mesh already has
+ * the material assigned, there is no need to continue. */
+ return;
+ }
+
+ // Define a geometry subset per material.
+ for (const MaterialFaceGroups::value_type &face_group : usd_face_groups) {
+ short material_number = face_group.first;
+ const pxr::VtIntArray &face_indices = face_group.second;
+
+ Material *material = BKE_object_material_get(context.object, material_number + 1);
+ if (material == nullptr) {
+ continue;
+ }
+
+ pxr::UsdShadeMaterial usd_material = ensure_usd_material(material);
+ pxr::TfToken material_name = usd_material.GetPath().GetNameToken();
+
+ pxr::UsdShadeMaterialBindingAPI api = pxr::UsdShadeMaterialBindingAPI(usd_mesh);
+ pxr::UsdGeomSubset usd_face_subset = api.CreateMaterialBindSubset(material_name, face_indices);
+ usd_material.Bind(usd_face_subset.GetPrim());
+ }
+}
+
+void USDGenericMeshWriter::write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh)
+{
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ const float(*lnors)[3] = static_cast<float(*)[3]>(CustomData_get_layer(&mesh->ldata, CD_NORMAL));
+
+ pxr::VtVec3fArray loop_normals;
+ loop_normals.reserve(mesh->totloop);
+
+ if (lnors != nullptr) {
+ /* Export custom loop normals. */
+ for (int loop_idx = 0, totloop = mesh->totloop; loop_idx < totloop; ++loop_idx) {
+ loop_normals.push_back(pxr::GfVec3f(lnors[loop_idx]));
+ }
+ }
+ else {
+ /* Compute the loop normals based on the 'smooth' flag. */
+ float normal[3];
+ MPoly *mpoly = mesh->mpoly;
+ const MVert *mvert = mesh->mvert;
+ for (int poly_idx = 0, totpoly = mesh->totpoly; poly_idx < totpoly; ++poly_idx, ++mpoly) {
+ MLoop *mloop = mesh->mloop + mpoly->loopstart;
+
+ if ((mpoly->flag & ME_SMOOTH) == 0) {
+ /* Flat shaded, use common normal for all verts. */
+ BKE_mesh_calc_poly_normal(mpoly, mloop, mvert, normal);
+ pxr::GfVec3f pxr_normal(normal);
+ for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx) {
+ loop_normals.push_back(pxr_normal);
+ }
+ }
+ else {
+ /* Smooth shaded, use individual vert normals. */
+ for (int loop_idx = 0; loop_idx < mpoly->totloop; ++loop_idx, ++mloop) {
+ normal_short_to_float_v3(normal, mvert[mloop->v].no);
+ loop_normals.push_back(pxr::GfVec3f(normal));
+ }
+ }
+ }
+ }
+
+ pxr::UsdAttribute attr_normals = usd_mesh.CreateNormalsAttr(pxr::VtValue(), true);
+ if (!attr_normals.HasValue()) {
+ attr_normals.Set(loop_normals, pxr::UsdTimeCode::Default());
+ }
+ usd_value_writer_.SetAttribute(attr_normals, pxr::VtValue(loop_normals), timecode);
+ usd_mesh.SetNormalsInterpolation(pxr::UsdGeomTokens->faceVarying);
+}
+
+void USDGenericMeshWriter::write_surface_velocity(Object *object,
+ const Mesh *mesh,
+ pxr::UsdGeomMesh usd_mesh)
+{
+ /* Only velocities from the fluid simulation are exported. This is the most important case,
+ * though, as the baked mesh changes topology all the time, and thus computing the velocities
+ * at import time in a post-processing step is hard. */
+ ModifierData *md = modifiers_findByType(object, eModifierType_Fluidsim);
+ if (md == nullptr) {
+ return;
+ }
+
+ /* Check that the fluid sim modifier is enabled and has useful data. */
+ const bool use_render = (DEG_get_mode(usd_export_context_.depsgraph) == DAG_EVAL_RENDER);
+ const ModifierMode required_mode = use_render ? eModifierMode_Render : eModifierMode_Realtime;
+ const Scene *scene = DEG_get_evaluated_scene(usd_export_context_.depsgraph);
+ if (!modifier_isEnabled(scene, md, required_mode)) {
+ return;
+ }
+ FluidsimModifierData *fsmd = reinterpret_cast<FluidsimModifierData *>(md);
+ if (!fsmd->fss || fsmd->fss->type != OB_FLUIDSIM_DOMAIN) {
+ return;
+ }
+ FluidsimSettings *fss = fsmd->fss;
+ if (!fss->meshVelocities) {
+ return;
+ }
+
+ /* Export per-vertex velocity vectors. */
+ pxr::VtVec3fArray usd_velocities;
+ usd_velocities.reserve(mesh->totvert);
+
+ FluidVertexVelocity *mesh_velocities = fss->meshVelocities;
+ for (int vertex_idx = 0, totvert = mesh->totvert; vertex_idx < totvert;
+ ++vertex_idx, ++mesh_velocities) {
+ usd_velocities.push_back(pxr::GfVec3f(mesh_velocities->vel));
+ }
+
+ pxr::UsdTimeCode timecode = get_export_time_code();
+ usd_mesh.CreateVelocitiesAttr().Set(usd_velocities, timecode);
+}
+
+USDMeshWriter::USDMeshWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx)
+{
+}
+
+Mesh *USDMeshWriter::get_export_mesh(Object *object_eval, bool & /*r_needsfree*/)
+{
+ return BKE_object_get_evaluated_mesh(object_eval);
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_mesh.h b/source/blender/io/usd/intern/usd_writer_mesh.h
new file mode 100644
index 00000000000..4175e2b7e27
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_mesh.h
@@ -0,0 +1,66 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_MESH_H__
+#define __USD_WRITER_MESH_H__
+
+#include "usd_writer_abstract.h"
+
+#include <pxr/usd/usdGeom/mesh.h>
+
+namespace USD {
+
+struct USDMeshData;
+
+/* Writer for USD geometry. Does not assume the object is a mesh object. */
+class USDGenericMeshWriter : public USDAbstractWriter {
+ public:
+ USDGenericMeshWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual bool is_supported(const HierarchyContext *context) const override;
+ virtual void do_write(HierarchyContext &context) override;
+
+ virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) = 0;
+ virtual void free_export_mesh(Mesh *mesh);
+
+ private:
+ /* Mapping from material slot number to array of face indices with that material. */
+ typedef std::map<short, pxr::VtIntArray> MaterialFaceGroups;
+
+ void write_mesh(HierarchyContext &context, Mesh *mesh);
+ void get_geometry_data(const Mesh *mesh, struct USDMeshData &usd_mesh_data);
+ void assign_materials(const HierarchyContext &context,
+ pxr::UsdGeomMesh usd_mesh,
+ const MaterialFaceGroups &usd_face_groups);
+ void write_uv_maps(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
+ void write_normals(const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
+ void write_surface_velocity(Object *object, const Mesh *mesh, pxr::UsdGeomMesh usd_mesh);
+};
+
+class USDMeshWriter : public USDGenericMeshWriter {
+ public:
+ USDMeshWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_MESH_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_metaball.cc b/source/blender/io/usd/intern/usd_writer_metaball.cc
new file mode 100644
index 00000000000..25b216d20f3
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_metaball.cc
@@ -0,0 +1,81 @@
+/*
+ * 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) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_metaball.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/usd/usdGeom/mesh.h>
+#include <pxr/usd/usdShade/material.h>
+#include <pxr/usd/usdShade/materialBindingAPI.h>
+
+extern "C" {
+#include "BLI_assert.h"
+
+#include "BKE_displist.h"
+#include "BKE_lib_id.h"
+#include "BKE_mball.h"
+#include "BKE_mesh.h"
+#include "BKE_object.h"
+
+#include "DNA_mesh_types.h"
+#include "DNA_meta_types.h"
+}
+
+namespace USD {
+
+USDMetaballWriter::USDMetaballWriter(const USDExporterContext &ctx) : USDGenericMeshWriter(ctx)
+{
+}
+
+bool USDMetaballWriter::is_supported(const HierarchyContext *context) const
+{
+ Scene *scene = DEG_get_input_scene(usd_export_context_.depsgraph);
+ return is_basis_ball(scene, context->object) && USDGenericMeshWriter::is_supported(context);
+}
+
+bool USDMetaballWriter::check_is_animated(const HierarchyContext & /*context*/) const
+{
+ /* We assume that metaballs are always animated, as the current object may
+ * not be animated but another ball in the same group may be. */
+ return true;
+}
+
+Mesh *USDMetaballWriter::get_export_mesh(Object *object_eval, bool &r_needsfree)
+{
+ Mesh *mesh_eval = BKE_object_get_evaluated_mesh(object_eval);
+ if (mesh_eval != nullptr) {
+ /* Mesh_eval only exists when generative modifiers are in use. */
+ r_needsfree = false;
+ return mesh_eval;
+ }
+ r_needsfree = true;
+ return BKE_mesh_new_from_object(usd_export_context_.depsgraph, object_eval, false);
+}
+
+void USDMetaballWriter::free_export_mesh(Mesh *mesh)
+{
+ BKE_id_free(nullptr, mesh);
+}
+
+bool USDMetaballWriter::is_basis_ball(Scene *scene, Object *ob) const
+{
+ Object *basis_ob = BKE_mball_basis_find(scene, ob);
+ return ob == basis_ob;
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_metaball.h b/source/blender/io/usd/intern/usd_writer_metaball.h
new file mode 100644
index 00000000000..1a86daae2ae
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_metaball.h
@@ -0,0 +1,42 @@
+/*
+ * 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) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_METABALL_H__
+#define __USD_WRITER_METABALL_H__
+
+#include "usd_writer_mesh.h"
+
+namespace USD {
+
+class USDMetaballWriter : public USDGenericMeshWriter {
+ public:
+ USDMetaballWriter(const USDExporterContext &ctx);
+
+ protected:
+ virtual Mesh *get_export_mesh(Object *object_eval, bool &r_needsfree) override;
+ virtual void free_export_mesh(Mesh *mesh) override;
+ virtual bool is_supported(const HierarchyContext *context) const override;
+ virtual bool check_is_animated(const HierarchyContext &context) const override;
+
+ private:
+ bool is_basis_ball(Scene *scene, Object *ob) const;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_METABALL_H__ */
diff --git a/source/blender/io/usd/intern/usd_writer_transform.cc b/source/blender/io/usd/intern/usd_writer_transform.cc
new file mode 100644
index 00000000000..321b516221a
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_transform.cc
@@ -0,0 +1,64 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#include "usd_writer_transform.h"
+#include "usd_hierarchy_iterator.h"
+
+#include <pxr/base/gf/matrix4f.h>
+#include <pxr/usd/usdGeom/xform.h>
+
+extern "C" {
+#include "BKE_object.h"
+
+#include "BLI_math_matrix.h"
+
+#include "DNA_layer_types.h"
+}
+
+namespace USD {
+
+USDTransformWriter::USDTransformWriter(const USDExporterContext &ctx) : USDAbstractWriter(ctx)
+{
+}
+
+void USDTransformWriter::do_write(HierarchyContext &context)
+{
+ float parent_relative_matrix[4][4]; // The object matrix relative to the parent.
+ mul_m4_m4m4(parent_relative_matrix, context.parent_matrix_inv_world, context.matrix_world);
+
+ // Write the transform relative to the parent.
+ pxr::UsdGeomXform xform = pxr::UsdGeomXform::Define(usd_export_context_.stage,
+ usd_export_context_.usd_path);
+ if (!xformOp_) {
+ xformOp_ = xform.AddTransformOp();
+ }
+ xformOp_.Set(pxr::GfMatrix4d(parent_relative_matrix), get_export_time_code());
+}
+
+bool USDTransformWriter::check_is_animated(const HierarchyContext &context) const
+{
+ if (context.duplicator != NULL) {
+ /* This object is being duplicated, so could be emitted by a particle system and thus
+ * influenced by forces. TODO(Sybren): Make this more strict. Probably better to get from the
+ * depsgraph whether this object instance has a time source. */
+ return true;
+ }
+ return BKE_object_moves_in_time(context.object, context.animation_check_include_parent);
+}
+
+} // namespace USD
diff --git a/source/blender/io/usd/intern/usd_writer_transform.h b/source/blender/io/usd/intern/usd_writer_transform.h
new file mode 100644
index 00000000000..52c4a657f33
--- /dev/null
+++ b/source/blender/io/usd/intern/usd_writer_transform.h
@@ -0,0 +1,42 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+#ifndef __USD_WRITER_TRANSFORM_H__
+#define __USD_WRITER_TRANSFORM_H__
+
+#include "usd_writer_abstract.h"
+
+#include <pxr/usd/usdGeom/xform.h>
+
+namespace USD {
+
+class USDTransformWriter : public USDAbstractWriter {
+ private:
+ pxr::UsdGeomXformOp xformOp_;
+
+ public:
+ USDTransformWriter(const USDExporterContext &ctx);
+
+ protected:
+ void do_write(HierarchyContext &context) override;
+ bool check_is_animated(const HierarchyContext &context) const override;
+};
+
+} // namespace USD
+
+#endif /* __USD_WRITER_TRANSFORM_H__ */
diff --git a/source/blender/io/usd/usd.h b/source/blender/io/usd/usd.h
new file mode 100644
index 00000000000..8a5575d53cf
--- /dev/null
+++ b/source/blender/io/usd/usd.h
@@ -0,0 +1,63 @@
+/*
+ * 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) 2019 Blender Foundation.
+ * All rights reserved.
+ */
+
+#ifndef __USD_H__
+#define __USD_H__
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include "DEG_depsgraph.h"
+
+struct Scene;
+struct bContext;
+
+struct USDExportParams {
+ bool export_animation;
+ bool export_hair;
+ bool export_uvmaps;
+ bool export_normals;
+ bool export_materials;
+ bool selected_objects_only;
+ bool use_instancing;
+ enum eEvaluationMode evaluation_mode;
+};
+
+/* The USD_export takes a as_background_job parameter, and returns a boolean.
+ *
+ * When as_background_job=true, returns false immediately after scheduling
+ * a background job.
+ *
+ * When as_background_job=false, performs the export synchronously, and returns
+ * true when the export was ok, and false if there were any errors.
+ */
+
+bool USD_export(struct bContext *C,
+ const char *filepath,
+ const struct USDExportParams *params,
+ bool as_background_job);
+
+int USD_get_version(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __USD_H__ */