mirror of
https://github.com/MapMakersAndProgrammers/io_scene_a3d.git
synced 2025-10-26 09:59:11 -07:00
Initial version 2 export support
This commit is contained in:
@@ -20,7 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from .IOTools import unpackStream, readNullTerminatedString, calculatePadding
|
from io import BytesIO
|
||||||
|
|
||||||
|
from .IOTools import unpackStream, packStream, readNullTerminatedString, calculatePadding
|
||||||
from . import A3DObjects
|
from . import A3DObjects
|
||||||
|
|
||||||
'''
|
'''
|
||||||
@@ -65,6 +67,23 @@ class A3D:
|
|||||||
self.readRootBlock2(stream)
|
self.readRootBlock2(stream)
|
||||||
elif self.version == 3:
|
elif self.version == 3:
|
||||||
self.readRootBlock3(stream)
|
self.readRootBlock3(stream)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unknown A3D version: {self.version}")
|
||||||
|
|
||||||
|
def write(self, stream, version=2):
|
||||||
|
# Write header data
|
||||||
|
stream.write(A3D_SIGNATURE)
|
||||||
|
packStream("<2H", stream, version, 0)
|
||||||
|
|
||||||
|
# Write root block
|
||||||
|
if version == 1:
|
||||||
|
self.writeRootBlock1(stream)
|
||||||
|
elif version == 2:
|
||||||
|
self.writeRootBlock2(stream)
|
||||||
|
elif version == 3:
|
||||||
|
self.writeRootBlock3(stream)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unknown A3D version: {version} whilst writing A3D")
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Root data blocks
|
Root data blocks
|
||||||
@@ -72,6 +91,9 @@ class A3D:
|
|||||||
def readRootBlock1(self, stream):
|
def readRootBlock1(self, stream):
|
||||||
raise RuntimeError("Version 1 files are not supported yet")
|
raise RuntimeError("Version 1 files are not supported yet")
|
||||||
|
|
||||||
|
def writeRootBlock1(self, stream):
|
||||||
|
raise RuntimeError("Version 1 files are not supported yet")
|
||||||
|
|
||||||
def readRootBlock2(self, stream):
|
def readRootBlock2(self, stream):
|
||||||
# Verify signature
|
# Verify signature
|
||||||
signature, _ = unpackStream("<2I", stream)
|
signature, _ = unpackStream("<2I", stream)
|
||||||
@@ -85,6 +107,21 @@ class A3D:
|
|||||||
self.readTransformBlock2(stream)
|
self.readTransformBlock2(stream)
|
||||||
self.readObjectBlock2(stream)
|
self.readObjectBlock2(stream)
|
||||||
|
|
||||||
|
def writeRootBlock2(self, stream):
|
||||||
|
buffer = BytesIO()
|
||||||
|
|
||||||
|
# Write data to the buffer
|
||||||
|
print("Writing root block")
|
||||||
|
self.writeMaterialBlock2(buffer)
|
||||||
|
self.writeMeshBlock2(buffer)
|
||||||
|
self.writeTransformBlock2(buffer)
|
||||||
|
self.writeObjectBlock2(buffer)
|
||||||
|
|
||||||
|
# Write buffer to stream
|
||||||
|
packStream("<2I", stream, A3D_ROOTBLOCK_SIGNATURE, buffer.tell())
|
||||||
|
buffer.seek(0, 0)
|
||||||
|
stream.write(buffer.read())
|
||||||
|
|
||||||
def readRootBlock3(self, stream):
|
def readRootBlock3(self, stream):
|
||||||
# Verify signature
|
# Verify signature
|
||||||
signature, length = unpackStream("<2I", stream)
|
signature, length = unpackStream("<2I", stream)
|
||||||
@@ -101,6 +138,9 @@ class A3D:
|
|||||||
padding = calculatePadding(length)
|
padding = calculatePadding(length)
|
||||||
stream.read(padding)
|
stream.read(padding)
|
||||||
|
|
||||||
|
def writeRootBlock3(self, stream):
|
||||||
|
raise RuntimeError("Version 3 files are not supported yet")
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Material data blocks
|
Material data blocks
|
||||||
'''
|
'''
|
||||||
@@ -117,6 +157,20 @@ class A3D:
|
|||||||
material.read2(stream)
|
material.read2(stream)
|
||||||
self.materials.append(material)
|
self.materials.append(material)
|
||||||
|
|
||||||
|
def writeMaterialBlock2(self, stream):
|
||||||
|
buffer = BytesIO()
|
||||||
|
|
||||||
|
# Write data to the buffer
|
||||||
|
print("Writing material block")
|
||||||
|
packStream("<I", buffer, len(self.materials))
|
||||||
|
for material in self.materials:
|
||||||
|
material.write2(buffer)
|
||||||
|
|
||||||
|
# Write buffer to stream
|
||||||
|
packStream("<2I", stream, A3D_MATERIALBLOCK_SIGNATURE, buffer.tell())
|
||||||
|
buffer.seek(0, 0)
|
||||||
|
stream.write(buffer.read())
|
||||||
|
|
||||||
def readMaterialBlock3(self, stream):
|
def readMaterialBlock3(self, stream):
|
||||||
# Verify signature
|
# Verify signature
|
||||||
signature, length, materialCount = unpackStream("<3I", stream)
|
signature, length, materialCount = unpackStream("<3I", stream)
|
||||||
@@ -150,6 +204,20 @@ class A3D:
|
|||||||
mesh.read2(stream)
|
mesh.read2(stream)
|
||||||
self.meshes.append(mesh)
|
self.meshes.append(mesh)
|
||||||
|
|
||||||
|
def writeMeshBlock2(self, stream):
|
||||||
|
buffer = BytesIO()
|
||||||
|
|
||||||
|
# Write data to the buffer
|
||||||
|
print("Writing mesh block")
|
||||||
|
packStream("<I", buffer, len(self.meshes))
|
||||||
|
for mesh in self.meshes:
|
||||||
|
mesh.write2(buffer)
|
||||||
|
|
||||||
|
# Write buffer to stream
|
||||||
|
packStream("<2I", stream, A3D_MESHBLOCK_SIGNATURE, buffer.tell())
|
||||||
|
buffer.seek(0, 0)
|
||||||
|
stream.write(buffer.read())
|
||||||
|
|
||||||
def readMeshBlock3(self, stream):
|
def readMeshBlock3(self, stream):
|
||||||
# Verify signature
|
# Verify signature
|
||||||
signature, length, meshCount = unpackStream("<3I", stream)
|
signature, length, meshCount = unpackStream("<3I", stream)
|
||||||
@@ -187,6 +255,22 @@ class A3D:
|
|||||||
parentID, = unpackStream("<i", stream)
|
parentID, = unpackStream("<i", stream)
|
||||||
self.transformParentIDs.append(parentID)
|
self.transformParentIDs.append(parentID)
|
||||||
|
|
||||||
|
def writeTransformBlock2(self, stream):
|
||||||
|
buffer = BytesIO()
|
||||||
|
|
||||||
|
# Write data to the buffer
|
||||||
|
print("Writing transform block")
|
||||||
|
packStream("<I", buffer, len(self.transforms))
|
||||||
|
for transform in self.transforms:
|
||||||
|
transform.write2(buffer)
|
||||||
|
for parentID in self.transformParentIDs:
|
||||||
|
packStream("<i", buffer, parentID)
|
||||||
|
|
||||||
|
# Write buffer to stream
|
||||||
|
packStream("<2I", stream, A3D_TRANSFORMBLOCK_SIGNATURE, buffer.tell())
|
||||||
|
buffer.seek(0, 0)
|
||||||
|
stream.write(buffer.read())
|
||||||
|
|
||||||
def readTransformBlock3(self, stream):
|
def readTransformBlock3(self, stream):
|
||||||
# Verify signature
|
# Verify signature
|
||||||
signature, length, transformCount = unpackStream("<3I", stream)
|
signature, length, transformCount = unpackStream("<3I", stream)
|
||||||
@@ -225,6 +309,20 @@ class A3D:
|
|||||||
objec.read2(stream)
|
objec.read2(stream)
|
||||||
self.objects.append(objec)
|
self.objects.append(objec)
|
||||||
|
|
||||||
|
def writeObjectBlock2(self, stream):
|
||||||
|
buffer = BytesIO()
|
||||||
|
|
||||||
|
# Write data to the buffer
|
||||||
|
print("Writing object block")
|
||||||
|
packStream("<I", buffer, len(self.objects))
|
||||||
|
for objec in self.objects:
|
||||||
|
objec.write2(buffer)
|
||||||
|
|
||||||
|
# Write buffer to stream
|
||||||
|
packStream("<2I", stream, A3D_OBJECTBLOCK_SIGNATURE, buffer.tell())
|
||||||
|
buffer.seek(0, 0)
|
||||||
|
stream.write(buffer.read())
|
||||||
|
|
||||||
def readObjectBlock3(self, stream):
|
def readObjectBlock3(self, stream):
|
||||||
# Verify signature
|
# Verify signature
|
||||||
signature, length, objectCount = unpackStream("<3I", stream)
|
signature, length, objectCount = unpackStream("<3I", stream)
|
||||||
|
|||||||
134
io_scene_a3d/A3DBlenderExporter.py
Normal file
134
io_scene_a3d/A3DBlenderExporter.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
'''
|
||||||
|
Copyright (c) 2025 Pyogenics <https://github.com/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 . import A3DObjects
|
||||||
|
from .A3DObjects import (
|
||||||
|
A3D_VERTEXTYPE_COORDINATE,
|
||||||
|
A3D_VERTEXTYPE_UV1,
|
||||||
|
A3D_VERTEXTYPE_NORMAL1,
|
||||||
|
A3D_VERTEXTYPE_UV2,
|
||||||
|
A3D_VERTEXTYPE_COLOR,
|
||||||
|
A3D_VERTEXTYPE_NORMAL2
|
||||||
|
)
|
||||||
|
|
||||||
|
class A3DBlenderExporter:
|
||||||
|
def __init__(self, modelData, objects):
|
||||||
|
self.modelData = modelData
|
||||||
|
self.objects = objects
|
||||||
|
|
||||||
|
def exportData(self):
|
||||||
|
print("Exporting blender data to A3D")
|
||||||
|
|
||||||
|
# Process objects
|
||||||
|
materials = {}
|
||||||
|
meshes = []
|
||||||
|
transforms = {}
|
||||||
|
objects = []
|
||||||
|
for ob in self.objects:
|
||||||
|
me = ob.data
|
||||||
|
|
||||||
|
# Process materials
|
||||||
|
for ma in me.materials:
|
||||||
|
# Make sure we haven't processed this data block already
|
||||||
|
if ma.name in materials:
|
||||||
|
continue
|
||||||
|
|
||||||
|
materialData = A3DObjects.A3DMaterial()
|
||||||
|
materialData.name = ma.name
|
||||||
|
materialData.diffuseMap = ""
|
||||||
|
colorR, colorG, colorB, _ = ma.diffuse_color
|
||||||
|
materialData.color = (colorR, colorG, colorB)
|
||||||
|
|
||||||
|
materials[ma.name] = materialData
|
||||||
|
# Create mesh
|
||||||
|
mesh = self.buildA3DMesh(me)
|
||||||
|
meshes.append(mesh)
|
||||||
|
# Create transform
|
||||||
|
transform = A3DObjects.A3DTransform()
|
||||||
|
transform.position = ob.location
|
||||||
|
rotationW, rotationX, rotationY, rotationZ = ob.rotation_quaternion
|
||||||
|
transform.rotation = (rotationX, rotationY, rotationZ, rotationW)
|
||||||
|
transform.scale = ob.scale
|
||||||
|
transforms[ob.name] = transform
|
||||||
|
# Create object
|
||||||
|
objec = A3DObjects.A3DObject()
|
||||||
|
objec.name = ob.name
|
||||||
|
objec.meshID = len(meshes) - 1
|
||||||
|
objec.transformID = len(transforms) - 1
|
||||||
|
objects.append(objec)
|
||||||
|
# Create parentIDs
|
||||||
|
transformParentIDs = []
|
||||||
|
for ob in self.objects:
|
||||||
|
parentOB = ob.parent
|
||||||
|
if (parentOB == None) or (ob.name not in transforms):
|
||||||
|
transformParentIDs.append(0) #XXX: this is only for version 2
|
||||||
|
else:
|
||||||
|
parentIndex = list(transforms.keys()).index(ob.name)
|
||||||
|
transformParentIDs.append(parentIndex)
|
||||||
|
|
||||||
|
self.modelData.materials = materials.values()
|
||||||
|
self.modelData.meshes = meshes
|
||||||
|
self.modelData.transforms = transforms.values()
|
||||||
|
self.modelData.transformParentIDs = transformParentIDs
|
||||||
|
self.modelData.objects = objects
|
||||||
|
|
||||||
|
def buildA3DMesh(self, me):
|
||||||
|
mesh = A3DObjects.A3DMesh()
|
||||||
|
mesh.vertexCount = len(me.vertices)
|
||||||
|
|
||||||
|
# Create vertex buffers
|
||||||
|
coordinateBuffer = A3DObjects.A3DVertexBuffer()
|
||||||
|
coordinateBuffer.bufferType = A3D_VERTEXTYPE_COORDINATE
|
||||||
|
normal1Buffer = A3DObjects.A3DVertexBuffer()
|
||||||
|
normal1Buffer.bufferType = A3D_VERTEXTYPE_NORMAL1
|
||||||
|
for vertex in me.vertices:
|
||||||
|
coordinateBuffer.data.append(vertex.co)
|
||||||
|
normal1Buffer.data.append(vertex.normal)
|
||||||
|
uv1Buffer = A3DObjects.A3DVertexBuffer()
|
||||||
|
uv1Buffer.bufferType = A3D_VERTEXTYPE_UV1
|
||||||
|
uv1Data = me.uv_layers[0]
|
||||||
|
for vertex in uv1Data.uv:
|
||||||
|
uv1Buffer.data.append(vertex.vector)
|
||||||
|
mesh.vertexBufferCount = 2 #XXX: We only do coordinate, normal1 and uv1
|
||||||
|
mesh.vertexBuffers = [coordinateBuffer, normal1Buffer]
|
||||||
|
|
||||||
|
# Create submeshes
|
||||||
|
indexArrays = {} # material_index: index array
|
||||||
|
lastMaterialIndex = None
|
||||||
|
for polygon in me.polygons:
|
||||||
|
if polygon.material_index != lastMaterialIndex:
|
||||||
|
indexArrays[polygon.material_index] = []
|
||||||
|
|
||||||
|
indexArrays[polygon.material_index] += polygon.vertices
|
||||||
|
lastMaterialIndex = polygon.material_index
|
||||||
|
submeshes = []
|
||||||
|
for materialID, indexArray in indexArrays.items():
|
||||||
|
submesh = A3DObjects.A3DSubmesh()
|
||||||
|
submesh.indexCount = len(indexArray)
|
||||||
|
submesh.indices = indexArray
|
||||||
|
submesh.materialID = materialID
|
||||||
|
submesh.smoothingGroups = [0] * (len(indexArray)//3) # Just set all faces to 0
|
||||||
|
submeshes.append(submesh)
|
||||||
|
mesh.submeshCount = len(submeshes)
|
||||||
|
mesh.submeshes = submeshes
|
||||||
|
|
||||||
|
return mesh
|
||||||
@@ -20,7 +20,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from .IOTools import unpackStream, readNullTerminatedString, readLengthPrefixedString, calculatePadding
|
from .IOTools import unpackStream, packStream, readNullTerminatedString, writeNullTerminatedString, readLengthPrefixedString, calculatePadding
|
||||||
|
|
||||||
class A3DMaterial:
|
class A3DMaterial:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -35,6 +35,12 @@ class A3DMaterial:
|
|||||||
|
|
||||||
print(f"[A3DMaterial name: {self.name} color: {self.color} diffuse map: {self.diffuseMap}]")
|
print(f"[A3DMaterial name: {self.name} color: {self.color} diffuse map: {self.diffuseMap}]")
|
||||||
|
|
||||||
|
def write2(self, stream):
|
||||||
|
writeNullTerminatedString(stream, self.name)
|
||||||
|
colorR, colorG, colorB = self.color
|
||||||
|
packStream("<3f", stream, colorR, colorG, colorB)
|
||||||
|
writeNullTerminatedString(stream, self.diffuseMap)
|
||||||
|
|
||||||
def read3(self, stream):
|
def read3(self, stream):
|
||||||
self.name = readLengthPrefixedString(stream)
|
self.name = readLengthPrefixedString(stream)
|
||||||
self.color = unpackStream("<3f", stream)
|
self.color = unpackStream("<3f", stream)
|
||||||
@@ -71,6 +77,15 @@ class A3DMesh:
|
|||||||
|
|
||||||
print(f"[A3DMesh name: {self.name} bbox max: {self.bboxMax} bbox min: {self.bboxMin} vertex buffers: {len(self.vertexBuffers)} submeshes: {len(self.submeshes)}]")
|
print(f"[A3DMesh name: {self.name} bbox max: {self.bboxMax} bbox min: {self.bboxMin} vertex buffers: {len(self.vertexBuffers)} submeshes: {len(self.submeshes)}]")
|
||||||
|
|
||||||
|
def write2(self, stream):
|
||||||
|
packStream("<2I", stream, self.vertexCount, self.vertexBufferCount)
|
||||||
|
for vertexBuffer in self.vertexBuffers:
|
||||||
|
vertexBuffer.write2(stream)
|
||||||
|
|
||||||
|
packStream("<I", stream, self.submeshCount)
|
||||||
|
for submesh in self.submeshes:
|
||||||
|
submesh.write2(stream)
|
||||||
|
|
||||||
def read3(self, stream):
|
def read3(self, stream):
|
||||||
# Read mesh info
|
# Read mesh info
|
||||||
self.name = readLengthPrefixedString(stream)
|
self.name = readLengthPrefixedString(stream)
|
||||||
@@ -126,6 +141,12 @@ class A3DVertexBuffer:
|
|||||||
|
|
||||||
print(f"[A3DVertexBuffer data: {len(self.data)} buffer type: {self.bufferType}]")
|
print(f"[A3DVertexBuffer data: {len(self.data)} buffer type: {self.bufferType}]")
|
||||||
|
|
||||||
|
def write2(self, stream):
|
||||||
|
packStream("<I", stream, self.bufferType)
|
||||||
|
for vertex in self.data:
|
||||||
|
for vertexElement in vertex:
|
||||||
|
packStream("<f", stream, vertexElement)
|
||||||
|
|
||||||
class A3DSubmesh:
|
class A3DSubmesh:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.indices = []
|
self.indices = []
|
||||||
@@ -135,14 +156,23 @@ class A3DSubmesh:
|
|||||||
self.indexCount = 0
|
self.indexCount = 0
|
||||||
|
|
||||||
def read2(self, stream):
|
def read2(self, stream):
|
||||||
self.indexCount, = unpackStream("<I", stream) # This is just the face count so multiply it by 3
|
faceCount, = unpackStream("<I", stream)
|
||||||
self.indexCount *= 3
|
self.indexCount = faceCount * 3
|
||||||
self.indices = list(unpackStream(f"<{self.indexCount}H", stream))
|
self.indices = list(unpackStream(f"<{self.indexCount}H", stream))
|
||||||
self.smoothingGroups = list(unpackStream(f"<{self.indexCount//3}I", stream))
|
self.smoothingGroups = list(unpackStream(f"<{self.indexCount//3}I", stream))
|
||||||
self.materialID, = unpackStream("<H", stream)
|
self.materialID, = unpackStream("<H", stream)
|
||||||
|
|
||||||
print(f"[A3DSubmesh indices: {len(self.indices)} smoothing groups: {len(self.smoothingGroups)} materialID: {self.materialID}]")
|
print(f"[A3DSubmesh indices: {len(self.indices)} smoothing groups: {len(self.smoothingGroups)} materialID: {self.materialID}]")
|
||||||
|
|
||||||
|
def write2(self, stream):
|
||||||
|
faceCount = self.indexCount // 3
|
||||||
|
packStream("<I", stream, faceCount)
|
||||||
|
for index in self.indices:
|
||||||
|
packStream("<H", stream, index)
|
||||||
|
for smoothingGroup in self.smoothingGroups:
|
||||||
|
packStream("<I", stream, smoothingGroup)
|
||||||
|
packStream("<H", stream, self.materialID)
|
||||||
|
|
||||||
def read3(self, stream):
|
def read3(self, stream):
|
||||||
# Read indices
|
# Read indices
|
||||||
self.indexCount, = unpackStream("<I", stream)
|
self.indexCount, = unpackStream("<I", stream)
|
||||||
@@ -168,6 +198,14 @@ class A3DTransform:
|
|||||||
|
|
||||||
print(f"[A3DTransform position: {self.position} rotation: {self.rotation} scale: {self.scale}]")
|
print(f"[A3DTransform position: {self.position} rotation: {self.rotation} scale: {self.scale}]")
|
||||||
|
|
||||||
|
def write2(self, stream):
|
||||||
|
positionX, positionY, positionZ = self.position
|
||||||
|
packStream("<3f", stream, positionX, positionY, positionZ)
|
||||||
|
rotationX, rotationY, rotationZ, rotationW = self.rotation
|
||||||
|
packStream("<4f", stream, rotationX, rotationY, rotationZ, rotationW)
|
||||||
|
scaleX, scaleY, scaleZ = self.scale
|
||||||
|
packStream("<3f", stream, scaleX, scaleY, scaleZ)
|
||||||
|
|
||||||
def read3(self, stream):
|
def read3(self, stream):
|
||||||
self.name = readLengthPrefixedString(stream)
|
self.name = readLengthPrefixedString(stream)
|
||||||
self.position = unpackStream("<3f", stream)
|
self.position = unpackStream("<3f", stream)
|
||||||
@@ -191,6 +229,10 @@ class A3DObject:
|
|||||||
|
|
||||||
print(f"[A3DObject name: {self.name} meshID: {self.meshID} transformID: {self.transformID} materialIDs: {len(self.materialIDs)}]")
|
print(f"[A3DObject name: {self.name} meshID: {self.meshID} transformID: {self.transformID} materialIDs: {len(self.materialIDs)}]")
|
||||||
|
|
||||||
|
def write2(self, stream):
|
||||||
|
writeNullTerminatedString(stream, self.name)
|
||||||
|
packStream("<2I", stream, self.meshID, self.transformID)
|
||||||
|
|
||||||
def read3(self, stream):
|
def read3(self, stream):
|
||||||
self.meshID, self.transformID, self.materialCount = unpackStream("<3I", stream)
|
self.meshID, self.transformID, self.materialCount = unpackStream("<3I", stream)
|
||||||
|
|
||||||
|
|||||||
@@ -20,20 +20,29 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|||||||
SOFTWARE.
|
SOFTWARE.
|
||||||
'''
|
'''
|
||||||
|
|
||||||
from struct import unpack, calcsize
|
from struct import unpack, pack, calcsize
|
||||||
|
|
||||||
def unpackStream(format, stream):
|
def unpackStream(format, stream):
|
||||||
size = calcsize(format)
|
size = calcsize(format)
|
||||||
data = stream.read(size)
|
data = stream.read(size)
|
||||||
return unpack(format, data)
|
return unpack(format, data)
|
||||||
|
|
||||||
|
def packStream(format, stream, *data):
|
||||||
|
packedData = pack(format, *data)
|
||||||
|
stream.write(packedData)
|
||||||
|
|
||||||
def readNullTerminatedString(stream):
|
def readNullTerminatedString(stream):
|
||||||
string = b""
|
string = b""
|
||||||
char = stream.read(1)
|
char = stream.read(1)
|
||||||
while char != b"\x00":
|
while char != b"\x00":
|
||||||
string += char
|
string += char
|
||||||
char = stream.read(1)
|
char = stream.read(1)
|
||||||
return string.decode("utf8", errors="ignore")
|
return string.decode("utf-8", errors="ignore")
|
||||||
|
|
||||||
|
def writeNullTerminatedString(stream, string):
|
||||||
|
string = string.encode("utf-8")
|
||||||
|
stream.write(string)
|
||||||
|
stream.write(b"\x00")
|
||||||
|
|
||||||
def calculatePadding(length):
|
def calculatePadding(length):
|
||||||
# (it basically works with rounding)
|
# (it basically works with rounding)
|
||||||
|
|||||||
@@ -23,10 +23,11 @@ SOFTWARE.
|
|||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Operator, OperatorFileListElement, AddonPreferences
|
from bpy.types import Operator, OperatorFileListElement, AddonPreferences
|
||||||
from bpy.props import StringProperty, BoolProperty, CollectionProperty, FloatProperty
|
from bpy.props import StringProperty, BoolProperty, CollectionProperty, FloatProperty
|
||||||
from bpy_extras.io_utils import ImportHelper
|
from bpy_extras.io_utils import ImportHelper, ExportHelper
|
||||||
|
|
||||||
from .A3D import A3D
|
from .A3D import A3D
|
||||||
from .A3DBlenderImporter import A3DBlenderImporter
|
from .A3DBlenderImporter import A3DBlenderImporter
|
||||||
|
from .A3DBlenderExporter import A3DBlenderExporter
|
||||||
from .BattleMap import BattleMap
|
from .BattleMap import BattleMap
|
||||||
from .BattleMapBlenderImporter import BattleMapBlenderImporter
|
from .BattleMapBlenderImporter import BattleMapBlenderImporter
|
||||||
from .LightmapData import LightmapData
|
from .LightmapData import LightmapData
|
||||||
@@ -99,6 +100,34 @@ class ImportA3D(Operator, ImportHelper):
|
|||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
class ExportA3D(Operator, ExportHelper):
|
||||||
|
bl_idname = "export_scene.alternativa"
|
||||||
|
bl_label = "Export A3D"
|
||||||
|
bl_description = "Export an A3D model"
|
||||||
|
bl_options = {'PRESET', 'UNDO'}
|
||||||
|
|
||||||
|
filter_glob: StringProperty(default="*.a3d", options={'HIDDEN'})
|
||||||
|
filename_ext: StringProperty(default=".a3d", options={'HIDDEN'})
|
||||||
|
|
||||||
|
def draw(self, context):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return ExportHelper.invoke(self, context, event)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
print(f"Exporting blender data to {self.filepath}")
|
||||||
|
|
||||||
|
modelData = A3D()
|
||||||
|
modelExporter = A3DBlenderExporter(modelData, bpy.context.selected_objects)
|
||||||
|
modelExporter.exportData()
|
||||||
|
|
||||||
|
# Write file
|
||||||
|
with open(self.filepath, "wb") as file:
|
||||||
|
modelData.write(file, version=2)
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
class ImportBattleMap(Operator, ImportHelper):
|
class ImportBattleMap(Operator, ImportHelper):
|
||||||
bl_idname = "import_scene.tanki_battlemap"
|
bl_idname = "import_scene.tanki_battlemap"
|
||||||
bl_label = "Import map"
|
bl_label = "Import map"
|
||||||
@@ -180,6 +209,9 @@ def import_panel_options_battlemap(layout, operator):
|
|||||||
def menu_func_import_a3d(self, context):
|
def menu_func_import_a3d(self, context):
|
||||||
self.layout.operator(ImportA3D.bl_idname, text="Alternativa3D HTML5 (.a3d)")
|
self.layout.operator(ImportA3D.bl_idname, text="Alternativa3D HTML5 (.a3d)")
|
||||||
|
|
||||||
|
def menu_func_export_a3d(self, context):
|
||||||
|
self.layout.operator(ExportA3D.bl_idname, text="Alternativa3D HTML5 (.a3d)")
|
||||||
|
|
||||||
def menu_func_import_battlemap(self, context):
|
def menu_func_import_battlemap(self, context):
|
||||||
self.layout.operator(ImportBattleMap.bl_idname, text="Tanki Online BattleMap (.bin)")
|
self.layout.operator(ImportBattleMap.bl_idname, text="Tanki Online BattleMap (.bin)")
|
||||||
|
|
||||||
@@ -189,6 +221,7 @@ Registration
|
|||||||
classes = [
|
classes = [
|
||||||
Preferences,
|
Preferences,
|
||||||
ImportA3D,
|
ImportA3D,
|
||||||
|
ExportA3D,
|
||||||
ImportBattleMap
|
ImportBattleMap
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -196,12 +229,14 @@ def register():
|
|||||||
for c in classes:
|
for c in classes:
|
||||||
bpy.utils.register_class(c)
|
bpy.utils.register_class(c)
|
||||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import_a3d)
|
bpy.types.TOPBAR_MT_file_import.append(menu_func_import_a3d)
|
||||||
|
bpy.types.TOPBAR_MT_file_export.append(menu_func_export_a3d)
|
||||||
bpy.types.TOPBAR_MT_file_import.append(menu_func_import_battlemap)
|
bpy.types.TOPBAR_MT_file_import.append(menu_func_import_battlemap)
|
||||||
|
|
||||||
def unregister():
|
def unregister():
|
||||||
for c in classes:
|
for c in classes:
|
||||||
bpy.utils.unregister_class(c)
|
bpy.utils.unregister_class(c)
|
||||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_a3d)
|
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_a3d)
|
||||||
|
bpy.types.TOPBAR_MT_file_export.remove(menu_func_export_a3d)
|
||||||
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_battlemap)
|
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_battlemap)
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
Reference in New Issue
Block a user