在之前的文章《ParaView和VTK中场景的导入与导出》中分享过一个示例,通过VTK读入3DS MAX的文件,然后导出obj文件,并在ParaView中进行呈现,如下所示:
最近有小伙伴问:obj文件在ParaView里面为什么没有颜色渲染?这是因为obj里面只保存了几何信息,材质相关的信息保存在.mtl格式的文件中,ParaView不能读取.mtl格式文件,所以没有颜色渲染。
其实在之前的示例中,导出obj文件时,mtl文件也一并导出了,需要转换一下格式就能被ParaView读取了。Kitware的开发人员在GItHub上分享了转换程序obj-mtl-importer.py,粘贴如下:
#!/Applications/ParaView-5.4.0.app/Contents/bin/pvpython
# -----------------------------------------------------------------------------
# User configuration
# -----------------------------------------------------------------------------
# objToLoad = '/Users/seb/Documents/code/Web/vtk-js/Data/obj/ferrari-f1-race-car/ferrari-f1-race-car.obj'
# mtlToLoad = '/Users/seb/Documents/code/Web/vtk-js/Data/obj/ferrari-f1-race-car/ferrari-f1-race-car.mtl'
# objToLoad = '/Users/seb/Documents/code/Web/vtk-js/Data/obj/blskes-plane/blskes-plane.obj'
# mtlToLoad = '/Users/seb/Documents/code/Web/vtk-js/Data/obj/blskes-plane/blskes-plane.mtl'
# objToLoad = '/Users/seb/Documents/code/Web/vtk-js/Data/obj/mini-cooper/mini-cooper.obj'
# mtlToLoad = '/Users/seb/Documents/code/Web/vtk-js/Data/obj/mini-cooper/mini-cooper.mtl'
# objToLoad = '/Users/seb/Documents/code/Web/vtk-js/Data/obj/space-shuttle-orbiter/space-shuttle-orbiter.obj'
# mtlToLoad = '/Users/seb/Documents/code/Web/vtk-js/Data/obj/space-shuttle-orbiter/space-shuttle-orbiter.mtl'
# -----------------------------------------------------------------------------
from vtk import *
from paraview import simple
import os
import sys
import hashlib
import json
# -----------------------------------------------------------------------------
def nameRemap(name):
if 'Paneel' in name:
return '3204_Paneel_4Hoek_Definition1'
if 'Zijgevel' in name:
return 'seb_white_steel'
return name
# -----------------------------------------------------------------------------
# obj Parser
# -----------------------------------------------------------------------------
class OBJParser(object):
def __init__(self, objFilePath, splitMode = None):
self.splitOn = splitMode
self.pieces = [];
self.v = [];
self.vt = [];
self.vn = [];
self.f = [[]];
self.size = 0;
self.output = []
with open(objFilePath, "r") as objLines:
for line in objLines:
self.parseLine(line.rstrip('\n'))
self.end();
@staticmethod
def createPoints(pythonArray):
pts = vtkPoints()
nbPoints = len(pythonArray) / 3
pts.SetNumberOfPoints(nbPoints);
for i in range(nbPoints):
pts.SetPoint(i, pythonArray[(i * 3) + 0], pythonArray[(i * 3) + 1], pythonArray[(i * 3) + 2])
return pts
@staticmethod
def createCellArray(pythonArray, nbCells):
cellArray = vtkCellArray()
cellArray.SetNumberOfCells(nbCells)
idArray = cellArray.GetData()
idArray.SetNumberOfTuples(len(pythonArray))
for i in range(len(pythonArray)):
idArray.SetValue(i, pythonArray[i])
return cellArray
@staticmethod
def createFloatArray(name, nbComponents, pythonArray):
array = vtkFloatArray()
array.SetName(name)
array.SetNumberOfComponents(nbComponents)
array.SetNumberOfTuples(len(pythonArray) / nbComponents)
for i in range(len(pythonArray)):
array.SetValue(i, pythonArray[i])
return array
@staticmethod
def pushVector(src, srcOffset, dst, vectorSize):
for i in range(vectorSize):
dst.append(src[srcOffset + i])
@staticmethod
def faceMap(str):
idxs = [int(i) if len(i) else 1 for i in str.split('/')]
vertexIdx = int(idxs[0] - 1);
textCoordIdx = int(idxs[1] - 1) if len(idxs) > 1 else vertexIdx
vertexNormal = int(idxs[2] - 1) if len(idxs) > 2 else vertexIdx
return [vertexIdx, textCoordIdx, vertexNormal]
def parseLine(self, line):
if len(line.strip()) == 0 or line[0] == '#':
return
tokens = line.strip().split()
if tokens[0] == self.splitOn:
tokens.pop(0)
self.pieces.append(' '.join(tokens).strip())
self.f.append([])
self.size += 1;
elif tokens[0] == 'v':
self.v.append(float(tokens[1]))
self.v.append(float(tokens[2]))
self.v.append(float(tokens[3]))
elif tokens[0] == 'vt':
self.vt.append(float(tokens[1]))
self.vt.append(float(tokens[2]))
elif tokens[0] == 'vn':
self.vn.append(float(tokens[1]))
self.vn.append(float(tokens[2]))
self.vn.append(float(tokens[3]))
elif tokens[0] == 'f':
# Handle triangles for now
if self.size == 0:
self.size += 1
cells = self.f[self.size - 1];
tokens.pop(0)
size = len(tokens)
cells.append(size)
for i in range(size):
cells.append(OBJParser.faceMap(tokens[i]))
def end(self):
hasTcoords = True if len(self.vt) > 0 else False
hasNormals = True if len(self.vn) > 0 else False
if self.splitOn:
for idx in range(self.size):
ctMapping = {}
polydata = vtkPolyData()
pts = []
tc = []
normals = []
polys = []
nbCells = 0
polyIn = self.f[idx]
nbElems = len(polyIn)
offset = 0
while offset < nbElems:
cellSize = polyIn[offset]
nbCells += 1
polys.append(cellSize)
for pIdx in range(cellSize):
vIdx, tcIdx, nIdx = polyIn[offset + pIdx + 1]
key = '%d/%d/%d' % (vIdx, tcIdx, nIdx)
if key not in ctMapping:
ctMapping[key] = len(pts) / 3
OBJParser.pushVector(self.v, vIdx * 3, pts, 3)
if hasTcoords:
OBJParser.pushVector(self.vt, tcIdx * 2, tc, 2)
if hasNormals:
OBJParser.pushVector(self.vn, nIdx * 3, normals, 3)
polys.append(ctMapping[key])
offset += cellSize + 1;
polydata.SetPoints(OBJParser.createPoints(pts))
polydata.SetPolys(OBJParser.createCellArray(polys, nbCells))
if hasTcoords:
tcoords = OBJParser.createFloatArray('TextureCoordinates', 2, tc)
polydata.GetPointData().SetTCoords(tcoords);
if hasNormals:
normalsArray = OBJParser.createFloatArray('Normals', 3, normals)
polydata.GetPointData().SetNormals(normalsArray)
# register in output
self.output.append(polydata)
print(self.pieces[idx])
else:
polydata = vtkPolyData()
polydata.SetPoints(OBJParser.createPoints(self.v))
if hasTcoords and (len(self.v) / 3) == (len(self.vt) / 2):
tcoords = OBJParser.createFloatArray('TextureCoordinates', 2, self.vt)
polydata.GetPointData().SetTCoords(tcoords);
if hasNormals and (len(self.v) == len(self.vn)):
normalsArray = OBJParser.createFloatArray('Normals', 3, self.vn)
polydata.GetPointData().SetNormals(normalsArray)
polys = []
polyIn = self.f[0]
nbElems = len(polyIn)
offset = 0
nbCells = 0
while offset < nbElems:
cellSize = polyIn[offset]
nbCells += 1
polys.append(cellSize)
for pIdx in range(cellSize):
polys.append(polyIn[offset + pIdx + 1][0])
offset += cellSize + 1
polydata.SetPolys(OBJParser.createCellArray(polys, nbCells))
self.output.append(polydata)
# -----------------------------------------------------------------------------
# mtl Parser
# -----------------------------------------------------------------------------
def materialToSHA(mat):
keys = mat.keys()
keys.sort()
m = hashlib.md5()
for key in keys:
m.update(key)
for token in mat[key]:
m.update(token)
return m.hexdigest()
class MTLParser(object):
def __init__(self, mtlFilePath):
self.materials = {}
self.currentMaterial = None
self.textures = {}
self.baseDir = os.path.dirname(mtlFilePath)
self.reducedMaterialMap = {}
self.reverseReduceMap = {}
self.representationsParameters = {}
with open(mtlFilePath, "r") as lines:
for line in lines:
self.parseLine(line.rstrip('\n'))
def parseLine(self, line):
if len(line.strip()) == 0 or line[0] == '#':
return
tokens = line.strip().split()
if tokens[0] == 'newmtl':
tokens.pop(0);
self.currentMaterial = ' '.join(tokens).strip()
elif self.currentMaterial:
if self.currentMaterial not in self.materials:
self.materials[self.currentMaterial] = {}
if len(tokens) > 1:
self.materials[self.currentMaterial][tokens[0]] = tokens[1:]
def reduceMaterialDefinitions(self):
for name in self.materials:
sha = materialToSHA(self.materials[name])
self.reducedMaterialMap[name] = sha
self.reverseReduceMap[sha] = name
print('Reducing materials from %s to %s' % (len(self.reducedMaterialMap), len(self.reverseReduceMap)))
def applyMaterialToRepresentation(self, name, representation):
self.representationsParameters[name] = {}
material = {}
if name in self.materials:
material = self.materials[name]
if name in self.reverseReduceMap:
material = self.materials[self.reverseReduceMap[name]]
if 'map_Kd' in material:
if name not in self.textures:
from paraview import servermanager
texture = servermanager._getPyProxy(servermanager.CreateProxy('textures', 'ImageTexture'))
texture.FileName = os.path.join(self.baseDir, material['map_Kd'][0])
self.textures[name] = texture
servermanager.Register(texture)
representation.Texture = self.textures[name]
if 'Ka' in material:
representation.AmbientColor = [float(n) for n in material['Ka']]
self.representationsParameters[name]['AmbientColor'] = [float(n) for n in material['Ka']]
if 'Ks' in material:
representation.SpecularColor = [float(v) for v in material['Ks']]
self.representationsParameters[name]['SpecularColor'] = [float(v) for v in material['Ks']]
if 'Kd' in material:
representation.DiffuseColor = [float(v) for v in material['Kd']]
self.representationsParameters[name]['DiffuseColor'] = [float(v) for v in material['Kd']]
if 'd' in material:
representation.Opacity = float(material['d'][0])
self.representationsParameters[name]['Opacity'] = float(material['d'][0])
if 'Ns' in material:
representation.SpecularPower = float(material['Ns'][0])
self.representationsParameters[name]['SpecularPower'] = float(material['Ns'][0])
if 'illum' in material:
representation.Ambient = 1.0 if 0 <= float(material['illum'][0]) else 0.0
representation.Diffuse = 1.0 if 1 <= float(material['illum'][0]) else 0.0
representation.Specular = 1.0 if 2 <= float(material['illum'][0]) else 0.0
self.representationsParameters[name]['Ambient'] = 1.0 if 0 <= float(material['illum'][0]) else 0.0
self.representationsParameters[name]['Diffuse'] = 1.0 if 1 <= float(material['illum'][0]) else 0.0
self.representationsParameters[name]['Specular'] = 1.0 if 2 <= float(material['illum'][0]) else 0.0
# else:
# representation.Ambient = 1.0
# representation.Diffuse = 1.0
# representation.Specular = 1.0
# -----------------------------------------------------------------------------
# Mesh writer builder
# -----------------------------------------------------------------------------
def writeMeshes(meshBaseDirectory, objReader, nameMapping = {}):
nameToFilePath = {}
writer = vtkXMLPolyDataWriter()
ext = '.vtp'
if not os.path.exists(meshBaseDirectory):
os.makedirs(meshBaseDirectory)
nbPieces = len(objReader.pieces)
dsList = {}
nameToKey = nameMapping
for idx in range(nbPieces):
name = objReader.pieces[idx]
if name not in nameToKey:
nameToKey[name] = name
# Gather polydata with same textures
for idx in range(nbPieces):
name = objReader.pieces[idx]
key = nameToKey[name]
if key not in dsList:
dsList[key] = []
dsList[key].append(objReader.output[idx])
# Write each dataset
idx = 0
size = len(dsList)
for name in dsList:
fullPath = os.path.join(meshBaseDirectory, '%s%s' % (name, ext))
merge = vtkAppendPolyData()
for block in dsList[name]:
merge.AddInputData(block)
merge.Update()
writer.SetInputData(merge.GetOutput(0))
writer.SetFileName(fullPath)
writer.Modified()
print('%d - %d/%d - %s => %s' % ((idx + 1), len(dsList[name]), size, name, fullPath))
writer.Write()
nameToFilePath[name] = fullPath
idx += 1
return nameToFilePath;
# -----------------------------------------------------------------------------
# Scene Loader
# -----------------------------------------------------------------------------
def loadScene(objFilePath, mtlFilePath):
mtlReader = MTLParser(mtlFilePath)
mtlReader.reduceMaterialDefinitions()
objReader = OBJParser(objFilePath, 'usemtl')
meshBaseDirectory = os.path.join(os.path.dirname(objFilePath), os.path.basename(objFilePath)[:-4])
# custom remap
mapToSha = {}
for key in mtlReader.reducedMaterialMap:
mapToSha[key] = mtlReader.reducedMaterialMap[nameRemap(key)]
meshMapping = writeMeshes(meshBaseDirectory, objReader, mapToSha)
for name in meshMapping:
source = simple.OpenDataFile(meshMapping[name], guiName=name)
rep = simple.Show(source)
mtlReader.applyMaterialToRepresentation(name, rep)
with open('%s/representations.json' % meshBaseDirectory, "w") as text_file:
text_file.write(json.dumps(mtlReader.representationsParameters, indent=2, sort_keys=True))
simple.Render()
# -----------------------------------------------------------------------------
# Main
# -----------------------------------------------------------------------------
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: obj-mtl-importer /path/to/file.obj")
else:
objPath = sys.argv[1]
loadScene(objPath, '%s.mtl' % objPath[:-4])
simple.SaveState('%s-pv-state.pvsm' % objPath[:-4])
使用这个脚本的命令如下:
/path/to/paraview/bin/pvpython obj-mtl-importer.py /path/to/your/file.obj
通过pvpython运行脚本obj-mtl-importer.py,后面是需要转换的obj文件,默认mtl文件和obj是在同一目录下的。转换后会在脚本的目录下生成一个*.pvsm的状态文件,一系列包含几何拓扑、属性等信息的*.vtp文件和一个包含材质信息的*.json文件。
最后,通过ParaView的load state加载car-pv-state.pvsm,就可读入带材质的几何了,如下:
来源:CSDN
作者:_黄岛主_
链接:https://blog.csdn.net/dsfsdffgfd/article/details/103794025