mirror of
https://github.com/MapMakersAndProgrammers/io_scene_a3d.git
synced 2025-10-26 09:59:11 -07:00
Add initial sources
This commit is contained in:
@@ -1,2 +1,7 @@
|
|||||||
# io_scene_a3d
|
# io_scene_a3d
|
||||||
Blender plugin to read modern A3D3.x models
|
Blender plugin to load A3D 3.2 and 3.3 models (3.1 not supported), 3.2 is most complete 3.3 is not so complete; this code will eventually be merged into the alternativa3d_tools github repo.
|
||||||
|
|
||||||
|
The code can read all the A3D3 data but not all of it is imported yet, some material data + transforms + some vertex data and the codebase could use a cleanup.
|
||||||
|
|
||||||
|
https://github.com/davidejones/alternativa3d_tools/issues/9
|
||||||
|
https://github.com/Pyogenics
|
||||||
40
io_scene_a3d/A3D3Shared.py
Normal file
40
io_scene_a3d/A3D3Shared.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
'''
|
||||||
|
Copyright (C) 2024 Pyogenics <https://www.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.
|
||||||
|
'''
|
||||||
|
|
||||||
|
class A3D3Mesh:
|
||||||
|
def __init__(self, coordinates, uv1, normals, uv2, colors, unknown, submeshes):
|
||||||
|
# Vertex data
|
||||||
|
self.coordinates = coordinates
|
||||||
|
self.uv1 = uv1
|
||||||
|
self.normals = normals
|
||||||
|
self.uv2 = uv2
|
||||||
|
self.colors = colors
|
||||||
|
self.unknown = unknown
|
||||||
|
|
||||||
|
self.submeshes = submeshes
|
||||||
|
self.faces = [] # Aggregate of all submesh face data, easier for blender importing
|
||||||
|
|
||||||
|
# Object data
|
||||||
|
self.name = ""
|
||||||
|
self.transform = None
|
||||||
|
|
||||||
|
class A3D3Submesh:
|
||||||
|
def __init__(self, faces, smoothingGroups, material):
|
||||||
|
self.faces = faces
|
||||||
|
self.smoothingGroups = smoothingGroups
|
||||||
|
self.material = material
|
||||||
|
|
||||||
|
class A3D3Transform:
|
||||||
|
def __init__(self, position, rotation, scale, name):
|
||||||
|
self.position = position
|
||||||
|
self.rotation = rotation
|
||||||
|
self.scale = scale
|
||||||
|
self.name = name
|
||||||
|
self.parentID = 0
|
||||||
157
io_scene_a3d/A3D3_2.py
Normal file
157
io_scene_a3d/A3D3_2.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
'''
|
||||||
|
Copyright (C) 2024 Pyogenics <https://www.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 .A3DIOTools import unpackStream, readNullTerminatedString
|
||||||
|
from .A3D3Shared import A3D3Mesh, A3D3Submesh, A3D3Transform
|
||||||
|
|
||||||
|
'''
|
||||||
|
A3D version 3 type 2
|
||||||
|
'''
|
||||||
|
class A3D3_2:
|
||||||
|
def __init__(self):
|
||||||
|
# Object data
|
||||||
|
self.materialNames = [] # Used to lookup names from materialID
|
||||||
|
self.materials = {}
|
||||||
|
self.meshes = []
|
||||||
|
self.transforms = []
|
||||||
|
|
||||||
|
'''
|
||||||
|
IO
|
||||||
|
'''
|
||||||
|
def readSubmesh(self, stream):
|
||||||
|
print("Reading submesh")
|
||||||
|
|
||||||
|
faceCount, = unpackStream("<I", stream)
|
||||||
|
faces = []
|
||||||
|
for _ in range(faceCount):
|
||||||
|
face = unpackStream("<3H", stream)
|
||||||
|
faces.append(face)
|
||||||
|
smoothGroups = []
|
||||||
|
for _ in range(faceCount):
|
||||||
|
smoothGroup, = unpackStream("<I", stream)
|
||||||
|
smoothGroups.append(smoothGroup)
|
||||||
|
materialID, = unpackStream("<H", stream)
|
||||||
|
|
||||||
|
material = self.materialNames[materialID]
|
||||||
|
submesh = A3D3Submesh(faces, smoothGroups, material)
|
||||||
|
return submesh
|
||||||
|
|
||||||
|
def readVertices(self, vertexCount, floatCount, stream):
|
||||||
|
vertices = []
|
||||||
|
for _ in range(vertexCount):
|
||||||
|
vertex = unpackStream(f"{floatCount}f", stream)
|
||||||
|
vertices.append(vertex)
|
||||||
|
return vertices
|
||||||
|
|
||||||
|
def readMaterialBlock(self, stream):
|
||||||
|
print("Reading material block")
|
||||||
|
marker, _, materialCount = unpackStream("<3I", stream)
|
||||||
|
if marker != 4:
|
||||||
|
raise RuntimeError(f"Invalid material block marker: {marker}")
|
||||||
|
|
||||||
|
for _ in range(materialCount):
|
||||||
|
materialName = readNullTerminatedString(stream)
|
||||||
|
_ = unpackStream("3f", stream)
|
||||||
|
diffuseMap = readNullTerminatedString(stream)
|
||||||
|
|
||||||
|
self.materialNames.append(materialName)
|
||||||
|
self.materials[materialName] = diffuseMap
|
||||||
|
|
||||||
|
def readMeshBlock(self, stream):
|
||||||
|
print("Reading mesh block")
|
||||||
|
marker, _, meshCount = unpackStream("<3I", stream)
|
||||||
|
if marker != 2:
|
||||||
|
raise RuntimeError(f"Invalid mesh block marker: {marker}")
|
||||||
|
|
||||||
|
for meshI in range(meshCount):
|
||||||
|
print(f"Reading mesh {meshI}")
|
||||||
|
|
||||||
|
# Vertices
|
||||||
|
coordinates = []
|
||||||
|
uv1 = []
|
||||||
|
normals = []
|
||||||
|
uv2 = []
|
||||||
|
colors = []
|
||||||
|
unknown = []
|
||||||
|
|
||||||
|
submeshes = []
|
||||||
|
|
||||||
|
# Read vertex buffers
|
||||||
|
vertexCount, vertexBufferCount = unpackStream("<2I", stream)
|
||||||
|
for vertexBufferI in range(vertexBufferCount):
|
||||||
|
print(f"Reading vertex buffer {vertexBufferI} with {vertexCount} vertices")
|
||||||
|
|
||||||
|
bufferType, = unpackStream("<I", stream)
|
||||||
|
if bufferType == 1:
|
||||||
|
coordinates = self.readVertices(vertexCount, 3, stream)
|
||||||
|
elif bufferType == 2:
|
||||||
|
uv1 = self.readVertices(vertexCount, 2, stream)
|
||||||
|
elif bufferType == 3:
|
||||||
|
normals = self.readVertices(vertexCount, 3, stream)
|
||||||
|
elif bufferType == 4:
|
||||||
|
uv2 = self.readVertices(vertexCount, 2, stream)
|
||||||
|
elif bufferType == 5:
|
||||||
|
colors = self.readVertices(vertexCount, 4, stream)
|
||||||
|
elif bufferType == 6:
|
||||||
|
unknown = self.readVertices(vertexCount, 3, stream)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unknown vertex buffer type {bufferType}")
|
||||||
|
|
||||||
|
# Read submeshes
|
||||||
|
submeshCount, = unpackStream("<I", stream)
|
||||||
|
for _ in range(submeshCount):
|
||||||
|
submesh = self.readSubmesh(stream)
|
||||||
|
submeshes.append(submesh)
|
||||||
|
|
||||||
|
mesh = A3D3Mesh(coordinates, uv1, normals, uv2, colors, unknown, submeshes)
|
||||||
|
mesh.faces += submesh.faces
|
||||||
|
self.meshes.append(mesh)
|
||||||
|
|
||||||
|
def readTransformBlock(self, stream):
|
||||||
|
print("Reading transform block")
|
||||||
|
marker, _, transformCount = unpackStream("<3I", stream)
|
||||||
|
if marker != 3:
|
||||||
|
raise RuntimeError(f"Invalid transform block marker: {marker}")
|
||||||
|
|
||||||
|
for _ in range(transformCount):
|
||||||
|
position = unpackStream("<3f", stream)
|
||||||
|
rotation = unpackStream("<4f", stream)
|
||||||
|
scale = unpackStream("<3f", stream)
|
||||||
|
|
||||||
|
transform = A3D3Transform(position, rotation, scale, "")
|
||||||
|
self.transforms.append(transform)
|
||||||
|
for transformI in range(transformCount):
|
||||||
|
transformID, = unpackStream("<I", stream)
|
||||||
|
self.transforms[transformI].id = transformID
|
||||||
|
|
||||||
|
# Heirarchy data
|
||||||
|
def readObjectBlock(self, stream):
|
||||||
|
print("Reading object block")
|
||||||
|
marker, _, objectCount = unpackStream("<3I", stream)
|
||||||
|
if marker != 5:
|
||||||
|
raise RuntimeError(f"Invalid object block marker: {marker}")
|
||||||
|
|
||||||
|
for _ in range(objectCount):
|
||||||
|
objectName = readNullTerminatedString(stream)
|
||||||
|
meshID, transformID = unpackStream("<2I", stream)
|
||||||
|
|
||||||
|
self.meshes[meshID].transform = self.transforms[transformID]
|
||||||
|
self.meshes[meshID].name = objectName
|
||||||
|
|
||||||
|
'''
|
||||||
|
Drivers
|
||||||
|
'''
|
||||||
|
def read(self, stream):
|
||||||
|
print("Reading A3D3 type 2")
|
||||||
|
|
||||||
|
self.readMaterialBlock(stream)
|
||||||
|
self.readMeshBlock(stream)
|
||||||
|
self.readTransformBlock(stream)
|
||||||
|
self.readObjectBlock(stream)
|
||||||
157
io_scene_a3d/A3D3_3.py
Normal file
157
io_scene_a3d/A3D3_3.py
Normal file
@@ -0,0 +1,157 @@
|
|||||||
|
'''
|
||||||
|
Copyright (C) 2024 Pyogenics <https://www.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 .A3DIOTools import unpackStream, readString, calculatePadding
|
||||||
|
from .A3D3Shared import A3D3Mesh, A3D3Submesh, A3D3Transform
|
||||||
|
|
||||||
|
'''
|
||||||
|
A3D version 3 type 3
|
||||||
|
'''
|
||||||
|
class A3D3_3:
|
||||||
|
def __init__(self):
|
||||||
|
self.materials = {}
|
||||||
|
self.materialNames = []
|
||||||
|
self.meshes = []
|
||||||
|
self.transforms = []
|
||||||
|
|
||||||
|
def readSubmesh(self, stream):
|
||||||
|
print("Reading submesh")
|
||||||
|
|
||||||
|
indexCount, = unpackStream("<I", stream)
|
||||||
|
faces = []
|
||||||
|
for _ in range(indexCount//3):
|
||||||
|
face = unpackStream("<3H", stream)
|
||||||
|
faces.append(face)
|
||||||
|
|
||||||
|
paddingSize = calculatePadding(indexCount*2)
|
||||||
|
stream.read(paddingSize)
|
||||||
|
|
||||||
|
submesh = A3D3Submesh(faces, [], 0) # XXX: Maybe this should be `None` instead of 0?
|
||||||
|
return submesh
|
||||||
|
|
||||||
|
def readVertices(self, vertexCount, floatCount, stream):
|
||||||
|
vertices = []
|
||||||
|
for _ in range(vertexCount):
|
||||||
|
vertex = unpackStream(f"{floatCount}f", stream)
|
||||||
|
vertices.append(vertex)
|
||||||
|
return vertices
|
||||||
|
|
||||||
|
def readMaterialBlock(self, stream):
|
||||||
|
print("Reading material block")
|
||||||
|
marker, _, materialCount = unpackStream("<3I", stream)
|
||||||
|
if marker != 4:
|
||||||
|
raise RuntimeError(f"Invalid material block marker: {marker}")
|
||||||
|
|
||||||
|
for _ in range(materialCount):
|
||||||
|
materialName = readString(stream)
|
||||||
|
floats = unpackStream("<3f", stream)
|
||||||
|
|
||||||
|
diffuseMap = readString(stream)
|
||||||
|
print(f"{materialName} {floats} {diffuseMap}")
|
||||||
|
|
||||||
|
self.materialNames.append(materialName)
|
||||||
|
self.materials[materialName] = diffuseMap
|
||||||
|
|
||||||
|
def readMeshBlock(self, stream):
|
||||||
|
print("Reading mesh block")
|
||||||
|
marker, _, meshCount = unpackStream("<3I", stream)
|
||||||
|
if marker != 2:
|
||||||
|
raise RuntimeError(f"Invalid mesh block marker: {marker}")
|
||||||
|
|
||||||
|
for meshI in range(meshCount):
|
||||||
|
print(f"Reading mesh {meshI} @ {stream.tell()}")
|
||||||
|
|
||||||
|
# Vertices
|
||||||
|
coordinates = []
|
||||||
|
uv1 = []
|
||||||
|
normals = []
|
||||||
|
uv2 = []
|
||||||
|
colors = []
|
||||||
|
unknown = []
|
||||||
|
|
||||||
|
submeshes = []
|
||||||
|
|
||||||
|
meshName = readString(stream)
|
||||||
|
unknownFloats = unpackStream("7f", stream)
|
||||||
|
print(f"{meshName} {unknownFloats}")
|
||||||
|
|
||||||
|
# Read vertex buffers
|
||||||
|
vertexCount, vertexBufferCount = unpackStream("<2I", stream)
|
||||||
|
for vertexBufferI in range(vertexBufferCount):
|
||||||
|
print(f"Reading vertex buffer {vertexBufferI} with {vertexCount} vertices")
|
||||||
|
|
||||||
|
bufferType, = unpackStream("<I", stream)
|
||||||
|
if bufferType == 1:
|
||||||
|
coordinates = self.readVertices(vertexCount, 3, stream)
|
||||||
|
elif bufferType == 2:
|
||||||
|
uv1 = self.readVertices(vertexCount, 2, stream)
|
||||||
|
elif bufferType == 3:
|
||||||
|
normals = self.readVertices(vertexCount, 3, stream)
|
||||||
|
elif bufferType == 4:
|
||||||
|
uv2 = self.readVertices(vertexCount, 2, stream)
|
||||||
|
elif bufferType == 5:
|
||||||
|
colors = self.readVertices(vertexCount, 4, stream)
|
||||||
|
elif bufferType == 6:
|
||||||
|
unknown = self.readVertices(vertexCount, 3, stream)
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unknown vertex buffer type {bufferType}")
|
||||||
|
|
||||||
|
# Read submeshes
|
||||||
|
submeshCount, = unpackStream("<I", stream)
|
||||||
|
for _ in range(submeshCount):
|
||||||
|
submesh = self.readSubmesh(stream)
|
||||||
|
submeshes.append(submesh)
|
||||||
|
|
||||||
|
mesh = A3D3Mesh(coordinates, uv1, normals, uv2, colors, unknown, submeshes)
|
||||||
|
mesh.faces += submesh.faces
|
||||||
|
self.meshes.append(mesh)
|
||||||
|
|
||||||
|
def readTransformBlock(self, stream):
|
||||||
|
print("Reading transform block")
|
||||||
|
marker, _, transformCount = unpackStream("<3I", stream)
|
||||||
|
if marker != 3:
|
||||||
|
raise RuntimeError(f"Invalid transform block marker: {marker}")
|
||||||
|
|
||||||
|
for _ in range(transformCount):
|
||||||
|
name = readString(stream)
|
||||||
|
position = unpackStream("<3f", stream)
|
||||||
|
rotation = unpackStream("<4f", stream)
|
||||||
|
scale = unpackStream("<3f", stream)
|
||||||
|
|
||||||
|
print(f"{name} {position} {rotation} {scale}")
|
||||||
|
transform = A3D3Transform(position, rotation, scale, name)
|
||||||
|
self.transforms.append(transform)
|
||||||
|
for transformI in range(transformCount):
|
||||||
|
transformID, = unpackStream("<I", stream)
|
||||||
|
self.transforms[transformI].id = transformID
|
||||||
|
|
||||||
|
# Heirarchy data
|
||||||
|
def readObjectBlock(self, stream):
|
||||||
|
print("Reading object block")
|
||||||
|
marker, _, objectCount = unpackStream("<3I", stream)
|
||||||
|
if marker != 5:
|
||||||
|
raise RuntimeError(f"Invalid object block marker: {marker}")
|
||||||
|
|
||||||
|
for _ in range(objectCount):
|
||||||
|
meshID, transformID, materialCount = unpackStream("<3I", stream)
|
||||||
|
for materialI in range(materialCount):
|
||||||
|
materialID, = unpackStream("<i", stream)
|
||||||
|
if materialID >= 0:
|
||||||
|
self.meshes[meshID].submeshes[materialI].material = self.materialNames[materialID]
|
||||||
|
|
||||||
|
self.meshes[meshID].transform = self.transforms[transformID]
|
||||||
|
|
||||||
|
def read(self, stream):
|
||||||
|
print("Reading A3D3 type 3")
|
||||||
|
|
||||||
|
self.readMaterialBlock(stream)
|
||||||
|
self.readMeshBlock(stream)
|
||||||
|
self.readTransformBlock(stream)
|
||||||
|
self.readObjectBlock(stream)
|
||||||
38
io_scene_a3d/A3DIOTools.py
Normal file
38
io_scene_a3d/A3DIOTools.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'''
|
||||||
|
Copyright (C) 2024 Pyogenics <https://www.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 struct import unpack, calcsize
|
||||||
|
|
||||||
|
def unpackStream(format, stream):
|
||||||
|
size = calcsize(format)
|
||||||
|
data = stream.read(size)
|
||||||
|
return unpack(format, data)
|
||||||
|
|
||||||
|
def readNullTerminatedString(stream):
|
||||||
|
string = b""
|
||||||
|
char = stream.read(1)
|
||||||
|
while char != b"\x00":
|
||||||
|
string += char
|
||||||
|
char = stream.read(1)
|
||||||
|
return string.decode("utf8")
|
||||||
|
|
||||||
|
def calculatePadding(length):
|
||||||
|
# (it basically works with rounding)
|
||||||
|
paddingSize = (((length + 3) // 4) * 4) - length
|
||||||
|
return paddingSize
|
||||||
|
|
||||||
|
def readString(stream):
|
||||||
|
length, = unpackStream("<I", stream)
|
||||||
|
string = stream.read(length)
|
||||||
|
|
||||||
|
paddingSize = calculatePadding(length)
|
||||||
|
stream.read(paddingSize)
|
||||||
|
|
||||||
|
return string.decode("utf8")
|
||||||
196
io_scene_a3d/__init__.py
Normal file
196
io_scene_a3d/__init__.py
Normal file
@@ -0,0 +1,196 @@
|
|||||||
|
'''
|
||||||
|
Copyright (C) 2024 Pyogenics <https://www.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.
|
||||||
|
'''
|
||||||
|
|
||||||
|
bl_info = {
|
||||||
|
"name": "Modern A3D",
|
||||||
|
"description": "Support for modern a3d models",
|
||||||
|
"author": "Pyogenics, https://www.github.com/Pyogenics",
|
||||||
|
"version": (1, 0, 0),
|
||||||
|
"blender": (4, 0, 0),
|
||||||
|
"location": "File > Import-Export",
|
||||||
|
"category": "Import-Export"
|
||||||
|
}
|
||||||
|
|
||||||
|
import bmesh
|
||||||
|
import bpy
|
||||||
|
from bpy.types import Operator
|
||||||
|
from bpy.props import StringProperty
|
||||||
|
from bpy_extras.io_utils import ImportHelper
|
||||||
|
|
||||||
|
from .A3D3_2 import A3D3_2
|
||||||
|
from .A3D3_3 import A3D3_3
|
||||||
|
from .A3DIOTools import unpackStream
|
||||||
|
|
||||||
|
'''
|
||||||
|
Operators
|
||||||
|
'''
|
||||||
|
class ImportA3DModern(Operator, ImportHelper):
|
||||||
|
bl_idname = "import_scene.a3dmodern"
|
||||||
|
bl_label = "Import A3D"
|
||||||
|
bl_description = "Import an A3D model"
|
||||||
|
|
||||||
|
filter_glob: StringProperty(default="*.a3d", options={'HIDDEN'})
|
||||||
|
|
||||||
|
def invoke(self, context, event):
|
||||||
|
return ImportHelper.invoke(self, context, event)
|
||||||
|
|
||||||
|
def execute(self, context):
|
||||||
|
filepath = self.filepath
|
||||||
|
print(f"Importing A3D scene from {filepath}")
|
||||||
|
|
||||||
|
with open(filepath, "rb") as file:
|
||||||
|
signature = file.read(4)
|
||||||
|
if signature != b"A3D\0":
|
||||||
|
raise RuntimeError(f"Invalid A3D signature: {signature}")
|
||||||
|
|
||||||
|
variant, _, rootBlockMarker, _ = unpackStream("<2H2I", file)
|
||||||
|
if rootBlockMarker != 1:
|
||||||
|
raise RuntimeError(f"Invalid root block marker: {rootBlockMarker}")
|
||||||
|
|
||||||
|
if variant == 3:
|
||||||
|
a3d = A3D3_3()
|
||||||
|
a3d.read(file)
|
||||||
|
|
||||||
|
for mesh in a3d.meshes:
|
||||||
|
blenderMesh = self.createBlenderMeshMin(mesh)
|
||||||
|
blenderObject = bpy.data.objects.new(mesh.name, blenderMesh)
|
||||||
|
bpy.context.collection.objects.link(blenderObject)
|
||||||
|
elif variant == 2:
|
||||||
|
a3d = A3D3_2()
|
||||||
|
a3d.read(file)
|
||||||
|
|
||||||
|
# Create our materials
|
||||||
|
materials = {}
|
||||||
|
for materialName in a3d.materialNames:
|
||||||
|
materials[materialName] = bpy.data.materials.new(materialName)
|
||||||
|
|
||||||
|
a3dMesh = a3d.meshes[0]
|
||||||
|
blenderMesh = self.createBlenderMesh(a3dMesh, materials)
|
||||||
|
blenderObject = bpy.data.objects.new(a3dMesh.name, blenderMesh)
|
||||||
|
bpy.context.collection.objects.link(blenderObject)
|
||||||
|
elif variant == 1:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
pass
|
||||||
|
|
||||||
|
self.report({"INFO"}, f"Loaded A3D")
|
||||||
|
|
||||||
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
def createBlenderMeshMin(self, mesh):
|
||||||
|
me = bpy.data.meshes.new(mesh.name)
|
||||||
|
bm = bmesh.new()
|
||||||
|
|
||||||
|
for coord in mesh.coordinates:
|
||||||
|
bm.verts.new(coord)
|
||||||
|
bm.verts.ensure_lookup_table()
|
||||||
|
bm.verts.index_update()
|
||||||
|
for face in mesh.faces:
|
||||||
|
v1, v2, v3 = face
|
||||||
|
bm.faces.new([
|
||||||
|
bm.verts[v1],
|
||||||
|
bm.verts[v2],
|
||||||
|
bm.verts[v3]
|
||||||
|
])
|
||||||
|
|
||||||
|
layers = []
|
||||||
|
if len(mesh.uv1) != 0:
|
||||||
|
layers.append(
|
||||||
|
(bm.loops.layers.uv.new("UV1"), mesh.uv1)
|
||||||
|
)
|
||||||
|
print("has UV1")
|
||||||
|
if len(mesh.uv2) != 0:
|
||||||
|
layers.append(
|
||||||
|
(bm.loops.layers.uv.new("UV2"), mesh.uv2)
|
||||||
|
)
|
||||||
|
print("has UV2")
|
||||||
|
for face in bm.faces:
|
||||||
|
for loop in face.loops:
|
||||||
|
for uvLayer, uvData in layers: loop[uvLayer].uv = uvData[loop.vert.index]
|
||||||
|
loop.vert.normal = mesh.normals[loop.vert.index]
|
||||||
|
|
||||||
|
bm.to_mesh(me)
|
||||||
|
me.update()
|
||||||
|
|
||||||
|
return me
|
||||||
|
|
||||||
|
def createBlenderMesh(self, mesh, materials):
|
||||||
|
me = bpy.data.meshes.new(mesh.name)
|
||||||
|
bm = bmesh.new()
|
||||||
|
|
||||||
|
for coord in mesh.coordinates:
|
||||||
|
bm.verts.new(coord)
|
||||||
|
bm.verts.ensure_lookup_table()
|
||||||
|
bm.verts.index_update()
|
||||||
|
for face in mesh.faces:
|
||||||
|
v1, v2, v3 = face
|
||||||
|
bm.faces.new([
|
||||||
|
bm.verts[v1],
|
||||||
|
bm.verts[v2],
|
||||||
|
bm.verts[v3]
|
||||||
|
])
|
||||||
|
|
||||||
|
layers = []
|
||||||
|
if len(mesh.uv1) != 0:
|
||||||
|
layers.append(
|
||||||
|
(bm.loops.layers.uv.new("UV1"), mesh.uv1)
|
||||||
|
)
|
||||||
|
print("has UV1")
|
||||||
|
if len(mesh.uv2) != 0:
|
||||||
|
layers.append(
|
||||||
|
(bm.loops.layers.uv.new("UV2"), mesh.uv2)
|
||||||
|
)
|
||||||
|
print("has UV2")
|
||||||
|
for face in bm.faces:
|
||||||
|
for loop in face.loops:
|
||||||
|
for uvLayer, uvData in layers: loop[uvLayer].uv = uvData[loop.vert.index]
|
||||||
|
loop.vert.normal = mesh.normals[loop.vert.index]
|
||||||
|
|
||||||
|
bm.to_mesh(me)
|
||||||
|
me.update()
|
||||||
|
|
||||||
|
# Materials
|
||||||
|
for submesh in mesh.submeshes:
|
||||||
|
material = materials[submesh.material]
|
||||||
|
me.materials.append(material)
|
||||||
|
materialI = len(me.materials) - 1
|
||||||
|
for polygon in me.polygons:
|
||||||
|
polygon.material_index = materialI
|
||||||
|
|
||||||
|
return me
|
||||||
|
|
||||||
|
'''
|
||||||
|
Menu
|
||||||
|
'''
|
||||||
|
def menu_func_import_a3d(self, context):
|
||||||
|
self.layout.operator(ImportA3DModern.bl_idname, text="A3D Modern")
|
||||||
|
|
||||||
|
'''
|
||||||
|
Register
|
||||||
|
'''
|
||||||
|
classes = {
|
||||||
|
ImportA3DModern
|
||||||
|
}
|
||||||
|
|
||||||
|
def register():
|
||||||
|
# Register classes
|
||||||
|
for c in classes:
|
||||||
|
bpy.utils.register_class(c)
|
||||||
|
# File > Import-Export
|
||||||
|
bpy.types.TOPBAR_MT_file_import.append(menu_func_import_a3d)
|
||||||
|
# bpy.types.TOPBAR_MT_file_export.append(menu_func_export_dava)
|
||||||
|
|
||||||
|
def unregister():
|
||||||
|
# Unregister classes
|
||||||
|
for c in classes:
|
||||||
|
bpy.utils.unregister_class(c)
|
||||||
|
# Remove `File > Import-Export`
|
||||||
|
bpy.types.TOPBAR_MT_file_import.remove(menu_func_import_a3d)
|
||||||
|
# bpy.types.TOPBAR_MT_file_export.remove(menu_func_export_dava)
|
||||||
38
io_scene_a3d/__main__.py
Normal file
38
io_scene_a3d/__main__.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
'''
|
||||||
|
Copyright (C) 2024 Pyogenics <https://www.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 .A3DIOTools import unpackStream
|
||||||
|
from .A3D3_2 import A3D3_2
|
||||||
|
from .A3D3_3 import A3D3_3
|
||||||
|
|
||||||
|
def readA3D(file):
|
||||||
|
signature = file.read(4)
|
||||||
|
if signature != b"A3D\0":
|
||||||
|
raise RuntimeError(f"Invalid A3D signature: {signature}")
|
||||||
|
|
||||||
|
variant, _, rootBlockMarker, _ = unpackStream("<2H2I", file)
|
||||||
|
if rootBlockMarker != 1:
|
||||||
|
raise RuntimeError(f"Invalid root block marker: {rootBlockMarker}")
|
||||||
|
|
||||||
|
if variant == 3:
|
||||||
|
a3d = A3D3_3()
|
||||||
|
a3d.read(file)
|
||||||
|
elif variant == 2:
|
||||||
|
a3d = A3D3_2()
|
||||||
|
a3d.read(file)
|
||||||
|
elif variant == 1:
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
raise RuntimeError(f"Unknown A3D variant: {variant}")
|
||||||
|
|
||||||
|
from sys import argv
|
||||||
|
if __name__ == "__main__":
|
||||||
|
with open(argv[1], "rb") as file:
|
||||||
|
readA3D(file)
|
||||||
Reference in New Issue
Block a user