mirror of
https://github.com/MapMakersAndProgrammers/io_scene_a3d.git
synced 2025-10-26 01:49:13 -07:00
Extract blender import code to improve code quality
This commit is contained in:
195
io_scene_a3d/A3DBlenderImporter.py
Normal file
195
io_scene_a3d/A3DBlenderImporter.py
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
'''
|
||||||
|
Copyright (c) 2024 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.
|
||||||
|
'''
|
||||||
|
|
||||||
|
import bpy
|
||||||
|
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
|
||||||
|
|
||||||
|
from .A3DObjects import (
|
||||||
|
A3D_VERTEXTYPE_COORDINATE,
|
||||||
|
A3D_VERTEXTYPE_UV1,
|
||||||
|
A3D_VERTEXTYPE_NORMAL1,
|
||||||
|
A3D_VERTEXTYPE_UV2,
|
||||||
|
A3D_VERTEXTYPE_COLOR,
|
||||||
|
A3D_VERTEXTYPE_NORMAL2
|
||||||
|
)
|
||||||
|
|
||||||
|
class A3DBlenderImporter:
|
||||||
|
def __init__(self, modelData, create_collection=True):
|
||||||
|
self.modelData = modelData
|
||||||
|
self.materials = []
|
||||||
|
self.meshes = []
|
||||||
|
|
||||||
|
# User settings
|
||||||
|
self.create_collection = create_collection
|
||||||
|
|
||||||
|
def importData(self):
|
||||||
|
print("Importing A3D model data into blender")
|
||||||
|
|
||||||
|
# Create materials
|
||||||
|
for materialData in self.modelData.materials:
|
||||||
|
ma = self.buildBlenderMaterial(materialData)
|
||||||
|
self.materials.append(ma)
|
||||||
|
|
||||||
|
# Build meshes
|
||||||
|
for meshData in self.modelData.meshes:
|
||||||
|
me = self.buildBlenderMesh(meshData)
|
||||||
|
self.meshes.append(me)
|
||||||
|
|
||||||
|
# Create objects
|
||||||
|
collection = bpy.context.collection # By default use the current active collection
|
||||||
|
if self.create_collection:
|
||||||
|
collection = bpy.data.collections.new("Object")
|
||||||
|
bpy.context.collection.children.link(collection)
|
||||||
|
for objectData in self.modelData.objects:
|
||||||
|
ob = self.buildBlenderObject(objectData)
|
||||||
|
collection.objects.link(ob)
|
||||||
|
|
||||||
|
'''
|
||||||
|
Blender data builders
|
||||||
|
'''
|
||||||
|
def buildBlenderMaterial(self, materialData):
|
||||||
|
ma = bpy.data.materials.new(materialData.name)
|
||||||
|
maWrapper = PrincipledBSDFWrapper(ma, is_readonly=False, use_nodes=True)
|
||||||
|
maWrapper.base_color = materialData.color
|
||||||
|
maWrapper.roughness = 1.0
|
||||||
|
|
||||||
|
return ma
|
||||||
|
|
||||||
|
def buildBlenderMesh(self, meshData):
|
||||||
|
me = bpy.data.meshes.new(meshData.name)
|
||||||
|
|
||||||
|
# Gather all vertex data
|
||||||
|
coordinates = []
|
||||||
|
uv1 = []
|
||||||
|
normal1 = []
|
||||||
|
uv2 = []
|
||||||
|
colors = []
|
||||||
|
normal2 = []
|
||||||
|
for vertexBuffer in meshData.vertexBuffers:
|
||||||
|
if vertexBuffer.bufferType == A3D_VERTEXTYPE_COORDINATE:
|
||||||
|
coordinates += vertexBuffer.data
|
||||||
|
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_UV1:
|
||||||
|
uv1 += vertexBuffer.data
|
||||||
|
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_NORMAL1:
|
||||||
|
normal1 += vertexBuffer.data
|
||||||
|
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_UV2:
|
||||||
|
uv2 += vertexBuffer.data
|
||||||
|
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_COLOR:
|
||||||
|
colors += vertexBuffer.data
|
||||||
|
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_NORMAL2:
|
||||||
|
normal2 += vertexBuffer.data
|
||||||
|
|
||||||
|
# Add blender vertices
|
||||||
|
blenderVertexIndices = []
|
||||||
|
blenderVertices = []
|
||||||
|
blenderUV1s = []
|
||||||
|
blenderUV2s = []
|
||||||
|
for submesh in meshData.submeshes:
|
||||||
|
polygonCount = len(submesh.indices) // 3
|
||||||
|
me.vertices.add(polygonCount*3)
|
||||||
|
me.loops.add(polygonCount*3)
|
||||||
|
me.polygons.add(polygonCount)
|
||||||
|
|
||||||
|
for indexI in range(submesh.indexCount):
|
||||||
|
index = submesh.indices[indexI]
|
||||||
|
blenderVertexIndices.append(indexI)
|
||||||
|
blenderVertices += list(coordinates[index])
|
||||||
|
if len(uv1) != 0:
|
||||||
|
for indexI in range(submesh.indexCount):
|
||||||
|
index = submesh.indices[indexI]
|
||||||
|
x, y = uv1[index]
|
||||||
|
blenderUV1s.append((x, 1-y))
|
||||||
|
if len(uv2) != 0:
|
||||||
|
for indexI in range(submesh.indexCount):
|
||||||
|
index = submesh.indices[indexI]
|
||||||
|
x, y = uv2[index]
|
||||||
|
blenderUV2s.append((x, 1-y))
|
||||||
|
me.vertices.foreach_set("co", blenderVertices)
|
||||||
|
me.polygons.foreach_set("loop_start", range(0, len(blenderVertices)//3, 3))
|
||||||
|
me.loops.foreach_set("vertex_index", blenderVertexIndices)
|
||||||
|
|
||||||
|
# UVs
|
||||||
|
if len(uv1) != 0:
|
||||||
|
uvData = me.uv_layers.new(name="UV1").data
|
||||||
|
for polygonI, po in enumerate(me.polygons):
|
||||||
|
indexI = polygonI * 3
|
||||||
|
uvData[po.loop_start].uv = blenderUV1s[blenderVertexIndices[indexI]]
|
||||||
|
uvData[po.loop_start+1].uv = blenderUV1s[blenderVertexIndices[indexI+1]]
|
||||||
|
uvData[po.loop_start+2].uv = blenderUV1s[blenderVertexIndices[indexI+2]]
|
||||||
|
if len(uv2) != 0:
|
||||||
|
uvData = me.uv_layers.new(name="UV2").data
|
||||||
|
for polygonI, po in enumerate(me.polygons):
|
||||||
|
indexI = polygonI * 3
|
||||||
|
uvData[po.loop_start].uv = blenderUV2s[blenderVertexIndices[indexI]]
|
||||||
|
uvData[po.loop_start+1].uv = blenderUV2s[blenderVertexIndices[indexI+1]]
|
||||||
|
uvData[po.loop_start+2].uv = blenderUV2s[blenderVertexIndices[indexI+2]]
|
||||||
|
|
||||||
|
# Apply materials (version 2)
|
||||||
|
faceIndexBase = 0
|
||||||
|
for submeshI, submesh in enumerate(meshData.submeshes):
|
||||||
|
if submesh.materialID == None:
|
||||||
|
continue
|
||||||
|
me.materials.append(self.materials[submesh.materialID])
|
||||||
|
for faceI in range(submesh.indexCount//3):
|
||||||
|
me.polygons[faceI+faceIndexBase].material_index = submeshI
|
||||||
|
faceIndexBase += submesh.indexCount//3
|
||||||
|
|
||||||
|
# Finalise
|
||||||
|
me.validate()
|
||||||
|
me.update()
|
||||||
|
return me
|
||||||
|
|
||||||
|
def buildBlenderObject(self, objectData):
|
||||||
|
me = self.meshes[objectData.meshID]
|
||||||
|
mesh = self.modelData.meshes[objectData.meshID]
|
||||||
|
transform = self.modelData.transforms[objectData.transformID]
|
||||||
|
|
||||||
|
# Apply materials to mesh (version 3)
|
||||||
|
for materialID in objectData.materialIDs:
|
||||||
|
if materialID == -1:
|
||||||
|
continue
|
||||||
|
me.materials.append(self.materials[materialID])
|
||||||
|
# Set the default material to the first one we added
|
||||||
|
for polygon in me.polygons:
|
||||||
|
polygon.material_index = 0
|
||||||
|
|
||||||
|
# Select a name for the blender object
|
||||||
|
#XXX: review this, maybe we should just stick to the name we are given
|
||||||
|
name = ""
|
||||||
|
if objectData.name != "":
|
||||||
|
name = objectData.name
|
||||||
|
elif mesh.name != "":
|
||||||
|
name = mesh.name
|
||||||
|
else:
|
||||||
|
name = transform.name
|
||||||
|
|
||||||
|
# Create the object
|
||||||
|
ob = bpy.data.objects.new(name, me)
|
||||||
|
|
||||||
|
# Set transform
|
||||||
|
ob.location = transform.position
|
||||||
|
ob.scale = transform.scale
|
||||||
|
ob.rotation_mode = "QUATERNION"
|
||||||
|
x, y, z, w = transform.rotation
|
||||||
|
ob.rotation_quaternion = (w, x, y, z)
|
||||||
|
|
||||||
|
return ob
|
||||||
@@ -1,20 +1,32 @@
|
|||||||
import bmesh
|
'''
|
||||||
|
Copyright (c) 2024 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.
|
||||||
|
'''
|
||||||
|
|
||||||
import bpy
|
import bpy
|
||||||
from bpy.types import Operator
|
from bpy.types import Operator
|
||||||
from bpy.props import StringProperty, BoolProperty
|
from bpy.props import StringProperty, BoolProperty
|
||||||
from bpy_extras.io_utils import ImportHelper
|
from bpy_extras.io_utils import ImportHelper
|
||||||
from bpy_extras.node_shader_utils import PrincipledBSDFWrapper
|
|
||||||
from bpy_extras.image_utils import load_image
|
|
||||||
|
|
||||||
from .A3D import A3D
|
from .A3D import A3D
|
||||||
from .A3DObjects import (
|
from .A3DBlenderImporter import A3DBlenderImporter
|
||||||
A3D_VERTEXTYPE_COORDINATE,
|
|
||||||
A3D_VERTEXTYPE_UV1,
|
|
||||||
A3D_VERTEXTYPE_NORMAL1,
|
|
||||||
A3D_VERTEXTYPE_UV2,
|
|
||||||
A3D_VERTEXTYPE_COLOR,
|
|
||||||
A3D_VERTEXTYPE_NORMAL2
|
|
||||||
)
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
Operators
|
Operators
|
||||||
@@ -47,139 +59,8 @@ class ImportA3D(Operator, ImportHelper):
|
|||||||
modelData.read(file)
|
modelData.read(file)
|
||||||
|
|
||||||
# Import data into blender
|
# Import data into blender
|
||||||
print("Importing mesh data into blender")
|
modelImporter = A3DBlenderImporter(modelData, self.create_collection)
|
||||||
# Create materials
|
modelImporter.importData()
|
||||||
materials = []
|
|
||||||
for material in modelData.materials:
|
|
||||||
ma = bpy.data.materials.new(material.name)
|
|
||||||
maWrapper = PrincipledBSDFWrapper(ma, is_readonly=False, use_nodes=True)
|
|
||||||
maWrapper.base_color = material.color
|
|
||||||
maWrapper.roughness = 1.0
|
|
||||||
|
|
||||||
materials.append(ma)
|
|
||||||
# Build meshes
|
|
||||||
meshes = []
|
|
||||||
for mesh in modelData.meshes:
|
|
||||||
me = bpy.data.meshes.new(mesh.name)
|
|
||||||
|
|
||||||
# Gather all vertex data
|
|
||||||
coordinates = []
|
|
||||||
uv1 = []
|
|
||||||
normal1 = []
|
|
||||||
uv2 = []
|
|
||||||
colors = []
|
|
||||||
normal2 = []
|
|
||||||
for vertexBuffer in mesh.vertexBuffers:
|
|
||||||
if vertexBuffer.bufferType == A3D_VERTEXTYPE_COORDINATE:
|
|
||||||
coordinates += vertexBuffer.data
|
|
||||||
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_UV1:
|
|
||||||
uv1 += vertexBuffer.data
|
|
||||||
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_NORMAL1:
|
|
||||||
normal1 += vertexBuffer.data
|
|
||||||
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_UV2:
|
|
||||||
uv2 += vertexBuffer.data
|
|
||||||
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_COLOR:
|
|
||||||
colors += vertexBuffer.data
|
|
||||||
elif vertexBuffer.bufferType == A3D_VERTEXTYPE_NORMAL2:
|
|
||||||
normal2 += vertexBuffer.data
|
|
||||||
|
|
||||||
# Add blender vertices
|
|
||||||
blenderVertexIndices = []
|
|
||||||
blenderVertices = []
|
|
||||||
blenderUV1s = []
|
|
||||||
blenderUV2s = []
|
|
||||||
for submesh in mesh.submeshes:
|
|
||||||
polygonCount = len(submesh.indices) // 3
|
|
||||||
me.vertices.add(polygonCount*3)
|
|
||||||
me.loops.add(polygonCount*3)
|
|
||||||
me.polygons.add(polygonCount)
|
|
||||||
|
|
||||||
for indexI in range(submesh.indexCount):
|
|
||||||
index = submesh.indices[indexI]
|
|
||||||
blenderVertexIndices.append(indexI)
|
|
||||||
blenderVertices += list(coordinates[index])
|
|
||||||
if len(uv1) != 0:
|
|
||||||
for indexI in range(submesh.indexCount):
|
|
||||||
index = submesh.indices[indexI]
|
|
||||||
x, y = uv1[index]
|
|
||||||
blenderUV1s.append((x, 1-y))
|
|
||||||
if len(uv2) != 0:
|
|
||||||
for indexI in range(submesh.indexCount):
|
|
||||||
index = submesh.indices[indexI]
|
|
||||||
x, y = uv2[index]
|
|
||||||
blenderUV2s.append((x, 1-y))
|
|
||||||
me.vertices.foreach_set("co", blenderVertices)
|
|
||||||
me.polygons.foreach_set("loop_start", range(0, len(blenderVertices)//3, 3))
|
|
||||||
me.loops.foreach_set("vertex_index", blenderVertexIndices)
|
|
||||||
|
|
||||||
# UVs
|
|
||||||
if len(uv1) != 0:
|
|
||||||
uvData = me.uv_layers.new(name="UV1").data
|
|
||||||
for polygonI, po in enumerate(me.polygons):
|
|
||||||
indexI = polygonI * 3
|
|
||||||
uvData[po.loop_start].uv = blenderUV1s[blenderVertexIndices[indexI]]
|
|
||||||
uvData[po.loop_start+1].uv = blenderUV1s[blenderVertexIndices[indexI+1]]
|
|
||||||
uvData[po.loop_start+2].uv = blenderUV1s[blenderVertexIndices[indexI+2]]
|
|
||||||
if len(uv2) != 0:
|
|
||||||
uvData = me.uv_layers.new(name="UV2").data
|
|
||||||
for polygonI, po in enumerate(me.polygons):
|
|
||||||
indexI = polygonI * 3
|
|
||||||
uvData[po.loop_start].uv = blenderUV2s[blenderVertexIndices[indexI]]
|
|
||||||
uvData[po.loop_start+1].uv = blenderUV2s[blenderVertexIndices[indexI+1]]
|
|
||||||
uvData[po.loop_start+2].uv = blenderUV2s[blenderVertexIndices[indexI+2]]
|
|
||||||
|
|
||||||
# Apply materials (version 2)
|
|
||||||
faceIndexBase = 0
|
|
||||||
for submeshI, submesh in enumerate(mesh.submeshes):
|
|
||||||
if submesh.materialID == None:
|
|
||||||
continue
|
|
||||||
me.materials.append(materials[submesh.materialID])
|
|
||||||
for faceI in range(submesh.indexCount//3):
|
|
||||||
me.polygons[faceI+faceIndexBase].material_index = submeshI
|
|
||||||
faceIndexBase += submesh.indexCount//3
|
|
||||||
|
|
||||||
# Finalise
|
|
||||||
me.validate()
|
|
||||||
me.update()
|
|
||||||
meshes.append(me)
|
|
||||||
# Create objects
|
|
||||||
collection = bpy.context.collection # By default use the active collection
|
|
||||||
if self.create_collection:
|
|
||||||
collection = bpy.data.collections.new("Object")
|
|
||||||
bpy.context.collection.children.link(collection)
|
|
||||||
for objec in modelData.objects:
|
|
||||||
me = meshes[objec.meshID]
|
|
||||||
mesh = modelData.meshes[objec.meshID]
|
|
||||||
transform = modelData.transforms[objec.transformID]
|
|
||||||
|
|
||||||
# Select a name for the blender object
|
|
||||||
name = ""
|
|
||||||
if objec.name != "":
|
|
||||||
name = objec.name
|
|
||||||
elif mesh.name != "":
|
|
||||||
name = mesh.name
|
|
||||||
else:
|
|
||||||
name = transform.name
|
|
||||||
|
|
||||||
# Create the object
|
|
||||||
ob = bpy.data.objects.new(name, me)
|
|
||||||
collection.objects.link(ob)
|
|
||||||
|
|
||||||
# Set transform
|
|
||||||
ob.location = transform.position
|
|
||||||
ob.scale = transform.scale
|
|
||||||
ob.rotation_mode = "QUATERNION"
|
|
||||||
x, y, z, w = transform.rotation
|
|
||||||
ob.rotation_quaternion = (w, x, y, z)
|
|
||||||
|
|
||||||
# Apply materials (version 3)
|
|
||||||
for materialID in objec.materialIDs:
|
|
||||||
if materialID == -1:
|
|
||||||
continue
|
|
||||||
me.materials.append(materials[materialID])
|
|
||||||
# Set the default material to the first one we added
|
|
||||||
for polygon in me.polygons:
|
|
||||||
polygon.material_index = 0
|
|
||||||
|
|
||||||
return {"FINISHED"}
|
return {"FINISHED"}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user