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

Feat(CS): Add More Selection Controls

See merge request OpenMW/openmw!3674
macos_ci_fix
psi29a 1 year ago
commit cb24475662

@ -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

@ -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));

@ -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" };

@ -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));

@ -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.

@ -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,

@ -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;

@ -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();

@ -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" },

@ -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,

@ -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;

@ -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);

@ -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)

@ -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();

@ -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,

@ -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);
} }

@ -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;

@ -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
{ {

@ -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;

@ -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
{ {

@ -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;

@ -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)

@ -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();

@ -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

@ -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

@ -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() {}
}

@ -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
Loading…
Cancel
Save