From 620242477f6bd19fda2380e0a7d9aca673d889a5 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Thu, 29 Oct 2026 03:29:32 +0000 Subject: [PATCH 01/20] Add a pivot option to the mesh uploader The uploader always centres a model on its bounding box, so an object's origin, and the axis it turns about, sits at the box's centre. A door had to carry hidden geometry to put its hinge on an edge. The Upload options tab gets a pivot within the bounding box, -1 to 1 per axis. The uploader shifts every stored position domain, and the analyzed hulls' domain, so that the origin lands there; the quantized positions stay as they are. The sim keeps analyzed hulls where their domain puts them but fits any other physics shape to the box, so the pivot is held at the centre while a physics model is set but not analyzed, and a warning says the object will collide as its centred default hull when there is no physics model. It is held as well for rigged mesh, which its skeleton places, and for an upload of more than one model, whose pieces it would pull apart. The preview's display options get Origin, which draws the object's axes at the pivot, and Joint position overrides no longer wraps, to make room. Co-Authored-By: Claude Opus 5.5 --- indra/llprimitive/llmodel.cpp | 18 +++- indra/llprimitive/llmodel.h | 3 +- indra/newview/llfloatermodelpreview.cpp | 40 +++++++- indra/newview/llfloatermodelpreview.h | 1 + indra/newview/llmeshrepository.cpp | 13 ++- indra/newview/llmeshrepository.h | 7 +- indra/newview/llmodelpreview.cpp | 94 +++++++++++++++++++ indra/newview/llmodelpreview.h | 11 +++ .../default/xui/en/floater_model_preview.xml | 82 +++++++++++++++- 9 files changed, 254 insertions(+), 15 deletions(-) diff --git a/indra/llprimitive/llmodel.cpp b/indra/llprimitive/llmodel.cpp index 9521c82269e..c0ec5d69dd7 100644 --- a/indra/llprimitive/llmodel.cpp +++ b/indra/llprimitive/llmodel.cpp @@ -627,7 +627,8 @@ LLSD LLModel::writeModel( bool lock_scale_if_joint_position, EWriteModelMode write_mode, bool as_slm, - int submodel_id) + int submodel_id, + const LLVector3& position_offset) // Mesh uploader pivot { LLSD mdl; @@ -665,6 +666,15 @@ LLSD LLModel::writeModel( model[LLModel::LOD_PHYSICS] = NULL; } + // Mesh uploader pivot: the sim keeps analyzed hulls in their domain but fits the bounding hull to the box + if (!decomp.mHull.empty() && mdl.has("physics_convex")) + { + LLSD& convex = mdl["physics_convex"]; + convex["Min"] = (LLVector3(convex["Min"]) + position_offset).getValue(); + convex["Max"] = (LLVector3(convex["Max"]) + position_offset).getValue(); + } + // + if (as_slm) { //save material list names for (U32 i = 0; i < high->mMaterialList.size(); ++i) @@ -813,8 +823,10 @@ LLSD LLModel::writeModel( } //write out face data - mdl[model_names[idx]][i]["PositionDomain"]["Min"] = min_pos.getValue(); - mdl[model_names[idx]][i]["PositionDomain"]["Max"] = max_pos.getValue(); + // Mesh uploader pivot: the quantized positions stay, only their domain moves + mdl[model_names[idx]][i]["PositionDomain"]["Min"] = (min_pos + position_offset).getValue(); + mdl[model_names[idx]][i]["PositionDomain"]["Max"] = (max_pos + position_offset).getValue(); + // mdl[model_names[idx]][i]["NormalizedScale"] = face.mNormalizedScale.getValue(); mdl[model_names[idx]][i]["Position"] = verts; diff --git a/indra/llprimitive/llmodel.h b/indra/llprimitive/llmodel.h index 55adbe7412b..99e6f93c60d 100644 --- a/indra/llprimitive/llmodel.h +++ b/indra/llprimitive/llmodel.h @@ -186,7 +186,8 @@ class alignas(16) LLModel : public LLVolume bool lock_scale_if_joint_position, EWriteModelMode write_mode = WRITE_BINARY, bool as_slm = false, - int submodel_id = 0); + int submodel_id = 0, + const LLVector3& position_offset = LLVector3::zero); // Mesh uploader pivot: shifts the stored domains off the prim's origin static LLSD writeModelToStream( std::ostream& ostr, diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 40578901136..73508358d6d 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -191,6 +191,12 @@ bool LLFloaterModelPreview::postBuild() childSetCommitCallback("import_scale", onImportScaleCommit, this); childSetCommitCallback("pelvis_offset", onPelvisOffsetCommit, this); + // Mesh uploader pivot + for (const char* axis : { "pivot_x", "pivot_y", "pivot_z" }) + { + getChild(axis)->setCommitCallback(boost::bind(&LLFloaterModelPreview::onPivotCommit, this)); + } + // getChild("description_form")->setKeystrokeCallback(boost::bind(&LLFloaterModelPreview::onDescriptionKeystroke, this, _1), NULL); @@ -200,6 +206,7 @@ bool LLFloaterModelPreview::postBuild() getChild("show_skin_weight")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onShowSkinWeightChecked, this, _1)); getChild("show_joint_overrides")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); getChild("show_joint_positions")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); + getChild("show_origin")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onViewOptionChecked, this, _1)); // Mesh uploader pivot childDisable("upload_skin"); childDisable("upload_joints"); @@ -510,7 +517,8 @@ void LLFloaterModelPreview::onClickCalculateBtn() childGetValue("upload_textures").asBoolean(), upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, mUploadModelUrl, mDestinationFolderId, false, - getWholeModelFeeObserverHandle()); + getWholeModelFeeObserverHandle(), + LLHandle(), mModelPreview->getUploadPositionOffset()); // Mesh uploader pivot toggleCalculateButton(false); mUploadBtn->setEnabled(false); @@ -675,6 +683,24 @@ void LLFloaterModelPreview::onImportScaleCommit(LLUICtrl*,void* userdata) fp->mModelPreview->refresh(); } + +// Mesh uploader pivot +void LLFloaterModelPreview::onPivotCommit() +{ + if (!mModelPreview) + { + return; + } + + mModelPreview->setUploadPivot(LLVector3((F32)childGetValue("pivot_x").asReal(), + (F32)childGetValue("pivot_y").asReal(), + (F32)childGetValue("pivot_z").asReal())); + mModelPreview->updateStatusMessages(); + toggleCalculateButton(true); + mModelPreview->refresh(); +} +// + //static void LLFloaterModelPreview::onPelvisOffsetCommit( LLUICtrl*, void* userdata ) { @@ -1739,7 +1765,8 @@ void LLFloaterModelPreview::onUpload(void* user_data) mp->childGetValue("upload_textures").asBoolean(), upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, mp->mUploadModelUrl, mp->mDestinationFolderId, - true, LLHandle(), mp->getWholeModelUploadObserverHandle()); + true, LLHandle(), mp->getWholeModelUploadObserverHandle(), + mp->mModelPreview->getUploadPositionOffset()); // Mesh uploader pivot } @@ -1873,6 +1900,15 @@ void LLFloaterModelPreview::resetDisplayOptions() void LLFloaterModelPreview::resetUploadOptions() { childSetValue("import_scale", 1); + // Mesh uploader pivot + childSetValue("pivot_x", 0); + childSetValue("pivot_y", 0); + childSetValue("pivot_z", 0); + if (mModelPreview) + { + mModelPreview->setUploadPivot(LLVector3::zero); + } + // childSetValue("pelvis_offset", 0); childSetValue("physics_explode", 0); childSetValue("physics_file", ""); diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h index 39df7448f0d..1893be8cbe7 100644 --- a/indra/newview/llfloatermodelpreview.h +++ b/indra/newview/llfloatermodelpreview.h @@ -144,6 +144,7 @@ class LLFloaterModelPreview final : public LLFloaterModelUploadBase void onDescriptionKeystroke(LLUICtrl*); static void onImportScaleCommit(LLUICtrl*, void*); + void onPivotCommit(); // Mesh uploader pivot static void onPelvisOffsetCommit(LLUICtrl*, void*); static void onPreviewLODCommit(LLUICtrl*,void*); diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index e2620c6fc5d..a3afc5df1e3 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2699,7 +2699,8 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list_t& data bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, const std::string & upload_url, LLUUID destination_folder_id, bool do_upload, LLHandle fee_observer, - LLHandle upload_observer) + LLHandle upload_observer, + const LLVector3& position_offset) // Mesh uploader pivot : LLThread("mesh upload"), LLCore::HttpHandler(), mDiscarded(false), @@ -2715,6 +2716,7 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list_t& data mUploadSkin = upload_skin; mUploadJoints = upload_joints; mLockScaleIfJointPosition = lock_scale_if_joint_position; + mPositionOffset = position_offset; // Mesh uploader pivot mMutex = new LLMutex(); mPendingUploads = 0; mFinished = false; @@ -2879,7 +2881,8 @@ void LLMeshUploadThread::packModelIntance( mLockScaleIfJointPosition, LLModel::WRITE_BINARY, false, - data.mBaseModel->mSubmodelID); + data.mBaseModel->mSubmodelID, + mPositionOffset); // Mesh uploader pivot data.mAssetData = ostr.str(); std::string str = ostr.str(); @@ -5556,11 +5559,13 @@ void LLMeshRepository::uploadModel(std::vector& data, const std LLVector3& scale, bool upload_textures, bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, std::string upload_url, const LLUUID& destination_folder_id, bool do_upload, - LLHandle fee_observer, LLHandle upload_observer) + LLHandle fee_observer, LLHandle upload_observer, + const LLVector3& position_offset) // Mesh uploader pivot { LLMeshUploadThread* thread = new LLMeshUploadThread(data, lod_sources, scale, upload_textures, upload_skin, upload_joints, lock_scale_if_joint_position, - upload_url, destination_folder_id, do_upload, fee_observer, upload_observer); + upload_url, destination_folder_id, do_upload, fee_observer, upload_observer, + position_offset); // Mesh uploader pivot mUploadWaitList.push_back(thread); } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 5060e55d051..516a2283de6 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -801,6 +801,7 @@ class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler bool mUploadSkin; bool mUploadJoints; bool mLockScaleIfJointPosition; + LLVector3 mPositionOffset; // Mesh uploader pivot: added to every stored domain volatile bool mDiscarded; LLHost mHost; @@ -815,7 +816,8 @@ class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler const LLUUID destination_folder_id = LLUUID::null, bool do_upload = true, LLHandle fee_observer = (LLHandle()), - LLHandle upload_observer = (LLHandle())); + LLHandle upload_observer = (LLHandle()), + const LLVector3& position_offset = LLVector3::zero); // Mesh uploader pivot ~LLMeshUploadThread(); bool finished() const { return mFinished; } @@ -1008,7 +1010,8 @@ class LLMeshRepository const LLUUID& destination_folder_id = LLUUID::null, bool do_upload = true, LLHandle fee_observer= (LLHandle()), - LLHandle upload_observer = (LLHandle())); + LLHandle upload_observer = (LLHandle()), + const LLVector3& position_offset = LLVector3::zero); // Mesh uploader pivot S32 getMeshSize(const LLUUID& mesh_id, S32 lod) const; diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 374821d3e4f..0376164f322 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -2182,6 +2182,53 @@ void LLModelPreview::updateStatusMessages() } } + // Mesh uploader pivot: the sim keeps analyzed hulls where the pivot puts them but fits + // any other physics shape to the box, so a pivot needs analyzed physics or none at all. + { + bool has_physics = false; + bool analyzed = true; + bool rigged = false; + for (const LLModelInstance& instance : mUploadData) + { + rigged |= instance.mModel && !instance.mModel->mSkinWeights.empty(); + const LLModel* physics = instance.mLOD[LLModel::LOD_PHYSICS]; + if (physics && physics->getNumVolumeFaces() > 0) + { + has_physics = true; + analyzed &= !physics->mPhysics.mHull.empty(); + } + } + + std::string pivot_status; + mUploadPivotAllowed = true; + if (rigged) + { + pivot_status = mFMP->getString("pivot_status_rigged"); + mUploadPivotAllowed = false; + } + else if (mUploadData.size() > 1) + { + pivot_status = mFMP->getString("pivot_status_multiple"); + mUploadPivotAllowed = false; + } + else if (has_physics && !analyzed) + { + pivot_status = mFMP->getString("pivot_status_analyze"); + mUploadPivotAllowed = false; + } + else if (!has_physics && !mUploadPivot.isExactlyZero()) + { + pivot_status = mFMP->getString("pivot_status_no_physics"); + } + + for (const char* axis : { "pivot_x", "pivot_y", "pivot_z" }) + { + mFMP->childSetEnabled(axis, mUploadPivotAllowed); + } + mFMP->childSetValue("pivot_status", pivot_status); + } + // + if (!mModelNoErrors || mHasDegenerate) { mFMP->childDisable("ok_btn"); @@ -3641,6 +3688,53 @@ bool LLModelPreview::render() } } + // Mesh uploader pivot: the object's origin, drawn through the model + if (mViewOption["show_origin"]) + { + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + gDebugProgram.bind(); + gGL.getTextureSlot(0)->unbind(); + LLGLDepthTest gls_depth(GL_FALSE); + gGL.setLineWidth(2.f); + + const LLVector3 pivot = getEffectiveUploadPivot() * 0.5f; + for (const LLModelInstance& instance : mUploadData) + { + const LLMatrix4& mat = instance.mTransform; + const LLVector3 origin = pivot * mat; + + LLVector3 axes[3]; + F32 reach = 0.f; + for (S32 i = 0; i < 3; ++i) + { + axes[i].set(mat.mMatrix[i][0], mat.mMatrix[i][1], mat.mMatrix[i][2]); + reach = llmax(reach, axes[i].normalize()); + } + reach *= 0.6f; + + static const LLColor4 colors[3] = { LLColor4::red, LLColor4::green, LLColor4::blue }; + gGL.begin(LLRender::LINES); + for (S32 i = 0; i < 3; ++i) + { + gGL.color4f(colors[i].mV[VRED] * 0.5f, colors[i].mV[VGREEN] * 0.5f, colors[i].mV[VBLUE] * 0.5f, 1.f); + gGL.vertex3fv((origin - axes[i] * reach).mV); + gGL.vertex3fv(origin.mV); + gGL.color4fv(colors[i].mV); + gGL.vertex3fv(origin.mV); + gGL.vertex3fv((origin + axes[i] * reach).mV); + } + gGL.end(); + } + gGL.flush(); + gGL.setLineWidth(1.f); + + if (shader) + { + shader->bind(); + } + } + // + gObjectPreviewProgram.unbind(); gGL.popMatrix(); diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index b62030a349c..f5a09606208 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -172,6 +172,15 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex, public LLH void setHasPivot(bool val) { mHasPivot = val; } void setModelPivot(const LLVector3& pivot) { mModelPivot = pivot; } + // Mesh uploader pivot: the object's origin within its bounding box, -1 to 1 per axis + void setUploadPivot(const LLVector3& pivot) { mUploadPivot = pivot; } + const LLVector3& getUploadPivot() const { return mUploadPivot; } + // The pivot in effect: centred while the physics or the model count rules it out. + LLVector3 getEffectiveUploadPivot() const { return mUploadPivotAllowed ? mUploadPivot : LLVector3::zero; } + // What the stored domains are shifted by so that the origin lands on the pivot. + LLVector3 getUploadPositionOffset() const { return getEffectiveUploadPivot() * -0.5f; } + // + //Is a rig valid so that it can be used as a criteria for allowing for uploading of joint positions //Accessors for joint position upload friendly rigs const bool isRigValidForJointPositionUpload(void) const { return mRigValidJointUpload; } @@ -271,6 +280,8 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex, public LLH bool mResetJoints; bool mModelNoErrors; bool mLookUpLodFiles; + LLVector3 mUploadPivot; // Mesh uploader pivot + bool mUploadPivotAllowed{true}; // Mesh uploader pivot std::map mViewOption; diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index dee158eaa9e..c8f14716c77 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -35,6 +35,10 @@ LOD materials are not a subset of reference model. Some physical hulls exceed vertex limitations. The physics mesh is too dense or contains degenerate triangles. Use Analyze/Simplify to resolve. + A pivot is for unrigged mesh; rigged mesh is placed by its skeleton. + A pivot can be set only when uploading a single model. + Analyze the physics to set a pivot: unanalyzed physics stays centred on the object. + No physics shape is defined, so the object collides with its default hull, centred on itself. Fine for a phantom piece or physics type None. All Analyzing... Simplifying... @@ -1365,6 +1369,70 @@ label_text.text_color="White" left="20" top_pad="20"/> + + + Pivot: + + + + + + @@ -1934,8 +2000,18 @@ Analysed: label_text.text_color="White" layout="topleft" name="show_joint_positions" - top_pad="21"> + top_pad="10"> + + + + Date: Thu, 29 Oct 2026 03:31:56 +0000 Subject: [PATCH 02/20] Show the uploader pivot in percent or metres Percent of the half-extent suits snapping to a face; metres from the centre suit a hinge at a known distance. The pivot is kept as a fraction of the box, so metres follow the scale and the model's size. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llfloatermodelpreview.cpp | 66 +++++++++++++++++-- indra/newview/llfloatermodelpreview.h | 7 +- indra/newview/llmodelpreview.cpp | 7 ++ .../default/xui/en/floater_model_preview.xml | 42 +++++++----- 4 files changed, 99 insertions(+), 23 deletions(-) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 73508358d6d..9a27e98a22c 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -196,6 +196,8 @@ bool LLFloaterModelPreview::postBuild() { getChild(axis)->setCommitCallback(boost::bind(&LLFloaterModelPreview::onPivotCommit, this)); } + childSetValue("pivot_units", "percent"); + getChild("pivot_units")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onPivotUnitsCommit, this)); // getChild("description_form")->setKeystrokeCallback(boost::bind(&LLFloaterModelPreview::onDescriptionKeystroke, this, _1), NULL); @@ -685,6 +687,17 @@ void LLFloaterModelPreview::onImportScaleCommit(LLUICtrl*,void* userdata) } // Mesh uploader pivot +static const char* const PIVOT_AXES[3] = { "pivot_x", "pivot_y", "pivot_z" }; + +LLVector3 LLFloaterModelPreview::getPivotHalfExtents() const +{ + if (!mModelPreview) + { + return LLVector3::zero; + } + return mModelPreview->mPreviewScale * (F32)childGetValue("import_scale").asReal(); +} + void LLFloaterModelPreview::onPivotCommit() { if (!mModelPreview) @@ -692,13 +705,55 @@ void LLFloaterModelPreview::onPivotCommit() return; } - mModelPreview->setUploadPivot(LLVector3((F32)childGetValue("pivot_x").asReal(), - (F32)childGetValue("pivot_y").asReal(), - (F32)childGetValue("pivot_z").asReal())); + const bool metres = childGetValue("pivot_units").asString() == "metres"; + const LLVector3 half = getPivotHalfExtents(); + LLVector3 pivot; + for (S32 i = 0; i < 3; ++i) + { + const F32 shown = (F32)childGetValue(PIVOT_AXES[i]).asReal(); + if (metres) + { + pivot.mV[i] = half.mV[i] > 0.f ? shown / half.mV[i] : 0.f; + } + else + { + pivot.mV[i] = shown / 100.f; + } + pivot.mV[i] = llclamp(pivot.mV[i], -1.f, 1.f); + } + + mModelPreview->setUploadPivot(pivot); mModelPreview->updateStatusMessages(); toggleCalculateButton(true); mModelPreview->refresh(); } + +void LLFloaterModelPreview::onPivotUnitsCommit() +{ + updatePivotControls(); +} + +void LLFloaterModelPreview::updatePivotControls() +{ + if (!mModelPreview) + { + return; + } + + const bool metres = childGetValue("pivot_units").asString() == "metres"; + const LLVector3 half = getPivotHalfExtents(); + const LLVector3& pivot = mModelPreview->getUploadPivot(); + for (S32 i = 0; i < 3; ++i) + { + LLSpinCtrl* spinner = getChild(PIVOT_AXES[i]); + const F32 range = metres ? half.mV[i] : 100.f; + spinner->setMinValue(-range); + spinner->setMaxValue(range); + spinner->setIncrement(metres ? 0.01f : 50.f); + spinner->setPrecision(metres ? 3 : 1); + spinner->setValue(pivot.mV[i] * range); + } +} // //static @@ -1901,13 +1956,12 @@ void LLFloaterModelPreview::resetUploadOptions() { childSetValue("import_scale", 1); // Mesh uploader pivot - childSetValue("pivot_x", 0); - childSetValue("pivot_y", 0); - childSetValue("pivot_z", 0); + childSetValue("pivot_units", "percent"); if (mModelPreview) { mModelPreview->setUploadPivot(LLVector3::zero); } + updatePivotControls(); // childSetValue("pelvis_offset", 0); childSetValue("physics_explode", 0); diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h index 1893be8cbe7..902f9c63d4e 100644 --- a/indra/newview/llfloatermodelpreview.h +++ b/indra/newview/llfloatermodelpreview.h @@ -144,7 +144,12 @@ class LLFloaterModelPreview final : public LLFloaterModelUploadBase void onDescriptionKeystroke(LLUICtrl*); static void onImportScaleCommit(LLUICtrl*, void*); - void onPivotCommit(); // Mesh uploader pivot + // Mesh uploader pivot: shown in percent of the half-extent or in metres from the centre + void onPivotCommit(); + void onPivotUnitsCommit(); + void updatePivotControls(); + LLVector3 getPivotHalfExtents() const; + // static void onPelvisOffsetCommit(LLUICtrl*, void*); static void onPreviewLODCommit(LLUICtrl*,void*); diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 0376164f322..9b582477967 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -279,6 +279,13 @@ void LLModelPreview::updateDimentionsAndOffsets() mDetailsSignal((F32)(mPreviewScale[0] * scale), (F32)(mPreviewScale[1] * scale), (F32)(mPreviewScale[2] * scale)); + // Mesh uploader pivot: metres follow the model's size + if (LLFloaterModelPreview::sInstance && LLFloaterModelPreview::sInstance->mModelPreview == this) + { + LLFloaterModelPreview::sInstance->updatePivotControls(); + } + // + updateStatusMessages(); } diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index c8f14716c77..28f79f1a4e9 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1378,51 +1378,61 @@ text_color="White" top_pad="20" width="50" - tool_tip="Where the object's origin, and so its rotation, sits within its bounding box: -1 and 1 are its faces, 0 its centre."> + tool_tip="Where the object's origin, and so its rotation, sits within its bounding box: in percent, -100 and 100 are its faces and 0 its centre; in metres, the distance from its centre."> Pivot: + width="80"/> + width="80"/> + width="80"/> + + % + m + Date: Mon, 2 Nov 2026 05:03:36 +0000 Subject: [PATCH 03/20] Fit the uploader's display options inside their panel The right panel was 140 tall, so the Origin check box, and part of Joints, lay below it where clicks never reached them; the preview gives up the height. Co-Authored-By: Claude Opus 5.5 --- indra/newview/skins/default/xui/en/floater_model_preview.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 28f79f1a4e9..83af7d78667 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1932,10 +1932,10 @@ Analysed: name="preview_panel" top_pad="4" width="325" - height="408"/> + height="340"/> Date: Mon, 2 Nov 2026 05:05:24 +0000 Subject: [PATCH 04/20] Give the pivot units their values back A combo item's text is its value, so the metres item reported "m" and the pivot stayed in percent; the labels are attributes now, as the file's other combos have them. Co-Authored-By: Claude Opus 5.5 --- indra/newview/skins/default/xui/en/floater_model_preview.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 83af7d78667..c4396eb5f1b 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1430,8 +1430,8 @@ name="pivot_units" top_delta="0" width="50"> - % - m + + Date: Mon, 2 Nov 2026 05:08:32 +0000 Subject: [PATCH 05/20] Draw the uploader origin as the translate gizmo's arrows Each axis is a shaft and a solid cone in the gizmo's colours, red X forward, so the origin reads as the object's frame rather than three crossing lines. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llmodelpreview.cpp | 37 ++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 9b582477967..450a0aac416 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -3719,16 +3719,45 @@ bool LLModelPreview::render() } reach *= 0.6f; + // Arrows as the translate gizmo draws them: a shaft and a solid cone per axis. static const LLColor4 colors[3] = { LLColor4::red, LLColor4::green, LLColor4::blue }; + const F32 head_length = reach * 0.2f; + const F32 head_radius = reach * 0.06f; + constexpr S32 HEAD_SEGMENTS = 12; + gGL.begin(LLRender::LINES); for (S32 i = 0; i < 3; ++i) { - gGL.color4f(colors[i].mV[VRED] * 0.5f, colors[i].mV[VGREEN] * 0.5f, colors[i].mV[VBLUE] * 0.5f, 1.f); - gGL.vertex3fv((origin - axes[i] * reach).mV); - gGL.vertex3fv(origin.mV); gGL.color4fv(colors[i].mV); gGL.vertex3fv(origin.mV); - gGL.vertex3fv((origin + axes[i] * reach).mV); + gGL.vertex3fv((origin + axes[i] * (reach - head_length)).mV); + } + gGL.end(); + + gGL.begin(LLRender::TRIANGLES); + for (S32 i = 0; i < 3; ++i) + { + const LLVector3 tip = origin + axes[i] * reach; + const LLVector3 base = origin + axes[i] * (reach - head_length); + const LLVector3 u = axes[(i + 1) % 3] * head_radius; + const LLVector3 v = axes[(i + 2) % 3] * head_radius; + for (S32 s = 0; s < HEAD_SEGMENTS; ++s) + { + const F32 a0 = F_TWO_PI * s / HEAD_SEGMENTS; + const F32 a1 = F_TWO_PI * (s + 1) / HEAD_SEGMENTS; + const LLVector3 r0 = base + u * cosf(a0) + v * sinf(a0); + const LLVector3 r1 = base + u * cosf(a1) + v * sinf(a1); + // Shade the side by its angle so the cone reads as solid. + const F32 shade = 0.65f + 0.35f * cosf(a0); + gGL.color4f(colors[i].mV[VRED] * shade, colors[i].mV[VGREEN] * shade, colors[i].mV[VBLUE] * shade, 1.f); + gGL.vertex3fv(tip.mV); + gGL.vertex3fv(r0.mV); + gGL.vertex3fv(r1.mV); + gGL.color4f(colors[i].mV[VRED] * 0.5f, colors[i].mV[VGREEN] * 0.5f, colors[i].mV[VBLUE] * 0.5f, 1.f); + gGL.vertex3fv(base.mV); + gGL.vertex3fv(r1.mV); + gGL.vertex3fv(r0.mV); + } } gGL.end(); } From ded3ab2ea25b623fc1880c238ae54acc57b6c89d Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Mon, 2 Nov 2026 07:19:43 +0000 Subject: [PATCH 06/20] Colour the uploader origin's arrows per axis gDebugProgram draws one uniform colour, so every arrow took the last one set, blue; gUIProgram takes the vertex colours. Culling is off so the cones show from any side. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llmodelpreview.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 450a0aac416..6e2598bb78c 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -3699,9 +3699,10 @@ bool LLModelPreview::render() if (mViewOption["show_origin"]) { LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; - gDebugProgram.bind(); + gUIProgram.bind(); // takes the vertex colours, which gDebugProgram replaces with one uniform gGL.getTextureSlot(0)->unbind(); LLGLDepthTest gls_depth(GL_FALSE); + LLGLDisable no_cull(GL_CULL_FACE); gGL.setLineWidth(2.f); const LLVector3 pivot = getEffectiveUploadPivot() * 0.5f; From 23f68546595736c8a4af54ca680246748926a221 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Mon, 2 Nov 2026 08:33:19 +0000 Subject: [PATCH 07/20] Measure the uploader pivot along the mesh's own axes Metres divided by the scene's bounding box, so a rotated instance moved by the wrong amount; they now use the instance's local half-extents, as the pivot and the Origin arrows do. A flat axis keeps its pivot at zero in percent too, and an analyzed hull on the base model, which the upload sends when there is no physics model, counts as physics. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llfloatermodelpreview.cpp | 20 ++++++++++++++++---- indra/newview/llmodelpreview.cpp | 5 +++++ 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 9a27e98a22c..1af60b238d1 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -695,7 +695,19 @@ LLVector3 LLFloaterModelPreview::getPivotHalfExtents() const { return LLVector3::zero; } - return mModelPreview->mPreviewScale * (F32)childGetValue("import_scale").asReal(); + const F32 scale = (F32)childGetValue("import_scale").asReal(); + if (mModelPreview->mUploadData.size() == 1) + { + // The pivot moves along the mesh's own axes, so a rotated instance measures along them. + const LLMatrix4& mat = mModelPreview->mUploadData[0].mTransform; + LLVector3 half; + for (S32 i = 0; i < 3; ++i) + { + half.mV[i] = LLVector3(mat.mMatrix[i][0], mat.mMatrix[i][1], mat.mMatrix[i][2]).length() * 0.5f * scale; + } + return half; + } + return mModelPreview->mPreviewScale * scale; } void LLFloaterModelPreview::onPivotCommit() @@ -711,13 +723,13 @@ void LLFloaterModelPreview::onPivotCommit() for (S32 i = 0; i < 3; ++i) { const F32 shown = (F32)childGetValue(PIVOT_AXES[i]).asReal(); - if (metres) + if (half.mV[i] <= 0.f) { - pivot.mV[i] = half.mV[i] > 0.f ? shown / half.mV[i] : 0.f; + pivot.mV[i] = 0.f; // a flat axis has nowhere to put it } else { - pivot.mV[i] = shown / 100.f; + pivot.mV[i] = metres ? shown / half.mV[i] : shown / 100.f; } pivot.mV[i] = llclamp(pivot.mV[i], -1.f, 1.f); } diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 6e2598bb78c..5b9b374debe 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -2198,12 +2198,17 @@ void LLModelPreview::updateStatusMessages() for (const LLModelInstance& instance : mUploadData) { rigged |= instance.mModel && !instance.mModel->mSkinWeights.empty(); + // The hulls uploaded are the physics model's, or the base model's without one. const LLModel* physics = instance.mLOD[LLModel::LOD_PHYSICS]; if (physics && physics->getNumVolumeFaces() > 0) { has_physics = true; analyzed &= !physics->mPhysics.mHull.empty(); } + else if (!physics && instance.mModel && !instance.mModel->mPhysics.mHull.empty()) + { + has_physics = true; + } } std::string pivot_status; From e0db0c770391ed2fb09b47b97ac34b3d58f29064 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 00:17:58 +0000 Subject: [PATCH 08/20] Take the uploader pivot from the file by default The file already says where each model's origin is, such as its object origin in Blender: the loader centres the model on its box and records where it moved the centre from. The pivot now has three modes: - From file, the default: each model's origin as the file gives it, for uploads of one model or several. A model whose origin lies outside its box keeps its centre, since that origin is a scene's. - Centre: as uploads were before. - Custom: the spinners, for a single model, starting from the pivot last shown. Each model's offset now travels with it to the upload, and each instance stands on its own pivot, so a linkset's parts stay where the file placed them. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llfloatermodelpreview.cpp | 44 +++++++++-- indra/newview/llfloatermodelpreview.h | 1 + indra/newview/llmeshrepository.cpp | 21 ++++-- indra/newview/llmeshrepository.h | 10 ++- indra/newview/llmodelpreview.cpp | 73 ++++++++++++++++--- indra/newview/llmodelpreview.h | 27 ++++--- .../default/xui/en/floater_model_preview.xml | 40 +++++++++- 7 files changed, 182 insertions(+), 34 deletions(-) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 1af60b238d1..91c1d551470 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -31,6 +31,8 @@ #include "llfloatermodelpreview.h" +#include "llradiogroup.h" // Mesh uploader pivot + #include "llfilepicker.h" #include "llimagebmp.h" #include "llimagetga.h" @@ -198,6 +200,8 @@ bool LLFloaterModelPreview::postBuild() } childSetValue("pivot_units", "percent"); getChild("pivot_units")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onPivotUnitsCommit, this)); + childSetValue("pivot_mode", "file"); + getChild("pivot_mode")->setCommitCallback(boost::bind(&LLFloaterModelPreview::onPivotModeCommit, this)); // getChild("description_form")->setKeystrokeCallback(boost::bind(&LLFloaterModelPreview::onDescriptionKeystroke, this, _1), NULL); @@ -520,7 +524,7 @@ void LLFloaterModelPreview::onClickCalculateBtn() upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, mUploadModelUrl, mDestinationFolderId, false, getWholeModelFeeObserverHandle(), - LLHandle(), mModelPreview->getUploadPositionOffset()); // Mesh uploader pivot + LLHandle(), mModelPreview->getUploadPositionOffsets()); // Mesh uploader pivot toggleCalculateButton(false); mUploadBtn->setEnabled(false); @@ -734,7 +738,7 @@ void LLFloaterModelPreview::onPivotCommit() pivot.mV[i] = llclamp(pivot.mV[i], -1.f, 1.f); } - mModelPreview->setUploadPivot(pivot); + mModelPreview->setCustomPivot(pivot); mModelPreview->updateStatusMessages(); toggleCalculateButton(true); mModelPreview->refresh(); @@ -745,6 +749,22 @@ void LLFloaterModelPreview::onPivotUnitsCommit() updatePivotControls(); } +void LLFloaterModelPreview::onPivotModeCommit() +{ + if (!mModelPreview) + { + return; + } + const std::string mode = childGetValue("pivot_mode").asString(); + mModelPreview->setPivotMode(mode == "custom" ? LLModelPreview::PIVOT_CUSTOM + : mode == "centre" ? LLModelPreview::PIVOT_CENTRE + : LLModelPreview::PIVOT_FILE); + updatePivotControls(); + mModelPreview->updateStatusMessages(); + toggleCalculateButton(true); + mModelPreview->refresh(); +} + void LLFloaterModelPreview::updatePivotControls() { if (!mModelPreview) @@ -752,9 +772,22 @@ void LLFloaterModelPreview::updatePivotControls() return; } + // Custom is for one model: several each take their own origin from the file, or the centre. + const bool single = mModelPreview->mUploadData.size() == 1; + if (LLRadioGroup* modes = getChild("pivot_mode")) + { + if (!single && mModelPreview->getPivotMode() == LLModelPreview::PIVOT_CUSTOM) + { + mModelPreview->setPivotMode(LLModelPreview::PIVOT_FILE); + } + modes->setIndexEnabled(2, single); + const LLModelPreview::EPivotMode mode = mModelPreview->getPivotMode(); + modes->setValue(mode == LLModelPreview::PIVOT_CUSTOM ? "custom" : mode == LLModelPreview::PIVOT_CENTRE ? "centre" : "file"); + } + const bool metres = childGetValue("pivot_units").asString() == "metres"; const LLVector3 half = getPivotHalfExtents(); - const LLVector3& pivot = mModelPreview->getUploadPivot(); + const LLVector3 pivot = single ? mModelPreview->getWantedPivot(mModelPreview->mUploadData[0].mModel) : LLVector3::zero; for (S32 i = 0; i < 3; ++i) { LLSpinCtrl* spinner = getChild(PIVOT_AXES[i]); @@ -1833,7 +1866,7 @@ void LLFloaterModelPreview::onUpload(void* user_data) upload_skinweights, upload_joint_positions, lock_scale_if_joint_position, mp->mUploadModelUrl, mp->mDestinationFolderId, true, LLHandle(), mp->getWholeModelUploadObserverHandle(), - mp->mModelPreview->getUploadPositionOffset()); // Mesh uploader pivot + mp->mModelPreview->getUploadPositionOffsets()); // Mesh uploader pivot } @@ -1971,7 +2004,8 @@ void LLFloaterModelPreview::resetUploadOptions() childSetValue("pivot_units", "percent"); if (mModelPreview) { - mModelPreview->setUploadPivot(LLVector3::zero); + mModelPreview->setPivotMode(LLModelPreview::PIVOT_FILE); + mModelPreview->setCustomPivot(LLVector3::zero); } updatePivotControls(); // diff --git a/indra/newview/llfloatermodelpreview.h b/indra/newview/llfloatermodelpreview.h index 902f9c63d4e..347d215a9ee 100644 --- a/indra/newview/llfloatermodelpreview.h +++ b/indra/newview/llfloatermodelpreview.h @@ -147,6 +147,7 @@ class LLFloaterModelPreview final : public LLFloaterModelUploadBase // Mesh uploader pivot: shown in percent of the half-extent or in metres from the centre void onPivotCommit(); void onPivotUnitsCommit(); + void onPivotModeCommit(); void updatePivotControls(); LLVector3 getPivotHalfExtents() const; // diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index a3afc5df1e3..e2329accc3b 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2700,7 +2700,7 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list_t& data const std::string & upload_url, LLUUID destination_folder_id, bool do_upload, LLHandle fee_observer, LLHandle upload_observer, - const LLVector3& position_offset) // Mesh uploader pivot + const position_offsets_t& position_offsets) // Mesh uploader pivot : LLThread("mesh upload"), LLCore::HttpHandler(), mDiscarded(false), @@ -2716,7 +2716,7 @@ LLMeshUploadThread::LLMeshUploadThread(LLMeshUploadThread::instance_list_t& data mUploadSkin = upload_skin; mUploadJoints = upload_joints; mLockScaleIfJointPosition = lock_scale_if_joint_position; - mPositionOffset = position_offset; // Mesh uploader pivot + mPositionOffsets = position_offsets; // Mesh uploader pivot mMutex = new LLMutex(); mPendingUploads = 0; mFinished = false; @@ -2827,6 +2827,14 @@ LLSD llsd_from_file(std::string filename) return result; } +// Mesh uploader pivot +LLVector3 LLMeshUploadThread::getPositionOffset(const LLModel* model) const +{ + auto it = mPositionOffsets.find(model); + return it != mPositionOffsets.end() ? it->second : LLVector3::zero; +} +// + void LLMeshUploadThread::packModelIntance( LLModel* model, LLMeshUploadThread::instance_list_t& instance_list, @@ -2882,7 +2890,7 @@ void LLMeshUploadThread::packModelIntance( LLModel::WRITE_BINARY, false, data.mBaseModel->mSubmodelID, - mPositionOffset); // Mesh uploader pivot + getPositionOffset(data.mBaseModel)); // Mesh uploader pivot data.mAssetData = ostr.str(); std::string str = ostr.str(); @@ -2910,6 +2918,9 @@ void LLMeshUploadThread::packModelIntance( LLQuaternion rot; LLMatrix4 transformation = instance.mTransform; decomposeMeshMatrix(transformation, pos, rot, scale); + // Mesh uploader pivot: the prim stands on its pivot, so the mesh stays where it was placed + pos = (-getPositionOffset(data.mBaseModel)) * transformation; + // instance_entry["position"] = ll_sd_from_vector3(pos); instance_entry["rotation"] = ll_sd_from_quaternion(rot); instance_entry["scale"] = ll_sd_from_vector3(scale); @@ -5560,12 +5571,12 @@ void LLMeshRepository::uploadModel(std::vector& data, const std bool upload_skin, bool upload_joints, bool lock_scale_if_joint_position, std::string upload_url, const LLUUID& destination_folder_id, bool do_upload, LLHandle fee_observer, LLHandle upload_observer, - const LLVector3& position_offset) // Mesh uploader pivot + const LLMeshUploadThread::position_offsets_t& position_offsets) // Mesh uploader pivot { LLMeshUploadThread* thread = new LLMeshUploadThread(data, lod_sources, scale, upload_textures, upload_skin, upload_joints, lock_scale_if_joint_position, upload_url, destination_folder_id, do_upload, fee_observer, upload_observer, - position_offset); // Mesh uploader pivot + position_offsets); // Mesh uploader pivot mUploadWaitList.push_back(thread); } diff --git a/indra/newview/llmeshrepository.h b/indra/newview/llmeshrepository.h index 516a2283de6..5e5ebc7993e 100644 --- a/indra/newview/llmeshrepository.h +++ b/indra/newview/llmeshrepository.h @@ -801,7 +801,11 @@ class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler bool mUploadSkin; bool mUploadJoints; bool mLockScaleIfJointPosition; - LLVector3 mPositionOffset; // Mesh uploader pivot: added to every stored domain + // Mesh uploader pivot: added to each base model's stored domains + typedef boost::unordered_map position_offsets_t; + position_offsets_t mPositionOffsets; + LLVector3 getPositionOffset(const LLModel* model) const; + // volatile bool mDiscarded; LLHost mHost; @@ -817,7 +821,7 @@ class LLMeshUploadThread : public LLThread, public LLCore::HttpHandler bool do_upload = true, LLHandle fee_observer = (LLHandle()), LLHandle upload_observer = (LLHandle()), - const LLVector3& position_offset = LLVector3::zero); // Mesh uploader pivot + const position_offsets_t& position_offsets = position_offsets_t()); // Mesh uploader pivot ~LLMeshUploadThread(); bool finished() const { return mFinished; } @@ -1011,7 +1015,7 @@ class LLMeshRepository bool do_upload = true, LLHandle fee_observer= (LLHandle()), LLHandle upload_observer = (LLHandle()), - const LLVector3& position_offset = LLVector3::zero); // Mesh uploader pivot + const LLMeshUploadThread::position_offsets_t& position_offsets = LLMeshUploadThread::position_offsets_t()); // Mesh uploader pivot S32 getMeshSize(const LLUUID& mesh_id, S32 lod) const; diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 5b9b374debe..4e675d9cb67 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -1846,6 +1846,64 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d << ", failures: " << meshes_fail_count << LL_ENDL; } +// Mesh uploader pivot +void LLModelPreview::setPivotMode(EPivotMode mode) +{ + if (mode == PIVOT_CUSTOM && mPivotMode != PIVOT_CUSTOM && mUploadData.size() == 1) + { + mCustomPivot = getWantedPivot(mUploadData[0].mModel); + } + mPivotMode = mode; +} + +// static +LLVector3 LLModelPreview::getFileOriginPivot(const LLModel* model) +{ + if (!model) + { + return LLVector3::zero; + } + // The loader centred the model on its box; the file's origin is where it moved the centre from. + LLVector3 size, centre; + model->getNormalizedScaleTranslation(size, centre); + LLVector3 pivot; + for (S32 i = 0; i < 3; ++i) + { + pivot.mV[i] = size.mV[i] > 0.f ? -2.f * centre.mV[i] / size.mV[i] : 0.f; + // An origin outside the box is a scene's, from a file that placed its vertices in the scene: centre it. + if (fabsf(pivot.mV[i]) > 1.001f) + { + return LLVector3::zero; + } + pivot.mV[i] = llclamp(pivot.mV[i], -1.f, 1.f); + } + return pivot; +} + +LLVector3 LLModelPreview::getWantedPivot(const LLModel* model) const +{ + switch (mPivotMode) + { + case PIVOT_FILE: + return getFileOriginPivot(model); + case PIVOT_CUSTOM: + return mUploadData.size() == 1 ? mCustomPivot : LLVector3::zero; + default: + return LLVector3::zero; + } +} + +LLMeshUploadThread::position_offsets_t LLModelPreview::getUploadPositionOffsets() const +{ + LLMeshUploadThread::position_offsets_t offsets; + for (const LLModelInstance& instance : mUploadData) + { + offsets[instance.mModel.get()] = getUploadPivot(instance.mModel) * -0.5f; + } + return offsets; +} +// + void LLModelPreview::updateStatusMessages() { // bit mask values for physics errors. used to prevent overwrite of single line status @@ -2218,25 +2276,23 @@ void LLModelPreview::updateStatusMessages() pivot_status = mFMP->getString("pivot_status_rigged"); mUploadPivotAllowed = false; } - else if (mUploadData.size() > 1) - { - pivot_status = mFMP->getString("pivot_status_multiple"); - mUploadPivotAllowed = false; - } else if (has_physics && !analyzed) { pivot_status = mFMP->getString("pivot_status_analyze"); mUploadPivotAllowed = false; } - else if (!has_physics && !mUploadPivot.isExactlyZero()) + else if (!has_physics && std::any_of(mUploadData.begin(), mUploadData.end(), [this](const LLModelInstance& instance) + { return !getWantedPivot(instance.mModel).isExactlyZero(); })) { pivot_status = mFMP->getString("pivot_status_no_physics"); } + const bool custom = mFMP->childGetValue("pivot_mode").asString() == "custom"; for (const char* axis : { "pivot_x", "pivot_y", "pivot_z" }) { - mFMP->childSetEnabled(axis, mUploadPivotAllowed); + mFMP->childSetEnabled(axis, mUploadPivotAllowed && custom); } + mFMP->childSetEnabled("pivot_mode", mUploadPivotAllowed); mFMP->childSetValue("pivot_status", pivot_status); } // @@ -3710,11 +3766,10 @@ bool LLModelPreview::render() LLGLDisable no_cull(GL_CULL_FACE); gGL.setLineWidth(2.f); - const LLVector3 pivot = getEffectiveUploadPivot() * 0.5f; for (const LLModelInstance& instance : mUploadData) { const LLMatrix4& mat = instance.mTransform; - const LLVector3 origin = pivot * mat; + const LLVector3 origin = (getUploadPivot(instance.mModel) * 0.5f) * mat; LLVector3 axes[3]; F32 reach = 0.f; diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index f5a09606208..6c363e79faa 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -172,13 +172,19 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex, public LLH void setHasPivot(bool val) { mHasPivot = val; } void setModelPivot(const LLVector3& pivot) { mModelPivot = pivot; } - // Mesh uploader pivot: the object's origin within its bounding box, -1 to 1 per axis - void setUploadPivot(const LLVector3& pivot) { mUploadPivot = pivot; } - const LLVector3& getUploadPivot() const { return mUploadPivot; } - // The pivot in effect: centred while the physics or the model count rules it out. - LLVector3 getEffectiveUploadPivot() const { return mUploadPivotAllowed ? mUploadPivot : LLVector3::zero; } - // What the stored domains are shifted by so that the origin lands on the pivot. - LLVector3 getUploadPositionOffset() const { return getEffectiveUploadPivot() * -0.5f; } + // Mesh uploader pivot: each model's origin within its bounding box, -1 to 1 per axis + enum EPivotMode { PIVOT_FILE, PIVOT_CENTRE, PIVOT_CUSTOM }; + // Custom starts from the pivot the last mode gave a single model. + void setPivotMode(EPivotMode mode); + EPivotMode getPivotMode() const { return mPivotMode; } + void setCustomPivot(const LLVector3& pivot) { mCustomPivot = pivot; } + // The origin the file gave the model, where its vertices' origin sits in its box. + static LLVector3 getFileOriginPivot(const LLModel* model); + // The pivot the mode asks for, and the one in effect: centred while the physics or rigging rules it out. + LLVector3 getWantedPivot(const LLModel* model) const; + LLVector3 getUploadPivot(const LLModel* model) const { return mUploadPivotAllowed ? getWantedPivot(model) : LLVector3::zero; } + // What each model's stored domains are shifted by so that its origin lands on its pivot. + LLMeshUploadThread::position_offsets_t getUploadPositionOffsets() const; // //Is a rig valid so that it can be used as a criteria for allowing for uploading of joint positions @@ -280,8 +286,11 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex, public LLH bool mResetJoints; bool mModelNoErrors; bool mLookUpLodFiles; - LLVector3 mUploadPivot; // Mesh uploader pivot - bool mUploadPivotAllowed{true}; // Mesh uploader pivot + // Mesh uploader pivot + EPivotMode mPivotMode{PIVOT_FILE}; + LLVector3 mCustomPivot; + bool mUploadPivotAllowed{true}; + // std::map mViewOption; diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index c4396eb5f1b..df00392e25a 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -36,7 +36,6 @@ Some physical hulls exceed vertex limitations. The physics mesh is too dense or contains degenerate triangles. Use Analyze/Simplify to resolve. A pivot is for unrigged mesh; rigged mesh is placed by its skeleton. - A pivot can be set only when uploading a single model. Analyze the physics to set a pivot: unanalyzed physics stays centred on the object. No physics shape is defined, so the object collides with its default hull, centred on itself. Fine for a phantom piece or physics type None. All @@ -1381,6 +1380,41 @@ tool_tip="Where the object's origin, and so its rotation, sits within its bounding box: in percent, -100 and 100 are its faces and 0 its centre; in metres, the distance from its centre."> Pivot: + + + + + Date: Tue, 3 Nov 2026 00:30:25 +0000 Subject: [PATCH 09/20] Lay the pivot modes out in a row The radio items took their default layout, so they stacked under the label and over the spinners. Co-Authored-By: Claude Opus 5.5 --- indra/newview/skins/default/xui/en/floater_model_preview.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index df00392e25a..d0e5775de1c 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1381,6 +1381,7 @@ Pivot: Date: Tue, 3 Nov 2026 00:30:56 +0000 Subject: [PATCH 10/20] Set the pivot spinners beside its modes The modes stack in a column after the label, with the spinners and the status to their right. Co-Authored-By: Claude Opus 5.5 --- .../default/xui/en/floater_model_preview.xml | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index d0e5775de1c..82172309f07 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1383,11 +1383,11 @@ + width="90"> @@ -1426,11 +1426,11 @@ increment="50" label="X" label_width="12" - left="70" + left_pad="10" max_val="100" min_val="-100" name="pivot_x" - top_pad="6" + top_delta="-2" value="0" width="80"/> From e19466f3ff1d0986f636104d24d4b89c4616b709 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 01:39:33 +0000 Subject: [PATCH 11/20] Place uploaded meshes from the region's origin again The pivot override of each instance's position dropped the upload origin that decomposeMeshMatrix adds, so pivoted uploads landed at the region's corner. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llmeshrepository.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llmeshrepository.cpp b/indra/newview/llmeshrepository.cpp index e2329accc3b..7f81db1e7cc 100644 --- a/indra/newview/llmeshrepository.cpp +++ b/indra/newview/llmeshrepository.cpp @@ -2919,7 +2919,7 @@ void LLMeshUploadThread::packModelIntance( LLMatrix4 transformation = instance.mTransform; decomposeMeshMatrix(transformation, pos, rot, scale); // Mesh uploader pivot: the prim stands on its pivot, so the mesh stays where it was placed - pos = (-getPositionOffset(data.mBaseModel)) * transformation; + pos = (-getPositionOffset(data.mBaseModel)) * transformation + mOrigin; // instance_entry["position"] = ll_sd_from_vector3(pos); instance_entry["rotation"] = ll_sd_from_quaternion(rot); From e25b56a6a82c3d9a206a9bd36cd61ac151f6c2cc Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 01:39:33 +0000 Subject: [PATCH 12/20] Measure the pivot's metres without scaling twice The instance transform already carries the import scale, so multiplying by it again doubled the half extents a pivot in metres was read against. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llfloatermodelpreview.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 91c1d551470..3c000533654 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -699,19 +699,19 @@ LLVector3 LLFloaterModelPreview::getPivotHalfExtents() const { return LLVector3::zero; } - const F32 scale = (F32)childGetValue("import_scale").asReal(); if (mModelPreview->mUploadData.size() == 1) { // The pivot moves along the mesh's own axes, so a rotated instance measures along them. + // The transform carries the import scale already. const LLMatrix4& mat = mModelPreview->mUploadData[0].mTransform; LLVector3 half; for (S32 i = 0; i < 3; ++i) { - half.mV[i] = LLVector3(mat.mMatrix[i][0], mat.mMatrix[i][1], mat.mMatrix[i][2]).length() * 0.5f * scale; + half.mV[i] = LLVector3(mat.mMatrix[i][0], mat.mMatrix[i][1], mat.mMatrix[i][2]).length() * 0.5f; } return half; } - return mModelPreview->mPreviewScale * scale; + return mModelPreview->mPreviewScale * (F32)childGetValue("import_scale").asReal(); } void LLFloaterModelPreview::onPivotCommit() From 94aea1d1ebf2cc7b494b877ba0a2a2059dacac0d Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 01:59:58 +0000 Subject: [PATCH 13/20] Hide the uploader origin while skin weights are shown With skin weights shown, the mesh is drawn on the preview avatar, away from the instance transform the origin is drawn from. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llmodelpreview.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 4e675d9cb67..5a0bc823eac 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -3757,7 +3757,7 @@ bool LLModelPreview::render() } // Mesh uploader pivot: the object's origin, drawn through the model - if (mViewOption["show_origin"]) + if (mViewOption["show_origin"] && !mViewOption["show_skin_weight"]) // rigged mesh is drawn on the avatar { LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; gUIProgram.bind(); // takes the vertex colours, which gDebugProgram replaces with one uniform From d41e240f3efeeb452db555ddd74932cbe00ebf8d Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 01:59:58 +0000 Subject: [PATCH 14/20] Settle the pivot against the data an upload sends Whether a pivot is allowed was last worked out before the upload data was rebuilt, so a physics file loaded since could still be sent unanalyzed under a pivot the sim would not follow. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llfloatermodelpreview.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 3c000533654..7ca22b3336b 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -508,6 +508,7 @@ void LLFloaterModelPreview::onClickCalculateBtn() clearLogTab(); addStringToLog("Calculating model data.", false); mModelPreview->rebuildUploadData(); + mModelPreview->updateStatusMessages(); // Mesh uploader pivot: whether a pivot is allowed follows the rebuilt data bool upload_skinweights = childGetValue("upload_skin").asBoolean(); bool upload_joint_positions = childGetValue("upload_joints").asBoolean(); @@ -1848,6 +1849,7 @@ void LLFloaterModelPreview::onUpload(void* user_data) mp->mUploadBtn->setEnabled(false); mp->mModelPreview->rebuildUploadData(); + mp->mModelPreview->updateStatusMessages(); // Mesh uploader pivot: whether a pivot is allowed follows the rebuilt data bool upload_skinweights = mp->childGetValue("upload_skin").asBoolean(); bool upload_joint_positions = mp->childGetValue("upload_joints").asBoolean(); From cd5ed5a2ea1a54417776c58518ef1dd64c57aa02 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 02:16:54 +0000 Subject: [PATCH 15/20] Guide setting object origins for the uploader's pivot How to place an origin on a hinge or joint in Blender, what the Pivot modes and status lines mean, and the usual mistakes. Co-Authored-By: Claude Opus 5.5 --- doc/mesh/object-origins.md | 59 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 doc/mesh/object-origins.md diff --git a/doc/mesh/object-origins.md b/doc/mesh/object-origins.md new file mode 100644 index 00000000000..6d37e07bcfe --- /dev/null +++ b/doc/mesh/object-origins.md @@ -0,0 +1,59 @@ +# Object Origins for Prim-Animated Doors, Hinges and Joints + +## Why the origin matters + +A prim in Second Life turns about its own position, and a mesh prim's position is the object origin it was uploaded with. Put the origin on the hinge, and a script that turns the door with `llSetLocalRot` swings it on its hinge. Leave it in the middle, and the door spins about its centre. + +Until now the uploader always moved the origin to the middle of the object's bounding box. Doors were built with an invisible extra half, or a script had to move the prim as it turned it. The uploader's **Pivot** option keeps the origin you set in Blender instead. + +## Setting the origin in Blender + +1. Apply rotation and scale first: select the object, **Ctrl+A > Rotation & Scale**. +2. Put the 3D cursor on the hinge or joint: in Edit Mode select the vertices along the hinge line, then **Shift+S > Cursor to Selected**. +3. Back in Object Mode, **Object > Set Origin > Origin to 3D Cursor**. +4. Check it: rotate the object (R, then an axis key). It should turn about the hinge, and the orange dot should sit on it. + +- **The origin must lie inside or on the object's bounding box.** The uploader stores it as a point within the box, so an origin outside is moved back to the centre. +- **Turn about a local axis.** Model the part so the hinge runs along X, Y or Z, so a script needs only one angle. +- Each part of a linkset has its own origin. An arm needs one per joint: the turret on its vertical axis, each limb on its elbow. + +## The uploader's Pivot + +The **Pivot** row is on the **Upload options** tab. It has three modes, X/Y/Z spinners beside them, and a status line under the spinners. + +| Mode | The origin goes | Works for | +| --- | --- | --- | +| From file (default) | where Blender's origin was, for each object | one object or many | +| Centre | to the middle of the bounding box, as uploads always did | one object or many | +| Custom | where you type it in the spinners | a single object only | + +- The spinners show the pivot along the object's own axes, in **percent** of its half size (−100 to 100, 0 = centre) or in **metres**; the dropdown beside them switches the unit. +- They are editable only in Custom, which starts from the pivot shown when you switch to it. +- Tick **Origin** under the preview to see the pivot: coloured arrows (X red, Y green, Z blue) drawn at each object's pivot. +- In a multi-object upload each part keeps its own origin, and its position in the linkset moves with it, so every part lands where it sat in Blender. + +The status line says when the pivot is held at the centre, or when the collision stays centred: + +| Status | Why | Fix | +| --- | --- | --- | +| Rigged mesh | rigged mesh is placed by its skeleton | none needed | +| Physics not analyzed | the sim fits a mesh physics shape to the box until it's analyzed | click **Analyze** on the Physics tab | +| No physics shape | the part moves to its pivot, but collides with a default hull centred on itself | fine for phantom parts or physics type None; otherwise add a physics shape and analyze it | + +## Examples and common mistakes + +| Part | Where the origin goes | Turns about | +| --- | --- | --- | +| Door | on the hinge edge, halfway up | its vertical axis | +| Chest lid | on the back edge, along the hinge | the hinge's axis | +| Arm turret | on the base's centre line | its vertical axis | +| Arm limb | in the joint at the limb's near end | the joint's axis | + +A limb's joint is often at the very end of the part. That's fine: the origin may sit on the box's edge, just not outside it. + +| You see | Likely cause | +| --- | --- | +| The part spins about its middle | the pivot is on Centre, the origin lies outside the box, or the physics isn't analyzed | +| The spinners read 0 0 0 on From file | Blender's origin was at the middle, or outside the box | +| The part rotates about the wrong axis | rotation wasn't applied before export | +| Custom is greyed out | the upload has several objects; Custom is for a single one | From 5c751f8eb88158f25caff73159b7cf307f78cdcf Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 02:21:32 +0000 Subject: [PATCH 16/20] Show the uploader's Pivot row in the origins guide Co-Authored-By: Claude Opus 5.5 --- doc/mesh/images/uploader-pivot.png | Bin 0 -> 89058 bytes doc/mesh/object-origins.md | 2 ++ 2 files changed, 2 insertions(+) create mode 100644 doc/mesh/images/uploader-pivot.png diff --git a/doc/mesh/images/uploader-pivot.png b/doc/mesh/images/uploader-pivot.png new file mode 100644 index 0000000000000000000000000000000000000000..a609c185af6783a2f231d213659f9f2ab9f1c103 GIT binary patch literal 89058 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

okt{{ZB;;n7a+ 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%0A%)(8!UTj{w?bKt@DFhh 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 Date: Tue, 3 Nov 2026 05:37:10 +0000 Subject: [PATCH 17/20] Count submodels with their root for the pivot, and warn per part A model with more than eight materials uploads as a root and submodels, so Custom and metres were refused for what is one model. The warning that a pivoted part collides with a centred default hull was also hidden whenever any other part of the linkset had physics. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llfloatermodelpreview.cpp | 4 ++-- indra/newview/llmodelpreview.cpp | 22 ++++++++++++++++------ indra/newview/llmodelpreview.h | 2 ++ 3 files changed, 20 insertions(+), 8 deletions(-) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index 7ca22b3336b..bdaad3c1e06 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -700,7 +700,7 @@ LLVector3 LLFloaterModelPreview::getPivotHalfExtents() const { return LLVector3::zero; } - if (mModelPreview->mUploadData.size() == 1) + if (mModelPreview->isSingleModel()) { // The pivot moves along the mesh's own axes, so a rotated instance measures along them. // The transform carries the import scale already. @@ -774,7 +774,7 @@ void LLFloaterModelPreview::updatePivotControls() } // Custom is for one model: several each take their own origin from the file, or the centre. - const bool single = mModelPreview->mUploadData.size() == 1; + const bool single = mModelPreview->isSingleModel(); if (LLRadioGroup* modes = getChild("pivot_mode")) { if (!single && mModelPreview->getPivotMode() == LLModelPreview::PIVOT_CUSTOM) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 5a0bc823eac..69656842abf 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -1849,7 +1849,7 @@ void LLModelPreview::genMeshOptimizerLODs(S32 which_lod, S32 meshopt_mode, U32 d // Mesh uploader pivot void LLModelPreview::setPivotMode(EPivotMode mode) { - if (mode == PIVOT_CUSTOM && mPivotMode != PIVOT_CUSTOM && mUploadData.size() == 1) + if (mode == PIVOT_CUSTOM && mPivotMode != PIVOT_CUSTOM && isSingleModel()) { mCustomPivot = getWantedPivot(mUploadData[0].mModel); } @@ -1887,12 +1887,19 @@ LLVector3 LLModelPreview::getWantedPivot(const LLModel* model) const case PIVOT_FILE: return getFileOriginPivot(model); case PIVOT_CUSTOM: - return mUploadData.size() == 1 ? mCustomPivot : LLVector3::zero; + return isSingleModel() ? mCustomPivot : LLVector3::zero; default: return LLVector3::zero; } } +bool LLModelPreview::isSingleModel() const +{ + // A model with more than eight materials uploads as a root and its submodels, sharing one transform. + return std::count_if(mUploadData.begin(), mUploadData.end(), + [](const LLModelInstance& instance) { return instance.mModel && !instance.mModel->mSubmodelID; }) == 1; +} + LLMeshUploadThread::position_offsets_t LLModelPreview::getUploadPositionOffsets() const { LLMeshUploadThread::position_offsets_t offsets; @@ -2253,20 +2260,24 @@ void LLModelPreview::updateStatusMessages() bool has_physics = false; bool analyzed = true; bool rigged = false; + bool pivot_without_physics = false; // a pivoted part that collides with a default hull centred on itself for (const LLModelInstance& instance : mUploadData) { rigged |= instance.mModel && !instance.mModel->mSkinWeights.empty(); // The hulls uploaded are the physics model's, or the base model's without one. const LLModel* physics = instance.mLOD[LLModel::LOD_PHYSICS]; + bool instance_physics = false; if (physics && physics->getNumVolumeFaces() > 0) { - has_physics = true; + instance_physics = true; analyzed &= !physics->mPhysics.mHull.empty(); } else if (!physics && instance.mModel && !instance.mModel->mPhysics.mHull.empty()) { - has_physics = true; + instance_physics = true; } + has_physics |= instance_physics; + pivot_without_physics |= !instance_physics && !getWantedPivot(instance.mModel).isExactlyZero(); } std::string pivot_status; @@ -2281,8 +2292,7 @@ void LLModelPreview::updateStatusMessages() pivot_status = mFMP->getString("pivot_status_analyze"); mUploadPivotAllowed = false; } - else if (!has_physics && std::any_of(mUploadData.begin(), mUploadData.end(), [this](const LLModelInstance& instance) - { return !getWantedPivot(instance.mModel).isExactlyZero(); })) + else if (pivot_without_physics) { pivot_status = mFMP->getString("pivot_status_no_physics"); } diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index 6c363e79faa..663a68ba9cb 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -185,6 +185,8 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex, public LLH LLVector3 getUploadPivot(const LLModel* model) const { return mUploadPivotAllowed ? getWantedPivot(model) : LLVector3::zero; } // What each model's stored domains are shifted by so that its origin lands on its pivot. LLMeshUploadThread::position_offsets_t getUploadPositionOffsets() const; + // One model to upload, counting a root and its submodels once: what Custom and metres are for. + bool isSingleModel() const; // //Is a rig valid so that it can be used as a criteria for allowing for uploading of joint positions From 82b3dbe59cb5060e637971b1bef9f34a2da76f12 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 09:42:06 +0000 Subject: [PATCH 18/20] Hold only the parts that cannot take a pivot One rigged part, or one with physics not yet analyzed, held every part of the upload at its centre; now only that part is held. Skin weights left out of the upload no longer count as rigged, and a metre pivot reads the import scale as it is now rather than as the upload data was last built. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llfloatermodelpreview.cpp | 6 ++-- indra/newview/llmodelpreview.cpp | 39 ++++++++++++++++--------- indra/newview/llmodelpreview.h | 6 ++-- 3 files changed, 34 insertions(+), 17 deletions(-) diff --git a/indra/newview/llfloatermodelpreview.cpp b/indra/newview/llfloatermodelpreview.cpp index bdaad3c1e06..ef5d78cab45 100644 --- a/indra/newview/llfloatermodelpreview.cpp +++ b/indra/newview/llfloatermodelpreview.cpp @@ -703,12 +703,14 @@ LLVector3 LLFloaterModelPreview::getPivotHalfExtents() const if (mModelPreview->isSingleModel()) { // The pivot moves along the mesh's own axes, so a rotated instance measures along them. - // The transform carries the import scale already. + // The transform carries the import scale it was built with; a scale changed since is applied here. const LLMatrix4& mat = mModelPreview->mUploadData[0].mTransform; + const F32 built = mModelPreview->mPivotScale; + const F32 rescale = built > 0.f ? (F32)childGetValue("import_scale").asReal() / built : 1.f; LLVector3 half; for (S32 i = 0; i < 3; ++i) { - half.mV[i] = LLVector3(mat.mMatrix[i][0], mat.mMatrix[i][1], mat.mMatrix[i][2]).length() * 0.5f; + half.mV[i] = LLVector3(mat.mMatrix[i][0], mat.mMatrix[i][1], mat.mMatrix[i][2]).length() * 0.5f * rescale; } return half; } diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 69656842abf..281f5794bdb 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -304,6 +304,7 @@ void LLModelPreview::rebuildUploadData() LLSpinCtrl* scale_spinner = mFMP->getChild("import_scale"); F32 scale = (F32)scale_spinner->getValue().asReal(); + mPivotScale = scale; // Mesh uploader pivot: the scale the transforms below carry LLMatrix4 scale_mat; scale_mat.initScale(LLVector3(scale, scale, scale)); @@ -2257,40 +2258,52 @@ void LLModelPreview::updateStatusMessages() // Mesh uploader pivot: the sim keeps analyzed hulls where the pivot puts them but fits // any other physics shape to the box, so a pivot needs analyzed physics or none at all. { - bool has_physics = false; - bool analyzed = true; - bool rigged = false; + // Each part is judged on its own, so one rigged or unanalyzed part holds only itself at the centre. + const bool uploading_skin = mFMP->childGetValue("upload_skin").asBoolean(); + bool any_rigged = false; + bool any_unanalyzed = false; bool pivot_without_physics = false; // a pivoted part that collides with a default hull centred on itself + mPivotHeld.clear(); for (const LLModelInstance& instance : mUploadData) { - rigged |= instance.mModel && !instance.mModel->mSkinWeights.empty(); + const LLModel* model = instance.mModel; + if (!model) + { + continue; + } + // Skin weights left out of the upload make a static mesh, which can take a pivot. + const bool rigged = uploading_skin && !model->mSkinWeights.empty(); // The hulls uploaded are the physics model's, or the base model's without one. const LLModel* physics = instance.mLOD[LLModel::LOD_PHYSICS]; bool instance_physics = false; + bool unanalyzed = false; if (physics && physics->getNumVolumeFaces() > 0) { instance_physics = true; - analyzed &= !physics->mPhysics.mHull.empty(); + unanalyzed = physics->mPhysics.mHull.empty(); } - else if (!physics && instance.mModel && !instance.mModel->mPhysics.mHull.empty()) + else if (!physics && !model->mPhysics.mHull.empty()) { instance_physics = true; } - has_physics |= instance_physics; - pivot_without_physics |= !instance_physics && !getWantedPivot(instance.mModel).isExactlyZero(); + if (rigged || unanalyzed) + { + mPivotHeld.insert(model); + } + any_rigged |= rigged; + any_unanalyzed |= unanalyzed && !rigged; + pivot_without_physics |= !rigged && !instance_physics && !getWantedPivot(model).isExactlyZero(); } std::string pivot_status; - mUploadPivotAllowed = true; - if (rigged) + mUploadPivotAllowed = mPivotHeld.size() < mUploadData.size(); + if (any_rigged) { pivot_status = mFMP->getString("pivot_status_rigged"); - mUploadPivotAllowed = false; } - else if (has_physics && !analyzed) + else if (any_unanalyzed) { pivot_status = mFMP->getString("pivot_status_analyze"); - mUploadPivotAllowed = false; } else if (pivot_without_physics) { diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index 663a68ba9cb..094561c11dd 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -182,7 +182,7 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex, public LLH static LLVector3 getFileOriginPivot(const LLModel* model); // The pivot the mode asks for, and the one in effect: centred while the physics or rigging rules it out. LLVector3 getWantedPivot(const LLModel* model) const; - LLVector3 getUploadPivot(const LLModel* model) const { return mUploadPivotAllowed ? getWantedPivot(model) : LLVector3::zero; } + LLVector3 getUploadPivot(const LLModel* model) const { return mPivotHeld.count(model) ? LLVector3::zero : getWantedPivot(model); } // What each model's stored domains are shifted by so that its origin lands on its pivot. LLMeshUploadThread::position_offsets_t getUploadPositionOffsets() const; // One model to upload, counting a root and its submodels once: what Custom and metres are for. @@ -291,7 +291,9 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex, public LLH // Mesh uploader pivot EPivotMode mPivotMode{PIVOT_FILE}; LLVector3 mCustomPivot; - bool mUploadPivotAllowed{true}; + bool mUploadPivotAllowed{true}; // some part may take a pivot + std::set mPivotHeld; // parts held at the centre: rigged, or with physics not yet analyzed + F32 mPivotScale{1.f}; // the import scale the upload data was last built with // std::map mViewOption; From b83cdc5eb637abacad466d0f118648c70f9cc0d0 Mon Sep 17 00:00:00 2001 From: Martin Pitt Date: Tue, 3 Nov 2026 10:01:37 +0000 Subject: [PATCH 19/20] Enable the pivot by instance and show every reason it is held Two instances of one rigged model left the pivot controls enabled though both were held at the centre, and a rigged part hid the warning that a pivoted static part without physics collides with a centred hull. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llmodelpreview.cpp | 19 +++++++++++++------ .../default/xui/en/floater_model_preview.xml | 2 +- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 281f5794bdb..775d7854e4f 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -2295,19 +2295,26 @@ void LLModelPreview::updateStatusMessages() pivot_without_physics |= !rigged && !instance_physics && !getWantedPivot(model).isExactlyZero(); } + // Some instance must be free of the hold: several instances of one held model are all held. + mUploadPivotAllowed = std::any_of(mUploadData.begin(), mUploadData.end(), [this](const LLModelInstance& instance) + { return instance.mModel && !mPivotHeld.count(instance.mModel); }); + // Every reason that applies is shown, since each concerns different parts. std::string pivot_status; - mUploadPivotAllowed = mPivotHeld.size() < mUploadData.size(); + auto add_status = [&](const char* name) + { + pivot_status += (pivot_status.empty() ? "" : " ") + mFMP->getString(name); + }; if (any_rigged) { - pivot_status = mFMP->getString("pivot_status_rigged"); + add_status("pivot_status_rigged"); } - else if (any_unanalyzed) + if (any_unanalyzed) { - pivot_status = mFMP->getString("pivot_status_analyze"); + add_status("pivot_status_analyze"); } - else if (pivot_without_physics) + if (pivot_without_physics) { - pivot_status = mFMP->getString("pivot_status_no_physics"); + add_status("pivot_status_no_physics"); } const bool custom = mFMP->childGetValue("pivot_mode").asString() == "custom"; diff --git a/indra/newview/skins/default/xui/en/floater_model_preview.xml b/indra/newview/skins/default/xui/en/floater_model_preview.xml index 82172309f07..ac7527fdb22 100644 --- a/indra/newview/skins/default/xui/en/floater_model_preview.xml +++ b/indra/newview/skins/default/xui/en/floater_model_preview.xml @@ -1473,7 +1473,7 @@ Date: Wed, 4 Nov 2026 13:05:13 +0000 Subject: [PATCH 20/20] Keep rigged mesh on the default position domain The upload takes its skin from the High model, which can carry weights its base does not, so such a mesh escaped the rigged check and had its domain shifted. Rigged mesh is now judged from both models, and its upload offset is zero whatever the pivot mode says. Co-Authored-By: Claude Opus 5.5 --- indra/newview/llmodelpreview.cpp | 17 ++++++++++++++--- indra/newview/llmodelpreview.h | 2 ++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/indra/newview/llmodelpreview.cpp b/indra/newview/llmodelpreview.cpp index 775d7854e4f..242bd99e840 100644 --- a/indra/newview/llmodelpreview.cpp +++ b/indra/newview/llmodelpreview.cpp @@ -1894,6 +1894,17 @@ LLVector3 LLModelPreview::getWantedPivot(const LLModel* model) const } } +bool LLModelPreview::uploadsRigged(const LLModelInstance& instance) const +{ + // The upload takes its skin from the High model, which may carry weights its base does not. + if (!mFMP || !mFMP->childGetValue("upload_skin").asBoolean()) + { + return false; + } + const LLModel* high = instance.mLOD[LLModel::LOD_HIGH]; + return (instance.mModel && !instance.mModel->mSkinWeights.empty()) || (high && !high->mSkinWeights.empty()); +} + bool LLModelPreview::isSingleModel() const { // A model with more than eight materials uploads as a root and its submodels, sharing one transform. @@ -1906,7 +1917,8 @@ LLMeshUploadThread::position_offsets_t LLModelPreview::getUploadPositionOffsets( LLMeshUploadThread::position_offsets_t offsets; for (const LLModelInstance& instance : mUploadData) { - offsets[instance.mModel.get()] = getUploadPivot(instance.mModel) * -0.5f; + // Rigged mesh is placed by its skeleton and keeps the default position domain, whatever the pivot. + offsets[instance.mModel.get()] = uploadsRigged(instance) ? LLVector3::zero : getUploadPivot(instance.mModel) * -0.5f; } return offsets; } @@ -2259,7 +2271,6 @@ void LLModelPreview::updateStatusMessages() // any other physics shape to the box, so a pivot needs analyzed physics or none at all. { // Each part is judged on its own, so one rigged or unanalyzed part holds only itself at the centre. - const bool uploading_skin = mFMP->childGetValue("upload_skin").asBoolean(); bool any_rigged = false; bool any_unanalyzed = false; bool pivot_without_physics = false; // a pivoted part that collides with a default hull centred on itself @@ -2272,7 +2283,7 @@ void LLModelPreview::updateStatusMessages() continue; } // Skin weights left out of the upload make a static mesh, which can take a pivot. - const bool rigged = uploading_skin && !model->mSkinWeights.empty(); + const bool rigged = uploadsRigged(instance); // The hulls uploaded are the physics model's, or the base model's without one. const LLModel* physics = instance.mLOD[LLModel::LOD_PHYSICS]; bool instance_physics = false; diff --git a/indra/newview/llmodelpreview.h b/indra/newview/llmodelpreview.h index 094561c11dd..10a465845bb 100644 --- a/indra/newview/llmodelpreview.h +++ b/indra/newview/llmodelpreview.h @@ -187,6 +187,8 @@ class LLModelPreview : public LLViewerDynamicTexture, public LLMutex, public LLH LLMeshUploadThread::position_offsets_t getUploadPositionOffsets() const; // One model to upload, counting a root and its submodels once: what Custom and metres are for. bool isSingleModel() const; + // Whether this instance uploads skinned, from its base or its High model: such mesh takes no pivot. + bool uploadsRigged(const LLModelInstance& instance) const; // //Is a rig valid so that it can be used as a criteria for allowing for uploading of joint positions