跳过正文

quixel导入blender的bl插件以及中文化适配&bug修改

·3382 字·7 分钟
杂谈 Unity Blender Shader 渲染 材质 色彩 插件
AxonSin
作者
AxonSin
梦想是复活在赛博世界,成为一名赛博垃圾人。

Quixel是来自Epic的一个模型资产库,其中的Bridge可以将资产库下载到本地并且进行个性化导出,如Blender、3dsmax、unity等等。但是遗憾的是blender在2.8版本后就不再支持。

不过Quixel将他们的导入blender插件进行了开源,有人将这个项目进行了fork并做修改。主要是适配了新版blender部分api和节点名字的变化。但是在导入的时候我们会发现导入后的物体大小放大了100倍,而且此时的旋转出现了绕x轴旋转了-90°。(其实旋转是fbx的通病,因为fbx和blender默认的坐标轴的定义不一致出现的错误,主要是着手修改放大问题)

所以我们找到blender的插件地址进行修改。地址在

"C:\Users\Danny\AppData\Roaming\Blender Foundation\Blender\4.2\scripts\addons\NodePreview\__init__.py"

修改一:将Principled BSDF改为”原理化 BSDF“
#

位于代码的340行。这是节点生成器的一部分,由于blender更改语言会导致节点api名称也发生变化,因此我们将

 self.parentName = "Principled BSDF"修改为

 self.parentName = "原理化 BSDF"

修改二:增加导入后的缩放锁定&旋转适配
#

位于代码的198行。导入fbx后我们需要对缩放进行调整。

if meshFormat.lower() == "fbx":
                        bpy.ops.import_scene.fbx(filepath=meshPath)

此时我们要修改成为

if meshFormat.lower() == "fbx":
                        bpy.ops.import_scene.fbx(filepath=meshPath,global_scale=0.1,axis_forward='Z',axis_up='Y')
                        # 缩放和旋转调整
                        imported_objects = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']
                        for obj in imported_objects:
                            obj.scale = (0.01, 0.01, 0.01)  # 将模型缩小100倍
                            obj.rotation_euler[0] += 1.5708  # 绕X轴旋转 90 度 (1.5708弧度 ≈ 90度)

                        # get selected objects
                        obj_objects = [ o for o in bpy.context.scene.objects if o.select_get() ]
                        self.selectedObjects += obj_objects

对bpy.ops.import_scene.fbx(filepath=meshPath,global_scale=0.1,axis_forward=‘Z’,axis_up=‘Y’)之后进行了缩放调整和绕x轴旋转90°的调整。

最后附上修改后的源码:

# ##### QUIXEL AB - MEGASCANS PLugin FOR BLENDER #####
#
# The Megascans Plugin  plugin for Blender is an add-on that lets
# you instantly import assets with their shader setup with one click only.
#
# Because it relies on some of the latest 2.80 features, this plugin is currently
# only available for Blender 2.80 and forward.
#
# You are free to modify, add features or tweak this add-on as you see fit, and
# don't hesitate to send us some feedback if you've done something cool with it.
#
# ##### QUIXEL AB - MEGASCANS PLUGIN FOR BLENDER #####

import bpy, threading, os, time, json, socket
from bpy.app.handlers import persistent

globals()['Megascans_DataSet'] = None

# This stuff is for the Alembic support
globals()['MG_Material'] = []
globals()['MG_AlembicPath'] = []
globals()['MG_ImportComplete'] = False


bl_info = {
    "name": "Megascans Plugin Fork",
    "description": "Connects Blender to Quixel Bridge for one-click imports with shader setup and geometry",
    "author": "Quixel \ Sören Schmidt-Clausen",
    "version": (3, 7, 0),
    "blender": (3, 4, 0),
    "location": "File > Import",
    "warning": "", # used for warning icon and text in addons panel
    "wiki_url": "https://docs.quixel.org/bridge/livelinks/blender/info_quickstart.html",
    "tracker_url": "https://docs.quixel.org/bridge/livelinks/blender/info_quickstart#release_notes",
    "support": "COMMUNITY",
    "category": "Import-Export"
}


# MS_Init_ImportProcess is the main asset import class.
# This class is invoked whenever a new asset is set from Bridge.

class MS_Init_ImportProcess():

    # This initialization method create the data structure to process our assets
    # later on in the initImportProcess method. The method loops on all assets
    # that have been sent by Bridge.
    def __init__(self):
        print("Initialized import class...")
        try:
            # Check if there's any incoming data
            if globals()['Megascans_DataSet'] != None:

                globals()['MG_AlembicPath'] = []
                globals()['MG_Material'] = []
                globals()['MG_ImportComplete'] = False

                self.json_Array = json.loads(globals()['Megascans_DataSet'])

                # Start looping over each asset in the self.json_Array list
                for js in self.json_Array:

                    self.json_data = js

                

                    self.selectedObjects = []
                    
                    self.IOR = 1.45
                    self.assetType = self.json_data["type"]
                    self.assetPath = self.json_data["path"]
                    self.assetID = self.json_data["id"]
                    self.isMetal = bool(self.json_data["category"] == "Metal")
                    # Workflow setup.
                    self.isHighPoly = bool(self.json_data["activeLOD"] == "high")
                    self.activeLOD = self.json_data["activeLOD"]
                    self.minLOD = self.json_data["minLOD"]
                    self.RenderEngine = bpy.context.scene.render.engine.lower() # Get the current render engine. i.e. blender_eevee or cycles
                    self.Workflow = self.json_data.get('pbrWorkflow', 'specular')
                    self.DisplacementSetup = 'regular'
                    self.isCycles = bool(self.RenderEngine == 'cycles')
                    self.isScatterAsset = self.CheckScatterAsset()
                    self.textureList = []
                    self.isBillboard = self.CheckIsBillboard()
                    self.ApplyToSelection = False
                    self.isSpecularWorkflow = True
                    self.isAlembic = False

                    self.NormalSetup = False
                    self.BumpSetup = False

                    if "workflow" in self.json_data.keys():
                        self.isSpecularWorkflow = bool(self.json_data["workflow"] == "specular")

                    if "applyToSelection" in self.json_data.keys():
                        self.ApplyToSelection = bool(self.json_data["applyToSelection"])

                    if (self.isCycles):
                        if(bpy.context.scene.cycles.feature_set == 'EXPERIMENTAL'):
                            self.DisplacementSetup = 'adaptive'
                    
                    texturesListName = "components"
                    if(self.isBillboard):
                        texturesListName = "components"

                    # Get a list of all available texture maps. item[1] returns the map type (albedo, normal, etc...).
                    self.textureTypes = [obj["type"] for obj in self.json_data[texturesListName]]
                    self.textureList = []

                    for obj in self.json_data[texturesListName]:
                        texFormat = obj["format"]
                        texType = obj["type"]
                        texPath = obj["path"]

                        if texType == "displacement" and texFormat != "exr":
                            texDir = os.path.dirname(texPath)
                            texName = os.path.splitext(os.path.basename(texPath))[0]

                            if os.path.exists(os.path.join(texDir, texName + ".exr")):
                                texPath = os.path.join(texDir, texName + ".exr")
                                texFormat = "exr"
                        # Replace diffuse texture type with albedo so we don't have to add more conditions to handle diffuse map.
                        if texType == "diffuse" and "albedo" not in self.textureTypes:
                            texType = "albedo"
                            self.textureTypes.append("albedo")
                            self.textureTypes.remove("diffuse")

                        # Normal / Bump setup checks
                        if texType == "normal":
                            self.NormalSetup = True
                        if texType == "bump":
                            self.BumpSetup = True

                        self.textureList.append((texFormat, texType, texPath))

                    # Create a tuple list of all the 3d meshes  available.
                    # This tuple is composed of (meshFormat, meshPath)
                    self.geometryList = [(obj["format"], obj["path"]) for obj in self.json_data["meshList"]]

                    # Create name of our asset. Multiple conditions are set here
                    # in order to make sure the asset actually has a name and that the name
                    # is short enough for us to use it. We compose a name with the ID otherwise.
                    if "name" in self.json_data.keys():
                        self.assetName = self.json_data["name"].replace(" ", "_")
                    else:
                        self.assetName = os.path.basename(self.json_data["path"]).replace(" ", "_")
                    if len(self.assetName.split("_")) > 2:
                        self.assetName = "_".join(self.assetName.split("_")[:-1])

                    self.materialName = self.assetName + '_' + self.assetID
                    self.colorSpaces = ["sRGB", "Non-Color", "Linear"]

                    # Initialize the import method to start building our shader and import our geometry
                    self.initImportProcess()
                    print("Imported asset from " + self.assetName + " Quixel Bridge")
        
            if len(globals()['MG_AlembicPath']) > 0:
                globals()['MG_ImportComplete'] = True        
        except Exception as e:
            print( "Megascans Plugin Error initializing the import process. Error: ", str(e) )
        
        globals()['Megascans_DataSet'] = None
    
    # this method is used to import the geometry and create the material setup.
    def initImportProcess(self):
        try:
            if len(self.textureList) >= 1:
                
                if(self.ApplyToSelection and self.assetType not in ["3dplant", "3d"]):
                    self.CollectSelectedObjects()

                self.ImportGeometry()
                self.CreateMaterial()
                self.ApplyMaterialToGeometry()
                if(self.isScatterAsset and len(self.selectedObjects) > 1):
                    self.ScatterAssetSetup()
                elif (self.assetType == "3dplant" and len(self.selectedObjects) > 1):
                    self.PlantAssetSetup()

                self.SetupMaterial()

                if self.isAlembic:
                    globals()['MG_Material'].append(self.mat)

        except Exception as e:
            print( "Megascans Plugin Error while importing textures/geometry or setting up material. Error: ", str(e) )

    def ImportGeometry(self):
        try:
            # Import geometry
            abcPaths = []
            if len(self.geometryList) >= 1:
                for obj in self.geometryList:
                    meshPath = obj[1]
                    meshFormat = obj[0]

                    if meshFormat.lower() == "fbx":
                        bpy.ops.import_scene.fbx(filepath=meshPath,global_scale=0.1,axis_forward='Z',axis_up='Y')
                        # 缩放和旋转调整
                        imported_objects = [obj for obj in bpy.context.selected_objects if obj.type == 'MESH']
                        for obj in imported_objects:
                            obj.scale = (0.01, 0.01, 0.01)  # 将模型缩小100倍
                            obj.rotation_euler[0] += 1.5708  # 绕X轴旋转 90 度 (1.5708弧度 ≈ 90度)

                        # get selected objects
                        obj_objects = [ o for o in bpy.context.scene.objects if o.select_get() ]
                        self.selectedObjects += obj_objects

                    elif meshFormat.lower() == "obj":
                        if bpy.app.version < (2, 92, 0):
                            bpy.ops.import_scene.obj(filepath=meshPath, use_split_objects = True, use_split_groups = True, global_clight_size = 1.0)
                        else:
                            bpy.ops.import_scene.obj(filepath=meshPath, use_split_objects = True, use_split_groups = True, global_clamp_size  = 1.0)
                        # get selected objects
                        obj_objects = [ o for o in bpy.context.scene.objects if o.select_get() ]
                        self.selectedObjects += obj_objects

                    elif meshFormat.lower() == "abc":
                        self.isAlembic = True
                        abcPaths.append(meshPath)
            
            if self.isAlembic:
                globals()['MG_AlembicPath'].append(abcPaths)
        except Exception as e:
            print( "Megascans Plugin Error while importing textures/geometry or setting up material. Error: ", str(e) )

    def dump(self, obj):
        for attr in dir(obj):
            print("obj.%s = %r" % (attr, getattr(obj, attr)))

    def CollectSelectedObjects(self):
        try:
            sceneSelectedObjects = [ o for o in bpy.context.scene.objects if o.select_get() ]
            for obj in sceneSelectedObjects:
                if obj.type == "MESH":
                    self.selectedObjects.append(obj)
        except Exception as e:
            print("Megascans Plugin Error::CollectSelectedObjects::", str(e) )

    def ApplyMaterialToGeometry(self):
        for obj in self.selectedObjects:
            # assign material to obj
            obj.active_material = self.mat

    def CheckScatterAsset(self):
        if('scatter' in self.json_data['categories'] or 'scatter' in self.json_data['tags'] or 'cmb_asset' in self.json_data['categories'] or 'cmb_asset' in self.json_data['tags']):
            return True
        return False

    def CheckIsBillboard(self):
        # Use billboard textures if importing the Billboard LOD.
        if(self.assetType == "3dplant"):
            if (self.activeLOD == self.minLOD):
                return True
        return False

    #Add empty parent for the scatter assets.
    def ScatterAssetSetup(self):
        bpy.ops.object.empty_add(type='ARROWS')
        emptyRefList = [ o for o in bpy.context.scene.objects if o.select_get() and o not in self.selectedObjects ]
        for scatterParentObject in emptyRefList:
            scatterParentObject.name = self.assetID + "_" + self.assetName
            for obj in self.selectedObjects:
                obj.parent = scatterParentObject
            break
    
    #Add empty parent for plants.
    def PlantAssetSetup(self):
        bpy.ops.object.empty_add(type='ARROWS')
        emptyRefList = [ o for o in bpy.context.scene.objects if o.select_get() and o not in self.selectedObjects ]
        for plantParentObject in emptyRefList:
            plantParentObject.name = self.assetID + "_" + self.assetName
            for obj in self.selectedObjects:
                obj.parent = plantParentObject
            break

    # def AddModifiersToGeomtry(self, geo_list, mat):
    #     for obj in geo_list:
    #         # assign material to obj
    #         bpy.ops.object.modifier_add(type='SOLIDIFY')

    #Shader setups for all asset types. Some type specific functionality is also handled here.
    def SetupMaterial (self):
        if "albedo" in self.textureTypes:
            if "ao" in self.textureTypes:
                self.CreateTextureMultiplyNode("albedo", "ao", -250, 320, -640, 460, -640, 200, 0, 1, True, "Base Color")
            else:
                self.CreateTextureNode("albedo", -640, 420, 0, True, "Base Color")
        
        if self.isSpecularWorkflow:
            if "specular" in self.textureTypes:
                self.CreateTextureNode("specular", -1150, 200, 0, True, "Specular")
            
            if "gloss" in self.textureTypes:
                glossNode = self.CreateTextureNode("gloss", -1150, -60)
                invertNode = self.CreateGenericNode("ShaderNodeInvert", -250, 60)
                # Add glossNode to invertNode connection
                self.node_group.links.new(invertNode.inputs["Color"], glossNode.outputs["Color"])
                # Connect roughness node to the material parent node.
                self.ConnectNodeToMaterial("Roughness", invertNode)
            elif "roughness" in self.textureTypes:
                self.CreateTextureNode("roughness", -1150, -60, 1, True, "Roughness")
        else:
            if "metalness" in self.textureTypes:
                self.CreateTextureNode("metalness", -1150, 200, 1, True, "Metallic")
            
            if "roughness" in self.textureTypes:
                self.CreateTextureNode("roughness", -1150, -60, 1, True, "Roughness")
            elif "gloss" in self.textureTypes:
                glossNode = self.CreateTextureNode("gloss", -1150, -60)
                invertNode = self.CreateGenericNode("ShaderNodeInvert", -250, 60)
                # Add glossNode to invertNode connection
                self.node_group.links.new(invertNode.inputs["Color"], glossNode.outputs["Color"])
                # Connect roughness node to the material parent node.
                self.node_group.links.new(self.nodes.get(self.parentName).inputs["Roughness"], invertNode.outputs["Color"])
                self.ConnectNodeToMaterial("Roughness", invertNode)
            
        if "opacity" in self.textureTypes:
            self.CreateTextureNode("opacity", -1550, -160, 1, True, "Alpha")
            self.mat.blend_method = 'HASHED'

        if "translucency" in self.textureTypes:
            self.CreateTextureNode("translucency", -1550, -420, 0, True, "Transmission")
        elif "transmission" in self.textureTypes:
            self.CreateTextureNode("transmission", -1550, -420, 1, True, "Transmission")

        # If HIGH POLY selected > use normal_bump and no displacement
        # If LODs selected > use corresponding LODs normal + displacement
        if self.isHighPoly:
            self.BumpSetup = False
        self.CreateNormalNodeSetup(True, "Normal")

        if "displacement" in self.textureTypes and not self.isHighPoly:
            self.CreateDisplacementSetup(True)

    def CreateMaterial(self):
        self.mat = (bpy.data.materials.get( self.materialName ) or bpy.data.materials.new( self.materialName ))
        self.mat.use_nodes = True
        self.nodes = self.mat.node_tree.nodes
        self.parentName = "原理化 BSDF"
        self.materialOutputName = "Material Output"

        self.mat.node_tree.nodes[self.parentName].distribution = 'MULTI_GGX'
        #self.mat.node_tree.nodes[self.parentName].inputs["Metallic"].default_value = 1 if self.isMetal else 0 # Metallic value
        #self.mat.node_tree.nodes[self.parentName].inputs["IOR"].default_value = self.IOR
        
        #self.mat.node_tree.nodes[self.parentName].inputs["Specular"].default_value = 0 Macht kein Sinn! Sieht mit Specular besser aus.
        #self.mat.node_tree.nodes[self.parentName].inputs["Clearcoat"].default_value = 0

        
        #Create Node Group
        self.node_group = bpy.data.node_groups.new(name=self.assetType + "_" + self.materialName, type="ShaderNodeTree")

        #Create Input
        self.node_group_in = self.node_group.nodes.new("NodeGroupInput")
        self.node_group_in.location = (-1500, 0)
        self.node_group.interface.new_socket(name="NodeSocketVector", socket_type="NodeSocketVector")

        #Create Output
        self.node_group_out = self.node_group.nodes.new("NodeGroupOutput")
        self.node_group_out.location = (500, 0)
        #Outputs are assigned later
        
        
        #Instance it in node Tree
        self.node_group_inst = self.mat.node_tree.nodes.new("ShaderNodeGroup")
        self.node_group_inst.node_tree = self.node_group
        self.node_group_inst.location = (-600, 297)
    
        
        self.mappingNode = None

        #Hehe do it anyway
        #if self.isCycles and self.assetType not in ["3d", "3dplant"]:
        # Create mapping node.
        self.mappingNode = self.mat.node_tree.nodes.new("ShaderNodeMapping")
        self.mappingNode.location = (-1000, 389)
        self.mappingNode.vector_type = 'TEXTURE'
        # Create texture coordinate node.
        texCoordNode = self.mat.node_tree.nodes.new("ShaderNodeTexCoord")
        texCoordNode.location = (-1200, 389)
        # Connect texCoordNode to the mappingNode
        self.mat.node_tree.links.new(self.mappingNode.inputs["Vector"], texCoordNode.outputs["UV"])

        #Connect mapping node to Group Input
        self.mat.node_tree.links.new(self.mappingNode.outputs["Vector"], self.node_group_inst.inputs[0])

    def CreateTextureNode(self, textureType, PosX, PosY, colorspace = 1, connectToMaterial = False, materialInputIndex = ""):
        texturePath = self.GetTexturePath(textureType)
        textureNode = self.CreateGenericNode('ShaderNodeTexImage', PosX, PosY)
        textureNode.image = bpy.data.images.load(texturePath)
        textureNode.show_texture = True
        textureNode.image.colorspace_settings.name = self.colorSpaces[colorspace] # "sRGB", "Non-Color", "Linear"
        
        if textureType in ["albedo", "specular", "translucency"]:
            if self.GetTextureFormat(textureType) in "exr":
                textureNode.image.colorspace_settings.name = self.colorSpaces[2] # "sRGB", "Non-Color", "Linear"

        if connectToMaterial:
            self.ConnectNodeToMaterial(materialInputIndex, textureNode)

        #Connect Uvs to Vector unput
        self.node_group.links.new(textureNode.inputs["Vector"], self.node_group_in.outputs[0])

        return textureNode

    def CreateTextureMultiplyNode(self, aTextureType, bTextureType, PosX, PosY, aPosX, aPosY, bPosX, bPosY, aColorspace, bColorspace, connectToMaterial, materialInputIndex):
        #Add Color>MixRGB node, transform it in the node editor, change it's operation to Multiply and finally we colapse the node.
        multiplyNode = self.CreateGenericNode('ShaderNodeMixRGB', PosX, PosY)
        multiplyNode.blend_type = 'MULTIPLY'
        #Setup A and B nodes
        textureNodeA = self.CreateTextureNode(aTextureType, aPosX, aPosY, aColorspace)
        textureNodeB = self.CreateTextureNode(bTextureType, bPosX, bPosY, bColorspace)
        # Conned albedo and ao node to the multiply node.
        self.node_group.links.new(multiplyNode.inputs["Color1"], textureNodeA.outputs["Color"])
        self.node_group.links.new(multiplyNode.inputs["Color2"], textureNodeB.outputs["Color"])

        if connectToMaterial:
            self.ConnectNodeToMaterial(materialInputIndex, multiplyNode)

        return multiplyNode

    def CreateNormalNodeSetup(self, connectToMaterial, materialInputIndex):
        
        bumpNode = None
        normalNode = None
        bumpMapNode = None
        normalMapNode = None

        if self.NormalSetup and self.BumpSetup:
            bumpMapNode = self.CreateTextureNode("bump", -640, -130)
            normalMapNode = self.CreateTextureNode("normal", -1150, -580)
            bumpNode = self.CreateGenericNode("ShaderNodeBump", -250, -170)
            bumpNode.inputs["Strength"].default_value = 0.1
            normalNode = self.CreateGenericNode("ShaderNodeNormalMap", -640, -400)
            # Add normalMapNode to normalNode connection
            self.node_group.links.new(normalNode.inputs["Color"], normalMapNode.outputs["Color"])
            # Add bumpMapNode and normalNode connection to the bumpNode
            self.node_group.links.new(bumpNode.inputs["Height"], bumpMapNode.outputs["Color"])
            if (2, 81, 0) > bpy.app.version:
                self.node_group.links.new(bumpNode.inputs["Normal"], normalNode.outputs["Normal"])
            else:
                self.node_group.links.new(bumpNode.inputs["Normal"], normalNode.outputs["Normal"])
            # Add bumpNode connection to the material parent node
            if connectToMaterial:
                self.ConnectNodeToMaterial(materialInputIndex, bumpNode)
        elif self.NormalSetup:
            normalMapNode = self.CreateTextureNode("normal", -640, -207)
            normalNode = self.CreateGenericNode("ShaderNodeNormalMap", -250, -170)
            # Add normalMapNode to normalNode connection
            self.node_group.links.new(normalNode.inputs["Color"], normalMapNode.outputs["Color"])
            # Add normalNode connection to the material parent node
            if connectToMaterial:
                self.ConnectNodeToMaterial(materialInputIndex, normalNode)
        elif self.BumpSetup:
            bumpMapNode = self.CreateTextureNode("bump", -640, -207)
            bumpNode = self.CreateGenericNode("ShaderNodeBump", -250, -170)
            bumpNode.inputs["Strength"].default_value = 0.1
            # Add bumpMapNode and normalNode connection to the bumpNode
            self.node_group.links.new(bumpNode.inputs["Height"], bumpMapNode.outputs["Color"])
            # Add bumpNode connection to the material parent node
            if connectToMaterial:
                self.ConnectNodeToMaterial(materialInputIndex, bumpNode)

    def CreateDisplacementSetup(self, connectToMaterial):
        #Achtung könnte was kaputt machen was ich hier gemnacht hab SOEREN
        displacementMapNode = self.CreateTextureNode("displacement", -640, -740)
        self.JustConnectToGroupOutput(displacementMapNode, "NodeSocketFloat", "Height")

    def JustConnectToGroupOutput(self, textureNode, socket_type, name):
        #Create the output for the node_group:
        output = self.node_group.interface.new_socket(name=name, in_out="OUTPUT", socket_type=socket_type)

        #Connect to node group
        self.node_group.links.new(textureNode.outputs[0], self.node_group_out.inputs[name])

    def ConnectNodeToMaterial(self, materialInputIndex, textureNode):
        #Get the input in the Principled BSDF:
        #Name for the Material
        bsdf_in = self.nodes.get(self.parentName).inputs[materialInputIndex] 

        # Remove Factor from ID, I actually have no idea why its even standing there
        node_socket_id = bsdf_in.bl_idname.replace("Factor", "")

        self.JustConnectToGroupOutput(textureNode, node_socket_id, bsdf_in.name)

        #Note this doesnt work for duplicate names, but this shouldn't happen here
        self.mat.node_tree.links.new(self.node_group_inst.outputs[bsdf_in.name], bsdf_in)

    def CreateGenericNode(self, nodeName, PosX, PosY):
        genericNode = self.node_group.nodes.new(nodeName)
        genericNode.location = (PosX, PosY)
        return genericNode

    def GetTexturePath(self, textureType):
        for item in self.textureList:
            if item[1] == textureType:
                path = item[2]

                path = path.replace("\\\\", "*")
                path = path.replace("\\", "/")
                path = path.replace("*", "\\\\")

                print(path)
                return path

    def GetTextureFormat(self, textureType):
        for item in self.textureList:
            if item[1] == textureType:
                return item[0].lower()

class ms_Init(threading.Thread):
    
	#Initialize the thread and assign the method (i.e. importer) to be called when it receives JSON data.
    def __init__(self, importer):
        threading.Thread.__init__(self)
        self.importer = importer

	#Start the thread to start listing to the port.
    def run(self):
        try:
            run_livelink = True
            host, port = 'localhost', 28888
            #Making a socket object.
            socket_ = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            #Binding the socket to host and port number mentioned at the start.
            socket_.bind((host, port))

            #Run until the thread starts receiving data.
            while run_livelink:
                socket_.listen(5)
                #Accept connection request.
                client, addr = socket_.accept()
                data = ""
                buffer_size = 4096*2
                #Receive data from the client. 
                data = client.recv(buffer_size)
                if data == b'Bye Megascans':
                    run_livelink = False
                    break

                #If any data is received over the port.
                if data != "":
                    self.TotalData = b""
                    self.TotalData += data #Append the previously received data to the Total Data.
                    #Keep running until the connection is open and we are receiving data.
                    while run_livelink:
                        #Keep receiving data from client.
                        data = client.recv(4096*2)
                        if data == b'Bye Megascans':
                            run_livelink = False
                            break
                        #if we are getting data keep appending it to the Total data.
                        if data : self.TotalData += data
                        else:
                            #Once the data transmission is over call the importer method and send the collected TotalData.
                            self.importer(self.TotalData)
                            break
        except Exception as e:
            print( "Megascans Plugin Error initializing the thread. Error: ", str(e) )

class thread_checker(threading.Thread):
    
	#Initialize the thread and assign the method (i.e. importer) to be called when it receives JSON data.
    def __init__(self):
        threading.Thread.__init__(self)

	#Start the thread to start listing to the port.
    def run(self):
        try:
            run_checker = True
            while run_checker:
                time.sleep(3)
                for i in threading.enumerate():
                    if(i.getName() == "MainThread" and i.is_alive() == False):
                        host, port = 'localhost', 28888
                        s = socket.socket()
                        s.connect((host,port))
                        data = "Bye Megascans"
                        s.send(data.encode())
                        s.close()
                        run_checker = False
                        break
        except Exception as e:
            print( "Megascans Plugin Error initializing thread checker. Error: ", str(e) )
            pass

class MS_Init_LiveLink(bpy.types.Operator):

    bl_idname = "bridge.plugin"
    bl_label = "Megascans Plugin"
    socketCount = 0

    def execute(self, context):

        try:
            globals()['Megascans_DataSet'] = None
            self.thread_ = threading.Thread(target = self.socketMonitor)
            self.thread_.start()
            bpy.app.timers.register(self.newDataMonitor)
            return {'FINISHED'}
        except Exception as e:
            print( "Megascans Plugin Error starting blender plugin. Error: ", str(e) )
            return {"FAILED"}

    def newDataMonitor(self):
        try:
            if globals()['Megascans_DataSet'] != None:
                MS_Init_ImportProcess()
                globals()['Megascans_DataSet'] = None       
        except Exception as e:
            print( "Megascans Plugin Error starting blender plugin (newDataMonitor). Error: ", str(e) )
            return {"FAILED"}
        return 1.0


    def socketMonitor(self):
        try:
            #Making a thread object
            threadedServer = ms_Init(self.importer)
            #Start the newly created thread.
            threadedServer.start()
            #Making a thread object
            thread_checker_ = thread_checker()
            #Start the newly created thread.
            thread_checker_.start()
        except Exception as e:
            print( "Megascans Plugin Error starting blender plugin (socketMonitor). Error: ", str(e) )
            return {"FAILED"}

    def importer (self, recv_data):
        try:
            globals()['Megascans_DataSet'] = recv_data
        except Exception as e:
            print( "Megascans Plugin Error starting blender plugin (importer). Error: ", str(e) )
            return {"FAILED"}

class MS_Init_Abc(bpy.types.Operator):

    bl_idname = "ms_livelink_abc.py"
    bl_label = "Import ABC"

    def execute(self, context):

        try:
            if globals()['MG_ImportComplete']:
                
                assetMeshPaths = globals()['MG_AlembicPath']
                assetMaterials = globals()['MG_Material']
                
                if len(assetMeshPaths) > 0 and len(assetMaterials) > 0:

                    materialIndex = 0
                    old_materials = []
                    for meshPaths in assetMeshPaths:
                        for meshPath in meshPaths:
                            bpy.ops.wm.alembic_import(filepath=meshPath, as_background_job=False)
                            for o in bpy.context.scene.objects:
                                if o.select_get():
                                    old_materials.append(o.active_material)
                                    o.active_material = assetMaterials[materialIndex]
                                    
                        
                        materialIndex += 1
                    
                    for mat in old_materials:
                        try:
                            if mat is not None:
                                bpy.data.materials.remove(mat)
                        except:
                            pass

                    globals()['MG_AlembicPath'] = []
                    globals()['MG_Material'] = []
                    globals()['MG_ImportComplete'] = False

            return {'FINISHED'}
        except Exception as e:
            print( "Megascans Plugin Error starting MS_Init_Abc. Error: ", str(e) )
            return {"CANCELLED"}

@persistent
def load_plugin(scene):
    try:
        bpy.ops.bridge.plugin()
    except Exception as e:
        print( "Bridge Plugin Error::Could not start the plugin. Description: ", str(e) )

def menu_func_import(self, context):
    self.layout.operator(MS_Init_Abc.bl_idname, text="Megascans: Import Alembic Files")

def register():
    if len(bpy.app.handlers.load_post) > 0:
        # Check if trying to register twice.
        if "load_plugin" in bpy.app.handlers.load_post[0].__name__.lower() or load_plugin in bpy.app.handlers.load_post:
            return
    bpy.utils.register_class(MS_Init_LiveLink)
    bpy.utils.register_class(MS_Init_Abc)
    bpy.app.handlers.load_post.append(load_plugin)
    bpy.types.TOPBAR_MT_file_import.append(menu_func_import)

def unregister():
    bpy.types.TOPBAR_MT_file_import.remove(menu_func_import)
    if len(bpy.app.handlers.load_post) > 0:
        # Check if trying to register twice.
        if "load_plugin" in bpy.app.handlers.load_post[0].__name__.lower() or load_plugin in bpy.app.handlers.load_post:
            bpy.app.handlers.load_post.remove(load_plugin)
Reply by Email

相关文章

无厚度_低厚度的zfighting问题
·3192 字·7 分钟
杂谈 Unity Blender Shader 渲染 材质 光照 物理 色彩 故障排除 教程 技巧
Blender导入Unity指南
Houdini:从Mantra切换为Karma的材质工作流切换
·2819 字·6 分钟
杂谈 Shader Houdini VEX 渲染 材质 色彩 故障排除 教程 插件
VEX编程技巧
Blender小技巧:用噪声消除重复纹理
·629 字·2 分钟
杂谈 Blender Shader 渲染 材质 色彩 故障排除 教程 技巧
问题解决方案