diff --git a/io_scene_a3d/BattleMap.py b/io_scene_a3d/BattleMap.py index 4a337e7..de2288e 100644 --- a/io_scene_a3d/BattleMap.py +++ b/io_scene_a3d/BattleMap.py @@ -36,12 +36,13 @@ class AtlasRect: self.y = 0 def read(self, stream, optionalMask): - print("Read AtlasRect") self.height, = unpackStream(">I", stream) self.libraryName = AlternativaProtocol.readString(stream) self.name = AlternativaProtocol.readString(stream) self.width, self.x, self.y = unpackStream(">3I", stream) + print(f"[AtlasRect height: {self.height} libraryName: {self.libraryName} name: {self.name} width: {self.width} x: {self.x} y: {self.y}]") + class CollisionBox: def __init__(self): self.position = (0.0, 0.0, 0.0) @@ -49,10 +50,11 @@ class CollisionBox: self.size = (0.0, 0.0, 0.0) def read(self, stream, optionalMask): - print("Read CollisionBox") self.position = unpackStream(">3f", stream) self.rotation = unpackStream(">3f", stream) self.size = unpackStream(">3f", stream) + + # print(f"[CollisionBox position: {self.position} rotation: {self.rotation} size: {self.size}]") class CollisionPlane: def __init__(self): @@ -62,11 +64,12 @@ class CollisionPlane: self.width = 0.0 def read(self, stream, optionalMask): - print("Read CollisionPlane") self.length, = unpackStream(">d", stream) self.position = unpackStream(">3f", stream) self.rotation = unpackStream(">3f", stream) self.width, = unpackStream(">d", stream) + + # print(f"[CollisionPlane lenght: {self.length} position: {self.position} rotation: {self.rotation} width: {self.width}]") class CollisionTriangle: def __init__(self): @@ -78,13 +81,14 @@ class CollisionTriangle: self.v2 = (0.0, 0.0, 0.0) def read(self, stream, optionalMask): - print("Read CollisionTriangle") self.length, = unpackStream(">d", stream) self.position = unpackStream(">3f", stream) self.rotation = unpackStream(">3f", stream) self.v0 = unpackStream(">3f", stream) self.v1 = unpackStream(">3f", stream) self.v2 = unpackStream(">3f", stream) + + # print(f"[CollisionTriangle length: {self.length} position: {self.position} rotation: {self.rotation} v0: {self.v0} v1: {self.v1} v2: {self.v2}]") class ScalarParameter: def __init__(self): @@ -92,7 +96,6 @@ class ScalarParameter: self.value = 0.0 def read(self, stream, optionalMask): - print("Read ScalarParameters") self.name = AlternativaProtocol.readString(stream) self.value, = unpackStream(">f", stream) @@ -105,7 +108,6 @@ class TextureParameter: self.libraryName = None def read(self, stream, optionalMask): - print("Read TextureParameter") if optionalMask.getOptional(): self.libraryName = AlternativaProtocol.readString(stream) self.name = AlternativaProtocol.readString(stream) @@ -117,7 +119,6 @@ class Vector2Parameter: self.value = (0.0, 0.0) def __init__(self, stream, optionalMask): - print("Read Vector2Parameters") self.name = AlternativaProtocol.readString(stream) self.value = unpackStream(">2f", stream) @@ -127,7 +128,6 @@ class Vector3Parameter: self.value = (0.0, 0.0, 0.0) def __init__(self, stream, optionalMask): - print("Read Vector3Parameters") self.name = AlternativaProtocol.readString(stream) self.value = unpackStream(">3f", stream) @@ -137,7 +137,6 @@ class Vector4Parameter: self.value = (0.0, 0.0, 0.0, 0.0) def read(self, stream, optionalMask): - print("Read Vector4Parameters") self.name = AlternativaProtocol.readString(stream) self.value = unpackStream(">4f", stream) @@ -243,7 +242,6 @@ class SpawnPoint: self.type = 0 def read(self, stream, optionalMask): - print("Read SpawnPoint") self.position = unpackStream(">3f", stream) self.rotation = unpackStream(">3f", stream) self.type, = unpackStream(">I", stream) @@ -259,10 +257,9 @@ class Prop: # Optional self.groupName = "" self.rotation = (0.0, 0.0, 0.0) - self.scale = (0.0, 0.0, 0.0) + self.scale = (1.0, 1.0, 1.0) def read(self, stream, optionalMask): - print(f"Read Prop") if optionalMask.getOptional(): self.groupName = AlternativaProtocol.readString(stream) self.ID, = unpackStream(">I", stream) @@ -305,11 +302,6 @@ class BattleMap: # Read packet packet = AlternativaProtocol.readPacket(stream) - with open("packet.bin", "wb") as packetFile: - packetFile.write( - packet.read() - ) - packet.seek(0) optionalMask = AlternativaProtocol.OptionalMask() optionalMask.read(packet) diff --git a/io_scene_a3d/BattleMapBlenderImporter.py b/io_scene_a3d/BattleMapBlenderImporter.py new file mode 100644 index 0000000..9148da4 --- /dev/null +++ b/io_scene_a3d/BattleMapBlenderImporter.py @@ -0,0 +1,105 @@ +''' +Copyright (c) 2025 Pyogenics + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +''' + +from json import load + +from .A3D import A3D +from .A3DBlenderImporter import A3DBlenderImporter + +class PropLibrary: + propCache = {} + + 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 + + return self.propCache[name] + + +class BattleMapBlenderImporter: + # Allows subsequent map loads to be faster + libraryCache = {} + + def __init__(self, mapData, propLibrarySourcePath): + self.mapData = mapData + self.propLibrarySourcePath = propLibrarySourcePath + + def importData(self): + print("Importing BattleMap data into blender") + + # Load props + propObjects = [] + for propData in self.mapData.staticGeometry: + ob = self.getBlenderProp(propData) + propObjects.append(ob) + + return propObjects + + def getBlenderProp(self, propData): + # First check if we've already loaded the required prop library + if not propData.libraryName in self.libraryCache: + # Load the proplib + libraryPath = f"{self.propLibrarySourcePath}/{propData.libraryName}" # XXX: Get platform agnostic way of doing this + library = PropLibrary(libraryPath) + self.libraryCache[propData.libraryName] = library + + # Load prop + propLibrary = self.libraryCache[propData.libraryName] + propOB = propLibrary.getProp(propData.name, propData.groupName) + propOB = propOB.copy() # We want to use a copy of the prop object + + # Assign data + propOB.name = f"{propData.name}_{propData.ID}" + propOB.location = propData.position + propOB.rotation_mode = "XYZ" + propOB.rotation_euler = propData.rotation + propOB.scale = propData.scale + + return propOB \ No newline at end of file diff --git a/io_scene_a3d/__init__.py b/io_scene_a3d/__init__.py index bb3e346..89a762e 100644 --- a/io_scene_a3d/__init__.py +++ b/io_scene_a3d/__init__.py @@ -21,16 +21,29 @@ SOFTWARE. ''' import bpy -from bpy.types import Operator, OperatorFileListElement +from bpy.types import Operator, OperatorFileListElement, AddonPreferences from bpy.props import StringProperty, BoolProperty, CollectionProperty from bpy_extras.io_utils import ImportHelper from .A3D import A3D from .A3DBlenderImporter import A3DBlenderImporter from .BattleMap import BattleMap +from .BattleMapBlenderImporter import BattleMapBlenderImporter from glob import glob +''' +Addon preferences +''' +class Preferences(AddonPreferences): + bl_idname = __package__ + + propLibrarySourcePath: StringProperty(name="Prop library source path", subtype='FILE_PATH') + + def draw(self, context): + layout = self.layout + layout.prop(self, "propLibrarySourcePath") + ''' Operators ''' @@ -100,6 +113,16 @@ class ImportBattleMap(Operator, ImportHelper): with open(self.filepath, "rb") as file: mapData.read(file) + # Import data into blender + preferences = context.preferences.addons[__package__].preferences + mapImporter = BattleMapBlenderImporter(mapData, preferences.propLibrarySourcePath) + objects = mapImporter.importData() + + # Link objects + collection = bpy.context.collection + for ob in objects: + collection.objects.link(ob) + return {"FINISHED"} ''' @@ -123,6 +146,7 @@ def menu_func_import_battlemap(self, context): Registration ''' classes = [ + Preferences, ImportA3D, ImportBattleMap ]