Extract blender import code to improve code quality

This commit is contained in:
Pyogenics
2025-01-19 12:04:28 +00:00
parent db68e3c8f4
commit d3988cd10f
2 changed files with 220 additions and 144 deletions

View 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

View File

@@ -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"}