T45687: Rework the Export/Import of Animations

This started with a fix for an animated Object Hierarchy. Then i decided to cleanup and optimize a bit. But at the end this has become a more or less full rewrite of the Animation Exporter. All of this happened in a separate local branch and i have retained all my local commits to better see what i have done.

Brief description:

* I fixed a few issues with exporting keyframed animations of object hierarchies where the objects have parent inverse matrices which differ from the Identity matrix.
* I added the option to export sampled animations with a user defined sampling rate (new user interface option)
* I briefly tested Object Animations and Rig Animations.

What is still needed:

* Cleanup the code
* Optimize the user interface
* Do the Documentation

Reviewers: mont29

Reviewed By: mont29

Differential Revision: https://developer.blender.org/D3070
This commit is contained in:
Gaia Clary 2018-02-24 13:11:30 +01:00
parent 4403ca80bd
commit dd7b9a362d
13 changed files with 688 additions and 101 deletions

View File

@ -49,38 +49,210 @@ bool AnimationExporter::exportAnimations(Scene *sce)
return has_animations;
}
// called for each exported object
/*
* This function creates a complete LINEAR Collada <Animation> Entry with all needed
* <source>, <sampler>, and <channel> entries.
* This is is used for creating sampled Transformation Animations for either:
*
* 1-axis animation:
* times contains the time points in seconds from within the timeline
* values contains the data (list of single floats)
* channel_count = 1
* axis_name = ['X' | 'Y' | 'Z']
* is_rot indicates if the animation is a rotation
*
* 3-axis animation:
* times contains the time points in seconds from within the timeline
* values contains the data (list of floats where each 3 entries are one vector)
* channel_count = 3
* axis_name = "" (actually not used)
* is_rot = false (see xxx below)
*
* xxx: I tried to create a 3 axis rotation animation
* like for translation or scale. But i could not
* figure out how to setup the channel for this case.
* So for now rotations are exported as 3 separate 1-axis collada animations
* See export_sampled_animation() further down.
*/
void AnimationExporter::create_sampled_animation(int channel_count,
std::vector<float> &times,
std::vector<float> &values,
std::string ob_name,
std::string label,
std::string axis_name,
bool is_rot)
{
char anim_id[200];
BLI_snprintf(anim_id, sizeof(anim_id), "%s_%s_%s", (char *)translate_id(ob_name).c_str(), label.c_str(), axis_name.c_str());
openAnimation(anim_id, COLLADABU::Utils::EMPTY_STRING);
/* create input source */
std::string input_id = create_source_from_vector(COLLADASW::InputSemantic::INPUT, times, false, anim_id, "");
/* create output source */
std::string output_id;
if (channel_count == 1)
output_id = create_source_from_array(COLLADASW::InputSemantic::OUTPUT, &values[0], values.size(), is_rot, anim_id, axis_name.c_str());
else if(channel_count = 3)
output_id = create_xyz_source(&values[0], times.size(), anim_id);
std::string sampler_id = std::string(anim_id) + SAMPLER_ID_SUFFIX;
COLLADASW::LibraryAnimations::Sampler sampler(sw, sampler_id);
std::string empty;
sampler.addInput(COLLADASW::InputSemantic::INPUT, COLLADABU::URI(empty, input_id));
sampler.addInput(COLLADASW::InputSemantic::OUTPUT, COLLADABU::URI(empty, output_id));
/* TODO create in/out tangents source (LINEAR) */
std::string interpolation_id = fake_interpolation_source(times.size(), anim_id, "");
/* Create Sampler */
sampler.addInput(COLLADASW::InputSemantic::INTERPOLATION, COLLADABU::URI(empty, interpolation_id));
addSampler(sampler);
/* Create channel */
std::string target = translate_id(ob_name) + "/" + label + axis_name + ((is_rot) ? ".ANGLE" : "");
addChannel(COLLADABU::URI(empty, sampler_id), target);
closeAnimation();
}
/*
* Export all animation FCurves of an Object.
*
* Note: This uses the keyframes as sample points,
* and exports "baked keyframes" while keeping the tangent infromation
* of the FCurves intact. This works for simple cases, but breaks
* especially when negative scales are involved in the animation.
*
* If it is necessary to conserve the Animation precisely then
* use export_sampled_animation_set() instead.
*/
void AnimationExporter::export_keyframed_animation_set(Object *ob)
{
FCurve *fcu = (FCurve *)ob->adt->action->curves.first;
char *transformName;
while (fcu) {
//for armature animations as objects
if (ob->type == OB_ARMATURE)
transformName = fcu->rna_path;
else
transformName = extract_transform_name(fcu->rna_path);
if (
STREQ(transformName, "location") ||
STREQ(transformName, "scale") ||
(STREQ(transformName, "rotation_euler") && ob->rotmode == ROT_MODE_EUL) ||
STREQ(transformName, "rotation_quaternion"))
{
create_keyframed_animation(ob, fcu, transformName, false);
}
fcu = fcu->next;
}
}
/*
* Export the sampled animation of an Object.
*
* Note: This steps over all animation frames (step size is given in export_settings.sample_size)
* and then evaluates the transformation,
* and exports "baked samples" This works always, however currently the interpolation type is set
* to LINEAR for now. (maybe later this can be changed to BEZIER)
*
* Note: If it is necessary to keep the FCurves intact, then use export_keyframed_animation_set() instead.
* However be aware that exporting keyframed animation may modify the animation slightly.
* Also keyframed animation exports tend to break when negative scales are involved.
*/
void AnimationExporter::export_sampled_animation_set(Object *ob)
{
static int LOC = 0;
static int EULX = 1;
static int EULY = 2;
static int EULZ = 3;
static int SCALE = 4;
static int TIME = 5;
if (this->export_settings->sampling_rate < 1)
return; // to avoid infinite loop
std::vector<float> baked_curves[6];
std::vector<float> &ctimes = baked_curves[TIME];
find_sampleframes(ob, ctimes);
for (std::vector<float>::iterator ctime = ctimes.begin(); ctime != ctimes.end(); ++ctime ) {
float fmat[4][4];
float floc[3];
float fquat[4];
float fsize[3];
float feul[3];
evaluate_anim_with_constraints(ob, *ctime); // set object transforms to the frame
BKE_object_matrix_local_get(ob, fmat);
mat4_decompose(floc, fquat, fsize, fmat);
quat_to_eul(feul, fquat);
baked_curves[LOC].push_back(floc[0]);
baked_curves[LOC].push_back(floc[1]);
baked_curves[LOC].push_back(floc[2]);
baked_curves[EULX].push_back(feul[0]);
baked_curves[EULY].push_back(feul[1]);
baked_curves[EULZ].push_back(feul[2]);
baked_curves[SCALE].push_back(fsize[0]);
baked_curves[SCALE].push_back(fsize[1]);
baked_curves[SCALE].push_back(fsize[2]);
}
std::string ob_name = id_name(ob);
create_sampled_animation(3, baked_curves[TIME], baked_curves[SCALE], ob_name, "scale", "", false);
create_sampled_animation(3, baked_curves[TIME], baked_curves[LOC], ob_name, "location", "", false);
/* Not sure how to export rotation as a 3channel animation,
* so separate into 3 single animations for now:
*/
create_sampled_animation(1, baked_curves[TIME], baked_curves[EULX], ob_name, "rotation", "X", true);
create_sampled_animation(1, baked_curves[TIME], baked_curves[EULY], ob_name, "rotation", "Y", true);
create_sampled_animation(1, baked_curves[TIME], baked_curves[EULZ], ob_name, "rotation", "Z", true);
fprintf(stdout, "Animation Export: Baked %zd frames for %s (sampling rate: %d)\n",
baked_curves[0].size(),
ob->id.name,
this->export_settings->sampling_rate);
}
/* called for each exported object */
void AnimationExporter::operator()(Object *ob)
{
FCurve *fcu;
char *transformName;
/* bool isMatAnim = false; */ /* UNUSED */
//Export transform animations
if (ob->adt && ob->adt->action) {
fcu = (FCurve *)ob->adt->action->curves.first;
//transform matrix export for bones are temporarily disabled here.
if (ob->type == OB_ARMATURE) {
bArmature *arm = (bArmature *)ob->data;
for (Bone *bone = (Bone *)arm->bonebase.first; bone; bone = bone->next)
write_bone_animation_matrix(ob, bone);
}
while (fcu) {
//for armature animations as objects
if (ob->type == OB_ARMATURE)
transformName = fcu->rna_path;
else
transformName = extract_transform_name(fcu->rna_path);
if ((STREQ(transformName, "location") || STREQ(transformName, "scale")) ||
(STREQ(transformName, "rotation_euler") && ob->rotmode == ROT_MODE_EUL) ||
(STREQ(transformName, "rotation_quaternion")))
{
dae_animation(ob, fcu, transformName, false);
else {
if (this->export_settings->sampling_rate == -1) {
export_keyframed_animation_set(ob);
}
else {
export_sampled_animation_set(ob);
}
fcu = fcu->next;
}
}
@ -92,14 +264,14 @@ void AnimationExporter::operator()(Object *ob)
//Export Lamp parameter animations
if ( (ob->type == OB_LAMP) && ((Lamp *)ob->data)->adt && ((Lamp *)ob->data)->adt->action) {
fcu = (FCurve *)(((Lamp *)ob->data)->adt->action->curves.first);
FCurve *fcu = (FCurve *)(((Lamp *)ob->data)->adt->action->curves.first);
while (fcu) {
transformName = extract_transform_name(fcu->rna_path);
if ((STREQ(transformName, "color")) || (STREQ(transformName, "spot_size")) ||
(STREQ(transformName, "spot_blend")) || (STREQ(transformName, "distance")))
{
dae_animation(ob, fcu, transformName, true);
create_keyframed_animation(ob, fcu, transformName, true);
}
fcu = fcu->next;
}
@ -107,7 +279,7 @@ void AnimationExporter::operator()(Object *ob)
//Export Camera parameter animations
if ( (ob->type == OB_CAMERA) && ((Camera *)ob->data)->adt && ((Camera *)ob->data)->adt->action) {
fcu = (FCurve *)(((Camera *)ob->data)->adt->action->curves.first);
FCurve *fcu = (FCurve *)(((Camera *)ob->data)->adt->action->curves.first);
while (fcu) {
transformName = extract_transform_name(fcu->rna_path);
@ -116,7 +288,7 @@ void AnimationExporter::operator()(Object *ob)
(STREQ(transformName, "clip_end")) ||
(STREQ(transformName, "clip_start")))
{
dae_animation(ob, fcu, transformName, true);
create_keyframed_animation(ob, fcu, transformName, true);
}
fcu = fcu->next;
}
@ -128,7 +300,7 @@ void AnimationExporter::operator()(Object *ob)
if (!ma) continue;
if (ma->adt && ma->adt->action) {
/* isMatAnim = true; */
fcu = (FCurve *)ma->adt->action->curves.first;
FCurve *fcu = (FCurve *)ma->adt->action->curves.first;
while (fcu) {
transformName = extract_transform_name(fcu->rna_path);
@ -136,7 +308,7 @@ void AnimationExporter::operator()(Object *ob)
(STREQ(transformName, "diffuse_color")) || (STREQ(transformName, "alpha")) ||
(STREQ(transformName, "ior")))
{
dae_animation(ob, fcu, transformName, true, ma);
create_keyframed_animation(ob, fcu, transformName, true, ma);
}
fcu = fcu->next;
}
@ -167,7 +339,7 @@ void AnimationExporter::export_morph_animation(Object *ob)
while (fcu) {
transformName = extract_transform_name(fcu->rna_path);
dae_animation(ob, fcu, transformName, true);
create_keyframed_animation(ob, fcu, transformName, true);
fcu = fcu->next;
}
@ -200,7 +372,7 @@ void AnimationExporter::make_anim_frames_from_targets(Object *ob, std::vector<fl
obtar = ct->tar;
if (obtar)
find_frames(obtar, frames);
find_keyframes(obtar, frames);
}
if (cti->flush_constraint_targets)
@ -265,8 +437,8 @@ std::string AnimationExporter::getAnimationPathId(const FCurve *fcu)
return translate_id(rna_path);
}
//convert f-curves to animation curves and write
void AnimationExporter::dae_animation(Object *ob, FCurve *fcu, char *transformName, bool is_param, Material *ma)
/* convert f-curves to animation curves and write */
void AnimationExporter::create_keyframed_animation(Object *ob, FCurve *fcu, char *transformName, bool is_param, Material *ma)
{
const char *axis_name = NULL;
char anim_id[200];
@ -274,6 +446,8 @@ void AnimationExporter::dae_animation(Object *ob, FCurve *fcu, char *transformNa
bool has_tangents = false;
bool quatRotation = false;
Object *obj = NULL;
if (STREQ(transformName, "rotation_quaternion") ) {
fprintf(stderr, "quaternion rotation curves are not supported. rotation curve will not be exported\n");
quatRotation = true;
@ -291,15 +465,26 @@ void AnimationExporter::dae_animation(Object *ob, FCurve *fcu, char *transformNa
axis_name = axis_names[fcu->array_index];
}
//axis names for transforms
else if (STREQ(transformName, "location") ||
STREQ(transformName, "scale") ||
STREQ(transformName, "rotation_euler") ||
STREQ(transformName, "rotation_quaternion"))
/*
* Note: Handle transformation animations separately (to apply matrix inverse to fcurves)
* We will use the object to evaluate the animation on all keyframes and calculate the
* resulting object matrix. We need this to incorporate the
* effects of the parent inverse matrix (when it contains a rotation component)
*
* TODO: try to combine exported fcurves into 3 channel animations like done
* in export_sampled_animation(). For now each channel is exported as separate <Animation>.
*/
else if (
STREQ(transformName, "scale") ||
STREQ(transformName, "location") ||
STREQ(transformName, "rotation_euler"))
{
const char *axis_names[] = {"X", "Y", "Z"};
if (fcu->array_index < 3)
if (fcu->array_index < 3) {
axis_name = axis_names[fcu->array_index];
obj = ob;
}
}
else {
/* no axis name. single parameter */
@ -308,7 +493,7 @@ void AnimationExporter::dae_animation(Object *ob, FCurve *fcu, char *transformNa
std::string ob_name = std::string("null");
//Create anim Id
/* Create anim Id */
if (ob->type == OB_ARMATURE) {
ob_name = getObjectBoneName(ob, fcu);
BLI_snprintf(
@ -357,7 +542,7 @@ void AnimationExporter::dae_animation(Object *ob, FCurve *fcu, char *transformNa
output_id = create_lens_source_from_fcurve((Camera *) ob->data, COLLADASW::InputSemantic::OUTPUT, fcu, anim_id);
}
else {
output_id = create_source_from_fcurve(COLLADASW::InputSemantic::OUTPUT, fcu, anim_id, axis_name);
output_id = create_source_from_fcurve(COLLADASW::InputSemantic::OUTPUT, fcu, anim_id, axis_name, obj);
}
// create interpolations source
@ -369,10 +554,10 @@ void AnimationExporter::dae_animation(Object *ob, FCurve *fcu, char *transformNa
if (has_tangents) {
// create in_tangent source
intangent_id = create_source_from_fcurve(COLLADASW::InputSemantic::IN_TANGENT, fcu, anim_id, axis_name);
intangent_id = create_source_from_fcurve(COLLADASW::InputSemantic::IN_TANGENT, fcu, anim_id, axis_name, obj);
// create out_tangent source
outtangent_id = create_source_from_fcurve(COLLADASW::InputSemantic::OUT_TANGENT, fcu, anim_id, axis_name);
outtangent_id = create_source_from_fcurve(COLLADASW::InputSemantic::OUT_TANGENT, fcu, anim_id, axis_name, obj);
}
std::string sampler_id = std::string(anim_id) + SAMPLER_ID_SUFFIX;
@ -475,8 +660,11 @@ void AnimationExporter::sample_and_write_bone_animation_matrix(Object *ob_arm, B
if (!pchan)
return;
//every inserted keyframe of bones.
find_frames(ob_arm, fra);
if (this->export_settings->sampling_rate < 1)
find_keyframes(ob_arm, fra);
else
find_sampleframes(ob_arm, fra);
if (flag & ARM_RESTPOS) {
arm->flag &= ~ARM_RESTPOS;
@ -755,14 +943,60 @@ void AnimationExporter::get_source_values(BezTriple *bezt, COLLADASW::InputSeman
}
}
// old function to keep compatibility for calls where offset and object are not needed
std::string AnimationExporter::create_source_from_fcurve(COLLADASW::InputSemantic::Semantics semantic, FCurve *fcu, const std::string& anim_id, const char *axis_name)
{
return create_source_from_fcurve(semantic, fcu, anim_id, axis_name, NULL);
}
void AnimationExporter::evaluate_anim_with_constraints(Object *ob, float ctime)
{
BKE_animsys_evaluate_animdata(scene, &ob->id, ob->adt, ctime, ADT_RECALC_ALL);
ListBase *conlist = get_active_constraints(ob);
bConstraint *con;
for (con = (bConstraint *)conlist->first; con; con = con->next) {
ListBase targets = { NULL, NULL };
const bConstraintTypeInfo *cti = BKE_constraint_typeinfo_get(con);
if (cti && cti->get_constraint_targets) {
bConstraintTarget *ct;
Object *obtar;
cti->get_constraint_targets(con, &targets);
for (ct = (bConstraintTarget *)targets.first; ct; ct = ct->next) {
obtar = ct->tar;
if (obtar) {
BKE_animsys_evaluate_animdata(scene, &obtar->id, obtar->adt, ctime, ADT_RECALC_ANIM);
BKE_object_where_is_calc_time(scene, obtar, ctime);
}
}
if (cti->flush_constraint_targets)
cti->flush_constraint_targets(con, &targets, 1);
}
}
BKE_object_where_is_calc_time(scene, ob, ctime);
}
/*
* ob is needed to aply parent inverse information to fcurve.
* TODO: Here we have to step over all keyframes for each object and for each fcurve.
* Instead of processing each fcurve one by one,
* step over the animation from keyframe to keyframe,
* then create adjusted fcurves (and entries) for all affected objects.
* Then we would need to step through the scene only once.
*/
std::string AnimationExporter::create_source_from_fcurve(COLLADASW::InputSemantic::Semantics semantic, FCurve *fcu, const std::string& anim_id, const char *axis_name, Object *ob)
{
std::string source_id = anim_id + get_semantic_suffix(semantic);
//bool is_angle = STREQ(fcu->rna_path, "rotation");
bool is_angle = false;
if (strstr(fcu->rna_path, "rotation") || strstr(fcu->rna_path,"spot_size")) is_angle = true;
bool is_angle = (strstr(fcu->rna_path, "rotation") || strstr(fcu->rna_path, "spot_size"));
bool is_euler = strstr(fcu->rna_path, "rotation_euler");
bool is_translation = strstr(fcu->rna_path, "location");
bool is_scale = strstr(fcu->rna_path, "scale");
bool is_tangent = false;
int offset_index = 0;
COLLADASW::FloatSourceF source(mSW);
source.setId(source_id);
@ -773,27 +1007,76 @@ std::string AnimationExporter::create_source_from_fcurve(COLLADASW::InputSemanti
case COLLADASW::InputSemantic::INPUT:
case COLLADASW::InputSemantic::OUTPUT:
source.setAccessorStride(1);
offset_index = 0;
break;
case COLLADASW::InputSemantic::IN_TANGENT:
case COLLADASW::InputSemantic::OUT_TANGENT:
source.setAccessorStride(2);
offset_index = 1;
is_tangent = true;
break;
default:
break;
}
COLLADASW::SourceBase::ParameterNameList &param = source.getParameterNameList();
add_source_parameters(param, semantic, is_angle, axis_name, false);
source.prepareToAppendValues();
for (unsigned int i = 0; i < fcu->totvert; i++) {
for (unsigned int frame_index = 0; frame_index < fcu->totvert; frame_index++) {
float fixed_val = 0;
if (ob) {
float fmat[4][4];
float frame = fcu->bezt[frame_index].vec[1][0];
float ctime = BKE_scene_frame_get_from_ctime(scene, frame);
evaluate_anim_with_constraints(ob, ctime); // set object transforms to fcurve's i'th keyframe
BKE_object_matrix_local_get(ob, fmat);
float floc[3];
float fquat[4];
float fsize[3];
mat4_decompose(floc, fquat, fsize, fmat);
if (is_euler) {
float eul[3];
quat_to_eul(eul, fquat);
fixed_val = RAD2DEGF(eul[fcu->array_index]);
}
else if (is_translation) {
fixed_val = floc[fcu->array_index];
}
else if (is_scale) {
fixed_val = fsize[fcu->array_index];
}
}
float values[3]; // be careful!
float offset = 0;
int length = 0;
get_source_values(&fcu->bezt[i], semantic, is_angle, values, &length);
for (int j = 0; j < length; j++)
source.appendValues(values[j]);
get_source_values(&fcu->bezt[frame_index], semantic, is_angle, values, &length);
if (is_tangent) {
float bases[3];
int len = 0;
get_source_values(&fcu->bezt[frame_index], COLLADASW::InputSemantic::OUTPUT, is_angle, bases, &len);
offset = values[offset_index] - bases[0];
}
for (int j = 0; j < length; j++) {
float val;
if (j == offset_index) {
if (ob) {
val = fixed_val + offset;
}
else {
val = values[j] + offset;
}
} else {
val = values[j];
}
source.appendValues(val);
}
}
source.finish();
@ -837,9 +1120,9 @@ std::string AnimationExporter::create_lens_source_from_fcurve(Camera *cam, COLLA
return source_id;
}
//Currently called only to get OUTPUT source values ( if rotation and hence the axis is also specified )
/*
* only to get OUTPUT source values ( if rotation and hence the axis is also specified )
*/
std::string AnimationExporter::create_source_from_array(COLLADASW::InputSemantic::Semantics semantic, float *v, int tot, bool is_rot, const std::string& anim_id, const char *axis_name)
{
std::string source_id = anim_id + get_semantic_suffix(semantic);
@ -869,7 +1152,10 @@ std::string AnimationExporter::create_source_from_array(COLLADASW::InputSemantic
return source_id;
}
// only used for sources with INPUT semantic
/*
* only used for sources with INPUT semantic
*/
std::string AnimationExporter::create_source_from_vector(COLLADASW::InputSemantic::Semantics semantic, std::vector<float> &fra, bool is_rot, const std::string& anim_id, const char *axis_name)
{
std::string source_id = anim_id + get_semantic_suffix(semantic);
@ -935,9 +1221,10 @@ std::string AnimationExporter::create_4x4_source(std::vector<float> &frames, Obj
int j = 0;
for (it = frames.begin(); it != frames.end(); it++) {
float mat[4][4], ipar[4][4];
float frame = *it;
float ctime = BKE_scene_frame_get_from_ctime(scene, *it);
CFRA = BKE_scene_frame_get_from_ctime(scene, *it);
float ctime = BKE_scene_frame_get_from_ctime(scene, frame);
CFRA = BKE_scene_frame_get_from_ctime(scene, frame);
//BKE_scene_update_for_newframe(G.main->eval_ctx, G.main,scene,scene->lay);
BKE_animsys_evaluate_animdata(scene, &ob->id, ob->adt, ctime, ADT_RECALC_ALL);
@ -959,9 +1246,10 @@ std::string AnimationExporter::create_4x4_source(std::vector<float> &frames, Obj
else
copy_m4_m4(mat, pchan->pose_mat);
// OPEN_SIM_COMPATIBILITY
// AFAIK animation to second life is via BVH, but no
// reason to not have the collada-animation be correct
/* OPEN_SIM_COMPATIBILITY
* AFAIK animation to second life is via BVH, but no
* reason to not have the collada-animation be correct
*/
if (export_settings->open_sim) {
float temp[4][4];
copy_m4_m4(temp, bone->arm_mat);
@ -980,7 +1268,11 @@ std::string AnimationExporter::create_4x4_source(std::vector<float> &frames, Obj
}
else {
calc_ob_mat_at_time(ob, ctime, mat);
BKE_scene_frame_set(scene, ctime);
Main *bmain = bc_get_main();
EvaluationContext *ev_context = bc_get_evaluation_context();
BKE_scene_update_for_newframe(ev_context, bmain, scene, scene->lay);
copy_m4_m4(mat, ob->obmat);
}
UnitConverter converter;
@ -1008,7 +1300,9 @@ std::string AnimationExporter::create_4x4_source(std::vector<float> &frames, Obj
}
// only used for sources with OUTPUT semantic ( locations and scale)
/*
* only used for sources with OUTPUT semantic ( locations and scale)
*/
std::string AnimationExporter::create_xyz_source(float *v, int tot, const std::string& anim_id)
{
COLLADASW::InputSemantic::Semantics semantic = COLLADASW::InputSemantic::OUTPUT;
@ -1192,8 +1486,10 @@ std::string AnimationExporter::get_camera_param_sid(char *rna_path, int tm_type,
return std::string("");
}
// Assign sid of the animated parameter or transform
// for rotation, axis name is always appended and the value of append_axis is ignored
/*
* Assign sid of the animated parameter or transform for rotation,
* axis name is always appended and the value of append_axis is ignored
*/
std::string AnimationExporter::get_transform_sid(char *rna_path, int tm_type, const char *axis_name, bool append_axis)
{
std::string tm_name;
@ -1277,29 +1573,10 @@ char *AnimationExporter::extract_transform_name(char *rna_path)
return dot ? (dot + 1) : rna_path;
}
//find keyframes of all the objects animations
void AnimationExporter::find_frames(Object *ob, std::vector<float> &fra)
{
if (ob->adt && ob->adt->action) {
FCurve *fcu = (FCurve *)ob->adt->action->curves.first;
for (; fcu; fcu = fcu->next) {
for (unsigned int i = 0; i < fcu->totvert; i++) {
float f = fcu->bezt[i].vec[1][0];
if (std::find(fra.begin(), fra.end(), f) == fra.end())
fra.push_back(f);
}
}
// keep the keys in ascending order
std::sort(fra.begin(), fra.end());
}
}
// enable fcurves driving a specific bone, disable all the rest
// if bone_name = NULL enable all fcurves
/*
* enable fcurves driving a specific bone, disable all the rest
* if bone_name = NULL enable all fcurves
*/
void AnimationExporter::enable_fcurves(bAction *act, char *bone_name)
{
FCurve *fcu;
@ -1364,14 +1641,53 @@ bool AnimationExporter::hasAnimations(Scene *sce)
void AnimationExporter::find_rotation_frames(Object *ob, std::vector<float> &fra, const char *prefix, int rotmode)
{
if (rotmode > 0)
find_frames(ob, fra, prefix, "rotation_euler");
find_keyframes(ob, fra, prefix, "rotation_euler");
else if (rotmode == ROT_MODE_QUAT)
find_frames(ob, fra, prefix, "rotation_quaternion");
find_keyframes(ob, fra, prefix, "rotation_quaternion");
/*else if (rotmode == ROT_MODE_AXISANGLE)
;*/
}
void AnimationExporter::find_frames(Object *ob, std::vector<float> &fra, const char *prefix, const char *tm_name)
/* Take care to always have the first frame and the last frame in the animation
* regardless of the sampling_rate setting
*/
void AnimationExporter::find_sampleframes(Object *ob, std::vector<float> &fra)
{
int frame = scene->r.sfra;
do {
float ctime = BKE_scene_frame_get_from_ctime(scene, frame);
fra.push_back(ctime);
if (frame == scene->r.efra)
break;
frame += this->export_settings->sampling_rate;
if (frame > scene->r.efra)
frame = scene->r.efra; // make sure the last frame is always exported
} while (true);
}
/*
* find keyframes of all the objects animations
*/
void AnimationExporter::find_keyframes(Object *ob, std::vector<float> &fra)
{
if (ob->adt && ob->adt->action) {
FCurve *fcu = (FCurve *)ob->adt->action->curves.first;
for (; fcu; fcu = fcu->next) {
for (unsigned int i = 0; i < fcu->totvert; i++) {
float f = fcu->bezt[i].vec[1][0];
if (std::find(fra.begin(), fra.end(), f) == fra.end())
fra.push_back(f);
}
}
// keep the keys in ascending order
std::sort(fra.begin(), fra.end());
}
}
void AnimationExporter::find_keyframes(Object *ob, std::vector<float> &fra, const char *prefix, const char *tm_name)
{
if (ob->adt && ob->adt->action) {
FCurve *fcu = (FCurve *)ob->adt->action->curves.first;
@ -1429,10 +1745,10 @@ void AnimationExporter::sample_and_write_bone_animation(Object *ob_arm, Bone *bo
find_rotation_frames(ob_arm, fra, prefix, pchan->rotmode);
break;
case 1:
find_frames(ob_arm, fra, prefix, "scale");
find_keyframes(ob_arm, fra, prefix, "scale");
break;
case 2:
find_frames(ob_arm, fra, prefix, "location");
find_keyframes(ob_arm, fra, prefix, "location");
break;
default:
return;
@ -1539,8 +1855,23 @@ bool AnimationExporter::validateConstraints(bConstraint *con)
return valid;
}
void AnimationExporter::calc_ob_mat_at_time(Object *ob, float ctime , float mat[][4])
#if 0
/*
* Needed for sampled animations.
* This function calculates the object matrix at a given time,
* also taking constraints into account.
*
* XXX: Why looking at the constraints here is necessary?
* Maybe this can be done better?
*/
void AnimationExporter::calc_obmat_at_time(Object *ob, float ctime )
{
BKE_scene_frame_set(scene, ctime);
Main *bmain = bc_get_main();
EvaluationContext *ev_context = bc_get_evaluation_context();
BKE_scene_update_for_newframe(ev_context, bmain, scene, scene->lay);
ListBase *conlist = get_active_constraints(ob);
bConstraint *con;
for (con = (bConstraint *)conlist->first; con; con = con->next) {
@ -1566,6 +1897,6 @@ void AnimationExporter::calc_ob_mat_at_time(Object *ob, float ctime , float mat[
}
}
BKE_object_where_is_calc_time(scene, ob, ctime);
copy_m4_m4(mat, ob->obmat);
}
#endif

View File

@ -90,9 +90,11 @@ private:
public:
AnimationExporter(COLLADASW::StreamWriter *sw, const ExportSettings *export_settings):
COLLADASW::LibraryAnimations(sw), export_settings(export_settings)
{ this->sw = sw; }
COLLADASW::LibraryAnimations(sw),
export_settings(export_settings)
{
this->sw = sw;
}
bool exportAnimations(Scene *sce);
@ -102,7 +104,6 @@ public:
protected:
const ExportSettings *export_settings;
void dae_animation(Object *ob, FCurve *fcu, char *transformName, bool is_param, Material *ma = NULL);
void export_object_constraint_animation(Object *ob);
@ -143,7 +144,15 @@ protected:
float* get_eul_source_for_quat(Object *ob );
void export_keyframed_animation_set(Object *ob);
void create_keyframed_animation(Object *ob, FCurve *fcu, char *transformName, bool is_param, Material *ma = NULL);
void export_sampled_animation_set(Object *ob);
void create_sampled_animation(int channel_count, std::vector<float> &times, std::vector<float> &values, std::string, std::string label, std::string axis_name, bool is_rot);
void evaluate_anim_with_constraints(Object *ob, float ctime);
std::string create_source_from_fcurve(COLLADASW::InputSemantic::Semantics semantic, FCurve *fcu, const std::string& anim_id, const char *axis_name);
std::string create_source_from_fcurve(COLLADASW::InputSemantic::Semantics semantic, FCurve *fcu, const std::string& anim_id, const char *axis_name, Object *ob);
std::string create_lens_source_from_fcurve(Camera *cam, COLLADASW::InputSemantic::Semantics semantic, FCurve *fcu, const std::string& anim_id);
@ -164,8 +173,10 @@ protected:
std::string get_light_param_sid(char *rna_path, int tm_type, const char *axis_name, bool append_axis);
std::string get_camera_param_sid(char *rna_path, int tm_type, const char *axis_name, bool append_axis);
void find_frames(Object *ob, std::vector<float> &fra, const char *prefix, const char *tm_name);
void find_frames(Object *ob, std::vector<float> &fra);
void find_keyframes(Object *ob, std::vector<float> &fra, const char *prefix, const char *tm_name);
void find_keyframes(Object *ob, std::vector<float> &fra);
void find_sampleframes(Object *ob, std::vector<float> &fra);
void make_anim_frames_from_targets(Object *ob, std::vector<float> &frames );
@ -186,6 +197,6 @@ protected:
bool validateConstraints(bConstraint *con);
void calc_ob_mat_at_time(Object *ob, float ctime , float mat[][4]);
//void calc_obmat_at_time(Object *ob, float ctime);
};

View File

@ -817,7 +817,6 @@ void AnimationImporter::apply_matrix_curves(Object *ob, std::vector<FCurve *>& a
}
float rot[4], loc[3], scale[3];
transpose_m4(mat);
bc_rotate_from_reference_quat(rot, qref, mat);
copy_qt_qt(qref, rot);

View File

@ -39,6 +39,7 @@ public:
bool include_armatures;
bool include_shapekeys;
bool deform_bones_only;
int sampling_rate;
bool active_uv_only;
BC_export_texture_type export_texture_type;

View File

@ -78,6 +78,7 @@ int collada_export(Scene *sce,
int include_armatures,
int include_shapekeys,
int deform_bones_only,
int sampling_rate,
int active_uv_only,
BC_export_texture_type export_texture_type,
@ -103,6 +104,7 @@ int collada_export(Scene *sce,
export_settings.include_armatures = include_armatures != 0;
export_settings.include_shapekeys = include_shapekeys != 0;
export_settings.deform_bones_only = deform_bones_only != 0;
export_settings.sampling_rate = sampling_rate;
export_settings.active_uv_only = active_uv_only != 0;
export_settings.export_texture_type = export_texture_type;

View File

@ -77,6 +77,7 @@ int collada_export(struct Scene *sce,
int include_armatures,
int include_shapekeys,
int deform_bones_only,
int sampling_rate,
int active_uv_only,
BC_export_texture_type export_texture_type,

View File

@ -53,6 +53,7 @@ extern "C" {
#include "BKE_mesh.h"
#include "BKE_scene.h"
#include "BKE_DerivedMesh.h"
#include "BKE_main.h"
#include "ED_armature.h"
@ -132,6 +133,17 @@ int bc_set_parent(Object *ob, Object *par, bContext *C, bool is_parent_space)
return true;
}
Main *bc_get_main()
{
return G.main;
}
EvaluationContext *bc_get_evaluation_context()
{
Main *bmain = G.main;
return bmain->eval_ctx;
}
Object *bc_add_object(Scene *scene, int type, const char *name)
{
Object *ob = BKE_object_add_only_object(G.main, type, name);

View File

@ -63,6 +63,9 @@ extern "C" {
typedef std::map<COLLADAFW::TextureMapId, std::vector<MTex *> > TexIndexTextureArrayMap;
extern Main *bc_get_main();
extern EvaluationContext *bc_get_evaluation_context();
extern float bc_get_float_value(const COLLADAFW::FloatOrDoubleArray& array, unsigned int index);
extern int bc_test_parent_loop(Object *par, Object *ob);
extern int bc_set_parent(Object *ob, Object *par, bContext *C, bool is_parent_space = true);

View File

@ -86,6 +86,7 @@ static int wm_collada_export_exec(bContext *C, wmOperator *op)
int include_armatures;
int include_shapekeys;
int deform_bones_only;
int sampling_rate;
int export_texture_type;
int use_texture_copies;
@ -136,6 +137,7 @@ static int wm_collada_export_exec(bContext *C, wmOperator *op)
include_children = RNA_boolean_get(op->ptr, "include_children");
include_armatures = RNA_boolean_get(op->ptr, "include_armatures");
include_shapekeys = RNA_boolean_get(op->ptr, "include_shapekeys");
sampling_rate = RNA_int_get(op->ptr, "sampling_rate");
deform_bones_only = RNA_boolean_get(op->ptr, "deform_bones_only");
export_texture_type = RNA_enum_get(op->ptr, "export_texture_type_selection");
@ -165,6 +167,7 @@ static int wm_collada_export_exec(bContext *C, wmOperator *op)
include_armatures,
include_shapekeys,
deform_bones_only,
sampling_rate,
active_uv_only,
export_texture_type,
@ -229,6 +232,10 @@ static void uiCollada_exportSettings(uiLayout *layout, PointerRNA *imfptr)
uiItemR(row, imfptr, "include_shapekeys", 0, NULL, ICON_NONE);
uiLayoutSetEnabled(row, RNA_boolean_get(imfptr, "selected"));
row = uiLayoutRow(box, false);
uiItemR(row, imfptr, "sampling_rate", 0, NULL, ICON_NONE);
/* Texture options */
box = uiLayoutBox(layout);
row = uiLayoutRow(box, false);
@ -251,6 +258,7 @@ static void uiCollada_exportSettings(uiLayout *layout, PointerRNA *imfptr)
row = uiLayoutRow(box, false);
uiItemR(row, imfptr, "deform_bones_only", 0, NULL, ICON_NONE);
row = uiLayoutRow(box, false);
uiItemR(row, imfptr, "open_sim", 0, NULL, ICON_NONE);
@ -366,7 +374,10 @@ void WM_OT_collada_export(wmOperatorType *ot)
"Export all Shape Keys from Mesh Objects");
RNA_def_boolean(func, "deform_bones_only", 0, "Deform Bones only",
"Only export deforming bones with armatures");
"Only export deforming bones with armatures");
RNA_def_int(func, "sampling_rate", 0, -1, INT_MAX,
"Samplintg Rate", "The maximum distance of frames between 2 keyframes. Disabled when value is -1", -1, INT_MAX);
RNA_def_boolean(func, "active_uv_only", 0, "Only Selected UV Map",
"Export only the selected UV Map");

View File

@ -278,6 +278,7 @@ static void rna_Scene_collada_export(
int include_armatures,
int include_shapekeys,
int deform_bones_only,
int sampling_rate,
int active_uv_only,
int export_texture_type,
int use_texture_copies,
@ -301,6 +302,7 @@ static void rna_Scene_collada_export(
include_armatures,
include_shapekeys,
deform_bones_only,
sampling_rate,
active_uv_only,
export_texture_type,
@ -400,6 +402,9 @@ void RNA_api_scene(StructRNA *srna)
RNA_def_boolean(func, "deform_bones_only", false,
"Deform Bones only", "Only export deforming bones with armatures");
RNA_def_int(func, "sampling_rate", 0, -1, INT_MAX,
"Samplintg Rate", "The maximum distance of frames between 2 keyframes. Disabled when value is -1", -1, INT_MAX);
RNA_def_boolean(func, "active_uv_only", false, "Only Selected UV Map", "Export only the selected UV Map");
RNA_def_int(func, "export_texture_type", 0, INT_MIN, INT_MAX,

View File

@ -616,3 +616,5 @@ if(WITH_ALEMBIC)
--with-legacy-depsgraph=${WITH_LEGACY_DEPSGRAPH}
)
endif()
add_subdirectory(collada)

View File

@ -0,0 +1,65 @@
# ***** BEGIN GPL LICENSE BLOCK *****
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# Contributor(s): Jacques Beaurain.
#
# ***** END GPL LICENSE BLOCK *****
# --env-system-scripts allows to run without the install target.
# Use '--write-blend=/tmp/test.blend' to view output
# Some tests are interesting but take too long to run
# and don't give deterministic results
set(USE_EXPERIMENTAL_TESTS FALSE)
set(TEST_SRC_DIR ${CMAKE_SOURCE_DIR}/../lib/tests)
set(TEST_OUT_DIR ${CMAKE_BINARY_DIR}/tests)
# ugh, any better way to do this on testing only?
execute_process(COMMAND ${CMAKE_COMMAND} -E make_directory ${TEST_OUT_DIR})
#~ if(NOT IS_DIRECTORY ${TEST_SRC_DIR})
#~ message(FATAL_ERROR "CMake test directory not found!")
#~ endif()
# all calls to blender use this
if(APPLE)
if(${CMAKE_GENERATOR} MATCHES "Xcode")
set(TEST_BLENDER_EXE_PARAMS --background -noaudio --factory-startup)
else()
set(TEST_BLENDER_EXE_PARAMS --background -noaudio --factory-startup --env-system-scripts ${CMAKE_SOURCE_DIR}/release/scripts)
endif()
else()
set(TEST_BLENDER_EXE_PARAMS --background -noaudio --factory-startup --env-system-scripts ${CMAKE_SOURCE_DIR}/release/scripts)
endif()
# for testing with valgrind prefix: valgrind --track-origins=yes --error-limit=no
# set(TEST_BLENDER_EXE_BARE ${TEST_BLENDER_EXE})
# set(TEST_BLENDER_EXE ${TEST_BLENDER_EXE} ${TEST_BLENDER_EXE_PARAMS} )
# ------------------------------------------------------------------------------
# GENERAL PYTHON CORRECTNESS TESTS
macro (COLLADA_TEST module test_name)
add_test(
NAME collada_${test_name}
COMMAND "$<TARGET_FILE:blender>" ${TEST_BLENDER_EXE_PARAMS} ${TEST_SRC_DIR}/collada/${module}/${test_name}.blend
--python ${CMAKE_CURRENT_LIST_DIR}/${module}/test_${test_name}.py --
--testdir ${TEST_SRC_DIR}/collada/${module}
)
endmacro()
COLLADA_TEST(mesh mesh_simple)

View File

@ -0,0 +1,144 @@
#!/usr/bin/env python3
# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
#
# Call as follows:
# python collada_mesh_simple.py --blender PATH_TO_BLENDER_EXE --testdir PATH_TO_SVN/lib/tests/collada/mesh
#
import sys
import bpy
import argparse
import functools
import shutil
import tempfile
import unittest
import difflib
import pathlib
from pathlib import Path
def with_tempdir(wrapped):
"""Creates a temporary directory for the function, cleaning up after it returns normally.
When the wrapped function raises an exception, the contents of the temporary directory
remain available for manual inspection.
The wrapped function is called with an extra positional argument containing
the pathlib.Path() of the temporary directory.
"""
@functools.wraps(wrapped)
def decorator(*args, **kwargs):
dirname = tempfile.mkdtemp(prefix='blender-collada-test')
#print("Using tempdir %s" % dirname)
try:
retval = wrapped(*args, pathlib.Path(dirname), **kwargs)
except:
print('Exception in %s, not cleaning up temporary directory %s' % (wrapped, dirname))
raise
else:
shutil.rmtree(dirname)
return retval
return decorator
class AbstractColladaTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.testdir = pathlib.Path(args.testdir)
def checkdae(self, reference, export):
"""
collada verifier checks if exported dae file is the same as reference dae
"""
ref = open(reference)
exp = open(export)
diff=difflib.unified_diff(ref.readlines(), exp.readlines(), lineterm='', n=0)
ref.close()
exp.close()
diff_count = 0;
for line in diff:
error = True
for prefix in ('---', '+++', '@@'):
# Ignore diff metadata
if line.startswith(prefix):
error=False
break
else:
# Ignore time stamps
for ignore in ('<created>', '<modified>', '<authoring_tool>'):
if line[1:].strip().startswith(ignore):
error=False
break
if error:
diff_count +=1
print ("%s"%line.strip())
if diff_count > 0:
print("Generated file differs from reference")
print("reference: %s" % reference)
print("result : %s" % export)
return diff_count == 0
class MeshExportTest(AbstractColladaTest):
@with_tempdir
def test_export_single_mesh(self, tempdir: pathlib.Path):
test = "mesh_simple_001"
reference_dae = self.testdir / Path("%s.dae" % test)
outfile = tempdir / Path("%s_out.dae" % test)
bpy.ops.wm.collada_export(filepath="%s" % str(outfile),
check_existing=True,
filemode=8,
display_type="DEFAULT",
sort_method="FILE_SORT_ALPHA",
apply_modifiers=False,
export_mesh_type=0,
export_mesh_type_selection="view",
selected=False,
include_children=False,
include_armatures=False,
include_shapekeys=True,
deform_bones_only=False,
sampling_rate=0,
active_uv_only=False,
use_texture_copies=True,
triangulate=False,
use_object_instantiation=True,
use_blender_profile=True,
sort_by_name=False,
export_transformation_type=0,
export_transformation_type_selection="matrix",
export_texture_type=0,
export_texture_type_selection="mat",
open_sim=False,
limit_precision=False,
keep_bind_info=False)
# Now check the resulting Collada file.
self.assertTrue(self.checkdae(reference_dae, outfile))
if __name__ == '__main__':
sys.argv = [__file__] + (sys.argv[sys.argv.index("--") + 1:] if "--" in sys.argv else [])
parser = argparse.ArgumentParser()
parser.add_argument('--testdir', required=True)
args, remaining = parser.parse_known_args()
unittest.main(argv=sys.argv[0:1]+remaining)