Merged in DRTVWR-559-SL-17695 (pull request #1089)

SL-17695 Give materials and textures imported by Material Editor a sensible name - first pass - likely need to be refined but may be good enough for now

Approved-by: Andrey Kleshchev
Approved-by: Dave Parks
meow-7.2.2
Callum Linden 2022-08-10 21:32:00 +00:00
commit a5bdc3c1f3
3 changed files with 276 additions and 3 deletions

View File

@ -42,6 +42,7 @@
#include "llselectmgr.h"
#include "llstatusbar.h" // can_afford_transaction()
#include "llviewerinventory.h"
#include "llinventory.h"
#include "llviewerregion.h"
#include "llvovolume.h"
#include "roles_constants.h"
@ -658,6 +659,57 @@ bool LLMaterialEditor::decodeAsset(const std::vector<char>& buffer)
return false;
}
/**
* Build a description of the material we just imported.
* Currently this means a list of the textures present but we
* may eventually want to make it more complete - will be guided
* by what the content creators say they need.
*/
const std::string LLMaterialEditor::buildMaterialDescription()
{
std::ostringstream desc;
desc << LLTrans::getString("Material Texture Name Header");
// add the texture names for each just so long as the material
// we loaded has an entry for it (i think testing the texture
// control UUI for NULL is a valid metric for if it was loaded
// or not but I suspect this code will change a lot so may need
// to revisit
if (!mAlbedoTextureCtrl->getValue().asUUID().isNull())
{
desc << mAlbedoName;
desc << ", ";
}
if (!mMetallicTextureCtrl->getValue().asUUID().isNull())
{
desc << mMetallicRoughnessName;
desc << ", ";
}
if (!mEmissiveTextureCtrl->getValue().asUUID().isNull())
{
desc << mEmissiveName;
desc << ", ";
}
if (!mNormalTextureCtrl->getValue().asUUID().isNull())
{
desc << mNormalName;
}
// trim last char if it's a ',' in case there is no normal texture
// present and the code above inserts one
// (no need to check for string length - always has initial string)
std::string::iterator iter = desc.str().end() - 1;
if (*iter == ',')
{
desc.str().erase(iter);
}
// sanitize the material description so that it's compatible with the inventory
LLInventoryObject::correctInventoryName(desc.str());
return desc.str();
}
bool LLMaterialEditor::saveIfNeeded()
{
if (mUploadingTexturesCount > 0)
@ -703,7 +755,7 @@ bool LLMaterialEditor::saveIfNeeded()
LLTransactionID tid;
tid.generate(); // timestamp-based randomization + uniquification
LLAssetID new_asset_id = tid.makeAssetID(gAgent.getSecureSessionID());
std::string res_desc = "Saved Material";
std::string res_desc = buildMaterialDescription();
U32 next_owner_perm = LLPermissions::DEFAULT.getMaskNextOwner();
LLUUID parent = gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL);
const U8 subtype = NO_INV_SUBTYPE; // TODO maybe use AT_SETTINGS and LLSettingsType::ST_MATERIAL ?
@ -1262,8 +1314,7 @@ void LLMaterialFilePicker::loadMaterial(const std::string& filename)
mME->setFromGltfModel(model_in);
std::string new_material = LLTrans::getString("New Material");
mME->setMaterialName(new_material);
mME->setFromGltfMetaData(filename_lc, model_in);
mME->setHasUnsavedChanges(true);
mME->openFloater();
@ -1346,6 +1397,215 @@ bool LLMaterialEditor::setFromGltfModel(tinygltf::Model& model, bool set_texture
return true;
}
/**
* Build a texture name from the contents of the (in tinyGLFT parlance)
* Image URI. This often is filepath to the original image on the users'
* local file system.
*/
const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, const std::string texture_type)
{
// getBaseFileName() works differently on each platform and file patchs
// can contain both types of delimiter so unify them then extract the
// base name (no path or extension)
std::replace(image_uri.begin(), image_uri.end(), '\\', gDirUtilp->getDirDelimiter()[0]);
std::replace(image_uri.begin(), image_uri.end(), '/', gDirUtilp->getDirDelimiter()[0]);
const bool strip_extension = true;
std::string stripped_uri = gDirUtilp->getBaseFileName(image_uri, strip_extension);
// sometimes they can be really long and unwieldy - 64 chars is enough for anyone :)
const int max_texture_name_length = 64;
if (stripped_uri.length() > max_texture_name_length)
{
stripped_uri = stripped_uri.substr(0, max_texture_name_length - 1);
}
// We intend to append the type of texture (albedo, emissive etc.) to the
// name of the texture but sometimes the creator already did that. To try
// to avoid repeats (not perfect), we look for the texture type in the name
// and if we find it, do not append the type, later on. One way this fails
// (and it's fine for now) is I see some texture/image uris have a name like
// "metallic roughness" and of course, that doesn't match our predefined
// name "metallicroughness" - consider fix later..
bool name_includes_type = false;
std::string stripped_uri_lower = stripped_uri;
LLStringUtil::toLower(stripped_uri_lower);
stripped_uri_lower.erase(std::remove_if(stripped_uri_lower.begin(), stripped_uri_lower.end(), isspace), stripped_uri_lower.end());
std::string texture_type_lower = texture_type;
LLStringUtil::toLower(texture_type_lower);
texture_type_lower.erase(std::remove_if(texture_type_lower.begin(), texture_type_lower.end(), isspace), texture_type_lower.end());
if (stripped_uri_lower.find(texture_type_lower) != std::string::npos)
{
name_includes_type = true;
}
// uri doesn't include the type at all
if (name_includes_type == false)
{
// uri doesn't include the type and the uri is not empty
// so we can include everything
if (stripped_uri.length() > 0)
{
// example "DamagedHelmet: base layer (Albedo)"
return STRINGIZE(
mMaterialNameShort <<
": " <<
stripped_uri <<
" (" <<
texture_type <<
")"
);
}
else
// uri doesn't include the type (because the uri is empty)
// so we must reorganize the string a bit to include the name
// and an explicit name type
{
// example "DamagedHelmet: (Emissive)"
return STRINGIZE(
mMaterialNameShort <<
" (" <<
texture_type <<
")"
);
}
}
else
// uri includes the type so just use it directly with the
// name of the material
{
return STRINGIZE(
// example: AlienBust: normal_layer
mMaterialNameShort <<
": " <<
stripped_uri
);
}
}
/**
* Update the metadata for the material based on what we find in the loaded
* file (along with some assumptions and interpretations...). Fields include
* the name of the material, a material description and the names of the
* composite textures.
*/
void LLMaterialEditor::setFromGltfMetaData(const std::string& filename, tinygltf::Model& model)
{
// Use the name (without any path/extension) of the file that was
// uploaded as the base of the material name. Then if the name of the
// scene is present and not blank, append that and use the result as
// the name of the material. This is a first pass at creating a
// naming scheme that is useful to real content creators and hopefully
// avoid 500 materials in your inventory called "scene" or "Default"
const bool strip_extension = true;
std::string base_filename = gDirUtilp->getBaseFileName(filename, strip_extension);
// Extract the name of the scene. Note it is often blank or some very
// generic name like "Scene" or "Default" so using this in the name
// is less useful than you might imagine.
std::string scene_name;
if (model.scenes.size() > 0)
{
tinygltf::Scene& scene_in = model.scenes[0];
if (scene_in.name.length())
{
scene_name = scene_in.name;
}
else
{
// scene name is empty so no point using it
}
}
else
{
// scene name isn't present so no point using it
}
// If we have a valid scene name, use it to build the short and
// long versions of the material name. The long version is used
// as you might expect, for the material name. The short version is
// used as part of the image/texture name - the theory is that will
// allow content creators to track the material and the corresponding
// textures
if (scene_name.length())
{
mMaterialNameShort = base_filename;
mMaterialName = STRINGIZE(
base_filename <<
" " <<
"(" <<
scene_name <<
")"
);
}
else
// otherwise, just use the trimmed filename as is
{
mMaterialNameShort = base_filename;
mMaterialName = base_filename;
}
// sanitize the material name so that it's compatible with the inventory
LLInventoryObject::correctInventoryName(mMaterialName);
LLInventoryObject::correctInventoryName(mMaterialNameShort);
// We also set the title of the floater to match the
// name of the material
setTitle(mMaterialName);
/**
* Extract / derive the names of each composite texture. For each, the
* index in the first material (we only support 1 material currently) is
* used to to determine which of the "Images" is used. If the index is -1
* then that texture type is not present in the material (Seems to be
* quite common that a material is missing 1 or more types of texture)
*/
if (model.materials.size() > 0)
{
const tinygltf::Material& first_material = model.materials[0];
mAlbedoName = MATERIAL_ALBEDO_DEFAULT_NAME;
// note: unlike the other textures, albedo doesn't have its own entry
// in the tinyGLTF Material struct. Rather, it is taken from a
// sub-texture in the pbrMetallicRoughness member
int index = first_material.pbrMetallicRoughness.baseColorTexture.index;
if (index > -1 && index < model.images.size())
{
// sanitize the name we decide to use for each texture
std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_ALBEDO_DEFAULT_NAME);
LLInventoryObject::correctInventoryName(texture_name);
mAlbedoName = texture_name;
}
mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME;
index = first_material.emissiveTexture.index;
if (index > -1 && index < model.images.size())
{
std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_EMISSIVE_DEFAULT_NAME);
LLInventoryObject::correctInventoryName(texture_name);
mEmissiveName = texture_name;
}
mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME;
index = first_material.pbrMetallicRoughness.metallicRoughnessTexture.index;
if (index > -1 && index < model.images.size())
{
std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_METALLIC_DEFAULT_NAME);
LLInventoryObject::correctInventoryName(texture_name);
mMetallicRoughnessName = texture_name;
}
mNormalName = MATERIAL_NORMAL_DEFAULT_NAME;
index = first_material.normalTexture.index;
if (index > -1 && index < model.images.size())
{
std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_NORMAL_DEFAULT_NAME);
LLInventoryObject::correctInventoryName(texture_name);
mNormalName = texture_name;
}
}
}
void LLMaterialEditor::importMaterial()
{
(new LLMaterialFilePicker(this))->getFile();

View File

@ -47,6 +47,8 @@ public:
bool setFromGltfModel(tinygltf::Model& model, bool set_textures = false);
void setFromGltfMetaData(const std::string& filename, tinygltf::Model& model);
// open a file dialog and select a gltf/glb file for import
void importMaterial();
@ -194,9 +196,17 @@ private:
LLPointer<LLImageJ2C> mMetallicRoughnessJ2C;
LLPointer<LLImageJ2C> mEmissiveJ2C;
// utility function for converting image uri into a texture name
const std::string getImageNameFromUri(std::string image_uri, const std::string texture_type);
// utility function for building a description of the imported material
// based on what we know about it.
const std::string buildMaterialDescription();
bool mHasUnsavedChanges;
S32 mUploadingTexturesCount;
S32 mExpectedUploadCost;
std::string mMaterialNameShort;
std::string mMaterialName;
};

View File

@ -4264,6 +4264,9 @@ name="Command_360_Capture_Tooltip">Capture a 360 equirectangular image</string>
<string name="ExperiencePermissionShort16">Sit</string>
<string name="ExperiencePermissionShort17">Environment</string>
<!-- PBR Materials -->
<string name="Material Texture Name Header">Textures present this material: </string>
<!-- Conversation log messages -->
<string name="logging_calls_disabled_log_empty">
Conversations are not being logged. To begin keeping a log, choose "Save: Log only" or "Save: Log and transcripts" under Preferences > Chat.