[UE] Plugin (Save StaticMesh .csv)

플러그인 메인 클래스 구현 플러그인 메인 클래스는 모듈을 초기화하고 실행하는 역할을 한다. Unreal Editor에서 "Add New -> C++ Class"를 선택하고 "Object"를 선택하여 플러그인 메인 클래스를 만든다. 이 클래스는 모듈을 초기화하고 실행할 때 호출된다.
목차
Plugins | ||
Example | ||
Example.Build.cs ExampleConsoleCommand.h .cpp ExampleDebuggerCategory.h .cpp ExampleModule.h .cpp StaticMesh_Detail.h .cpp |
||
Source | ||
Utilities | ||
CHelper.h CLog.h .cpp |
||
Global.h CStaticMesh.h .cpp .Build.cs |
||
.uproject | ||
이론 설명
MainFrame과 WinAPI
----------------Viewport------------ MainFrame
---------------------------------------- WinAPI
WinAPI에 종속될 파일
단편화
CPU의 Register
|
Cache Memory (Locality 지역성)
|
RAM
↕
I/O (SSD) 가상 메모리 영역
셋 다 간편화 발생한다.
디스크 단편화

부족한 RAM 메모리를 커버하기 위해 SSD의 메모리를 사용한다. CPU에서 보기에는 같은 주소체계를 가지고 있다.
RAM과 I/O(SSD) 사이에도 단편화가 발생한다.
와일드카드 문자
와일드카드 문자의 예 - Microsoft 지원
맞춤법을 정확히 기억하지 못하면 특정 항목을 찾으려고 쿼리에서 와일드카드 문자를 사용해 엽니다. 와일드카드는 텍스트 값에서 알 수 없는 문자를 사용할 수 있는 특수 문자로, 유사하지만
support.microsoft.com
Plugin 코드 추가
Example.Build.cs
Example.Build.cs
using UnrealBuildTool; public class Example : ModuleRules { public Example(ReadOnlyTargetRules Target) : base(Target) //public은 외부에 공개 { PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs; PublicIncludePaths.Add(ModuleDirectory); PublicDependencyModuleNames.Add("Core"); PrivateDependencyModuleNames.Add("U2212_05"); //본인 모델은 private PrivateDependencyModuleNames.Add("CoreUObject"); PrivateDependencyModuleNames.Add("Engine"); PrivateDependencyModuleNames.Add("Slate"); PrivateDependencyModuleNames.Add("SlateCore"); PrivateDependencyModuleNames.Add("GameplayDebugger"); PrivateDependencyModuleNames.Add("DesktopPlatform"); PrivateDependencyModuleNames.Add("MainFrame"); } }
PrivateDependencyModule 추가
- PrivateDependencyModuleNames.Add("DesktopPlatform");
- PrivateDependencyModuleNames.Add("MainFrame");
StaticMesh_Detail
StaticMesh_Detail.h
#pragma once #include "CoreMinimal.h" #include "IDetailCustomization.h" struct FStaticMesh_DetailData //UObject로부터 상속받은것이 아니기 때문에 직접 직렬화를 해줘야 한다. { TArray<FVector> Positions; TArray<FVector> Normals; TArray<FColor> Colors; TArray<FVector2D> Uvs; TArray<int32> Indices; FVector Extent; //부피 float Radius; //반지름이지만 지름으로 사용. friend FArchive& operator<<(FArchive& InArchive, FStaticMesh_DetailData& InValue) { return InArchive << InValue.Positions << InValue.Normals << InValue.Uvs << InValue.Colors << InValue.Indices << InValue.Extent << InValue.Radius; } }; class EXAMPLE_API FStaticMesh_Detail : public IDetailCustomization //IDetailCustomization로부터 상속받는다. { public: static TSharedRef<IDetailCustomization> MakeInstance(); public: void CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) override; private: FReply OnClicked_Paint(); FReply OnClicked_SaveMesh(); private: TArray<TWeakObjectPtr<UObject>> Objects; };
변화없음
StaticMesh_Detail.cpp
#include "StaticMesh_Detail.h" #include "DetailLayoutBuilder.h" #include "DetailCategoryBuilder.h" #include "DetailWidgetRow.h" #include "Misc/MessageDialog.h" #include "Serialization/BufferArchive.h" //행렬화 관련 헤더. Buffer를 활용하여 내보내는데 필요하다. #include "DesktopPlatformModule.h" #include "Interfaces/IMainFrameModule.h" #include "U2212_05/CStaticMesh.h" TSharedRef<IDetailCustomization> FStaticMesh_Detail::MakeInstance() { return MakeShareable(new FStaticMesh_Detail()); } void FStaticMesh_Detail::CustomizeDetails(IDetailLayoutBuilder& DetailBuilder) { IDetailCategoryBuilder& actor = DetailBuilder.EditCategory("Actor"); //actor.SetCategoryVisibility(false); //actor 카테고리를 숨긴다. TArray<TSharedRef<IPropertyHandle>> handles; actor.GetDefaultProperties(handles); for(TSharedRef<IPropertyHandle> handle : handles) { // GLog->Log(handle->GetProperty()->GetName()); // GLog->Log(handle->GetProperty()->GetPathName()); // GLog->Log(handle->GetProperty()->GetFullName()); if (handle->GetProperty()->GetName().Compare("bCanBeDamaged") == 0) DetailBuilder.HideProperty(handle); //해당 조건이면 handle를 숨겨준다.(=actor에 있는 bCanBeDamaged를 숨겨준다. } IDetailCategoryBuilder& mesh = DetailBuilder.EditCategory("Mesh"); mesh.AddCustomRow(FText::FromString("Mesh")) .NameContent() [ SNew(STextBlock) //SNew는 Slate UI들을 동적할당 한다. .Text(FText::FromString("Color")) ] .ValueContent() [ SNew(SButton) .VAlign(VAlign_Center) .HAlign(HAlign_Fill) .OnClicked(this, &FStaticMesh_Detail::OnClicked_Paint) //.Content() [ SNew(STextBlock) .Text(FText::FromString("Paint")) ] ]; mesh.AddCustomRow(FText::FromString("SaveMesh")) .NameContent() [ SNew(STextBlock) //SNew는 Slate UI들을 동적할당 한다. .Text(FText::FromString("Mesh")) ] .ValueContent() [ SNew(SButton) .VAlign(VAlign_Center) .HAlign(HAlign_Fill) .OnClicked(this, &FStaticMesh_Detail::OnClicked_SaveMesh) //.Content() [ SNew(STextBlock) .Text(FText::FromString("SaveMesh")) ] ]; DetailBuilder.GetObjectsBeingCustomized(Objects);//TWeakObjectPtr의 Objcts를 사용하니 가비지 콜렉터에 의해 관리될 것이다. } FReply FStaticMesh_Detail::OnClicked_Paint() { for(TWeakObjectPtr<UObject> obj : Objects) { ACStaticMesh* mesh = Cast<ACStaticMesh>(obj); //CStaticMesh 캐스팅한다. if (!!mesh) //mesh가 있다면 mesh->Paint(); //mesh에 Paint()를 사용하여 색을 적용. } return FReply::Handled(); } FReply FStaticMesh_Detail::OnClicked_SaveMesh() { if(Objects[0]->GetWorld()->IsPlayInEditor()) //Objecs[0]이 Editor상에서 플레이 되었다면 { FMessageDialog dialog; dialog.Debugf(FText::FromString("In GameMode, it is not working.")); return FReply::Unhandled();//처리하지 못했기 때문에 다음으로 넘겨줘야 한다. 그래서 Unhandled()로 return } TArray<ACStaticMesh*> meshActors; for (TWeakObjectPtr<UObject> obj : Objects) { ACStaticMesh* mesh = Cast<ACStaticMesh>(obj); if (mesh == nullptr) continue; //캐스팅 실패 시에 저장할 자료형이 아니므로 continue로 패스 meshActors.Add(mesh);//캐스팅 성공 시에 mesh 추가 } for(int32 i=0; i< meshActors.Num(); i++) { UActorComponent* actorComponent = meshActors[i]->GetComponentByClass(UStaticMeshComponent::StaticClass());//Component의 최상위인 UActorComponent로 리턴 UStaticMeshComponent* MeshComponent = Cast<UStaticMeshComponent>(actorComponent);//UStaticMeshComponent로 사용하기 위해 actorComponent 캐스팅 if (MeshComponent == nullptr) return FReply::Unhandled();//meshComponent가 없는 경우, Unhandled()가서 처리하라. UStaticMesh* mesh = MeshComponent->GetStaticMesh(); if (mesh == nullptr) return FReply::Unhandled(); //mesh가 없는 경우, , Unhandled()가서 처리하라. FStaticMeshRenderData* renderData = mesh->RenderData.Get(); //renderData는 정점 데이터 if (renderData->LODResources.Num() < 1) return FReply::Unhandled(); FStaticMeshLODResources& lod = renderData->LODResources[0]; FPositionVertexBuffer& vb = lod.VertexBuffers.PositionVertexBuffer;//PositionVertexBuffer 정보 FStaticMeshVertexBuffer& meshVB = lod.VertexBuffers.StaticMeshVertexBuffer;//StatMeshVertexBuffer 정보(Uv, Normal 정보를 포함함) FColorVertexBuffer& colorVB = lod.VertexBuffers.ColorVertexBuffer;//ColorVertexBuffer 정보 FRawStaticIndexBuffer& ib = lod.IndexBuffer;//IndexBuffer 정보 //인덱스는 그리기 순서. 양수여야 하지만 누락시키는 경우를 위해 음수도 넣어줄 수 있다. uint32 vertexCount = vb.GetNumVertices(); int32 triangleCount = ib.GetNumIndices() / 3;//인덱스 3개가 모여야 삼각형이 되어 하나의 단위가 된다. 엔진에서는 주로 triangleCount를 사용한다. GLog->Logf(L"%d Mesh, Vertex Count : %d", i + 1, vertexCount); GLog->Logf(L"%d Mesh, Triangle Count : %d", i + 1, triangleCount); FStaticMesh_DetailData data; //헤더의 구조체 FStaticMesh_DetailData 사용 TArray<FColor> colors; colorVB.GetVertexColors(colors); if(colors.Num() < 1) { for (uint32 index = 0; index < vertexCount; index++) colors.Add(FColor(255, 255, 255, 255)); } FVector minBounds = FVector(+MAX_FLT, +MAX_FLT, +MAX_FLT);//MAX_FLT는 float의 최대값 매크로 FVector maxBounds = FVector(-MAX_FLT, -MAX_FLT, -MAX_FLT); for (uint32 index = 0; index < vertexCount; index++) { FVector position = vb.VertexPosition(index); //최대 최소를 구한다. if (position.X < minBounds.X) minBounds.X = position.X; if (position.Y < minBounds.Y) minBounds.Y = position.Y; if (position.Z < minBounds.Z) minBounds.Z = position.Z; if (position.X > maxBounds.X) maxBounds.X = position.X; if (position.Y > maxBounds.Y) maxBounds.Y = position.Y; if (position.Z > maxBounds.Z) maxBounds.Z = position.Z; //정점 정보를 저장한다. data.Positions.Add(position);//위치 저장. data.Normals.Add(meshVB.VertexTangentZ(index)); //z값은 수직이기 때문에 사용. data.Uvs.Add(meshVB.GetVertexUV(index, 0));//Uv 레이어 번호는 0 data.Colors.Add(colors[index]); } float x = FMath::Abs(minBounds.X - maxBounds.X); float y = FMath::Abs(minBounds.Y - maxBounds.Y); float z = FMath::Abs(minBounds.Z - maxBounds.Z); data.Extent = FVector(x, y, z); //부피 data.Radius = FMath::Max(x, y); data.Radius = FMath::Max(data.Radius, z); //가장 큰 값을 지름으로 설정하였다. GLog->Logf(L"x : %0.1f, y : %0.1f, z : %0.1f", x, y, z); GLog->Logf(L"Radius : %f", data.Radius); TArray<uint32> indices; ib.GetCopy(indices); data.Indices.Insert((int32*)indices.GetData(), triangleCount * 3, 0);//pointer를 사용하여 Insert// triangleCount를 위에서 /3 해주었기 때문에 *3을 해서 돌려준다. IMainFrameModule& mainFrame = FModuleManager::LoadModuleChecked<IMainFrameModule>("MainFrame"); void* handle = mainFrame.GetParentWindow()->GetNativeWindow()->GetOSWindowHandle();//NativeWindow()가 실제 창 객체. GetOSWindowHandle()는 각 운영체제에 맞는 WindowHandle를 리턴해준다.//WindowHandle식별자는 dword x64면 가변형이다. 4byte로 처리가능하면 4byte, 불가능하면 8byte이다. FString title = meshActors[i]->GetName(); FString path; FPaths::GetPath(path); TArray<FString> fileNames; IDesktopPlatform* platform = FDesktopPlatformModule::Get(); platform->SaveFileDialog(handle, title, path, title + ".bin", "Mesh Binary File(*.bin)|*.bin", EFileDialogFlags::None, fileNames);//title은 파일 이름, EFileDialogFlags는 공유속성(None으로 설정하였다) if (fileNames.Num() < 1) continue; // for (const FString& name : fileNames) // GLog->Log(name); FBufferArchive buffer; buffer << data; FFileHelper::SaveArrayToFile(buffer, *fileNames[0]); buffer.FlushCache(); buffer.Empty(); FString text; for (int32 index = 0; index < data.Positions.Num(); index++) { text.Append(data.Positions[index].ToString() + ", "); text.Append(data.Normals[index].ToString() + ", "); text.Append(data.Uvs[index].ToString() + ", "); text.Append(data.Colors[index].ToString() + "\r\n");//\r 줄 끝으로 보내준다. } FString textFileName = FPaths::GetBaseFilename(fileNames[0], false); FString textSaveName = textFileName + ".csv"; //파일확장자 .csv FFileHelper::SaveStringToFile(text, *textSaveName);//확장자.csv를 포함한 파일이름으로 저장 FString str; str.Append(FPaths::GetBaseFilename(fileNames[0])); str.Append(" Saved."); GLog->Log(str); } return FReply::Handled(); }
EFileDialogFlags
- ex. 엑셀은 파일을 연 상태에서 같은 파일을 열어 동시 편집할 수 없다. 반면에 메모장은 동시 편집이 가능하다.
- 이와 같은 기능을 공유속성(Sharable)이라고 한다.
- EFileDialogFlags::None으로 설정하면 공유속성이 없게 만드는 것이다.
※참고사항: FDesktopPlatformModule 클래스

실행화면



'⭐ Unreal Engine > UE Plugin - Basic' 카테고리의 다른 글
[UE] 툴바 만들기(ButtonCommand), 문자(MBCS, Unicode) (0) | 2023.04.18 |
---|---|
[UE] Plugin (Button Command) (0) | 2023.04.14 |
[UE] Plugin (Save StaticMesh & RenderData, LOD) (0) | 2023.04.12 |
[UE] Plugin (StaticMesh Detail) (0) | 2023.04.07 |
[UE] StaticMesh (0) | 2023.04.06 |
댓글
이 글 공유하기
다른 글
-
[UE] 툴바 만들기(ButtonCommand), 문자(MBCS, Unicode)
[UE] 툴바 만들기(ButtonCommand), 문자(MBCS, Unicode)
2023.04.18목차 Plugins Example ButtonCommand.h .cppExample.Build.csExampleConsoleCommand.h .cpp ExampleDebuggerCategory.h .cppExampleModule.h .cppStaticMesh_Detail.h .cppSource Utilities CHelper.hCLog.h .cpp Global.hCStaticMesh.h .cpp.Build.cs .uproject 툴바 만들기 CPC-949 (MBCS) vs. euc-kr (Unicode) 한글은 아스키코드의 줄바뀜 문자(\n) 뒤에 위치해있기 때문에한글을 줄바꿈 문자로 간주한다. CPC-949 (MBCS, Multi-Byte Character Set) … -
[UE] Plugin (Button Command)
[UE] Plugin (Button Command)
2023.04.14Unreal Engine에서 플러그인을 구현하는 것은 복잡할 수 있지만, Unreal Engine이 제공하는 다양한 기능을 사용하면 플러그인을 쉽게 구현할 수 있다. 목차 Plugins Example ButtonCommand.h .cpp 생성Example.Build.csExampleConsoleCommand.h .cpp ExampleDebuggerCategory.h .cppExampleModule.h .cppStaticMesh_Detail.h .cppSource U2212_05 Utilities CHelper.hCLog.h .cpp Global.hCStaticMesh.h .cppU2212_05.Build.cs U2212_05.uproject Plugin (Button Co… -
[UE] Plugin (Save StaticMesh & RenderData, LOD)
[UE] Plugin (Save StaticMesh & RenderData, LOD)
2023.04.12모듈 기능 구현 모듈 클래스에는 플러그인 기능을 구현하는 C++ 코드가 포함되어 있다. 모듈 클래스에서 필요한 함수를 구현하고 기능을 구현한다. Unreal Engine은 많은 기능을 제공하며, 개발자는 이러한 기능을 사용하여 모듈을 구현할 수 있다. 목차 Plugins Example Example.Build.csExampleConsoleCommand.h .cpp ExampleDebuggerCategory.h .cppExampleModule.h .cppStaticMesh_Detail.h .cppSource Utilities CHelper.hCLog.h .cpp Global.hCStaticMesh.h .cpp.Build.cs .uproject StaticMesh_Detail를 활용… -
[UE] Plugin (StaticMesh Detail)
[UE] Plugin (StaticMesh Detail)
2023.04.07플러그인 모듈 생성 플러그인 모듈은 플러그인의 기능을 구현하는 C++ 코드다. 모듈은 모든 Unreal Engine 모듈과 동일한 방식으로 작동하며, 헤더 파일과 소스 파일로 구성된다. Unreal Editor에서 "Add New -> C++ Class"를 선택하고 "Module Class"를 선택하여 모듈 클래스를 만들 수 있다. 목차 Plugins Example Example.Build.csExampleConsoleCommand.h .cpp ExampleDebuggerCategory.h .cppExampleModule.h .cppStaticMesh_Detail.h .cppSource Utilities CHelper.hCLog.h .cpp Global.hCStaticMesh.h .cpp….
댓글을 사용할 수 없습니다.