summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPtolemy <ptolemy@lindenlab.com>2022-06-30 12:50:49 -0700
committerPtolemy <ptolemy@lindenlab.com>2022-06-30 12:50:49 -0700
commit82232de0d2aecfddca2988249e79a73b4d972aa5 (patch)
treef6dfb8a8c2d0ca9ee3a3e3dca37386130ffe1653
parent38b6f10717918643c1169a4007dc92cfe36fc4d5 (diff)
parent68dfa1f5501f58d1ca158aeabcc3fd07e8a2e70f (diff)
Merge branch 'DRTVWR-559' of bitbucket.org:lindenlab/viewer into DRTVWR-559
-rw-r--r--indra/llprimitive/llgltfmaterial.h2
-rw-r--r--indra/llprimitive/llprimitive.cpp109
-rw-r--r--indra/llprimitive/llprimitive.h24
-rw-r--r--indra/newview/CMakeLists.txt2
-rw-r--r--indra/newview/llgltfmateriallist.cpp186
-rw-r--r--indra/newview/llgltfmateriallist.h48
-rw-r--r--indra/newview/llinventorybridge.cpp2
-rw-r--r--indra/newview/llmaterialeditor.cpp56
-rw-r--r--indra/newview/llmaterialeditor.h4
-rw-r--r--indra/newview/llpanelgroupnotices.cpp1
-rw-r--r--indra/newview/llpanelobjectinventory.cpp1
-rw-r--r--indra/newview/lltooldraganddrop.cpp80
-rw-r--r--indra/newview/lltooldraganddrop.h10
-rw-r--r--indra/newview/llviewerobject.cpp84
-rw-r--r--indra/newview/llviewerobject.h8
-rw-r--r--indra/newview/llviewertexteditor.cpp1
-rw-r--r--indra/newview/llvovolume.cpp27
-rw-r--r--indra/newview/llvovolume.h4
-rw-r--r--indra/newview/skins/default/xui/en/floater_material_editor.xml3
19 files changed, 602 insertions, 50 deletions
diff --git a/indra/llprimitive/llgltfmaterial.h b/indra/llprimitive/llgltfmaterial.h
index d6f59cd1a3..a8d5fb8e85 100644
--- a/indra/llprimitive/llgltfmaterial.h
+++ b/indra/llprimitive/llgltfmaterial.h
@@ -53,6 +53,8 @@ public:
F32 mMetallicFactor = 0.f;
F32 mRoughnessFactor = 0.f;
+ F32 mAlphaCutoff = 0.f;
+
bool mDoubleSided = false;
AlphaMode mAlphaMode = ALPHA_MODE_OPAQUE;
diff --git a/indra/llprimitive/llprimitive.cpp b/indra/llprimitive/llprimitive.cpp
index 9e0a079fd9..3f0059b759 100644
--- a/indra/llprimitive/llprimitive.cpp
+++ b/indra/llprimitive/llprimitive.cpp
@@ -1700,7 +1700,7 @@ BOOL LLNetworkData::isValid(U16 param_type, U32 size)
case PARAMS_EXTENDED_MESH:
return (size == 4);
case PARAMS_RENDER_MATERIAL:
- return (size == 16);
+ return (size > 1);
}
return FALSE;
@@ -2312,18 +2312,32 @@ LLRenderMaterialParams::LLRenderMaterialParams()
mType = PARAMS_RENDER_MATERIAL;
}
-BOOL LLRenderMaterialParams::pack(LLDataPacker &dp) const
+BOOL LLRenderMaterialParams::pack(LLDataPacker& dp) const
{
- return dp.packUUID(mMaterial, "material");
+ U8 count = (U8)llmin((S32)mEntries.size(), 14); //limited to 255 bytes, no more than 14 material ids
-// return TRUE;
+ dp.packU8(count, "count");
+ for (auto& entry : mEntries)
+ {
+ dp.packU8(entry.te_idx, "te_idx");
+ dp.packUUID(entry.id, "id");
+ }
+
+ return TRUE;
}
-BOOL LLRenderMaterialParams::unpack(LLDataPacker &dp)
+BOOL LLRenderMaterialParams::unpack(LLDataPacker& dp)
{
- return dp.unpackUUID(mMaterial, "material");
+ U8 count;
+ dp.unpackU8(count, "count");
+ mEntries.resize(count);
+ for (auto& entry : mEntries)
+ {
+ dp.unpackU8(entry.te_idx, "te_idx");
+ dp.unpackUUID(entry.id, "te_id");
+ }
-// return TRUE;
+ return TRUE;
}
bool LLRenderMaterialParams::operator==(const LLNetworkData& data) const
@@ -2333,40 +2347,99 @@ bool LLRenderMaterialParams::operator==(const LLNetworkData& data) const
return false;
}
- const LLRenderMaterialParams &param = static_cast<const LLRenderMaterialParams&>(data);
+ const LLRenderMaterialParams& param = static_cast<const LLRenderMaterialParams&>(data);
+
+ if (param.mEntries.size() != mEntries.size())
+ {
+ return false;
+ }
+
+ for (auto& entry : mEntries)
+ {
+ if (param.getMaterial(entry.te_idx) != entry.id)
+ {
+ return false;
+ }
+ }
- return param.mMaterial == mMaterial;
+ return true;
}
void LLRenderMaterialParams::copy(const LLNetworkData& data)
{
llassert_always(data.mType == PARAMS_RENDER_MATERIAL);
- const LLRenderMaterialParams &param = static_cast<const LLRenderMaterialParams&>(data);
- mMaterial = param.mMaterial;
+ const LLRenderMaterialParams& param = static_cast<const LLRenderMaterialParams&>(data);
+ mEntries = param.mEntries;
}
LLSD LLRenderMaterialParams::asLLSD() const
{
- return llsd::map("material", mMaterial);
+ LLSD ret;
+
+ for (int i = 0; i < mEntries.size(); ++i)
+ {
+ ret[i]["te_idx"] = mEntries[i].te_idx;
+ ret[i]["id"] = mEntries[i].id;
+ }
+
+ return ret;
}
bool LLRenderMaterialParams::fromLLSD(LLSD& sd)
{
- if (sd.has("material"))
+ if (sd.isArray())
{
- setMaterial(sd["material"]);
+ mEntries.resize(sd.size());
+ for (int i = 0; i < sd.size(); ++i)
+ {
+ if (sd[i].has("te_idx") && sd.has("id"))
+ {
+ mEntries[i].te_idx = sd[i]["te_idx"].asInteger();
+ mEntries[i].id = sd[i]["id"].asUUID();
+ }
+ else
+ {
+ return false;
+ }
+ }
+
return true;
}
return false;
}
-void LLRenderMaterialParams::setMaterial(const LLUUID & id)
+void LLRenderMaterialParams::setMaterial(U8 te, const LLUUID& id)
{
- mMaterial = id;
+ for (int i = 0; i < mEntries.size(); ++i)
+ {
+ if (mEntries[i].te_idx == te)
+ {
+ if (id.isNull())
+ {
+ mEntries.erase(mEntries.begin() + i);
+ }
+ else
+ {
+ mEntries[i].id = id;
+ }
+ return;
+ }
+ }
+
+ mEntries.push_back({ te, id });
}
-LLUUID LLRenderMaterialParams::getMaterial() const
+const LLUUID& LLRenderMaterialParams::getMaterial(U8 te) const
{
- return mMaterial;
+ for (int i = 0; i < mEntries.size(); ++i)
+ {
+ if (mEntries[i].te_idx == te)
+ {
+ return mEntries[i].id;
+ }
+ }
+
+ return LLUUID::null;
}
+
diff --git a/indra/llprimitive/llprimitive.h b/indra/llprimitive/llprimitive.h
index 25196fb894..d2adfa4a3d 100644
--- a/indra/llprimitive/llprimitive.h
+++ b/indra/llprimitive/llprimitive.h
@@ -369,22 +369,30 @@ public:
class LLRenderMaterialParams : public LLNetworkData
{
private:
- LLUUID mMaterial;
+ struct Entry
+ {
+ U8 te_idx;
+ LLUUID id;
+ };
+ std::vector< Entry > mEntries;
public:
LLRenderMaterialParams();
- BOOL pack(LLDataPacker &dp) const override;
- BOOL unpack(LLDataPacker &dp) override;
+ BOOL pack(LLDataPacker& dp) const override;
+ BOOL unpack(LLDataPacker& dp) override;
bool operator==(const LLNetworkData& data) const override;
void copy(const LLNetworkData& data) override;
LLSD asLLSD() const;
operator LLSD() const { return asLLSD(); }
bool fromLLSD(LLSD& sd);
- void setMaterial(const LLUUID & id);
- LLUUID getMaterial() const;
+ void setMaterial(U8 te_idx, const LLUUID& id);
+ const LLUUID& getMaterial(U8 te_idx) const;
+
+ bool isEmpty() { return mEntries.empty(); }
};
+
// This code is not naming-standards compliant. Leaving it like this for
// now to make the connection to code in
// BOOL packTEMessage(LLDataPacker &dp) const;
@@ -480,12 +488,12 @@ public:
virtual S32 setTEMediaFlags(const U8 te, const U8 flags);
virtual S32 setTEGlow(const U8 te, const F32 glow);
virtual S32 setTEMaterialID(const U8 te, const LLMaterialID& pMaterialID);
- virtual S32 setTEMaterialParams(const U8 index, const LLMaterialPtr pMaterialParams);
+ virtual S32 setTEMaterialParams(const U8 index, const LLMaterialPtr pMaterialParams);
virtual BOOL setMaterial(const U8 material); // returns TRUE if material changed
virtual void setTESelected(const U8 te, bool sel);
LLMaterialPtr getTEMaterialParams(const U8 index);
-
+
void copyTEs(const LLPrimitive *primitive);
S32 packTEField(U8 *cur_ptr, U8 *data_ptr, U8 data_size, U8 last_face_index, EMsgVariableType type) const;
BOOL packTEMessage(LLMessageSystem *mesgsys) const;
@@ -563,6 +571,8 @@ public:
static LLPCode legacyToPCode(const U8 legacy);
static U8 pCodeToLegacy(const LLPCode pcode);
static bool getTESTAxes(const U8 face, U32* s_axis, U32* t_axis);
+
+ BOOL hasRenderMaterialParams() const;
inline static BOOL isPrimitive(const LLPCode pcode);
inline static BOOL isApp(const LLPCode pcode);
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index d4bd1c8b57..b67bc91277 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -344,6 +344,7 @@ set(viewer_SOURCE_FILES
llgesturemgr.cpp
llgiveinventory.cpp
llglsandbox.cpp
+ llgltfmateriallist.cpp
llgroupactions.cpp
llgroupiconctrl.cpp
llgrouplist.cpp
@@ -986,6 +987,7 @@ set(viewer_HEADER_FILES
llgesturelistener.h
llgesturemgr.h
llgiveinventory.h
+ llgltfmateriallist.h
llgroupactions.h
llgroupiconctrl.h
llgrouplist.h
diff --git a/indra/newview/llgltfmateriallist.cpp b/indra/newview/llgltfmateriallist.cpp
new file mode 100644
index 0000000000..7ecbc6eeac
--- /dev/null
+++ b/indra/newview/llgltfmateriallist.cpp
@@ -0,0 +1,186 @@
+/**
+ * @file llgltfmateriallist.cpp
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, 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 "llgltfmateriallist.h"
+
+#include "llassetstorage.h"
+#include "llfilesystem.h"
+#include "llsdserialize.h"
+
+#include "tinygltf/tiny_gltf.h"
+#include <strstream>
+
+LLGLTFMaterialList gGLTFMaterialList;
+
+static LLColor4 get_color(const std::vector<double>& in)
+{
+ LLColor4 out;
+ for (S32 i = 0; i < llmin((S32)in.size(), 4); ++i)
+ {
+ out.mV[i] = in[i];
+ }
+
+ return out;
+}
+
+static void set_from_model(LLGLTFMaterial* mat, tinygltf::Model& model)
+{
+
+ S32 index;
+
+ auto& material_in = model.materials[0];
+
+ // get albedo texture
+ index = material_in.pbrMetallicRoughness.baseColorTexture.index;
+ if (index >= 0)
+ {
+ mat->mAlbedoId.set(model.images[index].uri);
+ }
+ else
+ {
+ mat->mAlbedoId.setNull();
+ }
+
+ // get normal map
+ index = material_in.normalTexture.index;
+ if (index >= 0)
+ {
+ mat->mNormalId.set(model.images[index].uri);
+ }
+ else
+ {
+ mat->mNormalId.setNull();
+ }
+
+ // get metallic-roughness texture
+ index = material_in.pbrMetallicRoughness.metallicRoughnessTexture.index;
+ if (index >= 0)
+ {
+ mat->mMetallicRoughnessId.set(model.images[index].uri);
+ }
+ else
+ {
+ mat->mMetallicRoughnessId.setNull();
+ }
+
+ // get emissive texture
+ index = material_in.emissiveTexture.index;
+ if (index >= 0)
+ {
+ mat->mEmissiveId.set(model.images[index].uri);
+ }
+ else
+ {
+ mat->mEmissiveId.setNull();
+ }
+
+ mat->setAlphaMode(material_in.alphaMode);
+ mat->mAlphaCutoff = material_in.alphaCutoff;
+
+ mat->mAlbedoColor = get_color(material_in.pbrMetallicRoughness.baseColorFactor);
+ mat->mEmissiveColor = get_color(material_in.emissiveFactor);
+
+ mat->mMetallicFactor = material_in.pbrMetallicRoughness.metallicFactor;
+ mat->mRoughnessFactor = material_in.pbrMetallicRoughness.roughnessFactor;
+
+ mat->mDoubleSided = material_in.doubleSided;
+}
+
+LLGLTFMaterial* LLGLTFMaterialList::getMaterial(const LLUUID& id)
+{
+ List::iterator iter = mList.find(id);
+ if (iter == mList.end())
+ {
+ LLGLTFMaterial* mat = new LLGLTFMaterial();
+ mList[id] = mat;
+
+ mat->ref();
+
+ gAssetStorage->getAssetData(id, LLAssetType::AT_MATERIAL,
+ [=](const LLUUID& id, LLAssetType::EType asset_type, void* user_data, S32 status, LLExtStat ext_status)
+ {
+ if (status)
+ {
+ LL_WARNS() << "Error getting material asset data: " << LLAssetStorage::getErrorString(status) << " (" << status << ")" << LL_ENDL;
+ }
+
+ LLFileSystem file(id, asset_type, LLFileSystem::READ);
+
+ std::vector<char> buffer;
+ buffer.resize(file.getSize());
+ file.read((U8*)&buffer[0], buffer.size());
+
+ LLSD asset;
+
+ // read file into buffer
+ std::istrstream str(&buffer[0], buffer.size());
+
+ if (LLSDSerialize::deserialize(asset, str, buffer.size()))
+ {
+ if (asset.has("version") && asset["version"] == "1.0")
+ {
+ if (asset.has("type") && asset["type"].asString() == "GLTF 2.0")
+ {
+ if (asset.has("data") && asset["data"].isString())
+ {
+ std::string data = asset["data"];
+
+ tinygltf::TinyGLTF gltf;
+ tinygltf::TinyGLTF loader;
+ std::string error_msg;
+ std::string warn_msg;
+
+ tinygltf::Model model_in;
+
+ if (loader.LoadASCIIFromString(&model_in, &error_msg, &warn_msg, data.c_str(), data.length(), ""))
+ {
+ set_from_model(mat, model_in);
+ }
+ else
+ {
+ LL_WARNS() << "Failed to decode material asset: " << LL_ENDL;
+ LL_WARNS() << warn_msg << LL_ENDL;
+ LL_WARNS() << error_msg << LL_ENDL;
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ LL_WARNS() << "Failed to deserialize material LLSD" << LL_ENDL;
+ }
+
+ mat->unref();
+ }, nullptr);
+
+ return mat;
+ }
+
+ return iter->second;
+}
+
diff --git a/indra/newview/llgltfmateriallist.h b/indra/newview/llgltfmateriallist.h
new file mode 100644
index 0000000000..c22134c468
--- /dev/null
+++ b/indra/newview/llgltfmateriallist.h
@@ -0,0 +1,48 @@
+/**
+ * @file llgltfmateriallist.h
+ *
+ * $LicenseInfo:firstyear=2022&license=viewerlgpl$
+ * Second Life Viewer Source Code
+ * Copyright (C) 2022, 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$
+ */
+
+
+#pragma once
+
+#include "llgltfmaterial.h"
+#include "llpointer.h"
+
+#include <unordered_map>
+
+class LLGLTFMaterialList
+{
+public:
+ LLGLTFMaterialList() {}
+
+ typedef std::unordered_map<LLUUID, LLPointer<LLGLTFMaterial > > List;
+ List mList;
+
+ LLGLTFMaterial* getMaterial(const LLUUID& id);
+
+};
+
+extern LLGLTFMaterialList gGLTFMaterialList;
+
+
diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp
index 2bb2c9676b..d325831c8f 100644
--- a/indra/newview/llinventorybridge.cpp
+++ b/indra/newview/llinventorybridge.cpp
@@ -4403,6 +4403,7 @@ BOOL LLFolderBridge::dragOrDrop(MASK mask, BOOL drop,
case DAD_GESTURE:
case DAD_MESH:
case DAD_SETTINGS:
+ case DAD_MATERIAL:
accept = dragItemIntoFolder(inv_item, drop, tooltip_msg);
break;
case DAD_LINK:
@@ -6032,6 +6033,7 @@ BOOL LLCallingCardBridge::dragOrDrop(MASK mask, BOOL drop,
case DAD_GESTURE:
case DAD_MESH:
case DAD_SETTINGS:
+ case DAD_MATERIAL:
{
LLInventoryItem* inv_item = (LLInventoryItem*)cargo_data;
const LLPermissions& perm = inv_item->getPermissions();
diff --git a/indra/newview/llmaterialeditor.cpp b/indra/newview/llmaterialeditor.cpp
index 2455ad2926..67d963bb63 100644
--- a/indra/newview/llmaterialeditor.cpp
+++ b/indra/newview/llmaterialeditor.cpp
@@ -29,6 +29,7 @@
#include "llmaterialeditor.h"
#include "llagent.h"
+#include "llagentbenefits.h"
#include "llappviewer.h"
#include "llcombobox.h"
#include "llinventorymodel.h"
@@ -43,7 +44,6 @@
#include "llviewerregion.h"
#include "llvovolume.h"
#include "roles_constants.h"
-#include "tinygltf/tiny_gltf.h"
#include "llviewerobjectlist.h"
#include "llfloaterreg.h"
#include "llfilesystem.h"
@@ -52,6 +52,7 @@
#include "llviewertexturelist.h"
#include "llfloaterperms.h"
+#include "tinygltf/tiny_gltf.h"
#include <strstream>
///----------------------------------------------------------------------------
@@ -86,6 +87,12 @@ BOOL LLMaterialEditor::postBuild()
childSetAction("save_as", boost::bind(&LLMaterialEditor::onClickSaveAs, this));
childSetAction("cancel", boost::bind(&LLMaterialEditor::onClickCancel, this));
+ S32 upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost();
+ getChild<LLUICtrl>("albedo_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
+ getChild<LLUICtrl>("metallic_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
+ getChild<LLUICtrl>("emissive_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
+ getChild<LLUICtrl>("normal_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
+
boost::function<void(LLUICtrl*, void*)> changes_callback = [this](LLUICtrl * ctrl, void*)
{
setHasUnsavedChanges(true);
@@ -142,7 +149,12 @@ void LLMaterialEditor::setAlbedoId(const LLUUID& id)
{
mAlbedoTextureCtrl->setValue(id);
mAlbedoTextureCtrl->setDefaultImageAssetID(id);
+}
+void LLMaterialEditor::setAlbedoUploadId(const LLUUID& id)
+{
+ // Might be better to use local textures and
+ // assign a fee in case of a local texture
if (id.notNull())
{
// todo: this does not account for posibility of texture
@@ -151,6 +163,7 @@ void LLMaterialEditor::setAlbedoId(const LLUUID& id)
// Only set if we will need to upload this texture
mAlbedoTextureUploadId = id;
}
+ setHasUnsavedChanges(true);
}
LLColor4 LLMaterialEditor::getAlbedoColor()
@@ -211,7 +224,10 @@ void LLMaterialEditor::setMetallicRoughnessId(const LLUUID& id)
{
mMetallicTextureCtrl->setValue(id);
mMetallicTextureCtrl->setDefaultImageAssetID(id);
+}
+void LLMaterialEditor::setMetallicRoughnessUploadId(const LLUUID& id)
+{
if (id.notNull())
{
// todo: this does not account for posibility of texture
@@ -219,6 +235,7 @@ void LLMaterialEditor::setMetallicRoughnessId(const LLUUID& id)
childSetValue("metallic_upload_fee", getString("upload_fee_string"));
mMetallicTextureUploadId = id;
}
+ setHasUnsavedChanges(true);
}
F32 LLMaterialEditor::getMetalnessFactor()
@@ -250,7 +267,10 @@ void LLMaterialEditor::setEmissiveId(const LLUUID& id)
{
mEmissiveTextureCtrl->setValue(id);
mEmissiveTextureCtrl->setDefaultImageAssetID(id);
+}
+void LLMaterialEditor::setEmissiveUploadId(const LLUUID& id)
+{
if (id.notNull())
{
// todo: this does not account for posibility of texture
@@ -258,6 +278,7 @@ void LLMaterialEditor::setEmissiveId(const LLUUID& id)
childSetValue("emissive_upload_fee", getString("upload_fee_string"));
mEmissiveTextureUploadId = id;
}
+ setHasUnsavedChanges(true);
}
LLColor4 LLMaterialEditor::getEmissiveColor()
@@ -279,7 +300,10 @@ void LLMaterialEditor::setNormalId(const LLUUID& id)
{
mNormalTextureCtrl->setValue(id);
mNormalTextureCtrl->setDefaultImageAssetID(id);
+}
+void LLMaterialEditor::setNormalUploadId(const LLUUID& id)
+{
if (id.notNull())
{
// todo: this does not account for posibility of texture
@@ -287,6 +311,7 @@ void LLMaterialEditor::setNormalId(const LLUUID& id)
childSetValue("normal_upload_fee", getString("upload_fee_string"));
mNormalTextureUploadId = id;
}
+ setHasUnsavedChanges(true);
}
bool LLMaterialEditor::getDoubleSided()
@@ -306,6 +331,27 @@ void LLMaterialEditor::setHasUnsavedChanges(bool value)
mHasUnsavedChanges = value;
childSetVisible("unsaved_changes", value);
}
+
+ S32 upload_texture_count = 0;
+ if (mAlbedoTextureUploadId.notNull() && mAlbedoTextureUploadId == getAlbedoId())
+ {
+ upload_texture_count++;
+ }
+ if (mMetallicTextureUploadId.notNull() && mMetallicTextureUploadId == getMetallicRoughnessId())
+ {
+ upload_texture_count++;
+ }
+ if (mEmissiveTextureUploadId.notNull() && mEmissiveTextureUploadId == getEmissiveId())
+ {
+ upload_texture_count++;
+ }
+ if (mNormalTextureUploadId.notNull() && mNormalTextureUploadId == getNormalId())
+ {
+ upload_texture_count++;
+ }
+
+ S32 upload_cost = upload_texture_count * LLAgentBenefitsMgr::current().getTextureUploadCost();
+ getChild<LLUICtrl>("total_upload_fee")->setTextArg("[FEE]", llformat("%d", upload_cost));
}
void LLMaterialEditor::onCommitAlbedoTexture(LLUICtrl * ctrl, const LLSD & data)
@@ -866,6 +912,8 @@ static void pack_textures(tinygltf::Model& model, tinygltf::Material& material,
LLPointer<LLImageJ2C>& mr_j2c,
LLPointer<LLImageJ2C>& emissive_j2c)
{
+ // todo: consider using LLLocalBitmapMgr or storing textures' pointers somewhere in floater
+ // otherwise images won't exist for long if texture ctrl temporaly switches to something else
if (albedo_img)
{
albedo_tex = LLViewerTextureManager::getFetchedTexture(albedo_img, FTType::FTT_LOCAL_FILE, true);
@@ -1035,9 +1083,13 @@ void LLMaterialFilePicker::loadMaterial(const std::string& filename)
}
mME->setAlbedoId(albedo_id);
+ mME->setAlbedoUploadId(albedo_id);
mME->setMetallicRoughnessId(mr_id);
+ mME->setMetallicRoughnessUploadId(mr_id);
mME->setEmissiveId(emissive_id);
+ mME->setEmissiveUploadId(emissive_id);
mME->setNormalId(normal_id);
+ mME->setNormalUploadId(normal_id);
mME->setFromGltfModel(model_in);
@@ -1383,7 +1435,7 @@ void LLMaterialEditor::saveTexture(LLImageJ2C* img, const std::string& name, con
std::string buffer;
buffer.assign((const char*) img->getData(), img->getDataSize());
- U32 expected_upload_cost = 10; // TODO: where do we get L$10 for textures from?
+ U32 expected_upload_cost = LLAgentBenefitsMgr::current().getTextureUploadCost();
LLAssetStorage::LLStoreAssetCallback callback;
diff --git a/indra/newview/llmaterialeditor.h b/indra/newview/llmaterialeditor.h
index 6ccb27cf27..b2a6e96eac 100644
--- a/indra/newview/llmaterialeditor.h
+++ b/indra/newview/llmaterialeditor.h
@@ -99,6 +99,7 @@ public:
LLUUID getAlbedoId();
void setAlbedoId(const LLUUID& id);
+ void setAlbedoUploadId(const LLUUID& id);
LLColor4 getAlbedoColor();
@@ -118,6 +119,7 @@ public:
LLUUID getMetallicRoughnessId();
void setMetallicRoughnessId(const LLUUID& id);
+ void setMetallicRoughnessUploadId(const LLUUID& id);
F32 getMetalnessFactor();
void setMetalnessFactor(F32 factor);
@@ -127,12 +129,14 @@ public:
LLUUID getEmissiveId();
void setEmissiveId(const LLUUID& id);
+ void setEmissiveUploadId(const LLUUID& id);
LLColor4 getEmissiveColor();
void setEmissiveColor(const LLColor4& color);
LLUUID getNormalId();
void setNormalId(const LLUUID& id);
+ void setNormalUploadId(const LLUUID& id);
bool getDoubleSided();
void setDoubleSided(bool double_sided);
diff --git a/indra/newview/llpanelgroupnotices.cpp b/indra/newview/llpanelgroupnotices.cpp
index ab32ea3956..82f880c9ee 100644
--- a/indra/newview/llpanelgroupnotices.cpp
+++ b/indra/newview/llpanelgroupnotices.cpp
@@ -156,6 +156,7 @@ BOOL LLGroupDropTarget::handleDragAndDrop(S32 x, S32 y, MASK mask, BOOL drop,
case DAD_CALLINGCARD:
case DAD_MESH:
case DAD_SETTINGS:
+ case DAD_MATERIAL:
{
LLViewerInventoryItem* inv_item = (LLViewerInventoryItem*)cargo_data;
if(gInventory.getItem(inv_item->getUUID())
diff --git a/indra/newview/llpanelobjectinventory.cpp b/indra/newview/llpanelobjectinventory.cpp
index defd7025b8..998cffef4a 100644
--- a/indra/newview/llpanelobjectinventory.cpp
+++ b/indra/newview/llpanelobjectinventory.cpp
@@ -704,6 +704,7 @@ BOOL LLTaskCategoryBridge::dragOrDrop(MASK mask, BOOL drop,
case DAD_CALLINGCARD:
case DAD_MESH:
case DAD_SETTINGS:
+ case DAD_MATERIAL:
accept = LLToolDragAndDrop::isInventoryDropAcceptable(object, (LLViewerInventoryItem*)cargo_data);
if(accept && drop)
{
diff --git a/indra/newview/lltooldraganddrop.cpp b/indra/newview/lltooldraganddrop.cpp
index 50868d0fa5..55e8a3b98b 100644
--- a/indra/newview/lltooldraganddrop.cpp
+++ b/indra/newview/lltooldraganddrop.cpp
@@ -256,7 +256,8 @@ LLToolDragAndDrop::LLDragAndDropDictionary::LLDragAndDropDictionary()
// |-------------------------------|----------------------------------------------|-----------------------------------------------|---------------------------------------------------|--------------------------------|
addEntry(DAD_NONE, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL));
addEntry(DAD_TEXTURE, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dTextureObject, &LLToolDragAndDrop::dad3dNULL));
- addEntry(DAD_SOUND, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL));
+ addEntry(DAD_MATERIAL, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dMaterialObject, &LLToolDragAndDrop::dad3dNULL));
+ addEntry(DAD_SOUND, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL));
addEntry(DAD_CALLINGCARD, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL));
addEntry(DAD_LANDMARK, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL));
addEntry(DAD_SCRIPT, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dRezScript, &LLToolDragAndDrop::dad3dNULL));
@@ -271,6 +272,7 @@ LLToolDragAndDrop::LLDragAndDropDictionary::LLDragAndDropDictionary()
addEntry(DAD_LINK, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL));
addEntry(DAD_MESH, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dMeshObject, &LLToolDragAndDrop::dad3dNULL));
addEntry(DAD_SETTINGS, new DragAndDropEntry(&LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dNULL, &LLToolDragAndDrop::dad3dGiveInventory, &LLToolDragAndDrop::dad3dUpdateInventory, &LLToolDragAndDrop::dad3dNULL));
+
// TODO: animation on self could play it? edit it?
// TODO: gesture on self could play it? edit it?
};
@@ -1062,6 +1064,64 @@ void LLToolDragAndDrop::dropTextureAllFaces(LLViewerObject* hit_obj,
hit_obj->sendTEUpdate();
}
+
+void LLToolDragAndDrop::dropMaterialOneFace(LLViewerObject* hit_obj,
+ S32 hit_face,
+ LLInventoryItem* item,
+ LLToolDragAndDrop::ESource source,
+ const LLUUID& src_id)
+{
+ if (hit_face == -1) return;
+ if (!item || item->getInventoryType() != LLInventoryType::IT_MATERIAL)
+ {
+ LL_WARNS() << "LLToolDragAndDrop::dropTextureOneFace no material item." << LL_ENDL;
+ return;
+ }
+ LLUUID asset_id = item->getAssetUUID();
+ BOOL success = handleDropTextureProtections(hit_obj, item, source, src_id);
+ if (!success)
+ {
+ return;
+ }
+
+ hit_obj->setRenderMaterialID(hit_face, asset_id);
+
+ dialog_refresh_all();
+
+ // send the update to the simulator
+ hit_obj->sendTEUpdate();
+}
+
+
+void LLToolDragAndDrop::dropMaterialAllFaces(LLViewerObject* hit_obj,
+ LLInventoryItem* item,
+ LLToolDragAndDrop::ESource source,
+ const LLUUID& src_id)
+{
+ if (!item || item->getInventoryType() != LLInventoryType::IT_MATERIAL)
+ {
+ LL_WARNS() << "LLToolDragAndDrop::dropTextureAllFaces no material item." << LL_ENDL;
+ return;
+ }
+ LLUUID asset_id = item->getAssetUUID();
+ BOOL success = handleDropTextureProtections(hit_obj, item, source, src_id);
+ if (!success)
+ {
+ return;
+ }
+
+ S32 num_faces = hit_obj->getNumTEs();
+ for (S32 face = 0; face < num_faces; face++)
+ {
+ // update viewer side material in anticipation of update from simulator
+ hit_obj->setRenderMaterialID(face, asset_id);
+ dialog_refresh_all();
+ }
+ // send the update to the simulator
+ hit_obj->sendTEUpdate();
+}
+
+
void LLToolDragAndDrop::dropMesh(LLViewerObject* hit_obj,
LLInventoryItem* item,
LLToolDragAndDrop::ESource source,
@@ -1628,6 +1688,7 @@ bool LLToolDragAndDrop::handleGiveDragAndDrop(LLUUID dest_agent, LLUUID session_
case DAD_MESH:
case DAD_CATEGORY:
case DAD_SETTINGS:
+ case DAD_MATERIAL:
{
LLInventoryObject* inv_obj = (LLInventoryObject*)cargo_data;
if(gInventory.getCategory(inv_obj->getUUID()) || (gInventory.getItem(inv_obj->getUUID())
@@ -1982,6 +2043,17 @@ EAcceptance LLToolDragAndDrop::dad3dApplyToObject(
dropTextureOneFace(obj, face, item, mSource, mSourceID);
}
}
+ else if (cargo_type == DAD_MATERIAL)
+ {
+ if ((mask & MASK_SHIFT))
+ {
+ dropMaterialAllFaces(obj, item, mSource, mSourceID);
+ }
+ else
+ {
+ dropMaterialOneFace(obj, face, item, mSource, mSourceID);
+ }
+ }
else if (cargo_type == DAD_MESH)
{
dropMesh(obj, item, mSource, mSourceID);
@@ -2010,6 +2082,12 @@ EAcceptance LLToolDragAndDrop::dad3dTextureObject(
return dad3dApplyToObject(obj, face, mask, drop, DAD_TEXTURE);
}
+EAcceptance LLToolDragAndDrop::dad3dMaterialObject(
+ LLViewerObject* obj, S32 face, MASK mask, BOOL drop)
+{
+ return dad3dApplyToObject(obj, face, mask, drop, DAD_MATERIAL);
+}
+
EAcceptance LLToolDragAndDrop::dad3dMeshObject(
LLViewerObject* obj, S32 face, MASK mask, BOOL drop)
{
diff --git a/indra/newview/lltooldraganddrop.h b/indra/newview/lltooldraganddrop.h
index 24a712029c..2f6423080e 100644
--- a/indra/newview/lltooldraganddrop.h
+++ b/indra/newview/lltooldraganddrop.h
@@ -168,6 +168,8 @@ protected:
MASK mask, BOOL drop);
EAcceptance dad3dTextureObject(LLViewerObject* obj, S32 face,
MASK mask, BOOL drop);
+ EAcceptance dad3dMaterialObject(LLViewerObject* obj, S32 face,
+ MASK mask, BOOL drop);
EAcceptance dad3dMeshObject(LLViewerObject* obj, S32 face,
MASK mask, BOOL drop);
// EAcceptance dad3dTextureSelf(LLViewerObject* obj, S32 face,
@@ -249,6 +251,14 @@ public:
LLInventoryItem* item,
ESource source,
const LLUUID& src_id);
+ static void dropMaterialOneFace(LLViewerObject* hit_obj, S32 hit_face,
+ LLInventoryItem* item,
+ ESource source,
+ const LLUUID& src_id);
+ static void dropMaterialAllFaces(LLViewerObject* hit_obj,
+ LLInventoryItem* item,
+ ESource source,
+ const LLUUID& src_id);
static void dropMesh(LLViewerObject* hit_obj,
LLInventoryItem* item,
ESource source,
diff --git a/indra/newview/llviewerobject.cpp b/indra/newview/llviewerobject.cpp
index 16479e02a2..753fb014c9 100644
--- a/indra/newview/llviewerobject.cpp
+++ b/indra/newview/llviewerobject.cpp
@@ -107,6 +107,7 @@
#include "llcleanup.h"
#include "llcallstack.h"
#include "llmeshrepository.h"
+#include "llgltfmateriallist.h"
#include "llgl.h"
//#define DEBUG_UPDATE_TYPE
@@ -4906,6 +4907,18 @@ void LLViewerObject::updateTEMaterialTextures(U8 te)
};
LLGLTFMaterial* mat = getTE(te)->getGLTFMaterial();
+ LLUUID mat_id = getRenderMaterialID(te);
+ if (mat == nullptr && mat_id.notNull())
+ {
+ mat = gGLTFMaterialList.getMaterial(mat_id);
+ getTE(te)->setGLTFMaterial(mat);
+ }
+ else if (mat_id.isNull() && mat != nullptr)
+ {
+ mat = nullptr;
+ getTE(te)->setGLTFMaterial(nullptr);
+ }
+
if (mat != nullptr)
{
mGLTFAlbedoMaps[te] = fetch_texture(mat->mAlbedoId);
@@ -4913,6 +4926,14 @@ void LLViewerObject::updateTEMaterialTextures(U8 te)
mGLTFMetallicRoughnessMaps[te] = fetch_texture(mat->mMetallicRoughnessId);
mGLTFEmissiveMaps[te] = fetch_texture(mat->mEmissiveId);
}
+ else
+ {
+ mGLTFAlbedoMaps[te] = nullptr;
+ mGLTFNormalMaps[te] = nullptr;
+ mGLTFMetallicRoughnessMaps[te] = nullptr;
+ mGLTFEmissiveMaps[te] = nullptr;
+ }
+
}
void LLViewerObject::refreshBakeTexture()
@@ -6991,6 +7012,69 @@ LLVOAvatar* LLViewerObject::getAvatar() const
return NULL;
}
+bool LLViewerObject::hasRenderMaterialParams() const
+{
+ return getParameterEntryInUse(LLNetworkData::PARAMS_RENDER_MATERIAL);
+}
+
+void LLViewerObject::setHasRenderMaterialParams(bool has_materials)
+{
+ bool had_materials = hasRenderMaterialParams();
+
+ if (had_materials != has_materials)
+ {
+ if (has_materials)
+ {
+ setParameterEntryInUse(LLNetworkData::PARAMS_RENDER_MATERIAL, TRUE, true);
+ }
+ else
+ {
+ setParameterEntryInUse(LLNetworkData::PARAMS_RENDER_MATERIAL, FALSE, true);
+ }
+ }
+}
+
+const LLUUID& LLViewerObject::getRenderMaterialID(U8 te) const
+{
+ LLRenderMaterialParams* param_block = (LLRenderMaterialParams*)getParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL);
+ if (param_block)
+ {
+ return param_block->getMaterial(te);
+ }
+
+ return LLUUID::null;
+}
+
+void LLViewerObject::setRenderMaterialID(U8 te, const LLUUID& id)
+{
+ if (id.notNull())
+ {
+ getTE(te)->setGLTFMaterial(gGLTFMaterialList.getMaterial(id));
+ setHasRenderMaterialParams(true);
+ }
+ else
+ {
+ getTE(te)->setGLTFMaterial(nullptr);
+ }
+
+ faceMappingChanged();
+ gPipeline.markTextured(mDrawable);
+
+ LLRenderMaterialParams* param_block = (LLRenderMaterialParams*)getParameterEntry(LLNetworkData::PARAMS_RENDER_MATERIAL);
+ if (param_block)
+ {
+ param_block->setMaterial(te, id);
+
+ if (param_block->isEmpty())
+ { // might be empty if id is null
+ setHasRenderMaterialParams(false);
+ }
+ else
+ {
+ parameterChanged(LLNetworkData::PARAMS_RENDER_MATERIAL, true);
+ }
+ }
+}
class ObjectPhysicsProperties : public LLHTTPNode
{
diff --git a/indra/newview/llviewerobject.h b/indra/newview/llviewerobject.h
index 5136a7e5ee..109c96dc9c 100644
--- a/indra/newview/llviewerobject.h
+++ b/indra/newview/llviewerobject.h
@@ -179,6 +179,13 @@ public:
const std::string& getAttachmentItemName() const;
virtual LLVOAvatar* getAvatar() const; //get the avatar this object is attached to, or NULL if object is not an attachment
+
+ bool hasRenderMaterialParams() const;
+ void setHasRenderMaterialParams(bool has_params);
+
+ const LLUUID& getRenderMaterialID(U8 te) const;
+ void setRenderMaterialID(U8 te, const LLUUID& id);
+
virtual BOOL isHUDAttachment() const { return FALSE; }
virtual BOOL isTempAttachment() const;
@@ -200,6 +207,7 @@ public:
// Graphical stuff for objects - maybe broken out into render class later?
virtual void updateTextures();
+ virtual void faceMappingChanged() {}
virtual void boostTexturePriority(BOOL boost_children = TRUE); // When you just want to boost priority of this object
virtual LLDrawable* createDrawable(LLPipeline *pipeline);
diff --git a/indra/newview/llviewertexteditor.cpp b/indra/newview/llviewertexteditor.cpp
index e2de7ac825..7c860936a5 100644
--- a/indra/newview/llviewertexteditor.cpp
+++ b/indra/newview/llviewertexteditor.cpp
@@ -879,6 +879,7 @@ BOOL LLViewerTextEditor::handleDragAndDrop(S32 x, S32 y, MASK mask,
case DAD_ANIMATION:
case DAD_GESTURE:
case DAD_MESH:
+ case DAD_MATERIAL:
{
supported = true;
break;
diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp
index 0ae13e67cd..4fa9f05c09 100644
--- a/indra/newview/llvovolume.cpp
+++ b/indra/newview/llvovolume.cpp
@@ -2161,6 +2161,7 @@ void LLVOVolume::setNumTEs(const U8 num_tes)
return ;
}
+
//virtual
void LLVOVolume::changeTEImage(S32 index, LLViewerTexture* imagep)
{
@@ -3500,6 +3501,11 @@ F32 LLVOVolume::getLightCutoff() const
}
}
+BOOL LLVOVolume::isReflectionProbe() const
+{
+ return getParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE);
+}
+
void LLVOVolume::setIsReflectionProbe(BOOL is_probe)
{
BOOL was_probe = isReflectionProbe();
@@ -3570,25 +3576,6 @@ void LLVOVolume::setReflectionProbeIsDynamic(bool is_dynamic)
}
}
-
-BOOL LLVOVolume::isReflectionProbe() const
-{
- // HACK - make this object a Reflection Probe if a certain UUID is detected
- static LLCachedControl<std::string> reflection_probe_id(gSavedSettings, "RenderReflectionProbeTextureHackID", "");
- LLUUID probe_id(reflection_probe_id);
-
- for (U8 i = 0; i < getNumTEs(); ++i)
- {
- if (getTE(i)->getID() == probe_id)
- {
- return true;
- }
- }
- // END HACK
-
- return getParameterEntryInUse(LLNetworkData::PARAMS_REFLECTION_PROBE);
-}
-
F32 LLVOVolume::getReflectionProbeAmbiance() const
{
const LLReflectionProbeParams* param_block = (const LLReflectionProbeParams*)getParameterEntry(LLNetworkData::PARAMS_REFLECTION_PROBE);
@@ -5874,6 +5861,8 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group)
continue;
}
+ // HACK -- brute force this check every time a drawable gets rebuilt
+ vobj->updateTEMaterialTextures(i);
#if 0
#if LL_RELEASE_WITH_DEBUG_INFO
const LLUUID pbr_id( "49c88210-7238-2a6b-70ac-92d4f35963cf" );
diff --git a/indra/newview/llvovolume.h b/indra/newview/llvovolume.h
index f0a4fd427e..db586fd741 100644
--- a/indra/newview/llvovolume.h
+++ b/indra/newview/llvovolume.h
@@ -172,7 +172,7 @@ public:
void markForUpdate(BOOL priority) override;
void markForUnload() { LLViewerObject::markForUnload(TRUE); mVolumeChanged = TRUE; }
- void faceMappingChanged() { mFaceMappingChanged=TRUE; };
+ void faceMappingChanged() override { mFaceMappingChanged=TRUE; }
/*virtual*/ void onShift(const LLVector4a &shift_vector) override; // Called when the drawable shifts
@@ -284,7 +284,7 @@ public:
F32 getLightRadius() const;
F32 getLightFalloff(const F32 fudge_factor = 1.f) const;
F32 getLightCutoff() const;
-
+
// Reflection Probes
void setIsReflectionProbe(BOOL is_probe);
void setReflectionProbeAmbiance(F32 ambiance);
diff --git a/indra/newview/skins/default/xui/en/floater_material_editor.xml b/indra/newview/skins/default/xui/en/floater_material_editor.xml
index df06896fa5..7d532ecd7b 100644
--- a/indra/newview/skins/default/xui/en/floater_material_editor.xml
+++ b/indra/newview/skins/default/xui/en/floater_material_editor.xml
@@ -10,7 +10,7 @@
title="[MATERIAL_NAME]"
width="256">
<string name="no_upload_fee_string">no upload fee</string>
- <string name="upload_fee_string">L$10 upload fee</string>
+ <string name="upload_fee_string">L$[FEE] upload fee</string>
<check_box
follows="left|top"
label="Double Sided"
@@ -473,6 +473,7 @@
layout="topleft"
left="10"
top_pad="5"
+ name="total_upload_fee"
>
Total upload fee: L$ [FEE]
</text>