vault backup: 2024-10-12 17:19:45

This commit is contained in:
2024-10-12 17:19:46 +08:00
parent ff94ddca61
commit 244c0c52f6
960 changed files with 31348 additions and 10 deletions

View File

@@ -0,0 +1,75 @@
# MaterialControlFlow
- **功能描述:** 标识该UMaterialExpression为一个控制流节点当前在材质蓝图右键菜单中隐藏。
- **使用位置:** UCLASS
- **引擎模块:** Material
- **元数据类型:** bool
- **限制类型:** UMaterialExpression子类
- **常用程度:** ★
标识该UMaterialExpression为一个控制流节点当前在材质蓝图右键菜单中隐藏。
一般是在IfThenElseForLoop这种节点上当前引擎实现还未完善因此该功能禁用。
## 源码例子:
```cpp
UCLASS(collapsecategories, hidecategories=Object, MinimalAPI)
class UMaterialExpressionIf : public UMaterialExpression
{}
UCLASS(collapsecategories, hidecategories = Object, MinimalAPI, meta=(MaterialControlFlow))
class UMaterialExpressionIfThenElse : public UMaterialExpression
{}
```
## 测试效果:
可以找到If节点但是无法调用IfThenElse节点。
![Untitled](Untitled.png)
## 原理:
在遍历所有可用FMaterialExpression的时候如果有MaterialControlFlow则略过。当前引擎下AllowMaterialControlFlow一直未false未实现。
```cpp
// r.MaterialEnableControlFlow is removed and the feature is forced disabled as how control flow should be
// implemented in the material editor is still under discussion.
inline bool AllowMaterialControlFlow()
{
return false;
}
void MaterialExpressionClasses::InitMaterialExpressionClasses()
{
static const auto CVarMaterialEnableNewHLSLGenerator = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MaterialEnableNewHLSLGenerator"));
const bool bEnableControlFlow = AllowMaterialControlFlow();
const bool bEnableNewHLSLGenerator = CVarMaterialEnableNewHLSLGenerator->GetValueOnAnyThread() != 0;
for( TObjectIterator<UClass> It ; It ; ++It )
{
if( Class->IsChildOf(UMaterialExpression::StaticClass()) )
{
// Hide node types related to control flow, unless it's enabled
if (!bEnableControlFlow && Class->HasMetaData("MaterialControlFlow"))
{
continue;
}
if (!bEnableNewHLSLGenerator && Class->HasMetaData("MaterialNewHLSLGenerator"))
{
continue;
}
// Hide node types that are tagged private
if(Class->HasMetaData(TEXT("Private")))
{
continue;
}
AllExpressionClasses.Add(MaterialExpression);
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

View File

@@ -0,0 +1,76 @@
# MaterialNewHLSLGenerator
- **功能描述:** 标识该UMaterialExpression为采用新HLSL生成器的节点当前在材质蓝图右键菜单中隐藏。
- **使用位置:** UCLASS
- **引擎模块:** Material
- **元数据类型:** bool
- **限制类型:** UMaterialExpression子类
- **常用程度:** ★
标识该UMaterialExpression为采用新HLSL生成器的节点当前在材质蓝图右键菜单中隐藏。
## 源码例子:
```cpp
UCLASS(MinimalAPI, meta = (MaterialNewHLSLGenerator))
class UMaterialExpressionLess : public UMaterialExpressionBinaryOp
{
GENERATED_UCLASS_BODY()
#if WITH_EDITOR
virtual UE::HLSLTree::EOperation GetBinaryOp() const override { return UE::HLSLTree::EOperation::Less; }
#endif // WITH_EDITOR
};
```
## 测试效果:
材质蓝图里无法调用Less。
![Untitled](Untitled.png)
## 原理:
在遍历所有可用FMaterialExpression的时候如果有MaterialNewHLSLGenerator则略过。当前引擎下r.MaterialEnableNewHLSLGenerator是只读的且实现未完全。
```cpp
static TAutoConsoleVariable<int32> CVarMaterialEnableNewHLSLGenerator(
TEXT("r.MaterialEnableNewHLSLGenerator"),
0,
TEXT("Enables the new (WIP) material HLSL generator.\n")
TEXT("0 - Don't allow\n")
TEXT("1 - Allow if enabled by material\n")
TEXT("2 - Force all materials to use new generator\n"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
void MaterialExpressionClasses::InitMaterialExpressionClasses()
{
static const auto CVarMaterialEnableNewHLSLGenerator = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MaterialEnableNewHLSLGenerator"));
const bool bEnableControlFlow = AllowMaterialControlFlow();
const bool bEnableNewHLSLGenerator = CVarMaterialEnableNewHLSLGenerator->GetValueOnAnyThread() != 0;
for( TObjectIterator<UClass> It ; It ; ++It )
{
if( Class->IsChildOf(UMaterialExpression::StaticClass()) )
{
// Hide node types related to control flow, unless it's enabled
if (!bEnableControlFlow && Class->HasMetaData("MaterialControlFlow"))
{
continue;
}
if (!bEnableNewHLSLGenerator && Class->HasMetaData("MaterialNewHLSLGenerator"))
{
continue;
}
// Hide node types that are tagged private
if(Class->HasMetaData(TEXT("Private")))
{
continue;
}
AllExpressionClasses.Add(MaterialExpression);
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@@ -0,0 +1,73 @@
# MaterialParameterCollectionFunction
- **功能描述:** 指定该函数是用于操作UMaterialParameterCollection从而支持ParameterName的提取和验证
- **使用位置:** UFUNCTION
- **引擎模块:** Material
- **元数据类型:** bool
- **限制类型:** 带有UMaterialParameterCollection参数的函数
- **常用程度:** ★★★
指定该函数是用于操作UMaterialParameterCollection从而支持ParameterName的提取和验证。
## 测试代码:
```cpp
UCLASS(Blueprintable, BlueprintType)
class INSIDER_API UMyFunction_Material :public UBlueprintFunctionLibrary
{
public:
GENERATED_BODY()
public:
UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject", MaterialParameterCollectionFunction))
static void MySetScalarParameterValue(UObject* WorldContextObject, UMaterialParameterCollection* Collection, FName ParameterName, float ParameterValue);
UFUNCTION(BlueprintCallable, meta=(WorldContext="WorldContextObject"))
static void MySetScalarParameterValue_NoError(UObject* WorldContextObject, UMaterialParameterCollection* Collection, FName ParameterName, float ParameterValue);
};
```
## 蓝图中效果:
引擎自带的UKismetMaterialLibrary::SetScalarParameterValue和我们自己手动编写的MySetScalarParameterValue会触发材质参数集合的蓝图节点验证检测。如果没有指定ParameterName则会产生编译错误。而没有MaterialParameterCollectionFunction标记的MySetScalarParameterValue_NoError函数版本则只是当作一个普通的函数一样一是不会自动提取MPC中的Parameters集合来选择二是没有ParameterName为空的错误验证。
![Untitled](Untitled.png)
## 原理:
```cpp
UBlueprintFunctionNodeSpawner* UBlueprintFunctionNodeSpawner::Create(UFunction const* const Function, UObject* Outer/* = nullptr*/)
{
bool const bIsMaterialParamCollectionFunc = Function->HasMetaData(FBlueprintMetadata::MD_MaterialParameterCollectionFunction);
if (bIsMaterialParamCollectionFunc)
{
NodeClass = UK2Node_CallMaterialParameterCollectionFunction::StaticClass();
}
else
{
NodeClass = UK2Node_CallFunction::StaticClass();
}
}
TSharedPtr<SGraphNode> FNodeFactory::CreateNodeWidget(UEdGraphNode* InNode)
{
if (UK2Node_CallMaterialParameterCollectionFunction* CallFunctionNode = Cast<UK2Node_CallMaterialParameterCollectionFunction>(InNode))
{
return SNew(SGraphNodeCallParameterCollectionFunction, CallFunctionNode);
}
}
TSharedPtr<SGraphPin> SGraphNodeCallParameterCollectionFunction::CreatePinWidget(UEdGraphPin* Pin) const
{
//提取MPC中参数列表等操作
if (Collection)
{
// Populate the ParameterName pin combobox with valid options from the Collection
const bool bVectorParameters = CallFunctionNode->FunctionReference.GetMemberName().ToString().Contains(TEXT("Vector"));
Collection->GetParameterNames(NameList, bVectorParameters);
}
}
```
MaterialParameterCollectionFunction这个标记的会采用UK2Node_CallMaterialParameterCollectionFunction来验证材质函数的正确书写与否。同时UK2Node_CallMaterialParameterCollectionFunction这个蓝图节点也在引擎内部继续被识别以进一步定制化ParameterName的Pin节点。
引擎源码内采用MaterialParameterCollectionFunction标记的函数只有UKismetMaterialLibrary里的函数。

Binary file not shown.

After

Width:  |  Height:  |  Size: 237 KiB

View File

@@ -0,0 +1,147 @@
# OverridingInputProperty
- **功能描述:** 在UMaterialExpression中指定本float要覆盖的其他FExpressionInput 属性。
- **使用位置:** UPROPERTY
- **引擎模块:** Material
- **元数据类型:** bool
- **限制类型:** UMaterialExpression::float
- **关联项:** [RequiredInput](../RequiredInput.md)
- **常用程度:** ★★★
在UMaterialExpression中指定本float要覆盖的其他FExpressionInput 属性。
- 在材质表达式里有些属性当用户没连接的时候我们想提供一个默认值。这个时候这个Float属性的指就可以当作默认值。
- 而当用户连接的时候这个引脚又要变成一个正常的输入因此得有另一个FExpressionInput 属性所以才需要用OverridingInputProperty 指定另一个属性。
- 被OverridingInputProperty 指定的FExpressionInput 属性一般是RequiredInput = "false"因为正常的逻辑是你都提供默认值了那当然用户就不一定非得输入这个值了。当然也可以RequiredInput = "true",提醒用户这个引脚最好要有个输入,但如果真没有,也可以用默认值。
- 输出节点上的很多BaseColor之类的引脚就是又RequiredInput又提供默认值的。
## 测试代码:
在Compile函数中模仿源码给大家示范如何正确处理这种属性来获得值。大家也可以参照源码中别的例子来参考。
```cpp
UCLASS()
class UMyProperty_MyMaterialExpression : public UMaterialExpression
{
GENERATED_UCLASS_BODY()
public:
UPROPERTY()
FExpressionInput MyInput_Default;
UPROPERTY(meta = (RequiredInput = "true"))
FExpressionInput MyInput_Required;
UPROPERTY(meta = (RequiredInput = "false"))
FExpressionInput MyInput_NotRequired;
public:
UPROPERTY(EditAnywhere, Category = OverridingInputProperty, meta = (RequiredInput = "false"))
FExpressionInput MyAlpha;
/** only used if MyAlpha is not hooked up */
UPROPERTY(EditAnywhere, Category = OverridingInputProperty, meta = (OverridingInputProperty = "MyAlpha"))
float ConstAlpha = 1.f;
UPROPERTY(EditAnywhere, Category = OverridingInputProperty, meta = (RequiredInput = "true"))
FExpressionInput MyAlpha2;
/** only used if MyAlpha is not hooked up */
UPROPERTY(EditAnywhere, Category = OverridingInputProperty, meta = (OverridingInputProperty = "MyAlpha2"))
float ConstAlpha2 = 1.f;
public:
//~ Begin UMaterialExpression Interface
#if WITH_EDITOR
virtual int32 Compile(class FMaterialCompiler* Compiler, int32 OutputIndex) override
{
int32 IndexAlpha = MyAlpha.GetTracedInput().Expression ? MyAlpha.Compile(Compiler) : Compiler->Constant(ConstAlpha);
return 0;
}
virtual void GetCaption(TArray<FString>& OutCaptions) const override;
virtual bool GenerateHLSLExpression(FMaterialHLSLGenerator& Generator, UE::HLSLTree::FScope& Scope, int32 OutputIndex, UE::HLSLTree::FExpression const*& OutExpression) const override;
#endif
//~ End UMaterialExpression Interface
};
```
## 测试结果:
可见MyAlpha和MyAlpha2的右边都有个默认值二者又因为RequiredInput的不同而一个灰色一个白色。
其他3个MyInput用来给大家对比。
右侧的材质最终输出表达式上的各个引脚更是有各种情况来让大家参考。
![Untitled](Untitled.png)
## 原理:
其实也是根据Meta的标记来改变引脚的可编辑状态以及输入框。
```cpp
bool UMaterialExpression::CanEditChange(const FProperty* InProperty) const
{
bool bIsEditable = Super::CanEditChange(InProperty);
if (bIsEditable && InProperty != nullptr)
{
// Automatically set property as non-editable if it has OverridingInputProperty metadata
// pointing to an FExpressionInput property which is hooked up as an input.
//
// e.g. in the below snippet, meta=(OverridingInputProperty = "A") indicates that ConstA will
// be overridden by an FExpressionInput property named 'A' if one is connected, and will thereby
// be set as non-editable.
//
// UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Defaults to 'ConstA' if not specified"))
// FExpressionInput A;
//
// UPROPERTY(EditAnywhere, Category = MaterialExpressionAdd, meta = (OverridingInputProperty = "A"))
// float ConstA;
//
static FName OverridingInputPropertyMetaData(TEXT("OverridingInputProperty"));
if (InProperty->HasMetaData(OverridingInputPropertyMetaData))
{
const FString& OverridingPropertyName = InProperty->GetMetaData(OverridingInputPropertyMetaData);
FStructProperty* StructProp = FindFProperty<FStructProperty>(GetClass(), *OverridingPropertyName);
if (ensure(StructProp != nullptr))
{
static FName RequiredInputMetaData(TEXT("RequiredInput"));
// Must be a single FExpressionInput member, not an array, and must be tagged with metadata RequiredInput="false"
if (ensure( StructProp->Struct->GetFName() == NAME_ExpressionInput &&
StructProp->ArrayDim == 1 &&
StructProp->HasMetaData(RequiredInputMetaData) &&
!StructProp->GetBoolMetaData(RequiredInputMetaData)))
{
const FExpressionInput* Input = StructProp->ContainerPtrToValuePtr<FExpressionInput>(this);
if (Input->Expression != nullptr && Input->GetTracedInput().Expression != nullptr)
{
bIsEditable = false;
}
}
}
}
if (bIsEditable)
{
// If the property has EditCondition metadata, then whether it's editable depends on the other EditCondition property
const FString EditConditionPropertyName = InProperty->GetMetaData(TEXT("EditCondition"));
if (!EditConditionPropertyName.IsEmpty())
{
FBoolProperty* EditConditionProperty = FindFProperty<FBoolProperty>(GetClass(), *EditConditionPropertyName);
{
bIsEditable = *EditConditionProperty->ContainerPtrToValuePtr<bool>(this);
}
}
}
}
return bIsEditable;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 282 KiB

View File

@@ -0,0 +1,76 @@
# Private
- **功能描述:** 标识该UMaterialExpression为私有节点当前在材质蓝图右键菜单中隐藏。
- **使用位置:** UCLASS
- **引擎模块:** Material
- **元数据类型:** bool
- **限制类型:** UMaterialExpression子类
- **常用程度:** ★
标识该UMaterialExpression为私有节点当前在材质蓝图右键菜单中隐藏。
在MaterialX模块中使用目前是把里面的Expression暂时先都隐藏起来。
## 源码例子:
```cpp
UCLASS()
class UMyMaterialExpression_NotPrivate : public UMaterialExpression
{}
UCLASS(meta=(Private))
class UMyMaterialExpression_Private : public UMaterialExpression
{}
```
## 测试效果:
材质蓝图里只能调用UMyMaterialExpression_NotPrivate 。
![Untitled](Untitled.png)
## 原理:
在遍历所有可用FMaterialExpression的时候如果有Private则略过。
```cpp
static TAutoConsoleVariable<int32> CVarMaterialEnableNewHLSLGenerator(
TEXT("r.MaterialEnableNewHLSLGenerator"),
0,
TEXT("Enables the new (WIP) material HLSL generator.\n")
TEXT("0 - Don't allow\n")
TEXT("1 - Allow if enabled by material\n")
TEXT("2 - Force all materials to use new generator\n"),
ECVF_RenderThreadSafe | ECVF_ReadOnly);
void MaterialExpressionClasses::InitMaterialExpressionClasses()
{
static const auto CVarMaterialEnableNewHLSLGenerator = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MaterialEnableNewHLSLGenerator"));
const bool bEnableControlFlow = AllowMaterialControlFlow();
const bool bEnableNewHLSLGenerator = CVarMaterialEnableNewHLSLGenerator->GetValueOnAnyThread() != 0;
for( TObjectIterator<UClass> It ; It ; ++It )
{
if( Class->IsChildOf(UMaterialExpression::StaticClass()) )
{
// Hide node types related to control flow, unless it's enabled
if (!bEnableControlFlow && Class->HasMetaData("MaterialControlFlow"))
{
continue;
}
if (!bEnableNewHLSLGenerator && Class->HasMetaData("MaterialNewHLSLGenerator"))
{
continue;
}
// Hide node types that are tagged private
if(Class->HasMetaData(TEXT("Private")))
{
continue;
}
AllExpressionClasses.Add(MaterialExpression);
}
}
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -0,0 +1,83 @@
# RequiredInput
- **功能描述:** 在UMaterialExpression中指定FExpressionInput属性是否要求输入引脚显示白色或灰色。
- **使用位置:** UPROPERTY
- **引擎模块:** Material
- **元数据类型:** bool
- **限制类型:** UMaterialExpression::FExpressionInput
- **关联项:** [OverridingInputProperty](OverridingInputProperty/OverridingInputProperty.md)
在UMaterialExpression中指定FExpressionInput属性是否要求输入引脚显示白色或灰色。
一般都配合OverridingInputProperty使用。
代码和效果参见OverridingInputProperty
## 原理:
```cpp
bool UMaterialExpression::CanEditChange(const FProperty* InProperty) const
{
bool bIsEditable = Super::CanEditChange(InProperty);
if (bIsEditable && InProperty != nullptr)
{
// Automatically set property as non-editable if it has OverridingInputProperty metadata
// pointing to an FExpressionInput property which is hooked up as an input.
//
// e.g. in the below snippet, meta=(OverridingInputProperty = "A") indicates that ConstA will
// be overridden by an FExpressionInput property named 'A' if one is connected, and will thereby
// be set as non-editable.
//
// UPROPERTY(meta = (RequiredInput = "false", ToolTip = "Defaults to 'ConstA' if not specified"))
// FExpressionInput A;
//
// UPROPERTY(EditAnywhere, Category = MaterialExpressionAdd, meta = (OverridingInputProperty = "A"))
// float ConstA;
//
static FName OverridingInputPropertyMetaData(TEXT("OverridingInputProperty"));
if (InProperty->HasMetaData(OverridingInputPropertyMetaData))
{
const FString& OverridingPropertyName = InProperty->GetMetaData(OverridingInputPropertyMetaData);
FStructProperty* StructProp = FindFProperty<FStructProperty>(GetClass(), *OverridingPropertyName);
if (ensure(StructProp != nullptr))
{
static FName RequiredInputMetaData(TEXT("RequiredInput"));
// Must be a single FExpressionInput member, not an array, and must be tagged with metadata RequiredInput="false"
if (ensure( StructProp->Struct->GetFName() == NAME_ExpressionInput &&
StructProp->ArrayDim == 1 &&
StructProp->HasMetaData(RequiredInputMetaData) &&
!StructProp->GetBoolMetaData(RequiredInputMetaData)))
{
const FExpressionInput* Input = StructProp->ContainerPtrToValuePtr<FExpressionInput>(this);
if (Input->Expression != nullptr && Input->GetTracedInput().Expression != nullptr)
{
bIsEditable = false;
}
}
}
}
if (bIsEditable)
{
// If the property has EditCondition metadata, then whether it's editable depends on the other EditCondition property
const FString EditConditionPropertyName = InProperty->GetMetaData(TEXT("EditCondition"));
if (!EditConditionPropertyName.IsEmpty())
{
FBoolProperty* EditConditionProperty = FindFProperty<FBoolProperty>(GetClass(), *EditConditionPropertyName);
{
bIsEditable = *EditConditionProperty->ContainerPtrToValuePtr<bool>(this);
}
}
}
}
return bIsEditable;
}
```

View File

@@ -0,0 +1,102 @@
# ShowAsInputPin
- **功能描述:** 使得UMaterialExpression里的一些基础类型属性变成材质节点上的引脚。
- **使用位置:** UPROPERTY
- **引擎模块:** Material
- **元数据类型:** bool
- **限制类型:** UMaterialExpression里的属性
- **常用程度:** ★★★
使得UMaterialExpression里的一些基础类型属性变成材质节点上的引脚。
- 所谓基础类型指的是floatFVector这种常用的类型。
- 默认这些基础类型属性是不显示为引脚的。
- ShowAsInputPin 值有两个选项Primary可以直接显示出来Advanced需要展开箭头后显示。
## 测试代码:
```cpp
public:
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinTest)
float MyFloat;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinTest, meta = (ShowAsInputPin = "Primary"))
float MyFloat_Primary;
UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = PinTest, meta = (ShowAsInputPin = "Advanced"))
float MyFloat_Advanced;
```
## 测试结果:
可见MyFloat没有显示在节点上。
MyFloat_Primary显示在节点上。
MyFloat_Advanced需要展开箭头后才显示出来。
![Untitled](Untitled.png)
## 原理:
遍历UMaterialExpression里的属性根据含有ShowAsInputPin 的类型来生成引脚。
```cpp
TArray<FProperty*> UMaterialExpression::GetPropertyInputs() const
{
TArray<FProperty*> PropertyInputs;
static FName OverridingInputPropertyMetaData(TEXT("OverridingInputProperty"));
static FName ShowAsInputPinMetaData(TEXT("ShowAsInputPin"));
for (TFieldIterator<FProperty> InputIt(GetClass(), EFieldIteratorFlags::IncludeSuper, EFieldIteratorFlags::ExcludeDeprecated); InputIt; ++InputIt)
{
bool bCreateInput = false;
FProperty* Property = *InputIt;
// Don't create an expression input if the property is already associated with one explicitly declared
bool bOverridingInputProperty = Property->HasMetaData(OverridingInputPropertyMetaData);
// It needs to have the 'EditAnywhere' specifier
const bool bEditAnywhere = Property->HasAnyPropertyFlags(CPF_Edit);
// It needs to be marked with a valid pin category meta data
const FString ShowAsInputPin = Property->GetMetaData(ShowAsInputPinMetaData);
const bool bShowAsInputPin = ShowAsInputPin == TEXT("Primary") || ShowAsInputPin == TEXT("Advanced");
if (!bOverridingInputProperty && bEditAnywhere && bShowAsInputPin)
{
// Check if the property type fits within the allowed widget types
FFieldClass* PropertyClass = Property->GetClass();
if (PropertyClass == FFloatProperty::StaticClass()
|| PropertyClass == FDoubleProperty::StaticClass()
|| PropertyClass == FIntProperty::StaticClass()
|| PropertyClass == FUInt32Property::StaticClass()
|| PropertyClass == FByteProperty::StaticClass()
|| PropertyClass == FBoolProperty::StaticClass()
)
{
bCreateInput = true;
}
else if (PropertyClass == FStructProperty::StaticClass())
{
FStructProperty* StructProperty = CastField<FStructProperty>(Property);
UScriptStruct* Struct = StructProperty->Struct;
if (Struct == TBaseStructure<FLinearColor>::Get()
|| Struct == TBaseStructure<FVector4>::Get()
|| Struct == TVariantStructure<FVector4d>::Get()
|| Struct == TBaseStructure<FVector>::Get()
|| Struct == TVariantStructure<FVector3f>::Get()
|| Struct == TBaseStructure<FVector2D>::Get()
)
{
bCreateInput = true;
}
}
}
if (bCreateInput)
{
PropertyInputs.Add(Property);
}
}
return PropertyInputs;
}
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB