W.I.P. Add Prim::add_child() and add make unique Prim name feature.

This commit is contained in:
Syoyo Fujita
2023-05-29 21:57:36 +09:00
parent 8e7498a86d
commit 0bfe1ab531
8 changed files with 262 additions and 25 deletions

View File

@@ -24,6 +24,8 @@ void CreateScene(tinyusdz::Stage *stage) {
// scene(Stage) in multi-threaded context, The app must take care of resource locks
// in the app layer.
std::string err;
//
// Create simple material with UsdPrevieSurface.
//
@@ -49,22 +51,27 @@ void CreateScene(tinyusdz::Stage *stage) {
surfaceShader.metallic = 0.3f;
// TODO: UsdUVTexture, UsdPrimvarReader***, UsdTransform2d
// Connect to UsdPreviewSurface's outputs:surface by setting targetPath.
//
//
// token outputs:surface = </mat/defaultPBR.outputs:surface>
mat.surface.set(tinyusdz::Path(/* prim path */"/mat/defaultPBR", /* prop path */"outputs:surface"));
//
// Shaer::value is `value::Value` type, so can use '=' to assign Shader object.
//
shader.value = std::move(surfaceShader);
shader.value = std::move(surfaceShader);
}
tinyusdz::Prim shaderPrim(shader);
tinyusdz::Prim matPrim(mat);
matPrim.children().emplace_back(std::move(shaderPrim));
//matPrim.children().emplace_back(std::move(shaderPrim)); // no uniqueness check
// Use add_child() to ensure child Prim has unique name.
if (!matPrim.add_child(std::move(shaderPrim), /* rename Prim name if_required */true, &err)) {
std::cerr << "Failed to constrcut Scene: " << err << "\n";
}
//
// To construct Prim, first create concrete Prim object(e.g. Xform, GeomMesh),
@@ -287,15 +294,20 @@ void CreateScene(tinyusdz::Stage *stage) {
}
tinyusdz::GeomSphere sphere;
tinyusdz::GeomSphere sphere1;
{
sphere.name = "sphere0";
sphere.radius = 3.14;
sphere1.name = "sphere0";
sphere1.radius = 3.14;
}
tinyusdz::GeomSphere sphere2;
{
sphere2.name = ""; // unique name will be assigned in add_child().
sphere2.radius = 1.05;
}
//
// Create Scene(Stage) hierarchy.
// You can add child Prim to parent Prim's `children()`.
@@ -303,7 +315,8 @@ void CreateScene(tinyusdz::Stage *stage) {
// [Xform]
// |
// +- [Mesh]
// +- [Sphere]
// +- [Sphere0]
// +- [Sphere1]
//
// [Material]
// |
@@ -315,7 +328,7 @@ void CreateScene(tinyusdz::Stage *stage) {
// Xform::name, ...)
tinyusdz::Prim meshPrim(mesh);
tinyusdz::Prim spherePrim(sphere);
tinyusdz::Prim spherePrim(sphere1);
{
// variantSet is maniuplated in Prim.
// Currently we don't provide easy API for variantSet.
@@ -328,7 +341,7 @@ void CreateScene(tinyusdz::Stage *stage) {
// key = variantSet name, value = default Variant selection
vsmap.emplace("colorVariant", "red");
spherePrim.metas().variants = vsmap;
spherePrim.metas().variantSets = std::make_pair(tinyusdz::ListEditQual::Append, variantSetList);
@@ -357,23 +370,38 @@ void CreateScene(tinyusdz::Stage *stage) {
variantSet.name = "green";
variantSet.variantSet.emplace("green", redVariant);
spherePrim.variantSets().emplace("colorVariant", variantSet);
}
tinyusdz::Prim spherePrim2(sphere2);
tinyusdz::Prim xformPrim(xform);
xformPrim.children().emplace_back(std::move(meshPrim));
xformPrim.children().emplace_back(std::move(spherePrim));
//xformPrim.children().emplace_back(std::move(meshPrim));
//xformPrim.children().emplace_back(std::move(spherePrim));
// Use add_child() to ensure child Prim has unique name.
if (!xformPrim.add_child(std::move(meshPrim), /* rename Prim name if_required */true, &err)) {
std::cerr << "Failed to constrcut Scene: " << err << "\n";
}
if (!xformPrim.add_child(std::move(spherePrim), /* rename Prim name if_required */true, &err)) {
std::cerr << "Failed to constrcut Scene: " << err << "\n";
}
#if 0
// set rename Prim `true`, otherwise `add_child` fails since spherePrim2 does not have valid & unique Prim name.
if (!xformPrim.add_child(std::move(spherePrim2), /* rename Prim name if_required */true, &err)) {
std::cerr << "Failed to constrcut Scene: " << err << "\n";
}
#endif
// If you want to specify the appearance/traversal order of child Prim(e.g. Showing Prim tree in GUI, Ascii output), set "primChildren"(token[]) metadata
// xfromPrim.metas().primChildren.size() must be identical to xformPrim.children().size()
xformPrim.metas().primChildren.push_back(tinyusdz::value::token(spherePrim.element_name()));
xformPrim.metas().primChildren.push_back(tinyusdz::value::token(meshPrim.element_name()));
std::cout << "sphere.element_name = " << spherePrim.element_name() << "\n";
std::cout << "mesh.element_name = " << meshPrim.element_name() << "\n";
xformPrim.metas().primChildren.push_back(tinyusdz::value::token(xformPrim.children()[1].element_name()));
xformPrim.metas().primChildren.push_back(tinyusdz::value::token(xformPrim.children()[0].element_name()));
//xformPrim.metas().primChildren.push_back(tinyusdz::value::token(xformPrim.children()[2].element_name()));
stage->metas().defaultPrim = tinyusdz::value::token(xformPrim.element_name()); // token

View File

@@ -1,6 +1,7 @@
// SPDX-License-Identifier: MIT
// Copyright 2021 - Present, Syoyo Fujita.
#include <algorithm>
#include <cstdio>
#include <limits>
#include <numeric>
//
@@ -30,6 +31,59 @@
namespace tinyusdz {
namespace {
///
/// Simply add number suffix to make unique string.
///
/// - plane -> plane1
/// - sphere1 -> sphere11
/// - xform4 -> xform41
///
///
bool makeUniqueName(std::multiset<std::string> &nameSet, const std::string &name, std::string *unique_name) {
if (!unique_name) {
return false;
}
if (nameSet.count(name) == 0) {
(*unique_name) = name;
return 0;
}
// Simply add number
const size_t kMaxLoop = 1024; // to avoid infinite loop.
std::string new_name = name;
size_t cnt = 0;
while (cnt < kMaxLoop) {
size_t i = nameSet.count(new_name);
if (i == 0) {
// This should not happen though.
return false;
}
new_name += std::to_string(i);
if (nameSet.count(new_name) == 0) {
(*unique_name) = new_name;
return true;
}
cnt++;
}
return false;
}
} // namespace
nonstd::optional<Interpolation> InterpolationFromString(const std::string &v) {
if ("faceVarying" == v) {
return Interpolation::FaceVarying;
@@ -851,6 +905,7 @@ nonstd::optional<std::string> GetPrimElementName(const value::Value &v) {
EXTRACT_NAME_AND_RETURN_PATH(RectLight)
EXTRACT_NAME_AND_RETURN_PATH(Material)
EXTRACT_NAME_AND_RETURN_PATH(Shader)
// TODO: extract name must be handled in Shader class
EXTRACT_NAME_AND_RETURN_PATH(UsdPreviewSurface)
EXTRACT_NAME_AND_RETURN_PATH(UsdUVTexture)
EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_int)
@@ -863,6 +918,7 @@ nonstd::optional<std::string> GetPrimElementName(const value::Value &v) {
EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_vector)
EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_point)
EXTRACT_NAME_AND_RETURN_PATH(UsdPrimvarReader_matrix)
//
EXTRACT_NAME_AND_RETURN_PATH(SkelRoot)
EXTRACT_NAME_AND_RETURN_PATH(Skeleton)
EXTRACT_NAME_AND_RETURN_PATH(SkelAnimation)
@@ -904,6 +960,7 @@ bool SetPrimElementName(value::Value &v, const std::string &elementName) {
SET_ELEMENT_NAME(elementName, RectLight)
SET_ELEMENT_NAME(elementName, Material)
SET_ELEMENT_NAME(elementName, Shader)
// TODO: set element name must be handled in Shader class
SET_ELEMENT_NAME(elementName, UsdPreviewSurface)
SET_ELEMENT_NAME(elementName, UsdUVTexture)
SET_ELEMENT_NAME(elementName, UsdPrimvarReader_int)
@@ -911,6 +968,12 @@ bool SetPrimElementName(value::Value &v, const std::string &elementName) {
SET_ELEMENT_NAME(elementName, UsdPrimvarReader_float2)
SET_ELEMENT_NAME(elementName, UsdPrimvarReader_float3)
SET_ELEMENT_NAME(elementName, UsdPrimvarReader_float4)
SET_ELEMENT_NAME(elementName, UsdPrimvarReader_string)
SET_ELEMENT_NAME(elementName, UsdPrimvarReader_normal)
SET_ELEMENT_NAME(elementName, UsdPrimvarReader_vector)
SET_ELEMENT_NAME(elementName, UsdPrimvarReader_point)
SET_ELEMENT_NAME(elementName, UsdPrimvarReader_matrix)
//
SET_ELEMENT_NAME(elementName, SkelRoot)
SET_ELEMENT_NAME(elementName, Skeleton)
SET_ELEMENT_NAME(elementName, SkelAnimation)
@@ -980,6 +1043,78 @@ Prim::Prim(const std::string &elementPath, value::Value &&rhs) {
}
}
bool Prim::add_child(Prim &&rhs, const bool rename_prim_name, std::string *err) {
#if defined(TINYUSD_ENABLE_THREAD)
// TODO: Only take a lock when dirty.
std::lock_guard<std::mutex> lock(_mutex);
#endif
std::string elementName = rhs.element_name();
if (elementName.empty()) {
if (err) {
(*err) = "Prim has empty elementName.\n";
}
return false;
} else {
if (rename_prim_name) {
// assign default name `default`
elementName = "default";
}
}
if (_children.size() != _childrenNameSet.size()) {
// Rebuild _childrenNames
_childrenNameSet.clear();
for (size_t i = 0; i < _children.size(); i++) {
if (_childrenNameSet.count(_children[i].element_name())) {
if (err) {
(*err) = "Internal error: _children contains Prim with same elementName.\n";
}
return false;
}
_childrenNameSet.insert(_children[i].element_name());
}
}
if (_childrenNameSet.count(elementName)) {
if (rename_prim_name) {
std::string unique_name;
if (!makeUniqueName(_childrenNameSet, elementName, &unique_name)) {
if (err) {
(*err) = fmt::format("Internal error. cannot assign unique name for `{}`.\n", elementName);
}
return false;
}
elementName = unique_name;
if (!SetPrimElementName(rhs.get_data(), elementName)) {
if (err) {
(*err) = fmt::format("Internal error. cannot modify Prim's elementName.\n");
}
return false;
}
} else {
if (err) {
(*err) = fmt::format("Prim name(elementName) {} already exists in children.\n", rhs.element_name());
}
return false;
}
}
_childrenNameSet.insert(elementName);
_children.emplace_back(std::move(rhs));
_child_dirty = true;
return true;
}
const std::vector<int64_t> &Prim::get_child_indices_from_primChildren(bool force_update, bool *indices_is_valid) const {
#if defined(TINYUSD_ENABLE_THREAD)
// TODO: Only take a lock when dirty.

View File

@@ -2656,17 +2656,47 @@ class Prim {
_elementPath = Path(elementName, "");
}
void add_child(Prim &&prim) {
_children.emplace_back(std::move(prim));
_child_dirty = true;
}
///
/// Add Prim as a child.
/// When `rename_element_name` is true, rename input Prims elementName to make it unique among children(since USD(Crate) spec doesn't allow same Prim elementName in the same Prim hierarchy.
///
/// Renaming rule is Maya-like:
/// - No elementName given: `default`
/// - Add or increment number suffix to the elementName:
/// - `plane` => `plane1`
/// - `plane1` => `plane2`
///
/// Note: This function is thread-safe.
///
/// @return true Upon success. false when failed(e.g. Prim with same Prim::element_name() already exists when `rename_element_name` is false) and fill `err` with error message
///
bool add_child(Prim &&prim, const bool rename_element_name = true, std::string *err = nullptr);
// TODO: Deprecate this API to disallow modification of children?.
#if 0
///
/// Add Prim as a child.
///
///
/// @return true Upon success. false when failed(e.g. Prim with same Prim::element_name() already exists) and fill `err` with error message
///
/// Note: This function is thread-safe.
///
bool add_child(Prim &&prim, const std::string &basename, std::string *err = nullptr);
#endif
//{
//
// _children.emplace_back(std::move(prim));
// _child_dirty = true;
//}
// TODO: Deprecate this API to disallow direct modification of children.
std::vector<Prim> &children() { return _children; }
const std::vector<Prim> &children() const { return _children; }
const value::Value &data() const { return _data; }
value::Value &get_data() { return _data; }
Specifier &specifier() { return _specifier; }
@@ -2788,7 +2818,10 @@ class Prim {
Specifier::Invalid}; // `def`, `over` or `class`. Usually `def`
value::Value
_data; // Generic container for concrete Prim object. GPrim, Xform, ...
std::vector<Prim> _children; // child Prim nodes
//std::set<std::string> _childrenNames; // child Prim name(elementName).
std::multiset<std::string> _childrenNameSet; // Stores input child Prim's elementName to assign unique elementName in `add_child`
mutable bool _child_dirty{false};
mutable bool _primChildrenIndicesIsValid{

View File

@@ -181,4 +181,16 @@ struct TokenKeyEqual {
#endif // TINYUSDZ_USE_STRING_ID_FOR_TOKEN_TYPE
inline bool operator==(const Token &lhs, const Token &rhs) {
return TokenKeyEqual()(lhs, rhs);
}
inline bool operator!=(const Token &lhs, const Token &rhs) {
return !TokenKeyEqual()(lhs, rhs);
}
inline bool operator<(const Token &lhs, const Token &rhs) {
return lhs.str() < rhs.str();
}
} // namespace tinyusdz

View File

@@ -21,6 +21,7 @@
TEST_LIST = {
{ "prim_type_test", prim_type_test },
{ "prim_add_test", prim_add_test },
{ "primvar_test", primvar_test },
{ "value_types_test", value_types_test },
{ "xformOp_test", xformOp_test },

View File

@@ -115,3 +115,23 @@ void prim_type_test(void) {
}
}
void prim_add_test(void) {
Model amodel;
Model bmodel;
Model cmodel;
Model rootmodel;
Prim aprim("test01", amodel);
Prim bprim("test02", bmodel);
Prim cprim("test01", cmodel);
Prim root("root", rootmodel);
TEST_CHECK(root.add_child(std::move(aprim)));
TEST_CHECK(root.add_child(std::move(bprim)));
// cannot add child Prim with same elementName
TEST_CHECK(!root.add_child(std::move(bprim)));
}

View File

@@ -1,3 +1,4 @@
#pragma once
void prim_type_test(void);
void prim_add_test(void);

View File

@@ -12,6 +12,13 @@ using namespace tinyusdz;
void value_types_test(void) {
value::token tok1("bora");
value::token tok2("muda");
value::token tok3("bora");
TEST_CHECK(tok1 == tok1);
TEST_CHECK(tok1 != tok2);
TEST_CHECK(tok1 == tok3);
TEST_CHECK(value::GetTypeName(value::TYPE_ID_TOKEN) == "token");
TEST_CHECK(value::GetTypeName(value::TYPE_ID_TOKEN|value::TYPE_ID_1D_ARRAY_BIT) == "token[]");