imporove USD <-> JSON conversion

This commit is contained in:
Syoyo Fujita
2025-08-25 22:50:55 +09:00
parent 14ec29a86a
commit c0376f6bbb
5 changed files with 932 additions and 22 deletions

266
primspec-search-demo.md Normal file
View File

@@ -0,0 +1,266 @@
# PrimSpec JavaScript Functions Demo
This demonstrates the new JavaScript functions that have been added to the TinyUSDZ JavaScript scripting interface for working with PrimSpecs:
1. `findPrimSpecByPath()` - Search for PrimSpec by absolute path
2. `getPrimSpecMetadata()` - Get PrimSpec metadata by absolute path
## Function Overview
### 1. findPrimSpecByPath(pathString)
Searches for and retrieves basic PrimSpec information by absolute path string.
**Function Signature:**
```javascript
findPrimSpecByPath(pathString) -> PrimSpec object | null
```
- **Parameters**:
- `pathString` (string): Absolute USD path string (e.g., "/root/child/prim")
- **Returns**:
- PrimSpec object if found
- `null` if not found or invalid path
### 2. getPrimSpecMetadata(pathString)
Retrieves detailed metadata information for a PrimSpec by absolute path string.
**Function Signature:**
```javascript
getPrimSpecMetadata(pathString) -> Metadata object | null
```
- **Parameters**:
- `pathString` (string): Absolute USD path string (e.g., "/root/child/prim")
- **Returns**:
- Metadata object if found
- `null` if not found or invalid path
### PrimSpec Object Structure
When found, the function returns a JSON object with the following structure:
```javascript
{
"name": "primName", // Name of the PrimSpec
"typeName": "Mesh", // Type name (e.g., "Mesh", "Xform", etc.)
"specifier": "def", // Specifier: "def", "over", "class", or "invalid"
"propertyCount": 5, // Number of properties
"childrenCount": 2, // Number of child PrimSpecs
"propertyNames": [ // Array of property names
"points",
"faceVertexIndices",
"extent"
],
"childrenNames": [ // Array of child PrimSpec names
"material",
"childPrim"
]
}
```
### Metadata Object Structure
When found, the `getPrimSpecMetadata()` function returns a JSON object with the following structure:
```javascript
{
"active": true, // Active state (true/false/null)
"hidden": false, // Hidden state (true/false/null)
"instanceable": null, // Instanceable state (true/false/null)
"kind": "component", // USD Kind (string)
"documentation": "This is a mesh", // Documentation (string/null)
"comment": "Auto-generated", // Comment (string/null)
"displayName": "My Mesh", // Display name (string/null)
"sceneName": "Scene1", // Scene name (string/null)
"hasReferences": true, // Whether prim has references
"referencesCount": 2, // Number of references
"hasPayload": false, // Whether prim has payload
"payloadCount": 0, // Number of payloads
"hasInherits": false, // Whether prim has inherits
"inheritsCount": 0, // Number of inherits
"hasVariants": true, // Whether prim has variants
"variantsCount": 2, // Number of variant selections
"variantNames": ["modelingVariant", "shadingVariant"], // Variant names
"hasVariantSets": true, // Whether prim has variant sets
"variantSetsCount": 1, // Number of variant sets
"variantSetNames": ["material"], // Variant set names
"hasCustomData": true, // Whether prim has custom data
"hasAssetInfo": false, // Whether prim has asset info
"unregisteredMetasCount": 3, // Number of unregistered metadata
"unregisteredMetaNames": ["myCustomMeta1", "myCustomMeta2"], // Custom metadata names
"authored": true // Whether metadata is authored
}
```
## Usage Example
```javascript
// Search for a specific PrimSpec
var rootPrim = findPrimSpecByPath("/Root");
if (rootPrim) {
console.log("Found root prim:", rootPrim.name);
console.log("Type:", rootPrim.typeName);
console.log("Properties:", rootPrim.propertyNames);
console.log("Children:", rootPrim.childrenNames);
} else {
console.log("Root prim not found");
}
// Get metadata for the same PrimSpec
var rootMeta = getPrimSpecMetadata("/Root");
if (rootMeta) {
console.log("Root prim is active:", rootMeta.active);
console.log("Kind:", rootMeta.kind);
console.log("Has references:", rootMeta.hasReferences);
console.log("Variant sets:", rootMeta.variantSetNames);
if (rootMeta.documentation) {
console.log("Documentation:", rootMeta.documentation);
}
if (rootMeta.unregisteredMetasCount > 0) {
console.log("Custom metadata:", rootMeta.unregisteredMetaNames);
}
} else {
console.log("Root prim metadata not found");
}
// Search for a nested PrimSpec and its metadata
var meshPrim = findPrimSpecByPath("/Root/Geometry/Mesh");
var meshMeta = getPrimSpecMetadata("/Root/Geometry/Mesh");
if (meshPrim && meshMeta) {
console.log("Found mesh with", meshPrim.propertyCount, "properties");
console.log("Mesh is instanceable:", meshMeta.instanceable);
console.log("Mesh variants:", meshMeta.variantNames);
} else {
console.log("Mesh or its metadata not found");
}
// Example: Check if a prim has composition arcs
var composedPrimMeta = getPrimSpecMetadata("/ComposedPrim");
if (composedPrimMeta) {
var hasComposition = composedPrimMeta.hasReferences ||
composedPrimMeta.hasPayload ||
composedPrimMeta.hasInherits;
if (hasComposition) {
console.log("Prim has composition arcs:");
if (composedPrimMeta.hasReferences) {
console.log("- References:", composedPrimMeta.referencesCount);
}
if (composedPrimMeta.hasPayload) {
console.log("- Payloads:", composedPrimMeta.payloadCount);
}
if (composedPrimMeta.hasInherits) {
console.log("- Inherits:", composedPrimMeta.inheritsCount);
}
}
}
```
## C++ Integration
To use these functions from C++, you need to:
1. Create or load a USD Layer
2. Use `RunJSScriptWithLayer()` to execute JavaScript code with access to the Layer
```cpp
#include "src/tydra/js-script.hh"
// Assuming you have a Layer object
tinyusdz::Layer layer;
// ... populate layer with PrimSpecs ...
std::string jsCode = R"(
// Find PrimSpec basic info
var prim = findPrimSpecByPath("/myPrim");
if (prim) {
console.log("Found:", prim.name, "type:", prim.typeName);
console.log("Properties:", prim.propertyCount);
console.log("Children:", prim.childrenCount);
}
// Get detailed metadata
var meta = getPrimSpecMetadata("/myPrim");
if (meta) {
console.log("Active:", meta.active);
console.log("Kind:", meta.kind);
console.log("Has composition:",
meta.hasReferences || meta.hasPayload || meta.hasInherits);
if (meta.hasVariants) {
console.log("Variants:", meta.variantNames);
}
}
)";
std::string err;
bool success = tinyusdz::tydra::RunJSScriptWithLayer(jsCode, &layer, err);
if (!success) {
std::cerr << "JavaScript error: " << err << std::endl;
}
```
## Implementation Details
Both functions are implemented in `src/tydra/js-script.cc` and include:
### Common Features:
- **Path validation**: Ensures the input string is a valid USD path
- **Layer search**: Uses the existing `Layer::find_primspec_at()` method
- **Error handling**: Returns `null` for invalid paths or missing PrimSpecs
### findPrimSpecByPath():
- **JSON conversion**: Converts basic PrimSpec data to JSON
- **Structure info**: Provides name, type, properties, and children info
### getPrimSpecMetadata():
- **Comprehensive metadata**: Extracts all USD metadata fields
- **Composition info**: Details about references, payloads, inherits
- **Variant info**: Information about variants and variant sets
- **Custom metadata**: Handles unregistered metadata fields
- **Boolean flags**: Convenient flags for common checks
## Requirements
- TinyUSDZ must be built with `TINYUSDZ_WITH_QJS=ON` to enable QuickJS support
- The functions are only available when using `RunJSScriptWithLayer()`
## Error Cases
Both functions return `null` in these cases:
- Invalid path string (empty, malformed)
- Path not found in the Layer
- Relative paths (not yet supported)
- No Layer context available
## Metadata Fields Reference
The `getPrimSpecMetadata()` function provides access to the following USD metadata:
### Core Metadata:
- `active` - Whether the prim is active in the scene
- `hidden` - Whether the prim is hidden from traversal
- `instanceable` - Whether the prim can be instanced
- `kind` - USD Kind classification (model, component, assembly, etc.)
### Documentation:
- `documentation` - Formal documentation string
- `comment` - Informal comment string
- `displayName` - Human-readable display name (extension)
- `sceneName` - Scene name (USDZ extension)
### Composition Arcs:
- `hasReferences` / `referencesCount` - Reference composition
- `hasPayload` / `payloadCount` - Payload composition
- `hasInherits` / `inheritsCount` - Inheritance composition
### Variants:
- `hasVariants` / `variantsCount` / `variantNames` - Variant selections
- `hasVariantSets` / `variantSetsCount` / `variantSetNames` - Variant set definitions
### Custom Data:
- `hasCustomData` - Whether prim has custom data dictionary
- `hasAssetInfo` - Whether prim has asset info dictionary
- `unregisteredMetasCount` / `unregisteredMetaNames` - Custom metadata fields
- `authored` - Whether any metadata is authored

View File

@@ -151,6 +151,7 @@ static std::string LayerMetasToJSON(const LayerMetas* metas) {
static const LayerMetas* g_current_layer_metas = nullptr;
static const Attribute* g_current_attribute = nullptr;
static const class Layer* g_current_layer = nullptr;
// JavaScript fp16 library and TUSDZFloat16Array implementation
static const char* fp16_library_js = R"(
@@ -845,6 +846,223 @@ static std::string AttributeToJSON(const Attribute* attr) {
return oss.str();
}
static std::string PrimMetasToJSON(const PrimMeta* metas) {
if (!metas) {
return "null";
}
std::ostringstream oss;
oss << std::setprecision(17);
oss << "{";
// Basic metadata flags
oss << "\"active\":";
if (metas->active.has_value()) {
oss << (metas->active.value() ? "true" : "false");
} else {
oss << "null";
}
oss << ",";
oss << "\"hidden\":";
if (metas->hidden.has_value()) {
oss << (metas->hidden.value() ? "true" : "false");
} else {
oss << "null";
}
oss << ",";
oss << "\"instanceable\":";
if (metas->instanceable.has_value()) {
oss << (metas->instanceable.value() ? "true" : "false");
} else {
oss << "null";
}
oss << ",";
// Kind
oss << "\"kind\":\"" << metas->get_kind() << "\",";
// Documentation and comment
oss << "\"documentation\":";
if (metas->doc.has_value()) {
oss << "\"" << metas->doc.value().value << "\"";
} else {
oss << "null";
}
oss << ",";
oss << "\"comment\":";
if (metas->comment.has_value()) {
oss << "\"" << metas->comment.value().value << "\"";
} else {
oss << "null";
}
oss << ",";
// Display name and scene name (extensions)
oss << "\"displayName\":";
if (metas->displayName.has_value()) {
oss << "\"" << metas->displayName.value() << "\"";
} else {
oss << "null";
}
oss << ",";
oss << "\"sceneName\":";
if (metas->sceneName.has_value()) {
oss << "\"" << metas->sceneName.value() << "\"";
} else {
oss << "null";
}
oss << ",";
// References count
oss << "\"hasReferences\":";
if (metas->references.has_value() && !metas->references.value().second.empty()) {
oss << "true,";
oss << "\"referencesCount\":" << metas->references.value().second.size();
} else {
oss << "false,";
oss << "\"referencesCount\":0";
}
oss << ",";
// Payload count
oss << "\"hasPayload\":";
if (metas->payload.has_value() && !metas->payload.value().second.empty()) {
oss << "true,";
oss << "\"payloadCount\":" << metas->payload.value().second.size();
} else {
oss << "false,";
oss << "\"payloadCount\":0";
}
oss << ",";
// Inherits count
oss << "\"hasInherits\":";
if (metas->inherits.has_value() && !metas->inherits.value().second.empty()) {
oss << "true,";
oss << "\"inheritsCount\":" << metas->inherits.value().second.size();
} else {
oss << "false,";
oss << "\"inheritsCount\":0";
}
oss << ",";
// Variants info
oss << "\"hasVariants\":";
if (metas->variants.has_value() && !metas->variants.value().empty()) {
oss << "true,";
oss << "\"variantsCount\":" << metas->variants.value().size() << ",";
oss << "\"variantNames\":[";
bool first = true;
for (const auto& variant : metas->variants.value()) {
if (!first) oss << ",";
oss << "\"" << variant.first << "\"";
first = false;
}
oss << "]";
} else {
oss << "false,";
oss << "\"variantsCount\":0,";
oss << "\"variantNames\":[]";
}
oss << ",";
// VariantSets info
oss << "\"hasVariantSets\":";
if (metas->variantSets.has_value() && !metas->variantSets.value().second.empty()) {
oss << "true,";
oss << "\"variantSetsCount\":" << metas->variantSets.value().second.size() << ",";
oss << "\"variantSetNames\":[";
bool first = true;
for (const auto& varSet : metas->variantSets.value().second) {
if (!first) oss << ",";
oss << "\"" << varSet << "\"";
first = false;
}
oss << "]";
} else {
oss << "false,";
oss << "\"variantSetsCount\":0,";
oss << "\"variantSetNames\":[]";
}
oss << ",";
// Custom data and unregistered metas
oss << "\"hasCustomData\":" << (metas->customData.has_value() ? "true" : "false") << ",";
oss << "\"hasAssetInfo\":" << (metas->assetInfo.has_value() ? "true" : "false") << ",";
oss << "\"unregisteredMetasCount\":" << metas->unregisteredMetas.size() << ",";
// Unregistered metadata names
oss << "\"unregisteredMetaNames\":[";
bool first = true;
for (const auto& meta : metas->unregisteredMetas) {
if (!first) oss << ",";
oss << "\"" << meta.first << "\"";
first = false;
}
oss << "],";
// Authored flag
oss << "\"authored\":" << (metas->authored() ? "true" : "false");
oss << "}";
return oss.str();
}
static std::string PrimSpecToJSON(const PrimSpec* ps) {
if (!ps) {
return "null";
}
std::ostringstream oss;
oss << std::setprecision(17);
oss << "{";
// Basic PrimSpec info
oss << "\"name\":\"" << ps->name() << "\",";
oss << "\"typeName\":\"" << ps->typeName() << "\",";
oss << "\"specifier\":\"";
switch (ps->specifier()) {
case Specifier::Def: oss << "def"; break;
case Specifier::Over: oss << "over"; break;
case Specifier::Class: oss << "class"; break;
case Specifier::Invalid: oss << "invalid"; break;
}
oss << "\",";
// Add property count
oss << "\"propertyCount\":" << ps->props().size() << ",";
// Add children count
oss << "\"childrenCount\":" << ps->children().size() << ",";
// Property names array
oss << "\"propertyNames\":[";
bool first = true;
for (const auto& prop : ps->props()) {
if (!first) oss << ",";
oss << "\"" << prop.first << "\"";
first = false;
}
oss << "],";
// Children names array
oss << "\"childrenNames\":[";
first = true;
for (const auto& child : ps->children()) {
if (!first) oss << ",";
oss << "\"" << child.name() << "\"";
first = false;
}
oss << "]";
oss << "}";
return oss.str();
}
static JSValue js_getLayerMetas(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValueConst *func_data) {
std::string json = LayerMetasToJSON(g_current_layer_metas);
@@ -865,6 +1083,88 @@ static JSValue js_getAttribute(JSContext *ctx, JSValueConst this_val, int argc,
return result;
}
static JSValue js_findPrimSpecByPath(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValueConst *func_data) {
if (!g_current_layer) {
return JS_NULL;
}
if (argc < 1) {
return JS_ThrowTypeError(ctx, "findPrimSpecByPath requires 1 argument (path string)");
}
const char *path_str = JS_ToCString(ctx, argv[0]);
if (!path_str) {
return JS_EXCEPTION;
}
// Parse the path string into a Path object
tinyusdz::Path path(path_str);
if (!path.is_valid()) {
JS_FreeCString(ctx, path_str);
return JS_NULL;
}
// Find the PrimSpec
const PrimSpec *ps = nullptr;
std::string err;
bool found = g_current_layer->find_primspec_at(path, &ps, &err);
JS_FreeCString(ctx, path_str);
if (!found || !ps) {
return JS_NULL;
}
// Convert PrimSpec to JSON and parse it
std::string json = PrimSpecToJSON(ps);
JSValue result = JS_ParseJSON(ctx, json.c_str(), json.length(), "<primspec>");
if (JS_IsException(result)) {
return JS_EXCEPTION;
}
return result;
}
static JSValue js_getPrimSpecMetadata(JSContext *ctx, JSValueConst this_val, int argc, JSValueConst *argv, int magic, JSValueConst *func_data) {
if (!g_current_layer) {
return JS_NULL;
}
if (argc < 1) {
return JS_ThrowTypeError(ctx, "getPrimSpecMetadata requires 1 argument (path string)");
}
const char *path_str = JS_ToCString(ctx, argv[0]);
if (!path_str) {
return JS_EXCEPTION;
}
// Parse the path string into a Path object
tinyusdz::Path path(path_str);
if (!path.is_valid()) {
JS_FreeCString(ctx, path_str);
return JS_NULL;
}
// Find the PrimSpec
const PrimSpec *ps = nullptr;
std::string err;
bool found = g_current_layer->find_primspec_at(path, &ps, &err);
JS_FreeCString(ctx, path_str);
if (!found || !ps) {
return JS_NULL;
}
// Convert PrimSpec metadata to JSON and parse it
std::string json = PrimMetasToJSON(&ps->metas());
JSValue result = JS_ParseJSON(ctx, json.c_str(), json.length(), "<primspec_metadata>");
if (JS_IsException(result)) {
return JS_EXCEPTION;
}
return result;
}
bool RunJSScript(const std::string &js_code, std::string &err) {
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
@@ -1007,6 +1307,57 @@ bool RunJSScriptWithAttribute(const std::string &js_code, const Attribute* attri
return success;
}
bool RunJSScriptWithLayer(const std::string &js_code, const class Layer* layer, std::string &err) {
JSRuntime *rt = JS_NewRuntime();
if (!rt) {
err = "Failed to create JavaScript runtime";
return false;
}
JSContext *ctx = JS_NewContext(rt);
if (!ctx) {
err = "Failed to create JavaScript context";
JS_FreeRuntime(rt);
return false;
}
// Set the global Layer pointer and add functions to the global context
g_current_layer = layer;
JSValue global_obj = JS_GetGlobalObject(ctx);
JSValue findFunc = JS_NewCFunctionData(ctx, js_findPrimSpecByPath, 1, 0, 0, nullptr);
JS_SetPropertyStr(ctx, global_obj, "findPrimSpecByPath", findFunc);
JSValue metaFunc = JS_NewCFunctionData(ctx, js_getPrimSpecMetadata, 1, 0, 0, nullptr);
JS_SetPropertyStr(ctx, global_obj, "getPrimSpecMetadata", metaFunc);
JS_FreeValue(ctx, global_obj);
JSValue result = JS_Eval(ctx, js_code.c_str(), js_code.length(),
"<eval>", JS_EVAL_TYPE_GLOBAL);
bool success = true;
if (JS_IsException(result)) {
success = false;
JSValue exception = JS_GetException(ctx);
const char *error_str = JS_ToCString(ctx, exception);
if (error_str) {
err = std::string("JavaScript error: ") + error_str;
JS_FreeCString(ctx, error_str);
} else {
err = "JavaScript error: unable to get error message";
}
JS_FreeValue(ctx, exception);
}
JS_FreeValue(ctx, result);
JS_FreeContext(ctx);
JS_FreeRuntime(rt);
return success;
}
#if defined(__clang__)
#pragma clang diagnostic pop
#endif
@@ -1036,6 +1387,13 @@ bool RunJSScriptWithAttribute(const std::string &js_code, const Attribute* attri
(void)attribute;
return false;
}
bool RunJSScriptWithLayer(const std::string &js_code, const class Layer* layer, std::string &err) {
err = "JavaScript is not supported in this build.\n";
(void)js_code;
(void)layer;
return false;
}
#endif
} // namespace tydra

View File

@@ -13,6 +13,8 @@ bool RunJSScriptWithLayerMetas(const std::string &js_code, const LayerMetas* lay
bool RunJSScriptWithAttribute(const std::string &js_code, const Attribute* attribute, std::string &err);
bool RunJSScriptWithLayer(const std::string &js_code, const class Layer* layer, std::string &err);
} // namespace tydra
} // namespace tinyusdz

View File

@@ -86,24 +86,19 @@ std::string SerializeArrayToBase64(const std::vector<T>& array) {
return base64_encode(bytes, static_cast<unsigned int>(byte_size));
}
#if 0
// Specialized versions for different types
std::string SerializeIntArrayToBase64(const std::vector<int>& array) {
return SerializeArrayToBase64(array);
}
#endif
std::string SerializeFloatArrayToBase64(const std::vector<float>& array) {
return SerializeArrayToBase64(array);
}
#if 0
std::string SerializeDoubleArrayToBase64(const std::vector<double>& array) {
return SerializeArrayToBase64(array);
}
#endif
#if 0
// Helper functions for mixed-mode serialization
template<typename T>
json SerializeArrayData(const std::vector<T>& array, USDToJSONContext* context,
@@ -138,7 +133,6 @@ json SerializeArrayData(const std::vector<T>& array, USDToJSONContext* context,
};
}
}
#endif
// Helper function to serialize attribute metadata
json SerializeAttributeMetadata(const AttrMetas& metas) {
@@ -261,8 +255,11 @@ json SerializeAttributeMetadata(const AttrMetas& metas) {
return metadata;
}
#if 0
// Specialized array serialization functions
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
#pragma clang diagnostic ignored "-Wunused-template"
json SerializeIntArray(const std::vector<int>& array, USDToJSONContext* context = nullptr) {
return SerializeArrayData(array, context, "UNSIGNED_INT", "SCALAR");
}
@@ -274,9 +271,7 @@ json SerializeFloatArray(const std::vector<float>& array, USDToJSONContext* cont
json SerializeDoubleArray(const std::vector<double>& array, USDToJSONContext* context = nullptr) {
return SerializeArrayData(array, context, "FLOAT", "SCALAR"); // Note: JSON doesn't distinguish float/double
}
#endif
#if 0
// Overloaded functions with attribute metadata support
template<typename T>
json SerializeArrayDataWithMetadata(const std::vector<T>& array, const AttrMetas* metas, USDToJSONContext* context,
@@ -293,22 +288,16 @@ json SerializeArrayDataWithMetadata(const std::vector<T>& array, const AttrMetas
return result;
}
#endif
#if 0
// Metadata-aware array serialization functions
json SerializeIntArrayWithMetadata(const std::vector<int>& array, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) {
return SerializeArrayDataWithMetadata(array, metas, context, "UNSIGNED_INT", "SCALAR");
}
#endif
#if 0
json SerializeFloatArrayWithMetadata(const std::vector<float>& array, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) {
return SerializeArrayDataWithMetadata(array, metas, context, "FLOAT", "SCALAR");
}
#endif
#if 0
json SerializeDoubleArrayWithMetadata(const std::vector<double>& array, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) {
return SerializeArrayDataWithMetadata(array, metas, context, "FLOAT", "SCALAR");
}
@@ -696,7 +685,6 @@ json SerializeHalf4Array(const std::vector<value::half4>& vectors, USDToJSONCont
};
}
}
#endif
// Metadata-aware vector serialization functions
json SerializePoint3fArrayWithMetadata(const std::vector<value::point3f>& points, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) {
@@ -748,7 +736,6 @@ json SerializePoint3fArrayWithMetadata(const std::vector<value::point3f>& points
return result;
}
#if 0
json SerializeNormal3fArrayWithMetadata(const std::vector<value::normal3f>& normals, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) {
if (normals.empty()) {
return json::object();
@@ -797,9 +784,7 @@ json SerializeNormal3fArrayWithMetadata(const std::vector<value::normal3f>& norm
return result;
}
#endif
#if 0
// Matrix array serialization
template<typename MatrixType>
std::string SerializeMatrixArrayToBase64(const std::vector<MatrixType>& array) {
@@ -908,7 +893,11 @@ std::string SerializeMatrix4dArrayToBase64(const std::vector<value::matrix4d>& a
}
return SerializeDoubleArrayToBase64(double_data);
}
#endif
#pragma clang diagnostic pop
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
json ToJSON(tinyusdz::Xform& xform) {
json j;
@@ -934,7 +923,6 @@ json ToJSON(tinyusdz::GeomMesh& mesh) {
return ToJSON(mesh, nullptr); // Use base64 mode by default
}
#if 0
json ToJSON(tinyusdz::GeomMesh& mesh, USDToJSONContext* context) {
json j;
@@ -1040,7 +1028,6 @@ json ToJSON(tinyusdz::GeomMesh& mesh, USDToJSONContext* context) {
return j;
}
#endif
json ToJSON(tinyusdz::GeomBasisCurves& curves) {
@@ -1385,6 +1372,8 @@ bool to_json_string(const tinyusdz::Layer &layer, std::string *json_str, std::st
}
#pragma clang diagnostic pop
bool to_json_string(const tinyusdz::Layer &layer, const USDToJSONOptions& options, std::string *json_str, std::string *warn, std::string *err) {
// TODO: options
@@ -1394,5 +1383,280 @@ bool to_json_string(const tinyusdz::Layer &layer, const USDToJSONOptions& option
}
// ================================================================
// Property, Attribute, and Relationship to JSON conversion
// ================================================================
json ToJSON(const tinyusdz::Attribute& attribute, USDToJSONContext* /* context */) {
json j;
// Basic attribute information
j["name"] = attribute.name();
j["typeName"] = attribute.type_name();
// Variability
switch (attribute.variability()) {
case Variability::Varying:
j["variability"] = "varying";
break;
case Variability::Uniform:
j["variability"] = "uniform";
break;
case Variability::Config:
j["variability"] = "config";
break;
case Variability::Invalid:
j["variability"] = "invalid";
break;
}
// Interpolation (from metadata)
if (attribute.metas().interpolation) {
switch (attribute.metas().interpolation.value()) {
case Interpolation::Invalid:
j["interpolation"] = "invalid";
break;
case Interpolation::Constant:
j["interpolation"] = "constant";
break;
case Interpolation::Uniform:
j["interpolation"] = "uniform";
break;
case Interpolation::Varying:
j["interpolation"] = "varying";
break;
case Interpolation::Vertex:
j["interpolation"] = "vertex";
break;
case Interpolation::FaceVarying:
j["interpolation"] = "faceVarying";
break;
}
}
// Attribute metadata
if (attribute.metas().authored()) {
j["metadata"] = SerializeAttributeMetadata(attribute.metas());
}
// Connection information
if (attribute.is_connection()) {
j["isConnection"] = true;
auto connections = attribute.connections();
if (connections.size() == 1) {
j["connection"] = connections[0].full_path_name();
} else if (connections.size() > 1) {
json connections_array = json::array();
for (const auto& conn : connections) {
connections_array.push_back(conn.full_path_name());
}
j["connections"] = connections_array;
}
} else {
j["isConnection"] = false;
}
// Value information
if (attribute.is_blocked()) {
j["hasValue"] = false;
j["valueType"] = "blocked";
j["value"] = nullptr;
} else {
// Check if attribute has value by accessing the internal value container
const auto& var = attribute.get_var();
if (var.is_valid()) {
j["hasValue"] = true;
j["valueType"] = "data";
// For now, serialize as a string representation
// TODO: Implement proper value type serialization based on type_id
j["value"] = "[Attribute value - serialization not yet implemented]";
// Store type information for debugging
j["valueTypeName"] = attribute.type_name();
} else {
j["hasValue"] = false;
j["valueType"] = "empty";
j["value"] = nullptr;
}
}
// Time samples information
if (attribute.is_timesamples()) {
j["hasTimeSamples"] = true;
// TODO: Serialize time sample data
j["timeSamples"] = "[TimeSamples data - not yet serialized]";
} else {
j["hasTimeSamples"] = false;
}
return j;
}
json ToJSON(const tinyusdz::Relationship& relationship) {
json j;
j["type"] = "relationship";
// List edit qualifier
switch (relationship.get_listedit_qual()) {
case ListEditQual::ResetToExplicit:
j["listEditQual"] = "resetToExplicit";
break;
case ListEditQual::Append:
j["listEditQual"] = "append";
break;
case ListEditQual::Add:
j["listEditQual"] = "add";
break;
case ListEditQual::Delete:
j["listEditQual"] = "delete";
break;
case ListEditQual::Prepend:
j["listEditQual"] = "prepend";
break;
case ListEditQual::Order:
j["listEditQual"] = "order";
break;
case ListEditQual::Invalid:
j["listEditQual"] = "invalid";
break;
}
// Relationship value type and targets
switch (relationship.type) {
case Relationship::Type::DefineOnly:
j["valueType"] = "defineOnly";
j["hasTargets"] = false;
break;
case Relationship::Type::Path:
j["valueType"] = "path";
j["hasTargets"] = true;
j["target"] = relationship.targetPath.full_path_name();
break;
case Relationship::Type::PathVector:
{
j["valueType"] = "pathVector";
j["hasTargets"] = true;
json targets_array = json::array();
for (const auto& path : relationship.targetPathVector) {
targets_array.push_back(path.full_path_name());
}
j["targets"] = targets_array;
j["targetCount"] = relationship.targetPathVector.size();
break;
}
case Relationship::Type::ValueBlock:
j["valueType"] = "valueBlock";
j["hasTargets"] = false;
j["blocked"] = true;
break;
}
return j;
}
json ToJSON(const tinyusdz::Property& property, USDToJSONContext* context) {
json j;
// Property type
switch (property.get_property_type()) {
case Property::Type::EmptyAttrib:
j["propertyType"] = "emptyAttribute";
j["typeName"] = property.value_type_name();
break;
case Property::Type::Attrib:
j["propertyType"] = "attribute";
j["attribute"] = ToJSON(property.get_attribute(), context);
break;
case Property::Type::Relation:
j["propertyType"] = "relationship";
j["relationship"] = ToJSON(property.get_relationship());
break;
case Property::Type::NoTargetsRelation:
j["propertyType"] = "noTargetsRelationship";
j["relationship"] = ToJSON(property.get_relationship());
break;
case Property::Type::Connection:
j["propertyType"] = "connection";
j["attribute"] = ToJSON(property.get_attribute(), context);
j["valueTypeName"] = property.value_type_name();
break;
}
// Custom flag
j["isCustom"] = property.has_custom();
// List edit qualifier (mainly for relationships)
switch (property.get_listedit_qual()) {
case ListEditQual::ResetToExplicit:
j["listEditQual"] = "resetToExplicit";
break;
case ListEditQual::Append:
j["listEditQual"] = "append";
break;
case ListEditQual::Add:
j["listEditQual"] = "add";
break;
case ListEditQual::Delete:
j["listEditQual"] = "delete";
break;
case ListEditQual::Prepend:
j["listEditQual"] = "prepend";
break;
case ListEditQual::Order:
j["listEditQual"] = "order";
break;
case ListEditQual::Invalid:
j["listEditQual"] = "invalid";
break;
}
// Convenience methods for relationships
if (property.is_relationship()) {
auto target = property.get_relationTarget();
if (target) {
j["relationTarget"] = target->full_path_name();
}
auto targets = property.get_relationTargets();
if (!targets.empty()) {
json targets_array = json::array();
for (const auto& t : targets) {
targets_array.push_back(t.full_path_name());
}
j["relationTargets"] = targets_array;
}
}
// Helper flags
j["isAttribute"] = property.is_attribute();
j["isRelationship"] = property.is_relationship();
j["isEmpty"] = property.is_empty();
j["isAttributeConnection"] = property.is_attribute_connection();
return j;
}
json PropertiesToJSON(const std::map<std::string, tinyusdz::Property>& properties, USDToJSONContext* context) {
json j = json::object();
for (const auto& prop_pair : properties) {
const std::string& prop_name = prop_pair.first;
const Property& property = prop_pair.second;
j[prop_name] = ToJSON(property, context);
}
return j;
}
} // namespace tinyusdz

View File

@@ -130,4 +130,24 @@ bool to_json_string(const tinyusdz::Layer &layer, const USDToJSONOptions& option
///
nlohmann::json ToJSON(tinyusdz::GeomMesh& mesh, USDToJSONContext* context);
///
/// Convert Attribute to JSON
///
nlohmann::json ToJSON(const tinyusdz::Attribute& attribute, USDToJSONContext* context = nullptr);
///
/// Convert Relationship to JSON
///
nlohmann::json ToJSON(const tinyusdz::Relationship& relationship);
///
/// Convert Property to JSON
///
nlohmann::json ToJSON(const tinyusdz::Property& property, USDToJSONContext* context = nullptr);
///
/// Convert Properties map to JSON
///
nlohmann::json PropertiesToJSON(const std::map<std::string, tinyusdz::Property>& properties, USDToJSONContext* context = nullptr);
} // namespace tinyusdz