Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ keywords = ["usd", "usda", "usdc", "usdz", "pxr"]
geom = []
lux = []
physics = []
render = []
shade = []
skel = []
serde = ["dep:serde", "half/serde"]
Expand Down Expand Up @@ -71,3 +72,7 @@ required-features = ["lux"]
[[test]]
name = "shade_reader"
required-features = ["shade"]

[[test]]
name = "render_reader"
required-features = ["render"]
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ Features from the C++ reference implementation not covered by the core specifica
| [UsdSkel](https://openusd.org/release/api/usd_skel_page_front.html) (skeletons, skinning) | :white_check_mark: | `0.4.0` | Schema reader behind `skel` feature: SkelRoot, Skeleton, SkelAnimation, BlendShape (incl. inbetween shapes), and SkelBindingAPI with namespace-inherited bindings<br>Remaining — authoring helpers; stage-level interpolation of SkelAnimation time samples (left to the consumer) |
| [UsdVol](https://openusd.org/release/api/usd_vol_page_front.html) (volumes) | :construction: | | |
| [UsdPhysics](https://openusd.org/release/api/usd_physics_page_front.html) schema reader | :white_check_mark: | `0.4.0` | Reader behind `physics` feature: all 8 prim types, the 7 single-apply APIs, and the LimitAPI / DriveAPI multi-apply schemas<br>Remaining — authoring helpers; collection evaluation for `CollisionGroup` (tracked under Spec 15) |
| [UsdRender](https://openusd.org/release/api/usd_render_page_front.html) | :construction: | | |
| [UsdRender](https://openusd.org/release/api/usd_render_page_front.html) | :white_check_mark: | `main` | Reader + authoring behind `render` feature: RenderSettingsBase / RenderSettings / RenderProduct / RenderVar / RenderPass, and the computed render spec (`compute_render_spec`) with product-overrides-settings inheritance, the aspect-ratio conform policy, render-var de-duplication, and per-level `namespacedSettings`<br>Remaining — RenderPass collection memberships (needs CollectionAPI evaluation); node-graph-driven namespaced settings (needs UsdShade value resolution); authoring `renderSettingsPrimPath` stage metadata; UsdRenderDenoisePass (dev schema) |
| UsdMedia, UsdUI, UsdProc | :construction: | | |
| [Flatten / export](https://openusd.org/release/api/flatten_utils_8h.html) | :construction: | | |
| [Namespace editing](https://openusd.org/release/api/class_usd_namespace_editor.html) | :construction: | | |
Expand Down
55 changes: 55 additions & 0 deletions fixtures/usdRender_scene.usda
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#usda 1.0
(
renderSettingsPrimPath = "/Render/settings"
)

def Xform "World"
{
def Camera "Camera"
{
float horizontalAperture = 24
float verticalAperture = 24
}
}

def Scope "Render"
{
def RenderSettings "settings"
{
rel camera = </World/Camera>
uniform int2 resolution = (1920, 1080)
uniform token aspectRatioConformPolicy = "expandAperture"
uniform token[] includedPurposes = ["default", "render"]
uniform token[] materialBindingPurposes = ["full", ""]
uniform token renderingColorSpace = "lin_rec709"
rel products = </Render/products/beauty>
int ri:hider:maxsamples = 64
}

def Scope "products"
{
def RenderProduct "beauty"
{
uniform token productType = "raster"
token productName = "beauty.exr"
uniform int2 resolution = (1024, 512)
rel orderedVars = [</Render/vars/color>, </Render/vars/alpha>]
}
}

def Scope "vars"
{
def RenderVar "color"
{
uniform token dataType = "color3f"
uniform token sourceType = "raw"
uniform string sourceName = "Ci"
}

def RenderVar "alpha"
{
uniform token dataType = "float"
uniform string sourceName = "a"
}
}
}
5 changes: 5 additions & 0 deletions fixtures/usdRender_session.usda
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
#usda 1.0
(
"""Session layer overriding the stage's renderSettingsPrimPath."""
renderSettingsPrimPath = "/Render/sessionSettings"
)
26 changes: 26 additions & 0 deletions src/pcp/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,32 @@ impl Cache {
Ok(targets)
}

/// Returns pseudo-root stage metadata, composing session-layer opinions
/// over the root layer (strongest first).
///
/// Unlike [`Self::root_layer_field`] — which is root-layer-only for the
/// spec 12.2.7 fields such as `defaultPrim` — general stage metadata
/// (e.g. `renderSettingsPrimPath`) honors a session-layer override,
/// matching C++ `UsdStage::GetMetadata`. A [`Value::ValueBlock`] in a
/// stronger layer blocks weaker opinions.
pub fn stage_metadata(&self, field: &str) -> Result<Option<Value>> {
let root = Path::abs_root();
// Slots `0..session_layer_count` hold the session layers; the root
// layer sits at `session_layer_count`. Walk session-then-root so the
// session opinion wins.
for index in 0..=self.stack.session_layer_count {
let Some(layer) = self.stack.layers.get(index) else {
break;
};
match layer.try_get(&root, field)? {
Some(value) if matches!(value.as_ref(), Value::ValueBlock) => return Ok(None),
Some(value) => return Ok(Some(value.into_owned())),
None => {}
}
}
Ok(None)
}

/// Returns pseudo-root layer metadata from the root layer only.
///
/// Session-layer and sublayer opinions are intentionally ignored here,
Expand Down
4 changes: 4 additions & 0 deletions src/schemas/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
//! | `skel` | `skel` | `UsdSkel` reader + skinning toolkit (Topology, AnimMapper, SkeletonResolver, SkinningResolver, pure-math LBS). |
//! | `lux` | `lux` | `UsdLux` reader (8 concrete light prims + LightAPI / ShapingAPI / ShadowAPI / LightListAPI). |
//! | `shade` | `shade` | `UsdShade` reader + authoring (Material / NodeGraph / Shader, connectable inputs / outputs, MaterialBindingAPI, UsdPreviewSurface). |
//! | `render` | `render` | `UsdRender` reader + authoring (RenderSettings / Product / Var / Pass + the computed render spec). |
//!
//! See [`registry`] for the eventual schema-registry surface
//! (currently a stub).
Expand All @@ -26,6 +27,7 @@
feature = "geom",
feature = "lux",
feature = "physics",
feature = "render",
feature = "shade",
feature = "skel"
))]
Expand All @@ -37,6 +39,8 @@ pub mod geom;
pub mod lux;
#[cfg(feature = "physics")]
pub mod physics;
#[cfg(feature = "render")]
pub mod render;
#[cfg(feature = "shade")]
pub mod shade;
#[cfg(feature = "skel")]
Expand Down
184 changes: 184 additions & 0 deletions src/schemas/render/author/base.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
//! Shared `RenderSettingsBase` authoring.
//!
//! [`SettingsBaseSetters`] is mixed into both the `RenderSettings` and
//! `RenderProduct` author handles (the `*ApiSetters` idiom) — the two
//! concrete schemas that inherit `RenderSettingsBase`, so they share the
//! camera + framing setters.

use anyhow::Result;

use crate::sdf::{Path, Value, Variability};
use crate::usd::{Prim, Stage};

use crate::schemas::common::{author_rel_targets, author_uniform_token};
use crate::schemas::render::tokens::*;
use crate::schemas::render::types::AspectRatioConformPolicy;

/// Setters for the attributes declared on `RenderSettingsBase`
/// (`resolution`, `pixelAspectRatio`, `aspectRatioConformPolicy`,
/// `dataWindowNDC`, the motion-blur / depth-of-field toggles, and the
/// `camera` relationship). Implementors expose their underlying prim.
pub trait SettingsBaseSetters<'s>: Sized {
/// Borrow the underlying prim handle.
fn prim(&self) -> &Prim<'s>;

/// Set `resolution` (`uniform int2`).
fn set_resolution(self, value: [i32; 2]) -> Result<Self> {
author_uniform_int2(self.prim().stage(), self.prim().path(), A_RESOLUTION, value)?;
Ok(self)
}

/// Set `pixelAspectRatio` (`uniform float`).
fn set_pixel_aspect_ratio(self, value: f32) -> Result<Self> {
author_uniform_float(self.prim().stage(), self.prim().path(), A_PIXEL_ASPECT_RATIO, value)?;
Ok(self)
}

/// Set `aspectRatioConformPolicy` (`uniform token`).
fn set_aspect_ratio_conform_policy(self, value: AspectRatioConformPolicy) -> Result<Self> {
author_uniform_token(
self.prim().stage(),
self.prim().path(),
A_ASPECT_RATIO_CONFORM_POLICY,
value.as_token(),
)?;
Ok(self)
}

/// Set `dataWindowNDC` (`uniform float4`) — `(xmin, ymin, xmax, ymax)`.
fn set_data_window_ndc(self, value: [f32; 4]) -> Result<Self> {
author_uniform_float4(self.prim().stage(), self.prim().path(), A_DATA_WINDOW_NDC, value)?;
Ok(self)
}

/// Set `instantaneousShutter` (`uniform bool`) — deprecated in the C++
/// `UsdRender` schema; prefer [`Self::set_disable_motion_blur`].
fn set_instantaneous_shutter(self, value: bool) -> Result<Self> {
author_uniform_bool(self.prim().stage(), self.prim().path(), A_INSTANTANEOUS_SHUTTER, value)?;
Ok(self)
}

/// Set `disableMotionBlur` (`uniform bool`).
fn set_disable_motion_blur(self, value: bool) -> Result<Self> {
author_uniform_bool(self.prim().stage(), self.prim().path(), A_DISABLE_MOTION_BLUR, value)?;
Ok(self)
}

/// Set `disableDepthOfField` (`uniform bool`).
fn set_disable_depth_of_field(self, value: bool) -> Result<Self> {
author_uniform_bool(self.prim().stage(), self.prim().path(), A_DISABLE_DEPTH_OF_FIELD, value)?;
Ok(self)
}

/// Set the `camera` relationship to the primary `UsdGeomCamera`.
fn set_camera(self, camera: impl Into<Path>) -> Result<Self> {
author_rel_targets(self.prim().stage(), self.prim().path(), REL_CAMERA, [camera.into()])?;
Ok(self)
}
}

// ── private uniform-attribute helpers ───────────────────────────────

fn author_uniform_int2(stage: &Stage, prim: &Path, name: &str, value: [i32; 2]) -> Result<()> {
let attr_path = prim.append_property(name)?;
stage
.create_attribute(attr_path, "int2")?
.set_variability(Variability::Uniform)?
.set_custom(false)?
.set(Value::Vec2i(value))?;
Ok(())
}

fn author_uniform_float(stage: &Stage, prim: &Path, name: &str, value: f32) -> Result<()> {
let attr_path = prim.append_property(name)?;
stage
.create_attribute(attr_path, "float")?
.set_variability(Variability::Uniform)?
.set_custom(false)?
.set(Value::Float(value))?;
Ok(())
}

fn author_uniform_float4(stage: &Stage, prim: &Path, name: &str, value: [f32; 4]) -> Result<()> {
let attr_path = prim.append_property(name)?;
stage
.create_attribute(attr_path, "float4")?
.set_variability(Variability::Uniform)?
.set_custom(false)?
.set(Value::Vec4f(value))?;
Ok(())
}

fn author_uniform_bool(stage: &Stage, prim: &Path, name: &str, value: bool) -> Result<()> {
let attr_path = prim.append_property(name)?;
stage
.create_attribute(attr_path, "bool")?
.set_variability(Variability::Uniform)?
.set_custom(false)?
.set(Value::Bool(value))?;
Ok(())
}

#[cfg(test)]
mod tests {
use super::*;
use crate::schemas::render::tokens::T_RENDER_SETTINGS;
use crate::schemas::render::{read_settings_base, ReadSettingsBase};
use crate::sdf;

/// Minimal host for the shared trait so it can be exercised before the
/// concrete `RenderSettings` / `RenderProduct` handles exist.
struct TestBase<'s> {
prim: Prim<'s>,
}
impl<'s> SettingsBaseSetters<'s> for TestBase<'s> {
fn prim(&self) -> &Prim<'s> {
&self.prim
}
}

#[test]
fn base_attrs_roundtrip() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
let prim = stage
.define_prim(sdf::path("/Render/Settings")?)?
.set_type_name(T_RENDER_SETTINGS)?;
TestBase { prim }
.set_resolution([1920, 1080])?
.set_pixel_aspect_ratio(2.0)?
.set_aspect_ratio_conform_policy(AspectRatioConformPolicy::CropAperture)?
.set_data_window_ndc([0.1, 0.2, 0.9, 0.8])?
.set_disable_motion_blur(true)?
.set_disable_depth_of_field(true)?
.set_camera(sdf::path("/World/Cam")?)?;

let base = read_settings_base(&stage, &sdf::path("/Render/Settings")?)?;
assert_eq!(base.resolution, [1920, 1080]);
assert!((base.pixel_aspect_ratio - 2.0).abs() < 1e-6);
assert_eq!(base.aspect_ratio_conform_policy, AspectRatioConformPolicy::CropAperture);
assert_eq!(base.data_window_ndc, [0.1, 0.2, 0.9, 0.8]);
assert!(base.disable_motion_blur);
assert!(base.disable_depth_of_field);
assert!(!base.instantaneous_shutter);
assert_eq!(base.camera.as_deref(), Some("/World/Cam"));
Ok(())
}

#[test]
fn base_attrs_fall_back_to_spec_defaults() -> Result<()> {
let stage = Stage::builder().in_memory("anon.usda")?;
stage
.define_prim(sdf::path("/Render/Settings")?)?
.set_type_name(T_RENDER_SETTINGS)?;

let base = read_settings_base(&stage, &sdf::path("/Render/Settings")?)?;
assert_eq!(base, ReadSettingsBase::default());
assert_eq!(base.resolution, [2048, 1080]);
assert_eq!(
base.aspect_ratio_conform_policy,
AspectRatioConformPolicy::ExpandAperture
);
assert_eq!(base.camera, None);
Ok(())
}
}
23 changes: 23 additions & 0 deletions src/schemas/render/author/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//! Authoring helpers for the [UsdRender](super) schema family.
//!
//! Same shape as the [`super::read`] surface, in reverse: each helper
//! writes a typed prim or attribute in the form the corresponding reader
//! decodes losslessly. Helpers wrap [`crate::usd::Stage`]'s authoring
//! entry points and only know the type tokens / attribute names / type
//! names / variability that Pixar's `usdRender/schema.usda` requires.
//!
//! Opinions land in the stage's current edit target; layer routing is the
//! caller's job via
//! [`Stage::set_edit_target`](crate::usd::Stage::set_edit_target).

mod base;
mod pass;
mod product;
mod settings;
mod var;

pub use base::SettingsBaseSetters;
pub use pass::{define_render_pass, RenderPassAuthor};
pub use product::{define_render_product, RenderProductAuthor};
pub use settings::{define_render_settings, RenderSettingsAuthor};
pub use var::{define_render_var, RenderVarAuthor};
Loading
Loading