diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp
index a2cd68259bec81605fa694480d12c5943c4176aa..27b5d508e02ff1cfe4381e35213c58fa91f9c1e2 100644
--- a/indra/newview/llmaterialeditor.cpp
+++ b/indra/newview/llmaterialeditor.cpp
@@ -1,6 +1,6 @@
 /** 
  * @file llmaterialeditor.cpp
- * @brief Implementation of the notecard editor
+ * @brief Implementation of the gltf material editor
  *
  * $LicenseInfo:firstyear=2022&license=viewerlgpl$
  * Second Life Viewer Source Code
@@ -150,16 +150,44 @@ void LLFloaterComboOptions::onCancel()
 class LLMaterialEditorCopiedCallback : public LLInventoryCallback
 {
 public:
-    LLMaterialEditorCopiedCallback(const std::string &buffer, const LLUUID &old_item_id) : mBuffer(buffer), mOldItemId(old_item_id) {}
+    LLMaterialEditorCopiedCallback(
+        const std::string &buffer,
+        const LLSD &old_key,
+        bool has_unsaved_changes)
+        : mBuffer(buffer),
+          mOldKey(old_key),
+          mHasUnsavedChanges(has_unsaved_changes)
+    {}
+
+    LLMaterialEditorCopiedCallback(
+        const LLSD &old_key,
+        const std::string &new_name)
+        : mOldKey(old_key),
+          mNewName(new_name),
+          mHasUnsavedChanges(false)
+    {}
 
     virtual void fire(const LLUUID& inv_item_id)
     {
-        LLMaterialEditor::finishSaveAs(mOldItemId, inv_item_id, mBuffer);
+        if (!mNewName.empty())
+        {
+            // making a copy from a notecard doesn't change name, do it now
+            LLViewerInventoryItem* item = gInventory.getItem(inv_item_id);
+            if (item->getName() != mNewName)
+            {
+                LLSD updates;
+                updates["name"] = mNewName;
+                update_inventory_item(inv_item_id, updates, NULL);
+            }
+        }
+        LLMaterialEditor::finishSaveAs(mOldKey, inv_item_id, mBuffer, mHasUnsavedChanges);
     }
 
 private:
     std::string mBuffer;
-    LLUUID mOldItemId;
+    LLSD mOldKey;
+    std::string mNewName;
+    bool mHasUnsavedChanges;
 };
 
 ///----------------------------------------------------------------------------
@@ -190,6 +218,15 @@ void LLMaterialEditor::setObjectID(const LLUUID& object_id)
     }
 }
 
+void LLMaterialEditor::setAuxItem(const LLInventoryItem* item)
+{
+    LLPreview::setAuxItem(item);
+    if (item)
+    {
+        mAssetID = item->getAssetUUID();
+    }
+}
+
 BOOL LLMaterialEditor::postBuild()
 {
     mBaseColorTextureCtrl = getChild<LLTextureCtrl>("base_color_texture");
@@ -456,10 +493,25 @@ void LLMaterialEditor::setDoubleSided(bool double_sided)
 
 void LLMaterialEditor::setHasUnsavedChanges(bool value)
 {
-    if (value != mHasUnsavedChanges)
+    mHasUnsavedChanges = value;
+    childSetVisible("unsaved_changes", value);
+
+    if (mHasUnsavedChanges)
     {
-        mHasUnsavedChanges = value;
-        childSetVisible("unsaved_changes", value);
+        const LLInventoryItem* item = getItem();
+        if (item)
+        {
+            LLPermissions perm(item->getPermissions());
+            bool allow_modify = canModify(mObjectUUID, item);
+            bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
+            bool source_notecard = mNotecardInventoryID.notNull();
+
+            setCanSave(allow_modify && !source_library && !source_notecard);
+        }
+    }
+    else
+    {
+        setCanSave(false);
     }
 
     S32 upload_texture_count = 0;
@@ -1019,23 +1071,39 @@ void LLMaterialEditor::finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID
     }
 }
 
-void LLMaterialEditor::finishSaveAs(const LLUUID &oldItemId, const LLUUID &newItemId, const std::string &buffer)
+void LLMaterialEditor::finishSaveAs(
+    const LLSD &oldKey,
+    const LLUUID &newItemId,
+    const std::string &buffer,
+    bool has_unsaved_changes)
 {
-    LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", LLSD(oldItemId));
+    LLMaterialEditor* me = LLFloaterReg::findTypedInstance<LLMaterialEditor>("material_editor", oldKey);
     LLViewerInventoryItem* item = gInventory.getItem(newItemId);
     if (item)
     {
         if (me)
         {
             me->mItemUUID = newItemId;
+            me->mObjectUUID = LLUUID::null;
+            me->mNotecardInventoryID = LLUUID::null;
+            me->mNotecardObjectID = LLUUID::null;
+            me->mAuxItem = nullptr;
             me->setKey(LLSD(newItemId)); // for findTypedInstance
             me->setMaterialName(item->getName());
-            if (!saveToInventoryItem(buffer, newItemId, LLUUID::null))
+            if (has_unsaved_changes)
             {
+                if (!saveToInventoryItem(buffer, newItemId, LLUUID::null))
+                {
+                    me->setEnabled(true);
+                }
+            }
+            else
+            {
+                me->loadAsset();
                 me->setEnabled(true);
             }
         }
-        else
+        else if(has_unsaved_changes)
         {
             saveToInventoryItem(buffer, newItemId, LLUUID::null);
         }
@@ -1052,17 +1120,24 @@ void LLMaterialEditor::refreshFromInventory(const LLUUID& new_item_id)
     if (new_item_id.notNull())
     {
         mItemUUID = new_item_id;
-        if (mObjectUUID.isNull())
+        if (mNotecardInventoryID.notNull())
         {
-            setKey(LLSD(new_item_id));
+            LLSD floater_key;
+            floater_key["objectid"] = mNotecardObjectID;
+            floater_key["notecardid"] = mNotecardInventoryID;
+            setKey(floater_key);
         }
-        else
+        else if (mObjectUUID.notNull())
         {
             LLSD floater_key;
             floater_key["taskid"] = new_item_id;
             floater_key["itemid"] = mObjectUUID;
             setKey(floater_key);
         }
+        else
+        {
+            setKey(LLSD(new_item_id));
+        }
     }
     LL_DEBUGS() << "LLPreviewNotecard::refreshFromInventory()" << LL_ENDL;
     loadAsset();
@@ -1094,7 +1169,15 @@ void LLMaterialEditor::onSaveAsMsgCallback(const LLSD& notification, const LLSD&
         LLInventoryObject::correctInventoryName(new_name);
         if (!new_name.empty())
         {
-            const LLInventoryItem* item = getItem();
+            const LLInventoryItem* item;
+            if (mNotecardInventoryID.notNull())
+            {
+                item = mAuxItem.get();
+            }
+            else
+            {
+                item = getItem();
+            }
             if (item)
             {
                 const LLUUID &marketplacelistings_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MARKETPLACE_LISTINGS, false);
@@ -1105,15 +1188,27 @@ void LLMaterialEditor::onSaveAsMsgCallback(const LLSD& notification, const LLSD&
                 }
 
                 // A two step process, first copy an existing item, then create new asset
-                std::string buffer = getEncodedAsset();
-                LLPointer<LLInventoryCallback> cb = new LLMaterialEditorCopiedCallback(buffer, item->getUUID());
-                copy_inventory_item(
-                    gAgent.getID(),
-                    item->getPermissions().getOwner(),
-                    item->getUUID(),
-                    parent_id,
-                    new_name,
-                    cb);
+                if (mNotecardInventoryID.notNull())
+                {
+                    LLPointer<LLInventoryCallback> cb = new LLMaterialEditorCopiedCallback(getKey(), new_name);
+                    copy_inventory_from_notecard(parent_id,
+                        mNotecardObjectID,
+                        mNotecardInventoryID,
+                        mAuxItem.get(),
+                        gInventoryCallbacks.registerCB(cb));
+                }
+                else
+                {
+                    std::string buffer = getEncodedAsset();
+                    LLPointer<LLInventoryCallback> cb = new LLMaterialEditorCopiedCallback(buffer, getKey(), mHasUnsavedChanges);
+                    copy_inventory_item(
+                        gAgent.getID(),
+                        item->getPermissions().getOwner(),
+                        item->getUUID(),
+                        parent_id,
+                        new_name,
+                        cb);
+                }
 
                 mAssetStatus = PREVIEW_ASSET_LOADING;
                 setEnabled(false);
@@ -1772,19 +1867,26 @@ void LLMaterialEditor::loadAsset()
     // TODO: see commented out "editor" references and make them do something appropriate to the UI
    
     // request the asset.
-    const LLInventoryItem* item = getItem();
+    const LLInventoryItem* item;
+    if (mNotecardInventoryID.notNull())
+    {
+        item = mAuxItem.get();
+    }
+    else
+    {
+        item = getItem();
+    }
     
     bool fail = false;
 
     if (item)
     {
         LLPermissions perm(item->getPermissions());
-        BOOL allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE);
-        BOOL allow_modify = canModify(mObjectUUID, item);
-        BOOL source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
+        bool allow_copy = gAgent.allowOperation(PERM_COPY, perm, GP_OBJECT_MANIPULATE);
+        bool allow_modify = canModify(mObjectUUID, item);
+        bool source_library = mObjectUUID.isNull() && gInventory.isObjectDescendentOf(mItemUUID, gInventory.getLibraryRootFolderID());
 
         setCanSaveAs(allow_copy);
-        setCanSave(allow_modify && !source_library);
         setMaterialName(item->getName());
 
         {
@@ -1801,7 +1903,11 @@ void LLMaterialEditor::loadAsset()
                 LLHost source_sim = LLHost();
                 LLSD* user_data = new LLSD();
 
-                if (mObjectUUID.notNull())
+                if (mNotecardInventoryID.notNull())
+                {
+                    user_data->with("objectid", mNotecardObjectID).with("notecardid", mNotecardInventoryID);
+                }
+                else if (mObjectUUID.notNull())
                 {
                     LLViewerObject* objectp = gObjectList.findObject(mObjectUUID);
                     if (objectp && objectp->getRegion())
diff --git a/indra/newview/llmaterialeditor.h b/indra/newview/llmaterialeditor.h
index 4e17cee154c6bf03cbba96f02938eae0644d7342..d329222648b69f7748a5bf7408b7556b93f2890a 100644
--- a/indra/newview/llmaterialeditor.h
+++ b/indra/newview/llmaterialeditor.h
@@ -136,7 +136,11 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener
 
     static void finishTaskUpload(LLUUID itemId, LLUUID newAssetId, LLUUID taskId);
 
-    static void finishSaveAs(const LLUUID &oldItemId, const LLUUID &newItemId, const std::string &buffer);
+    static void finishSaveAs(
+        const LLSD &oldKey,
+        const LLUUID &newItemId,
+        const std::string &buffer,
+        bool has_unsaved_changes);
 
     void refreshFromInventory(const LLUUID& new_item_id = LLUUID::null);
 
@@ -147,6 +151,7 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener
 
     // llpreview
     void setObjectID(const LLUUID& object_id) override;
+    void setAuxItem(const LLInventoryItem* item) override;
 
 	// llpanel
 	BOOL postBuild() override;
diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp
index c5bf18a065dd52c772ceda09b8a783286348fac3..d61cc26f62943b09aa1099f53726a4d4544b1424 100644
--- a/indra/newview/llpanelobjectinventory.cpp
+++ b/indra/newview/llpanelobjectinventory.cpp
@@ -1198,10 +1198,12 @@ void LLTaskMaterialBridge::openItem()
         LLSD floater_key;
         floater_key["taskid"] = mPanel->getTaskUUID();
         floater_key["itemid"] = mUUID;
-        LLMaterialEditor* mat = LLFloaterReg::showTypedInstance<LLMaterialEditor>("material_editor", floater_key, TAKE_FOCUS_YES);
+        LLMaterialEditor* mat = LLFloaterReg::getTypedInstance<LLMaterialEditor>("material_editor", floater_key);
         if (mat)
         {
             mat->setObjectID(mPanel->getTaskUUID());
+            mat->openFloater(floater_key);
+            mat->setFocus(TRUE);
         }
     }
 }
diff --git a/indra/newview/llpreview.h b/indra/newview/llpreview.h
index 9ac15d16399981caa2b7778ff06d3e15d639489c..ab60f4c008653fa5d06b13e4b509e2dfd2a1960e 100644
--- a/indra/newview/llpreview.h
+++ b/indra/newview/llpreview.h
@@ -83,7 +83,7 @@ class LLPreview : public LLFloater, LLInventoryObserver
 	virtual BOOL handleHover(S32 x, S32 y, MASK mask);
 	virtual void onOpen(const LLSD& key);
 	
-	void setAuxItem( const LLInventoryItem* item );
+	virtual void setAuxItem( const LLInventoryItem* item );
 
 	static void			onBtnCopyToInv(void* userdata);
 
diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp
index 7c860936a52e18b21d8870ada0c572a75ae0f97c..3f302d4f457b44aec1b0e47b0c77386fc3d1e624 100644
--- a/indra/newview/llviewertexteditor.cpp
+++ b/indra/newview/llviewertexteditor.cpp
@@ -42,6 +42,7 @@
 #include "lllandmark.h"
 #include "lllandmarkactions.h"
 #include "lllandmarklist.h"
+#include "llmaterialeditor.h"
 #include "llmemorystream.h"
 #include "llmenugl.h"
 #include "llnotecard.h"
@@ -542,6 +543,7 @@ LLUIImagePtr LLEmbeddedItems::getItemImage(llwchar ext_char) const
 			case LLAssetType::AT_GESTURE:		img_name = "Inv_Gesture";	break;
 			case LLAssetType::AT_MESH:      	img_name = "Inv_Mesh";	    break;
             case LLAssetType::AT_SETTINGS:      img_name = "Inv_Settings"; break;
+            case LLAssetType::AT_MATERIAL:      img_name = "Inv_Material"; break;
 			default:                        	img_name = "Inv_Invalid";  break; // use the Inv_Invalid icon for undefined object types (see MAINT-3981)
 
 		}
@@ -1127,6 +1129,9 @@ BOOL LLViewerTextEditor::openEmbeddedItem(LLPointer<LLInventoryItem> item, llwch
 		case LLAssetType::AT_SETTINGS:
 			openEmbeddedSetting(item, wc);
 			return TRUE;
+        case LLAssetType::AT_MATERIAL:
+            openEmbeddedGLTFMaterial(item, wc);
+            return TRUE;
 		case LLAssetType::AT_NOTECARD:
 		case LLAssetType::AT_LSL_TEXT:
 		case LLAssetType::AT_CLOTHING:
@@ -1213,6 +1218,26 @@ void LLViewerTextEditor::openEmbeddedSetting(LLInventoryItem* item, llwchar wc)
 	}
 }
 
+void LLViewerTextEditor::openEmbeddedGLTFMaterial(LLInventoryItem* item, llwchar wc)
+{
+    if (!item)
+    {
+        return;
+    }
+
+    LLSD floater_key;
+    floater_key["objectid"] = mObjectID;
+    floater_key["notecardid"] = mNotecardInventoryID;
+    LLMaterialEditor* preview = LLFloaterReg::getTypedInstance<LLMaterialEditor>("material_editor", floater_key);
+    if (preview)
+    {
+        preview->setAuxItem(item);
+        preview->setNotecardInfo(mNotecardInventoryID, mObjectID);
+        preview->openFloater(floater_key);
+        preview->setFocus(TRUE);
+    }
+}
+
 void LLViewerTextEditor::showUnsavedAlertDialog( LLInventoryItem* item )
 {
 	LLSD payload;
diff --git a/indra/newview/llviewertexteditor.h b/indra/newview/llviewertexteditor.h
index a6d7fef4099bc6790168a3d387ce1121702d89b3..6170d476b863deacbfad070b741a29fc6472ffde 100644
--- a/indra/newview/llviewertexteditor.h
+++ b/indra/newview/llviewertexteditor.h
@@ -108,6 +108,7 @@ class LLViewerTextEditor : public LLTextEditor
 	void			openEmbeddedLandmark( LLPointer<LLInventoryItem> item_ptr, llwchar wc );
 	void			openEmbeddedCallingcard( LLInventoryItem* item, llwchar wc);
 	void			openEmbeddedSetting(LLInventoryItem* item, llwchar wc);
+    void			openEmbeddedGLTFMaterial(LLInventoryItem* item, llwchar wc);
 	void			showCopyToInvDialog( LLInventoryItem* item, llwchar wc );
 	void			showUnsavedAlertDialog( LLInventoryItem* item );