mirror of
https://github.com/lighttransport/tinyusdz.git
synced 2026-01-18 01:11:17 +01:00
add --json option to tusdcat.
add callback interface in USDA parser.
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
};
|
||||
|
||||
///
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user