QmlDesigner: Ensure material library is created when needed

Added material library accessor to AbstractView, which creates the
material library and moves existing materials under it in case it
doesn't yet exist. Also added material assignment function to
AbstractView. The reason these were added to AbstractView instead of
being handled e.g. via custom notification in material editor is that
they need to be called from multiple different views in the same
transaction that triggers the need of material library.

Fixes: QDS-7081
Change-Id: If2bb884f87d04c9f3599c2342df66ef51ec238ee
Reviewed-by: Mahmoud Badri <mahmoud.badri@qt.io>
Reviewed-by: Thomas Hartmann <thomas.hartmann@qt.io>
This commit is contained in:
Miikka Heikkinen
2022-06-07 13:39:25 +03:00
parent d2cc440c68
commit 7269aafbd8
9 changed files with 144 additions and 145 deletions

View File

@@ -186,6 +186,7 @@ void Edit3DCanvas::dragEnterEvent(QDragEnterEvent *e)
void Edit3DCanvas::dropEvent(QDropEvent *e)
{
m_parent->view()->executeInTransaction(__FUNCTION__, [&] {
auto modelNode = QmlVisualNode::createQml3DNode(m_parent->view(), m_itemLibraryEntry, m_activeScene).modelNode();
QTC_ASSERT(modelNode.isValid(), return);
@@ -193,35 +194,9 @@ void Edit3DCanvas::dropEvent(QDropEvent *e)
m_parent->view()->setSelectedModelNode(modelNode);
// if added node is a Model, assign it a material
if (modelNode.isSubclassOf("QtQuick3D.Model")) {
ModelNode matLib = m_parent->view()->modelNodeForId(Constants::MATERIAL_LIB_ID);
QTC_ASSERT(matLib.isValid(), return);
const QList<ModelNode> materials = matLib.directSubModelNodes();
ModelNode material;
if (materials.size() > 0) {
for (const ModelNode &mat : materials) {
if (mat.isSubclassOf("QtQuick3D.Material")) {
material = mat;
break;
}
}
}
// if no valid material, create a new default material
if (!material.isValid()) {
NodeMetaInfo metaInfo = m_parent->view()->model()->metaInfo("QtQuick3D.DefaultMaterial");
material = m_parent->view()->createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(),
metaInfo.minorVersion());
VariantProperty matNameProp = material.variantProperty("objectName");
matNameProp.setValue("New Material");
material.validId();
matLib.defaultNodeListProperty().reparentHere(material);
}
BindingProperty modelMatsProp = modelNode.bindingProperty("materials");
modelMatsProp.setExpression(material.id());
}
if (modelNode.isSubclassOf("QtQuick3D.Model"))
m_parent->view()->assignMaterialTo3dModel(modelNode);
});
}
void Edit3DCanvas::focusOutEvent(QFocusEvent *focusEvent)

View File

@@ -33,6 +33,8 @@
#include <rewritingexception.h>
#include "qmldesignerconstants.h"
#include <utils/qtcassert.h>
#include <QDebug>
#include <QGraphicsSceneMouseEvent>
#include <QLoggingCategory>
@@ -405,10 +407,23 @@ void DragTool::move(const QPointF &scenePosition, const QList<QGraphicsItem *> &
void DragTool::commitTransaction()
{
try {
handleView3dDrop();
m_rewriterTransaction.commit();
} catch (const RewritingException &e) {
e.showException();
}
}
void DragTool::handleView3dDrop()
{
// If a View3D is dropped, we need to assign material to the included model
for (const QmlItemNode &dragNode : qAsConst(m_dragNodes)) {
if (dragNode.modelNode().isSubclassOf("QtQuick3D.View3D")) {
const QList<ModelNode> models = dragNode.modelNode().subModelNodesOfType("QtQuick3D.Model");
QTC_ASSERT(models.size() == 1, return);
view()->assignMaterialTo3dModel(models.at(0));
}
}
}
} // namespace QmlDesigner

View File

@@ -90,6 +90,7 @@ protected:
void move(const QPointF &scenePos, const QList<QGraphicsItem *> &itemList);
void createDragNodes(const QMimeData *mimeData, const QPointF &scenePosition, const QList<QGraphicsItem *> &itemList);
void commitTransaction();
void handleView3dDrop();
private:
MoveManipulator m_moveManipulator;

View File

@@ -78,47 +78,6 @@ MaterialEditorView::MaterialEditorView(QWidget *parent)
m_stackedWidget->setMinimumWidth(250);
}
void MaterialEditorView::ensureMaterialLibraryNode()
{
if (!m_hasQuick3DImport)
return;
m_materialLibrary = modelNodeForId(Constants::MATERIAL_LIB_ID);
if (m_materialLibrary.isValid())
return;
// create material library node
TypeName nodeType = rootModelNode().isSubclassOf("QtQuick3D.Node") ? "QtQuick3D.Node" : "QtQuick.Item";
NodeMetaInfo metaInfo = model()->metaInfo(nodeType);
m_materialLibrary = createModelNode(nodeType, metaInfo.majorVersion(), metaInfo.minorVersion());
m_materialLibrary.setIdWithoutRefactoring(Constants::MATERIAL_LIB_ID);
rootModelNode().defaultNodeListProperty().reparentHere(m_materialLibrary);
const QList<ModelNode> materials = rootModelNode().subModelNodesOfType("QtQuick3D.Material");
if (materials.isEmpty())
return;
RewriterTransaction transaction = beginRewriterTransaction(
"MaterialEditorView::ensureMaterialLibraryNode");
try {
// move all materials to under material library node
for (const ModelNode &node : materials) {
// if material has no name, set name to id
QString matName = node.variantProperty("objectName").value().toString();
if (matName.isEmpty()) {
VariantProperty objNameProp = node.variantProperty("objectName");
objNameProp.setValue(node.id());
}
m_materialLibrary.defaultNodeListProperty().reparentHere(node);
}
} catch (Exception &e) {
e.showException();
}
}
MaterialEditorView::~MaterialEditorView()
{
qDeleteAll(m_qmlBackendHash);
@@ -447,15 +406,13 @@ void MaterialEditorView::handleToolBarAction(int action)
}
case MaterialEditorContextObject::AddNewMaterial: {
ensureMaterialLibraryNode();
executeInTransaction("MaterialEditorView:handleToolBarAction", [&] {
NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.DefaultMaterial");
ModelNode newMatNode = createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(),
metaInfo.minorVersion());
renameMaterial(newMatNode, "New Material");
m_materialLibrary.defaultNodeListProperty().reparentHere(newMatNode);
materialLibraryNode().defaultNodeListProperty().reparentHere(newMatNode);
});
break;
}
@@ -759,8 +716,6 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material)
{
QTC_ASSERT(material.isValid(), return);
ensureMaterialLibraryNode();
TypeName matType = material.type();
QmlObjectNode sourceMat(material);
@@ -786,7 +741,7 @@ void MaterialEditorView::duplicateMaterial(const ModelNode &material)
duplicateMat.setBindingProperty(prop.name(), prop.toBindingProperty().expression());
}
m_materialLibrary.defaultNodeListProperty().reparentHere(duplicateMat);
materialLibraryNode().defaultNodeListProperty().reparentHere(duplicateMat);
});
}

View File

@@ -99,7 +99,6 @@ private:
void highlightSupportedProperties(bool highlight = true);
QString generateIdFromName(const QString &name);
void ensureMaterialLibraryNode();
void requestPreviewRender();
void applyMaterialToSelectedModels(const ModelNode &material, bool add = false);
@@ -115,7 +114,6 @@ private:
bool noValidSelection() const;
ModelNode m_selectedMaterial;
ModelNode m_materialLibrary;
QShortcut *m_updateShortcut = nullptr;
int m_timerId = 0;
QStackedWidget *m_stackedWidget = nullptr;

View File

@@ -686,18 +686,31 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
bool validContainer = false;
ModelNode targetNode = targetProperty.parentModelNode();
// don't allow dropping materials on any node but Models
QString itemType = QString::fromLatin1(itemLibraryEntry.typeName());
if (itemType.startsWith("QtQuick3D.") && itemType.endsWith("Material")
&& !targetNode.isSubclassOf("QtQuick3D.Model")) {
return;
}
QmlObjectNode newQmlObjectNode;
m_view->executeInTransaction("NavigatorTreeModel::handleItemLibraryItemDrop", [&] {
newQmlObjectNode = QmlItemNode::createQmlObjectNode(m_view, itemLibraryEntry, QPointF(), targetProperty, false);
ModelNode newModelNode = newQmlObjectNode.modelNode();
if (newModelNode.isValid()) {
if (newModelNode.isSubclassOf("QtQuick3D.Material")) {
// Don't allow dropping materials on any node but Models
if (!targetNode.isSubclassOf("QtQuick3D.Model")) {
newQmlObjectNode.destroy();
return;
}
// We can't have material initially parented if material library is created in this
// same transaction (rewriter will not allow it for some reason)
ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID);
if (!matLib.isValid()) {
newQmlObjectNode.destroy();
newQmlObjectNode = QmlItemNode::createQmlObjectNode(
m_view, itemLibraryEntry, QPointF(), NodeAbstractProperty(), false);
newModelNode = newQmlObjectNode.modelNode();
if (!newModelNode.isValid())
return;
}
m_view->assignMaterialTo3dModel(targetNode, newModelNode);
}
ChooseFromPropertyListDialog *dialog = ChooseFromPropertyListDialog::createIfNeeded(
targetNode, newModelNode, Core::ICore::dialogParent());
if (dialog) {
@@ -734,28 +747,10 @@ void NavigatorTreeModel::handleItemLibraryItemDrop(const QMimeData *mimeData, in
if (newModelNode.isSubclassOf("QtQuick3D.View3D")) {
const QList<ModelNode> models = newModelNode.subModelNodesOfType("QtQuick3D.Model");
QTC_ASSERT(models.size() == 1, return);
assignMaterialToModel(models.at(0));
m_view->assignMaterialTo3dModel(models.at(0));
} else if (newModelNode.isSubclassOf("QtQuick3D.Model")) {
assignMaterialToModel(newModelNode);
}
// dropping a material on a model
if (newModelNode.isSubclassOf("QtQuick3D.Material")
&& targetNode.isSubclassOf("QtQuick3D.Model")) {
// parent material to material library and assign it to target model
ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID);
QTC_ASSERT(matLib.isValid(), return);
VariantProperty objName = newModelNode.variantProperty("objectName");
objName.setValue("New Material");
BindingProperty matsProp = targetNode.bindingProperty("materials");
matsProp.setExpression(newModelNode.id());
matLib.defaultNodeListProperty().reparentHere(newModelNode);
return;
m_view->assignMaterialTo3dModel(newModelNode);
}
if (!validContainer) {
@@ -1089,40 +1084,6 @@ ModelNode NavigatorTreeModel::createTextureNode(const NodeAbstractProperty &targ
return {};
}
// Add a material to a Quick3D.Model node
void NavigatorTreeModel::assignMaterialToModel(const ModelNode &node)
{
ModelNode matLib = m_view->modelNodeForId(Constants::MATERIAL_LIB_ID);
QTC_ASSERT(matLib.isValid(), return);
QTC_ASSERT(node.isSubclassOf("QtQuick3D.Model"), return);
const QList<ModelNode> materials = matLib.directSubModelNodes();
ModelNode material;
if (materials.size() > 0) {
for (const ModelNode &mat : materials) {
if (mat.isSubclassOf("QtQuick3D.Material")) {
material = mat;
break;
}
}
}
// if no valid material, create a new default material
if (!material.isValid()) {
NodeMetaInfo metaInfo = m_view->model()->metaInfo("QtQuick3D.DefaultMaterial");
material = m_view->createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(),
metaInfo.minorVersion());
VariantProperty matNameProp = material.variantProperty("objectName");
matNameProp.setValue("New Material");
material.validId();
matLib.defaultNodeListProperty().reparentHere(material);
}
BindingProperty modelMatsProp = node.bindingProperty("materials");
modelMatsProp.setExpression(material.id());
}
TypeName propertyType(const NodeAbstractProperty &property)
{
return property.parentModelNode().metaInfo().propertyTypeName(property.name());

View File

@@ -130,7 +130,6 @@ private:
bool dropAsImage3dTexture(const ModelNode &targetNode, const NodeAbstractProperty &targetProp,
const QString &imagePath, ModelNode &newNode, bool &outMoveNodesAfter);
ModelNode createTextureNode(const NodeAbstractProperty &targetProp, const QString &imagePath);
void assignMaterialToModel(const ModelNode &node);
QList<QPersistentModelIndex> nodesToPersistentIndex(const QList<ModelNode> &modelNodes);
void addImport(const QString &importName);
QList<ModelNode> filteredList(const NodeListProperty &property, bool filter, bool reverseOrder) const;

View File

@@ -259,6 +259,9 @@ public:
void changeRootNodeType(const TypeName &type, int majorVersion, int minorVersion);
ModelNode materialLibraryNode();
void assignMaterialTo3dModel(const ModelNode &modelNode, const ModelNode &materialNode = {});
NodeInstanceView *nodeInstanceView() const;
RewriterView *rewriterView() const;

View File

@@ -31,6 +31,10 @@
#include "nodeinstanceview.h"
#include <qmlstate.h>
#include <qmltimeline.h>
#include <qmldesignerconstants.h>
#include <nodelistproperty.h>
#include <variantproperty.h>
#include <bindingproperty.h>
#ifndef QMLDESIGNER_TEST
#include <qmldesignerplugin.h>
@@ -805,6 +809,94 @@ void AbstractView::changeRootNodeType(const TypeName &type, int majorVersion, in
m_model.data()->d->changeRootNodeType(type, majorVersion, minorVersion);
}
// Returns ModelNode for project's material library.
// If the material library doesn't exist yet, it is created and all existing materials are moved
// under material library.
// This function should be called only form inside a transaction, as it potentially does many
// changes to model.
ModelNode AbstractView::materialLibraryNode()
{
ModelNode matLib = modelNodeForId(Constants::MATERIAL_LIB_ID);
if (matLib.isValid())
return matLib;
// Create material library node
TypeName nodeType = rootModelNode().isSubclassOf("QtQuick3D.Node") ? "QtQuick3D.Node"
: "QtQuick.Item";
NodeMetaInfo metaInfo = model()->metaInfo(nodeType);
matLib = createModelNode(nodeType, metaInfo.majorVersion(), metaInfo.minorVersion());
matLib.setIdWithoutRefactoring(Constants::MATERIAL_LIB_ID);
rootModelNode().defaultNodeListProperty().reparentHere(matLib);
const QList<ModelNode> materials = rootModelNode().subModelNodesOfType("QtQuick3D.Material");
if (materials.isEmpty())
return matLib;
// Move all materials to under material library node
for (const ModelNode &node : materials) {
// If material has no name, set name to id
QString matName = node.variantProperty("objectName").value().toString();
if (matName.isEmpty()) {
VariantProperty objNameProp = node.variantProperty("objectName");
objNameProp.setValue(node.id());
}
matLib.defaultNodeListProperty().reparentHere(node);
}
return matLib;
}
// Assigns given material to a 3D model.
// The assigned material is also inserted into material library if not already there.
// If given material is not valid, first existing material from material library is used,
// or if material library is empty, a new material is created.
// This function should be called only from inside a transaction, as it potentially does many
// changes to model.
void AbstractView::assignMaterialTo3dModel(const ModelNode &modelNode, const ModelNode &materialNode)
{
QTC_ASSERT(modelNode.isValid() && modelNode.isSubclassOf("QtQuick3D.Model"), return);
ModelNode matLib = materialLibraryNode();
ModelNode newMaterialNode;
if (materialNode.isValid() && materialNode.isSubclassOf("QtQuick3D.Material")) {
newMaterialNode = materialNode;
} else {
const QList<ModelNode> materials = matLib.directSubModelNodes();
if (materials.size() > 0) {
for (const ModelNode &mat : materials) {
if (mat.isSubclassOf("QtQuick3D.Material")) {
newMaterialNode = mat;
break;
}
}
}
// if no valid material, create a new default material
if (!newMaterialNode.isValid()) {
NodeMetaInfo metaInfo = model()->metaInfo("QtQuick3D.DefaultMaterial");
newMaterialNode = createModelNode("QtQuick3D.DefaultMaterial", metaInfo.majorVersion(),
metaInfo.minorVersion());
newMaterialNode.validId();
}
}
QTC_ASSERT(newMaterialNode.isValid(), return);
VariantProperty matNameProp = newMaterialNode.variantProperty("objectName");
if (matNameProp.value().isNull())
matNameProp.setValue("New Material");
if (!newMaterialNode.hasParentProperty()
|| newMaterialNode.parentProperty() != matLib.defaultNodeListProperty()) {
matLib.defaultNodeListProperty().reparentHere(newMaterialNode);
}
BindingProperty modelMatsProp = modelNode.bindingProperty("materials");
modelMatsProp.setExpression(newMaterialNode.id());
}
ModelNode AbstractView::currentStateNode() const
{
if (model())