1
0
Fork 0
mirror of https://github.com/OpenMW/openmw.git synced 2025-01-21 06:53:53 +00:00

Merge branch 'what-a-selection-you-have' into 'master'

Feat(CS): Add More Selection Controls

See merge request OpenMW/openmw!3674
This commit is contained in:
psi29a 2023-12-30 09:40:29 +00:00
commit cb24475662
27 changed files with 381 additions and 5 deletions

View file

@ -25,6 +25,7 @@
#include <components/esm3/loadsoun.hpp> #include <components/esm3/loadsoun.hpp>
#include <components/esm3/loadspel.hpp> #include <components/esm3/loadspel.hpp>
#include <components/esm3/loadsscr.hpp> #include <components/esm3/loadsscr.hpp>
#include <components/esm3/selectiongroup.hpp>
#include "../world/data.hpp" #include "../world/data.hpp"
#include "../world/idcollection.hpp" #include "../world/idcollection.hpp"
@ -52,6 +53,9 @@ CSMDoc::Saving::Saving(Document& document, const std::filesystem::path& projectP
appendStage(new WriteCollectionStage<CSMWorld::IdCollection<ESM::Script>>( appendStage(new WriteCollectionStage<CSMWorld::IdCollection<ESM::Script>>(
mDocument.getData().getScripts(), mState, CSMWorld::Scope_Project)); mDocument.getData().getScripts(), mState, CSMWorld::Scope_Project));
appendStage(new WriteCollectionStage<CSMWorld::IdCollection<ESM::SelectionGroup>>(
mDocument.getData().getSelectionGroups(), mState, CSMWorld::Scope_Project));
appendStage(new CloseSaveStage(mState)); appendStage(new CloseSaveStage(mState));
// save content file // save content file

View file

@ -415,6 +415,29 @@ void CSMPrefs::State::declare()
declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T)); declareShortcut("scene-focus-toolbar", "Toggle Toolbar Focus", QKeySequence(Qt::Key_T));
declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3)); declareShortcut("scene-render-stats", "Debug Rendering Stats", QKeySequence(Qt::Key_F3));
declareShortcut("scene-duplicate", "Duplicate Instance", QKeySequence(Qt::ShiftModifier | Qt::Key_C)); declareShortcut("scene-duplicate", "Duplicate Instance", QKeySequence(Qt::ShiftModifier | Qt::Key_C));
declareShortcut("scene-clear-selection", "Clear Selection", QKeySequence(Qt::Key_Space));
declareShortcut("scene-unhide-all", "Unhide All Objects", QKeySequence(Qt::AltModifier | Qt::Key_H));
declareShortcut("scene-toggle-visibility", "Toggle Selection Visibility", QKeySequence(Qt::Key_H));
declareShortcut("scene-group-1", "Select Group 1", QKeySequence(Qt::Key_1));
declareShortcut("scene-save-1", "Save Group 1", QKeySequence(Qt::ControlModifier | Qt::Key_1));
declareShortcut("scene-group-2", "Select Group 2", QKeySequence(Qt::Key_2));
declareShortcut("scene-save-2", "Save Group 2", QKeySequence(Qt::ControlModifier | Qt::Key_2));
declareShortcut("scene-group-3", "Select Group 3", QKeySequence(Qt::Key_3));
declareShortcut("scene-save-3", "Save Group 3", QKeySequence(Qt::ControlModifier | Qt::Key_3));
declareShortcut("scene-group-4", "Select Group 4", QKeySequence(Qt::Key_4));
declareShortcut("scene-save-4", "Save Group 4", QKeySequence(Qt::ControlModifier | Qt::Key_4));
declareShortcut("scene-group-5", "Selection Group 5", QKeySequence(Qt::Key_5));
declareShortcut("scene-save-5", "Save Group 5", QKeySequence(Qt::ControlModifier | Qt::Key_5));
declareShortcut("scene-group-6", "Selection Group 6", QKeySequence(Qt::Key_6));
declareShortcut("scene-save-6", "Save Group 6", QKeySequence(Qt::ControlModifier | Qt::Key_6));
declareShortcut("scene-group-7", "Selection Group 7", QKeySequence(Qt::Key_7));
declareShortcut("scene-save-7", "Save Group 7", QKeySequence(Qt::ControlModifier | Qt::Key_7));
declareShortcut("scene-group-8", "Selection Group 8", QKeySequence(Qt::Key_8));
declareShortcut("scene-save-8", "Save Group 8", QKeySequence(Qt::ControlModifier | Qt::Key_8));
declareShortcut("scene-group-9", "Selection Group 9", QKeySequence(Qt::Key_9));
declareShortcut("scene-save-9", "Save Group 9", QKeySequence(Qt::ControlModifier | Qt::Key_9));
declareShortcut("scene-group-0", "Selection Group 10", QKeySequence(Qt::Key_0));
declareShortcut("scene-save-0", "Save Group 10", QKeySequence(Qt::ControlModifier | Qt::Key_0));
declareSubcategory("1st/Free Camera"); declareSubcategory("1st/Free Camera");
declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W)); declareShortcut("free-forward", "Forward", QKeySequence(Qt::Key_W));

View file

@ -477,6 +477,29 @@ namespace CSMPrefs
Settings::SettingValue<std::string> mSceneEditAbort{ mIndex, sName, "scene-edit-abort", "Escape" }; Settings::SettingValue<std::string> mSceneEditAbort{ mIndex, sName, "scene-edit-abort", "Escape" };
Settings::SettingValue<std::string> mSceneFocusToolbar{ mIndex, sName, "scene-focus-toolbar", "T" }; Settings::SettingValue<std::string> mSceneFocusToolbar{ mIndex, sName, "scene-focus-toolbar", "T" };
Settings::SettingValue<std::string> mSceneRenderStats{ mIndex, sName, "scene-render-stats", "F3" }; Settings::SettingValue<std::string> mSceneRenderStats{ mIndex, sName, "scene-render-stats", "F3" };
Settings::SettingValue<std::string> mSceneClearSelection{ mIndex, sName, "scene-clear-selection", "Space" };
Settings::SettingValue<std::string> mSceneUnhideAll{ mIndex, sName, "scene-unhide-all", "Alt+H" };
Settings::SettingValue<std::string> mSceneToggleHidden{ mIndex, sName, "scene-toggle-visibility", "H" };
Settings::SettingValue<std::string> mSceneSelectGroup1{ mIndex, sName, "scene-group-1", "1" };
Settings::SettingValue<std::string> mSceneSaveGroup1{ mIndex, sName, "scene-save-1", "Ctrl+1" };
Settings::SettingValue<std::string> mSceneSelectGroup2{ mIndex, sName, "scene-group-2", "2" };
Settings::SettingValue<std::string> mSceneSaveGroup2{ mIndex, sName, "scene-save-2", "Ctrl+2" };
Settings::SettingValue<std::string> mSceneSelectGroup3{ mIndex, sName, "scene-group-3", "3" };
Settings::SettingValue<std::string> mSceneSaveGroup3{ mIndex, sName, "scene-save-3", "Ctrl+3" };
Settings::SettingValue<std::string> mSceneSelectGroup4{ mIndex, sName, "scene-group-4", "4" };
Settings::SettingValue<std::string> mSceneSaveGroup4{ mIndex, sName, "scene-save-4", "Ctrl+4" };
Settings::SettingValue<std::string> mSceneSelectGroup5{ mIndex, sName, "scene-group-5", "5" };
Settings::SettingValue<std::string> mSceneSaveGroup5{ mIndex, sName, "scene-save-5", "Ctrl+5" };
Settings::SettingValue<std::string> mSceneSelectGroup6{ mIndex, sName, "scene-group-6", "6" };
Settings::SettingValue<std::string> mSceneSaveGroup6{ mIndex, sName, "scene-save-6", "Ctrl+6" };
Settings::SettingValue<std::string> mSceneSelectGroup7{ mIndex, sName, "scene-group-7", "7" };
Settings::SettingValue<std::string> mSceneSaveGroup7{ mIndex, sName, "scene-save-7", "Ctrl+7" };
Settings::SettingValue<std::string> mSceneSelectGroup8{ mIndex, sName, "scene-group-8", "8" };
Settings::SettingValue<std::string> mSceneSaveGroup8{ mIndex, sName, "scene-save-8", "Ctrl+8" };
Settings::SettingValue<std::string> mSceneSelectGroup9{ mIndex, sName, "scene-group-9", "9" };
Settings::SettingValue<std::string> mSceneSaveGroup9{ mIndex, sName, "scene-save-9", "Ctrl+9" };
Settings::SettingValue<std::string> mSceneSelectGroup10{ mIndex, sName, "scene-group-0", "0" };
Settings::SettingValue<std::string> mSceneSaveGroup10{ mIndex, sName, "scene-save-0", "Ctrl+0" };
Settings::SettingValue<std::string> mFreeForward{ mIndex, sName, "free-forward", "W" }; Settings::SettingValue<std::string> mFreeForward{ mIndex, sName, "free-forward", "W" };
Settings::SettingValue<std::string> mFreeBackward{ mIndex, sName, "free-backward", "S" }; Settings::SettingValue<std::string> mFreeBackward{ mIndex, sName, "free-backward", "S" };
Settings::SettingValue<std::string> mFreeLeft{ mIndex, sName, "free-left", "A" }; Settings::SettingValue<std::string> mFreeLeft{ mIndex, sName, "free-left", "A" };

View file

@ -333,6 +333,37 @@ namespace CSMWorld
return true; return true;
} }
SelectionGroupColumn::SelectionGroupColumn()
: Column<ESM::SelectionGroup>(Columns::ColumnId_SelectionGroupObjects, ColumnBase::Display_None)
{
}
QVariant SelectionGroupColumn::get(const Record<ESM::SelectionGroup>& record) const
{
QVariant data;
QStringList selectionInfo;
const std::vector<std::string>& instances = record.get().selectedInstances;
for (const std::string& instance : instances)
selectionInfo << QString::fromStdString(instance);
data.setValue(selectionInfo);
return data;
}
void SelectionGroupColumn::set(Record<ESM::SelectionGroup>& record, const QVariant& data)
{
ESM::SelectionGroup record2 = record.get();
for (const auto& item : data.toStringList())
record2.selectedInstances.push_back(item.toStdString());
record.setModified(record2);
}
bool SelectionGroupColumn::isEditable() const
{
return false;
}
std::optional<std::uint32_t> getSkillIndex(std::string_view value) std::optional<std::uint32_t> getSkillIndex(std::string_view value)
{ {
int index = ESM::Skill::refIdToIndex(ESM::RefId::stringRefId(value)); int index = ESM::Skill::refIdToIndex(ESM::RefId::stringRefId(value));

View file

@ -16,6 +16,7 @@
#include <components/esm3/loadinfo.hpp> #include <components/esm3/loadinfo.hpp>
#include <components/esm3/loadrace.hpp> #include <components/esm3/loadrace.hpp>
#include <components/esm3/loadskil.hpp> #include <components/esm3/loadskil.hpp>
#include <components/esm3/selectiongroup.hpp>
#include <components/esm3/variant.hpp> #include <components/esm3/variant.hpp>
#include <optional> #include <optional>
@ -2391,6 +2392,17 @@ namespace CSMWorld
void set(Record<ESM::BodyPart>& record, const QVariant& data) override; void set(Record<ESM::BodyPart>& record, const QVariant& data) override;
bool isEditable() const override; bool isEditable() const override;
}; };
struct SelectionGroupColumn : public Column<ESM::SelectionGroup>
{
SelectionGroupColumn();
QVariant get(const Record<ESM::SelectionGroup>& record) const override;
void set(Record<ESM::SelectionGroup>& record, const QVariant& data) override;
bool isEditable() const override;
};
} }
// This is required to access the type as a QVariant. // This is required to access the type as a QVariant.

View file

@ -347,6 +347,8 @@ namespace CSMWorld
ColumnId_LevelledCreatureId = 315, ColumnId_LevelledCreatureId = 315,
ColumnId_SelectionGroupObjects = 316,
// Allocated to a separate value range, so we don't get a collision should we ever need // Allocated to a separate value range, so we don't get a collision should we ever need
// to extend the number of use values. // to extend the number of use values.
ColumnId_UseValue1 = 0x10000, ColumnId_UseValue1 = 0x10000,

View file

@ -620,6 +620,10 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
mDebugProfiles.addColumn(new DescriptionColumn<ESM::DebugProfile>); mDebugProfiles.addColumn(new DescriptionColumn<ESM::DebugProfile>);
mDebugProfiles.addColumn(new ScriptColumn<ESM::DebugProfile>(ScriptColumn<ESM::DebugProfile>::Type_Lines)); mDebugProfiles.addColumn(new ScriptColumn<ESM::DebugProfile>(ScriptColumn<ESM::DebugProfile>::Type_Lines));
mSelectionGroups.addColumn(new StringIdColumn<ESM::SelectionGroup>);
mSelectionGroups.addColumn(new RecordStateColumn<ESM::SelectionGroup>);
mSelectionGroups.addColumn(new SelectionGroupColumn);
mMetaData.appendBlankRecord(ESM::RefId::stringRefId("sys::meta")); mMetaData.appendBlankRecord(ESM::RefId::stringRefId("sys::meta"));
mMetaData.addColumn(new StringIdColumn<MetaData>(true)); mMetaData.addColumn(new StringIdColumn<MetaData>(true));
@ -664,6 +668,7 @@ CSMWorld::Data::Data(ToUTF8::FromType encoding, const Files::PathContainer& data
addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Textures)), UniversalId::Type_Texture); addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Textures)), UniversalId::Type_Texture);
addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Videos)), UniversalId::Type_Video); addModel(new ResourceTable(&mResourcesManager.get(UniversalId::Type_Videos)), UniversalId::Type_Video);
addModel(new IdTable(&mMetaData), UniversalId::Type_MetaData); addModel(new IdTable(&mMetaData), UniversalId::Type_MetaData);
addModel(new IdTable(&mSelectionGroups), UniversalId::Type_SelectionGroup);
mActorAdapter = std::make_unique<ActorAdapter>(*this); mActorAdapter = std::make_unique<ActorAdapter>(*this);
@ -908,6 +913,16 @@ CSMWorld::IdCollection<ESM::DebugProfile>& CSMWorld::Data::getDebugProfiles()
return mDebugProfiles; return mDebugProfiles;
} }
CSMWorld::IdCollection<ESM::SelectionGroup>& CSMWorld::Data::getSelectionGroups()
{
return mSelectionGroups;
}
const CSMWorld::IdCollection<ESM::SelectionGroup>& CSMWorld::Data::getSelectionGroups() const
{
return mSelectionGroups;
}
const CSMWorld::IdCollection<CSMWorld::Land>& CSMWorld::Data::getLand() const const CSMWorld::IdCollection<CSMWorld::Land>& CSMWorld::Data::getLand() const
{ {
return mLand; return mLand;
@ -1369,6 +1384,17 @@ bool CSMWorld::Data::continueLoading(CSMDoc::Messages& messages)
mDebugProfiles.load(*mReader, mBase); mDebugProfiles.load(*mReader, mBase);
break; break;
case ESM::REC_SELG:
if (!mProject)
{
unhandledRecord = true;
break;
}
mSelectionGroups.load(*mReader, mBase);
break;
default: default:
unhandledRecord = true; unhandledRecord = true;

View file

@ -33,6 +33,7 @@
#include <components/esm3/loadsoun.hpp> #include <components/esm3/loadsoun.hpp>
#include <components/esm3/loadspel.hpp> #include <components/esm3/loadspel.hpp>
#include <components/esm3/loadsscr.hpp> #include <components/esm3/loadsscr.hpp>
#include <components/esm3/selectiongroup.hpp>
#include <components/files/multidircollection.hpp> #include <components/files/multidircollection.hpp>
#include <components/misc/algorithm.hpp> #include <components/misc/algorithm.hpp>
#include <components/to_utf8/to_utf8.hpp> #include <components/to_utf8/to_utf8.hpp>
@ -105,6 +106,7 @@ namespace CSMWorld
IdCollection<ESM::BodyPart> mBodyParts; IdCollection<ESM::BodyPart> mBodyParts;
IdCollection<ESM::MagicEffect> mMagicEffects; IdCollection<ESM::MagicEffect> mMagicEffects;
IdCollection<ESM::DebugProfile> mDebugProfiles; IdCollection<ESM::DebugProfile> mDebugProfiles;
IdCollection<ESM::SelectionGroup> mSelectionGroups;
IdCollection<ESM::SoundGenerator> mSoundGens; IdCollection<ESM::SoundGenerator> mSoundGens;
IdCollection<ESM::StartScript> mStartScripts; IdCollection<ESM::StartScript> mStartScripts;
NestedInfoCollection mTopicInfos; NestedInfoCollection mTopicInfos;
@ -251,6 +253,10 @@ namespace CSMWorld
IdCollection<ESM::DebugProfile>& getDebugProfiles(); IdCollection<ESM::DebugProfile>& getDebugProfiles();
const IdCollection<ESM::SelectionGroup>& getSelectionGroups() const;
IdCollection<ESM::SelectionGroup>& getSelectionGroups();
const IdCollection<CSMWorld::Land>& getLand() const; const IdCollection<CSMWorld::Land>& getLand() const;
IdCollection<CSMWorld::Land>& getLand(); IdCollection<CSMWorld::Land>& getLand();

View file

@ -68,6 +68,7 @@ namespace
":./resources-video" }, ":./resources-video" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_DebugProfiles, "Debug Profiles",
":./debug-profile.png" }, ":./debug-profile.png" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SelectionGroup, "Selection Groups", "" },
{ CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":./run-log.png" }, { CSMWorld::UniversalId::Class_Transient, CSMWorld::UniversalId::Type_RunLog, "Run Log", ":./run-log.png" },
{ CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators", { CSMWorld::UniversalId::Class_RecordList, CSMWorld::UniversalId::Type_SoundGens, "Sound Generators",
":./sound-generator.png" }, ":./sound-generator.png" },

View file

@ -133,6 +133,7 @@ namespace CSMWorld
Type_LandTexture, Type_LandTexture,
Type_Pathgrids, Type_Pathgrids,
Type_Pathgrid, Type_Pathgrid,
Type_SelectionGroup,
Type_StartScripts, Type_StartScripts,
Type_StartScript, Type_StartScript,
Type_Search, Type_Search,

View file

@ -612,6 +612,30 @@ osg::ref_ptr<CSVRender::TagBase> CSVRender::Cell::getSnapTarget(unsigned int ele
return result; return result;
} }
void CSVRender::Cell::selectFromGroup(const std::vector<std::string> group)
{
for (const auto& [_, object] : mObjects)
{
for (const auto& objectName : group)
{
if (objectName == object->getReferenceId())
{
object->setSelected(true, osg::Vec4f(1, 0, 1, 1));
}
}
}
}
void CSVRender::Cell::unhideAll()
{
for (const auto& [_, object] : mObjects)
{
osg::ref_ptr<osg::Group> rootNode = object->getRootNode();
if (rootNode->getNodeMask() == Mask_Hidden)
rootNode->setNodeMask(Mask_Reference);
}
}
std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::Cell::getSelection(unsigned int elementMask) const std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::Cell::getSelection(unsigned int elementMask) const
{ {
std::vector<osg::ref_ptr<TagBase>> result; std::vector<osg::ref_ptr<TagBase>> result;

View file

@ -148,6 +148,10 @@ namespace CSVRender
// already selected // already selected
void selectAllWithSameParentId(int elementMask); void selectAllWithSameParentId(int elementMask);
void selectFromGroup(const std::vector<std::string> group);
void unhideAll();
void handleSelectDrag(Object* object, DragMode dragMode); void handleSelectDrag(Object* object, DragMode dragMode);
void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode); void selectInsideCube(const osg::Vec3d& pointA, const osg::Vec3d& pointB, DragMode dragMode);

View file

@ -186,6 +186,71 @@ osg::Vec3f CSVRender::InstanceMode::getMousePlaneCoords(const QPoint& point, con
return mousePlanePoint; return mousePlanePoint;
} }
void CSVRender::InstanceMode::saveSelectionGroup(const int group)
{
QStringList strings;
QUndoStack& undoStack = getWorldspaceWidget().getDocument().getUndoStack();
QVariant selectionObjects;
CSMWorld::CommandMacro macro(undoStack, "Replace Selection Group");
std::string groupName = "project::" + std::to_string(group);
const auto& selection = getWorldspaceWidget().getSelection(Mask_Reference);
const int selectionObjectsIndex
= mSelectionGroups->findColumnIndex(CSMWorld::Columns::ColumnId_SelectionGroupObjects);
if (dynamic_cast<CSVRender::PagedWorldspaceWidget*>(&getWorldspaceWidget()))
groupName += "-ext";
else
groupName += "-" + getWorldspaceWidget().getCellId(osg::Vec3f(0, 0, 0));
CSMWorld::CreateCommand* newGroup = new CSMWorld::CreateCommand(*mSelectionGroups, groupName);
newGroup->setType(CSMWorld::UniversalId::Type_SelectionGroup);
for (const auto& object : selection)
if (const CSVRender::ObjectTag* objectTag = dynamic_cast<CSVRender::ObjectTag*>(object.get()))
strings << QString::fromStdString(objectTag->mObject->getReferenceId());
selectionObjects.setValue(strings);
newGroup->addValue(selectionObjectsIndex, selectionObjects);
if (mSelectionGroups->getModelIndex(groupName, 0).row() != -1)
macro.push(new CSMWorld::DeleteCommand(*mSelectionGroups, groupName));
macro.push(newGroup);
getWorldspaceWidget().clearSelection(Mask_Reference);
}
void CSVRender::InstanceMode::getSelectionGroup(const int group)
{
std::string groupName = "project::" + std::to_string(group);
std::vector<std::string> targets;
const auto& selection = getWorldspaceWidget().getSelection(Mask_Reference);
const int selectionObjectsIndex
= mSelectionGroups->findColumnIndex(CSMWorld::Columns::ColumnId_SelectionGroupObjects);
if (dynamic_cast<CSVRender::PagedWorldspaceWidget*>(&getWorldspaceWidget()))
groupName += "-ext";
else
groupName += "-" + getWorldspaceWidget().getCellId(osg::Vec3f(0, 0, 0));
const QModelIndex groupSearch = mSelectionGroups->getModelIndex(groupName, selectionObjectsIndex);
if (groupSearch.row() == -1)
return;
for (const QString& target : groupSearch.data().toStringList())
targets.push_back(target.toStdString());
if (!selection.empty())
getWorldspaceWidget().clearSelection(Mask_Reference);
getWorldspaceWidget().selectGroup(targets);
}
CSVRender::InstanceMode::InstanceMode( CSVRender::InstanceMode::InstanceMode(
WorldspaceWidget* worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget* parent) WorldspaceWidget* worldspaceWidget, osg::ref_ptr<osg::Group> parentNode, QWidget* parent)
: EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain, : EditMode(worldspaceWidget, QIcon(":scenetoolbar/editing-instance"), Mask_Reference | Mask_Terrain,
@ -199,6 +264,9 @@ CSVRender::InstanceMode::InstanceMode(
, mUnitScaleDist(1) , mUnitScaleDist(1)
, mParentNode(std::move(parentNode)) , mParentNode(std::move(parentNode))
{ {
mSelectionGroups = dynamic_cast<CSMWorld::IdTable*>(
worldspaceWidget->getDocument().getData().getTableModel(CSMWorld::UniversalId::Type_SelectionGroup));
connect(this, &InstanceMode::requestFocus, worldspaceWidget, &WorldspaceWidget::requestFocus); connect(this, &InstanceMode::requestFocus, worldspaceWidget, &WorldspaceWidget::requestFocus);
CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget); CSMPrefs::Shortcut* deleteShortcut = new CSMPrefs::Shortcut("scene-delete", worldspaceWidget);
@ -229,6 +297,14 @@ CSVRender::InstanceMode::InstanceMode(
= new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget); = new CSMPrefs::Shortcut("scene-instance-drop-terrain-separately", worldspaceWidget);
connect(dropToTerrainLevelShortcut2, qOverload<>(&CSMPrefs::Shortcut::activated), this, connect(dropToTerrainLevelShortcut2, qOverload<>(&CSMPrefs::Shortcut::activated), this,
&InstanceMode::dropSelectedInstancesToTerrainSeparately); &InstanceMode::dropSelectedInstancesToTerrainSeparately);
for (short i = 0; i <= 9; i++)
{
connect(new CSMPrefs::Shortcut("scene-group-" + std::to_string(i), worldspaceWidget),
qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, i] { this->getSelectionGroup(i); });
connect(new CSMPrefs::Shortcut("scene-save-" + std::to_string(i), worldspaceWidget),
qOverload<>(&CSMPrefs::Shortcut::activated), this, [this, i] { this->saveSelectionGroup(i); });
}
} }
void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar) void CSVRender::InstanceMode::activate(CSVWidget::SceneToolbar* toolbar)

View file

@ -14,6 +14,8 @@
#include "editmode.hpp" #include "editmode.hpp"
#include "instancedragmodes.hpp" #include "instancedragmodes.hpp"
#include <apps/opencs/model/world/idtable.hpp>
#include <components/esm3/selectiongroup.hpp>
class QDragEnterEvent; class QDragEnterEvent;
class QDropEvent; class QDropEvent;
@ -60,6 +62,7 @@ namespace CSVRender
osg::ref_ptr<osg::Group> mParentNode; osg::ref_ptr<osg::Group> mParentNode;
osg::Vec3 mDragStart; osg::Vec3 mDragStart;
std::vector<osg::Vec3> mObjectsAtDragStart; std::vector<osg::Vec3> mObjectsAtDragStart;
CSMWorld::IdTable* mSelectionGroups;
int getSubModeFromId(const std::string& id) const; int getSubModeFromId(const std::string& id) const;
@ -133,6 +136,8 @@ namespace CSVRender
void subModeChanged(const std::string& id); void subModeChanged(const std::string& id);
void deleteSelectedInstances(); void deleteSelectedInstances();
void cloneSelectedInstances(); void cloneSelectedInstances();
void getSelectionGroup(const int group);
void saveSelectionGroup(const int group);
void dropSelectedInstancesToCollision(); void dropSelectedInstancesToCollision();
void dropSelectedInstancesToTerrain(); void dropSelectedInstancesToTerrain();
void dropSelectedInstancesToCollisionSeparately(); void dropSelectedInstancesToCollisionSeparately();

View file

@ -11,6 +11,7 @@ namespace CSVRender
enum Mask : unsigned int enum Mask : unsigned int
{ {
// elements that are part of the actual scene // elements that are part of the actual scene
Mask_Hidden = 0x0,
Mask_Reference = 0x2, Mask_Reference = 0x2,
Mask_Pathgrid = 0x4, Mask_Pathgrid = 0x4,
Mask_Water = 0x8, Mask_Water = 0x8,

View file

@ -33,7 +33,6 @@
#include <osg/StateAttribute> #include <osg/StateAttribute>
#include <osg/StateSet> #include <osg/StateSet>
#include <osg/Vec3> #include <osg/Vec3>
#include <osg/Vec4f>
#include <osgFX/Scribe> #include <osgFX/Scribe>
@ -485,7 +484,7 @@ CSVRender::Object::~Object()
mParentNode->removeChild(mRootNode); mParentNode->removeChild(mRootNode);
} }
void CSVRender::Object::setSelected(bool selected) void CSVRender::Object::setSelected(bool selected, osg::Vec4f color)
{ {
mSelected = selected; mSelected = selected;
@ -499,7 +498,7 @@ void CSVRender::Object::setSelected(bool selected)
mRootNode->removeChild(mBaseNode); mRootNode->removeChild(mBaseNode);
if (selected) if (selected)
{ {
mOutline->setWireframeColor(osg::Vec4f(1, 1, 1, 1)); mOutline->setWireframeColor(color);
mOutline->addChild(mBaseNode); mOutline->addChild(mBaseNode);
mRootNode->addChild(mOutline); mRootNode->addChild(mOutline);
} }

View file

@ -5,6 +5,7 @@
#include <string> #include <string>
#include <osg/Vec3f> #include <osg/Vec3f>
#include <osg/Vec4f>
#include <osg/ref_ptr> #include <osg/ref_ptr>
#include <components/esm/defs.hpp> #include <components/esm/defs.hpp>
@ -138,7 +139,7 @@ namespace CSVRender
~Object(); ~Object();
/// Mark the object as selected, selected objects show an outline effect /// Mark the object as selected, selected objects show an outline effect
void setSelected(bool selected); void setSelected(bool selected, osg::Vec4f color = osg::Vec4f(1, 1, 1, 1));
bool getSelected() const; bool getSelected() const;

View file

@ -875,6 +875,18 @@ std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::PagedWorldspaceWidget::
return result; return result;
} }
void CSVRender::PagedWorldspaceWidget::selectGroup(std::vector<std::string> group) const
{
for (const auto& [_, cell] : mCells)
cell->selectFromGroup(group);
}
void CSVRender::PagedWorldspaceWidget::unhideAll() const
{
for (const auto& [_, cell] : mCells)
cell->unhideAll();
}
std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::PagedWorldspaceWidget::getEdited( std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::PagedWorldspaceWidget::getEdited(
unsigned int elementMask) const unsigned int elementMask) const
{ {

View file

@ -163,6 +163,10 @@ namespace CSVRender
std::vector<osg::ref_ptr<TagBase>> getSelection(unsigned int elementMask) const override; std::vector<osg::ref_ptr<TagBase>> getSelection(unsigned int elementMask) const override;
void selectGroup(const std::vector<std::string> group) const override;
void unhideAll() const override;
std::vector<osg::ref_ptr<TagBase>> getEdited(unsigned int elementMask) const override; std::vector<osg::ref_ptr<TagBase>> getEdited(unsigned int elementMask) const override;
void setSubMode(int subMode, unsigned int elementMask) override; void setSubMode(int subMode, unsigned int elementMask) override;

View file

@ -199,6 +199,16 @@ std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::UnpagedWorldspaceWidget
return mCell->getSelection(elementMask); return mCell->getSelection(elementMask);
} }
void CSVRender::UnpagedWorldspaceWidget::selectGroup(const std::vector<std::string> group) const
{
mCell->selectFromGroup(group);
}
void CSVRender::UnpagedWorldspaceWidget::unhideAll() const
{
mCell->unhideAll();
}
std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::UnpagedWorldspaceWidget::getEdited( std::vector<osg::ref_ptr<CSVRender::TagBase>> CSVRender::UnpagedWorldspaceWidget::getEdited(
unsigned int elementMask) const unsigned int elementMask) const
{ {

View file

@ -93,6 +93,10 @@ namespace CSVRender
std::vector<osg::ref_ptr<TagBase>> getSelection(unsigned int elementMask) const override; std::vector<osg::ref_ptr<TagBase>> getSelection(unsigned int elementMask) const override;
void selectGroup(const std::vector<std::string> group) const override;
void unhideAll() const override;
std::vector<osg::ref_ptr<TagBase>> getEdited(unsigned int elementMask) const override; std::vector<osg::ref_ptr<TagBase>> getEdited(unsigned int elementMask) const override;
void setSubMode(int subMode, unsigned int elementMask) override; void setSubMode(int subMode, unsigned int elementMask) override;

View file

@ -50,6 +50,7 @@
#include "cameracontroller.hpp" #include "cameracontroller.hpp"
#include "instancemode.hpp" #include "instancemode.hpp"
#include "mask.hpp"
#include "object.hpp" #include "object.hpp"
#include "pathgridmode.hpp" #include "pathgridmode.hpp"
@ -135,6 +136,15 @@ CSVRender::WorldspaceWidget::WorldspaceWidget(CSMDoc::Document& document, QWidge
CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this); CSMPrefs::Shortcut* abortShortcut = new CSMPrefs::Shortcut("scene-edit-abort", this);
connect(abortShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::abortDrag); connect(abortShortcut, qOverload<>(&CSMPrefs::Shortcut::activated), this, &WorldspaceWidget::abortDrag);
connect(new CSMPrefs::Shortcut("scene-toggle-visibility", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
&WorldspaceWidget::toggleHiddenInstances);
connect(new CSMPrefs::Shortcut("scene-unhide-all", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
&WorldspaceWidget::unhideAll);
connect(new CSMPrefs::Shortcut("scene-clear-selection", this), qOverload<>(&CSMPrefs::Shortcut::activated), this,
[this] { this->clearSelection(Mask_Reference); });
mInConstructor = false; mInConstructor = false;
} }
@ -740,6 +750,23 @@ void CSVRender::WorldspaceWidget::speedMode(bool activate)
mSpeedMode = activate; mSpeedMode = activate;
} }
void CSVRender::WorldspaceWidget::toggleHiddenInstances()
{
const std::vector<osg::ref_ptr<TagBase>> selection = getSelection(Mask_Reference);
if (selection.empty())
return;
const CSVRender::ObjectTag* firstSelection = dynamic_cast<CSVRender::ObjectTag*>(selection.begin()->get());
const CSVRender::Mask firstMask
= firstSelection->mObject->getRootNode()->getNodeMask() == Mask_Hidden ? Mask_Reference : Mask_Hidden;
for (const auto& object : selection)
if (const auto objectTag = dynamic_cast<CSVRender::ObjectTag*>(object.get()))
objectTag->mObject->getRootNode()->setNodeMask(firstMask);
}
void CSVRender::WorldspaceWidget::handleInteraction(InteractionType type, bool activate) void CSVRender::WorldspaceWidget::handleInteraction(InteractionType type, bool activate)
{ {
if (activate) if (activate)

View file

@ -201,6 +201,10 @@ namespace CSVRender
virtual std::vector<osg::ref_ptr<TagBase>> getSelection(unsigned int elementMask) const = 0; virtual std::vector<osg::ref_ptr<TagBase>> getSelection(unsigned int elementMask) const = 0;
virtual void selectGroup(const std::vector<std::string>) const = 0;
virtual void unhideAll() const = 0;
virtual std::vector<osg::ref_ptr<TagBase>> getEdited(unsigned int elementMask) const = 0; virtual std::vector<osg::ref_ptr<TagBase>> getEdited(unsigned int elementMask) const = 0;
virtual void setSubMode(int subMode, unsigned int elementMask) = 0; virtual void setSubMode(int subMode, unsigned int elementMask) = 0;
@ -300,6 +304,8 @@ namespace CSVRender
void speedMode(bool activate); void speedMode(bool activate);
void toggleHiddenInstances();
protected slots: protected slots:
void elementSelectionChanged(); void elementSelectionChanged();

View file

@ -166,7 +166,7 @@ add_component_dir (esm3
inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats
weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile
aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache aisequence magiceffects custommarkerstate stolenitems transport animationstate controlsstate mappings readerscache
infoorder timestamp formatversion landrecorddata infoorder timestamp formatversion landrecorddata selectiongroup
) )
add_component_dir (esmterrain add_component_dir (esmterrain

View file

@ -170,6 +170,8 @@ namespace ESM
// format 1 // format 1
REC_FILT = esm3Recname("FILT"), REC_FILT = esm3Recname("FILT"),
REC_DBGP = esm3Recname("DBGP"), ///< only used in project files REC_DBGP = esm3Recname("DBGP"), ///< only used in project files
REC_SELG = esm3Recname("SELG"),
REC_LUAL = esm3Recname("LUAL"), // LuaScriptsCfg (only in omwgame or omwaddon) REC_LUAL = esm3Recname("LUAL"), // LuaScriptsCfg (only in omwgame or omwaddon)
// format 16 - Lua scripts in saved games // format 16 - Lua scripts in saved games

View file

@ -0,0 +1,38 @@
#include "selectiongroup.hpp"
#include "esmreader.hpp"
#include "esmwriter.hpp"
namespace ESM
{
void SelectionGroup::load(ESMReader& esm, bool& isDeleted)
{
while (esm.hasMoreSubs())
{
esm.getSubName();
switch (esm.retSubName().toInt())
{
case fourCC("SELC"):
mId = esm.getRefId();
break;
case fourCC("SELI"):
selectedInstances.push_back(esm.getRefId().getRefIdString());
break;
default:
esm.fail("Unknown subrecord");
break;
}
}
}
void SelectionGroup::save(ESMWriter& esm, bool isDeleted) const
{
esm.writeHNCRefId("SELC", mId);
for (std::string id : selectedInstances)
esm.writeHNCString("SELI", id);
}
void SelectionGroup::blank() {}
}

View file

@ -0,0 +1,34 @@
#ifndef COMPONENTS_ESM_SELECTIONGROUP_H
#define COMPONENTS_ESM_SELECTIONGROUP_H
#include <string>
#include "components/esm/defs.hpp"
#include "components/esm/refid.hpp"
namespace ESM
{
class ESMReader;
class ESMWriter;
struct SelectionGroup
{
constexpr static RecNameInts sRecordId = REC_SELG;
static constexpr std::string_view getRecordType() { return "SelectionGroup"; }
uint32_t mRecordFlags = 0;
RefId mId;
std::vector<std::string> selectedInstances;
void load(ESMReader& esm, bool& isDeleted);
void save(ESMWriter& esm, bool isDeleted = false) const;
/// Set record to default state (does not touch the ID).
void blank();
};
}
#endif