diff --git a/io_scene_a3d/A3D.py b/io_scene_a3d/A3D.py index 3c96d8e..97b685b 100644 --- a/io_scene_a3d/A3D.py +++ b/io_scene_a3d/A3D.py @@ -20,7 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' -from .IOTools import unpackStream, readNullTerminatedString, calculatePadding +from io import BytesIO + +from .IOTools import unpackStream, packStream, readNullTerminatedString, calculatePadding from . import A3DObjects ''' @@ -65,6 +67,23 @@ class A3D: self.readRootBlock2(stream) elif self.version == 3: 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 @@ -72,6 +91,9 @@ class A3D: def readRootBlock1(self, stream): 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): # Verify signature signature, _ = unpackStream("<2I", stream) @@ -85,6 +107,21 @@ class A3D: self.readTransformBlock2(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): # Verify signature signature, length = unpackStream("<2I", stream) @@ -101,6 +138,9 @@ class A3D: padding = calculatePadding(length) stream.read(padding) + def writeRootBlock3(self, stream): + raise RuntimeError("Version 3 files are not supported yet") + ''' Material data blocks ''' @@ -117,6 +157,20 @@ class A3D: material.read2(stream) self.materials.append(material) + def writeMaterialBlock2(self, stream): + buffer = BytesIO() + + # Write data to the buffer + print("Writing material block") + packStream(" + +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 diff --git a/io_scene_a3d/A3DObjects.py b/io_scene_a3d/A3DObjects.py index dac8ad5..ba1d7cf 100644 --- a/io_scene_a3d/A3DObjects.py +++ b/io_scene_a3d/A3DObjects.py @@ -20,7 +20,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ''' -from .IOTools import unpackStream, readNullTerminatedString, readLengthPrefixedString, calculatePadding +from .IOTools import unpackStream, packStream, readNullTerminatedString, writeNullTerminatedString, readLengthPrefixedString, calculatePadding class A3DMaterial: def __init__(self): @@ -35,6 +35,12 @@ class A3DMaterial: 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): self.name = readLengthPrefixedString(stream) self.color = unpackStream("<3f", stream) @@ -70,7 +76,16 @@ class A3DMesh: self.submeshes.append(submesh) 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("