Skip to content

Add OpenPBR version conversion tool (1.1 <-> 1.2)#307

Open
portsmouth wants to merge 3 commits into
AcademySoftwareFoundation:dev_1.2from
portsmouth:add-version-conversion
Open

Add OpenPBR version conversion tool (1.1 <-> 1.2)#307
portsmouth wants to merge 3 commits into
AcademySoftwareFoundation:dev_1.2from
portsmouth:add-version-conversion

Conversation

@portsmouth

Copy link
Copy Markdown
Contributor

Summary

Adds versioning/, a tool that converts an OpenPBR Surface parameter set between specification versions, in either direction, choosing the parameter correspondence that yields the closest match of the resulting appearance.

It is both a Python API and a CLI, and is architected so each future release (1.3, …) is added as a single adjacent migration step — multi-version conversions then compose automatically.

The closest-match philosophy

A version converter only changes parameters, so it can compensate only for differences that are themselves parameterized. Every 1.1↔1.2 difference is classified as:

  • A — Exactly invertible: a pure reparametrization; appearance identical after the remap (applied).
  • B — Lossy / bounded: mappable over part of the domain; clamped + warned elsewhere.
  • C — Not parameter-correctable: internal BSDF / reference-MaterialX-graph fixes that no parameter controls — reported as notes, never as parameter edits.

A warning-free, note-free result means the conversion is exact for that material.

The 1.1 ↔ 1.2 mapping (derived by diffing the v1.1 tag against the 1.2 spec)

Four parameters were added in 1.2 and none removed; two shared parameters were reinterpreted; one default changed.

Change Class Map
emission_weight (#231) + emission_luminance default 0→1000 A up: emission_weight=1; down: emission_luminance ← weight×luminance, drop weight
transmission_scatter coefficient↔albedo (#286) B Ω = −S/ln TS = −Ω·ln T per channel; clamp+warn in the grey-shift / dark-color / no-extinction regimes
specular_weight refraction decoupling (#247) B passthrough (matches the highlight) + warn when ≠1 and transmission active
specular_haze (#254) / specular_retroreflectivity (#255) A↑ / B↓ inert at default going up; dropped (warn if non-default) going down
coat-darkening #253, F82 clamp #256, input clamp #277, graph fixes #240/#251/#250/#292/#293 C reported as notes; no parameter change

Note: this surfaced that specular_haze (#254) is in the 1.2 spec but missing from CHANGELOG.md.

Architecture

A linear VERSION_ORDER ladder plus a registry of adjacent Migrations (each holding an upgrade and downgrade rule). convert_params walks the ladder and composes adjacent steps, so e.g. 1.1→1.3 runs 1.1→1.2 then 1.2→1.3. Adding 1.3 is a localized change — see the checklist in VERSIONING.md.

Two layers:

  • convert_params(params, from_version, to_version) — dependency-free dict core (stdlib only), embeddable in any app/renderer.
  • convert(input_path/input_string, …) — MaterialX .mtlx wrapper (requires the MaterialX package; otherwise self-contained). Touches only the inputs the conversion changed.

Contents

  • versioning/openpbr_version.py — core + MaterialX wrapper + CLI
  • versioning/test_openpbr_version.py — 32 numeric tests (closed-form maps, both round-trips, bounded-loss edge cases, and the chaining architecture via a synthetic 1.3). All passing; no rendering, no MaterialX dependency.
  • versioning/VERSIONING.md — logic reference + "adding a new version" checklist

Testing

$ python versioning/test_openpbr_version.py
32 passed, 0 failed (32 total)

CLI

python versioning/openpbr_version.py glass.mtlx --from 1.2 --to 1.1 -o glass_v1.1.mtlx

🤖 Generated with Claude Code

portsmouth and others added 3 commits June 10, 2026 22:00
Adds versioning/, a tool that converts an OpenPBR Surface parameter set
between specification versions, choosing the parameter correspondence that
yields the closest match of the resulting appearance.

Changes are classified as:
  A. Exactly invertible reparametrizations (applied)
  B. Lossy / bounded maps (applied + warned where they diverge)
  C. Internal BSDF / MaterialX-graph fixes (no remap; reported as notes)

1.1 <-> 1.2 mapping:
  - emission_weight (AcademySoftwareFoundation#231) + emission_luminance default 0->1000: exact fold
  - transmission_scatter coefficient<->albedo (AcademySoftwareFoundation#286): Omega = -S/ln(T)
  - specular_weight refraction decoupling (AcademySoftwareFoundation#247): passthrough + warn
  - specular_haze (AcademySoftwareFoundation#254) / specular_retroreflectivity (AcademySoftwareFoundation#255): inert at default

Architecture: a linear version ladder with a registry of adjacent migrations;
multi-version conversions (e.g. 1.1->1.3) compose automatically. Adding 1.3 is
a localized change (see VERSIONING.md).

  - openpbr_version.py:  dependency-free dict core + MaterialX .mtlx wrapper + CLI
  - test_openpbr_version.py: 32 numeric tests (maps, round-trips, chaining)
  - VERSIONING.md: logic reference + "adding a new version" checklist

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The value-only core cannot see node-connected inputs, so when
emission_luminance is texture-driven (rather than a literal value) the
1.1 -> 1.2 upgrade previously left emission_weight unauthored, defaulting it
to 0 and silently zeroing the textured emission.

The MaterialX wrapper now detects a connected emission_luminance and authors
the constant emission_weight = 1 (exact, since 1.2 emission is
weight * color * luminance), with a warning. The valued case is unchanged
(handled by the core).

Adds wrapper tests (skipped when MaterialX is absent) and documents the
valued-vs-connected distinction and the remaining application-owned coupled
case (transmission_scatter when transmission_color is textured) in
VERSIONING.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Adds VERSIONING.md section 4 as the source of truth for renderer/DCC plugin
authors (Arnold mtoa/htoa, etc.) implementing version migration natively.

Reframes a migration as a per-parameter transform with two realizations: fold
to a constant when the input is valued, or insert an equivalent node network
when the input is connected to an upstream output (value known only at render
time). Documents:

  - the five operation kinds (PASS / SET_CONST / TRANSFORM / DROP / RENAME)
  - the full 1.1 <-> 1.2 operation table, flagging which maps need a network
  - the exact MaterialX node networks for the TRANSFORM cases
    (transmission_scatter both directions; emission_luminance downgrade),
    including the T -> 1 singularity guard
  - that for connected inputs, Class-B author-time warnings degrade to
    in-graph clamps

The "adding a new version" checklist now points maintainers to document any
new TRANSFORM map's network in section 4.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant