Fix T51534: Alembic: added support for face-varying vertex colours
Houdini writes vertex data in a different format than Blender does; Houdini uses "face-varying scope", which means that the vertex colours are indexed by an ever-increasing number over all vertices of all faces instead of the vertex index. I've also merged the read_custom_data_mcols() and read_mcols() functions, because the latter was only called from the former, and the changes in this commit would add yet more function parameters to pass.
This commit is contained in:
@@ -227,44 +227,6 @@ using Alembic::AbcGeom::IC3fGeomParam;
|
|||||||
using Alembic::AbcGeom::IC4fGeomParam;
|
using Alembic::AbcGeom::IC4fGeomParam;
|
||||||
using Alembic::AbcGeom::IV2fGeomParam;
|
using Alembic::AbcGeom::IV2fGeomParam;
|
||||||
|
|
||||||
static void read_mcols(const CDStreamConfig &config, void *data,
|
|
||||||
const C3fArraySamplePtr &c3f_ptr,
|
|
||||||
const C4fArraySamplePtr &c4f_ptr)
|
|
||||||
{
|
|
||||||
MCol *cfaces = static_cast<MCol *>(data);
|
|
||||||
MPoly *polys = config.mpoly;
|
|
||||||
MLoop *mloops = config.mloop;
|
|
||||||
|
|
||||||
/* Either one or the other should be given. */
|
|
||||||
BLI_assert(c3f_ptr || c4f_ptr);
|
|
||||||
const bool use_c3f_ptr = (c3f_ptr.get() != nullptr);
|
|
||||||
|
|
||||||
for (int i = 0; i < config.totpoly; ++i) {
|
|
||||||
MPoly *p = &polys[i];
|
|
||||||
MCol *cface = &cfaces[p->loopstart + p->totloop];
|
|
||||||
MLoop *mloop = &mloops[p->loopstart + p->totloop];
|
|
||||||
|
|
||||||
for (int j = 0; j < p->totloop; ++j) {
|
|
||||||
cface--;
|
|
||||||
mloop--;
|
|
||||||
|
|
||||||
if (use_c3f_ptr) {
|
|
||||||
const Imath::C3f &color = (*c3f_ptr)[mloop->v];
|
|
||||||
cface->a = FTOCHAR(color[0]);
|
|
||||||
cface->r = FTOCHAR(color[1]);
|
|
||||||
cface->g = FTOCHAR(color[2]);
|
|
||||||
cface->b = 255;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
const Imath::C4f &color = (*c4f_ptr)[mloop->v];
|
|
||||||
cface->a = FTOCHAR(color[0]);
|
|
||||||
cface->r = FTOCHAR(color[1]);
|
|
||||||
cface->g = FTOCHAR(color[2]);
|
|
||||||
cface->b = FTOCHAR(color[3]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static void read_uvs(const CDStreamConfig &config, void *data,
|
static void read_uvs(const CDStreamConfig &config, void *data,
|
||||||
const Alembic::AbcGeom::V2fArraySamplePtr &uvs,
|
const Alembic::AbcGeom::V2fArraySamplePtr &uvs,
|
||||||
@@ -290,34 +252,83 @@ static void read_uvs(const CDStreamConfig &config, void *data,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void read_custom_data_mcols(const ICompoundProperty &prop,
|
static void read_custom_data_mcols(const ICompoundProperty &arbGeomParams,
|
||||||
const PropertyHeader &prop_header,
|
const PropertyHeader &prop_header,
|
||||||
const CDStreamConfig &config,
|
const CDStreamConfig &config,
|
||||||
const Alembic::Abc::ISampleSelector &iss)
|
const Alembic::Abc::ISampleSelector &iss)
|
||||||
{
|
{
|
||||||
C3fArraySamplePtr c3f_ptr = C3fArraySamplePtr();
|
C3fArraySamplePtr c3f_ptr = C3fArraySamplePtr();
|
||||||
C4fArraySamplePtr c4f_ptr = C4fArraySamplePtr();
|
C4fArraySamplePtr c4f_ptr = C4fArraySamplePtr();
|
||||||
|
bool use_c3f_ptr;
|
||||||
|
bool is_facevarying;
|
||||||
|
|
||||||
|
/* Find the correct interpretation of the data */
|
||||||
if (IC3fGeomParam::matches(prop_header)) {
|
if (IC3fGeomParam::matches(prop_header)) {
|
||||||
IC3fGeomParam color_param(prop, prop_header.getName());
|
IC3fGeomParam color_param(arbGeomParams, prop_header.getName());
|
||||||
IC3fGeomParam::Sample sample;
|
IC3fGeomParam::Sample sample;
|
||||||
|
BLI_assert(!strcmp("rgb", color_param.getInterpretation()));
|
||||||
|
|
||||||
color_param.getIndexed(sample, iss);
|
color_param.getIndexed(sample, iss);
|
||||||
|
is_facevarying = sample.getScope() == kFacevaryingScope &&
|
||||||
|
config.totloop == sample.getIndices()->size();
|
||||||
|
|
||||||
c3f_ptr = sample.getVals();
|
c3f_ptr = sample.getVals();
|
||||||
|
use_c3f_ptr = true;
|
||||||
}
|
}
|
||||||
else if (IC4fGeomParam::matches(prop_header)) {
|
else if (IC4fGeomParam::matches(prop_header)) {
|
||||||
IC4fGeomParam color_param(prop, prop_header.getName());
|
IC4fGeomParam color_param(arbGeomParams, prop_header.getName());
|
||||||
IC4fGeomParam::Sample sample;
|
IC4fGeomParam::Sample sample;
|
||||||
|
BLI_assert(!strcmp("rgba", color_param.getInterpretation()));
|
||||||
|
|
||||||
color_param.getIndexed(sample, iss);
|
color_param.getIndexed(sample, iss);
|
||||||
|
is_facevarying = sample.getScope() == kFacevaryingScope &&
|
||||||
|
config.totloop == sample.getIndices()->size();
|
||||||
|
|
||||||
c4f_ptr = sample.getVals();
|
c4f_ptr = sample.getVals();
|
||||||
|
use_c3f_ptr = false;
|
||||||
}
|
}
|
||||||
|
else {
|
||||||
|
/* this won't happen due to the checks in read_custom_data() */
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
BLI_assert(c3f_ptr || c4f_ptr);
|
||||||
|
|
||||||
|
/* Read the vertex colors */
|
||||||
void *cd_data = config.add_customdata_cb(config.user_data,
|
void *cd_data = config.add_customdata_cb(config.user_data,
|
||||||
prop_header.getName().c_str(),
|
prop_header.getName().c_str(),
|
||||||
CD_MLOOPCOL);
|
CD_MLOOPCOL);
|
||||||
|
MCol *cfaces = static_cast<MCol *>(cd_data);
|
||||||
|
MPoly *mpolys = config.mpoly;
|
||||||
|
MLoop *mloops = config.mloop;
|
||||||
|
|
||||||
read_mcols(config, cd_data, c3f_ptr, c4f_ptr);
|
size_t face_index = 0;
|
||||||
|
size_t color_index;
|
||||||
|
for (int i = 0; i < config.totpoly; ++i) {
|
||||||
|
MPoly *poly = &mpolys[i];
|
||||||
|
MCol *cface = &cfaces[poly->loopstart + poly->totloop];
|
||||||
|
MLoop *mloop = &mloops[poly->loopstart + poly->totloop];
|
||||||
|
|
||||||
|
for (int j = 0; j < poly->totloop; ++j, ++face_index) {
|
||||||
|
--cface;
|
||||||
|
--mloop;
|
||||||
|
color_index = is_facevarying ? face_index : mloop->v;
|
||||||
|
|
||||||
|
if (use_c3f_ptr) {
|
||||||
|
const Imath::C3f &color = (*c3f_ptr)[color_index];
|
||||||
|
cface->a = FTOCHAR(color[0]);
|
||||||
|
cface->r = FTOCHAR(color[1]);
|
||||||
|
cface->g = FTOCHAR(color[2]);
|
||||||
|
cface->b = 255;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const Imath::C4f &color = (*c4f_ptr)[color_index];
|
||||||
|
cface->a = FTOCHAR(color[0]);
|
||||||
|
cface->r = FTOCHAR(color[1]);
|
||||||
|
cface->g = FTOCHAR(color[2]);
|
||||||
|
cface->b = FTOCHAR(color[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void read_custom_data_uvs(const ICompoundProperty &prop,
|
static void read_custom_data_uvs(const ICompoundProperty &prop,
|
||||||
|
@@ -31,7 +31,7 @@ import bpy
|
|||||||
args = None
|
args = None
|
||||||
|
|
||||||
|
|
||||||
class SimpleImportTest(unittest.TestCase):
|
class AbstractAlembicTest(unittest.TestCase):
|
||||||
@classmethod
|
@classmethod
|
||||||
def setUpClass(cls):
|
def setUpClass(cls):
|
||||||
cls.testdir = args.testdir
|
cls.testdir = args.testdir
|
||||||
@@ -43,6 +43,18 @@ class SimpleImportTest(unittest.TestCase):
|
|||||||
# Make sure we always start with a known-empty file.
|
# Make sure we always start with a known-empty file.
|
||||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
|
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
|
||||||
|
|
||||||
|
def assertAlmostEqualFloatArray(self, actual, expect, places=6, delta=None):
|
||||||
|
"""Asserts that the arrays of floats are almost equal."""
|
||||||
|
|
||||||
|
self.assertEqual(len(actual), len(expect),
|
||||||
|
'Actual array has %d items, expected %d' % (len(actual), len(expect)))
|
||||||
|
|
||||||
|
for idx, (act, exp) in enumerate(zip(actual, expect)):
|
||||||
|
self.assertAlmostEqual(act, exp, places=places, delta=delta,
|
||||||
|
msg='%f != %f at index %d' % (act, exp, idx))
|
||||||
|
|
||||||
|
|
||||||
|
class SimpleImportTest(AbstractAlembicTest):
|
||||||
def test_import_cube_hierarchy(self):
|
def test_import_cube_hierarchy(self):
|
||||||
res = bpy.ops.wm.alembic_import(
|
res = bpy.ops.wm.alembic_import(
|
||||||
filepath=str(self.testdir / "cubes-hierarchy.abc"),
|
filepath=str(self.testdir / "cubes-hierarchy.abc"),
|
||||||
@@ -158,6 +170,38 @@ class SimpleImportTest(unittest.TestCase):
|
|||||||
self.assertEqual('CubeShape', bpy.data.objects['Cube'].data.name)
|
self.assertEqual('CubeShape', bpy.data.objects['Cube'].data.name)
|
||||||
|
|
||||||
|
|
||||||
|
class VertexColourImportTest(AbstractAlembicTest):
|
||||||
|
def test_import_from_houdini(self):
|
||||||
|
# Houdini saved "face-varying", and as RGB.
|
||||||
|
res = bpy.ops.wm.alembic_import(
|
||||||
|
filepath=str(self.testdir / "vertex-colours-houdini.abc"),
|
||||||
|
as_background_job=False)
|
||||||
|
self.assertEqual({'FINISHED'}, res)
|
||||||
|
|
||||||
|
ob = bpy.context.active_object
|
||||||
|
layer = ob.data.vertex_colors['Cf'] # MeshLoopColorLayer
|
||||||
|
|
||||||
|
# Test some known-good values.
|
||||||
|
self.assertAlmostEqualFloatArray(layer.data[0].color, (0, 0, 0))
|
||||||
|
self.assertAlmostEqualFloatArray(layer.data[98].color, (0.9019607, 0.4745098, 0.2666666))
|
||||||
|
self.assertAlmostEqualFloatArray(layer.data[99].color, (0.8941176, 0.4705882, 0.2627451))
|
||||||
|
|
||||||
|
def test_import_from_blender(self):
|
||||||
|
# Blender saved per-vertex, and as RGBA.
|
||||||
|
res = bpy.ops.wm.alembic_import(
|
||||||
|
filepath=str(self.testdir / "vertex-colours-blender.abc"),
|
||||||
|
as_background_job=False)
|
||||||
|
self.assertEqual({'FINISHED'}, res)
|
||||||
|
|
||||||
|
ob = bpy.context.active_object
|
||||||
|
layer = ob.data.vertex_colors['Cf'] # MeshLoopColorLayer
|
||||||
|
|
||||||
|
# Test some known-good values.
|
||||||
|
self.assertAlmostEqualFloatArray(layer.data[0].color, (1.0, 0.0156862, 0.3607843))
|
||||||
|
self.assertAlmostEqualFloatArray(layer.data[98].color, (0.0941176, 0.1215686, 0.9137254))
|
||||||
|
self.assertAlmostEqualFloatArray(layer.data[99].color, (0.1294117, 0.3529411, 0.7529411))
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
global args
|
global args
|
||||||
import argparse
|
import argparse
|
||||||
|
Reference in New Issue
Block a user