Merge branch 'blender-v3.5-release'

This commit is contained in:
Hans Goudey 2023-03-18 19:57:58 -04:00
commit 45c4a0b1ef
4 changed files with 181 additions and 47 deletions

@ -370,6 +370,26 @@ CustomDataLayer *BKE_id_attribute_duplicate(ID *id, const char *name, ReportList
return BKE_id_attribute_search(id, uniquename, CD_MASK_PROP_ALL, ATTR_DOMAIN_MASK_ALL);
}
static int color_name_to_index(ID *id, const char *name)
{
const CustomDataLayer *layer = BKE_id_attribute_search(
id, name, CD_MASK_COLOR_ALL, ATTR_DOMAIN_MASK_COLOR);
return BKE_id_attribute_to_index(id, layer, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
}
static int color_clamp_index(ID *id, int index)
{
const int length = BKE_id_attributes_length(id, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
return min_ii(index, length - 1);
}
static const char *color_name_from_index(ID *id, int index)
{
const CustomDataLayer *layer = BKE_id_attribute_from_index(
id, index, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
return layer ? layer->name : nullptr;
}
bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports)
{
using namespace blender;
@ -403,21 +423,25 @@ bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports)
BM_data_layer_free_named(em->bm, data, BKE_uv_map_pin_name_get(name, buffer_src));
}
}
/* Because it's possible that name is owned by the layer and will be freed
* when freeing the layer, do these checks before freeing. */
/* Update active and default color attributes. */
const bool is_active_color_attribute = name == StringRef(mesh->active_color_attribute);
const bool is_default_color_attribute = name == StringRef(mesh->default_color_attribute);
if (BM_data_layer_free_named(em->bm, data, name)) {
if (is_active_color_attribute) {
MEM_SAFE_FREE(mesh->active_color_attribute);
const int active_index = color_name_to_index(id, mesh->active_color_attribute);
const int default_index = color_name_to_index(id, mesh->default_color_attribute);
if (!BM_data_layer_free_named(em->bm, data, name)) {
return false;
}
else if (is_default_color_attribute) {
MEM_SAFE_FREE(mesh->default_color_attribute);
if (is_active_color_attribute) {
BKE_id_attributes_active_color_set(
id, color_name_from_index(id, color_clamp_index(id, active_index)));
}
if (is_default_color_attribute) {
BKE_id_attributes_default_color_set(
id, color_name_from_index(id, color_clamp_index(id, default_index)));
}
return true;
}
}
}
return false;
}
}
@ -429,7 +453,6 @@ bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports)
}
if (GS(id->name) == ID_ME) {
std::optional<blender::bke::AttributeMetaData> metadata = attributes->lookup_meta_data(name);
if (metadata->data_type == CD_PROP_FLOAT2) {
/* remove UV sub-attributes. */
@ -438,6 +461,24 @@ bool BKE_id_attribute_remove(ID *id, const char *name, ReportList *reports)
BKE_id_attribute_remove(id, BKE_uv_map_edge_select_name_get(name, buffer_src), reports);
BKE_id_attribute_remove(id, BKE_uv_map_pin_name_get(name, buffer_src), reports);
}
/* Update active and default color attributes. */
Mesh *mesh = reinterpret_cast<Mesh *>(id);
const bool is_active_color_attribute = name == StringRef(mesh->active_color_attribute);
const bool is_default_color_attribute = name == StringRef(mesh->default_color_attribute);
const int active_index = color_name_to_index(id, mesh->active_color_attribute);
const int default_index = color_name_to_index(id, mesh->default_color_attribute);
if (!attributes->remove(name)) {
return false;
}
if (is_active_color_attribute) {
BKE_id_attributes_active_color_set(
id, color_name_from_index(id, color_clamp_index(id, active_index)));
}
if (is_default_color_attribute) {
BKE_id_attributes_default_color_set(
id, color_name_from_index(id, color_clamp_index(id, default_index)));
}
return true;
}
return attributes->remove(name);

@ -107,36 +107,6 @@ static int geometry_attribute_add_exec(bContext *C, wmOperator *op)
return OPERATOR_FINISHED;
}
static void next_color_attribute(ID *id, const StringRefNull name, bool is_render)
{
const CustomDataLayer *layer = BKE_id_attributes_color_find(id, name.c_str());
int index = BKE_id_attribute_to_index(id, layer, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
index++;
layer = BKE_id_attribute_from_index(id, index, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
if (!layer) {
index = 0;
layer = BKE_id_attribute_from_index(id, index, ATTR_DOMAIN_MASK_COLOR, CD_MASK_COLOR_ALL);
}
if (layer) {
if (is_render) {
BKE_id_attributes_active_color_set(id, layer->name);
}
else {
BKE_id_attributes_default_color_set(id, layer->name);
}
}
}
static void next_color_attributes(ID *id, const StringRefNull name)
{
next_color_attribute(id, name, false); /* active */
next_color_attribute(id, name, true); /* render */
}
void GEOMETRY_OT_attribute_add(wmOperatorType *ot)
{
/* identifiers */
@ -182,8 +152,6 @@ static int geometry_attribute_remove_exec(bContext *C, wmOperator *op)
ID *id = static_cast<ID *>(ob->data);
CustomDataLayer *layer = BKE_id_attributes_active_get(id);
next_color_attributes(id, layer->name);
if (!BKE_id_attribute_remove(id, layer->name, op->reports)) {
return OPERATOR_CANCELLED;
}
@ -436,8 +404,6 @@ static int geometry_color_attribute_remove_exec(bContext *C, wmOperator *op)
return OPERATOR_CANCELLED;
}
next_color_attributes(id, active_name);
if (!BKE_id_attribute_remove(id, active_name.c_str(), op->reports)) {
return OPERATOR_CANCELLED;
}

@ -786,10 +786,26 @@ void USDMaterialReader::convert_usd_primvar_reader_float2(
/* Set the texmap name. */
pxr::UsdShadeInput varname_input = usd_shader.GetInput(usdtokens::varname);
/* First check if the shader's "varname" input is connected to another source,
* and use that instead if so. */
if (varname_input) {
for (const pxr::UsdShadeConnectionSourceInfo& source_info : varname_input.GetConnectedSources()) {
pxr::UsdShadeShader shader = pxr::UsdShadeShader(source_info.source.GetPrim());
pxr::UsdShadeInput secondary_varname_input = shader.GetInput(source_info.sourceName);
if (secondary_varname_input) {
varname_input = secondary_varname_input;
break;
}
}
}
if (varname_input) {
pxr::VtValue varname_val;
if (varname_input.Get(&varname_val) && varname_val.IsHolding<pxr::TfToken>()) {
std::string varname = varname_val.Get<pxr::TfToken>().GetString();
/* The varname input may be a string or TfToken, so just cast it to a string.
* The Cast function is defined to provide an empty result if it fails. */
if (varname_input.Get(&varname_val) && varname_val.CanCastToTypeid(typeid(std::string))) {
std::string varname = varname_val.Cast<std::string>().Get<std::string>();
if (!varname.empty()) {
NodeShaderUVMap *storage = (NodeShaderUVMap *)uv_map->storage;
BLI_strncpy(storage->uv_map, varname.c_str(), sizeof(storage->uv_map));

@ -3,6 +3,11 @@
import pathlib
import sys
import unittest
import tempfile
from pxr import Usd
from pxr import UsdShade
from pxr import UsdGeom
from pxr import Sdf
import bpy
@ -13,6 +18,8 @@ class AbstractUSDTest(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.testdir = args.testdir
cls._tempdir = tempfile.TemporaryDirectory()
cls.tempdir = pathlib.Path(cls._tempdir.name)
def setUp(self):
self.assertTrue(self.testdir.exists(),
@ -21,6 +28,9 @@ class AbstractUSDTest(unittest.TestCase):
# Make sure we always start with a known-empty file.
bpy.ops.wm.open_mainfile(filepath=str(self.testdir / "empty.blend"))
def tearDown(self):
self._tempdir.cleanup()
class USDImportTest(AbstractUSDTest):
@ -189,6 +199,107 @@ class USDImportTest(AbstractUSDTest):
self.assertAlmostEqual(1.234, test_cam.shift_x, 3)
self.assertAlmostEqual(5.678, test_cam.shift_y, 3)
def test_import_shader_varname_with_connection(self):
"""Test importing USD shader where uv primvar is a connection"""
varname = "testmap"
texfile = str(self.testdir / "textures/test_grid_1001.png")
# Create the test USD file.
temp_usd_file = str(self.tempdir / "usd_varname_test.usda")
stage = Usd.Stage.CreateNew(temp_usd_file)
mesh1 = stage.DefinePrim("/mesh1", "Mesh")
mesh2 = stage.DefinePrim("/mesh2", "Mesh")
# Create two USD preview surface shaders in two materials.
m1 = UsdShade.Material.Define(stage, "/mat1")
s1 = UsdShade.Shader.Define(stage, "/mat1/previewshader")
s1.CreateIdAttr("UsdPreviewSurface")
m1.CreateSurfaceOutput().ConnectToSource(s1.ConnectableAPI(), "surface")
t1 = UsdShade.Shader.Define(stage, "/mat1/diffuseTexture")
t1.CreateIdAttr("UsdUVTexture")
t1.CreateInput('file', Sdf.ValueTypeNames.Asset).Set(texfile)
t1.CreateOutput("rgb", Sdf.ValueTypeNames.Float3)
s1.CreateInput("diffuseColor", Sdf.ValueTypeNames.Color3f).ConnectToSource(t1.ConnectableAPI(), "rgb")
t2 = UsdShade.Shader.Define(stage, "/mat1/roughnessTexture")
t2.CreateIdAttr("UsdUVTexture")
t2.CreateInput('file', Sdf.ValueTypeNames.Asset).Set(texfile)
t2.CreateOutput("rgb", Sdf.ValueTypeNames.Float3)
s1.CreateInput("roughness", Sdf.ValueTypeNames.Color3f).ConnectToSource(t2.ConnectableAPI(), "rgb")
m2 = UsdShade.Material.Define(stage, "/mat2")
s2 = UsdShade.Shader.Define(stage, "/mat2/previewshader")
s2.CreateIdAttr("UsdPreviewSurface")
m2.CreateSurfaceOutput().ConnectToSource(s2.ConnectableAPI(), "surface")
t3 = UsdShade.Shader.Define(stage, "/mat2/diffuseTexture")
t3.CreateIdAttr("UsdUVTexture")
t3.CreateInput('file', Sdf.ValueTypeNames.Asset).Set(texfile)
t3.CreateOutput("rgb", Sdf.ValueTypeNames.Float3)
s2.CreateInput("diffuseColor", Sdf.ValueTypeNames.Color3f).ConnectToSource(t3.ConnectableAPI(), "rgb")
t4 = UsdShade.Shader.Define(stage, "/mat2/roughnessTexture")
t4.CreateIdAttr("UsdUVTexture")
t4.CreateInput('file', Sdf.ValueTypeNames.Asset).Set(texfile)
t4.CreateOutput("rgb", Sdf.ValueTypeNames.Float3)
s2.CreateInput("roughness", Sdf.ValueTypeNames.Color3f).ConnectToSource(t4.ConnectableAPI(), "rgb")
# Bind mat1 to mesh1, mat2 to mesh2.
bindingAPI = UsdShade.MaterialBindingAPI.Apply(mesh1)
bindingAPI.Bind(m1)
bindingAPI = UsdShade.MaterialBindingAPI.Apply(mesh2)
bindingAPI.Bind(m2)
# Create varname defined as a token.
s3 = UsdShade.Shader.Define(stage, "/mat1/primvar_reader1")
s3.CreateIdAttr('UsdPrimvarReader_float2')
s3input = s3.CreateInput("varname", Sdf.ValueTypeNames.Token)
s3input.Set(varname)
t1.CreateInput("st", Sdf.ValueTypeNames.TexCoord2f).ConnectToSource(s3.ConnectableAPI(), "result")
# Create varname defined as a connection to a token.
varname1 = m1.CreateInput("varname", Sdf.ValueTypeNames.Token)
varname1.Set(varname)
s4 = UsdShade.Shader.Define(stage, "/mat1/primvar_reader2")
s4.CreateIdAttr('UsdPrimvarReader_float2')
s4input = s4.CreateInput("varname", Sdf.ValueTypeNames.Token)
UsdShade.ConnectableAPI.ConnectToSource(s4input, varname1)
t2.CreateInput("st", Sdf.ValueTypeNames.TexCoord2f).ConnectToSource(s4.ConnectableAPI(), "result")
# Create varname defined as a string.
s5 = UsdShade.Shader.Define(stage, "/mat2/primvar_reader1")
s5.CreateIdAttr('UsdPrimvarReader_float2')
s5input = s5.CreateInput("varname", Sdf.ValueTypeNames.String)
s5input.Set(varname)
t3.CreateInput("st", Sdf.ValueTypeNames.TexCoord2f).ConnectToSource(s5.ConnectableAPI(), "result")
# Create varname defined as a connection to a string.
varname2 = m2.CreateInput("varname", Sdf.ValueTypeNames.String)
varname2.Set(varname)
s6 = UsdShade.Shader.Define(stage, "/mat2/primvar_reader2")
s6.CreateIdAttr('UsdPrimvarReader_float2')
s6input = s6.CreateInput("varname", Sdf.ValueTypeNames.String)
UsdShade.ConnectableAPI.ConnectToSource(s6input, varname2)
t4.CreateInput("st", Sdf.ValueTypeNames.TexCoord2f).ConnectToSource(s6.ConnectableAPI(), "result")
stage.Save()
# Now import the USD file.
res = bpy.ops.wm.usd_import(filepath=temp_usd_file, import_all_materials=True)
self.assertEqual({'FINISHED'}, res)
# Ensure that we find the correct varname for all four primvar readers.
num_uvmaps_found = 0
mats_to_test = []
mats_to_test.append(bpy.data.materials["mat1"])
mats_to_test.append(bpy.data.materials["mat2"])
for mat in mats_to_test:
self.assertIsNotNone(mat.node_tree, "Material node tree is empty")
for node in mat.node_tree.nodes:
if node.type == "UVMAP":
self.assertEqual(varname, node.uv_map, "Unexpected value for varname")
num_uvmaps_found += 1
self.assertEqual(4, num_uvmaps_found, "One or more test materials failed to import")
def main():
global args