// Copyright Epic Games, Inc. All Rights Reserved. #include "DatasmithMaxLogger.h" #include "DatasmithMaxDirectLink.h" #include "DatasmithMaxExporterDefines.h" #include "HAL/UnrealMemory.h" #include "Windows/AllowWindowsPlatformTypes.h" MAX_INCLUDES_START #include "max.h" MAX_INCLUDES_END DatasmithMaxLogger& DatasmithMaxLogger::Get() { static TSharedRef< DatasmithMaxLogger > Instance = MakeShared< DatasmithMaxLogger >(); // Create a shared ref because we support TSharedFromThis return Instance.Get(); } void DatasmithMaxLogger::Purge() { ResetGeneralErrors(); ResetTextureErrors(); ResetMissingAssetErrors(); PartialSupportedMats.Empty(); UnsupportedMats.Empty(); PartialSupportedMaps.Empty(); UnsupportedMaps.Empty(); UnsupportedLight.Empty(); FailUVs.Empty(); FailObjs.Empty(); InvalidTransforms.Empty(); } void DatasmithMaxLogger::AddPartialSupportedMat(Mtl* Mat) { for (int i = 0; i < PartialSupportedMats.Num(); i++) { if (Mat->ClassID() == PartialSupportedMats[i]->ClassID()) { return; } } PartialSupportedMats.Add(Mat); } void DatasmithMaxLogger::AddUnsupportedMat(Mtl* Mat) { for (int i = 0; i < UnsupportedMats.Num(); i++) { if (Mat->ClassID() == UnsupportedMats[i]->ClassID()) { return; } } UnsupportedMats.Add(Mat); } void DatasmithMaxLogger::AddPartialSupportedMap(Texmap* Map) { for (int i = 0; i < PartialSupportedMaps.Num(); i++) { if (Map->ClassID() == PartialSupportedMaps[i]->ClassID()) { return; } } PartialSupportedMaps.Add(Map); } void DatasmithMaxLogger::AddUnsupportedMap(Texmap* Map) { for (int i = 0; i < UnsupportedMaps.Num(); i++) { if (Map->ClassID() == UnsupportedMaps[i]->ClassID()) { return; } } MSTR Classname; Map->GetClassName(Classname); DatasmithMaxDirectLink::LogWarning(FString::Printf(TEXT("Unsupported texmap \"%s\" of type %s (0x%08x-0x%08x)"), Map->GetName().ToBSTR(), Classname.ToBSTR(), Map->ClassID().PartA(), Map->ClassID().PartB())); UnsupportedMaps.Add(Map); } void DatasmithMaxLogger::AddUnsupportedLight(INode* Light) { for (int i = 0; i < UnsupportedLight.Num(); i++) { if (Light->ClassID() == UnsupportedLight[i]->ClassID()) { return; } } UnsupportedLight.Add(Light); } void DatasmithMaxLogger::AddUnsupportedUV(INode* Node) { FailUVs.AddUnique(Node); } void DatasmithMaxLogger::AddInvalidObj(INode* Node) { FailObjs.AddUnique(Node); } TArray DatasmithMaxLogger::GetInvalidObjects() const { return FailObjs; } void DatasmithMaxLogger::AddInvalidTransform(INode* Node) { InvalidTransforms.AddUnique(Node); } bool DatasmithMaxLogger::HasWarnings() { if (GetGeneralErrorsCount() > 0 || GetTextureErrorsCount() > 0 || GetMissingAssetErrorsCount() > 0 || PartialSupportedMats.Num() > 0 || UnsupportedMats.Num() > 0 || PartialSupportedMaps.Num()>0 || UnsupportedMaps.Num()>0 || UnsupportedLight.Num() > 0 || FailUVs.Num() > 0) { return true; } else { return false; } } bool DatasmithMaxLogger::CopyToClipBoard() { if ( ShowMessage.IsEmpty() ) { return false; } if (OpenClipboard(NULL)) { EmptyClipboard(); HGLOBAL HClipboardData; size_t Num = (ShowMessage.Len() + 1) * sizeof(WCHAR); HClipboardData = GlobalAlloc(NULL, Num); WCHAR* PchData = (WCHAR*)GlobalLock(HClipboardData); FMemory::Memcpy(PchData, *ShowMessage, Num); SetClipboardData(CF_UNICODETEXT, HClipboardData); GlobalUnlock(HClipboardData); CloseClipboard(); return true; } return false; } void DatasmithMaxLogger::AddItem(const TCHAR* Msg, HWND Handle, FString& FullMsg) { SendDlgItemMessage(Handle, IDC_ERROR_MSG_LIST, LB_ADDSTRING, NULL, (LPARAM)Msg); FullMsg += Msg; FullMsg += LINE_TERMINATOR; } void DatasmithMaxLogger::AddObjectList(TArray< INode* > ObjectList, HWND Handle, const TCHAR* Header, const TCHAR* Description) { const int32 MAX_ITEMS_TO_SHOW = 20; if ( ObjectList.Num() > 0 ) { AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(*(FString( TEXT("\t") ) + Header), Handle, ShowMessage); AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); if ( Description && FCString::Strlen( Description ) ) { AddItem(Description, Handle, ShowMessage); } FString ObjectListString; int j = 0; for (int i = 0; i < FMath::Min( ObjectList.Num(), MAX_ITEMS_TO_SHOW ); i++) { if (j > 0) { ObjectListString += TEXT(", ["); } else { ObjectListString += TEXT("["); } ObjectListString += ObjectList[i]->GetName(); ObjectListString += TEXT("]"); j++; if (j == 4) { j = 0; AddItem(*ObjectListString, Handle, ShowMessage); ObjectListString.Empty(); } } if (!ObjectListString.IsEmpty()) { AddItem(*ObjectListString, Handle, ShowMessage); } if (ObjectList.Num() > MAX_ITEMS_TO_SHOW) { ObjectListString = TEXT("...and ") + FString::FromInt(ObjectList.Num() - MAX_ITEMS_TO_SHOW) + TEXT(" more"); AddItem(*ObjectListString, Handle, ShowMessage); } AddItem(TEXT("\n\n"), Handle, ShowMessage); } } FString DatasmithMaxLogger::GetLightDescription(INode* LightNode) { ObjectState State = LightNode->EvalWorldState(0); LightObject *Light = (LightObject*)State.obj; MSTR Classname; Light->GetClassName(Classname); return FString::Printf(TEXT("\"%s\" of type %s (0x%08x-0x%08x)"), LightNode->GetName(), Classname.ToBSTR(), Light->ClassID().PartA(), Light->ClassID().PartB()); } void DatasmithMaxLogger::Show(HWND Handle) { ShowMessage = TEXT(""); if (GetGeneralErrorsCount() > 0) { AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(TEXT("\tGENERAL MESSAGES"), Handle, ShowMessage); AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); for (int i = 0; i < GetGeneralErrorsCount(); i++) { AddItem(GetGeneralError(i), Handle, ShowMessage); } AddItem(TEXT("\n\n"), Handle, ShowMessage); } if (GetMissingAssetErrorsCount() > 0) { AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(TEXT("\tMISSING EXTERNAL FILES"), Handle, ShowMessage); AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(TEXT("One or more file required to export correctly to Unreal is missing,"), Handle, ShowMessage); AddItem(TEXT("which can produce incorrect results. We recommend that you solve the issue"), Handle, ShowMessage); AddItem(TEXT("with the 3ds Max's Asset Tracking tool prior to export."), Handle, ShowMessage); for (int i = 0; i < GetMissingAssetErrorsCount(); i++) { AddItem(GetMissingAssetError(i), Handle, ShowMessage); } AddItem(TEXT("\n\n"), Handle, ShowMessage); } // Invalid Objects AddObjectList( FailObjs, Handle, TEXT("INVALID OBJECTS (unsupported, corrupted or too small geometry?)") ); if (PartialSupportedMats.Num() > 0 || PartialSupportedMaps.Num()>0) { AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(TEXT("\tPARTIALLY SUPPORTED"), Handle, ShowMessage); AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); for (int i = 0; i < PartialSupportedMats.Num(); i++) { MSTR Classname; PartialSupportedMats[i]->GetClassName(Classname); FString Msg = TEXT("Materials of type ") + FString(Classname.ToBSTR()) + TEXT(" are not supported and its first children will be used."); AddItem(*Msg, Handle, ShowMessage); } for (int i = 0; i < PartialSupportedMaps.Num(); i++) { MSTR Classname; PartialSupportedMaps[i]->GetClassName(Classname);; FString Msg = TEXT("Texmaps of type ") + FString(Classname.ToBSTR()) + TEXT(" are not supported and its first children will be used."); AddItem(*Msg, Handle, ShowMessage); } ShowMessage += TEXT("\n\n"); SendDlgItemMessage(Handle, IDC_ERROR_MSG_LIST, LB_ADDSTRING, NULL, (LPARAM)TEXT("\n")); SendDlgItemMessage(Handle, IDC_ERROR_MSG_LIST, LB_ADDSTRING, NULL, (LPARAM)TEXT("\n")); } if (UnsupportedMats.Num() > 0) { AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(TEXT("\tMATERIALS"), Handle, ShowMessage); AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(TEXT("Some materials used in the scene are not supported by the Unreal Datasmith format in the moment. They will not be imported inside Unreal Engine."), Handle, ShowMessage); for (int i = 0; i < UnsupportedMats.Num(); i++) { MSTR Classname; UnsupportedMats[i]->GetClassName(Classname); Mtl* Mat = UnsupportedMats[i]; FString Msg = FString::Printf(TEXT("\"%s\" of type %s (0x%08x-0x%08x)"), Mat->GetName().ToBSTR(), Classname.ToBSTR(), Mat->ClassID().PartA(), Mat->ClassID().PartB()); AddItem(*Msg, Handle, ShowMessage); } AddItem(TEXT("\n\n"), Handle, ShowMessage); } if (UnsupportedMaps.Num() > 0 || GetTextureErrorsCount() > 0) { AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(TEXT("\tTEXTUREMAPS"), Handle, ShowMessage); AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); if (UnsupportedMaps.Num() > 0) { AddItem(TEXT("Some texture maps used in the scene are not supported by the Unreal Datasmith format in the moment. They will not be imported inside Unreal Engine."), Handle, ShowMessage); for (int i = 0; i < UnsupportedMaps.Num(); i++) { MSTR Classname; UnsupportedMaps[i]->GetClassName(Classname); Texmap* Map = UnsupportedMaps[i]; FString Msg = FString::Printf(TEXT("\"%s\" of type %s (0x%08x-0x%08x)"), Map->GetName().ToBSTR(), Classname.ToBSTR(), Map->ClassID().PartA(), Map->ClassID().PartB()); AddItem(*Msg, Handle, ShowMessage); } } if (GetTextureErrorsCount() > 0) { for (int i = 0; i < GetTextureErrorsCount(); i++) { AddItem(GetTextureError(i), Handle, ShowMessage); } } AddItem(TEXT("\n\n"), Handle, ShowMessage); } if (UnsupportedLight.Num() > 0) { AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(TEXT("\tLIGHTS"), Handle, ShowMessage); AddItem(TEXT("------------------------------------------------------------------------------------------"), Handle, ShowMessage); AddItem(TEXT("Some lights used in the scene are not supported by the Unreal Datasmith format in the moment. They will not be imported inside Unreal Engine."), Handle, ShowMessage); for (int i = 0; i < UnsupportedLight.Num(); i++) { FString Msg = GetLightDescription(UnsupportedLight[i]); AddItem(*Msg, Handle, ShowMessage); } AddItem(TEXT("\n\n"), Handle, ShowMessage); } // Invalid UVs AddObjectList( FailUVs, Handle, TEXT("MESHES WITHOUT UV CHANNEL 2 (USED FOR LIGHT BAKING)") ); // Invalid Transforms FString InvalidTransformsDescription = TEXT("Some objects have customized pivots and nonuniform scales.") + FString( LINE_TERMINATOR ); InvalidTransformsDescription += TEXT("Those combinations are not supported by Unreal and will produce incorrect results.") + FString( LINE_TERMINATOR ); InvalidTransformsDescription += TEXT("It is recommended to use the Reset XForm Utility on those objects:") + FString( LINE_TERMINATOR ); AddObjectList( InvalidTransforms, Handle, TEXT("INVALID TRANSFORMS"), *InvalidTransformsDescription ); } INT_PTR CALLBACK MsgListDlgProc(HWND hDlg, UINT iMsg, WPARAM wParam, LPARAM lParam) { int Tabs[3] = { 24, 24, 24 }; switch (iMsg) { case WM_INITDIALOG: CenterWindow(hDlg, GetWindow(hDlg, GW_OWNER)); SendDlgItemMessage(hDlg, IDC_ERROR_MSG_LIST, LB_RESETCONTENT, 0, 0); SendDlgItemMessage(hDlg, IDC_ERROR_MSG_LIST, LB_SETTABSTOPS, (WPARAM)3, (LPARAM)Tabs); EnableWindow(GetDlgItem(hDlg, IDSELECTINVALID), DatasmithMaxLogger::Get().GetInvalidObjects().Num()>0); DatasmithMaxLogger::Get().Show(hDlg); return TRUE; case WM_COMMAND: switch (LOWORD(wParam)) { case IDCOPY: DatasmithMaxLogger::Get().CopyToClipBoard(); break; case IDSELECTINVALID: INodeTab SelectedNodes; for (int i = 0; i < DatasmithMaxLogger::Get().GetInvalidObjects().Num(); i++) { SelectedNodes.AppendNode(DatasmithMaxLogger::Get().GetInvalidObjects()[i]); } GetCOREInterface()->ClearNodeSelection(); GetCOREInterface()->SelectNodeTab(SelectedNodes,TRUE,TRUE); break; } break; case WM_CLOSE: EndDialog(hDlg, TRUE); break; case WM_SIZE: { int DialogWidth = (int)LOWORD(lParam); int DialogHeight = (int)HIWORD(lParam); HWND hList = GetDlgItem(hDlg, IDC_ERROR_MSG_LIST); HWND hCopy = GetDlgItem(hDlg, IDCOPY); HWND hInvalid = GetDlgItem(hDlg, IDSELECTINVALID); MoveWindow(hList, 6, 7, (DialogWidth - 12), (DialogHeight - 42), true); MoveWindow(hCopy, 24, DialogHeight - 31, (DialogWidth / 2 - 24), 26, true); MoveWindow(hInvalid, (DialogWidth / 2) + 3 , DialogHeight - 31, (DialogWidth / 2 - 23), 26, true); break; } } return FALSE; } #include "Windows/HideWindowsPlatformTypes.h"