forked from qt-creator/qt-creator
QmlDesigner: Allow saving a 3D node to the content library
Fixes: QDS-12393 Change-Id: I3112244bcbe74ea0f8f2eda8ccb6ad7470ca0e1e Reviewed-by: Miikka Heikkinen <miikka.heikkinen@qt.io> Reviewed-by: Qt CI Patch Build Bot <ci_patchbuild_bot@qt.io>
This commit is contained in:
@@ -65,7 +65,7 @@ public:
|
||||
externalDependencies,
|
||||
true)
|
||||
, collectionView{externalDependencies}
|
||||
, contentLibraryView{externalDependencies}
|
||||
, contentLibraryView{imageCache, externalDependencies}
|
||||
, componentView{externalDependencies}
|
||||
#ifndef QTC_USE_QML_DESIGNER_LITE
|
||||
, edit3DView{externalDependencies}
|
||||
|
@@ -98,17 +98,35 @@ void ContentLibraryUserModel::addMaterial(const QString &name, const QString &qm
|
||||
const QUrl &icon, const QStringList &files)
|
||||
{
|
||||
auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
|
||||
|
||||
QString typePrefix = compUtils.userMaterialsBundleType();
|
||||
TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
|
||||
|
||||
auto libMat = new ContentLibraryMaterial(this, name, qml, type, icon, files,
|
||||
Paths::bundlesPathSetting().append("/User/materials"));
|
||||
|
||||
m_userMaterials.append(libMat);
|
||||
|
||||
int matSectionIdx = 0;
|
||||
emit dataChanged(index(matSectionIdx), index(matSectionIdx));
|
||||
}
|
||||
|
||||
void ContentLibraryUserModel::add3DItem(const QString &name, const QString &qml,
|
||||
const QUrl &icon, const QStringList &files)
|
||||
{
|
||||
auto compUtils = QmlDesignerPlugin::instance()->documentManager().generatedComponentUtils();
|
||||
|
||||
QString typePrefix = compUtils.user3DBundleType();
|
||||
TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
|
||||
|
||||
m_user3DItems.append(new ContentLibraryItem(this, name, qml, type, icon, files));
|
||||
}
|
||||
|
||||
void ContentLibraryUserModel::refresh3DSection()
|
||||
{
|
||||
int sectionIdx = 2;
|
||||
emit dataChanged(index(sectionIdx), index(sectionIdx));
|
||||
}
|
||||
|
||||
void ContentLibraryUserModel::addTextures(const QStringList &paths)
|
||||
{
|
||||
QDir bundleDir{Paths::bundlesPathSetting() + "/User/textures"};
|
||||
@@ -198,7 +216,7 @@ void ContentLibraryUserModel::removeFromContentLib(ContentLibraryMaterial *mat)
|
||||
}
|
||||
|
||||
// returns unique library material's name and qml component
|
||||
QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNameAndQml(const QString &matName) const
|
||||
QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNameAndQml(const QString &defaultName) const
|
||||
{
|
||||
QTC_ASSERT(!m_bundleObjMaterial.isEmpty(), return {});
|
||||
|
||||
@@ -209,10 +227,9 @@ QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNameAndQml(
|
||||
for (const QString &matName : matNames)
|
||||
matQmls.append(matsObj.value(matName).toObject().value("qml").toString().chopped(4)); // remove .qml
|
||||
|
||||
QString retName = matName.isEmpty() ? "Material" : matName;
|
||||
retName = retName.trimmed();
|
||||
|
||||
QString retName = defaultName.isEmpty() ? "Material" : defaultName.trimmed();
|
||||
QString retQml = retName;
|
||||
|
||||
retQml.remove(' ');
|
||||
if (retQml.at(0).isLower())
|
||||
retQml[0] = retQml.at(0).toUpper();
|
||||
@@ -232,6 +249,32 @@ QPair<QString, QString> ContentLibraryUserModel::getUniqueLibMaterialNameAndQml(
|
||||
return {retName, retQml + ".qml"};
|
||||
}
|
||||
|
||||
QString ContentLibraryUserModel::getUniqueLib3DQmlName(const QString &defaultName) const
|
||||
{
|
||||
QTC_ASSERT(!m_bundleObj3D.isEmpty(), return {});
|
||||
|
||||
const QJsonArray itemsArr = m_bundleObj3D.value("items").toArray();
|
||||
|
||||
QStringList itemQmls;
|
||||
for (const QJsonValueConstRef &itemRef : itemsArr)
|
||||
itemQmls.append(itemRef.toObject().value("qml").toString().chopped(4)); // remove .qml
|
||||
|
||||
QString baseQml = defaultName.isEmpty() ? "Item" : defaultName.trimmed();
|
||||
baseQml.remove(' ');
|
||||
baseQml[0] = baseQml.at(0).toUpper();
|
||||
baseQml.prepend("My");
|
||||
|
||||
QString uniqueQml = baseQml;
|
||||
|
||||
int counter = 1;
|
||||
while (itemQmls.contains(uniqueQml)) {
|
||||
uniqueQml = QString("%1%2").arg(uniqueQml).arg(counter);
|
||||
++counter;
|
||||
}
|
||||
|
||||
return uniqueQml + ".qml";
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const
|
||||
{
|
||||
static const QHash<int, QByteArray> roles {
|
||||
@@ -242,11 +285,16 @@ QHash<int, QByteArray> ContentLibraryUserModel::roleNames() const
|
||||
return roles;
|
||||
}
|
||||
|
||||
QJsonObject &ContentLibraryUserModel::bundleJsonObjectRef()
|
||||
QJsonObject &ContentLibraryUserModel::bundleJsonMaterialObjectRef()
|
||||
{
|
||||
return m_bundleObjMaterial;
|
||||
}
|
||||
|
||||
QJsonObject &ContentLibraryUserModel::bundleJson3DObjectRef()
|
||||
{
|
||||
return m_bundleObj3D;
|
||||
}
|
||||
|
||||
void ContentLibraryUserModel::loadBundles()
|
||||
{
|
||||
loadMaterialBundle();
|
||||
@@ -356,15 +404,15 @@ void ContentLibraryUserModel::load3DBundle()
|
||||
int section3DIdx = 2;
|
||||
|
||||
m_bundlePath3D = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d");
|
||||
m_bundlePath3D.createDir();
|
||||
m_bundlePath3D.ensureWritableDir();
|
||||
m_bundlePath3D.pathAppended("icons").ensureWritableDir();
|
||||
|
||||
auto jsonFilePath = m_bundlePath3D.pathAppended("user_3d_bundle.json");
|
||||
|
||||
if (!jsonFilePath.exists()) {
|
||||
QByteArray jsonContent = "{\n";
|
||||
jsonContent += " \"id\": \"User3D\",\n";
|
||||
jsonContent += " \"items\": {\n";
|
||||
jsonContent += " }\n";
|
||||
jsonContent += " \"items\": []\n";
|
||||
jsonContent += "}";
|
||||
Utils::expected_str<qint64> res = jsonFilePath.writeFileContents(jsonContent);
|
||||
if (!res.has_value()) {
|
||||
@@ -393,24 +441,21 @@ void ContentLibraryUserModel::load3DBundle()
|
||||
m_bundleObj3D["id"] = m_bundleId3D;
|
||||
|
||||
// parse 3d items
|
||||
const QJsonObject itemsObj = m_bundleObj3D.value("items").toObject();
|
||||
const QStringList itemNames = itemsObj.keys();
|
||||
QString typePrefix = compUtils.user3DBundleType();
|
||||
for (const QString &itemName : itemNames) {
|
||||
const QJsonObject itemObj = itemsObj.value(itemName).toObject();
|
||||
const QJsonArray itemsArr = m_bundleObj3D.value("items").toArray();
|
||||
for (const QJsonValueConstRef &itemRef : itemsArr) {
|
||||
const QJsonObject itemObj = itemRef.toObject();
|
||||
|
||||
QString name = itemObj.value("name").toString();
|
||||
QString qml = itemObj.value("qml").toString();
|
||||
TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
|
||||
QUrl icon = m_bundlePath3D.pathAppended(itemObj.value("icon").toString()).toUrl();
|
||||
QStringList files;
|
||||
const QJsonArray assetsArr = itemObj.value("files").toArray();
|
||||
for (const QJsonValueConstRef &asset : assetsArr)
|
||||
files.append(asset.toString());
|
||||
|
||||
QUrl icon = m_bundlePath3D.pathAppended(itemObj.value("icon").toString()).toUrl();
|
||||
QString qml = itemObj.value("qml").toString();
|
||||
TypeName type = QLatin1String("%1.%2").arg(typePrefix, qml.chopped(4)).toLatin1();
|
||||
|
||||
auto bundleItem = new ContentLibraryItem(nullptr, itemName, qml, type, icon, files);
|
||||
|
||||
m_user3DItems.append(bundleItem);
|
||||
m_user3DItems.append(new ContentLibraryItem(nullptr, name, qml, type, icon, files));
|
||||
}
|
||||
|
||||
m_bundle3DSharedFiles.clear();
|
||||
|
@@ -43,7 +43,8 @@ public:
|
||||
void setSearchText(const QString &searchText);
|
||||
void updateImportedState(const QStringList &importedItems);
|
||||
|
||||
QPair<QString, QString> getUniqueLibMaterialNameAndQml(const QString &matName) const;
|
||||
QPair<QString, QString> getUniqueLibMaterialNameAndQml(const QString &defaultName = {}) const;
|
||||
QString getUniqueLib3DQmlName(const QString &defaultName = {}) const;
|
||||
|
||||
void setQuick3DImportVersion(int major, int minor);
|
||||
|
||||
@@ -59,12 +60,15 @@ public:
|
||||
void updateIsEmpty3D();
|
||||
|
||||
void addMaterial(const QString &name, const QString &qml, const QUrl &icon, const QStringList &files);
|
||||
void add3DItem(const QString &name, const QString &qml, const QUrl &icon, const QStringList &files);
|
||||
void refresh3DSection();
|
||||
void addTextures(const QStringList &paths);
|
||||
|
||||
void add3DInstance(ContentLibraryItem *bundleItem);
|
||||
|
||||
void setBundleObj(const QJsonObject &newBundleObj);
|
||||
QJsonObject &bundleJsonObjectRef();
|
||||
QJsonObject &bundleJsonMaterialObjectRef();
|
||||
QJsonObject &bundleJson3DObjectRef();
|
||||
|
||||
void loadBundles();
|
||||
|
||||
|
@@ -44,8 +44,10 @@
|
||||
|
||||
namespace QmlDesigner {
|
||||
|
||||
ContentLibraryView::ContentLibraryView(ExternalDependenciesInterface &externalDependencies)
|
||||
ContentLibraryView::ContentLibraryView(AsynchronousImageCache &imageCache,
|
||||
ExternalDependenciesInterface &externalDependencies)
|
||||
: AbstractView(externalDependencies)
|
||||
, m_imageCache(imageCache)
|
||||
, m_createTexture(this)
|
||||
{}
|
||||
|
||||
@@ -364,6 +366,8 @@ void ContentLibraryView::customNotification(const AbstractView *view,
|
||||
addLibMaterial(nodeList.first(), data.first().value<QPixmap>());
|
||||
} else if (identifier == "add_assets_to_content_lib") {
|
||||
addLibAssets(data.first().toStringList());
|
||||
} else if (identifier == "add_3d_to_content_lib") {
|
||||
addLib3DItem(nodeList.first());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,10 +517,10 @@ void ContentLibraryView::addLibMaterial(const ModelNode &mat, const QPixmap &ico
|
||||
qWarning() << __FUNCTION__ << "icon save failed";
|
||||
|
||||
// generate and save material Qml file
|
||||
const QStringList depAssets = writeLibMaterialQml(mat, qml);
|
||||
const QStringList depAssets = writeLibItemQml(mat, qml);
|
||||
|
||||
// add the material to the bundle json
|
||||
QJsonObject &jsonRef = m_widget->userModel()->bundleJsonObjectRef();
|
||||
QJsonObject &jsonRef = m_widget->userModel()->bundleJsonMaterialObjectRef();
|
||||
QJsonObject matsObj = jsonRef.value("materials").toObject();
|
||||
QJsonObject matObj;
|
||||
matObj.insert("qml", qml);
|
||||
@@ -554,14 +558,16 @@ void ContentLibraryView::addLibMaterial(const ModelNode &mat, const QPixmap &ico
|
||||
m_widget->userModel()->addMaterial(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets);
|
||||
}
|
||||
|
||||
QStringList ContentLibraryView::writeLibMaterialQml(const ModelNode &mat, const QString &qml)
|
||||
QStringList ContentLibraryView::writeLibItemQml(const ModelNode &node, const QString &qml)
|
||||
{
|
||||
QStringList depListIds;
|
||||
auto [qmlString, assets] = modelNodeToQmlString(mat, depListIds);
|
||||
auto [qmlString, assets] = modelNodeToQmlString(node, depListIds);
|
||||
|
||||
qmlString.prepend("import QtQuick\nimport QtQuick3D\n\n");
|
||||
|
||||
auto qmlPath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/materials/" + qml);
|
||||
QString itemType = QLatin1String(node.metaInfo().isQtQuick3DMaterial() ? "material" : "3d");
|
||||
auto qmlPath = Utils::FilePath::fromString(QLatin1String("%1/User/%2/%3")
|
||||
.arg(Paths::bundlesPathSetting(), itemType, qml));
|
||||
auto result = qmlPath.writeFileContents(qmlString.toUtf8());
|
||||
if (!result)
|
||||
qWarning() << __FUNCTION__ << result.error();
|
||||
@@ -584,16 +590,24 @@ QPair<QString, QSet<QString>> ContentLibraryView::modelNodeToQmlString(const Mod
|
||||
|
||||
qml += indent + "id: " + (depth == 0 ? "root" : node.id()) + " \n\n";
|
||||
|
||||
const QList<PropertyName> excludedProps = {"x", "y", "z", "eulerRotation.x", "eulerRotation.y",
|
||||
"eulerRotation.z", "scale.x", "scale.y", "scale.z",
|
||||
"pivot.x", "pivot.y", "pivot.z"};
|
||||
const QList<AbstractProperty> matProps = node.properties();
|
||||
for (const AbstractProperty &p : matProps) {
|
||||
if (excludedProps.contains(p.name()))
|
||||
continue;
|
||||
|
||||
if (p.isVariantProperty()) {
|
||||
QVariant pValue = p.toVariantProperty().value();
|
||||
QString val;
|
||||
if (strcmp(pValue.typeName(), "QString") == 0 || strcmp(pValue.typeName(), "QColor") == 0) {
|
||||
val = QLatin1String("\"%1\"").arg(pValue.toString());
|
||||
} else if (strcmp(pValue.typeName(), "QUrl") == 0) {
|
||||
val = QLatin1String("\"%1\"").arg(pValue.toString());
|
||||
assets.insert(pValue.toString());
|
||||
QString pValueStr = pValue.toString();
|
||||
val = QLatin1String("\"%1\"").arg(pValueStr);
|
||||
if (!pValueStr.startsWith("#"))
|
||||
assets.insert(pValue.toString());
|
||||
} else if (strcmp(pValue.typeName(), "QmlDesigner::Enumeration") == 0) {
|
||||
val = pValue.value<QmlDesigner::Enumeration>().toString();
|
||||
} else {
|
||||
@@ -649,6 +663,82 @@ void ContentLibraryView::addLibAssets(const QStringList &paths)
|
||||
m_widget->userModel()->addTextures(pathsInBundle);
|
||||
}
|
||||
|
||||
void ContentLibraryView::addLib3DItem(const ModelNode &node)
|
||||
{
|
||||
auto bundlePath = Utils::FilePath::fromString(Paths::bundlesPathSetting() + "/User/3d/");
|
||||
|
||||
QString name = node.variantProperty("objectName").value().toString();
|
||||
QString qml = m_widget->userModel()->getUniqueLib3DQmlName(node.id());
|
||||
QString iconPath = QLatin1String("icons/%1.png").arg(node.id()); // TODO: make sure path is unique
|
||||
|
||||
// generate and save item Qml file
|
||||
const QStringList depAssets = writeLibItemQml(node, qml);
|
||||
|
||||
// generate and save icon
|
||||
QString qmlPath = QLatin1String("%1/User/3d/%2").arg(Paths::bundlesPathSetting(), qml);
|
||||
QString fullIconPath = bundlePath.pathAppended(iconPath).toString();
|
||||
genAndSaveIcon(qmlPath, fullIconPath);
|
||||
|
||||
// add the item to the bundle json
|
||||
QJsonObject &jsonRef = m_widget->userModel()->bundleJson3DObjectRef();
|
||||
QJsonArray itemsArr = jsonRef.value("items").toArray();
|
||||
QJsonObject itemObj;
|
||||
itemObj.insert("name", name);
|
||||
itemObj.insert("qml", qml);
|
||||
itemObj.insert("icon", iconPath);
|
||||
QJsonArray filesArr;
|
||||
for (const QString &assetPath : depAssets)
|
||||
filesArr.append(assetPath);
|
||||
itemObj.insert("files", filesArr);
|
||||
|
||||
itemsArr.append(itemObj);
|
||||
jsonRef["items"] = itemsArr;
|
||||
|
||||
auto result = bundlePath.pathAppended("user_3d_bundle.json")
|
||||
.writeFileContents(QJsonDocument(jsonRef).toJson());
|
||||
if (!result)
|
||||
qWarning() << __FUNCTION__ << result.error();
|
||||
|
||||
// copy item's assets to bundle folder
|
||||
for (const QString &assetPath : depAssets) {
|
||||
Utils::FilePath assetPathSource = DocumentManager::currentResourcePath().pathAppended(assetPath);
|
||||
Utils::FilePath assetPathTarget = bundlePath.pathAppended(assetPath);
|
||||
assetPathTarget.parentDir().ensureWritableDir();
|
||||
|
||||
auto result = assetPathSource.copyFile(assetPathTarget);
|
||||
if (!result)
|
||||
qWarning() << __FUNCTION__ << result.error();
|
||||
}
|
||||
|
||||
m_widget->userModel()->add3DItem(name, qml, QUrl::fromLocalFile(fullIconPath), depAssets);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generates an icon image from a qml component
|
||||
* @param qmlPath path to the qml component file to be rendered
|
||||
* @param iconPath output save path of the generated icon
|
||||
*/
|
||||
void ContentLibraryView::genAndSaveIcon(const QString &qmlPath, const QString &iconPath)
|
||||
{
|
||||
m_imageCache.requestSmallImage(
|
||||
Utils::PathString{qmlPath},
|
||||
[&, qmlPath, iconPath](const QImage &image) {
|
||||
bool iconSaved = image.save(iconPath);
|
||||
if (iconSaved)
|
||||
m_widget->userModel()->refresh3DSection();
|
||||
else
|
||||
qWarning() << "ContentLibraryView::genAndSaveIcon(): icon save failed";
|
||||
},
|
||||
[](ImageCache::AbortReason abortReason) {
|
||||
if (abortReason == ImageCache::AbortReason::Abort)
|
||||
qWarning() << "ContentLibraryView::genAndSaveIcon(): icon generation aborted, reason: Abort";
|
||||
else if (abortReason == ImageCache::AbortReason::Failed)
|
||||
qWarning() << "ContentLibraryView::genAndSaveIcon(): icon generation aborted, reason: Failed";
|
||||
else if (abortReason == ImageCache::AbortReason::NoEntry)
|
||||
qWarning() << "ContentLibraryView::genAndSaveIcon(): icon generation aborted, reason: NoEntry";
|
||||
});
|
||||
}
|
||||
|
||||
ModelNode ContentLibraryView::getBundleMaterialDefaultInstance(const TypeName &type)
|
||||
{
|
||||
ModelNode matLib = Utils3D::materialLibraryNode(this);
|
||||
|
@@ -3,6 +3,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <asynchronousimagecache.h>
|
||||
#include <abstractview.h>
|
||||
#include <createtexture.h>
|
||||
#include <nodemetainfo.h>
|
||||
@@ -25,7 +26,8 @@ class ContentLibraryView : public AbstractView
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ContentLibraryView(ExternalDependenciesInterface &externalDependencies);
|
||||
ContentLibraryView(AsynchronousImageCache &imageCache,
|
||||
ExternalDependenciesInterface &externalDependencies);
|
||||
~ContentLibraryView() override;
|
||||
|
||||
bool hasWidget() const override;
|
||||
@@ -55,7 +57,9 @@ private:
|
||||
void updateBundlesQuick3DVersion();
|
||||
void addLibMaterial(const ModelNode &mat, const QPixmap &icon);
|
||||
void addLibAssets(const QStringList &paths);
|
||||
QStringList writeLibMaterialQml(const ModelNode &mat, const QString &qml);
|
||||
void addLib3DItem(const ModelNode &node);
|
||||
void genAndSaveIcon(const QString &qmlPath, const QString &iconPath);
|
||||
QStringList writeLibItemQml(const ModelNode &node, const QString &qml);
|
||||
QPair<QString, QSet<QString>> modelNodeToQmlString(const ModelNode &node, QStringList &depListIds,
|
||||
int depth = 0);
|
||||
|
||||
@@ -79,6 +83,7 @@ private:
|
||||
ContentLibraryMaterial *m_draggedBundleMaterial = nullptr;
|
||||
ContentLibraryTexture *m_draggedBundleTexture = nullptr;
|
||||
ContentLibraryItem *m_draggedBundleItem = nullptr;
|
||||
AsynchronousImageCache &m_imageCache;
|
||||
bool m_bundleMaterialAddToSelected = false;
|
||||
bool m_hasQuick3DImport = false;
|
||||
qint32 m_sceneId = -1;
|
||||
|
@@ -359,6 +359,14 @@ void Edit3DWidget::createContextMenu()
|
||||
resetAction->setToolTip(tr("Reset all shading options for all viewports."));
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
|
||||
m_addToContentLibAction = m_contextMenu->addAction(
|
||||
contextIcon(DesignerIcons::CreateIcon), // TODO: placeholder icon
|
||||
tr("Add to Content Library"), [&] {
|
||||
view()->emitCustomNotification("add_3d_to_content_lib", {m_contextMenuTarget});
|
||||
});
|
||||
|
||||
m_contextMenu->addSeparator();
|
||||
}
|
||||
|
||||
bool Edit3DWidget::isPasteAvailable() const
|
||||
@@ -612,6 +620,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode
|
||||
m_contextMenuPos3d = pos3d;
|
||||
|
||||
const bool isModel = modelNode.metaInfo().isQtQuick3DModel();
|
||||
const bool isNode = modelNode.metaInfo().isQtQuick3DNode();
|
||||
const bool allowAlign = view()->edit3DAction(View3DActionType::AlignCamerasToView)->action()->isEnabled();
|
||||
const bool isSingleComponent = view()->hasSingleSelectedModelNode() && modelNode.isComponent();
|
||||
const bool anyNodeSelected = view()->hasSelectedModelNodes();
|
||||
@@ -633,6 +642,7 @@ void Edit3DWidget::showContextMenu(const QPoint &pos, const ModelNode &modelNode
|
||||
m_toggleGroupAction->setEnabled(true);
|
||||
m_bakeLightsAction->setVisible(view()->bakeLightsAction()->action()->isVisible());
|
||||
m_bakeLightsAction->setEnabled(view()->bakeLightsAction()->action()->isEnabled());
|
||||
m_addToContentLibAction->setEnabled(isNode);
|
||||
|
||||
if (m_view) {
|
||||
int idx = m_view->activeSplit();
|
||||
|
@@ -100,6 +100,7 @@ private:
|
||||
QPointer<QAction> m_selectParentAction;
|
||||
QPointer<QAction> m_toggleGroupAction;
|
||||
QPointer<QAction> m_wireFrameAction;
|
||||
QPointer<QAction> m_addToContentLibAction;
|
||||
QHash<int, QPointer<QAction>> m_matOverrideActions;
|
||||
QPointer<QMenu> m_createSubMenu;
|
||||
ModelNode m_contextMenuTarget;
|
||||
|
Reference in New Issue
Block a user