Add Python bindings and comprehensive variant documentation

Python Bindings:
- Expose VariantOption, VariantSet, VariantGroup, VariantSelection classes
- Expose VariantConverter for extracting variants from USD files
- Expose DefaultVariantManager for querying and selecting variants
- Expose VariantStatistics for complexity metrics
- All classes accessible via tinyusdz.tydra module
- Full API support: selection, querying, statistics, reset

Documentation:
- VARIANT_USAGE_GUIDE.md: Comprehensive guide covering:
  * Quick start for C++, Python, and CLI tools
  * Detailed architecture and data structures
  * Complete API reference
  * Python bindings documentation
  * Best practices and performance tips
  * 3 detailed usage examples

- VARIANT_BEST_PRACTICES.md: Best practices covering:
  * Design principles (clarity, single responsibility, explicit defaults)
  * Variant hierarchy design patterns
  * Naming conventions and style guidelines
  * Performance optimization strategies
  * Integration patterns (UI, undo, presets, export)
  * Common pitfalls and how to avoid them
  * Testing and validation approaches

Examples:
- python/examples/variant_example.py: Complete example showing:
  * Loading USD files
  * Extracting variants with VariantConverter
  * Listing available variants
  * Selecting different variants
  * Querying variant statistics
  * Resetting to defaults

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Syoyo Fujita
2025-11-09 20:53:53 +09:00
parent b169cbae79
commit b442b1a04d
4 changed files with 1398 additions and 0 deletions

View File

@@ -0,0 +1,587 @@
# USD Variant Best Practices Guide
This guide provides best practices for designing, implementing, and using USD variants in TinyUSDZ applications.
## Table of Contents
1. [Design Principles](#design-principles)
2. [Structure Guidelines](#structure-guidelines)
3. [Naming Conventions](#naming-conventions)
4. [Performance Optimization](#performance-optimization)
5. [Integration Patterns](#integration-patterns)
6. [Common Pitfalls](#common-pitfalls)
7. [Testing and Validation](#testing-and-validation)
## Design Principles
### 1. Clarity Over Brevity
Variants should be self-documenting. Use clear, descriptive names that indicate purpose:
```
✓ GOOD:
geometry_lod
material_finish
assembly_type
locale_variant
✗ BAD:
var1, var2, v3
opt_a, opt_b
geom, mat
```
### 2. Single Responsibility
Each variant set should represent a single dimension of variation:
```
✓ GOOD (separate concerns):
VariantSet "level_of_detail" → [high, medium, low]
VariantSet "material_type" → [plastic, metal, rubber]
VariantSet "damage" → [pristine, scratched, broken]
✗ BAD (mixed concerns):
VariantSet "variations" → [high_plastic, low_metal, broken_rubber]
```
### 3. Explicit Defaults
Always explicitly set and document default variant selections:
```cpp
struct VariantSet {
std::string name;
std::vector<VariantOption> options;
int32_t default_option_index = 0; // Always explicit
// ...
};
```
### 4. Consistent Ordering
Maintain consistent ordering of options within a variant set:
```
✓ GOOD (logical ordering):
lod: [high, medium, low] // Descending detail
quality: [maximum, high, medium, low] // Descending quality
✗ BAD (random ordering):
lod: [low, high, medium] // Confusing order
```
### 5. Avoid Over-nesting
While USD supports unlimited nesting, limit to 3 levels for maintainability:
```
✓ GOOD (3 levels):
Level 1: Asset variant sets
Level 2: LOD option variants
Level 3: Material option variants
✗ BAD (excessive nesting):
Level 1 → Level 2 → Level 3 → Level 4 → Level 5
```
## Structure Guidelines
### Variant Hierarchy Design
Design variant hierarchies bottom-up, starting with dependencies:
```
Character Asset
├── VariantSet "character_type" [primary dimension]
│ ├── Option "hero"
│ │ └── VariantSet "skin_tone"
│ │ ├── "light"
│ │ ├── "medium"
│ │ └── "dark"
│ ├── Option "villain"
│ │ └── VariantSet "damage_level"
│ │ ├── "pristine"
│ │ └── "damaged"
│ └── Option "extra"
│ └── VariantSet "costume"
│ ├── "red"
│ └── "blue"
└── VariantSet "cloth_material" [independent dimension]
├── "cotton"
├── "silk"
└── "leather"
```
### Content Organization
Organize content (geometry, materials) to align with variant structure:
```
/Characters/Hero
├── Geometry (LOD variants)
│ ├── high_poly (for "high" LOD)
│ ├── medium_poly (for "medium" LOD)
│ └── low_poly (for "low" LOD)
├── Materials (material variants)
│ ├── Material_Metal
│ ├── Material_Plastic
│ └── Material_Rubber
└── Animations (animation variants)
├── Anim_Run_Fast
├── Anim_Run_Slow
└── Anim_Walk
```
### Variant Composition Strategy
**Strategy 1: Content Swapping (Recommended)**
Different geometry/materials per option:
```
✓ Efficient for: Material variations, LOD transitions
✗ Inefficient for: Many similar options with minor differences
```
**Strategy 2: Property Overrides**
Same content with different properties:
```
✓ Efficient for: Color variants, parameter adjustments
✗ Inefficient for: Major structural changes
```
**Strategy 3: Hybrid Approach**
Combine content swapping and property overrides:
```
✓ Use content swapping for major differences (LOD, materials)
✓ Use property overrides for minor adjustments (colors, scales)
```
## Naming Conventions
### Variant Set Names
Use snake_case for variant set names:
```
✓ GOOD:
level_of_detail
material_finish
assembly_configuration
regional_variant
✗ BAD:
LevelOfDetail
materialFinish
assembly-configuration
```
### Option Names
Use descriptive, lowercase names:
```
✓ GOOD:
high, medium, low (clarity)
plastic, metal, rubber (material types)
damaged, scratched, pristine (conditions)
✗ BAD:
h, m, l (abbreviations)
mtl1, mtl2, mtl3 (cryptic)
var_a, var_b, var_c (meaningless)
```
### Description Fields
Always provide descriptions for complex or non-obvious options:
```cpp
VariantOption option;
option.name = "ultra_high_poly";
option.description = "Ultra-high detail geometry (50M polygons, 4K textures)";
option.description += " - Recommended for close-up shots and hero renders";
```
## Performance Optimization
### 1. Minimize Variant Combinations
Excessive combinations hurt performance:
```
✗ BAD (96 combinations):
lod: [high, medium, low] (3 options)
material: [gold, silver, bronze] (3 options)
color: [red, green, blue, black] (4 options)
size: [small, medium, large] (4 options)
finish: [matte, glossy, metallic] (3 options)
Total: 3 × 3 × 4 × 4 × 3 = 432 combinations
✓ GOOD (12 combinations):
lod: [high, medium, low] (3 options)
variant: [gold, silver, bronze] (3 options)
Total: 3 × 3 = 9 combinations
(Other variations via materials/properties)
```
### 2. Lazy Load Variant Content
Load variant content only when selected:
```cpp
class SmartVariantManager {
std::unordered_map<std::string, ContentCache> cache;
void SelectVariant(const std::string& variant) {
if (cache.find(variant) == cache.end()) {
// Load on-demand
cache[variant] = LoadVariantContent(variant);
}
// Apply cached content
ApplyContent(cache[variant]);
}
};
```
### 3. Use Indices Instead of Names
In performance-critical code, use indices:
```cpp
// ✓ GOOD (O(1) lookup):
manager.SelectVariantByIndex(group_id, set_id, option_index);
// ✗ SLOWER (string comparison):
manager.SelectVariant(group_id, set_name, option_name);
```
### 4. Batch Variant Selections
When changing multiple variants, batch the selections:
```cpp
// ✓ GOOD (single state update):
std::vector<VariantSelection> selections = {
{group_0, set_0, index_a},
{group_1, set_1, index_b},
{group_2, set_2, index_c}
};
manager.ApplySelections(selections);
// ✗ INEFFICIENT (multiple updates):
manager.SelectVariant(group_0, set_0_name, option_a);
manager.SelectVariant(group_1, set_1_name, option_b);
manager.SelectVariant(group_2, set_2_name, option_c);
```
### 5. Profile Variant Operations
Measure variant switching performance:
```cpp
auto start = std::chrono::high_resolution_clock::now();
manager.SelectVariant(group_id, set_name, option_name);
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Selection took: " << duration.count() << "ms\n";
```
## Integration Patterns
### Pattern 1: Variant UI Panel
Create a UI for variant selection:
```python
class VariantPanel:
def __init__(self, manager: VariantManager):
self.manager = manager
self.groups = manager.get_variant_groups()
def render_variants(self):
for i, group in enumerate(self.groups):
print(f"\n{group.prim_path}")
for j, var_set in enumerate(group.variant_sets):
print(f" {var_set.name}:")
for k, option in enumerate(var_set.options):
selected = "" if self.is_selected(i, j, k) else " "
print(f" [{selected}] {option.name}")
def on_variant_clicked(self, group_id, set_id, option_id):
self.manager.select_variant_by_index(group_id, set_id, option_id)
```
### Pattern 2: Variant History/Undo
Maintain variant selection history:
```cpp
class VariantHistory {
std::vector<std::vector<VariantSelection>> history;
size_t current_index = 0;
void Push(const std::vector<VariantSelection>& selections) {
// Remove forward history if we're not at the end
history.erase(history.begin() + current_index + 1, history.end());
history.push_back(selections);
current_index++;
}
bool Undo() {
if (current_index > 0) {
current_index--;
ApplySelections(history[current_index]);
return true;
}
return false;
}
bool Redo() {
if (current_index < history.size() - 1) {
current_index++;
ApplySelections(history[current_index]);
return true;
}
return false;
}
};
```
### Pattern 3: Variant Presets
Save and restore variant configurations:
```python
class VariantPreset:
def __init__(self, name: str):
self.name = name
self.selections = []
def save(self, manager: DefaultVariantManager):
self.selections = list(manager.get_all_selections())
def apply(self, manager: DefaultVariantManager):
for selection in self.selections:
manager.apply_selection(selection)
def to_json(self):
return json.dumps([{
'group_id': s.variant_group_id,
'set_id': s.variant_set_id,
'option': s.selected_option_index
} for s in self.selections])
```
### Pattern 4: Variant Export
Export to different formats based on variant selection:
```cpp
void ExportWithVariant(const std::string& variant,
const std::string& output_format) {
// Select variant
manager.SelectVariant(0, "material", variant);
// Export based on format
if (output_format == "glb") {
ExportGLB(scene, "model_" + variant + ".glb");
} else if (output_format == "usda") {
ExportUSDA(stage, "model_" + variant + ".usda");
}
}
```
## Common Pitfalls
### Pitfall 1: Case-Sensitive Names
Variant names are case-sensitive:
```cpp
// These are DIFFERENT:
manager.SelectVariant(0, "color", "Red"); // May fail
manager.SelectVariant(0, "color", "red"); // Correct
```
**Solution:** Use lowercase consistently.
### Pitfall 2: Forgetting Default Index
Not setting default_option_index:
```cpp
// ✗ WRONG (no default):
variant_set.options = [{name: "high"}, {name: "low"}];
// ✓ CORRECT:
variant_set.options = [{name: "high"}, {name: "low"}];
variant_set.default_option_index = 0; // "high" is default
```
### Pitfall 3: Invalid Index Access
Accessing variant options with wrong indices:
```cpp
// ✗ WRONG (may crash):
auto option = variant_set.options[999];
// ✓ CORRECT (bounds checking):
if (option_index >= 0 && option_index < variant_set.options.size()) {
auto option = variant_set.options[option_index];
}
```
### Pitfall 4: Modifying Shared References
Modifying variant data that's referenced elsewhere:
```cpp
// ✗ WRONG (modifying shared data):
auto* group = manager.FindVariantGroup(path);
group->prim_path = "new_path"; // Affects other references
// ✓ CORRECT (work with copies):
auto groups = manager.GetVariantGroups();
for (auto& group : groups) {
// Read-only operations, or
// Copy data before modifying
}
```
### Pitfall 5: Excessive Nesting Depth
Using too many nesting levels:
```
✗ BAD (5 levels):
Level 1: Character type
Level 2: Outfit type
Level 3: Color
Level 4: Texture quality
Level 5: Special effects
✓ GOOD (2-3 levels):
Level 1: Character type → Outfit type
Level 2: Color variant
(Texture quality as property override, not nested variant)
```
## Testing and Validation
### Test 1: Variant Coverage
Ensure all variant paths are tested:
```cpp
TEST(VariantTest, AllVariantCombinations) {
for (const auto& group : scene.variant_groups) {
for (const auto& var_set : group.variant_sets) {
for (const auto& option : var_set.options) {
EXPECT_TRUE(manager.SelectVariant(
group.id, var_set.name, option.name));
ValidateVariantContent(scene);
}
}
}
}
```
### Test 2: Selection Validation
Verify selections are applied correctly:
```cpp
TEST(VariantTest, SelectionValidation) {
manager.SelectVariant(0, "color", "red");
const auto* sel = manager.GetCurrentSelection(0);
EXPECT_NE(sel, nullptr);
EXPECT_EQ(sel->selected_option_index, 0); // "red" is index 0
}
```
### Test 3: Performance Benchmarking
Measure variant switching performance:
```cpp
TEST(VariantTest, PerformanceBenchmark) {
const int ITERATIONS = 1000;
auto start = std::chrono::high_resolution_clock::now();
for (int i = 0; i < ITERATIONS; i++) {
manager.SelectVariant(0, "lod", "high");
manager.SelectVariant(0, "lod", "low");
}
auto end = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
auto avg = duration.count() / (double)(ITERATIONS * 2);
EXPECT_LT(avg, 1.0); // Should be < 1ms per selection
}
```
### Test 4: Variant Data Integrity
Ensure variant data is correctly extracted:
```cpp
TEST(VariantConverterTest, DataIntegrity) {
const auto& group = scene.variant_groups[0];
EXPECT_FALSE(group.prim_path.empty());
EXPECT_FALSE(group.variant_sets.empty());
for (const auto& var_set : group.variant_sets) {
EXPECT_FALSE(var_set.name.empty());
EXPECT_FALSE(var_set.options.empty());
EXPECT_GE(var_set.default_option_index, 0);
EXPECT_LT(var_set.default_option_index, var_set.options.size());
}
}
```
### Test 5: Nested Variant Validation
Verify nested variants are correctly handled:
```cpp
TEST(VariantTest, NestedVariantValidation) {
for (const auto& group : scene.variant_groups) {
for (const auto& var_set : group.variant_sets) {
for (const auto& option : var_set.options) {
// Check nesting depth
uint32_t depth = 1;
for (const auto& nested : option.nested_variant_sets) {
EXPECT_LE(++depth, MAX_NESTING_DEPTH);
}
}
}
}
}
```
## Summary
Key takeaways for variant best practices:
1. **Design for clarity** - Use self-documenting structures
2. **Limit complexity** - Keep nesting and combinations reasonable
3. **Follow conventions** - Use consistent naming and ordering
4. **Optimize performance** - Minimize variant combinations and use indices
5. **Integrate thoughtfully** - Use patterns for UI, undo, presets, export
6. **Avoid pitfalls** - Watch for case sensitivity, bounds checking, defaults
7. **Test thoroughly** - Validate all variant combinations and paths
## See Also
- [VARIANT_USAGE_GUIDE.md](VARIANT_USAGE_GUIDE.md) - Comprehensive API reference
- USD Variant Specification: https://openusd.org/docs/api/class_usd_variant_set.html
- TinyUSDZ Variant Examples: `python/examples/variant_example.py`

599
doc/VARIANT_USAGE_GUIDE.md Normal file
View File

@@ -0,0 +1,599 @@
# USD Variant Support in TinyUSDZ
This guide covers how to work with USD variants using TinyUSDZ's comprehensive variant support system.
## Table of Contents
1. [Overview](#overview)
2. [Quick Start](#quick-start)
3. [Architecture](#architecture)
4. [API Reference](#api-reference)
5. [CLI Tools](#cli-tools)
6. [Python Bindings](#python-bindings)
7. [Best Practices](#best-practices)
8. [Examples](#examples)
## Overview
USD variants are a powerful feature that allow you to create multiple, mutually exclusive representations of a USD asset. TinyUSDZ provides comprehensive support for:
- **Variant Extraction**: Convert USD variant structures into RenderScene variant groups
- **Variant Analysis**: Query and analyze variant complexity metrics
- **Variant Selection**: Dynamically select different variants for rendering
- **Nested Variants**: Support for USD's nested variant hierarchy (up to 3 levels)
- **Change Tracking**: Track what content changes when variants are selected
### Common Use Cases
- **Level of Detail (LOD)**: Provide different geometry complexity levels for different viewing distances
- **Material Variants**: Offer different material or color options for assets
- **Geometry Variations**: Support different shapes, configurations, or assembly options
- **Asset Variants**: Multiple versions of characters, props, or environments
- **Locale Variants**: Different versions for different regions or languages
## Quick Start
### Using the Command-Line Tools
#### List variants in a USD file:
```bash
variant-lister model.usda
variant-lister model.usda --summary
variant-lister model.usda --json
```
#### Analyze variant complexity:
```bash
variant-analyzer model.usda
variant-analyzer model.usda --detailed
variant-analyzer model.usda --json
```
### Using C++ API
```cpp
#include "tinyusdz.hh"
#include "tydra/variant-support.hh"
#include "tydra/variant-converter.hh"
using namespace tinyusdz;
using namespace tinyusdz::tydra;
// Load USD file
Stage stage;
std::string err;
bool ret = LoadUSDFromFile("model.usda", &stage, nullptr, &err);
// Extract variants
RenderScene scene;
VariantConverter converter;
if (converter.ConvertVariants(stage, &scene, &err)) {
// Work with variants
DefaultVariantManager manager;
manager.SetVariantGroups(scene.variant_groups);
// Select a variant
manager.SelectVariant(0, "color", "red");
// Get statistics
auto stats = manager.GetStatistics();
std::cout << "Found " << stats.num_variant_groups << " variant groups\n";
}
```
### Using Python API
```python
import tinyusdz
# Load USD file
stage = tinyusdz.load_usd_from_file("model.usda")
# Extract variants
scene = tinyusdz.tydra.RenderScene()
converter = tinyusdz.tydra.VariantConverter()
converter.convert_variants(stage, scene)
# Create manager and select variants
manager = tinyusdz.tydra.DefaultVariantManager()
manager.set_variant_groups(scene.variant_groups)
# List available options
for group in scene.variant_groups:
print(f"Prim: {group.prim_path}")
for var_set in group.variant_sets:
options = [opt.name for opt in var_set.options]
print(f" {var_set.name}: {options}")
# Select a variant
manager.select_variant(0, "color", "red")
```
## Architecture
### Core Data Structures
#### VariantOption
Represents a single variant option within a variant set.
```cpp
struct VariantOption {
std::string name; // Option name
std::string description; // Optional description
std::vector<int32_t> mesh_ids; // Associated mesh IDs
std::vector<int32_t> material_ids; // Associated material IDs
std::vector<int32_t> node_ids; // Affected node IDs
std::vector<int32_t> animation_ids; // Animation IDs
std::map<std::string, std::string> property_overrides; // Property changes
std::vector<std::shared_ptr<VariantSet>> nested_variant_sets; // Nested variants
};
```
#### VariantSet
A set of mutually exclusive variant options.
```cpp
struct VariantSet {
std::string name; // Set name (e.g., "color", "lod")
std::vector<VariantOption> options; // Available options
int32_t default_option_index{0}; // Default option index
int32_t parent_prim_id{-1}; // Parent for nested variants
std::string parent_variant_option_name; // Parent option name
};
```
#### VariantGroup
All variant sets for a specific prim/node.
```cpp
struct VariantGroup {
std::string prim_path; // USD prim path
std::vector<VariantSet> variant_sets; // All variant sets for this prim
int32_t affected_node_id{-1}; // Primary node affected
std::vector<int32_t> secondary_node_ids; // Secondary nodes affected
};
```
#### VariantSelection
Records which variant option is currently selected.
```cpp
struct VariantSelection {
int32_t variant_group_id; // Which group
int32_t variant_set_id; // Which set in the group
int32_t selected_option_index; // Which option is selected
};
```
### Variant Manager
The `DefaultVariantManager` provides high-level APIs for working with variants:
**Key Methods:**
- `HasVariants()` - Check if any variants exist
- `FindVariantGroup(prim_path)` - Get variant group by prim path
- `SelectVariant(group_id, set_name, option_name)` - Select a variant option
- `SelectVariantByIndex(group_id, set_id, option_index)` - Select by index
- `GetCurrentSelection(group_id)` - Get current selection
- `ResetToDefaults()` - Reset to default options
- `GetStatistics()` - Get variant metrics
### Variant Converter
The `VariantConverter` extracts USD variant information and maps it to RenderScene:
```cpp
class VariantConverter {
public:
// Main entry point: convert all variants from USD Stage to RenderScene
bool ConvertVariants(const Stage& stage, RenderScene* scene, std::string* err);
// Configure maximum nesting depth to process
void SetMaxNestingDepth(uint32_t depth);
};
```
## API Reference
### C++ API
#### VariantConverter
```cpp
// Extract variant information from a USD Stage
bool ConvertVariants(const Stage& stage, RenderScene* scene, std::string* err);
// Set maximum nesting depth (default: 3)
void SetMaxNestingDepth(uint32_t depth);
```
#### DefaultVariantManager
```cpp
// Initialize with variant groups
void SetVariantGroups(const std::vector<VariantGroup>& groups);
// Query variant information
bool HasVariants() const;
VariantGroup* FindVariantGroup(const std::string& prim_path);
VariantSet* FindVariantSet(int32_t group_id, const std::string& set_name);
VariantOption* FindVariantOption(int32_t group_id, int32_t set_id,
const std::string& option_name);
// Select variants
bool SelectVariant(int32_t group_id, const std::string& set_name,
const std::string& option_name, std::string* err = nullptr);
bool SelectVariantByIndex(int32_t group_id, int32_t set_id,
int32_t option_index, std::string* err = nullptr);
// Query selections
const VariantSelection* GetCurrentSelection(int32_t group_id) const;
const std::vector<VariantSelection>& GetAllSelections() const;
// Reset state
bool ResetToDefaults(std::string* err = nullptr);
// Statistics
VariantStatistics GetStatistics() const;
```
### Python API
All C++ classes are exposed to Python with snake_case method names:
```python
manager.has_variants()
manager.find_variant_group(prim_path)
manager.select_variant(group_id, set_name, option_name)
manager.get_current_selection(group_id)
manager.get_statistics()
manager.reset_to_defaults()
```
## CLI Tools
### variant-lister
Lists all variants in a USD file with tree-format display.
```bash
# Basic usage
variant-lister model.usda
# Show summary statistics
variant-lister model.usda --summary
# JSON output
variant-lister model.usda --json
# Verbose output
variant-lister model.usda --verbose
```
**Output:**
```
=== Variants in model.usda ===
├─ Prim: /Root/Character
└─ VariantSet: "lod"
├─ Default: high
├─ "high" [+nested: 1]
│ └─ VariantSet: "material" (2 options)
└─ "low" [+nested: 1]
└─ VariantSet: "material" (1 options)
```
### variant-analyzer
Analyzes variant complexity and provides recommendations.
```bash
# Basic analysis
variant-analyzer model.usda
# Detailed breakdown
variant-analyzer model.usda --detailed
# JSON output
variant-analyzer model.usda --json
```
**Output:**
```
=== Variant Complexity Analysis ===
File: model.usda
=== Summary ===
Prims with variants: 2
Total variant sets: 3
Total variant options: 8
=== Statistics ===
Max variants per prim: 2
Max options per set: 4
Avg options per set: 2.67
Max nesting depth: 2
Total combinations: 12
=== Complexity Assessment ===
✓ Low nesting depth (2)
✓ Manageable variant combinations (12)
⚡ Medium-sized variant set (4 options)
```
## Python Bindings
### Installation
Python bindings are included in the standard TinyUSDZ package:
```bash
pip install tinyusdz
```
### Available Classes
- `tinyusdz.tydra.VariantOption` - Variant option definition
- `tinyusdz.tydra.VariantSet` - Set of variant options
- `tinyusdz.tydra.VariantGroup` - All variants for a prim
- `tinyusdz.tydra.VariantSelection` - Current variant selection
- `tinyusdz.tydra.VariantConverter` - Extract variants from USD
- `tinyusdz.tydra.DefaultVariantManager` - Manage variant selections
- `tinyusdz.tydra.VariantStatistics` - Variant metrics
### Example Usage
```python
import tinyusdz
# Load USD
stage = tinyusdz.load_usd_from_file("model.usda")
# Extract variants
scene = tinyusdz.tydra.RenderScene()
converter = tinyusdz.tydra.VariantConverter()
converter.convert_variants(stage, scene)
# Manage variants
manager = tinyusdz.tydra.DefaultVariantManager()
manager.set_variant_groups(scene.variant_groups)
# Select variant
if manager.has_variants():
groups = manager.get_variant_groups()
for i, group in enumerate(groups):
print(f"Group {i}: {group.prim_path}")
for var_set in group.variant_sets:
print(f" {var_set.name}: {[o.name for o in var_set.options]}")
# Select first option from first set
if group.variant_sets:
var_set = group.variant_sets[0]
option_name = var_set.options[0].name if var_set.options else None
if option_name:
manager.select_variant(i, var_set.name, option_name)
```
## Best Practices
### 1. Organize Variants Hierarchically
Use nested variants for complex variation patterns:
```
Asset/
├── VariantSet "lod"
│ ├── "high" (nested: material variant)
│ ├── "medium" (nested: material variant)
│ └── "low" (nested: material variant)
└── VariantSet "color"
├── "red"
├── "blue"
└── "green"
```
### 2. Use Meaningful Names
Choose clear, descriptive names:
```
✓ Good names:
- "lod", "variant_lod", "lod_level"
- "material_type", "material_variant"
- "color_variant", "color"
✗ Avoid:
- "v1", "v2", "var1"
- "opt1", "option_a", "option_b"
```
### 3. Set Appropriate Defaults
Always specify meaningful default selections:
```cpp
variant_set.default_option_index = 0; // First option is default
```
### 4. Validate Variant Selections
Always check return values:
```cpp
std::string err;
if (!manager.SelectVariant(0, "color", "red", &err)) {
std::cerr << "Failed to select variant: " << err << "\n";
}
```
### 5. Limit Nesting Depth
Keep variant nesting to 3 levels maximum for performance:
```
Level 1: Prim variant sets
Level 2: Option variant sets
Level 3: Nested option variant sets
```
### 6. Document Variant Content
Add descriptions to important variants:
```cpp
option.description = "High-detail geometry (10M polygons)";
```
### 7. Cache Variant Selections
For frequently accessed variants, store selections:
```python
# Cache variant selection
current_lod = manager.get_current_selection(0)
```
## Examples
### Example 1: Load and List Variants
**C++:**
```cpp
#include "tinyusdz.hh"
#include "tydra/variant-converter.hh"
using namespace tinyusdz;
Stage stage;
LoadUSDFromFile("model.usda", &stage, nullptr, nullptr);
RenderScene scene;
VariantConverter converter;
converter.ConvertVariants(stage, &scene, nullptr);
std::cout << "Found " << scene.variant_groups.size() << " variant groups:\n";
for (const auto& group : scene.variant_groups) {
std::cout << group.prim_path << ":\n";
for (const auto& var_set : group.variant_sets) {
std::cout << " " << var_set.name << ": ";
for (const auto& opt : var_set.options) {
std::cout << opt.name << " ";
}
std::cout << "\n";
}
}
```
**Python:**
```python
import tinyusdz
stage = tinyusdz.load_usd_from_file("model.usda")
scene = tinyusdz.tydra.RenderScene()
converter = tinyusdz.tydra.VariantConverter()
converter.convert_variants(stage, scene)
print(f"Found {len(scene.variant_groups)} variant groups:")
for group in scene.variant_groups:
print(f"{group.prim_path}:")
for var_set in group.variant_sets:
options = [opt.name for opt in var_set.options]
print(f" {var_set.name}: {options}")
```
### Example 2: Select Variants
**C++:**
```cpp
DefaultVariantManager manager;
manager.SetVariantGroups(scene.variant_groups);
// Select "high" LOD
if (manager.SelectVariant(0, "lod", "high")) {
std::cout << "Switched to high LOD\n";
}
// Get current selection
const auto* selection = manager.GetCurrentSelection(0);
if (selection) {
std::cout << "Current selection index: " << selection->selected_option_index << "\n";
}
```
**Python:**
```python
manager = tinyusdz.tydra.DefaultVariantManager()
manager.set_variant_groups(scene.variant_groups)
# Select "high" LOD
if manager.select_variant(0, "lod", "high"):
print("Switched to high LOD")
# Get current selection
selection = manager.get_current_selection(0)
if selection:
print(f"Current selection: {selection.selected_option_index}")
```
### Example 3: Analyze Complexity
```bash
# Analyze variant complexity
variant-analyzer model.usda --detailed
# Output:
# Prim 1: /Characters/Hero
# Variant sets: 2
# - "lod": 3 options [with nested variants]
# Possible combinations: 6
# - "material": 2 options
# Possible combinations: 2
```
## Troubleshooting
### No variants found in file
**Cause:** File doesn't contain variant definitions or they use unsupported structure
**Solution:**
1. Verify file contains variant sets in USD
2. Check with `variant-lister model.usda`
3. Ensure variants are defined in the prim spec, not overrides
### Variant selection fails
**Cause:** Invalid variant set or option name, or selection already active
**Solution:**
1. List available options: `variant-lister model.usda`
2. Check for typos in names (case-sensitive)
3. Verify variant group ID is valid (0-based index)
### Slow variant operations
**Cause:** High nesting depth or many variants
**Solution:**
1. Check complexity: `variant-analyzer model.usda`
2. Reduce nesting depth if possible
3. Consolidate related variants
## Performance Characteristics
| Operation | Complexity | Notes |
|-----------|-----------|-------|
| Load USD file | O(n) | n = file size |
| Convert variants | O(p * v) | p = prims, v = avg variants |
| Select variant | O(1) | Hash-based lookup |
| Query selection | O(1) | Direct array access |
| Get statistics | O(p * v) | One-time calculation |
| Reset defaults | O(s) | s = num selections |
## See Also
- [USD Variant Documentation](https://openusd.org/docs/api/class_usd_variant_set.html)
- [glTF Material Variants Extension](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_variants)
- TinyUSDZ Main Documentation

View File

@@ -0,0 +1,121 @@
#!/usr/bin/env python3
"""
Example: Working with USD Variants using TinyUSDZ Python Bindings
This example demonstrates how to:
1. Load a USD file
2. Extract variant information using VariantConverter
3. List all available variants
4. Select different variants
5. Query variant statistics
"""
import tinyusdz
def list_variants(stage):
"""Extract and list all variants in a USD stage."""
scene = tinyusdz.tydra.RenderScene()
converter = tinyusdz.tydra.VariantConverter()
if not converter.convert_variants(stage, scene):
print("Failed to convert variants")
return None
print(f"Found {len(scene.variant_groups)} variant groups:\n")
for group in scene.variant_groups:
print(f"Prim: {group.prim_path}")
print(f" Variant sets: {len(group.variant_sets)}")
for variant_set in group.variant_sets:
print(f" - {variant_set.name}")
print(f" Options: {[opt.name for opt in variant_set.options]}")
print(f" Default: {variant_set.options[variant_set.default_option_index].name}")
print()
return scene
def work_with_variants(scene):
"""Demonstrate how to use the VariantManager API."""
manager = tinyusdz.tydra.DefaultVariantManager()
manager.set_variant_groups(scene.variant_groups)
# Get variant statistics
stats = manager.get_statistics()
print("Variant Statistics:")
print(f" Total groups: {stats.num_variant_groups}")
print(f" Total sets: {stats.num_variant_sets}")
print(f" Total options: {stats.num_variant_options}")
print(f" Max nesting depth: {stats.max_nesting_depth}\n")
# Find specific variant group
if manager.has_variants():
# Get first variant group
groups = manager.get_variant_groups()
if groups:
first_group = groups[0]
print(f"First variant group: {first_group.prim_path}")
# Try to select a variant
if first_group.variant_sets:
var_set = first_group.variant_sets[0]
if var_set.options:
option_name = var_set.options[0].name
print(f" Selecting '{option_name}' from variant set '{var_set.name}'")
# Select the variant
success = manager.select_variant(0, var_set.name, option_name)
if success:
print(f" ✓ Variant selection successful")
# Get current selection
selection = manager.get_current_selection(0)
if selection:
print(f" Current selection: {selection.selected_option_index}")
else:
print(f" ✗ Failed to select variant")
# Reset to defaults
print("\n Resetting to defaults...")
manager.reset_to_defaults()
print(" ✓ Reset complete")
def main():
"""Main example demonstrating variant API usage."""
import sys
if len(sys.argv) < 2:
print("Usage: python variant_example.py <usd_file>")
print("\nExample:")
print(" python variant_example.py model.usda")
sys.exit(1)
usd_file = sys.argv[1]
# Load USD file
print(f"Loading USD file: {usd_file}\n")
stage = tinyusdz.load_usd_from_file(usd_file)
if not stage:
print(f"Error: Failed to load {usd_file}")
sys.exit(1)
# Extract and list variants
print("=" * 60)
print("VARIANT LISTING")
print("=" * 60 + "\n")
scene = list_variants(stage)
if scene and scene.variant_groups:
# Work with variants
print("=" * 60)
print("VARIANT MANAGEMENT")
print("=" * 60 + "\n")
work_with_variants(scene)
else:
print("No variants found in the USD file.")
if __name__ == "__main__":
main()

View File

@@ -7,6 +7,8 @@
#include "tinyusdz.hh"
#include "prim-pprint.hh"
#include "tydra/render-data.hh"
#include "tydra/variant-support.hh"
#include "tydra/variant-converter.hh"
//
#include "value-type-macros.inc"
@@ -508,5 +510,94 @@ PYBIND11_MODULE(ctinyusdz, m) {
m_tydra.def("to_render_scene", [](const Stage &stage) {
py::print("TODO");
}, py::arg("config") = tydra::RenderSceneConverterConfig());
// Variant support bindings
py::class_<tydra::VariantOption>(m_tydra, "VariantOption")
.def(py::init<>())
.def_readwrite("name", &tydra::VariantOption::name)
.def_readwrite("description", &tydra::VariantOption::description)
.def_readwrite("mesh_ids", &tydra::VariantOption::mesh_ids)
.def_readwrite("material_ids", &tydra::VariantOption::material_ids)
.def_readwrite("node_ids", &tydra::VariantOption::node_ids)
.def_readwrite("animation_ids", &tydra::VariantOption::animation_ids)
.def_readwrite("property_overrides", &tydra::VariantOption::property_overrides)
;
py::class_<tydra::VariantSet>(m_tydra, "VariantSet")
.def(py::init<>())
.def_readwrite("name", &tydra::VariantSet::name)
.def_readwrite("options", &tydra::VariantSet::options)
.def_readwrite("default_option_index", &tydra::VariantSet::default_option_index)
.def_readwrite("parent_prim_id", &tydra::VariantSet::parent_prim_id)
.def_readwrite("parent_variant_option_name", &tydra::VariantSet::parent_variant_option_name)
;
py::class_<tydra::VariantGroup>(m_tydra, "VariantGroup")
.def(py::init<>())
.def_readwrite("prim_path", &tydra::VariantGroup::prim_path)
.def_readwrite("variant_sets", &tydra::VariantGroup::variant_sets)
.def_readwrite("affected_node_id", &tydra::VariantGroup::affected_node_id)
.def_readwrite("secondary_node_ids", &tydra::VariantGroup::secondary_node_ids)
;
py::class_<tydra::VariantSelection>(m_tydra, "VariantSelection")
.def(py::init<>())
.def_readwrite("variant_group_id", &tydra::VariantSelection::variant_group_id)
.def_readwrite("variant_set_id", &tydra::VariantSelection::variant_set_id)
.def_readwrite("selected_option_index", &tydra::VariantSelection::selected_option_index)
;
py::class_<tydra::VariantConverter>(m_tydra, "VariantConverter")
.def(py::init<>())
.def("convert_variants", [](tydra::VariantConverter &converter, const Stage &stage, RenderScene &scene) -> bool {
std::string err;
return converter.ConvertVariants(stage, &scene, &err);
}, py::arg("stage"), py::arg("scene"))
;
// VariantStatistics
py::class_<tydra::VariantStatistics>(m_tydra, "VariantStatistics")
.def(py::init<>())
.def_readwrite("num_variant_groups", &tydra::VariantStatistics::num_variant_groups)
.def_readwrite("num_variant_sets", &tydra::VariantStatistics::num_variant_sets)
.def_readwrite("num_variant_options", &tydra::VariantStatistics::num_variant_options)
.def_readwrite("max_nesting_depth", &tydra::VariantStatistics::max_nesting_depth)
;
py::class_<tydra::DefaultVariantManager>(m_tydra, "DefaultVariantManager")
.def(py::init<>())
.def("has_variants", &tydra::DefaultVariantManager::HasVariants)
.def("find_variant_group", &tydra::DefaultVariantManager::FindVariantGroup,
py::arg("prim_path"), py::return_value_policy::reference)
.def("find_variant_set", [](tydra::DefaultVariantManager &mgr, int32_t group_id, const std::string &set_name) -> tydra::VariantSet* {
return mgr.FindVariantSet(group_id, set_name);
}, py::arg("group_id"), py::arg("set_name"), py::return_value_policy::reference)
.def("find_variant_option", [](tydra::DefaultVariantManager &mgr, int32_t group_id, int32_t set_id, const std::string &option_name) -> tydra::VariantOption* {
return mgr.FindVariantOption(group_id, set_id, option_name);
}, py::arg("group_id"), py::arg("set_id"), py::arg("option_name"), py::return_value_policy::reference)
.def("select_variant", [](tydra::DefaultVariantManager &mgr, int32_t group_id, const std::string &set_name, const std::string &option_name) -> bool {
std::string err;
return mgr.SelectVariant(group_id, set_name, option_name, &err);
}, py::arg("group_id"), py::arg("set_name"), py::arg("option_name"))
.def("select_variant_by_index", [](tydra::DefaultVariantManager &mgr, int32_t group_id, int32_t set_id, int32_t option_index) -> bool {
std::string err;
return mgr.SelectVariantByIndex(group_id, set_id, option_index, &err);
}, py::arg("group_id"), py::arg("set_id"), py::arg("option_index"))
.def("get_current_selection", &tydra::DefaultVariantManager::GetCurrentSelection,
py::arg("group_id"), py::return_value_policy::reference)
.def("get_all_selections", &tydra::DefaultVariantManager::GetAllSelections,
py::return_value_policy::reference)
.def("reset_to_defaults", [](tydra::DefaultVariantManager &mgr) -> bool {
std::string err;
return mgr.ResetToDefaults(&err);
})
.def("get_mutable_variant_groups", &tydra::DefaultVariantManager::GetMutableVariantGroups,
py::return_value_policy::reference)
.def("get_variant_groups", &tydra::DefaultVariantManager::GetVariantGroups,
py::return_value_policy::reference)
.def("set_variant_groups", &tydra::DefaultVariantManager::SetVariantGroups,
py::arg("groups"))
.def("get_statistics", &tydra::DefaultVariantManager::GetStatistics)
;
}
}