diff --git a/io_scene_a3d/BattleMap.py b/io_scene_a3d/BattleMap.py index a5fb3dc..067e370 100644 --- a/io_scene_a3d/BattleMap.py +++ b/io_scene_a3d/BattleMap.py @@ -274,7 +274,7 @@ class Prop: self.position = (0.0, 0.0, 0.0) # Optional - self.groupName = "" + self.groupName = None self.rotation = (0.0, 0.0, 0.0) self.scale = (1.0, 1.0, 1.0) diff --git a/io_scene_a3d/BattleMapBlenderImporter.py b/io_scene_a3d/BattleMapBlenderImporter.py index aefc5e8..e66d0f5 100644 --- a/io_scene_a3d/BattleMapBlenderImporter.py +++ b/io_scene_a3d/BattleMapBlenderImporter.py @@ -26,48 +26,122 @@ import bpy from bpy_extras.image_utils import load_image from bpy_extras.node_shader_utils import PrincipledBSDFWrapper import bmesh +from mathutils import Matrix from .A3D import A3D from .A3DBlenderImporter import A3DBlenderImporter from .BlenderMaterialUtils import addImageTextureToMaterial -class PropLibrary: - propCache = {} +class Prop: + def __init__(self): + self.objects = [] + self.mainObject = None + def loadModel(self, modelPath): + fileExtension = modelPath.split(".")[-1] + if fileExtension == "a3d": + modelData = A3D() + with open(modelPath, "rb") as file: modelData.read(file) + + # Import the model + modelImporter = A3DBlenderImporter(modelData, None, reset_empty_transform=False, try_import_textures=False) + self.objects = modelImporter.importData() + elif fileExtension == "3ds": + bpy.ops.import_scene.max3ds(filepath=modelPath, use_apply_transform=False) + for ob in bpy.context.selectable_objects: + # The imported objects are added to the active collection, remove them + bpy.context.collection.objects.unlink(ob) + + # Correct the origin XXX: this does not work for all cases, investigate more + ob.animation_data_clear() + x, y, z = -ob.location.x, -ob.location.y, -ob.location.z + objectOrigin = Matrix.Translation((x, y, z)) + ob.data.transform(objectOrigin) + ob.location = (0.0, 0.0, 0.0) + + self.objects.append(ob) + else: + raise RuntimeError(f"Unknown model file extension: {fileExtension}") + + # Identify the main parent object + for ob in self.objects: + if ob.parent == None: self.mainObject = ob + if self.mainObject == None: + raise RuntimeError(f"Unable to find the parent object for: {modelPath}") + + def loadSprite(self, propInfo): + spriteInfo = propInfo["sprite"] + + # Create a plane we can use for the sprite + me = bpy.data.meshes.new(propInfo["name"]) + + bm = bmesh.new() + bmesh.ops.create_grid(bm, x_segments=1, y_segments=1, size=spriteInfo["scale"]*100) + bm.to_mesh(me) + bm.free() + + ob = bpy.data.objects.new(me.name, me) + + # Assign data + ob.scale = (spriteInfo["width"], 1.0, spriteInfo["height"]) #XXX: this should involve spriteInfo["scale"] probably? + spriteOrigin = Matrix.Translation((0.0, spriteInfo["originY"], 0.0)) + me.transform(spriteOrigin) + + # Finalise + self.objects.append(ob) + self.mainObject = ob + +class PropLibrary: + propGroups = {} def __init__(self, directory): self.directory = directory - - # Load library json self.libraryInfo = {} - with open(f"{self.directory}/library.json", "r") as file: # XXX: Get platform agnostic way of doing this - self.libraryInfo = load(file) - - print(f"Loaded prop library: {self.libraryInfo["name"]}") - - def getProp(self, name, groupName): - # XXX: Handle group names, this code can only load from the remaster libs - # Check if the prop is cached - if not name in self.propCache: - # Get the prop's info - propGroupInfo = self.libraryInfo["groups"][0] - propInfo = {} - for propInfo in propGroupInfo["props"]: - if propInfo["name"] == name: break - # Load the prop - modelFilePath = f"{self.directory}/{propInfo['mesh']['file']}" # XXX: Get platform agnostic way of doing this - modelData = A3D() - with open(modelFilePath, "rb") as file: - modelData.read(file) - - # Import into blender - modelImporter = A3DBlenderImporter(modelData, self.directory, try_import_textures=False) - ob, = modelImporter.importData() - - self.propCache[name] = ob + # Load library info + with open(f"{self.directory}/library.json", "r") as file: self.libraryInfo = load(file) + print(f"Loaded prop library: " + self.libraryInfo["name"]) + + def getProp(self, propName, groupName): + # Create the prop group if it's not already loaded + if not groupName in self.propGroups: + self.propGroups[groupName] = {} - return self.propCache[name] - + # Load the prop if it's not already loaded + if not propName in self.propGroups[groupName]: + # Find the prop group + groupInfo = None + for group in self.libraryInfo["groups"]: + if group["name"] == groupName: + groupInfo = group + break + if groupInfo == None: + raise RuntimeError(f"Unable to find prop group with name {groupName} in " + self.libraryInfo["name"]) + + # Find the prop + propInfo = None + for prop in groupInfo["props"]: + if prop["name"] == propName: + propInfo = prop + break + if propInfo == None: + raise RuntimeError(f"Unable to find prop with name {propName} in {groupName} from " + self.libraryInfo["name"]) + + # Create the prop + prop = Prop() + meshInfo = propInfo["mesh"] + spriteInfo = propInfo["sprite"] + if meshInfo != None: + modelPath = f"{self.directory}/" + meshInfo["file"] + prop.loadModel(modelPath) + elif spriteInfo != None: + prop.loadSprite(propInfo) + else: + #XXX: Uhhhhhh, empty prop? + pass + self.propGroups[groupName][propName] = prop + + return self.propGroups[groupName][propName] + def getTexture(self, textureName): im = load_image(textureName, self.directory) return im @@ -149,14 +223,22 @@ class BattleMapBlenderImporter: return self.libraryCache[libraryName] + def tryLoadTexture(self, textureName, libraryName): + if libraryName == None: + libraryName = "Remaster" + + propLibrary = self.getPropLibrary(libraryName) + texture = propLibrary.getTexture(f"{textureName}.webp") + return texture + ''' Blender data builders ''' def getBlenderProp(self, propData): # Load prop propLibrary = self.getPropLibrary(propData.libraryName) - propOB = propLibrary.getProp(propData.name, propData.groupName) - propOB = propOB.copy() # We want to use a copy of the prop object + prop = propLibrary.getProp(propData.name, propData.groupName) + propOB = prop.mainObject.copy() # We want to use a copy of the prop object # Assign data propOB.name = f"{propData.name}_{propData.ID}" @@ -167,7 +249,8 @@ class BattleMapBlenderImporter: # Material ma = self.materials[propData.materialID] - propOB.data.materials[0] = ma + if len(propOB.data.materials) != 0: + propOB.data.materials[0] = ma return propOB @@ -264,15 +347,14 @@ class BattleMapBlenderImporter: ma = bpy.data.materials.new(f"{materialData.ID}_{materialData.name}") # Shader specific logic - if materialData.shader == "TankiOnline/SingleTextureShader": + if materialData.shader == "TankiOnline/SingleTextureShader" or materialData.shader == "TankiOnline/SingleTextureShaderWinter": bsdf = PrincipledBSDFWrapper(ma, is_readonly=False, use_nodes=True) bsdf.roughness_set(1.0) bsdf.ior_set(1.0) # Try load texture textureParameter = materialData.textureParameters[0] - propLibrary = self.getPropLibrary("Remaster") - texture = propLibrary.getTexture(f"{textureParameter.textureName}.webp") + texture = self.tryLoadTexture(textureParameter.textureName, textureParameter.libraryName) addImageTextureToMaterial(texture, ma.node_tree) elif materialData.shader == "TankiOnline/SpriteShader": @@ -282,8 +364,7 @@ class BattleMapBlenderImporter: # Try load texture textureParameter = materialData.textureParameters[0] - propLibrary = self.getPropLibrary("Remaster") - texture = propLibrary.getTexture(f"{textureParameter.textureName}.webp") + texture = self.tryLoadTexture(textureParameter.textureName, textureParameter.libraryName) addImageTextureToMaterial(texture, ma.node_tree, linkAlpha=True) elif materialData.shader == "TankiOnline/Terrain": @@ -292,5 +373,7 @@ class BattleMapBlenderImporter: bsdf.roughness_set(1.0) bsdf.ior_set(1.0) bsdf.base_color_set((0.0, 0.0, 0.0)) + else: + pass # Unknown shader return ma \ No newline at end of file