I'm currently working with large .obj files in Apple's SceneKit/Model I/O that contain multiple objects within, each with separate textures and materials. This means I cannot apply one single texture to the file like many other form posts suggest. Is there a good way to import in the the materials and textures?
I have my obj mtl and jpg's all in one directory where I'm also putting the scn scene.
The code currently follows this design, where I access it from its respective location, load it into a MDLAsset then place it into a SCNScene where it is saved back to a file to be loaded in later in the code.
//...
// Get the mesh from the obj object
let asset = MDLAsset(url:url)
//asset.loadTextures()
guard let object = asset.object(at: 0) as? MDLMesh else {
fatalError("Failed to get mesh from obj asset.")
}
// Wrap the ModelIO object in a SceneKit object
let scene = SCNScene()
let node = SCNNode(mdlObject: object)
scene.rootNode.addChildNode(node)
// Get the document directory name the write a url for the object replacing its extention with scn
let dirPaths = FileManager().urls(for: .documentDirectory, in: .userDomainMask)
let fileUrl = dirPaths[0].appendingPathComponent(url.lastPathComponent.replacingOccurrences(of: ".obj", with: ".scn"))
// Write the scene to the new url
if !scene.write(to: fileUrl, delegate: nil) {
print("Failed to write scn scene to file!")
return nil
}
// ...
The MDLAsset.loadTextures function has no documentation and only causes a memory leak so at the time of this post, it's not an option. Opening the model by hand and hitting the convert to SCNScene dosn't work either as I still lose the materials. Additionally I want this to be automated in code to allow for downloading and conversion of models at runtime.
It seems like there is not built in way to do this except to do each texture and material by hand in the code, which is easy when it's only one complete texture but this model might have 100 different materials. It looks like it requires me to parse the obj/mtl manually and then create and assign the materials by hand. This seems completely unreasonable and I figure there must be a better way that I don't know about.
When you import an OBJ file via Model I/O as an MDLAsset, it will arrive as a collection of one or more MDLMeshes. The meshes will have MDLMaterials associated with them, and the MDLMaterial will have attributes. Those attributes will be numeric, file paths, or images. You need to iterate the properties, and check if there is a path.
https://developer.apple.com/documentation/modelio/mdlmaterialproperty
If there is, it will likely be a fileURL with the same content as was in the OBJ file's associated MTL file.
The properties described in the MDLScatteringFunction correspond to the various properties in a typical MTL file.
https://developer.apple.com/documentation/modelio/mdlscatteringfunction
MDLAsset.loadTextures will add an MDLTextureSampler value to the property, if Model IO can actually find the texture referenced in the MTL file.