add --json option to tusdcat.

add callback interface in USDA parser.
This commit is contained in:
Syoyo Fujita
2025-08-26 22:28:46 +09:00
parent ee730671cb
commit 3e50c7a84c
6 changed files with 615 additions and 140 deletions

View File

@@ -8,6 +8,7 @@
#include "pprinter.hh"
#include "str-util.hh"
#include "io-util.hh"
#include "usd-to-json.hh"
#include "tydra/scene-access.hh"
@@ -34,7 +35,7 @@ static std::string str_tolower(std::string s) {
}
void print_help() {
std::cout << "Usage tusdcat [--flatten] [--loadOnly] [--composition=STRLIST] [--relative] [--extract-variants] input.usda/usdc/usdz\n";
std::cout << "Usage tusdcat [--flatten] [--loadOnly] [--composition=STRLIST] [--relative] [--extract-variants] [-j|--json] input.usda/usdc/usdz\n";
std::cout << "\n --flatten (not fully implemented yet) Do composition(load sublayers, refences, payload, evaluate `over`, inherit, variants..)";
std::cout << " --composition: Specify which composition feature to be "
"enabled(valid when `--flatten` is supplied). Comma separated "
@@ -45,6 +46,7 @@ void print_help() {
std::cout << "\n --extract-variants (w.i.p) Dump variants information to .json\n";
std::cout << "\n --relative (not implemented yet) Print Path as relative Path\n";
std::cout << "\n -l, --loadOnly Load(Parse) USD file only(Check if input USD is valid or not)\n";
std::cout << "\n -j, --json Output parsed USD as JSON string\n";
}
@@ -58,6 +60,7 @@ int main(int argc, char **argv) {
bool has_relative{false};
bool has_extract_variants{false};
bool load_only{false};
bool json_output{false};
constexpr int kMaxIteration = 128;
@@ -77,6 +80,8 @@ int main(int argc, char **argv) {
has_relative = true;
} else if ((arg.compare("-l") == 0) || (arg.compare("--loadOnly") == 0)) {
load_only = true;
} else if ((arg.compare("-j") == 0) || (arg.compare("--json") == 0)) {
json_output = true;
} else if (arg.compare("--extract-variants") == 0) {
has_extract_variants = true;
} else if (tinyusdz::startsWith(arg, "--composition=")) {
@@ -159,7 +164,17 @@ int main(int argc, char **argv) {
return EXIT_FAILURE;
}
std::cout << to_string(stage) << "\n";
if (json_output) {
auto json_result = tinyusdz::ToJSON(stage);
if (json_result) {
std::cout << json_result.value() << "\n";
} else {
std::cerr << "Failed to convert USDZ stage to JSON: " << json_result.error() << "\n";
return EXIT_FAILURE;
}
} else {
std::cout << to_string(stage) << "\n";
}
return EXIT_SUCCESS;
}
@@ -347,7 +362,17 @@ int main(int argc, char **argv) {
std::cerr << err << "\n";
}
std::cout << comp_stage.ExportToString() << "\n";
if (json_output) {
auto json_result = tinyusdz::ToJSON(comp_stage);
if (json_result) {
std::cout << json_result.value() << "\n";
} else {
std::cerr << "Failed to convert composed stage to JSON: " << json_result.error() << "\n";
return EXIT_FAILURE;
}
} else {
std::cout << comp_stage.ExportToString() << "\n";
}
using MeshMap = std::map<std::string, const tinyusdz::GeomMesh *>;
MeshMap meshmap;
@@ -384,8 +409,18 @@ int main(int argc, char **argv) {
return EXIT_SUCCESS;
}
std::string s = stage.ExportToString(has_relative);
std::cout << s << "\n";
if (json_output) {
auto json_result = tinyusdz::ToJSON(stage);
if (json_result) {
std::cout << json_result.value() << "\n";
} else {
std::cerr << "Failed to convert stage to JSON: " << json_result.error() << "\n";
return EXIT_FAILURE;
}
} else {
std::string s = stage.ExportToString(has_relative);
std::cout << s << "\n";
}
if (has_extract_variants) {
tinyusdz::Dictionary dict;

View File

@@ -6,41 +6,103 @@
#include "usd-to-json.hh"
#include "usda-reader.hh"
void print_usage(const char* program_name) {
std::cout << "Usage: " << program_name << " <input.usdz> [output_prefix]\n";
std::cout << "\nConverts USDZ to separate JSON files:\n";
std::cout << " - <output_prefix>_usd.json : USD scene content\n";
std::cout << " - <output_prefix>_assets.json : Asset data (base64 encoded)\n";
std::cout << "\nExample:\n";
std::cout << " " << program_name << " model.usdz output\n";
std::cout << " -> creates output_usd.json and output_assets.json\n";
}
int main(int argc, char **argv) {
if (argc < 2) {
std::cout << "Need input.usda/.usdc/.usdz\n";
exit(-1);
print_usage(argv[0]);
return 1;
}
std::string filename = argv[1];
// Check if file is USDZ
std::string ext = tinyusdz::io::GetFileExtension(filename);
bool is_usdz = (ext == "usdz" || ext == "USDZ");
if (!is_usdz) {
std::cerr << "This tool is specifically for USDZ files. For other USD formats, use the regular USD to JSON converter.\n";
return 1;
}
// Determine output prefix
std::string output_prefix;
if (argc >= 3) {
output_prefix = argv[2];
} else {
// Use input filename without extension as prefix
size_t dot_pos = filename.find_last_of('.');
size_t slash_pos = filename.find_last_of("/\\");
if (slash_pos == std::string::npos) slash_pos = 0; else slash_pos++;
if (dot_pos != std::string::npos && dot_pos > slash_pos) {
output_prefix = filename.substr(slash_pos, dot_pos - slash_pos);
} else {
output_prefix = filename.substr(slash_pos);
}
}
tinyusdz::Stage stage;
std::string warn;
std::string err;
bool ret = tinyusdz::LoadUSDFromFile(filename, &stage, &warn, &err);
std::cout << "Converting USDZ to JSON: " << filename << std::endl;
// Convert USDZ to JSON
tinyusdz::USDZToJSONResult result;
std::string warn, err;
tinyusdz::USDToJSONOptions options;
bool success = tinyusdz::USDZToJSON(filename, &result, &warn, &err, options);
if (!warn.empty()) {
std::cerr << "WARN: " << warn << "\n";
std::cerr << "Warnings: " << warn << std::endl;
}
if (!err.empty()) {
std::cerr << "ERR: " << err << "\n";
if (!success) {
std::cerr << "Failed to convert USDZ to JSON: " << err << std::endl;
return 1;
}
if (!ret) {
std::cerr << "Failed to load USD file: " << filename << "\n";
return EXIT_FAILURE;
std::cout << "Successfully converted USDZ!" << std::endl;
std::cout << "Main USD file: " << result.main_usd_filename << std::endl;
std::cout << "Total assets: " << result.asset_filenames.size() << std::endl;
// Write USD content to JSON file
std::string usd_json_filename = output_prefix + "_usd.json";
std::ofstream usd_file(usd_json_filename);
if (!usd_file.is_open()) {
std::cerr << "Failed to create USD JSON file: " << usd_json_filename << std::endl;
return 1;
}
nonstd::expected<std::string, std::string> jret = ToJSON(stage);
if (!jret) {
std::cerr << jret.error();
return -1;
usd_file << result.usd_json;
usd_file.close();
std::cout << "USD content written to: " << usd_json_filename << std::endl;
// Write assets to JSON file
std::string assets_json_filename = output_prefix + "_assets.json";
std::ofstream assets_file(assets_json_filename);
if (!assets_file.is_open()) {
std::cerr << "Failed to create assets JSON file: " << assets_json_filename << std::endl;
return 1;
}
std::cout << *jret << "\n";
assets_file << result.assets_json;
assets_file.close();
std::cout << "Assets written to: " << assets_json_filename << std::endl;
// Print summary
std::cout << "\nAsset summary:" << std::endl;
for (const auto& asset_filename : result.asset_filenames) {
std::cout << " - " << asset_filename << std::endl;
}
std::cout << "\nConversion complete!" << std::endl;
return 0;
}

View File

@@ -4628,6 +4628,11 @@ bool AsciiParser::ParseProperties(std::map<std::string, Property> *props,
// | 'rel' name '=' path
// ;
// Report progress and check for cancellation
if (!ReportProgress()) {
PUSH_ERROR_AND_RETURN("Parsing cancelled by progress callback.");
}
if (!SkipWhitespace()) {
return false;
}
@@ -4677,6 +4682,32 @@ void AsciiParser::Setup() {
RegisterAPISchemas(_supported_api_schemas);
}
bool AsciiParser::ReportProgress() {
// Check if callback exists and is callable
if (!_progress_callback) {
return true; // No callback, continue parsing
}
if (!_sr) {
return true; // No stream reader, can't compute progress
}
// Calculate progress based on current position in stream
uint64_t current_pos = _sr->tell();
uint64_t total_size = _sr->size();
float progress = 0.0f;
if (total_size > 0) {
progress = static_cast<float>(current_pos) / static_cast<float>(total_size);
// Clamp to [0, 1] range
if (progress > 1.0f) progress = 1.0f;
if (progress < 0.0f) progress = 0.0f;
}
// Call the callback and return its result
return _progress_callback(progress, _progress_userptr);
}
AsciiParser::~AsciiParser() {}
bool AsciiParser::CheckHeader() { return ParseMagicHeader(); }
@@ -4878,6 +4909,11 @@ bool AsciiParser::ParseBlock(const Specifier spec, const int64_t primIdx,
DCOUT("ParseBlock");
// Report progress and check for cancellation
if (!ReportProgress()) {
PUSH_ERROR_AND_RETURN("Parsing cancelled by progress callback.");
}
if (!SkipCommentAndWhitespaceAndNewline()) {
DCOUT("SkipCommentAndWhitespaceAndNewline failed");
return false;

View File

@@ -6,7 +6,7 @@
#pragma once
// #include <functional>
#include <functional>
#include <stdio.h>
#include <stack>
@@ -52,6 +52,14 @@ struct PathIdentifier : std::string {
// using std::string;
};
///
/// Progress callback function type.
/// @param[in] progress Progress value between 0.0 and 1.0
/// @param[in] userptr User-provided pointer for custom data
/// @return true to continue parsing, false to cancel
///
using ProgressCallback = std::function<bool(float progress, void *userptr)>;
///
/// Parser configuration options.
/// For strict configurations (e.g. reading USDZ on mobile devices),
@@ -334,6 +342,16 @@ class AsciiParser {
_max_memory_limit_bytes = limit_mb * 1024ull * 1024ull;
}
///
/// Set progress callback function
/// @param[in] callback Progress callback function
/// @param[in] userptr User-provided pointer for custom data
///
void SetProgressCallback(ProgressCallback callback, void *userptr = nullptr) {
_progress_callback = callback;
_progress_userptr = userptr;
}
///
/// Check if header data is USDA
///
@@ -915,6 +933,15 @@ class AsciiParser {
// For composition. PrimSpec is typeless so single callback function only.
PrimSpecFunction _primspec_fun{nullptr};
// Progress callback
ProgressCallback _progress_callback; // Default-initialized (empty)
void *_progress_userptr{nullptr};
///
/// Call progress callback and return false if parsing should be cancelled
///
bool ReportProgress();
};
///

View File

@@ -2,7 +2,9 @@
// Copyright 2022 - Present, Syoyo Fujita.
#include "usd-to-json.hh"
#include <algorithm>
#include "tinyusdz.hh"
#include "io-util.hh"
#ifdef __clang__
#pragma clang diagnostic push
@@ -919,115 +921,6 @@ json ToJSON(tinyusdz::Xform& xform) {
return j;
}
json ToJSON(tinyusdz::GeomMesh& mesh) {
return ToJSON(mesh, nullptr); // Use base64 mode by default
}
json ToJSON(tinyusdz::GeomMesh& mesh, USDToJSONContext* context) {
json j;
j["name"] = mesh.name;
j["typeName"] = "GeomMesh";
// Serialize geometry arrays using context-aware method
// Points array (point3f[])
if (mesh.points.authored()) {
auto points_opt = mesh.points.get_value();
if (points_opt) {
std::vector<value::point3f> points_data;
if (points_opt.value().get(value::TimeCode::Default(), &points_data)) {
j["points"] = SerializePoint3fArrayWithMetadata(points_data, &mesh.points.metas(), context);
}
}
}
// Face vertex counts (int[])
if (mesh.faceVertexCounts.authored()) {
auto face_counts_opt = mesh.faceVertexCounts.get_value();
if (face_counts_opt) {
std::vector<int> face_counts_data;
if (face_counts_opt.value().get(value::TimeCode::Default(), &face_counts_data)) {
j["faceVertexCounts"] = SerializeIntArrayWithMetadata(face_counts_data, &mesh.faceVertexCounts.metas(), context);
}
}
}
// Face vertex indices (int[])
if (mesh.faceVertexIndices.authored()) {
auto face_indices_opt = mesh.faceVertexIndices.get_value();
if (face_indices_opt) {
std::vector<int> face_indices_data;
if (face_indices_opt.value().get(value::TimeCode::Default(), &face_indices_data)) {
j["faceVertexIndices"] = SerializeIntArrayWithMetadata(face_indices_data, &mesh.faceVertexIndices.metas(), context);
}
}
}
// Normals array (normal3f[])
if (mesh.normals.authored()) {
auto normals_opt = mesh.normals.get_value();
if (normals_opt) {
std::vector<value::normal3f> normals_data;
if (normals_opt.value().get(value::TimeCode::Default(), &normals_data)) {
j["normals"] = SerializeNormal3fArrayWithMetadata(normals_data, &mesh.normals.metas(), context);
}
}
}
// Subdivision surface arrays
if (mesh.cornerIndices.authored()) {
auto corner_indices_opt = mesh.cornerIndices.get_value();
if (corner_indices_opt) {
std::vector<int> corner_indices_data;
if (corner_indices_opt.value().get(value::TimeCode::Default(), &corner_indices_data)) {
j["cornerIndices"] = SerializeIntArrayWithMetadata(corner_indices_data, &mesh.cornerIndices.metas(), context);
}
}
}
if (mesh.cornerSharpnesses.authored()) {
auto corner_sharpnesses_opt = mesh.cornerSharpnesses.get_value();
if (corner_sharpnesses_opt) {
std::vector<float> corner_sharpnesses_data;
if (corner_sharpnesses_opt.value().get(value::TimeCode::Default(), &corner_sharpnesses_data)) {
j["cornerSharpnesses"] = SerializeFloatArrayWithMetadata(corner_sharpnesses_data, &mesh.cornerSharpnesses.metas(), context);
}
}
}
if (mesh.creaseIndices.authored()) {
auto crease_indices_opt = mesh.creaseIndices.get_value();
if (crease_indices_opt) {
std::vector<int> crease_indices_data;
if (crease_indices_opt.value().get(value::TimeCode::Default(), &crease_indices_data)) {
j["creaseIndices"] = SerializeIntArrayWithMetadata(crease_indices_data, &mesh.creaseIndices.metas(), context);
}
}
}
if (mesh.creaseLengths.authored()) {
auto crease_lengths_opt = mesh.creaseLengths.get_value();
if (crease_lengths_opt) {
std::vector<int> crease_lengths_data;
if (crease_lengths_opt.value().get(value::TimeCode::Default(), &crease_lengths_data)) {
j["creaseLengths"] = SerializeIntArrayWithMetadata(crease_lengths_data, &mesh.creaseLengths.metas(), context);
}
}
}
if (mesh.creaseSharpnesses.authored()) {
auto crease_sharpnesses_opt = mesh.creaseSharpnesses.get_value();
if (crease_sharpnesses_opt) {
std::vector<float> crease_sharpnesses_data;
if (crease_sharpnesses_opt.value().get(value::TimeCode::Default(), &crease_sharpnesses_data)) {
j["creaseSharpnesses"] = SerializeFloatArrayWithMetadata(crease_sharpnesses_data, &mesh.creaseSharpnesses.metas(), context);
}
}
}
return j;
}
json ToJSON(tinyusdz::GeomBasisCurves& curves) {
@@ -1168,6 +1061,66 @@ json SerializeContextToJSON(const USDToJSONContext& context) {
} // namespace
// GeomMesh ToJSON functions (moved outside anonymous namespace for proper linking)
static json ToJSON(tinyusdz::GeomMesh& mesh) {
USDToJSONContext context; // Use default context
return ToJSON(mesh, &context);
}
json ToJSON(tinyusdz::GeomMesh& mesh, USDToJSONContext* context) {
json j;
j["name"] = mesh.name;
j["typeName"] = "GeomMesh";
// Serialize geometry arrays using context-aware method
// Points array (point3f[])
if (mesh.points.authored()) {
auto points_opt = mesh.points.get_value();
if (points_opt) {
std::vector<value::point3f> points_data;
if (points_opt.value().get(value::TimeCode::Default(), &points_data)) {
j["points"] = SerializePoint3fArrayWithMetadata(points_data, &mesh.points.metas(), context);
}
}
}
// Face vertex counts (int[])
if (mesh.faceVertexCounts.authored()) {
auto counts_opt = mesh.faceVertexCounts.get_value();
if (counts_opt) {
std::vector<int> counts_data;
if (counts_opt.value().get(value::TimeCode::Default(), &counts_data)) {
j["faceVertexCounts"] = SerializeIntArrayWithMetadata(counts_data, &mesh.faceVertexCounts.metas(), context);
}
}
}
// Face vertex indices (int[])
if (mesh.faceVertexIndices.authored()) {
auto indices_opt = mesh.faceVertexIndices.get_value();
if (indices_opt) {
std::vector<int> indices_data;
if (indices_opt.value().get(value::TimeCode::Default(), &indices_data)) {
j["faceVertexIndices"] = SerializeIntArrayWithMetadata(indices_data, &mesh.faceVertexIndices.metas(), context);
}
}
}
// Normals (normal3f[])
if (mesh.normals.authored()) {
auto normals_opt = mesh.normals.get_value();
if (normals_opt) {
std::vector<value::normal3f> normals_data;
if (normals_opt.value().get(value::TimeCode::Default(), &normals_data)) {
j["normals"] = SerializeNormal3fArrayWithMetadata(normals_data, &mesh.normals.metas(), context);
}
}
}
return j;
}
json ToJSON(const tinyusdz::Layer& layer) {
USDToJSONContext context; // Default context (base64 mode)
@@ -1658,5 +1611,305 @@ json PropertiesToJSON(const std::map<std::string, tinyusdz::Property>& propertie
return j;
}
// ================================================================
// Additional Stage to JSON conversion support
// ================================================================
// Helper function to convert Stage to JSON object (for internal use)
json ToJSON(const tinyusdz::Stage& stage, USDToJSONContext* context) {
(void)context; // Currently unused
// Reuse existing implementation pattern but return json object instead of string
json j; // root
auto jstageMetas = ToJSON(stage.metas());
if (jstageMetas) {
// Stage metadatum is represented as properties.
if (!jstageMetas->is_null()) {
j["properties"] = *jstageMetas;
}
}
j["version"] = 1.0;
json cj;
for (const auto& item : stage.root_prims()) {
if (!PrimToJSONRec(cj, item, 0)) {
return json::object(); // Return empty on error
}
}
j["primChildren"] = cj;
return j;
}
// Overload with options
nonstd::expected<std::string, std::string> ToJSON(const tinyusdz::Stage &stage, const USDToJSONOptions& options) {
USDToJSONContext context(options);
json j = ToJSON(stage, &context);
if (j.empty()) {
return nonstd::make_unexpected("Failed to convert Stage to JSON");
}
std::string json_str = j.dump(2); // Pretty print with 2-space indent
return json_str;
}
// ================================================================
// USDZ to JSON conversion implementation
// ================================================================
namespace {
// Helper function for case conversion
std::string str_tolower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c) { return std::tolower(c); });
return s;
}
}
bool USDZAssetsToJSON(const tinyusdz::USDZAsset& usdz_asset, std::string* assets_json,
std::string* warn, std::string* err) {
if (!assets_json) {
if (err) {
(*err) += "assets_json parameter is null\n";
}
return false;
}
json assets_obj = json::object();
for (const auto& asset_pair : usdz_asset.asset_map) {
const std::string& filename = asset_pair.first;
size_t byte_begin = asset_pair.second.first;
size_t byte_end = asset_pair.second.second;
// Validate byte range
if (byte_begin >= byte_end) {
if (warn) {
(*warn) += "Invalid byte range for asset: " + filename + "\n";
}
continue;
}
size_t asset_size = byte_end - byte_begin;
const uint8_t* asset_data = nullptr;
// Get asset data based on storage mode
if (!usdz_asset.data.empty()) {
// Data stored in vector
if (byte_end > usdz_asset.data.size()) {
if (warn) {
(*warn) += "Asset byte range exceeds data size for: " + filename + "\n";
}
continue;
}
asset_data = usdz_asset.data.data() + byte_begin;
} else if (usdz_asset.addr && usdz_asset.size > 0) {
// Data stored via memory mapping
if (byte_end > usdz_asset.size) {
if (warn) {
(*warn) += "Asset byte range exceeds mapped size for: " + filename + "\n";
}
continue;
}
asset_data = usdz_asset.addr + byte_begin;
} else {
if (warn) {
(*warn) += "No data available for asset: " + filename + "\n";
}
continue;
}
// Convert to base64
std::string base64_data = base64_encode(asset_data, static_cast<unsigned int>(asset_size));
// Create asset info object
json asset_info;
asset_info["filename"] = filename;
asset_info["size"] = asset_size;
asset_info["data"] = base64_data;
assets_obj[filename] = asset_info;
}
(*assets_json) = assets_obj.dump(2);
return true;
}
bool USDZToJSONFromMemory(const uint8_t* addr, size_t length, const std::string& filename,
USDZToJSONResult* result, std::string* warn, std::string* err,
const USDToJSONOptions& options) {
if (!addr || length == 0) {
if (err) {
(*err) += "Invalid memory address or length\n";
}
return false;
}
if (!result) {
if (err) {
(*err) += "result parameter is null\n";
}
return false;
}
// Parse USDZ and extract asset information
USDZAsset usdz_asset;
if (!ReadUSDZAssetInfoFromMemory(addr, length, true, &usdz_asset, warn, err)) {
return false;
}
// Find main USD file (prefer USDC over USDA)
std::string main_usd_filename;
std::string main_usd_ext;
for (const auto& asset_pair : usdz_asset.asset_map) {
const std::string& asset_filename = asset_pair.first;
std::string ext = str_tolower(io::GetFileExtension(asset_filename));
if (ext == "usdc" || ext == "usda") {
if (main_usd_filename.empty() || (ext == "usdc" && main_usd_ext == "usda")) {
main_usd_filename = asset_filename;
main_usd_ext = ext;
}
}
}
if (main_usd_filename.empty()) {
if (err) {
(*err) += "No USD file found in USDZ archive\n";
}
return false;
}
result->main_usd_filename = main_usd_filename;
// Extract USD content and convert to JSON
auto usd_asset_iter = usdz_asset.asset_map.find(main_usd_filename);
if (usd_asset_iter == usdz_asset.asset_map.end()) {
if (err) {
(*err) += "Could not find main USD file in asset map: " + main_usd_filename + "\n";
}
return false;
}
size_t usd_byte_begin = usd_asset_iter->second.first;
size_t usd_byte_end = usd_asset_iter->second.second;
size_t usd_size = usd_byte_end - usd_byte_begin;
const uint8_t* usd_data = usdz_asset.addr + usd_byte_begin;
// Load USD from memory and convert to JSON
Stage stage;
std::string usd_warn, usd_err;
bool usd_loaded = false;
if (main_usd_ext == "usdc") {
usd_loaded = LoadUSDCFromMemory(usd_data, usd_size, filename, &stage, &usd_warn, &usd_err);
} else if (main_usd_ext == "usda") {
std::string base_dir = io::GetBaseDir(filename);
usd_loaded = LoadUSDAFromMemory(usd_data, usd_size, base_dir, &stage, &usd_warn, &usd_err);
}
if (!usd_loaded) {
if (err) {
(*err) += "Failed to load USD content from USDZ: " + usd_err + "\n";
}
return false;
}
if (!usd_warn.empty() && warn) {
(*warn) += "USD loading warnings: " + usd_warn + "\n";
}
// Convert Stage to JSON
auto stage_json_result = ToJSON(stage, options);
if (!stage_json_result) {
if (err) {
(*err) += "Failed to convert USD Stage to JSON: " + stage_json_result.error() + "\n";
}
return false;
}
result->usd_json = stage_json_result.value();
// Extract all asset filenames (excluding the main USD file)
for (const auto& asset_pair : usdz_asset.asset_map) {
const std::string& asset_filename = asset_pair.first;
result->asset_filenames.push_back(asset_filename);
}
// Convert assets to JSON
if (!USDZAssetsToJSON(usdz_asset, &result->assets_json, warn, err)) {
return false;
}
return true;
}
bool USDZToJSON(const std::string& filename, USDZToJSONResult* result,
std::string* warn, std::string* err,
const USDToJSONOptions& options) {
if (!result) {
if (err) {
(*err) += "result parameter is null\n";
}
return false;
}
// Read USDZ file into memory
std::string filepath = io::ExpandFilePath(filename, nullptr);
if (io::IsMMapSupported()) {
// Use memory mapping for better performance with large files
io::MMapFileHandle handle;
std::string mmap_err;
if (!io::MMapFile(filepath, &handle, false, &mmap_err)) {
if (err) {
(*err) += "Failed to memory map file: " + mmap_err + "\n";
}
return false;
}
if (!mmap_err.empty() && warn) {
(*warn) += "Memory mapping warning: " + mmap_err + "\n";
}
bool success = USDZToJSONFromMemory(handle.addr, size_t(handle.size), filepath,
result, warn, err, options);
// Clean up memory mapping
std::string unmap_err;
io::UnmapFile(handle, &unmap_err);
if (!unmap_err.empty() && warn) {
(*warn) += "Memory unmap warning: " + unmap_err + "\n";
}
return success;
} else {
// Read entire file into memory
std::vector<uint8_t> data;
size_t max_bytes = 1024 * 1024 * 1024; // 1GB default limit
if (!io::ReadWholeFile(&data, err, filepath, max_bytes, nullptr)) {
return false;
}
if (data.empty()) {
if (err) {
(*err) += "File is empty: " + filepath + "\n";
}
return false;
}
return USDZToJSONFromMemory(data.data(), data.size(), filepath, result, warn, err, options);
}
}
} // namespace tinyusdz

View File

@@ -105,6 +105,11 @@ nonstd::expected<std::string, std::string> ToJSON(const tinyusdz::Stage &stage);
///
nonstd::expected<std::string, std::string> ToJSON(const tinyusdz::Stage &stage, const USDToJSONOptions& options);
///
/// Convert USD Stage to JSON (nlohmann::json object)
///
nlohmann::json ToJSON(const tinyusdz::Stage &stage, USDToJSONContext* context);
///
/// Convert USD Layer to JSON (nlohmann::json object)
///
@@ -150,4 +155,61 @@ nlohmann::json ToJSON(const tinyusdz::Property& property, USDToJSONContext* cont
///
nlohmann::json PropertiesToJSON(const std::map<std::string, tinyusdz::Property>& properties, USDToJSONContext* context = nullptr);
///
/// USDZ to JSON conversion result structure
///
struct USDZToJSONResult {
std::string usd_json; // JSON representation of USD content
std::string assets_json; // JSON representation of assets (filename -> base64 data)
std::string main_usd_filename; // Name of the main USD file in USDZ
std::vector<std::string> asset_filenames; // List of all asset filenames
USDZToJSONResult() = default;
};
///
/// Convert USDZ file to JSON (separate USD content and assets)
///
/// @param[in] filename Path to USDZ file
/// @param[out] result USDZ to JSON conversion result
/// @param[out] warn Warning messages
/// @param[out] err Error messages
/// @param[in] options Conversion options
///
/// @return true on success, false on failure
///
bool USDZToJSON(const std::string& filename, USDZToJSONResult* result,
std::string* warn, std::string* err,
const USDToJSONOptions& options = USDToJSONOptions());
///
/// Convert USDZ from memory to JSON (separate USD content and assets)
///
/// @param[in] addr Pointer to USDZ data in memory
/// @param[in] length Size of USDZ data in bytes
/// @param[in] filename Filename (for reference, can be empty)
/// @param[out] result USDZ to JSON conversion result
/// @param[out] warn Warning messages
/// @param[out] err Error messages
/// @param[in] options Conversion options
///
/// @return true on success, false on failure
///
bool USDZToJSONFromMemory(const uint8_t* addr, size_t length, const std::string& filename,
USDZToJSONResult* result, std::string* warn, std::string* err,
const USDToJSONOptions& options = USDToJSONOptions());
///
/// Extract assets from USDZ and convert to JSON map (filename -> base64 data)
///
/// @param[in] usdz_asset USDZ asset data structure
/// @param[out] assets_json JSON string containing asset map
/// @param[out] warn Warning messages
/// @param[out] err Error messages
///
/// @return true on success, false on failure
///
bool USDZAssetsToJSON(const tinyusdz::USDZAsset& usdz_asset, std::string* assets_json,
std::string* warn, std::string* err);
} // namespace tinyusdz