[{"data":1,"prerenderedAt":2065},["ShallowReactive",2],{"page-Blender-1":3,"page-count-Blender":119},[4,1195,1406,1728,1957],{"id":5,"title":6,"body":7,"date":1180,"description":13,"extension":1181,"meta":1182,"navigation":289,"path":1188,"seo":1189,"stem":1190,"tags":1191,"__hash__":1194},"blogs\u002F_legacy\u002F2026\u002F2026-02-06-send-to-unreal-texture-fix.md","Send to unreal贴图丢失问题",{"type":8,"value":9,"toc":1175},"minimark",[10,14,25,38,42,45,51,54,57,60,63,176,182,185,192,195,795,806,815,818,1168,1171],[11,12,13],"p",{},"在使用Send to Unreal的过程中，发现对于部分模型，会出现贴图导入错误的问题。",[11,15,16,17,24],{},"目前使用的其实是第三方维护的",[18,19,23],"a",{"href":20,"rel":21},"https:\u002F\u002Fgithub.com\u002Fpoly-hammer\u002FBlenderTools",[22],"nofollow","Send to Unreal","，因为官方的已经很久没有更新了。使用的工具的版本信息：",[26,27,28,32,35],"ul",{},[29,30,31],"li",{},"UE5.7",[29,33,34],{},"Send to Unreal 2.6.7",[29,36,37],{},"Blender4.2",[39,40,41],"h2",{"id":41},"贴图丢失",[11,43,44],{},"在进行模型导入后，部分贴图会进入一种错误的状态，当保存时会报错并提示：",[46,47,48],"blockquote",{},[11,49,50],{},"Filename '_11' resolves to 'None' and cannot be used",[11,52,53],{},"且在编辑器中无法看到贴图资产。材质和Mesh表现正常，但是重启后由于这些贴图没有保存，就会变成没有贴图的状态。",[11,55,56],{},"这个问题可以手动解决，只要在材质中打开使用的贴图，然后另存为一下就可以了。但是由于有的模型材质比较多，手动处理会非常麻烦，介于手动处理的步骤只是在使用编辑器功能，所以肯定是能自动处理的。",[39,58,59],{"id":59},"查找未保存的资产",[11,61,62],{},"引擎是有提供接口的，毕竟当点击关闭的时候编辑器会提示需要保存的资产，用这个窗口的文本直接搜索就能找到对应的代码了。",[64,65,70],"pre",{"className":66,"code":67,"language":68,"meta":69,"style":69},"language-cpp shiki shiki-themes github-light-high-contrast github-dark monokai","\u002F\u002F A list of all packages that need to be saved\nTArray\u003CUPackage*> PackagesToSave;\nif (bSaveMapPackages)\n{\n    FEditorFileUtils::GetDirtyWorldPackages(PackagesToSave, ShouldIgnorePackageFunction);\n}\n\u002F\u002F Don't iterate through content packages if we don't plan on saving them\nif (bSaveContentPackages)\n{\n    FEditorFileUtils::GetDirtyContentPackages(PackagesToSave, ShouldIgnorePackageFunction);\n}\n","cpp","",[71,72,73,82,102,111,117,134,140,146,154,159,171],"code",{"__ignoreMap":69},[74,75,78],"span",{"class":76,"line":77},"line",1,[74,79,81],{"class":80},"sZdD5","\u002F\u002F A list of all packages that need to be saved\n",[74,83,85,89,93,96,99],{"class":76,"line":84},2,[74,86,88],{"class":87},"s0idv","TArray",[74,90,92],{"class":91},"sLXdl","\u003C",[74,94,95],{"class":87},"UPackage",[74,97,98],{"class":91},"*>",[74,100,101],{"class":87}," PackagesToSave;\n",[74,103,105,108],{"class":76,"line":104},3,[74,106,107],{"class":91},"if",[74,109,110],{"class":87}," (bSaveMapPackages)\n",[74,112,114],{"class":76,"line":113},4,[74,115,116],{"class":87},"{\n",[74,118,120,124,127,131],{"class":76,"line":119},5,[74,121,123],{"class":122},"sfUV7","    FEditorFileUtils",[74,125,126],{"class":87},"::",[74,128,130],{"class":129},"scAT4","GetDirtyWorldPackages",[74,132,133],{"class":87},"(PackagesToSave, ShouldIgnorePackageFunction);\n",[74,135,137],{"class":76,"line":136},6,[74,138,139],{"class":87},"}\n",[74,141,143],{"class":76,"line":142},7,[74,144,145],{"class":80},"\u002F\u002F Don't iterate through content packages if we don't plan on saving them\n",[74,147,149,151],{"class":76,"line":148},8,[74,150,107],{"class":91},[74,152,153],{"class":87}," (bSaveContentPackages)\n",[74,155,157],{"class":76,"line":156},9,[74,158,116],{"class":87},[74,160,162,164,166,169],{"class":76,"line":161},10,[74,163,123],{"class":122},[74,165,126],{"class":87},[74,167,168],{"class":129},"GetDirtyContentPackages",[74,170,133],{"class":87},[74,172,174],{"class":76,"line":173},11,[74,175,139],{"class":87},[11,177,178,179,181],{},"由于只关注贴图保存问题，所以使用",[71,180,168],{},"就可以了。",[39,183,184],{"id":184},"资产重新保存",[11,186,187,188,191],{},"一开始是想用",[71,189,190],{},"UEditorAssetLibrary::RenameLoadedAsset","来解决的，但是发现无论怎么修改调用方式都会出现错误。",[11,193,194],{},"与ChatGpt沟通了半天，终于找到了解决方案：",[64,196,200],{"className":197,"code":198,"language":199,"meta":69,"style":69},"language-c shiki shiki-themes github-light-high-contrast github-dark monokai","bool FixInvalidTexture(UTexture2D* Texture)\n{\n    if (!Texture)\n    {\n        UE_LOG(LogTemp, Warning, TEXT(\"FixInvalidTexture: Bad texture\"));\n        return false;\n    }\n\n    UPackage* OldPackage = Texture->GetOutermost();\n    FString OldPackageName = OldPackage->GetName();\n    FString OldTexturePath = GetPathNameSafe(Texture);\n\n    \u002F\u002F ---------- 1. 生成合法新路径 ----------\n    FString AssetName = FPackageName::GetShortName(OldPackageName);\n\n    \u002F\u002F 以数字或 _ 开头 → 修复\n    if (!FChar::IsAlpha(AssetName[0]))\n    {\n        AssetName = TEXT(\"T\") + AssetName;\n    }\n\n    FString NewPackagePath =\n        FPackageName::GetLongPackagePath(OldPackageName) \u002F AssetName;\n\n    \u002F\u002F ---------- 2. 创建新 Package ----------\n    UPackage* NewPackage = CreatePackage(*NewPackagePath);\n    if (!NewPackage)\n        return false;\n\n    \u002F\u002F ---------- 3. 重命名 UObject 到新 Package ----------\n    Texture->Rename(\n        *AssetName,\n        NewPackage,\n        REN_DontCreateRedirectors | REN_NonTransactional);\n\n    \u002F\u002F ---------- 4. 标记 & 注册 Asset ----------\n    Texture->MarkPackageDirty();\n    Texture->PostEditChange();\n\n    FAssetRegistryModule::AssetRenamed(Texture, OldTexturePath);\n    FAssetRegistryModule::AssetCreated(Texture);\n\n    \u002F\u002F ---------- 5. 保存 ----------\n    FString Filename =\n        FPackageName::LongPackageNameToFilename(\n            NewPackagePath,\n            FPackageName::GetAssetPackageExtension());\n\n    UE_LOG(LogTemp, Log, TEXT(\"FileName=%s\"), *Filename);\n\n    FSavePackageArgs TsArgs;\n    TsArgs.TopLevelFlags = EObjectFlags::RF_Public | EObjectFlags::RF_Standalone;\n    bool bSaveSuccess = UPackage::SavePackage(\n        NewPackage,\n        Texture,\n        *Filename,\n        TsArgs\n    );\n\n    return bSaveSuccess;\n}\n","c",[71,201,202,224,228,242,247,268,280,285,291,313,328,341,346,352,366,371,377,404,409,434,439,444,453,467,472,478,500,512,521,526,532,544,553,559,571,576,582,592,602,607,616,624,629,635,643,651,657,666,671,702,707,713,729,745,750,756,764,770,776,781,790],{"__ignoreMap":69},[74,203,204,208,211,214,217,221],{"class":76,"line":77},[74,205,207],{"class":206},"sGZGq","bool",[74,209,210],{"class":129}," FixInvalidTexture",[74,212,213],{"class":87},"(UTexture2D",[74,215,216],{"class":91},"*",[74,218,220],{"class":219},"sbfHu"," Texture",[74,222,223],{"class":87},")\n",[74,225,226],{"class":76,"line":84},[74,227,116],{"class":87},[74,229,230,233,236,239],{"class":76,"line":104},[74,231,232],{"class":91},"    if",[74,234,235],{"class":87}," (",[74,237,238],{"class":91},"!",[74,240,241],{"class":87},"Texture)\n",[74,243,244],{"class":76,"line":113},[74,245,246],{"class":87},"    {\n",[74,248,249,252,255,258,261,265],{"class":76,"line":119},[74,250,251],{"class":129},"        UE_LOG",[74,253,254],{"class":87},"(LogTemp, Warning, ",[74,256,257],{"class":129},"TEXT",[74,259,260],{"class":87},"(",[74,262,264],{"class":263},"sZnmU","\"FixInvalidTexture: Bad texture\"",[74,266,267],{"class":87},"));\n",[74,269,270,273,277],{"class":76,"line":136},[74,271,272],{"class":91},"        return",[74,274,276],{"class":275},"stP0Q"," false",[74,278,279],{"class":87},";\n",[74,281,282],{"class":76,"line":142},[74,283,284],{"class":87},"    }\n",[74,286,287],{"class":76,"line":148},[74,288,290],{"emptyLinePlaceholder":289},true,"\n",[74,292,293,296,298,301,304,307,310],{"class":76,"line":156},[74,294,295],{"class":87},"    UPackage",[74,297,216],{"class":91},[74,299,300],{"class":87}," OldPackage ",[74,302,303],{"class":91},"=",[74,305,306],{"class":87}," Texture->",[74,308,309],{"class":129},"GetOutermost",[74,311,312],{"class":87},"();\n",[74,314,315,318,320,323,326],{"class":76,"line":161},[74,316,317],{"class":87},"    FString OldPackageName ",[74,319,303],{"class":91},[74,321,322],{"class":87}," OldPackage->",[74,324,325],{"class":129},"GetName",[74,327,312],{"class":87},[74,329,330,333,335,338],{"class":76,"line":173},[74,331,332],{"class":87},"    FString OldTexturePath ",[74,334,303],{"class":91},[74,336,337],{"class":129}," GetPathNameSafe",[74,339,340],{"class":87},"(Texture);\n",[74,342,344],{"class":76,"line":343},12,[74,345,290],{"emptyLinePlaceholder":289},[74,347,349],{"class":76,"line":348},13,[74,350,351],{"class":80},"    \u002F\u002F ---------- 1. 生成合法新路径 ----------\n",[74,353,355,358,360,363],{"class":76,"line":354},14,[74,356,357],{"class":87},"    FString AssetName ",[74,359,303],{"class":91},[74,361,362],{"class":129}," FPackageName::GetShortName",[74,364,365],{"class":87},"(OldPackageName);\n",[74,367,369],{"class":76,"line":368},15,[74,370,290],{"emptyLinePlaceholder":289},[74,372,374],{"class":76,"line":373},16,[74,375,376],{"class":80},"    \u002F\u002F 以数字或 _ 开头 → 修复\n",[74,378,380,382,384,386,389,391,395,398,401],{"class":76,"line":379},17,[74,381,232],{"class":91},[74,383,235],{"class":87},[74,385,238],{"class":91},[74,387,388],{"class":129},"FChar::IsAlpha",[74,390,260],{"class":87},[74,392,394],{"class":393},"sQA8F","AssetName",[74,396,397],{"class":87},"[",[74,399,400],{"class":275},"0",[74,402,403],{"class":87},"]))\n",[74,405,407],{"class":76,"line":406},18,[74,408,246],{"class":87},[74,410,412,415,417,420,422,425,428,431],{"class":76,"line":411},19,[74,413,414],{"class":87},"        AssetName ",[74,416,303],{"class":91},[74,418,419],{"class":129}," TEXT",[74,421,260],{"class":87},[74,423,424],{"class":263},"\"T\"",[74,426,427],{"class":87},") ",[74,429,430],{"class":91},"+",[74,432,433],{"class":87}," AssetName;\n",[74,435,437],{"class":76,"line":436},20,[74,438,284],{"class":87},[74,440,442],{"class":76,"line":441},21,[74,443,290],{"emptyLinePlaceholder":289},[74,445,447,450],{"class":76,"line":446},22,[74,448,449],{"class":87},"    FString NewPackagePath ",[74,451,452],{"class":91},"=\n",[74,454,456,459,462,465],{"class":76,"line":455},23,[74,457,458],{"class":129},"        FPackageName::GetLongPackagePath",[74,460,461],{"class":87},"(OldPackageName) ",[74,463,464],{"class":91},"\u002F",[74,466,433],{"class":87},[74,468,470],{"class":76,"line":469},24,[74,471,290],{"emptyLinePlaceholder":289},[74,473,475],{"class":76,"line":474},25,[74,476,477],{"class":80},"    \u002F\u002F ---------- 2. 创建新 Package ----------\n",[74,479,481,483,485,488,490,493,495,497],{"class":76,"line":480},26,[74,482,295],{"class":87},[74,484,216],{"class":91},[74,486,487],{"class":87}," NewPackage ",[74,489,303],{"class":91},[74,491,492],{"class":129}," CreatePackage",[74,494,260],{"class":87},[74,496,216],{"class":91},[74,498,499],{"class":87},"NewPackagePath);\n",[74,501,503,505,507,509],{"class":76,"line":502},27,[74,504,232],{"class":91},[74,506,235],{"class":87},[74,508,238],{"class":91},[74,510,511],{"class":87},"NewPackage)\n",[74,513,515,517,519],{"class":76,"line":514},28,[74,516,272],{"class":91},[74,518,276],{"class":275},[74,520,279],{"class":87},[74,522,524],{"class":76,"line":523},29,[74,525,290],{"emptyLinePlaceholder":289},[74,527,529],{"class":76,"line":528},30,[74,530,531],{"class":80},"    \u002F\u002F ---------- 3. 重命名 UObject 到新 Package ----------\n",[74,533,535,538,541],{"class":76,"line":534},31,[74,536,537],{"class":87},"    Texture->",[74,539,540],{"class":129},"Rename",[74,542,543],{"class":87},"(\n",[74,545,547,550],{"class":76,"line":546},32,[74,548,549],{"class":91},"        *",[74,551,552],{"class":87},"AssetName,\n",[74,554,556],{"class":76,"line":555},33,[74,557,558],{"class":87},"        NewPackage,\n",[74,560,562,565,568],{"class":76,"line":561},34,[74,563,564],{"class":87},"        REN_DontCreateRedirectors ",[74,566,567],{"class":91},"|",[74,569,570],{"class":87}," REN_NonTransactional);\n",[74,572,574],{"class":76,"line":573},35,[74,575,290],{"emptyLinePlaceholder":289},[74,577,579],{"class":76,"line":578},36,[74,580,581],{"class":80},"    \u002F\u002F ---------- 4. 标记 & 注册 Asset ----------\n",[74,583,585,587,590],{"class":76,"line":584},37,[74,586,537],{"class":87},[74,588,589],{"class":129},"MarkPackageDirty",[74,591,312],{"class":87},[74,593,595,597,600],{"class":76,"line":594},38,[74,596,537],{"class":87},[74,598,599],{"class":129},"PostEditChange",[74,601,312],{"class":87},[74,603,605],{"class":76,"line":604},39,[74,606,290],{"emptyLinePlaceholder":289},[74,608,610,613],{"class":76,"line":609},40,[74,611,612],{"class":129},"    FAssetRegistryModule::AssetRenamed",[74,614,615],{"class":87},"(Texture, OldTexturePath);\n",[74,617,619,622],{"class":76,"line":618},41,[74,620,621],{"class":129},"    FAssetRegistryModule::AssetCreated",[74,623,340],{"class":87},[74,625,627],{"class":76,"line":626},42,[74,628,290],{"emptyLinePlaceholder":289},[74,630,632],{"class":76,"line":631},43,[74,633,634],{"class":80},"    \u002F\u002F ---------- 5. 保存 ----------\n",[74,636,638,641],{"class":76,"line":637},44,[74,639,640],{"class":87},"    FString Filename ",[74,642,452],{"class":91},[74,644,646,649],{"class":76,"line":645},45,[74,647,648],{"class":129},"        FPackageName::LongPackageNameToFilename",[74,650,543],{"class":87},[74,652,654],{"class":76,"line":653},46,[74,655,656],{"class":87},"            NewPackagePath,\n",[74,658,660,663],{"class":76,"line":659},47,[74,661,662],{"class":129},"            FPackageName::GetAssetPackageExtension",[74,664,665],{"class":87},"());\n",[74,667,669],{"class":76,"line":668},48,[74,670,290],{"emptyLinePlaceholder":289},[74,672,674,677,680,682,684,687,691,694,697,699],{"class":76,"line":673},49,[74,675,676],{"class":129},"    UE_LOG",[74,678,679],{"class":87},"(LogTemp, Log, ",[74,681,257],{"class":129},[74,683,260],{"class":87},[74,685,686],{"class":263},"\"FileName=",[74,688,690],{"class":689},"sAO_F","%s",[74,692,693],{"class":263},"\"",[74,695,696],{"class":87},"), ",[74,698,216],{"class":91},[74,700,701],{"class":87},"Filename);\n",[74,703,705],{"class":76,"line":704},50,[74,706,290],{"emptyLinePlaceholder":289},[74,708,710],{"class":76,"line":709},51,[74,711,712],{"class":87},"    FSavePackageArgs TsArgs;\n",[74,714,716,719,721,724,726],{"class":76,"line":715},52,[74,717,718],{"class":87},"    TsArgs.TopLevelFlags ",[74,720,303],{"class":91},[74,722,723],{"class":87}," EObjectFlags::RF_Public ",[74,725,567],{"class":91},[74,727,728],{"class":87}," EObjectFlags::RF_Standalone;\n",[74,730,732,735,738,740,743],{"class":76,"line":731},53,[74,733,734],{"class":206},"    bool",[74,736,737],{"class":87}," bSaveSuccess ",[74,739,303],{"class":91},[74,741,742],{"class":129}," UPackage::SavePackage",[74,744,543],{"class":87},[74,746,748],{"class":76,"line":747},54,[74,749,558],{"class":87},[74,751,753],{"class":76,"line":752},55,[74,754,755],{"class":87},"        Texture,\n",[74,757,759,761],{"class":76,"line":758},56,[74,760,549],{"class":91},[74,762,763],{"class":87},"Filename,\n",[74,765,767],{"class":76,"line":766},57,[74,768,769],{"class":87},"        TsArgs\n",[74,771,773],{"class":76,"line":772},58,[74,774,775],{"class":87},"    );\n",[74,777,779],{"class":76,"line":778},59,[74,780,290],{"emptyLinePlaceholder":289},[74,782,784,787],{"class":76,"line":783},60,[74,785,786],{"class":91},"    return",[74,788,789],{"class":87}," bSaveSuccess;\n",[74,791,793],{"class":76,"line":792},61,[74,794,139],{"class":87},[11,796,797,798,801,802,805],{},"这里传入的是贴图，执行成功时会自动将",[71,799,800],{},"_","开头的贴图名称添加",[71,803,804],{},"T","前缀进行保存。",[46,807,808],{},[11,809,810,811,814],{},"如果要修复的贴图不是这个规则的，可以在",[71,812,813],{},"以数字或 _ 开头 → 修复","处修改名称逻辑",[11,816,817],{},"结合上面的寻找未保存资产的接口使用即可：",[64,819,821],{"className":66,"code":820,"language":68,"meta":69,"style":69},"TArray\u003CUPackage*> PackagesToSave;\n\u002F\u002FFEditorFileUtils::GetDirtyWorldPackages(PackagesToSave);\nFEditorFileUtils::GetDirtyContentPackages(PackagesToSave);\nUEditorLoadingAndSavingUtils::SavePackages(PackagesToSave, false);\n\n\u002F\u002F 理论上这里其实不需要两层遍历，不过为了日志清晰，还是按照Package为单位来处理\nfor (UPackage* IterPkg : PackagesToSave)\n{\n    if (!IterPkg)\n    {\n        continue;\n    }\n\n    UE_LOG(LogTemp, Log, TEXT(\"UCommonAssetEditFuncLib::SaveBadTextures: Inspecting Package=(%p)%s\"), IterPkg, *GetFullNameSafe(IterPkg));\n    \u002F\u002F 获取包内主资源（贴图）\n    TArray\u003CUObject*> PkgObjects;\n    TArray\u003CUPackage*> PackagesTmp = { IterPkg };\n    UPackageTools::GetObjectsInPackages(&PackagesTmp, PkgObjects);\n    for (UObject* IterObj : PkgObjects)\n    {\n        \u002F\u002F 仅处理贴图\n        UTexture2D* Texture = Cast\u003CUTexture2D>(IterObj);\n        if (!Texture)\n        {\n            UE_LOG(LogTemp, Log, TEXT(\"UCommonAssetEditFuncLib::SaveBadTextures: Object no texture=(%p)%s\"), IterObj, *GetFullNameSafe(IterObj));\n            continue;\n        }\n        UE_LOG(LogTemp, Log, TEXT(\"UCommonAssetEditFuncLib::SaveBadTextures: Object=(%p)%s\"), IterObj, *GetFullNameSafe(IterObj));\n        FixInvalidTexture(Texture);\n    }\n}\n",[71,822,823,835,840,852,871,875,880,894,898,909,913,920,924,928,962,967,982,1000,1018,1032,1036,1041,1064,1075,1080,1112,1119,1124,1153,1160,1164],{"__ignoreMap":69},[74,824,825,827,829,831,833],{"class":76,"line":77},[74,826,88],{"class":87},[74,828,92],{"class":91},[74,830,95],{"class":87},[74,832,98],{"class":91},[74,834,101],{"class":87},[74,836,837],{"class":76,"line":84},[74,838,839],{"class":80},"\u002F\u002FFEditorFileUtils::GetDirtyWorldPackages(PackagesToSave);\n",[74,841,842,845,847,849],{"class":76,"line":104},[74,843,844],{"class":122},"FEditorFileUtils",[74,846,126],{"class":87},[74,848,168],{"class":129},[74,850,851],{"class":87},"(PackagesToSave);\n",[74,853,854,857,859,862,865,868],{"class":76,"line":113},[74,855,856],{"class":122},"UEditorLoadingAndSavingUtils",[74,858,126],{"class":87},[74,860,861],{"class":129},"SavePackages",[74,863,864],{"class":87},"(PackagesToSave, ",[74,866,867],{"class":275},"false",[74,869,870],{"class":87},");\n",[74,872,873],{"class":76,"line":119},[74,874,290],{"emptyLinePlaceholder":289},[74,876,877],{"class":76,"line":136},[74,878,879],{"class":80},"\u002F\u002F 理论上这里其实不需要两层遍历，不过为了日志清晰，还是按照Package为单位来处理\n",[74,881,882,885,887,889,891],{"class":76,"line":142},[74,883,884],{"class":91},"for",[74,886,235],{"class":87},[74,888,95],{"class":122},[74,890,216],{"class":91},[74,892,893],{"class":87}," IterPkg : PackagesToSave)\n",[74,895,896],{"class":76,"line":148},[74,897,116],{"class":87},[74,899,900,902,904,906],{"class":76,"line":156},[74,901,232],{"class":91},[74,903,235],{"class":87},[74,905,238],{"class":91},[74,907,908],{"class":87},"IterPkg)\n",[74,910,911],{"class":76,"line":161},[74,912,246],{"class":87},[74,914,915,918],{"class":76,"line":173},[74,916,917],{"class":91},"        continue",[74,919,279],{"class":87},[74,921,922],{"class":76,"line":343},[74,923,284],{"class":87},[74,925,926],{"class":76,"line":348},[74,927,290],{"emptyLinePlaceholder":289},[74,929,930,932,934,936,938,941,944,947,949,951,954,956,959],{"class":76,"line":354},[74,931,676],{"class":129},[74,933,679],{"class":87},[74,935,257],{"class":129},[74,937,260],{"class":87},[74,939,940],{"class":263},"\"UCommonAssetEditFuncLib::SaveBadTextures: Inspecting Package=(",[74,942,943],{"class":689},"%p",[74,945,946],{"class":263},")",[74,948,690],{"class":689},[74,950,693],{"class":263},[74,952,953],{"class":87},"), IterPkg, ",[74,955,216],{"class":91},[74,957,958],{"class":129},"GetFullNameSafe",[74,960,961],{"class":87},"(IterPkg));\n",[74,963,964],{"class":76,"line":368},[74,965,966],{"class":80},"    \u002F\u002F 获取包内主资源（贴图）\n",[74,968,969,972,974,977,979],{"class":76,"line":373},[74,970,971],{"class":122},"    TArray",[74,973,92],{"class":87},[74,975,976],{"class":122},"UObject",[74,978,216],{"class":91},[74,980,981],{"class":87},"> PkgObjects;\n",[74,983,984,986,988,990,992,995,997],{"class":76,"line":379},[74,985,971],{"class":122},[74,987,92],{"class":87},[74,989,95],{"class":122},[74,991,216],{"class":91},[74,993,994],{"class":87},"> PackagesTmp ",[74,996,303],{"class":91},[74,998,999],{"class":87}," { IterPkg };\n",[74,1001,1002,1005,1007,1010,1012,1015],{"class":76,"line":406},[74,1003,1004],{"class":122},"    UPackageTools",[74,1006,126],{"class":87},[74,1008,1009],{"class":129},"GetObjectsInPackages",[74,1011,260],{"class":87},[74,1013,1014],{"class":91},"&",[74,1016,1017],{"class":87},"PackagesTmp, PkgObjects);\n",[74,1019,1020,1023,1025,1027,1029],{"class":76,"line":411},[74,1021,1022],{"class":91},"    for",[74,1024,235],{"class":87},[74,1026,976],{"class":122},[74,1028,216],{"class":91},[74,1030,1031],{"class":87}," IterObj : PkgObjects)\n",[74,1033,1034],{"class":76,"line":436},[74,1035,246],{"class":87},[74,1037,1038],{"class":76,"line":441},[74,1039,1040],{"class":80},"        \u002F\u002F 仅处理贴图\n",[74,1042,1043,1046,1048,1051,1053,1056,1058,1061],{"class":76,"line":446},[74,1044,1045],{"class":122},"        UTexture2D",[74,1047,216],{"class":91},[74,1049,1050],{"class":87}," Texture ",[74,1052,303],{"class":91},[74,1054,1055],{"class":129}," Cast",[74,1057,92],{"class":87},[74,1059,1060],{"class":122},"UTexture2D",[74,1062,1063],{"class":87},">(IterObj);\n",[74,1065,1066,1069,1071,1073],{"class":76,"line":455},[74,1067,1068],{"class":91},"        if",[74,1070,235],{"class":87},[74,1072,238],{"class":91},[74,1074,241],{"class":87},[74,1076,1077],{"class":76,"line":469},[74,1078,1079],{"class":87},"        {\n",[74,1081,1082,1085,1087,1089,1091,1094,1096,1098,1100,1102,1105,1107,1109],{"class":76,"line":474},[74,1083,1084],{"class":129},"            UE_LOG",[74,1086,679],{"class":87},[74,1088,257],{"class":129},[74,1090,260],{"class":87},[74,1092,1093],{"class":263},"\"UCommonAssetEditFuncLib::SaveBadTextures: Object no texture=(",[74,1095,943],{"class":689},[74,1097,946],{"class":263},[74,1099,690],{"class":689},[74,1101,693],{"class":263},[74,1103,1104],{"class":87},"), IterObj, ",[74,1106,216],{"class":91},[74,1108,958],{"class":129},[74,1110,1111],{"class":87},"(IterObj));\n",[74,1113,1114,1117],{"class":76,"line":480},[74,1115,1116],{"class":91},"            continue",[74,1118,279],{"class":87},[74,1120,1121],{"class":76,"line":502},[74,1122,1123],{"class":87},"        }\n",[74,1125,1126,1128,1130,1132,1134,1137,1139,1141,1143,1145,1147,1149,1151],{"class":76,"line":514},[74,1127,251],{"class":129},[74,1129,679],{"class":87},[74,1131,257],{"class":129},[74,1133,260],{"class":87},[74,1135,1136],{"class":263},"\"UCommonAssetEditFuncLib::SaveBadTextures: Object=(",[74,1138,943],{"class":689},[74,1140,946],{"class":263},[74,1142,690],{"class":689},[74,1144,693],{"class":263},[74,1146,1104],{"class":87},[74,1148,216],{"class":91},[74,1150,958],{"class":129},[74,1152,1111],{"class":87},[74,1154,1155,1158],{"class":76,"line":523},[74,1156,1157],{"class":129},"        FixInvalidTexture",[74,1159,340],{"class":87},[74,1161,1162],{"class":76,"line":528},[74,1163,284],{"class":87},[74,1165,1166],{"class":76,"line":534},[74,1167,139],{"class":87},[11,1169,1170],{},"这样处理其实还是有问题的，需要在编辑器中选中材质所在文件夹然后点击右键全部重新保存一次。目测是相关的引用没有正确的更新，但是一时半会也找不到自动关联到这些材质或者触发保存的方式，所幸只是手动点击一下鼠标，就不继续投入时间了。",[1172,1173,1174],"style",{},"html pre.shiki code .sZdD5, html code.shiki .sZdD5{--shiki-default:#66707B;--shiki-dark:#6A737D;--shiki-sepia:#88846F}html pre.shiki code .s0idv, html code.shiki .s0idv{--shiki-default:#0E1116;--shiki-dark:#E1E4E8;--shiki-sepia:#F8F8F2}html pre.shiki code .sLXdl, html code.shiki .sLXdl{--shiki-default:#A0111F;--shiki-dark:#F97583;--shiki-sepia:#F92672}html pre.shiki code .sfUV7, html code.shiki .sfUV7{--shiki-default:#702C00;--shiki-default-text-decoration:inherit;--shiki-dark:#B392F0;--shiki-dark-text-decoration:inherit;--shiki-sepia:#A6E22E;--shiki-sepia-text-decoration:underline}html pre.shiki code .scAT4, html code.shiki .scAT4{--shiki-default:#622CBC;--shiki-dark:#B392F0;--shiki-sepia:#A6E22E}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html .sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html.sepia .shiki span {color: var(--shiki-sepia);background: var(--shiki-sepia-bg);font-style: var(--shiki-sepia-font-style);font-weight: var(--shiki-sepia-font-weight);text-decoration: var(--shiki-sepia-text-decoration);}html pre.shiki code .sGZGq, html code.shiki .sGZGq{--shiki-default:#A0111F;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit;--shiki-sepia:#66D9EF;--shiki-sepia-font-style:italic}html pre.shiki code .sbfHu, html code.shiki .sbfHu{--shiki-default:#702C00;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit;--shiki-sepia:#FD971F;--shiki-sepia-font-style:italic}html pre.shiki code .sZnmU, html code.shiki .sZnmU{--shiki-default:#032563;--shiki-dark:#9ECBFF;--shiki-sepia:#E6DB74}html pre.shiki code .stP0Q, html code.shiki .stP0Q{--shiki-default:#023B95;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}html pre.shiki code .sQA8F, html code.shiki .sQA8F{--shiki-default:#702C00;--shiki-dark:#FFAB70;--shiki-sepia:#F8F8F2}html pre.shiki code .sAO_F, html code.shiki .sAO_F{--shiki-default:#A0111F;--shiki-dark:#79B8FF;--shiki-sepia:#AE81FF}",{"title":69,"searchDepth":84,"depth":104,"links":1176},[1177,1178,1179],{"id":41,"depth":84,"text":41},{"id":59,"depth":84,"text":59},{"id":184,"depth":84,"text":184},"2026-02-06","md",{"layout":1183,"excerpt":1184},"post",{"type":8,"value":1185},[1186],[11,1187,13],{},"\u002F2026-02-06-send-to-unreal-texture-fix",{"title":6,"description":13},"_legacy\u002F2026\u002F2026-02-06-send-to-unreal-texture-fix",[1192,1193],"UE5","Blender","OyC8nTZpLwnhpQh5SWX0QCKQZ3XzQItDOM9CDyTs94Q",{"id":1196,"title":1197,"body":1198,"date":1387,"description":1202,"extension":1181,"meta":1388,"navigation":289,"path":1401,"seo":1402,"stem":1403,"tags":1404,"__hash__":1405},"blogs\u002F_legacy\u002F2020\u002F2020-09-26-blender-plugin-note.md","Blender插件制作笔记",{"type":8,"value":1199,"toc":1378},[1200,1203,1206,1209,1212,1216,1219,1226,1229,1232,1235,1238,1246,1249,1252,1258,1261,1264,1267,1273,1276,1280,1283,1286,1293,1296,1299,1302,1305,1308,1314,1317,1324,1327,1338,1341,1347,1350,1353,1356,1359,1362,1366,1369,1375],[11,1201,1202],{},"这里记录的都是Blender相关的，和Python基础错误关系不大。",[11,1204,1205],{},"当前的blender版本为2.83.5 。",[11,1207,1208],{},"暂时打算停留在lts版本上，主干的开发速度太快了，跟不上节奏",[11,1210,1211],{},"blender的api文档有些难以查询，有的时候想要特定的功能反而直接求助于搜索引擎比较快……",[39,1213,1215],{"id":1214},"ui禁用","UI禁用",[11,1217,1218],{},"对于有些UI需要禁用而不是不绘制的，直接操作UI的enabled属性就可以了。",[11,1220,1221,1222],{},"UILayeout相关的文档可以在这里看到：",[18,1223,1224],{"href":1224,"rel":1225},"https:\u002F\u002Fdocs.blender.org\u002Fapi\u002Fcurrent\u002Fbpy.types.UILayout.html",[22],[39,1227,1228],{"id":1228},"类型注册",[11,1230,1231],{},"所有的blender相关的类型必须经过register_class才能使用，要留意在插件解除注册的时候同时unregister_class。",[11,1233,1234],{},"对于需要注册的属性也同样，属性可以注册在Object上，这样每个物品都会有。注册在Scene上的属性只有Scene会有。",[11,1236,1237],{},"从别人的插件里面看到，把class放到一个列表里面统一操作的方式比较好：",[64,1239,1244],{"className":1240,"code":1242,"language":1243},[1241],"language-text","def register():\n    from bpy.utils import register_class\n    for cls in classes:\n        register_class(cls)\n\ndef unregister():\n    from bpy.utils import unregister_class\n    for cls in reversed(classes):\n        unregister_class(cls)\n","text",[71,1245,1242],{"__ignoreMap":69},[39,1247,1248],{"id":1248},"物品选择",[11,1250,1251],{},"对于想要选择的物品，在UI中想要限定类型的话，需要在注册PointerProperty的时候指定",[64,1253,1256],{"className":1254,"code":1255,"language":1243},[1241],"map_target: bpy.props.PointerProperty(\n    type=bpy.types.Object,\n    name=\"FollowTarget\",\n    poll=lambda self, obj: obj.type == 'ARMATURE' and obj != bpy.context.object\n    )\n",[71,1257,1255],{"__ignoreMap":69},[11,1259,1260],{},"这样的话，在进行属性显示时使用layout.prop就能只选择ARMATURE类型了。",[39,1262,1263],{"id":1263},"属性",[11,1265,1266],{},"属性的变更需要注册update",[64,1268,1271],{"className":1269,"code":1270,"language":1243},[1241],"source_follow_rotation: BoolProperty(\n    update=OnSourceFollowTypeChange, default=True)\n\n\n",[71,1272,1270],{"__ignoreMap":69},[11,1274,1275],{},"update传入时的self就是这个属性本身，很方便。",[39,1277,1279],{"id":1278},"uilist","UIList",[11,1281,1282],{},"ui基本上按照固定的流程来操作就不会有问题。",[11,1284,1285],{},"遇到问题比较多的是UIList，这个文档描述有些不清晰，导致使用上很多靠猜，而Python又没有类型检查，真的很苦恼。",[11,1287,1288,1289],{},"官方的文档在这里：",[18,1290,1291],{"href":1291,"rel":1292,"title":1291},"https:\u002F\u002Fdocs.blender.org\u002Fapi\u002Fcurrent\u002Fbpy.types.UIList.html",[22],[11,1294,1295],{},"主要的问题是对初次接触的人很不友好，如果你和我一样对Python也不是很熟悉的话会一头雾水。原因是第一个例子太简单，而第二个例子太复杂。",[11,1297,1298],{},"其实，对于UIList而言，一般只要重载draw_item就可以了，在里面可以和其他UI一样执行绘制。",[11,1300,1301],{},"如果需要过滤功能的话，默认的就可以了。但是如果你需要过滤的属性不是name的话，才需要重写filter_items，不然过滤会不成功。",[11,1303,1304],{},"如果需要添加一些自定义的过滤的话，才需要最复杂的处理：额外的重载draw_filter来绘制额外的属性，在filter_itmes根据属性进行过滤。",[11,1306,1307],{},"列表示例：",[64,1309,1312],{"className":1310,"code":1311,"language":1243},[1241],"# ListBox\n# Show list of retarget cell\nclass QM_UL_ControlCell(UIList):\n    \"\"\"\n    List box for retarget cells, this is the list in side panel\n    \"\"\"\n    # Constants (flags)\n    # Be careful not to shadow FILTER_ITEM!\n    VGROUP_EMPTY = 1 \u003C\u003C 0\n\n    # Custom properties, saved with .blend file.\n    use_filter_name_reverse: bpy.props.BoolProperty(name=\"Reverse Name\", default=False, options=set(),\n                                                    description=\"Reverse name filtering\")\n\n    use_order_name: bpy.props.BoolProperty(name=\"Name\", default=False, options=set(),\n                                           description=\"Sort groups by their name (case-insensitive)\")\n\n    use_filter_linked: bpy.props.BoolProperty(name=\"Linked\", default=False, options=set(),\n                                              description=\"Filter linked only\")\n\n    def draw_item(self, context, layout, data, item, icon, active_data, active_propname, index):\n        row = layout.row()\n\n        subrow = row.row(align=True)\n        subrow.prop(item, \"source_name\", text=\"\", emboss=False)\n\n        subrow = subrow.row(align=True)\n        if len(item.target_name) > 0:\n            subrow.prop(item, \"source_follow_location\", text=\"\",\n                        toggle=True, icon=\"CON_LOCLIKE\")\n            subrow.prop(item, \"source_follow_rotation\", text=\"\",\n                        toggle=True, icon=\"CON_ROTLIKE\")\n\n    def invoke(self, context, event):\n        pass\n\n    def draw_filter(self, context, layout):\n        # Nothing much to say here, it's usual UI code...\n        row = layout.row()\n\n        subrow = row.row(align=True)\n        subrow.prop(self, \"filter_name\", text=\"\")\n        icon = 'ZOOM_OUT' if self.use_filter_name_reverse else 'ZOOM_IN'\n        subrow.prop(self, \"use_filter_name_reverse\", text=\"\", icon=icon)\n\n        icon = 'LINKED' if self.use_filter_linked else 'UNLINKED'\n        subrow.prop(self, \"use_filter_linked\", text=\"\", icon=icon)\n\n        subrow = layout.row(align=True)\n        subrow.label(text=\"Order by:\")\n        subrow.prop(self, \"use_order_name\", toggle=True)\n\n    # Filter\n    # 1. we use [source_name] not [name]\n    # 2. provide filter for targeted cell\n    def filter_items(self, context, data, propname):\n        statelist = getattr(data, propname)\n        helper_funcs = bpy.types.UI_UL_list\n\n        # Default return values.\n        flt_flags = []\n        flt_neworder = []\n\n        # Filtering by name\n        if self.filter_name:\n            flt_flags = helper_funcs.filter_items_by_name(self.filter_name, self.bitflag_filter_item, statelist, \"source_name\",\n                                                          reverse=self.use_filter_name_reverse)\n        if not flt_flags:\n            flt_flags = [self.bitflag_filter_item] * len(statelist)\n\n        # Filtering cell with target\n        if self.use_filter_linked:\n            for i, item in enumerate(statelist):\n                if len(item.target_name) == 0:\n                    flt_flags[i] &= ~self.bitflag_filter_item\n\n        # Reorder by name\n        if self.use_order_name:\n            flt_neworder = helper_funcs.sort_items_by_name(\n                statelist, \"source_name\")\n\n        return flt_flags, flt_neworder\n\n",[71,1313,1311],{"__ignoreMap":69},[11,1315,1316],{},"最终结果是这样的：",[11,1318,1319],{},[1320,1321],"img",{"alt":1322,"src":1323},"image","\u002Fwp-content\u002Fuploads\u002F2020\u002F09\u002Fimage_thumb-24.png",[11,1325,1326],{},"主要起作用的其实就是",[26,1328,1329,1332,1335],{},[29,1330,1331],{},"draw_item: 绘制单个列表",[29,1333,1334],{},"draw_filter: 绘制下面的过滤区域，这个不指定也会有一个默认的",[29,1336,1337],{},"filter_item: 用于实际对列表的元素进行过滤",[11,1339,1340],{},"在列表绘制时可以指定列表元素和选中的index：",[64,1342,1345],{"className":1343,"code":1344,"language":1243},[1241],"layout.template_list(\"QM_UL_ControlCell\", \"\",\n    qm_state, \"quickmap_celllist\",\n    qm_state, \"quickmap_celllist_index\"\n    )\n",[71,1346,1344],{"__ignoreMap":69},[11,1348,1349],{},"这样就可以在选中指定列表元素时根据index的update来得到通知了。",[39,1351,1352],{"id":1352},"文件读写",[11,1354,1355],{},"python对json的支持比较好，读写很方便。",[11,1357,1358],{},"文件本身需要让operator继承ExportHelper和ImportHelper才能分别进行blender封装好的文件操作。",[11,1360,1361],{},"本身很简单，网上随便就能找到例子，这里就不列出占位置了。",[39,1363,1365],{"id":1364},"collection","Collection",[11,1367,1368],{},"这部分操作有点迷，在网上抄了一段代码直接用就好了：",[64,1370,1373],{"className":1371,"code":1372,"language":1243},[1241],"# Collection Operate\ndef make_collection(collection_name):\n    if collection_name in bpy.data.collections:  # Does the collection already exist?\n        return bpy.data.collections[collection_name]\n    else:\n        new_collection = bpy.data.collections.new(collection_name)\n        bpy.context.scene.collection.children.link(\n            new_collection)  # Add the new collection under a parent\n        return new_collection\n",[71,1374,1372],{"__ignoreMap":69},[11,1376,1377],{},"主要是collection的注册关系需要留意，不过别人封好了就不用管了~",{"title":69,"searchDepth":84,"depth":104,"links":1379},[1380,1381,1382,1383,1384,1385,1386],{"id":1214,"depth":84,"text":1215},{"id":1228,"depth":84,"text":1228},{"id":1248,"depth":84,"text":1248},{"id":1263,"depth":84,"text":1263},{"id":1278,"depth":84,"text":1279},{"id":1352,"depth":84,"text":1352},{"id":1364,"depth":84,"text":1365},"2020-09-26",{"layout":1183,"status":1389,"published":289,"author":1390,"author_login":1392,"author_email":1393,"wordpress_id":1394,"wordpress_url":1395,"date_gmt":1396,"excerpt":1397},"publish",{"display_name":1391,"login":1392,"email":1393,"url":69},"风铃","flinkor","flinkor@foxmail.com",3062,"\u002F?p=3062","2020-09-26 13:07:30 +0000",{"type":8,"value":1398},[1399],[11,1400,1202],{},"\u002F2020-09-26-blender-plugin-note",{"title":1197,"description":1202},"_legacy\u002F2020\u002F2020-09-26-blender-plugin-note",[1193],"vSpYGo9iGu31hKuBi00WOCDKGYNSEyUQhAZypHcrca8",{"id":1407,"title":1408,"body":1409,"date":1712,"description":1413,"extension":1181,"meta":1713,"navigation":289,"path":1722,"seo":1723,"stem":1724,"tags":1725,"__hash__":1727},"blogs\u002F_legacy\u002F2020\u002F2020-05-10-ue4-blender-hair-groom.md","UE4使用Blender的HairGroom笔记",{"type":8,"value":1410,"toc":1699},[1411,1414,1417,1432,1436,1445,1448,1451,1456,1459,1463,1466,1469,1475,1478,1490,1493,1496,1502,1505,1510,1513,1519,1522,1528,1532,1536,1539,1545,1548,1554,1557,1560,1563,1566,1569,1575,1578,1581,1584,1590,1596,1599,1605,1608,1611,1614,1617,1623,1626,1632,1635,1641,1644,1649,1652,1657,1660,1663,1668,1671,1676,1679,1682,1685,1688,1693,1696],[11,1412,1413],{},"UE4最近更新了对HairGroom的支持，于是对其进行了一些测试。",[11,1415,1416],{},"当前使用的UE4版本为4.25.0，Blender版本为2.82a。",[11,1418,1419,1420,1425,1426,1431],{},"本文主要参考Jayanam的教程[",[18,1421,1424],{"href":1422,"rel":1423},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=Oq0q8AmTnDU",[22],"Blender 2.8 Hair Beginner Tutorial","]和Marvel Master的教程[",[18,1427,1430],{"href":1428,"rel":1429},"https:\u002F\u002Fwww.youtube.com\u002Fwatch?v=vQIkyk0-TD4",[22],"Dynamic Hair Tutorial - Blender to Unreal Engine 4 Groom UE4.25","]。",[39,1433,1435],{"id":1434},"ue4设置","UE4设置",[11,1437,1438,1439,1444],{},"这部分按照[",[18,1440,1443],{"href":1441,"rel":1442},"https:\u002F\u002Fdocs.unrealengine.com\u002Fzh-CN\u002FEngine\u002FHairRendering\u002FOverview\u002Findex.html",[22],"官方文档","]操作就可以了。",[11,1446,1447],{},"当然，跟着Marvel Master的教程做也是可以的。需要注意的是，当前Groom支持在实验阶段，所以有些部分可能会与实际操作不一致。",[11,1449,1450],{},"例如Niagra发射器的部分，按照官方文档操作之后",[11,1452,1453],{},[1320,1454],{"alt":1322,"src":1455},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb.png",[11,1457,1458],{},"Override Parameters并没有出现，由于并不影响实际效果，所以就先放置了。",[39,1460,1462],{"id":1461},"blender内制作groom","Blender内制作Groom",[11,1464,1465],{},"教程里面是直接在导出的SkeletonMesh上做Groom的，默认情况下会从全体表面来发射头发，以功能验证而言倒是无所谓。",[11,1467,1468],{},"总之首先就是加个粒子系统，然后改成头发",[11,1470,1471],{},[1320,1472],{"alt":1473,"src":1474},"clip_image001","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image001_thumb.png",[11,1476,1477],{},"对于需要控制生长区域的情况，目前看主要有三种方法：",[1479,1480,1481,1484,1487],"ol",{},[29,1482,1483],{},"单独拉出Mesh用于头发发射",[29,1485,1486],{},"使用头发发射器的顶点组功能",[29,1488,1489],{},"使用贴图进行权重控制",[11,1491,1492],{},"似乎第三个方法主要用于微调的样子。",[11,1494,1495],{},"定点组的设置比较直观，使用贴图Mask的时候需要注意，必须调整贴图的影响方式",[11,1497,1498],{},[1320,1499],{"alt":1500,"src":1501},"clip_image0014","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0014_thumb.png",[11,1503,1504],{},"完成粒子编辑后，将粒子转换为Curv",[11,1506,1507],{},[1320,1508],{"alt":1322,"src":1509},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-2.png",[11,1511,1512],{},"然后作Curv转换",[11,1514,1515],{},[1320,1516],{"alt":1517,"src":1518},"clip_image0016","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0016_thumb.png",[11,1520,1521],{},"导出Abc，注意调整缩放",[11,1523,1524],{},[1320,1525],{"alt":1526,"src":1527},"clip_image0018","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0018_thumb.png",[39,1529,1531],{"id":1530},"导入ue4","导入UE4",[1533,1534,1535],"h3",{"id":1535},"头发导入",[11,1537,1538],{},"导入的选项直接参照教程即可，流程上似乎有些奇怪，不过现在似乎就是这样工作的",[11,1540,1541],{},[1320,1542],{"alt":1543,"src":1544},"clip_image00110","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image00110_thumb.png",[11,1546,1547],{},"导入之后，在Asset里面打开物理模拟",[11,1549,1550],{},[1320,1551],{"alt":1552,"src":1553},"clip_image00112","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image00112_thumb.png",[11,1555,1556],{},"此时可以将资源拖到地图中预览。",[11,1558,1559],{},"这边遇到问题是点开之后拖到地图里面没有反应，此时，将Asset直接添加到角色蓝图内，会发现能动了，但是没有跟随骨骼限制运动。这时再拖到地图里面就会有物理反应了，可能是哪里有bug。",[1533,1561,1562],{"id":1562},"基础导入",[11,1564,1565],{},"无论如何，要让效果跟随物理基础，需要再次做一下导入工作。感觉之后的版本应该会改进，不然这样操作很奇怪",[11,1567,1568],{},"在blender中删除转换后的Mesh，选中角色模型",[11,1570,1571],{},[1320,1572],{"alt":1573,"src":1574},"clip_image00114","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image00114_thumb.png",[11,1576,1577],{},"再导出一次abc，覆盖掉旧文件",[11,1579,1580],{},"在UE4的旧Asset上点击重新导入",[11,1582,1583],{},"需要重新设置物理模拟",[11,1585,1586],{},[1320,1587],{"alt":1588,"src":1589},"clip_image002","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image002_thumb.png",[11,1591,1592],{},[1320,1593],{"alt":1594,"src":1595},"clip_image003","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image003_thumb.png",[11,1597,1598],{},"这个时候就可以看到Groom的跟随骨骼动画的效果了",[11,1600,1601],{},[1320,1602],{"alt":1603,"src":1604},"clip_image004","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image004_thumb.png",[39,1606,1607],{"id":1607},"头发试作",[11,1609,1610],{},"为了实际看看Groom的头发大概是什么风格，这边简单的进行了下测试",[1533,1612,1613],{"id":1613},"顶点组绘制控制",[11,1615,1616],{},"在模型上添加一个顶点组",[11,1618,1619],{},[1320,1620],{"alt":1621,"src":1622},"clip_image00116","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image00116_thumb.png",[11,1624,1625],{},"然后将模式切换到权重绘制模式",[11,1627,1628],{},[1320,1629],{"alt":1630,"src":1631},"clip_image0024","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0024_thumb.png",[11,1633,1634],{},"这个时候就可以在模型上绘制顶点组的范围了",[11,1636,1637],{},[1320,1638],{"alt":1639,"src":1640},"clip_image0034","\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fclip_image0034_thumb.png",[11,1642,1643],{},"绘制完成后在粒子的顶点组里面将density指定到这个顶点组",[11,1645,1646],{},[1320,1647],{"alt":1322,"src":1648},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-3.png",[11,1650,1651],{},"就不会从整个头上发射头发了",[11,1653,1654],{},[1320,1655],{"alt":1322,"src":1656},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-4.png",[1533,1658,1659],{"id":1659},"粒子调整",[11,1661,1662],{},"切换到粒子编辑模式随便梳理一下头发",[11,1664,1665],{},[1320,1666],{"alt":1322,"src":1667},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-5.png",[11,1669,1670],{},"这里可以通过选项让梳理的时候能够看到子粒子的效果",[11,1672,1673],{},[1320,1674],{"alt":1322,"src":1675},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-7.png",[11,1677,1678],{},"这个梳理模式(粒子编辑器)还是挺强大的，有看到教程是直接从空白处开始刷出粒子的，不过由于没有什么美术细胞，就只能从简了。",[11,1680,1681],{},"按照教程里面的进行导出操作，就可以看到效果了",[1533,1683,1684],{"id":1684},"效果",[11,1686,1687],{},"努力进行了一番效果调整后，结果是这样的：",[11,1689,1690],{},[1320,1691],{"alt":1322,"src":1692},"\u002Fwp-content\u002Fuploads\u002F2020\u002F05\u002Fimage_thumb-1.png",[11,1694,1695],{},"由于全局只用了一个发射器，尤其是没有做前发的部分，所以效果不是很好。另外，不知是没有处理好头发的效果，还是过于写实的原因，感觉Groom的和动画风格的模型有些不匹配。",[11,1697,1698],{},"本来是想拉个双马尾的，但是由于对Blender的操作还不是很熟悉，最后放弃了……感觉要达到ArtStation上看到的大神们做出来的效果对我而言暂时似乎是不太可能的。",{"title":69,"searchDepth":84,"depth":104,"links":1700},[1701,1702,1703,1707],{"id":1434,"depth":84,"text":1435},{"id":1461,"depth":84,"text":1462},{"id":1530,"depth":84,"text":1531,"children":1704},[1705,1706],{"id":1535,"depth":104,"text":1535},{"id":1562,"depth":104,"text":1562},{"id":1607,"depth":84,"text":1607,"children":1708},[1709,1710,1711],{"id":1613,"depth":104,"text":1613},{"id":1659,"depth":104,"text":1659},{"id":1684,"depth":104,"text":1684},"2020-05-10",{"layout":1183,"status":1389,"published":289,"author":1714,"author_login":1392,"author_email":1393,"wordpress_id":1715,"wordpress_url":1716,"date_gmt":1717,"excerpt":1718},{"display_name":1391,"login":1392,"email":1393,"url":69},2876,"\u002F?p=2876","2020-05-10 12:56:56 +0000",{"type":8,"value":1719},[1720],[11,1721,1413],{},"\u002F2020-05-10-ue4-blender-hair-groom",{"title":1408,"description":1413},"_legacy\u002F2020\u002F2020-05-10-ue4-blender-hair-groom",[1726,1193],"UE4","Yspt_F27sh5kZzkyHIEkNbC-a3oyN24-P5N4FvzBm1c",{"id":1729,"title":1730,"body":1731,"date":1942,"description":1735,"extension":1181,"meta":1943,"navigation":289,"path":1952,"seo":1953,"stem":1954,"tags":1955,"__hash__":1956},"blogs\u002F_legacy\u002F2020\u002F2020-04-11-blender-ue4-pipeline-note.md","Blender到UE4笔记",{"type":8,"value":1732,"toc":1927},[1733,1736,1739,1742,1746,1749,1754,1757,1761,1764,1775,1778,1781,1784,1787,1796,1805,1808,1811,1814,1817,1823,1826,1829,1832,1837,1840,1843,1846,1849,1853,1862,1868,1871,1876,1880,1889,1895,1898,1902,1905,1908,1911,1916,1919,1924],[11,1734,1735],{},"Blender导出FBX到UE4的路径有些常见的问题，每次碰到都重新去找原因造成时间浪费，于是这边记录下。",[11,1737,1738],{},"当前UE4版本为UE4.25.0；当前Blender版本为Blender2.82a。\nBlender最近升级到2.8了，有不少变化，不过一些基本的操作还是一样的。",[39,1740,1741],{"id":1741},"导出导入",[1533,1743,1745],{"id":1744},"smooth-group","Smooth Group",[11,1747,1748],{},"导入的时候提示没有SmoothGroup，需要修改FBX的默认导出选项",[11,1750,1751],{},[1320,1752],{"alt":1473,"src":1753},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fclip_image001_thumb.png",[11,1755,1756],{},"这样就不会提示了",[1533,1758,1760],{"id":1759},"bind-pose","Bind pose",[11,1762,1763],{},"导入UE4时提示",[46,1765,1766,1769,1772],{},[11,1767,1768],{},"The following bones are missing from the bind pose:",[11,1770,1771],{},"This can happen for bones that are not vert weighted. If they are not in the correct orientation after importing,",[11,1773,1774],{},"please set the \"Use T0 as ref pose\" option or add them to the bind pose and reimport the skeletal mesh.",[11,1776,1777],{},"按照提示直接勾选Use T0 as ref pose，就不会提示了，结果没有明显变化。",[11,1779,1780],{},"是否有隐藏问题还不是很确定。",[1533,1782,1783],{"id":1783},"贴图丢了",[11,1785,1786],{},"这个是Blender2.8的新坑，主要是材质节点的组织方式变了。",[11,1788,1789,1790,1795],{},"从[",[18,1791,1794],{"href":1792,"rel":1793},"https:\u002F\u002Fdeveloper.blender.org\u002FT68399",[22],"官方社区的讨论","]来看，这个似乎不是BUG而是一个特性。",[11,1797,1798,1799,1804],{},"2.8的[",[18,1800,1803],{"href":1801,"rel":1802},"https:\u002F\u002Fwiki.blender.org\u002Fwiki\u002FReference\u002FRelease_Notes\u002F2.80\u002FPython_API\u002FModules",[22],"API变更记录","]里面可以看到，导出使用的帮助类为bpy_extras.node_shader_utils。对于导出失败的材质，实际使用这个helper去尝试获取材质，就会发现无法正确的获取到贴图信息。主要原因是一些复杂的材质使用逻辑导致的，对于只是将Blender当作文件转换工具的用户而言就有些困扰了呢。",[11,1806,1807],{},"暂时没有什么特别好的解决方案，对于没有特别必要升级2.8的情况可以回到2.79……",[1533,1809,1810],{"id":1810},"贴图扭曲",[11,1812,1813],{},"模型导入后，部分贴图效果有微妙的扭曲感。在一些部位能明显的看到贴图出现了偏移。在对Blender内的UV进行查看后，发现在整个路径中，UV的值过大了。虽然说在理论上这并不会造成问题，但是实际是在导入UE4后出现了较为明显的UV偏差。",[11,1815,1816],{},"详细的发生原因没有时间去细究，不确定问题是在Blender侧还是UE侧，通过使用脚本在Blender中直接将UV全部裁剪到0~1的范围内解决了问题。",[64,1818,1821],{"className":1819,"code":1820,"language":1243},[1241],"import bpy\n\nfor ts_mesh in bpy.data.meshes:\n    print(ts_mesh.name)\n    for ts_uvlayer in ts_mesh.uv_layers:\n        print(ts_uvlayer)\n        for ts_data in ts_uvlayer.data:\n            ts_uv = ts_data.uv\n            ts_uv.x = ts_uv.x % 1.0\n            ts_uv.y = ts_uv.y % 1.0\n            print(ts_data.uv)\n\n\n",[71,1822,1820],{"__ignoreMap":69},[1533,1824,1825],{"id":1825},"光照模式下出现断层",[11,1827,1828],{},"在UnLit模式下模型正常，光照模式下出现一个明显的断层。",[11,1830,1831],{},"这种时候可以对GBuffer进行可视化来排查问题",[11,1833,1834],{},[1320,1835],{"alt":1322,"src":1836},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-4.png",[11,1838,1839],{},"这次遇到的是法线贴图的处理方式有问题，能够明显看到在World Normal的可视化里面有一个断层，导致Detail Light那边同样出现了断层。",[11,1841,1842],{},"问题是法线贴图的混合，法线贴图不能通过简单的加法或者乘法来混合，UE4有提供混合节点BlendAngleCorrectedNormals，直接使用即可~",[39,1844,1845],{"id":1845},"材质节点",[11,1847,1848],{},"Blender和UE4的材质节点有很多不同的地方，需要至少保证一些数学计算是一致的",[1533,1850,1852],{"id":1851},"rgb-to-bw","Rgb to bw",[11,1854,1855,1856,1861],{},"似乎是在做灰度转换的样子，[",[18,1857,1860],{"href":1858,"rel":1859},"https:\u002F\u002Fdocs.blender.org\u002Fmanual\u002Fen\u002Flatest\u002Fcompositing\u002Ftypes\u002Fconverter\u002Frgb_to_bw.html",[22],"官方Rgb_to_bw文档","]没有给出详细的计算方式。到源码里面的utils.py找到了",[64,1863,1866],{"className":1864,"code":1865,"language":1243},[1241],"def rgb_to_bw(r, g, b):\n    \"\"\"Method to convert rgb to a bw intensity value.\"\"\"\n    return 0.35 * r + 0.45 * g + 0.2 * b\n",[71,1867,1865],{"__ignoreMap":69},[11,1869,1870],{},"参照着实现",[11,1872,1873],{},[1320,1874],{"alt":1322,"src":1875},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-3.png",[1533,1877,1879],{"id":1878},"invert","Invert",[11,1881,1882,1883,1888],{},"按照[",[18,1884,1887],{"href":1885,"rel":1886},"https:\u002F\u002Fdocs.blender.org\u002Fmanual\u002Fen\u002Flatest\u002Frender\u002Fshader_nodes\u002Fcolor\u002Finvert.html",[22],"Invert Node","]的描述，就是反转，但是有一个factor的参数，暂时采用svm_invert.h的实现",[64,1890,1893],{"className":1891,"code":1892,"language":1243},[1241],"ccl_device float invert(float color, float factor)\n{\n  return factor * (1.0f - color) + (1.0f - factor) * color;\n}\n",[71,1894,1892],{"__ignoreMap":69},[11,1896,1897],{},"没有配置factor的话就是OneMinus",[1533,1899,1901],{"id":1900},"mix","Mix",[11,1903,1904],{},"Mix等效于UE4的Interp节点。",[39,1906,1907],{"id":1907},"功能差异",[11,1909,1910],{},"这次遇到一个Blender和UE4之间节点上的表现差异导致的误操作，在UE4里面，材质节点是这样的：",[11,1912,1913],{},[1320,1914],{"alt":1322,"src":1915},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-5.png",[11,1917,1918],{},"这里面RGB和下面的R\\G\\B是指向同样的值的，而在Blender里面，如果有这样的节点",[11,1920,1921],{},[1320,1922],{"alt":1322,"src":1923},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-6.png",[11,1925,1926],{},"这个时候Color和R\\G\\B其实是完全不同的东西，如果在进行材质功能映射的时候一定不要按照UE4这边的习惯去理解~",{"title":69,"searchDepth":84,"depth":104,"links":1928},[1929,1936,1941],{"id":1741,"depth":84,"text":1741,"children":1930},[1931,1932,1933,1934,1935],{"id":1744,"depth":104,"text":1745},{"id":1759,"depth":104,"text":1760},{"id":1783,"depth":104,"text":1783},{"id":1810,"depth":104,"text":1810},{"id":1825,"depth":104,"text":1825},{"id":1845,"depth":84,"text":1845,"children":1937},[1938,1939,1940],{"id":1851,"depth":104,"text":1852},{"id":1878,"depth":104,"text":1879},{"id":1900,"depth":104,"text":1901},{"id":1907,"depth":84,"text":1907},"2020-04-11",{"layout":1183,"status":1389,"published":289,"author":1944,"author_login":1392,"author_email":1393,"wordpress_id":1945,"wordpress_url":1946,"date_gmt":1947,"excerpt":1948},{"display_name":1391,"login":1392,"email":1393,"url":69},2831,"\u002F?p=2831","2020-04-11 11:51:28 +0000",{"type":8,"value":1949},[1950],[11,1951,1735],{},"\u002F2020-04-11-blender-ue4-pipeline-note",{"title":1730,"description":1735},"_legacy\u002F2020\u002F2020-04-11-blender-ue4-pipeline-note",[1726,1193],"rStqfgrMz4Rim14QEzNGZDIhyjXIBdOStHTnvMtMDcM",{"id":1958,"title":1959,"body":1960,"date":1942,"description":1964,"extension":1181,"meta":2050,"navigation":289,"path":2059,"seo":2060,"stem":2061,"tags":2062,"__hash__":2064},"blogs\u002F_legacy\u002F2020\u002F2020-04-11-blender-ue4-texture-python-material-fix.md","Blender和UE4的贴图修复脚本",{"type":8,"value":1961,"toc":2045},[1962,1965,1968,1971,1974,1977,1980,1984,1987,1992,1995,2001,2004,2008,2011,2021,2027,2030,2036,2039,2042],[11,1963,1964],{},"最近遇到了Blender导出的材质没有贴图的问题，刚好顺便研究下UE4和Blender的脚本结构。",[11,1966,1967],{},"当前UE4版本为UE4.25.0；当前Blender版本为Blender2.82a。",[11,1969,1970],{},"首先，目前Blender和UE4的脚本版本是不同的。Blender使用的是python3，而UE4停留在python2.7。",[11,1972,1973],{},"这边使用Blender其实主要是作为模型导出的中继，比如MMD、VRM、XPS以及一些其他的非主流模型，通过Blender这边导出为FBX，然后输入到UE4去。",[11,1975,1976],{},"由于Blender和UE4的材质结构是不同的，到了UE4那边之后肯定要重新调整节点。所以对于缺少贴图的情况，最致命的问题就是，我们很难把材质名称和它使用的贴图对应起来。",[11,1978,1979],{},"因此这边就是实现这个过程的自动化，首先，从Blender这边导出材质使用到的贴图，然后在UE4那边还原到材质中去。",[39,1981,1983],{"id":1982},"blender贴图导出","Blender贴图导出",[11,1985,1986],{},"blender这边的script编辑器里面有个需要注意的地方，就是他的输出不是在左边的那个窗口输出的。虽然左边的console窗口可以用于测试指令，但是如果你在右侧的脚本上点击运行的话。输出其实是在外部的那个Console里面的。如果默认没有的话，需要在菜单中打开",[11,1988,1989],{},[1320,1990],{"alt":1322,"src":1991},"\u002Fwp-content\u002Fuploads\u002F2020\u002F04\u002Fimage_thumb-2.png",[11,1993,1994],{},"Blender的脚本文档还是比较丰富的，不过由于功能比较多，这边只是做一个简单的材质中的贴图导出，所以并不一定是一个好的实现",[64,1996,1999],{"className":1997,"code":1998,"language":1243},[1241],"# blender script\n# python3\n\nimport bpy\nimport json\n\nEXPORT_PATH = 'F:\u002Fmaterials.json'\n\nclass MatCollector:\n    MatDic = {}\n\n    # Collet material texture for single material\n    def _CollectMaterial(self, InMaterial):\n        MatTextures = []\n        for ts_node in InMaterial.node_tree.nodes:\n            if ts_node.bl_static_type == 'TEX_IMAGE' and ts_node.image:\n                MatTextures.append(ts_node.image.filepath)\n\n        tstr_MatName = InMaterial.name\n        if len(MatTextures) == 0:\n            print(\"[ExportMaterialTextures] \u003C\"+tstr_MatName+\"> has no texture\")\n            return\n\n        self.MatDic[tstr_MatName] = MatTextures\n        print(\"[ExportMaterialTextures] Material \" + tstr_MatName + \" with \" + str(len(MatTextures)) + \" texture\")\n        pass\n\n# List all materials and try pickout texture info\ndef ExportMaterialTextures(InPath):\n    print(\"[ExportMaterialTextures] Begin: \"+ InPath)\n    ts_MatCollector = MatCollector()\n    for Mat in bpy.data.materials:\n        ts_MatCollector._CollectMaterial(Mat)\n        \n    print(\"[ExportMaterialTextures] Material with texture count \" + str(len(ts_MatCollector.MatDic)))\n    with open(InPath, 'w') as f:\n        json.dump(ts_MatCollector.MatDic, f)\n\n    print(\"[ExportMaterialTextures] End\")\n\n\ndef main():\n    ExportMaterialTextures(EXPORT_PATH)\n\nif __name__ == \"__main__\":\n    main()\n",[71,2000,1998],{"__ignoreMap":69},[11,2002,2003],{},"脚本的作用很简单，就是遍历场景内所有的材质，然后将每个材质中带有的所有贴图都输出出来。",[39,2005,2007],{"id":2006},"ue4导入","UE4导入",[11,2009,2010],{},"当前UE4的编辑器Python插件是Beta状态，需要打开才能使用。",[11,2012,2013,2014,2017,2018,2020],{},"友情提醒：",[2015,2016],"br",{},"\n使用本文中的脚本前请注意备份项目文件!",[2015,2019],{},"\n脚本操作可能会导致不可预期的结果，请在了解这种危险的情况下使用脚本操作！\nUE4这边在完成FBX的导入之后，会看到所有的材质都是白的。所以我们这边用脚本，将对应的贴图添加到材质中去。不过，要在代码中操作材质是很麻烦的，连接的工作还是保留在了材质制作中。",[64,2022,2025],{"className":2023,"code":2024,"language":1243},[1241],"# unreal script\n# python2.7\n\nimport json\nimport unreal\n\nfrom sets import Set\n\nIMPORT_PATH = 'F:\u002Fmaterials.json'\nIMPORT_TARGET = '\u002FGame\u002FChara\u002F'\nTEXTURE_PATH = '\u002FGame\u002FChara\u002FTextures\u002F'\nTEXTURE_BASE = 'F:\u002Fz'\n\n\nclass MatTextureImporter:\n    IsStateGood = True\n\n    def ReadFromConfig(self):\n        unreal.log(\"[MaterialTextureImport] Begin\")\n\n        # Load from Json config\n        with open(IMPORT_PATH, 'r') as myfile:\n            data=myfile.read()\n        MatConf = json.loads(data)\n\n        # Try import texture\n        self._PreLoadTexture(MatConf)\n\n        if self.IsStateGood == False:\n            unreal.log_error(\"[MaterialTextureImport] Asset state not good, abort\")\n            return\n\n        # Try apply texture to material\n        unreal.log(\"[MaterialTextureImport] Total target material: \" + str(len(MatConf)))\n        with unreal.ScopedSlowTask(len(MatConf), \"Modifing materials\") as slow_task:\n            for ts_key in MatConf:\n                if slow_task.should_cancel():\n                    break\n                \n                slow_task.enter_progress_frame(1)\n                self._AddMaterialTexture(ts_key, MatConf[ts_key])\n\n        unreal.log(\"[MaterialTextureImport] End\")\n        return  \n\n    def _PreLoadTexture(self, InDic):\n        print(\"[MaterialTextureImport] Pre load textures - Begin\")\n\n        # Build texture list\n        tset_TexturePath = Set()\n        for ts_MatKey in InDic:\n            for ts_Texture in InDic[ts_MatKey]:\n                ts_TextureName = unreal.Paths.get_base_filename(ts_Texture)\n                ts_TextureUePath = TEXTURE_PATH + ts_TextureName\n\n                if unreal.EditorAssetLibrary.does_asset_exist(ts_TextureUePath):\n                    ts_TextureData = unreal.EditorAssetLibrary.find_asset_data(ts_TextureUePath)\n                    if ts_TextureData.is_valid():\n                        if ts_TextureData.asset_class == 'Texture2D':\n                            # We will never find a good name while operation takes more than once, so just ignore\n                            unreal.log_warning(\"[MaterialTextureImport] Texture \" + ts_TextureUePath + \" alread exist\")\n                            continue\n                        else:\n                            # While asset name conflicts, we will stop operation, as replacing or rename may break our project\n                            unreal.log_error(\"[MaterialTextureImport] Asset \" + ts_TextureUePath + \" alread exist and it is not a texture!\")\n                            self.IsStateGood = False\n                            return\n\n                tset_TexturePath.add(ts_Texture)\n\n        print(\"[MaterialTextureImport] Total texture need load: \" + str(len(tset_TexturePath)))\n\n        # Build import tasks\n        import_tasks = []\n        for ts_Texture in tset_TexturePath:\n            print(\"[MaterialTextureImport] - \" + ts_Texture)\n            AssetImportTask = unreal.AssetImportTask()\n            AssetImportTask.set_editor_property('filename', TEXTURE_BASE + ts_Texture)\n            AssetImportTask.set_editor_property('destination_path', TEXTURE_PATH)\n            AssetImportTask.set_editor_property('save', True)\n            import_tasks.append(AssetImportTask)\n\n        # Import textures\n        AssetTools = unreal.AssetToolsHelpers.get_asset_tools() \n        AssetTools.import_asset_tasks(import_tasks)\n\n        print(\"[MaterialTextureImport] Pre load textures - End\")\n        \n        return\n\n    def _AddMaterialTexture(self, InName, InList):\n        InName = InName.replace(' ', '_')\n        InName = InName.replace('.', '_')\n\n        print(\"[MaterialTextureImport] Material: \" + InName)\n\n        # Try load material\n        matpath = \"Material'\" + IMPORT_TARGET + InName + '.' + InName + \"'\"\n        ts_LoadedMat = unreal.load_asset(matpath)\n        if ts_LoadedMat is None:\n            unreal.log_warning(\"[MaterialTextureImport] Invalid material path: \" + matpath)\n            return\n\n        # Add texture to material\n        #unreal.MaterialEditingLibrary.delete_all_material_expressions(ts_LoadedMat)\n        td_TextureAdded = 0\n        for ts_TexturePath in InList:\n            ts_TextureName = unreal.Paths.get_base_filename(ts_TexturePath)\n            ts_TextureUePath = TEXTURE_PATH + ts_TextureName\n            ts_LoadedTexture = unreal.EditorAssetLibrary.load_asset(ts_TextureUePath)\n\n            if ts_LoadedTexture is None:\n                unreal.log_warning(\"[MaterialTextureImport] Could not load texture \"+ ts_TextureUePath)\n                continue\n\n            ts_TextureNodeBc = unreal.MaterialEditingLibrary.create_material_expression(ts_LoadedMat, unreal.MaterialExpressionTextureSample, -400, 250 * td_TextureAdded)\n            if ts_TextureNodeBc is None:\n                unreal.log_warning(\"[MaterialTextureImport] Could not create node\")\n                continue\n\n            ts_TextureNodeBc.set_editor_property(\"texture\", ts_LoadedTexture)\n            ts_TextureNodeBc.set_editor_property(\"desc\", ts_TextureName)\n            td_TextureAdded += 1\n\n        print(\"[MaterialTextureImport] Material get  \" + str(td_TextureAdded) + \" texture\")\n\n        return\n\ndef main():\n    ts_MatChanger = MatTextureImporter()\n    ts_MatChanger.ReadFromConfig()\n\nif __name__ == \"__main__\":\n    main()\n\n",[71,2026,2024],{"__ignoreMap":69},[11,2028,2029],{},"在制作中遇到的另一个问题是，由于模型那边Blender的材质使用到了复杂的Group Node，这边也对应的实现了Material Function，但是需要将材质改为使用Attribute输出。于是又写了个批量修改的脚本",[64,2031,2034],{"className":2032,"code":2033,"language":1243},[1241],"import unreal\n\ndef list_assets(InPath, InClass):\n    MatchedAssets = []\n\n    for ts_AssetPath in unreal.EditorAssetLibrary.list_assets(InPath):\n        ts_AssetData = unreal.EditorAssetLibrary.find_asset_data(ts_AssetPath)\n        if ts_AssetData.asset_class == InClass:\n            MatchedAssets.append(ts_AssetPath)\n\n    return MatchedAssets\n\n\n\ndef ModifyMaterials():\n    ts_Mats = list_assets(\"\u002FGame\u002FChara\u002F\", 'Material')\n    print(ts_Mats)\n    ts_MatModified = []\n    for ts_matPath in ts_Mats:\n        ts_mat = unreal.EditorAssetLibrary.load_asset(ts_matPath)\n        if ts_mat is None:\n            unreal.log_warning(\"[MatModify] No material in \" + ts_mat)\n            continue\n        #ts_mat.set_editor_property(\"use_material_attributes\", True)  \n\n        #unreal.MaterialEditingLibrary.recompile_material(ts_mat)\n        ts_MatModified.append(ts_mat)\n\n    unreal.EditorAssetLibrary.save_loaded_assets(ts_MatModified)\n\ndef main():\n    ModifyMaterials()\n\nif __name__ == \"__main__\":\n    main()\n\n",[71,2035,2033],{"__ignoreMap":69},[39,2037,2038],{"id":2038},"总结",[11,2040,2041],{},"Blender和UE4两边的Python脚本都还是挺好用的。",[11,2043,2044],{},"不过UE4这边在批量修改完材质后会有一个卡顿的感觉，使用SlowTask包裹依然没有改善，不过由于是自用的脚本，就不纠结了。",{"title":69,"searchDepth":84,"depth":104,"links":2046},[2047,2048,2049],{"id":1982,"depth":84,"text":1983},{"id":2006,"depth":84,"text":2007},{"id":2038,"depth":84,"text":2038},{"layout":1183,"status":1389,"published":289,"author":2051,"author_login":1392,"author_email":1393,"wordpress_id":2052,"wordpress_url":2053,"date_gmt":2054,"excerpt":2055},{"display_name":1391,"login":1392,"email":1393,"url":69},2815,"\u002F?p=2815","2020-04-11 13:04:30 +0000",{"type":8,"value":2056},[2057],[11,2058,1964],{},"\u002F2020-04-11-blender-ue4-texture-python-material-fix",{"title":1959,"description":1964},"_legacy\u002F2020\u002F2020-04-11-blender-ue4-texture-python-material-fix",[1726,1193,2063],"Python","YOQkIQJdqcC6WBD4OxarTU9B2stqZH2vyA88rL6ZNz8",1788763176493]