diff --git a/Common/DzRuntimePluginAction.cpp b/Common/DzRuntimePluginAction.cpp index d9de683..a08b93c 100644 --- a/Common/DzRuntimePluginAction.cpp +++ b/Common/DzRuntimePluginAction.cpp @@ -206,7 +206,7 @@ void DzRuntimePluginAction::ExportNode(DzNode* Node) ExportOptions.setBoolValue("doLights", false); ExportOptions.setBoolValue("doCameras", false); ExportOptions.setBoolValue("doAnims", true); - if (AssetType == "SkeletalMesh" && ExportMorphs && MorphString != "") + if ((AssetType == "Animation" || AssetType == "SkeletalMesh") && ExportMorphs && MorphString != "") { ExportOptions.setBoolValue("doMorphs", true); ExportOptions.setStringValue("rules", MorphString); diff --git a/Unreal/DazStudioPlugin/DzUnrealAction.cpp b/Unreal/DazStudioPlugin/DzUnrealAction.cpp index 052b577..88cd507 100644 --- a/Unreal/DazStudioPlugin/DzUnrealAction.cpp +++ b/Unreal/DazStudioPlugin/DzUnrealAction.cpp @@ -83,6 +83,7 @@ void DzUnrealAction::executeAction() ShowFbxDialog = dlg->showFbxDialogCheckBox->isChecked(); SubdivisionDialog = DzUnrealSubdivisionDialog::Get(dlg); SubdivisionDialog->LockSubdivisionProperties(ExportSubdivisions); + FBXVersion = dlg->fbxVersionCombo->currentText(); Export(); } diff --git a/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.cpp b/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.cpp index 2113d13..4e7eee9 100644 --- a/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.cpp +++ b/Unreal/DazStudioPlugin/DzUnrealMorphSelectionDialog.cpp @@ -24,6 +24,7 @@ #include "dzactionmgr.h" #include "dzaction.h" #include "dzskeleton.h" +#include "dzfigure.h" #include "dzobject.h" #include "dzshape.h" #include "dzmodifier.h" @@ -187,6 +188,22 @@ QSize DzUnrealMorphSelectionDialog::minimumSizeHint() const void DzUnrealMorphSelectionDialog::PrepareDialog() { DzNode* Selection = dzScene->getPrimarySelection(); + + // For items like clothing, create the morph list from the character + DzNode* ParentFigureNode = Selection; + while (ParentFigureNode->getNodeParent()) + { + ParentFigureNode = ParentFigureNode->getNodeParent(); + if (DzSkeleton* Skeleton = ParentFigureNode->getSkeleton()) + { + if (DzFigure* Figure = qobject_cast(Skeleton)) + { + Selection = ParentFigureNode; + break; + } + } + } + morphs.clear(); morphList = GetAvailableMorphs(Selection); for (int ChildIndex = 0; ChildIndex < Selection->getNumNodeChildren(); ChildIndex++) @@ -682,7 +699,7 @@ QString DzUnrealMorphSelectionDialog::GetMorphCSVString() foreach(MorphInfo exportMorph, morphsToExport) { morphList.append(exportMorph.Name); - morphString += "\"" + exportMorph.Name + ",\"Export\"\n"; + morphString += "\"" + exportMorph.Name + "\",\"Export\"\n"; } morphString += "\".CTRLVS\", \"Ignore\"\n"; morphString += "\"Anything\", \"Bake\"\n"; diff --git a/Unreal/UnrealPlugin/DazToUnreal/Content/BasePBRSkinMaterial.uasset b/Unreal/UnrealPlugin/DazToUnreal/Content/BasePBRSkinMaterial.uasset new file mode 100644 index 0000000..ec7e50f Binary files /dev/null and b/Unreal/UnrealPlugin/DazToUnreal/Content/BasePBRSkinMaterial.uasset differ diff --git a/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis3JCMPostProcess.uasset b/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis3JCMPostProcess.uasset index 9830e8f..3bc0a6f 100644 Binary files a/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis3JCMPostProcess.uasset and b/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis3JCMPostProcess.uasset differ diff --git a/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis8BaseSkeleton.uasset b/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis8BaseSkeleton.uasset index 3721302..afb39fe 100644 Binary files a/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis8BaseSkeleton.uasset and b/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis8BaseSkeleton.uasset differ diff --git a/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis8JCMPostProcess.uasset b/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis8JCMPostProcess.uasset index 317ff88..b8fa2f7 100644 Binary files a/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis8JCMPostProcess.uasset and b/Unreal/UnrealPlugin/DazToUnreal/Content/Genesis8JCMPostProcess.uasset differ diff --git a/Unreal/UnrealPlugin/DazToUnreal/Content/PBRSkinParameters.uasset b/Unreal/UnrealPlugin/DazToUnreal/Content/PBRSkinParameters.uasset new file mode 100644 index 0000000..2ba4519 Binary files /dev/null and b/Unreal/UnrealPlugin/DazToUnreal/Content/PBRSkinParameters.uasset differ diff --git a/Unreal/UnrealPlugin/DazToUnreal/DazToUnreal.uplugin b/Unreal/UnrealPlugin/DazToUnreal/DazToUnreal.uplugin index e033c02..e09d97a 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/DazToUnreal.uplugin +++ b/Unreal/UnrealPlugin/DazToUnreal/DazToUnreal.uplugin @@ -1,7 +1,7 @@ { "FileVersion": 3, - "Version": 3, - "VersionName": "3.0", + "Version": 4, + "VersionName": "4.0", "FriendlyName": "DazToUnreal", "Description": "", "Category": "Importers", @@ -10,7 +10,6 @@ "DocsURL": "http://docs.daz3d.com/doku.php/public/read_me/index/72003/start", "MarketplaceURL": "", "SupportURL": "https://www.daz3d.com/help/", - "EngineVersion": "4.25.0", "CanContainContent": true, "Installed": true, "Modules": [ @@ -21,8 +20,26 @@ "WhitelistPlatforms": [ "Win64" ] + }, + { + "Name": "DazToUnrealDeveloper", + "Type": "UncookedOnly", + "LoadingPhase": "Default", + "WhitelistPlatforms": [ + "Win64" + ] + }, + { + "Name": "DazToUnrealRuntime", + "Type": "Runtime", + "LoadingPhase": "Default", + "WhitelistPlatforms": [ + "Win64" + ] } ], + + "Plugins": [ { "Name": "EditorScriptingUtilities", diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/DazToUnreal.Build.cs b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/DazToUnreal.Build.cs index 066e715..c15fbc7 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/DazToUnreal.Build.cs +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/DazToUnreal.Build.cs @@ -32,12 +32,15 @@ public DazToUnreal(ReadOnlyTargetRules Target) : base(Target) "Engine", "Slate", "SlateCore", - "EditorScriptingUtilities", + "EditorScriptingUtilities", // ... add private dependencies that you statically link with here ... } ); - - + +#if UE_4_26_OR_LATER + PrivateDependencyModuleNames.Add("DeveloperSettings"); +#endif + DynamicallyLoadedModuleNames.AddRange( new string[] { diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnreal.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnreal.cpp index 657ca91..f56e791 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnreal.cpp +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnreal.cpp @@ -6,6 +6,7 @@ #include "DazToUnrealUtils.h" #include "DazToUnrealFbx.h" #include "DazToUnrealEnvironment.h" +#include "DazToUnrealPoses.h" #include "LevelEditor.h" #include "Widgets/Docking/SDockTab.h" @@ -151,7 +152,18 @@ void FDazToUnrealModule::StartupModule() TSharedPtr ToolbarExtender = MakeShareable(new FExtender); ToolbarExtender->AddToolBarExtension("Settings", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FDazToUnrealModule::AddToolbarExtension)); - //LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + + FString InstallerPath = IPluginManager::Get().FindPlugin("DazToUnreal")->GetBaseDir() / TEXT("Resources") / TEXT("DazToUnrealSetup.exe"); + FString InstallerAbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*InstallerPath); + + FString DazStudioPluginPath = IPluginManager::Get().FindPlugin("DazToUnreal")->GetBaseDir() / TEXT("Resources") / TEXT("dzunrealbridge.dll"); + FString DazStudioPluginAbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*InstallerPath); + + if (FPaths::FileExists(InstallerAbsolutePath) + && FPaths::FileExists(DazStudioPluginAbsolutePath)) + { + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + } } /*FGlobalTabmanager::Get()->RegisterNomadTabSpawner(DazToUnrealTabName, FOnSpawnTab::CreateRaw(this, &FDazToUnrealModule::OnSpawnPluginTab)) @@ -314,6 +326,8 @@ UObject* FDazToUnrealModule::ImportFromDaz(TSharedPtr JsonObject) AssetType = DazAssetType::Animation; else if (JsonObject->GetStringField(TEXT("Asset Type")) == TEXT("Environment")) AssetType = DazAssetType::Environment; + else if (JsonObject->GetStringField(TEXT("Asset Type")) == TEXT("Pose")) + AssetType = DazAssetType::Pose; // Set up the folder paths FString ImportDirectory = FPaths::ProjectDir() / TEXT("Import"); @@ -346,14 +360,14 @@ UObject* FDazToUnrealModule::ImportFromDaz(TSharedPtr JsonObject) FString LocalCharacterMaterialFolder = CharacterMaterialFolder.Replace(TEXT("/Game/"), *ContentDirectory); // Make any needed folders. If any of these fail, don't continue - if (!MakeDirectoryAndCheck(ImportDirectory)) return false; - if (!MakeDirectoryAndCheck(ImportCharacterFolder)) return false; - if (!MakeDirectoryAndCheck(ImportCharacterTexturesFolder)) return false; - if (!MakeDirectoryAndCheck(LocalDAZImportFolder)) return false; - if (!MakeDirectoryAndCheck(LocalDAZAnimationImportFolder)) return false; - if (!MakeDirectoryAndCheck(LocalCharacterFolder)) return false; - if (!MakeDirectoryAndCheck(LocalCharacterTexturesFolder)) return false; - if (!MakeDirectoryAndCheck(LocalCharacterMaterialFolder)) return false; + if (!FDazToUnrealUtils::MakeDirectoryAndCheck(ImportDirectory)) return false; + if (!FDazToUnrealUtils::MakeDirectoryAndCheck(ImportCharacterFolder)) return false; + if (!FDazToUnrealUtils::MakeDirectoryAndCheck(ImportCharacterTexturesFolder)) return false; + if (!FDazToUnrealUtils::MakeDirectoryAndCheck(LocalDAZImportFolder)) return false; + if (!FDazToUnrealUtils::MakeDirectoryAndCheck(LocalDAZAnimationImportFolder)) return false; + if (!FDazToUnrealUtils::MakeDirectoryAndCheck(LocalCharacterFolder)) return false; + if (!FDazToUnrealUtils::MakeDirectoryAndCheck(LocalCharacterTexturesFolder)) return false; + if (!FDazToUnrealUtils::MakeDirectoryAndCheck(LocalCharacterMaterialFolder)) return false; if (AssetType == DazAssetType::Environment) { @@ -466,6 +480,20 @@ UObject* FDazToUnrealModule::ImportFromDaz(TSharedPtr JsonObject) { Property.Type = TEXT("Color"); } + + // Properties that end with Enabled are switches for functionality + if (Property.Name.EndsWith(TEXT(" Enable"))) + { + Property.Type = TEXT("Switch"); + if (Property.Value == TEXT("0")) + { + Property.Value = TEXT("false"); + } + else + { + Property.Value = TEXT("true"); + } + } MaterialProperties[MaterialName].Add(Property); if (!TextureName.IsEmpty()) @@ -540,11 +568,10 @@ UObject* FDazToUnrealModule::ImportFromDaz(TSharedPtr JsonObject) } // Daz Studio puts the base bone rotations in a different place than Unreal expects them. - //if (AssetType == DazAssetType::SkeletalMesh && RootBone) - //{ - // FDazToUnrealFbx::FixBoneRotations(RootBone); - // FDazToUnrealFbx::FixBindPose(Scene, RootBone); - //} + if (CachedSettings->FixBoneRotationsOnImport && AssetType == DazAssetType::SkeletalMesh && RootBone) + { + FDazToUnrealFbx::FixClusterTranformLinks(Scene, RootBone); + } // If this is a skeleton mesh, but a root bone wasn't found, it may be a scene under a group node or something similar // So create a root node. @@ -994,7 +1021,33 @@ UObject* FDazToUnrealModule::ImportFromDaz(TSharedPtr JsonObject) for (int32 i = 0; i < morphList.Num(); i++) { TSharedPtr morph = morphList[i]->AsObject(); - MorphMappings.Add(morph->GetStringField(TEXT("Name")), morph->GetStringField(TEXT("Label"))); + FString MorphName = morph->GetStringField(TEXT("Name")); + FString MorphLabel = morph->GetStringField(TEXT("Label")); + + // Daz Studio seems to strip the part of the name before a period when exporting the morph to FBX + if (MorphName.Contains(TEXT("."))) + { + FString Left; + MorphName.Split(TEXT("."), &Left, &MorphName); + } + + MorphMappings.Add(MorphName, MorphLabel); + } + + // Get a list of morph name mappings + TArray PoseNameList; + const TArray>* PoseList; + if (JsonObject->TryGetArrayField(TEXT("Poses"), PoseList)) + { + PoseNameList.Add(TEXT("ReferencePose")); + for (int32 i = 0; i < PoseList->Num(); i++) + { + TSharedPtr Pose = (*PoseList)[i]->AsObject(); + FString PoseName = Pose->GetStringField(TEXT("Name")); + FString PoseLabel = Pose->GetStringField(TEXT("Label")); + + PoseNameList.Add(PoseLabel); + } } // Combine clothing and body morphs @@ -1133,23 +1186,23 @@ UObject* FDazToUnrealModule::ImportFromDaz(TSharedPtr JsonObject) // If this is a character, determine the type. DazCharacterType CharacterType = DazCharacterType::Unknown; FString CharacterTypeName = RootBoneName.Replace(TEXT("\0"), TEXT("")); - if (RootBoneName.Contains(TEXT("Genesis3Male"))) + if (RootBoneName == TEXT("Genesis3Male")) { CharacterType = DazCharacterType::Genesis3Male; } - else if (RootBoneName.Contains(TEXT("Genesis3Female"))) + else if (RootBoneName == TEXT("Genesis3Female")) { CharacterType = DazCharacterType::Genesis3Female; } - else if (RootBoneName.Contains(TEXT("Genesis8Male"))) + else if (RootBoneName == TEXT("Genesis8Male")) { CharacterType = DazCharacterType::Genesis8Male; } - else if (RootBoneName.Contains(TEXT("Genesis8Female"))) + else if (RootBoneName == TEXT("Genesis8Female")) { CharacterType = DazCharacterType::Genesis8Female; } - else if (RootBoneName.Contains(TEXT("Genesis"))) + else if (RootBoneName == TEXT("Genesis")) { CharacterType = DazCharacterType::Genesis1; } @@ -1246,14 +1299,19 @@ UObject* FDazToUnrealModule::ImportFromDaz(TSharedPtr JsonObject) } } - // Create Material Instances - /*if (AssetType == DazAssetType::SkeletalMesh || AssetType == DazAssetType::StaticMesh) + // Import FBX + UObject* NewObject = ImportFBXAsset(UpdatedFBXFile, CharacterFolder, AssetType, CharacterType, CharacterTypeName); + + // If this is a Pose transfer, an AnimSequence was created. Make a PoseAsset from it. + if (AssetType == DazAssetType::Pose) { - CreateMaterials(CharacterMaterialFolder, CharacterTexturesFolder, MaterialNames, MaterialProperties, CharacterType); - }*/ + if (UAnimSequence* AnimSequence = Cast(NewObject)) + { + FDazToUnrealPoses::CreatePoseAsset(AnimSequence, PoseNameList); + } + } - // Import FBX - return ImportFBXAsset(UpdatedFBXFile, CharacterFolder, AssetType, CharacterType, CharacterTypeName); + return NewObject; } @@ -1399,7 +1457,7 @@ UObject* FDazToUnrealModule::ImportFBXAsset(const FString& SourcePath, const FSt FbxFactory->ImportUI->bImportMaterials = true; FbxFactory->ImportUI->MeshTypeToImport = FBXIT_StaticMesh; } - if (AssetType == DazAssetType::Animation) + if (AssetType == DazAssetType::Animation || AssetType == DazAssetType::Pose) { FbxFactory->ImportUI->bImportAsSkeletal = true; FbxFactory->ImportUI->Skeleton = Skeleton; @@ -1416,7 +1474,7 @@ UObject* FDazToUnrealModule::ImportFBXAsset(const FString& SourcePath, const FSt ImportData->Filenames = FileNames; ImportData->DestinationPath = ImportLocation; ImportData->bReplaceExisting = true; - if (AssetType == DazAssetType::Animation) + if (AssetType == DazAssetType::Animation || AssetType == DazAssetType::Pose) { ImportData->DestinationPath = CachedSettings->AnimationImportDirectory.Path; } @@ -1481,7 +1539,7 @@ UObject* FDazToUnrealModule::ImportFBXAsset(const FString& SourcePath, const FSt //EditableSkeleton->Set CachedSettings->OtherSkeletons.Add(CharacterTypeName, Skeleton); - CachedSettings->SaveConfig(); + CachedSettings->SaveConfig(CPF_Config, *CachedSettings->GetDefaultConfigFilename()); } } } diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealFbx.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealFbx.cpp index a47b71d..1191804 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealFbx.cpp +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealFbx.cpp @@ -35,63 +35,77 @@ void FDazToUnrealFbx::RenameDuplicateBones(FbxNode* RootNode, TMap } } -void FDazToUnrealFbx::FixBoneRotations(FbxNode* RootNode) +void FDazToUnrealFbx::FixClusterTranformLinks(FbxScene* Scene, FbxNode* RootNode) { - if (RootNode == nullptr) return; - + FbxGeometry* NodeGeometry = static_cast(RootNode->GetMesh()); - RootNode->LclRotation.Set(RootNode->PostRotation.Get()); + // Create missing weights + if (NodeGeometry) + { + for (int DeformerIndex = 0; DeformerIndex < NodeGeometry->GetDeformerCount(); ++DeformerIndex) + { + FbxSkin* Skin = static_cast(NodeGeometry->GetDeformer(DeformerIndex)); + if (Skin) + { + for (int ClusterIndex = 0; ClusterIndex < Skin->GetClusterCount(); ++ClusterIndex) + { + // Get the current tranform + FbxAMatrix Matrix; + FbxCluster* Cluster = Skin->GetCluster(ClusterIndex); + Cluster->GetTransformLinkMatrix(Matrix); + + // Update the rotation + FbxDouble3 Rotation = Cluster->GetLink()->PostRotation.Get(); + Matrix.SetR(Rotation); + Cluster->SetTransformLinkMatrix(Matrix); + } + } + } + } for (int ChildIndex = 0; ChildIndex < RootNode->GetChildCount(); ++ChildIndex) { - FbxNode * ChildNode = RootNode->GetChild(ChildIndex); - FixBoneRotations(ChildNode); + FbxNode* ChildNode = RootNode->GetChild(ChildIndex); + FixClusterTranformLinks(Scene, ChildNode); } } -void FDazToUnrealFbx::FixBindPose(FbxScene* Scene, FbxNode* RootNode) +void FDazToUnrealFbx::AddWeightsToAllNodes(FbxNode* Parent) { - if (RootNode == nullptr) return; - - for (int PoseIndex = 0; PoseIndex < Scene->GetPoseCount(); PoseIndex++) + //for (int ChildIndex = Parent->GetChildCount() - 1; ChildIndex >= 0; --ChildIndex) { - FbxPose* Pose = Scene->GetPose(PoseIndex); - int PoseNodeIndex = Pose->Find(RootNode); - if (PoseNodeIndex != -1) - { - FbxMatrix Matrix = Pose->GetMatrix(PoseNodeIndex); + FbxNode* ChildNode = Parent;//Parent->GetChild(ChildIndex); + //RootBone->AddChild(ChildNode); + FString ChildName = UTF8_TO_TCHAR(ChildNode->GetName()); + UE_LOG(LogTemp, Warning, TEXT("ChildNode %s Checking Weights"), *ChildName); - FbxVector4 TargetPosition; - FbxVector4 TargetRotation; - FbxVector4 TargetShearing; - FbxVector4 TargetScale; - double Sign; - Matrix.GetElements(TargetPosition, TargetRotation, TargetShearing, TargetScale, Sign); - - FbxDouble3 Rotation = RootNode->PostRotation.Get(); - - TargetRotation.Set(Rotation[0], Rotation[1], Rotation[2]); - Matrix.SetTRS(TargetPosition, TargetRotation, TargetScale); - - Pose->Remove(PoseNodeIndex); - Pose->Add(RootNode, Matrix, true); - } - /*for (int PoseNodeIndex = 0; PoseNodeIndex < Pose->GetCount(); PoseNodeIndex++) + if (FbxGeometry* NodeGeometry = static_cast(ChildNode->GetMesh())) { - FbxNode* PoseNode = Pose->GetNode(PoseIndex); - if (PoseNode == RootNode) + UE_LOG(LogTemp, Warning, TEXT(" No Deformers"), *ChildName); + if (NodeGeometry->GetDeformerCount() == 0) { - + FbxCluster* Cluster = FbxCluster::Create(Parent->GetScene(), ""); + Cluster->SetLink(Parent); + Cluster->SetLinkMode(FbxCluster::eTotalOne); + + FbxSkin* Skin = FbxSkin::Create(Parent->GetScene(), ""); + Skin->AddCluster(Cluster); + NodeGeometry->AddDeformer(Skin); + + for (int PolygonIndex = 0; PolygonIndex < ChildNode->GetMesh()->GetPolygonCount(); ++PolygonIndex) + { + for (int VertexIndex = 0; VertexIndex < ChildNode->GetMesh()->GetPolygonSize(PolygonIndex); ++VertexIndex) + { + int Vertex = ChildNode->GetMesh()->GetPolygonVertex(PolygonIndex, VertexIndex); + Cluster->AddControlPointIndex(Vertex, 1.0f); + } + } } - }*/ + } + + //AddWeightsToAllNodes(ChildNode); } - //RootNode->LclRotation.Set(RootNode->PostRotation.Get()); - for (int ChildIndex = 0; ChildIndex < RootNode->GetChildCount(); ++ChildIndex) - { - FbxNode * ChildNode = RootNode->GetChild(ChildIndex); - FixBindPose(Scene, ChildNode); - } -} \ No newline at end of file +} diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealMaterials.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealMaterials.cpp index 8835481..0204ffa 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealMaterials.cpp +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealMaterials.cpp @@ -87,6 +87,7 @@ FSoftObjectPath FDazToUnrealMaterials::GetBaseMaterial(FString MaterialName, TAr MaterialName.EndsWith(TEXT("_Hips")) || MaterialName.EndsWith(TEXT("_Feet")) || MaterialName.EndsWith(TEXT("_Torso")) || + MaterialName.EndsWith(TEXT("_Body")) || MaterialName.EndsWith(TEXT("_Neck")) || MaterialName.EndsWith(TEXT("_Shoulders")) || MaterialName.EndsWith(TEXT("_Arms")) || @@ -208,22 +209,9 @@ UMaterialInstanceConstant* FDazToUnrealMaterials::CreateMaterial(const FString C } } } - /* - // Set the default material type - if (CachedSettings->DefaultShaderMaterials.Contains(ShaderName)) - { - BaseMaterialAssetPath = CachedSettings->DefaultShaderMaterials[ShaderName]; - }*/ - /*if (AssetType == TEXT("Follower/Hair")) - { - BaseMaterialAssetPath = CachedSettings->BaseHairMaterial; - if (MaterialName.EndsWith(TEXT("_scalp"))) - { - BaseMaterialAssetPath = CachedSettings->BaseScalpMaterial; - } - }*/ - /*else*/ if (AssetType == TEXT("Follower/Attachment/Head/Face/Eyelashes")) + if (AssetType == TEXT("Follower/Attachment/Head/Face/Eyelashes") || + AssetType == TEXT("Follower/Attachment/Head/Face/Eyes")) { if (MaterialName.Contains(TEXT("_EyeMoisture")) || MaterialName.EndsWith(TEXT("_EyeReflection"))) { @@ -233,6 +221,13 @@ UMaterialInstanceConstant* FDazToUnrealMaterials::CreateMaterial(const FString C SetMaterialProperty(MaterialName, TEXT("Diffuse Color"), TEXT("Color"), TEXT("#bababa"), MaterialProperties); SetMaterialProperty(MaterialName, TEXT("Index of Refraction"), TEXT("Double"), TEXT("1.0"), MaterialProperties); } + else if (MaterialName.EndsWith(TEXT("_Tear"))) + { + //BaseMaterialAssetPath = CachedSettings->BaseCorneaMaterial; + SetMaterialProperty(MaterialName, TEXT("Metallic Weight"), TEXT("Double"), TEXT("1"), MaterialProperties); + SetMaterialProperty(MaterialName, TEXT("Opacity Strength"), TEXT("Double"), TEXT("0.05"), MaterialProperties); + SetMaterialProperty(MaterialName, TEXT("Index of Refraction"), TEXT("Double"), TEXT("1.0"), MaterialProperties); + } else { //BaseMaterialAssetPath = CachedSettings->BaseAlphaMaterial; diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealPoses.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealPoses.cpp new file mode 100644 index 0000000..1f78131 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealPoses.cpp @@ -0,0 +1,78 @@ +#include "DazToUnrealPoses.h" +#include "DazToUnrealSettings.h" +#include "DazToUnrealTextures.h" +#include "DazToUnrealUtils.h" + +#include "Animation/Skeleton.h" +#include "Animation/AnimSequence.h" +#include "Animation/PoseAsset.h" + +#include "Factories/PoseAssetFactory.h" +#include "IContentBrowserSingleton.h" +#include "ContentBrowserModule.h" +#include "AssetRegistryModule.h" +#include "PackageTools.h" + +// Partially taken from UPoseAssetFactory::FactoryCreateNew +void FDazToUnrealPoses::CreatePoseAsset(UAnimSequence* SourceAnimation, TArray PoseNames) +{ + if (SourceAnimation) + { + const UDazToUnrealSettings* CachedSettings = GetDefault(); + + // Make sure the path exists + FString ContentDirectory = FPaths::ProjectContentDir(); + FString DAZPoseImportFolder = CachedSettings->PoseImportDirectory.Path.Replace(TEXT("/Game/"), *ContentDirectory); + if (!FDazToUnrealUtils::MakeDirectoryAndCheck(DAZPoseImportFolder)) + { + //log error + } + + USkeleton* TargetSkeleton = SourceAnimation->GetSkeleton(); + + TArray InputPoseNames; + if (PoseNames.Num() > 0) + { + for (int32 Index = 0; Index < PoseNames.Num(); ++Index) + { + FName PoseName = FName(*PoseNames[Index]); + FSmartName NewName; + if (TargetSkeleton->GetSmartNameByName(USkeleton::AnimCurveMappingName, PoseName, NewName) == false) + { + // if failed, add it + TargetSkeleton->AddSmartNameAndModify(USkeleton::AnimCurveMappingName, PoseName, NewName); + } + + // we want same names in multiple places + InputPoseNames.AddUnique(NewName); + } + } + + FString PackageName = UPackageTools::SanitizePackageName(*(CachedSettings->PoseImportDirectory.Path / FString(SourceAnimation->GetName()))); +#if ENGINE_MAJOR_VERSION == 4 && ENGINE_MINOR_VERSION < 26 + UPackage* Pkg = CreatePackage(nullptr, *PackageName); +#else + UPackage* Pkg = CreatePackage(*PackageName); +#endif + EObjectFlags Flags = RF_Public | RF_Standalone | RF_Transactional; + + UPoseAsset* PoseAsset = NewObject(Pkg, FName(*SourceAnimation->GetName()), Flags); + //PoseAsset->bAdditivePose = true; + //PoseAsset->BasePoseIndex = 0; + PoseAsset->CreatePoseFromAnimation(SourceAnimation, &InputPoseNames); + PoseAsset->SetSkeleton(TargetSkeleton); + PoseAsset->ConvertSpace(true, 0); + } + //return NewPose; + + //UPoseAssetFactory* Factory = NewObject(); + //Factory->TargetSkeleton = Skeleton; + //Factory->PreviewSkeletalMesh = SkeletalMesh; + + //FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser"); + //ContentBrowserModule.Get().CreateNewAsset(Name, FPackageName::GetLongPackagePath(PackageName), T::StaticClass(), Factory); + + /*UPoseAsset* PoseAsset = NewObject(InParent, Class, Name, Flags); + PoseAsset->CreatePoseFromAnimation(SourceAnimation, &InputPoseNames); + PoseAsset->SetSkeleton(TargetSkeleton);*/ +} \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealUtils.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealUtils.cpp index 7cfacbb..5539d85 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealUtils.cpp +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Private/DazToUnrealUtils.cpp @@ -1,4 +1,6 @@ #include "DazToUnrealUtils.h" +#include "GenericPlatform/GenericPlatformFile.h" +#include "Misc/Paths.h" FString FDazToUnrealUtils::SanitizeName(FString OriginalName) { @@ -13,4 +15,18 @@ FString FDazToUnrealUtils::SanitizeName(FString OriginalName) .Replace(TEXT(">"), TEXT("_")) .Replace(TEXT("?"), TEXT("_")) .Replace(TEXT("\\"), TEXT("_")); +} + +bool FDazToUnrealUtils::MakeDirectoryAndCheck(FString& Directory) +{ + IPlatformFile& PlatformFile = FPlatformFileManager::Get().GetPlatformFile(); + if (!FPaths::DirectoryExists(Directory)) + { + PlatformFile.CreateDirectory(*Directory); + if (!FPaths::DirectoryExists(Directory)) + { + return false; + } + } + return true; } \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnreal.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnreal.h index 7f4c378..eeb64fb 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnreal.h +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnreal.h @@ -20,7 +20,8 @@ enum DazAssetType SkeletalMesh, StaticMesh, Animation, - Environment + Environment, + Pose }; diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealFbx.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealFbx.h index 63a6490..45d83af 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealFbx.h +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealFbx.h @@ -60,9 +60,8 @@ class FDazToUnrealFbx { public: static void RenameDuplicateBones(FbxNode* RootNode); - static void FixBoneRotations(FbxNode* RootNode); - static void FixBindPose(FbxScene* Scene, FbxNode* RootNode); - + static void FixClusterTranformLinks(FbxScene* Scene, FbxNode* RootNode); + static void AddWeightsToAllNodes(FbxNode* Parent); private: static void RenameDuplicateBones(FbxNode* RootNode, TMap& ExistingBones); }; \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealPoses.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealPoses.h new file mode 100644 index 0000000..02625b8 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealPoses.h @@ -0,0 +1,11 @@ +#pragma once + +#include "CoreMinimal.h" +#include "DazToUnrealEnums.h" + + +class FDazToUnrealPoses +{ +public: + static void CreatePoseAsset(UAnimSequence* SourceAnimation, TArray PoseNames); +}; \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealSettings.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealSettings.h index 3fa7c85..93e6b1b 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealSettings.h +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealSettings.h @@ -19,11 +19,16 @@ class DAZTOUNREAL_API UDazToUnrealSettings : public UDeveloperSettings Port = 32345; ImportDirectory.Path = TEXT("/Game/DazToUnreal"); AnimationImportDirectory.Path = TEXT("/Game/DazToUnreal/Animation"); + PoseImportDirectory.Path = TEXT("/Game/DazToUnreal/Pose"); ShowFBXImportDialog = false; FrameZeroIsReferencePose = false; + FixBoneRotationsOnImport = false; + Genesis1Skeleton = FSoftObjectPath(TEXT("/DazToUnreal/Genesis1BaseSkeleton.Genesis1BaseSkeleton")); Genesis3Skeleton = FSoftObjectPath(TEXT("/DazToUnreal/Genesis3BaseSkeleton.Genesis3BaseSkeleton")); Genesis8Skeleton = FSoftObjectPath(TEXT("/DazToUnreal/Genesis8BaseSkeleton.Genesis8BaseSkeleton")); + OtherSkeletons.Add(TEXT("Genesis8_1Male"), FSoftObjectPath(TEXT("/DazToUnreal/Genesis8BaseSkeleton.Genesis8BaseSkeleton"))); + OtherSkeletons.Add(TEXT("Genesis8_1Female"), FSoftObjectPath(TEXT("/DazToUnreal/Genesis8BaseSkeleton.Genesis8BaseSkeleton"))); Genesis1PostProcessAnimation = FSoftClassPath(TEXT("/DazToUnreal/Genesis1JCMPostProcess.Genesis1JCMPostProcess_C")); Genesis3PostProcessAnimation = FSoftClassPath(TEXT("/DazToUnreal/Genesis3JCMPostProcess.Genesis3JCMPostProcess_C")); @@ -33,11 +38,13 @@ class DAZTOUNREAL_API UDazToUnrealSettings : public UDeveloperSettings BaseShaderMaterials.Add(TEXT("omUberSurface"), FSoftObjectPath(TEXT("/DazToUnreal/omUberBaseMaterial.omUberBaseMaterial"))); BaseShaderMaterials.Add(TEXT("AoA_Subsurface"), FSoftObjectPath(TEXT("/DazToUnreal/AoASubsurfaceBaseMaterial.AoASubsurfaceBaseMaterial"))); BaseShaderMaterials.Add(TEXT("Iray Uber"), FSoftObjectPath(TEXT("/DazToUnreal/IrayUberBaseMaterial.IrayUberBaseMaterial"))); + BaseShaderMaterials.Add(TEXT("PBRSkin"), FSoftObjectPath(TEXT("/DazToUnreal/BasePBRSkinMaterial.BasePBRSkinMaterial"))); SkinShaderMaterials.Add(TEXT("Daz Studio Default"), FSoftObjectPath(TEXT("/DazToUnreal/DSDBaseMaterial.DSDBaseMaterial"))); SkinShaderMaterials.Add(TEXT("omUberSurface"), FSoftObjectPath(TEXT("/DazToUnreal/omUberSkinMaterial.omUberSkinMaterial"))); SkinShaderMaterials.Add(TEXT("AoA_Subsurface"), FSoftObjectPath(TEXT("/DazToUnreal/AoASubsurfaceSkinMaterial.AoASubsurfaceSkinMaterial"))); SkinShaderMaterials.Add(TEXT("Iray Uber"), FSoftObjectPath(TEXT("/DazToUnreal/IrayUberSkinMaterial.IrayUberSkinMaterial"))); + SkinShaderMaterials.Add(TEXT("PBRSkin"), FSoftObjectPath(TEXT("/DazToUnreal/BasePBRSkinMaterial.BasePBRSkinMaterial"))); BaseMaterial = FSoftObjectPath(TEXT("/DazToUnreal/BaseMaterial.BaseMaterial")); BaseAlphaMaterial = FSoftObjectPath(TEXT("/DazToUnreal/BaseAlphaMaterial.BaseAlphaMaterial")); @@ -94,6 +101,10 @@ class DAZTOUNREAL_API UDazToUnrealSettings : public UDeveloperSettings UPROPERTY(config, EditAnywhere, Category = PluginSettings, meta = (RelativeToGameContentDir, LongPackageName)) FDirectoryPath AnimationImportDirectory; + /** Directory PoseAssets will be imported to */ + UPROPERTY(config, EditAnywhere, Category = PluginSettings, meta = (RelativeToGameContentDir, LongPackageName)) + FDirectoryPath PoseImportDirectory; + /** Show the FBX Import dialog when importing the udpated FBX file */ UPROPERTY(config, EditAnywhere, Category = PluginSettings, meta = (RelativeToGameContentDir, LongPackageName)) bool ShowFBXImportDialog; @@ -102,6 +113,10 @@ class DAZTOUNREAL_API UDazToUnrealSettings : public UDeveloperSettings UPROPERTY(config, EditAnywhere, Category = PluginSettings) bool FrameZeroIsReferencePose; + /** Updates the bones to use a locale rotation. This currently breaks animations coming from Daz Studio. */ + UPROPERTY(config, EditAnywhere, Category = PluginSettings) + bool FixBoneRotationsOnImport; + /** Skeleton to use for Genesis 1 characters */ UPROPERTY(config, EditAnywhere, Category = SkeletonSettings, meta = (AllowedClasses = "Skeleton")) FSoftObjectPath Genesis1Skeleton; diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealUtils.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealUtils.h index 27253a9..cfccc20 100644 --- a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealUtils.h +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnreal/Public/DazToUnrealUtils.h @@ -7,4 +7,5 @@ class FDazToUnrealUtils { public: static FString SanitizeName(FString OriginalName); + static bool MakeDirectoryAndCheck(FString& Directory); }; \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/DazToUnrealDeveloper.Build.cs b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/DazToUnrealDeveloper.Build.cs new file mode 100644 index 0000000..9e2e6c2 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/DazToUnrealDeveloper.Build.cs @@ -0,0 +1,38 @@ +// Copyright 2018-2019 David Vodhanel. All Rights Reserved. + +using UnrealBuildTool; + +public class DazToUnrealDeveloper : ModuleRules +{ + public DazToUnrealDeveloper(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; + + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "UnrealEd", + "Slate", + "SlateCore", + "AnimGraph", + "AnimGraphRuntime", + "BlueprintGraph", + "EditorStyle", + "DazToUnrealRuntime", + // ... add private dependencies that you statically link with here ... + } + ); + } +} diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Private/AnimGraphNode_DazMorphDriver.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Private/AnimGraphNode_DazMorphDriver.cpp new file mode 100644 index 0000000..c353722 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Private/AnimGraphNode_DazMorphDriver.cpp @@ -0,0 +1,466 @@ +// This node is a stripped down version of UAnimGraphNode_BoneDrivenController with some rotation improvements. + +#include "AnimGraphNode_DazMorphDriver.h" +#include "AnimNode_DazMorphDriver.h" +#include "SceneManagement.h" +#include "Components/SkeletalMeshComponent.h" +#include "Widgets/Text/STextBlock.h" +#include "Kismet2/CompilerResultsLog.h" +#include "PropertyHandle.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "IDetailPropertyRow.h" +#include "DetailCategoryBuilder.h" +#include "AnimationCustomVersion.h" + +#define LOCTEXT_NAMESPACE "DazToUnreal" + +UAnimGraphNode_DazMorphDriver::UAnimGraphNode_DazMorphDriver(const FObjectInitializer& ObjectInitializer) + :Super(ObjectInitializer) +{ + +} + +void UAnimGraphNode_DazMorphDriver::Serialize(FArchive& Ar) +{ + Super::Serialize(Ar); + + Ar.UsingCustomVersion(FAnimationCustomVersion::GUID); + + const int32 AnimVersion = Ar.CustomVer(FAnimationCustomVersion::GUID); + + if (AnimVersion < FAnimationCustomVersion::BoneDrivenControllerRemapping) + { + if (AnimVersion < FAnimationCustomVersion::BoneDrivenControllerMatchingMaya) + { + + // The old definition of range was clamping the output, rather than the input + if (Node.bUseRange && !FMath::IsNearlyZero(Node.Multiplier)) + { + // Before: Output = clamp(Input * Multipler) + // After: Output = clamp(Input) * Multiplier + Node.RangeMin /= Node.Multiplier; + Node.RangeMax /= Node.Multiplier; + } + } + + Node.RemappedMin = Node.RangeMin; + Node.RemappedMax = Node.RangeMax; + } +} + +FText UAnimGraphNode_DazMorphDriver::GetTooltipText() const +{ + return LOCTEXT("UAnimGraphNode_DazMorphDriver_ToolTip", "Drives a morph target using the transform of a bone"); +} + +FText UAnimGraphNode_DazMorphDriver::GetNodeTitle(ENodeTitleType::Type TitleType) const +{ + if ((Node.SourceBone.BoneName == NAME_None) && ((TitleType == ENodeTitleType::ListView) || (TitleType == ENodeTitleType::MenuTitle))) + { + return GetControllerDescription(); + } + else + { + // Determine the mapping + FText FinalSourceExpression; + { + FFormatNamedArguments SourceArgs; + SourceArgs.Add(TEXT("SourceBone"), FText::FromName(Node.SourceBone.BoneName)); + SourceArgs.Add(TEXT("SourceComponent"), ComponentTypeToText(Node.SourceComponent)); + + if (Node.DrivingCurve != nullptr) + { + FinalSourceExpression = LOCTEXT("BoneDrivenByCurve", "curve({SourceBone}.{SourceComponent})"); + } + else + { + if (Node.bUseRange) + { + if (Node.Multiplier == 1.0f) + { + FinalSourceExpression = LOCTEXT("WithRangeBoneMultiplierIs1", "remap({SourceBone}.{SourceComponent})"); + } + else + { + SourceArgs.Add(TEXT("Multiplier"), FText::AsNumber(Node.Multiplier)); + FinalSourceExpression = LOCTEXT("WithRangeNonUnityMultiplier", "remap({SourceBone}.{SourceComponent}) * {Multiplier}"); + } + } + else + { + if (Node.Multiplier == 1.0f) + { + FinalSourceExpression = LOCTEXT("BoneMultiplierIs1", "{SourceBone}.{SourceComponent}"); + } + else + { + SourceArgs.Add(TEXT("Multiplier"), FText::AsNumber(Node.Multiplier)); + FinalSourceExpression = LOCTEXT("NonUnityMultiplier", "{SourceBone}.{SourceComponent} * {Multiplier}"); + } + } + } + + FinalSourceExpression = FText::Format(FinalSourceExpression, SourceArgs); + } + + FFormatNamedArguments Args; + Args.Add(TEXT("ControllerDesc"), GetControllerDescription()); + + Args.Add(TEXT("MorphName"), FText::FromName(Node.MorphName)); + + + // Determine the target component + int32 NumComponents = 0; + EComponentType::Type TargetComponent = EComponentType::None; + + #define UE_CHECK_TARGET_COMPONENT(ComponentProperty, ComponentChoice) if (ComponentProperty) { ++NumComponents; TargetComponent = ComponentChoice; } + + Args.Add(TEXT("TargetComponents"), (NumComponents <= 1) ? ComponentTypeToText(TargetComponent) : LOCTEXT("MultipleTargetComponents", "multiple")); + + if ((TitleType == ENodeTitleType::ListView) || (TitleType == ENodeTitleType::MenuTitle)) + { + Args.Add(TEXT("Delim"), FText::FromString(TEXT(" - "))); + } + else + { + Args.Add(TEXT("Delim"), FText::FromString(TEXT("\n"))); + } + + Args.Add(TEXT("SourceExpression"), FinalSourceExpression); + + FText FormattedText; + + FormattedText = FText::Format(LOCTEXT("AnimGraphNode_BoneDrivenController_Title_Curve", "{MorphName} = {SourceExpression}{Delim}{ControllerDesc}"), Args); + + + return FormattedText; + } +} + +FText UAnimGraphNode_DazMorphDriver::GetControllerDescription() const +{ + return LOCTEXT("DazMorphDriver", "Daz Morph Driver"); +} + +void UAnimGraphNode_DazMorphDriver::Draw(FPrimitiveDrawInterface* PDI, USkeletalMeshComponent* SkelMeshComp) const +{ + static const float ArrowHeadWidth = 5.0f; + static const float ArrowHeadHeight = 8.0f; + + const int32 SourceIdx = SkelMeshComp->GetBoneIndex(Node.SourceBone.BoneName); + + + if ((SourceIdx != INDEX_NONE)) + { + const FTransform SourceTM = SkelMeshComp->GetComponentSpaceTransforms()[SourceIdx] * SkelMeshComp->GetComponentTransform(); + + PDI->DrawPoint(SourceTM.GetTranslation(), FLinearColor(0.8f, 0.8f, 0.2f), 5.0f, SDPG_Foreground); + + } +} + +void UAnimGraphNode_DazMorphDriver::ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog) +{ + if (ForSkeleton->GetReferenceSkeleton().FindBoneIndex(Node.SourceBone.BoneName) == INDEX_NONE) + { + MessageLog.Warning(*LOCTEXT("DriverJoint_NoSourceBone", "@@ - You must pick a source bone as the Driver joint").ToString(), this); + } + + if (Node.SourceComponent == EComponentType::None) + { + MessageLog.Warning(*LOCTEXT("DriverJoint_NoSourceComponent", "@@ - You must pick a source component on the Driver joint").ToString(), this); + } + + Super::ValidateAnimNodeDuringCompilation(ForSkeleton, MessageLog); +} + + +void UAnimGraphNode_DazMorphDriver::AddTripletPropertyRow(const FText& Name, const FText& Tooltip, IDetailCategoryBuilder& Category, TSharedRef PropertyHandle, const FName XPropertyName, const FName YPropertyName, const FName ZPropertyName, TAttribute VisibilityAttribute) +{ + const float XYZPadding = 5.0f; + + TSharedPtr XProperty = PropertyHandle->GetChildHandle(XPropertyName); + Category.GetParentLayout().HideProperty(XProperty); + + TSharedPtr YProperty = PropertyHandle->GetChildHandle(YPropertyName); + Category.GetParentLayout().HideProperty(YProperty); + + TSharedPtr ZProperty = PropertyHandle->GetChildHandle(ZPropertyName); + Category.GetParentLayout().HideProperty(ZProperty); + + Category.AddCustomRow(Name) + .Visibility(VisibilityAttribute) + .NameContent() + [ + SNew(STextBlock) + .Text(Name) + .ToolTipText(Tooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(0.f, 0.f, XYZPadding, 0.f) + .AutoWidth() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + XProperty->CreatePropertyNameWidget() + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + XProperty->CreatePropertyValueWidget() + ] + ] + + + SHorizontalBox::Slot() + .Padding(0.f, 0.f, XYZPadding, 0.f) + .AutoWidth() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + YProperty->CreatePropertyNameWidget() + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + YProperty->CreatePropertyValueWidget() + ] + ] + + + SHorizontalBox::Slot() + .Padding(0.f, 0.f, XYZPadding, 0.f) + .AutoWidth() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .AutoWidth() + [ + ZProperty->CreatePropertyNameWidget() + ] + + SHorizontalBox::Slot() + .AutoWidth() + [ + ZProperty->CreatePropertyValueWidget() + ] + ] + ]; +} + +void UAnimGraphNode_DazMorphDriver::AddRangePropertyRow(const FText& Name, const FText& Tooltip, IDetailCategoryBuilder& Category, TSharedRef PropertyHandle, const FName MinPropertyName, const FName MaxPropertyName, TAttribute VisibilityAttribute) +{ + const float MiddlePadding = 4.0f; + + TSharedPtr MinProperty = PropertyHandle->GetChildHandle(MinPropertyName); + Category.GetParentLayout().HideProperty(MinProperty); + + TSharedPtr MaxProperty = PropertyHandle->GetChildHandle(MaxPropertyName); + Category.GetParentLayout().HideProperty(MaxProperty); + + Category.AddCustomRow(Name) + .Visibility(VisibilityAttribute) + .NameContent() + [ + SNew(STextBlock) + .Text(Name) + .ToolTipText(Tooltip) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + .MinDesiredWidth(100.0f * 2.0f) + .MaxDesiredWidth(100.0f * 2.0f) + [ + SNew(SHorizontalBox) + + +SHorizontalBox::Slot() + .FillWidth(1) + .Padding(0.0f, 0.0f, MiddlePadding, 0.0f) + .VAlign(VAlign_Center) + [ + MinProperty->CreatePropertyValueWidget() + ] + + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(STextBlock) + .Text(LOCTEXT("MinMaxSpacer", "..")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + + +SHorizontalBox::Slot() + .FillWidth(1) + .Padding(MiddlePadding, 0.0f, 0.0f, 0.0f) + .VAlign(VAlign_Center) + [ + MaxProperty->CreatePropertyValueWidget() + ] + ]; +} + +void UAnimGraphNode_DazMorphDriver::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) +{ + Super::CustomizeDetails(DetailBuilder); + + TSharedRef NodeHandle = DetailBuilder.GetProperty(GET_MEMBER_NAME_CHECKED(UAnimGraphNode_DazMorphDriver, Node), GetClass()); + + // if it doesn't have this node, that means this isn't really bone driven controller + if (!NodeHandle->IsValidHandle()) + { + return; + } + + TAttribute NotUsingCurveVisibility = TAttribute::Create(TAttribute::FGetter::CreateStatic(&UAnimGraphNode_DazMorphDriver::AreNonCurveMappingValuesVisible, &DetailBuilder)); + TAttribute MapRangeVisiblity = TAttribute::Create(TAttribute::FGetter::CreateStatic(&UAnimGraphNode_DazMorphDriver::AreRemappingValuesVisible, &DetailBuilder)); + + // Source (Driver) category + IDetailCategoryBuilder& SourceCategory = DetailBuilder.EditCategory(TEXT("Source (Driver)")); + + // Mapping category + IDetailCategoryBuilder& MappingCategory = DetailBuilder.EditCategory(TEXT("Mapping")); + MappingCategory.AddProperty(NodeHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, DrivingCurve))); + + MappingCategory.AddProperty(NodeHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, bUseRange))).Visibility(NotUsingCurveVisibility); + + AddRangePropertyRow( + /*Name=*/ LOCTEXT("InputRangeLabel", "Source Range"), + /*Tooltip=*/ LOCTEXT("InputRangeTooltip", "The range (relative to the reference pose) over which to limit the effect of the input component on the output component"), + /*Category=*/ MappingCategory, + /*PropertyHandle=*/ NodeHandle, + /*X=*/ GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, RangeMin), + /*Y=*/ GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, RangeMax), + MapRangeVisiblity); + AddRangePropertyRow( + /*Name=*/ LOCTEXT("MappedRangeLabel", "Mapped Range"), + /*Tooltip=*/ LOCTEXT("MappedRangeTooltip", "The range of mapped values that correspond to the input range"), + /*Category=*/ MappingCategory, + /*PropertyHandle=*/ NodeHandle, + /*X=*/ GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, RemappedMin), + /*Y=*/ GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, RemappedMax), + MapRangeVisiblity); + + MappingCategory.AddProperty(NodeHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, Multiplier))).Visibility(NotUsingCurveVisibility); + + // Destination visiblity + TAttribute BoneTargetVisibility = TAttribute::Create(TAttribute::FGetter::CreateStatic(&UAnimGraphNode_DazMorphDriver::AreTargetBonePropertiesVisible, &DetailBuilder)); + TAttribute CurveTargetVisibility = TAttribute::Create(TAttribute::FGetter::CreateStatic(&UAnimGraphNode_DazMorphDriver::AreTargetCurvePropertiesVisible, &DetailBuilder)); + + // Destination (Driven) category + IDetailCategoryBuilder& TargetCategory = DetailBuilder.EditCategory(TEXT("Destination (Driven)")); + + //TargetCategory.AddProperty(NodeHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, DestinationMode))); + + TargetCategory.AddProperty(NodeHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, MorphName))).Visibility(CurveTargetVisibility); + + // Add a note about the space (it is not configurable, and this helps set expectations) + const FText TargetBoneSpaceName = LOCTEXT("TargetComponentSpace", "Target Component Space"); + TargetCategory.AddCustomRow(TargetBoneSpaceName) + .NameContent() + [ + SNew(STextBlock) + .Text(TargetBoneSpaceName) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .ValueContent() + [ + SNew(STextBlock) + .Text(LOCTEXT("TargetComponentSpaceIsAlwaysParentBoneSpace", "Parent Bone Space")) + .Font(IDetailLayoutBuilder::GetDetailFont()) + ] + .Visibility(BoneTargetVisibility); + + + //TargetCategory.AddProperty(NodeHandle->GetChildHandle(GET_MEMBER_NAME_CHECKED(FAnimNode_DazMorphDriver, ModificationMode))).Visibility(BoneTargetVisibility); +} + +FText UAnimGraphNode_DazMorphDriver::ComponentTypeToText(EComponentType::Type Component) +{ + switch (Component) + { + case EComponentType::TranslationX: + return LOCTEXT("ComponentType_TranslationX", "translateX"); + case EComponentType::TranslationY: + return LOCTEXT("ComponentType_TranslationY", "translateY"); + case EComponentType::TranslationZ: + return LOCTEXT("ComponentType_TranslationZ", "translateZ"); + case EComponentType::RotationX: + return LOCTEXT("ComponentType_RotationX", "rotateX"); + case EComponentType::RotationY: + return LOCTEXT("ComponentType_RotationY", "rotateY"); + case EComponentType::RotationZ: + return LOCTEXT("ComponentType_RotationZ", "rotateZ"); + case EComponentType::Scale: + return LOCTEXT("ComponentType_ScaleMax", "scaleMax"); + case EComponentType::ScaleX: + return LOCTEXT("ComponentType_ScaleX", "scaleX"); + case EComponentType::ScaleY: + return LOCTEXT("ComponentType_ScaleY", "scaleY"); + case EComponentType::ScaleZ: + return LOCTEXT("ComponentType_ScaleZ", "scaleZ"); + default: + return LOCTEXT("ComponentType_None", "(none)"); + } +} + +EVisibility UAnimGraphNode_DazMorphDriver::AreNonCurveMappingValuesVisible(IDetailLayoutBuilder* DetailLayoutBuilder) +{ + const TArray>& SelectedObjectsList = DetailLayoutBuilder->GetSelectedObjects(); + for(TWeakObjectPtr Object : SelectedObjectsList) + { + if(UAnimGraphNode_DazMorphDriver* BoneDrivenController = Cast(Object.Get())) + { + if (BoneDrivenController->Node.DrivingCurve == nullptr) + { + return EVisibility::Visible; + } + } + } + + return EVisibility::Collapsed; +} + +EVisibility UAnimGraphNode_DazMorphDriver::AreRemappingValuesVisible(IDetailLayoutBuilder* DetailLayoutBuilder) +{ + const TArray>& SelectedObjectsList = DetailLayoutBuilder->GetSelectedObjects(); + for(TWeakObjectPtr Object : SelectedObjectsList) + { + if(UAnimGraphNode_DazMorphDriver* BoneDrivenController = Cast(Object.Get())) + { + if((BoneDrivenController->Node.DrivingCurve == nullptr) && BoneDrivenController->Node.bUseRange) + { + return EVisibility::Visible; + } + } + } + + return EVisibility::Collapsed; +} + +EVisibility UAnimGraphNode_DazMorphDriver::AreTargetBonePropertiesVisible(IDetailLayoutBuilder* DetailLayoutBuilder) +{ + return EVisibility::Collapsed; +} + +EVisibility UAnimGraphNode_DazMorphDriver::AreTargetCurvePropertiesVisible(IDetailLayoutBuilder* DetailLayoutBuilder) +{ + TArray> SelectedObjectsList = DetailLayoutBuilder->GetSelectedObjects(); + for(TWeakObjectPtr Object : SelectedObjectsList) + { + if(UAnimGraphNode_DazMorphDriver* BoneDrivenController = Cast(Object.Get())) + { + return EVisibility::Visible; + } + } + + return EVisibility::Collapsed; +} + + +#undef LOCTEXT_NAMESPACE diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Private/DazToUnrealDeveloper.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Private/DazToUnrealDeveloper.cpp new file mode 100644 index 0000000..37508d5 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Private/DazToUnrealDeveloper.cpp @@ -0,0 +1,17 @@ +#include "DazToUnrealDeveloper.h" + +#define LOCTEXT_NAMESPACE "FDazToUnrealDeveloperModule" + +void FDazToUnrealDeveloperModule::StartupModule() +{ + +} + +void FDazToUnrealDeveloperModule::ShutdownModule() +{ + +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FDazToUnrealDeveloperModule, DazToUnrealDeveloper) \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Public/AnimGraphNode_DazMorphDriver.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Public/AnimGraphNode_DazMorphDriver.h new file mode 100644 index 0000000..b9f27bb --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Public/AnimGraphNode_DazMorphDriver.h @@ -0,0 +1,68 @@ +// This node is a stripped down version of UAnimGraphNode_BoneDrivenController with some rotation improvements. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Misc/Attribute.h" +#include "Layout/Visibility.h" +#include "AnimGraphNode_SkeletalControlBase.h" +#include "AnimNode_DazMorphDriver.h" +#include "AnimGraphNode_DazMorphDriver.generated.h" + +class FCompilerResultsLog; +class FPrimitiveDrawInterface; +class IDetailCategoryBuilder; +class IDetailLayoutBuilder; +class IPropertyHandle; +class USkeletalMeshComponent; + +/** + * This is the 'source version' of a bone driven controller, which maps part of the state from one bone to another (e.g., 2 * source.x -> target.z) + */ +UCLASS() +class DAZTOUNREALDEVELOPER_API UAnimGraphNode_DazMorphDriver : public UAnimGraphNode_SkeletalControlBase +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(EditAnywhere, Category = Settings) + FAnimNode_DazMorphDriver Node; + +public: + // UObject interface + virtual void Serialize(FArchive& Ar) override; + // End of UObject interface + + // UEdGraphNode interface + virtual FText GetNodeTitle(ENodeTitleType::Type TitleType) const override; + virtual FText GetTooltipText() const override; + // End of UEdGraphNode interface + + // UAnimGraphNode_Base interface + virtual void ValidateAnimNodeDuringCompilation(USkeleton* ForSkeleton, FCompilerResultsLog& MessageLog) override; + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; + // End of UAnimGraphNode_Base interface + + // UAnimGraphNode_SkeletalControlBase interface + virtual void Draw(FPrimitiveDrawInterface* PDI, USkeletalMeshComponent* SkelMeshComp) const override; + // End of UAnimGraphNode_SkeletalControlBase interface + +protected: + + // UAnimGraphNode_SkeletalControlBase protected interface + virtual FText GetControllerDescription() const override; + virtual const FAnimNode_SkeletalControlBase* GetNode() const override { return &Node; } + // End of UAnimGraphNode_SkeletalControlBase protected interface + + // Should non-curve mapping values be shown (multiplier, range)? + static EVisibility AreNonCurveMappingValuesVisible(IDetailLayoutBuilder* DetailLayoutBuilder); + static EVisibility AreRemappingValuesVisible(IDetailLayoutBuilder* DetailLayoutBuilder); + + // Should destination bone or morph target properties be visible + static EVisibility AreTargetBonePropertiesVisible(IDetailLayoutBuilder* DetailLayoutBuilder); + static EVisibility AreTargetCurvePropertiesVisible(IDetailLayoutBuilder* DetailLayoutBuilder); + + static void AddTripletPropertyRow(const FText& Name, const FText& Tooltip, IDetailCategoryBuilder& Category, TSharedRef PropertyHandle, const FName XPropertyName, const FName YPropertyName, const FName ZPropertyName, TAttribute VisibilityAttribute); + static void AddRangePropertyRow(const FText& Name, const FText& Tooltip, IDetailCategoryBuilder& Category, TSharedRef PropertyHandle, const FName MinPropertyName, const FName MaxPropertyName, TAttribute VisibilityAttribute); + static FText ComponentTypeToText(EComponentType::Type Component); +}; diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Public/DazToUnrealDeveloper.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Public/DazToUnrealDeveloper.h new file mode 100644 index 0000000..6f8879d --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealDeveloper/Public/DazToUnrealDeveloper.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FDazToUnrealDeveloperModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + static inline FDazToUnrealDeveloperModule& Get() + { + return FModuleManager::LoadModuleChecked< FDazToUnrealDeveloperModule >("DazToUnrealDeveloper"); + } + + +}; \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/DazToUnrealRuntime.Build.cs b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/DazToUnrealRuntime.Build.cs new file mode 100644 index 0000000..1b30b37 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/DazToUnrealRuntime.Build.cs @@ -0,0 +1,30 @@ +// Copyright 2018-2019 David Vodhanel. All Rights Reserved. + +using UnrealBuildTool; + +public class DazToUnrealRuntime : ModuleRules +{ + public DazToUnrealRuntime(ReadOnlyTargetRules Target) : base(Target) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + // ... add other public dependencies that you statically link with here ... + } + ); + + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "CoreUObject", + "Engine", + "AnimGraphRuntime", + // ... add private dependencies that you statically link with here ... + } + ); + + PrecompileForTargets = PrecompileTargetsType.Any; + } +} diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/AnimNode_DazMorphDriver.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/AnimNode_DazMorphDriver.cpp new file mode 100644 index 0000000..988194a --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/AnimNode_DazMorphDriver.cpp @@ -0,0 +1,279 @@ +// This node is a stripped down version of FAnimNode_BoneDrivenController with some rotation improvements. + +#include "AnimNode_DazMorphDriver.h" + +#include "Curves/CurveFloat.h" +#include "Animation/AnimInstanceProxy.h" +#include "Animation/AnimTrace.h" + + +FAnimNode_DazMorphDriver::FAnimNode_DazMorphDriver() + : DrivingCurve(nullptr) + , Multiplier(1.0f) + , RangeMin(-1.0f) + , RangeMax(1.0f) + , RemappedMin(0.0f) + , RemappedMax(1.0f) + , SourceComponent(EComponentType::None) + , RotationConversionOrder(EDazMorphDriverRotationOrder::Auto) + , bUseRange(false) +{ +} + +void FAnimNode_DazMorphDriver::GatherDebugData(FNodeDebugData& DebugData) +{ + DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(GatherDebugData) + FString DebugLine = DebugData.GetNodeName(this); + DebugLine += "("; + AddDebugNodeData(DebugLine); + + DebugLine += FString::Printf(TEXT(" DrivingBone: %s\nDrivenMorph: %s"), *SourceBone.BoneName.ToString(), *MorphName.ToString()); + + + DebugData.AddDebugItem(DebugLine); + + ComponentPose.GatherDebugData(DebugData); +} + +void FAnimNode_DazMorphDriver::EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) +{ + +} + +void FAnimNode_DazMorphDriver::EvaluateComponentSpaceInternal(FComponentSpacePoseContext& Context) +{ + DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(EvaluateComponentSpaceInternal) + // Early out if we're not driving from or to anything + if (SourceComponent == EComponentType::None) + { + return; + } + + // Get the Local space transform and the ref pose transform to see how the transform for the source bone has changed + const FBoneContainer& BoneContainer = Context.Pose.GetPose().GetBoneContainer(); + const FTransform& SourceRefPoseBoneTransform = BoneContainer.GetRefPoseArray()[SourceBone.BoneIndex]; + const FTransform SourceCurrentBoneTransform = Context.Pose.GetLocalSpaceTransform(SourceBone.GetCompactPoseIndex(BoneContainer)); + + const float FinalDriverValue = ExtractSourceValue(SourceCurrentBoneTransform, SourceRefPoseBoneTransform); + + USkeleton* Skeleton = Context.AnimInstanceProxy->GetSkeleton(); + SmartName::UID_Type NameUID = Skeleton->GetUIDByName(USkeleton::AnimCurveMappingName, MorphName); + if (NameUID != SmartName::MaxUID) + { + Context.Curve.Set(NameUID, FinalDriverValue); + } +} + +const float FAnimNode_DazMorphDriver::ExtractSourceValue(const FTransform &InCurrentBoneTransform, const FTransform &InRefPoseBoneTransform) +{ + // Resolve source value + float SourceValue = 0.0f; + if (SourceComponent < EComponentType::RotationX) + { + const FVector TranslationDiff = InCurrentBoneTransform.GetLocation() - InRefPoseBoneTransform.GetLocation(); + SourceValue = TranslationDiff[(int32)(SourceComponent - EComponentType::TranslationX)]; + } + else if (SourceComponent < EComponentType::Scale) + { + EDazMorphDriverRotationOrder RotationOrder = RotationConversionOrder; + + if (RotationOrder == EDazMorphDriverRotationOrder::Auto) + { + if (SourceComponent == EComponentType::RotationX) + { + RotationOrder = EDazMorphDriverRotationOrder::ZYX; + } + if (SourceComponent == EComponentType::RotationY) + { + RotationOrder = EDazMorphDriverRotationOrder::ZXY; + } + if (SourceComponent == EComponentType::RotationZ) + { + RotationOrder = EDazMorphDriverRotationOrder::YXZ; + } + } + + const FVector RotationDiff = EulerFromQuat((InCurrentBoneTransform * InRefPoseBoneTransform.Inverse()).GetRotation(), RotationOrder); + SourceValue = RotationDiff[(int32)(SourceComponent - EComponentType::RotationX)]; + if (SourceComponent != EComponentType::RotationZ) + { + SourceValue = SourceValue * -1.0f; + } + } + else if (SourceComponent == EComponentType::Scale) + { + const FVector CurrentScale = InCurrentBoneTransform.GetScale3D(); + const FVector RefScale = InRefPoseBoneTransform.GetScale3D(); + const float ScaleDiff = FMath::Max3(CurrentScale[0], CurrentScale[1], CurrentScale[2]) - FMath::Max3(RefScale[0], RefScale[1], RefScale[2]); + SourceValue = ScaleDiff; + } + else + { + const FVector ScaleDiff = InCurrentBoneTransform.GetScale3D() - InRefPoseBoneTransform.GetScale3D(); + SourceValue = ScaleDiff[(int32)(SourceComponent - EComponentType::ScaleX)]; + } + + // Determine the resulting value + float FinalDriverValue = SourceValue; + if (DrivingCurve != nullptr) + { + // Remap thru the curve if set + FinalDriverValue = DrivingCurve->GetFloatValue(FinalDriverValue); + } + else + { + // Apply the fixed function remapping/clamping + if (bUseRange) + { + const float ClampedAlpha = FMath::Clamp(FMath::GetRangePct(RangeMin, RangeMax, FinalDriverValue), 0.0f, 1.0f); + FinalDriverValue = FMath::Lerp(RemappedMin, RemappedMax, ClampedAlpha); + } + + FinalDriverValue *= Multiplier; + } + + return FinalDriverValue; +} + +bool FAnimNode_DazMorphDriver::IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) +{ + return SourceBone.IsValidToEvaluate(RequiredBones); +} + + +void FAnimNode_DazMorphDriver::InitializeBoneReferences(const FBoneContainer& RequiredBones) +{ + DECLARE_SCOPE_HIERARCHICAL_COUNTER_ANIMNODE(InitializeBoneReferences) + SourceBone.Initialize(RequiredBones); +} + +// Copied from FControlRigMathLibrary::EulerFromQuat which is in an experimental plugin +FVector FAnimNode_DazMorphDriver::EulerFromQuat(const FQuat& Rotation, EDazMorphDriverRotationOrder RotationOrder) +{ + float X = Rotation.X; + float Y = Rotation.Y; + float Z = Rotation.Z; + float W = Rotation.W; + float X2 = X * 2.f; + float Y2 = Y * 2.f; + float Z2 = Z * 2.f; + float XX2 = X * X2; + float XY2 = X * Y2; + float XZ2 = X * Z2; + float YX2 = Y * X2; + float YY2 = Y * Y2; + float YZ2 = Y * Z2; + float ZX2 = Z * X2; + float ZY2 = Z * Y2; + float ZZ2 = Z * Z2; + float WX2 = W * X2; + float WY2 = W * Y2; + float WZ2 = W * Z2; + + FVector AxisX, AxisY, AxisZ; + AxisX.X = (1.f - (YY2 + ZZ2)); + AxisY.X = (XY2 + WZ2); + AxisZ.X = (XZ2 - WY2); + AxisX.Y = (XY2 - WZ2); + AxisY.Y = (1.f - (XX2 + ZZ2)); + AxisZ.Y = (YZ2 + WX2); + AxisX.Z = (XZ2 + WY2); + AxisY.Z = (YZ2 - WX2); + AxisZ.Z = (1.f - (XX2 + YY2)); + + FVector Result = FVector::ZeroVector; + + if (RotationOrder == EDazMorphDriverRotationOrder::XYZ) + { + Result.Y = FMath::Asin(-FMath::Clamp(AxisZ.X, -1.f, 1.f)); + + if (FMath::Abs(AxisZ.X) < 1.f - SMALL_NUMBER) + { + Result.X = FMath::Atan2(AxisZ.Y, AxisZ.Z); + Result.Z = FMath::Atan2(AxisY.X, AxisX.X); + } + else + { + Result.X = 0.f; + Result.Z = FMath::Atan2(-AxisX.Y, AxisY.Y); + } + } + else if (RotationOrder == EDazMorphDriverRotationOrder::XZY) + { + + Result.Z = FMath::Asin(FMath::Clamp(AxisY.X, -1.f, 1.f)); + + if (FMath::Abs(AxisY.X) < 1.f - SMALL_NUMBER) + { + Result.X = FMath::Atan2(-AxisY.Z, AxisY.Y); + Result.Y = FMath::Atan2(-AxisZ.X, AxisX.X); + } + else + { + Result.X = 0.f; + Result.Y = FMath::Atan2(AxisX.Z, AxisZ.Z); + } + } + else if (RotationOrder == EDazMorphDriverRotationOrder::YXZ) + { + Result.X = FMath::Asin(FMath::Clamp(AxisZ.Y, -1.f, 1.f)); + + if (FMath::Abs(AxisZ.Y) < 1.f - SMALL_NUMBER) + { + Result.Y = FMath::Atan2(-AxisZ.X, AxisZ.Z); + Result.Z = FMath::Atan2(-AxisX.Y, AxisY.Y); + } + else + { + Result.Y = 0.f; + Result.Z = FMath::Atan2(AxisY.X, AxisX.X); + } + } + else if (RotationOrder == EDazMorphDriverRotationOrder::YZX) + { + Result.Z = FMath::Asin(-FMath::Clamp(AxisX.Y, -1.f, 1.f)); + + if (FMath::Abs(AxisX.Y) < 1.f - SMALL_NUMBER) + { + Result.X = FMath::Atan2(AxisZ.Y, AxisY.Y); + Result.Y = FMath::Atan2(AxisX.Z, AxisX.X); + } + else + { + Result.X = FMath::Atan2(-AxisY.Z, AxisZ.Z); + Result.Y = 0.f; + } + } + else if (RotationOrder == EDazMorphDriverRotationOrder::ZXY) + { + Result.X = FMath::Asin(-FMath::Clamp(AxisY.Z, -1.f, 1.f)); + + if (FMath::Abs(AxisY.Z) < 1.f - SMALL_NUMBER) + { + Result.Y = FMath::Atan2(AxisX.Z, AxisZ.Z); + Result.Z = FMath::Atan2(AxisY.X, AxisY.Y); + } + else + { + Result.Y = FMath::Atan2(-AxisZ.X, AxisX.X); + Result.Z = 0.f; + } + } + else if (RotationOrder == EDazMorphDriverRotationOrder::ZYX) + { + Result.Y = FMath::Asin(FMath::Clamp(AxisX.Z, -1.f, 1.f)); + + if (FMath::Abs(AxisX.Z) < 1.f - SMALL_NUMBER) + { + Result.X = FMath::Atan2(-AxisY.Z, AxisZ.Z); + Result.Z = FMath::Atan2(-AxisX.Y, AxisX.X); + } + else + { + Result.X = FMath::Atan2(AxisZ.Y, AxisY.Y); + Result.Z = 0.f; + } + } + + return Result * 180.f / PI; +} \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/DazToUnrealRuntime.cpp b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/DazToUnrealRuntime.cpp new file mode 100644 index 0000000..db22162 --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Private/DazToUnrealRuntime.cpp @@ -0,0 +1,17 @@ +#include "DazToUnrealRuntime.h" + +#define LOCTEXT_NAMESPACE "FDazToUnrealRuntimeModule" + +void FDazToUnrealRuntimeModule::StartupModule() +{ + +} + +void FDazToUnrealRuntimeModule::ShutdownModule() +{ + +} + +#undef LOCTEXT_NAMESPACE + +IMPLEMENT_MODULE(FDazToUnrealRuntimeModule, DazToUnrealRuntime) \ No newline at end of file diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/AnimNode_DazMorphDriver.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/AnimNode_DazMorphDriver.h new file mode 100644 index 0000000..6fcd93c --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/AnimNode_DazMorphDriver.h @@ -0,0 +1,109 @@ +// This node is a stripped down version of FAnimNode_BoneDrivenController with some rotation improvements. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "BoneContainer.h" +#include "BonePose.h" +#include "BoneControllers/AnimNode_SkeletalControlBase.h" +#include "Animation/AnimTypes.h" +#include "AnimNode_DazMorphDriver.generated.h" + +class UCurveFloat; +class USkeletalMeshComponent; + +UENUM() +enum class EDazMorphDriverRotationOrder : uint8 +{ + Auto, + XYZ, + XZY, + YXZ, + YZX, + ZXY, + ZYX +}; + +/** + * This is the runtime version of a bone driven controller, which maps part of the state from one bone to another (e.g., 2 * source.x -> target.z) + */ +USTRUCT() +struct DAZTOUNREALRUNTIME_API FAnimNode_DazMorphDriver : public FAnimNode_SkeletalControlBase +{ + GENERATED_USTRUCT_BODY() + + // Bone to use as controller input + UPROPERTY(EditAnywhere, Category="Source (driver)") + FBoneReference SourceBone; + + /** Curve used to map from the source attribute to the driven attributes if present (otherwise the Multiplier will be used) */ + UPROPERTY(EditAnywhere, Category=Mapping) + UCurveFloat* DrivingCurve; + + // Multiplier to apply to the input value (Note: Ignored when a curve is used) + UPROPERTY(EditAnywhere, Category=Mapping) + float Multiplier; + + // Minimum limit of the input value (mapped to RemappedMin, only used when limiting the source range) + // If this is rotation, the unit is radian + UPROPERTY(EditAnywhere, Category=Mapping, meta=(EditCondition=bUseRange, DisplayName="Source Range Min")) + float RangeMin; + + // Maximum limit of the input value (mapped to RemappedMax, only used when limiting the source range) + // If this is rotation, the unit is radian + UPROPERTY(EditAnywhere, Category=Mapping, meta=(EditCondition=bUseRange, DisplayName="Source Range Max")) + float RangeMax; + + // Minimum value to apply to the destination (remapped from the input range) + // If this is rotation, the unit is radian + UPROPERTY(EditAnywhere, Category=Mapping, meta=(EditCondition=bUseRange, DisplayName="Mapped Range Min")) + float RemappedMin; + + // Maximum value to apply to the destination (remapped from the input range) + // If this is rotation, the unit is radian + UPROPERTY(EditAnywhere, Category = Mapping, meta = (EditCondition = bUseRange, DisplayName="Mapped Range Max")) + float RemappedMax; + + /** Name of Morph Target to drive using the source attribute */ + UPROPERTY(EditAnywhere, Category = "Destination (driven)") + FName MorphName; + +public: + + // Transform component to use as input + UPROPERTY(EditAnywhere, Category="Source (driver)") + TEnumAsByte SourceComponent; + + // Transform component to use as input + UPROPERTY(EditAnywhere, Category = "Source (driver)") + EDazMorphDriverRotationOrder RotationConversionOrder; + + // Whether or not to clamp the driver value and remap it before scaling it + UPROPERTY(EditAnywhere, Category=Mapping, meta=(DisplayName="Remap Source")) + uint8 bUseRange : 1; + +public: + FAnimNode_DazMorphDriver(); + + // FAnimNode_Base interface + virtual void GatherDebugData(FNodeDebugData& DebugData) override; + // End of FAnimNode_Base interface + + // FAnimNode_SkeletalControlBase interface + virtual void EvaluateSkeletalControl_AnyThread(FComponentSpacePoseContext& Output, TArray& OutBoneTransforms) override; + virtual void EvaluateComponentSpaceInternal(FComponentSpacePoseContext& Context); + virtual bool IsValidToEvaluate(const USkeleton* Skeleton, const FBoneContainer& RequiredBones) override; + // End of FAnimNode_SkeletalControlBase interface + +protected: + + // FAnimNode_SkeletalControlBase protected interface + virtual void InitializeBoneReferences(const FBoneContainer& RequiredBones) override; + + /** Extracts the value used to drive the target bone or parameter */ + const float ExtractSourceValue(const FTransform& InCurrentBoneTransform, const FTransform& InRefPoseBoneTransform); + // End of FAnimNode_SkeletalControlBase protected interface + + FVector EulerFromQuat(const FQuat& Rotation, EDazMorphDriverRotationOrder RotationOrder); +}; diff --git a/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/DazToUnrealRuntime.h b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/DazToUnrealRuntime.h new file mode 100644 index 0000000..88448cc --- /dev/null +++ b/Unreal/UnrealPlugin/DazToUnreal/Source/DazToUnrealRuntime/Public/DazToUnrealRuntime.h @@ -0,0 +1,20 @@ +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleManager.h" + +class FDazToUnrealRuntimeModule : public IModuleInterface +{ +public: + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + static inline FDazToUnrealRuntimeModule& Get() + { + return FModuleManager::LoadModuleChecked< FDazToUnrealRuntimeModule >("DazToUnrealRuntime"); + } + + +}; \ No newline at end of file diff --git a/enc_temp_folder/6a78368314534b289560b0c36b376e2d/DzUnrealMorphSelectionDialog.cpp b/enc_temp_folder/6a78368314534b289560b0c36b376e2d/DzUnrealMorphSelectionDialog.cpp new file mode 100644 index 0000000..abc8e75 --- /dev/null +++ b/enc_temp_folder/6a78368314534b289560b0c36b376e2d/DzUnrealMorphSelectionDialog.cpp @@ -0,0 +1,722 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dzapp.h" +#include "dzscene.h" +#include "dzstyle.h" +#include "dzmainwindow.h" +#include "dzactionmgr.h" +#include "dzaction.h" +#include "dzskeleton.h" +#include "dzfigure.h" +#include "dzobject.h" +#include "dzshape.h" +#include "dzmodifier.h" +#include "dzpresentation.h" +#include "dzassetmgr.h" +#include "dzproperty.h" +#include "dzsettings.h" +#include "dzmorph.h" +#include "DzUnrealMorphSelectionDialog.h" + +#include "QtGui/qlayout.h" +#include "QtGui/qlineedit.h" + +#include + +/***************************** +Local definitions +*****************************/ +#define DAZ_TO_UNREAL_PLUGIN_NAME "DazToUnreal" + + +DzUnrealMorphSelectionDialog* DzUnrealMorphSelectionDialog::singleton = nullptr; + +// For sorting the lists +class SortingListItem : public QListWidgetItem { + +public: + virtual bool operator< (const QListWidgetItem &otherItem) const + { + if (this->checkState() != otherItem.checkState()) + { + return (this->checkState() == Qt::Checked); + } + return QListWidgetItem::operator<(otherItem); + } +}; + +DzUnrealMorphSelectionDialog::DzUnrealMorphSelectionDialog(QWidget *parent) : + DzBasicDialog(parent, DAZ_TO_UNREAL_PLUGIN_NAME) +{ + morphListWidget = NULL; + morphExportListWidget = NULL; + morphTreeWidget = NULL; + filterEdit = NULL; + presetCombo = NULL; + fullBodyMorphTreeItem = NULL; + charactersTreeItem = NULL; + + // Set the dialog title + setWindowTitle(tr("Select Morphs")); + + // Setup folder + presetsFolder = QDesktopServices::storageLocation(QDesktopServices::DocumentsLocation) + QDir::separator() + "DazToUnreal" + QDir::separator() + "Presets"; + + + QVBoxLayout* mainLayout = new QVBoxLayout(this); + + // Left tree with morph structure + morphTreeWidget = new QTreeWidget(this); + morphTreeWidget->setHeaderHidden(true); + + // Center list showing morhps for selected tree items + morphListWidget = new QListWidget(this); + morphListWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + + // Right list showing morphs that will export + morphExportListWidget = new QListWidget(this); + morphExportListWidget->setSelectionMode(QAbstractItemView::ExtendedSelection); + + // Quick filter box + QHBoxLayout* filterLayout = new QHBoxLayout(this); + filterLayout->addWidget(new QLabel("filter")); + filterEdit = new QLineEdit(); + connect(filterEdit, SIGNAL(textChanged(const QString &)), this, SLOT(FilterChanged(const QString &))); + filterLayout->addWidget(filterEdit); + + // Presets + QHBoxLayout* settingsLayout = new QHBoxLayout(this); + presetCombo = new QComboBox(this); + QPushButton* savePresetButton = new QPushButton("Save Preset", this); + connect(savePresetButton, SIGNAL(released()), this, SLOT(HandleSavePreset())); + settingsLayout->addWidget(new QLabel("Choose Preset")); + settingsLayout->addWidget(presetCombo); + settingsLayout->addWidget(savePresetButton); + settingsLayout->addStretch(); + + // All Morphs + QHBoxLayout* morphsLayout = new QHBoxLayout(this); + + // Left Tree + QVBoxLayout* treeLayout = new QVBoxLayout(this); + treeLayout->addWidget(new QLabel("Morph Groups")); + treeLayout->addWidget(new QLabel("Select to see available morphs")); + treeLayout->addWidget(morphTreeWidget); + + // Buttons for quickly adding certain JCMs + QGroupBox* JCMGroupBox = new QGroupBox("Add JCMs", this); + JCMGroupBox->setLayout(new QGridLayout(this)); + QPushButton* ArmsJCMButton = new QPushButton("Arms"); + QPushButton* LegsJCMButton = new QPushButton("Legs"); + QPushButton* TorsoJCMButton = new QPushButton("Torso"); + ((QGridLayout*)JCMGroupBox->layout())->addWidget(ArmsJCMButton, 0, 0); + ((QGridLayout*)JCMGroupBox->layout())->addWidget(LegsJCMButton, 0, 1); + ((QGridLayout*)JCMGroupBox->layout())->addWidget(TorsoJCMButton, 0, 2); + + connect(ArmsJCMButton, SIGNAL(released()), this, SLOT(HandleArmJCMMorphsButton())); + connect(LegsJCMButton, SIGNAL(released()), this, SLOT(HandleLegJCMMorphsButton())); + connect(TorsoJCMButton, SIGNAL(released()), this, SLOT(HandleTorsoJCMMorphsButton())); + + treeLayout->addWidget(JCMGroupBox); + morphsLayout->addLayout(treeLayout); + + + // Center List of morphs based on tree selection + QVBoxLayout* morphListLayout = new QVBoxLayout(this); + morphListLayout->addWidget(new QLabel("Morphs in Group")); + morphListLayout->addWidget(new QLabel("Select and click Add for Export")); + morphListLayout->addLayout(filterLayout); + morphListLayout->addWidget(morphListWidget); + + // Button for adding morphs + QPushButton* addMorphsButton = new QPushButton("Add For Export", this); + connect(addMorphsButton, SIGNAL(released()), this, SLOT(HandleAddMorphsButton())); + morphListLayout->addWidget(addMorphsButton); + morphsLayout->addLayout(morphListLayout); + + // Right List of morphs that will export + QVBoxLayout* selectedListLayout = new QVBoxLayout(this); + selectedListLayout->addWidget(new QLabel("Morphs to Export")); + selectedListLayout->addWidget(morphExportListWidget); + + // Button for clearing morphs from export + QPushButton* removeMorphsButton = new QPushButton("Remove From Export", this); + connect(removeMorphsButton, SIGNAL(released()), this, SLOT(HandleRemoveMorphsButton())); + selectedListLayout->addWidget(removeMorphsButton); + morphsLayout->addLayout(selectedListLayout); + + mainLayout->addLayout(settingsLayout); + mainLayout->addLayout(morphsLayout); + + this->addLayout(mainLayout); + resize(QSize(800, 800));//.expandedTo(minimumSizeHint())); + setFixedWidth(width()); + setFixedHeight(height()); + RefreshPresetsCombo(); + + connect(morphListWidget, SIGNAL(itemChanged(QListWidgetItem*)), + this, SLOT(ItemChanged(QListWidgetItem*))); + + connect(morphTreeWidget, SIGNAL(itemSelectionChanged()), + this, SLOT(ItemSelectionChanged())); + +} + +QSize DzUnrealMorphSelectionDialog::minimumSizeHint() const +{ + return QSize(800, 800); +} + +// Build out the Left morphs tree based on the current selection +void DzUnrealMorphSelectionDialog::PrepareDialog() +{ + DzNode* Selection = dzScene->getPrimarySelection(); + + DzNode* ParentFigureNode = Selection; + // If this is a figure, send it as a skeletal mesh + while (ParentFigureNode->getNodeParent()) + { + ParentFigureNode = ParentFigureNode->getNodeParent(); + if (DzSkeleton* Skeleton = ParentFigureNode->getSkeleton()) + { + if (DzFigure* Figure = qobject_cast(Skeleton)) + { + Selection = ParentFigureNode; + break; + } + } + } + + morphs.clear(); + morphList = GetAvailableMorphs(Selection); + for (int ChildIndex = 0; ChildIndex < Selection->getNumNodeChildren(); ChildIndex++) + { + DzNode* ChildNode = Selection->getNodeChild(ChildIndex); + morphList.append(GetAvailableMorphs(ChildNode)); + } + UpdateMorphsTree(); + HandlePresetChanged("LastUsed.csv"); +} + +// When the filter text is changed, update the center list +void DzUnrealMorphSelectionDialog::FilterChanged(const QString& filter) +{ + morphListWidget->clear(); + QString newFilter = filter; + morphListWidget->clear(); + foreach(MorphInfo morphInfo, selectedInTree) + { + if (newFilter == NULL || newFilter.isEmpty() || morphInfo.Label.contains(newFilter, Qt::CaseInsensitive)) + { + SortingListItem* item = new SortingListItem();// modLabel, morphListWidget); + item->setText(morphInfo.Label); + item->setData(Qt::UserRole, morphInfo.Name); + + morphListWidget->addItem(item); + } + } + + morphListWidget->sortItems(); +} + +// Build a list of availaboe morphs for the node +// TODO: This function evolved a lot as I figured out where to find the morphs. +// There may be dead code in here. +QStringList DzUnrealMorphSelectionDialog::GetAvailableMorphs(DzNode* Node) +{ + QStringList newMorphList; + + DzObject* Object = Node->getObject(); + DzShape* Shape = Object ? Object->getCurrentShape() : NULL; + + for (int index = 0; index < Node->getNumProperties(); index++) + { + DzProperty* property = Node->getProperty(index); + QString propName = property->getName(); + QString propLabel = property->getLabel(); + DzPresentation* presentation = property->getPresentation(); + if (presentation) + { + MorphInfo morphInfo; + morphInfo.Name = propName; + morphInfo.Label = propLabel; + morphInfo.Path = Node->getLabel() + "/" + property->getPath(); + morphInfo.Type = presentation->getType(); + if (!morphs.contains(morphInfo.Name)) + { + morphs.insert(morphInfo.Name, morphInfo); + } + //qDebug() << "Property Name " << propName << " Label " << propLabel << " Presentation Type:" << presentation->getType() << "Path: " << property->getPath(); + //qDebug() << "Path " << property->getGroupOnlyPath(); + } + if (presentation && presentation->getType() == "Modifier/Shape") + { + SortingListItem* item = new SortingListItem();// modLabel, morphListWidget); + item->setText(propLabel); + item->setFlags(item->flags() | Qt::ItemIsUserCheckable); + if (morphList.contains(propLabel)) + { + item->setCheckState(Qt::Checked); + newMorphList.append(propName); + } + else + { + item->setCheckState(Qt::Unchecked); + } + item->setData(Qt::UserRole, propName); + morphNameMapping.insert(propName, propLabel); + } + } + + if (Object) + { + for (int index = 0; index < Object->getNumModifiers(); index++) + { + DzModifier* modifier = Object->getModifier(index); + QString modName = modifier->getName(); + QString modLabel = modifier->getLabel(); + DzMorph* mod = qobject_cast(modifier); + if (mod) + { + for (int propindex = 0; propindex < modifier->getNumProperties(); propindex++) + { + DzProperty* property = modifier->getProperty(propindex); + QString propName = property->getName(); + QString propLabel = property->getLabel(); + DzPresentation* presentation = property->getPresentation(); + if (presentation) + { + MorphInfo morphInfoProp; + morphInfoProp.Name = modName; + morphInfoProp.Label = propLabel; + morphInfoProp.Path = Node->getLabel() + "/" + property->getPath(); + morphInfoProp.Type = presentation->getType(); + if (!morphs.contains(morphInfoProp.Name)) + { + morphs.insert(morphInfoProp.Name, morphInfoProp); + } + //qDebug() << "Modifier Name " << modName << " Label " << propLabel << " Presentation Type:" << presentation->getType() << " Path: " << property->getPath(); + //qDebug() << "Path " << property->getGroupOnlyPath(); + } + } + + } + + } + } + + return newMorphList; +} + +// Build out the left tree +void DzUnrealMorphSelectionDialog::UpdateMorphsTree() +{ + morphTreeWidget->clear(); + morphsForNode.clear(); + foreach(QString morph, morphs.keys()) + { + QString path = morphs[morph].Path; + QTreeWidgetItem* parentItem = nullptr; + foreach(QString pathPart, path.split("/")) + { + if (pathPart == "") continue; + parentItem = FindTreeItem(parentItem, pathPart); + + if (!morphsForNode.keys().contains(parentItem)) + { + morphsForNode.insert(parentItem, QList()); + } + morphsForNode[parentItem].append(morphs[morph]); + } + } +} + +// This function could be better named. It will find the node matching the property path +// but it will also create the structure of that path in the tree as needed as it searches +QTreeWidgetItem* DzUnrealMorphSelectionDialog::FindTreeItem(QTreeWidgetItem* parent, QString name) +{ + if (parent == nullptr) + { + for(int i = 0; i < morphTreeWidget->topLevelItemCount(); i++) + { + QTreeWidgetItem* item = morphTreeWidget->topLevelItem(i); + if (item->text(0) == name) + { + return item; + } + } + + QTreeWidgetItem* newItem = new QTreeWidgetItem(morphTreeWidget); + newItem->setText(0, name); + newItem->setExpanded(true); + morphTreeWidget->addTopLevelItem(newItem); + return newItem; + } + else + { + for (int i = 0; i < parent->childCount(); i++) + { + QTreeWidgetItem* item = parent->child(i); + if (item->text(0) == name) + { + return item; + } + } + + QTreeWidgetItem* newItem = new QTreeWidgetItem(parent); + newItem->setText(0, name); + newItem->setExpanded(true); + parent->addChild(newItem); + return newItem; + } +} + +// For selection changes in the Left Tree +void DzUnrealMorphSelectionDialog::ItemSelectionChanged() +{ + selectedInTree.clear(); + foreach(QTreeWidgetItem* selectedItem, morphTreeWidget->selectedItems()) + { + SelectMorphsInNode(selectedItem); + } + + FilterChanged(filterEdit->text()); +} + +// Updates the list of selected morphs in the Left Tree +// including any children +void DzUnrealMorphSelectionDialog::SelectMorphsInNode(QTreeWidgetItem* item) +{ + if (morphsForNode.keys().contains(item)) + { + selectedInTree.append(morphsForNode[item]); + } +} + +// Add Morphs for export +void DzUnrealMorphSelectionDialog::HandleAddMorphsButton() +{ + foreach(QListWidgetItem* selectedItem, morphListWidget->selectedItems()) + { + QString morphName = selectedItem->data(Qt::UserRole).toString(); + if (morphs.contains(morphName) && !morphsToExport.contains(morphs[morphName])) + { + morphsToExport.append(morphs[morphName]); + } + } + RefreshExportMorphList(); + RefreshPresetsCombo(); +} + +// Remove morph from export list +void DzUnrealMorphSelectionDialog::HandleRemoveMorphsButton() +{ + foreach(QListWidgetItem* selectedItem, morphExportListWidget->selectedItems()) + { + QString morphName = selectedItem->data(Qt::UserRole).toString(); + if (morphs.keys().contains(morphName)) + { + morphsToExport.removeAll(morphs[morphName]); + } + } + RefreshExportMorphList(); + RefreshPresetsCombo(); +} + +// Brings up a dialgo for choosing a preset name +void DzUnrealMorphSelectionDialog::HandleSavePreset() +{ + QString filters("CSV Files (*.csv)"); + QString defaultFilter("CSV Files (*.csv)"); + QDir dir; + dir.mkpath(presetsFolder); + + QString presetName = QFileDialog::getSaveFileName(this, QString("Save Preset"), + presetsFolder, + filters, + &defaultFilter); + + if (presetName != NULL) + { + SavePresetFile(presetName); + } +} + +// Saves out a preset. If the path isn't supplied, it's saved as the last selection +void DzUnrealMorphSelectionDialog::SavePresetFile(QString filePath) +{ + QDir dir; + dir.mkpath(presetsFolder); + if (filePath == NULL) + { + filePath = presetsFolder + QDir::separator() + "LastUsed.csv"; + } + + QFile file(filePath); + file.open(QIODevice::WriteOnly | QIODevice::Text); + QTextStream out(&file); + out << GetMorphCSVString(); + + // optional, as QFile destructor will already do it: + file.close(); + RefreshPresetsCombo(); + +} + +// Hard coded list of morphs for Genesis 3 and 8 +// It just adds them all, the other functions will ignore any that don't fit the character +void DzUnrealMorphSelectionDialog::HandleArmJCMMorphsButton() +{ + QStringList MorphsToAdd; + + MorphsToAdd.append("pJCMCollarTwist_n30_L"); + MorphsToAdd.append("pJCMCollarTwist_n30_R"); + MorphsToAdd.append("pJCMCollarTwist_p30_L"); + MorphsToAdd.append("pJCMCollarTwist_p30_R"); + MorphsToAdd.append("pJCMCollarUp_55_L"); + MorphsToAdd.append("pJCMCollarUp_55_R"); + MorphsToAdd.append("pJCMCollarUp_50_L"); + MorphsToAdd.append("pJCMCollarUp_50_R"); + MorphsToAdd.append("pJCMForeArmFwd_135_L"); + MorphsToAdd.append("pJCMForeArmFwd_135_R"); + MorphsToAdd.append("pJCMForeArmFwd_75_L"); + MorphsToAdd.append("pJCMForeArmFwd_75_R"); + MorphsToAdd.append("pJCMHandDwn_70_L"); + MorphsToAdd.append("pJCMHandDwn_70_R"); + MorphsToAdd.append("pJCMHandUp_80_L"); + MorphsToAdd.append("pJCMHandUp_80_R"); + MorphsToAdd.append("pJCMShldrDown_40_L"); + MorphsToAdd.append("pJCMShldrDown_40_R"); + MorphsToAdd.append("pJCMShldrDown_75_L"); + MorphsToAdd.append("pJCMShldrDown_75_R"); + MorphsToAdd.append("pJCMShldrDown2_75_L"); + MorphsToAdd.append("pJCMShldrDown2_75_R"); + MorphsToAdd.append("pJCMShldrFront_n110_Bend_n40_L"); + MorphsToAdd.append("pJCMShldrFront_n110_Bend_p90_L"); + MorphsToAdd.append("pJCMShldrFront_p110_Bend_n90_R"); + MorphsToAdd.append("pJCMShldrFront_p110_Bend_p40_R"); + MorphsToAdd.append("pJCMShldrFwdDwn_110_75_L"); + MorphsToAdd.append("pJCMShldrFwdDwn_110_75_R"); + MorphsToAdd.append("pJCMShldrFwd_110_L"); + MorphsToAdd.append("pJCMShldrFwd_110_R"); + MorphsToAdd.append("pJCMShldrFwd_95_L"); + MorphsToAdd.append("pJCMShldrFwd_95_R"); + MorphsToAdd.append("pJCMShldrUp_90_L"); + MorphsToAdd.append("pJCMShldrUp_90_R"); + MorphsToAdd.append("pJCMShldrUp_35_L"); + MorphsToAdd.append("pJCMShldrUp_35_R"); + + // Add the list for export + foreach(QString MorphName, MorphsToAdd) + { + if (morphs.contains(MorphName) && !morphsToExport.contains(morphs[MorphName])) + { + morphsToExport.append(morphs[MorphName]); + } + } + RefreshExportMorphList(); +} + +// Hard coded list of morphs for Genesis 3 and 8 +// It just adds them all, the other functions will ignore any that don't fit the character +void DzUnrealMorphSelectionDialog::HandleLegJCMMorphsButton() +{ + QStringList MorphsToAdd; + + MorphsToAdd.append("pJCMBigToeDown_45_L"); + MorphsToAdd.append("pJCMBigToeDown_45_R"); + MorphsToAdd.append("pJCMFootDwn_75_L"); + MorphsToAdd.append("pJCMFootDwn_75_R"); + MorphsToAdd.append("pJCMFootUp_40_L"); + MorphsToAdd.append("pJCMFootUp_40_R"); + MorphsToAdd.append("pJCMShinBend_155_L"); + MorphsToAdd.append("pJCMShinBend_155_R"); + MorphsToAdd.append("pJCMShinBend_90_L"); + MorphsToAdd.append("pJCMShinBend_90_R"); + MorphsToAdd.append("pJCMThighBack_35_L"); + MorphsToAdd.append("pJCMThighBack_35_R"); + MorphsToAdd.append("pJCMThighFwd_115_L"); + MorphsToAdd.append("pJCMThighFwd_115_R"); + MorphsToAdd.append("pJCMThighFwd_57_L"); + MorphsToAdd.append("pJCMThighFwd_57_R"); + MorphsToAdd.append("pJCMThighSide_85_L"); + MorphsToAdd.append("pJCMThighSide_85_R"); + MorphsToAdd.append("pJCMToesUp_60_L"); + MorphsToAdd.append("pJCMToesUp_60_R"); + + // Add the list for export + foreach(QString MorphName, MorphsToAdd) + { + if (morphs.contains(MorphName) && !morphsToExport.contains(morphs[MorphName])) + { + morphsToExport.append(morphs[MorphName]); + } + } + RefreshExportMorphList(); +} + +// Hard coded list of morphs for Genesis 3 and 8 +// It just adds them all, the other functions will ignore any that don't fit the character +void DzUnrealMorphSelectionDialog::HandleTorsoJCMMorphsButton() +{ + QStringList MorphsToAdd; + + MorphsToAdd.append("pJCMAbdomen2Fwd_40"); + MorphsToAdd.append("pJCMAbdomen2Side_24_L"); + MorphsToAdd.append("pJCMAbdomen2Side_24_R"); + MorphsToAdd.append("pJCMAbdomenFwd_35"); + MorphsToAdd.append("pJCMAbdomenLowerFwd_Navel"); + MorphsToAdd.append("pJCMAbdomenUpperFwd_Navel"); + MorphsToAdd.append("pJCMHeadBack_27"); + MorphsToAdd.append("pJCMHeadFwd_25"); + MorphsToAdd.append("pJCMNeckBack_27"); + MorphsToAdd.append("pJCMNeckFwd_35"); + MorphsToAdd.append("pJCMNeckLowerSide_40_L"); + MorphsToAdd.append("pJCMNeckLowerSide_40_R"); + MorphsToAdd.append("pJCMNeckTwist_22_L"); + MorphsToAdd.append("pJCMNeckTwist_22_R"); + MorphsToAdd.append("pJCMNeckTwist_Reverse"); + MorphsToAdd.append("pJCMPelvisFwd_25"); + MorphsToAdd.append("pJCMChestFwd_35"); + MorphsToAdd.append("pJCMChestSide_20_L"); + MorphsToAdd.append("pJCMChestSide_20_R"); + + // Add the list for export + foreach(QString MorphName, MorphsToAdd) + { + if (morphs.contains(MorphName) && !morphsToExport.contains(morphs[MorphName])) + { + morphsToExport.append(morphs[MorphName]); + } + } + RefreshExportMorphList(); +} + +// Refresh the Right export list +void DzUnrealMorphSelectionDialog::RefreshExportMorphList() +{ + morphExportListWidget->clear(); + foreach(MorphInfo morphInfo, morphsToExport) + { + SortingListItem* item = new SortingListItem(); + item->setText(morphInfo.Label); + item->setData(Qt::UserRole, morphInfo.Name); + + morphExportListWidget->addItem(item); + } + SavePresetFile(NULL); +} + +// Refresh the list of preset csvs from the files in the folder +void DzUnrealMorphSelectionDialog::RefreshPresetsCombo() +{ + disconnect(presetCombo, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(HandlePresetChanged(const QString &))); + + presetCombo->clear(); + presetCombo->addItem("None"); + + QDirIterator it(presetsFolder, QStringList() << "*.csv", QDir::NoFilter, QDirIterator::NoIteratorFlags); + while (it.hasNext()) + { + QString Path = it.next(); + QString NewPath = Path.right(Path.length() - presetsFolder.length() - 1); + presetCombo->addItem(NewPath); + } + connect(presetCombo, SIGNAL(currentIndexChanged(const QString &)), this, SLOT(HandlePresetChanged(const QString &))); +} + +// Call when the preset combo is changed by the user +void DzUnrealMorphSelectionDialog::HandlePresetChanged(const QString& presetName) +{ + morphsToExport.clear(); + QString PresetFilePath = presetsFolder + QDir::separator() + presetName; + + QFile file(PresetFilePath); + if (!file.open(QIODevice::ReadOnly)) { + // TODO: should be an error dialog + return; + } + + // load the selected csv from disk into the export list on the right + QTextStream InStream(&file); + + while (!InStream.atEnd()) { + QString MorphLine = InStream.readLine(); + if (MorphLine.endsWith("\"Export\"")) + { + QStringList Items = MorphLine.split(","); + QString MorphName = Items[0].replace("\"", ""); + if (morphs.contains(MorphName)) + { + morphsToExport.append(morphs[MorphName]); + } + } + } + + RefreshExportMorphList(); + file.close(); +} + +// Get the morph string in the format for the Daz FBX Export +QString DzUnrealMorphSelectionDialog::GetMorphString() +{ + if (morphsToExport.length() == 0) + { + return ""; + } + QStringList morphNamesToExport; + foreach(MorphInfo exportMorph, morphsToExport) + { + morphNamesToExport.append(exportMorph.Name); + } + QString morphString = morphNamesToExport.join("\n1\n"); + morphString += "\n1\n.CTRLVS\n2\nAnything\n0"; + return morphString; +} + +// Get the morph string in the format used for presets +QString DzUnrealMorphSelectionDialog::GetMorphCSVString() +{ + morphList.clear(); + QString morphString; + foreach(MorphInfo exportMorph, morphsToExport) + { + morphList.append(exportMorph.Name); + morphString += "\"" + exportMorph.Name + "\",\"Export\"\n"; + } + morphString += "\".CTRLVS\", \"Ignore\"\n"; + morphString += "\"Anything\", \"Bake\"\n"; + return morphString; +} + +// Get the morph string in an internal name = friendly name format +// Used to rename them to the friendly name in Unreal +QMap DzUnrealMorphSelectionDialog::GetMorphRenaming() +{ + morphNameMapping.clear(); + foreach(MorphInfo exportMorph, morphsToExport) + { + morphNameMapping.insert(exportMorph.Name, exportMorph.Label); + } + + return morphNameMapping; +} + +#include "moc_DzUnrealMorphSelectionDialog.cpp"