"""
Utility functions related to loading
and saving PyUnity meshes and scenes.
This will be imported as ``pyunity.Loader``.
"""
from .vector3 import Vector3
from .quaternion import Quaternion
from .meshes import Mesh
from .core import *
from .scenes import SceneManager
from .files import Behaviour, Project, Scripts
from .render import Camera
from .audio import AudioSource, AudioListener
from .physics import AABBoxCollider, SphereCollider # , PhysicMaterial
from uuid import uuid4
import inspect
import json
import os
[docs]def LoadObj(filename):
"""
Loads a .obj file to a PyUnity mesh.
Parameters
----------
filename : str
Name of file
Returns
-------
Mesh
A mesh of the object file
"""
vertices = []
normals = []
faces = []
for line in open(filename, "r"):
if line.startswith("#"):
continue
values = line.split()
if not values:
continue
if values[0] == "v":
v = Vector3(float(values[1]), float(values[3]), float(values[2]))
vertices.append(v)
elif values[0] == "f":
face = []
for v in values[1:]:
w = v.split("/")
face.append(int(w[0]) - 1)
face.reverse()
faces.append(face)
for face in faces:
a = vertices[face[2]] - vertices[face[1]]
b = vertices[face[0]] - vertices[face[1]]
normal = a.cross(b).normalized()
normals.append(normal)
return Mesh(vertices, faces, normals)
def SaveObj(mesh, name, filePath=None):
if filePath:
directory = os.path.dirname(os.path.realpath(filePath))
else:
directory = os.getcwd()
os.makedirs(directory, exist_ok=True)
with open(os.path.join(directory, name + ".obj"), "w+") as f:
for vertex in mesh.verts:
f.write("v " + " ".join(map(str, round(vertex, 8))) + "\n")
for normal in mesh.normals:
f.write("vn " + " ".join(map(str, round(normal, 8))) + "\n")
for face in mesh.triangles:
face = " ".join([
str(face[0] + 1) + "//" + str(face[0] + 1),
str(face[1] + 1) + "//" + str(face[1] + 1),
str(face[2] + 1) + "//" + str(face[2] + 1),
])
f.write("f " + face + "\n")
[docs]def LoadMesh(filename):
"""
Loads a .mesh file generated by
`SaveMesh`. It is optimized for faster
loading.
Parameters
----------
filename : str
Name of file relative to the cwd
Returns
-------
Mesh
Generated mesh
"""
with open(filename, "r") as f:
lines = list(map(lambda x: x.rstrip(), f.readlines()))
if "" in lines:
lines.remove("")
vertices = list(map(float, lines[0].split("/")))
vertices = [
Vector3(vertices[i], vertices[i + 1], vertices[i + 2]) for i in range(0, len(vertices), 3)
]
faces = list(map(int, lines[1].split("/")))
faces = [
[faces[i], faces[i + 1], faces[i + 2]] for i in range(0, len(faces), 3)
]
normals = list(map(float, lines[2].split("/")))
normals = [
Vector3(normals[i], normals[i + 1], normals[i + 2]) for i in range(0, len(normals), 3)
]
texcoords = list(map(float, lines[3].split("/")))
texcoords = [
[texcoords[i], texcoords[i + 1]] for i in range(0, len(texcoords), 2)
]
return Mesh(vertices, faces, normals, texcoords)
[docs]def SaveMesh(mesh, name, filePath=None):
"""
Saves a mesh to a .mesh file
for faster loading.
Parameters
----------
mesh : Mesh
Mesh to save
name : str
Name of the mesh
filePath : str, optional
Pass in `__file__` to save in
directory of script, otherwise
pass in the path of where you
want to save the file. For example, if you
want to save in C:\Downloads, then give
"C:\Downloads\mesh.mesh". If not
specified, then the mesh is saved
in the cwd.
"""
if filePath:
directory = os.path.dirname(os.path.realpath(filePath))
else:
directory = os.getcwd()
os.makedirs(directory, exist_ok=True)
with open(os.path.join(directory, name + ".mesh"), "w+") as f:
i = 0
for vertex in mesh.verts:
i += 1
f.write(str(round(vertex.x, 8)) + "/")
f.write(str(round(vertex.y, 8)) + "/")
f.write(str(round(vertex.z, 8)))
if i != len(mesh.verts):
f.write("/")
f.write("\n")
i = 0
for triangle in mesh.triangles:
i += 1
j = 0
for item in triangle:
j += 1
f.write(str(item))
if i != len(mesh.triangles) or j != 3:
f.write("/")
f.write("\n")
i = 0
for normal in mesh.normals:
i += 1
f.write(str(round(normal.x, 8)) + "/")
f.write(str(round(normal.y, 8)) + "/")
f.write(str(round(normal.z, 8)))
if i != len(mesh.normals):
f.write("/")
f.write("\n")
i = 0
for texcoord in mesh.texcoords:
i += 1
f.write(str(texcoord[0]) + "/")
f.write(str(texcoord[1]))
if i != len(mesh.texcoords):
f.write("/")
f.write("\n")
def GetImports(file):
with open(file) as f:
lines = f.read().rstrip().splitlines()
imports = []
for line in lines:
line = line.lstrip()
if line.startswith("import") or (line.startswith("from") and " import " in line):
imports.append(line)
return "\n".join(imports) + "\n\n"
def SaveSceneToProject(scene, filePath=None):
if filePath:
directory = os.path.dirname(os.path.realpath(filePath))
else:
directory = os.getcwd()
directory = os.path.join(directory, scene.name)
os.makedirs(directory, exist_ok=True)
project = Project(directory, scene.name)
project.import_file(os.path.join("Scenes", scene.name + ".scene"), None)
SaveScene(scene, directory, project)
return project
def SaveAllScenes(name, filePath=None):
if filePath:
directory = os.path.dirname(os.path.realpath(filePath))
else:
directory = os.getcwd()
directory = os.path.join(directory, name)
os.makedirs(directory, exist_ok=True)
project = Project(directory, name)
for scene in SceneManager.scenesByIndex:
SaveScene(scene, directory, project)
project.import_file(os.path.join(
"Scenes", scene.name + ".scene"), None)
return project
def SaveScene(scene, directory, project):
os.makedirs(os.path.join(directory, "Scenes"), exist_ok=True)
f = open(os.path.join(directory, "Scenes", scene.name + ".scene"), "w+")
f.write("Scene : " + str(uuid4()) + "\n")
f.write(" name: " + json.dumps(scene.name) + "\n")
ids = {}
for gameObject in scene.gameObjects:
uuid = str(uuid4())
ids[id(gameObject)] = uuid
f.write("GameObject : " + uuid + "\n")
f.write(" name: " + json.dumps(gameObject.name) + "\n")
f.write(" tag: " + str(gameObject.tag.tag) + "\n")
uuid = str(uuid4())
ids[id(gameObject.transform)] = uuid
f.write(" transform: " + uuid + "\n")
for component in gameObject.components:
if id(component) in ids:
uuid = ids[id(component)]
else:
uuid = str(uuid4())
ids[id(component)] = uuid
if issubclass(type(component), Behaviour):
name = type(component).__name__ + "(Behaviour)"
path = os.path.join(directory, "Scripts",
type(component).__name__ + ".py")
os.makedirs(os.path.dirname(path), exist_ok=True)
with open(path, "w+") as f2:
f2.write(GetImports(inspect.getfile(type(component))))
f2.write(inspect.getsource(type(component)))
project.import_file(os.path.join("Scripts", type(
component).__name__ + ".py"), "Behaviour", uuid)
else:
name = type(component).__name__ + "(Component)"
f.write(name + " : " + uuid + "\n")
f.write(" gameObject: " + ids[id(gameObject)] + "\n")
for attr in component.attrs:
value = getattr(component, attr)
if isinstance(value, Mesh):
if id(value) in ids:
written = ids[id(value)]
else:
written = str(uuid4())
SaveMesh(value, gameObject.name, os.path.join(
directory, "Meshes", gameObject.name + ".mesh"))
project.import_file(os.path.join(
"Meshes", gameObject.name + ".mesh"), "Mesh", written)
elif isinstance(value, Material):
if id(value) in ids:
written = ids[id(value)]
else:
written = str(uuid4())
project.save_mat(value, gameObject.name)
project.import_file(os.path.join(
"Materials", gameObject.name + ".mat"), "Material", written)
else:
written = str(value)
f.write(" " + attr + ": " + written + "\n")
project.write_project()
class ObjectInfo:
def __init__(self, uuid, type, attrs):
self.uuid = uuid
self.type = type
self.attrs = attrs
def __getattr__(self, attr):
return self.attrs[attr]
components = {
"Transform": Transform,
"Camera": Camera,
"Light": Light,
"MeshRenderer": MeshRenderer,
"AABBoxCollider": AABBoxCollider,
"SphereCollider": SphereCollider,
"AudioSource": AudioSource,
"AudioListener": AudioListener
}
"""List of all components by name"""
def parse_string(string):
if string.startswith("Vector3("):
return True, Vector3(*list(map(float, string[8:-1].split(", "))))
if string.startswith("Quaternion("):
return True, Quaternion(*list(map(float, string[11:-1].split(", "))))
if string in ["True", "False"]:
return True, string == "True"
if string == "None":
return True, None
if string.isdigit():
return True, int(string)
try:
return True, float(string)
except (ValueError, OverflowError):
pass
try:
return True, json.loads(string)
except json.decoder.JSONDecodeError:
pass
if string.startswith("(") and string.endswith(")"):
check, items = zip(*list(map(parse_string, string.split(", "))))
if all(check):
return True, tuple(items)
if string.startswith("[") and string.endswith("]"):
check, items = zip(*list(map(parse_string, string[1:-1].split(", "))))
if all(check):
return True, list(items)
return False, None
def LoadProject(filePath):
project = Project.from_folder(filePath)
scenes = [value[1]
for value in project.files.values() if value[0].type == "Scene"]
for path in scenes:
with open(os.path.join(project.path, path), "r") as f:
lines = f.read().rstrip().splitlines()
data = []
for line in lines:
if not line.startswith(" "):
data.append([line])
else:
data[-1].append(line)
infos = []
for info in data:
type_, uuid = info[0].split(" : ")
attrs = {attr: value for attr, value in map(
lambda x: x[4:].split(": "), info[1:])}
infos.append(ObjectInfo(uuid, type_, attrs))
gameObjectInfo = list(filter(lambda x: x.type == "GameObject", infos))
componentInfo = list(filter(lambda x: "(Component)" in x.type, infos))
behaviourInfo = list(filter(lambda x: "(Behaviour)" in x.type, infos))
scene_info = infos.pop(0)
scene = SceneManager.AddBareScene(json.loads(scene_info.name))
ids = {}
gameObjects = []
for info in gameObjectInfo:
gameObject = GameObject.BareObject(json.loads(info.name))
gameObjects.append(gameObject)
gameObject.tag = Tag(int(info.tag))
ids[info.uuid] = gameObject
for info in componentInfo:
gameObject = ids[info.gameObject]
del info.attrs["gameObject"]
component = components[info.type[:-11]]
component = gameObject.AddComponent(component)
ids[info.uuid] = component
for name, value in reversed(info.attrs.items()):
check, obj = parse_string(value)
if check:
setattr(component, name, obj)
elif value in ids:
setattr(component, name, ids[value])
elif value in project.files:
file = project.files[value][0]
if file.type == "Material":
obj = project.load_mat(file)
elif file.type == "Mesh":
obj = LoadMesh(os.path.join(project.path, file.path))
setattr(component, name, obj)
for info in behaviourInfo:
gameObject = ids[info.gameObject]
del info.attrs["gameObject"]
script = Scripts.LoadScripts(os.path.join(filePath, "Scripts"))
gameObject.AddComponent(getattr(script, info.type[:-11]))
for gameObject in gameObjects:
scene.Add(gameObject)
scene.mainCamera = scene.FindGameObjectsByName(
"Main Camera")[0].GetComponent(Camera)
return project
[docs]class Primitives:
"""
Primitive preloaded meshes.
Do not instantiate this class.
"""
__path = os.path.dirname(os.path.realpath(__file__))
cube = LoadMesh(os.path.join(__path, "primitives/cube.mesh"))
quad = LoadMesh(os.path.join(__path, "primitives/quad.mesh"))
double_quad = LoadMesh(os.path.join(__path, "primitives/double_quad.mesh"))
sphere = LoadMesh(os.path.join(__path, "primitives/sphere.mesh"))
capsule = LoadMesh(os.path.join(__path, "primitives/capsule.mesh"))
cylinder = LoadMesh(os.path.join(__path, "primitives/cylinder.mesh"))