2025 lines
64 KiB
C++
2025 lines
64 KiB
C++
|
#include "MujocoFactory.h"
|
||
|
#include "EngineUtils.h"
|
||
|
#include "Math/TransformVectorized.h"
|
||
|
#include "ObjectTools.h"
|
||
|
#include "PackageTools.h"
|
||
|
#include "Kismet2/KismetEditorUtilities.h"
|
||
|
#include "KismetCompilerModule.h"
|
||
|
#include "AssetRegistry/AssetRegistryModule.h"
|
||
|
#include "UObject/SavePackage.h"
|
||
|
#include "mujoco/mujoco.h"
|
||
|
#include "StaticMeshAttributes.h"
|
||
|
#include "Containers/StringConv.h"
|
||
|
#include "IAssetTools.h"
|
||
|
#include "AssetToolsModule.h"
|
||
|
#include "Factories/FbxFactory.h"
|
||
|
#include "Factories/FbxImportUI.h"
|
||
|
#include "Factories/FbxStaticMeshImportData.h"
|
||
|
#include "Kismet2/BlueprintEditorUtils.h"
|
||
|
#include "Engine/SimpleConstructionScript.h"
|
||
|
#include "Engine/SCS_Node.h"
|
||
|
#include "StaticMeshDescription.h"
|
||
|
#include "MeshTypes.h"
|
||
|
#include "Misc/FileHelper.h"
|
||
|
#include "Widgets/SWindow.h"
|
||
|
#include "IDetailsView.h"
|
||
|
#include "PropertyEditorModule.h"
|
||
|
#include "Interfaces/IMainFrameModule.h"
|
||
|
#include "PropertyCustomizationHelpers.h"
|
||
|
#include "GameFramework/Character.h"
|
||
|
#include "Widgets/Input/SComboBox.h"
|
||
|
#include "Widgets/Input/SButton.h"
|
||
|
#include "tinyxml2.h"
|
||
|
#include "Misc/MujocoWrapper.h"
|
||
|
#include "UObject/SoftObjectPath.h"
|
||
|
#include "DetailLayoutBuilder.h"
|
||
|
#include "Misc/Optional.h"
|
||
|
#include "Components/MujocoSiteComponent.h"
|
||
|
#include "Components/MujocoBodyComponent.h"
|
||
|
#include "Components/MujocoJointComponent.h"
|
||
|
#include "Components/MujocoGeomComponent.h"
|
||
|
#include "UObject/UnrealType.h"
|
||
|
#include "STLImportFactory.h"
|
||
|
#include "array"
|
||
|
#include "Factories/MaterialInstanceConstantFactoryNew.h"
|
||
|
#include "Materials/MaterialInstance.h"
|
||
|
#include "Materials/MaterialInstanceConstant.h"
|
||
|
#include "Factories/MaterialFactoryNew.h"
|
||
|
#include "Materials/MaterialExpressionVectorParameter.h"
|
||
|
#include "Factories/TextureFactory.h"
|
||
|
#include "ImageUtils.h"
|
||
|
#include "Materials/MaterialExpressionTextureSample.h"
|
||
|
#include "Materials/MaterialExpressionMultiply.h"
|
||
|
#include "Materials/MaterialExpressionScalarParameter.h"
|
||
|
#include "Materials/MaterialExpressionTextureObject.h"
|
||
|
#include "MaterialEditingLibrary.h"
|
||
|
#include "Engine/StaticMesh.h"
|
||
|
#include "MeshDescription.h"
|
||
|
#include "MujocoMeshFactory.h"
|
||
|
#include "LuckyMujocoEditor.h"
|
||
|
#include "Engine/Texture2D.h"
|
||
|
#include "IImageWrapper.h"
|
||
|
#include "IImageWrapperModule.h"
|
||
|
#include "Modules/ModuleManager.h"
|
||
|
#include "HAL/FileManager.h"
|
||
|
#include "ImageCore.h"
|
||
|
#include "Formats/HdrImageWrapper.h"
|
||
|
#include "Components/MujocoTendonComponent.h"
|
||
|
#include "Components/MujocoEqualityComponent.h"
|
||
|
#include "Components/MujocoActuatorComponent.h"
|
||
|
#include "MujocoAttributeHandler.h"
|
||
|
#include "unordered_map"
|
||
|
#include "unordered_set"
|
||
|
|
||
|
#define LOCTEXT_NAMESPACE "MujocoFactory"
|
||
|
|
||
|
class FStringUtils
|
||
|
{
|
||
|
public:
|
||
|
static FString ConvertToPascalCase(const FString& Input)
|
||
|
{
|
||
|
FString Output;
|
||
|
bool bNextUpperCase = true;
|
||
|
|
||
|
for (const TCHAR& Char : Input)
|
||
|
{
|
||
|
if (IsSymbol(Char))
|
||
|
{
|
||
|
bNextUpperCase = true;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
if (bNextUpperCase)
|
||
|
{
|
||
|
Output.AppendChar(FChar::ToUpper(Char));
|
||
|
bNextUpperCase = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
Output.AppendChar(FChar::ToLower(Char));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return Output;
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
static bool IsSymbol(const TCHAR& Char) { return !FChar::IsAlnum(Char); }
|
||
|
};
|
||
|
|
||
|
class FDefaultsToInline
|
||
|
{
|
||
|
private:
|
||
|
struct FDefaultBlock
|
||
|
{
|
||
|
std::unordered_map<std::string, std::unordered_map<std::string, std::string>> TagDefaults;
|
||
|
std::unordered_map<std::string, FDefaultBlock> NestedBlocks;
|
||
|
};
|
||
|
|
||
|
std::unordered_map<tinyxml2::XMLElement*, std::unordered_map<std::string, std::string>> InitialAttributes;
|
||
|
|
||
|
FDefaultBlock ParseDefaultNode(tinyxml2::XMLElement* Element)
|
||
|
{
|
||
|
FDefaultBlock Block;
|
||
|
for (auto* Child = Element->FirstChildElement(); Child; Child = Child->NextSiblingElement())
|
||
|
{
|
||
|
if (std::string_view{ Child->Name() } == "default")
|
||
|
{
|
||
|
if (const char* ClassAttr = Child->Attribute("class"))
|
||
|
Block.NestedBlocks.emplace(ClassAttr, ParseDefaultNode(Child));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
std::string TagName = Child->Name();
|
||
|
for (auto* Attr = Child->FirstAttribute(); Attr; Attr = Attr->Next())
|
||
|
Block.TagDefaults[TagName][Attr->Name()] = Attr->Value();
|
||
|
}
|
||
|
}
|
||
|
return Block;
|
||
|
}
|
||
|
|
||
|
std::unordered_map<std::string, FDefaultBlock> ParseGlobalDefaults(tinyxml2::XMLElement* GlobalDefault)
|
||
|
{
|
||
|
std::unordered_map<std::string, FDefaultBlock> Mapping;
|
||
|
Mapping.emplace("global", ParseDefaultNode(GlobalDefault));
|
||
|
return Mapping;
|
||
|
}
|
||
|
|
||
|
void MergeAttributes(tinyxml2::XMLElement* Element, const std::unordered_map<std::string, std::string>& Defaults)
|
||
|
{
|
||
|
for (const auto& [AttrName, AttrValue] : Defaults)
|
||
|
Element->SetAttribute(AttrName.c_str(), AttrValue.c_str());
|
||
|
|
||
|
auto It = InitialAttributes.find(Element);
|
||
|
if (It != InitialAttributes.end())
|
||
|
{
|
||
|
for (const auto& [AttrName, AttrValue] : It->second)
|
||
|
{
|
||
|
if (AttrName == "class" || AttrName == "childclass")
|
||
|
continue;
|
||
|
Element->SetAttribute(AttrName.c_str(), AttrValue.c_str());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const FDefaultBlock* GetDefaultForClass(const FDefaultBlock* Base, const std::string& ClassVal)
|
||
|
{
|
||
|
if (!Base)
|
||
|
return nullptr;
|
||
|
if (auto It = Base->NestedBlocks.find(ClassVal); It != Base->NestedBlocks.end())
|
||
|
return &It->second;
|
||
|
for (const auto& Pair : Base->NestedBlocks)
|
||
|
{
|
||
|
if (const FDefaultBlock* Found = GetDefaultForClass(&Pair.second, ClassVal))
|
||
|
return Found;
|
||
|
}
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
void ProcessNode(tinyxml2::XMLElement* Element, const FDefaultBlock* Context, const std::unordered_map<std::string, FDefaultBlock>* GlobalDefaults)
|
||
|
{
|
||
|
for (auto* Attr = Element->FirstAttribute(); Attr; Attr = Attr->Next())
|
||
|
InitialAttributes[Element][Attr->Name()] = Attr->Value();
|
||
|
|
||
|
if (!Context)
|
||
|
{
|
||
|
if (GlobalDefaults)
|
||
|
{
|
||
|
auto It = GlobalDefaults->find("global");
|
||
|
if (It != GlobalDefaults->end())
|
||
|
Context = &It->second;
|
||
|
}
|
||
|
}
|
||
|
if (Context)
|
||
|
{
|
||
|
if (auto It = Context->TagDefaults.find(Element->Name()); It != Context->TagDefaults.end())
|
||
|
MergeAttributes(Element, It->second);
|
||
|
}
|
||
|
if (const char* ClassAttr = Element->Attribute("class"))
|
||
|
{
|
||
|
Element->DeleteAttribute("class");
|
||
|
std::string ClassVal{ ClassAttr };
|
||
|
const FDefaultBlock* FoundDefault = GetDefaultForClass(Context, ClassVal);
|
||
|
if (!FoundDefault && GlobalDefaults)
|
||
|
{
|
||
|
auto It = GlobalDefaults->find("global");
|
||
|
if (It != GlobalDefaults->end())
|
||
|
FoundDefault = GetDefaultForClass(&It->second, ClassVal);
|
||
|
}
|
||
|
if (FoundDefault)
|
||
|
{
|
||
|
if (auto It = FoundDefault->TagDefaults.find(Element->Name()); It != FoundDefault->TagDefaults.end())
|
||
|
MergeAttributes(Element, It->second);
|
||
|
}
|
||
|
}
|
||
|
const FDefaultBlock* ChildContext = Context;
|
||
|
if (const char* ChildClassAttr = Element->Attribute("childclass"))
|
||
|
{
|
||
|
Element->DeleteAttribute("childclass");
|
||
|
std::string ChildClass{ ChildClassAttr };
|
||
|
const FDefaultBlock* FoundChild = GetDefaultForClass(Context, ChildClass);
|
||
|
if (!FoundChild && GlobalDefaults)
|
||
|
{
|
||
|
auto It = GlobalDefaults->find("global");
|
||
|
if (It != GlobalDefaults->end())
|
||
|
FoundChild = GetDefaultForClass(&It->second, ChildClass);
|
||
|
}
|
||
|
ChildContext = FoundChild;
|
||
|
}
|
||
|
for (auto* Child = Element->FirstChildElement(); Child; Child = Child->NextSiblingElement())
|
||
|
ProcessNode(Child, ChildContext, GlobalDefaults);
|
||
|
}
|
||
|
|
||
|
public:
|
||
|
TUniquePtr<tinyxml2::XMLDocument> Parse(const char* FileName)
|
||
|
{
|
||
|
auto Document = MakeUnique<tinyxml2::XMLDocument>();
|
||
|
if (Document->LoadFile(FileName) != tinyxml2::XML_SUCCESS)
|
||
|
return nullptr;
|
||
|
if (auto* MujocoElement = Document->FirstChildElement("mujoco"))
|
||
|
{
|
||
|
if (auto* GlobalDefault = MujocoElement->FirstChildElement("default"))
|
||
|
{
|
||
|
auto GlobalDefaults = ParseGlobalDefaults(GlobalDefault);
|
||
|
MujocoElement->DeleteChild(GlobalDefault);
|
||
|
for (auto* Child = MujocoElement->FirstChildElement(); Child; Child = Child->NextSiblingElement())
|
||
|
ProcessNode(Child, nullptr, &GlobalDefaults);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
for (auto* Child = MujocoElement->FirstChildElement(); Child; Child = Child->NextSiblingElement())
|
||
|
ProcessNode(Child, nullptr, nullptr);
|
||
|
}
|
||
|
}
|
||
|
return MoveTemp(Document);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
class SMujocoImportWindow : public SWindow
|
||
|
{
|
||
|
TSharedPtr<IDetailsView> DetailsView;
|
||
|
TObjectPtr<UMjocoImportSettings> ImportSettings;
|
||
|
UFactory* Factory = nullptr;
|
||
|
|
||
|
TSharedPtr<SVerticalBox> MainVerticalBox;
|
||
|
TSharedPtr<SButton> ImportButton;
|
||
|
TSharedPtr<SButton> CancelButton;
|
||
|
TSharedPtr<STextBlock> ErrorText;
|
||
|
TSharedPtr<STextBlock> ProgressText;
|
||
|
TSharedPtr<SComboBox<TSharedPtr<FString>>> ClassPropertyEntryBox;
|
||
|
|
||
|
FString Filename;
|
||
|
TArray<const UClass*> AllowedClasses;
|
||
|
TArray<TSharedPtr<FString>> AllowedClassNames;
|
||
|
TUniquePtr<tinyxml2::XMLDocument> Doc;
|
||
|
|
||
|
FString MujocoMeshAssetPath;
|
||
|
FString MujocoTextureAssetPath;
|
||
|
FString MujocoAssetPath;
|
||
|
|
||
|
AActor* GeneratedActor = nullptr;
|
||
|
UPackage* ParentPackage = nullptr;
|
||
|
UPackage* GeneratedPackage = nullptr;
|
||
|
UBlueprint* Blueprint = nullptr;
|
||
|
UClass* SelectedClass = nullptr;
|
||
|
|
||
|
FName GeneratedName = NAME_None;
|
||
|
EObjectFlags GeneratedFlags = RF_Transient;
|
||
|
|
||
|
FText SelectedClassText;
|
||
|
TMujocoModelPtr Model;
|
||
|
mjSpec* MjSpec = nullptr;
|
||
|
|
||
|
public:
|
||
|
SLATE_BEGIN_ARGS(SMujocoImportWindow) {}
|
||
|
SLATE_END_ARGS()
|
||
|
void Construct(const FArguments& InArgs, FString InFilename, UPackage* InParent, FName InName, EObjectFlags InFlags, UFactory* InFactory)
|
||
|
{
|
||
|
ParentPackage = InParent;
|
||
|
GeneratedName = InName;
|
||
|
GeneratedFlags = InFlags;
|
||
|
Factory = InFactory;
|
||
|
|
||
|
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
|
||
|
FDetailsViewArgs DetailsViewArgs;
|
||
|
DetailsViewArgs.bAllowSearch = false;
|
||
|
DetailsViewArgs.bHideSelectionTip = true;
|
||
|
DetailsViewArgs.bShowOptions = false;
|
||
|
DetailsViewArgs.bShowModifiedPropertiesOption = false;
|
||
|
DetailsViewArgs.bShowScrollBar = true;
|
||
|
DetailsViewArgs.bShowOptions = false;
|
||
|
DetailsViewArgs.bShowPropertyMatrixButton = false;
|
||
|
DetailsView = PropertyModule.CreateDetailView(DetailsViewArgs);
|
||
|
|
||
|
ImportSettings = NewObject<UMjocoImportSettings>(ParentPackage);
|
||
|
ImportSettings->SetFlags(RF_Transient | RF_Standalone | RF_Transactional);
|
||
|
|
||
|
ImportSettings->ParentClass = AActor::StaticClass();
|
||
|
|
||
|
ImportSettings->LoadConfig();
|
||
|
SelectedClass = const_cast<UClass*>(ImportSettings->ParentClass);
|
||
|
|
||
|
Filename = InFilename;
|
||
|
DetailsView->SetObject(ImportSettings);
|
||
|
|
||
|
AllowedClasses.Add(AActor::StaticClass());
|
||
|
AllowedClasses.Add(APawn::StaticClass());
|
||
|
AllowedClasses.Add(ACharacter::StaticClass());
|
||
|
|
||
|
FDefaultsToInline MergeXmlDefaults;
|
||
|
Doc = MergeXmlDefaults.Parse(TCHAR_TO_UTF8(*InFilename));
|
||
|
|
||
|
for (const UClass* Class : AllowedClasses)
|
||
|
{
|
||
|
AllowedClassNames.Add(MakeShared<FString>(Class->GetName()));
|
||
|
}
|
||
|
|
||
|
ImportButton = SNew(SButton).Text(FText::FromString(TEXT("Import"))).OnClicked(this, &SMujocoImportWindow::OnImportClicked);
|
||
|
CancelButton = SNew(SButton).Text(FText::FromString(TEXT("Cancel"))).OnClicked(this, &SMujocoImportWindow::OnCancelClicked);
|
||
|
|
||
|
// clang-format off
|
||
|
|
||
|
int32 SelectedIndex = AllowedClassNames.IndexOfByPredicate([this](TSharedPtr<FString> Item) { return *Item == SelectedClass->GetName(); });
|
||
|
SelectedClassText = FText::FromString(*AllowedClassNames[SelectedIndex].Get());
|
||
|
|
||
|
SAssignNew(ClassPropertyEntryBox, SComboBox<TSharedPtr<FString>>)
|
||
|
.OptionsSource(&AllowedClassNames)
|
||
|
.OnGenerateWidget_Lambda([](TSharedPtr<FString> InItem)
|
||
|
{
|
||
|
return SNew(STextBlock)
|
||
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
||
|
.Text(FText::FromString(*InItem));
|
||
|
})
|
||
|
.OnSelectionChanged_Lambda([this](TSharedPtr<FString> InItem, ESelectInfo::Type SelectInfo)
|
||
|
{
|
||
|
int32 SelectedIndex = AllowedClassNames.IndexOfByPredicate([InItem](TSharedPtr<FString> Item) { return *Item == *InItem; });
|
||
|
|
||
|
ImportSettings->ParentClass = AllowedClasses[SelectedIndex];
|
||
|
SelectedClass = const_cast<UClass*>(ImportSettings->ParentClass);
|
||
|
|
||
|
SelectedClassText = FText::FromString(*AllowedClassNames[SelectedIndex].Get());
|
||
|
})
|
||
|
[
|
||
|
SNew(STextBlock)
|
||
|
.Font(IDetailLayoutBuilder::GetDetailFont())
|
||
|
.Text_Lambda([this]() { return SelectedClassText; })
|
||
|
];
|
||
|
|
||
|
SAssignNew(MainVerticalBox, SVerticalBox)
|
||
|
+ SVerticalBox::Slot()
|
||
|
.HAlign(HAlign_Fill)
|
||
|
.AutoHeight()
|
||
|
.Padding(4, 8)
|
||
|
[
|
||
|
SNew(SHorizontalBox)
|
||
|
+ SHorizontalBox::Slot()
|
||
|
.AutoWidth()
|
||
|
.Padding(5, 5)
|
||
|
.VAlign(VAlign_Center)
|
||
|
[
|
||
|
SNew(STextBlock)
|
||
|
.Text(FText::FromString(TEXT("Parent Class")))
|
||
|
]
|
||
|
+ SHorizontalBox::Slot()
|
||
|
.FillWidth(1.0f)
|
||
|
.Padding(0)
|
||
|
[
|
||
|
ClassPropertyEntryBox.ToSharedRef()
|
||
|
]
|
||
|
]
|
||
|
+ SVerticalBox::Slot()
|
||
|
.HAlign(HAlign_Fill)
|
||
|
.FillHeight(1.0f)
|
||
|
.Padding(0, 0)
|
||
|
[
|
||
|
DetailsView.ToSharedRef()
|
||
|
]
|
||
|
+ SVerticalBox::Slot()
|
||
|
.AutoHeight()
|
||
|
.Padding(6, 4)
|
||
|
.HAlign(HAlign_Fill)
|
||
|
[
|
||
|
SNew(SHorizontalBox)
|
||
|
+ SHorizontalBox::Slot()
|
||
|
.AutoWidth()
|
||
|
.HAlign(HAlign_Fill)
|
||
|
[
|
||
|
SAssignNew(ProgressText, STextBlock)
|
||
|
.Text(FText::FromString(TEXT("")))
|
||
|
]
|
||
|
+ SHorizontalBox::Slot()
|
||
|
.AutoWidth()
|
||
|
.HAlign(HAlign_Right)
|
||
|
[
|
||
|
SNew(SHorizontalBox)
|
||
|
+ SHorizontalBox::Slot()
|
||
|
.AutoWidth()
|
||
|
.Padding(5)
|
||
|
[
|
||
|
ImportButton.ToSharedRef()
|
||
|
]
|
||
|
+ SHorizontalBox::Slot()
|
||
|
.AutoWidth()
|
||
|
.Padding(5)
|
||
|
[
|
||
|
CancelButton.ToSharedRef()
|
||
|
]
|
||
|
]
|
||
|
|
||
|
];
|
||
|
|
||
|
SAssignNew(ErrorText, STextBlock)
|
||
|
.Text(FText::FromString(TEXT("")))
|
||
|
.Visibility(EVisibility::Collapsed);
|
||
|
|
||
|
if(!Doc)
|
||
|
{
|
||
|
SetErrorText(FString::Printf(TEXT("Failed to parse XML file: %s"), *InFilename));
|
||
|
}
|
||
|
|
||
|
std::string ErrMsg{};
|
||
|
ErrMsg.resize(1024);
|
||
|
|
||
|
tinyxml2::XMLPrinter Printer;
|
||
|
Doc->Print(&Printer);
|
||
|
|
||
|
MjSpec = mj_parseXMLString(Printer.CStr(), nullptr, ErrMsg.data(), ErrMsg.capacity());
|
||
|
if (!MjSpec)
|
||
|
{
|
||
|
SetErrorText(FString::Printf(TEXT("%s"), UTF8_TO_TCHAR(ErrMsg.c_str())));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
const char* AssetDir = mjs_getString(MjSpec->modelfiledir);
|
||
|
if (AssetDir)
|
||
|
{
|
||
|
FString BaseDir = FPaths::GetPath(InFilename);
|
||
|
MujocoAssetPath = AssetDir;
|
||
|
|
||
|
if(FPaths::IsRelative(MujocoAssetPath))
|
||
|
MujocoAssetPath = FPaths::Combine(BaseDir, AssetDir);
|
||
|
|
||
|
mjs_setString(MjSpec->modelfiledir, TCHAR_TO_UTF8(*MujocoAssetPath));
|
||
|
}
|
||
|
|
||
|
const char* MeshDir = mjs_getString(MjSpec->meshdir);
|
||
|
if (MeshDir)
|
||
|
{
|
||
|
FString BaseDir = FPaths::GetPath(InFilename);
|
||
|
MujocoMeshAssetPath = MeshDir;
|
||
|
|
||
|
if(FPaths::IsRelative(MujocoMeshAssetPath))
|
||
|
MujocoMeshAssetPath = FPaths::Combine(BaseDir, MeshDir);
|
||
|
|
||
|
mjs_setString(MjSpec->meshdir, TCHAR_TO_UTF8(*MujocoMeshAssetPath));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
MujocoMeshAssetPath = MujocoAssetPath;
|
||
|
}
|
||
|
|
||
|
const char* TextureDir = mjs_getString(MjSpec->texturedir);
|
||
|
if (TextureDir)
|
||
|
{
|
||
|
FString BaseDir = FPaths::GetPath(InFilename);
|
||
|
MujocoTextureAssetPath = TextureDir;
|
||
|
|
||
|
if(FPaths::IsRelative(MujocoTextureAssetPath))
|
||
|
MujocoTextureAssetPath = FPaths::Combine(BaseDir, MujocoTextureAssetPath);
|
||
|
|
||
|
mjs_setString(MjSpec->texturedir, TCHAR_TO_UTF8(*MujocoTextureAssetPath));
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
MujocoTextureAssetPath = MujocoAssetPath;
|
||
|
}
|
||
|
|
||
|
Model = MakeMujocoModelPtr(mj_compile(MjSpec, nullptr));
|
||
|
if (!Model)
|
||
|
{
|
||
|
std::string SpecErrMsg = mjs_getError(MjSpec);
|
||
|
SetErrorText(FString::Printf(TEXT("%s"), UTF8_TO_TCHAR(SpecErrMsg.c_str())));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SWindow::Construct(SWindow::FArguments()
|
||
|
.Title(FText::FromString(TEXT("Import Mujoco Model")))
|
||
|
.ClientSize(FVector2D(500, 300))
|
||
|
.SupportsMaximize(false)
|
||
|
.SupportsMinimize(false)
|
||
|
.CreateTitleBar(true)
|
||
|
.SizingRule(ESizingRule::UserSized)
|
||
|
.FocusWhenFirstShown(true)
|
||
|
.ActivationPolicy(EWindowActivationPolicy::Always)
|
||
|
[
|
||
|
SNew(SVerticalBox)
|
||
|
+ SVerticalBox::Slot()
|
||
|
.HAlign(HAlign_Center)
|
||
|
.VAlign(VAlign_Center)
|
||
|
.FillHeight(1.0f)
|
||
|
.Padding(4, 8)
|
||
|
[
|
||
|
ErrorText.ToSharedRef()
|
||
|
]
|
||
|
+ SVerticalBox::Slot()
|
||
|
.FillHeight(1.0f)
|
||
|
.Padding(0, 0)
|
||
|
[
|
||
|
MainVerticalBox.ToSharedRef()
|
||
|
]
|
||
|
]
|
||
|
);
|
||
|
// clang-format on
|
||
|
}
|
||
|
|
||
|
UObject* GetGeneratedAsset() const { return ImportSettings; }
|
||
|
|
||
|
void SetErrorText(const FString& Text)
|
||
|
{
|
||
|
ErrorText->SetText(FText::FromString(Text));
|
||
|
ErrorText->SetVisibility(EVisibility::Visible);
|
||
|
MainVerticalBox->SetVisibility(EVisibility::Collapsed);
|
||
|
}
|
||
|
|
||
|
bool GenerateBluePrint()
|
||
|
{
|
||
|
const FString FileExt{ FPaths::GetExtension(Filename) };
|
||
|
const FString BaseFilename{ FPaths::GetBaseFilename(Filename) };
|
||
|
|
||
|
FString ModelName{ UPackageTools::SanitizePackageName(BaseFilename) };
|
||
|
|
||
|
ModelName = ObjectTools::SanitizeObjectName(ModelName);
|
||
|
ModelName = FStringUtils::ConvertToPascalCase(ModelName);
|
||
|
|
||
|
FString PackagePath{ FPaths::GetPath(ParentPackage->GetName()) };
|
||
|
|
||
|
const FString BPName{ FString::Printf(TEXT("BP_%s"), *ModelName) };
|
||
|
const FString BPAssetPath{ FString::Printf(TEXT("%s/%s"), *PackagePath, *BPName) };
|
||
|
|
||
|
GeneratedPackage = CreatePackage(*BPAssetPath);
|
||
|
GeneratedPackage->FullyLoad();
|
||
|
|
||
|
GeneratedActor = NewObject<AActor>(GetTransientPackage(), SelectedClass, GeneratedName, RF_Transactional | RF_Transient);
|
||
|
|
||
|
if (!GeneratedActor)
|
||
|
{
|
||
|
SetErrorText(FString::Printf(TEXT("Failed to create actor: %s"), *BPAssetPath));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
GeneratedActor->PostLoad();
|
||
|
FAssetRegistryModule::AssetCreated(GeneratedActor);
|
||
|
|
||
|
FKismetEditorUtilities::FCreateBlueprintFromActorParams BPParams;
|
||
|
BPParams.ParentClassOverride = const_cast<UClass*>(SelectedClass);
|
||
|
BPParams.bReplaceActor = false;
|
||
|
BPParams.bOpenBlueprint = false;
|
||
|
|
||
|
// first check if the blueprint already exists
|
||
|
auto ExistingBlueprint = Cast<UBlueprint>(StaticLoadObject(UBlueprint::StaticClass(), nullptr, *BPAssetPath));
|
||
|
if (ExistingBlueprint)
|
||
|
{
|
||
|
ExistingBlueprint->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors);
|
||
|
ExistingBlueprint->ClearFlags(RF_Public | RF_Standalone | RF_Transactional);
|
||
|
ExistingBlueprint->RemoveFromRoot();
|
||
|
ExistingBlueprint->MarkAsGarbage();
|
||
|
}
|
||
|
|
||
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPreImport(Factory, UBlueprint::StaticClass(), Blueprint, FName(*BPName), TEXT("xml"));
|
||
|
|
||
|
Blueprint = FKismetEditorUtilities::CreateBlueprintFromActor(BPAssetPath, GeneratedActor, BPParams);
|
||
|
if (!Blueprint)
|
||
|
{
|
||
|
SetErrorText(FString::Printf(TEXT("Failed to create blueprint: %s"), *BPAssetPath));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Blueprint->PostLoad();
|
||
|
FAssetRegistryModule::AssetCreated(Blueprint);
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool ResolveOrientation(FQuat& OutQuat, bool bDegrees, const FString& Sequence, const FMujocoOrientation& Orient)
|
||
|
{
|
||
|
if (Sequence.Len() < 3 && Orient.Type == EMujocoOrientationType::Euler)
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
switch (Orient.Type)
|
||
|
{
|
||
|
case EMujocoOrientationType::AxisAngle:
|
||
|
{
|
||
|
FVector Axis(Orient.AxisAngle[0], Orient.AxisAngle[1], Orient.AxisAngle[2]);
|
||
|
float Angle = Orient.AxisAngle[3];
|
||
|
|
||
|
if (bDegrees)
|
||
|
{
|
||
|
Angle = FMath::DegreesToRadians(Angle);
|
||
|
}
|
||
|
|
||
|
if (Axis.IsNearlyZero())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
Axis.Normalize();
|
||
|
OutQuat = FQuat(Axis, Angle);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EMujocoOrientationType::XYAxes:
|
||
|
{
|
||
|
FVector XAxis(Orient.XYAxes[0], Orient.XYAxes[1], Orient.XYAxes[2]);
|
||
|
FVector YAxis(Orient.XYAxes[3], Orient.XYAxes[4], Orient.XYAxes[5]);
|
||
|
|
||
|
if (XAxis.IsNearlyZero())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
XAxis.Normalize();
|
||
|
YAxis -= XAxis * FVector::DotProduct(XAxis, YAxis);
|
||
|
|
||
|
if (YAxis.IsNearlyZero())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
YAxis.Normalize();
|
||
|
FVector ZAxis = FVector::CrossProduct(XAxis, YAxis);
|
||
|
|
||
|
if (ZAxis.IsNearlyZero())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
OutQuat = FQuat(FRotationMatrix::MakeFromXY(XAxis, YAxis));
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EMujocoOrientationType::ZAxis:
|
||
|
{
|
||
|
FVector ZAxis(Orient.ZAxis[0], Orient.ZAxis[1], Orient.ZAxis[2]);
|
||
|
|
||
|
if (ZAxis.IsNearlyZero())
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
ZAxis.Normalize();
|
||
|
OutQuat = FQuat::FindBetweenNormals(FVector::UpVector, ZAxis);
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
case EMujocoOrientationType::Euler:
|
||
|
{
|
||
|
float EulerAngles[3] = { Orient.Euler[0], Orient.Euler[1], Orient.Euler[2] };
|
||
|
|
||
|
if (bDegrees)
|
||
|
{
|
||
|
for (float& Angle : EulerAngles)
|
||
|
{
|
||
|
Angle = FMath::DegreesToRadians(Angle);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
OutQuat = FQuat::Identity;
|
||
|
|
||
|
for (int32 i = 0; i < 3; ++i)
|
||
|
{
|
||
|
FVector Axis;
|
||
|
|
||
|
switch (Sequence[i])
|
||
|
{
|
||
|
case 'x':
|
||
|
case 'X':
|
||
|
Axis = FVector::ForwardVector;
|
||
|
break;
|
||
|
case 'y':
|
||
|
case 'Y':
|
||
|
Axis = FVector::RightVector;
|
||
|
break;
|
||
|
case 'z':
|
||
|
case 'Z':
|
||
|
Axis = FVector::UpVector;
|
||
|
break;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
FQuat RotQuat(Axis, EulerAngles[i]);
|
||
|
OutQuat = FChar::IsLower(Sequence[i]) ? OutQuat * RotQuat : RotQuat * OutQuat;
|
||
|
}
|
||
|
|
||
|
OutQuat.W *= -1.0f;
|
||
|
OutQuat.Y *= -1.0f;
|
||
|
|
||
|
OutQuat.Normalize();
|
||
|
break;
|
||
|
}
|
||
|
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void SetGeomMesh(UMujocoGeomComponent* GeomComp, FVector& MeshScale, UStaticMesh* Mesh)
|
||
|
{
|
||
|
GeomComp->Modify();
|
||
|
bool HasCollision = true;
|
||
|
if (GeomComp->Geom.ConType.IsSet())
|
||
|
{
|
||
|
HasCollision = GeomComp->Geom.ConType.GetValue() != 0;
|
||
|
}
|
||
|
if (!HasCollision)
|
||
|
{
|
||
|
GeomComp->SetCollisionEnabled(ECollisionEnabled::NoCollision);
|
||
|
GeomComp->SetCollisionProfileName(UCollisionProfile::NoCollision_ProfileName);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
GeomComp->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics);
|
||
|
GeomComp->SetCollisionProfileName(UCollisionProfile::BlockAll_ProfileName);
|
||
|
}
|
||
|
|
||
|
if (MeshScale != FVector::One())
|
||
|
{
|
||
|
GeomComp->SetRelativeScale3D(MeshScale);
|
||
|
}
|
||
|
|
||
|
GeomComp->SetStaticMesh(Mesh);
|
||
|
GeomComp->MujocoStaticMesh = Mesh;
|
||
|
|
||
|
FProperty* ChangedProp = FindFProperty<FProperty>(GeomComp->GetClass(), GET_MEMBER_NAME_CHECKED(UMujocoGeomComponent, MujocoStaticMesh));
|
||
|
FPropertyChangedEvent Event(ChangedProp, EPropertyChangeType::ValueSet);
|
||
|
GeomComp->PostEditChangeProperty(Event);
|
||
|
|
||
|
GeomComp->PostEditChange();
|
||
|
}
|
||
|
|
||
|
bool AddComponents()
|
||
|
{
|
||
|
if (!Blueprint)
|
||
|
{
|
||
|
SetErrorText(FString::Printf(TEXT("Blueprint not found")));
|
||
|
return false;
|
||
|
}
|
||
|
auto* GeneratedCls = CastChecked<UBlueprintGeneratedClass>(Blueprint->GeneratedClass);
|
||
|
USimpleConstructionScript* SCS = Blueprint->SimpleConstructionScript;
|
||
|
if (!SCS)
|
||
|
{
|
||
|
SetErrorText(FString::Printf(TEXT("Failed to get SCS")));
|
||
|
return false;
|
||
|
}
|
||
|
if (!Doc)
|
||
|
{
|
||
|
SetErrorText(FString::Printf(TEXT("Failed to parse XML file")));
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
// Lambda to set a scene component's transform based on XML attributes,
|
||
|
// applying a coordinate conversion.
|
||
|
auto SetSceneComponentTransformFromXml = [&](tinyxml2::XMLElement* Element, USceneComponent* SceneComp) {
|
||
|
if (const char* PosAttr = Element->Attribute("pos"))
|
||
|
{
|
||
|
TArray<FString> PosTokens;
|
||
|
FString PosStr = UTF8_TO_TCHAR(PosAttr);
|
||
|
PosStr.ParseIntoArray(PosTokens, TEXT(" "), true);
|
||
|
if (PosTokens.Num() >= 3)
|
||
|
{
|
||
|
// Parse the position from XML.
|
||
|
const float X = FCString::Atof(*PosTokens[0]);
|
||
|
const float Y = FCString::Atof(*PosTokens[1]);
|
||
|
const float Z = FCString::Atof(*PosTokens[2]);
|
||
|
// Convert from MuJoCo (meters, different axes) to UE coordinate system.
|
||
|
// Example conversion: scale X and Z by 100, invert Y.
|
||
|
const FVector ConvertedPos = FVector(X, Y, Z) * FVector(100.0f, -100.0f, 100.0f);
|
||
|
SceneComp->SetRelativeLocation(ConvertedPos);
|
||
|
}
|
||
|
}
|
||
|
if (const char* QuatAttr = Element->Attribute("quat"))
|
||
|
{
|
||
|
TArray<FString> QuatTokens;
|
||
|
FString QuatStr = UTF8_TO_TCHAR(QuatAttr);
|
||
|
QuatStr.ParseIntoArray(QuatTokens, TEXT(" "), true);
|
||
|
if (QuatTokens.Num() >= 4)
|
||
|
{
|
||
|
// Assume the XML order is "w x y z". Adjust conversion as needed.
|
||
|
const float W = FCString::Atof(*QuatTokens[0]) * -1.0;
|
||
|
const float X = FCString::Atof(*QuatTokens[1]);
|
||
|
const float Y = FCString::Atof(*QuatTokens[2]) * -1.0;
|
||
|
const float Z = FCString::Atof(*QuatTokens[3]);
|
||
|
// For example, invert the Y axis to match UE's coordinate system.
|
||
|
FQuat ConvertedQuat(X, Y, Z, W);
|
||
|
ConvertedQuat.Normalize();
|
||
|
SceneComp->SetRelativeRotation(ConvertedQuat.Rotator());
|
||
|
}
|
||
|
}
|
||
|
if (const char* ScaleAttr = Element->Attribute("euler"))
|
||
|
{
|
||
|
bool IsDegree = MjSpec->compiler.degree != 0;
|
||
|
char EulerSeq[4] = { 0 };
|
||
|
memcpy(EulerSeq, MjSpec->compiler.eulerseq, 3);
|
||
|
|
||
|
TArray<FString> EulerTokens;
|
||
|
FString EulerStr = UTF8_TO_TCHAR(ScaleAttr);
|
||
|
EulerStr.ParseIntoArray(EulerTokens, TEXT(" "), true);
|
||
|
|
||
|
if (EulerTokens.Num() >= 3)
|
||
|
{
|
||
|
FMujocoOrientation Orient = {};
|
||
|
Orient.Euler.Init(0.0f, 3);
|
||
|
|
||
|
Orient.Type = EMujocoOrientationType::Euler;
|
||
|
Orient.Euler[0] = FCString::Atof(*EulerTokens[0]);
|
||
|
Orient.Euler[1] = FCString::Atof(*EulerTokens[1]);
|
||
|
Orient.Euler[2] = FCString::Atof(*EulerTokens[2]);
|
||
|
|
||
|
FQuat Quat;
|
||
|
if (ResolveOrientation(Quat, IsDegree, EulerSeq, Orient))
|
||
|
{
|
||
|
SceneComp->SetRelativeRotation(Quat);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (const char* ScaleAttr = Element->Attribute("xyaxes"))
|
||
|
{
|
||
|
TArray<FString> ScaleTokens;
|
||
|
FString ScaleStr = UTF8_TO_TCHAR(ScaleAttr);
|
||
|
ScaleStr.ParseIntoArray(ScaleTokens, TEXT(" "), true);
|
||
|
if (ScaleTokens.Num() >= 6)
|
||
|
{
|
||
|
FMujocoOrientation Orient = {};
|
||
|
Orient.XYAxes.Init(0.0f, 6);
|
||
|
Orient.Type = EMujocoOrientationType::XYAxes;
|
||
|
Orient.XYAxes[0] = FCString::Atof(*ScaleTokens[0]);
|
||
|
Orient.XYAxes[1] = FCString::Atof(*ScaleTokens[1]);
|
||
|
Orient.XYAxes[2] = FCString::Atof(*ScaleTokens[2]);
|
||
|
Orient.XYAxes[3] = FCString::Atof(*ScaleTokens[3]);
|
||
|
Orient.XYAxes[4] = FCString::Atof(*ScaleTokens[4]);
|
||
|
Orient.XYAxes[5] = FCString::Atof(*ScaleTokens[5]);
|
||
|
FQuat Quat;
|
||
|
if (ResolveOrientation(Quat, false, TEXT("xy"), Orient))
|
||
|
{
|
||
|
SceneComp->SetRelativeRotation(Quat);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (const char* ScaleAttr = Element->Attribute("zaxis"))
|
||
|
{
|
||
|
TArray<FString> ScaleTokens;
|
||
|
FString ScaleStr = UTF8_TO_TCHAR(ScaleAttr);
|
||
|
ScaleStr.ParseIntoArray(ScaleTokens, TEXT(" "), true);
|
||
|
if (ScaleTokens.Num() >= 3)
|
||
|
{
|
||
|
FMujocoOrientation Orient = {};
|
||
|
Orient.ZAxis.Init(0.0f, 3);
|
||
|
Orient.Type = EMujocoOrientationType::ZAxis;
|
||
|
Orient.ZAxis[0] = FCString::Atof(*ScaleTokens[0]);
|
||
|
Orient.ZAxis[1] = FCString::Atof(*ScaleTokens[1]);
|
||
|
Orient.ZAxis[2] = FCString::Atof(*ScaleTokens[2]);
|
||
|
FQuat Quat;
|
||
|
if (ResolveOrientation(Quat, false, TEXT("z"), Orient))
|
||
|
{
|
||
|
SceneComp->SetRelativeRotation(Quat);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (const char* ScaleAttr = Element->Attribute("axisangle"))
|
||
|
{
|
||
|
TArray<FString> ScaleTokens;
|
||
|
FString ScaleStr = UTF8_TO_TCHAR(ScaleAttr);
|
||
|
ScaleStr.ParseIntoArray(ScaleTokens, TEXT(" "), true);
|
||
|
if (ScaleTokens.Num() >= 4)
|
||
|
{
|
||
|
FMujocoOrientation Orient = {};
|
||
|
Orient.AxisAngle.Init(0.0f, 4);
|
||
|
Orient.Type = EMujocoOrientationType::AxisAngle;
|
||
|
Orient.AxisAngle[0] = FCString::Atof(*ScaleTokens[0]);
|
||
|
Orient.AxisAngle[1] = FCString::Atof(*ScaleTokens[1]);
|
||
|
Orient.AxisAngle[2] = FCString::Atof(*ScaleTokens[2]);
|
||
|
Orient.AxisAngle[3] = FCString::Atof(*ScaleTokens[3]);
|
||
|
FQuat Quat;
|
||
|
if (ResolveOrientation(Quat, false, TEXT("xyz"), Orient))
|
||
|
{
|
||
|
SceneComp->SetRelativeRotation(Quat);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
auto ApplyAttributes = [&](UStruct* StructDef, void* DataPtr, tinyxml2::XMLElement* Element, bool LogMissing = false) -> void {
|
||
|
TSet<FString> HandledAttributes;
|
||
|
|
||
|
FString ElementName = UTF8_TO_TCHAR(Element->Name());
|
||
|
|
||
|
for (FProperty* Prop : TFieldRange<FProperty>(StructDef))
|
||
|
{
|
||
|
FString AttrKey = Prop->GetMetaData(TEXT("Attribute"));
|
||
|
FString PropName = Prop->GetName();
|
||
|
|
||
|
if (PropName == TEXT("Type"))
|
||
|
{
|
||
|
AttrKey = TEXT("type");
|
||
|
}
|
||
|
else if (AttrKey.IsEmpty())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (const char* XmlVal = Element->Attribute(TCHAR_TO_ANSI(*AttrKey)))
|
||
|
{
|
||
|
ParseMujocoElementAttribute(Element, AttrKey, DataPtr, Prop);
|
||
|
|
||
|
FString Combined = ElementName + TEXT(":") + AttrKey;
|
||
|
|
||
|
HandledAttributes.Add(Combined);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (LogMissing)
|
||
|
{
|
||
|
static std::unordered_set<std::string> Logged;
|
||
|
for (auto* Attr = Element->FirstAttribute(); Attr; Attr = Attr->Next())
|
||
|
{
|
||
|
FString AttrKey = UTF8_TO_TCHAR(Attr->Name());
|
||
|
FString Combined = ElementName + TEXT(":") + AttrKey;
|
||
|
|
||
|
if (HandledAttributes.Contains(Combined))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (Logged.find(TCHAR_TO_UTF8(*Combined)) == Logged.end())
|
||
|
{
|
||
|
Logged.insert(TCHAR_TO_UTF8(*Combined));
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Unhandled attribute: %s"), *Combined);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
FString PackagePath{ FPaths::GetPath(GeneratedPackage->GetName()) };
|
||
|
FString PackageName{ FPaths::GetBaseFilename(GeneratedPackage->GetName()) };
|
||
|
|
||
|
TMap<FString, TArray<USCS_Node*>> ImportedMeshFiles;
|
||
|
TMap<FString, TArray<USCS_Node*>> ImportedMaterials;
|
||
|
TMap<FString, FString> ToBeImportedMeshFiles;
|
||
|
TMap<FString, FString> ToBeImportedMaterials;
|
||
|
TMap<FString, FString> ToBeImportedTextures;
|
||
|
TMap<FString, FString> MeshNameToPath;
|
||
|
TMap<FString, FVector> MeshSizes;
|
||
|
|
||
|
if (ImportSettings->bImportMeshes)
|
||
|
{
|
||
|
if (tinyxml2::XMLElement* RootElem = Doc->RootElement())
|
||
|
{
|
||
|
if (tinyxml2::XMLElement* AssetElem = RootElem->FirstChildElement("asset"))
|
||
|
{
|
||
|
if (ImportSettings->bImportTextures)
|
||
|
{
|
||
|
FString TexturePath = FPaths::Combine(PackagePath, PackageName);
|
||
|
|
||
|
if (!ImportSettings->TextureSubdir.IsEmpty())
|
||
|
{
|
||
|
TexturePath = FPaths::Combine(PackagePath, PackageName, ImportSettings->TextureSubdir);
|
||
|
}
|
||
|
|
||
|
FAssetToolsModule& AssetTools = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||
|
|
||
|
for (tinyxml2::XMLElement* MeshElem = AssetElem->FirstChildElement("texture"); MeshElem; MeshElem = MeshElem->NextSiblingElement("texture"))
|
||
|
{
|
||
|
const char* TextureFileCStr = MeshElem->Attribute("file");
|
||
|
if (!TextureFileCStr)
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Texture element missing file attribute"));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
FString TextureFile = TextureFileCStr ? UTF8_TO_TCHAR(TextureFileCStr) : TEXT("");
|
||
|
FString FileName = FPaths::GetBaseFilename(TextureFile);
|
||
|
FString TextureFilePath = FPaths::Combine(MujocoTextureAssetPath, TextureFile);
|
||
|
TexturePath = FPaths::Combine(TexturePath, FileName);
|
||
|
|
||
|
if (!FPaths::FileExists(TextureFilePath))
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Texture file not found: %s"), *TextureFilePath);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
UPackage* TexturePackage = CreatePackage(*TexturePath);
|
||
|
if (!TexturePackage)
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Failed to create package for texture: %s"), *TextureFilePath);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (UTexture2D* SourceTexture = FImageUtils::ImportFileAsTexture2D(TextureFilePath))
|
||
|
{
|
||
|
UTexture2D* NewTexture = NewObject<UTexture2D>(TexturePackage, *FPaths::GetBaseFilename(TexturePath), RF_Public | RF_Standalone);
|
||
|
if (!NewTexture)
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Failed to create texture object for: %s"), *TextureFilePath);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
int32 Width = SourceTexture->GetSizeX();
|
||
|
int32 Height = SourceTexture->GetSizeY();
|
||
|
|
||
|
NewTexture->CompressionSettings = TextureCompressionSettings::TC_Default;
|
||
|
NewTexture->SRGB = true;
|
||
|
NewTexture->LODGroup = TEXTUREGROUP_World;
|
||
|
NewTexture->MipGenSettings = TextureMipGenSettings::TMGS_NoMipmaps;
|
||
|
NewTexture->NeverStream = true;
|
||
|
|
||
|
FTexture2DMipMap& Mip = SourceTexture->GetPlatformData()->Mips[0];
|
||
|
const uint8* RawData = static_cast<const uint8*>(Mip.BulkData.Lock(LOCK_READ_ONLY));
|
||
|
|
||
|
if (RawData)
|
||
|
{
|
||
|
NewTexture->Source.Init(Width, Height, 1, 1, ETextureSourceFormat::TSF_BGRA8, RawData);
|
||
|
}
|
||
|
Mip.BulkData.Unlock();
|
||
|
|
||
|
NewTexture->UpdateResource();
|
||
|
NewTexture->PreEditChange(nullptr);
|
||
|
NewTexture->PostEditChange();
|
||
|
NewTexture->MarkPackageDirty();
|
||
|
|
||
|
FAssetRegistryModule::AssetCreated(NewTexture);
|
||
|
UE_LOG(LogTemp, Log, TEXT("Texture Created: %s"), *NewTexture->GetName());
|
||
|
|
||
|
TexturePackage->MarkPackageDirty();
|
||
|
|
||
|
ToBeImportedTextures.Add(FileName, TexturePath);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if (ImportSettings->bImportMaterials)
|
||
|
{
|
||
|
|
||
|
FString MaterialPath = FPaths::Combine(PackagePath, PackageName);
|
||
|
|
||
|
if (!ImportSettings->MaterialSubdir.IsEmpty())
|
||
|
{
|
||
|
MaterialPath = FPaths::Combine(PackagePath, PackageName, ImportSettings->MaterialSubdir);
|
||
|
}
|
||
|
|
||
|
FAssetToolsModule& AssetTools = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
|
||
|
|
||
|
for (tinyxml2::XMLElement* MeshElem = AssetElem->FirstChildElement("material"); MeshElem; MeshElem = MeshElem->NextSiblingElement("material"))
|
||
|
{
|
||
|
const char* MaterialNameCStr = MeshElem->Attribute("name");
|
||
|
if (!MaterialNameCStr)
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Warning, TEXT("Material element missing name attribute"));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
FString MaterialName = UTF8_TO_TCHAR(MaterialNameCStr);
|
||
|
FString MaterialAssetName = TEXT("MI_") + FStringUtils::ConvertToPascalCase(MaterialName);
|
||
|
|
||
|
const char* MaterialColorCStr = MeshElem->Attribute("rgba");
|
||
|
const char* TextureNameStr = MeshElem->Attribute("texture");
|
||
|
|
||
|
const char* SpecularStr = MeshElem->Attribute("specular");
|
||
|
const char* ShininessStr = MeshElem->Attribute("shininess");
|
||
|
const char* ReflectanceStr = MeshElem->Attribute("reflectance");
|
||
|
|
||
|
FColor MaterialColor = FColor::White;
|
||
|
|
||
|
UMaterialFactoryNew* MaterialFactory = NewObject<UMaterialFactoryNew>();
|
||
|
if (UMaterial* NewMaterial = Cast<UMaterial>(AssetTools.Get().CreateAsset(MaterialAssetName, MaterialPath, UMaterial::StaticClass(), MaterialFactory)))
|
||
|
{
|
||
|
UMaterialEditorOnlyData& Data = *NewMaterial->GetEditorOnlyData();
|
||
|
|
||
|
if (MaterialColorCStr)
|
||
|
{
|
||
|
TArray<FString> Components;
|
||
|
FString MaterialColorStr = UTF8_TO_TCHAR(MaterialColorCStr);
|
||
|
MaterialColorStr.ParseIntoArray(Components, TEXT(" "), true);
|
||
|
if (Components.Num() >= 4)
|
||
|
{
|
||
|
const float RedValue = FCString::Atof(*Components[0]);
|
||
|
const float GreenValue = FCString::Atof(*Components[1]);
|
||
|
const float BlueValue = FCString::Atof(*Components[2]);
|
||
|
const float AlphaValue = FCString::Atof(*Components[3]);
|
||
|
const FColor ColorValue{ static_cast<uint8>(RedValue * 255.f), static_cast<uint8>(GreenValue * 255.f), static_cast<uint8>(BlueValue * 255.f), static_cast<uint8>(AlphaValue * 255.f) };
|
||
|
|
||
|
UMaterialExpressionVectorParameter* DiffuseParam = NewObject<UMaterialExpressionVectorParameter>(NewMaterial);
|
||
|
DiffuseParam->ParameterName = TEXT("DiffuseColor");
|
||
|
DiffuseParam->DefaultValue = FLinearColor(ColorValue);
|
||
|
|
||
|
NewMaterial->GetExpressionCollection().AddExpression(DiffuseParam);
|
||
|
|
||
|
Data.BaseColor.Expression = DiffuseParam;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (TextureNameStr)
|
||
|
{
|
||
|
FString TextureName = UTF8_TO_TCHAR(TextureNameStr);
|
||
|
FString TexturePath = ToBeImportedTextures.FindRef(TextureName);
|
||
|
if (!TexturePath.IsEmpty())
|
||
|
{
|
||
|
UMaterialExpressionTextureSample* TextureExpression = NewObject<UMaterialExpressionTextureSample>(NewMaterial);
|
||
|
auto* Texture = Cast<UTexture>(StaticLoadObject(UTexture::StaticClass(), nullptr, *TexturePath));
|
||
|
TextureExpression->SamplerType = SAMPLERTYPE_Color;
|
||
|
|
||
|
NewMaterial->GetExpressionCollection().AddExpression(TextureExpression);
|
||
|
|
||
|
if (Texture)
|
||
|
{
|
||
|
TextureExpression->Texture = Texture;
|
||
|
}
|
||
|
Data.BaseColor.Expression = TextureExpression;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (SpecularStr)
|
||
|
{
|
||
|
float SpecularValue = FCString::Atof(UTF8_TO_TCHAR(SpecularStr));
|
||
|
UMaterialExpressionScalarParameter* SpecularParam = NewObject<UMaterialExpressionScalarParameter>(NewMaterial);
|
||
|
SpecularParam->ParameterName = TEXT("Specular");
|
||
|
SpecularParam->DefaultValue = SpecularValue;
|
||
|
|
||
|
NewMaterial->GetExpressionCollection().AddExpression(SpecularParam);
|
||
|
|
||
|
Data.Specular.Expression = SpecularParam;
|
||
|
}
|
||
|
|
||
|
if (ShininessStr)
|
||
|
{
|
||
|
float ShininessValue = FCString::Atof(UTF8_TO_TCHAR(ShininessStr));
|
||
|
UMaterialExpressionScalarParameter* ShininessParam = NewObject<UMaterialExpressionScalarParameter>(NewMaterial);
|
||
|
ShininessParam->ParameterName = TEXT("Shininess");
|
||
|
ShininessParam->DefaultValue = ShininessValue;
|
||
|
|
||
|
NewMaterial->GetExpressionCollection().AddExpression(ShininessParam);
|
||
|
|
||
|
Data.Roughness.Expression = ShininessParam;
|
||
|
}
|
||
|
|
||
|
if (ReflectanceStr)
|
||
|
{
|
||
|
float ReflectanceValue = FCString::Atof(UTF8_TO_TCHAR(ReflectanceStr));
|
||
|
UMaterialExpressionScalarParameter* ReflectanceParam = NewObject<UMaterialExpressionScalarParameter>(NewMaterial);
|
||
|
ReflectanceParam->ParameterName = TEXT("Reflectance");
|
||
|
ReflectanceParam->DefaultValue = ReflectanceValue;
|
||
|
|
||
|
NewMaterial->GetExpressionCollection().AddExpression(ReflectanceParam);
|
||
|
|
||
|
Data.Metallic.Expression = ReflectanceParam;
|
||
|
}
|
||
|
|
||
|
NewMaterial->PreEditChange(nullptr);
|
||
|
NewMaterial->PostEditChange();
|
||
|
NewMaterial->MarkPackageDirty();
|
||
|
|
||
|
FAssetRegistryModule::AssetCreated(NewMaterial);
|
||
|
|
||
|
ToBeImportedMaterials.Add(MaterialName, NewMaterial->GetPathName());
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
for (tinyxml2::XMLElement* MeshElem = AssetElem->FirstChildElement("mesh"); MeshElem; MeshElem = MeshElem->NextSiblingElement("mesh"))
|
||
|
{
|
||
|
const char* MeshFileCStr = MeshElem->Attribute("file");
|
||
|
if (!MeshFileCStr)
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
FString MeshFile = MeshFileCStr ? UTF8_TO_TCHAR(MeshFileCStr) : TEXT("");
|
||
|
|
||
|
const char* MeshNameCStr = MeshElem->Attribute("name");
|
||
|
FString MeshName = MeshNameCStr ? UTF8_TO_TCHAR(MeshNameCStr) : TEXT("");
|
||
|
FString FileName = FPaths::GetBaseFilename(MeshFile);
|
||
|
if (MeshName.IsEmpty())
|
||
|
{
|
||
|
MeshName = FileName;
|
||
|
}
|
||
|
|
||
|
const char* MeshSizeCStr = MeshElem->Attribute("scale");
|
||
|
FVector MeshSize = FVector::One();
|
||
|
if (MeshSizeCStr)
|
||
|
{
|
||
|
TArray<FString> Components;
|
||
|
FString MeshSizeStr = UTF8_TO_TCHAR(MeshSizeCStr);
|
||
|
MeshSizeStr.ParseIntoArray(Components, TEXT(" "), true);
|
||
|
if (Components.Num() >= 3)
|
||
|
{
|
||
|
MeshSize = FVector(FCString::Atof(*Components[0]), FCString::Atof(*Components[1]), FCString::Atof(*Components[2]));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
MeshSizes.Add(MeshName, MeshSize);
|
||
|
|
||
|
FString MeshPath = FPaths::Combine(MujocoMeshAssetPath, MeshFile);
|
||
|
ToBeImportedMeshFiles.Add(MeshName, MeshPath);
|
||
|
|
||
|
FString PostFix = "OBJ";
|
||
|
if (MeshFile.EndsWith(".stl"))
|
||
|
{
|
||
|
PostFix = "STL";
|
||
|
}
|
||
|
|
||
|
if (!ImportSettings->MeshSubdir.IsEmpty())
|
||
|
{
|
||
|
FString MeshAssetPath = FPaths::Combine(PackagePath, PackageName, ImportSettings->MeshSubdir, PostFix, FileName);
|
||
|
MeshNameToPath.Add(MeshName, MeshAssetPath);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
FString MeshAssetPath = FPaths::Combine(PackagePath, PackageName, PostFix, FileName);
|
||
|
MeshNameToPath.Add(MeshName, MeshAssetPath);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const TObjectPtr<UMjocoImportSettings>& Settings = ImportSettings;
|
||
|
|
||
|
int32 GeomCounter = 0;
|
||
|
int32 JointCounter = 0;
|
||
|
int32 SiteCounter = 0;
|
||
|
int32 TendonCounter = 0;
|
||
|
int32 ActuatorCounter = 0;
|
||
|
int32 EqualityCounter = 0;
|
||
|
|
||
|
// Recursive lambda to process body elements.
|
||
|
std::function<USCS_Node*(tinyxml2::XMLElement*, USCS_Node*)> ProcessBody = [&](tinyxml2::XMLElement* BodyElem, USCS_Node* ParentNode) -> USCS_Node* {
|
||
|
const char* BodyNameCStr = BodyElem->Attribute("name");
|
||
|
FString BodyName = BodyNameCStr ? UTF8_TO_TCHAR(BodyNameCStr) : TEXT("");
|
||
|
|
||
|
USCS_Node* BodyNode = SCS->CreateNode(UMujocoBodyComponent::StaticClass(), FName(*FString::Printf(TEXT("Body_%s"), *BodyName)));
|
||
|
UMujocoBodyComponent* BodyComp = Cast<UMujocoBodyComponent>(BodyNode->ComponentTemplate);
|
||
|
|
||
|
// Set this body component's transform using its "pos" and "quat" attributes.
|
||
|
SetSceneComponentTransformFromXml(BodyElem, BodyComp);
|
||
|
// Apply other body-specific attributes (e.g. "mocap", "gravcomp").
|
||
|
ApplyAttributes(UMujocoBodyComponent::StaticClass(), BodyComp, BodyElem);
|
||
|
|
||
|
for (tinyxml2::XMLElement* Child = BodyElem->FirstChildElement(); Child; Child = Child->NextSiblingElement())
|
||
|
{
|
||
|
if (!strcmp(Child->Name(), "inertial"))
|
||
|
{
|
||
|
// Process inertial sub-element into the BodyComp's inertial data.
|
||
|
if (auto* OptionalInertial = static_cast<TOptional<FMujocoInertial>*>(UMujocoBodyComponent::StaticClass()->FindPropertyByName(TEXT("Inertial"))->ContainerPtrToValuePtr<void>(BodyComp)))
|
||
|
{
|
||
|
FMujocoInertial Inertial;
|
||
|
*OptionalInertial = TOptional<FMujocoInertial>{ Inertial };
|
||
|
|
||
|
ApplyAttributes(FMujocoInertial::StaticStruct(), &OptionalInertial->GetValue(), Child, true);
|
||
|
}
|
||
|
}
|
||
|
else if (!strcmp(Child->Name(), "joint") || !strcmp(Child->Name(), "freejoint"))
|
||
|
{
|
||
|
const char* JointNameCStr = Child->Attribute("name");
|
||
|
FString JointName = UTF8_TO_TCHAR(JointNameCStr);
|
||
|
|
||
|
if (JointName.IsEmpty())
|
||
|
{
|
||
|
JointName = FString::Printf(TEXT("joint_%d"), JointCounter++);
|
||
|
}
|
||
|
|
||
|
if (!strcmp(Child->Name(), "freejoint"))
|
||
|
{
|
||
|
Child->SetAttribute("type", "free");
|
||
|
}
|
||
|
|
||
|
USCS_Node* JointNode = SCS->CreateNode(UMujocoJointComponent::StaticClass(), FName(*FString::Printf(TEXT("%s"), *JointName)));
|
||
|
UMujocoJointComponent* JointComp = Cast<UMujocoJointComponent>(JointNode->ComponentTemplate);
|
||
|
|
||
|
FMujocoJoint* JointCompJoint = static_cast<FMujocoJoint*>(UMujocoJointComponent::StaticClass()->FindPropertyByName(TEXT("Joint"))->ContainerPtrToValuePtr<void>(JointComp));
|
||
|
|
||
|
// Set joint transform.
|
||
|
SetSceneComponentTransformFromXml(Child, JointComp);
|
||
|
ApplyAttributes(UMujocoJointComponent::StaticClass(), JointComp, Child);
|
||
|
ApplyAttributes(FMujocoJoint::StaticStruct(), JointCompJoint, Child, true);
|
||
|
|
||
|
BodyNode->AddChildNode(JointNode);
|
||
|
}
|
||
|
else if (!strcmp(Child->Name(), "site"))
|
||
|
{
|
||
|
const char* SiteNameCStr = Child->Attribute("name");
|
||
|
FString SiteName = UTF8_TO_TCHAR(SiteNameCStr);
|
||
|
|
||
|
if (SiteName.IsEmpty())
|
||
|
{
|
||
|
SiteName = FString::Printf(TEXT("site_%d"), SiteCounter++);
|
||
|
}
|
||
|
|
||
|
USCS_Node* SiteNode = SCS->CreateNode(UMujocoSiteComponent::StaticClass(), FName(*FString::Printf(TEXT("%s"), *SiteName)));
|
||
|
UMujocoSiteComponent* SiteComp = Cast<UMujocoSiteComponent>(SiteNode->ComponentTemplate);
|
||
|
|
||
|
FMujocoSite* SiteCompSite = static_cast<FMujocoSite*>(UMujocoSiteComponent::StaticClass()->FindPropertyByName(TEXT("Site"))->ContainerPtrToValuePtr<void>(SiteComp));
|
||
|
|
||
|
// Set site transform.
|
||
|
SetSceneComponentTransformFromXml(Child, SiteComp);
|
||
|
ApplyAttributes(UMujocoSiteComponent::StaticClass(), SiteComp, Child);
|
||
|
ApplyAttributes(FMujocoSite::StaticStruct(), SiteCompSite, Child, true);
|
||
|
BodyNode->AddChildNode(SiteNode);
|
||
|
}
|
||
|
else if (!strcmp(Child->Name(), "geom"))
|
||
|
{
|
||
|
const char* GeomNameCStr = Child->Attribute("name");
|
||
|
|
||
|
FString GeomName = UTF8_TO_TCHAR(GeomNameCStr);
|
||
|
|
||
|
if (GeomName.IsEmpty())
|
||
|
{
|
||
|
GeomName = FString::Printf(TEXT("geom_%d"), GeomCounter++);
|
||
|
}
|
||
|
|
||
|
USCS_Node* GeomNode = SCS->CreateNode(UMujocoGeomComponent::StaticClass(), FName(*FString::Printf(TEXT("%s"), *GeomName)));
|
||
|
UMujocoGeomComponent* GeomComp = Cast<UMujocoGeomComponent>(GeomNode->ComponentTemplate);
|
||
|
|
||
|
FMujocoGeom* GeomCompGeom = static_cast<FMujocoGeom*>(UMujocoGeomComponent::StaticClass()->FindPropertyByName(TEXT("Geom"))->ContainerPtrToValuePtr<void>(GeomComp));
|
||
|
// Set geom transform.
|
||
|
SetSceneComponentTransformFromXml(Child, GeomComp);
|
||
|
ApplyAttributes(UMujocoGeomComponent::StaticClass(), GeomComp, Child);
|
||
|
ApplyAttributes(FMujocoGeom::StaticStruct(), GeomCompGeom, Child, true);
|
||
|
|
||
|
bool IsVisible = true;
|
||
|
if (GeomComp->Geom.Group.IsSet())
|
||
|
{
|
||
|
switch (GeomComp->Geom.Group.GetValue())
|
||
|
{
|
||
|
case 0:
|
||
|
IsVisible = Settings->VisibleGeomGroups.bGeomGroup0;
|
||
|
break;
|
||
|
case 1:
|
||
|
IsVisible = Settings->VisibleGeomGroups.bGeomGroup1;
|
||
|
break;
|
||
|
case 2:
|
||
|
IsVisible = Settings->VisibleGeomGroups.bGeomGroup2;
|
||
|
break;
|
||
|
case 3:
|
||
|
IsVisible = Settings->VisibleGeomGroups.bGeomGroup3;
|
||
|
break;
|
||
|
case 4:
|
||
|
IsVisible = Settings->VisibleGeomGroups.bGeomGroup4;
|
||
|
break;
|
||
|
case 5:
|
||
|
IsVisible = Settings->VisibleGeomGroups.bGeomGroup5;
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!IsVisible)
|
||
|
{
|
||
|
GeomComp->SetVisibility(false);
|
||
|
}
|
||
|
|
||
|
const char* Material = Child->Attribute("material");
|
||
|
if (Material)
|
||
|
{
|
||
|
FString MaterialStr = UTF8_TO_TCHAR(Material);
|
||
|
auto MaterialPath = ToBeImportedMaterials.Find(MaterialStr);
|
||
|
if (MaterialPath)
|
||
|
{
|
||
|
auto& NodeArray = ImportedMaterials.FindOrAdd(MaterialStr);
|
||
|
NodeArray.Add(GeomNode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
const char* Type = Child->Attribute("type");
|
||
|
const char* MeshName = Child->Attribute("mesh");
|
||
|
|
||
|
FString MeshAssetPath = FPaths::Combine(PackagePath, PackageName, ImportSettings->MeshSubdir);
|
||
|
FVector OneScale = FVector::One();
|
||
|
if (Type)
|
||
|
{
|
||
|
if (strcmp(Type, "mesh") == 0 && MeshName)
|
||
|
{
|
||
|
FString MeshNameStr = UTF8_TO_TCHAR(MeshName);
|
||
|
auto& NodeArray = ImportedMeshFiles.FindOrAdd(MeshNameStr);
|
||
|
NodeArray.Add(GeomNode);
|
||
|
}
|
||
|
// sphere, capsule, ellipsoid, cylinder, box
|
||
|
|
||
|
/*
|
||
|
sphere 1 Radius of the sphere.
|
||
|
capsule 1 or 2 Radius of the capsule; half-length of the cylinder part when not using the fromto specification.
|
||
|
ellipsoid 3 X radius; Y radius; Z radius.
|
||
|
cylinder 1 or 2 Radius of the cylinder; half-length of the cylinder when not using the fromto specification.
|
||
|
box 3 X half-size; Y half-size; Z half-size.
|
||
|
*/
|
||
|
|
||
|
// PackagePath, PackageName, GeomName
|
||
|
else if (strcmp(Type, "box") == 0)
|
||
|
{
|
||
|
FVector Size = FVector::One() * 100.0f;
|
||
|
const char* SizeCStr = Child->Attribute("size");
|
||
|
if (SizeCStr)
|
||
|
{
|
||
|
TArray<FString> Components;
|
||
|
FString SizeStr = UTF8_TO_TCHAR(SizeCStr);
|
||
|
SizeStr.ParseIntoArray(Components, TEXT(" "), true);
|
||
|
if (Components.Num() >= 3)
|
||
|
{
|
||
|
Size *= FVector(FCString::Atof(*Components[0]), FCString::Atof(*Components[1]), FCString::Atof(*Components[2]));
|
||
|
}
|
||
|
|
||
|
if (UStaticMesh* Mesh = MujocoMeshFactory::CreateBoxMesh(Size, MeshAssetPath, GeomName))
|
||
|
{
|
||
|
SetGeomMesh(GeomComp, OneScale, Mesh);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (strcmp(Type, "cylinder") == 0)
|
||
|
{
|
||
|
FVector Size = FVector::One() * 100.0f;
|
||
|
const char* SizeCStr = Child->Attribute("size");
|
||
|
if (SizeCStr)
|
||
|
{
|
||
|
TArray<FString> Components;
|
||
|
FString SizeStr = UTF8_TO_TCHAR(SizeCStr);
|
||
|
SizeStr.ParseIntoArray(Components, TEXT(" "), true);
|
||
|
if (Components.Num() >= 2)
|
||
|
{
|
||
|
Size *= FVector(FCString::Atof(*Components[0]), FCString::Atof(*Components[1]), FCString::Atof(*Components[1]));
|
||
|
}
|
||
|
|
||
|
if (UStaticMesh* Mesh = MujocoMeshFactory::CreateCylinderMesh(Size, MeshAssetPath, GeomName))
|
||
|
{
|
||
|
SetGeomMesh(GeomComp, OneScale, Mesh);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (strcmp(Type, "ellipsoid") == 0)
|
||
|
{
|
||
|
FVector Size = FVector::One() * 100.0f;
|
||
|
const char* SizeCStr = Child->Attribute("size");
|
||
|
if (SizeCStr)
|
||
|
{
|
||
|
TArray<FString> Components;
|
||
|
FString SizeStr = UTF8_TO_TCHAR(SizeCStr);
|
||
|
SizeStr.ParseIntoArray(Components, TEXT(" "), true);
|
||
|
if (Components.Num() >= 3)
|
||
|
{
|
||
|
Size *= FVector(FCString::Atof(*Components[0]), FCString::Atof(*Components[1]), FCString::Atof(*Components[2]));
|
||
|
}
|
||
|
|
||
|
if (UStaticMesh* Mesh = MujocoMeshFactory::CreateEllipsoidMesh(Size, MeshAssetPath, GeomName))
|
||
|
{
|
||
|
SetGeomMesh(GeomComp, OneScale, Mesh);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (strcmp(Type, "capsule") == 0)
|
||
|
{
|
||
|
FVector Size = FVector::One() * 100.0f;
|
||
|
const char* SizeCStr = Child->Attribute("size");
|
||
|
if (SizeCStr)
|
||
|
{
|
||
|
TArray<FString> Components;
|
||
|
FString SizeStr = UTF8_TO_TCHAR(SizeCStr);
|
||
|
SizeStr.ParseIntoArray(Components, TEXT(" "), true);
|
||
|
if (Components.Num() >= 2)
|
||
|
{
|
||
|
Size *= FVector(FCString::Atof(*Components[0]), FCString::Atof(*Components[1]), FCString::Atof(*Components[1]));
|
||
|
}
|
||
|
|
||
|
if (UStaticMesh* Mesh = MujocoMeshFactory::CreateCapsuleMesh(Size, MeshAssetPath, GeomName))
|
||
|
{
|
||
|
SetGeomMesh(GeomComp, OneScale, Mesh);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (strcmp(Type, "sphere") == 0)
|
||
|
{
|
||
|
FVector Size = FVector::One() * 100.0f;
|
||
|
const char* SizeCStr = Child->Attribute("size");
|
||
|
if (SizeCStr)
|
||
|
{
|
||
|
TArray<FString> Components;
|
||
|
FString SizeStr = UTF8_TO_TCHAR(SizeCStr);
|
||
|
SizeStr.ParseIntoArray(Components, TEXT(" "), true);
|
||
|
if (Components.Num() >= 1)
|
||
|
{
|
||
|
Size *= FVector(FCString::Atof(*Components[0]), FCString::Atof(*Components[0]), FCString::Atof(*Components[0]));
|
||
|
}
|
||
|
|
||
|
if (UStaticMesh* Mesh = MujocoMeshFactory::CreateSphereMesh(Size, MeshAssetPath, GeomName))
|
||
|
{
|
||
|
SetGeomMesh(GeomComp, OneScale, Mesh);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Warning, TEXT("Unsupported geom type: %s"), UTF8_TO_TCHAR(Type));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
BodyNode->AddChildNode(GeomNode);
|
||
|
}
|
||
|
else if (!strcmp(Child->Name(), "body"))
|
||
|
{
|
||
|
// Process nested body recursively.
|
||
|
ProcessBody(Child, BodyNode);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (ParentNode)
|
||
|
{
|
||
|
ParentNode->AddChildNode(BodyNode);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
SCS->AddNode(BodyNode);
|
||
|
}
|
||
|
return BodyNode;
|
||
|
};
|
||
|
|
||
|
// Process the "worldbody" element.
|
||
|
if (tinyxml2::XMLElement* RootElem = Doc->RootElement())
|
||
|
{
|
||
|
for (tinyxml2::XMLElement* Child = RootElem->FirstChildElement(); Child; Child = Child->NextSiblingElement())
|
||
|
{
|
||
|
if (!strcmp(Child->Name(), "worldbody"))
|
||
|
{
|
||
|
for (tinyxml2::XMLElement* BodyElem = Child->FirstChildElement("body"); BodyElem; BodyElem = BodyElem->NextSiblingElement("body"))
|
||
|
{
|
||
|
ProcessBody(BodyElem, nullptr);
|
||
|
}
|
||
|
}
|
||
|
else if (!strcmp(Child->Name(), "tendon"))
|
||
|
{
|
||
|
for (tinyxml2::XMLElement* TendonElem = Child->FirstChildElement(); TendonElem; TendonElem = TendonElem->NextSiblingElement())
|
||
|
{
|
||
|
TendonElem->SetAttribute("type", TendonElem->Name());
|
||
|
|
||
|
if (!strcmp(TendonElem->Name(), "fixed"))
|
||
|
{
|
||
|
const char* TendonNameCStr = TendonElem->Attribute("name");
|
||
|
FString TendonName = UTF8_TO_TCHAR(TendonNameCStr);
|
||
|
if (TendonName.IsEmpty())
|
||
|
{
|
||
|
TendonName = FString::Printf(TEXT("tendon_%d"), TendonCounter++);
|
||
|
}
|
||
|
|
||
|
USCS_Node* TendonNode = SCS->CreateNode(UMujocoTendonComponent::StaticClass(), FName(*FString::Printf(TEXT("%s"), *TendonName)));
|
||
|
|
||
|
UMujocoTendonComponent* TendonComp = Cast<UMujocoTendonComponent>(TendonNode->ComponentTemplate);
|
||
|
FMujocoTendon* TendonStruct = static_cast<FMujocoTendon*>(UMujocoTendonComponent::StaticClass()->FindPropertyByName(TEXT("Tendon"))->ContainerPtrToValuePtr<void>(TendonComp));
|
||
|
|
||
|
ApplyAttributes(FMujocoTendon::StaticStruct(), TendonStruct, TendonElem, true);
|
||
|
|
||
|
for (tinyxml2::XMLElement* JointElem = TendonElem->FirstChildElement(); JointElem; JointElem = JointElem->NextSiblingElement())
|
||
|
{
|
||
|
if (!strcmp(JointElem->Name(), "joint"))
|
||
|
{
|
||
|
const char* JointNameCStr = JointElem->Attribute("joint");
|
||
|
FString JointName = UTF8_TO_TCHAR(JointNameCStr);
|
||
|
if (JointName.IsEmpty())
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Joint name missing"));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
FMujocoTendonFixedJoint FixedJoint;
|
||
|
FixedJoint.Joint = *JointName;
|
||
|
|
||
|
const char* CoefCStr = JointElem->Attribute("coef");
|
||
|
if (CoefCStr)
|
||
|
{
|
||
|
FixedJoint.Coef = FCString::Atof(UTF8_TO_TCHAR(CoefCStr));
|
||
|
}
|
||
|
|
||
|
TendonStruct->FixedJoint.Add(FixedJoint);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
SCS->AddNode(TendonNode);
|
||
|
}
|
||
|
else if (!strcmp(TendonElem->Name(), "spatial"))
|
||
|
{
|
||
|
const char* TendonNameCStr = TendonElem->Attribute("name");
|
||
|
FString TendonName = UTF8_TO_TCHAR(TendonNameCStr);
|
||
|
if (TendonName.IsEmpty())
|
||
|
{
|
||
|
TendonName = FString::Printf(TEXT("tendon_%d"), TendonCounter++);
|
||
|
}
|
||
|
|
||
|
USCS_Node* TendonNode = SCS->CreateNode(UMujocoTendonComponent::StaticClass(), FName(*FString::Printf(TEXT("%s"), *TendonName)));
|
||
|
UMujocoTendonComponent* TendonComp = Cast<UMujocoTendonComponent>(TendonNode->ComponentTemplate);
|
||
|
|
||
|
FMujocoTendon* TendonStruct = static_cast<FMujocoTendon*>(UMujocoTendonComponent::StaticClass()->FindPropertyByName(TEXT("Tendon"))->ContainerPtrToValuePtr<void>(TendonComp));
|
||
|
ApplyAttributes(FMujocoTendon::StaticStruct(), TendonStruct, TendonElem, true);
|
||
|
|
||
|
for (tinyxml2::XMLElement* Elem = TendonElem->FirstChildElement(); Elem; Elem = Elem->NextSiblingElement())
|
||
|
{
|
||
|
if (!strcmp(Elem->Name(), "site"))
|
||
|
{
|
||
|
const char* SiteNameCStr = Elem->Attribute("site");
|
||
|
FString SiteName = UTF8_TO_TCHAR(SiteNameCStr);
|
||
|
if (SiteName.IsEmpty())
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Site name missing"));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
FMujocoTendonSpatialSite SpatialSite;
|
||
|
SpatialSite.Site = *SiteName;
|
||
|
|
||
|
TendonStruct->SpatialSite.Add(SpatialSite);
|
||
|
}
|
||
|
else if (!strcmp(Elem->Name(), "geom"))
|
||
|
{
|
||
|
FMujocoTendonSpatialGeom SpatialGeom;
|
||
|
|
||
|
const char* GeomNameCStr = Elem->Attribute("geom");
|
||
|
FString GeomName = UTF8_TO_TCHAR(GeomNameCStr);
|
||
|
if (!GeomName.IsEmpty())
|
||
|
{
|
||
|
SpatialGeom.Geom = *GeomName;
|
||
|
}
|
||
|
|
||
|
const char* SideSiteCStr = Elem->Attribute("sidesite");
|
||
|
FString SideSite = UTF8_TO_TCHAR(SideSiteCStr);
|
||
|
if (!SideSite.IsEmpty())
|
||
|
{
|
||
|
SpatialGeom.SideSite = *SideSite;
|
||
|
}
|
||
|
|
||
|
TendonStruct->SpatialGeom.Add(SpatialGeom);
|
||
|
}
|
||
|
else if (!strcmp(Elem->Name(), "pulley"))
|
||
|
{
|
||
|
const char* PulleyNameCStr = Elem->Attribute("pulley");
|
||
|
FString PulleyName = UTF8_TO_TCHAR(PulleyNameCStr);
|
||
|
if (PulleyName.IsEmpty())
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Pulley name missing"));
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
FMujocoTendonPulley SpatialPulley;
|
||
|
|
||
|
const char* DivisorStr = Elem->Attribute("divisor");
|
||
|
if (DivisorStr)
|
||
|
{
|
||
|
SpatialPulley.Divisor = FCString::Atoi(UTF8_TO_TCHAR(DivisorStr));
|
||
|
}
|
||
|
|
||
|
TendonStruct->Pulley.Add(SpatialPulley);
|
||
|
}
|
||
|
}
|
||
|
SCS->AddNode(TendonNode);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Unsupported tendon type: %s"), UTF8_TO_TCHAR(TendonElem->Name()));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
else if (!strcmp(Child->Name(), "equality"))
|
||
|
{
|
||
|
for (tinyxml2::XMLElement* Elem = Child->FirstChildElement(); Elem; Elem = Elem->NextSiblingElement())
|
||
|
{
|
||
|
const char* EqualityNameCStr = Elem->Attribute("name");
|
||
|
FString EqualityName = UTF8_TO_TCHAR(EqualityNameCStr);
|
||
|
if (EqualityName.IsEmpty())
|
||
|
{
|
||
|
EqualityName = FString::Printf(TEXT("equality_%d"), EqualityCounter++);
|
||
|
}
|
||
|
|
||
|
USCS_Node* EqualityNode = SCS->CreateNode(UMujocoEqualityComponent::StaticClass(), FName(*FString::Printf(TEXT("%s"), *EqualityName)));
|
||
|
UMujocoEqualityComponent* EqualityComp = Cast<UMujocoEqualityComponent>(EqualityNode->ComponentTemplate);
|
||
|
|
||
|
ApplyAttributes(UMujocoEqualityComponent::StaticClass(), EqualityComp, Elem);
|
||
|
|
||
|
FMujocoEquality* Equality = static_cast<FMujocoEquality*>(UMujocoEqualityComponent::StaticClass()->FindPropertyByName(TEXT("Equality"))->ContainerPtrToValuePtr<void>(EqualityComp));
|
||
|
|
||
|
Elem->SetAttribute("type", Elem->Name());
|
||
|
ApplyAttributes(FMujocoEquality::StaticStruct(), Equality, Elem, true);
|
||
|
|
||
|
SCS->AddNode(EqualityNode);
|
||
|
}
|
||
|
}
|
||
|
else if (!strcmp(Child->Name(), "actuator"))
|
||
|
{
|
||
|
for (tinyxml2::XMLElement* Elem = Child->FirstChildElement(); Elem; Elem = Elem->NextSiblingElement())
|
||
|
{
|
||
|
const char* NameCStr = Elem->Attribute("name");
|
||
|
FString Name = UTF8_TO_TCHAR(NameCStr);
|
||
|
if (Name.IsEmpty())
|
||
|
{
|
||
|
Name = FString::Printf(TEXT("actuator_%d"), ActuatorCounter++);
|
||
|
}
|
||
|
|
||
|
USCS_Node* Node = SCS->CreateNode(UMujocoActuatorComponent::StaticClass(), FName(*FString::Printf(TEXT("%s"), *Name)));
|
||
|
UMujocoActuatorComponent* Comp = Cast<UMujocoActuatorComponent>(Node->ComponentTemplate);
|
||
|
|
||
|
ApplyAttributes(UMujocoActuatorComponent::StaticClass(), Comp, Elem);
|
||
|
|
||
|
FMujocoActuatorV2* Data = static_cast<FMujocoActuatorV2*>(UMujocoActuatorComponent::StaticClass()->FindPropertyByName(TEXT("Actuator"))->ContainerPtrToValuePtr<void>(Comp));
|
||
|
|
||
|
Elem->SetAttribute("type", Elem->Name());
|
||
|
ApplyAttributes(FMujocoActuatorV2::StaticStruct(), Data, Elem, true);
|
||
|
|
||
|
SCS->AddNode(Node);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Import meshes.
|
||
|
if (!ToBeImportedMeshFiles.IsEmpty())
|
||
|
{
|
||
|
IAssetTools& AssetTools = FModuleManager::LoadModuleChecked<FAssetToolsModule>("AssetTools").Get();
|
||
|
|
||
|
TObjectPtr<UAutomatedAssetImportData> ImportData = NewObject<UAutomatedAssetImportData>();
|
||
|
UFbxFactory* FbxFactory = NewObject<UFbxFactory>();
|
||
|
FbxFactory->AddToRoot();
|
||
|
|
||
|
UUnrealSTLFactory* STLFactory = NewObject<UUnrealSTLFactory>();
|
||
|
STLFactory->AddToRoot();
|
||
|
|
||
|
float Scale{ 100.0f };
|
||
|
|
||
|
FbxFactory->ImportUI->StaticMeshImportData->ImportUniformScale = Scale;
|
||
|
FbxFactory->ImportUI->StaticMeshImportData->bConvertScene = false;
|
||
|
FbxFactory->ImportUI->StaticMeshImportData->bForceFrontXAxis = true;
|
||
|
FbxFactory->ImportUI->bImportMaterials = false;
|
||
|
|
||
|
STLFactory->ImportConfig.Transform.SetScale3D(FVector(1, 1, 1) * Scale);
|
||
|
STLFactory->ImportConfig.Transform.SetRotation(FRotator(0.0f, 0.0f, 0.0f).Quaternion());
|
||
|
|
||
|
ImportData->bReplaceExisting = true;
|
||
|
ImportData->bSkipReadOnly = true;
|
||
|
|
||
|
TArray<FString> STLFiles;
|
||
|
TArray<FString> FbxFiles;
|
||
|
|
||
|
for (const auto& MeshPair : ToBeImportedMeshFiles)
|
||
|
{
|
||
|
FString MeshName = MeshPair.Key;
|
||
|
FString MeshPath = MeshPair.Value;
|
||
|
|
||
|
FString MeshAssetPath = MeshNameToPath.FindRef(MeshName);
|
||
|
if (MeshAssetPath.IsEmpty())
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
if (MeshPath.EndsWith(".obj"))
|
||
|
{
|
||
|
FbxFiles.Add(MeshPath);
|
||
|
}
|
||
|
else if (MeshPath.EndsWith(".stl"))
|
||
|
{
|
||
|
STLFiles.Add(MeshPath);
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Unsupported mesh format: %s"), *MeshPath);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (FbxFiles.Num() > 0)
|
||
|
{
|
||
|
if (!ImportSettings->MeshSubdir.IsEmpty())
|
||
|
ImportData->DestinationPath = FPaths::Combine(PackagePath, PackageName, ImportSettings->MeshSubdir, "OBJ");
|
||
|
else
|
||
|
ImportData->DestinationPath = FPaths::Combine(PackagePath, PackageName, "OBJ");
|
||
|
ImportData->Factory = FbxFactory;
|
||
|
ImportData->Filenames = FbxFiles;
|
||
|
AssetTools.ImportAssetsAutomated(ImportData);
|
||
|
}
|
||
|
|
||
|
if (STLFiles.Num() > 0)
|
||
|
{
|
||
|
if (!ImportSettings->MeshSubdir.IsEmpty())
|
||
|
ImportData->DestinationPath = FPaths::Combine(PackagePath, PackageName, ImportSettings->MeshSubdir, "STL");
|
||
|
else
|
||
|
ImportData->DestinationPath = FPaths::Combine(PackagePath, PackageName, "STL");
|
||
|
ImportData->Factory = STLFactory;
|
||
|
ImportData->Filenames = STLFiles;
|
||
|
AssetTools.ImportAssetsAutomated(ImportData);
|
||
|
}
|
||
|
|
||
|
FbxFactory->RemoveFromRoot();
|
||
|
STLFactory->RemoveFromRoot();
|
||
|
}
|
||
|
|
||
|
if (!ImportedMeshFiles.IsEmpty())
|
||
|
{
|
||
|
for (const auto& MeshPair : ImportedMeshFiles)
|
||
|
{
|
||
|
FString MeshName = MeshPair.Key;
|
||
|
const TArray<USCS_Node*>& Nodes = MeshPair.Value;
|
||
|
|
||
|
FString MeshAssetPath = MeshNameToPath.FindRef(MeshName);
|
||
|
|
||
|
if (MeshAssetPath.IsEmpty())
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("MeshAssetPath empty: %s"), *MeshName);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
UStaticMesh* Mesh = LoadObject<UStaticMesh>(nullptr, *MeshAssetPath);
|
||
|
if (!Mesh)
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Failed to load mesh: %s"), *MeshAssetPath);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (USCS_Node* Node : Nodes)
|
||
|
{
|
||
|
UMujocoGeomComponent* GeomComp = Cast<UMujocoGeomComponent>(Node->ComponentTemplate);
|
||
|
if (GeomComp)
|
||
|
{
|
||
|
FVector MeshScale = MeshSizes.FindRef(MeshName);
|
||
|
|
||
|
SetGeomMesh(GeomComp, MeshScale, Mesh);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!ImportedMaterials.IsEmpty())
|
||
|
{
|
||
|
for (const auto& MaterialPair : ImportedMaterials)
|
||
|
{
|
||
|
FString MaterialName = MaterialPair.Key;
|
||
|
const TArray<USCS_Node*>& Nodes = MaterialPair.Value;
|
||
|
|
||
|
FString MaterialAssetPath = ToBeImportedMaterials.FindRef(MaterialName);
|
||
|
if (MaterialAssetPath.IsEmpty())
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("MaterialAssetPath empty: %s"), *MaterialName);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
UMaterial* Material = LoadObject<UMaterial>(nullptr, *MaterialAssetPath);
|
||
|
if (!Material)
|
||
|
{
|
||
|
UE_LOG(LogMujoco, Error, TEXT("Failed to load material: %s"), *MaterialAssetPath);
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
for (USCS_Node* Node : Nodes)
|
||
|
{
|
||
|
UMujocoGeomComponent* GeomComp = Cast<UMujocoGeomComponent>(Node->ComponentTemplate);
|
||
|
if (GeomComp)
|
||
|
{
|
||
|
GeomComp->SetMaterial(0, Material);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
FReply OnImportClicked()
|
||
|
{
|
||
|
ImportSettings->SaveConfig();
|
||
|
|
||
|
ClassPropertyEntryBox->SetEnabled(false);
|
||
|
DetailsView->SetEnabled(false);
|
||
|
|
||
|
ImportButton->SetEnabled(false);
|
||
|
CancelButton->SetEnabled(false);
|
||
|
|
||
|
if (!GenerateBluePrint())
|
||
|
{
|
||
|
return FReply::Handled();
|
||
|
}
|
||
|
|
||
|
if (!AddComponents())
|
||
|
{
|
||
|
return FReply::Handled();
|
||
|
}
|
||
|
|
||
|
FAssetRegistryModule::AssetCreated(Blueprint);
|
||
|
FKismetEditorUtilities::CompileBlueprint(Blueprint);
|
||
|
|
||
|
GEditor->GetEditorSubsystem<UImportSubsystem>()->BroadcastAssetPostImport(Factory, Blueprint);
|
||
|
|
||
|
GeneratedPackage->MarkPackageDirty();
|
||
|
|
||
|
RequestDestroyWindow();
|
||
|
|
||
|
return FReply::Handled();
|
||
|
}
|
||
|
|
||
|
FReply OnCancelClicked()
|
||
|
{
|
||
|
RequestDestroyWindow();
|
||
|
return FReply::Handled();
|
||
|
}
|
||
|
|
||
|
static TSharedPtr<SMujocoImportWindow> ShowDialog(FString InFilename, UPackage* InParent, FName InName, EObjectFlags InFlags, UFactory* InFactory)
|
||
|
{
|
||
|
TSharedRef<SMujocoImportWindow> Window = SNew(SMujocoImportWindow, InFilename, InParent, InName, InFlags, InFactory);
|
||
|
TSharedPtr<SWindow> ParentWindow;
|
||
|
if (FModuleManager::Get().IsModuleLoaded("MainFrame"))
|
||
|
{
|
||
|
IMainFrameModule& MainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame");
|
||
|
ParentWindow = MainFrame.GetParentWindow();
|
||
|
}
|
||
|
if (ParentWindow.IsValid())
|
||
|
{
|
||
|
FSlateApplication::Get().AddModalWindow(Window, ParentWindow.ToSharedRef());
|
||
|
}
|
||
|
return Window;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
UMujocoFactory::UMujocoFactory()
|
||
|
{
|
||
|
SupportedClass = NULL;
|
||
|
Formats.Add(TEXT("xml;Mujoco XML files"));
|
||
|
|
||
|
bCreateNew = false;
|
||
|
bText = false;
|
||
|
bEditorImport = true;
|
||
|
}
|
||
|
|
||
|
bool UMujocoFactory::ConfigureProperties()
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
void UMujocoFactory::PostInitProperties()
|
||
|
{
|
||
|
Super::PostInitProperties();
|
||
|
|
||
|
bEditorImport = true;
|
||
|
bText = false;
|
||
|
}
|
||
|
|
||
|
bool UMujocoFactory::FactoryCanImport(const FString& Filename)
|
||
|
{
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
bool UMujocoFactory::CanImportBeCanceled() const
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
IImportSettingsParser* UMujocoFactory::GetImportSettingsParser()
|
||
|
{
|
||
|
return nullptr;
|
||
|
}
|
||
|
|
||
|
TArray<FString> UMujocoFactory::GetFormats() const
|
||
|
{
|
||
|
return Formats;
|
||
|
}
|
||
|
|
||
|
bool UMujocoFactory::DoesSupportClass(UClass* Class)
|
||
|
{
|
||
|
return Class == UObject::StaticClass();
|
||
|
}
|
||
|
|
||
|
UClass* UMujocoFactory::ResolveSupportedClass()
|
||
|
{
|
||
|
return UObject::StaticClass();
|
||
|
}
|
||
|
|
||
|
UObject* UMujocoFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags InFlags, const FString& InFilename, const TCHAR* InParms, FFeedbackContext* InWarn, bool& bOutOperationCanceled)
|
||
|
{
|
||
|
auto Window = SMujocoImportWindow::ShowDialog(InFilename, Cast<UPackage>(InParent), InName, InFlags, this);
|
||
|
|
||
|
if (Window->GetGeneratedAsset())
|
||
|
{
|
||
|
FSoftObjectPath FoundAssetPath = FSoftObjectPath(Window->GetGeneratedAsset()->GetPathName());
|
||
|
FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateLambda([this, FoundAssetPath](float DeltaTime) {
|
||
|
if (const IAssetRegistry* AssetRegistry = IAssetRegistry::Get())
|
||
|
{
|
||
|
const FAssetData AssetData = AssetRegistry->GetAssetByObjectPath(FoundAssetPath);
|
||
|
if (AssetData.IsValid())
|
||
|
{
|
||
|
ObjectTools::DeleteAssets({}, false);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (UObject* AssetObject = FoundAssetPath.ResolveObject())
|
||
|
{
|
||
|
AssetObject->Rename(nullptr, GetTransientPackage(), REN_DontCreateRedirectors);
|
||
|
|
||
|
AssetObject->RemoveFromRoot();
|
||
|
AssetObject->ClearFlags(RF_Public | RF_Standalone);
|
||
|
AssetObject->MarkAsGarbage();
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
return Window->GetGeneratedAsset();
|
||
|
}
|
||
|
|
||
|
#undef LOCTEXT_NAMESPACE
|