Add Chromium-only Blender WebEngine parity work
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup Alembic
|
||||
*/
|
||||
|
||||
#include "abc_axis_conversion.h"
|
||||
|
||||
#include "BLI_assert.h"
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_math_vector.h"
|
||||
|
||||
#include "BKE_object_types.hh"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
namespace blender::io::alembic {
|
||||
|
||||
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);
|
||||
} // namespace
|
||||
// alembicvoidcreate_swapped_rotation_matrix(floatrot_x_mat[3][3],floatrot_y_mat[3][3],floatrot_z_mat[3][3],constfloateuler[3],AbcAxisSwapModemode)
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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->runtime->world_to_object.ptr(),
|
||||
obj->parent->object_to_world().ptr());
|
||||
mul_m4_m4m4(zup_mat, obj->parent->world_to_object().ptr(), obj->object_to_world().ptr());
|
||||
}
|
||||
else {
|
||||
copy_m4_m4(zup_mat, obj->object_to_world().ptr());
|
||||
}
|
||||
|
||||
if (proxy_from) {
|
||||
mul_m4_m4m4(zup_mat, proxy_from->object_to_world().ptr(), zup_mat);
|
||||
}
|
||||
|
||||
copy_m44_axis_swap(r_yup_mat, zup_mat, ABC_YUP_FROM_ZUP);
|
||||
}
|
||||
|
||||
} // namespace blender::io::alembic
|
||||
@@ -0,0 +1,98 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Kévin Dietrich & Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup Alembic
|
||||
*/
|
||||
|
||||
#include "BLI_compiler_compat.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Object;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
/* 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. */
|
||||
|
||||
BLI_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;
|
||||
}
|
||||
|
||||
BLI_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. */
|
||||
|
||||
BLI_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;
|
||||
}
|
||||
|
||||
BLI_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(). */
|
||||
|
||||
enum AbcAxisSwapMode {
|
||||
ABC_ZUP_FROM_YUP = 1,
|
||||
ABC_YUP_FROM_ZUP = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
enum AbcMatrixMode {
|
||||
ABC_MATRIX_WORLD = 1,
|
||||
ABC_MATRIX_LOCAL = 2,
|
||||
};
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
647
blender-5.2.0/source/blender/io/alembic/intern/abc_customdata.cc
Normal file
647
blender-5.2.0/source/blender/io/alembic/intern/abc_customdata.cc
Normal file
@@ -0,0 +1,647 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Kévin Dietrich. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_customdata.h"
|
||||
#include "BLI_color_types.hh"
|
||||
#include "abc_axis_conversion.h"
|
||||
#include "abc_util.h"
|
||||
|
||||
#include <Alembic/Abc/ICompoundProperty.h>
|
||||
#include <Alembic/Abc/ISampleSelector.h>
|
||||
#include <Alembic/Abc/OCompoundProperty.h>
|
||||
#include <Alembic/Abc/TypedArraySample.h>
|
||||
#include <Alembic/AbcCoreAbstract/PropertyHeader.h>
|
||||
#include <Alembic/AbcGeom/GeometryScope.h>
|
||||
#include <Alembic/AbcGeom/IGeomParam.h>
|
||||
#include <Alembic/AbcGeom/OGeomParam.h>
|
||||
|
||||
#include "DNA_customdata_types.h"
|
||||
#include "DNA_mesh_types.h"
|
||||
#include "DNA_meshdata_types.h"
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
#include "BLI_math_vector.h"
|
||||
#include "BLI_math_vector_types.hh"
|
||||
#include "BLI_utildefines.h"
|
||||
|
||||
#include "BKE_attribute.h"
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_customdata.hh"
|
||||
#include "BKE_mesh.hh"
|
||||
|
||||
#include "IO_validate.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
/* 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::kVaryingScope;
|
||||
using Alembic::AbcGeom::kVertexScope;
|
||||
|
||||
using Alembic::Abc::C4fArraySample;
|
||||
using Alembic::Abc::UInt32ArraySample;
|
||||
using Alembic::Abc::V2fArraySample;
|
||||
|
||||
using Alembic::AbcGeom::OC4fGeomParam;
|
||||
using Alembic::AbcGeom::OV2fGeomParam;
|
||||
using Alembic::AbcGeom::OV3fGeomParam;
|
||||
namespace io::alembic {
|
||||
|
||||
/* ORCO, Generated Coordinates, and Reference Points ("Pref") are all terms for the same thing.
|
||||
* Other applications (Maya, Houdini) write these to a property called "Pref". */
|
||||
static const std::string propNameOriginalCoordinates("Pref");
|
||||
|
||||
static void get_uvs(const CDStreamConfig &config,
|
||||
std::vector<Imath::V2f> &uvs,
|
||||
std::vector<uint32_t> &uvidx,
|
||||
const Span<float2> uv_map_array)
|
||||
{
|
||||
const OffsetIndices faces = config.mesh->faces();
|
||||
int *corner_verts = config.corner_verts;
|
||||
|
||||
if (!config.pack_uvs) {
|
||||
int count = 0;
|
||||
uvidx.resize(config.totloop);
|
||||
uvs.resize(config.totloop);
|
||||
|
||||
/* Iterate in reverse order to match exported polygons. */
|
||||
for (const int i : faces.index_range()) {
|
||||
const IndexRange face = faces[i];
|
||||
const float2 *loopuv = uv_map_array.data() + face.start() + face.size();
|
||||
|
||||
for (int j = 0; j < face.size(); j++, count++) {
|
||||
loopuv--;
|
||||
|
||||
uvidx[count] = count;
|
||||
uvs[count][0] = (*loopuv)[0];
|
||||
uvs[count][1] = (*loopuv)[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 (const int i : faces.index_range()) {
|
||||
const IndexRange face = faces[i];
|
||||
int *face_verts = corner_verts + face.start() + face.size();
|
||||
const float2 *loopuv = uv_map_array.data() + face.start() + face.size();
|
||||
|
||||
for (int j = 0; j < face.size(); j++) {
|
||||
face_verts--;
|
||||
loopuv--;
|
||||
|
||||
Imath::V2f uv((*loopuv)[0], (*loopuv)[1]);
|
||||
bool found_same = false;
|
||||
|
||||
/* Find UV already in uvs array. */
|
||||
for (uint32_t uv_idx : idx_map[*face_verts]) {
|
||||
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[*face_verts].push_back(uv_idx);
|
||||
uvidx.push_back(uv_idx);
|
||||
uvs.push_back(uv);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const char *get_uv_sample(UVSample &sample, const CDStreamConfig &config, const Mesh &mesh)
|
||||
{
|
||||
const StringRefNull name = mesh.active_uv_map_name();
|
||||
if (name.is_empty()) {
|
||||
return "";
|
||||
}
|
||||
const VArraySpan uv_map = *mesh.attributes().lookup<float2>(name, bke::AttrDomain::Corner);
|
||||
if (uv_map.is_empty()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
get_uvs(config, sample.uvs, sample.indices, uv_map);
|
||||
|
||||
return name.c_str();
|
||||
}
|
||||
|
||||
/* 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,
|
||||
CDStreamConfig &config,
|
||||
const Span<float2> data,
|
||||
const std::string &uv_map_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 = config.abc_uv_maps[uv_map_name];
|
||||
|
||||
if (!param.valid()) {
|
||||
param = OV2fGeomParam(prop, uv_map_name, true, kFacevaryingScope, 1);
|
||||
}
|
||||
OV2fGeomParam::Sample sample(V2fArraySample(&uvs.front(), uvs.size()),
|
||||
UInt32ArraySample(&indices.front(), indices.size()),
|
||||
kFacevaryingScope);
|
||||
param.set(sample);
|
||||
param.setTimeSampling(config.timesample_index);
|
||||
|
||||
config.abc_uv_maps[uv_map_name] = param;
|
||||
}
|
||||
|
||||
static void get_cols(const CDStreamConfig &config,
|
||||
std::vector<Imath::C4f> &buffer,
|
||||
std::vector<uint32_t> &uvidx,
|
||||
const void *cd_data)
|
||||
{
|
||||
const float cscale = 1.0f / 255.0f;
|
||||
const OffsetIndices faces = config.mesh->faces();
|
||||
const MCol *cfaces = static_cast<const MCol *>(cd_data);
|
||||
|
||||
buffer.reserve(config.totvert);
|
||||
uvidx.reserve(config.totvert);
|
||||
|
||||
Imath::C4f col;
|
||||
|
||||
for (const int i : faces.index_range()) {
|
||||
const IndexRange face = faces[i];
|
||||
const MCol *cface = &cfaces[face.start() + face.size()];
|
||||
|
||||
for (int j = 0; j < face.size(); j++) {
|
||||
cface--;
|
||||
|
||||
col[0] = cface->a * cscale;
|
||||
col[1] = cface->r * cscale;
|
||||
col[2] = cface->g * cscale;
|
||||
col[3] = cface->b * cscale;
|
||||
|
||||
buffer.push_back(col);
|
||||
uvidx.push_back(buffer.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Convention to write Vertex Colors:
|
||||
* - C3fGeomParam/C4fGeomParam on the arbGeomParam
|
||||
* - set scope as vertex varying
|
||||
*/
|
||||
static void write_mcol(const OCompoundProperty &prop,
|
||||
CDStreamConfig &config,
|
||||
const void *data,
|
||||
const std::string &vcol_name)
|
||||
{
|
||||
std::vector<uint32_t> indices;
|
||||
std::vector<Imath::C4f> buffer;
|
||||
|
||||
get_cols(config, buffer, indices, data);
|
||||
|
||||
if (indices.empty() || buffer.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
OC4fGeomParam param = config.abc_vertex_colors[vcol_name];
|
||||
|
||||
if (!param.valid()) {
|
||||
param = OC4fGeomParam(prop, vcol_name, true, kFacevaryingScope, 1);
|
||||
}
|
||||
|
||||
OC4fGeomParam::Sample sample(C4fArraySample(&buffer.front(), buffer.size()),
|
||||
UInt32ArraySample(&indices.front(), indices.size()),
|
||||
kVertexScope);
|
||||
|
||||
param.set(sample);
|
||||
param.setTimeSampling(config.timesample_index);
|
||||
|
||||
config.abc_vertex_colors[vcol_name] = param;
|
||||
}
|
||||
|
||||
void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config)
|
||||
{
|
||||
Mesh *mesh = config.mesh;
|
||||
const void *customdata = CustomData_get_layer(&mesh->vert_data, CD_ORCO);
|
||||
if (customdata == nullptr) {
|
||||
/* Data not available, so don't even bother creating an Alembic property for it. */
|
||||
return;
|
||||
}
|
||||
const float (*orcodata)[3] = static_cast<const float (*)[3]>(customdata);
|
||||
|
||||
/* Convert 3D vertices from float[3] z=up to V3f y=up. */
|
||||
std::vector<Imath::V3f> coords(config.totvert);
|
||||
float orco_yup[3];
|
||||
for (int vertex_idx = 0; vertex_idx < config.totvert; vertex_idx++) {
|
||||
copy_yup_from_zup(orco_yup, orcodata[vertex_idx]);
|
||||
coords[vertex_idx].setValue(orco_yup[0], orco_yup[1], orco_yup[2]);
|
||||
}
|
||||
|
||||
/* ORCOs are always stored in the normalized 0..1 range in Blender, but Alembic stores them
|
||||
* unnormalized, so we need to unnormalize (invert transform) them. */
|
||||
BKE_mesh_orco_verts_transform(
|
||||
mesh, reinterpret_cast<float (*)[3]>(coords.data()), mesh->verts_num, true);
|
||||
|
||||
if (!config.abc_orco.valid()) {
|
||||
/* Create the Alembic property and keep a reference so future frames can reuse it. */
|
||||
config.abc_orco = OV3fGeomParam(prop, propNameOriginalCoordinates, false, kVertexScope, 1);
|
||||
}
|
||||
|
||||
OV3fGeomParam::Sample sample(coords, kVertexScope);
|
||||
config.abc_orco.set(sample);
|
||||
}
|
||||
|
||||
void write_custom_data(const OCompoundProperty &prop,
|
||||
CDStreamConfig &config,
|
||||
const Mesh &mesh,
|
||||
int data_type)
|
||||
{
|
||||
const bke::AttributeAccessor attributes = mesh.attributes();
|
||||
if (data_type == CD_PROP_FLOAT2) {
|
||||
const StringRef active_uv_name = mesh.active_uv_map_name();
|
||||
for (const StringRefNull name : mesh.uv_map_names()) {
|
||||
if (name == active_uv_name) {
|
||||
/* Already exported. */
|
||||
continue;
|
||||
}
|
||||
const VArraySpan uv_map = *attributes.lookup<float2>(name, bke::AttrDomain::Corner);
|
||||
write_uv(prop, config, uv_map, get_valid_abc_name(name.c_str()));
|
||||
}
|
||||
}
|
||||
else if (data_type == CD_PROP_BYTE_COLOR) {
|
||||
mesh.attributes().foreach_attribute([&](const bke::AttributeIter &iter) {
|
||||
if (iter.data_type != bke::AttrType::ColorByte) {
|
||||
return;
|
||||
}
|
||||
if (iter.domain != bke::AttrDomain::Corner) {
|
||||
return;
|
||||
}
|
||||
const VArraySpan attr = *attributes.lookup<ColorGeometry4b>(iter.name,
|
||||
bke::AttrDomain::Corner);
|
||||
write_mcol(prop, config, attr.data(), get_valid_abc_name(iter.name.c_str()));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
||||
using Alembic::Abc::C3fArraySamplePtr;
|
||||
using Alembic::Abc::C4fArraySamplePtr;
|
||||
using Alembic::Abc::PropertyHeader;
|
||||
using Alembic::Abc::UInt32ArraySamplePtr;
|
||||
|
||||
using Alembic::AbcGeom::IC3fGeomParam;
|
||||
using Alembic::AbcGeom::IC4fGeomParam;
|
||||
using Alembic::AbcGeom::IV2fGeomParam;
|
||||
using Alembic::AbcGeom::IV3fGeomParam;
|
||||
|
||||
static void read_uvs(const CDStreamConfig &config,
|
||||
MutableSpan<float2> uv_map,
|
||||
const AbcUvScope uv_scope,
|
||||
const Alembic::AbcGeom::V2fArraySamplePtr &uvs,
|
||||
const UInt32ArraySamplePtr &indices)
|
||||
{
|
||||
const OffsetIndices faces = config.mesh->faces();
|
||||
const int *corner_verts = config.corner_verts;
|
||||
const int64_t indices_size = int64_t(indices->size());
|
||||
const int64_t uvs_size = int64_t(uvs->size());
|
||||
|
||||
BLI_assert(uv_scope != ABC_UV_SCOPE_NONE);
|
||||
const bool do_uvs_per_loop = (uv_scope == ABC_UV_SCOPE_LOOP);
|
||||
|
||||
for (const int64_t i : faces.index_range()) {
|
||||
const IndexRange face = faces[i];
|
||||
const int64_t rev_loop_offset = face.start() + face.size() - 1;
|
||||
|
||||
for (int64_t f = 0; f < face.size(); f++) {
|
||||
const int64_t rev_loop_index = rev_loop_offset - f;
|
||||
const int64_t loop_index = do_uvs_per_loop ? face.start() + f : corner_verts[rev_loop_index];
|
||||
if (!validate::index_in_range(loop_index, indices_size)) {
|
||||
continue;
|
||||
}
|
||||
const int64_t uv_index = (*indices)[loop_index];
|
||||
if (!validate::index_in_range(uv_index, uvs_size)) {
|
||||
continue;
|
||||
}
|
||||
const Imath::V2f &uv = (*uvs)[uv_index];
|
||||
|
||||
float2 &loopuv = uv_map[rev_loop_index];
|
||||
loopuv[0] = uv[0];
|
||||
loopuv[1] = uv[1];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int64_t mcols_out_of_bounds_check(const int64_t color_index,
|
||||
const int64_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 (validate::index_in_range(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(STREQ("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(STREQ("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 */
|
||||
bke::MutableAttributeAccessor attributes = config.mesh->attributes_for_write();
|
||||
bke::SpanAttributeWriter attr = attributes.lookup_or_add_for_write_span<ColorGeometry4b>(
|
||||
prop_header.getName(), bke::AttrDomain::Corner);
|
||||
const OffsetIndices faces = config.mesh->faces();
|
||||
const int *corner_verts = config.corner_verts;
|
||||
|
||||
int64_t face_index = 0;
|
||||
int64_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 #53745), which
|
||||
* is why we have to check for indices->size() > 0 */
|
||||
bool use_dual_indexing = is_facevarying && indices->size() > 0;
|
||||
|
||||
for (const int64_t i : faces.index_range()) {
|
||||
const IndexRange face = faces[i];
|
||||
int64_t corner = face.start() + face.size();
|
||||
|
||||
for (int64_t j = 0; j < face.size(); j++, face_index++) {
|
||||
corner--;
|
||||
|
||||
color_index = is_facevarying ? face_index : corner_verts[corner];
|
||||
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];
|
||||
attr.span[corner].r = unit_float_to_uchar_clamp(color[0]);
|
||||
attr.span[corner].g = unit_float_to_uchar_clamp(color[1]);
|
||||
attr.span[corner].b = unit_float_to_uchar_clamp(color[2]);
|
||||
attr.span[corner].a = 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];
|
||||
attr.span[corner].r = unit_float_to_uchar_clamp(color[0]);
|
||||
attr.span[corner].g = unit_float_to_uchar_clamp(color[1]);
|
||||
attr.span[corner].b = unit_float_to_uchar_clamp(color[2]);
|
||||
attr.span[corner].a = unit_float_to_uchar_clamp(color[3]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
attr.finish();
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
UInt32ArraySamplePtr uvs_indices = sample.getIndices();
|
||||
|
||||
const AbcUvScope uv_scope = get_uv_scope(uv_param.getScope(), config, uvs_indices);
|
||||
|
||||
if (uv_scope == ABC_UV_SCOPE_NONE) {
|
||||
return;
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attributes = config.mesh->attributes_for_write();
|
||||
bke::SpanAttributeWriter uv_map = attributes.lookup_or_add_for_write_span<float2>(
|
||||
prop_header.getName(), bke::AttrDomain::Corner);
|
||||
|
||||
read_uvs(config, uv_map.span, uv_scope, sample.getVals(), uvs_indices);
|
||||
|
||||
uv_map.finish();
|
||||
}
|
||||
|
||||
void read_velocity(const V3fArraySamplePtr &velocities,
|
||||
const CDStreamConfig &config,
|
||||
const float velocity_scale)
|
||||
{
|
||||
if (velocities->size() != config.mesh->verts_num) {
|
||||
/* Files containing videogrammetry data may be malformed and export velocity data on missing
|
||||
* frames (most likely by copying the last valid data). */
|
||||
return;
|
||||
}
|
||||
const int64_t num_velocity_vectors = config.mesh->verts_num;
|
||||
|
||||
bke::MutableAttributeAccessor attributes = config.mesh->attributes_for_write();
|
||||
bke::SpanAttributeWriter attr = attributes.lookup_or_add_for_write_span<float3>(
|
||||
"velocity", bke::AttrDomain::Point);
|
||||
MutableSpan<float3> velocity = attr.span;
|
||||
for (int64_t i = 0; i < num_velocity_vectors; i++) {
|
||||
const Imath::V3f &vel_in = (*velocities)[i];
|
||||
copy_zup_from_yup(velocity[i], vel_in.getValue());
|
||||
mul_v3_fl(velocity[i], velocity_scale);
|
||||
}
|
||||
attr.finish();
|
||||
}
|
||||
|
||||
void read_generated_coordinates(const ICompoundProperty &prop,
|
||||
const CDStreamConfig &config,
|
||||
const Alembic::Abc::ISampleSelector &iss)
|
||||
{
|
||||
if (!prop.valid() || prop.getPropertyHeader(propNameOriginalCoordinates) == nullptr) {
|
||||
/* The ORCO property isn't there, so don't bother trying to process it. */
|
||||
return;
|
||||
}
|
||||
|
||||
IV3fGeomParam param(prop, propNameOriginalCoordinates);
|
||||
if (!param.valid() || param.isIndexed()) {
|
||||
/* Invalid or indexed coordinates aren't supported. */
|
||||
return;
|
||||
}
|
||||
if (param.getScope() != kVertexScope) {
|
||||
/* These are original vertex coordinates, so must be vertex-scoped. */
|
||||
return;
|
||||
}
|
||||
|
||||
IV3fGeomParam::Sample sample = param.getExpandedValue(iss);
|
||||
Alembic::AbcGeom::V3fArraySamplePtr abc_orco = sample.getVals();
|
||||
const size_t totvert = abc_orco.get()->size();
|
||||
Mesh *mesh = config.mesh;
|
||||
|
||||
if (totvert != mesh->verts_num) {
|
||||
/* Either the data is somehow corrupted, or we have a dynamic simulation where only the ORCOs
|
||||
* for the first frame were exported. */
|
||||
return;
|
||||
}
|
||||
|
||||
void *cd_data;
|
||||
if (CustomData_has_layer(&mesh->vert_data, CD_ORCO)) {
|
||||
cd_data = CustomData_get_layer_for_write(&mesh->vert_data, CD_ORCO, mesh->verts_num);
|
||||
}
|
||||
else {
|
||||
cd_data = CustomData_add_layer(&mesh->vert_data, CD_ORCO, CD_CONSTRUCT, totvert);
|
||||
}
|
||||
|
||||
float (*orcodata)[3] = static_cast<float (*)[3]>(cd_data);
|
||||
for (int vertex_idx = 0; vertex_idx < totvert; ++vertex_idx) {
|
||||
const Imath::V3f &abc_coords = (*abc_orco)[vertex_idx];
|
||||
copy_zup_from_yup(orcodata[vertex_idx], abc_coords.getValue());
|
||||
}
|
||||
|
||||
/* ORCOs are always stored in the normalized 0..1 range in Blender, but Alembic stores them
|
||||
* unnormalized, so we need to normalize them. */
|
||||
BKE_mesh_orco_verts_transform(mesh, orcodata, mesh->verts_num, false);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
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)) {
|
||||
read_custom_data_mcols(iobject_full_name, prop, prop_header, config, iss);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AbcUvScope get_uv_scope(const Alembic::AbcGeom::GeometryScope scope,
|
||||
const CDStreamConfig &config,
|
||||
const Alembic::AbcGeom::UInt32ArraySamplePtr &indices)
|
||||
{
|
||||
if (scope == kFacevaryingScope && indices->size() == config.totloop) {
|
||||
return ABC_UV_SCOPE_LOOP;
|
||||
}
|
||||
|
||||
/* kVaryingScope is sometimes used for vertex scopes as the values vary across the vertices. To
|
||||
* be sure, one has to check the size of the data against the number of vertices, as it could
|
||||
* also be a varying attribute across the faces (i.e. one value per face). */
|
||||
if (ELEM(scope, kVaryingScope, kVertexScope) && indices->size() == config.totvert) {
|
||||
return ABC_UV_SCOPE_VERTEX;
|
||||
}
|
||||
|
||||
return ABC_UV_SCOPE_NONE;
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
133
blender-5.2.0/source/blender/io/alembic/intern/abc_customdata.h
Normal file
133
blender-5.2.0/source/blender/io/alembic/intern/abc_customdata.h
Normal file
@@ -0,0 +1,133 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Kévin Dietrich. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "BLI_math_vector_types.hh"
|
||||
|
||||
#include <Alembic/Abc/ICompoundProperty.h>
|
||||
#include <Alembic/Abc/ISampleSelector.h>
|
||||
#include <Alembic/Abc/OCompoundProperty.h>
|
||||
#include <Alembic/Abc/TypedArraySample.h>
|
||||
#include <Alembic/AbcCoreAbstract/Foundation.h>
|
||||
#include <Alembic/AbcGeom/GeometryScope.h>
|
||||
#include <Alembic/AbcGeom/OGeomParam.h>
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
|
||||
#include <cstdint>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct CustomData;
|
||||
struct Mesh;
|
||||
|
||||
using Alembic::Abc::ICompoundProperty;
|
||||
using Alembic::Abc::OCompoundProperty;
|
||||
using Alembic::Abc::UInt32ArraySamplePtr;
|
||||
using Alembic::Abc::V2fArraySamplePtr;
|
||||
using Alembic::Abc::V3fArraySamplePtr;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
struct UVSample {
|
||||
std::vector<Imath::V2f> uvs;
|
||||
std::vector<uint32_t> indices;
|
||||
};
|
||||
|
||||
enum AbcUvScope {
|
||||
ABC_UV_SCOPE_NONE,
|
||||
ABC_UV_SCOPE_LOOP,
|
||||
ABC_UV_SCOPE_VERTEX,
|
||||
};
|
||||
|
||||
struct CDStreamConfig {
|
||||
int *corner_verts = nullptr;
|
||||
int totloop = 0;
|
||||
|
||||
int *face_offsets = nullptr;
|
||||
int faces_num = 0;
|
||||
|
||||
float3 *positions = nullptr;
|
||||
int totvert = 0;
|
||||
|
||||
bke::SpanAttributeWriter<float2> uv_map;
|
||||
|
||||
bool pack_uvs = false;
|
||||
|
||||
/* 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 = nullptr;
|
||||
|
||||
Alembic::Abc::chrono_t time = 0.0;
|
||||
int timesample_index = 0;
|
||||
|
||||
const char **modifier_error_message = nullptr;
|
||||
|
||||
/* Alembic needs Blender to keep references to C++ objects (the destructors finalize the writing
|
||||
* to ABC). The following fields are all used to keep these references. */
|
||||
|
||||
/* Mapping from UV map name to its ABC property, for the 2nd and subsequent UV maps; the primary
|
||||
* UV map is kept alive by the Alembic mesh sample itself. */
|
||||
std::map<std::string, Alembic::AbcGeom::OV2fGeomParam> abc_uv_maps;
|
||||
|
||||
/* ORCO coordinates, aka Generated Coordinates. */
|
||||
Alembic::AbcGeom::OV3fGeomParam abc_orco;
|
||||
|
||||
/* Mapping from vertex color layer name to its Alembic color data. */
|
||||
std::map<std::string, Alembic::AbcGeom::OC4fGeomParam> abc_vertex_colors;
|
||||
|
||||
AbcUvScope uv_scope;
|
||||
V2fArraySamplePtr uvs;
|
||||
UInt32ArraySamplePtr uvs_indices;
|
||||
|
||||
CDStreamConfig() = default;
|
||||
};
|
||||
|
||||
/* 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, const Mesh &mesh);
|
||||
|
||||
void write_generated_coordinates(const OCompoundProperty &prop, CDStreamConfig &config);
|
||||
|
||||
void read_velocity(const V3fArraySamplePtr &velocities,
|
||||
const CDStreamConfig &config,
|
||||
const float velocity_scale);
|
||||
|
||||
void read_generated_coordinates(const ICompoundProperty &prop,
|
||||
const CDStreamConfig &config,
|
||||
const Alembic::Abc::ISampleSelector &iss);
|
||||
|
||||
void write_custom_data(const OCompoundProperty &prop,
|
||||
CDStreamConfig &config,
|
||||
const Mesh &mesh,
|
||||
int data_type);
|
||||
|
||||
void read_custom_data(const std::string &iobject_full_name,
|
||||
const ICompoundProperty &prop,
|
||||
const CDStreamConfig &config,
|
||||
const Alembic::Abc::ISampleSelector &iss);
|
||||
|
||||
/**
|
||||
* UVs can be defined per-loop (one value per vertex per face), or per-vertex (one value per
|
||||
* vertex). The first case is the most common, as this is the standard way of storing this data
|
||||
* given that some vertices might be on UV seams and have multiple possible UV coordinates; the
|
||||
* second case can happen when the mesh is split according to the UV islands, in which case storing
|
||||
* a single UV value per vertex allows to de-duplicate data and thus to reduce the file size since
|
||||
* vertices are guaranteed to only have a single UV coordinate.
|
||||
*/
|
||||
AbcUvScope get_uv_scope(const Alembic::AbcGeom::GeometryScope scope,
|
||||
const CDStreamConfig &config,
|
||||
const Alembic::AbcGeom::UInt32ArraySamplePtr &indices);
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
111
blender-5.2.0/source/blender/io/alembic/intern/abc_keyframing.cc
Normal file
111
blender-5.2.0/source/blender/io/alembic/intern/abc_keyframing.cc
Normal file
@@ -0,0 +1,111 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "abc_keyframing.h"
|
||||
#include "abc_reader_archive.h"
|
||||
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
#include "ANIM_animdata.hh"
|
||||
|
||||
#include "BKE_fcurve.hh"
|
||||
|
||||
using Alembic::Abc::ISampleSelector;
|
||||
|
||||
namespace blender {
|
||||
namespace io::alembic {
|
||||
|
||||
/* Utility: create new fcurve and add it as a channel to a group. */
|
||||
static FCurve *create_fcurve(animrig::Channelbag &channelbag,
|
||||
const animrig::FCurveDescriptor &fcurve_descriptor,
|
||||
const int sample_count)
|
||||
{
|
||||
FCurve *fcurve = channelbag.fcurve_create_unique(nullptr, fcurve_descriptor);
|
||||
BLI_assert_msg(fcurve, "The same F-Curve is being created twice, this is unexpected.");
|
||||
if (fcurve) {
|
||||
BKE_fcurve_bezt_resize(*fcurve, sample_count);
|
||||
}
|
||||
return fcurve;
|
||||
}
|
||||
|
||||
/* Utility: fill in a single fcurve sample at the provided index. */
|
||||
void set_fcurve_sample(FCurve *fcu, int64_t sample_index, const float frame, const float value)
|
||||
{
|
||||
BLI_assert(sample_index >= 0 && sample_index < fcu->totvert);
|
||||
BezTriple &bez = fcu->bezt[sample_index];
|
||||
bez.vec[1][0] = frame;
|
||||
bez.vec[1][1] = value;
|
||||
bez.ipo = BEZT_IPO_LIN;
|
||||
bez.f1 = bez.f2 = bez.f3 = BEZT_FLAG_SELECT;
|
||||
bez.h1 = bez.h2 = HD_AUTO;
|
||||
}
|
||||
|
||||
FCurveCreationHelper::~FCurveCreationHelper() = default;
|
||||
|
||||
void FCurveCreationHelper::ensure_action_data(Main *bmain, const int sample_count)
|
||||
{
|
||||
action_ = animrig::id_action_ensure(bmain, id_);
|
||||
channelbag = &animrig::action_channelbag_ensure(*action_, *id_);
|
||||
create_fcurves(sample_count);
|
||||
}
|
||||
|
||||
FCurve *FCurveCreationHelper::create_fcurve(const animrig::FCurveDescriptor &fcurve_descriptor,
|
||||
const int sample_count)
|
||||
{
|
||||
return alembic::create_fcurve(*channelbag, fcurve_descriptor, sample_count);
|
||||
}
|
||||
|
||||
void FCurveCreationHelper::finish()
|
||||
{
|
||||
remove_unnecessary_fcurves();
|
||||
|
||||
for (FCurve *fcu : channelbag->fcurves()) {
|
||||
if (fcu) {
|
||||
BKE_fcurve_handles_recalc(*fcu);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void create_keyframes(Main *bmain,
|
||||
Scene *scene,
|
||||
Span<std::unique_ptr<FCurveCreationHelper>> helpers,
|
||||
const TimeInfo time_info)
|
||||
{
|
||||
if (helpers.is_empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double fps = scene->frames_per_second();
|
||||
const int start_frame = int(round(time_info.min_time * fps));
|
||||
const int end_frame = int(round(time_info.max_time * fps));
|
||||
|
||||
const int sample_count = end_frame - start_frame + 1;
|
||||
|
||||
for (const std::unique_ptr<FCurveCreationHelper> &helper : helpers) {
|
||||
helper->ensure_action_data(bmain, sample_count);
|
||||
}
|
||||
|
||||
int64_t sample_index = 0;
|
||||
for (int i = start_frame; i <= end_frame; i++) {
|
||||
const double frame_time = (double(i) / fps);
|
||||
const ISampleSelector selector = ISampleSelector(frame_time);
|
||||
|
||||
FrameSampleInfo sample_info;
|
||||
sample_info.frame = float(i);
|
||||
sample_info.sample_index = sample_index++;
|
||||
sample_info.selector = selector;
|
||||
|
||||
for (const std::unique_ptr<FCurveCreationHelper> &helper : helpers) {
|
||||
helper->set_fcurves_sample(sample_info);
|
||||
}
|
||||
}
|
||||
|
||||
for (const std::unique_ptr<FCurveCreationHelper> &helper : helpers) {
|
||||
helper->finish();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,82 @@
|
||||
/* SPDX-FileCopyrightText: 2026 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
#include <Alembic/Abc/ISampleSelector.h>
|
||||
|
||||
#include "BLI_set.hh"
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "RNA_path.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
namespace animrig {
|
||||
class Channelbag;
|
||||
struct FCurveDescriptor;
|
||||
} // namespace animrig
|
||||
|
||||
struct bAction;
|
||||
struct FCurve;
|
||||
struct ID;
|
||||
struct Main;
|
||||
struct Scene;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
struct TimeInfo;
|
||||
|
||||
struct FrameSampleInfo {
|
||||
Alembic::Abc::ISampleSelector selector{};
|
||||
int64_t sample_index = 0;
|
||||
float frame = 0.0f;
|
||||
};
|
||||
|
||||
/* Base class for creating FCurves and setting their samples for each frame.
|
||||
* The actual FCurve creation is delegated to derived classes. */
|
||||
class FCurveCreationHelper {
|
||||
protected:
|
||||
ID *id_ = nullptr;
|
||||
bAction *action_ = nullptr;
|
||||
animrig::Channelbag *channelbag = nullptr;
|
||||
|
||||
public:
|
||||
FCurveCreationHelper(ID *id) : id_(id) {}
|
||||
|
||||
virtual ~FCurveCreationHelper();
|
||||
|
||||
void ensure_action_data(Main *bmain, const int sample_count);
|
||||
|
||||
/* Called every frame. Derived classes should set the sample for every FCurve that they have
|
||||
* created. */
|
||||
virtual void set_fcurves_sample(const FrameSampleInfo &sample_info) = 0;
|
||||
|
||||
void finish();
|
||||
|
||||
protected:
|
||||
FCurve *create_fcurve(const animrig::FCurveDescriptor &fcurve_descriptor,
|
||||
const int sample_count);
|
||||
|
||||
/* This is where derived classes should create FCurves for every property that they want to see
|
||||
* key-framed. FCurves should be created using the #create_fcurve method above. */
|
||||
virtual void create_fcurves(const int sample_count) = 0;
|
||||
|
||||
/* Derived classes can implement this to remove any FCurve for any property which was not
|
||||
* actually animated. */
|
||||
virtual void remove_unnecessary_fcurves() {}
|
||||
};
|
||||
|
||||
/* Create keyframes for the entire range of the supplied #TimeInfo. */
|
||||
void create_keyframes(Main *bmain,
|
||||
Scene *scene,
|
||||
Span<std::unique_ptr<FCurveCreationHelper>> helpers,
|
||||
const TimeInfo time_info);
|
||||
|
||||
void set_fcurve_sample(FCurve *fcu, int64_t sample_index, const float frame, const float value);
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,174 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Kévin Dietrich. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_archive.h"
|
||||
|
||||
#include "Alembic/Abc/ArchiveInfo.h"
|
||||
#include "Alembic/AbcCoreAbstract/MetaData.h"
|
||||
#include "Alembic/AbcCoreLayer/Read.h"
|
||||
#include "Alembic/AbcCoreOgawa/ReadWrite.h"
|
||||
|
||||
#include "BKE_main.hh"
|
||||
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#ifdef WIN32
|
||||
# include "utfconv.hh"
|
||||
#endif
|
||||
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
namespace blender {
|
||||
|
||||
using Alembic::Abc::chrono_t;
|
||||
using Alembic::Abc::ErrorHandler;
|
||||
using Alembic::Abc::Exception;
|
||||
using Alembic::Abc::IArchive;
|
||||
using Alembic::Abc::kWrapExisting;
|
||||
using Alembic::Abc::MetaData;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
static IArchive open_archive(const std::string &filename,
|
||||
const std::vector<std::istream *> &input_streams)
|
||||
{
|
||||
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';
|
||||
|
||||
/* Inspect the file to see whether it's actually 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) != 0) {
|
||||
std::cerr << filename << " has an unknown file format, unable to read." << std::endl;
|
||||
}
|
||||
else {
|
||||
std::cerr << filename << " is in the obsolete HDF5 format, unable to read." << std::endl;
|
||||
}
|
||||
|
||||
if (the_file.is_open()) {
|
||||
the_file.close();
|
||||
}
|
||||
}
|
||||
|
||||
return IArchive();
|
||||
}
|
||||
|
||||
ArchiveReader *ArchiveReader::get(const Main *bmain, const std::vector<const char *> &filenames)
|
||||
{
|
||||
std::vector<ArchiveReader *> readers;
|
||||
|
||||
for (const char *filename : filenames) {
|
||||
ArchiveReader *reader = new ArchiveReader(bmain, filename);
|
||||
|
||||
if (!reader->valid()) {
|
||||
delete reader;
|
||||
continue;
|
||||
}
|
||||
|
||||
readers.push_back(reader);
|
||||
}
|
||||
|
||||
if (readers.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (readers.size() == 1) {
|
||||
return readers[0];
|
||||
}
|
||||
|
||||
return new ArchiveReader(readers);
|
||||
}
|
||||
|
||||
ArchiveReader::ArchiveReader(const std::vector<ArchiveReader *> &readers) : m_readers(readers)
|
||||
{
|
||||
Alembic::AbcCoreLayer::ArchiveReaderPtrs archives;
|
||||
|
||||
for (ArchiveReader *reader : readers) {
|
||||
archives.push_back(reader->m_archive.getPtr());
|
||||
}
|
||||
|
||||
Alembic::AbcCoreLayer::ReadArchive layer;
|
||||
Alembic::AbcCoreAbstract::ArchiveReaderPtr arPtr = layer(archives);
|
||||
|
||||
m_archive = IArchive(arPtr, kWrapExisting, ErrorHandler::kThrowPolicy);
|
||||
}
|
||||
|
||||
ArchiveReader::ArchiveReader(const Main *bmain, const char *filename)
|
||||
{
|
||||
char abs_filepath[FILE_MAX];
|
||||
STRNCPY(abs_filepath, filename);
|
||||
BLI_path_abs(abs_filepath, BKE_main_blendfile_path(bmain));
|
||||
|
||||
#ifdef WIN32
|
||||
UTF16_ENCODE(abs_filepath);
|
||||
std::wstring wstr(abs_filepath_16);
|
||||
m_infile.open(wstr.c_str(), std::ios::in | std::ios::binary);
|
||||
UTF16_UN_ENCODE(abs_filepath);
|
||||
#else
|
||||
m_infile.open(abs_filepath, std::ios::in | std::ios::binary);
|
||||
#endif
|
||||
|
||||
m_streams.push_back(&m_infile);
|
||||
|
||||
m_archive = open_archive(abs_filepath, m_streams);
|
||||
}
|
||||
|
||||
ArchiveReader::~ArchiveReader()
|
||||
{
|
||||
for (ArchiveReader *reader : m_readers) {
|
||||
delete reader;
|
||||
}
|
||||
}
|
||||
|
||||
bool ArchiveReader::valid() const
|
||||
{
|
||||
return m_archive.valid();
|
||||
}
|
||||
|
||||
Alembic::Abc::IObject ArchiveReader::getTop()
|
||||
{
|
||||
return m_archive.getTop();
|
||||
}
|
||||
|
||||
bool ArchiveReader::is_blender_archive_version_prior_44()
|
||||
{
|
||||
const MetaData &abc_metadata = m_archive.getPtr()->getMetaData();
|
||||
|
||||
/* Was the incoming Archive written by Blender? If so, make the version check. */
|
||||
if (abc_metadata.get(Alembic::Abc::kApplicationNameKey) == "Blender") {
|
||||
return abc_metadata.get("blender_version") < "v4.4";
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
TimeInfo ArchiveReader::getTimeInfo()
|
||||
{
|
||||
chrono_t min_time = std::numeric_limits<chrono_t>::max();
|
||||
chrono_t max_time = -std::numeric_limits<chrono_t>::max();
|
||||
|
||||
Alembic::Abc::GetArchiveStartAndEndTime(m_archive, min_time, max_time);
|
||||
|
||||
return {min_time, max_time};
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,68 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Kévin Dietrich. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include <Alembic/Abc/IArchive.h>
|
||||
#include <Alembic/Abc/IObject.h>
|
||||
|
||||
#include <fstream>
|
||||
#include <vector>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Main;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
/* Represents the time range in seconds for animated data inside of an Alembic archive. The time
|
||||
* range is [min, max]. */
|
||||
struct TimeInfo {
|
||||
Alembic::Abc::chrono_t min_time = std::numeric_limits<Alembic::Abc::chrono_t>::max();
|
||||
Alembic::Abc::chrono_t max_time = -std::numeric_limits<Alembic::Abc::chrono_t>::max();
|
||||
|
||||
bool is_valid() const
|
||||
{
|
||||
return min_time <= max_time &&
|
||||
min_time != std::numeric_limits<Alembic::Abc::chrono_t>::max() &&
|
||||
max_time != -std::numeric_limits<Alembic::Abc::chrono_t>::max();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrappers around input and output archives. The goal is to be able to use
|
||||
* streams so that unicode paths work on Windows (#49112), 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;
|
||||
|
||||
std::vector<ArchiveReader *> m_readers;
|
||||
|
||||
ArchiveReader(const std::vector<ArchiveReader *> &readers);
|
||||
|
||||
ArchiveReader(const struct Main *bmain, const char *filename);
|
||||
|
||||
public:
|
||||
static ArchiveReader *get(const struct Main *bmain, const std::vector<const char *> &filenames);
|
||||
|
||||
~ArchiveReader();
|
||||
|
||||
bool valid() const;
|
||||
|
||||
Alembic::Abc::IObject getTop();
|
||||
|
||||
/* Detect if the Archive was written by Blender prior to 4.4. */
|
||||
bool is_blender_archive_version_prior_44();
|
||||
|
||||
TimeInfo getTimeInfo();
|
||||
};
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,202 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_camera.h"
|
||||
#include "abc_keyframing.h"
|
||||
#include "abc_util.h"
|
||||
|
||||
/* Silence warnings from copying deprecated fields. */
|
||||
#define DNA_DEPRECATED_ALLOW
|
||||
|
||||
#include "DNA_camera_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "ANIM_action.hh"
|
||||
#include "ANIM_fcurve.hh"
|
||||
|
||||
#include "BLI_math_base.h"
|
||||
|
||||
#include "BKE_camera.h"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
using Alembic::AbcGeom::CameraSample;
|
||||
using Alembic::AbcGeom::ICamera;
|
||||
using Alembic::AbcGeom::ICompoundProperty;
|
||||
using Alembic::AbcGeom::IFloatProperty;
|
||||
using Alembic::AbcGeom::ISampleSelector;
|
||||
using Alembic::AbcGeom::kWrapExisting;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
AbcCameraReader::AbcCameraReader(const AbcReaderConstructorArgs &args) : AbcObjectReader(args)
|
||||
{
|
||||
ICamera abc_cam(m_iobject, kWrapExisting);
|
||||
m_schema = abc_cam.getSchema();
|
||||
}
|
||||
|
||||
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 **r_err_str) const
|
||||
{
|
||||
if (!Alembic::AbcGeom::ICamera::matches(alembic_header)) {
|
||||
*r_err_str = RPT_(
|
||||
"Object type mismatch, Alembic object path pointed to Camera when importing, but not any "
|
||||
"more");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ob->type != OB_CAMERA) {
|
||||
*r_err_str = RPT_("Object type mismatch, Alembic object path points to Camera");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void read_camera_sample(Camera *bcam,
|
||||
const ICamera::schema_type &schema,
|
||||
const ISampleSelector &sample_sel)
|
||||
{
|
||||
CameraSample cam_sample;
|
||||
schema.get(cam_sample, sample_sel);
|
||||
|
||||
ICompoundProperty customDataContainer = 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 = float(cam_sample.getFocalLength());
|
||||
const float apperture_x = float(cam_sample.getHorizontalAperture());
|
||||
const float apperture_y = float(cam_sample.getVerticalAperture());
|
||||
const float h_film_offset = float(cam_sample.getHorizontalFilmOffset());
|
||||
const float v_film_offset = 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, float(cam_sample.getNearClippingPlane()));
|
||||
bcam->clip_end = float(cam_sample.getFarClippingPlane());
|
||||
bcam->dof.focus_distance = float(cam_sample.getFocusDistance());
|
||||
bcam->dof.aperture_fstop = float(cam_sample.getFStop());
|
||||
}
|
||||
|
||||
void AbcCameraReader::readObjectData(Main *bmain, const ISampleSelector &sample_sel)
|
||||
{
|
||||
Camera *bcam = BKE_camera_add(bmain, m_data_name.c_str());
|
||||
read_camera_sample(bcam, m_schema, sample_sel);
|
||||
m_object = BKE_object_add_only_object(bmain, OB_CAMERA, m_object_name.c_str());
|
||||
m_object->data = id_cast<ID *>(bcam);
|
||||
}
|
||||
|
||||
/* The macro that needs to be passed should have arguments :
|
||||
* (short_name, rna_path, member_accessor) */
|
||||
#define ENUMERATE_CAMERA_PROPERTIES(X) \
|
||||
X(lens, lens, lens) \
|
||||
X(sensor_width, sensor_width, sensor_x) \
|
||||
X(sensor_height, sensor_height, sensor_y) \
|
||||
X(clip_start, clip_start, clip_start) \
|
||||
X(clip_end, clip_end, clip_end) \
|
||||
X(shift_x, shift_x, shiftx) \
|
||||
X(shift_y, shift_y, shifty) \
|
||||
X(focus_distance, dof.focus_distance, dof.focus_distance) \
|
||||
X(aperture_fstop, dof.aperture_fstop, dof.aperture_fstop) \
|
||||
X(interocular_distance, stereo.interocular_distance, stereo.interocular_distance) \
|
||||
X(convergence_distance, stereo.convergence_distance, stereo.convergence_distance)
|
||||
|
||||
class CameraFCurveCreationHelper : public FCurveCreationHelper {
|
||||
Camera *camera_ = nullptr;
|
||||
const Alembic::AbcGeom::ICameraSchema &schema_{};
|
||||
|
||||
/* Keep track of what has been modified to remove unnecessary fcurves at the end as Alembic
|
||||
* seemingly does not have per property information. */
|
||||
struct MemberModified {
|
||||
#define DECLARE_MEMBER(short_name, rna_path, member_accessor) bool short_name = false;
|
||||
ENUMERATE_CAMERA_PROPERTIES(DECLARE_MEMBER)
|
||||
#undef DECLARE_MEMBER
|
||||
};
|
||||
|
||||
MemberModified member_modified_{};
|
||||
|
||||
#define DECLARE_FCURVES(short_name, rna_path, member_accessor) \
|
||||
FCurve *short_name##_fcurve = nullptr;
|
||||
ENUMERATE_CAMERA_PROPERTIES(DECLARE_FCURVES)
|
||||
#undef DECLARE_FCURVES
|
||||
|
||||
public:
|
||||
CameraFCurveCreationHelper(Camera *camera, const Alembic::AbcGeom::ICameraSchema &schema)
|
||||
: FCurveCreationHelper(&camera->id), camera_(camera), schema_(schema)
|
||||
{
|
||||
}
|
||||
|
||||
void create_fcurves(const int sample_count) override
|
||||
{
|
||||
#define CREATE_FCURVE(short_name, rna_path, member_accessor) \
|
||||
short_name##_fcurve = create_fcurve({#rna_path, 0}, sample_count);
|
||||
ENUMERATE_CAMERA_PROPERTIES(CREATE_FCURVE)
|
||||
#undef CREATE_FCURVE
|
||||
}
|
||||
|
||||
void set_fcurves_sample(const FrameSampleInfo &sample_info) override
|
||||
{
|
||||
/* To detect what has been modified. */
|
||||
Camera last_camera = *camera_;
|
||||
read_camera_sample(camera_, schema_, sample_info.selector);
|
||||
|
||||
#define SET_FCURVE_SAMPLE(short_name, rna_path, member_accessor) \
|
||||
set_fcurve_sample(short_name##_fcurve, \
|
||||
sample_info.sample_index, \
|
||||
sample_info.frame, \
|
||||
camera_->member_accessor); \
|
||||
member_modified_.short_name |= last_camera.member_accessor != camera_->member_accessor;
|
||||
ENUMERATE_CAMERA_PROPERTIES(SET_FCURVE_SAMPLE)
|
||||
#undef SET_FCURVE_SAMPLE
|
||||
}
|
||||
|
||||
void remove_unnecessary_fcurves() override
|
||||
{
|
||||
#define REMOVE_UNNECESSARY_FCURVE(short_name, rna_path, member_accessor) \
|
||||
if (member_modified_.short_name == false) { \
|
||||
channelbag->fcurve_remove(*short_name##_fcurve); \
|
||||
}
|
||||
ENUMERATE_CAMERA_PROPERTIES(REMOVE_UNNECESSARY_FCURVE)
|
||||
#undef REMOVE_UNNECESSARY_FCURVE
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<FCurveCreationHelper> AbcCameraReader::getKeyFramingHelper()
|
||||
{
|
||||
if (m_schema.isConstant()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Camera *camera = id_cast<Camera *>(m_object->data);
|
||||
return std::make_unique<CameraFCurveCreationHelper>(camera, m_schema);
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,32 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_object.h"
|
||||
|
||||
#include <Alembic/AbcGeom/ICamera.h>
|
||||
|
||||
namespace blender::io::alembic {
|
||||
|
||||
class AbcCameraReader final : public AbcObjectReader {
|
||||
Alembic::AbcGeom::ICameraSchema m_schema;
|
||||
|
||||
public:
|
||||
AbcCameraReader(const AbcReaderConstructorArgs &args);
|
||||
|
||||
bool valid() const override;
|
||||
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
|
||||
const Object *const ob,
|
||||
const char **r_err_str) const override;
|
||||
|
||||
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
|
||||
|
||||
std::unique_ptr<FCurveCreationHelper> getKeyFramingHelper() override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::alembic
|
||||
@@ -0,0 +1,593 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Kévin Dietrich. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_curves.h"
|
||||
#include "abc_axis_conversion.h"
|
||||
#include "abc_util.h"
|
||||
|
||||
#include "DNA_curves_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
#include "BKE_curves.hh"
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
#include "IO_validate.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
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;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
static CLG_LogRef LOG = {"io.alembic"};
|
||||
|
||||
static int16_t get_curve_resolution(const ICurvesSchema &schema,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel)
|
||||
{
|
||||
ICompoundProperty user_props = schema.getUserProperties();
|
||||
if (!user_props) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
const PropertyHeader *header = user_props.getPropertyHeader(ABC_CURVE_RESOLUTION_U_PROPNAME);
|
||||
if (!header || !header->isScalar() || !IInt16Property::matches(*header)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
IInt16Property resolu(user_props, header->getName());
|
||||
return resolu.getValue(sample_sel);
|
||||
}
|
||||
|
||||
static int16_t get_curve_order(const Alembic::AbcGeom::CurveType abc_curve_type,
|
||||
const UcharArraySamplePtr orders,
|
||||
const size_t curve_index)
|
||||
{
|
||||
switch (abc_curve_type) {
|
||||
case Alembic::AbcGeom::kCubic:
|
||||
return 4;
|
||||
case Alembic::AbcGeom::kVariableOrder:
|
||||
if (orders && orders->size() > curve_index) {
|
||||
return int16_t((*orders)[curve_index]);
|
||||
}
|
||||
ATTR_FALLTHROUGH;
|
||||
case Alembic::AbcGeom::kLinear:
|
||||
default:
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
|
||||
static int8_t get_knot_mode(const Alembic::AbcGeom::CurveType abc_curve_type)
|
||||
{
|
||||
if (abc_curve_type == Alembic::AbcGeom::kCubic) {
|
||||
return NURBS_KNOT_MODE_ENDPOINT;
|
||||
}
|
||||
|
||||
return NURBS_KNOT_MODE_NORMAL;
|
||||
}
|
||||
|
||||
static int get_curve_overlap(const P3fArraySamplePtr positions,
|
||||
const int idx,
|
||||
const int num_verts,
|
||||
const int16_t order)
|
||||
{
|
||||
/* 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;
|
||||
|
||||
const int safe_order = order <= num_verts ? order : num_verts;
|
||||
for (int j = start, k = end - safe_order; j < (start + safe_order); 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;
|
||||
}
|
||||
|
||||
return overlap;
|
||||
}
|
||||
|
||||
static CurveType get_curve_type(const Alembic::AbcGeom::BasisType basis)
|
||||
{
|
||||
switch (basis) {
|
||||
case Alembic::AbcGeom::kNoBasis:
|
||||
return CURVE_TYPE_POLY;
|
||||
case Alembic::AbcGeom::kBezierBasis:
|
||||
return CURVE_TYPE_BEZIER;
|
||||
case Alembic::AbcGeom::kBsplineBasis:
|
||||
return CURVE_TYPE_NURBS;
|
||||
case Alembic::AbcGeom::kCatmullromBasis:
|
||||
return CURVE_TYPE_CATMULL_ROM;
|
||||
case Alembic::AbcGeom::kHermiteBasis:
|
||||
case Alembic::AbcGeom::kPowerBasis:
|
||||
/* Those types are unknown to Blender, use a default poly type. */
|
||||
return CURVE_TYPE_POLY;
|
||||
}
|
||||
return CURVE_TYPE_POLY;
|
||||
}
|
||||
|
||||
static inline int bezier_point_count(int alembic_count, bool is_cyclic)
|
||||
{
|
||||
return is_cyclic ? (alembic_count / 3) : ((alembic_count / 3) + 1);
|
||||
}
|
||||
|
||||
static inline float3 to_zup_float3(Imath::V3f v)
|
||||
{
|
||||
float3 p;
|
||||
copy_zup_from_yup(p, v.getValue());
|
||||
return p;
|
||||
}
|
||||
|
||||
static bool curves_topology_changed(const bke::CurvesGeometry &curves,
|
||||
Span<int> preprocessed_offsets)
|
||||
{
|
||||
if (curves.offsets() != preprocessed_offsets) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template<typename SampleType>
|
||||
static bool samples_have_same_topology(const SampleType &sample, const SampleType &ceil_sample)
|
||||
{
|
||||
const P3fArraySamplePtr positions = sample.getPositions();
|
||||
const Int32ArraySamplePtr per_curve_vertices_count = sample.getCurvesNumVertices();
|
||||
|
||||
const P3fArraySamplePtr ceil_positions = ceil_sample.getPositions();
|
||||
const Int32ArraySamplePtr ceil_per_curve_vertices_count = ceil_sample.getCurvesNumVertices();
|
||||
|
||||
/* It the counters are different, we can be sure the topology is different. */
|
||||
const bool different_counters = positions->size() != ceil_positions->size() ||
|
||||
per_curve_vertices_count->size() !=
|
||||
ceil_per_curve_vertices_count->size();
|
||||
if (different_counters) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Otherwise check the curve vertex counts. */
|
||||
if (memcmp(per_curve_vertices_count->get(),
|
||||
ceil_per_curve_vertices_count->get(),
|
||||
per_curve_vertices_count->size() * sizeof(int)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Preprocessed data to help and simplify converting curve data from Alembic to Blender.
|
||||
* As some operations may require to look up the Alembic sample multiple times, we just
|
||||
* do it once and cache the results in this.
|
||||
*/
|
||||
struct PreprocessedSampleData {
|
||||
/* This holds one value for each spline. This will be used to lookup the data at the right
|
||||
* indices, and will also be used to set #CurveGeometry.offsets. */
|
||||
Vector<int> offset_in_blender;
|
||||
/* This holds one value for each spline, and tells where in the Alembic curve sample the spline
|
||||
* actually starts, accounting for duplicate points indicating cyclicity. */
|
||||
Vector<int> offset_in_alembic;
|
||||
/* This holds one value for each spline to tell whether it is cyclic. */
|
||||
Vector<bool> curves_cyclic;
|
||||
/* This holds one value for each spline which define its order. */
|
||||
Vector<int8_t> curves_orders;
|
||||
|
||||
/* True if any values of `curves_overlaps` is true. If so, we will need to copy the
|
||||
* `curves_overlaps` to an attribute on the Blender curves. */
|
||||
bool do_cyclic = false;
|
||||
|
||||
/* Only one curve type for the whole objects. */
|
||||
CurveType curve_type = CURVE_TYPE_POLY;
|
||||
int8_t knot_mode = 0;
|
||||
|
||||
/* Optional settings for reading interpolated vertices. If present, `ceil_positions` has to be
|
||||
* valid. */
|
||||
std::optional<SampleInterpolationSettings> interpolation_settings;
|
||||
|
||||
/* Store the pointers during preprocess so we do not have to look up the sample twice. */
|
||||
P3fArraySamplePtr positions = nullptr;
|
||||
P3fArraySamplePtr ceil_positions = nullptr;
|
||||
FloatArraySamplePtr weights = nullptr;
|
||||
FloatArraySamplePtr radii = nullptr;
|
||||
};
|
||||
|
||||
/* Compute topological information about the curves. We do this step mainly to properly account
|
||||
* for curves overlaps which imply different offsets between Blender and Alembic, but also to
|
||||
* validate the data and cache some values. */
|
||||
static std::optional<PreprocessedSampleData> preprocess_sample(StringRefNull iobject_name,
|
||||
bool use_interpolation,
|
||||
const ICurvesSchema &schema,
|
||||
const ISampleSelector sample_sel)
|
||||
{
|
||||
|
||||
ICurvesSchema::Sample smp;
|
||||
try {
|
||||
smp = schema.getValue(sample_sel);
|
||||
}
|
||||
catch (Alembic::Util::Exception &ex) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Error reading curve sample for '%s/%s' at time %f: %s",
|
||||
iobject_name.c_str(),
|
||||
schema.getName().c_str(),
|
||||
sample_sel.getRequestedTime(),
|
||||
ex.what());
|
||||
return {};
|
||||
}
|
||||
|
||||
/* NOTE: although Alembic can store knots, we do not read them as the functionality is not
|
||||
* exposed by the Blender's Curves API yet. */
|
||||
const Int32ArraySamplePtr per_curve_vertices_count = smp.getCurvesNumVertices();
|
||||
const P3fArraySamplePtr positions = smp.getPositions();
|
||||
const FloatArraySamplePtr weights = smp.getPositionWeights();
|
||||
const CurvePeriodicity periodicity = smp.getWrap();
|
||||
const UcharArraySamplePtr orders = smp.getOrders();
|
||||
|
||||
if (positions->size() == 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!validate::size_fits_in_int(positions->size()) ||
|
||||
!validate::size_fits_in_int(per_curve_vertices_count->size()))
|
||||
{
|
||||
CLOG_WARN(&LOG,
|
||||
"Curves too large to import for '%s/%s' at time %f, exceeds max int size",
|
||||
iobject_name.c_str(),
|
||||
schema.getName().c_str(),
|
||||
sample_sel.getRequestedTime());
|
||||
return {};
|
||||
}
|
||||
|
||||
const IFloatGeomParam widths_param = schema.getWidthsParam();
|
||||
FloatArraySamplePtr radii;
|
||||
if (widths_param.valid()) {
|
||||
IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel);
|
||||
radii = wsample.getVals();
|
||||
}
|
||||
|
||||
const int curve_count = per_curve_vertices_count->size();
|
||||
|
||||
PreprocessedSampleData data;
|
||||
/* Add 1 as these store offsets with the actual value being `offset[i + 1] - offset[i]`. */
|
||||
data.offset_in_blender.resize(curve_count + 1);
|
||||
data.offset_in_alembic.resize(curve_count + 1);
|
||||
data.curves_cyclic.resize(curve_count);
|
||||
data.curve_type = get_curve_type(smp.getBasis());
|
||||
data.knot_mode = get_knot_mode(smp.getType());
|
||||
data.do_cyclic = periodicity == Alembic::AbcGeom::kPeriodic;
|
||||
|
||||
/* If #kVariableOrder is set then we must have order data. If not, this sample is suspect.
|
||||
* Interpret the data as linear as a fallback. See #126324 for one such example.
|
||||
* See also: Alembic source code in `ICurves.h`, #ICurvesSchema::Sample::valid() */
|
||||
if (smp.getType() == Alembic::AbcGeom::kVariableOrder && !orders) {
|
||||
data.curve_type = CURVE_TYPE_POLY;
|
||||
data.knot_mode = NURBS_KNOT_MODE_NORMAL;
|
||||
data.do_cyclic = false;
|
||||
}
|
||||
|
||||
if (data.curve_type == CURVE_TYPE_NURBS) {
|
||||
data.curves_orders.resize(curve_count);
|
||||
}
|
||||
|
||||
/* Compute topological information. */
|
||||
|
||||
const int positions_size = positions->size();
|
||||
int blender_offset = 0;
|
||||
int alembic_offset = 0;
|
||||
for (size_t i = 0; i < curve_count; i++) {
|
||||
int vertices_count = (*per_curve_vertices_count)[i];
|
||||
|
||||
/* Guard against invalid vertex counts. */
|
||||
if (vertices_count < 0 || vertices_count > positions_size - alembic_offset) {
|
||||
vertices_count = std::max(0, positions_size - alembic_offset);
|
||||
}
|
||||
|
||||
const int curve_order = get_curve_order(smp.getType(), orders, i);
|
||||
|
||||
data.offset_in_blender[i] = blender_offset;
|
||||
data.offset_in_alembic[i] = alembic_offset;
|
||||
data.curves_cyclic[i] = data.do_cyclic;
|
||||
|
||||
if (data.curve_type == CURVE_TYPE_NURBS) {
|
||||
data.curves_orders[i] = curve_order;
|
||||
}
|
||||
|
||||
/* Some software writes repeated vertices to indicate periodicity but Blender
|
||||
* should skip these if present. */
|
||||
const int overlap = data.do_cyclic ?
|
||||
get_curve_overlap(
|
||||
positions, alembic_offset, vertices_count, curve_order) :
|
||||
0;
|
||||
|
||||
if (data.curve_type == CURVE_TYPE_BEZIER) {
|
||||
blender_offset += bezier_point_count(vertices_count, data.do_cyclic);
|
||||
}
|
||||
else {
|
||||
blender_offset += (overlap >= vertices_count) ? vertices_count : (vertices_count - overlap);
|
||||
}
|
||||
|
||||
alembic_offset += vertices_count;
|
||||
}
|
||||
data.offset_in_blender[curve_count] = blender_offset;
|
||||
data.offset_in_alembic[curve_count] = alembic_offset;
|
||||
|
||||
/* Store relevant pointers. */
|
||||
|
||||
data.positions = positions;
|
||||
|
||||
if (weights && weights->size() > 1) {
|
||||
data.weights = weights;
|
||||
}
|
||||
|
||||
if (radii && radii->size() > 1) {
|
||||
data.radii = radii;
|
||||
}
|
||||
|
||||
const std::optional<SampleInterpolationSettings> interpolation_settings =
|
||||
get_sample_interpolation_settings(
|
||||
sample_sel, schema.getTimeSampling(), schema.getNumSamples());
|
||||
|
||||
if (use_interpolation && interpolation_settings.has_value()) {
|
||||
Alembic::AbcGeom::ICurvesSchema::Sample ceil_smp;
|
||||
schema.get(ceil_smp, Alembic::Abc::ISampleSelector(interpolation_settings->ceil_index));
|
||||
if (samples_have_same_topology(smp, ceil_smp)) {
|
||||
data.ceil_positions = ceil_smp.getPositions();
|
||||
data.interpolation_settings = interpolation_settings;
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
AbcCurveReader::AbcCurveReader(const AbcReaderConstructorArgs &args) : AbcObjectReader(args)
|
||||
{
|
||||
ICurves abc_curves(m_iobject, kWrapExisting);
|
||||
m_curves_schema = abc_curves.getSchema();
|
||||
}
|
||||
|
||||
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 **r_err_str) const
|
||||
{
|
||||
if (!Alembic::AbcGeom::ICurves::matches(alembic_header)) {
|
||||
*r_err_str = RPT_(
|
||||
"Object type mismatch, Alembic object path pointed to Curves when importing, but not "
|
||||
"anymore.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ob->type != OB_CURVES) {
|
||||
*r_err_str = RPT_("Object type mismatch, Alembic object path points to Curves.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AbcCurveReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
|
||||
{
|
||||
Curves *curves = BKE_curves_add(bmain, m_data_name.c_str());
|
||||
|
||||
m_object = BKE_object_add_only_object(bmain, OB_CURVES, m_object_name.c_str());
|
||||
m_object->data = id_cast<ID *>(curves);
|
||||
|
||||
read_curves_sample(curves, false, m_curves_schema, sample_sel);
|
||||
|
||||
if (m_settings->always_add_cache_reader || has_animations(m_curves_schema, m_settings)) {
|
||||
addCacheModifier();
|
||||
}
|
||||
}
|
||||
|
||||
BLI_INLINE float3 interpolate_to_zup(const Span<Imath::V3f> &floor_positions,
|
||||
const Span<Imath::V3f> &ceil_positions,
|
||||
int i,
|
||||
float weight)
|
||||
{
|
||||
float3 p;
|
||||
const Imath::V3f &floor_pos = floor_positions[i];
|
||||
const Imath::V3f &ceil_pos = ceil_positions[i];
|
||||
|
||||
interp_v3_v3v3(p, floor_pos.getValue(), ceil_pos.getValue(), weight);
|
||||
copy_zup_from_yup(p, p);
|
||||
return p;
|
||||
}
|
||||
|
||||
static void add_bezier_control_point(int cp,
|
||||
int offset,
|
||||
const Span<Imath::V3f> floor_positions,
|
||||
const Span<Imath::V3f> ceil_positions,
|
||||
MutableSpan<float3> positions,
|
||||
MutableSpan<float3> handles_left,
|
||||
MutableSpan<float3> handles_right,
|
||||
float weight)
|
||||
{
|
||||
positions[cp] = interpolate_to_zup(floor_positions, ceil_positions, offset, weight);
|
||||
if (offset == 0) {
|
||||
handles_right[cp] = interpolate_to_zup(floor_positions, ceil_positions, offset + 1, weight);
|
||||
handles_left[cp] = 2.0f * positions[cp] - handles_right[cp];
|
||||
}
|
||||
else if (offset == floor_positions.size() - 1) {
|
||||
handles_left[cp] = interpolate_to_zup(floor_positions, ceil_positions, offset - 1, weight);
|
||||
handles_right[cp] = 2.0f * positions[cp] - handles_left[cp];
|
||||
}
|
||||
else {
|
||||
handles_left[cp] = interpolate_to_zup(floor_positions, ceil_positions, offset - 1, weight);
|
||||
handles_right[cp] = interpolate_to_zup(floor_positions, ceil_positions, offset + 1, weight);
|
||||
}
|
||||
}
|
||||
|
||||
void AbcCurveReader::read_curves_sample(Curves *curves_id,
|
||||
bool use_interpolation,
|
||||
const ICurvesSchema &schema,
|
||||
const ISampleSelector &sample_sel)
|
||||
{
|
||||
std::optional<PreprocessedSampleData> opt_preprocess = preprocess_sample(
|
||||
m_iobject.getFullName(), use_interpolation, schema, sample_sel);
|
||||
if (!opt_preprocess) {
|
||||
return;
|
||||
}
|
||||
|
||||
const PreprocessedSampleData &data = opt_preprocess.value();
|
||||
|
||||
const int point_count = data.offset_in_blender.last();
|
||||
const int curve_count = data.offset_in_blender.size() - 1;
|
||||
|
||||
bke::CurvesGeometry &curves = curves_id->geometry.wrap();
|
||||
|
||||
if (curves_topology_changed(curves, data.offset_in_blender)) {
|
||||
curves.resize(point_count, curve_count);
|
||||
curves.offsets_for_write().copy_from(data.offset_in_blender);
|
||||
}
|
||||
|
||||
curves.fill_curve_types(data.curve_type);
|
||||
|
||||
if (data.curve_type != CURVE_TYPE_POLY) {
|
||||
int16_t curve_resolution = get_curve_resolution(schema, sample_sel);
|
||||
if (curve_resolution > 0) {
|
||||
curves.resolution_for_write().fill(curve_resolution);
|
||||
}
|
||||
}
|
||||
|
||||
MutableSpan<float3> curves_positions = curves.positions_for_write();
|
||||
|
||||
Span<Imath::V3f> alembic_points{&(*data.positions)[0], int64_t((*data.positions).size())};
|
||||
Span<Imath::V3f> alembic_points_ceil;
|
||||
float interp_weight = 0.0f;
|
||||
if (data.interpolation_settings.has_value()) {
|
||||
alembic_points_ceil = {&(*data.ceil_positions)[0], int64_t((*data.ceil_positions).size())};
|
||||
interp_weight = data.interpolation_settings->weight;
|
||||
}
|
||||
else {
|
||||
alembic_points_ceil = alembic_points;
|
||||
}
|
||||
|
||||
if (data.curve_type == CURVE_TYPE_BEZIER) {
|
||||
curves.handle_types_left_for_write().fill(BEZIER_HANDLE_ALIGN);
|
||||
curves.handle_types_right_for_write().fill(BEZIER_HANDLE_ALIGN);
|
||||
|
||||
MutableSpan<float3> handles_right = curves.handle_positions_right_for_write();
|
||||
MutableSpan<float3> handles_left = curves.handle_positions_left_for_write();
|
||||
|
||||
int point_offset = 0;
|
||||
for (const int i_curve : curves.curves_range()) {
|
||||
const int alembic_point_offset = data.offset_in_alembic[i_curve];
|
||||
const int alembic_point_count = data.offset_in_alembic[i_curve + 1] - alembic_point_offset;
|
||||
const int cp_count = data.offset_in_blender[i_curve + 1] - data.offset_in_blender[i_curve];
|
||||
|
||||
int cp_offset = 0;
|
||||
for (const int cp : IndexRange(cp_count)) {
|
||||
add_bezier_control_point(
|
||||
cp,
|
||||
cp_offset,
|
||||
alembic_points.slice(alembic_point_offset, alembic_point_count),
|
||||
alembic_points_ceil.slice(alembic_point_offset, alembic_point_count),
|
||||
curves_positions.slice(point_offset, point_count),
|
||||
handles_left.slice(point_offset, point_count),
|
||||
handles_right.slice(point_offset, point_count),
|
||||
interp_weight);
|
||||
cp_offset += 3;
|
||||
}
|
||||
|
||||
point_offset += cp_count;
|
||||
}
|
||||
}
|
||||
else {
|
||||
for (const int i_curve : curves.curves_range()) {
|
||||
int position_offset = data.offset_in_alembic[i_curve];
|
||||
for (const int i_point : curves.points_by_curve()[i_curve]) {
|
||||
if (data.interpolation_settings.has_value()) {
|
||||
curves_positions[i_point] = interpolate_to_zup(
|
||||
alembic_points, alembic_points_ceil, position_offset++, interp_weight);
|
||||
}
|
||||
else {
|
||||
curves_positions[i_point] = to_zup_float3(alembic_points[position_offset++]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.do_cyclic) {
|
||||
curves.cyclic_for_write().copy_from(data.curves_cyclic);
|
||||
}
|
||||
|
||||
if (data.radii) {
|
||||
MutableSpan<float> radii = curves.radius_for_write();
|
||||
|
||||
Alembic::Abc::FloatArraySample alembic_widths = *data.radii;
|
||||
for (const int i_point : curves.points_range()) {
|
||||
radii[i_point] = alembic_widths[i_point] / 2.0f;
|
||||
}
|
||||
}
|
||||
|
||||
if (data.curve_type == CURVE_TYPE_NURBS) {
|
||||
curves.nurbs_orders_for_write().copy_from(data.curves_orders);
|
||||
curves.nurbs_knots_modes_for_write().fill(data.knot_mode);
|
||||
|
||||
if (data.weights) {
|
||||
MutableSpan<float> curves_weights = curves.nurbs_weights_for_write();
|
||||
Span<float> data_weights_span = {data.weights->get(), int64_t(data.weights->size())};
|
||||
for (const int i_curve : curves.curves_range()) {
|
||||
const int alembic_offset = data.offset_in_alembic[i_curve];
|
||||
const IndexRange points = curves.points_by_curve()[i_curve];
|
||||
curves_weights.slice(points).copy_from(
|
||||
data_weights_span.slice(alembic_offset, points.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbcCurveReader::read_geometry(bke::GeometrySet &geometry_set,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel,
|
||||
const AbcReadGeometryParams &read_params,
|
||||
const char ** /*r_err_str*/)
|
||||
{
|
||||
Curves *curves = geometry_set.get_curves_for_write();
|
||||
|
||||
bool use_interpolation = read_params.read_flag & MOD_MESHSEQ_INTERPOLATE_VERTICES;
|
||||
read_curves_sample(curves, use_interpolation, m_curves_schema, sample_sel);
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,47 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Kévin Dietrich. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_object.h"
|
||||
|
||||
#include <Alembic/AbcGeom/ICurves.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Curves;
|
||||
|
||||
#define ABC_CURVE_RESOLUTION_U_PROPNAME "blender:resolution"
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
class AbcCurveReader final : public AbcObjectReader {
|
||||
Alembic::AbcGeom::ICurvesSchema m_curves_schema;
|
||||
|
||||
public:
|
||||
AbcCurveReader(const AbcReaderConstructorArgs &args);
|
||||
|
||||
bool valid() const override;
|
||||
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
|
||||
const Object *const ob,
|
||||
const char **r_err_str) const override;
|
||||
|
||||
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
|
||||
|
||||
void read_geometry(bke::GeometrySet &geometry_set,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel,
|
||||
const AbcReadGeometryParams &read_params,
|
||||
const char **r_err_str) override;
|
||||
|
||||
void read_curves_sample(Curves *curves_id,
|
||||
bool use_interpolation,
|
||||
const Alembic::AbcGeom::ICurvesSchema &schema,
|
||||
const Alembic::Abc::ISampleSelector &sample_selector);
|
||||
};
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
1228
blender-5.2.0/source/blender/io/alembic/intern/abc_reader_mesh.cc
Normal file
1228
blender-5.2.0/source/blender/io/alembic/intern/abc_reader_mesh.cc
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,90 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include "abc_reader_object.h"
|
||||
|
||||
#include <Alembic/AbcGeom/IPolyMesh.h>
|
||||
#include <Alembic/AbcGeom/ISubD.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Mesh;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
class AbcMeshReader final : public AbcObjectReader {
|
||||
Alembic::AbcGeom::IPolyMeshSchema m_schema;
|
||||
|
||||
public:
|
||||
AbcMeshReader(const AbcReaderConstructorArgs &args);
|
||||
|
||||
bool valid() const override;
|
||||
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
|
||||
const Object *const ob,
|
||||
const char **r_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,
|
||||
const AbcReadGeometryParams &read_params,
|
||||
const char **r_err_str);
|
||||
|
||||
void read_geometry(bke::GeometrySet &geometry_set,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel,
|
||||
const AbcReadGeometryParams &read_params,
|
||||
const char **r_err_str) override;
|
||||
|
||||
bool topology_changed(const 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_material_indices(const Alembic::Abc::ISampleSelector &sample_sel,
|
||||
MutableSpan<int> material_indices,
|
||||
std::map<std::string, int> &r_mat_map);
|
||||
};
|
||||
|
||||
class AbcSubDReader final : public AbcObjectReader {
|
||||
Alembic::AbcGeom::ISubDSchema m_schema;
|
||||
|
||||
public:
|
||||
AbcSubDReader(const AbcReaderConstructorArgs &args);
|
||||
|
||||
bool valid() const override;
|
||||
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
|
||||
const Object *const ob,
|
||||
const char **r_err_str) const override;
|
||||
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
|
||||
|
||||
void read_geometry(bke::GeometrySet &geometry_set,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel,
|
||||
const AbcReadGeometryParams &read_params,
|
||||
const char **r_err_str) override;
|
||||
|
||||
bool topology_changed(const Mesh *existing_mesh,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel) override;
|
||||
|
||||
private:
|
||||
struct Mesh *read_mesh(struct Mesh *existing_mesh,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel,
|
||||
const AbcReadGeometryParams &read_params,
|
||||
const char **r_err_str);
|
||||
};
|
||||
|
||||
void read_mverts(Mesh &mesh,
|
||||
const Alembic::AbcGeom::P3fArraySamplePtr positions,
|
||||
const Alembic::AbcGeom::N3fArraySamplePtr normals);
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,241 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_nurbs.h"
|
||||
#include "abc_axis_conversion.h"
|
||||
#include "abc_util.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_curve_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "BKE_curve.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
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;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
static CLG_LogRef LOG = {"io.alembic"};
|
||||
|
||||
AbcNurbsReader::AbcNurbsReader(const AbcReaderConstructorArgs &args) : AbcObjectReader(args)
|
||||
{
|
||||
getNurbsPatches(m_iobject);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool AbcNurbsReader::accepts_object_type(
|
||||
const Alembic::AbcCoreAbstract::v12::ObjectHeader &alembic_header,
|
||||
const Object *const ob,
|
||||
const char **r_err_str) const
|
||||
{
|
||||
if (!Alembic::AbcGeom::INuPatch::matches(alembic_header)) {
|
||||
*r_err_str = RPT_(
|
||||
"Object type mismatch, Alembic object path pointed to NURBS when importing, but not any "
|
||||
"more");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ob->type != OB_CURVES_LEGACY) {
|
||||
*r_err_str = RPT_("Object type mismatch, Alembic object path points to NURBS");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool set_knots(const FloatArraySamplePtr &knots, float *&nu_knots)
|
||||
{
|
||||
if (!knots || knots->size() < 2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/* Skip first and last knots, as they are used for padding. */
|
||||
const size_t num_knots = knots->size() - 2;
|
||||
nu_knots = MEM_new_array_zeroed<float>(num_knots, "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 = BKE_curve_add(bmain, m_data_name.c_str(), 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 = MEM_new<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) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Error reading nurbs sample for '%s/%s' at time %f: %s",
|
||||
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();
|
||||
const bool has_weights = weights && weights->size() >= num_points;
|
||||
|
||||
nu->bp = MEM_new_array_zeroed<BPoint>(num_points, "abc_setsplinetype");
|
||||
|
||||
BPoint *bp = nu->bp;
|
||||
float posw_in = 1.0f;
|
||||
|
||||
for (size_t i = 0; i < num_points; i++, bp++) {
|
||||
const Imath::V3f &pos_in = (*positions)[i];
|
||||
|
||||
if (has_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);
|
||||
}
|
||||
|
||||
m_object = BKE_object_add_only_object(bmain, OB_SURF, m_object_name.c_str());
|
||||
m_object->data = id_cast<ID *>(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.emplace_back(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.emplace_back(schem, child);
|
||||
}
|
||||
|
||||
getNurbsPatches(child);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,34 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_object.h"
|
||||
|
||||
#include <Alembic/AbcGeom/INuPatch.h>
|
||||
|
||||
namespace blender::io::alembic {
|
||||
|
||||
class AbcNurbsReader final : public AbcObjectReader {
|
||||
std::vector<std::pair<Alembic::AbcGeom::INuPatchSchema, Alembic::Abc::IObject>> m_schemas;
|
||||
|
||||
public:
|
||||
AbcNurbsReader(const AbcReaderConstructorArgs &args);
|
||||
|
||||
bool valid() const override;
|
||||
|
||||
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
|
||||
const Object *const ob,
|
||||
const char **r_err_str) const override;
|
||||
|
||||
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
|
||||
|
||||
private:
|
||||
void getNurbsPatches(const Alembic::Abc::IObject &obj);
|
||||
};
|
||||
|
||||
} // namespace blender::io::alembic
|
||||
@@ -0,0 +1,425 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_object.h"
|
||||
#include "abc_axis_conversion.h"
|
||||
#include "abc_keyframing.h"
|
||||
#include "abc_util.h"
|
||||
|
||||
#include "DNA_cachefile_types.h"
|
||||
#include "DNA_constraint_types.h"
|
||||
#include "DNA_modifier_types.h"
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "ANIM_fcurve.hh"
|
||||
|
||||
#include "BKE_constraint.h"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_modifier.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_object_types.hh"
|
||||
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_math_rotation.h"
|
||||
#include "BLI_string.h"
|
||||
|
||||
#include "Alembic/AbcGeom/Visibility.h"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
using Alembic::AbcGeom::IObject;
|
||||
using Alembic::AbcGeom::ISampleSelector;
|
||||
using Alembic::AbcGeom::IVisibilityProperty;
|
||||
using Alembic::AbcGeom::IXform;
|
||||
using Alembic::AbcGeom::IXformSchema;
|
||||
using Alembic::AbcGeom::ObjectVisibility;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
static CLG_LogRef LOG = {"io.alembic"};
|
||||
|
||||
AbcReaderConstructorArgs create_reader_constructor_args(const IObject &object,
|
||||
ImportSettings &settings)
|
||||
{
|
||||
return AbcReaderConstructorArgs{.object = object, .settings = settings};
|
||||
}
|
||||
|
||||
AbcObjectReader::AbcObjectReader(const AbcReaderConstructorArgs &args)
|
||||
: m_object(nullptr),
|
||||
m_iobject(args.object),
|
||||
m_settings(&args.settings),
|
||||
m_is_reading_a_file_sequence(args.settings.is_sequence),
|
||||
m_refcount(0),
|
||||
parent_reader(nullptr)
|
||||
{
|
||||
m_name = m_iobject.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();
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 double 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.
|
||||
*/
|
||||
|
||||
convert_matrix_datatype(m0, mat0);
|
||||
convert_matrix_datatype(m1, mat1);
|
||||
interp_m4_m4m4(ret, mat0, mat1, float(weight));
|
||||
return convert_matrix_datatype(ret);
|
||||
}
|
||||
|
||||
Imath::M44d get_matrix(const IXformSchema &schema, const chrono_t time)
|
||||
{
|
||||
Alembic::AbcGeom::ISampleSelector selector(time);
|
||||
|
||||
const std::optional<SampleInterpolationSettings> interpolation_settings =
|
||||
get_sample_interpolation_settings(
|
||||
selector, schema.getTimeSampling(), schema.getNumSamples());
|
||||
|
||||
if (!interpolation_settings.has_value()) {
|
||||
/* No interpolation, just read the current time. */
|
||||
Alembic::AbcGeom::XformSample s0;
|
||||
schema.get(s0, selector);
|
||||
return s0.getMatrix();
|
||||
}
|
||||
|
||||
Alembic::AbcGeom::XformSample s0, s1;
|
||||
schema.get(s0, Alembic::AbcGeom::ISampleSelector(interpolation_settings->index));
|
||||
schema.get(s1, Alembic::AbcGeom::ISampleSelector(interpolation_settings->ceil_index));
|
||||
return blend_matrices(s0.getMatrix(), s1.getMatrix(), interpolation_settings->weight);
|
||||
}
|
||||
|
||||
void AbcObjectReader::read_geometry(bke::GeometrySet & /*geometry_set*/,
|
||||
const Alembic::Abc::ISampleSelector & /*sample_sel*/,
|
||||
const AbcReadGeometryParams & /*read_params*/,
|
||||
const char ** /*r_err_str*/)
|
||||
{
|
||||
}
|
||||
|
||||
bool AbcObjectReader::topology_changed(const 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;
|
||||
}
|
||||
|
||||
class VisibilityFCurveCreationHelper : public FCurveCreationHelper {
|
||||
IObject vis_object_{};
|
||||
IVisibilityProperty vis_prop_{};
|
||||
|
||||
FCurve *viewport_fcurve = nullptr;
|
||||
FCurve *render_fcurve = nullptr;
|
||||
|
||||
public:
|
||||
VisibilityFCurveCreationHelper(Object *object,
|
||||
const IObject &vis_object,
|
||||
const IVisibilityProperty &vis_prop)
|
||||
: FCurveCreationHelper(&object->id), vis_object_(vis_object), vis_prop_(vis_prop)
|
||||
{
|
||||
}
|
||||
|
||||
void create_fcurves(const int sample_count) override
|
||||
{
|
||||
viewport_fcurve = create_fcurve({"hide_viewport", 0}, sample_count);
|
||||
render_fcurve = create_fcurve({"hide_render", 0}, sample_count);
|
||||
}
|
||||
|
||||
void set_fcurves_sample(const FrameSampleInfo &sample_info) override
|
||||
{
|
||||
ObjectVisibility vis = ObjectVisibility(vis_prop_.getValue(sample_info.selector));
|
||||
|
||||
if (vis == Alembic::AbcGeom::kVisibilityDeferred) {
|
||||
IObject parent = vis_object_.getParent();
|
||||
|
||||
while (parent) {
|
||||
const IVisibilityProperty &parent_vis_prop(
|
||||
Alembic::AbcGeom::GetVisibilityProperty(parent));
|
||||
if (parent_vis_prop) {
|
||||
vis = ObjectVisibility(parent_vis_prop.getValue(sample_info.selector));
|
||||
if (vis != Alembic::AbcGeom::kVisibilityDeferred) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
parent = parent.getParent();
|
||||
}
|
||||
}
|
||||
|
||||
const float hidden = (vis == ObjectVisibility::kVisibilityHidden) ? 1.0f : 0.0f;
|
||||
set_fcurve_sample(viewport_fcurve, sample_info.sample_index, sample_info.frame, hidden);
|
||||
set_fcurve_sample(render_fcurve, sample_info.sample_index, sample_info.frame, hidden);
|
||||
}
|
||||
};
|
||||
|
||||
void AbcObjectReader::getKeyFramingHelpers(
|
||||
Vector<std::unique_ptr<FCurveCreationHelper>> &keyframing_helpers)
|
||||
{
|
||||
/* Check if we have animated visibility. */
|
||||
IObject vis_object = m_iobject;
|
||||
ObjectVisibility vis = Alembic::AbcGeom::kVisibilityDeferred;
|
||||
while (vis_object) {
|
||||
IVisibilityProperty vis_prop = Alembic::AbcGeom::GetVisibilityProperty(vis_object);
|
||||
if (vis_prop) {
|
||||
if (!vis_prop.isConstant()) {
|
||||
std::unique_ptr<FCurveCreationHelper> helper =
|
||||
std::make_unique<VisibilityFCurveCreationHelper>(m_object, vis_object, vis_prop);
|
||||
keyframing_helpers.append(std::move(helper));
|
||||
m_has_visibility_keyframes = true;
|
||||
break;
|
||||
}
|
||||
|
||||
vis = ObjectVisibility(vis_prop.getValue(ISampleSelector()));
|
||||
if (vis != Alembic::AbcGeom::kVisibilityDeferred) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
vis_object = vis_object.getParent();
|
||||
}
|
||||
|
||||
/* Helper for the object data. */
|
||||
std::unique_ptr<FCurveCreationHelper> specific_helper = getKeyFramingHelper();
|
||||
if (specific_helper) {
|
||||
keyframing_helpers.append(std::move(specific_helper));
|
||||
}
|
||||
}
|
||||
|
||||
std::unique_ptr<FCurveCreationHelper> AbcObjectReader::getKeyFramingHelper()
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AbcObjectReader::setupObjectTransform(const chrono_t 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->runtime->object_to_world.ptr());
|
||||
|
||||
if (!is_constant || m_settings->always_add_cache_reader) {
|
||||
bConstraint *con = BKE_constraint_add_for_object(
|
||||
m_object, nullptr, CONSTRAINT_TYPE_TRANSFORM_CACHE);
|
||||
bTransformCacheConstraint *data = static_cast<bTransformCacheConstraint *>(con->data);
|
||||
STRNCPY(data->object_path, m_iobject.getFullName().c_str());
|
||||
|
||||
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) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Error reading object transform for '%s': %s",
|
||||
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 recognized 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) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Error reading object transform for '%s': %s",
|
||||
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 chrono_t time,
|
||||
const float scale,
|
||||
bool &r_is_constant)
|
||||
{
|
||||
IXform ixform = xform();
|
||||
if (!ixform) {
|
||||
unit_m4(r_mat);
|
||||
r_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);
|
||||
}
|
||||
|
||||
r_is_constant = schema.isConstant();
|
||||
}
|
||||
|
||||
void AbcObjectReader::addCacheModifier()
|
||||
{
|
||||
ModifierData *md = BKE_modifier_new(eModifierType_MeshSequenceCache);
|
||||
BLI_addtail(&m_object->modifiers, md);
|
||||
BKE_modifiers_persistent_uid_init(*m_object, *md);
|
||||
|
||||
MeshSeqCacheModifierData *mcmd = reinterpret_cast<MeshSeqCacheModifierData *>(md);
|
||||
|
||||
mcmd->cache_file = m_settings->cache_file;
|
||||
id_us_plus(&mcmd->cache_file->id);
|
||||
|
||||
STRNCPY(mcmd->object_path, m_iobject.getFullName().c_str());
|
||||
}
|
||||
|
||||
void AbcObjectReader::readVisibility()
|
||||
{
|
||||
IObject vis_object = m_iobject;
|
||||
ObjectVisibility vis = Alembic::AbcGeom::kVisibilityDeferred;
|
||||
while (vis_object) {
|
||||
IVisibilityProperty vis_prop = Alembic::AbcGeom::GetVisibilityProperty(vis_object);
|
||||
if (vis_prop) {
|
||||
if (!vis_prop.isConstant()) {
|
||||
return;
|
||||
}
|
||||
vis = ObjectVisibility(vis_prop.getValue(ISampleSelector()));
|
||||
if (vis != Alembic::AbcGeom::kVisibilityDeferred) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
vis_object = vis_object.getParent();
|
||||
}
|
||||
|
||||
if (vis == Alembic::AbcGeom::kVisibilityHidden) {
|
||||
m_object->visibility_flag |= (OB_HIDE_RENDER | OB_HIDE_VIEWPORT);
|
||||
}
|
||||
}
|
||||
|
||||
int AbcObjectReader::refcount() const
|
||||
{
|
||||
return m_refcount;
|
||||
}
|
||||
|
||||
void AbcObjectReader::incref()
|
||||
{
|
||||
m_refcount++;
|
||||
}
|
||||
|
||||
void AbcObjectReader::decref()
|
||||
{
|
||||
m_refcount--;
|
||||
BLI_assert(m_refcount >= 0);
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,195 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "BLI_vector.hh"
|
||||
|
||||
#include <Alembic/Abc/IObject.h>
|
||||
#include <Alembic/Abc/ISampleSelector.h>
|
||||
#include <Alembic/AbcCoreAbstract/Foundation.h>
|
||||
#include <Alembic/AbcCoreAbstract/ObjectHeader.h>
|
||||
#include <Alembic/AbcGeom/IXform.h>
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct CacheFile;
|
||||
struct Main;
|
||||
struct Mesh;
|
||||
struct Object;
|
||||
|
||||
namespace bke {
|
||||
struct GeometrySet;
|
||||
}
|
||||
|
||||
using Alembic::AbcCoreAbstract::chrono_t;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
class FCurveCreationHelper;
|
||||
|
||||
struct TimeInfo;
|
||||
|
||||
struct ImportSettings {
|
||||
bool blender_archive_version_prior_44 = false;
|
||||
|
||||
bool do_convert_mat = false;
|
||||
float conversion_mat[4][4];
|
||||
|
||||
int from_up = 0;
|
||||
int from_forward = 0;
|
||||
float scale = 1.0f;
|
||||
bool is_sequence = false;
|
||||
bool set_frame_range = false;
|
||||
|
||||
/* Min and max frame detected from file sequences. */
|
||||
int sequence_min_frame = 0;
|
||||
int sequence_max_frame = 1;
|
||||
|
||||
/* From MeshSeqCacheModifierData.read_flag */
|
||||
int read_flag = 0;
|
||||
|
||||
/* From CacheFile and MeshSeqCacheModifierData */
|
||||
std::string velocity_name;
|
||||
float velocity_scale = 1.0f;
|
||||
|
||||
bool validate_meshes = false;
|
||||
bool always_add_cache_reader = false;
|
||||
|
||||
CacheFile *cache_file = nullptr;
|
||||
|
||||
ImportSettings() = default;
|
||||
};
|
||||
|
||||
template<typename Schema> static bool has_animations(Schema &schema, ImportSettings *settings)
|
||||
{
|
||||
return settings->is_sequence || !schema.isConstant();
|
||||
}
|
||||
|
||||
struct AbcReadGeometryParams {
|
||||
std::string velocity_name;
|
||||
int read_flag = 0;
|
||||
float velocity_scale = 1.0f;
|
||||
};
|
||||
|
||||
struct AbcReaderConstructorArgs {
|
||||
const Alembic::Abc::IObject &object;
|
||||
ImportSettings &settings;
|
||||
};
|
||||
|
||||
AbcReaderConstructorArgs create_reader_constructor_args(const Alembic::Abc::IObject &object,
|
||||
ImportSettings &settings);
|
||||
|
||||
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;
|
||||
|
||||
/* XXX - This used to reference stack memory for MeshSequenceCache scenarios. That has been
|
||||
* addressed but ownership of these settings should be made more apparent to prevent similar
|
||||
* issues in the future. */
|
||||
ImportSettings *m_settings;
|
||||
/* This is initialized from the ImportSettings above on construction. It will need to be removed
|
||||
* once we fix the stack memory reference situation. */
|
||||
bool m_is_reading_a_file_sequence = false;
|
||||
|
||||
/* Use reference counting since the same reader may be used by multiple
|
||||
* modifiers and/or constraints. */
|
||||
int m_refcount;
|
||||
|
||||
bool m_inherits_xform;
|
||||
|
||||
bool m_has_visibility_keyframes = false;
|
||||
|
||||
public:
|
||||
AbcObjectReader *parent_reader;
|
||||
|
||||
public:
|
||||
explicit AbcObjectReader(const AbcReaderConstructorArgs &args);
|
||||
|
||||
virtual ~AbcObjectReader() = default;
|
||||
|
||||
const Alembic::Abc::IObject &iobject() const;
|
||||
|
||||
using ptr_vector = std::vector<AbcObjectReader *>;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
bool has_visibility_keyframes() const
|
||||
{
|
||||
return m_has_visibility_keyframes;
|
||||
}
|
||||
|
||||
virtual bool valid() const = 0;
|
||||
virtual bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
|
||||
const Object *const ob,
|
||||
const char **r_err_str) const = 0;
|
||||
|
||||
virtual void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) = 0;
|
||||
|
||||
virtual void read_geometry(bke::GeometrySet &geometry_set,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel,
|
||||
const AbcReadGeometryParams &read_params,
|
||||
const char **r_err_str);
|
||||
|
||||
virtual bool topology_changed(const Mesh *existing_mesh,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel);
|
||||
|
||||
void getKeyFramingHelpers(Vector<std::unique_ptr<FCurveCreationHelper>> &keyframing_helpers);
|
||||
|
||||
virtual std::unique_ptr<FCurveCreationHelper> getKeyFramingHelper();
|
||||
|
||||
/** Reads the object matrix and sets up an object transform if animated. */
|
||||
void setupObjectTransform(chrono_t time);
|
||||
|
||||
void addCacheModifier();
|
||||
void readVisibility();
|
||||
|
||||
int refcount() const;
|
||||
void incref();
|
||||
void decref();
|
||||
|
||||
void read_matrix(float r_mat[4][4], chrono_t time, float scale, bool &is_constant);
|
||||
|
||||
protected:
|
||||
/** Determine whether we can inherit our parent's XForm. */
|
||||
void determine_inherits_xform();
|
||||
};
|
||||
|
||||
Imath::M44d get_matrix(const Alembic::AbcGeom::IXformSchema &schema, chrono_t time);
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,294 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Kévin Dietrich. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_points.h"
|
||||
#include "abc_axis_conversion.h"
|
||||
#include "abc_util.h"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_pointcloud_types.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "BKE_geometry_set.hh"
|
||||
#include "BKE_object.hh"
|
||||
#include "BKE_pointcloud.hh"
|
||||
|
||||
#include "BLI_color_types.hh"
|
||||
|
||||
#include "IO_validate.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace blender {
|
||||
|
||||
using namespace Alembic::AbcGeom;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
static CLG_LogRef LOG = {"io.alembic"};
|
||||
|
||||
AbcPointsReader::AbcPointsReader(const AbcReaderConstructorArgs &args) : AbcObjectReader(args)
|
||||
{
|
||||
IPoints ipoints(m_iobject, kWrapExisting);
|
||||
m_schema = ipoints.getSchema();
|
||||
}
|
||||
|
||||
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 **r_err_str) const
|
||||
{
|
||||
if (!Alembic::AbcGeom::IPoints::matches(alembic_header)) {
|
||||
*r_err_str = RPT_(
|
||||
"Object type mismatch, Alembic object path pointed to Points when importing, but not any "
|
||||
"more");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ob->type != OB_POINTCLOUD) {
|
||||
*r_err_str = RPT_("Object type mismatch, Alembic object path points to Points.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AbcPointsReader::readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel)
|
||||
{
|
||||
PointCloud *pointcloud = BKE_pointcloud_add(bmain, m_data_name.c_str());
|
||||
|
||||
bke::GeometrySet geometry_set = bke::GeometrySet::from_pointcloud(
|
||||
pointcloud, bke::GeometryOwnershipType::Editable);
|
||||
AbcReadGeometryParams read_params{};
|
||||
read_geometry(geometry_set, sample_sel, read_params, nullptr);
|
||||
|
||||
PointCloud *read_pointcloud =
|
||||
geometry_set.get_component_for_write<bke::PointCloudComponent>().release();
|
||||
|
||||
if (read_pointcloud != pointcloud) {
|
||||
BKE_pointcloud_nomain_to_pointcloud(read_pointcloud, pointcloud);
|
||||
}
|
||||
|
||||
m_object = BKE_object_add_only_object(bmain, OB_POINTCLOUD, m_object_name.c_str());
|
||||
m_object->data = id_cast<ID *>(pointcloud);
|
||||
|
||||
if (m_settings->always_add_cache_reader || has_animations(m_schema, m_settings)) {
|
||||
addCacheModifier();
|
||||
}
|
||||
}
|
||||
|
||||
static void read_points(const P3fArraySamplePtr positions, MutableSpan<float3> r_points)
|
||||
{
|
||||
for (size_t i = 0; i < positions->size(); i++) {
|
||||
copy_zup_from_yup(r_points[i], (*positions)[i].getValue());
|
||||
}
|
||||
}
|
||||
|
||||
static void read_points_sample(const IPointsSchema &schema,
|
||||
const ISampleSelector &selector,
|
||||
MutableSpan<float3> r_points)
|
||||
{
|
||||
Alembic::AbcGeom::IPointsSchema::Sample sample = schema.getValue(selector);
|
||||
|
||||
const P3fArraySamplePtr &positions = sample.getPositions();
|
||||
read_points(positions, r_points);
|
||||
}
|
||||
|
||||
template<typename TOut, typename TIn> static TOut convert_abc_value(const TIn &in)
|
||||
{
|
||||
static_assert(std::is_same_v<TIn, TOut>,
|
||||
"convert_abc_value needs to be explicitly specialized for each pair of types");
|
||||
return in;
|
||||
}
|
||||
|
||||
template<> float3 convert_abc_value(const V3f &in)
|
||||
{
|
||||
float3 out;
|
||||
copy_zup_from_yup(out, in.getValue());
|
||||
return out;
|
||||
}
|
||||
|
||||
template<> ColorGeometry4f convert_abc_value(const C3f &in)
|
||||
{
|
||||
return ColorGeometry4f(in[0], in[1], in[2], 1.0f);
|
||||
}
|
||||
|
||||
template<> float2 convert_abc_value(const V2f &in)
|
||||
{
|
||||
return in.getValue();
|
||||
}
|
||||
|
||||
template<typename TArrayProperty, typename TWriteValue>
|
||||
static void read_typed_property_sample(const ICompoundProperty &parent,
|
||||
const ISampleSelector &selector,
|
||||
const std::string &name,
|
||||
bke::MutableAttributeAccessor &attribute_accessor)
|
||||
{
|
||||
const TArrayProperty &array_prop = TArrayProperty(parent, name);
|
||||
if (array_prop) {
|
||||
using SamplePtr = typename TArrayProperty::sample_ptr_type;
|
||||
using ValueType = typename TArrayProperty::value_type;
|
||||
|
||||
const SamplePtr sample_ptr = array_prop.getValue(selector);
|
||||
bke::SpanAttributeWriter<TWriteValue> writer =
|
||||
attribute_accessor.lookup_or_add_for_write_span<TWriteValue>(name, bke::AttrDomain::Point);
|
||||
MutableSpan<TWriteValue> span = writer.span;
|
||||
for (const int64_t i : IndexRange(std::min(span.size(), int64_t(sample_ptr->size())))) {
|
||||
ValueType value = (*sample_ptr)[i];
|
||||
span[i] = convert_abc_value<TWriteValue>(value);
|
||||
}
|
||||
writer.finish();
|
||||
}
|
||||
}
|
||||
|
||||
static void read_point_arb_geom_params(const IPointsSchema &schema,
|
||||
const ISampleSelector &selector,
|
||||
bke::MutableAttributeAccessor &attribute_accessor)
|
||||
{
|
||||
const ICompoundProperty prop = schema.getArbGeomParams();
|
||||
if (!prop.valid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < prop.getNumProperties(); i++) {
|
||||
const PropertyHeader header = prop.getPropertyHeader(i);
|
||||
const PropertyType property_type = header.getPropertyType();
|
||||
if (property_type != kArrayProperty) {
|
||||
// currently unsupported
|
||||
continue;
|
||||
}
|
||||
|
||||
const DataType data_type = header.getDataType();
|
||||
const MetaData metadata = header.getMetaData();
|
||||
const std::string interpretation = metadata.get("interpretation");
|
||||
const std::string name = header.getName();
|
||||
|
||||
if (data_type == DataType(kFloat32POD, 3)) {
|
||||
if (interpretation == C3fTPTraits::interpretation()) {
|
||||
read_typed_property_sample<IC3fArrayProperty, ColorGeometry4f>(
|
||||
prop, selector, name, attribute_accessor);
|
||||
}
|
||||
else if (interpretation == N3fTPTraits::interpretation()) {
|
||||
read_typed_property_sample<IN3fArrayProperty, float3>(
|
||||
prop, selector, name, attribute_accessor);
|
||||
}
|
||||
else {
|
||||
read_typed_property_sample<IV3fArrayProperty, float3>(
|
||||
prop, selector, name, attribute_accessor);
|
||||
}
|
||||
}
|
||||
else if (data_type == DataType(kFloat32POD, 2)) {
|
||||
read_typed_property_sample<IV2fArrayProperty, float2>(
|
||||
prop, selector, name, attribute_accessor);
|
||||
}
|
||||
else if (data_type == DataType(kFloat32POD, 1)) {
|
||||
read_typed_property_sample<IFloatArrayProperty, float>(
|
||||
prop, selector, name, attribute_accessor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AbcPointsReader::read_geometry(bke::GeometrySet &geometry_set,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel,
|
||||
const AbcReadGeometryParams &read_params,
|
||||
const char **r_err_str)
|
||||
{
|
||||
BLI_assert(geometry_set.has_pointcloud());
|
||||
|
||||
IPointsSchema::Sample sample;
|
||||
try {
|
||||
sample = m_schema.getValue(sample_sel);
|
||||
}
|
||||
catch (Alembic::Util::Exception &ex) {
|
||||
*r_err_str = RPT_("Error reading points sample; more detail on the console");
|
||||
CLOG_WARN(&LOG,
|
||||
"Error reading points sample for '%s/%s' at time %f: %s",
|
||||
m_iobject.getFullName().c_str(),
|
||||
m_schema.getName().c_str(),
|
||||
sample_sel.getRequestedTime(),
|
||||
ex.what());
|
||||
return;
|
||||
}
|
||||
|
||||
PointCloud *existing_pointcloud = geometry_set.get_pointcloud_for_write();
|
||||
PointCloud *pointcloud = existing_pointcloud;
|
||||
|
||||
const P3fArraySamplePtr &positions = sample.getPositions();
|
||||
|
||||
const IFloatGeomParam widths_param = m_schema.getWidthsParam();
|
||||
FloatArraySamplePtr widths;
|
||||
|
||||
if (widths_param.valid()) {
|
||||
IFloatGeomParam::Sample wsample = widths_param.getExpandedValue(sample_sel);
|
||||
widths = wsample.getVals();
|
||||
}
|
||||
|
||||
if (!validate::size_fits_in_int(positions->size())) {
|
||||
CLOG_WARN(&LOG,
|
||||
"Point cloud too large to import for '%s/%s' at time %f, exceeds max int size",
|
||||
m_iobject.getFullName().c_str(),
|
||||
m_schema.getName().c_str(),
|
||||
sample_sel.getRequestedTime());
|
||||
return;
|
||||
}
|
||||
|
||||
if (pointcloud->totpoint != positions->size()) {
|
||||
pointcloud = BKE_pointcloud_new_nomain(positions->size());
|
||||
}
|
||||
|
||||
bke::MutableAttributeAccessor attribute_accessor = pointcloud->attributes_for_write();
|
||||
|
||||
MutableSpan<float3> point_positions = pointcloud->positions_for_write();
|
||||
read_points_sample(m_schema, sample_sel, point_positions);
|
||||
|
||||
if (widths) {
|
||||
MutableSpan<float> point_radii = pointcloud->radius_for_write();
|
||||
for (const int64_t i : IndexRange(std::min(point_radii.size(), int64_t(widths->size())))) {
|
||||
point_radii[i] = (*widths)[i] / 2.0f;
|
||||
}
|
||||
}
|
||||
else {
|
||||
attribute_accessor.remove("radius");
|
||||
attribute_accessor.add<float>(
|
||||
"radius", bke::AttrDomain::Point, bke::AttributeInitValue(0.01f));
|
||||
}
|
||||
|
||||
read_point_arb_geom_params(m_schema, sample_sel, attribute_accessor);
|
||||
|
||||
if (read_params.velocity_name != "" && read_params.velocity_scale != 0.0f) {
|
||||
V3fArraySamplePtr velocities = get_velocity_prop(
|
||||
m_schema, sample_sel, read_params.velocity_name);
|
||||
if (velocities && pointcloud->totpoint == int(velocities->size())) {
|
||||
bke::SpanAttributeWriter<float3> velocity_writer =
|
||||
attribute_accessor.lookup_or_add_for_write_span<float3>("velocity",
|
||||
bke::AttrDomain::Point);
|
||||
MutableSpan<float3> point_velocity = velocity_writer.span;
|
||||
for (const int64_t i :
|
||||
IndexRange(std::min(point_velocity.size(), int64_t(velocities->size()))))
|
||||
{
|
||||
const Imath::V3f &vel_in = (*velocities)[i];
|
||||
copy_zup_from_yup(point_velocity[i], vel_in.getValue());
|
||||
point_velocity[i] *= read_params.velocity_scale;
|
||||
}
|
||||
velocity_writer.finish();
|
||||
}
|
||||
}
|
||||
|
||||
geometry_set.replace_pointcloud(pointcloud);
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,36 @@
|
||||
/* SPDX-FileCopyrightText: 2016 Kévin Dietrich. All rights reserved.
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_object.h"
|
||||
|
||||
#include <Alembic/AbcGeom/IPoints.h>
|
||||
|
||||
namespace blender::io::alembic {
|
||||
|
||||
class AbcPointsReader final : public AbcObjectReader {
|
||||
Alembic::AbcGeom::IPointsSchema m_schema;
|
||||
Alembic::AbcGeom::IPointsSchema::Sample m_sample;
|
||||
|
||||
public:
|
||||
AbcPointsReader(const AbcReaderConstructorArgs &args);
|
||||
|
||||
bool valid() const override;
|
||||
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
|
||||
const Object *const ob,
|
||||
const char **r_err_str) const override;
|
||||
|
||||
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
|
||||
|
||||
void read_geometry(bke::GeometrySet &geometry_set,
|
||||
const Alembic::Abc::ISampleSelector &sample_sel,
|
||||
const AbcReadGeometryParams &read_params,
|
||||
const char **r_err_str) override;
|
||||
};
|
||||
|
||||
} // namespace blender::io::alembic
|
||||
@@ -0,0 +1,66 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_transform.h"
|
||||
#include "abc_util.h"
|
||||
|
||||
#include "DNA_object_types.h"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "BKE_object.hh"
|
||||
|
||||
namespace blender {
|
||||
|
||||
using Alembic::Abc::ISampleSelector;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
AbcEmptyReader::AbcEmptyReader(const AbcReaderConstructorArgs &args) : AbcObjectReader(args)
|
||||
{
|
||||
/* 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 = m_iobject.getName();
|
||||
|
||||
Alembic::AbcGeom::IXform xform(m_iobject, Alembic::AbcGeom::kWrapExisting);
|
||||
m_schema = xform.getSchema();
|
||||
}
|
||||
|
||||
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 **r_err_str) const
|
||||
{
|
||||
if (!Alembic::AbcGeom::IXform::matches(alembic_header)) {
|
||||
*r_err_str = RPT_(
|
||||
"Object type mismatch, Alembic object path pointed to XForm when importing, but not any "
|
||||
"more");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ob->type != OB_EMPTY) {
|
||||
*r_err_str = RPT_("Object type mismatch, Alembic object path points to XForm");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void AbcEmptyReader::readObjectData(Main *bmain, const ISampleSelector & /*sample_sel*/)
|
||||
{
|
||||
m_object = BKE_object_add_only_object(bmain, OB_EMPTY, m_object_name.c_str());
|
||||
m_object->data = nullptr;
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
@@ -0,0 +1,38 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_object.h"
|
||||
|
||||
#include <Alembic/Abc/IObject.h>
|
||||
#include <Alembic/Abc/ISampleSelector.h>
|
||||
#include <Alembic/AbcCoreAbstract/ObjectHeader.h>
|
||||
#include <Alembic/AbcGeom/IXform.h>
|
||||
|
||||
namespace blender {
|
||||
|
||||
struct Object;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
class AbcEmptyReader final : public AbcObjectReader {
|
||||
Alembic::AbcGeom::IXformSchema m_schema;
|
||||
|
||||
public:
|
||||
AbcEmptyReader(const AbcReaderConstructorArgs &args);
|
||||
|
||||
bool valid() const override;
|
||||
bool accepts_object_type(const Alembic::AbcCoreAbstract::ObjectHeader &alembic_header,
|
||||
const Object *const ob,
|
||||
const char **r_err_str) const override;
|
||||
|
||||
void readObjectData(Main *bmain, const Alembic::Abc::ISampleSelector &sample_sel) override;
|
||||
};
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
210
blender-5.2.0/source/blender/io/alembic/intern/abc_util.cc
Normal file
210
blender-5.2.0/source/blender/io/alembic/intern/abc_util.cc
Normal file
@@ -0,0 +1,210 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \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_points.h"
|
||||
#include "abc_reader_transform.h"
|
||||
|
||||
#include <Alembic/AbcGeom/ILight.h>
|
||||
#include <Alembic/AbcGeom/INuPatch.h>
|
||||
#include <Alembic/AbcMaterial/IMaterial.h>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace blender {
|
||||
|
||||
using Alembic::Abc::IV3fArrayProperty;
|
||||
using Alembic::Abc::PropertyHeader;
|
||||
using Alembic::Abc::V3fArraySamplePtr;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
std::string get_valid_abc_name(const char *name)
|
||||
{
|
||||
std::string abc_name(name);
|
||||
std::replace(abc_name.begin(), abc_name.end(), ' ', '_');
|
||||
std::replace(abc_name.begin(), abc_name.end(), '.', '_');
|
||||
std::replace(abc_name.begin(), abc_name.end(), ':', '_');
|
||||
std::replace(abc_name.begin(), abc_name.end(), '/', '_');
|
||||
return abc_name;
|
||||
}
|
||||
|
||||
Imath::M44d convert_matrix_datatype(const float mat[4][4])
|
||||
{
|
||||
Imath::M44d m;
|
||||
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++) {
|
||||
m[i][j] = double(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] = 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool has_property(const Alembic::Abc::ICompoundProperty &prop, const std::string &name)
|
||||
{
|
||||
if (!prop.valid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return prop.getPropertyHeader(name) != nullptr;
|
||||
}
|
||||
|
||||
V3fArraySamplePtr get_velocity_prop(const Alembic::Abc::ICompoundProperty &schema,
|
||||
const Alembic::AbcGeom::ISampleSelector &selector,
|
||||
const std::string &name)
|
||||
{
|
||||
for (size_t i = 0; i < schema.getNumProperties(); i++) {
|
||||
const PropertyHeader &header = schema.getPropertyHeader(i);
|
||||
|
||||
if (header.isCompound()) {
|
||||
const Alembic::Abc::ICompoundProperty &prop = Alembic::Abc::ICompoundProperty(
|
||||
schema, header.getName());
|
||||
|
||||
if (has_property(prop, name)) {
|
||||
/* Header cannot be null here, as its presence is checked via has_property, so it is safe
|
||||
* to dereference. */
|
||||
const PropertyHeader *header = prop.getPropertyHeader(name);
|
||||
if (!IV3fArrayProperty::matches(*header)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(prop, name, 0);
|
||||
if (velocity_prop) {
|
||||
return velocity_prop.getValue(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (header.isArray()) {
|
||||
if (header.getName() == name && IV3fArrayProperty::matches(header)) {
|
||||
const IV3fArrayProperty &velocity_prop = IV3fArrayProperty(schema, name, 0);
|
||||
return velocity_prop.getValue(selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return V3fArraySamplePtr();
|
||||
}
|
||||
|
||||
using index_time_pair_t = std::pair<Alembic::AbcCoreAbstract::index_t, Alembic::AbcGeom::chrono_t>;
|
||||
|
||||
std::optional<SampleInterpolationSettings> get_sample_interpolation_settings(
|
||||
const Alembic::AbcGeom::ISampleSelector &selector,
|
||||
const Alembic::AbcCoreAbstract::TimeSamplingPtr &time_sampling,
|
||||
size_t samples_number)
|
||||
{
|
||||
const chrono_t time = selector.getRequestedTime();
|
||||
samples_number = std::max(samples_number, size_t(1));
|
||||
|
||||
index_time_pair_t t0 = time_sampling->getFloorIndex(time, samples_number);
|
||||
Alembic::AbcCoreAbstract::index_t i0 = t0.first;
|
||||
|
||||
if (samples_number == 1 || (fabs(time - t0.second) < 0.0001)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
index_time_pair_t t1 = time_sampling->getCeilIndex(time, samples_number);
|
||||
Alembic::AbcCoreAbstract::index_t i1 = t1.first;
|
||||
|
||||
if (i0 == i1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const double bias = (time - t0.second) / (t1.second - t0.second);
|
||||
|
||||
if (fabs(1.0 - bias) < 0.0001) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return SampleInterpolationSettings{i0, i1, bias};
|
||||
}
|
||||
|
||||
// #define USE_NURBS
|
||||
|
||||
AbcObjectReader *create_reader(const AbcReaderConstructorArgs &args)
|
||||
{
|
||||
AbcObjectReader *reader = nullptr;
|
||||
|
||||
const Alembic::AbcGeom::MetaData &md = args.object.getMetaData();
|
||||
|
||||
if (Alembic::AbcGeom::IXform::matches(md)) {
|
||||
reader = new AbcEmptyReader(args);
|
||||
}
|
||||
else if (Alembic::AbcGeom::IPolyMesh::matches(md)) {
|
||||
reader = new AbcMeshReader(args);
|
||||
}
|
||||
else if (Alembic::AbcGeom::ISubD::matches(md)) {
|
||||
reader = new AbcSubDReader(args);
|
||||
}
|
||||
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(args);
|
||||
#endif
|
||||
}
|
||||
else if (Alembic::AbcGeom::ICamera::matches(md)) {
|
||||
reader = new AbcCameraReader(args);
|
||||
}
|
||||
else if (Alembic::AbcGeom::IPoints::matches(md)) {
|
||||
reader = new AbcPointsReader(args);
|
||||
}
|
||||
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(args);
|
||||
}
|
||||
else {
|
||||
std::cerr << "Alembic: unknown how to handle objects of schema '" << md.get("schemaObjTitle")
|
||||
<< "', skipping object '" << args.object.getFullName() << "'" << std::endl;
|
||||
}
|
||||
|
||||
return reader;
|
||||
}
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
84
blender-5.2.0/source/blender/io/alembic/intern/abc_util.h
Normal file
84
blender-5.2.0/source/blender/io/alembic/intern/abc_util.h
Normal file
@@ -0,0 +1,84 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#pragma once
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "abc_reader_object.h"
|
||||
|
||||
#include <Alembic/Abc/Foundation.h>
|
||||
#include <Alembic/Abc/ICompoundProperty.h>
|
||||
#include <Alembic/Abc/IObject.h>
|
||||
#include <Alembic/Abc/ISampleSelector.h>
|
||||
#include <Alembic/Abc/TypedArraySample.h>
|
||||
#include <Alembic/AbcCoreAbstract/Foundation.h>
|
||||
#include <Alembic/AbcCoreAbstract/TimeSampling.h>
|
||||
#include <Alembic/AbcGeom/IXform.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace blender {
|
||||
|
||||
using Alembic::Abc::chrono_t;
|
||||
using Alembic::Abc::V3fArraySamplePtr;
|
||||
|
||||
struct ID;
|
||||
struct Object;
|
||||
|
||||
namespace io::alembic {
|
||||
|
||||
class AbcObjectReader;
|
||||
struct AbcReaderConstructorArgs;
|
||||
|
||||
std::string get_valid_abc_name(const char *name);
|
||||
|
||||
/* Convert from float to Alembic matrix representations. Does NOT convert from Z-up to Y-up. */
|
||||
Imath::M44d convert_matrix_datatype(const 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]);
|
||||
|
||||
void split(const std::string &s, 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());
|
||||
}
|
||||
|
||||
bool has_property(const Alembic::Abc::ICompoundProperty &prop, const std::string &name);
|
||||
V3fArraySamplePtr get_velocity_prop(const Alembic::Abc::ICompoundProperty &schema,
|
||||
const Alembic::AbcGeom::ISampleSelector &selector,
|
||||
const std::string &name);
|
||||
|
||||
/**
|
||||
* The SampleInterpolationSettings struct holds information for interpolating data between two
|
||||
* samples.
|
||||
*/
|
||||
struct SampleInterpolationSettings {
|
||||
/* Index of the first ("floor") sample. */
|
||||
Alembic::AbcGeom::index_t index;
|
||||
/* Index of the second ("ceil") sample. */
|
||||
Alembic::AbcGeom::index_t ceil_index;
|
||||
/* Factor to interpolate between the `index` and `ceil_index`. */
|
||||
double weight;
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the requested time from the \a selector falls between two sampling time from the
|
||||
* \a time_sampling. If so, returns a #SampleInterpolationSettings with the required data to
|
||||
* interpolate. If not, returns nothing and we can assume that the requested time falls on a
|
||||
* specific sampling time of \a time_sampling and no interpolation is necessary.
|
||||
*/
|
||||
std::optional<SampleInterpolationSettings> get_sample_interpolation_settings(
|
||||
const Alembic::AbcGeom::ISampleSelector &selector,
|
||||
const Alembic::AbcCoreAbstract::TimeSamplingPtr &time_sampling,
|
||||
size_t samples_number);
|
||||
|
||||
AbcObjectReader *create_reader(const AbcReaderConstructorArgs &args);
|
||||
|
||||
} // namespace io::alembic
|
||||
} // namespace blender
|
||||
997
blender-5.2.0/source/blender/io/alembic/intern/alembic_capi.cc
Normal file
997
blender-5.2.0/source/blender/io/alembic/intern/alembic_capi.cc
Normal file
@@ -0,0 +1,997 @@
|
||||
/* SPDX-FileCopyrightText: 2023 Blender Authors
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
/** \file
|
||||
* \ingroup balembic
|
||||
*/
|
||||
|
||||
#include "../ABC_alembic.h"
|
||||
#include "IO_types.hh"
|
||||
|
||||
#include <Alembic/AbcGeom/ILight.h>
|
||||
#include <Alembic/AbcGeom/INuPatch.h>
|
||||
#include <Alembic/AbcMaterial/IMaterial.h>
|
||||
|
||||
#include "abc_keyframing.h"
|
||||
#include "abc_reader_archive.h"
|
||||
#include "abc_reader_camera.h"
|
||||
#include "abc_reader_curves.h"
|
||||
#include "abc_reader_mesh.h"
|
||||
#ifdef USE_NURBS
|
||||
# include "abc_reader_nurbs.h"
|
||||
#endif
|
||||
#include "abc_reader_points.h"
|
||||
#include "abc_reader_transform.h"
|
||||
#include "abc_util.h"
|
||||
|
||||
#include "MEM_guardedalloc.h"
|
||||
|
||||
#include "DNA_cachefile_types.h"
|
||||
#include "DNA_collection_types.h"
|
||||
#include "DNA_listBase.h"
|
||||
#include "DNA_object_types.h"
|
||||
#include "DNA_scene_types.h"
|
||||
|
||||
#include "BKE_cachefile.hh"
|
||||
#include "BKE_context.hh"
|
||||
#include "BKE_global.hh"
|
||||
#include "BKE_layer.hh"
|
||||
#include "BKE_lib_id.hh"
|
||||
#include "BKE_library.hh"
|
||||
#include "BKE_object.hh"
|
||||
|
||||
#include "DEG_depsgraph.hh"
|
||||
#include "DEG_depsgraph_build.hh"
|
||||
|
||||
#include "ED_undo.hh"
|
||||
|
||||
#include "BLI_compiler_compat.h"
|
||||
#include "BLI_listbase.h"
|
||||
#include "BLI_math_matrix.h"
|
||||
#include "BLI_path_utils.hh"
|
||||
#include "BLI_sort.hh"
|
||||
#include "BLI_span.hh"
|
||||
#include "BLI_string.h"
|
||||
#include "BLI_timeit.hh"
|
||||
|
||||
#include "BLT_translation.hh"
|
||||
|
||||
#include "WM_api.hh"
|
||||
#include "WM_types.hh"
|
||||
|
||||
#include "CLG_log.h"
|
||||
|
||||
namespace blender {
|
||||
|
||||
static CLG_LogRef LOG = {"io.alembic"};
|
||||
|
||||
using Alembic::Abc::IV3fArrayProperty;
|
||||
using Alembic::Abc::ObjectHeader;
|
||||
using Alembic::Abc::PropertyHeader;
|
||||
using Alembic::Abc::V3fArraySamplePtr;
|
||||
using Alembic::AbcGeom::ICamera;
|
||||
using Alembic::AbcGeom::ICurves;
|
||||
using Alembic::AbcGeom::IFaceSet;
|
||||
using Alembic::AbcGeom::ILight;
|
||||
using Alembic::AbcGeom::INuPatch;
|
||||
using Alembic::AbcGeom::IObject;
|
||||
using Alembic::AbcGeom::IPoints;
|
||||
using Alembic::AbcGeom::IPolyMesh;
|
||||
using Alembic::AbcGeom::IPolyMeshSchema;
|
||||
using Alembic::AbcGeom::ISampleSelector;
|
||||
using Alembic::AbcGeom::ISubD;
|
||||
using Alembic::AbcGeom::IXform;
|
||||
using Alembic::AbcGeom::kWrapExisting;
|
||||
using Alembic::AbcGeom::MetaData;
|
||||
using Alembic::AbcMaterial::IMaterial;
|
||||
|
||||
using namespace blender::io::alembic;
|
||||
|
||||
struct AlembicArchiveData {
|
||||
ArchiveReader *archive_reader = nullptr;
|
||||
ImportSettings *settings = nullptr;
|
||||
|
||||
AlembicArchiveData() = default;
|
||||
~AlembicArchiveData()
|
||||
{
|
||||
delete archive_reader;
|
||||
delete settings;
|
||||
}
|
||||
|
||||
AlembicArchiveData(const AlembicArchiveData &) = delete;
|
||||
AlembicArchiveData &operator==(const AlembicArchiveData &) = delete;
|
||||
};
|
||||
|
||||
BLI_INLINE AlembicArchiveData *archive_from_handle(CacheArchiveHandle *handle)
|
||||
{
|
||||
return reinterpret_cast<AlembicArchiveData *>(handle);
|
||||
}
|
||||
|
||||
BLI_INLINE CacheArchiveHandle *handle_from_archive(AlembicArchiveData *archive)
|
||||
{
|
||||
return reinterpret_cast<CacheArchiveHandle *>(archive);
|
||||
}
|
||||
|
||||
/* Add the object's path to list of object paths. No duplication is done, callers are
|
||||
* responsible for ensuring that only unique paths are added to the list.
|
||||
*/
|
||||
static void add_object_path(ListBaseT<CacheObjectPath> *object_paths, const IObject &object)
|
||||
{
|
||||
CacheObjectPath *abc_path = MEM_new<CacheObjectPath>("CacheObjectPath");
|
||||
STRNCPY(abc_path->path, object.getFullName().c_str());
|
||||
BLI_addtail(object_paths, abc_path);
|
||||
}
|
||||
|
||||
// #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, ListBaseT<CacheObjectPath> *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) {
|
||||
add_object_path(object_paths, object);
|
||||
}
|
||||
|
||||
return parent_is_part_of_this_object;
|
||||
}
|
||||
|
||||
CacheArchiveHandle *ABC_create_handle(const Main *bmain,
|
||||
const char *filepath,
|
||||
const CacheFileLayer *layers,
|
||||
ListBaseT<CacheObjectPath> *object_paths)
|
||||
{
|
||||
std::vector<const char *> filepaths;
|
||||
filepaths.push_back(filepath);
|
||||
|
||||
while (layers) {
|
||||
if ((layers->flag & CACHEFILE_LAYER_HIDDEN) == 0) {
|
||||
filepaths.push_back(layers->filepath);
|
||||
}
|
||||
layers = layers->next;
|
||||
}
|
||||
|
||||
/* We need to reverse the order as overriding archives should come first. */
|
||||
std::reverse(filepaths.begin(), filepaths.end());
|
||||
|
||||
ArchiveReader *archive = ArchiveReader::get(bmain, filepaths);
|
||||
|
||||
if (!archive || !archive->valid()) {
|
||||
delete archive;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (object_paths) {
|
||||
gather_objects_paths(archive->getTop(), object_paths);
|
||||
}
|
||||
|
||||
AlembicArchiveData *archive_data = new AlembicArchiveData();
|
||||
archive_data->archive_reader = archive;
|
||||
archive_data->settings = new ImportSettings();
|
||||
|
||||
return handle_from_archive(archive_data);
|
||||
}
|
||||
|
||||
void ABC_free_handle(CacheArchiveHandle *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;
|
||||
}
|
||||
|
||||
/* ********************** 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 *>(nullptr));
|
||||
}
|
||||
|
||||
/* 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 == nullptr) {
|
||||
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());
|
||||
UNUSED_VARS_NDEBUG(children_claiming_this_object);
|
||||
|
||||
AbcObjectReader *reader = nullptr;
|
||||
const MetaData &md = object.getMetaData();
|
||||
bool parent_is_part_of_this_object = false;
|
||||
|
||||
const AbcReaderConstructorArgs args = create_reader_constructor_args(object, settings);
|
||||
|
||||
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(args);
|
||||
}
|
||||
}
|
||||
else if (IPolyMesh::matches(md)) {
|
||||
reader = new AbcMeshReader(args);
|
||||
parent_is_part_of_this_object = true;
|
||||
}
|
||||
else if (ISubD::matches(md)) {
|
||||
reader = new AbcSubDReader(args);
|
||||
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(args);
|
||||
parent_is_part_of_this_object = true;
|
||||
#endif
|
||||
}
|
||||
else if (ICamera::matches(md)) {
|
||||
reader = new AbcCameraReader(args);
|
||||
parent_is_part_of_this_object = true;
|
||||
}
|
||||
else if (IPoints::matches(md)) {
|
||||
reader = new AbcPointsReader(args);
|
||||
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(args);
|
||||
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();
|
||||
|
||||
add_object_path(&settings.cache_file->object_paths, object);
|
||||
|
||||
/* 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.empty()) {
|
||||
/* 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,
|
||||
};
|
||||
|
||||
struct ImportJobData {
|
||||
bContext *C;
|
||||
Main *bmain;
|
||||
Scene *scene;
|
||||
ViewLayer *view_layer;
|
||||
wmWindowManager *wm;
|
||||
|
||||
ImportSettings settings;
|
||||
|
||||
Vector<ArchiveReader *> archives;
|
||||
Vector<AbcObjectReader *> readers;
|
||||
|
||||
Vector<std::string> paths;
|
||||
|
||||
/** Min time read from file import. */
|
||||
chrono_t min_time = std::numeric_limits<chrono_t>::max();
|
||||
/** Max time read from file import. */
|
||||
chrono_t max_time = -std::numeric_limits<chrono_t>::max();
|
||||
|
||||
bool *stop;
|
||||
bool *do_update;
|
||||
float *progress;
|
||||
|
||||
char error_code;
|
||||
bool was_cancelled;
|
||||
bool import_ok;
|
||||
bool is_background_job;
|
||||
timeit::TimePoint start_time;
|
||||
};
|
||||
|
||||
static void report_job_duration(const ImportJobData *data)
|
||||
{
|
||||
timeit::Nanoseconds duration = timeit::Clock::now() - data->start_time;
|
||||
std::cout << "Alembic import took ";
|
||||
timeit::print_duration(duration);
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
static void sort_readers(MutableSpan<AbcObjectReader *> readers)
|
||||
{
|
||||
parallel_sort(
|
||||
readers.begin(), readers.end(), [](const AbcObjectReader *a, const AbcObjectReader *b) {
|
||||
const char *na = a->name().c_str();
|
||||
const char *nb = b->name().c_str();
|
||||
return BLI_strcasecmp(na, nb) < 0;
|
||||
});
|
||||
}
|
||||
|
||||
static void import_file(ImportJobData *data, const char *filepath, float progress_factor)
|
||||
{
|
||||
timeit::TimePoint start_time = timeit::Clock::now();
|
||||
|
||||
ArchiveReader *archive = ArchiveReader::get(data->bmain, {filepath});
|
||||
|
||||
if (!archive || !archive->valid()) {
|
||||
data->error_code = ABC_ARCHIVE_FAIL;
|
||||
delete archive;
|
||||
return;
|
||||
}
|
||||
|
||||
CacheFile *cache_file = static_cast<CacheFile *>(
|
||||
BKE_cachefile_add(data->bmain, BLI_path_basename(filepath)));
|
||||
|
||||
/* 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 be 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, filepath);
|
||||
|
||||
data->archives.append(archive);
|
||||
data->settings.cache_file = cache_file;
|
||||
data->settings.blender_archive_version_prior_44 = archive->is_blender_archive_version_prior_44();
|
||||
|
||||
*data->do_update = true;
|
||||
*data->progress += 0.05f * progress_factor;
|
||||
|
||||
/* Parse Alembic Archive. */
|
||||
AbcObjectReader::ptr_vector assign_as_parent;
|
||||
std::vector<AbcObjectReader *> readers{};
|
||||
visit_object(archive->getTop(), readers, data->settings, assign_as_parent);
|
||||
|
||||
/* There shouldn't be any orphans. */
|
||||
BLI_assert(assign_as_parent.empty());
|
||||
|
||||
if (G.is_break) {
|
||||
data->was_cancelled = true;
|
||||
data->readers.extend(readers);
|
||||
return;
|
||||
}
|
||||
|
||||
*data->do_update = true;
|
||||
*data->progress += 0.05f * progress_factor;
|
||||
|
||||
/* Create objects and set scene frame range. */
|
||||
|
||||
/* Sort readers by name: when creating a lot of objects in Blender,
|
||||
* it is much faster if the order is sorted by name. */
|
||||
sort_readers(readers);
|
||||
data->readers.extend(readers);
|
||||
|
||||
const float size = float(readers.size());
|
||||
|
||||
ISampleSelector sample_sel(0.0);
|
||||
std::vector<AbcObjectReader *>::iterator iter;
|
||||
const float read_object_progress_step = (0.6f / size) * progress_factor;
|
||||
for (iter = readers.begin(); iter != readers.end(); ++iter) {
|
||||
AbcObjectReader *reader = *iter;
|
||||
|
||||
if (reader->valid()) {
|
||||
reader->readObjectData(data->bmain, sample_sel);
|
||||
reader->readVisibility();
|
||||
}
|
||||
else {
|
||||
std::cerr << "Object " << reader->name() << " in Alembic file " << filepath
|
||||
<< " is invalid.\n";
|
||||
}
|
||||
*data->progress += read_object_progress_step;
|
||||
*data->do_update = true;
|
||||
|
||||
if (G.is_break) {
|
||||
data->was_cancelled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const TimeInfo time_info = archive->getTimeInfo();
|
||||
if (time_info.is_valid()) {
|
||||
data->min_time = std::min(data->min_time, time_info.min_time);
|
||||
data->max_time = std::max(data->max_time, time_info.max_time);
|
||||
|
||||
Vector<std::unique_ptr<FCurveCreationHelper>> keyframing_helpers;
|
||||
|
||||
for (iter = readers.begin(); iter != readers.end(); ++iter) {
|
||||
AbcObjectReader *reader = *iter;
|
||||
|
||||
if (reader->valid()) {
|
||||
reader->getKeyFramingHelpers(keyframing_helpers);
|
||||
}
|
||||
}
|
||||
|
||||
create_keyframes(data->bmain, data->scene, keyframing_helpers, time_info);
|
||||
}
|
||||
|
||||
/* Setup parenthood. */
|
||||
for (iter = readers.begin(); iter != readers.end(); ++iter) {
|
||||
const AbcObjectReader *reader = *iter;
|
||||
const AbcObjectReader *parent_reader = reader->parent_reader;
|
||||
Object *ob = reader->object();
|
||||
|
||||
if (parent_reader == nullptr || !reader->inherits_xform()) {
|
||||
ob->parent = nullptr;
|
||||
}
|
||||
else {
|
||||
ob->parent = parent_reader->object();
|
||||
}
|
||||
}
|
||||
|
||||
/* Setup transformations and constraints. */
|
||||
const float setup_object_transform_progress_step = (0.3f / size) * progress_factor;
|
||||
for (iter = readers.begin(); iter != readers.end(); ++iter) {
|
||||
AbcObjectReader *reader = *iter;
|
||||
reader->setupObjectTransform(0.0);
|
||||
|
||||
*data->progress += setup_object_transform_progress_step;
|
||||
*data->do_update = true;
|
||||
|
||||
if (G.is_break) {
|
||||
data->was_cancelled = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
timeit::Nanoseconds duration = timeit::Clock::now() - start_time;
|
||||
std::cout << "Alembic import " << filepath << " took ";
|
||||
timeit::print_duration(duration);
|
||||
std::cout << '\n';
|
||||
}
|
||||
|
||||
static void set_frame_range(ImportJobData *data)
|
||||
{
|
||||
if (!data->settings.set_frame_range) {
|
||||
return;
|
||||
}
|
||||
Scene *scene = data->scene;
|
||||
if (data->settings.is_sequence) {
|
||||
scene->r.sfra = data->settings.sequence_min_frame;
|
||||
scene->r.efra = data->settings.sequence_max_frame;
|
||||
scene->r.cfra = scene->r.sfra;
|
||||
}
|
||||
else if (data->min_time < data->max_time) {
|
||||
scene->r.sfra = int(round(data->min_time * scene->frames_per_second()));
|
||||
scene->r.efra = int(round(data->max_time * scene->frames_per_second()));
|
||||
scene->r.cfra = scene->r.sfra;
|
||||
}
|
||||
}
|
||||
|
||||
static void import_startjob(void *user_data, wmJobWorkerStatus *worker_status)
|
||||
{
|
||||
ImportJobData *data = static_cast<ImportJobData *>(user_data);
|
||||
data->stop = &worker_status->stop;
|
||||
data->do_update = &worker_status->do_update;
|
||||
data->progress = &worker_status->progress;
|
||||
data->start_time = timeit::Clock::now();
|
||||
|
||||
WM_locked_interface_set(data->wm, true);
|
||||
float file_progress_factor = 1.0f / float(data->paths.size());
|
||||
for (int idx : data->paths.index_range()) {
|
||||
import_file(data, data->paths[idx].c_str(), file_progress_factor);
|
||||
|
||||
if (G.is_break || data->was_cancelled) {
|
||||
data->was_cancelled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
worker_status->progress = float(idx + 1) * file_progress_factor;
|
||||
}
|
||||
set_frame_range(data);
|
||||
}
|
||||
|
||||
static void import_endjob(void *user_data)
|
||||
{
|
||||
ImportJobData *data = static_cast<ImportJobData *>(user_data);
|
||||
|
||||
/* Delete objects on cancellation. */
|
||||
if (data->was_cancelled) {
|
||||
for (AbcObjectReader *reader : data->readers) {
|
||||
Object *ob = reader->object();
|
||||
|
||||
/* It's possible that cancellation occurred between the creation of
|
||||
* the reader and the creation of the Blender object. */
|
||||
if (ob == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
BKE_id_free_us(data->bmain, ob);
|
||||
}
|
||||
}
|
||||
else {
|
||||
const Main *bmain = data->bmain;
|
||||
const Scene *scene = data->scene;
|
||||
ViewLayer *view_layer = data->view_layer;
|
||||
|
||||
BKE_view_layer_base_deselect_all(*bmain, scene, view_layer);
|
||||
|
||||
LayerCollection *lc = BKE_layer_collection_get_active_editable(view_layer);
|
||||
if (!ID_IS_EDITABLE(lc->collection)) {
|
||||
WM_global_report(RPT_WARNING,
|
||||
"Could not find an editable collection in current scene, imported data "
|
||||
"will not be instantiated");
|
||||
}
|
||||
|
||||
for (AbcObjectReader *reader : data->readers) {
|
||||
Object *ob = reader->object();
|
||||
BKE_collection_object_add(data->bmain, lc->collection, ob);
|
||||
}
|
||||
/* Sync and do the view layer operations. */
|
||||
BKE_view_layer_synced_ensure(*bmain, scene, view_layer);
|
||||
bool has_instantiated_object = false;
|
||||
bool has_uninstantiated_object = false;
|
||||
for (AbcObjectReader *reader : data->readers) {
|
||||
Object *ob = reader->object();
|
||||
Base *base = BKE_view_layer_base_find(view_layer, ob);
|
||||
if (!base) {
|
||||
/* Object not instantiated in current viewlayer. */
|
||||
has_uninstantiated_object = true;
|
||||
continue;
|
||||
}
|
||||
has_instantiated_object = true;
|
||||
/* TODO: is setting active needed? */
|
||||
BKE_view_layer_base_select_and_set_active(view_layer, base);
|
||||
|
||||
/* If the object is hidden, we set the base as hidden instead so that hide/unhide shortcuts
|
||||
* work and the outliner shows the right value. We also unset the flag on the object as users
|
||||
* are more likely to interact with viewport visibility from the outliner or shortcuts than
|
||||
* in the object visibility panel.
|
||||
* We don't do this if keyframes are added for the visibility, otherwise the objects won't
|
||||
* show up in the viewport and we cannot transfer the keyframes to the base. */
|
||||
if ((ob->visibility_flag & OB_HIDE_VIEWPORT) != 0 && !reader->has_visibility_keyframes()) {
|
||||
base->flag |= BASE_HIDDEN;
|
||||
ob->visibility_flag &= ~OB_HIDE_VIEWPORT;
|
||||
/* Needed for the shortcut (ALT+H) to work. */
|
||||
BKE_base_eval_flags(base);
|
||||
}
|
||||
|
||||
DEG_id_tag_update(&lc->collection->id, ID_RECALC_SYNC_TO_EVAL);
|
||||
DEG_id_tag_update_ex(data->bmain,
|
||||
&ob->id,
|
||||
ID_RECALC_TRANSFORM | ID_RECALC_GEOMETRY | ID_RECALC_ANIMATION |
|
||||
ID_RECALC_BASE_FLAGS);
|
||||
}
|
||||
|
||||
if (has_instantiated_object && has_uninstantiated_object) {
|
||||
CLOG_ERROR(&LOG, "Some imported objects were not instantiated, while others were");
|
||||
}
|
||||
|
||||
DEG_id_tag_update(&data->scene->id, ID_RECALC_BASE_FLAGS);
|
||||
DEG_relations_tag_update(data->bmain);
|
||||
|
||||
if (data->is_background_job) {
|
||||
/* Blender already returned from the import operator, so we need to store our own extra undo
|
||||
* step. */
|
||||
ED_undo_push(data->C, "Alembic Import Finished");
|
||||
}
|
||||
}
|
||||
|
||||
for (AbcObjectReader *reader : data->readers) {
|
||||
reader->decref();
|
||||
|
||||
if (reader->refcount() == 0) {
|
||||
delete reader;
|
||||
}
|
||||
}
|
||||
|
||||
WM_locked_interface_set(data->wm, false);
|
||||
|
||||
switch (data->error_code) {
|
||||
default:
|
||||
case ABC_NO_ERROR:
|
||||
data->import_ok = !data->was_cancelled;
|
||||
break;
|
||||
case ABC_ARCHIVE_FAIL:
|
||||
WM_global_report(RPT_ERROR,
|
||||
"Could not open Alembic archive for reading, see console for detail");
|
||||
break;
|
||||
}
|
||||
|
||||
WM_main_add_notifier(NC_ID | NA_ADDED, nullptr);
|
||||
report_job_duration(data);
|
||||
}
|
||||
|
||||
static void import_freejob(void *user_data)
|
||||
{
|
||||
ImportJobData *data = static_cast<ImportJobData *>(user_data);
|
||||
for (ArchiveReader *archive : data->archives) {
|
||||
delete archive;
|
||||
}
|
||||
delete data;
|
||||
}
|
||||
|
||||
bool ABC_import(bContext *C, const AlembicImportParams *params, bool as_background_job)
|
||||
{
|
||||
/* Using new here since MEM_* functions do not call constructor to properly initialize data. */
|
||||
ImportJobData *job = new ImportJobData();
|
||||
job->C = C;
|
||||
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;
|
||||
job->paths = params->paths;
|
||||
|
||||
job->settings.scale = params->global_scale;
|
||||
job->settings.is_sequence = params->is_sequence;
|
||||
job->settings.set_frame_range = params->set_frame_range;
|
||||
job->settings.sequence_min_frame = params->sequence_min_frame;
|
||||
job->settings.sequence_max_frame = params->sequence_max_frame;
|
||||
job->settings.validate_meshes = params->validate_meshes;
|
||||
job->settings.always_add_cache_reader = params->always_add_cache_reader;
|
||||
job->error_code = ABC_NO_ERROR;
|
||||
job->was_cancelled = false;
|
||||
job->is_background_job = as_background_job;
|
||||
|
||||
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,
|
||||
"Importing Alembic...",
|
||||
WM_JOB_PROGRESS,
|
||||
WM_JOB_TYPE_ALEMBIC_IMPORT);
|
||||
|
||||
/* 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, nullptr, nullptr, import_endjob);
|
||||
|
||||
WM_jobs_start(CTX_wm_manager(C), wm_job);
|
||||
}
|
||||
else {
|
||||
wmJobWorkerStatus worker_status = {};
|
||||
import_startjob(job, &worker_status);
|
||||
import_endjob(job);
|
||||
import_ok = job->import_ok;
|
||||
|
||||
import_freejob(job);
|
||||
}
|
||||
|
||||
return import_ok;
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
||||
void ABC_get_transform(CacheReader *reader, float r_mat_world[4][4], double time, float scale)
|
||||
{
|
||||
if (!reader) {
|
||||
return;
|
||||
}
|
||||
|
||||
AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
|
||||
|
||||
bool is_constant = false;
|
||||
|
||||
/* Convert from the local matrix we obtain from Alembic to world coordinates
|
||||
* for Blender. This conversion is done here rather than by Blender due to
|
||||
* work around the non-standard interpretation of CONSTRAINT_SPACE_LOCAL in
|
||||
* BKE_constraint_mat_convertspace(). */
|
||||
Object *object = abc_reader->object();
|
||||
if (object->parent == nullptr) {
|
||||
/* No parent, so local space is the same as world space. */
|
||||
abc_reader->read_matrix(r_mat_world, time, scale, is_constant);
|
||||
return;
|
||||
}
|
||||
|
||||
float mat_parent[4][4];
|
||||
BKE_object_get_parent_matrix(object, object->parent, mat_parent);
|
||||
|
||||
float mat_local[4][4];
|
||||
abc_reader->read_matrix(mat_local, time, scale, is_constant);
|
||||
mul_m4_m4m4(r_mat_world, mat_parent, object->parentinv);
|
||||
mul_m4_m4m4(r_mat_world, r_mat_world, mat_local);
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
||||
static AbcObjectReader *get_abc_reader(CacheReader *reader, Object *ob, const char **r_err_str)
|
||||
{
|
||||
AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
|
||||
IObject iobject = abc_reader->iobject();
|
||||
|
||||
if (!iobject.valid()) {
|
||||
*r_err_str = RPT_("Invalid object: verify object path");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ObjectHeader &header = iobject.getHeader();
|
||||
if (!abc_reader->accepts_object_type(header, ob, r_err_str)) {
|
||||
/* r_err_str is set by acceptsObjectType() */
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return abc_reader;
|
||||
}
|
||||
|
||||
static ISampleSelector sample_selector_for_time(chrono_t time)
|
||||
{
|
||||
/* kFloorIndex is used to be compatible with non-interpolating
|
||||
* properties; they use the floor. */
|
||||
return ISampleSelector(time, ISampleSelector::kFloorIndex);
|
||||
}
|
||||
|
||||
void ABC_read_geometry(CacheReader *reader,
|
||||
Object *ob,
|
||||
bke::GeometrySet &geometry_set,
|
||||
const ABCReadParams *params,
|
||||
const char **r_err_str)
|
||||
{
|
||||
AbcObjectReader *abc_reader = get_abc_reader(reader, ob, r_err_str);
|
||||
if (abc_reader == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
ISampleSelector sample_sel = sample_selector_for_time(params->time);
|
||||
AbcReadGeometryParams read_params;
|
||||
read_params.read_flag = params->read_flags;
|
||||
read_params.velocity_name = params->velocity_name ? params->velocity_name : "";
|
||||
read_params.velocity_scale = params->velocity_scale;
|
||||
abc_reader->read_geometry(geometry_set, sample_sel, read_params, r_err_str);
|
||||
}
|
||||
|
||||
bool ABC_mesh_topology_changed(CacheReader *reader,
|
||||
Object *ob,
|
||||
const Mesh *existing_mesh,
|
||||
const double time,
|
||||
const char **r_err_str)
|
||||
{
|
||||
AbcObjectReader *abc_reader = get_abc_reader(reader, ob, r_err_str);
|
||||
if (abc_reader == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ISampleSelector sample_sel = sample_selector_for_time(time);
|
||||
return abc_reader->topology_changed(existing_mesh, sample_sel);
|
||||
}
|
||||
|
||||
/* ************************************************************************** */
|
||||
|
||||
void ABC_CacheReader_free(CacheReader *reader)
|
||||
{
|
||||
AbcObjectReader *abc_reader = reinterpret_cast<AbcObjectReader *>(reader);
|
||||
abc_reader->decref();
|
||||
|
||||
if (abc_reader->refcount() == 0) {
|
||||
delete abc_reader;
|
||||
}
|
||||
}
|
||||
|
||||
CacheReader *CacheReader_open_alembic_object(CacheArchiveHandle *handle,
|
||||
CacheReader *reader,
|
||||
Object *object,
|
||||
const char *object_path,
|
||||
const bool is_sequence)
|
||||
{
|
||||
if (object_path[0] == '\0') {
|
||||
return reader;
|
||||
}
|
||||
|
||||
AlembicArchiveData *archive_data = archive_from_handle(handle);
|
||||
if (!archive_data) {
|
||||
return reader;
|
||||
}
|
||||
|
||||
ArchiveReader *archive = archive_data->archive_reader;
|
||||
if (!archive || !archive->valid()) {
|
||||
return reader;
|
||||
}
|
||||
|
||||
IObject iobject;
|
||||
find_iobject(archive->getTop(), iobject, object_path);
|
||||
|
||||
if (reader) {
|
||||
ABC_CacheReader_free(reader);
|
||||
}
|
||||
|
||||
archive_data->settings->is_sequence = is_sequence;
|
||||
archive_data->settings->blender_archive_version_prior_44 =
|
||||
archive->is_blender_archive_version_prior_44();
|
||||
|
||||
const AbcReaderConstructorArgs args = create_reader_constructor_args(iobject,
|
||||
*archive_data->settings);
|
||||
|
||||
AbcObjectReader *abc_reader = create_reader(args);
|
||||
if (abc_reader == nullptr) {
|
||||
/* This object is not supported */
|
||||
return nullptr;
|
||||
}
|
||||
abc_reader->object(object);
|
||||
abc_reader->incref();
|
||||
|
||||
return reinterpret_cast<CacheReader *>(abc_reader);
|
||||
}
|
||||
|
||||
} // namespace blender
|
||||
Reference in New Issue
Block a user