Fix T103354: Author extents on UsdGeomMesh #104676

Merged
Sybren A. Stüvel merged 6 commits from wave/blender_wave_Apple:contribs/T103354_author_extents into main 2023-02-14 12:12:05 +01:00
5 changed files with 71 additions and 15 deletions
Showing only changes of commit 5089928eb0 - Show all commits

View File

@ -154,18 +154,22 @@ bool USDAbstractWriter::mark_as_instance(const HierarchyContext &context, const
void USDAbstractWriter::author_extent(const pxr::UsdTimeCode timecode, pxr::UsdGeomBoundable &prim)
{
/* Do not use any existing `extentsHint` that may be authored, instead recompute the extent when authoring it. */
/* Do not use any existing `extentsHint` that may be authored, instead recompute the extent when
* authoring it. */
const bool useExtentsHint = false;
const pxr::TfTokenVector includedPurposes{pxr::UsdGeomTokens->default_};
pxr::UsdGeomBBoxCache bboxCache(timecode, includedPurposes, useExtentsHint);
pxr::GfBBox3d bounds = bboxCache.ComputeLocalBound(prim.GetPrim());
if (pxr::GfBBox3d() == bounds) {
/* This will occur, for example, if a mesh does not have any vertices. */
WM_reportf(RPT_ERROR, "USD Export: no bounds could be computed for %s", prim.GetPrim().GetName().GetText());
WM_reportf(RPT_ERROR,
wave marked this conversation as resolved

Since this situation doesn't seem to abort the export itself, I think RPT_WARNING would be more suitable here.

Since this situation doesn't seem to abort the export itself, I think `RPT_WARNING` would be more suitable here.
"USD Export: no bounds could be computed for %s",
prim.GetPrim().GetName().GetText());
return;
}
pxr::VtArray<pxr::GfVec3f> extent{ (pxr::GfVec3f)bounds.GetRange().GetMin(), (pxr::GfVec3f)bounds.GetRange().GetMax() };
pxr::VtArray<pxr::GfVec3f> extent{(pxr::GfVec3f)bounds.GetRange().GetMin(),
(pxr::GfVec3f)bounds.GetRange().GetMax()};
prim.CreateExtentAttr().Set(extent);
}

View File

@ -7,9 +7,9 @@
#include <pxr/usd/sdf/path.h>
#include <pxr/usd/usd/stage.h>
#include <pxr/usd/usdGeom/boundable.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdUtils/sparseValueWriter.h>
#include <pxr/usd/usdGeom/boundable.h>
#include <vector>
@ -68,7 +68,7 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
* Reference the original data instead of writing a copy.
*/
virtual bool mark_as_instance(const HierarchyContext &context, const pxr::UsdPrim &prim);
/**
* Compute the bounds for a boundable prim, and author the result as the `extent` attribute.
*
@ -76,9 +76,9 @@ class USDAbstractWriter : public AbstractHierarchyWriter {
* cached bounds when possible.
*
* This method does not author the `extentsHint` attribute, which is also important to provide.
* Whereas the `extent` attribute can only be authored on prims inheriting from `UsdGeomBoundable`,
* an `extentsHint` can be provided on any prim, including scopes. This `extentsHint` should be
* authored on every prim in a hierarchy being exported.
* Whereas the `extent` attribute can only be authored on prims inheriting from
* `UsdGeomBoundable`, an `extentsHint` can be provided on any prim, including scopes. This
* `extentsHint` should be authored on every prim in a hierarchy being exported.
*
* Note that this hint is only useful when importing or inspecting layers, and should not be
* taken into account when computing extents during export.

View File

@ -62,7 +62,7 @@ void USDHairWriter::do_write(HierarchyContext &context)
colors.push_back(pxr::GfVec3f(cache[0]->col));
curves.CreateDisplayColorAttr(pxr::VtValue(colors));
}
this->author_extent(timecode, curves);
}

View File

@ -3,9 +3,9 @@
#include "usd_writer_mesh.h"
#include "usd_hierarchy_iterator.h"
#include <pxr/usd/usdGeom/bboxCache.h>
#include <pxr/usd/usdGeom/mesh.h>
#include <pxr/usd/usdGeom/primvarsAPI.h>
#include <pxr/usd/usdGeom/bboxCache.h>
#include <pxr/usd/usdShade/material.h>
#include <pxr/usd/usdShade/materialBindingAPI.h>
@ -248,14 +248,14 @@ void USDGenericMeshWriter::write_mesh(HierarchyContext &context, Mesh *mesh)
if (usd_export_context_.export_params.export_materials) {
assign_materials(context, usd_mesh, usd_mesh_data.face_groups);
}
/* Blender grows its bounds cache to cover animated meshes, so only author once. */
float bound_min[3];
float bound_max[3];
INIT_MINMAX(bound_min, bound_max);
BKE_mesh_minmax(mesh, bound_min, bound_max);
pxr::VtArray<pxr::GfVec3f> extent{ pxr::GfVec3f{ bound_min[0], bound_min[1], bound_min[2]},
pxr::GfVec3f{ bound_max[0], bound_max[1], bound_max[2]} };
pxr::VtArray<pxr::GfVec3f> extent{pxr::GfVec3f{bound_min[0], bound_min[1], bound_min[2]},
pxr::GfVec3f{bound_max[0], bound_max[1], bound_max[2]}};
usd_mesh.CreateExtentAttr().Set(extent);
}

View File

@ -5,7 +5,10 @@ import pprint
import sys
import tempfile
import unittest
from pxr import Usd
from pxr import UsdUtils
from pxr import UsdGeom
from pxr import Gf
import bpy
@ -74,13 +77,62 @@ class USDExportTest(AbstractUSDTest):
self.assertFalse(collection, pprint.pformat(collection))
def compareVec3d(self, first, second):
places = 5
self.assertAlmostEqual(first[0], second[0], places)
self.assertAlmostEqual(first[1], second[1], places)
self.assertAlmostEqual(first[2], second[2], places)
def test_export_extents(self):
"""Test that exported scenes contain have a properly authored extent attribute on each boundable prim"""
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "usd_extent_test.blend"))
export_path = self.tempdir / "usd_extent_test.usda"
res = bpy.ops.wm.usd_export(
filepath=str(export_path),
export_materials=True,
evaluation_mode="RENDER",
)
self.assertEqual({Result.finished}, res, f"Unable to export to {export_path}")
# if prims are missing, the exporter must have skipped some objects
stats = UsdUtils.ComputeUsdStageStats(str(export_path))
self.assertEqual(stats["totalPrimCount"], 15, "Unexpected number of prims")
# validate the overall world bounds of the scene
stage = Usd.Stage.Open(str(export_path))
scenePrim = stage.GetPrimAtPath("/scene")
bboxcache = UsdGeom.BBoxCache(Usd.TimeCode.Default(), [UsdGeom.Tokens.default_])
bounds = bboxcache.ComputeWorldBound(scenePrim)
bound_min = bounds.GetRange().GetMin()
bound_max = bounds.GetRange().GetMax()
self.compareVec3d(bound_min, Gf.Vec3d(-5.752975881, -1, -2.798513651))
self.compareVec3d(bound_max, Gf.Vec3d(1, 2.9515805244, 2.7985136508))
# validate the locally authored extents
prim = stage.GetPrimAtPath("/scene/BigCube/BigCubeMesh")
extent = UsdGeom.Boundable(prim).GetExtentAttr().Get()
self.compareVec3d(Gf.Vec3d(extent[0]), Gf.Vec3d(-1, -1, -2.7985137))
self.compareVec3d(Gf.Vec3d(extent[1]), Gf.Vec3d(1, 1, 2.7985137))
prim = stage.GetPrimAtPath("/scene/LittleCube/LittleCubeMesh")
extent = UsdGeom.Boundable(prim).GetExtentAttr().Get()
self.compareVec3d(Gf.Vec3d(extent[0]), Gf.Vec3d(-1, -1, -1))
self.compareVec3d(Gf.Vec3d(extent[1]), Gf.Vec3d(1, 1, 1))
prim = stage.GetPrimAtPath("/scene/Volume/Volume")
extent = UsdGeom.Boundable(prim).GetExtentAttr().Get()
self.compareVec3d(
Gf.Vec3d(extent[0]), Gf.Vec3d(-0.7313742, -0.68043584, -0.5801515)
)
self.compareVec3d(
Gf.Vec3d(extent[1]), Gf.Vec3d(0.7515701, 0.5500924, 0.9027928)
)
def main():
global args
import argparse
if "--" in sys.argv:
argv = [sys.argv[0]] + sys.argv[sys.argv.index("--") + 1 :]
argv = [sys.argv[0]] + sys.argv[sys.argv.index("--") + 1:]
else:
argv = sys.argv