use crate::idtree;
use blender;
use crate::bones::Skeleton;
use crate::model::Model;
use crate::utils::{self, ObjectId, LibraryId};
use crate::curves;
use crate::lamp;
use crate::scene::{SceneData, BlenderObject};
use crate::empty::Empty;
use crate::shape_keys;
use hashbrown::HashMap;
use std::path:: PathBuf;
use crate::mesh::Mesh;
use crate::trimesh::TriMesh;
use super::packedfile;
use itertools::Itertools;
fn load_libraries(blend: &blender::File) -> Result<(HashMap<LibraryId, blender::File>, LibraryId), blender::Error> {
let mut libraries = HashMap::new();
let library_id = if let Some(file_path) = blend.file_path(){
LibraryId::Path(file_path.to_owned())
}else{
LibraryId::Packed(None, "/".to_owned())
};
load_libraries_recursively(blend, &library_id, &mut libraries)?;
libraries.insert(library_id.clone(), blend.clone());
Ok((libraries, library_id))
}
fn load_actions(blend: &blender::File, scene_data: &mut SceneData, library_id: &LibraryId, libraries: &HashMap<LibraryId, blender::File>){
scene_data.actions = curves::parse_actions(blend, library_id, libraries);
}
pub fn load(blend: &blender::File) -> Result<SceneData, blender::Error> {
let path = blend.file_path().map(|p| p.to_owned());
let mut scene_data = SceneData::new(path);
let (libraries, library_id) = load_libraries(blend)?;
load_actions(blend, &mut scene_data, &library_id, &libraries);
blend_objects_to_scene_data(&mut scene_data, blend, &library_id, None, None, &libraries);
Ok(scene_data)
}
fn group_objects<'a>(blend: &'a blender::File, group_names: &[&str]) -> Result<Vec<blender::Object<'a>>, blender::Error>{
let groups = blend.objects("Group");
let groups = groups
.filter(|g| group_names.contains(&g.name().unwrap()) )
.collect::<Vec<_>>();
if groups.len() == group_names.len() {
let objects = groups.into_iter()
.flat_map(|group| group.get_list("gobject").unwrap().iter()
.filter_map(|go| go.get_object("ob").ok()))
.collect();
Ok(objects)
}else{
Err(blender::Error::from(format!("Couldn't find some groups when loading blender file. Asked for {:?} got {:?}",
group_names,
groups.iter().map(|g| g.name().unwrap()).collect::<Vec<_>>())))
}
}
fn collection_objects<'a>(blend: &'a blender::File, group_names: &[&str]) -> Result<Vec<blender::Object<'a>>, blender::Error>{
let collections = blend.objects("Collection");
let collections = collections
.filter(|g| group_names.contains(&g.name().unwrap()))
.collect::<Vec<_>>();
if collections.len() == group_names.len() {
let objects = collections.into_iter()
.flat_map(|collection| parse_collection(collection))
.collect();
Ok(objects)
}else{
Err(blender::Error::from(format!("Couldn't find some collections when loading blender file. Asked for {:?} got {:?}",
group_names,
collections.iter().map(|c| c.name().unwrap()).collect::<Vec<_>>())))
}
}
pub fn load_groups(blend: &blender::File, group_names: &[&str]) -> Result<SceneData, blender::Error>{
let path = blend.file_path().map(|p| p.to_owned());
let mut scene_data = SceneData::new(path);
let (libraries, library_id) = load_libraries(blend)?;
load_actions(blend, &mut scene_data, &library_id, &libraries);
let version: u32 = blend.header().version_number().parse().unwrap();
let objects = if version >= 280 {
collection_objects(blend, group_names)?
}else{
group_objects(blend, group_names)?
};
blend_objects_to_scene_data(&mut scene_data, blend, &library_id, Some(&objects), None, &libraries);
Ok(scene_data)
}
fn objects<'a>(blend: &'a blender::File, object_names: &[&str]) -> Vec<blender::Object<'a>>{
let objects = blend.objects("Object");
objects
.filter(|o| object_names.contains(&o.name().unwrap()) )
.collect()
}
pub fn load_objects(blend: &blender::File, object_names: &[&str]) -> Result<SceneData, blender::Error> {
let path = blend.file_path().map(|p| p.to_owned());
let mut scene_data = SceneData::new(path);
let (libraries, library_id) = load_libraries(blend)?;
load_actions(blend, &mut scene_data, &library_id, &libraries);
let objects = objects(blend, object_names);
blend_objects_to_scene_data(&mut scene_data, blend, &library_id, Some(&objects), None, &libraries);
Ok(scene_data)
}
fn load_action(action_id: &ObjectId, scene_data: &mut SceneData, libraries: &HashMap<LibraryId, blender::File>){
if !scene_data.actions.contains_key(action_id) {
let library = &libraries[&action_id.source_file];
let fps = curves::fps_for_file(library);
let default_action = library.object_by_name(&action_id.name).unwrap();
let default_action = curves::parse_action(default_action, fps).unwrap();
scene_data.actions.insert(action_id.clone(), default_action);
}
}
fn load_blend_meshes(
scene_data: &mut SceneData,
library_id: LibraryId,
blend_mesh: &blender::Object,
vertex_groups: &[String],
libraries: &HashMap<LibraryId, blender::File>)
{
let object_id = library_id.object_id(blend_mesh);
if scene_data.meshes.contains_key(&object_id){
return;
}
let version = libraries[&object_id.source_file].header().version_number().parse().unwrap();
let mesh = Mesh::parse(&blend_mesh, version).ok();
if let Some(mesh) = mesh {
let trimesh = TriMesh::from(
&blend_mesh,
library_id.clone(),
&mesh,
libraries,
scene_data,
);
if let Some(key_id) = trimesh.key() {
if !scene_data.keys.contains_key(&key_id){
let library = &libraries[&key_id.source_file];
let key_obj = library.object_by_name(&key_id.name).unwrap();
let key = shape_keys::parse(&key_obj, key_id.source_file.clone(), libraries, scene_data);
scene_data.keys.insert(key_id.clone(), key);
}
if !scene_data.trimesh_keys.contains_key(&key_id) {
let trimeshkey = scene_data.keys[key_id].clone()
.to_trimesh(
&blend_mesh,
library_id.clone(),
&mesh,
vertex_groups,
libraries,
scene_data,
);
scene_data.trimesh_keys.insert(key_id.clone(), trimeshkey);
}
}
scene_data.meshes.insert(object_id.clone(), mesh);
scene_data.trimeshes.insert(object_id, trimesh);
}
}
fn load_libraries_recursively(
blend: &blender::File,
parent_id: &LibraryId,
libraries: &mut HashMap<LibraryId, blender::File>) -> Result<(), blender::Error>
{
for lib in blend.objects("Library") {
if lib.get_str("name").unwrap().len() > 2 {
let lib_name = &lib.get_str("name").unwrap()[2..];
let lib_id;
let lib = if let Ok(packedfile) = lib.get_object("packedfile"){
let data = packedfile::data(&packedfile).unwrap();
lib_id = if let Some(parent_path) = blend.file_path().map(|p| p.to_owned()) {
LibraryId::Packed(Some(parent_path), lib_name.to_owned())
}else{
parent_id.join(lib_name)
};
blender::File::from_bytes(data)
.map_err(|err| blender::Error(format!("Couldn't load linked library from packed file: {}", err)))?
}else{
let lib_path = if let Some(parent_path) = blend.file_path().and_then(|p| p.parent()){
parent_path.join(lib_name)
}else{
PathBuf::from(lib_name)
};
lib_id = LibraryId::Path(lib_path.clone());
blender::File::load(&lib_path)
.map_err(|err| blender::Error(format!("Couldn't load linked library from {}: {}", lib_path.display(), err)))?
};
load_libraries_recursively(&lib, &lib_id, libraries)?;
libraries.insert(lib_id, lib);
}
}
Ok(())
}
fn blend_objects_to_scene_data(
scene_data: &mut SceneData,
blend: &blender::File,
library_id: &LibraryId,
objects: Option<&[blender::Object]>,
parent: Option<idtree::NodeId>,
libraries: &HashMap<LibraryId, blender::File>)
{
let blender_scene = blend.find_object("Scene").unwrap();
let scene_eevee = blender_scene.get_object("eevee").ok();
match blender_scene.get_object("master_collection"){
Ok(master_collection) => {
let root_objects = parse_collection(master_collection).filter_map(|obj| {
if obj.get_object("parent").is_err(){
Some(obj)
}else{
None
}
});
for obj in root_objects {
parse_object(
&obj,
library_id,
parent,
objects,
"",
scene_data,
libraries,
scene_eevee.as_ref()
)
}
}
Err(_) => {
let root_objects = blender_scene.get_list("base").unwrap().iter().filter_map(|base| {
let obj = base.get_object("object").unwrap();
if obj.get_object("parent").is_err(){
Some(obj)
}else{
None
}
});
for obj in root_objects {
parse_object(
&obj,
library_id,
parent,
objects,
"",
scene_data,
libraries,
scene_eevee.as_ref()
)
}
}
}
}
fn parse_collection<'a>(collection: blender::Object<'a>) -> impl Iterator<Item = blender::Object<'a>>{
collection.get_list("children").unwrap().iter().flat_map(|child|{
let collection = child.get_object("collection").unwrap();
parse_collection(collection).collect::<Vec<_>>()
}).chain(collection.get_list("gobject").unwrap().iter().map(|obj| {
obj.get_object("ob").unwrap()
})).unique_by(|obj| obj.name().ok())
}
#[allow(dead_code)]
pub enum ObjectRestrict{
View = 1 << 0,
Select = 1 << 1,
Render = 1 << 2,
}
fn parse_empty_linked(
obj: &blender::Object,
parent_library_id: &LibraryId,
scene_data: &mut SceneData,
libraries: &HashMap<LibraryId, blender::File>,
parent: idtree::NodeId,
) -> Result<(), blender::Error>
{
let dup_group = obj.get_object("dup_group")?;
let group_name = dup_group.name()?;
let library_id= parent_library_id.linked_library_id(&dup_group)
.ok_or_else(|| "Can't find linked library".to_owned())?;
let lib = &libraries[&library_id];
trace!("Parsing library children for {} from group {} in file {:?}", obj.name().unwrap(), group_name, lib.file_path());
let version: u32 = lib.header().version_number().parse().unwrap();
let group_objects = if version >= 280 {
collection_objects(&lib, &[group_name])?
}else{
group_objects(&lib, &[group_name])?
};
blend_objects_to_scene_data(
scene_data,
&lib,
&library_id,
Some(&group_objects),
Some(parent),
libraries
);
trace!("Done parsing library children for {}", obj.name().unwrap());
Ok(())
}
fn parse_as_empty(
obj: &blender::Object,
library_id: LibraryId,
scene_data: &mut SceneData,
parent: Option<idtree::NodeId>,
libraries: &HashMap<LibraryId, blender::File>,
parsed_linked: bool) -> Option<idtree::NodeId>
{
trace!("Parsing empty {}", obj.name().unwrap());
let transformations = utils::transformations(&obj);
let name = obj.name().unwrap().to_owned();
let default_action =
curves::default_action_name_for(&obj, &library_id, libraries);
if let Some(default_action_id) = default_action.as_ref() {
load_action(default_action_id, scene_data, libraries);
}
let trafos = BlenderObject::Empty(Empty{
name: ObjectId::new(library_id.clone(), &name),
transformations,
default_action
});
let id = if let Some(parent) = parent{
parent.append_new(trafos, &mut scene_data.arena).id()
}else{
scene_data.arena.new_node(trafos).id()
};
if parsed_linked {
if let Err(_) = parse_empty_linked(obj, &library_id, scene_data, libraries, id){
trace!("No linked collection for this empty")
}
}
scene_data.empties.insert(ObjectId::new(library_id, &name), id);
Some(id)
}
fn check_children_in_groups(
parent: &blender::Object,
parent_library_id: &LibraryId,
objects: &[blender::Object],
libraries: &HashMap<LibraryId, blender::File>) -> bool
{
for child in parent.children() {
let name = child.name().unwrap().to_owned();
let obj;
let library_id;
if let Some(lib_id) = parent_library_id.linked_library_id(parent){
let lib = &libraries[&lib_id];
obj = lib.objects("Object")
.find(|linked| linked.name().unwrap() == child.name().unwrap())
.unwrap();
library_id = lib_id;
}else{
obj = child.clone();
library_id = parent_library_id.clone();
};
if objects.iter().find(|obj| obj.name() == Ok(&name)).is_some() {
return true
}
if check_children_in_groups(
&obj,
&library_id,
objects,
libraries)
{
return true
}
}
false
}
fn parse_object(blend_obj: &blender::Object,
parent_library_id: &LibraryId,
parent: Option<idtree::NodeId>,
objects: Option<&[blender::Object]>,
indent: &str,
scene_data: &mut SceneData,
libraries: &HashMap<LibraryId, blender::File>,
scene_eevee: Option<&blender::Object>,
)
{
let obj;
let library_id;
if let Some(lib_id) = parent_library_id.linked_library_id(blend_obj){
trace!("Following link {}", blend_obj.name().unwrap());
let lib = &libraries[&lib_id];
obj = lib.objects("Object")
.find(|linked| linked.name().unwrap() == blend_obj.name().unwrap())
.unwrap();
library_id = lib_id;
}else{
obj = blend_obj.clone();
library_id = parent_library_id.clone();
};
let ty = *obj.get::<blender::ObjectType>("type").unwrap();
let restrictflag: u8 = *obj.get("restrictflag").unwrap();
let hidden = restrictflag & ObjectRestrict::Render as u8 != 0;
let nodeid = if let Ok(proxied) = obj.get_object("proxy") {
let proxy_library_id = library_id.linked_library_id(&proxied)
.unwrap_or_else(|| library_id.clone());
trace!("Found proxy {} in {} -> {} in {}", obj.name().unwrap(), library_id, proxied.name().unwrap(), proxy_library_id);
scene_data.proxies.insert(
ObjectId::new(library_id.clone(), obj.name().unwrap()),
ObjectId::new(proxy_library_id, proxied.name().unwrap())
);
None
}else{
if objects.is_none() || objects.unwrap().iter()
.find(|obj2| obj2.name().unwrap() == obj.name().unwrap())
.is_some()
{
trace!("Found object {:?} type {:?}", obj.name(), ty);
match ty{
blender::ObjectType::Armature => {
trace!("Parsing armature {}", obj.name().unwrap());
let data = obj.get_object("data").unwrap();
let skeleton = if let Some(lib_id) = library_id.linked_library_id(&data) {
let lib = &libraries[&lib_id];
let data_name = data.name().unwrap();
let armature_data = lib
.objects("bArmature")
.find(|armature_data| {
armature_data.name() == Ok(data_name)
})
.unwrap();
let name = if let Ok(proxy) = obj.get_object("proxy"){
proxy.name().unwrap()
}else{
obj.name().unwrap()
};
trace!("From library as {}", name);
Skeleton::parse(name, &obj, &lib_id, &armature_data, libraries)
}else{
Skeleton::parse(obj.name().unwrap(), &obj, &library_id, &data, libraries)
};
if let Some(default_action) = skeleton.default_action() {
load_action(default_action, scene_data, libraries);
}
let skeleton = BlenderObject::Armature(skeleton);
let object_id = skeleton.name().clone();
let nodeid = if let Some(parent) = parent{
parent.append_new(skeleton, &mut scene_data.arena).id()
}else{
scene_data.arena.new_node(skeleton).id()
};
scene_data.skeletons.insert(object_id, nodeid);
Some(nodeid)
}
blender::ObjectType::Mesh => {
trace!("Parsing model {}", obj.name().unwrap());
let vertex_groups: Vec<String> = obj.get_list("defbase").unwrap().iter()
.map(|deformation| {
let vertex_group = deformation.name().unwrap().to_string();
vertex_group
}).collect();
let mesh_obj = obj.get_object("data").unwrap();
let model = if let Some(lib_id) = library_id.linked_library_id(&mesh_obj) {
let lib = &libraries[&lib_id];
let mesh_library_id = lib_id;
let mesh_name = mesh_obj.name().unwrap();
let mesh_data = lib.objects("Mesh")
.find(|mesh_data| {
mesh_data.name() == Ok(mesh_name)
})
.unwrap();
load_blend_meshes(
scene_data,
mesh_library_id.clone(),
&mesh_data,
&vertex_groups,
libraries
);
Model::parse(
&obj,
mesh_library_id.clone(),
&mesh_data,
!hidden,
scene_data,
libraries
).unwrap()
}else{
load_blend_meshes(
scene_data,
library_id.clone(),
&mesh_obj,
&vertex_groups,
libraries
);
Model::parse(
&obj,
library_id.clone(),
&mesh_obj,
!hidden,
scene_data,
libraries
).unwrap()
};
if let Some(default_action) = model.default_action() {
load_action(default_action, scene_data, libraries);
}
let object_id = model.name().clone();
let model = BlenderObject::Model(model);
let nodeid = if let Some(parent) = parent{
parent.append_new(model, &mut scene_data.arena).id()
}else{
scene_data.arena.new_node(model).id()
};
scene_data.models.insert(object_id, nodeid);
Some(nodeid)
}
blender::ObjectType::Lamp => {
trace!("Parsing lamp {}", obj.name().unwrap());
lamp::parse(&obj, library_id.clone(), !hidden, scene_eevee).map(|lamp| {
let name = lamp.name.name.clone();
let lamp = BlenderObject::Light(lamp);
let nodeid = if let Some(parent) = parent{
parent.append_new(lamp, &mut scene_data.arena).id()
}else{
scene_data.arena.new_node(lamp).id()
};
scene_data.lamps.insert(ObjectId::new(library_id.clone(), &name), nodeid);
nodeid
})
}
blender::ObjectType::Empty => {
parse_as_empty(&obj, library_id.clone(), scene_data, parent, libraries, true)
}
_ => {
trace!("Found an unsupported type {:?}, parsing as empty", ty);
parse_as_empty(&obj, library_id.clone(), scene_data, parent, libraries, false)
},
}
}else{
if check_children_in_groups(
&obj,
&library_id,
objects.as_ref().unwrap(),
libraries)
{
trace!("Found object {:?} not in list of objects, creating as empty and not parsing linked", obj.name());
parse_as_empty(&obj, library_id.clone(), scene_data, parent, libraries, false)
}else{
None
}
}
};
if let Some(nodeid) = nodeid{
if parent.is_none(){
scene_data.roots.push(nodeid);
}
for child in obj.children() {
trace!("Parsing {} child {:?}", obj.name().unwrap(), child.name());
parse_object(
&child,
&library_id,
Some(nodeid),
objects,
&(indent.to_string() + "\t"),
scene_data,
libraries,
scene_eevee)
}
}
}