Files
tinyusdz/examples/tydra_to_renderscene/to-renderscene-main.cc
Syoyo Fujita 5b74bb09aa Add YAML and JSON output formats for DumpRenderScene in Tydra
- Add --yaml and --json command-line options to tydra_to_renderscene
- Implement YAML format (human-readable, now default) with metadata header
- Implement JSON format (machine-readable) with metadata header
- Both formats include: format_version, generator, source_file, scene settings
  (upAxis, metersPerUnit, framesPerSecond, etc.), and summary counts
- Add configuration info output as comments (YAML: #, JSON: //)
- Config info includes: loading_method, input_file, triangulate,
  triangulation_method, build_vertex_indices, asset_resolver, timecode, etc.
- Keep original KDL format available via API with format="kdl"

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-14 05:26:28 +09:00

411 lines
13 KiB
C++

// SPDX-License-Identifier: Apache 2.0
// Copyright 2022-Present Light Transport Entertainment Inc.
//
// Command-line tool to convert USD Stage to RenderScene(glTF-like data
// structure)
//
#include <algorithm>
#include <fstream>
#include <iostream>
#include <sstream>
#include "io-util.hh"
#include "pprinter.hh"
#include "prim-pprint.hh"
#include "str-util.hh"
#include "tinyusdz.hh"
#include "tydra/obj-export.hh"
#include "tydra/render-data.hh"
#include "tydra/scene-access.hh"
#include "tydra/shader-network.hh"
#include "tydra/usd-export.hh"
#include "usdShade.hh"
#include "value-pprint.hh"
#include "value-types.hh"
static int NullARResolve(const char *asset_name,
const std::vector<std::string> &search_paths,
std::string *resolved_asset_name, std::string *err,
void *userdata) {
(void)err;
(void)userdata;
(void)search_paths;
(void)asset_name;
(void)resolved_asset_name;
return -1;
}
static int NullARSize(const char *asset_name, uint64_t *nbytes, std::string *err,
void *userdata) {
(void)userdata;
(void)asset_name;
(void)nbytes;
(void)err;
return -1;
}
static int NullARRead(const char *asset_name, uint64_t req_nbytes, uint8_t *out_buf,
uint64_t *nbytes, std::string *err, void *userdata) {
(void)asset_name;
(void)req_nbytes;
(void)out_buf;
(void)nbytes;
(void)err;
(void)userdata;
return -1;
}
static bool SetupNullAssetResolution(
tinyusdz::AssetResolutionResolver &resolver)
{
tinyusdz::AssetResolutionHandler handler;
handler.resolve_fun = NullARResolve;
handler.size_fun = NullARSize;
handler.read_fun = NullARRead;
handler.write_fun = nullptr;
handler.userdata = nullptr;
resolver.register_wildcard_asset_resolution_handler(handler);
return true;
}
int main(int argc, char **argv) {
if (argc < 2) {
std::cout << "Usage: " << argv[0] << " input.usd [OPTIONS].\n";
std::cout << "\n\nOptions\n\n";
std::cout << " --timecode VALUE: Specify timecode value(e.g. 3.14)\n";
std::cout << " --noidxbuild: Do not rebuild vertex indices\n";
std::cout << " --notri: Do not triangulate mesh\n";
std::cout << " --trifan: Use triangle fan triangulation (instead of earcut)\n";
std::cout << " --texload: Load textures\n";
std::cout << " --noar: Do not use (default) AssertResolver\n";
std::cout << " --usdprint: Print parsed USD\n";
std::cout
<< " --dumpobj: Dump mesh as wavefront .obj(for visual debugging)\n";
std::cout << " --dumpusd: Dump scene as USD(USDA Ascii)\n";
std::cout << " --yaml: Output RenderScene as YAML (human-readable)\n";
std::cout << " --json: Output RenderScene as JSON (machine-readable)\n";
return EXIT_FAILURE;
}
// When Xform, Mesh, Material, etc. have time-varying values,
// values are evaluated at `timecode` time(except for animation values in
// SkelAnimation)
double timecode = tinyusdz::value::TimeCode::Default();
bool build_indices = true;
bool triangulate = true;
bool use_triangle_fan = false;
bool export_obj = false;
bool export_usd = false;
bool usdprint = false;
bool texload = false;
bool no_assetresolver = false;
std::string output_format = "yaml"; // "yaml" (human-readable), "json" (machine-readable)
std::string filepath;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--notri") == 0) {
triangulate = false;
} else if (strcmp(argv[i], "--trifan") == 0) {
use_triangle_fan = true;
} else if (strcmp(argv[i], "--noidxbuild") == 0) {
build_indices = false;
} else if (strcmp(argv[i], "--usdprint") == 0) {
usdprint = true;
} else if (strcmp(argv[i], "--texload") == 0) {
texload = true;
} else if (strcmp(argv[i], "--noar") == 0) {
no_assetresolver = true;
} else if (strcmp(argv[i], "--dumpobj") == 0) {
export_obj = true;
} else if (strcmp(argv[i], "--dumpusd") == 0) {
export_usd = true;
} else if (strcmp(argv[i], "--timecode") == 0) {
if ((i + 1) >= argc) {
std::cerr << "arg is missing for --timecode flag.\n";
return -1;
}
timecode = std::stod(argv[i + 1]);
std::cout << "Use timecode: " << timecode << "\n";
i++;
} else if (strcmp(argv[i], "--yaml") == 0) {
output_format = "yaml";
} else if (strcmp(argv[i], "--json") == 0) {
output_format = "json";
} else {
filepath = argv[i];
}
}
std::string warn;
std::string err;
tinyusdz::Stage stage;
if (!tinyusdz::IsUSD(filepath)) {
std::cerr << "File not found or not a USD format: " << filepath << "\n";
}
bool is_usdz = tinyusdz::IsUSDZ(filepath);
// Collect config info for formatted output
std::vector<std::pair<std::string, std::string>> config_info;
// Use mmap if available to save memory (avoids copying entire file)
tinyusdz::io::MMapFileHandle mmap_handle;
bool using_mmap = false;
bool ret = false;
if (tinyusdz::io::IsMMapSupported()) {
config_info.push_back({"loading_method", "mmap"});
if (!tinyusdz::io::MMapFile(filepath, &mmap_handle, /* writable */false, &err)) {
std::cerr << "Failed to mmap USD file: " << err << "\n";
return EXIT_FAILURE;
}
using_mmap = true;
// Load USD from mmap'd memory
ret = tinyusdz::LoadUSDFromMemory(mmap_handle.addr, mmap_handle.size,
filepath, &stage, &warn, &err);
} else {
// Fallback to file-based loading
config_info.push_back({"loading_method", "file"});
ret = tinyusdz::LoadUSDFromFile(filepath, &stage, &warn, &err);
}
if (!warn.empty()) {
std::cerr << "WARN : " << warn << "\n";
}
if (!err.empty()) {
std::cerr << "ERR : " << err << "\n";
}
if (!ret) {
std::cerr << "Failed to load USD file: " << filepath << "\n";
if (using_mmap) {
tinyusdz::io::UnmapFile(mmap_handle, &err);
}
return EXIT_FAILURE;
}
if (usdprint) {
std::string s = stage.ExportToString();
std::cout << s << "\n";
std::cout << "--------------------------------------"
<< "\n";
}
// RenderScene: Scene graph object which is suited for GL/Vulkan renderer
tinyusdz::tydra::RenderScene render_scene;
tinyusdz::tydra::RenderSceneConverter converter;
tinyusdz::tydra::RenderSceneConverterEnv env(stage);
config_info.push_back({"input_file", filepath});
config_info.push_back({"is_usdz", is_usdz ? "true" : "false"});
config_info.push_back({"output_format", output_format});
config_info.push_back({"triangulate", triangulate ? "true" : "false"});
env.mesh_config.triangulate = triangulate;
if (use_triangle_fan) {
config_info.push_back({"triangulation_method", "TriangleFan"});
env.mesh_config.triangulation_method = tinyusdz::tydra::MeshConverterConfig::TriangulationMethod::TriangleFan;
} else {
config_info.push_back({"triangulation_method", "Earcut"});
env.mesh_config.triangulation_method = tinyusdz::tydra::MeshConverterConfig::TriangulationMethod::Earcut;
}
config_info.push_back({"build_vertex_indices", build_indices ? "true" : "false"});
env.mesh_config.build_vertex_indices = build_indices;
config_info.push_back({"load_texture_data", texload ? "true" : "false"});
env.scene_config.load_texture_assets = texload;
// Add base directory of .usd file to search path.
std::string usd_basedir = tinyusdz::io::GetBaseDir(filepath);
config_info.push_back({"search_path", usd_basedir});
tinyusdz::USDZAsset usdz_asset;
if (is_usdz) {
// Setup AssetResolutionResolver to read assets from USDZ container.
// Reuse the mmap handle if already mmap'd, otherwise fall back to file-based.
if (using_mmap) {
// Use ReadUSDZAssetInfoFromMemory with asset_on_memory=true
// This avoids copying the USDZ data, just references the mmap'd address
if (!tinyusdz::ReadUSDZAssetInfoFromMemory(
mmap_handle.addr, mmap_handle.size,
/* asset_on_memory */ true,
&usdz_asset, &warn, &err)) {
std::cerr << "Failed to read USDZ assetInfo from memory: " << err << "\n";
tinyusdz::io::UnmapFile(mmap_handle, &err);
return EXIT_FAILURE;
}
} else {
// Fallback to file-based loading (copies entire file into memory)
if (!tinyusdz::ReadUSDZAssetInfoFromFile(filepath, &usdz_asset, &warn,
&err)) {
std::cerr << "Failed to read USDZ assetInfo from file: " << err << "\n";
return EXIT_FAILURE;
}
}
if (warn.size()) {
std::cout << warn << "\n";
}
tinyusdz::AssetResolutionResolver arr;
if (no_assetresolver) {
SetupNullAssetResolution(arr);
config_info.push_back({"asset_resolver", "null"});
} else {
// NOTE: Pointer address of usdz_asset must be valid until the call of
// RenderSceneConverter::ConvertToRenderScene.
if (!tinyusdz::SetupUSDZAssetResolution(arr, &usdz_asset)) {
std::cerr << "Failed to setup AssetResolution for USDZ asset\n";
exit(-1);
};
config_info.push_back({"asset_resolver", "usdz"});
}
env.asset_resolver = arr;
} else {
env.set_search_paths({usd_basedir});
if (no_assetresolver) {
SetupNullAssetResolution(env.asset_resolver);
config_info.push_back({"asset_resolver", "null"});
} else {
config_info.push_back({"asset_resolver", "default"});
}
}
if (!tinyusdz::value::TimeCode(timecode).is_default()) {
config_info.push_back({"timecode", std::to_string(timecode)});
} else {
config_info.push_back({"timecode", "default"});
}
env.timecode = timecode;
ret = converter.ConvertToRenderScene(env, &render_scene);
if (!ret) {
std::cerr << "Failed to convert USD Stage to RenderScene: \n"
<< converter.GetError() << "\n";
return EXIT_FAILURE;
}
std::string converter_warn = converter.GetWarning();
if (!converter_warn.empty()) {
config_info.push_back({"converter_warning", converter_warn});
}
// Helper to escape value for single-line output
auto escape_for_comment = [](const std::string &s) -> std::string {
std::string result;
result.reserve(s.size());
for (char c : s) {
if (c == '\n') {
result += "\\n";
} else if (c == '\r') {
result += "\\r";
} else if (c == '\t') {
result += "\\t";
} else {
result += c;
}
}
return result;
};
// Output config info in appropriate format
if (output_format == "yaml") {
// YAML: Output as comments
std::cout << "# TinyUSDZ tydra_to_renderscene Configuration\n";
std::cout << "# ==========================================\n";
for (const auto &kv : config_info) {
std::cout << "# " << kv.first << ": " << escape_for_comment(kv.second) << "\n";
}
std::cout << "#\n";
} else if (output_format == "json") {
// JSON: Output config as a separate JSON object before main output
std::cout << "// TinyUSDZ tydra_to_renderscene Configuration\n";
std::cout << "// config: {\n";
for (size_t i = 0; i < config_info.size(); i++) {
std::cout << "// \"" << config_info[i].first << "\": \"" << escape_for_comment(config_info[i].second) << "\"";
if (i < config_info.size() - 1) std::cout << ",";
std::cout << "\n";
}
std::cout << "// }\n";
} else {
// KDL or other: output as comments
for (const auto &kv : config_info) {
std::cout << "// " << kv.first << ": " << escape_for_comment(kv.second) << "\n";
}
}
std::cout << DumpRenderScene(render_scene, output_format) << "\n";
if (export_obj) {
std::cout << "Dump RenderMesh as wavefront .obj\n";
for (size_t i = 0; i < render_scene.meshes.size(); i++) {
std::string obj_str;
std::string mtl_str;
if (!tinyusdz::tydra::export_to_obj(render_scene, i, obj_str, mtl_str,
&warn, &err)) {
std::cerr << "obj export error: " << err << "\n";
exit(-1);
}
std::string obj_filename =
std::to_string(i) + render_scene.meshes[i].prim_name + ".obj";
std::string mtl_filename =
std::to_string(i) + render_scene.meshes[i].prim_name + ".mtl";
{
std::ofstream obj_ofs(obj_filename);
obj_ofs << obj_str;
}
{
std::ofstream mtl_ofs(mtl_filename);
mtl_ofs << mtl_str;
}
std::cout << " Wrote " << obj_filename << "\n";
}
}
if (export_usd) {
std::string ext = tinyusdz::io::GetFileExtension(filepath);
std::string usd_basename = tinyusdz::io::GetBaseFilename(filepath);
std::string usd_filename =
tinyusdz::removeSuffix(usd_basename, ext) + "export.usda";
std::string usda_str;
if (!tinyusdz::tydra::export_to_usda(render_scene, usda_str, &warn, &err)) {
std::cerr << "Failed to export RenderScene to USDA: " << err << "\n";
}
if (warn.size()) {
std::cout << "WARN: " << warn << "\n";
}
{
std::ofstream ofs(usd_filename);
ofs << usda_str;
}
std::cout << "Exported RenderScene as USDA: " << usd_filename << "\n";
}
// Cleanup mmap if used
if (using_mmap) {
std::string unmap_err;
if (!tinyusdz::io::UnmapFile(mmap_handle, &unmap_err)) {
std::cerr << "WARN: Failed to unmap file: " << unmap_err << "\n";
}
}
return EXIT_SUCCESS;
}