mirror of
https://github.com/MapMakersAndProgrammers/io_scene_a3d.git
synced 2025-10-25 17:39:10 -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
|
||||
from bpy.types import Operator
|
||||
from bpy.props import StringProperty, BoolProperty
|
||||
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 .A3DObjects import (
|
||||
A3D_VERTEXTYPE_COORDINATE,
|
||||
A3D_VERTEXTYPE_UV1,
|
||||
A3D_VERTEXTYPE_NORMAL1,
|
||||
A3D_VERTEXTYPE_UV2,
|
||||
A3D_VERTEXTYPE_COLOR,
|
||||
A3D_VERTEXTYPE_NORMAL2
|
||||
)
|
||||
from .A3DBlenderImporter import A3DBlenderImporter
|
||||
|
||||
'''
|
||||
Operators
|
||||
@@ -47,139 +59,8 @@ class ImportA3D(Operator, ImportHelper):
|
||||
modelData.read(file)
|
||||
|
||||
# Import data into blender
|
||||
print("Importing mesh data into blender")
|
||||
# Create materials
|
||||
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
|
||||
modelImporter = A3DBlenderImporter(modelData, self.create_collection)
|
||||
modelImporter.importData()
|
||||
|
||||
return {"FINISHED"}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user