1 Star 0 Fork 0

tangyin025/blender-exporter

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
export_scene.py 18.94 KB
一键复制 编辑 原始数据 按行查看 历史
1vanK 提交于 2020-01-28 04:43 . Textures copying
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548
#
# This script is licensed as public domain.
#
from .utils import PathType, GetFilepath, CheckFilepath, \
FloatToString, Vector3ToString, Vector4ToString, \
WriteXmlFile
from xml.etree import ElementTree
from mathutils import Vector, Quaternion, Matrix
import bpy
import os
import logging
import math
import copy
log = logging.getLogger("ExportLogger")
#-------
# Utils
#-------
# Get the object quaternion rotation, convert if it uses other rotation modes
def GetQuatenion(obj):
# Quaternion mode
if obj.rotation_mode == 'QUATERNION':
return obj.rotation_quaternion
# Axis Angle mode
if obj.rotation_mode == 'AXIS_ANGLE':
rot = obj.rotation_axis_angle
return Quaternion(Vector((rot[1], rot[2], rot[3])), rot[0])
# Euler mode
return obj.rotation_euler.to_quaternion()
#-------------------------
# Scene and nodes classes
#-------------------------
# Options for scene and nodes export
class SOptions:
def __init__(self):
self.doObjectsPrefab = False
self.doCollectivePrefab = False
self.doFullScene = False
self.onlySelected = False
self.physics = False
self.collisionShape = None
self.trasfObjects = False
self.globalOrigin = False
self.orientation = Quaternion((1.0, 0.0, 0.0, 0.0))
class UrhoSceneMaterial:
def __init__(self):
# Material name
self.name = None
# List\Tuple of textures
self.texturesList = None
def LoadMaterial(self, uExportData, uGeometry):
self.name = uGeometry.uMaterialName
for uMaterial in uExportData.materials:
if uMaterial.name == self.name:
self.texturesList = uMaterial.getTextures()
break
class UrhoSceneModel:
def __init__(self):
# Model name
self.name = None
# Blender object name
self.blenderName = None
# Parent Blender object name
self.parentBlenderName = None
# Model type
self.type = None
# List of UrhoSceneMaterial
self.materialsList = []
# Model bounding box
self.boundingBox = None
# Model position
self.position = Vector()
# Model rotation
self.rotation = Quaternion((1.0, 0.0, 0.0, 0.0))
# Model scale
self.scale = Vector((1.0, 1.0, 1.0))
def LoadModel(self, uExportData, uModel, blenderObjectName, sOptions):
self.name = uModel.name
self.blenderName = blenderObjectName
if self.blenderName:
object = bpy.data.objects[self.blenderName]
# Get the local matrix (relative to parent)
objMatrix = object.matrix_local
# Reorient (normally only root objects need to be re-oriented but
# here we need to undo the previous rotation done by DecomposeMesh)
if sOptions.orientation:
om = sOptions.orientation.to_matrix().to_4x4()
objMatrix = om @ objMatrix @ om.inverted()
# Get pos/rot/scale
pos = objMatrix.to_translation()
rot = objMatrix.to_quaternion()
scale = objMatrix.to_scale()
# Convert pos/rot/scale
self.position = Vector((pos.x, pos.z, pos.y))
self.rotation = Quaternion((rot.w, -rot.x, -rot.z, -rot.y))
self.scale = Vector((scale.x, scale.z, scale.y))
# Get parent object
parentObject = object.parent
if parentObject and parentObject.type == 'MESH':
self.parentBlenderName = parentObject.name
if len(uModel.bones) > 0 or len(uModel.morphs) > 0:
self.type = "AnimatedModel"
else:
self.type = "StaticModel"
for uGeometry in uModel.geometries:
uSceneMaterial = UrhoSceneMaterial()
uSceneMaterial.LoadMaterial(uExportData, uGeometry)
self.materialsList.append(uSceneMaterial)
self.boundingBox = uModel.boundingBox
class UrhoScene:
def __init__(self, blenderScene):
# Blender scene name
self.blenderSceneName = blenderScene.name
# List of UrhoSceneModel
self.modelsList = []
# List of all files
self.files = {}
def LoadScene(self, uExportData, blenderObjectName, sOptions):
for uModel in uExportData.models:
uSceneModel = UrhoSceneModel()
uSceneModel.LoadModel(uExportData, uModel, blenderObjectName, sOptions)
self.modelsList.append(uSceneModel)
def AddFile(self, pathType, name, fileUrhoPath):
# Note: name must be unique in its type
if not name:
log.critical("Name null type:{:s} path:{:s}".format(pathType, fileUrhoPath) )
return False
if name in self.files:
log.critical("Already added type:{:s} name:{:s}".format(pathType, name) )
return False
self.files[pathType+name] = fileUrhoPath
return True
def FindFile(self, pathType, name):
if name is None:
return ""
try:
return self.files[pathType+name]
except KeyError:
return ""
#------------
# Scene sort
#------------
# Hierarchical sorting (based on a post by Hyperboreus at SO)
class Node:
def __init__(self, name):
self.name = name
self.children = []
self.parent = None
def to_list(self):
names = [self.name]
for child in self.children:
names.extend(child.to_list())
return names
class Tree:
def __init__(self):
self.nodes = {}
def push(self, item):
name, parent = item
if name not in self.nodes:
self.nodes[name] = Node(name)
if parent:
if parent not in self.nodes:
self.nodes[parent] = Node(parent)
if parent != name:
self.nodes[name].parent = self.nodes[parent]
self.nodes[parent].children.append(self.nodes[name])
def to_list(self):
names = []
for node in self.nodes.values():
if not node.parent:
names.extend(node.to_list())
return names
# Sort scene models by parent-child relation
def SceneModelsSort(scene):
names_tree = Tree()
for model in scene.modelsList:
##names_tree.push((model.objectName, model.parentBlenderName))
names_tree.push((model.name, model.parentBlenderName))
# Rearrange the model list in the new order
orderedModelsList = []
for name in names_tree.to_list():
for model in scene.modelsList:
##if model.objectName == name:
if model.name == name:
orderedModelsList.append(model)
# No need to reverse the list, we break straightway
scene.modelsList.remove(model)
break
scene.modelsList = orderedModelsList
#--------------
# XML elements
#--------------
# Create an XML element using 'tag' as name and the dictionary 'values'
# as attributes, if 'parent' is None a root element is created.
def XmlAddElement(parent, tag, ids=None, values=None):
if parent is not None:
element = ElementTree.SubElement(parent, tag)
else:
element = ElementTree.Element(tag)
if ids is not None:
element.set("id", str(ids[tag]))
ids[tag] += 1
if values is not None:
for name, value in values.items():
element.set(name, str(value))
return element
# Adds to parent an XML element with name "component" and attributes
# "type" and "id", the value of "id" is taken from the 'ids' dictionary.
def XmlAddComponent(parent=None, type=None, ids=None):
if parent is not None:
element = ElementTree.SubElement(parent, "component")
else:
element = ElementTree.Element("component")
if type is not None:
element.set("type", str(type))
if ids is not None:
element.set("id", str(ids["component"]))
ids["component"] += 1
return element
# Adds to parent an XML element with name "attribute" and attributes
# "name" and "value".
def XmlAddAttribute(parent=None, name=None, value=None):
if parent is not None:
element = ElementTree.SubElement(parent, "attribute")
else:
element = ElementTree.Element("attribute")
if name is not None:
element.set("name", str(name))
if value is not None:
element.set("value", str(value))
return element
# Removes from 'node' all the child nodes whose attribute 'name' is not
# in the list 'values'.
def XmlNodeFilter(node, name, values):
for child in list(node):
if child.tag != "node":
continue
value = child.get(name, None)
if value in values:
XmlNodeFilter(child, name, values)
else:
node.remove(child)
# Renumber the attribute "id" of 'node' and all its children, each different
# element tag has a different numbering starting from 1. Use the dictionary
# 'ids' ("tag":number) to specify a custom start.
def XmlIdSet(node, ids = None):
if ids is None: # dict as default arg is static
ids = {}
tag = node.tag
if tag not in ids:
ids[tag] = 1
if node.get("id", False):
node.set("id", str(ids[tag]))
ids[tag] += 1
for child in list(node):
XmlIdSet(child, ids)
#------------------------
# Export materials
#------------------------
"""
def UrhoWriteMaterial(uScene, uMaterial, filepath, fOptions):
material = XmlAddElement(None, "material")
# Technique
techniquFile = GetFilepath(PathType.TECHNIQUES, uMaterial.techniqueName, fOptions)
XmlAddElement(material, "technique",
values={"name": techniquFile[1]} )
# Textures
for texKey, texName in uMaterial.texturesNames.items():
if texName:
XmlAddElement(material, "texture",
values={"unit": texKey, "name": uScene.FindFile(PathType.TEXTURES, texName)} )
# PS defines
if uMaterial.psdefines != "":
XmlAddElement(material, "shader",
values={"psdefines": uMaterial.psdefines.lstrip()} )
# VS defines
if uMaterial.vsdefines != "":
XmlAddElement(material, "shader",
values={"vsdefines": uMaterial.vsdefines.lstrip()} )
# Parameters
if uMaterial.diffuseColor:
XmlAddElement(material, "parameter",
values={"name": "MatDiffColor", "value": Vector4ToString(uMaterial.diffuseColor)} )
if uMaterial.specularColor:
XmlAddElement(material, "parameter",
values={"name": "MatSpecColor", "value": Vector4ToString(uMaterial.specularColor)} )
if uMaterial.emissiveColor:
XmlAddElement(material, "parameter",
values={"name": "MatEmissiveColor", "value": Vector3ToString(uMaterial.emissiveColor)} )
if uMaterial.twoSided:
XmlAddElement(material, "cull",
values={"value": "none"} )
XmlAddElement(material, "shadowcull",
values={"value": "none"} )
WriteXmlFile(material, filepath, fOptions)
"""
def UrhoWriteMaterialsList(uScene, uModel, filepath):
# Search for the model in the UrhoScene
for uSceneModel in uScene.modelsList:
if uSceneModel.name == uModel.name:
break
else:
return
# Get the model materials and their corresponding file paths
content = ""
for uSceneMaterial in uSceneModel.materialsList:
file = uScene.FindFile(PathType.MATERIALS, uSceneMaterial.name)
# If the file is missing add a placeholder to preserve the order
if not file:
file = "null"
content += file + "\n"
try:
file = open(filepath, "w")
except Exception as e:
log.error("Cannot open file {:s} {:s}".format(filepath, e))
return
file.write(content)
file.close()
#------------------------
# Export scene and nodes
#------------------------
def UrhoExportScene(context, uScene, sOptions, fOptions):
ids = {}
ids["scene"] = 1
ids["node"] = 1
ids["component"] = 1
# Root XML element
root = XmlAddElement(None, "scene", ids=ids)
XmlAddComponent(root, type="Octree", ids=ids)
XmlAddComponent(root, type="DebugRenderer", ids=ids)
comp = XmlAddComponent(root, type="Light", ids=ids)
XmlAddAttribute(comp, name="Light Type", value="Directional")
if sOptions.physics:
XmlAddComponent(root, type="PhysicsWorld", ids=ids)
# Root node
rootNode = XmlAddElement(root, "node", ids=ids)
XmlAddAttribute(rootNode, name="Is Enabled", value="true") #extra
XmlAddAttribute(rootNode, name="Name", value=uScene.blenderSceneName)
XmlAddAttribute(rootNode, name="Tags") #extra
XmlAddAttribute(rootNode, name="Variables") #extra
# Create physics stuff for the root node
if sOptions.physics:
comp = XmlAddComponent(rootNode, type="RigidBody", ids=ids)
XmlAddAttribute(comp, name="Collision Layer", value="2")
XmlAddAttribute(comp, name="Use Gravity", value="false")
physicsModelFile = GetFilepath(PathType.MODELS, "Physics", fOptions)[1]
comp = XmlAddComponent(rootNode, type="CollisionShape", ids=ids)
XmlAddAttribute(comp, name="Shape Type", value="TriangleMesh")
XmlAddAttribute(comp, name="Model", value="Model;" + physicsModelFile)
if sOptions.trasfObjects and sOptions.globalOrigin:
log.warning("To export objects transformations you should use Origin = Local")
# Sort the models by parent-child relationship
SceneModelsSort(uScene)
# Blender object name to xml node collection
xmlNodes = {}
# Export each model object as a node
for uSceneModel in uScene.modelsList:
# Blender name is surely unique
blenderName = uSceneModel.blenderName
# Find the XML element of the model parent if it exists
parent = rootNode
if uSceneModel.type == "StaticModel":
parentName = uSceneModel.parentBlenderName
if parentName in xmlNodes:
parent = xmlNodes[parentName]
# Get model file relative path
modelFile = uScene.FindFile(PathType.MODELS, uSceneModel.name)
# Gather materials
materials = ""
for uSceneMaterial in uSceneModel.materialsList:
file = uScene.FindFile(PathType.MATERIALS, uSceneMaterial.name)
materials += ";" + file
# Generate the node XML content
node = XmlAddElement(parent, "node", ids=ids)
xmlNodes[blenderName] = node
XmlAddAttribute(node, name="Is Enabled", value="true") #extra
XmlAddAttribute(node, name="Name", value=uSceneModel.name)
XmlAddAttribute(node, name="Tags") #extra
if sOptions.trasfObjects:
XmlAddAttribute(node, name="Position", value=Vector3ToString(uSceneModel.position))
XmlAddAttribute(node, name="Rotation", value=Vector4ToString(uSceneModel.rotation))
XmlAddAttribute(node, name="Scale", value=Vector3ToString(uSceneModel.scale))
XmlAddAttribute(node, name="Variables") #extra
comp = XmlAddComponent(node, type=uSceneModel.type, ids=ids)
XmlAddAttribute(comp, name="Model", value="Model;" + modelFile)
XmlAddAttribute(comp, name="Material", value="Material" + materials)
if sOptions.physics:
# Use model's bounding box to compute CollisionShape's size and offset
bbox = uSceneModel.boundingBox
# Size
shapeSize = Vector()
if bbox.min and bbox.max:
shapeSize.x = bbox.max[0] - bbox.min[0]
shapeSize.y = bbox.max[1] - bbox.min[1]
shapeSize.z = bbox.max[2] - bbox.min[2]
# Offset
shapeOffset = Vector()
if bbox.max:
shapeOffset.x = bbox.max[0] - shapeSize.x / 2
shapeOffset.y = bbox.max[1] - shapeSize.y / 2
shapeOffset.z = bbox.max[2] - shapeSize.z / 2
comp = XmlAddComponent(node, type="RigidBody", ids=ids)
XmlAddAttribute(comp, name="Collision Layer", value="2")
XmlAddAttribute(comp, name="Use Gravity", value="false")
comp = XmlAddComponent(node, type="CollisionShape", ids=ids)
shapeType = sOptions.collisionShape
XmlAddAttribute(comp, name="Shape Type", value=shapeType)
if shapeType == "TriangleMesh":
XmlAddAttribute(comp, name="Model", value="Model;" + modelFile)
else:
XmlAddAttribute(comp, name="Size", value=Vector3ToString(shapeSize))
XmlAddAttribute(comp, name="Offset Position", value=Vector3ToString(shapeOffset))
# Names of Blender selected objects
selectedNames = []
for obj in context.selected_objects:
selectedNames.append(obj.name)
# Export full scene: scene elements + scene node
if sOptions.doFullScene:
filepath = GetFilepath(PathType.SCENES, uScene.blenderSceneName, fOptions)
if CheckFilepath(filepath[0], fOptions):
log.info( "Creating full scene {:s}".format(filepath[1]) )
WriteXmlFile(root, filepath[0], fOptions)
# Export a collective node
if sOptions.doCollectivePrefab:
rootNodeCopy = copy.deepcopy(rootNode)
if sOptions.onlySelected:
# Get the IDs of the node of the selected objects
selectedIds = []
for blenderName, xmlNode in xmlNodes.items():
if blenderName in selectedNames:
selectedIds.append(xmlNode.get("id", None))
# Keep only the nodes with the attribute "id" in the list
XmlNodeFilter(rootNodeCopy, "id", selectedIds)
XmlIdSet(rootNodeCopy)
filepath = GetFilepath(PathType.OBJECTS, uScene.blenderSceneName, fOptions)
if CheckFilepath(filepath[0], fOptions):
log.info( "Creating collective prefab {:s}".format(filepath[1]) )
WriteXmlFile(rootNodeCopy, filepath[0], fOptions)
# Export objects nodes (including their children)
if sOptions.doObjectsPrefab:
usedNames = []
noObject = True
for blenderName, xmlNode in xmlNodes.items():
# Filter selected objects
if sOptions.onlySelected and not blenderName in selectedNames:
continue
noObject = False
# From Blender name to plain name, this is useful for LODs but we can have
# duplicates, in that case fallback to the Blender name
name = None
for uSceneModel in uScene.modelsList:
if uSceneModel.blenderName == blenderName:
name = uSceneModel.name
break
if not name or name in usedNames:
name = blenderName
usedNames.append(name)
XmlIdSet(xmlNode)
filepath = GetFilepath(PathType.OBJECTS, name, fOptions)
if CheckFilepath(filepath[0], fOptions):
log.info( "Creating object prefab {:s}".format(filepath[1]) )
WriteXmlFile(xmlNode, filepath[0], fOptions)
if noObject:
log.warning("No object selected for prefab export")
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/tangyin025/blender-exporter.git
[email protected]:tangyin025/blender-exporter.git
tangyin025
blender-exporter
blender-exporter
main

搜索帮助