diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 93758b54c..2348f152b 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -248,6 +248,11 @@ protected: polymesh_map_t mPolyMeshes; avatar_joint_list_t mMeshLOD; +// +public: + const virtual avatar_joint_list_t& getMeshLOD() const { return mMeshLOD; } +// + /** Meshes ** ** *******************************************************************************/ diff --git a/indra/llappearance/llavatarjointmesh.h b/indra/llappearance/llavatarjointmesh.h index 17b7b9842..90a0a5725 100644 --- a/indra/llappearance/llavatarjointmesh.h +++ b/indra/llappearance/llavatarjointmesh.h @@ -131,6 +131,10 @@ public: void setIsTransparent(BOOL is_transparent) { mIsTransparent = is_transparent; } + // +public: + LLFace* getFace() { return mFace; } + // private: // Allocate skin data BOOL allocateSkinData( U32 numSkinJoints ); diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 7e7f91153..a8b4d24dc 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -86,6 +86,7 @@ set(viewer_SOURCE_FILES ascentprefschat.cpp ascentprefssys.cpp ascentprefsvan.cpp + awavefront.cpp chatbar_as_cmdline.cpp floaterao.cpp floaterlocalassetbrowse.cpp @@ -597,6 +598,7 @@ set(viewer_HEADER_FILES ascentprefschat.h ascentprefssys.h ascentprefsvan.h + awavefront.h chatbar_as_cmdline.h floaterao.h floaterlocalassetbrowse.h diff --git a/indra/newview/app_settings/settings_ascent.xml b/indra/newview/app_settings/settings_ascent.xml index 526a4a9c8..e60c658c2 100644 --- a/indra/newview/app_settings/settings_ascent.xml +++ b/indra/newview/app_settings/settings_ascent.xml @@ -642,5 +642,16 @@ Value 0 + OBJExportSwapYZ + + Comment + OBJ Files saved swap Y and Z axes (useful for Cinema 4D users) + Persist + 1 + Type + Boolean + Value + 0 + diff --git a/indra/newview/awavefront.cpp b/indra/newview/awavefront.cpp new file mode 100644 index 000000000..d4cc49520 --- /dev/null +++ b/indra/newview/awavefront.cpp @@ -0,0 +1,391 @@ +/** + * @file awavefront.cpp + * @brief A system which allows saving in-world objects to Wavefront .OBJ files for offline texturizing/shading. + * @author Apelsin + * + * $LicenseInfo:firstyear=2011&license=LGPLV3$ + * Copyright (C) 2011-2013 Apelsin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA */ + +#include "llviewerprecompiledheaders.h" + +#include "awavefront.h" + +// library includes +#include "aifilepicker.h" + +// newview includes +#include "llavatarappearancedefines.h" +#include "llface.h" +#include "llvoavatar.h" + +typedef std::vector avatar_joint_list_t; + +// menu includes +#include "llevent.h" +#include "llmemberlistener.h" +#include "llview.h" +#include "llselectmgr.h" + +LLVOAvatar* find_avatar_from_object(LLViewerObject* object); + +typedef LLMemberListener view_listener_t; + +namespace +{ + const std::string OBJ(".obj"); + void save_wavefront_continued(WavefrontSaver* wfsaver, AIFilePicker* filepicker) + { + if (filepicker->hasFilename()) + { + const std::string selected_filename = filepicker->getFilename(); + if (LLFILE* fp = LLFile::fopen(selected_filename, "wb")) + { + wfsaver->saveFile(fp); + llinfos << "OBJ file saved to " << selected_filename << llendl; + fclose(fp); + } + else llerrs << "can't open: " << selected_filename << llendl; + } + else llwarns << "No file; bailing" << llendl; + + delete wfsaver; + } +} + +Wavefront::Wavefront(vert_t v, tri_t t) +: name("") +, vertices(v) +, triangles(t) +{ +} + +Wavefront::Wavefront(const LLVolumeFace* face, const LLXform* transform, const LLXform* transform_normals) +: name("") +{ + class v4adapt + { + private: + LLStrider mV4aStrider; + public: + v4adapt(LLVector4a* vp){ mV4aStrider = vp; } + inline LLVector3 operator[] (const unsigned int i) + { + return LLVector3((F32*)&mV4aStrider[i]); + } + }; + v4adapt verts(face->mPositions); + for (S32 i = 0; i < face->mNumVertices; ++i) + { + LLVector3 v = verts[i]; + vertices.push_back(std::pair(v, face->mTexCoords[i])); + } + + if (transform) Transform(vertices, transform); + + v4adapt norms(face->mNormals); + for (S32 i = 0; i < face->mNumVertices; ++i) + normals.push_back(norms[i]); + + if (transform_normals) Transform(normals, transform_normals); + + for (S32 i = 0; i < face->mNumIndices/3; ++i) + { + triangles.push_back(tri(face->mIndices[i*3+0], face->mIndices[i*3+1], face->mIndices[i*3+2])); + } +} + +Wavefront::Wavefront(LLFace* face, LLPolyMesh* mesh, const LLXform* transform, const LLXform* transform_normals) +: name("") +{ + LLVertexBuffer* vb = face->getVertexBuffer(); + if (!vb) return; + + LLStrider getVerts; + LLStrider getNorms; + LLStrider getCoord; + LLStrider getIndices; + face->getGeometry(getVerts, getNorms, getCoord, getIndices); + + const U16 start = face->getGeomStart(); + const U32 end = start + (mesh ? mesh->getNumVertices() : vb->getNumVerts()) - 1; //vertices + for (int i = start; i <= end; ++i) + vertices.push_back(std::make_pair(getVerts[i], getCoord[i])); + + if (transform) Transform(vertices, transform); + + for (int i = start; i <= end; ++i) + normals.push_back(getNorms[i]); + + if (transform_normals) Transform(normals, transform_normals); + + const U32 pcount = mesh ? mesh->getNumFaces() : (vb->getNumIndices()/3); //indices + const U16 offset = face->getIndicesStart(); //indices + for (int i = 0; i < pcount; ++i) + { + triangles.push_back(tri(getIndices[i * 3 + offset] + start, getIndices[i * 3 + 1 + offset] + start, getIndices[i * 3 + 2 + offset] + start)); + } +} + +void Wavefront::Transform(vert_t& v, const LLXform* x) //recursive +{ + LLMatrix4 m; + x->getLocalMat4(m); + for (vert_t::iterator iterv = v.begin(); iterv != v.end(); ++iterv) + { + iterv->first = iterv->first * m; + } + + if (const LLXform* xp = x->getParent()) Transform(v, xp); +} + +void Wavefront::Transform(vec3_t& v, const LLXform* x) //recursive +{ + LLMatrix4 m; + x->getLocalMat4(m); + for (vec3_t::iterator iterv = v.begin(); iterv != v.end(); ++iterv) + { + *iterv = *iterv * m; + } + + if (const LLXform* xp = x->getParent()) Transform(v, xp); +} + +WavefrontSaver::WavefrontSaver() +{} + +void WavefrontSaver::Add(const Wavefront& obj) +{ + obj_v.push_back(obj); +} + +void WavefrontSaver::Add(const LLVolume* vol, const LLXform* transform, const LLXform* transform_normals) +{ + const int faces = vol->getNumVolumeFaces(); + for(int i = 0; i < faces; ++i) //each face will be treated as a separate Wavefront object + { + Add(Wavefront(&vol->getVolumeFace(i), transform, transform_normals)); + } +} +void WavefrontSaver::Add(const LLViewerObject* some_vo) +{ + LLXform v_form; + v_form.setScale(some_vo->getScale()); + v_form.setPosition(some_vo->getRenderPosition()); + v_form.setRotation(some_vo->getRenderRotation()); + + LLXform normfix; + normfix.setRotation(v_form.getRotation()); //Should work... + Add(some_vo->getVolume(), &v_form, &normfix); +} + +namespace +{ + class LLSaveSelectedObjects : public view_listener_t + { + bool handleEvent(LLPointer event, const LLSD& userdata) + { + if (LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection()) + { + WavefrontSaver* wfsaver = new WavefrontSaver; //deleted in callback + wfsaver->offset = -selection->getFirstRootObject()->getRenderPosition(); + for (LLObjectSelection::iterator iter = selection->begin(); iter != selection->end(); ++iter) + { + LLSelectNode* node = *iter; + wfsaver->Add(node->getObject()); + } + + AIFilePicker* filepicker = AIFilePicker::create(); + filepicker->open(selection->getFirstNode()->mName.c_str()+OBJ); + filepicker->run(boost::bind(&save_wavefront_continued, wfsaver, filepicker)); + } + return true; + } + }; +} + +void WavefrontSaver::Add(const LLVOAvatar* av_vo) //adds attachments, too! +{ + offset = -av_vo->getRenderPosition(); + avatar_joint_list_t vjv = av_vo->getMeshLOD(); + for (avatar_joint_list_t::const_iterator itervj = vjv.begin(); itervj != vjv.end(); ++itervj) + { + const LLViewerJoint* vj = dynamic_cast(*itervj); + if (!vj || vj->mMeshParts.empty()) continue; + + LLViewerJointMesh* vjm = dynamic_cast(vj->mMeshParts[0]); //highest LOD + if (!vjm) continue; + + vjm->updateJointGeometry(); + LLFace* face = vjm->getFace(); + if (!face) continue; + + //Beware: this is a hack because LLFace has multiple LODs + //'pm' supplies the right number of vertices and triangles! + LLPolyMesh* pm = vjm->getMesh(); + if (!pm) continue; + LLXform normfix; + normfix.setRotation(pm->getRotation()); + + //Special case for balls...I mean eyeballs! + static const std::string eyeLname = LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::getInstance()->getMeshEntry(LLAvatarAppearanceDefines::MESH_ID_EYEBALL_LEFT)->mName; + static const std::string eyeRname = LLAvatarAppearanceDefines::LLAvatarAppearanceDictionary::getInstance()->getMeshEntry(LLAvatarAppearanceDefines::MESH_ID_EYEBALL_RIGHT)->mName; + const std::string name = vj->getName(); + if (name == eyeLname || name == eyeRname) + { + LLXform lol; + lol.setPosition(-offset); + Add(Wavefront(face, pm, &lol, &normfix)); + } + else + Add(Wavefront(face, pm, NULL, &normfix)); + } + + for (LLVOAvatar::attachment_map_t::const_iterator iter = av_vo->mAttachmentPoints.begin(); iter != av_vo->mAttachmentPoints.end(); ++iter) + { + LLViewerJointAttachment* ja = iter->second; + if (!ja) continue; + + for (LLViewerJointAttachment::attachedobjs_vec_t::iterator itero = ja->mAttachedObjects.begin(); itero != ja->mAttachedObjects.end(); ++itero) + { + LLViewerObject* o = *itero; + if (!o) continue; + + LLDynamicArray prims = LLDynamicArray(); + o->addThisAndAllChildren(prims); + for (LLDynamicArray::iterator iterc = prims.begin(); iterc != prims.end(); ++iterc) + { + const LLViewerObject* c = *iterc; + if (!c) continue; + const LLVolume* vol = c->getVolume(); + if (!vol) continue; + + LLXform v_form; + v_form.setScale(c->getScale()); + v_form.setPosition(c->getRenderPosition()); + v_form.setRotation(c->getRenderRotation()); + + LLXform normfix; + normfix.setRotation(v_form.getRotation()); + + if (c->isHUDAttachment()) + { + v_form.addPosition(-offset); + //Normals of HUD elements are funky + //TO-DO: fix 'em! + } + Add(vol, &v_form, &normfix); + } + } + } +} + +namespace +{ + class LLSaveSelectedAvatar : public view_listener_t + { + bool handleEvent(LLPointer event, const LLSD& userdata) + { + if (const LLVOAvatar* avatar = find_avatar_from_object(LLSelectMgr::getInstance()->getSelection()->getPrimaryObject())) + { + WavefrontSaver* wfsaver = new WavefrontSaver; //deleted in callback + wfsaver->Add(avatar); + + AIFilePicker* filepicker = AIFilePicker::create(); + filepicker->open(avatar->getFullname()+OBJ); + filepicker->run(boost::bind(save_wavefront_continued, wfsaver, filepicker)); + } + return true; + } + }; +} + +namespace +{ + void write_or_bust(LLFILE* fp, const std::string outstring) + { + const size_t size = outstring.length(); + if (fwrite(outstring.c_str(), 1, size, fp) != size) + llwarns << "Short write" << llendl; + } +} + +bool WavefrontSaver::saveFile(LLFILE* fp) +{ + if (!fp) return false; + + int num = 0; + int index = 0; + for (std::vector::iterator w_iter = obj_v.begin(); w_iter != obj_v.end(); ++w_iter) + { + int count = 0; + + std::string name = (*w_iter).name; + if (name.empty()) name = llformat("%d", num++); + + vert_t vertices = (*w_iter).vertices; + vec3_t normals = (*w_iter).normals; + tri_t triangles = (*w_iter).triangles; + + //Write Object + write_or_bust(fp, "o " + name + "\n"); + + //Write vertices; swap axes if necessary + static const LLCachedControl swapYZ("OBJExportSwapYZ", false); + const double xm = swapYZ ? -1.0 : 1.0; + const int y = swapYZ ? 2 : 1; + const int z = swapYZ ? 1 : 2; + for (vert_t::iterator v_iter = vertices.begin(); v_iter != vertices.end(); ++v_iter) + { + ++count; + const LLVector3 v = v_iter->first + offset; + write_or_bust(fp, llformat("v %f %f %f\n",v[0] * xm, v[y], v[z])); + } + + for (vec3_t::iterator n_iter = normals.begin(); n_iter != normals.end(); ++n_iter) + { + const LLVector3 n = *n_iter; + write_or_bust(fp, llformat("vn %f %f %f\n",n[0] * xm, n[y], n[z])); + } + + for (vert_t::iterator v_iter = vertices.begin(); v_iter != vertices.end(); ++v_iter) + { + write_or_bust(fp, llformat("vt %f %f\n", v_iter->second[0], v_iter->second[1])); + } + + //Write triangles + for (tri_t::iterator t_iter = triangles.begin(); t_iter != triangles.end(); ++t_iter) + { + const int f1 = t_iter->v0 + index + 1; + const int f2 = t_iter->v1 + index + 1; + const int f3 = t_iter->v2 + index + 1; + write_or_bust(fp, llformat("f %d/%d/%d %d/%d/%d %d/%d/%d\n", + f1,f1,f1,f2,f2,f2,f3,f3,f3)); + } + index += count; + } + + return true; +} + +void addMenu(view_listener_t* menu, const std::string& name); +void add_wave_listeners() //Called in llviewermenu with other addMenu calls, function linked against +{ + addMenu(new LLSaveSelectedObjects(), "Object.SaveAsOBJ"); + addMenu(new LLSaveSelectedAvatar(), "Avatar.SaveAsOBJ"); +} + diff --git a/indra/newview/awavefront.h b/indra/newview/awavefront.h new file mode 100644 index 000000000..f97099cd1 --- /dev/null +++ b/indra/newview/awavefront.h @@ -0,0 +1,80 @@ +/** + * @file awavefront.h + * @brief A system which allows saving in-world objects to Wavefront .OBJ files for offline texturizing/shading. + * @author Apelsin + * + * $LicenseInfo:firstyear=2011&license=LGPLV3$ + * Copyright (C) 2011-2013 Apelsin + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General + * Public License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301 USA */ + +#ifndef AWAVEFRONT +#define AWAVEFRONT + +#include +#include "v3math.h" +#include "v2math.h" +#include "llface.h" +#include "llvolume.h" + +using namespace std; + +class LLFace; +class LLPolyMesh; +class LLViewerObject; +class LLVOAvatar; + +typedef std::vector > vert_t; +typedef std::vector vec3_t; + +struct tri +{ + tri(int a, int b, int c) : v0(a), v1(b), v2(c) {} + int v0; + int v1; + int v2; +}; +typedef std::vector tri_t; + +class Wavefront +{ +public: + vert_t vertices; + vec3_t normals; //null unless otherwise specified! + tri_t triangles; //because almost all surfaces in SL are triangles + std::string name; + Wavefront(vert_t v, tri_t t); + Wavefront(const LLVolumeFace* face, const LLXform* transform = NULL, const LLXform* transform_normals = NULL); + Wavefront(LLFace* face, LLPolyMesh* mesh = NULL, const LLXform* transform = NULL, const LLXform* transform_normals = NULL); + static void Transform(vert_t& v, const LLXform* x); //helper function + static void Transform(vec3_t& v, const LLXform* x); //helper function +}; + +class WavefrontSaver +{ +public: + std::vector obj_v; + LLVector3 offset; + WavefrontSaver(); + void Add(const Wavefront& obj); + void Add(const LLVolume* vol, const LLXform* transform = NULL, const LLXform* transform_normals = NULL); + void Add(const LLViewerObject* some_vo); + void Add(const LLVOAvatar* av_vo); + bool saveFile(LLFILE* fp); +}; + +#endif // AWAVEFRONT + diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index 389b02629..69bcbb308 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -171,6 +171,7 @@ #include "shfloatermediaticker.h" #include "llpacketring.h" #include "aihttpview.h" +#include "awavefront.h" // #include "scriptcounter.h" @@ -488,6 +489,11 @@ void handle_mesh_save_obj(void*); void handle_mesh_load_obj(void*); void handle_morph_save_obj(void*); void handle_morph_load_obj(void*); +void save_avatar_to_obj(LLVOAvatar *avatar); +void save_selected_avatar_to_obj(); +void save_selected_objects_to_obj(); +void save_wavefront_continued(WavefrontSaver* wfsaver, AIFilePicker* filepicker); +void handle_save_current_avatar_obj(void*); void handle_debug_avatar_textures(void*); void handle_dump_region_object_cache(void*); @@ -700,7 +706,6 @@ void init_menus() gAttachSubMenu = gMenuBarView->getChildMenuByName("Attach Object", TRUE); gDetachSubMenu = gMenuBarView->getChildMenuByName("Detach Object", TRUE); - // TomY TODO convert these two LLMenuGL*menu; // @@ -6132,6 +6137,24 @@ class LLAvatarInviteToGroup : public view_listener_t } }; +class LLAvatarSaveAsOBJ : public view_listener_t +{ + bool handleEvent(LLPointer event, const LLSD& userdata) + { + save_selected_avatar_to_obj(); + return true; + } +}; + +class LLSelectionSaveAsOBJ : public view_listener_t +{ + bool handleEvent(LLPointer event, const LLSD& userdata) + { + save_selected_objects_to_obj(); + return true; + } +}; + class LLAvatarAddFriend : public view_listener_t { bool handleEvent(LLPointer event, const LLSD& userdata) @@ -8515,6 +8538,104 @@ static void handle_morph_load_obj_continued(void* data, AIFilePicker* filepicker morph_data->setMorphFromMesh(&mesh); } +void handle_save_current_avatar_obj(void* data) +{ + if(gAgentAvatarp) + save_avatar_to_obj(gAgentAvatarp); +} + +void save_selected_avatar_to_obj() +{ + LLVOAvatar* avatar = find_avatar_from_object( LLSelectMgr::getInstance()->getSelection()->getPrimaryObject() ); + if(avatar) + save_avatar_to_obj(avatar); +} + +void save_avatar_to_obj(LLVOAvatar *avatar) +{ + std::string file_name = llformat("%s.obj", avatar->getFullname().c_str()); + std::string full_path = gDirUtilp->getExpandedFilename(LL_PATH_NONE, file_name); + + WavefrontSaver* wfsaver = new WavefrontSaver(); + wfsaver->Add((LLVOAvatar*)avatar); + + AIFilePicker* filepicker = AIFilePicker::create(); + filepicker->open(full_path); + filepicker->run(boost::bind(&save_wavefront_continued, wfsaver, filepicker)); +} + +void save_selected_objects_to_obj() +{ + struct ff : public LLSelectedNodeFunctor + { + virtual bool apply(LLSelectNode* node) + { + return LLObjectBackup::getInstance()->validatePerms(node->mPermissions); + } + } func; + + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + + if(!selection) + return; + + if (!selection->applyToNodes(&func, false)) + { + llwarns << "Incorrect permissions: Wavefront OBJ export aborted" << llendl; + LLSD args; + args["REASON"] = "Insufficient Permissions"; + LLNotificationsUtil::add("WavefrontExportFailed", args); + return; + } + + std::string file_name = llformat("%s.obj", selection->getFirstNode()->mName.c_str()); + std::string full_path = gDirUtilp->getExpandedFilename(LL_PATH_NONE, file_name); + + WavefrontSaver* wfsaver = new WavefrontSaver(); + LLSelectNode* root_one = (LLSelectNode *)*selection->root_begin(); + wfsaver->offset = -root_one->getObject()->getRenderPosition(); + for (LLObjectSelection::iterator iter = selection->begin(); + iter != selection->end(); iter++) + { + LLSelectNode* node = *iter; + LLViewerObject* object = node->getObject(); + wfsaver->Add(object); + } + + AIFilePicker* filepicker = AIFilePicker::create(); + filepicker->open(full_path); + filepicker->run(boost::bind(&save_wavefront_continued, wfsaver, filepicker)); +} + +void save_wavefront_continued(WavefrontSaver* wfsaver, AIFilePicker* filepicker) +{ + if (!filepicker->hasFilename()) + { + llwarns << "No file; bailing" << llendl; + return; + } + std::string selected_filename = filepicker->getFilename(); + LLFILE* fp = LLFile::fopen(selected_filename, "wb"); + if (!fp) + { + llerrs << "can't open: " << selected_filename << llendl; + return; + } + try + { + wfsaver->saveFile(fp); + } + catch(int e) + { + llwarns << "An exception occurred while generating / saving OBJ file. Exception #" << e << llendl; + } + llinfos << "OBJ file saved to " << selected_filename << llendl; + LLSD args; + args["FILENAME"] = selected_filename; + LLNotificationsUtil::add("WavefrontExportSuccess", args); + fclose(fp); +} + // Returns a pointer to the avatar give the UUID of the avatar OR of an attachment the avatar is wearing. // Returns NULL on failure. LLVOAvatar* find_avatar_from_object( LLViewerObject* object ) @@ -9388,6 +9509,7 @@ void initialize_menus() addMenu(new LLAvatarEnableFreezeEject(), "Avatar.EnableFreezeEject"); addMenu(new LLAvatarCopyUUID(), "Avatar.CopyUUID"); addMenu(new LLAvatarClientUUID(), "Avatar.ClientID"); + addMenu(new LLAvatarSaveAsOBJ(), "Avatar.SaveAsOBJ"); // Object pie menu addMenu(new LLObjectOpen(), "Object.Open"); @@ -9419,7 +9541,8 @@ void initialize_menus() addMenu(new LLObjectExport(), "Object.Export"); addMenu(new LLObjectImport(), "Object.Import"); addMenu(new LLObjectImportUpload(), "Object.ImportUpload"); - + addMenu(new LLSelectionSaveAsOBJ(), "Object.SaveAsOBJ"); + addMenu(new LLObjectEnableOpen(), "Object.EnableOpen"); addMenu(new LLObjectEnableTouch(), "Object.EnableTouch"); diff --git a/indra/newview/llviewerobjectbackup.h b/indra/newview/llviewerobjectbackup.h index 8fa577dfe..27ae4f2f9 100644 --- a/indra/newview/llviewerobjectbackup.h +++ b/indra/newview/llviewerobjectbackup.h @@ -93,6 +93,9 @@ public: // Export result flags for textures. U32 mNonExportedTextures; + // Is exporting these objects allowed + bool validatePerms(const LLPermissions* item_permissions); + private: // Static singleton stuff LLObjectBackup(); @@ -103,7 +106,6 @@ private: void updateExportNumbers(); // Permissions stuff. - bool validatePerms(const LLPermissions* item_permissions); LLUUID validateTextureID(LLUUID asset_id); // Convert a selection list of objects to LLSD diff --git a/indra/newview/skins/default/xui/en-us/menu_pie_object.xml b/indra/newview/skins/default/xui/en-us/menu_pie_object.xml index c6c714147..a3015ed48 100644 --- a/indra/newview/skins/default/xui/en-us/menu_pie_object.xml +++ b/indra/newview/skins/default/xui/en-us/menu_pie_object.xml @@ -60,13 +60,20 @@ - + + + + + diff --git a/indra/newview/skins/default/xui/en-us/notifications.xml b/indra/newview/skins/default/xui/en-us/notifications.xml index 0c809326c..0849f9b4d 100644 --- a/indra/newview/skins/default/xui/en-us/notifications.xml +++ b/indra/newview/skins/default/xui/en-us/notifications.xml @@ -9637,4 +9637,22 @@ Would you like to enable announcing keys to objects in the sim? canceltext="No, don't ask again!"/> + +Export to Wavefront OBJ file failed: + +[REASON] + + + +Object successfully exported in Wavefront OBJ format to: + +[FILENAME] + +