Skip to content

Commit

Permalink
Add support for Alembic Facesets as UsdGeomSubsets in usdAbc
Browse files Browse the repository at this point in the history
This work is a continuation of Adam Ferrall-Nunge's work in PixarAnimationStudios#219, which added support for Alembic facesets to USD. At that time they were represented as Faceset data on the parent prim, which was subsequently read by pxrUsdIn and expanded back to faceset nodes.
Since then, USD introduced the GeomSubset prim type. To a large extent this simplified the problem: The exact same hierarchy is maintained between Alembic and USD with only the type changing (Alembic "Faceset" to USD "GeomSubset"). pxrUsdIn already has support for GeomSubset, so there was no need to make any pxrUsdIn changes for Katana.
  • Loading branch information
abucior-ilm committed Jan 25, 2019
1 parent 1845291 commit 2125eab
Show file tree
Hide file tree
Showing 5 changed files with 300 additions and 4 deletions.
102 changes: 101 additions & 1 deletion pxr/usd/plugin/usdAbc/alembicReader.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2016 Pixar
// Copyright 2016-2019 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
Expand Down Expand Up @@ -2849,6 +2849,65 @@ struct _CopyFaceVaryingInterpolateBoundary : _CopyGeneric<IInt32Property> {
}
};


/// Base class to copy attributes of an alembic faceset to a USD GeomSubset
struct _CopyFaceSetBase {
IFaceSet object;

_CopyFaceSetBase(const IFaceSet& object_) : object(object_) { }

bool operator()(_IsValidTag) const
{
return object.valid();
}

const MetaData& operator()(_MetaDataTag) const
{
return object.getMetaData();
}

_AlembicTimeSamples operator()(_SampleTimesTag) const
{
return _GetSampleTimes(object);
}
};

/// Class to copy faceset isPartition into the family name
struct _CopyFaceSetFamilyName : _CopyFaceSetBase {
using _CopyFaceSetBase::operator();

_CopyFaceSetFamilyName(const IFaceSet& object_)
: _CopyFaceSetBase(object_) {};

bool operator()(const UsdAbc_AlembicDataAny& dst,
const ISampleSelector& iss) const
{
// Because the absence of the ".facesExclusive" will trigger an
// exception in IFaceSetSchema, we need to manually deal with the
// default state (and discard the exception thrown erroneously by
// getFaceExclusivity()).
//
// This is a bug in Alembic that has been fixed in Alembic 1.7.2, but
// the mininum required version for USD is 1.5.2. This workaround must
// remain until the required Alembic version is changed for USD.
//
// The Alembic issue can be tracked here:
// https://github.com/alembic/alembic/issues/129
bool isPartition = false;
try {
isPartition = object.getSchema().getFaceExclusivity() == kFaceSetExclusive;
}
catch(const Alembic::Util::Exception&) {}

if (isPartition)
{
return dst.Set(UsdGeomTokens->nonOverlapping);
}

return dst.Set(UsdGeomTokens->unrestricted);
}
};

static
TfToken
_ConvertCurveBasis(BasisType value)
Expand Down Expand Up @@ -3264,6 +3323,44 @@ _ReadSubD(_PrimReaderContext* context)
_ReadProperty<IV2fGeomParam, GfVec2f>(context, "uv", _GetUVPropertyName(), _GetUVTypeName());
}

static
void
_ReadFaceSet(_PrimReaderContext* context)
{
typedef IFaceSet Type;

// Wrap the object.
if (!Type::matches(context->GetObject().getHeader())) {
// Not of type Type.
return;
}

Type object(context->GetObject(), kWrapExisting);

// Add child properties under schema.
context->SetSchema(Type::schema_type::info_type::defaultName());

// Set prim type. This depends on the CurveType of the curve.
context->GetPrim().typeName = UsdAbcPrimTypeNames->GeomSubset;

context->AddProperty(
UsdGeomTokens->indices,
SdfValueTypeNames->IntArray,
_CopyGeneric<IInt32ArrayProperty, int>(
context->ExtractSchema(".faces")));
context->AddUniformProperty(
UsdGeomTokens->elementType,
SdfValueTypeNames->Token,
_CopySynthetic(UsdGeomTokens->face));
context->AddUniformProperty(
UsdGeomTokens->familyName,
SdfValueTypeNames->Token,
_CopyFaceSetFamilyName(object));

// Consume properties implicitly handled above.
context->Extract(Type::schema_type::info_type::defaultName());
}

static
void
_ReadCurves(_PrimReaderContext* context)
Expand Down Expand Up @@ -3803,6 +3900,9 @@ _ReaderSchemaBuilder::_ReaderSchemaBuilder()
.AppendReader(_ReadUserProperties)
.AppendReader(_ReadOther)
;
schema.AddType(FaceSetSchemaInfo::title())
.AppendReader(_ReadFaceSet)
;
schema.AddType(CurvesSchemaInfo::title())
.AppendReader(_ReadOrientation)
.AppendReader(_ReadCurves)
Expand Down
3 changes: 2 additions & 1 deletion pxr/usd/plugin/usdAbc/alembicUtil.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2016 Pixar
// Copyright 2016-2019 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
Expand Down Expand Up @@ -94,6 +94,7 @@ using namespace ::Alembic::Abc;
(PseudoRoot) \
(Scope) \
(Xform) \
(GeomSubset)\
/* end */
TF_DECLARE_PUBLIC_TOKENS(UsdAbcPrimTypeNames, USD_ABC_PRIM_TYPE_NAMES);

Expand Down
65 changes: 63 additions & 2 deletions pxr/usd/plugin/usdAbc/alembicWriter.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
//
// Copyright 2016 Pixar
// Copyright 2016-2019 Pixar
//
// Licensed under the Apache License, Version 2.0 (the "Apache License")
// with the following modification; you may not use this file except in
Expand Down Expand Up @@ -1932,7 +1932,6 @@ _CopyPointIds(const VtValue& src)
return _SampleForAlembic(std::vector<uint64_t>(value.begin(), value.end()));
}


// ----------------------------------------------------------------------------

//
Expand Down Expand Up @@ -2887,6 +2886,65 @@ _WritePolyMesh(_PrimWriterContext* context)
context->AddTimeSampling(context->GetSampleTimesUnion()));
}

static
void
_WriteFaceSet(_PrimWriterContext* context)
{
typedef OFaceSet Type;

const _WriterSchema& schema = context->GetSchema();

// Create the object and make it the parent.
shared_ptr<Type> object(new Type(context->GetParent(),
context->GetAlembicPrimName(),
_GetPrimMetadata(*context)));
context->SetParent(object);

// Collect the properties we need.
context->SetSampleTimesUnion(UsdAbc_TimeSamples());

UsdSamples indices =
context->ExtractSamples(UsdGeomTokens->indices,
SdfValueTypeNames->IntArray);
UsdSamples familyName =
context->ExtractSamples(UsdGeomTokens->familyName,
SdfValueTypeNames->Token);

// Copy all the samples.
typedef Type::schema_type::Sample SampleT;
SampleT sample;

for (double time : context->GetSampleTimesUnion()) {
// Build the sample.
sample.reset();
_SampleForAlembic alembicFaces =
_Copy(schema,
time, indices,
&sample, &SampleT::setFaces);

// Write the sample.
object->getSchema().set(sample);
}

// Face set exclusivity is not a property of the sample. Instead, it's set
// on the object schema and not time sampled.
FaceSetExclusivity faceSetExclusivity = kFaceSetNonExclusive;
if (!familyName.IsEmpty())
{
const TfToken& value = familyName.Get(0.0f).UncheckedGet<TfToken>();
if (!value.IsEmpty() &&
(value == UsdGeomTokens->partition ||
value == UsdGeomTokens->nonOverlapping)) {
faceSetExclusivity = kFaceSetExclusive;
}
}
object->getSchema().setFaceExclusivity(faceSetExclusivity);

// Set the time sampling.
object->getSchema().setTimeSampling(
context->AddTimeSampling(context->GetSampleTimesUnion()));
}

// As of Alembic-1.5.1, OSubD::schema_type::Sample has a bug:
// setHoles() actually sets cornerIndices. The member, m_holes, is
// protected so we subclass and fix setHoles().
Expand Down Expand Up @@ -3472,6 +3530,9 @@ _WriterSchemaBuilder::_WriterSchemaBuilder()
.AppendWriter(_WriteUserProperties)
.AppendWriter(_WriteOther)
;
schema.AddType(UsdAbcPrimTypeNames->GeomSubset)
.AppendWriter(_WriteFaceSet)
;

// This handles the root.
schema.AddType(UsdAbcPrimTypeNames->PseudoRoot)
Expand Down
90 changes: 90 additions & 0 deletions pxr/usd/plugin/usdAbc/testenv/testUsdAbcFaceset.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/pxrpythonsubst
#
# Copyright 2019 Pixar
#
# Licensed under the Apache License, Version 2.0 (the "Apache License")
# with the following modification; you may not use this file except in
# compliance with the Apache License and the following modification to it:
# Section 6. Trademarks. is deleted and replaced with:
#
# 6. Trademarks. This License does not grant permission to use the trade
# names, trademarks, service marks, or product names of the Licensor
# and its affiliates, except as required to comply with Section 4(c) of
# the License and to reproduce the content of the NOTICE file.
#
# You may obtain a copy of the Apache License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the Apache License with the above modification is
# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the Apache License for the specific
# language governing permissions and limitations under the Apache License.

from pxr import Usd, UsdAbc, UsdGeom, Gf
import unittest


class TestUsdAbcFaceset(unittest.TestCase):
def test_RoundTrip(self):
usdFile = 'original.usda'
abcFile = 'converted.abc'
self.assertTrue(UsdAbc._WriteAlembic(usdFile, abcFile))

stage = Usd.Stage.Open(abcFile)
prim = stage.GetPrimAtPath('/cube1')
self.assertTrue(prim.IsValid())

faceset1prim = stage.GetPrimAtPath('/cube1/faceset1')
faceset2prim = stage.GetPrimAtPath('/cube1/faceset2')
self.assertTrue(faceset1prim.IsValid())
self.assertTrue(faceset2prim.IsValid())

faceset1 = UsdGeom.Subset(faceset1prim)
faceset2 = UsdGeom.Subset(faceset2prim)

self.assertEqual(faceset1.GetFamilyNameAttr().Get(), 'nonOverlapping')
self.assertEqual(faceset2.GetFamilyNameAttr().Get(), 'nonOverlapping')

self.assertEqual(faceset1.GetElementTypeAttr().Get(), 'face')
self.assertEqual(faceset2.GetElementTypeAttr().Get(), 'face')

# Validate the indices for faceset1 (which is animated)
indices = faceset1.GetIndicesAttr()

timeSamples = indices.GetTimeSamples()
expectedTimeSamples = [0.0, 1.0]

self.assertEqual(len(timeSamples), len(expectedTimeSamples))

for c, e in zip(timeSamples, expectedTimeSamples):
self.assertTrue(Gf.IsClose(c, e, 1e-5))

expectedFaceIndices = {0.0: [0, 3, 5], 1.0: [3]}
for time, expectedValue in expectedFaceIndices.iteritems():
faceIndices = indices.Get(time)
for c, e in zip(faceIndices, expectedValue):
self.assertEqual(c, e)

# Validate the indices for faceset2 (which was constant, but promoted
# to a single sample at 0 during conversion)
indices = faceset2.GetIndicesAttr()

timeSamples = indices.GetTimeSamples()
expectedTimeSamples = [0.0]

self.assertEqual(len(timeSamples), len(expectedTimeSamples))

for c, e in zip(timeSamples, expectedTimeSamples):
self.assertTrue(Gf.IsClose(c, e, 1e-5))

expectedFaceIndices = {0.0: [1, 2, 4]}
for time, expectedValue in expectedFaceIndices.items():
faceIndices = indices.Get(time)
for c, e in zip(faceIndices, expectedValue):
self.assertEqual(c, e)


if __name__ == '__main__':
unittest.main()
44 changes: 44 additions & 0 deletions pxr/usd/plugin/usdAbc/testenv/testUsdAbcFaceset/original.usda
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#usda 1.0
(
defaultPrim = "cube1"
upAxis = "Y"
)

def Mesh "cube1"
{
float3[] extent = [(-0.5, -0.5, -0.5), (0.5, 0.5, 0.5)]

int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]
int[] faceVertexIndices = [2, 3, 1, 0, 6, 7, 3, 2, 4, 5, 7, 6, 0, 1, 5, 4, 3, 7, 5, 1, 6, 2, 0, 4]
normal3f[] normals = [(0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 0, 1), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 1, 0), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, 0, -1), (0, -1, 0), (0, -1, 0), (0, -1, 0), (0, -1, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0), (-1, 0, 0)] (
interpolation = "faceVarying"
)
uniform token orientation = "leftHanded"
point3f[] points = [(-0.5, -0.5, 0.5), (0.5, -0.5, 0.5), (-0.5, 0.5, 0.5), (0.5, 0.5, 0.5), (-0.5, -0.5, -0.5), (0.5, -0.5, -0.5), (-0.5, 0.5, -0.5), (0.5, 0.5, -0.5)] (
interpolation = "vertex"
)
float2[] primvars:uv = [(0, 1), (1, 1), (1, 0), (0, 0), (0, 2), (1, 2), (1, 1), (0, 1), (0, 3), (1, 3), (1, 2), (0, 2), (0, 4), (1, 4), (1, 3), (0, 3), (1, 1), (2, 1), (2, 0), (1, 0), (-1, 1), (0, 1), (0, 0), (-1, 0)] (
interpolation = "faceVarying"
)
uniform token subdivisionScheme = "none"

int[] faceVertexCounts = [4, 4, 4, 4, 4, 4]

def GeomSubset "faceset1"
{
uniform token elementType = "face"
uniform token familyName = "partition"
int[] indices.timeSamples = {
0: [0, 3, 5],
1: [3],
}
}

def GeomSubset "faceset2"
{
uniform token elementType = "face"
uniform token familyName = "partition"
int[] indices = [1, 2, 4];
}

}

0 comments on commit 2125eab

Please sign in to comment.