From 3b416b3e98fe80b1d9b26352551e2d5860016b78 Mon Sep 17 00:00:00 2001 From: Callum Linden Date: Mon, 8 Aug 2022 16:40:25 -0700 Subject: 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 --- indra/newview/llmaterialeditor.cpp | 245 ++++++++++++++++++++++++- indra/newview/llmaterialeditor.h | 10 + indra/newview/skins/default/xui/en/strings.xml | 3 + 3 files changed, 255 insertions(+), 3 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index 57cd74e0f2..c7fa2b6426 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -62,6 +62,7 @@ const std::string MATERIAL_NORMAL_DEFAULT_NAME = "Normal"; const std::string MATERIAL_METALLIC_DEFAULT_NAME = "Metallic Roughness"; const std::string MATERIAL_EMISSIVE_DEFAULT_NAME = "Emissive"; + class LLMaterialEditorCopiedCallback : public LLInventoryCallback { public: @@ -648,6 +649,54 @@ bool LLMaterialEditor::decodeAsset(const std::vector& 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); + } + + return desc.str(); +} + bool LLMaterialEditor::saveIfNeeded() { if (mUploadingTexturesCount > 0) @@ -693,7 +742,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 ? @@ -1252,8 +1301,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(); @@ -1336,6 +1384,197 @@ 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" - might 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 + // reorganize the string a bit to include the name and 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 + * data (along with some assumptions and interpretations...). Fields include + * the name of the material 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; + } + + // 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; + int index = first_material.pbrMetallicRoughness.baseColorTexture.index; + if (index > -1 && index < model.images.size()) + { + mAlbedoName = getImageNameFromUri(model.images[index].uri, MATERIAL_ALBEDO_DEFAULT_NAME); + } + + mEmissiveName = MATERIAL_EMISSIVE_DEFAULT_NAME; + index = first_material.emissiveTexture.index; + if (index > -1 && index < model.images.size()) + { + mEmissiveName = getImageNameFromUri(model.images[index].uri, MATERIAL_EMISSIVE_DEFAULT_NAME); + } + + mMetallicRoughnessName = MATERIAL_METALLIC_DEFAULT_NAME; + index = first_material.pbrMetallicRoughness.metallicRoughnessTexture.index; + if (index > -1 && index < model.images.size()) + { + mMetallicRoughnessName = getImageNameFromUri(model.images[index].uri, MATERIAL_METALLIC_DEFAULT_NAME); + } + + mNormalName = MATERIAL_NORMAL_DEFAULT_NAME; + index = first_material.normalTexture.index; + if (index > -1 && index < model.images.size()) + { + mNormalName = getImageNameFromUri(model.images[index].uri, MATERIAL_NORMAL_DEFAULT_NAME); + } + } +} + void LLMaterialEditor::importMaterial() { (new LLMaterialFilePicker(this))->getFile(); diff --git a/indra/newview/llmaterialeditor.h b/indra/newview/llmaterialeditor.h index 3da59c7f93..d5e0ecad13 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(); @@ -192,9 +194,17 @@ private: LLPointer mMetallicRoughnessJ2C; LLPointer 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 Sit Environment + + Textures present this material: + Conversations are not being logged. To begin keeping a log, choose "Save: Log only" or "Save: Log and transcripts" under Preferences > Chat. -- cgit v1.2.3 From c989e202f2cc2703b7e11bc769f412f350f24445 Mon Sep 17 00:00:00 2001 From: Callum Linden Date: Tue, 9 Aug 2022 10:38:33 -0700 Subject: SL-17695 minor cleanup pass to make the PR a bit easier to read --- indra/newview/llmaterialeditor.cpp | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index c7fa2b6426..ecaca1255b 100644 --- a/indra/newview/llmaterialeditor.cpp +++ b/indra/newview/llmaterialeditor.cpp @@ -62,7 +62,6 @@ const std::string MATERIAL_NORMAL_DEFAULT_NAME = "Normal"; const std::string MATERIAL_METALLIC_DEFAULT_NAME = "Metallic Roughness"; const std::string MATERIAL_EMISSIVE_DEFAULT_NAME = "Emissive"; - class LLMaterialEditorCopiedCallback : public LLInventoryCallback { public: @@ -1406,13 +1405,13 @@ const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, c stripped_uri = stripped_uri.substr(0, max_texture_name_length - 1); } - // We intend to append the type of texture (Albedo, emissive etc.) to the + // 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" - might fix later.. + // name "metallicroughness" - consider fix later.. bool name_includes_type = false; std::string stripped_uri_lower = stripped_uri; LLStringUtil::toLower(stripped_uri_lower); @@ -1443,8 +1442,9 @@ const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, c ); } else - // uri doesn't include the type (because the uri is empty) so - // reorganize the string a bit to include the name and type + // 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( @@ -1470,8 +1470,9 @@ const std::string LLMaterialEditor::getImageNameFromUri(std::string image_uri, c /** * Update the metadata for the material based on what we find in the loaded - * data (along with some assumptions and interpretations...). Fields include - * the name of the material and the names of the composite textures. + * 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) { -- cgit v1.2.3 From 8645ffe172e1e847758249af7536eef71e430a17 Mon Sep 17 00:00:00 2001 From: Callum Linden Date: Wed, 10 Aug 2022 12:52:14 -0700 Subject: SL-17695 Sensible names for material properties - sanitize the various names we use to store inventory items using a built in function --- indra/newview/llmaterialeditor.cpp | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) (limited to 'indra') diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp index ecaca1255b..ce2b229de9 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" @@ -693,6 +694,9 @@ const std::string LLMaterialEditor::buildMaterialDescription() desc.str().erase(iter); } + // sanitize the material description so that it's compatible with the inventory + LLInventoryObject::correctInventoryName(desc.str()); + return desc.str(); } @@ -1531,6 +1535,10 @@ void LLMaterialEditor::setFromGltfMetaData(const std::string& filename, tinygltf 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); @@ -1547,31 +1555,43 @@ void LLMaterialEditor::setFromGltfMetaData(const std::string& filename, tinygltf 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()) { - mAlbedoName = getImageNameFromUri(model.images[index].uri, MATERIAL_ALBEDO_DEFAULT_NAME); + // 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()) { - mEmissiveName = getImageNameFromUri(model.images[index].uri, MATERIAL_EMISSIVE_DEFAULT_NAME); + 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()) { - mMetallicRoughnessName = getImageNameFromUri(model.images[index].uri, MATERIAL_METALLIC_DEFAULT_NAME); + 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()) { - mNormalName = getImageNameFromUri(model.images[index].uri, MATERIAL_NORMAL_DEFAULT_NAME); + std::string texture_name = getImageNameFromUri(model.images[index].uri, MATERIAL_NORMAL_DEFAULT_NAME); + LLInventoryObject::correctInventoryName(texture_name); + mNormalName = texture_name; } } } -- cgit v1.2.3