Merge branch 'DRTVWR-559' of github.com:secondlife/viewer into DRTVWR-559

master
RunitaiLinden 2023-08-22 13:18:12 -05:00
commit a3a8116060
31 changed files with 1057 additions and 215 deletions

View File

@ -332,17 +332,6 @@ void LLMaterial::setAlphaMaskCutoff(U8 cutoff)
mAlphaMaskCutoff = cutoff;
}
LLUUID LLMaterial::getMaterialID() const
{
// TODO - not null
return LLUUID::null;
}
void LLMaterial::setMaterialID(const LLUUID &material_id)
{
// TODO - set
}
LLSD LLMaterial::asLLSD() const
{
LLSD material_data;

View File

@ -115,8 +115,6 @@ public:
void setDiffuseAlphaMode(U8 alpha_mode);
U8 getAlphaMaskCutoff() const;
void setAlphaMaskCutoff(U8 cutoff);
LLUUID getMaterialID() const;
void setMaterialID(LLUUID const & material_id);
bool isNull() const;
static const LLMaterial null;

View File

@ -246,6 +246,7 @@ set(viewer_SOURCE_FILES
llfloatermyscripts.cpp
llfloatermyenvironment.cpp
llfloaternamedesc.cpp
llfloaternewfeaturenotification.cpp
llfloaternotificationsconsole.cpp
llfloaternotificationstabbed.cpp
llfloateroutfitphotopreview.cpp
@ -895,6 +896,7 @@ set(viewer_HEADER_FILES
llfloatermyscripts.h
llfloatermyenvironment.h
llfloaternamedesc.h
llfloaternewfeaturenotification.h
llfloaternotificationsconsole.h
llfloaternotificationstabbed.h
llfloateroutfitphotopreview.h

View File

@ -73,7 +73,6 @@
<string>Avatar</string>
<string>Voice</string>
-->
<string>Capabilities</string>
</array>
</map>
</array>

View File

@ -250,7 +250,7 @@
<key>TextureLivePreview</key>
<map>
<key>Comment</key>
<string>Preview selections in texture picker immediately</string>
<string>Preview selections in texture picker or material picker immediately</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
@ -5519,6 +5519,17 @@
<key>Value</key>
<integer>0</integer>
</map>
<key>LastUIFeatureVersion</key>
<map>
<key>Comment</key>
<string>UI Feature Version number for tracking feature notification between viewer builds</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>LLSD</string>
<key>Value</key>
<string></string>
</map>
<key>LastFindPanel</key>
<map>
<key>Comment</key>

View File

@ -120,6 +120,11 @@ const F64 CHAT_AGE_FAST_RATE = 3.0;
const F32 MIN_FIDGET_TIME = 8.f; // seconds
const F32 MAX_FIDGET_TIME = 20.f; // seconds
const S32 UI_FEATURE_VERSION = 1;
// For version 1: 1 - inventory, 2 - gltf
// Will need to change to 3 once either inventory or gltf releases and cause a conflict
const S32 UI_FEATURE_FLAGS = 2;
// The agent instance.
LLAgent gAgent;
@ -372,7 +377,7 @@ LLAgent::LLAgent() :
mHideGroupTitle(FALSE),
mGroupID(),
mInitialized(FALSE),
mInitialized(false),
mListener(),
mDoubleTapRunTimer(),
@ -447,7 +452,7 @@ LLAgent::LLAgent() :
mNextFidgetTime(0.f),
mCurrentFidget(0),
mFirstLogin(FALSE),
mFirstLogin(false),
mOutfitChosen(FALSE),
mVoiceConnected(false),
@ -504,7 +509,7 @@ void LLAgent::init()
mHttpPolicy = app_core_http.getPolicy(LLAppCoreHttp::AP_AGENT);
mInitialized = TRUE;
mInitialized = true;
}
//-----------------------------------------------------------------------------
@ -559,6 +564,93 @@ void LLAgent::onAppFocusGained()
}
}
void LLAgent::setFirstLogin(bool b)
{
mFirstLogin = b;
if (mFirstLogin)
{
// Don't notify new users about new features
if (getFeatureVersion() <= UI_FEATURE_VERSION)
{
setFeatureVersion(UI_FEATURE_VERSION, UI_FEATURE_FLAGS);
}
}
}
void LLAgent::setFeatureVersion(S32 version, S32 flags)
{
LLSD updated_version;
updated_version["version"] = version;
updated_version["flags"] = flags;
gSavedSettings.setLLSD("LastUIFeatureVersion", updated_version);
}
S32 LLAgent::getFeatureVersion()
{
S32 version;
S32 flags;
getFeatureVersionAndFlags(version, flags);
return version;
}
void LLAgent::getFeatureVersionAndFlags(S32& version, S32& flags)
{
version = 0;
flags = 0;
LLSD feature_version = gSavedSettings.getLLSD("LastUIFeatureVersion");
if (feature_version.isInteger())
{
version = feature_version.asInteger();
flags = 1; // inventory flag
}
else if (feature_version.isMap())
{
version = feature_version["version"];
flags = feature_version["flags"];
}
else if (!feature_version.isString() && !feature_version.isUndefined())
{
// is something newer inside?
version = UI_FEATURE_VERSION;
flags = UI_FEATURE_FLAGS;
}
}
void LLAgent::showLatestFeatureNotification(const std::string key)
{
S32 version;
S32 flags; // a single release can have multiple new features
getFeatureVersionAndFlags(version, flags);
if (version <= UI_FEATURE_VERSION && (flags & UI_FEATURE_FLAGS) != UI_FEATURE_FLAGS)
{
S32 flag = 0;
if (key == "inventory")
{
// Notify user about new thumbnail support
flag = 1;
}
if (key == "gltf")
{
flag = 2;
}
if ((flags & flag) == 0)
{
// Need to open on top even if called from onOpen,
// do on idle to make sure it's on top
LLSD floater_key(key);
doOnIdleOneTime([floater_key]()
{
LLFloaterReg::showInstance("new_feature_notification", floater_key);
});
setFeatureVersion(UI_FEATURE_VERSION, flags | flag);
}
}
}
void LLAgent::ageChat()
{

View File

@ -117,15 +117,20 @@ private:
//--------------------------------------------------------------------
public:
void onAppFocusGained();
void setFirstLogin(BOOL b) { mFirstLogin = b; }
void setFirstLogin(bool b);
// Return TRUE if the database reported this login as the first for this particular user.
BOOL isFirstLogin() const { return mFirstLogin; }
BOOL isInitialized() const { return mInitialized; }
bool isFirstLogin() const { return mFirstLogin; }
bool isInitialized() const { return mInitialized; }
void setFeatureVersion(S32 version, S32 flags);
S32 getFeatureVersion();
void getFeatureVersionAndFlags(S32 &version, S32 &flags);
void showLatestFeatureNotification(const std::string key);
public:
std::string mMOTD; // Message of the day
private:
BOOL mInitialized;
BOOL mFirstLogin;
bool mInitialized;
bool mFirstLogin;
boost::shared_ptr<LLAgentListener> mListener;
//--------------------------------------------------------------------

View File

@ -76,6 +76,8 @@ BOOL LLFloaterBulkPermission::postBuild()
mBulkChangeIncludeSounds = gSavedSettings.getBOOL("BulkChangeIncludeSounds");
mBulkChangeIncludeTextures = gSavedSettings.getBOOL("BulkChangeIncludeTextures");
mBulkChangeIncludeSettings = gSavedSettings.getBOOL("BulkChangeIncludeSettings");
mBulkChangeIncludeMaterials = gSavedSettings.getBOOL("BulkChangeIncludeMaterials");
mBulkChangeShareWithGroup = gSavedSettings.getBOOL("BulkChangeShareWithGroup");
mBulkChangeEveryoneCopy = gSavedSettings.getBOOL("BulkChangeEveryoneCopy");
mBulkChangeNextOwnerModify = gSavedSettings.getBOOL("BulkChangeNextOwnerModify");
@ -188,6 +190,8 @@ void LLFloaterBulkPermission::onCloseBtn()
gSavedSettings.setBOOL("BulkChangeIncludeSounds", mBulkChangeIncludeSounds);
gSavedSettings.setBOOL("BulkChangeIncludeTextures", mBulkChangeIncludeTextures);
gSavedSettings.setBOOL("BulkChangeIncludeSettings", mBulkChangeIncludeSettings);
gSavedSettings.setBOOL("BulkChangeIncludeMaterials", mBulkChangeIncludeMaterials);
gSavedSettings.setBOOL("BulkChangeShareWithGroup", mBulkChangeShareWithGroup);
gSavedSettings.setBOOL("BulkChangeEveryoneCopy", mBulkChangeEveryoneCopy);
gSavedSettings.setBOOL("BulkChangeNextOwnerModify", mBulkChangeNextOwnerModify);
@ -284,6 +288,7 @@ void LLFloaterBulkPermission::doCheckUncheckAll(BOOL check)
gSavedSettings.setBOOL("BulkChangeIncludeSounds" , check);
gSavedSettings.setBOOL("BulkChangeIncludeTextures" , check);
gSavedSettings.setBOOL("BulkChangeIncludeSettings" , check);
gSavedSettings.setBOOL("BulkChangeIncludeMaterials" , check);
}

View File

@ -103,6 +103,8 @@ private:
bool mBulkChangeIncludeSounds;
bool mBulkChangeIncludeTextures;
bool mBulkChangeIncludeSettings;
bool mBulkChangeIncludeMaterials;
bool mBulkChangeShareWithGroup;
bool mBulkChangeEveryoneCopy;
bool mBulkChangeNextOwnerModify;

View File

@ -0,0 +1,83 @@
/**
* @file llfloaternewfeaturenotification.cpp
* @brief LLFloaterNewFeatureNotification class implementation
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2023, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#include "llviewerprecompiledheaders.h"
#include "llfloaternewfeaturenotification.h"
LLFloaterNewFeatureNotification::LLFloaterNewFeatureNotification(const LLSD& key)
: LLFloater(key)
{
}
LLFloaterNewFeatureNotification::~LLFloaterNewFeatureNotification()
{
}
BOOL LLFloaterNewFeatureNotification::postBuild()
{
setCanDrag(FALSE);
getChild<LLButton>("close_btn")->setCommitCallback(boost::bind(&LLFloaterNewFeatureNotification::onCloseBtn, this));
const std::string title_txt = "title_txt";
const std::string dsc_txt = "description_txt";
std::string feature = "_" + getKey().asString();
getChild<LLUICtrl>(title_txt)->setValue(getString(title_txt + feature));
getChild<LLUICtrl>(dsc_txt)->setValue(getString(dsc_txt + feature));
if (getKey().asString() == "gltf")
{
LLRect rect = getRect();
// make automatic?
reshape(rect.getWidth() + 90, rect.getHeight() + 45);
}
return TRUE;
}
void LLFloaterNewFeatureNotification::onOpen(const LLSD& key)
{
centerOnScreen();
}
void LLFloaterNewFeatureNotification::onCloseBtn()
{
closeFloater();
}
void LLFloaterNewFeatureNotification::centerOnScreen()
{
LLVector2 window_size = LLUI::getInstance()->getWindowSize();
centerWithin(LLRect(0, 0, ll_round(window_size.mV[VX]), ll_round(window_size.mV[VY])));
LLFloaterView* parent = dynamic_cast<LLFloaterView*>(getParent());
if (parent)
{
parent->bringToFront(this);
}
}

View File

@ -0,0 +1,49 @@
/**
* @file llfloaternewfeaturenotification.h
* @brief LLFloaterNewFeatureNotification class definition
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Second Life Viewer Source Code
* Copyright (C) 2023, Linden Research, Inc.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License only.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
* $/LicenseInfo$
*/
#ifndef LL_FLOATER_NEW_FEATURE_NOTOFICATION_H
#define LL_FLOATER_NEW_FEATURE_NOTOFICATION_H
#include "llfloater.h"
class LLFloaterNewFeatureNotification:
public LLFloater
{
friend class LLFloaterReg;
public:
BOOL postBuild() override;
void onOpen(const LLSD& key) override;
private:
LLFloaterNewFeatureNotification(const LLSD& key);
/*virtual*/ ~LLFloaterNewFeatureNotification();
void centerOnScreen();
void onCloseBtn();
};
#endif

View File

@ -37,6 +37,7 @@
#include "llfilesystem.h"
#include "llgltfmateriallist.h"
#include "llinventorymodel.h"
#include "llinventoryobserver.h"
#include "lllocalgltfmaterials.h"
#include "llnotificationsutil.h"
#include "lltexturectrl.h"
@ -237,7 +238,9 @@ struct LLSelectedTEGetMatData : public LLSelectedTEFunctor
LLUUID mTexEmissiveId;
LLUUID mTexNormalId;
LLUUID mObjectId;
LLViewerObject* mObject = nullptr;
S32 mObjectTE;
LLUUID mMaterialId;
LLPointer<LLGLTFMaterial> mMaterial;
LLPointer<LLLocalGLTFMaterial> mLocalMaterial;
};
@ -259,6 +262,7 @@ bool LLSelectedTEGetMatData::apply(LLViewerObject* objectp, S32 te_index)
return false;
}
LLUUID mat_id = objectp->getRenderMaterialID(te_index);
mMaterialId = mat_id;
bool can_use = mIsOverride ? objectp->permModify() : objectp->permCopy();
LLTextureEntry *tep = objectp->getTE(te_index);
// We might want to disable this entirely if at least
@ -290,6 +294,7 @@ bool LLSelectedTEGetMatData::apply(LLViewerObject* objectp, S32 te_index)
mTexEmissiveId = tex_emissive_id;
mTexNormalId = tex_normal_id;
mObjectTE = te_index;
mObject = objectp;
mObjectId = objectp->getID();
mFirst = false;
}
@ -318,6 +323,8 @@ bool LLSelectedTEGetMatData::apply(LLViewerObject* objectp, S32 te_index)
LLGLTFMaterial *mat = tep->getGLTFMaterial();
LLLocalGLTFMaterial *local_mat = dynamic_cast<LLLocalGLTFMaterial*>(mat);
mObject = objectp;
mObjectId = objectp->getID();
if (local_mat)
{
mLocalMaterial = local_mat;
@ -1192,7 +1199,9 @@ bool LLMaterialEditor::saveIfNeeded()
{
//make a new inventory item
std::string res_desc = buildMaterialDescription();
createInventoryItem(buffer, mMaterialName, res_desc);
LLPermissions local_permissions;
local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
createInventoryItem(buffer, mMaterialName, res_desc, local_permissions);
// We do not update floater with uploaded asset yet, so just close it.
closeFloater();
@ -1286,36 +1295,37 @@ bool LLMaterialEditor::updateInventoryItem(const std::string &buffer, const LLUU
return true;
}
void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc)
// Callback intended for when a material is saved from an object and needs to
// be modified to reflect the new asset/name.
class LLObjectsMaterialItemCallback : public LLInventoryCallback
{
// gen a new uuid for this asset
LLTransactionID tid;
tid.generate(); // timestamp-based randomization + uniquification
U32 next_owner_perm = LLFloaterPerms::getNextOwnerPerms("Materials");
LLUUID parent = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_MATERIAL);
const U8 subtype = NO_INV_SUBTYPE; // TODO maybe use AT_SETTINGS and LLSettingsType::ST_MATERIAL ?
public:
LLObjectsMaterialItemCallback(const LLPermissions& permissions, const std::string& asset_data, const std::string& new_name)
: mPermissions(permissions),
mAssetData(asset_data),
mNewName(new_name)
{
}
create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent, tid, name, desc,
LLAssetType::AT_MATERIAL, LLInventoryType::IT_MATERIAL, subtype, next_owner_perm,
new LLBoostFuncInventoryCallback([output = buffer](LLUUID const& inv_item_id)
void fire(const LLUUID& inv_item_id) override
{
LLViewerInventoryItem* item = gInventory.getItem(inv_item_id);
if (item)
if (!item)
{
// create_inventory_item doesn't allow presetting some permissions, fix it now
LLPermissions perm = item->getPermissions();
if (perm.getMaskEveryone() != LLFloaterPerms::getEveryonePerms("Materials")
|| perm.getMaskGroup() != LLFloaterPerms::getGroupPerms("Materials"))
{
perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Materials"));
perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Materials"));
return;
}
item->setPermissions(perm);
// create_inventory_item/copy_inventory_item don't allow presetting some permissions, fix it now
item->setPermissions(mPermissions);
item->updateServer(FALSE);
gInventory.updateItem(item);
gInventory.notifyObservers();
item->updateServer(FALSE);
gInventory.updateItem(item);
gInventory.notifyObservers();
}
if (item->getName() != mNewName)
{
LLSD updates;
updates["name"] = mNewName;
update_inventory_item(inv_item_id, updates, NULL);
}
// from reference in LLSettingsVOBase::createInventoryItem()/updateInventoryItem()
@ -1323,7 +1333,7 @@ void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std:
std::make_shared<LLBufferedAssetUploadInfo>(
inv_item_id,
LLAssetType::AT_MATERIAL,
output,
mAssetData,
[](LLUUID item_id, LLUUID new_asset_id, LLUUID new_item_id, LLSD response)
{
// done callback
@ -1342,8 +1352,25 @@ void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std:
}
LLViewerAssetUpload::EnqueueInventoryUpload(agent_url, uploadInfo);
}
})
);
}
private:
LLPermissions mPermissions;
std::string mAssetData;
std::string mNewName;
};
void LLMaterialEditor::createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions)
{
// gen a new uuid for this asset
LLTransactionID tid;
tid.generate(); // timestamp-based randomization + uniquification
LLUUID parent = gInventory.findUserDefinedCategoryUUIDForType(LLFolderType::FT_MATERIAL);
const U8 subtype = NO_INV_SUBTYPE; // TODO maybe use AT_SETTINGS and LLSettingsType::ST_MATERIAL ?
LLPointer<LLObjectsMaterialItemCallback> cb = new LLObjectsMaterialItemCallback(permissions, buffer, name);
create_inventory_item(gAgent.getID(), gAgent.getSessionID(), parent, tid, name, desc,
LLAssetType::AT_MATERIAL, LLInventoryType::IT_MATERIAL, subtype, permissions.getMaskNextOwner(),
cb);
}
void LLMaterialEditor::finishInventoryUpload(LLUUID itemId, LLUUID newAssetId, LLUUID newItemId)
@ -1792,55 +1819,151 @@ void LLMaterialEditor::loadLive()
}
}
void LLMaterialEditor::saveObjectsMaterialAs()
// *NOTE: permissions_out includes user preferences for new item creation (LLFloaterPerms)
bool can_use_objects_material(LLSelectedTEGetMatData& func, const std::vector<PermissionBit>& ops, LLPermissions& permissions_out, LLViewerInventoryItem*& item_out)
{
LLSelectedTEGetMatData func(false);
LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, true /*first applicable*/);
saveMaterialAs(func.mMaterial, func.mLocalMaterial);
}
void LLMaterialEditor::savePickedMaterialAs()
{
LLPickInfo pick = LLToolPie::getInstance()->getPick();
if (pick.mPickType != LLPickInfo::PICK_OBJECT || !pick.getObject())
if (!LLMaterialEditor::capabilitiesAvailable())
{
return;
return false;
}
LLPointer<LLGLTFMaterial> render_material;
LLPointer<LLLocalGLTFMaterial> local_material;
// func.mIsOverride=true is used for the singleton material editor floater
// associated with the build floater. This flag also excludes objects from
// the selection that do not satisfy PERM_MODIFY.
llassert(func.mIsOverride);
LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, true /*first applicable*/);
LLViewerObject *objectp = pick.getObject();
LLUUID mat_id = objectp->getRenderMaterialID(pick.mObjectFace);
if (mat_id.notNull() && objectp->permCopy())
LLViewerObject* selected_object = func.mObject;
if (!selected_object)
{
// Try a face user picked first
// (likely the only method we need, but in such case
// enable_object_save_gltf_material will need to check this)
LLTextureEntry *tep = objectp->getTE(pick.mObjectFace);
LLGLTFMaterial *mat = tep->getGLTFMaterial();
LLLocalGLTFMaterial *local_mat = dynamic_cast<LLLocalGLTFMaterial*>(mat);
if (local_mat)
// LLSelectedTEGetMatData can fail if there are no selected faces
// with materials, but we expect at least some object is selected.
llassert(LLSelectMgr::getInstance()->getSelection()->getFirstObject());
return false;
}
if (selected_object->isInventoryPending())
{
return false;
}
for (PermissionBit op : ops)
{
if (op == PERM_MODIFY && selected_object->isPermanentEnforced())
{
local_material = local_mat;
return false;
}
}
item_out = selected_object->getInventoryItemByAsset(func.mMaterialId);
LLPermissions item_permissions;
if (item_out)
{
item_permissions.set(item_out->getPermissions());
for (PermissionBit op : ops)
{
if (!gAgent.allowOperation(op, item_permissions, GP_OBJECT_MANIPULATE))
{
return false;
}
}
// Update flags for new owner
if (!item_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true))
{
llassert(false);
return false;
}
render_material = tep->getGLTFRenderMaterial();
}
else
{
// Find an applicable material.
// Do this before showing message, because
// message is going to drop selection.
LLSelectedTEGetMatData func(false);
LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func, true /*first applicable*/);
local_material = func.mLocalMaterial;
render_material = func.mMaterial;
item_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
}
saveMaterialAs(render_material, local_material);
// Use root object for permissions checking
LLViewerObject* root_object = selected_object->getRootEdit();
LLPermissions* object_permissions_p = LLSelectMgr::getInstance()->findObjectPermissions(root_object);
LLPermissions object_permissions;
if (object_permissions_p)
{
object_permissions.set(*object_permissions_p);
for (PermissionBit op : ops)
{
if (!gAgent.allowOperation(op, object_permissions, GP_OBJECT_MANIPULATE))
{
return false;
}
}
// Update flags for new owner
if (!object_permissions.setOwnerAndGroup(LLUUID::null, gAgent.getID(), LLUUID::null, true))
{
llassert(false);
return false;
}
}
else
{
object_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
}
LLPermissions floater_perm;
floater_perm.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
floater_perm.setMaskEveryone(LLFloaterPerms::getEveryonePerms("Materials"));
floater_perm.setMaskGroup(LLFloaterPerms::getGroupPerms("Materials"));
floater_perm.setMaskNext(LLFloaterPerms::getNextOwnerPerms("Materials"));
// *NOTE: A close inspection of LLPermissions::accumulate shows that
// conflicting UUIDs will be unset. This is acceptable behavior for now.
// The server will populate creator info based on the item creation method
// used.
// *NOTE: As far as I'm aware, there is currently no good way to preserve
// creation history when there's no material item present. In that case,
// the agent who saved the material will be considered the creator.
// -Cosmic,2023-08-07
if (item_out)
{
permissions_out.set(item_permissions);
}
else
{
permissions_out.set(object_permissions);
}
permissions_out.accumulate(floater_perm);
return true;
}
void LLMaterialEditor::saveMaterialAs(const LLGLTFMaterial* render_material, const LLLocalGLTFMaterial *local_material)
bool LLMaterialEditor::canModifyObjectsMaterial()
{
LLSelectedTEGetMatData func(true);
LLPermissions permissions;
LLViewerInventoryItem* item_out;
return can_use_objects_material(func, std::vector({PERM_MODIFY}), permissions, item_out);
}
bool LLMaterialEditor::canSaveObjectsMaterial()
{
LLSelectedTEGetMatData func(true);
LLPermissions permissions;
LLViewerInventoryItem* item_out;
return can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY}), permissions, item_out);
}
void LLMaterialEditor::saveObjectsMaterialAs()
{
LLSelectedTEGetMatData func(true);
LLPermissions permissions;
LLViewerInventoryItem* item = nullptr;
bool allowed = can_use_objects_material(func, std::vector({PERM_COPY, PERM_MODIFY}), permissions, item);
if (!allowed)
{
LL_WARNS("MaterialEditor") << "Failed to save GLTF material from object" << LL_ENDL;
return;
}
const LLUUID item_id = item ? item->getUUID() : LLUUID::null;
saveObjectsMaterialAs(func.mMaterial, func.mLocalMaterial, permissions, func.mObjectId, item_id);
}
void LLMaterialEditor::saveObjectsMaterialAs(const LLGLTFMaterial* render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions, const LLUUID& object_id, const LLUUID& item_id)
{
if (local_material)
{
@ -1916,26 +2039,98 @@ void LLMaterialEditor::saveMaterialAs(const LLGLTFMaterial* render_material, con
LLSD args;
args["DESC"] = LLTrans::getString("New Material");
LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2));
if (local_material)
{
LLPermissions local_permissions;
local_permissions.init(gAgent.getID(), gAgent.getID(), LLUUID::null, LLUUID::null);
LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, local_permissions));
}
else
{
if (item_id.notNull())
{
// Copy existing item from object inventory, and create new composite asset on top of it
LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onCopyObjectsMaterialAsMsgCallback, _1, _2, permissions, object_id, item_id));
}
else
{
LLNotificationsUtil::add("SaveMaterialAs", args, payload, boost::bind(&LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback, _1, _2, permissions));
}
}
}
void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response)
// static
void LLMaterialEditor::onCopyObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions, const LLUUID& object_id, const LLUUID& item_id)
{
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
if (0 == option)
if (0 != option)
{
LLSD asset;
asset["version"] = LLGLTFMaterial::ASSET_VERSION;
asset["type"] = LLGLTFMaterial::ASSET_TYPE;
// This is the string serialized from LLGLTFMaterial::asJSON
asset["data"] = notification["payload"]["data"];
std::ostringstream str;
LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY);
std::string new_name = response["message"].asString();
createInventoryItem(str.str(), new_name, std::string());
return;
}
LLSD asset;
asset["version"] = LLGLTFMaterial::ASSET_VERSION;
asset["type"] = LLGLTFMaterial::ASSET_TYPE;
// This is the string serialized from LLGLTFMaterial::asJSON
asset["data"] = notification["payload"]["data"];
std::ostringstream str;
LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY);
LLViewerObject* object = gObjectList.findObject(object_id);
if (!object)
{
return;
}
const LLInventoryItem* item = object->getInventoryItem(item_id);
if (!item)
{
return;
}
std::string new_name = response["message"].asString();
LLInventoryObject::correctInventoryName(new_name);
if (new_name.empty())
{
return;
}
const LLUUID destination_id = gInventory.findCategoryUUIDForType(LLFolderType::FT_MATERIAL);
LLPointer<LLInventoryCallback> cb = new LLObjectsMaterialItemCallback(permissions, str.str(), new_name);
// NOTE: This should be an item copy. Saving a material to an inventory should be disabled when the associated material is no-copy.
move_or_copy_inventory_from_object(destination_id,
object_id,
item_id,
cb);
}
// static
void LLMaterialEditor::onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions)
{
S32 option = LLNotificationsUtil::getSelectedOption(notification, response);
if (0 != option)
{
return;
}
LLSD asset;
asset["version"] = LLGLTFMaterial::ASSET_VERSION;
asset["type"] = LLGLTFMaterial::ASSET_TYPE;
// This is the string serialized from LLGLTFMaterial::asJSON
asset["data"] = notification["payload"]["data"];
std::ostringstream str;
LLSDSerialize::serialize(asset, str, LLSDSerialize::LLSD_BINARY);
std::string new_name = response["message"].asString();
LLInventoryObject::correctInventoryName(new_name);
if (new_name.empty())
{
return;
}
createInventoryItem(str.str(), new_name, std::string(), permissions);
}
const void upload_bulk(const std::vector<std::string>& filenames, LLFilePicker::ELoadFilter type);
@ -2708,7 +2903,10 @@ bool LLMaterialEditor::setFromSelection()
if (func.mMaterial.notNull())
{
setFromGLTFMaterial(func.mMaterial);
setEnableEditing(true);
LLViewerObject* selected_object = func.mObject;
const LLViewerInventoryItem* item = selected_object->getInventoryItemByAsset(func.mMaterialId);
const bool allow_modify = !item || canModify(selected_object, item);
setEnableEditing(allow_modify);
}
else
{

View File

@ -38,6 +38,7 @@ class LLGLTFMaterial;
class LLLocalGLTFMaterial;
class LLTextureCtrl;
class LLTextBox;
class LLViewerInventoryItem;
namespace tinygltf
{
@ -112,9 +113,11 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener
static void updateLive(const LLUUID &object_id, S32 te);
static void loadLive();
static bool canModifyObjectsMaterial();
static bool canSaveObjectsMaterial();
static void saveObjectsMaterialAs();
static void savePickedMaterialAs();
static void onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response);
static void onCopyObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions, const LLUUID& object_id, const LLUUID& item_id);
static void onSaveObjectsMaterialAsMsgCallback(const LLSD& notification, const LLSD& response, const LLPermissions& permissions);
static void onLoadComplete(const LLUUID& asset_uuid, LLAssetType::EType type, void* user_data, S32 status, LLExtStat ext_status);
@ -229,10 +232,10 @@ class LLMaterialEditor : public LLPreview, public LLVOInventoryListener
static bool capabilitiesAvailable();
private:
static void saveMaterialAs(const LLGLTFMaterial *render_material, const LLLocalGLTFMaterial *local_material);
static void saveObjectsMaterialAs(const LLGLTFMaterial *render_material, const LLLocalGLTFMaterial *local_material, const LLPermissions& permissions, const LLUUID& object_id /* = LLUUID::null */, const LLUUID& item /* = LLUUID::null */);
static bool updateInventoryItem(const std::string &buffer, const LLUUID &item_id, const LLUUID &task_id);
static void createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc);
static void createInventoryItem(const std::string &buffer, const std::string &name, const std::string &desc, const LLPermissions& permissions);
void setFromGLTFMaterial(LLGLTFMaterial* mat);
bool setFromSelection();

View File

@ -51,6 +51,7 @@
#include "llinventorymodelbackgroundfetch.h"
#include "llfloatermediasettings.h"
#include "llfloaterreg.h"
#include "llfloatertools.h"
#include "lllineeditor.h"
#include "llmaterialmgr.h"
#include "llmaterialeditor.h"
@ -77,6 +78,7 @@
#include "llviewerregion.h"
#include "llviewerstats.h"
#include "llvovolume.h"
#include "llvoinventorylistener.h"
#include "lluictrlfactory.h"
#include "llpluginclassmedia.h"
#include "llviewertexturelist.h"// Update sel manager as to which channel we're editing so it can reflect the correct overlay UI
@ -490,6 +492,15 @@ LLPanelFace::~LLPanelFace()
unloadMedia();
}
void LLPanelFace::onVisibilityChange(BOOL new_visibility)
{
if (new_visibility)
{
gAgent.showLatestFeatureNotification("gltf");
}
LLPanel::onVisibilityChange(new_visibility);
}
void LLPanelFace::draw()
{
updateCopyTexButton();
@ -991,7 +1002,8 @@ void LLPanelFace::getState()
void LLPanelFace::updateUI(bool force_set_values /*false*/)
{ //set state of UI to match state of texture entry(ies) (calls setEnabled, setValue, etc, but NOT setVisible)
LLViewerObject* objectp = LLSelectMgr::getInstance()->getSelection()->getFirstObject();
LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode();
LLViewerObject* objectp = node ? node->getObject() : NULL;
if (objectp
&& objectp->getPCode() == LL_PCODE_VOLUME
@ -1022,6 +1034,62 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
}
}
// *NOTE: The "identical" variable is currently only used to decide if
// the texgen control should be tentative - this is not used by GLTF
// materials. -Cosmic;2022-11-09
bool identical = true; // true because it is anded below
bool identical_diffuse = false;
bool identical_norm = false;
bool identical_spec = false;
LLTextureCtrl *texture_ctrl = getChild<LLTextureCtrl>("texture control");
LLTextureCtrl *shinytexture_ctrl = getChild<LLTextureCtrl>("shinytexture control");
LLTextureCtrl *bumpytexture_ctrl = getChild<LLTextureCtrl>("bumpytexture control");
LLUUID id;
LLUUID normmap_id;
LLUUID specmap_id;
LLSelectedTE::getTexId(id, identical_diffuse);
LLSelectedTEMaterial::getNormalID(normmap_id, identical_norm);
LLSelectedTEMaterial::getSpecularID(specmap_id, identical_spec);
static S32 selected_te = -1;
if ((LLToolFace::getInstance() == LLToolMgr::getInstance()->getCurrentTool()) &&
!LLSelectMgr::getInstance()->getSelection()->isMultipleTESelected())
{
S32 new_selection = -1; // Don't use getLastSelectedTE, it could have been deselected
S32 num_tes = llmin((S32)objectp->getNumTEs(), (S32)objectp->getNumFaces());
for (S32 te = 0; te < num_tes; ++te)
{
if (node->isTESelected(te))
{
new_selection = te;
break;
}
}
if (new_selection != selected_te)
{
bool te_has_media = objectp->getTE(new_selection) && objectp->getTE(new_selection)->hasMedia();
bool te_has_pbr = objectp->getRenderMaterialID(new_selection).notNull();
if (te_has_pbr && !((mComboMatMedia->getCurrentIndex() == MATMEDIA_MEDIA) && te_has_media))
{
mComboMatMedia->selectNthItem(MATMEDIA_PBR);
}
else if (te_has_media)
{
mComboMatMedia->selectNthItem(MATMEDIA_MEDIA);
}
else if (id.notNull() || normmap_id.notNull() || specmap_id.notNull())
{
mComboMatMedia->selectNthItem(MATMEDIA_MATERIAL);
}
selected_te = new_selection;
}
}
mComboMatMedia->setEnabled(editable);
LLRadioGroup* radio_mat_type = getChild<LLRadioGroup>("radio_material_type");
@ -1043,24 +1111,8 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
getChildView("checkbox_sync_settings")->setEnabled(editable);
childSetValue("checkbox_sync_settings", gSavedSettings.getBOOL("SyncMaterialSettings"));
updateVisibility();
updateVisibility(objectp);
// *NOTE: The "identical" variable is currently only used to decide if
// the texgen control should be tentative - this is not used by GLTF
// materials. -Cosmic;2022-11-09
bool identical = true; // true because it is anded below
bool identical_diffuse = false;
bool identical_norm = false;
bool identical_spec = false;
LLTextureCtrl* texture_ctrl = getChild<LLTextureCtrl>("texture control");
LLTextureCtrl* shinytexture_ctrl = getChild<LLTextureCtrl>("shinytexture control");
LLTextureCtrl* bumpytexture_ctrl = getChild<LLTextureCtrl>("bumpytexture control");
LLUUID id;
LLUUID normmap_id;
LLUUID specmap_id;
// Color swatch
{
getChildView("color label")->setEnabled(editable);
@ -1090,9 +1142,6 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
getChild<LLUICtrl>("ColorTrans")->setValue(editable ? transparency : 0);
getChildView("ColorTrans")->setEnabled(editable && has_material);
// Specular map
LLSelectedTEMaterial::getSpecularID(specmap_id, identical_spec);
U8 shiny = 0;
bool identical_shiny = false;
@ -1158,11 +1207,6 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
// Texture
{
LLSelectedTE::getTexId(id,identical_diffuse);
// Normal map
LLSelectedTEMaterial::getNormalID(normmap_id, identical_norm);
mIsAlpha = FALSE;
LLGLenum image_format = GL_RGB;
bool identical_image_format = false;
@ -1801,29 +1845,82 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
}
}
// One-off listener that updates the build floater UI when the prim inventory updates
class PBRPickerItemListener : public LLVOInventoryListener
{
protected:
LLViewerObject* mObjectp;
bool mChangePending = true;
public:
PBRPickerItemListener(LLViewerObject* object)
: mObjectp(object)
{
registerVOInventoryListener(mObjectp, nullptr);
}
const bool isListeningFor(const LLViewerObject* objectp) const
{
return mChangePending && (objectp == mObjectp);
}
void inventoryChanged(LLViewerObject* object,
LLInventoryObject::object_list_t* inventory,
S32 serial_num,
void* user_data) override
{
if (gFloaterTools)
{
gFloaterTools->dirty();
}
removeVOInventoryListener();
mChangePending = false;
}
~PBRPickerItemListener()
{
removeVOInventoryListener();
mChangePending = false;
}
};
void LLPanelFace::updateUIGLTF(LLViewerObject* objectp, bool& has_pbr_material, bool& has_faces_without_pbr, bool force_set_values)
{
has_pbr_material = false;
const bool editable = objectp->permModify() && !objectp->isPermanentEnforced();
bool has_pbr_capabilities = LLMaterialEditor::capabilitiesAvailable();
bool identical_pbr = true;
const bool settable = has_pbr_capabilities && objectp->permModify() && !objectp->isPermanentEnforced();
const bool editable = LLMaterialEditor::canModifyObjectsMaterial();
const bool saveable = LLMaterialEditor::canSaveObjectsMaterial();
// pbr material
LLTextureCtrl* pbr_ctrl = findChild<LLTextureCtrl>("pbr_control");
if (pbr_ctrl)
{
LLUUID pbr_id;
bool identical_pbr;
LLSelectedTE::getPbrMaterialId(pbr_id, identical_pbr, has_pbr_material, has_faces_without_pbr);
pbr_ctrl->setTentative(identical_pbr ? FALSE : TRUE);
pbr_ctrl->setEnabled(editable && has_pbr_capabilities);
pbr_ctrl->setEnabled(settable);
pbr_ctrl->setImageAssetID(pbr_id);
}
getChildView("pbr_from_inventory")->setEnabled(editable && has_pbr_capabilities);
getChildView("edit_selected_pbr")->setEnabled(editable && has_pbr_material && !has_faces_without_pbr && has_pbr_capabilities);
getChildView("save_selected_pbr")->setEnabled(objectp->permCopy() && has_pbr_material && !has_faces_without_pbr && has_pbr_capabilities);
getChildView("pbr_from_inventory")->setEnabled(settable);
getChildView("edit_selected_pbr")->setEnabled(editable && !has_faces_without_pbr);
getChildView("save_selected_pbr")->setEnabled(saveable && identical_pbr);
if (objectp->isInventoryPending())
{
// Reuse the same listener when possible
if (!mInventoryListener || !mInventoryListener->isListeningFor(objectp))
{
mInventoryListener = std::make_unique<PBRPickerItemListener>(objectp);
}
}
else
{
mInventoryListener = nullptr;
}
const bool show_pbr = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR && mComboMatMedia->getEnabled();
if (show_pbr)
@ -1848,9 +1945,10 @@ void LLPanelFace::updateUIGLTF(LLViewerObject* objectp, bool& has_pbr_material,
}
}
void LLPanelFace::updateVisibilityGLTF()
void LLPanelFace::updateVisibilityGLTF(LLViewerObject* objectp /*= nullptr */)
{
const bool show_pbr = mComboMatMedia->getCurrentIndex() == MATMEDIA_PBR && mComboMatMedia->getEnabled();
const bool inventory_pending = objectp && objectp->isInventoryPending();
LLRadioGroup* radio_pbr_type = findChild<LLRadioGroup>("radio_pbr_type");
radio_pbr_type->setVisible(show_pbr);
@ -1861,8 +1959,9 @@ void LLPanelFace::updateVisibilityGLTF()
getChildView("pbr_control")->setVisible(show_pbr_render_material_id);
getChildView("pbr_from_inventory")->setVisible(show_pbr_render_material_id);
getChildView("edit_selected_pbr")->setVisible(show_pbr_render_material_id);
getChildView("save_selected_pbr")->setVisible(show_pbr_render_material_id);
getChildView("edit_selected_pbr")->setVisible(show_pbr_render_material_id && !inventory_pending);
getChildView("save_selected_pbr")->setVisible(show_pbr_render_material_id && !inventory_pending);
getChildView("material_permissions_loading_label")->setVisible(show_pbr_render_material_id && inventory_pending);
getChildView("gltfTextureScaleU")->setVisible(show_pbr);
getChildView("gltfTextureScaleV")->setVisible(show_pbr);
@ -2703,7 +2802,7 @@ void LLPanelFace::onCommitMaterialsMedia(LLUICtrl* ctrl, void* userdata)
self->refreshMedia();
}
void LLPanelFace::updateVisibility()
void LLPanelFace::updateVisibility(LLViewerObject* objectp /* = nullptr */)
{
LLRadioGroup* radio_mat_type = findChild<LLRadioGroup>("radio_material_type");
LLRadioGroup* radio_pbr_type = findChild<LLRadioGroup>("radio_pbr_type");
@ -2794,7 +2893,7 @@ void LLPanelFace::updateVisibility()
getChild<LLSpinCtrl>("rptctrl")->setVisible(show_material || show_media);
// PBR controls
updateVisibilityGLTF();
updateVisibilityGLTF(objectp);
}
// static
@ -3060,11 +3159,7 @@ void LLPanelFace::onSelectPbr(const LLSD& data)
{
id = pbr_ctrl->getImageAssetID();
}
if (LLSelectMgr::getInstance()->selectionSetGLTFMaterial(id))
{
LLSelectedTEMaterial::setMaterialID(this, id);
}
else
if (!LLSelectMgr::getInstance()->selectionSetGLTFMaterial(id))
{
refresh();
}
@ -4676,13 +4771,6 @@ void LLPanelFace::updateGLTFTextureTransform(float value, U32 pbr_type, std::fun
edit(&new_transform);
}
});
LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode();
if (node)
{
LLViewerObject* object = node->getObject();
sMaterialOverrideSelection.setObjectUpdatePending(object->getID(), node->getLastSelectedTE());
}
}
void LLPanelFace::setMaterialOverridesFromSelection()
@ -4795,17 +4883,22 @@ bool LLPanelFace::Selection::update()
return changed;
}
void LLPanelFace::Selection::setObjectUpdatePending(const LLUUID &object_id, S32 side)
{
mPendingObjectID = object_id;
mPendingSide = side;
}
void LLPanelFace::Selection::onSelectedObjectUpdated(const LLUUID& object_id, S32 side)
{
if (object_id == mSelectedObjectID && side == mSelectedSide)
if (object_id == mSelectedObjectID)
{
mChanged = true;
if (side == mLastSelectedSide)
{
mChanged = true;
}
else if (mLastSelectedSide == -1) // if last selected face was deselected
{
LLSelectNode* node = LLSelectMgr::getInstance()->getSelection()->getFirstNode();
if (node && node->isTESelected(side))
{
mChanged = true;
}
}
}
}
@ -4818,8 +4911,9 @@ bool LLPanelFace::Selection::compareSelection()
mNeedsSelectionCheck = false;
const S32 old_object_count = mSelectedObjectCount;
const S32 old_te_count = mSelectedTECount;
const LLUUID old_object_id = mSelectedObjectID;
const S32 old_side = mSelectedSide;
const S32 old_side = mLastSelectedSide;
LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection();
LLSelectNode* node = selection->getFirstNode();
@ -4827,17 +4921,23 @@ bool LLPanelFace::Selection::compareSelection()
{
LLViewerObject* object = node->getObject();
mSelectedObjectCount = selection->getObjectCount();
mSelectedTECount = selection->getTECount();
mSelectedObjectID = object->getID();
mSelectedSide = node->getLastSelectedTE();
mLastSelectedSide = node->getLastSelectedTE();
}
else
{
mSelectedObjectCount = 0;
mSelectedTECount = 0;
mSelectedObjectID = LLUUID::null;
mSelectedSide = -1;
mLastSelectedSide = -1;
}
const bool selection_changed = old_object_count != mSelectedObjectCount || old_object_id != mSelectedObjectID || old_side != mSelectedSide;
const bool selection_changed =
old_object_count != mSelectedObjectCount
|| old_te_count != mSelectedTECount
|| old_object_id != mSelectedObjectID
|| old_side != mLastSelectedSide;
mChanged = mChanged || selection_changed;
return selection_changed;
}
@ -4956,23 +5056,24 @@ void LLPanelFace::onPbrSelectionChanged(LLInventoryItem* itemp)
LLSaleInfo sale_info;
LLSelectMgr::instance().selectGetSaleInfo(sale_info);
bool can_copy = itemp->getPermissions().allowCopyBy(gAgentID); // do we have perm to copy this texture?
bool can_transfer = itemp->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID); // do we have perm to transfer this texture?
bool is_object_owner = gAgentID == obj_owner_id; // does object for which we are going to apply texture belong to the agent?
bool not_for_sale = !sale_info.isForSale(); // is object for which we are going to apply texture not for sale?
bool can_copy = itemp->getPermissions().allowCopyBy(gAgentID); // do we have perm to copy this material?
bool can_transfer = itemp->getPermissions().allowOperationBy(PERM_TRANSFER, gAgentID); // do we have perm to transfer this material?
bool can_modify = itemp->getPermissions().allowOperationBy(PERM_MODIFY, gAgentID); // do we have perm to transfer this material?
bool is_object_owner = gAgentID == obj_owner_id; // does object for which we are going to apply material belong to the agent?
bool not_for_sale = !sale_info.isForSale(); // is object for which we are going to apply material not for sale?
if (can_copy && can_transfer)
if (can_copy && can_transfer && can_modify)
{
pbr_ctrl->setCanApply(true, true);
return;
}
// if texture has (no-transfer) attribute it can be applied only for object which we own and is not for sale
// if material has (no-transfer) attribute it can be applied only for object which we own and is not for sale
pbr_ctrl->setCanApply(false, can_transfer ? true : is_object_owner && not_for_sale);
if (gSavedSettings.getBOOL("TextureLivePreview"))
{
LLNotificationsUtil::add("LivePreviewUnavailable");
LLNotificationsUtil::add("LivePreviewUnavailablePBR");
}
}
}
@ -5058,14 +5159,18 @@ void LLPanelFace::LLSelectedTE::getTexId(LLUUID& id, bool& identical)
void LLPanelFace::LLSelectedTE::getPbrMaterialId(LLUUID& id, bool& identical, bool& has_faces_with_pbr, bool& has_faces_without_pbr)
{
struct LLSelectedTEGetmatId : public LLSelectedTEGetFunctor<LLUUID>
struct LLSelectedTEGetmatId : public LLSelectedTEFunctor
{
LLSelectedTEGetmatId()
: mHasFacesWithoutPBR(false)
, mHasFacesWithPBR(false)
, mIdenticalId(true)
, mIdenticalOverride(true)
, mInitialized(false)
, mMaterialOverride(LLGLTFMaterial::sDefault)
{
}
LLUUID get(LLViewerObject* object, S32 te_index)
bool apply(LLViewerObject* object, S32 te_index) override
{
LLUUID pbr_id = object->getRenderMaterialID(te_index);
if (pbr_id.isNull())
@ -5076,12 +5181,49 @@ void LLPanelFace::LLSelectedTE::getPbrMaterialId(LLUUID& id, bool& identical, bo
{
mHasFacesWithPBR = true;
}
return pbr_id;
if (mInitialized)
{
if (mPBRId != pbr_id)
{
mIdenticalId = false;
}
LLGLTFMaterial* te_override = object->getTE(te_index)->getGLTFMaterialOverride();
if (te_override)
{
LLGLTFMaterial override = *te_override;
override.sanitizeAssetMaterial();
mIdenticalOverride &= (override == mMaterialOverride);
}
else
{
mIdenticalOverride &= (mMaterialOverride == LLGLTFMaterial::sDefault);
}
}
else
{
mInitialized = true;
mPBRId = pbr_id;
LLGLTFMaterial* override = object->getTE(te_index)->getGLTFMaterialOverride();
if (override)
{
mMaterialOverride = *override;
mMaterialOverride.sanitizeAssetMaterial();
}
}
return true;
}
bool mHasFacesWithoutPBR;
bool mHasFacesWithPBR;
bool mIdenticalId;
bool mIdenticalOverride;
bool mInitialized;
LLGLTFMaterial mMaterialOverride;
LLUUID mPBRId;
} func;
identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&func, id);
LLSelectMgr::getInstance()->getSelection()->applyToTEs(&func);
id = func.mPBRId;
identical = func.mIdenticalId && func.mIdenticalOverride;
has_faces_with_pbr = func.mHasFacesWithPBR;
has_faces_without_pbr = func.mHasFacesWithoutPBR;
}

View File

@ -35,6 +35,8 @@
#include "lltextureentry.h"
#include "llselectmgr.h"
#include <memory>
class LLButton;
class LLCheckBoxCtrl;
class LLColorSwatchCtrl;
@ -51,6 +53,8 @@ class LLMaterialID;
class LLMediaCtrl;
class LLMenuButton;
class PBRPickerItemListener;
// Represents an edit for use in replicating the op across one or more materials in the selection set.
//
// The apply function optionally performs the edit which it implements
@ -105,6 +109,7 @@ public:
static void onMaterialOverrideReceived(const LLUUID& object_id, S32 side);
/*virtual*/ void onVisibilityChange(BOOL new_visibility);
/*virtual*/ void draw();
LLMaterialPtr createDefaultMaterial(LLMaterialPtr current_material)
@ -292,7 +297,7 @@ private:
//
// Do NOT call updateUI from within this function.
//
void updateVisibility();
void updateVisibility(LLViewerObject* objectp = nullptr);
// Hey look everyone, a type-safe alternative to copy and paste! :)
//
@ -453,7 +458,7 @@ private:
void onPbrSelectionChanged(LLInventoryItem* itemp);
void updateUIGLTF(LLViewerObject* objectp, bool& has_pbr_material, bool& has_faces_without_pbr, bool force_set_values);
void updateVisibilityGLTF();
void updateVisibilityGLTF(LLViewerObject* objectp = nullptr);
void updateSelectedGLTFMaterials(std::function<void(LLGLTFMaterial*)> func);
void updateGLTFTextureTransform(float value, U32 pbr_type, std::function<void(LLGLTFMaterial::TextureTransform*)> edit);
@ -482,7 +487,6 @@ private:
// Prevents update() returning true until the provided object is
// updated. Necessary to prevent controls updating when the mouse is
// held down.
void setObjectUpdatePending(const LLUUID &object_id, S32 side);
void setDirty() { mChanged = true; };
// Callbacks
@ -497,15 +501,15 @@ private:
boost::signals2::scoped_connection mSelectConnection;
bool mNeedsSelectionCheck = true;
S32 mSelectedObjectCount = 0;
S32 mSelectedTECount = 0;
LLUUID mSelectedObjectID;
S32 mSelectedSide = -1;
LLUUID mPendingObjectID;
S32 mPendingSide = -1;
S32 mLastSelectedSide = -1;
};
static Selection sMaterialOverrideSelection;
std::unique_ptr<PBRPickerItemListener> mInventoryListener;
public:
#if defined(DEF_GET_MAT_STATE)
#undef DEF_GET_MAT_STATE
@ -586,7 +590,6 @@ public:
DEF_EDIT_MAT_STATE(LLUUID,const LLUUID&,setNormalID);
DEF_EDIT_MAT_STATE(LLUUID,const LLUUID&,setSpecularID);
DEF_EDIT_MAT_STATE(LLColor4U, const LLColor4U&,setSpecularLightColor);
DEF_EDIT_MAT_STATE(LLUUID, const LLUUID&, setMaterialID);
};
class LLSelectedTE

View File

@ -241,14 +241,22 @@ void LLPreview::refreshFromItem()
// static
BOOL LLPreview::canModify(const LLUUID taskUUID, const LLInventoryItem* item)
{
const LLViewerObject* object = nullptr;
if (taskUUID.notNull())
{
LLViewerObject* object = gObjectList.findObject(taskUUID);
if(object && !object->permModify())
{
// No permission to edit in-world inventory
return FALSE;
}
object = gObjectList.findObject(taskUUID);
}
return canModify(object, item);
}
// static
BOOL LLPreview::canModify(const LLViewerObject* object, const LLInventoryItem* item)
{
if (object && !object->permModify())
{
// No permission to edit in-world inventory
return FALSE;
}
return item && gAgent.allowOperation(PERM_MODIFY, item->getPermissions(), GP_OBJECT_MANIPULATE);

View File

@ -36,6 +36,7 @@
#include <map>
class LLInventoryItem;
class LLViewerObject;
class LLLineEditor;
class LLRadioGroup;
class LLPreview;
@ -109,6 +110,7 @@ public:
// We can't modify Item or description in preview if either in-world Object
// or Item itself is unmodifiable
static BOOL canModify(const LLUUID taskUUID, const LLInventoryItem* item);
static BOOL canModify(const LLViewerObject* object, const LLInventoryItem* item);
protected:
virtual void onCommit();

View File

@ -1773,15 +1773,17 @@ void LLObjectSelection::applyNoCopyTextureToTEs(LLViewerInventoryItem* item)
}
}
void LLObjectSelection::applyNoCopyPbrMaterialToTEs(LLViewerInventoryItem* item)
bool LLObjectSelection::applyRestrictedPbrMaterialToTEs(LLViewerInventoryItem* item)
{
if (!item)
{
return;
return false;
}
LLUUID asset_id = item->getAssetUUID();
bool material_copied_all_faces = true;
for (iterator iter = begin(); iter != end(); ++iter)
{
LLSelectNode* node = *iter;
@ -1797,12 +1799,17 @@ void LLObjectSelection::applyNoCopyPbrMaterialToTEs(LLViewerInventoryItem* item)
{
if (node->isTESelected(te))
{
//(no-copy) materials must be moved to the object's inventory only once
//(no-copy), (no-modify), and (no-transfer) materials must be moved to the object's inventory only once
// without making any copies
if (!material_copied && asset_id.notNull())
{
LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null);
material_copied = true;
material_copied = (bool)LLToolDragAndDrop::handleDropMaterialProtections(object, item, LLToolDragAndDrop::SOURCE_AGENT, LLUUID::null);
}
if (!material_copied)
{
// Applying the material is not possible for this object given the current inventory
material_copied_all_faces = false;
break;
}
// apply texture for the selected faces
@ -1814,6 +1821,8 @@ void LLObjectSelection::applyNoCopyPbrMaterialToTEs(LLViewerInventoryItem* item)
}
LLGLTFMaterialList::flushUpdates();
return material_copied_all_faces;
}
@ -1924,6 +1933,8 @@ bool LLSelectMgr::selectionSetGLTFMaterial(const LLUUID& mat_id)
{
LLViewerInventoryItem* mItem;
LLUUID mMatId;
bool material_copied_any_face = false;
bool material_copied_all_faces = true;
f(LLViewerInventoryItem* item, const LLUUID& id) : mItem(item), mMatId(id) {}
bool apply(LLViewerObject* objectp, S32 te)
{
@ -1950,14 +1961,19 @@ bool LLSelectMgr::selectionSetGLTFMaterial(const LLUUID& mat_id)
}
};
if (item && !item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()))
bool success = true;
if (item &&
(!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()) ||
!item->getPermissions().allowOperationBy(PERM_TRANSFER, gAgent.getID()) ||
!item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID())
))
{
getSelection()->applyNoCopyPbrMaterialToTEs(item);
success = success && getSelection()->applyRestrictedPbrMaterialToTEs(item);
}
else
{
f setfunc(item, mat_id);
getSelection()->applyToTEs(&setfunc);
success = success && getSelection()->applyToTEs(&setfunc);
}
struct g : public LLSelectedObjectFunctor
@ -1986,11 +2002,11 @@ bool LLSelectMgr::selectionSetGLTFMaterial(const LLUUID& mat_id)
return true;
}
} sendfunc(item);
getSelection()->applyToObjects(&sendfunc);
success = success && getSelection()->applyToObjects(&sendfunc);
LLGLTFMaterialList::flushUpdates();
return true;
return success;
}
//-----------------------------------------------------------------------------

View File

@ -378,7 +378,17 @@ public:
* Then this only texture is used for all selected faces.
*/
void applyNoCopyTextureToTEs(LLViewerInventoryItem* item);
void applyNoCopyPbrMaterialToTEs(LLViewerInventoryItem* item);
/*
* Multi-purpose function for applying PBR materials to the
* selected object or faces, any combination of copy/mod/transfer
* permission restrictions. This method moves the restricted
* material to the object's inventory and doesn't make a copy of the
* material for each face. Then this only material is used for
* all selected faces.
* Returns false if applying the material failed on one or more selected
* faces.
*/
bool applyRestrictedPbrMaterialToTEs(LLViewerInventoryItem* item);
ESelectType getSelectType() const { return mSelectType; }
@ -635,7 +645,7 @@ public:
void selectionSetRestitution(F32 restitution);
void selectionSetMaterial(U8 material);
bool selectionSetImage(const LLUUID& imageid); // could be item or asset id
bool selectionSetGLTFMaterial(const LLUUID& mat_id); // could be item or asset id
bool selectionSetGLTFMaterial(const LLUUID& mat_id); // material id only
void selectionSetColor(const LLColor4 &color);
void selectionSetColorOnly(const LLColor4 &color); // Set only the RGB channels
void selectionSetAlphaOnly(const F32 alpha); // Set only the alpha channel

View File

@ -3617,7 +3617,7 @@ bool process_login_success_response()
std::string flag = login_flags["ever_logged_in"];
if(!flag.empty())
{
gAgent.setFirstLogin((flag == "N") ? TRUE : FALSE);
gAgent.setFirstLogin(flag == "N");
}
/* Flag is currently ignored by the viewer.
@ -3708,7 +3708,7 @@ bool process_login_success_response()
std::string fake_initial_outfit_name = gSavedSettings.getString("FakeInitialOutfitName");
if (!fake_initial_outfit_name.empty())
{
gAgent.setFirstLogin(TRUE);
gAgent.setFirstLogin(true);
sInitialOutfit = fake_initial_outfit_name;
if (sInitialOutfitGender.empty())
{

View File

@ -930,6 +930,8 @@ BOOL LLToolDragAndDrop::handleDropMaterialProtections(LLViewerObject* hit_obj,
LLToolDragAndDrop::ESource source,
const LLUUID& src_id)
{
if (!item) return FALSE;
// Always succeed if....
// material is from the library
// or already in the contents of the object
@ -948,7 +950,14 @@ BOOL LLToolDragAndDrop::handleDropMaterialProtections(LLViewerObject* hit_obj,
{
hit_obj->requestInventory();
LLSD args;
args["ERROR_MESSAGE"] = "Unable to add texture.\nPlease wait a few seconds and try again.";
if (LLAssetType::AT_MATERIAL == item->getType())
{
args["ERROR_MESSAGE"] = "Unable to add material.\nPlease wait a few seconds and try again.";
}
else
{
args["ERROR_MESSAGE"] = "Unable to add texture.\nPlease wait a few seconds and try again.";
}
LLNotificationsUtil::add("ErrorMessage", args);
return FALSE;
}
@ -957,11 +966,9 @@ BOOL LLToolDragAndDrop::handleDropMaterialProtections(LLViewerObject* hit_obj,
// if the asset is already in the object's inventory
// then it can always be added to a side.
// This saves some work if the task's inventory is already loaded
// and ensures that the texture item is only added once.
// and ensures that the asset item is only added once.
return TRUE;
}
if (!item) return FALSE;
LLPointer<LLViewerInventoryItem> new_item = new LLViewerInventoryItem(item);
if (!item->getPermissions().allowOperationBy(PERM_COPY, gAgent.getID()))
@ -995,7 +1002,7 @@ BOOL LLToolDragAndDrop::handleDropMaterialProtections(LLViewerObject* hit_obj,
return FALSE;
}
}
// Add the texture item to the target object's inventory.
// Add the asset item to the target object's inventory.
if (LLAssetType::AT_TEXTURE == new_item->getType()
|| LLAssetType::AT_MATERIAL == new_item->getType())
{
@ -1005,20 +1012,24 @@ BOOL LLToolDragAndDrop::handleDropMaterialProtections(LLViewerObject* hit_obj,
{
hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true);
}
// TODO: Check to see if adding the item was successful; if not, then
// we should return false here.
// Force the object to update and refetch its inventory so it has this asset.
hit_obj->dirtyInventory();
hit_obj->requestInventory();
// TODO: Check to see if adding the item was successful; if not, then
// we should return false here. This will requre a separate listener
// since without listener, we have no way to receive update
}
else if (!item->getPermissions().allowOperationBy(PERM_TRANSFER,
gAgent.getID()))
{
// Check that we can add the texture as inventory to the object
// Check that we can add the asset as inventory to the object
if (willObjectAcceptInventory(hit_obj,item) < ACCEPT_YES_COPY_SINGLE )
{
return FALSE;
}
// *FIX: may want to make sure agent can paint hit_obj.
// Add the texture item to the target object's inventory.
// Add the asset item to the target object's inventory.
if (LLAssetType::AT_TEXTURE == new_item->getType()
|| LLAssetType::AT_MATERIAL == new_item->getType())
{
@ -1028,7 +1039,27 @@ BOOL LLToolDragAndDrop::handleDropMaterialProtections(LLViewerObject* hit_obj,
{
hit_obj->updateInventory(new_item, TASK_INVENTORY_ITEM_KEY, true);
}
// Force the object to update and refetch its inventory so it has this texture.
// Force the object to update and refetch its inventory so it has this asset.
hit_obj->dirtyInventory();
hit_obj->requestInventory();
// TODO: Check to see if adding the item was successful; if not, then
// we should return false here. This will requre a separate listener
// since without listener, we have no way to receive update
}
else if (LLAssetType::AT_MATERIAL == new_item->getType() &&
!item->getPermissions().allowOperationBy(PERM_MODIFY, gAgent.getID()))
{
// Check that we can add the material as inventory to the object
if (willObjectAcceptInventory(hit_obj,item) < ACCEPT_YES_COPY_SINGLE )
{
return FALSE;
}
// *FIX: may want to make sure agent can paint hit_obj.
// Add the material item to the target object's inventory.
hit_obj->updateMaterialInventory(new_item, TASK_INVENTORY_ITEM_KEY, true);
// Force the object to update and refetch its inventory so it has this material.
hit_obj->dirtyInventory();
hit_obj->requestInventory();
// TODO: Check to see if adding the item was successful; if not, then

View File

@ -183,11 +183,15 @@ BOOL LLToolPie::handleMouseDown(S32 x, S32 y, MASK mask)
// an item.
BOOL LLToolPie::handleRightMouseDown(S32 x, S32 y, MASK mask)
{
BOOL pick_reflection_probe = gSavedSettings.getBOOL("SelectReflectionProbes");
// don't pick transparent so users can't "pay" transparent objects
mPick = gViewerWindow->pickImmediate(x, y,
/*BOOL pick_transparent*/ FALSE,
/*BOOL pick_rigged*/ TRUE,
/*BOOL pick_particle*/ TRUE);
/*BOOL pick_particle*/ TRUE,
/*BOOL pick_unselectable*/ TRUE,
pick_reflection_probe);
mPick.mKeyMask = mask;
// claim not handled so UI focus stays same

View File

@ -98,6 +98,7 @@
#include "llfloatermyscripts.h"
#include "llfloatermyenvironment.h"
#include "llfloaternamedesc.h"
#include "llfloaternewfeaturenotification.h"
#include "llfloaternotificationsconsole.h"
#include "llfloaternotificationstabbed.h"
#include "llfloaterobjectweights.h"
@ -229,6 +230,7 @@ public:
"avatar_picker",
"camera",
"camera_presets",
"change_item_thumbnail"
"classified",
"add_landmark",
"delete_pref_preset",
@ -247,6 +249,7 @@ public:
"message_critical", // Modal!!! Login specific. If this is in use elsewhere, better to create a non modal variant
"message_tos", // Modal!!! Login specific.
"mute_object_by_name",
"new_feature_notification",
"publish_classified",
"save_pref_preset",
"save_camera_preset",
@ -395,6 +398,7 @@ void LLViewerFloaterReg::registerFloaters()
LLFloaterReg::add("moveview", "floater_moveview.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMove>);
LLFloaterReg::add("mute_object_by_name", "floater_mute_object.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterGetBlockedObjectName>);
LLFloaterReg::add("mini_map", "floater_map.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterMap>);
LLFloaterReg::add("new_feature_notification", "floater_new_feature_notification.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterNewFeatureNotification>);
LLFloaterReg::add("notifications_console", "floater_notifications_console.xml", (LLFloaterBuildFunc)&LLFloaterReg::build<LLFloaterNotificationConsole>);

View File

@ -1604,6 +1604,67 @@ void copy_inventory_from_notecard(const LLUUID& destination_id,
}
}
void move_or_copy_inventory_from_object(const LLUUID& destination_id,
const LLUUID& object_id,
const LLUUID& item_id,
LLPointer<LLInventoryCallback> cb)
{
LLViewerObject* object = gObjectList.findObject(object_id);
if (!object)
{
return;
}
const LLInventoryItem* item = object->getInventoryItem(item_id);
if (!item)
{
return;
}
class LLItemAddedObserver : public LLInventoryObserver
{
public:
LLItemAddedObserver(const LLUUID& copied_asset_id, LLPointer<LLInventoryCallback> cb)
: LLInventoryObserver(),
mAssetId(copied_asset_id),
mCallback(cb)
{
}
void changed(U32 mask) override
{
if((mask & (LLInventoryObserver::ADD)) == 0)
{
return;
}
for (const LLUUID& changed_id : gInventory.getChangedIDs())
{
LLViewerInventoryItem* changed_item = gInventory.getItem(changed_id);
if (changed_item->getAssetUUID() == mAssetId)
{
changeComplete(changed_item->getUUID());
return;
}
}
}
private:
void changeComplete(const LLUUID& item_id)
{
mCallback->fire(item_id);
gInventory.removeObserver(this);
delete this;
}
LLUUID mAssetId;
LLPointer<LLInventoryCallback> mCallback;
};
const LLUUID& asset_id = item->getAssetUUID();
LLItemAddedObserver* observer = new LLItemAddedObserver(asset_id, cb);
gInventory.addObserver(observer);
object->moveInventory(destination_id, item_id);
}
void create_new_item(const std::string& name,
const LLUUID& parent_id,
LLAssetType::EType asset_type,

View File

@ -439,6 +439,10 @@ void copy_inventory_from_notecard(const LLUUID& destination_id,
const LLInventoryItem *src,
U32 callback_id = 0);
void move_or_copy_inventory_from_object(const LLUUID& destination_id,
const LLUUID& object_id,
const LLUUID& item_id,
LLPointer<LLInventoryCallback> cb);
void menu_create_inventory_item(LLInventoryPanel* root,
LLFolderBridge* bridge,

View File

@ -3610,6 +3610,17 @@ LLInventoryObject* LLViewerObject::getInventoryObject(const LLUUID& item_id)
return rv;
}
LLInventoryItem* LLViewerObject::getInventoryItem(const LLUUID& item_id)
{
LLInventoryObject* iobj = getInventoryObject(item_id);
if (!iobj || iobj->getType() == LLAssetType::AT_CATEGORY)
{
return NULL;
}
LLInventoryItem* item = dynamic_cast<LLInventoryItem*>(iobj);
return item;
}
void LLViewerObject::getInventoryContents(LLInventoryObject::object_list_t& objects)
{
if(mInventory)

View File

@ -494,6 +494,7 @@ public:
void updateInventoryLocal(LLInventoryItem* item, U8 key); // Update without messaging.
void updateMaterialInventory(LLViewerInventoryItem* item, U8 key, bool is_new);
LLInventoryObject* getInventoryObject(const LLUUID& item_id);
LLInventoryItem* getInventoryItem(const LLUUID& item_id);
// Get content except for root category
void getInventoryContents(LLInventoryObject::object_list_t& objects);

View File

@ -170,6 +170,20 @@
name="icon_setting"
tool_tip="Environment settings"
left_pad="2" />
<check_box
control_name="BulkChangeIncludeMaterials"
height="16"
name="check_materials"
top_pad="5"
left="245"
width="16" />
<icon
height="16"
image_name="Inv_Material"
mouse_opaque="true"
name="icon_materials"
tool_tip="Materials"
left_pad="2" />
<button
height="23"
layout="topleft"

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<floater
height="130"
width="300"
layout="topleft"
name="floater_new_feature_notification"
title="NEW FEATURE"
show_title="false"
header_height="0"
bg_opaque_image="Window_NoTitle_Foreground"
bg_alpha_image="Window_NoTitle_Background"
can_resize="false"
can_drag_on_left="false"
can_minimize="false"
can_close="false">
<floater.string name="title_txt_inventory">
New inventory features
</floater.string>
<floater.string name="description_txt_inventory">
You can now add preview images to inventory items and view a folder in its own window.
Learn more in this [https://community.secondlife.com/blogs/entry/13637-new-features-inventory-item-preview-and-single-folder-view/ blogpost]
</floater.string>
<floater.string name="title_txt_gltf">
New GLTF PBR materials support
</floater.string>
<floater.string name="description_txt_gltf">
You can now use expanded material support with the ability to import and edit GLTF Physically Based Rendering (PBR) Materials.
In order to support the addition of the GLTF format, some areas in the viewer may appear darker than usual.
Learn more about [https://wiki.secondlife.com/wiki/PBR_Materials Physically Based Rendering (PBR)]
</floater.string>
<text
type="string"
length="1"
follows="top|left|right"
font="SansSerifLargeBold"
text_color="White"
layout="topleft"
left="10"
height="14"
top="10"
right="-10"
name="title_txt">
New feature
</text>
<text
type="string"
length="1"
follows="top|left|right|bottom"
text_color="White"
layout="topleft"
left="10"
height="40"
top_pad="14"
right="-10"
word_wrap="true"
name="description_txt">
Feature description
</text>
<button
follows="bottom|left|right"
layout="topleft"
height="24"
label="Got it!"
left="104"
bottom="-10"
name="close_btn"
width="90"/>
</floater>

View File

@ -9209,6 +9209,18 @@ We cannot display a preview of this texture because it is no-copy and/or no-tran
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="LivePreviewUnavailablePBR"
type="alert">
We cannot display a preview of this material because it is no-copy, no-transfer, and/or no-modify.
<usetemplate
ignoretext="Warn me that Live Preview mode is not available for no-copy, no-transfer, and/or no-modify materials"
name="okignore"
yestext="OK"/>
</notification>
<notification
icon="alertmodal.tga"
name="FacePasteFailed"

View File

@ -284,12 +284,26 @@
name="pbr_from_inventory"
label="Choose from inventory"
width="140"/>
<text
visible="false"
type="string"
length="1"
follows="left|top"
height="10"
layout="topleft"
top_pad="4"
left_delta="0"
name="material_permissions_loading_label"
text_readonly_color="LabelDisabledColor"
width="160">
Loading contents...
</text>
<button
follows="left|top"
height="23"
layout="topleft"
left_delta="0"
top_pad="4"
top_delta="0"
name="edit_selected_pbr"
label="Edit Selected"
width="140"/>