diff --git a/indra/llaudio/llaudioengine.cpp b/indra/llaudio/llaudioengine.cpp index f07965877..aab511ef8 100644 --- a/indra/llaudio/llaudioengine.cpp +++ b/indra/llaudio/llaudioengine.cpp @@ -399,7 +399,7 @@ void LLAudioEngine::idle(F32 max_decode_time) } LLAudioChannel *channelp = sourcep->getChannel(); - bool is_stopped = channelp && channelp->isPlaying(); + bool is_stopped = !channelp || !channelp->isPlaying(); if (is_stopped || (sourcep->isLoop() && channelp->mLoopedThisFrame)) { // This sound isn't playing, so we just process move the queue diff --git a/indra/llwindow/glh/glh_linear.h b/indra/llwindow/glh/glh_linear.h index 04ae1bd06..c46b81531 100644 --- a/indra/llwindow/glh/glh_linear.h +++ b/indra/llwindow/glh/glh_linear.h @@ -71,10 +71,10 @@ glh_linear.h #define GLH_EPSILON GLH_REAL(10e-6) #define GLH_PI GLH_REAL(3.1415926535897932384626433832795) -#define equivalent(a,b) (((a < b + GLH_EPSILON) && (a > b - GLH_EPSILON)) ? true : false) namespace glh { + inline bool equivalent(GLH_REAL a, GLH_REAL b) { return b - GLH_EPSILON < a && a < b + GLH_EPSILON; } inline GLH_REAL to_degrees(GLH_REAL radians) { return radians*GLH_RAD_TO_DEG; } inline GLH_REAL to_radians(GLH_REAL degrees) { return degrees*GLH_DEG_TO_RAD; } diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index 22c90b7bf..7e7bb4796 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -17513,6 +17513,77 @@ This should be as low as possible, but too low may break functionality Value 1280.0 + ColladaExportFloaterRect + + Comment + Collada floater position + Persist + 1 + Type + Rect + Value + + 0 + 0 + 0 + 0 + + + DAEExportConsolidateMaterials + + Comment + Combine faces with same texture + Persist + 1 + Type + Boolean + Value + 1 + + DAEExportSkipTransparent + + Comment + Skip exporting faces with default transparent texture or full transparent + Persist + 1 + Type + Boolean + Value + 1 + + DAEExportTextures + + Comment + Export textures when exporting Collada + Persist + 1 + Type + Boolean + Value + 1 + + DAEExportTextureParams + + Comment + Apply texture params suchs as repeats to the exported UV map + Persist + 1 + Type + Boolean + Value + 1 + + DAEExportTexturesType + + Comment + Image file format to use when exporting Collada + Persist + 1 + Type + S32 + Value + 0 + diff --git a/indra/newview/daeexport.cpp b/indra/newview/daeexport.cpp index d56d446af..1e71bf61b 100644 --- a/indra/newview/daeexport.cpp +++ b/indra/newview/daeexport.cpp @@ -1,26 +1,26 @@ /** - * @file daeexport.cpp - * @brief A system which allows saving in-world objects to Collada .DAE files for offline texturizing/shading. - * @authors Latif Khalifa - * - * $LicenseInfo:firstyear=2013&license=LGPLV2.1$ - * Copyright (C) 2013 Latif Khalifa - * - * 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 2.1 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 - */ +* @file daeexport.cpp +* @brief A system which allows saving in-world objects to Collada .DAE files for offline texturizing/shading. +* @authors Latif Khalifa +* +* $LicenseInfo:firstyear=2013&license=LGPLV2.1$ +* Copyright (C) 2013 Latif Khalifa +* +* 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 2.1 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 "daeexport.h" @@ -62,7 +62,6 @@ // library includes #include "aifilepicker.h" #include "llnotificationsutil.h" -#include "boost/date_time/posix_time/posix_time.hpp" // newview includes #include "lfsimfeaturehandler.h" @@ -75,10 +74,26 @@ // menu includes #include "llevent.h" #include "llmemberlistener.h" -#include "llview.h" #include "llselectmgr.h" -#define ANY_FACE -1 +// Floater and UI +#include "llfloater.h" +#include "lluictrlfactory.h" +#include "llscrollcontainer.h" +#include "lltexturectrl.h" + +// Files and cache +#include "llcallbacklist.h" +#include "lldir.h" +#include "lltexturecache.h" +#include "llimage.h" +#include "llimagej2c.h" +#include "llimagepng.h" +#include "llimagetga.h" +#include "llimagebmp.h" +#include "llimagejpeg.h" + +#define TEXTURE_DOWNLOAD_TIMEOUT 60.0f extern LLUUID gAgentID; @@ -87,41 +102,59 @@ typedef LLMemberListener view_listener_t; namespace DAEExportUtil { - void saveImpl(DAESaver* daesaver, AIFilePicker* filepicker) + static LLUUID LL_TEXTURE_PLYWOOD = LLUUID("89556747-24cb-43ed-920b-47caed15465f"); + static LLUUID LL_TEXTURE_BLANK = LLUUID("5748decc-f629-461c-9a36-a35a221fe21f"); + static LLUUID LL_TEXTURE_INVISIBLE = LLUUID("38b86f85-2575-52a9-a531-23108d8da837"); + static LLUUID LL_TEXTURE_TRANSPARENT = LLUUID("8dcd4a48-2d37-4909-9f78-f7a9eb4ef903"); + static LLUUID LL_TEXTURE_MEDIA = LLUUID("8b5fec65-8d8d-9dc5-cda8-8fdf2716e361"); + + enum image_format_type { - if (filepicker->hasFilename()) + ft_tga, + ft_png, + ft_j2c, + ft_bmp, + ft_jpg + }; + + static const std::string image_format_ext[] = { "tga", "png", "j2c", "bmp", "jpg" }; + + static bool canExportTexture(const LLUUID& id, std::string* name = NULL) + { + // Find inventory items with asset id of the sculpt map + LLViewerInventoryCategory::cat_array_t cats; + LLViewerInventoryItem::item_array_t items; + LLAssetIDMatches asset_id_matches(id); + gInventory.collectDescendentsIf(LLUUID::null, + cats, + items, + LLInventoryModel::INCLUDE_TRASH, + asset_id_matches); + + // See if any of the inventory items matching this texture id are exportable + ExportPolicy policy = LFSimFeatureHandler::instance().exportPolicy(); + for (S32 i = 0; i < items.count(); i++) { - const std::string selected_filename = filepicker->getFilename(); - - daesaver->saveDAE(selected_filename); - LLNotificationsUtil::add("WavefrontExportSuccess", LLSD().with("FILENAME", selected_filename)); + const LLPermissions item_permissions = items[i]->getPermissions(); + if (item_permissions.allowExportBy(gAgentID, policy)) + { + if (name != NULL) + { + (*name) = items[i]->getName(); + } + return true; + } } - else llwarns << "No file; bailing" << llendl; - delete daesaver; + if (name != NULL) + { + (*name) = id.getString(); + } + + return (policy & ep_full_perm) == ep_full_perm; } - void filePicker(DAESaver* daesaver, std::string name) - { - AIFilePicker* filepicker = AIFilePicker::create(); - filepicker->open(name); - filepicker->run(boost::bind(&saveImpl, daesaver, filepicker)); - } - - void onPartialExportConfirm(const LLSD& notification, const LLSD& response, DAESaver* daesaver, std::string name) - { - if (LLNotificationsUtil::getSelectedOption(notification, response) == 0) // 0 - Proceed, first choice - { - filePicker(daesaver, name); - } - else - { - delete daesaver; - } - } - - // Identical to the one in awavefront.cpp - bool can_export_node(LLSelectNode* node) + static bool canExportNode(LLSelectNode* node) { LLPermissions* perms = node->mPermissions; // Is perms ever NULL? // This tests the PERM_EXPORT bit too, which is not really necessary (just checking if it's set @@ -137,92 +170,428 @@ namespace DAEExportUtil { LLSculptParams *sculpt_params = (LLSculptParams *)obj->getParameterEntry(LLNetworkData::PARAMS_SCULPT); LLUUID sculpt_id = sculpt_params->getSculptTexture(); - - // Find inventory items with asset id of the sculpt map - LLViewerInventoryCategory::cat_array_t cats; - LLViewerInventoryItem::item_array_t items; - LLAssetIDMatches asset_id_matches(sculpt_id); - gInventory.collectDescendentsIf(LLUUID::null, - cats, - items, - LLInventoryModel::INCLUDE_TRASH, - asset_id_matches); - - // See if any of the inventory items matching this sculpt id are exportable - for (S32 i = 0; i < items.count(); i++) - { - const LLPermissions item_permissions = items[i]->getPermissions(); - if (item_permissions.allowExportBy(gAgentID, LFSimFeatureHandler::instance().exportPolicy())) - { - return true; - } - } - - return false; + return canExportTexture(sculpt_id); } else // not sculpt, we already checked generic permissions { return true; } } +} - void saveSelectedObject() + +class ColladaExportFloater + : public LLFloater +{ +private: + typedef std::map texture_list_t; + LLView* mExportBtn; + LLView* mFileName; + LLView* mTextureTypeCombo; + + DAESaver mSaver; + texture_list_t mTexturesToSave; + S32 mTotal; + S32 mNumTextures; + S32 mNumExportableTextures; + std::string mObjectName; + LLTimer mTimer; + LLUIString mTitleProgress; + +public: + ColladaExportFloater() + : LLFloater(std::string("Collada Export"), std::string("ColladaExportFloaterRect"), LLStringUtil::null) + { + mCommitCallbackRegistrar.add("ColladaExport.FilePicker", boost::bind(&ColladaExportFloater::onClickBrowse, this)); + mCommitCallbackRegistrar.add("ColladaExport.Export", boost::bind(&ColladaExportFloater::onClickExport, this)); + mCommitCallbackRegistrar.add("ColladaExport.TextureTypeCombo", boost::bind(&ColladaExportFloater::onTextureTypeCombo, this, boost::bind(&LLUICtrl::getControlName, _1), _2)); + mCommitCallbackRegistrar.add("ColladaExport.TextureExport", boost::bind(&ColladaExportFloater::onTextureExportCheck, this, _2)); + LLUICtrlFactory::getInstance()->buildFloater(this, "floater_dae_export.xml"); + + addSelectedObjects(); + if (LLUICtrl* ctrl = findChild("Object Name")) + { + ctrl->setTextArg("[NAME]", mObjectName); + } + if (LLUICtrl* ctrl = findChild("Exportable Prims")) + { + ctrl->setTextArg("[COUNT]", llformat("%d", mSaver.mObjects.size())); + ctrl->setTextArg("[TOTAL]", llformat("%d", mTotal)); + } + if (LLUICtrl* ctrl = findChild("Exportable Textures")) + { + ctrl->setTextArg("[COUNT]", llformat("%d", mNumExportableTextures)); + ctrl->setTextArg("[TOTAL]", llformat("%d", mNumTextures)); + } + addTexturePreview(); + } + + virtual ~ColladaExportFloater() + { + if (gIdleCallbacks.containsFunction(saveTexturesWorker, this)) + { + gIdleCallbacks.deleteFunction(saveTexturesWorker, this); + } + } + + BOOL postBuild() + { + mFileName = getChildView("file name editor"); + mExportBtn = getChildView("export button"); + mTextureTypeCombo = getChildView("texture type combo"); + mTitleProgress = getString("texture_progress"); + + mTextureTypeCombo->setValue(gSavedSettings.getS32(mTextureTypeCombo->getControlName())); + onTextureExportCheck(getChildView("texture export check")->getValue()); + return TRUE; + } + + void updateTitleProgress() + { + mTitleProgress.setArg("COUNT", llformat("%d", mTexturesToSave.size())); + setTitle(mTitleProgress); + } + + void onTextureExportCheck(const LLSD& value) + { + mTextureTypeCombo->setEnabled(value); + } + + void onTextureTypeCombo(const std::string& control_name, const LLSD& value) + { + gSavedSettings.setS32(control_name, value); + } + + void onClickBrowse() { static const std::string file_ext = ".dae"; + AIFilePicker* filepicker = AIFilePicker::create(); + filepicker->open(mObjectName + file_ext); + filepicker->run(boost::bind(&ColladaExportFloater::onFilePicker, this, filepicker)); + } + void onFilePicker(AIFilePicker* filepicker) + { + if (filepicker->hasFilename()) + { + mFileName->setValue(filepicker->getFilename()); + mExportBtn->setEnabled(TRUE); + } + } + + void onClickExport() + { + if (gSavedSettings.getBOOL("DAEExportTextures")) + { + saveTextures(); + } + else + { + onTexturesSaved(); + } + } + + void onTexturesSaved() + { + std::string selected_filename = mFileName->getValue(); + mSaver.saveDAE(selected_filename); + LLNotificationsUtil::add("WavefrontExportSuccess", LLSD().with("FILENAME", selected_filename)); + close(); + } + + void addSelectedObjects() + { if (LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection()) { - DAESaver* daesaver = new DAESaver; // deleted in callback - daesaver->mOffset = -selection->getFirstRootObject()->getRenderPosition(); - S32 total = 0; - S32 included = 0; + mSaver.mOffset = -selection->getFirstRootObject()->getRenderPosition(); + mObjectName = selection->getFirstRootNode()->mName; + mTotal = 0; for (LLObjectSelection::iterator iter = selection->begin(); iter != selection->end(); ++iter) { - total++; + mTotal++; LLSelectNode* node = *iter; - if (!node->getObject()->getVolume() || !can_export_node(node)) continue; - included++; - daesaver->Add(node->getObject(), node->mName); + if (!node->getObject()->getVolume() || !DAEExportUtil::canExportNode(node)) continue; + mSaver.add(node->getObject(), node->mName); } - if (daesaver->mObjects.empty()) + if (mSaver.mObjects.empty()) { LLNotificationsUtil::add("ExportFailed"); - delete daesaver; + close(); return; } - if (total != included) - { - LLSD args; - args["TOTAL"] = total; - args["FAILED"] = total - included; - LLNotificationsUtil::add("WavefrontExportPartial", args, LLSD(), boost::bind(&onPartialExportConfirm, _1, _2, daesaver, selection->getFirstNode()->mName.c_str() + file_ext)); - return; - } - - filePicker(daesaver, selection->getFirstNode()->mName.c_str() + file_ext); + mSaver.updateTextureInfo(); + mNumTextures = mSaver.mTextures.size(); + mNumExportableTextures = getNumExportableTextures(); } - return; } - class DAESaveSelectedObjects : public view_listener_t + S32 getNumExportableTextures() { - bool handleEvent(LLPointer event, const LLSD& userdata) + S32 res = 0; + + for (DAESaver::string_list_t::const_iterator t = mSaver.mTextureNames.begin(); t != mSaver.mTextureNames.end(); ++t) { - saveSelectedObject(); - return true; + std::string name = *t; + if (!name.empty()) + { + ++res; + } + } + + return res; + } + + void addTexturePreview() + { + S32 num_text = mNumExportableTextures; + if (num_text == 0) return; + + S32 img_width = 100; + S32 img_height = img_width + 15; + S32 panel_height = (num_text / 3 + 1) * (img_height) + 10; + LLScrollContainer* scroll_container = getChild("textures container"); + LLPanel* panel = new LLPanel(std::string(), LLRect(0, panel_height, 350, 0), false); + scroll_container->setScrolledView(panel); + scroll_container->addChild(panel); + panel->setEnabled(FALSE); + S32 img_nr = 0; + for (S32 i=0; i < mSaver.mTextures.size(); i++) + { + if (mSaver.mTextureNames[i].empty()) continue; + + S32 left = 16 + (img_nr % 3) * (img_width + 13); + S32 bottom = panel_height - (10 + (img_nr / 3 + 1) * (img_height)); + + LLRect rect(left, bottom + img_height, left + img_width, bottom); + LLTextureCtrl* img = new LLTextureCtrl(std::string(), rect, std::string(), mSaver.mTextures[i], LLUUID(), std::string()); + panel->addChild(img); + img_nr++; + } + } + + void saveTextures() + { + mTexturesToSave.clear(); + for (S32 i=0; i < mSaver.mTextures.size(); i++) + { + if (mSaver.mTextureNames[i].empty()) continue; + mTexturesToSave[mSaver.mTextures[i]] = mSaver.mTextureNames[i]; + } + + mSaver.mImageFormat = DAEExportUtil::image_format_ext[(S32)mTextureTypeCombo->getValue()]; + + LL_INFOS("ColladaExport") << "Starting to save textures" << LL_ENDL; + mTimer.setTimerExpirySec(TEXTURE_DOWNLOAD_TIMEOUT); + mTimer.start(); + updateTitleProgress(); + gIdleCallbacks.addFunction(saveTexturesWorker, this); + } + + class CacheReadResponder : public LLTextureCache::ReadResponder + { + private: + LLPointer mFormattedImage; + LLUUID mID; + std::string mName; + S32 mImageType; + + public: + CacheReadResponder(const LLUUID& id, LLImageFormatted* image, std::string name, S32 img_type) + : mFormattedImage(image), mID(id), mName(name), mImageType(img_type) + { + setImage(image); + } + + void setData(U8* data, S32 datasize, S32 imagesize, S32 imageformat, BOOL imagelocal) + { + if (imageformat == IMG_CODEC_TGA && mFormattedImage->getCodec() == IMG_CODEC_J2C) + { + LL_WARNS("ColladaExport") << "FAILED: texture " << mID << " is formatted as TGA. Not saving." << LL_ENDL; + mFormattedImage = NULL; + mImageSize = 0; + return; + } + + if (mFormattedImage.notNull()) + { + if (mFormattedImage->getCodec() == imageformat) + { + mFormattedImage->appendData(data, datasize); + } + else + { + LL_WARNS("ColladaExport") << "FAILED: texture " << mID << " in wrong format." << LL_ENDL; + mFormattedImage = NULL; + mImageSize = 0; + return; + } + } + else + { + mFormattedImage = LLImageFormatted::createFromType(imageformat); + mFormattedImage->setData(data, datasize); + } + mImageSize = imagesize; + mImageLocal = imagelocal; + } + + virtual void completed(bool success) + { + if (success && mFormattedImage.notNull() && mImageSize > 0) + { + bool ok = false; + + // If we are saving jpeg2000, no need to do anything, just write to disk + if (mImageType == DAEExportUtil::ft_j2c) + { + mName += "." + mFormattedImage->getExtension(); + ok = mFormattedImage->save(mName); + } + else + { + // For other formats we need to decode first + if (mFormattedImage->updateData() && (mFormattedImage->getWidth() * mFormattedImage->getHeight() * mFormattedImage->getComponents())) + { + LLPointer raw = new LLImageRaw; + raw->resize(mFormattedImage->getWidth(), mFormattedImage->getHeight(), mFormattedImage->getComponents()); + + if (mFormattedImage->decode(raw, 0)) + { + LLPointer img = NULL; + switch (mImageType) + { + case DAEExportUtil::ft_tga: + img = new LLImageTGA; + break; + case DAEExportUtil::ft_png: + img = new LLImagePNG; + break; + case DAEExportUtil::ft_bmp: + img = new LLImageBMP; + break; + case DAEExportUtil::ft_jpg: + img = new LLImageJPEG; + break; + } + + if (!img.isNull()) + { + if (img->encode(raw, 0)) + { + mName += "." + img->getExtension(); + ok = img->save(mName); + } + } + } + } + } + + if (ok) + { + LL_INFOS("ColladaExport") << "Saved texture to " << mName << LL_ENDL; + } + else + { + LL_WARNS("ColladaExport") << "FAILED to save texture " << mID << LL_ENDL; + } + } + else + { + LL_WARNS("ColladaExport") << "FAILED to save texture " << mID << LL_ENDL; + } } }; -} + static void saveTexturesWorker(void* data) + { + ColladaExportFloater* me = (ColladaExportFloater *)data; + if (me->mTexturesToSave.size() == 0) + { + LL_INFOS("ColladaExport") << "Done saving textures" << LL_ENDL; + me->updateTitleProgress(); + gIdleCallbacks.deleteFunction(saveTexturesWorker, me); + me->mTimer.stop(); + me->onTexturesSaved(); + return; + } -void DAESaver::Add(const LLViewerObject* prim, const std::string name) + LLUUID id = me->mTexturesToSave.begin()->first; + LLViewerTexture* imagep = LLViewerTextureManager::findTexture(id); + if (!imagep) + { + me->mTexturesToSave.erase(id); + me->updateTitleProgress(); + me->mTimer.reset(); + } + else + { + if (imagep->getDiscardLevel() == 0) // image download is complete + { + LL_INFOS("ColladaExport") << "Saving texture " << id << LL_ENDL; + LLImageFormatted* img = new LLImageJ2C; + S32 img_type = me->mTextureTypeCombo->getValue(); + std::string name = gDirUtilp->getDirName(me->mFileName->getValue()); + name += gDirUtilp->getDirDelimiter() + me->mTexturesToSave[id]; + CacheReadResponder* responder = new CacheReadResponder(id, img, name, img_type); + LLAppViewer::getTextureCache()->readFromCache(id, LLWorkerThread::PRIORITY_HIGH, 0, 999999, responder); + me->mTexturesToSave.erase(id); + me->updateTitleProgress(); + me->mTimer.reset(); + } + else if (me->mTimer.hasExpired()) + { + LL_WARNS("ColladaExport") << "Timed out downloading texture " << id << LL_ENDL; + me->mTexturesToSave.erase(id); + me->updateTitleProgress(); + me->mTimer.reset(); + } + } + } + +}; + +void DAESaver::add(const LLViewerObject* prim, const std::string name) { mObjects.push_back(std::pair((LLViewerObject*)prim, name)); } +void DAESaver::updateTextureInfo() +{ + mTextures.clear(); + mTextureNames.clear(); + + for (obj_info_t::iterator obj_iter = mObjects.begin(); obj_iter != mObjects.end(); ++obj_iter) + { + LLViewerObject* obj = obj_iter->first; + S32 num_faces = obj->getVolume()->getNumVolumeFaces(); + for (S32 face_num = 0; face_num < num_faces; ++face_num) + { + LLTextureEntry* te = obj->getTE(face_num); + const LLUUID id = te->getID(); + if (std::find(mTextures.begin(), mTextures.end(), id) != mTextures.end()) + { + continue; + } + mTextures.push_back(id); + std::string name; + if (id != DAEExportUtil::LL_TEXTURE_BLANK && DAEExportUtil::canExportTexture(id, &name)) + { + std::string safe_name = gDirUtilp->getScrubbedFileName(name); + std::replace(safe_name.begin(), safe_name.end(), ' ', '_'); + mTextureNames.push_back(safe_name); + } + else + { + mTextureNames.push_back(std::string()); + } + } + } +} + + class v4adapt { private: @@ -262,7 +631,7 @@ void DAESaver::addSource(daeElement* mesh, const char* src_id, std::string param } } -void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int face_to_include) +void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int_list_t* faces_to_include) { domPolylist* polylist = daeSafeCast(mesh->add("polylist")); polylist->setMaterial(materialID); @@ -298,9 +667,11 @@ void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* mat S32 num_tris = 0; for (S32 face_num = 0; face_num < obj->getVolume()->getNumVolumeFaces(); face_num++) { + if (skipFace(obj->getTE(face_num))) continue; + const LLVolumeFace* face = (LLVolumeFace*)&obj->getVolume()->getVolumeFace(face_num); - if (face_to_include == ANY_FACE || face_to_include == face_num) + if (faces_to_include == NULL || (std::find(faces_to_include->begin(), faces_to_include->end(), face_num) != faces_to_include->end())) { for (S32 i = 0; i < face->mNumIndices; i++) { @@ -318,8 +689,54 @@ void DAESaver::addPolygons(daeElement* mesh, const char* geomID, const char* mat polylist->setCount(num_tris); } +void DAESaver::transformTexCoord(S32 num_vert, LLVector2* coord, LLVector3* positions, LLVector3* normals, LLTextureEntry* te, LLVector3 scale) +{ + F32 cosineAngle = cos(te->getRotation()); + F32 sinAngle = sin(te->getRotation()); + + for (S32 ii=0; iigetTexGen()) + { + LLVector3 normal = normals[ii]; + LLVector3 pos = positions[ii]; + LLVector3 binormal; + F32 d = normal * LLVector3::x_axis; + if (d >= 0.5f || d <= -0.5f) + { + binormal = LLVector3::y_axis; + if (normal.mV[0] < 0) binormal *= -1.0f; + } + else + { + binormal = LLVector3::x_axis; + if (normal.mV[1] > 0) binormal *= -1.0f; + } + LLVector3 tangent = binormal % normal; + LLVector3 scaledPos = pos.scaledVec(scale); + coord[ii].mV[0] = 1.f + ((binormal * scaledPos) * 2.f - 0.5f); + coord[ii].mV[1] = -((tangent * scaledPos) * 2.f - 0.5f); + } + + F32 repeatU; + F32 repeatV; + te->getScale(&repeatU, &repeatV); + F32 tX = coord[ii].mV[0] - 0.5f; + F32 tY = coord[ii].mV[1] - 0.5f; + + F32 offsetU; + F32 offsetV; + te->getOffset(&offsetU, &offsetV); + + coord[ii].mV[0] = (tX * cosineAngle + tY * sinAngle) * repeatU + offsetU + 0.5f; + coord[ii].mV[1] = (-tX * sinAngle + tY * cosineAngle) * repeatV + offsetV + 0.5f; + } +} + bool DAESaver::saveDAE(std::string filename) { + mAllMaterials.clear(); + mTotalNumMaterials = 0; DAE dae; // First set the filename to save daeElement* root = dae.add(filename); @@ -327,7 +744,10 @@ bool DAESaver::saveDAE(std::string filename) // Obligatory elements in header daeElement* asset = root->add("asset"); // Get ISO format time - std::string date = boost::posix_time::to_iso_extended_string(boost::posix_time::second_clock::local_time()); + time_t rawtime; + time(&rawtime); + struct tm* utc_time = gmtime(&rawtime); + std::string date = llformat("%04d-%02d-%02dT%02d:%02d:%02d", utc_time->tm_year + 1900, utc_time->tm_mon + 1, utc_time->tm_mday, utc_time->tm_hour, utc_time->tm_min, utc_time->tm_sec); daeElement* created = asset->add("created"); created->setCharData(date); daeElement* modified = asset->add("modified"); @@ -340,9 +760,10 @@ bool DAESaver::saveDAE(std::string filename) // File creator daeElement* contributor = asset->add("contributor"); - contributor->add("author")->setCharData("Singularity User"); - contributor->add("authoring_tool")->setCharData("Singularity Viewer Collada Export"); + contributor->add("author")->setCharData(LLAppViewer::instance()->getSecondLifeTitle() + " User"); + contributor->add("authoring_tool")->setCharData(LLAppViewer::instance()->getSecondLifeTitle() + " Collada Export"); + daeElement* images = root->add("library_images"); daeElement* geomLib = root->add("library_geometries"); daeElement* effects = root->add("library_effects"); daeElement* materials = root->add("library_materials"); @@ -350,6 +771,11 @@ bool DAESaver::saveDAE(std::string filename) scene->setAttribute("id", "Scene"); scene->setAttribute("name", "Scene"); + if (gSavedSettings.getBOOL("DAEExportTextures")) + { + generateImagesSection(images); + } + S32 prim_nr = 0; for (obj_info_t::iterator obj_iter = mObjects.begin(); obj_iter != mObjects.end(); ++obj_iter) @@ -369,16 +795,37 @@ bool DAESaver::saveDAE(std::string filename) std::vector position_data; std::vector normal_data; std::vector uv_data; + bool applyTexCoord = gSavedSettings.getBOOL("DAEExportTextureParams"); S32 num_faces = obj->getVolume()->getNumVolumeFaces(); - for (S32 face_num = 0; face_num < num_faces; face_num++) { + if (skipFace(obj->getTE(face_num))) continue; + const LLVolumeFace* face = (LLVolumeFace*)&obj->getVolume()->getVolumeFace(face_num); total_num_vertices += face->mNumVertices; v4adapt verts(face->mPositions); v4adapt norms(face->mNormals); + + LLVector2* newCoord = NULL; + + if (applyTexCoord) + { + newCoord = new LLVector2[face->mNumVertices]; + LLVector3* newPos = new LLVector3[face->mNumVertices]; + LLVector3* newNormal = new LLVector3[face->mNumVertices]; + for (S32 i = 0; i < face->mNumVertices; i++) + { + newPos[i] = verts[i]; + newNormal[i] = norms[i]; + newCoord[i] = face->mTexCoords[i]; + } + transformTexCoord(face->mNumVertices, newCoord, newPos, newNormal, obj->getTE(face_num), obj->getScale()); + delete[] newPos; + delete[] newNormal; + } + for (S32 i=0; i < face->mNumVertices; i++) { const LLVector3 v = verts[i]; @@ -391,13 +838,19 @@ bool DAESaver::saveDAE(std::string filename) normal_data.push_back(n.mV[VY]); normal_data.push_back(n.mV[VZ]); - const LLVector2 uv = face->mTexCoords[i]; + const LLVector2 uv = applyTexCoord ? newCoord[i] : face->mTexCoords[i]; + uv_data.push_back(uv.mV[VX]); uv_data.push_back(uv.mV[VY]); } + + if (applyTexCoord) + { + delete[] newCoord; + } } - - + + addSource(mesh, llformat("%s-%s", geomID, "positions").c_str(), "XYZ", position_data); addSource(mesh, llformat("%s-%s", geomID, "normals").c_str(), "XYZ", normal_data); addSource(mesh, llformat("%s-%s", geomID, "map0").c_str(), "ST", uv_data); @@ -411,33 +864,31 @@ bool DAESaver::saveDAE(std::string filename) verticesInput->setAttribute("source", llformat("#%s-%s", geomID, "positions").c_str()); } + material_list_t objMaterials; + getMaterials(obj, &objMaterials); + // Add triangles - for (S32 face_num = 0; face_num < num_faces; face_num++) + if (gSavedSettings.getBOOL("DAEExportConsolidateMaterials")) { - addPolygons(mesh, geomID, llformat("%s-f%d-%s", geomID, face_num, "material").c_str(), obj, face_num); + for (S32 objMaterial = 0; objMaterial < objMaterials.size(); objMaterial++) + { + int_list_t faces; + getFacesWithMaterial(obj, objMaterials[objMaterial], &faces); + std::string matName = objMaterials[objMaterial].name; + addPolygons(mesh, geomID, (matName + "-material").c_str(), obj, &faces); + } } - - // Effects (face color, alpha) - for (S32 face_num = 0; face_num < num_faces; face_num++) + else { - LLTextureEntry* te = obj->getTE(face_num); - LLColor4 color = te->getColor(); - domEffect* effect = (domEffect*)effects->add("effect"); - effect->setId(llformat("%s-f%d-%s", geomID, face_num, "fx").c_str()); - daeElement* t = effect->add("profile_COMMON technique"); - t->setAttribute("sid", "common"); - domElement* phong = t->add("phong"); - phong->add("diffuse color")->setCharData(llformat("%f %f %f %f", color.mV[0], color.mV[1], color.mV[2], color.mV[3]).c_str()); - phong->add("transparency float")->setCharData(llformat("%f", color.mV[3]).c_str()); - } - - // Materials - for (S32 face_num = 0; face_num < num_faces; face_num++) - { - domMaterial* mat = (domMaterial*)materials->add("material"); - mat->setId(llformat("%s-f%d-%s", geomID, face_num, "material").c_str()); - domElement* matEffect = mat->add("instance_effect"); - matEffect->setAttribute("url", llformat("#%s-f%d-%s", geomID, face_num, "fx").c_str()); + S32 mat_nr = 0; + for (S32 face_num = 0; face_num < num_faces; face_num++) + { + if (skipFace(obj->getTE(face_num))) continue; + int_list_t faces; + faces.push_back(face_num); + std::string matName = objMaterials[mat_nr++].name; + addPolygons(mesh, geomID, (matName + "-material").c_str(), obj, &faces); + } } daeElement* node = scene->add("node"); @@ -462,16 +913,30 @@ bool DAESaver::saveDAE(std::string filename) // Bind materials daeElement* tq = nodeGeometry->add("bind_material technique_common"); - for (S32 face_num = 0; face_num < num_faces; face_num++) + for (S32 objMaterial = 0; objMaterial < objMaterials.size(); objMaterial++) { + std::string matName = objMaterials[objMaterial].name; daeElement* instanceMaterial = tq->add("instance_material"); - instanceMaterial->setAttribute("symbol", llformat("%s-f%d-%s", geomID, face_num, "material").c_str()); - instanceMaterial->setAttribute("target", llformat("#%s-f%d-%s", geomID, face_num, "material").c_str()); + instanceMaterial->setAttribute("symbol", (matName + "-material").c_str()); + instanceMaterial->setAttribute("target", ("#" + matName + "-material").c_str()); } nodeGeometry->setAttribute("url", llformat("#%s-%s", geomID, "mesh").c_str()); } + + // Effects (face texture, color, alpha) + generateEffects(effects); + + // Materials + for (S32 objMaterial = 0; objMaterial < mAllMaterials.size(); objMaterial++) + { + daeElement* mat = materials->add("material"); + mat->setAttribute("id", (mAllMaterials[objMaterial].name + "-material").c_str()); + daeElement* matEffect = mat->add("instance_effect"); + matEffect->setAttribute("url", ("#" + mAllMaterials[objMaterial].name + "-fx").c_str()); + } + root->add("scene instance_visual_scene")->setAttribute("url", "#Scene"); return dae.writeAll(); @@ -480,8 +945,152 @@ bool DAESaver::saveDAE(std::string filename) DAESaver::DAESaver() {} +bool DAESaver::skipFace(LLTextureEntry *te) +{ + return (gSavedSettings.getBOOL("DAEExportSkipTransparent") + && (te->getColor().mV[3] < 0.01f || te->getID() == DAEExportUtil::LL_TEXTURE_TRANSPARENT)); +} + +DAESaver::MaterialInfo DAESaver::getMaterial(LLTextureEntry* te) +{ + if (gSavedSettings.getBOOL("DAEExportConsolidateMaterials")) + { + for (S32 i=0; i < mAllMaterials.size(); i++) + { + if (mAllMaterials[i].matches(te)) + { + return mAllMaterials[i]; + } + } + } + + MaterialInfo ret; + ret.textureID = te->getID(); + ret.color = te->getColor(); + ret.name = llformat("Material%d", mAllMaterials.size()); + mAllMaterials.push_back(ret); + return mAllMaterials[mAllMaterials.size() - 1]; +} + +void DAESaver::getMaterials(LLViewerObject* obj, material_list_t* ret) +{ + S32 num_faces = obj->getVolume()->getNumVolumeFaces(); + for (S32 face_num = 0; face_num < num_faces; ++face_num) + { + LLTextureEntry* te = obj->getTE(face_num); + + if (skipFace(te)) + { + continue; + } + + MaterialInfo mat = getMaterial(te); + + if (!gSavedSettings.getBOOL("DAEExportConsolidateMaterials") || std::find(ret->begin(), ret->end(), mat) == ret->end()) + { + ret->push_back(mat); + } + } +} + +void DAESaver::getFacesWithMaterial(LLViewerObject* obj, MaterialInfo& mat, int_list_t* ret) +{ + S32 num_faces = obj->getVolume()->getNumVolumeFaces(); + for (S32 face_num = 0; face_num < num_faces; ++face_num) + { + if (mat == getMaterial(obj->getTE(face_num))) + { + ret->push_back(face_num); + } + } +} + +void DAESaver::generateEffects(daeElement *effects) +{ + // Effects (face color, alpha) + bool export_textures = gSavedSettings.getBOOL("DAEExportTextures"); + + for (S32 mat = 0; mat < mAllMaterials.size(); mat++) + { + LLColor4 color = mAllMaterials[mat].color; + domEffect* effect = (domEffect*)effects->add("effect"); + effect->setId((mAllMaterials[mat].name + "-fx").c_str()); + daeElement* profile = effect->add("profile_COMMON"); + std::string colladaName; + + if (export_textures) + { + LLUUID textID; + S32 i = 0; + for (; i < mTextures.size(); i++) + { + if (mAllMaterials[mat].textureID == mTextures[i]) + { + textID = mTextures[i]; + break; + } + } + + if (!textID.isNull() && !mTextureNames[i].empty()) + { + colladaName = mTextureNames[i] + "_" + mImageFormat; + daeElement* newparam = profile->add("newparam"); + newparam->setAttribute("sid", (colladaName + "-surface").c_str()); + daeElement* surface = newparam->add("surface"); + surface->setAttribute("type", "2D"); + surface->add("init_from")->setCharData(colladaName.c_str()); + newparam = profile->add("newparam"); + newparam->setAttribute("sid", (colladaName + "-sampler").c_str()); + newparam->add("sampler2D source")->setCharData((colladaName + "-surface").c_str()); + } + } + + daeElement* t = profile->add("technique"); + t->setAttribute("sid", "common"); + domElement* phong = t->add("phong"); + domElement* diffuse = phong->add("diffuse"); + // Only one or can appear inside diffuse element + if (!colladaName.empty()) + { + daeElement* txtr = diffuse->add("texture"); + txtr->setAttribute("texture", (colladaName + "-sampler").c_str()); + txtr->setAttribute("texcoord", colladaName.c_str()); + } + else + { + daeElement* diffuseColor = diffuse->add("color"); + diffuseColor->setAttribute("sid", "diffuse"); + diffuseColor->setCharData(llformat("%f %f %f %f", color.mV[0], color.mV[1], color.mV[2], color.mV[3]).c_str()); + phong->add("transparency float")->setCharData(llformat("%f", color.mV[3]).c_str()); + } + } +} + +void DAESaver::generateImagesSection(daeElement* images) +{ + for (S32 i=0; i < mTextureNames.size(); i++) + { + std::string name = mTextureNames[i]; + if (name.empty()) continue; + std::string colladaName = name + "_" + mImageFormat; + daeElement* image = images->add("image"); + image->setAttribute("id", colladaName.c_str()); + image->setAttribute("name", colladaName.c_str()); + image->add("init_from")->setCharData(LLURI::escape(name + "." + mImageFormat)); + } +} + +class DAESaveSelectedObjects : public view_listener_t +{ + bool handleEvent(LLPointer event, const LLSD& userdata) + { + (new ColladaExportFloater())->open(); + return true; + } +}; + void addMenu(view_listener_t* menu, const std::string& name); void add_dae_listeners() // Called in llviewermenu with other addMenu calls, function linked against { - addMenu(new DAEExportUtil::DAESaveSelectedObjects(), "Object.SaveAsDAE"); + addMenu(new DAESaveSelectedObjects(), "Object.SaveAsDAE"); } diff --git a/indra/newview/daeexport.h b/indra/newview/daeexport.h index 3bfcf2ec6..fbdd5b84c 100644 --- a/indra/newview/daeexport.h +++ b/indra/newview/daeexport.h @@ -1,48 +1,110 @@ /** - * @file daeexport.h - * @brief A system which allows saving in-world objects to Collada .DAE files for offline texturizing/shading. - * @authors Latif Khalifa - * - * $LicenseInfo:firstyear=2013&license=LGPLV2.1$ - * Copyright (C) 2013 Latif Khalifa - * - * 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 2.1 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 - */ +* @file daeexport.h +* @brief A system which allows saving in-world objects to Collada .DAE files for offline texturizing/shading. +* @authors Latif Khalifa +* +* $LicenseInfo:firstyear=2013&license=LGPLV2.1$ +* Copyright (C) 2013 Latif Khalifa +* +* 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 2.1 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 DAEEXPORT_H_ #define DAEEXPORT_H_ #include +#include "lltextureentry.h" class LLViewerObject; class DAESaver { - typedef std::vector > obj_info_t; - public: + class MaterialInfo + { + public: + LLUUID textureID; + LLColor4 color; + std::string name; + + bool matches(LLTextureEntry* te) + { + return (textureID == te->getID()) && (color == te->getColor()); + } + + bool operator== (const MaterialInfo& rhs) + { + return (textureID == rhs.textureID) && (color == rhs.color) && (name == rhs.name); + } + + bool operator!= (const MaterialInfo& rhs) + { + return !(*this == rhs); + } + + MaterialInfo() + { + } + + MaterialInfo(const MaterialInfo& rhs) + { + textureID = rhs.textureID; + color = rhs.color; + name = rhs.name; + } + + MaterialInfo& operator= (const MaterialInfo& rhs) + { + textureID = rhs.textureID; + color = rhs.color; + name = rhs.name; + return *this; + } + + }; + + typedef std::vector > obj_info_t; + typedef std::vector id_list_t; + typedef std::vector string_list_t; + typedef std::vector int_list_t; + typedef std::vector material_list_t; + + material_list_t mAllMaterials; + id_list_t mTextures; + string_list_t mTextureNames; obj_info_t mObjects; LLVector3 mOffset; + std::string mImageFormat; + S32 mTotalNumMaterials; + DAESaver(); - void Add(const LLViewerObject* prim, const std::string name); + void updateTextureInfo(); + void add(const LLViewerObject* prim, const std::string name); bool saveDAE(std::string filename); private: + void transformTexCoord(S32 num_vert, LLVector2* coord, LLVector3* positions, LLVector3* normals, LLTextureEntry* te, LLVector3 scale); void addSource(daeElement* mesh, const char* src_id, std::string params, const std::vector &vals); - void addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int face_to_include); + void addPolygons(daeElement* mesh, const char* geomID, const char* materialID, LLViewerObject* obj, int_list_t* faces_to_include); + bool skipFace(LLTextureEntry *te); + MaterialInfo getMaterial(LLTextureEntry* te); + void getMaterials(LLViewerObject* obj, material_list_t* ret); + void getFacesWithMaterial(LLViewerObject* obj, MaterialInfo& mat, int_list_t* ret); + void generateEffects(daeElement *effects); + void generateImagesSection(daeElement* images); }; #endif // DAEEXPORT_H_ diff --git a/indra/newview/llinventorybridge.cpp b/indra/newview/llinventorybridge.cpp index f8e9390da..3dd268d60 100644 --- a/indra/newview/llinventorybridge.cpp +++ b/indra/newview/llinventorybridge.cpp @@ -3440,6 +3440,7 @@ void LLFolderBridge::buildContextMenuFolderOptions(U32 flags) mItems.push_back(std::string("Replace Outfit")); } + mItems.push_back(std::string("Replace Remove Separator")); mItems.push_back(std::string("Remove From Outfit")); if (!LLAppearanceMgr::getCanRemoveFromCOF(mUUID)) { diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp index ba734f7bd..b5d6bd86e 100644 --- a/indra/newview/lllogchat.cpp +++ b/indra/newview/lllogchat.cpp @@ -92,7 +92,7 @@ std::string LLLogChat::timestamp(bool withdate) std::string text; if (withdate) if (withseconds) - text = llformat("[%d-%02d-%02d %02d:%02d:%02d] ", (timep->tm_year-100)+2000, timep->tm_mon+1, timep->tm_mday, timep->tm_hour, timep->tm_min, timep->tm_sec); + text = llformat("[%d/%02d/%02d %02d:%02d:%02d] ", (timep->tm_year-100)+2000, timep->tm_mon+1, timep->tm_mday, timep->tm_hour, timep->tm_min, timep->tm_sec); else text = llformat("[%d/%02d/%02d %02d:%02d] ", (timep->tm_year-100)+2000, timep->tm_mon+1, timep->tm_mday, timep->tm_hour, timep->tm_min); else @@ -127,6 +127,19 @@ void LLLogChat::saveHistory(std::string const& filename, std::string line) } } +const std::streamoff BUFFER_SIZE(4096); + +// Read a chunk of size from pos in ifstr and prepend it to data +// return that chunk's newline count +U32 read_chunk(llifstream& ifstr, const std::streamoff& pos, U32 size, std::string& data) +{ + char buffer[BUFFER_SIZE]; + ifstr.seekg(pos); + ifstr.read(buffer, size); + data.insert(0, buffer, size); + return std::count(buffer, buffer + size, '\n'); +} + void LLLogChat::loadHistory(std::string const& filename , void (*callback)(ELogLineType,std::string,void*), void* userdata) { if(!filename.size()) @@ -143,10 +156,43 @@ void LLLogChat::loadHistory(std::string const& filename , void (*callback)(ELogL else { static const LLCachedControl lines("LogShowHistoryLines", 32); - std::string line; - for (U32 i = 0; i < lines && getline(ifstr, line); ++i) + ifstr.seekg(-1, std::ios_base::end); + if (!lines || !ifstr) { - callback(LOG_LINE, line, userdata); + callback(LOG_EMPTY,LLStringUtil::null,userdata); + return; + } + + std::string data; + U32 nlines = 0; + if (ifstr.get() != '\n') // in case file doesn't end with a newline + { + data.push_back('\n'); + ++nlines; + } + + // Read BUFFER_SIZE byte chunks until we have enough endlines accumulated + for(std::streamoff pos = ifstr.tellg() - BUFFER_SIZE; nlines < lines+1; pos -= BUFFER_SIZE) + { + if (pos > 0) + { + nlines += read_chunk(ifstr, pos, BUFFER_SIZE, data); + } + else // Ran out of file read the remaining from the start + { + nlines += read_chunk(ifstr, 0, pos + BUFFER_SIZE, data); + break; + } + } + + // Break data into lines + std::istringstream sstr(data); + for (std::string line; nlines > 0 && getline(sstr, line); --nlines) + { + if (nlines <= lines) + { + callback(LOG_LINE, line, userdata); + } } callback(LOG_END,LLStringUtil::null,userdata); } diff --git a/indra/newview/llviewermenu.cpp b/indra/newview/llviewermenu.cpp index b653f4457..1526fc8c1 100644 --- a/indra/newview/llviewermenu.cpp +++ b/indra/newview/llviewermenu.cpp @@ -2891,24 +2891,18 @@ class LLObjectEnableExport : public view_listener_t { bool handleEvent(LLPointer event, const LLSD& userdata) { - LLPermissions perms; - bool new_value = LLSelectMgr::getInstance()->selectGetPermissions(perms) && perms.isOwned(); // At least one object, accumulated permissions of all objects. ExportPolicy export_policy = LFSimFeatureHandler::instance().exportPolicy(); - if (new_value && !(export_policy == ep_export_bit && (perms.getMaskEveryone() & PERM_EXPORT))) // No need to call allowExportBy if PERM_EXPORT is set on (all) root objects. + bool can_export_any = false; + LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); + for (LLObjectSelection::iterator node = selection->begin(); node != selection->end(); ++node) { - bool can_export_any = false; - LLObjectSelectionHandle selection = LLSelectMgr::getInstance()->getSelection(); - for (LLObjectSelection::iterator node = selection->begin(); node != selection->end(); ++node) + if ((*node)->mPermissions->allowExportBy(gAgent.getID(), export_policy)) { - if ((*node)->mPermissions->allowExportBy(gAgent.getID(), export_policy)) - { - can_export_any = true; - break; - } + can_export_any = true; + break; } - new_value = can_export_any; } - gMenuHolder->findControl(userdata["control"].asString())->setValue(new_value); + gMenuHolder->findControl(userdata["control"].asString())->setValue(can_export_any); return true; } }; diff --git a/indra/newview/skins/default/xui/en-us/floater_dae_export.xml b/indra/newview/skins/default/xui/en-us/floater_dae_export.xml new file mode 100644 index 000000000..8d01c4ee0 --- /dev/null +++ b/indra/newview/skins/default/xui/en-us/floater_dae_export.xml @@ -0,0 +1,36 @@ + + + Collada Export: Saving textures ([COUNT] remaining) + File Name: + + + + + Object Info + Name: [NAME] + Exportable Prims: [COUNT]/[TOTAL] + Exportable Textures: [COUNT]/[TOTAL] + + + Options + + + + + TGA + PNG + J2C + BMP + JPG + + + + + + + + diff --git a/indra/newview/skins/default/xui/en-us/menu_inventory.xml b/indra/newview/skins/default/xui/en-us/menu_inventory.xml index 12ebca6f0..a248c26de 100644 --- a/indra/newview/skins/default/xui/en-us/menu_inventory.xml +++ b/indra/newview/skins/default/xui/en-us/menu_inventory.xml @@ -243,6 +243,7 @@ mouse_opaque="true" name="Replace Outfit" width="128"> + diff --git a/indra/plugins/filepicker/llfilepicker.cpp b/indra/plugins/filepicker/llfilepicker.cpp index 3650fb0cf..ab30d2a0b 100644 --- a/indra/plugins/filepicker/llfilepicker.cpp +++ b/indra/plugins/filepicker/llfilepicker.cpp @@ -1435,7 +1435,7 @@ static std::string add_imageload_filter_to_gtkchooser(GtkWindow *picker) return filtername; } -static std::string add_imagesave_filter_to_gtkchooser(GTKWindow *picker) +static std::string add_imagesave_filter_to_gtkchooser(GtkWindow *picker) { GtkFileFilter *gfilter = gtk_file_filter_new(); gtk_file_filter_add_mime_type(gfilter, "image/bmp");