Merge pull request #3973 from Hecklezz/improvement/pbr_rpm_and_panel_fixes

[#3972] Implemented Texture Panel Repeats per meter improvements and PBR feature
master
Jonathan "Geenz" Goodman 2025-04-28 17:15:20 -04:00 committed by GitHub
commit 281c7cf664
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 259 additions and 44 deletions

View File

@ -162,6 +162,36 @@ void LLPanelFace::updateSelectedGLTFMaterials(std::function<void(LLGLTFMaterial*
LLSelectMgr::getInstance()->getSelection()->applyToTEs(&select_func);
}
void LLPanelFace::updateSelectedGLTFMaterialsWithScale(std::function<void(LLGLTFMaterial*, const F32, const F32)> func)
{
struct LLSelectedTEGLTFMaterialFunctor : public LLSelectedTEFunctor
{
LLSelectedTEGLTFMaterialFunctor(std::function<void(LLGLTFMaterial*, const F32, const F32)> func) : mFunc(func) {}
virtual ~LLSelectedTEGLTFMaterialFunctor() {};
bool apply(LLViewerObject* object, S32 face) override
{
LLGLTFMaterial new_override;
const LLTextureEntry* tep = object->getTE(face);
if (tep->getGLTFMaterialOverride())
{
new_override = *tep->getGLTFMaterialOverride();
}
U32 s_axis = VX;
U32 t_axis = VY;
LLPrimitive::getTESTAxes(face, &s_axis, &t_axis);
mFunc(&new_override, object->getScale().mV[s_axis], object->getScale().mV[t_axis]);
LLGLTFMaterialList::queueModify(object, face, &new_override);
return true;
}
std::function<void(LLGLTFMaterial*, const F32, const F32)> mFunc;
} select_func(func);
LLSelectMgr::getInstance()->getSelection()->applyToTEs(&select_func);
}
template<typename T>
void readSelectedGLTFMaterial(std::function<T(const LLGLTFMaterial*)> func, T& value, bool& identical, bool has_tolerance, T tolerance)
{
@ -182,6 +212,36 @@ void readSelectedGLTFMaterial(std::function<T(const LLGLTFMaterial*)> func, T& v
identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&select_func, value, has_tolerance, tolerance);
}
void getSelectedGLTFMaterialMaxRepeats(LLGLTFMaterial::TextureInfo channel, F32& repeats, bool& identical)
{
// The All channel should read base color values
if (channel == LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_COUNT)
channel = LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_BASE_COLOR;
struct LLSelectedTEGetGLTFMaterialMaxRepeatsFunctor : public LLSelectedTEGetFunctor<F32>
{
LLSelectedTEGetGLTFMaterialMaxRepeatsFunctor(LLGLTFMaterial::TextureInfo channel) : mChannel(channel) {}
virtual ~LLSelectedTEGetGLTFMaterialMaxRepeatsFunctor() {};
F32 get(LLViewerObject* object, S32 face) override
{
const LLTextureEntry* tep = object->getTE(face);
const LLGLTFMaterial* render_material = tep->getGLTFRenderMaterial();
if (!render_material)
return 0.f;
U32 s_axis = VX;
U32 t_axis = VY;
LLPrimitive::getTESTAxes(face, &s_axis, &t_axis);
F32 repeats_u = render_material->mTextureTransform[mChannel].mScale[VX] / object->getScale().mV[s_axis];
F32 repeats_v = render_material->mTextureTransform[mChannel].mScale[VY] / object->getScale().mV[t_axis];
return llmax(repeats_u, repeats_v);
}
LLGLTFMaterial::TextureInfo mChannel;
} max_repeats_func(channel);
identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue(&max_repeats_func, repeats);
}
BOOST_STATIC_ASSERT(MATTYPE_DIFFUSE == LLRender::DIFFUSE_MAP && MATTYPE_NORMAL == LLRender::NORMAL_MAP && MATTYPE_SPECULAR == LLRender::SPECULAR_MAP);
//
@ -322,6 +382,7 @@ bool LLPanelFace::postBuild()
getChildSetCommitCallback(mPBRScaleU, "gltfTextureScaleU", [&](LLUICtrl*, const LLSD&) { onCommitGLTFTextureScaleU(); });
getChildSetCommitCallback(mPBRScaleV, "gltfTextureScaleV", [&](LLUICtrl*, const LLSD&) { onCommitGLTFTextureScaleV(); });
getChildSetCommitCallback(mPBRRepeat, "gltfRptctrl", [&](LLUICtrl*, const LLSD&) { onCommitGLTFRepeatsPerMeter(); });
getChildSetCommitCallback(mPBRRotate, "gltfTextureRotation", [&](LLUICtrl*, const LLSD&) { onCommitGLTFRotation(); });
getChildSetCommitCallback(mPBROffsetU, "gltfTextureOffsetU", [&](LLUICtrl*, const LLSD&) { onCommitGLTFTextureOffsetU(); });
getChildSetCommitCallback(mPBROffsetV, "gltfTextureOffsetV", [&](LLUICtrl*, const LLSD&) { onCommitGLTFTextureOffsetV(); });
@ -1395,9 +1456,18 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
spec_scale_s = editable ? spec_scale_s : 1.0f;
spec_scale_s *= identical_planar_texgen ? 2.0f : 1.0f;
mTexScaleU->setValue(diff_scale_s);
mShinyScaleU->setValue(spec_scale_s);
mBumpyScaleU->setValue(norm_scale_s);
if (force_set_values)
{
mTexScaleU->forceSetValue(diff_scale_s);
mShinyScaleU->forceSetValue(spec_scale_s);
mBumpyScaleU->forceSetValue(norm_scale_s);
}
else
{
mTexScaleU->setValue(diff_scale_s);
mShinyScaleU->setValue(spec_scale_s);
mBumpyScaleU->setValue(norm_scale_s);
}
mTexScaleU->setEnabled(editable && has_material);
mShinyScaleU->setEnabled(editable && has_material && specmap_id.notNull());
@ -1445,13 +1515,16 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
if (force_set_values)
{
mTexScaleV->forceSetValue(diff_scale_t);
mShinyScaleV->forceSetValue(spec_scale_t);
mBumpyScaleV->forceSetValue(norm_scale_t);
}
else
{
mTexScaleV->setValue(diff_scale_t);
mShinyScaleV->setValue(spec_scale_t);
mBumpyScaleV->setValue(norm_scale_t);
}
mShinyScaleV->setValue(spec_scale_t);
mBumpyScaleV->setValue(norm_scale_t);
mTexScaleV->setTentative(LLSD(diff_scale_tentative));
mShinyScaleV->setTentative(LLSD(spec_scale_tentative));
@ -1591,36 +1664,57 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
F32 repeats_norm = 1.f;
F32 repeats_spec = 1.f;
F32 repeats_pbr_basecolor = 1.f;
F32 repeats_pbr_metallic_roughness = 1.f;
F32 repeats_pbr_normal = 1.f;
F32 repeats_pbr_emissive = 1.f;
bool identical_diff_repeats = false;
bool identical_norm_repeats = false;
bool identical_spec_repeats = false;
LLSelectedTE::getMaxDiffuseRepeats(repeats_diff, identical_diff_repeats);
LLSelectedTEMaterial::getMaxNormalRepeats(repeats_norm, identical_norm_repeats);
LLSelectedTEMaterial::getMaxSpecularRepeats(repeats_spec, identical_spec_repeats);
bool identical_pbr_basecolor_repeats = false;
bool identical_pbr_metallic_roughness_repeats = false;
bool identical_pbr_normal_repeats = false;
bool identical_pbr_emissive_repeats = false;
{
LLSpinCtrl* repeats_spin_ctrl = nullptr;
S32 index = mComboTexGen ? mComboTexGen->getCurrentIndex() : 0;
bool enabled = editable && (index != 1);
bool identical_repeats = true;
S32 material_selection = mComboMatMedia->getCurrentIndex();
F32 repeats = 1.0f;
U32 material_type = MATTYPE_DIFFUSE;
if (material_selection == MATMEDIA_MATERIAL)
LLRender::eTexIndex material_channel = LLRender::DIFFUSE_MAP;
if (material_selection != MATMEDIA_PBR)
{
material_type = mRadioMaterialType->getSelectedIndex();
repeats_spin_ctrl = mTexRepeat;
material_channel = getMatTextureChannel();
LLSelectedTE::getMaxDiffuseRepeats(repeats_diff, identical_diff_repeats);
LLSelectedTEMaterial::getMaxNormalRepeats(repeats_norm, identical_norm_repeats);
LLSelectedTEMaterial::getMaxSpecularRepeats(repeats_spec, identical_spec_repeats);
}
else if (material_selection == MATMEDIA_PBR)
{
repeats_spin_ctrl = mPBRRepeat;
enabled = editable && has_pbr_material;
material_type = mRadioPbrType->getSelectedIndex();
material_channel = getPBRTextureChannel();
getSelectedGLTFMaterialMaxRepeats(LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_BASE_COLOR,
repeats_pbr_basecolor, identical_pbr_basecolor_repeats);
getSelectedGLTFMaterialMaxRepeats(LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS,
repeats_pbr_metallic_roughness, identical_pbr_metallic_roughness_repeats);
getSelectedGLTFMaterialMaxRepeats(LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_NORMAL,
repeats_pbr_normal, identical_pbr_normal_repeats);
getSelectedGLTFMaterialMaxRepeats(LLGLTFMaterial::TextureInfo::GLTF_TEXTURE_INFO_EMISSIVE,
repeats_pbr_emissive, identical_pbr_emissive_repeats);
}
switch (material_type)
switch (material_channel)
{
default:
case MATTYPE_DIFFUSE:
case LLRender::DIFFUSE_MAP:
if (material_selection != MATMEDIA_PBR)
{
enabled = editable && !id.isNull();
@ -1628,7 +1722,7 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
identical_repeats = identical_diff_repeats;
repeats = repeats_diff;
break;
case MATTYPE_SPECULAR:
case LLRender::SPECULAR_MAP:
if (material_selection != MATMEDIA_PBR)
{
enabled = (editable && ((shiny == SHINY_TEXTURE) && !specmap_id.isNull()));
@ -1636,7 +1730,7 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
identical_repeats = identical_spec_repeats;
repeats = repeats_spec;
break;
case MATTYPE_NORMAL:
case LLRender::NORMAL_MAP:
if (material_selection != MATMEDIA_PBR)
{
enabled = (editable && ((bumpy == BUMPY_TEXTURE) && !normmap_id.isNull()));
@ -1644,6 +1738,23 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
identical_repeats = identical_norm_repeats;
repeats = repeats_norm;
break;
case LLRender::NUM_TEXTURE_CHANNELS:
case LLRender::BASECOLOR_MAP:
identical_repeats = identical_pbr_basecolor_repeats;
repeats = repeats_pbr_basecolor;
break;
case LLRender::METALLIC_ROUGHNESS_MAP:
identical_repeats = identical_pbr_metallic_roughness_repeats;
repeats = repeats_pbr_metallic_roughness;
break;
case LLRender::GLTF_NORMAL_MAP:
identical_repeats = identical_pbr_normal_repeats;
repeats = repeats_pbr_normal;
break;
case LLRender::EMISSIVE_MAP:
identical_repeats = identical_pbr_emissive_repeats;
repeats = repeats_pbr_emissive;
break;
}
bool repeats_tentative = !identical_repeats;
@ -1651,14 +1762,14 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
if (force_set_values)
{
// onCommit, previosly edited element updates related ones
mTexRepeat->forceSetValue(editable ? repeats : 1.0f);
repeats_spin_ctrl->forceSetValue(editable ? repeats : 1.0f);
}
else
{
mTexRepeat->setValue(editable ? repeats : 1.0f);
repeats_spin_ctrl->setValue(editable ? repeats : 1.0f);
}
mTexRepeat->setTentative(LLSD(repeats_tentative));
mTexRepeat->setEnabled(has_material && !identical_planar_texgen && enabled);
repeats_spin_ctrl->setTentative(LLSD(repeats_tentative));
repeats_spin_ctrl->setEnabled(!identical_planar_texgen && enabled);
}
}
@ -1804,6 +1915,7 @@ void LLPanelFace::updateUI(bool force_set_values /*false*/)
}
mLabelColorTransp->setEnabled(false);
mTexRepeat->setEnabled(false);
mPBRRepeat->setEnabled(false);
mLabelTexGen->setEnabled(false);
mLabelShininess->setEnabled(false);
mLabelBumpiness->setEnabled(false);
@ -1999,6 +2111,7 @@ void LLPanelFace::updateVisibilityGLTF(LLViewerObject* objectp /*= nullptr */)
mPBRRotate->setVisible(show_pbr);
mPBROffsetU->setVisible(show_pbr);
mPBROffsetV->setVisible(show_pbr);
mPBRRepeat->setVisible(show_pbr);
}
void LLPanelFace::updateCopyTexButton()
@ -3649,18 +3762,8 @@ void LLPanelFace::onCommitRepeatsPerMeter()
if (gSavedSettings.getBOOL("SyncMaterialSettings"))
{
LLSelectMgr::getInstance()->selectionTexScaleAutofit(repeats_per_meter);
mBumpyScaleU->setValue(obj_scale_s * repeats_per_meter);
mBumpyScaleV->setValue(obj_scale_t * repeats_per_meter);
LLSelectedTEMaterial::setNormalRepeatX(this, obj_scale_s * repeats_per_meter);
LLSelectedTEMaterial::setNormalRepeatY(this, obj_scale_t * repeats_per_meter);
mShinyScaleU->setValue(obj_scale_s * repeats_per_meter);
mShinyScaleV->setValue(obj_scale_t * repeats_per_meter);
LLSelectedTEMaterial::setSpecularRepeatX(this, obj_scale_s * repeats_per_meter);
LLSelectedTEMaterial::setSpecularRepeatY(this, obj_scale_t * repeats_per_meter);
LLSelectedTEMaterial::selectionNormalScaleAutofit(this, repeats_per_meter);
LLSelectedTEMaterial::selectionSpecularScaleAutofit(this, repeats_per_meter);
}
else
{
@ -3671,18 +3774,10 @@ void LLPanelFace::onCommitRepeatsPerMeter()
LLSelectMgr::getInstance()->selectionTexScaleAutofit(repeats_per_meter);
break;
case MATTYPE_NORMAL:
mBumpyScaleU->setValue(obj_scale_s * repeats_per_meter);
mBumpyScaleV->setValue(obj_scale_t * repeats_per_meter);
LLSelectedTEMaterial::setNormalRepeatX(this, obj_scale_s * repeats_per_meter);
LLSelectedTEMaterial::setNormalRepeatY(this, obj_scale_t * repeats_per_meter);
LLSelectedTEMaterial::selectionNormalScaleAutofit(this, repeats_per_meter);
break;
case MATTYPE_SPECULAR:
mBumpyScaleU->setValue(obj_scale_s * repeats_per_meter);
mBumpyScaleV->setValue(obj_scale_t * repeats_per_meter);
LLSelectedTEMaterial::setSpecularRepeatX(this, obj_scale_s * repeats_per_meter);
LLSelectedTEMaterial::setSpecularRepeatY(this, obj_scale_t * repeats_per_meter);
LLSelectedTEMaterial::selectionSpecularScaleAutofit(this, repeats_per_meter);
break;
default:
llassert(false);
@ -3693,6 +3788,21 @@ void LLPanelFace::onCommitRepeatsPerMeter()
updateUI(true);
}
// Commit the number of GLTF repeats per meter
void LLPanelFace::onCommitGLTFRepeatsPerMeter()
{
F32 repeats_per_meter = (F32)mPBRRepeat->getValue().asReal();
LLGLTFMaterial::TextureInfo material_type = getPBRTextureInfo();
updateGLTFTextureTransformWithScale(material_type, [&](LLGLTFMaterial::TextureTransform* new_transform, F32 scale_s, F32 scale_t)
{
new_transform->mScale.mV[VX] = scale_s * repeats_per_meter;
new_transform->mScale.mV[VY] = scale_t * repeats_per_meter;
});
updateUI(true);
}
struct LLPanelFaceSetMediaFunctor : public LLSelectedTEFunctor
{
virtual bool apply(LLViewerObject* object, S32 te)
@ -4642,6 +4752,29 @@ void LLPanelFace::updateGLTFTextureTransform(std::function<void(LLGLTFMaterial::
}
}
void LLPanelFace::updateGLTFTextureTransformWithScale(const LLGLTFMaterial::TextureInfo texture_info, std::function<void(LLGLTFMaterial::TextureTransform*, const F32, const F32)> edit)
{
if (texture_info == LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT)
{
updateSelectedGLTFMaterialsWithScale([&](LLGLTFMaterial* new_override, const F32 scale_s, const F32 scale_t)
{
for (U32 i = 0; i < LLGLTFMaterial::GLTF_TEXTURE_INFO_COUNT; ++i)
{
LLGLTFMaterial::TextureTransform& new_transform = new_override->mTextureTransform[(LLGLTFMaterial::TextureInfo)i];
edit(&new_transform, scale_s, scale_t);
}
});
}
else
{
updateSelectedGLTFMaterialsWithScale([&](LLGLTFMaterial* new_override, const F32 scale_s, const F32 scale_t)
{
LLGLTFMaterial::TextureTransform& new_transform = new_override->mTextureTransform[texture_info];
edit(&new_transform, scale_s, scale_t);
});
}
}
void LLPanelFace::setMaterialOverridesFromSelection()
{
const LLGLTFMaterial::TextureInfo texture_info = getPBRTextureInfo();
@ -4717,8 +4850,9 @@ void LLPanelFace::setMaterialOverridesFromSelection()
}
}
mPBRScaleU->setValue(transform.mScale[VX]);
mPBRScaleV->setValue(transform.mScale[VY]);
// Force set scales just in case they were set by repeats per meter and their spinner is focused
mPBRScaleU->forceSetValue(transform.mScale[VX]);
mPBRScaleV->forceSetValue(transform.mScale[VY]);
mPBRRotate->setValue(transform.mRotation * RAD_TO_DEG);
mPBROffsetU->setValue(transform.mOffset[VX]);
mPBROffsetV->setValue(transform.mOffset[VY]);
@ -4728,6 +4862,12 @@ void LLPanelFace::setMaterialOverridesFromSelection()
mPBRRotate->setTentative(!rotation_same);
mPBROffsetU->setTentative(!offset_u_same);
mPBROffsetV->setTentative(!offset_v_same);
F32 repeats = 1.f;
bool identical = false;
getSelectedGLTFMaterialMaxRepeats(getPBRDropChannel(), repeats, identical);
mPBRRepeat->forceSetValue(repeats);
mPBRRepeat->setTentative(!identical || !scale_u_same || !scale_v_same);
}
void LLPanelFace::Selection::connect()
@ -5221,6 +5361,62 @@ void LLPanelFace::LLSelectedTEMaterial::getCurrentDiffuseAlphaMode(U8& diffuse_a
identical = LLSelectMgr::getInstance()->getSelection()->getSelectedTEValue( &get_diff_mode, diffuse_alpha_mode);
}
void LLPanelFace::LLSelectedTEMaterial::selectionNormalScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter)
{
struct f : public LLSelectedTEFunctor
{
LLPanelFace* mFacePanel;
F32 mRepeatsPerMeter;
f(LLPanelFace* face_panel, const F32& repeats_per_meter) : mFacePanel(face_panel), mRepeatsPerMeter(repeats_per_meter) {}
bool apply(LLViewerObject* object, S32 te)
{
if (object->permModify())
{
// Compute S,T to axis mapping
U32 s_axis, t_axis;
if (!LLPrimitive::getTESTAxes(te, &s_axis, &t_axis))
return true;
F32 new_s = object->getScale().mV[s_axis] * mRepeatsPerMeter;
F32 new_t = object->getScale().mV[t_axis] * mRepeatsPerMeter;
setNormalRepeatX(mFacePanel, new_s, te);
setNormalRepeatY(mFacePanel, new_t, te);
}
return true;
}
} setfunc(panel_face, repeats_per_meter);
LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc);
}
void LLPanelFace::LLSelectedTEMaterial::selectionSpecularScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter)
{
struct f : public LLSelectedTEFunctor
{
LLPanelFace* mFacePanel;
F32 mRepeatsPerMeter;
f(LLPanelFace* face_panel, const F32& repeats_per_meter) : mFacePanel(face_panel), mRepeatsPerMeter(repeats_per_meter) {}
bool apply(LLViewerObject* object, S32 te)
{
if (object->permModify())
{
// Compute S,T to axis mapping
U32 s_axis, t_axis;
if (!LLPrimitive::getTESTAxes(te, &s_axis, &t_axis))
return true;
F32 new_s = object->getScale().mV[s_axis] * mRepeatsPerMeter;
F32 new_t = object->getScale().mV[t_axis] * mRepeatsPerMeter;
setSpecularRepeatX(mFacePanel, new_s, te);
setSpecularRepeatY(mFacePanel, new_t, te);
}
return true;
}
} setfunc(panel_face, repeats_per_meter);
LLSelectMgr::getInstance()->getSelection()->applyToTEs(&setfunc);
}
void LLPanelFace::LLSelectedTE::getObjectScaleS(F32& scale_s, bool& identical)
{
struct LLSelectedTEGetObjectScaleS : public LLSelectedTEGetFunctor<F32>

View File

@ -250,6 +250,7 @@ protected:
void onCommitGLTFRotation();
void onCommitGLTFTextureOffsetU();
void onCommitGLTFTextureOffsetV();
void onCommitGLTFRepeatsPerMeter();
void onClickAutoFix();
void onAlignTexture();
@ -358,6 +359,7 @@ private:
LLButton* mDelMedia { nullptr };
LLSpinCtrl* mPBRScaleU { nullptr };
LLSpinCtrl* mPBRScaleV { nullptr };
LLSpinCtrl* mPBRRepeat { nullptr };
LLSpinCtrl* mPBRRotate { nullptr };
LLSpinCtrl* mPBROffsetU { nullptr };
LLSpinCtrl* mPBROffsetV { nullptr };
@ -551,7 +553,9 @@ private:
void updateVisibilityGLTF(LLViewerObject* objectp = nullptr);
void updateSelectedGLTFMaterials(std::function<void(LLGLTFMaterial*)> func);
void updateSelectedGLTFMaterialsWithScale(std::function<void(LLGLTFMaterial*, const F32, const F32)> func);
void updateGLTFTextureTransform(std::function<void(LLGLTFMaterial::TextureTransform*)> edit);
void updateGLTFTextureTransformWithScale(const LLGLTFMaterial::TextureInfo texture_info, std::function<void(LLGLTFMaterial::TextureTransform*, const F32, const F32)> edit);
void setMaterialOverridesFromSelection();
@ -646,6 +650,8 @@ public:
static void getMaxSpecularRepeats(F32& repeats, bool& identical);
static void getMaxNormalRepeats(F32& repeats, bool& identical);
static void getCurrentDiffuseAlphaMode(U8& diffuse_alpha_mode, bool& identical, bool diffuse_texture_has_alpha);
static void selectionNormalScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter);
static void selectionSpecularScaleAutofit(LLPanelFace* panel_face, F32 repeats_per_meter);
DEF_GET_MAT_STATE(LLUUID, const LLUUID&, getNormalID, LLUUID::null, false, LLUUID::null);
DEF_GET_MAT_STATE(LLUUID, const LLUUID&, getSpecularID, LLUUID::null, false, LLUUID::null);

View File

@ -985,6 +985,19 @@
max_val="100"
name="gltfTextureScaleV"
width="265" />
<spinner
decimal_digits="1"
follows="left|top"
height="19"
initial_value=""
label="Repeats per meter"
layout="topleft"
label_width="205"
left="10"
max_val="100"
min_val="-100"
name="gltfRptctrl"
width="265" />
<spinner
follows="left|top"
height="19"