summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCallum Linden <callum@lindenlab.com>2022-08-10 21:32:00 +0000
committerCallum Linden <callum@lindenlab.com>2022-08-10 21:32:00 +0000
commita5bdc3c1f370e22ac7ca51b7432d31a504d17fc3 (patch)
tree10aa36f0c3dde7fea6b1ff8c07c8f8a280a943af
parente408fb9f7c9cc0adf32a2977882fd0c741de66a9 (diff)
parent8645ffe172e1e847758249af7536eef71e430a17 (diff)
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
-rw-r--r--indra/newview/llmaterialeditor.cpp266
-rw-r--r--indra/newview/llmaterialeditor.h10
-rw-r--r--indra/newview/skins/default/xui/en/strings.xml3
3 files changed, 276 insertions, 3 deletions
diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp
index 13e250ec3b..ffeb2db4df 100644
--- a/indra/newview/llmaterialeditor.cpp
+++ b/indra/newview/llmaterialeditor.cpp
@@ -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();
diff --git a/indra/newview/llmaterialeditor.h b/indra/newview/llmaterialeditor.h
index 1b81a7144a..cf31eb5095 100644
--- a/indra/newview/llmaterialeditor.h
+++ b/indra/newview/llmaterialeditor.h
@@ -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;
};
diff --git a/indra/newview/skins/default/xui/en/strings.xml b/indra/newview/skins/default/xui/en/strings.xml
index 4169929be8..4b094da805 100644
--- a/indra/newview/skins/default/xui/en/strings.xml
+++ b/indra/newview/skins/default/xui/en/strings.xml
@@ -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.