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
+
+
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]
+
+