/**************************************************************************** ** ** Copyright (C) 2017 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt 3D Studio. ** ** $QT_BEGIN_LICENSE:GPL-EXCEPT$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "InspectorControlModel.h" #include "StudioApp.h" #include "Core.h" #include "Doc.h" #include "InspectorGroup.h" #include "Qt3DSDMInspectorGroup.h" #include "Qt3DSDMInspectorRow.h" #include "Qt3DSDMStudioSystem.h" #include "Qt3DSDMInspectable.h" #include "Qt3DSDMDataCore.h" #include "IDocumentEditor.h" #include "Qt3DSDMMetaData.h" #include "Qt3DSDMSignals.h" #include "CmdDataModelDeanimate.h" #include "GuideInspectable.h" #include "Qt3DSDMDataTypes.h" #include "IObjectReferenceHelper.h" #include "SlideSystem.h" #include "Qt3DSDMMaterialInspectable.h" #include "ClientDataModelBridge.h" #include "IDocumentReader.h" #include "IStudioRenderer.h" #include "Dialogs.h" #include "Dispatch.h" #include "VariantsGroupModel.h" #include "StudioProjectSettings.h" #include "Literals.h" #include static QStringList renderableItems() { QStringList renderables; renderables.push_back(QObject::tr("No renderable item")); const CDoc *doc = g_StudioApp.GetCore()->GetDoc(); Q3DStudio::CString docDir = Q3DStudio::CString::fromQString(doc->GetDocumentDirectory()); for (SubPresentationRecord r : qAsConst(g_StudioApp.m_subpresentations)) renderables.push_back(r.m_id); // second step, find the renderable plugins. { Q3DStudio::CFilePath pluginDir = Q3DStudio::CFilePath::CombineBaseAndRelative(docDir, "plugins"); if (pluginDir.Exists() && pluginDir.IsDirectory()) { std::vector dirFiles; pluginDir.ListFilesAndDirectories(dirFiles); for (size_t idx = 0, end = dirFiles.size(); idx < end; ++idx) { if (dirFiles[idx].IsFile()) { Q3DStudio::CFilePath relPath = Q3DStudio::CFilePath::GetRelativePathFromBase(docDir, dirFiles[idx]); renderables.push_back(relPath.toQString()); } } } } std::sort(renderables.begin() + 1, renderables.end()); return renderables; } static std::pair getSlideCharacteristics(qt3dsdm::Qt3DSDMInstanceHandle instance, const qt3dsdm::ISlideCore &slideCore, const qt3dsdm::ISlideSystem &slideSystem) { // Get the slide from the instance. qt3dsdm::Qt3DSDMSlideHandle slide = slideCore.GetSlideByInstance(instance); qt3dsdm::Qt3DSDMSlideHandle master = slideSystem.GetMasterSlide(slide); int index = int(slideSystem.GetSlideIndex(slide)); int count = int(slideSystem.GetSlideCount(master)); bool hasNextSlide = index > 0 && index < count - 1; bool hasPreviousSlide = index > 1; return std::make_pair(hasNextSlide, hasPreviousSlide); } InspectorControlModel::InspectorControlModel(VariantsGroupModel *variantsModel, QObject *parent) : QAbstractListModel(parent) , m_UpdatableEditor(*g_StudioApp.GetCore()->GetDoc()) , m_variantsModel(variantsModel) { m_modifiedProperty.first = 0; m_modifiedProperty.second = 0; } void InspectorControlModel::setInspectable(CInspectableBase *inInspectable) { // Commit any pending transactions on selection change m_UpdatableEditor.CommitEditor(); m_modifiedProperty.first = 0; m_modifiedProperty.second = 0; m_previouslyCommittedValue = {}; if (m_inspectableBase != inInspectable) { m_inspectableBase = inInspectable; rebuildTree(); } } CInspectableBase *InspectorControlModel::inspectable() const { return m_inspectableBase; } qt3dsdm::Qt3DSDMInstanceHandle InspectorControlModel::getReferenceMaterial( CInspectableBase *inspectable) const { if (inspectable) return getBridge()->getMaterialReference(inspectable->getInstance()); return 0; } void InspectorControlModel::notifyPropertyChanged(qt3dsdm::Qt3DSDMInstanceHandle inInstance, qt3dsdm::Qt3DSDMPropertyHandle inProperty) { auto doc = g_StudioApp.GetCore()->GetDoc(); if (!getBridge()->IsSceneGraphInstance(inInstance)) return; if (inProperty == getBridge()->getVariantsProperty(inInstance)) { // variants model is updated upon edit but this is needed to handle undoing m_variantsModel->refresh(); return; } bool changed = false; for (int row = 0; row < m_groupElements.count(); ++row) { auto group = m_groupElements[row]; for (int p = 0; p < group.controlElements.count(); ++p) { QVariant& element = group.controlElements[p]; InspectorControlBase *property = element.value(); qt3dsdm::Qt3DSDMInstanceHandle imageInstance; if (property->m_dataType == qt3dsdm::DataModelDataType::Long4 && property->m_property.Valid()) { imageInstance = doc->GetDocumentReader().GetImageInstanceForProperty( property->m_instance, property->m_property); } if (property->m_property == inProperty || imageInstance == inInstance) { updatePropertyValue(property); changed = true; } } } if (changed) Q_EMIT dataChanged(index(0), index(rowCount() - 1)); } bool InspectorControlModel::hasInstanceProperty(qt3dsdm::Qt3DSDMInstanceHandle instance, qt3dsdm::Qt3DSDMPropertyHandle handle) const { for (const auto &group : qAsConst(m_groupElements)) { for (const auto &element : qAsConst(group.controlElements)) { InspectorControlBase *property = element.value(); if (property->m_property == handle) { auto refInstance = getBridge()->getMaterialReference(property->m_instance); return property->m_instance == instance || refInstance == instance; } } } return false; } bool InspectorControlModel::isInsideMaterialContainer() const { return isInsideMaterialContainer(m_inspectableBase); } bool InspectorControlModel::isInsideMaterialContainer(CInspectableBase *inspectable) const { if (!inspectable || !inspectable->getInstance()) return false; return getBridge()->isInsideMaterialContainer(inspectable->getInstance()); } bool InspectorControlModel::isDefaultMaterial() const { if (m_inspectableBase) return getBridge()->isDefaultMaterial(m_inspectableBase->getInstance()); return false; } bool InspectorControlModel::isAnimatableMaterial() const { return isAnimatableMaterial(m_inspectableBase); } bool InspectorControlModel::isAnimatableMaterial(CInspectableBase *inspectable) const { if (!inspectable) return false; return inspectable->getObjectType() & (OBJTYPE_CUSTOMMATERIAL | OBJTYPE_MATERIAL); } bool InspectorControlModel::isBasicMaterial() const { return isBasicMaterial(m_inspectableBase); } bool InspectorControlModel::isBasicMaterial(CInspectableBase *inspectable) const { if (!inspectable) return false; if (inspectable->getObjectType() == OBJTYPE_REFERENCEDMATERIAL) { const auto instance = inspectable->getInstance(); if (!instance.Valid()) return false; const auto refMaterial = getBridge()->getMaterialReference(instance); if (refMaterial.Valid() && getBridge()->isInsideMaterialContainer(refMaterial)) return true; } return false; } bool InspectorControlModel::isMaterial() const { if (m_inspectableBase) return m_inspectableBase->getObjectType() & OBJTYPE_IS_MATERIAL; return false; } void InspectorControlModel::addMaterial() { const auto doc = g_StudioApp.GetCore()->GetDoc(); qt3dsdm::Qt3DSDMInstanceHandle instance = m_inspectableBase->getInstance(); if (!instance.Valid()) return; QString path = doc->getSceneEditor()->getMaterialDirectoryPath() + QStringLiteral("/Material"); QString extension = QStringLiteral(".materialdef"); auto absPath = path + extension; int i = 1; while (QFileInfo(absPath).exists()) { i++; absPath = path + QString::number(i) + extension; } qt3dsdm::Qt3DSDMInstanceHandle newMaterial; { newMaterial = Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, {}) ->getOrCreateMaterial(absPath, false); } // Several aspects of the editor are not updated correctly // if the data core is changed without a transaction // The above scope completes the transaction for creating a new material // Next the added undo has to be popped from the stack // TODO: Find a way to update the editor fully without a transaction g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo(); saveIfMaterial(newMaterial); Q3DStudio::ScopedDocumentEditor sceneEditor( Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type"))); doc->SelectDataModelObject(newMaterial); const auto type = getBridge()->GetObjectType(instance); if (type == OBJTYPE_REFERENCEDMATERIAL) { sceneEditor->setMaterialReferenceByPath(instance, absPath); const auto relPath = doc->GetRelativePathToDoc(absPath); sceneEditor->setMaterialSourcePath(instance, Q3DStudio::CString::fromQString(relPath)); sceneEditor->SetName(instance, getBridge()->GetName(newMaterial, true)); doc->GetStudioSystem()->GetFullSystemSignalSender()->SendInstancePropertyValue( instance, getBridge()->GetNameProperty()); } } void InspectorControlModel::duplicateMaterial() { qt3dsdm::Qt3DSDMInstanceHandle instance = m_inspectableBase->getInstance(); if (!instance.Valid()) return; const auto doc = g_StudioApp.GetCore()->GetDoc(); const auto type = m_inspectableBase->getObjectType(); if (type & ~OBJTYPE_IS_MATERIAL) return; auto material = instance; if (type == OBJTYPE_REFERENCEDMATERIAL) material = getReferenceMaterial(m_inspectableBase); if (material.Valid()) { const auto sceneEditor = doc->getSceneEditor(); auto originalMaterialName = sceneEditor->GetName(material).toQString() + QStringLiteral(" Copy"); int slashIndex = originalMaterialName.lastIndexOf(QLatin1Char('/')); if (slashIndex != -1) originalMaterialName = originalMaterialName.mid(slashIndex + 1); auto materialName = originalMaterialName; int i = 1; auto absPath = sceneEditor->getMaterialFilePath(materialName); while (QFileInfo(absPath).exists()) { i++; materialName = originalMaterialName + QString::number(i); absPath = sceneEditor->getMaterialFilePath(materialName); } qt3dsdm::Qt3DSDMInstanceHandle duplicate; { Q3DStudio::ScopedDocumentEditor scopedEditor( Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, {})); duplicate = scopedEditor->getOrCreateMaterial(materialName, false); scopedEditor->copyMaterialProperties(material, duplicate); } // Several aspects of the editor are not updated correctly // if the data core is changed without a transaction // The above scope completes the transaction for creating a new material // Next the added undo has to be popped from the stack // TODO: Find a way to update the editor fully without a transaction g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo(); saveIfMaterial(duplicate); Q3DStudio::ScopedDocumentEditor scopedEditor( Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type"))); doc->SelectDataModelObject(duplicate); if (type == OBJTYPE_REFERENCEDMATERIAL) { scopedEditor->setMaterialReferenceByPath(instance, absPath); const auto relPath = doc->GetRelativePathToDoc(absPath); scopedEditor->setMaterialSourcePath(instance, Q3DStudio::CString::fromQString(relPath)); scopedEditor->SetName(instance, getBridge()->GetName(duplicate, true)); doc->GetStudioSystem()->GetFullSystemSignalSender()->SendInstancePropertyValue( instance, getBridge()->GetNameProperty()); } } } void InspectorControlModel::updateMaterialValues(const QStringList &values, int elementIndex, bool updatingShaders) { // Find if there are any material items and update the values of those int startIndex = 0; bool isReferenced = !isAnimatableMaterial() && updatingShaders; if (isReferenced && m_groupElements.count() > 0) startIndex = m_groupElements.count() - 1; // Update the last group for referenced materials for (int row = startIndex; row < m_groupElements.count(); ++row) { const CInspectorGroup *inspectorGroup = m_inspectableBase->getGroup(row); const auto group = dynamic_cast(inspectorGroup); const auto materialGroup = dynamic_cast(group); if (materialGroup && (materialGroup->isMaterialGroup() || isReferenced)) { if (m_groupElements[row].controlElements.size()) { auto item = m_groupElements[row].controlElements[elementIndex] .value(); item->m_values = values; Q_EMIT item->valuesChanged(); // Changing values resets the selected index, so pretend the value has also changed Q_EMIT item->valueChanged(); } } } } void InspectorControlModel::updateShaderValues() { int index = 0; if (isAnimatableMaterial() && !isInsideMaterialContainer()) index = 1; updateMaterialValues(shaderValues(), index, true); } void InspectorControlModel::updateMatDataValues() { int index = 0; if (!isInsideMaterialContainer()) index = 1; updateMaterialValues(matDataValues(), index); } void InspectorControlModel::setMaterials(std::vector &materials) { m_materials.clear(); for (Q3DStudio::CFilePath &path : materials) { const QString absolutePath = g_StudioApp.GetCore()->GetDoc()->GetResolvedPathToDoc(path); const QString name = g_StudioApp.GetCore()->GetDoc()->GetDocumentReader() .GetCustomMaterialName(absolutePath).toQString(); m_materials.push_back({name, path.toQString()}); } if (!isDefaultMaterial()) updateShaderValues(); } void InspectorControlModel::setMatDatas(const std::vector &matDatas) { m_matDatas.clear(); const auto doc = g_StudioApp.GetCore()->GetDoc(); bool isDocModified = doc->isModified(); const auto sceneEditor = doc->getSceneEditor(); if (!sceneEditor) return; bool newMaterialSelected = false; for (const Q3DStudio::CFilePath &path : matDatas) { bool isNewFile = true; for (auto &oldPath : m_cachedMatDatas) { if (path.toQString() == oldPath.toQString()) { isNewFile = false; break; } } const QString relativePath = path.toQString(); const Q3DStudio::CFilePath absolutePath = Q3DStudio::CFilePath::CombineBaseAndRelative(doc->GetDocumentDirectory(), path); QString name; QMap values; QMap> textureValues; sceneEditor->getMaterialInfo( absolutePath.toQString(), name, values, textureValues); m_matDatas.push_back({name, relativePath, values, textureValues}); bool needRewrite = false; if (values.contains(QStringLiteral("path"))) { const QString oldPath = values[QStringLiteral("path")]; needRewrite = oldPath != absolutePath.toQString(); if (!QFileInfo(oldPath).exists()) { const auto instance = sceneEditor->getMaterial(oldPath); if (instance.Valid()) { const QString oldName = sceneEditor->GetName(instance).toQString(); const QString newName = sceneEditor ->getMaterialNameFromFilePath(relativePath); const QString actualPath = sceneEditor ->getFilePathFromMaterialName(oldName); if (actualPath == oldPath) { doc->queueMaterialRename(oldName, newName); Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Name")) ->setMaterialNameByPath(instance, relativePath); } } } } auto material = sceneEditor->getMaterial(relativePath); if (isNewFile && !newMaterialSelected && !material.Valid()) { { material = Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QString()) ->getOrCreateMaterial(relativePath, false); } // Several aspects of the editor are not updated correctly // if the data core is changed without a transaction // The above scope completes the transaction for creating a new material // Next the added undo has to be popped from the stack // TODO: Find a way to update the editor fully without a transaction g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo(); } if (material.Valid()) sceneEditor->setMaterialValues(relativePath, values, textureValues); if (isNewFile && !newMaterialSelected) { doc->SelectDataModelObject(material); newMaterialSelected = true; } if (needRewrite && material.Valid()) sceneEditor->writeMaterialFile(material, name, false, absolutePath.toQString()); } if (isBasicMaterial()) updateMatDataValues(); sceneEditor->removeDeletedFromMaterialContainer(); // Modified flag has to be restored because of the hidden transaction doc->SetModifiedFlag(isDocModified); m_cachedMatDatas = matDatas; } QString InspectorControlModel::getBasicMaterialString() const { return QObject::tr("Basic Material"); } QString InspectorControlModel::getAnimatableMaterialString() const { return QObject::tr("Animatable Material"); } QString InspectorControlModel::getReferencedMaterialString() const { return QObject::tr("Referenced Material"); } QString InspectorControlModel::getStandardMaterialString() const { return QObject::tr("Standard"); } QString InspectorControlModel::getDefaultMaterialString() const { return QObject::tr("Default"); } bool InspectorControlModel::isGroupCollapsed(int groupIdx) const { if (m_inspectableBase) { auto instance = m_inspectableBase->getInstance(); if (instance && groupIdx > -1 && groupIdx < m_groupElements.size() && m_collapseMap.contains(instance)) { return m_collapseMap[instance].contains(groupIdx); } } return false; } void InspectorControlModel::updateGroupCollapseState(int groupIdx, bool isCollapsed) { if (m_inspectableBase) { auto instance = m_inspectableBase->getInstance(); if (instance && groupIdx > -1 && groupIdx < m_groupElements.size()) { if (isCollapsed) m_collapseMap[instance][groupIdx] = true; else m_collapseMap[instance].remove(groupIdx); } } } void InspectorControlModel::updateFontValues(InspectorControlBase *element) const { // Find if there are any font items and update the values of those QVector fontElements; if (element) { fontElements.append(element); } else { for (int row = 0; row < m_groupElements.count(); ++row) { auto group = m_groupElements[row]; for (int p = 0; p < group.controlElements.count(); ++p) { QVariant &element = group.controlElements[p]; InspectorControlBase *property = element.value(); if (property->m_propertyType == qt3dsdm::AdditionalMetaDataType::Font) fontElements.append(property); } } } if (fontElements.size()) { std::vector fontNames; g_StudioApp.GetCore()->GetDoc()->GetProjectFonts(fontNames); QStringList possibleValues; for (const auto &fontName : fontNames) possibleValues.append(fontName); for (auto fontElement : qAsConst(fontElements)) { fontElement->m_values = possibleValues; Q_EMIT fontElement->valuesChanged(); // Changing values resets the selected index, so pretend the value has also changed Q_EMIT fontElement->valueChanged(); } } } QStringList InspectorControlModel::materialTypeValues() const { QStringList values; values.push_back(getBasicMaterialString()); values.push_back(getAnimatableMaterialString()); values.push_back(getReferencedMaterialString()); return values; } QStringList InspectorControlModel::shaderValues() const { QStringList values; values.push_back(getStandardMaterialString()); for (size_t matIdx = 0, end = m_materials.size(); matIdx < end; ++matIdx) values.push_back(m_materials[matIdx].m_name); return values; } QStringList InspectorControlModel::matDataValues() const { QStringList values; QStringList names; const QString defaultMaterialShownName = getDefaultMaterialString(); values.push_back(defaultMaterialShownName); names.push_back(defaultMaterialShownName); for (size_t matIdx = 0, end = m_matDatas.size(); matIdx < end; ++matIdx) { QString shownName = m_matDatas[matIdx].m_name; int slashIndex = shownName.lastIndexOf(QLatin1Char('/')); if (slashIndex != -1) shownName = shownName.mid(slashIndex + 1); if (names.contains(shownName)) shownName += QLatin1String(" (") + m_matDatas[matIdx].m_relativePath + QLatin1Char(')'); else names.push_back(shownName); values.push_back(shownName); } return values; } InspectorControlBase *InspectorControlModel::createMaterialTypeItem( Qt3DSDMInspectable *inspectable, int groupIndex) { InspectorControlBase *item = new InspectorControlBase; item->m_instance = inspectable->GetGroupInstance(groupIndex); item->m_title = tr("Material Type"); item->m_dataType = qt3dsdm::DataModelDataType::StringRef; item->m_propertyType = qt3dsdm::AdditionalMetaDataType::None; item->m_tooltip = tr("Type of material being used"); item->m_animatable = false; const QStringList values = materialTypeValues(); item->m_values = values; QString sourcePath = getBridge()->GetSourcePath(item->m_instance); switch (inspectable->getObjectType()) { case OBJTYPE_MATERIAL: case OBJTYPE_CUSTOMMATERIAL: item->m_value = getAnimatableMaterialString(); break; case OBJTYPE_REFERENCEDMATERIAL: item->m_value = getReferencedMaterialString(); if (sourcePath == getBridge()->getDefaultMaterialName()) item->m_value = getBasicMaterialString(); for (int matIdx = 0, end = int(m_matDatas.size()); matIdx < end; ++matIdx) { if (QString::compare(m_matDatas[matIdx].m_relativePath, sourcePath, Qt::CaseInsensitive) == 0) { item->m_value = getBasicMaterialString(); } } break; default: break; } return item; } InspectorControlBase *InspectorControlModel::createShaderItem( Qt3DSDMInspectable *inspectable, int groupIndex) { InspectorControlBase *item = new InspectorControlBase; item->m_instance = inspectable->GetGroupInstance(groupIndex); item->m_title = tr("Shader"); item->m_dataType = qt3dsdm::DataModelDataType::StringRef; item->m_propertyType = qt3dsdm::AdditionalMetaDataType::Renderable; item->m_tooltip = tr("Shader being used"); item->m_animatable = false; const QStringList values = shaderValues(); item->m_values = values; QString sourcePath = getBridge()->GetSourcePath(item->m_instance); item->m_value = values[0]; for (int matIdx = 0, end = int(m_materials.size()); matIdx < end; ++matIdx) { if (m_materials[matIdx].m_relativePath == sourcePath) item->m_value = values[matIdx + 1]; } return item; } InspectorControlBase *InspectorControlModel::createMatDataItem( Qt3DSDMInspectable *inspectable, int groupIndex) { InspectorControlBase *item = new InspectorControlBase; item->m_instance = inspectable->GetGroupInstance(groupIndex); item->m_title = tr("Source Material"); item->m_dataType = qt3dsdm::DataModelDataType::StringRef; item->m_propertyType = qt3dsdm::AdditionalMetaDataType::ObjectRef; item->m_tooltip = tr("Source material definitions used"); item->m_animatable = false; const QStringList values = matDataValues(); item->m_values = values; QString sourcePath = getBridge()->GetSourcePath(item->m_instance); item->m_value = getDefaultMaterialString(); for (int matIdx = 0, end = int(m_matDatas.size()); matIdx < end; ++matIdx) { if (QString::compare(m_matDatas[matIdx].m_relativePath, sourcePath, Qt::CaseInsensitive) == 0) { item->m_value = values[matIdx + 1]; // + 1 for Default basic material } } return item; } InspectorControlBase* InspectorControlModel::createItem(Qt3DSDMInspectable *inspectable, Q3DStudio::Qt3DSDMInspectorRow *row, int groupIndex) { return createItem(inspectable, row->GetMetaDataPropertyInfo(), groupIndex); } InspectorControlBase* InspectorControlModel::createItem(Qt3DSDMInspectable *inspectable, const qt3dsdm::SMetaDataPropertyInfo &metaProperty, int groupIndex) { const auto studio = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); if (metaProperty.m_IsHidden) return nullptr; Q3DStudio::CString title; title.Assign(metaProperty.m_FormalName.c_str()); if (title.IsEmpty()) title.Assign(metaProperty.m_Name.c_str()); // Hide name for basic materials if (title == "Name" && isBasicMaterial()) return nullptr; InspectorControlBase *item = new InspectorControlBase; item->m_property = metaProperty.m_Property; item->m_instance = inspectable->GetGroupInstance(groupIndex); item->m_metaProperty = metaProperty; item->m_title = title.toQString(); const auto propertySystem = studio->GetPropertySystem(); item->m_dataType = propertySystem->GetDataType(metaProperty.m_Property); item->m_propertyType = static_cast (propertySystem->GetAdditionalMetaDataType(item->m_instance, metaProperty.m_Property)); item->m_tooltip = Q3DStudio::CString(metaProperty.m_Description.c_str()).toQString(); // \n is parsed as \\n from the material and effect files. Replace them to fix multi-line // tooltips item->m_tooltip.replace(QLatin1String("\\n"), QLatin1String("\n")); item->m_animatable = metaProperty.m_Animatable && studio->GetAnimationSystem()->IsPropertyAnimatable(item->m_instance, metaProperty.m_Property); // If a property is animatable, it should be controllable in addition to // properties explicitly set as controllable in metadata item->m_controllable = item->m_animatable || metaProperty.m_Controllable; // disable IBL Override for reference and 'default basic' materials if (item->m_title == QLatin1String("IBL Override") && getBridge()->GetObjectType(item->m_instance) == OBJTYPE_REFERENCEDMATERIAL && (!isBasicMaterial() || getBridge()->isDefaultMaterial(item->m_instance))) { item->m_enabled = false; } auto signalProvider = studio->GetFullSystemSignalProvider(); if (item->m_animatable) { item->m_animated = studio->GetAnimationSystem()->IsPropertyAnimated(item->m_instance, metaProperty.m_Property); // Update the Animate Toggle on undo/redo item->m_connections.push_back(signalProvider->ConnectAnimationCreated( std::bind(&InspectorControlModel::updateAnimateToggleState, this, item))); item->m_connections.push_back(signalProvider->ConnectAnimationDeleted( std::bind(&InspectorControlModel::updateAnimateToggleState, this, item))); } if (item->m_controllable) { // Set the name of current controller item->m_controller = currentControllerValue(item->m_instance, item->m_property); // Update UI icon state and tooltip updateControlledToggleState(item); item->m_connections.push_back(signalProvider->ConnectControlledToggled( std::bind(&InspectorControlModel::updateControlledToggleState, this, item))); } // synchronize the value itself updatePropertyValue(item); return item; } qt3dsdm::SValue InspectorControlModel::currentPropertyValue(long instance, int handle) const { auto propertySystem = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetPropertySystem(); qt3dsdm::SValue value; propertySystem->GetInstancePropertyValue(instance, handle, value); return value; } QString InspectorControlModel::currentControllerValue(long instance, int handle) const { return g_StudioApp.GetCore()->GetDoc()->GetCurrentController(instance, handle); } void InspectorControlModel::updateControlledToggleState(InspectorControlBase* inItem) const { if (inItem->m_instance) { const auto studio = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); // toggle if controlledproperty contains the name of this property qt3dsdm::SValue currPropVal = currentPropertyValue( inItem->m_instance, studio->GetPropertySystem()->GetAggregateInstancePropertyByName( inItem->m_instance, qt3dsdm::TCharStr(L"controlledproperty"))); Q3DStudio::CString currPropValStr; if (!currPropVal.empty()) currPropValStr = qt3dsdm::get(currPropVal)->GetData(); // Restore original tool tip from metadata when turning control off if (!currPropValStr.size()) { inItem->m_controlled = false; inItem->m_controller = ""; } else { Q3DStudio::CString propName = studio->GetPropertySystem()->GetName(inItem->m_property).c_str(); // Search specifically for whitespace followed with registered property name. // This avoids finding datainput with same name as the property, as datainput // name is always prepended with "$" long propNamePos = currPropValStr.find(" " + propName); if ((propNamePos == currPropValStr.ENDOFSTRING) && (propNamePos != 0)) { inItem->m_controlled = false; inItem->m_controller = ""; } else { inItem->m_controlled = true; // controller name is prepended with "$" to differentiate from property // with same name. Reverse find specifically for $. long posCtrlr = currPropValStr.substr(0, propNamePos).ReverseFind("$"); // this is the first controller - property pair in controlledproperty if (posCtrlr < 0) posCtrlr = 0; // remove $ from controller name for showing it in UI posCtrlr++; const QString ctrlName = currPropValStr.substr( posCtrlr, propNamePos - posCtrlr).toQString(); inItem->m_controller = ctrlName; } } Q_EMIT inItem->tooltipChanged(); // Emit signal always to trigger updating of controller name in UI // also when user switches from one controller to another Q_EMIT inItem->controlledChanged(); } } void InspectorControlModel::updateAnimateToggleState(InspectorControlBase* inItem) { const auto studio = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem(); bool animated = studio->GetAnimationSystem()->IsPropertyAnimated(inItem->m_instance, inItem->m_property); if (animated != inItem->m_animated) { inItem->m_animated = animated; Q_EMIT inItem->animatedChanged(); } } bool InspectorControlModel::isTreeRebuildRequired(CInspectableBase* inspectBase) { if (inspectBase != m_inspectableBase || !inspectBase) return true; long theCount = m_inspectableBase->getGroupCount(); auto refMaterial = getReferenceMaterial(inspectBase); if (refMaterial != m_refMaterial) return true; long refMaterialGroupCount = 0; if (refMaterial.Valid()) refMaterialGroupCount = 1; // Only the last group of the refMaterial is used if (m_groupElements.size() != theCount + refMaterialGroupCount) return true; for (long theIndex = 0; theIndex < theCount; ++theIndex) { const CInspectorGroup *theInspectorGroup = m_inspectableBase->getGroup(theIndex); if (m_groupElements.at(theIndex).groupTitle != theInspectorGroup->GetName()) return true; } return false; } bool InspectorControlModel::isGroupRebuildRequired(CInspectableBase *inspectable, int theIndex) const { Q_ASSERT(theIndex < m_groupElements.size()); const CInspectorGroup *theInspectorGroup = inspectable->getGroup(theIndex); const auto existingGroup = m_groupElements.at(theIndex); if (existingGroup.groupTitle != theInspectorGroup->GetName()) return true; if (const auto cdmInspectable = dynamic_cast(inspectable)) { int existingIndex = 0; if (const auto group = dynamic_cast(theInspectorGroup)) { const auto materialGroup = dynamic_cast(group); if (materialGroup && materialGroup->isMaterialGroup()) { auto i = existingGroup.controlElements.at(existingIndex++).value(); if (i->m_instance != cdmInspectable->GetGroupInstance(theIndex)) return true; if (!isInsideMaterialContainer()) existingIndex++; // Add material type dropdown to existing elements } if ((existingGroup.controlElements.size() - existingIndex) != group->GetRows().size()) return true; for (const auto row : group->GetRows()) { auto i = existingGroup.controlElements.at(existingIndex++).value(); if (i->m_instance != cdmInspectable->GetGroupInstance(theIndex)) return true; if (i->m_property != row->GetMetaDataPropertyInfo().m_Property) return true; } } } return false; } CClientDataModelBridge *InspectorControlModel::getBridge() const { return g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetClientDataModelBridge(); } auto InspectorControlModel::computeTree(CInspectableBase *inspectBase) -> QVector { QVector result; if (inspectBase) { qt3dsdm::Qt3DSDMInstanceHandle instance = inspectBase->getInstance(); bool isMatFromFile = instance.Valid() && getBridge()->isInsideMaterialContainer(instance); long groupCount = inspectBase->getGroupCount(); for (long idx = 0; idx < groupCount; ++idx) result.append(computeGroup(inspectBase, idx, isMatFromFile, false)); if (isDefaultMaterial() && result.size() > 0) { result[result.size() - 1].groupInfo = tr("\nDefault material cannot be edited.\n\n" "Create new or import material, then apply."); } //Show original material properties for referenced materials auto refMaterial = getReferenceMaterial(inspectBase); if (refMaterial.Valid()) { auto refMaterialInspectable = g_StudioApp.getInspectableFromInstance(refMaterial); if (refMaterialInspectable) { QString materialSrcPath; if (instance.Valid()) materialSrcPath = getBridge()->GetSourcePath(instance); if (materialSrcPath != getBridge()->getDefaultMaterialName() && getBridge()->GetSourcePath(refMaterial) != getBridge()->getDefaultMaterialName()) { result.append(computeGroup(refMaterialInspectable, refMaterialInspectable->getGroupCount() - 1, true, true)); } } } } return result; } auto InspectorControlModel::computeGroup(CInspectableBase *inspectable, int theIndex, bool disableAnimation, bool isReference) -> GroupInspectorControl { CInspectorGroup *theInspectorGroup = inspectable->getGroup(theIndex); GroupInspectorControl result; result.groupTitle = theInspectorGroup->GetName(); result.groupInfo.clear(); if (isReference) result.groupTitle += tr(" (Reference)"); if (const auto cdmInspectable = dynamic_cast(inspectable)) { if (const auto group = dynamic_cast(theInspectorGroup)) { const auto materialGroup = dynamic_cast(group); bool isMatData = isBasicMaterial(cdmInspectable); if (materialGroup && materialGroup->isMaterialGroup()) { InspectorControlBase *item = nullptr; if (!isInsideMaterialContainer(cdmInspectable) && !isReference) { item = createMaterialTypeItem(cdmInspectable, theIndex); if (item) result.controlElements.push_back(QVariant::fromValue(item)); } if (isAnimatableMaterial(cdmInspectable)) { item = createShaderItem(cdmInspectable, theIndex); if (item) result.controlElements.push_back(QVariant::fromValue(item)); } else if (isMatData) { item = createMatDataItem(cdmInspectable, theIndex); if (item) result.controlElements.push_back(QVariant::fromValue(item)); } } for (const auto row : group->GetRows()) { InspectorControlBase *item = createItem(cdmInspectable, row, theIndex); if (!item) continue; if (disableAnimation) item->m_animatable = false; if (!isMatData || item->m_title != getReferencedMaterialString()) result.controlElements.push_back(QVariant::fromValue(item)); } } } else if (const auto guideInspectable = dynamic_cast(inspectable)) { // Guide properties don't come from metadata as they are not actual objects m_guideInspectable = guideInspectable; const auto &properties = m_guideInspectable->properties(); for (int i = 0, count = int(properties.size()); i < count; ++i) { auto &prop = properties[i]; InspectorControlBase *item = new InspectorControlBase; item->m_title = prop->GetInspectableFormalName(); item->m_dataType = prop->GetInspectableType(); item->m_propertyType = prop->GetInspectableAdditionalType(); item->m_tooltip = prop->GetInspectableDescription(); item->m_animatable = false; item->m_controllable = false; item->m_property = i + 1; // Zero property is considered invalid, so +1 result.controlElements.push_back(QVariant::fromValue(item)); updatePropertyValue(item); } } return result; } void InspectorControlModel::rebuildTree() { beginResetModel(); m_guideInspectable = nullptr; QVector deleteVector; for (int i = 0; i < m_groupElements.count(); ++i) { auto group = m_groupElements[i]; for (int p = 0; p < group.controlElements.count(); ++p) deleteVector.append(group.controlElements[p].value()); } m_groupElements = computeTree(m_inspectableBase); endResetModel(); // Clean the old objects after reset is done so that qml will not freak out about null pointers for (int i = 0; i < deleteVector.count(); ++i) deleteVector[i]->deleteLater(); m_refMaterial = getReferenceMaterial(m_inspectableBase); } int InspectorControlModel::rowCount(const QModelIndex &parent) const { if (parent.isValid()) return 0; return m_groupElements.count(); } void InspectorControlModel::updatePropertyValue(InspectorControlBase *element) const { const auto doc = g_StudioApp.GetCore()->GetDoc(); auto studioSystem = doc->GetStudioSystem(); const auto propertySystem = studioSystem->GetPropertySystem(); qt3dsdm::SValue value; auto instance = element->m_instance; qt3dsdm::Option info; // For ref materials update IBL Override from the referenced material. This applies only for // basic materials as IBL Override is disabled for referenced (and default basic) materials. if (element->m_property == getBridge()->GetObjectDefinitions().m_MaterialBase.m_IblProbe.m_Property) { int refInstance = getBridge()->getMaterialReference(instance); if (refInstance) instance = refInstance; } if (m_guideInspectable) { value = m_guideInspectable->properties() [handleToGuidePropIndex(element->m_property)]->GetInspectableData(); } else { if (!propertySystem->HandleValid(instance)) return; propertySystem->GetInstancePropertyValue(instance, element->m_property, value); if (value.getType() == qt3dsdm::DataModelDataType::None) return; const auto metaDataProvider = doc->GetStudioSystem()->GetActionMetaData(); info = metaDataProvider->GetMetaDataPropertyInfo( metaDataProvider->GetMetaDataProperty(instance, element->m_property)); } bool skipEmits = false; switch (element->m_dataType) { case qt3dsdm::DataModelDataType::String: { QString stringValue = qt3dsdm::get(value); if (getBridge()->isInsideMaterialContainer(element->m_instance)) { int index = stringValue.lastIndexOf(QLatin1Char('/')); if (index != -1) stringValue = stringValue.mid(index + 1); } element->m_value = stringValue; } Q_FALLTHROUGH(); // fall-through for other String-derived datatypes case qt3dsdm::DataModelDataType::StringOrInt: if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::StringList) { QStringList stringlist; if (m_guideInspectable) { const auto strings = m_guideInspectable->properties() [handleToGuidePropIndex(element->m_property)]->GetInspectableList(); for (auto &str : strings) stringlist.append(QString::fromWCharArray(str.wide_str())); } else { stringlist = qt3dsdm::get(info->m_MetaDataData); } auto slideSystem = studioSystem->GetSlideSystem(); if (element->m_title == QLatin1String("Play Mode")) { std::pair slideData( getSlideCharacteristics(element->m_instance, *studioSystem->GetSlideCore(), *slideSystem)); bool hasNextSlide(slideData.first); bool hasPreviousSlide(slideData.second); if (!hasNextSlide && !hasPreviousSlide) stringlist.removeAll("Play Through To..."); } else if (element->m_title == QLatin1String("Play Through To")) { // the code duplication is intentional as we may ask for slide characteristics // only if the property refers to slides std::pair slideData( getSlideCharacteristics(element->m_instance, *studioSystem->GetSlideCore(), *slideSystem)); bool hasNextSlide(slideData.first); bool hasPreviousSlide(slideData.second); if (!hasNextSlide) stringlist.removeAll("Next"); if (!hasPreviousSlide) stringlist.removeAll("Previous"); auto itemCount = stringlist.count(); QString listOpt; int selectedSlideHandle = 0; int selectedIndex = -1; qt3dsdm::SStringOrInt stringOrInt = qt3dsdm::get(value); if (stringOrInt.GetType() == qt3dsdm::SStringOrIntTypes::String) listOpt = QString::fromWCharArray(qt3dsdm::get (stringOrInt.m_Value)->GetData()); else selectedSlideHandle = qt3dsdm::get(stringOrInt.m_Value); selectedIndex = stringlist.indexOf(listOpt); // Add the slide names (exclude the master slide) auto slideHandle = slideSystem->GetSlideByInstance(instance); auto masterSlide = slideSystem->GetMasterSlide(slideHandle); long slideCount = long(slideSystem->GetSlideCount(masterSlide)); for (long slideIndex = 1; slideIndex < slideCount; ++slideIndex) { auto currentSlide = slideSystem->GetSlideByIndex(masterSlide, slideIndex); auto currentInstance = slideSystem->GetSlideInstance(currentSlide); QString slideName = getBridge()->GetName(currentInstance).toQString(); //hack to add a separator before the item if (slideIndex == 1 && itemCount > 0) slideName += "|separator"; stringlist.append(slideName); if (currentSlide.GetHandleValue() == selectedSlideHandle) selectedIndex = slideIndex + itemCount - 1; } element->m_value = QString(selectedIndex > 0 ? stringlist[selectedIndex] : stringlist.first()).replace(QLatin1String("|separator"), QString()); } element->m_values = stringlist; } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Import) { QStringList stringlist = qt3dsdm::get(info->m_MetaDataData); element->m_values = stringlist; } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Renderable) { element->m_values = renderableItems(); if (element->m_value.toString().isEmpty()) element->m_value = element->m_values.toStringList().at(0); } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::MultiLine) { element->m_value = qt3dsdm::get(value); } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Font) { updateFontValues(element); skipEmits = true; // updateFontValues handles emits in correct order } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Mesh) { QString meshValue = QFileInfo(qt3dsdm::get(value)).fileName(); element->m_value = meshValue.startsWith('#'_L1) ? meshValue.mid(1) : meshValue; } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Texture) { QFileInfo fileInfo(qt3dsdm::get(value)); element->m_value = fileInfo.fileName(); } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::PathBuffer) { element->m_value = qt3dsdm::get(value); } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::String) { // Basic string already handled, do not warn about that. // If we hit any other datatypes then give a warning } else { qWarning() << "KDAB_TODO: InspectorControlModel::updatePropertyValue: need to implement:" << element->m_dataType << " element->m_propertyType : " << element->m_propertyType; } break; case qt3dsdm::DataModelDataType::StringRef: if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) { element->m_value = qt3dsdm::get(value); } break; case qt3dsdm::DataModelDataType::Bool: element->m_value = qt3dsdm::get(value); break; case qt3dsdm::DataModelDataType::Long4: if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Image) { qt3dsdm::Option guid = qt3dsdm::get(value); qt3dsdm::Qt3DSDMInstanceHandle imageInstance = doc->GetDocumentReader() .GetInstanceForGuid(guid); if (imageInstance.Valid()) { Q3DStudio::CString path = doc->GetDocumentReader().GetSourcePath(imageInstance); Q3DStudio::CFilePath relPath(path); element->m_value = QVariant(relPath.GetFileName().toQString()); } else { element->m_value = QVariant(QString()); } } else { qWarning() << "KDAB_TODO: InspectorControlModel::updatePropertyValue: need to implement:" << element->m_dataType << " " << element->m_title; } break; case qt3dsdm::DataModelDataType::Long: if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Range) { element->m_value = qt3dsdm::get(value); qt3dsdm::SMetaDataRange ranges; if (m_guideInspectable) { const auto prop = m_guideInspectable->properties() [handleToGuidePropIndex(element->m_property)]; ranges.m_min = prop->GetInspectableMin(); ranges.m_max = prop->GetInspectableMax(); } else { ranges = qt3dsdm::get(info->m_MetaDataData); } const QList rangesValues{ranges.m_min, ranges.m_max, double(ranges.m_decimals)}; element->m_values = QVariant::fromValue >(rangesValues); } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::ShadowMapResolution) { element->m_value = qt3dsdm::get(value); } else { qWarning() << "KDAB_TODO: InspectorControlModel::updatePropertyValue: need to implement:" << element->m_dataType; } break; case qt3dsdm::DataModelDataType::Float3: if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Color) { element->m_value = qt3dsdm::get(value); } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Rotation) { const QVector3D theFloat3 = qt3dsdm::get(value); const QList float3Values{theFloat3.x(), theFloat3.y(), theFloat3.z()}; element->m_values = QVariant::fromValue >(float3Values); } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) { const QVector3D theFloat3 = qt3dsdm::get(value); const QList float3Values{theFloat3.x(), theFloat3.y(), theFloat3.z()}; element->m_values = QVariant::fromValue >(float3Values); } break; case qt3dsdm::DataModelDataType::Float4: if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Color) { element->m_value = qt3dsdm::get(value); } break; case qt3dsdm::DataModelDataType::Float2: if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) { const QVector2D theFloat2 = qt3dsdm::get(value); const QList float2Values{theFloat2.x(), theFloat2.y()}; element->m_values = QVariant::fromValue >(float2Values); } else { qWarning() << "TODO: InspectorControlModel::updatePropertyValue: need to implement:" << element->m_dataType << element->m_propertyType; } break; case qt3dsdm::DataModelDataType::Float: if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::None) { element->m_value = qt3dsdm::get(value); } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::Range) { element->m_value = qt3dsdm::get(value); const qt3dsdm::SMetaDataRange ranges = qt3dsdm::get(info->m_MetaDataData); const QList rangesValues{ranges.m_min, ranges.m_max, double(ranges.m_decimals)}; element->m_values = QVariant::fromValue >(rangesValues); } else if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::FontSize) { element->m_value = qt3dsdm::get(value); } break; case qt3dsdm::DataModelDataType::ObjectRef: if (element->m_propertyType == qt3dsdm::AdditionalMetaDataType::ObjectRef) { IObjectReferenceHelper *objRefHelper = doc->GetDataModelObjectReferenceHelper(); if (objRefHelper) { qt3dsdm::Qt3DSDMInstanceHandle refInstance = objRefHelper->Resolve(value, instance); QString refName = objRefHelper->LookupObjectFormalName(refInstance).toQString(); if (getBridge()->IsReferencedMaterialInstance(instance) && !refName.isEmpty()) { // get the material's object name (parent) auto parentInstance = getBridge()->GetParentInstance(refInstance); qt3dsdm::SValue vParent; propertySystem->GetInstancePropertyValue(parentInstance, getBridge()->GetObjectDefinitions().m_Named.m_NameProp, vParent); QString parentName = qt3dsdm::get(vParent); refName.append(QLatin1String(" (") + parentName + QLatin1String(")")); } element->m_value = refName; } } break; default: qWarning() << "TODO: InspectorControlModel::updatePropertyValue: I've no idea how to handle this datatype" << element->m_dataType; break; } if (!skipEmits) { Q_EMIT element->valueChanged(); Q_EMIT element->valuesChanged(); } // Controlled state must be manually set after undo operations, // as only the "controlledproperty" is restored in undo, // not the controlled flag nor the tooltip if (element->m_controllable) updateControlledToggleState(element); } void InspectorControlModel::refreshRenderables() { for (int row = 0; row < m_groupElements.count(); ++row) { auto group = m_groupElements[row]; for (int p = 0; p < group.controlElements.count(); ++p) { QVariant& element = group.controlElements[p]; InspectorControlBase *property = element.value(); if (property->m_property.Valid() && property->m_propertyType == qt3dsdm::AdditionalMetaDataType::Renderable) { updatePropertyValue(property); } } } } void InspectorControlModel::refreshTree() { //check if the structure has changed if (isTreeRebuildRequired(m_inspectableBase)) { rebuildTree(); } else { // group structure is intact, let's walk to see which rows changed QVector deleteVector; long theCount = m_inspectableBase->getGroupCount(); for (long theIndex = 0; theIndex < theCount; ++theIndex) { if (isGroupRebuildRequired(m_inspectableBase, theIndex)) { auto group = m_groupElements[theIndex]; for (int p = 0; p < group.controlElements.count(); ++p) deleteVector.append(group.controlElements[p].value()); m_groupElements[theIndex] = computeGroup(m_inspectableBase, theIndex); Q_EMIT dataChanged(index(theIndex), index(theIndex)); } } } } void InspectorControlModel::refresh() { refreshTree(); // update values for (int row = 0; row < m_groupElements.count(); ++row) { auto group = m_groupElements[row]; for (int p = 0; p < group.controlElements.count(); ++p) { QVariant& element = group.controlElements[p]; InspectorControlBase *property = element.value(); if (property->m_property.Valid()) { updatePropertyValue(property); updateControlledToggleState(property); } } } Q_EMIT dataChanged(index(0), index(rowCount() - 1)); } void InspectorControlModel::saveIfMaterial(qt3dsdm::Qt3DSDMInstanceHandle instance) { if (!instance.Valid()) return; const auto doc = g_StudioApp.GetCore()->GetDoc(); const auto sceneEditor = doc->getSceneEditor(); const auto studio = doc->GetStudioSystem(); EStudioObjectType type = getBridge()->GetObjectType(instance); auto material = instance; if (type == EStudioObjectType::OBJTYPE_IMAGE) material = sceneEditor->GetParent(instance); if (!material.Valid()) return; const auto refMaterial = getBridge()->getMaterialReference(material); if (refMaterial.Valid()) material = refMaterial; if (!getBridge()->isInsideMaterialContainer(material)) return; type = getBridge()->GetObjectType(material); if (type == EStudioObjectType::OBJTYPE_MATERIAL || type == EStudioObjectType::OBJTYPE_CUSTOMMATERIAL) { qt3dsdm::SValue value; studio->GetPropertySystem()->GetInstancePropertyValue( material, getBridge()->GetObjectDefinitions().m_Named.m_NameProp, value); qt3dsdm::TDataStrPtr namePtr(qt3dsdm::get(value)); QString materialName = QString::fromWCharArray(namePtr->GetData(), int(namePtr->GetLength())); QString sourcePath; for (int i = 0; i < m_matDatas.size(); ++i) { if (QString::compare(m_matDatas[i].m_name, materialName, Qt::CaseInsensitive) == 0) { sourcePath = doc->GetDocumentDirectory() + QLatin1Char('/') + m_matDatas[i].m_relativePath; } } sceneEditor->writeMaterialFile(material, materialName, sourcePath.isEmpty(), sourcePath); } } void InspectorControlModel::setMaterialTypeValue(long instance, int handle, const QVariant &value) { Q_UNUSED(handle) const QString typeValue = value.toString(); QString v; const auto doc = g_StudioApp.GetCore()->GetDoc(); const auto sceneEditor = doc->getSceneEditor(); const Q3DStudio::CString oldType = sceneEditor->GetObjectTypeName(instance); qt3dsdm::Qt3DSDMInstanceHandle refMaterial; if (oldType == "ReferencedMaterial") refMaterial = getBridge()->getMaterialReference(instance); bool changeMaterialFile = false; bool canCopyProperties = false; if (typeValue == getAnimatableMaterialString()) { v = QStringLiteral("Standard Material"); if (refMaterial.Valid()) { const auto refSourcePath = getBridge()->GetSourcePath(refMaterial); for (auto &material : m_materials) { if (refSourcePath == material.m_relativePath) { v = material.m_relativePath; break; } } } canCopyProperties = true; } else if (typeValue == getBasicMaterialString()) { v = QStringLiteral("Referenced Material"); changeMaterialFile = true; } else if (typeValue == getReferencedMaterialString()) { v = QStringLiteral("Referenced Material"); } Q3DStudio::ScopedDocumentEditor scopedEditor( Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type"))); scopedEditor->SetMaterialType(instance, v); if (refMaterial.Valid() && canCopyProperties) { const Q3DStudio::CString newType = sceneEditor->GetObjectTypeName(instance); const Q3DStudio::CString refType = sceneEditor->GetObjectTypeName(refMaterial); if (refType == newType) scopedEditor->copyMaterialProperties(refMaterial, instance); if (getBridge()->isInsideMaterialContainer(refMaterial)) { const auto name = scopedEditor->GetName(instance); if (!name.toQString().endsWith(QLatin1String("_animatable"))) scopedEditor->SetName(instance, name + "_animatable"); } } if (changeMaterialFile) { scopedEditor->setMaterialProperties(instance, Q3DStudio::CString::fromQString( getBridge()->getDefaultMaterialName()), {}, {}); // Select the original instance again since potentially creating a material selects the // created one doc->SelectDataModelObject(instance); rebuildTree(); // Hack to mimic value changing behavior of the type selector } saveIfMaterial(instance); } void InspectorControlModel::setShaderValue(long instance, int handle, const QVariant &value) { Q_UNUSED(handle) const QString typeValue = value.toString(); QString v = QStringLiteral("Standard Material"); for (size_t matIdx = 0, end = m_materials.size(); matIdx < end; ++matIdx) { if (m_materials[matIdx].m_name == typeValue) v = m_materials[matIdx].m_relativePath; } const auto doc = g_StudioApp.GetCore()->GetDoc(); Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QObject::tr("Set Material Type")) ->SetMaterialType(instance, v); const auto dispatch = g_StudioApp.GetCore()->GetDispatch(); QVector refMats; doc->getSceneReferencedMaterials(doc->GetSceneInstance(), refMats); for (auto &refMat : qAsConst(refMats)) { const auto origMat = getBridge()->getMaterialReference(refMat); if (origMat.Valid() && long(origMat) == instance) dispatch->FireImmediateRefreshInstance(refMat); } saveIfMaterial(instance); } void InspectorControlModel::setMatDataValue(long instance, int handle, const QVariant &value) { Q_UNUSED(handle) const QString typeValue = value.toString(); QString v; QString name; Q3DStudio::CString srcPath; QMap values; QMap> textureValues; const auto doc = g_StudioApp.GetCore()->GetDoc(); bool changeMaterialFile = false; if (typeValue == getDefaultMaterialString()) { v = QStringLiteral("Referenced Material"); name = getBridge()->getDefaultMaterialName(); srcPath = Q3DStudio::CString::fromQString(name); changeMaterialFile = true; } else { const auto sceneEditor = doc->getSceneEditor(); for (size_t matIdx = 0, end = m_matDatas.size(); matIdx < end; ++matIdx) { QString shownName = m_matDatas[matIdx].m_name; int slashIndex = shownName.lastIndexOf(QLatin1Char('/')); if (slashIndex != -1) shownName = shownName.mid(slashIndex + 1); if (QString::compare(shownName + QLatin1String(" (") + m_matDatas[matIdx].m_relativePath + QLatin1Char(')'), typeValue, Qt::CaseInsensitive) == 0 || QString::compare(shownName, typeValue, Qt::CaseInsensitive) == 0) { v = QStringLiteral("Referenced Material"); changeMaterialFile = true; name = m_matDatas[matIdx].m_name; srcPath = Q3DStudio::CString::fromQString(m_matDatas[matIdx].m_relativePath); const auto material = sceneEditor->getMaterial(srcPath.toQString()); if (material.Valid()) { // Get the correct case source path const auto absPath = sceneEditor->getFilePathFromMaterialName( sceneEditor->GetName(material).toQString()); const auto relPath = QDir(doc->GetDocumentDirectory()) .relativeFilePath(absPath); srcPath = Q3DStudio::CString::fromQString(relPath); } values = m_matDatas[matIdx].m_values; textureValues = m_matDatas[matIdx].m_textureValues; break; } } } if (changeMaterialFile) { { Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, QString()) ->setMaterialValues(srcPath.toQString(), values, textureValues); } // Several aspects of the editor are not updated correctly // if the data core is changed without a transaction // The above scope completes the transaction for creating a new material // Next the added undo has to be popped from the stack // TODO: Find a way to update the editor fully without a transaction g_StudioApp.GetCore()->GetCmdStack()->RemoveLastUndo(); } Q3DStudio::ScopedDocumentEditor scopedEditor( Q3DStudio::SCOPED_DOCUMENT_EDITOR(*doc, tr("Set Material Type"))); scopedEditor->SetMaterialType(instance, v); if (changeMaterialFile) { scopedEditor->setMaterialSourcePath(instance, srcPath); scopedEditor->setMaterialReferenceByPath(instance, srcPath.toQString()); // Select original instance again since potentially // creating a material selects the created one doc->SelectDataModelObject(instance); rebuildTree(); // Hack to mimic value changing behavior of the type selector } saveIfMaterial(instance); } void InspectorControlModel::setRenderableValue(long instance, int handle, const QVariant &value) { qt3dsdm::SValue oldValue = currentPropertyValue(instance, handle); QString v = value.toString(); if (v == QObject::tr("No renderable item")) v = QString(); if (v == qt3dsdm::get(oldValue)) return; Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Property")) ->SetInstancePropertyValueAsRenderable(instance, handle, Q3DStudio::CString::fromQString(v)); } void InspectorControlModel::setPropertyValue(long instance, int handle, const QVariant &value, bool commit) { const auto doc = g_StudioApp.GetCore()->GetDoc(); const auto studio = doc->GetStudioSystem(); // Name property needs special handling if (instance && handle == getBridge()->GetNameProperty()) { // Ignore preview of name property change if (!commit) return; m_modifiedProperty.first = 0; m_modifiedProperty.second = 0; m_previouslyCommittedValue = {}; Q3DStudio::CString currentName = getBridge()->GetName(instance, true); Q3DStudio::CString newName = Q3DStudio::CString::fromQString(value.toString()); if (!newName.IsEmpty()) { if (getBridge()->isInsideMaterialContainer(instance) && ((newName.Find('/') != Q3DStudio::CString::ENDOFSTRING || newName.Find('#') != Q3DStudio::CString::ENDOFSTRING || newName.Find(':') != Q3DStudio::CString::ENDOFSTRING) || m_suspendMaterialRename)) { return; } qt3dsdm::Qt3DSDMInstanceHandle parentInstance = getBridge() ->GetParentInstance(instance); if (parentInstance == doc->GetSceneInstance() && newName.toQString() == getBridge()->getMaterialContainerName()) { QString theTitle = QObject::tr("Rename Object Error"); QString theString = getBridge()->getMaterialContainerName() + QObject::tr(" is a reserved name."); // Display error message box asynchronously so focus loss won't trigger setting // the name again g_StudioApp.GetDialogs()->asyncDisplayMessageBox(theTitle, theString, Qt3DSMessageBox::ICON_ERROR); return; } Q3DStudio::CString realNewName = newName; if (getBridge()->isInsideMaterialContainer(instance)) { Q3DStudio::CString realName = getBridge()->GetName(instance); int slashIndex = realName.rfind('/'); if (slashIndex != Q3DStudio::CString::ENDOFSTRING) realNewName = realName.Left(slashIndex + 1) + newName; } if (!getBridge()->CheckNameUnique(parentInstance, instance, realNewName)) { QString origNewName = newName.toQString(); realNewName = getBridge()->GetUniqueChildName(parentInstance, instance, realNewName); newName = realNewName; if (getBridge()->isInsideMaterialContainer(instance)) { int slashIndex = newName.rfind('/'); if (slashIndex != Q3DStudio::CString::ENDOFSTRING) newName = newName.substr(slashIndex + 1); } // Display rename message box asynchronously so focus loss won't trigger setting // the name again g_StudioApp.GetDialogs()->DisplayObjectRenamed(origNewName, newName.toQString(), true); } const auto sceneEditor = doc->getSceneEditor(); // A materialdef with the same name might exists as a file but not in the container, // so an additional check is needed for that case if (getBridge()->isInsideMaterialContainer(instance)) { int i = 1; while (QFileInfo(sceneEditor->getFilePathFromMaterialName( realNewName.toQString())).exists()) { ++i; realNewName = Q3DStudio::CString::fromQString( realNewName.toQString() + QString::number(i)); if (!getBridge()->CheckNameUnique(parentInstance, instance, realNewName)) { realNewName = getBridge()->GetUniqueChildName(parentInstance, instance, realNewName); } } newName = realNewName; int slashIndex = newName.rfind('/'); if (slashIndex != Q3DStudio::CString::ENDOFSTRING) newName = newName.substr(slashIndex + 1); } if (newName != currentName) { if (getBridge()->isInsideMaterialContainer(instance)) { const auto properOldName = sceneEditor->GetName(instance).toQString(); const QString dirPath = doc->GetDocumentDirectory(); for (size_t matIdx = 0, end = m_matDatas.size(); matIdx < end; ++matIdx) { if (m_matDatas[matIdx].m_name == properOldName) { QFileInfo fileInfo(dirPath + QLatin1Char('/') + m_matDatas[matIdx].m_relativePath); const QString newFile = fileInfo.absolutePath() + QLatin1Char('/') + newName.toQString() + QStringLiteral(".materialdef"); const auto properNewName = sceneEditor->getMaterialNameFromFilePath(newFile); newName = Q3DStudio::CString::fromQString(properNewName); doc->queueMaterialRename(properOldName, properNewName); } } } Q3DStudio::SCOPED_DOCUMENT_EDITOR( *g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Name"))->SetName(instance, newName, false); } } return; } qt3dsdm::SValue oldValue = m_guideInspectable ? m_guideInspectable->properties()[handleToGuidePropIndex(handle)]->GetInspectableData() : currentPropertyValue(instance, handle); qt3dsdm::SValue v = value; const bool hasPreview = (m_modifiedProperty.first == instance && m_modifiedProperty.second == handle); // If this set is a commit for property that was previously changed without // committing, we must let the set go through even if the value hasn't changed // to finish the transaction. if (v == oldValue && !(commit && hasPreview)) return; if (!commit && !hasPreview) { m_previouslyCommittedValue = oldValue; m_modifiedProperty.first = instance; m_modifiedProperty.second = handle; } if (instance) { // If the user enters 0.0 to any (x, y, z) values of camera scale, // we reset the value back to original, because zero scale factor will crash // camera-specific inverse matrix math. (Additionally, scale of zero for a camera // is generally not useful anyway.) We could silently discard zero values also deeper in the // value setter code, but then the inspector panel value would not be updated as opposed // to both rejecting invalid and resetting the original value here. EStudioObjectType theType = getBridge()->GetObjectType(instance); if (theType == EStudioObjectType::OBJTYPE_CAMERA && studio->GetPropertySystem()->GetName(handle) == Q3DStudio::CString("scale")) { const QVector3D theFloat3 = qt3dsdm::get(v); if (theFloat3.x() == 0.0f || theFloat3.y() == 0.0f || theFloat3.z() == 0.0f ) v = oldValue; } // some properties may initialize OpenGL resources (e.g. loading meshes will // initialize vertex buffers), so the renderer's OpenGL context must be current Q3DStudio::IStudioRenderer &theRenderer(g_StudioApp.getRenderer()); theRenderer.MakeContextCurrent(); m_UpdatableEditor.EnsureEditor(QObject::tr("Set Property"), __FILE__, __LINE__) .SetInstancePropertyValue(instance, handle, v); theRenderer.ReleaseContext(); m_UpdatableEditor.FireImmediateRefresh(instance); } else if (m_guideInspectable) { m_guideInspectable->properties()[handleToGuidePropIndex(handle)]->ChangeInspectableData(v); } if (commit) { m_modifiedProperty.first = 0; m_modifiedProperty.second = 0; if (m_previouslyCommittedValue == v) { if (m_guideInspectable) m_guideInspectable->Rollback(); else m_UpdatableEditor.RollbackEditor(); } else { if (m_guideInspectable) { // If the guide ends up over the matte, destroy it QSize presSize = g_StudioApp.GetCore()->GetStudioProjectSettings() ->getPresentationSize(); bool isInPres = true; qt3dsdm::SValue posValue = m_guideInspectable->GetPosition(); float position = qt3dsdm::get(posValue); if (m_guideInspectable->isHorizontal()) isInPres = 0.f <= position && float(presSize.height()) >= position; else isInPres = 0.f <= position && float(presSize.width()) >= position; if (isInPres) m_guideInspectable->Commit(); else m_guideInspectable->Destroy(); } else { m_UpdatableEditor.CommitEditor(); } } m_previouslyCommittedValue = {}; refreshTree(); saveIfMaterial(instance); } } void InspectorControlModel::setSlideSelection(long instance, int handle, int index, const QStringList &list) { const auto doc = g_StudioApp.GetCore()->GetDoc(); auto studioSystem = doc->GetStudioSystem(); const auto metaDataProvider = doc->GetStudioSystem()->GetActionMetaData(); const auto info = metaDataProvider->GetMetaDataPropertyInfo( metaDataProvider->GetMetaDataProperty(instance, handle)); QStringList stringlist = qt3dsdm::get(info->m_MetaDataData); auto slideSystem = studioSystem->GetSlideSystem(); std::pair slideData( getSlideCharacteristics(instance, *studioSystem->GetSlideCore(), *slideSystem)); bool hasNextSlide(slideData.first); bool hasPreviousSlide(slideData.second); qt3dsdm::SStringOrInt newSelectedData; if (!hasNextSlide) stringlist.removeAll("Next"); if (!hasPreviousSlide) stringlist.removeAll("Previous"); auto itemCount = stringlist.count(); if (index < itemCount) { newSelectedData = qt3dsdm::SStringOrInt(std::make_shared (Q3DStudio::CString::fromQString(list[index]).c_str())); } else { auto slideHandle = slideSystem->GetSlideByInstance(instance); auto masterSlide = slideSystem->GetMasterSlide(slideHandle); long slideIndex = index - itemCount + 1; auto newSelectedSlide = slideSystem->GetSlideByIndex(masterSlide, slideIndex); newSelectedData = qt3dsdm::SStringOrInt((long)newSelectedSlide); } Q3DStudio::SCOPED_DOCUMENT_EDITOR(*g_StudioApp.GetCore()->GetDoc(), QObject::tr("Set Property")) ->SetInstancePropertyValue(instance, handle, newSelectedData); } // temporarily prevent material renaming when opening the colors dialog (fix for QT3DS-3407) void InspectorControlModel::suspendMaterialRename(bool flag) { m_suspendMaterialRename = flag; } void InspectorControlModel::setPropertyControllerInstance( long instance,int property, Q3DStudio::CString controllerInstance, bool controlled) { CDoc *doc = g_StudioApp.GetCore()->GetDoc(); IObjectReferenceHelper *objRefHelper = doc->GetDataModelObjectReferenceHelper(); Q3DStudio::CString instancepath = Q3DStudio::CString( objRefHelper->GetObjectReferenceString(doc->GetSceneInstance(), CRelativePathTools::EPATHTYPE_GUID, instance)); Q_ASSERT(instancepath.size()); doc->SetInstancePropertyControlled(instance, instancepath, property, controllerInstance, controlled); } void InspectorControlModel::setPropertyControlled(long instance, int property) { const auto signalSender = g_StudioApp.GetCore()->GetDoc()->GetStudioSystem()->GetFullSystemSignalSender(); signalSender->SendControlledToggled(instance, property); } bool InspectorControlModel::isLayer(long instance) const { return getBridge()->GetObjectType(instance) == EStudioObjectType::OBJTYPE_LAYER; } QString InspectorControlModel::renderableId(const QString &filePath) const { return g_StudioApp.getRenderableId(filePath); } void InspectorControlModel::setPropertyAnimated(long instance, int handle, bool animated) { CCmd* cmd = nullptr; auto doc = g_StudioApp.GetCore()->GetDoc(); if (animated) cmd = new CCmdDataModelAnimate(doc, instance, handle); else cmd = new CCmdDataModelDeanimate(doc, instance, handle); g_StudioApp.GetCore()->ExecuteCommand(cmd); } QVariant InspectorControlModel::data(const QModelIndex &index, int role) const { if (!hasIndex(index.row(), index.column(),index.parent())) return {}; const auto row = index.row(); switch (role) { case GroupValuesRole: return m_groupElements.at(row).controlElements; case GroupTitleRole: return m_groupElements.at(row).groupTitle; case GroupInfoRole: return m_groupElements.at(row).groupInfo; } return {}; } QHash InspectorControlModel::roleNames() const { auto names = QAbstractListModel::roleNames(); names.insert(GroupValuesRole, "values"); names.insert(GroupTitleRole, "title"); names.insert(GroupInfoRole, "info"); return names; } InspectorControlBase::~InspectorControlBase() { }