diff --git a/primspec-search-demo.md b/primspec-search-demo.md new file mode 100644 index 00000000..6b3deccb --- /dev/null +++ b/primspec-search-demo.md @@ -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 \ No newline at end of file diff --git a/src/tydra/js-script.cc b/src/tydra/js-script.cc index 12101d6d..ba69d42e 100644 --- a/src/tydra/js-script.cc +++ b/src/tydra/js-script.cc @@ -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(), ""); + 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(), ""); + 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(), + "", 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 diff --git a/src/tydra/js-script.hh b/src/tydra/js-script.hh index 36f59bc5..232efc35 100644 --- a/src/tydra/js-script.hh +++ b/src/tydra/js-script.hh @@ -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 diff --git a/src/usd-to-json.cc b/src/usd-to-json.cc index f95a8e6f..0474ea43 100644 --- a/src/usd-to-json.cc +++ b/src/usd-to-json.cc @@ -86,24 +86,19 @@ std::string SerializeArrayToBase64(const std::vector& array) { return base64_encode(bytes, static_cast(byte_size)); } -#if 0 // Specialized versions for different types std::string SerializeIntArrayToBase64(const std::vector& array) { return SerializeArrayToBase64(array); } -#endif std::string SerializeFloatArrayToBase64(const std::vector& array) { return SerializeArrayToBase64(array); } -#if 0 std::string SerializeDoubleArrayToBase64(const std::vector& array) { return SerializeArrayToBase64(array); } -#endif -#if 0 // Helper functions for mixed-mode serialization template json SerializeArrayData(const std::vector& array, USDToJSONContext* context, @@ -138,7 +133,6 @@ json SerializeArrayData(const std::vector& 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& array, USDToJSONContext* context = nullptr) { return SerializeArrayData(array, context, "UNSIGNED_INT", "SCALAR"); } @@ -274,9 +271,7 @@ json SerializeFloatArray(const std::vector& array, USDToJSONContext* cont json SerializeDoubleArray(const std::vector& 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 json SerializeArrayDataWithMetadata(const std::vector& array, const AttrMetas* metas, USDToJSONContext* context, @@ -293,22 +288,16 @@ json SerializeArrayDataWithMetadata(const std::vector& array, const AttrMetas return result; } -#endif -#if 0 // Metadata-aware array serialization functions json SerializeIntArrayWithMetadata(const std::vector& array, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) { return SerializeArrayDataWithMetadata(array, metas, context, "UNSIGNED_INT", "SCALAR"); } -#endif -#if 0 json SerializeFloatArrayWithMetadata(const std::vector& array, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) { return SerializeArrayDataWithMetadata(array, metas, context, "FLOAT", "SCALAR"); } -#endif -#if 0 json SerializeDoubleArrayWithMetadata(const std::vector& array, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) { return SerializeArrayDataWithMetadata(array, metas, context, "FLOAT", "SCALAR"); } @@ -696,7 +685,6 @@ json SerializeHalf4Array(const std::vector& vectors, USDToJSONCont }; } } -#endif // Metadata-aware vector serialization functions json SerializePoint3fArrayWithMetadata(const std::vector& points, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) { @@ -748,7 +736,6 @@ json SerializePoint3fArrayWithMetadata(const std::vector& points return result; } -#if 0 json SerializeNormal3fArrayWithMetadata(const std::vector& normals, const AttrMetas* metas = nullptr, USDToJSONContext* context = nullptr) { if (normals.empty()) { return json::object(); @@ -797,9 +784,7 @@ json SerializeNormal3fArrayWithMetadata(const std::vector& norm return result; } -#endif -#if 0 // Matrix array serialization template std::string SerializeMatrixArrayToBase64(const std::vector& array) { @@ -908,7 +893,11 @@ std::string SerializeMatrix4dArrayToBase64(const std::vector& 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& 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 diff --git a/src/usd-to-json.hh b/src/usd-to-json.hh index 961dac64..80a4034c 100644 --- a/src/usd-to-json.hh +++ b/src/usd-to-json.hh @@ -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& properties, USDToJSONContext* context = nullptr); + } // namespace tinyusdz