mirror of
https://github.com/blender/blender
synced 2026-09-29 04:37:17 +03:00
USD: Support colorspace conversions when reading and writing USD files
On export, apply the UsdColorSpaceAPI schema to the root prim, materials lights, color primvras and shader prims for image textures. By default the root prim would have been enough, but it is possible to export with out a root prim and this keeps behavior consistent independent of that setting. On import, use UsdColorSpaceAPI::ComputeColorSpaceName to resolve the colorspace for each color attribute through the USD hierarchy, and convert from that colorspace to scene linear. Pull Request: projects.blender.org/blender/blender/pulls/157196
This commit is contained in:
parent
cc83054d82
commit
da852a9b79
13 changed files with 255 additions and 6 deletions
|
|
@ -3,6 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
|
||||
#include "BLI_map.hh"
|
||||
|
|
@ -127,18 +128,18 @@ void copy_primvar_to_blender_attribute(const pxr::UsdGeomPrimvar &primvar,
|
|||
break;
|
||||
case bke::AttrType::ColorFloat: {
|
||||
const pxr::SdfValueTypeName pv_type = primvar.GetTypeName();
|
||||
MutableSpan<ColorGeometry4f> colors = attribute.span.typed<ColorGeometry4f>();
|
||||
if (ELEM(pv_type,
|
||||
pxr::SdfValueTypeNames->Color3fArray,
|
||||
pxr::SdfValueTypeNames->Color3hArray,
|
||||
pxr::SdfValueTypeNames->Color3dArray))
|
||||
{
|
||||
copy_primvar_to_blender_buffer<pxr::GfVec3f>(
|
||||
primvar, time, face_indices, attribute.span.typed<ColorGeometry4f>());
|
||||
copy_primvar_to_blender_buffer<pxr::GfVec3f>(primvar, time, face_indices, colors);
|
||||
}
|
||||
else {
|
||||
copy_primvar_to_blender_buffer<pxr::GfVec4f>(
|
||||
primvar, time, face_indices, attribute.span.typed<ColorGeometry4f>());
|
||||
copy_primvar_to_blender_buffer<pxr::GfVec4f>(primvar, time, face_indices, colors);
|
||||
}
|
||||
colorspace_attr_to_scene_linear(primvar.GetAttr(), colors);
|
||||
} break;
|
||||
case bke::AttrType::Bool:
|
||||
copy_primvar_to_blender_buffer<bool>(
|
||||
|
|
@ -196,6 +197,7 @@ void copy_blender_attribute_to_primvar(const GVArray &attribute,
|
|||
copy_blender_buffer_to_primvar<ColorGeometry4f, pxr::GfVec4f>(
|
||||
attribute.typed<ColorGeometry4f>(), time, primvar, value_writer);
|
||||
}
|
||||
colorspace_apply_to_prim(primvar.GetAttr().GetPrim());
|
||||
break;
|
||||
case bke::AttrType::ColorByte:
|
||||
if (primvar.GetTypeName() == pxr::SdfValueTypeNames->Color3fArray) {
|
||||
|
|
@ -206,6 +208,7 @@ void copy_blender_attribute_to_primvar(const GVArray &attribute,
|
|||
copy_blender_buffer_to_primvar<ColorGeometry4b, pxr::GfVec4f>(
|
||||
attribute.typed<ColorGeometry4b>(), time, primvar, value_writer);
|
||||
}
|
||||
colorspace_apply_to_prim(primvar.GetAttr().GetPrim());
|
||||
break;
|
||||
case bke::AttrType::Quaternion:
|
||||
copy_blender_buffer_to_primvar<math::Quaternion, pxr::GfQuatf>(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "IO_subdiv_disabler.hh"
|
||||
#include "usd.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_hook.hh"
|
||||
#include "usd_instancing_utils.hh"
|
||||
|
|
@ -190,6 +191,10 @@ static void ensure_root_prim(pxr::UsdStageRefPtr stage, const USDExportParams &p
|
|||
xf_api.SetRotate(pxr::GfVec3f(eul.x().degree(), eul.y().degree(), eul.z().degree()));
|
||||
}
|
||||
|
||||
/* Colorspace on the root prim. It's also applied on all individual prims that need
|
||||
* it, but perhaps this is useful to signal the overall colorspace of the file. */
|
||||
colorspace_apply_to_prim(root_xf.GetPrim());
|
||||
|
||||
for (const auto &path : pxr::SdfPath(params.root_prim_path).GetPrefixes()) {
|
||||
auto xform = pxr::UsdGeomXform::Define(stage, path);
|
||||
/* Tag generated primitives to allow filtering on import. */
|
||||
|
|
|
|||
|
|
@ -4,6 +4,9 @@
|
|||
|
||||
#include "usd_colorspace_utils.hh"
|
||||
|
||||
#include <pxr/usd/sdf/types.h>
|
||||
#include <pxr/usd/usd/colorSpaceAPI.h>
|
||||
|
||||
#include "BLI_string_utf8.h"
|
||||
|
||||
#include "DNA_image_types.h"
|
||||
|
|
@ -16,10 +19,64 @@ namespace usdtokens {
|
|||
static const pxr::TfToken sourceColorSpace("sourceColorSpace", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken auto_("auto", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken sRGB("sRGB", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken data("data", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken raw("raw", pxr::TfToken::Immortal);
|
||||
static const pxr::TfToken RAW("RAW", pxr::TfToken::Immortal);
|
||||
} // namespace usdtokens
|
||||
|
||||
pxr::TfToken colorspace_scene_linear_interop_id()
|
||||
{
|
||||
const char *scene_linear_name = IMB_colormanagement_role_colorspace_name_get(
|
||||
COLOR_ROLE_SCENE_LINEAR);
|
||||
const ColorSpace *cs = IMB_colormanagement_space_get_named(scene_linear_name);
|
||||
StringRefNull interop_id = (cs) ? IMB_colormanagement_space_get_interop_id(cs) : "";
|
||||
return (interop_id.is_empty()) ? pxr::TfToken() : pxr::TfToken(interop_id);
|
||||
}
|
||||
|
||||
void colorspace_apply_to_prim(const pxr::UsdPrim &prim)
|
||||
{
|
||||
const pxr::TfToken interop_id = colorspace_scene_linear_interop_id();
|
||||
if (interop_id.IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
pxr::UsdColorSpaceAPI cs_api = pxr::UsdColorSpaceAPI::Apply(prim);
|
||||
cs_api.CreateColorSpaceNameAttr(pxr::VtValue(interop_id));
|
||||
}
|
||||
|
||||
static const ColorSpace *colorspace_from_attr(const pxr::UsdAttribute &attr)
|
||||
{
|
||||
pxr::TfToken cs_name = pxr::UsdColorSpaceAPI::ComputeColorSpaceName(attr);
|
||||
if (cs_name.IsEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const ColorSpace *cs = IMB_colormanagement_space_get_named(cs_name.GetText());
|
||||
if (!cs || IMB_colormanagement_space_is_scene_linear(cs) ||
|
||||
IMB_colormanagement_space_is_data(cs))
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return cs;
|
||||
}
|
||||
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr, pxr::GfVec3f &color)
|
||||
{
|
||||
const ColorSpace *cs = colorspace_from_attr(attr);
|
||||
if (cs) {
|
||||
IMB_colormanagement_colorspace_to_scene_linear_v3(color.data(), cs);
|
||||
}
|
||||
}
|
||||
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr,
|
||||
MutableSpan<ColorGeometry4f> colors)
|
||||
{
|
||||
const ColorSpace *cs = colorspace_from_attr(attr);
|
||||
if (cs && !colors.is_empty()) {
|
||||
IMB_colormanagement_colorspace_to_scene_linear(&colors[0].r, colors.size(), 1, 4, cs, false);
|
||||
}
|
||||
}
|
||||
|
||||
static pxr::TfToken get_source_color_space(const pxr::UsdShadeShader &usd_shader)
|
||||
{
|
||||
if (!usd_shader) {
|
||||
|
|
@ -55,6 +112,10 @@ void colorspace_to_image_texture(const pxr::UsdShadeShader &usd_shader,
|
|||
color_space = file_input.GetAttr().GetColorSpace();
|
||||
}
|
||||
|
||||
if (color_space.IsEmpty()) {
|
||||
color_space = pxr::UsdColorSpaceAPI::ComputeColorSpaceName(usd_shader.GetPrim());
|
||||
}
|
||||
|
||||
if (color_space.IsEmpty()) {
|
||||
/* At this point, assume the "auto" space and translate accordingly. */
|
||||
color_space = usdtokens::auto_;
|
||||
|
|
@ -74,10 +135,16 @@ void colorspace_to_image_texture(const pxr::UsdShadeShader &usd_shader,
|
|||
/* Due to there being a lot of non-compliant USD assets out there, this is
|
||||
* a special case where we need to check for different spellings here.
|
||||
* On write, we are *only* using the correct, lower-case "raw" token. */
|
||||
else if (ELEM(color_space, usdtokens::RAW, usdtokens::raw)) {
|
||||
else if (ELEM(color_space, usdtokens::data, usdtokens::RAW, usdtokens::raw)) {
|
||||
STRNCPY_UTF8(image->colorspace_settings.name,
|
||||
IMB_colormanagement_role_colorspace_name_get(COLOR_ROLE_DATA));
|
||||
}
|
||||
else {
|
||||
const ColorSpace *cs = IMB_colormanagement_space_get_named(color_space.GetText());
|
||||
if (cs) {
|
||||
STRNCPY_UTF8(image->colorspace_settings.name, IMB_colormanagement_colorspace_get_name(cs));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void colorspace_from_image_texture(const Image *image, pxr::UsdShadeShader &shader)
|
||||
|
|
@ -95,6 +162,16 @@ void colorspace_from_image_texture(const Image *image, pxr::UsdShadeShader &shad
|
|||
shader.CreateInput(usdtokens::sourceColorSpace, pxr::SdfValueTypeNames->Token)
|
||||
.Set(usdtokens::sRGB);
|
||||
}
|
||||
|
||||
/* Write ColorSpaceAPI with the interop ID, which supports any colorspace. */
|
||||
const ColorSpace *cs = IMB_colormanagement_space_get_named(image->colorspace_settings.name);
|
||||
if (cs) {
|
||||
StringRefNull interop_id = IMB_colormanagement_space_get_interop_id(cs);
|
||||
if (!interop_id.is_empty()) {
|
||||
pxr::UsdColorSpaceAPI cs_api = pxr::UsdColorSpaceAPI::Apply(shader.GetPrim());
|
||||
cs_api.CreateColorSpaceNameAttr(pxr::VtValue(pxr::TfToken(interop_id)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace blender::io::usd
|
||||
|
|
|
|||
|
|
@ -4,6 +4,13 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include "BLI_color.hh"
|
||||
#include "BLI_span.hh"
|
||||
|
||||
#include <pxr/base/gf/vec3f.h>
|
||||
#include <pxr/base/tf/token.h>
|
||||
#include <pxr/usd/usd/attribute.h>
|
||||
#include <pxr/usd/usd/prim.h>
|
||||
#include <pxr/usd/usdShade/shader.h>
|
||||
|
||||
namespace blender {
|
||||
|
|
@ -12,6 +19,22 @@ struct Image;
|
|||
|
||||
namespace blender::io::usd {
|
||||
|
||||
/** Get the interop ID for tagging exported USD stages. */
|
||||
pxr::TfToken colorspace_scene_linear_interop_id();
|
||||
|
||||
/** Tag a prim with the scene linear color space. */
|
||||
void colorspace_apply_to_prim(const pxr::UsdPrim &prim);
|
||||
|
||||
/** Convert an imported USD color to scene linear. */
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr, pxr::GfVec3f &color);
|
||||
|
||||
/** Convert imported USD color array to scene linear. */
|
||||
void colorspace_attr_to_scene_linear(const pxr::UsdAttribute &attr,
|
||||
MutableSpan<ColorGeometry4f> colors);
|
||||
|
||||
/** Set the colorspace on an exported USD texture shader from a Blender image. */
|
||||
void colorspace_from_image_texture(const Image *image, pxr::UsdShadeShader &shader);
|
||||
|
||||
/** Set the Blender image colorspace from an imported USD texture shader. */
|
||||
void colorspace_to_image_texture(const pxr::UsdShadeShader &usd_shader,
|
||||
const pxr::UsdShadeInput &file_input,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "usd.hh"
|
||||
#include "usd_asset_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_private.hh"
|
||||
#include "usd_utils.hh"
|
||||
#include "usd_writer_material.hh"
|
||||
|
|
@ -171,6 +172,7 @@ void world_material_to_dome_light(const USDExportParams ¶ms,
|
|||
/* Create USD dome light. */
|
||||
pxr::SdfPath env_light_path = get_unique_path(stage, params.root_prim_path + "/env_light");
|
||||
pxr::UsdLuxDomeLight dome_light = pxr::UsdLuxDomeLight::Define(stage, env_light_path);
|
||||
colorspace_apply_to_prim(dome_light.GetPrim());
|
||||
|
||||
if (res.image) {
|
||||
/* Use existing image texture file. */
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
#include "usd_mesh_utils.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hash_types.hh"
|
||||
|
||||
#include "BKE_attribute.hh"
|
||||
|
|
@ -54,6 +55,7 @@ static void read_face_display_color(Mesh *mesh,
|
|||
}
|
||||
}
|
||||
|
||||
colorspace_attr_to_scene_linear(primvar.GetAttr(), color_data.span);
|
||||
color_data.finish();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_domelight.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_light_convert.hh"
|
||||
|
||||
#include <pxr/usd/usdLux/domeLight.h>
|
||||
|
|
@ -80,6 +81,9 @@ static bool get_color(const T &dome_light, const pxr::UsdTimeCode time, pxr::GfV
|
|||
{
|
||||
bool has_color = get_authored_value(
|
||||
dome_light.GetColorAttr(), time, dome_light.GetPrim(), usdtokens::color, color);
|
||||
if (has_color) {
|
||||
colorspace_attr_to_scene_linear(dome_light.GetColorAttr(), *color);
|
||||
}
|
||||
return has_color;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
|
||||
#include "usd_reader_light.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
|
||||
#include "BLI_math_rotation.h"
|
||||
|
||||
|
|
@ -158,6 +159,7 @@ void USDLightReader::read_object_data(Main *bmain, const pxr::UsdTimeCode time)
|
|||
if (pxr::UsdAttribute color_attr = light_api.GetColorAttr()) {
|
||||
pxr::GfVec3f color;
|
||||
if (color_attr.Get(&color, time)) {
|
||||
colorspace_attr_to_scene_linear(color_attr, color);
|
||||
blight->r = color[0];
|
||||
blight->g = color[1];
|
||||
blight->b = color[2];
|
||||
|
|
|
|||
|
|
@ -322,6 +322,7 @@ static void set_viewport_material_props(Material *mtl, const pxr::UsdShadeShader
|
|||
pxr::VtValue val;
|
||||
if (attrs[0].Get(&val) && val.IsHolding<pxr::GfVec3f>()) {
|
||||
pxr::GfVec3f color = val.UncheckedGet<pxr::GfVec3f>();
|
||||
io::usd::colorspace_attr_to_scene_linear(attrs[0], color);
|
||||
/* Note: The material is expected to be rendered by the Workbench render engine (Viewport
|
||||
* Display), so no need to define a material node tree. */
|
||||
mtl->r = color[0];
|
||||
|
|
@ -721,6 +722,7 @@ bool USDMaterialReader::set_node_input(const pxr::UsdShadeInput &usd_input,
|
|||
case SOCK_RGBA:
|
||||
if (val.IsHolding<pxr::GfVec3f>()) {
|
||||
pxr::GfVec3f v3f = val.UncheckedGet<pxr::GfVec3f>();
|
||||
colorspace_attr_to_scene_linear(attrs[0], v3f);
|
||||
copy_v3_v3(sock->default_value_typed<bNodeSocketValueRGBA>()->value, v3f.data());
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_abstract.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
#include "usd_utils.hh"
|
||||
#include "usd_writer_material.hh"
|
||||
|
|
@ -314,6 +315,7 @@ pxr::UsdShadeMaterial USDAbstractWriter::ensure_usd_material_created(
|
|||
auto prim = usd_material.GetPrim();
|
||||
add_to_prim_map(prim.GetPath(), &material->id);
|
||||
write_id_properties(prim, material->id, get_export_time_code());
|
||||
colorspace_apply_to_prim(prim);
|
||||
|
||||
return usd_material;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
* SPDX-License-Identifier: GPL-2.0-or-later */
|
||||
#include "usd_writer_light.hh"
|
||||
#include "usd_attribute_utils.hh"
|
||||
#include "usd_colorspace_utils.hh"
|
||||
#include "usd_hierarchy_iterator.hh"
|
||||
|
||||
#include <pxr/usd/usdLux/diskLight.h>
|
||||
|
|
@ -176,6 +177,7 @@ void USDLightWriter::do_write(HierarchyContext &context)
|
|||
pxr::UsdPrim prim = usd_light_api.GetPrim();
|
||||
add_to_prim_map(prim.GetPath(), &light->id);
|
||||
write_id_properties(prim, light->id, time);
|
||||
colorspace_apply_to_prim(prim);
|
||||
|
||||
/* Only a subset of light types are "boundable". */
|
||||
if (auto boundable = pxr::UsdGeomBoundable(prim)) {
|
||||
|
|
|
|||
|
|
@ -2164,6 +2164,49 @@ class USDExportTest(AbstractUSDTest):
|
|||
# Check that the accessibility information is pulled from the export args.
|
||||
verify_accessibility_api(root_prim, UsdUI.Tokens.default_, root_label, root_description)
|
||||
|
||||
def test_export_colorspace(self):
|
||||
"""Validate that exported USD files have ColorSpaceAPI applied to the
|
||||
relevant prims with scene linear interop ID."""
|
||||
|
||||
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
|
||||
|
||||
# Add a light with a specific color.
|
||||
bpy.ops.object.light_add(type='POINT')
|
||||
light_obj = bpy.context.active_object
|
||||
light_obj.data.color = (0.5, 0.3, 0.1)
|
||||
|
||||
# Add a mesh with a color attribute and a material.
|
||||
bpy.ops.mesh.primitive_plane_add()
|
||||
mesh_obj = bpy.context.active_object
|
||||
mesh_obj.data.color_attributes.new(name="Col", type='FLOAT_COLOR', domain='POINT')
|
||||
mat = bpy.data.materials.new(name="Mat")
|
||||
mat.use_nodes = True
|
||||
mesh_obj.data.materials.append(mat)
|
||||
|
||||
export_path = self.tempdir / "colorspace_export.usda"
|
||||
self.export_and_validate(
|
||||
filepath=str(export_path),
|
||||
evaluation_mode="RENDER",
|
||||
)
|
||||
|
||||
expected = bpy.data.colorspace.working_space_interop_id
|
||||
|
||||
def check_colorspace(prim, label):
|
||||
self.assertTrue(prim.IsValid(), f"{label} prim should exist")
|
||||
self.assertTrue(prim.HasAPI(Usd.ColorSpaceAPI),
|
||||
f"{label} prim should have ColorSpaceAPI applied")
|
||||
cs_name = Usd.ColorSpaceAPI(prim).GetColorSpaceNameAttr().Get()
|
||||
self.assertEqual(cs_name, expected,
|
||||
f"{label} colorspace '{cs_name}' should match working space "
|
||||
f"interop ID '{expected}'")
|
||||
|
||||
stage = Usd.Stage.Open(str(export_path))
|
||||
light_name = light_obj.name
|
||||
mesh_name = mesh_obj.name
|
||||
check_colorspace(stage.GetPrimAtPath(f"/root/{light_name}/{light_name}"), "Light")
|
||||
check_colorspace(stage.GetPrimAtPath(f"/root/_materials/{mat.name}"), "Material")
|
||||
check_colorspace(stage.GetPrimAtPath(f"/root/{mesh_name}/{mesh_name}"), "Mesh")
|
||||
|
||||
|
||||
class USDHookBase:
|
||||
instructions = {}
|
||||
|
|
|
|||
|
|
@ -8,9 +8,10 @@ import pathlib
|
|||
import sys
|
||||
import tempfile
|
||||
import unittest
|
||||
from pxr import Ar, Gf, Sdf, Usd, UsdGeom, UsdShade, UsdUI
|
||||
from pxr import Ar, Gf, Sdf, Usd, UsdGeom, UsdLux, UsdShade, UsdUI
|
||||
|
||||
import bpy
|
||||
import mathutils
|
||||
|
||||
sys.path.append(str(pathlib.Path(__file__).parent.absolute()))
|
||||
from modules.colored_print import (print_message, use_message_colors)
|
||||
|
|
@ -1847,6 +1848,87 @@ class USDImportTest(AbstractUSDTest):
|
|||
self.assertIn(alt_description_key, xform, "Alternate description should be imported")
|
||||
self.assertEqual(xform[alt_description_key], alt_description_attr.Get())
|
||||
|
||||
def test_import_colorspace(self):
|
||||
"""Test colorspace conversion on import, including hierarchy and per-prim override."""
|
||||
|
||||
texfile = str(self.testdir / "textures/test_grid_1001.png")
|
||||
usd_path = self.tempdir / "colorspace_test.usda"
|
||||
|
||||
light_color = mathutils.Color((0.8, 0.2, 0.1))
|
||||
mesh_color = mathutils.Color((0.3, 0.5, 1.0))
|
||||
|
||||
stage = Usd.Stage.CreateNew(str(usd_path))
|
||||
root = UsdGeom.Xform.Define(stage, "/root")
|
||||
stage.SetDefaultPrim(root.GetPrim())
|
||||
|
||||
# Set ACEScg linear colorspace on the root, inherited by children.
|
||||
cs_api = Usd.ColorSpaceAPI.Apply(root.GetPrim())
|
||||
cs_api.CreateColorSpaceNameAttr("lin_ap1_scene")
|
||||
|
||||
# Light inherits lin_ap1_scene from root.
|
||||
light = UsdLux.SphereLight.Define(stage, "/root/Light")
|
||||
light.CreateColorAttr(Gf.Vec3f(*light_color))
|
||||
light.CreateIntensityAttr(1.0)
|
||||
|
||||
# Mesh with displayColor overridden to sRGB.
|
||||
mesh = UsdGeom.Mesh.Define(stage, "/root/Mesh")
|
||||
mesh.CreatePointsAttr([Gf.Vec3f(0, 0, 0), Gf.Vec3f(1, 0, 0),
|
||||
Gf.Vec3f(1, 1, 0), Gf.Vec3f(0, 1, 0)])
|
||||
mesh.CreateFaceVertexCountsAttr([4])
|
||||
mesh.CreateFaceVertexIndicesAttr([0, 1, 2, 3])
|
||||
mesh_cs_api = Usd.ColorSpaceAPI.Apply(mesh.GetPrim())
|
||||
mesh_cs_api.CreateColorSpaceNameAttr("srgb_rec709_scene")
|
||||
pv_api = UsdGeom.PrimvarsAPI(mesh)
|
||||
color_pv = pv_api.CreatePrimvar("displayColor",
|
||||
Sdf.ValueTypeNames.Color3fArray,
|
||||
UsdGeom.Tokens.constant)
|
||||
color_pv.Set([Gf.Vec3f(*mesh_color)])
|
||||
|
||||
# Material with a texture that has ColorSpaceAPI set to Non-Color/data.
|
||||
mat = UsdShade.Material.Define(stage, "/root/Material")
|
||||
shader = UsdShade.Shader.Define(stage, "/root/Material/Surface")
|
||||
shader.CreateIdAttr("UsdPreviewSurface")
|
||||
mat.CreateSurfaceOutput().ConnectToSource(shader.ConnectableAPI(), "surface")
|
||||
tex = UsdShade.Shader.Define(stage, "/root/Material/Texture")
|
||||
tex.CreateIdAttr("UsdUVTexture")
|
||||
tex.CreateInput('file', Sdf.ValueTypeNames.Asset).Set(texfile)
|
||||
tex.CreateOutput("rgb", Sdf.ValueTypeNames.Float3)
|
||||
shader.CreateInput("diffuseColor", Sdf.ValueTypeNames.Color3f).ConnectToSource(
|
||||
tex.ConnectableAPI(), "rgb")
|
||||
tex_cs_api = Usd.ColorSpaceAPI.Apply(tex.GetPrim())
|
||||
tex_cs_api.CreateColorSpaceNameAttr("data")
|
||||
|
||||
# Bind material to mesh.
|
||||
UsdShade.MaterialBindingAPI.Apply(mesh.GetPrim()).Bind(mat)
|
||||
|
||||
stage.Save()
|
||||
|
||||
res = bpy.ops.wm.usd_import(filepath=str(usd_path))
|
||||
self.assertEqual({'FINISHED'}, res, f"Unable to import USD file {usd_path}")
|
||||
|
||||
# Light inherits lin_ap1_scene, gets converted to lin_rec709_scene.
|
||||
light_data = bpy.data.lights.get("Light")
|
||||
self.assertIsNotNone(light_data, "Light should be imported")
|
||||
expected_light = light_color.from_acescg_to_scene_linear()
|
||||
for i in range(3):
|
||||
self.assertAlmostEqual(light_data.color[i], expected_light[i], places=2)
|
||||
|
||||
# Mesh displayColor has sRGB override, gets converted to lin_rec709_scene.
|
||||
mesh_obj = bpy.data.objects.get("Mesh")
|
||||
self.assertIsNotNone(mesh_obj, "Mesh should be imported")
|
||||
color_attr = mesh_obj.data.color_attributes.get("displayColor")
|
||||
self.assertIsNotNone(color_attr, "displayColor attribute should exist")
|
||||
expected_mesh = mesh_color.from_srgb_to_scene_linear()
|
||||
for sample in color_attr.data:
|
||||
for i in range(3):
|
||||
self.assertAlmostEqual(sample.color[i], expected_mesh[i], places=2)
|
||||
|
||||
# Texture with "data" colorspace should be imported as Non-Color.
|
||||
tex_image = bpy.data.images.get("test_grid_1001.png")
|
||||
self.assertIsNotNone(tex_image, "Texture image should be imported")
|
||||
self.assertTrue(tex_image.colorspace_settings.name == "Non-Color",
|
||||
f"Texture should be non-color, got '{tex_image.colorspace_settings.name}'")
|
||||
|
||||
|
||||
class USDImportComparisonTest(unittest.TestCase):
|
||||
@classmethod
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue