#include #include #include #include #include #include #include #include #include "tinyusdz.hh" #include "layer.hh" #include "pprinter.hh" #include "str-util.hh" #include "io-util.hh" #include "usd-to-json.hh" #include "usd-dump.hh" #include "logger.hh" #include "tydra/scene-access.hh" struct CompositionFeatures { bool subLayers{true}; bool inherits{true}; bool variantSets{true}; bool references{true}; bool payload{true}; // Not 'payloads' bool specializes{true}; }; static std::string GetFileExtension(const std::string &filename) { if (filename.find_last_of('.') != std::string::npos) return filename.substr(filename.find_last_of('.') + 1); return ""; } static std::string str_tolower(std::string s) { std::transform(s.begin(), s.end(), s.begin(), [](unsigned char c) { return std::tolower(c); } ); return s; } static std::string format_memory_size(size_t bytes) { const char* units[] = {"B", "KB", "MB", "GB", "TB"}; int unit_index = 0; double size = static_cast(bytes); while (size >= 1024.0 && unit_index < 4) { size /= 1024.0; unit_index++; } std::stringstream ss; if (unit_index == 0) { ss << static_cast(size) << " " << units[unit_index]; } else { ss.precision(2); ss << std::fixed << size << " " << units[unit_index]; } return ss.str(); } // Progress bar state struct ProgressState { std::chrono::steady_clock::time_point start_time; bool display_started{false}; float last_progress{0.0f}; static constexpr int kBarWidth = 40; static constexpr double kDelaySeconds = 3.0; // Don't show progress under 3 seconds }; static bool progress_callback(float progress, void *userptr) { ProgressState *state = static_cast(userptr); if (!state) { return true; } auto now = std::chrono::steady_clock::now(); double elapsed = std::chrono::duration(now - state->start_time).count(); // Don't show progress if loading takes less than 3 seconds if (elapsed < ProgressState::kDelaySeconds) { return true; } // Only update display if progress changed significantly (1% or more) if (progress - state->last_progress < 0.01f && progress < 1.0f) { return true; } state->last_progress = progress; if (!state->display_started) { state->display_started = true; std::cerr << "\n"; // Start on new line } int percent = static_cast(progress * 100.0f); int filled = static_cast(progress * ProgressState::kBarWidth); std::cerr << "\r["; for (int i = 0; i < ProgressState::kBarWidth; ++i) { if (i < filled) { std::cerr << "="; } else if (i == filled) { std::cerr << ">"; } else { std::cerr << " "; } } std::cerr << "] " << std::setw(3) << percent << " %" << std::flush; if (progress >= 1.0f) { std::cerr << "\n"; // Finish with newline } return true; // Continue parsing } void print_help() { std::cout << "Usage: tusdcat [OPTIONS] input.usda/usdc/usdz\n"; std::cout << "\n"; std::cout << "Options:\n"; std::cout << " -h, --help Show this help message\n"; std::cout << " -f, --flatten Do composition (load sublayers, references,\n"; std::cout << " payload, evaluate `over`, inherit, variants)\n"; std::cout << " (not fully implemented yet)\n"; std::cout << " --composition=LIST Specify which composition features to enable\n"; std::cout << " (valid when --flatten is supplied).\n"; std::cout << " Comma-separated list of:\n"; std::cout << " l or subLayers, i or inherits,\n"; std::cout << " v or variantSets, r or references,\n"; std::cout << " p or payload, s or specializes\n"; std::cout << " Example: --composition=r,p\n"; std::cout << " --extract-variants Dump variants information to JSON (w.i.p)\n"; std::cout << " --relative Print Path as relative Path (not implemented)\n"; std::cout << " -l, --loadOnly Load/parse USD file only (validate input)\n"; std::cout << " -j, --json Output parsed USD as JSON string\n"; std::cout << " --memstat Print memory usage statistics\n"; std::cout << " --progress Show ASCII progress bar\n"; std::cout << " (only shown if loading takes > 3 seconds)\n"; std::cout << " --loglevel INT Set logging level:\n"; std::cout << " 0=Debug, 1=Warn, 2=Info,\n"; std::cout << " 3=Error, 4=Critical, 5=Off\n"; std::cout << "\n"; std::cout << "Inspect options (YAML-like tree output):\n"; std::cout << " --inspect Inspect Layer structure (YAML-like output)\n"; std::cout << " --inspect-json Inspect Layer structure (JSON output)\n"; std::cout << " --value=MODE Value printing mode:\n"; std::cout << " none = schema only, no values\n"; std::cout << " snip = first N items (default)\n"; std::cout << " full = all values\n"; std::cout << " --snip=N Show first N items in snip mode (default: 8)\n"; std::cout << " --path=PATTERN Filter prims by path glob pattern\n"; std::cout << " (* = any chars, ** = any path segments)\n"; std::cout << " --attr=PATTERN Filter attributes by name glob pattern\n"; std::cout << " --time=T Query TimeSamples at time T\n"; std::cout << " --time=S:E Query TimeSamples in range [S, E]\n"; } int main(int argc, char **argv) { // Enable DCOUT output if TINYUSDZ_ENABLE_DCOUT environment variable is set const char* enable_dcout_env = std::getenv("TINYUSDZ_ENABLE_DCOUT"); if (enable_dcout_env != nullptr && std::strlen(enable_dcout_env) > 0) { // Any non-empty value enables DCOUT tinyusdz::g_enable_dcout_output = true; } if (argc < 2) { print_help(); return EXIT_FAILURE; } bool has_flatten{false}; bool has_relative{false}; bool has_extract_variants{false}; bool load_only{false}; bool json_output{false}; bool memstat{false}; bool show_progress{false}; // Inspect options bool do_inspect{false}; tinyusdz::InspectOptions inspect_opts; constexpr int kMaxIteration = 128; std::string filepath; int input_index = -1; CompositionFeatures comp_features; for (size_t i = 1; i < argc; i++) { std::string arg = argv[i]; if ((arg.compare("-h") == 0) || (arg.compare("--help") ==0)) { print_help(); return EXIT_FAILURE; } else if ((arg.compare("-f") == 0) || (arg.compare("--flatten") == 0)) { has_flatten = true; } else if (arg.compare("--relative") == 0) { 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 (arg.compare("--memstat") == 0) { memstat = true; } else if (arg.compare("--progress") == 0) { show_progress = true; } else if (arg.compare("--inspect") == 0) { do_inspect = true; inspect_opts.format = tinyusdz::InspectOutputFormat::Yaml; } else if (arg.compare("--inspect-json") == 0) { do_inspect = true; inspect_opts.format = tinyusdz::InspectOutputFormat::Json; } else if (tinyusdz::startsWith(arg, "--value=")) { std::string value_str = tinyusdz::removePrefix(arg, "--value="); if (value_str == "none") { inspect_opts.value_mode = tinyusdz::InspectValueMode::NoValue; } else if (value_str == "snip") { inspect_opts.value_mode = tinyusdz::InspectValueMode::Snip; } else if (value_str == "full") { inspect_opts.value_mode = tinyusdz::InspectValueMode::Full; } else { std::cerr << "Invalid value mode: " << value_str << ". Use: none, snip, or full\n"; return EXIT_FAILURE; } } else if (tinyusdz::startsWith(arg, "--snip=")) { std::string snip_str = tinyusdz::removePrefix(arg, "--snip="); try { int snip_val = std::stoi(snip_str); if (snip_val < 1) { std::cerr << "Invalid snip value: " << snip_val << ". Must be >= 1\n"; return EXIT_FAILURE; } inspect_opts.snip_count = static_cast(snip_val); } catch (...) { std::cerr << "Invalid snip value: " << snip_str << "\n"; return EXIT_FAILURE; } } else if (tinyusdz::startsWith(arg, "--path=")) { inspect_opts.prim_path_pattern = tinyusdz::removePrefix(arg, "--path="); } else if (tinyusdz::startsWith(arg, "--attr=")) { inspect_opts.attr_pattern = tinyusdz::removePrefix(arg, "--attr="); } else if (tinyusdz::startsWith(arg, "--time=")) { std::string time_str = tinyusdz::removePrefix(arg, "--time="); inspect_opts.has_time_query = true; // Check for range format "start:end" size_t colon_pos = time_str.find(':'); if (colon_pos != std::string::npos) { std::string start_str = time_str.substr(0, colon_pos); std::string end_str = time_str.substr(colon_pos + 1); try { inspect_opts.time_start = std::stod(start_str); inspect_opts.time_end = std::stod(end_str); } catch (...) { std::cerr << "Invalid time range: " << time_str << "\n"; return EXIT_FAILURE; } } else { // Single time value try { double t = std::stod(time_str); inspect_opts.time_start = t; inspect_opts.time_end = t; } catch (...) { std::cerr << "Invalid time value: " << time_str << "\n"; return EXIT_FAILURE; } } } else if (arg.compare("--loglevel") == 0) { if (i + 1 >= argc) { std::cerr << "--loglevel requires an integer argument\n"; return EXIT_FAILURE; } i++; // Move to next argument try { int log_level = std::stoi(argv[i]); if (log_level < 0 || log_level > 5) { std::cerr << "Invalid log level: " << log_level << ". Must be between 0 and 5.\n"; return EXIT_FAILURE; } tinyusdz::logging::Logger::getInstance().setLogLevel( static_cast(log_level)); } catch (const std::invalid_argument& e) { std::cerr << "Invalid log level argument: " << argv[i] << ". Must be an integer.\n"; return EXIT_FAILURE; } catch (const std::out_of_range& e) { std::cerr << "Log level value out of range: " << argv[i] << "\n"; return EXIT_FAILURE; } } else if (tinyusdz::startsWith(arg, "--composition=")) { std::string value_str = tinyusdz::removePrefix(arg, "--composition="); if (value_str.empty()) { std::cerr << "No values specified to --composition.\n"; exit(-1); } std::vector items = tinyusdz::split(value_str, ","); comp_features.subLayers = false; comp_features.inherits = false; comp_features.variantSets = false; comp_features.references = false; comp_features.payload = false; comp_features.specializes = false; for (const auto &item : items) { if ((item == "l") || (item == "subLayers")) { comp_features.subLayers = true; } else if ((item == "i") || (item == "inherits")) { comp_features.inherits = true; } else if ((item == "v") || (item == "variantSets")) { comp_features.variantSets = true; } else if ((item == "r") || (item == "references")) { comp_features.references = true; } else if ((item == "p") || (item == "payload")) { comp_features.payload = true; } else if ((item == "s") || (item == "specializes")) { comp_features.specializes = true; } else { std::cerr << "Invalid string for --composition : " << item << "\n"; exit(-1); } } } else { filepath = arg; input_index = i; } } if (filepath.empty() || (input_index < 0)) { std::cout << "Input USD filename missing.\n"; return EXIT_FAILURE; } std::string warn; std::string err; std::string ext = str_tolower(GetFileExtension(filepath)); std::string base_dir; base_dir = tinyusdz::io::GetBaseDir(filepath); // Handle --inspect mode if (do_inspect) { // Load as Layer for inspection tinyusdz::Layer layer; bool ret = tinyusdz::LoadLayerFromFile(filepath, &layer, &warn, &err); if (!warn.empty()) { std::cerr << "WARN: " << warn << "\n"; } if (!ret) { std::cerr << "Failed to load USD file as Layer: " << filepath << "\n"; if (!err.empty()) { std::cerr << err << "\n"; } return EXIT_FAILURE; } // Output inspection result std::string output = tinyusdz::InspectLayer(layer, inspect_opts); std::cout << output; return EXIT_SUCCESS; } if (has_flatten) { if (load_only) { std::cerr << "--flatten and --loadOnly cannot be specified at a time\n"; return EXIT_FAILURE; } // TODO: flatten for USDZ if (tinyusdz::IsUSDZ(filepath)) { std::cout << "--flatten is ignored for USDZ at the moment.\n"; tinyusdz::Stage stage; tinyusdz::USDLoadOptions usdz_options; // Set up progress callback if requested ProgressState usdz_progress_state; if (show_progress) { usdz_progress_state.start_time = std::chrono::steady_clock::now(); usdz_options.progress_callback = progress_callback; usdz_options.progress_userptr = &usdz_progress_state; } bool ret = tinyusdz::LoadUSDZFromFile(filepath, &stage, &warn, &err, usdz_options); if (!warn.empty()) { std::cerr << "WARN : " << warn << "\n"; } if (!err.empty()) { std::cerr << "ERR : " << err << "\n"; //return EXIT_FAILURE; } if (!ret) { std::cerr << "Failed to load USDZ file: " << filepath << "\n"; return EXIT_FAILURE; } if (memstat) { size_t stage_mem = stage.estimate_memory_usage(); std::cout << "# Memory Statistics (Stage from USDZ)\n"; std::cout << " Stage memory usage: " << format_memory_size(stage_mem) << " (" << stage_mem << " bytes)\n\n"; } if (json_output) { #if defined(TINYUSDZ_WITH_JSON) 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"; #else std::cerr << "JSON output is not supported in this build\n"; return EXIT_FAILURE; #endif } return EXIT_SUCCESS; } tinyusdz::Layer root_layer; bool ret = tinyusdz::LoadLayerFromFile(filepath, &root_layer, &warn, &err); if (warn.size()) { std::cout << "WARN: " << warn << "\n"; } if (!ret) { std::cerr << "Failed to read USD data as Layer: \n"; std::cerr << err << "\n"; return -1; } if (memstat) { size_t layer_mem = root_layer.estimate_memory_usage(); std::cout << "# Memory Statistics (Layer)\n"; std::cout << " Layer memory usage: " << format_memory_size(layer_mem) << " (" << layer_mem << " bytes)\n\n"; } std::cout << "# input\n"; std::cout << root_layer << "\n"; tinyusdz::Stage stage; stage.metas() = root_layer.metas(); std::string warn; tinyusdz::AssetResolutionResolver resolver; resolver.set_current_working_path(base_dir); resolver.set_search_paths({base_dir}); // // LIVRPS strength ordering // - [x] Local(subLayers) // - [x] Inherits // - [x] VariantSets // - [x] References // - [x] Payload // - [ ] Specializes // tinyusdz::Layer src_layer = root_layer; if (comp_features.subLayers) { tinyusdz::Layer composited_layer; if (!tinyusdz::CompositeSublayers(resolver, src_layer, &composited_layer, &warn, &err)) { std::cerr << "Failed to composite subLayers: " << err << "\n"; return -1; } if (warn.size()) { std::cout << "WARN: " << warn << "\n"; } std::cout << "# `subLayers` composited\n"; std::cout << composited_layer << "\n"; src_layer = std::move(composited_layer); } // TODO: Find more better way to Recursively resolve references/payload/variants for (int i = 0; i < kMaxIteration; i++) { bool has_unresolved = false; if (comp_features.references) { if (!src_layer.check_unresolved_references()) { std::cout << "# iter " << i << ": no unresolved references.\n"; } else { has_unresolved = true; tinyusdz::Layer composited_layer; if (!tinyusdz::CompositeReferences(resolver, src_layer, &composited_layer, &warn, &err)) { std::cerr << "Failed to composite `references`: " << err << "\n"; return -1; } if (warn.size()) { std::cout << "WARN: " << warn << "\n"; } std::cout << "# `references` composited\n"; std::cout << composited_layer << "\n"; src_layer = std::move(composited_layer); } } if (comp_features.payload) { if (!src_layer.check_unresolved_payload()) { std::cout << "# iter " << i << ": no unresolved payload.\n"; } else { has_unresolved = true; tinyusdz::Layer composited_layer; if (!tinyusdz::CompositePayload(resolver, src_layer, &composited_layer, &warn, &err)) { std::cerr << "Failed to composite `payload`: " << err << "\n"; return -1; } if (warn.size()) { std::cout << "WARN: " << warn << "\n"; } std::cout << "# `payload` composited\n"; std::cout << composited_layer << "\n"; src_layer = std::move(composited_layer); } } if (comp_features.inherits) { if (!src_layer.check_unresolved_inherits()) { std::cout << "# iter " << i << ": no unresolved inherits.\n"; } else { has_unresolved = true; tinyusdz::Layer composited_layer; if (!tinyusdz::CompositeInherits(src_layer, &composited_layer, &warn, &err)) { std::cerr << "Failed to composite `inherits`: " << err << "\n"; return -1; } if (warn.size()) { std::cout << "WARN: " << warn << "\n"; } std::cout << "# `inherits` composited\n"; std::cout << composited_layer << "\n"; src_layer = std::move(composited_layer); } } if (comp_features.variantSets) { if (!src_layer.check_unresolved_variant()) { std::cout << "# iter " << i << ": no unresolved variant.\n"; } else { has_unresolved = true; tinyusdz::Layer composited_layer; if (!tinyusdz::CompositeVariant(src_layer, &composited_layer, &warn, &err)) { std::cerr << "Failed to composite `variantSet`: " << err << "\n"; return -1; } if (warn.size()) { std::cout << "WARN: " << warn << "\n"; } std::cout << "# `variantSet` composited\n"; std::cout << composited_layer << "\n"; src_layer = std::move(composited_layer); } } // TODO // - [ ] specializes // - [ ] `class` Prim? std::cout << "# has_unresolved_references: " << src_layer.check_unresolved_references() << "\n"; std::cout << "# all resolved? " << !has_unresolved << "\n"; if (!has_unresolved) { std::cout << "# of composition iteration to resolve fully: " << (i + 1) << "\n"; break; } } if (has_extract_variants) { tinyusdz::Dictionary dict; if (!tinyusdz::ExtractVariants(src_layer, &dict, &err)) { std::cerr << "Failed to extract variants info: " << err; } else { std::cout << "== Variants info ==\n" << tinyusdz::to_string(dict) << "\n"; } } tinyusdz::Stage comp_stage; ret = LayerToStage(std::move(src_layer), &comp_stage, &warn, &err); if (warn.size()) { std::cout << warn<< "\n"; } if (!ret) { std::cerr << err << "\n"; } if (memstat) { size_t stage_mem = comp_stage.estimate_memory_usage(); std::cout << "\n# Memory Statistics (Stage after composition)\n"; std::cout << " Stage memory usage: " << format_memory_size(stage_mem) << " (" << stage_mem << " bytes)\n\n"; } if (json_output) { #if defined(TINYUSDZ_WITH_JSON) 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::cerr << "JSON output is not supported in this build\n"; #endif } else { std::cout << comp_stage.ExportToString() << "\n"; } using MeshMap = std::map; MeshMap meshmap; tinyusdz::tydra::ListPrims(comp_stage, meshmap); for (const auto &item : meshmap) { std::cout << "Prim : " << item.first << "\n"; } } else { tinyusdz::Stage stage; tinyusdz::USDLoadOptions options; // Set up progress callback if requested ProgressState progress_state; if (show_progress) { progress_state.start_time = std::chrono::steady_clock::now(); options.progress_callback = progress_callback; options.progress_userptr = &progress_state; } // auto detect format. bool ret = tinyusdz::LoadUSDFromFile(filepath, &stage, &warn, &err, options); if (!warn.empty()) { std::cerr << "WARN : " << warn << "\n"; } if (!err.empty()) { std::cerr << "ERR : " << err << "\n"; //return EXIT_FAILURE; } if (!ret) { std::cerr << "Failed to load USD file: " << filepath << "\n"; return EXIT_FAILURE; } if (load_only) { if (memstat) { size_t stage_mem = stage.estimate_memory_usage(); std::cout << "# Memory Statistics (Stage)\n"; std::cout << " Stage memory usage: " << format_memory_size(stage_mem) << " (" << stage_mem << " bytes)\n"; } return EXIT_SUCCESS; } if (memstat) { size_t stage_mem = stage.estimate_memory_usage(); std::cout << "# Memory Statistics (Stage)\n"; std::cout << " Stage memory usage: " << format_memory_size(stage_mem) << " (" << stage_mem << " bytes)\n\n"; } if (json_output) { #if defined(TINYUSDZ_WITH_JSON) 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::cerr << "JSON output is not supported in this build\n"; #endif } else { std::string s = stage.ExportToString(has_relative); std::cout << s << "\n"; } if (has_extract_variants) { tinyusdz::Dictionary dict; if (!tinyusdz::ExtractVariants(stage, &dict, &err)) { std::cerr << "Failed to extract variants info: " << err; } else { std::cout << "== Variants info ==\n" << tinyusdz::to_string(dict) << "\n"; } } } return EXIT_SUCCESS; }