"""Unity 资产(.meta / .mat)文本模板与 GUID 管理。纯 stdlib,可独立测试。 模板采自 Unity 2022.3(TextureImporter serializedVersion 12 / Material 8 / ModelImporter 22200);升级 Unity 后如导入异常,在编辑器导出同类资产 diff 更新模板。""" import os import re import uuid XPBR_SHADER_GUID = "cab897163f45445fa34263d6670bfc9e" # Client/Assets/Game/shader/XShader/XPbr.shader.meta _GUID_RE = re.compile(r"^guid: ([0-9a-f]{32})", re.M) def new_guid(): return uuid.uuid4().hex def guid_for(asset_path): """资产已有 .meta 则复用其中 guid(重复转换不破坏 Unity 引用),否则新分配。""" meta = asset_path + ".meta" if os.path.isfile(meta): try: with open(meta, "r", encoding="utf-8", errors="replace") as f: m = _GUID_RE.search(f.read()) except OSError: m = None # meta 损坏/不可读时按无 meta 处理,重新分配 if m: return m.group(1) return new_guid() def safe_name(name): return re.sub(r"[^0-9A-Za-z_\-]", "_", name).strip("_") or "mat" def texture_meta(guid, srgb, max_size, alpha_is_transparency=False): return """fileFormatVersion: 2 guid: {guid} TextureImporter: internalIDToNameTable: [] externalObjects: {{}} serializedVersion: 12 mipmaps: mipMapMode: 0 enableMipMap: 1 sRGBTexture: {srgb} linearTexture: 0 fadeOut: 0 borderMipMap: 0 mipMapsPreserveCoverage: 0 alphaTestReferenceValue: 0.5 mipMapFadeDistanceStart: 1 mipMapFadeDistanceEnd: 3 bumpmap: convertToNormalMap: 0 externalNormalMap: 0 heightScale: 0.25 normalMapFilter: 0 flipGreenChannel: 0 isReadable: 0 streamingMipmaps: 0 streamingMipmapsPriority: 0 vTOnly: 0 ignoreMipmapLimit: 0 grayScaleToAlpha: 0 generateCubemap: 6 cubemapConvolution: 0 seamlessCubemap: 0 textureFormat: 1 maxTextureSize: {max_size} textureSettings: serializedVersion: 2 filterMode: 1 aniso: 1 mipBias: 0 wrapU: 0 wrapV: 0 wrapW: 0 nPOTScale: 1 lightmap: 0 compressionQuality: 50 spriteMode: 0 spriteExtrude: 1 spriteMeshType: 1 alignment: 0 spritePivot: {{x: 0.5, y: 0.5}} spritePixelsToUnits: 100 spriteBorder: {{x: 0, y: 0, z: 0, w: 0}} spriteGenerateFallbackPhysicsShape: 1 alphaUsage: 1 alphaIsTransparency: {ait} spriteTessellationDetail: -1 textureType: 0 textureShape: 1 singleChannelComponent: 0 flipbookRows: 1 flipbookColumns: 1 maxTextureSizeSet: 0 compressionQualitySet: 0 textureFormatSet: 0 ignorePngGamma: 0 applyGammaDecoding: 0 swizzle: 50462976 cookieLightType: 0 platformSettings: - serializedVersion: 3 buildTarget: DefaultTexturePlatform maxTextureSize: {max_size} resizeAlgorithm: 0 textureFormat: -1 textureCompression: 1 compressionQuality: 50 crunchedCompression: 0 allowsAlphaSplitting: 0 overridden: 0 ignorePlatformSupport: 0 androidETC2FallbackOverride: 0 forceMaximumCompressionQuality_BC6H_BC7: 0 spriteSheet: serializedVersion: 2 sprites: [] outline: [] physicsShape: [] bones: [] spriteID: internalID: 0 vertices: [] indices: edges: [] weights: [] secondaryTextures: [] nameFileIdTable: {{}} mipmapLimitGroupName: pSDRemoveMatte: 0 userData: assetBundleName: assetBundleVariant: """.format(guid=guid, srgb=1 if srgb else 0, max_size=max_size, ait=1 if alpha_is_transparency else 0) def material_yaml(name, shader_guid, base_tex_guid, mix_tex_guid, alpha_test): keywords = "\n - _ALPHATEST_ON" if alpha_test else " []" return """%YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!21 &2100000 Material: serializedVersion: 8 m_ObjectHideFlags: 0 m_CorrespondingSourceObject: {{fileID: 0}} m_PrefabInstance: {{fileID: 0}} m_PrefabAsset: {{fileID: 0}} m_Name: {name} m_Shader: {{fileID: 4800000, guid: {shader_guid}, type: 3}} m_Parent: {{fileID: 0}} m_ModifiedSerializedProperties: 0 m_ValidKeywords:{keywords} m_InvalidKeywords: [] m_LightmapFlags: 4 m_EnableInstancingVariants: 0 m_DoubleSidedGI: 0 m_CustomRenderQueue: -1 stringTagMap: {{}} disabledShaderPasses: [] m_LockedProperties: m_SavedProperties: serializedVersion: 3 m_TexEnvs: - _MainTex: m_Texture: {{fileID: 2800000, guid: {base_tex_guid}, type: 3}} m_Scale: {{x: 1, y: 1}} m_Offset: {{x: 0, y: 0}} - _MixTex: m_Texture: {{fileID: 2800000, guid: {mix_tex_guid}, type: 3}} m_Scale: {{x: 1, y: 1}} m_Offset: {{x: 0, y: 0}} m_Ints: [] m_Floats: - _AO: 1 - _AlphaTest: {alpha_test} - _Cutoff: 0.5 - _Metallic: 1 - _Roughness: 1 m_Colors: - _BaseColor: {{r: 1, g: 1, b: 1, a: 1}} m_BuildTextureStacks: [] """.format(name=name, shader_guid=shader_guid, base_tex_guid=base_tex_guid, mix_tex_guid=mix_tex_guid, keywords=keywords, alpha_test=1 if alpha_test else 0) def material_meta(guid): return """fileFormatVersion: 2 guid: {guid} NativeFormatImporter: externalObjects: {{}} mainObjectFileID: 2100000 userData: assetBundleName: assetBundleVariant: """.format(guid=guid) def model_meta(guid, material_remaps): """material_remaps: [(fbx 内材质名, mat 文件 guid), ...]""" if material_remaps: ext = "externalObjects:\n" for mat_name, mat_guid in material_remaps: ext += (" - first:\n" " type: UnityEngine:Material\n" " assembly: UnityEngine.CoreModule\n" " name: {n}\n" " second: {{fileID: 2100000, guid: {g}, type: 2}}\n" ).format(n=mat_name, g=mat_guid) else: ext = "externalObjects: {}\n" return """fileFormatVersion: 2 guid: {guid} ModelImporter: serializedVersion: 22200 internalIDToNameTable: [] {ext} materials: materialImportMode: 2 materialName: 0 materialSearch: 1 materialLocation: 1 animations: legacyGenerateAnimations: 4 bakeSimulation: 0 resampleCurves: 1 optimizeGameObjects: 0 removeConstantScaleCurves: 0 motionNodeName: animationImportErrors: animationImportWarnings: animationRetargetingWarnings: animationDoRetargetingWarnings: 0 importAnimatedCustomProperties: 0 importConstraints: 0 animationCompression: 1 animationRotationError: 0.5 animationPositionError: 0.5 animationScaleError: 0.5 animationWrapMode: 0 extraExposedTransformPaths: [] extraUserProperties: [] clipAnimations: [] isReadable: 0 meshes: lODScreenPercentages: [] globalScale: 1 meshCompression: 0 addColliders: 0 useSRGBMaterialColor: 1 sortHierarchyByName: 1 importPhysicalCameras: 1 importVisibility: 1 importBlendShapes: 1 importCameras: 1 importLights: 1 nodeNameCollisionStrategy: 1 fileIdsGeneration: 2 swapUVChannels: 0 generateSecondaryUV: 0 useFileUnits: 1 keepQuads: 0 weldVertices: 1 bakeAxisConversion: 0 preserveHierarchy: 0 skinWeightsMode: 0 maxBonesPerVertex: 4 minBoneWeight: 0.001 optimizeBones: 1 meshOptimizationFlags: -1 indexFormat: 0 secondaryUVAngleDistortion: 8 secondaryUVAreaDistortion: 15.000001 secondaryUVHardAngle: 88 secondaryUVMarginMethod: 1 secondaryUVMinLightmapResolution: 40 secondaryUVMinObjectScale: 1 secondaryUVPackMargin: 4 useFileScale: 1 strictVertexDataChecks: 0 tangentSpace: normalSmoothAngle: 60 normalImportMode: 0 tangentImportMode: 3 normalCalculationMode: 4 legacyComputeAllNormalsFromSmoothingGroupsWhenMeshHasBlendShapes: 0 blendShapeNormalImportMode: 1 normalSmoothingSource: 0 referencedClips: [] importAnimation: 1 humanDescription: serializedVersion: 3 human: [] skeleton: [] armTwist: 0.5 foreArmTwist: 0.5 upperLegTwist: 0.5 legTwist: 0.5 armStretch: 0.05 legStretch: 0.05 feetSpacing: 0 globalScale: 1 rootMotionBoneName: hasTranslationDoF: 0 hasExtraRoot: 0 skeletonHasParents: 1 lastHumanDescriptionAvatarSource: {{instanceID: 0}} autoGenerateAvatarMappingIfUnspecified: 1 animationType: 2 humanoidOversampling: 1 avatarSetup: 0 addHumanoidExtraRootOnlyWhenUsingAvatar: 1 importBlendShapeDeformPercent: 1 remapMaterialsIfMaterialImportModeIsNone: 0 additionalBone: 0 userData: assetBundleName: assetBundleVariant: """.format(guid=guid, ext=ext)