2 Commits

Author SHA1 Message Date
Pyogenics
542a4c9298 Hacky models.a3d support 2025-10-07 21:16:30 +01:00
Pyogenics
b07596676d Add notice for the new content guidelines 2025-07-13 18:14:42 +00:00
3 changed files with 43 additions and 7 deletions

View File

@@ -1,6 +1,10 @@
# io_scene_a3d
Blender plugin to import the proprietary model format `A3D` used by the game [Tanki Online](https://tankionline.com/en/) from [Alternativa Games](https://alternativa.games/), it is not compatible with older the formats used by the flash based Alternativa3D engine (see [this plugin by Davide Jones](https://github.com/davidejones/alternativa3d_tools) instead). The plugin can also import Tanki Online binary format maps: `map.bin`, both legacy maps and remaster maps work.
## Legal
Any ripped assets are subject to the Tanki Online [Fan Content Guidelines](https://en.tankiwiki.com/Creating_Fan_Content_Guide) and must only be used for producing fan content like fan art.
> Using original models, maps, or other in-game assets outside the scope of Tanki Online gameplay or fan art is not allowed.
## Installation
### Requirements: Blender version 4.2+
### Optional: io_scene_3ds plugin for importing legacy maps (non remaster)
@@ -43,4 +47,4 @@ The plugin also supports `lightmapdata` files that come with remaster maps, thes
- Lightprobes (not imported)
## File format
Check the wiki for file format documentation.
Check the wiki for file format documentation.

View File

@@ -36,8 +36,9 @@ class BattleMapBlenderImporter:
# Allows subsequent map loads to be faster
libraryCache = {}
def __init__(self, mapData, lightmapData, propLibrarySourcePath, map_scale_factor=0.01, import_static_geom=True, import_collision_geom=False, import_spawn_points=False, import_lightmapdata=False):
def __init__(self, mapData, mapDirectory, lightmapData, propLibrarySourcePath, map_scale_factor=0.01, import_static_geom=True, import_collision_geom=False, import_spawn_points=False, import_lightmapdata=False):
self.mapData = mapData
self.mapDirectory = mapDirectory
self.lightmapData = lightmapData
self.propLibrarySourcePath = propLibrarySourcePath
self.map_scale_factor = map_scale_factor
@@ -51,6 +52,7 @@ class BattleMapBlenderImporter:
self.collisionBoxMesh = None
self.materials = {}
self.modelsA3D = {}
def importData(self):
print("Importing BattleMap data into blender")
@@ -172,8 +174,11 @@ class BattleMapBlenderImporter:
def tryLoadTexture(self, textureName, libraryName):
if libraryName == None:
# For some reason Remaster proplib is alwaus marked as None? This is not true for the ny2024 remaster prop lib though
# For some reason Remaster proplib is always marked as None? This is not true for the ny2024 remaster prop lib though
libraryName = "Remaster"
elif libraryName == "":
# This is only true for a material that is using the atlas in case of models.a3d
return None
propLibrary = self.getPropLibrary(libraryName)
texture = propLibrary.getTexture(f"{textureName}.webp")
@@ -182,10 +187,33 @@ class BattleMapBlenderImporter:
'''
Blender data builders
'''
def getPropFromModelsA3D(self, propName):
if len(self.modelsA3D) == 0:
# Load models.a3d
modelData = A3D()
try:
with open(f"{self.mapDirectory}/models.a3d", "rb") as f: modelData.read(f)
except: return None
modelImporter = A3DBlenderImporter(modelData, None, reset_empty_transform=False, try_import_textures=False)
modelObjects = modelImporter.importData()
# Create props
for ob in modelObjects:
prop = Prop()
prop.createFromObject(ob)
self.modelsA3D[ob.name] = prop
return self.modelsA3D[propName]
def getBlenderProp(self, propData):
# Load prop
propLibrary = self.getPropLibrary(propData.libraryName)
prop = propLibrary.getProp(propData.name, propData.groupName)
prop = None
if propData.libraryName == "":
# Load prop from models.a3d first, we prefer it over the library where possible
prop = self.getPropFromModelsA3D(propData.name)
if prop == None:
# Load prop through libraries if we can't find it in models.a3d
propLibrary = self.getPropLibrary(propData.libraryName)
prop = propLibrary.getProp(propData.name, propData.groupName)
propOB = prop.mainObject.copy() # We want to use a copy of the prop object
# Assign data
@@ -390,6 +418,10 @@ class Prop:
self.objects = []
self.mainObject = None
def createFromObject(self, ob):
self.objects.append(ob)
self.mainObject = ob
def loadModel(self, modelPath):
fileExtension = modelPath.split(".")[-1].lower()
if fileExtension == "a3d":

View File

@@ -182,7 +182,7 @@ class ImportBattleMap(Operator, ImportHelper):
preferences = context.preferences.addons[__package__].preferences
if not isdir(preferences.propLibrarySourcePath):
raise RuntimeError("Please set a valid prop library folder in addon properties!")
mapImporter = BattleMapBlenderImporter(mapData, lightmapData, preferences.propLibrarySourcePath, self.map_scale_factor, self.import_static_geom, self.import_collision_geom, self.import_spawn_points, self.import_lightmapdata)
mapImporter = BattleMapBlenderImporter(mapData, self.directory, lightmapData, preferences.propLibrarySourcePath, self.map_scale_factor, self.import_static_geom, self.import_collision_geom, self.import_spawn_points, self.import_lightmapdata)
objects = mapImporter.importData()
# Link objects