content/hifi-content/caitlyn/scratch/hifi_tools (5)/world/scene.py
2022-02-13 22:19:19 +01:00

315 lines
12 KiB
Python

# ##### BEGIN GPL LICENSE BLOCK #####
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software Foundation,
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
# ##### END GPL LICENSE BLOCK #####
# Scene Logic for Parsing and Navigating Content Tree
# By Matti 'Menithal' Lahtinen
import bpy
from hashlib import md5
from mathutils import Quaternion, Vector, Euler, Matrix
from hifi_tools.world import primitives as prims
from hifi_tools.utils.helpers import PIVOT_VECTOR, swap_nyz, swap_nzy, parse_dict_quaternion, parse_dict_vector, swap_yz, swap_pivot, quat_swap_nyz
class HifiScene:
def __init__(self, json,
uv_sphere=False,
join_children=True,
merge_distance=0.01,
delete_interior_faces=True,
use_boolean_operation="NONE"):
json_entities = json['Entities']
self.uv_sphere = uv_sphere
self.join_children = join_children
self.merge_distance = merge_distance
self.delete_interior_faces = delete_interior_faces
self.use_boolean_operation = use_boolean_operation
self.entities = []
self.materials = []
self.entity_ids = []
self.material_index = []
self.materials = []
self.parent_entities = []
self.root = []
# Build Indices for entity ids, and build the Objects
print(' building indices ')
for idx, entity in enumerate(json_entities):
self.entity_ids.append(entity['id'])
hifi_entity = HifiObject(entity, self)
self.entities.append(hifi_entity)
# Build Trees by checking if parents exist in parent tree
print(' building parents ')
for entity in self.entities:
self.append_parent(entity)
self.build_scene()
# links Parents and children together. to build a tree
def append_parent(self, entity):
# Check if parent id for entity exists
result = self.search_entity(entity.parent_id)
if result is not None:
# add entity as a child of parent
result.add_child(entity)
# set parent as a parent of entity
entity.set_parent(result)
def search_entity(self, id):
# TODO: May need to do some more advanced tricks here maybe later.
found_entity = None
try:
# Get index of instance of the id from existing ids
index = self.entity_ids.index(id)
# if found, return found entity
found_entity = self.entities[index]
except e:
pass
finally:
# Regardless of failure, always return None or the entity
return found_entity
def build_scene(self):
# Store context to set cursor
current_context = bpy.context.area.type
bpy.context.area.type = 'VIEW_3D'
# set context to 3D View and set Cursor
bpy.context.space_data.cursor_location[0] = 0.0
bpy.context.space_data.cursor_location[1] = 0.0
bpy.context.space_data.cursor_location[2] = 0.0
# return context back to earlier, and build scene.
bpy.context.area.type = current_context
print("Building Scene out of " + str(len(self.entities)) + ' Objects and '
+ str(len(self.material_index)) + ' materials')
for entity in self.entities:
if entity.is_root():
entity.build()
def append_material(self, color):
# Just Hash result
material_hash = md5(
str(color[0] + color[1] << 2 + color[2] << 4).encode('utf-8')).hexdigest()
if material_hash not in self.material_index:
index = len(self.material_index)
self.material_index.append(material_hash)
mat = bpy.data.materials.new(str(color))
# convert from rgb to float
mat.diffuse_color = tuple(c/255 for c in color)
# Make sure material at first is not metallic
mat.specular_color = (0, 0, 0)
self.materials.append(mat)
return mat
return self.materials[self.material_index.index(material_hash)]
class HifiObject:
def __init__(self, entity, scene):
self.id = entity['id']
self.children = []
self.blender_object = None
self.scene = scene
# Make sure the Blender Object has a name: And to make it unique, append id of the entity as well.
if 'name' in entity and len(entity['name'].strip()) > 0:
self.name = entity['name'] + '-' + self.id
elif 'shape' in entity:
self.name = entity['shape'] + '-' + self.id
else:
self.name = entity['type'] + '-' + self.id
self.position_original = Vector(parse_dict_vector(entity, 'position'))
self.position = swap_nyz(parse_dict_vector(entity, 'position'))
self.pivot = swap_pivot(parse_dict_vector(
entity, 'registrationPoint', PIVOT_VECTOR))
self.dimensions = swap_yz(parse_dict_vector(entity, 'dimensions'))
self.rotation_original = Quaternion(
parse_dict_quaternion(entity, 'rotation'))
self.rotation = quat_swap_nyz(
parse_dict_quaternion(entity, 'rotation'))
self.parent = None
self.children = []
if 'parentID' in entity:
self.parent_id = entity['parentID']
else:
self.parent_id = None
self.type = entity['type']
self.shape = None
if 'shape' in entity:
self.shape = entity['shape']
if self.type != 'Light' and self.type != 'Zone' and self.type != 'Particle':
if 'color' in entity:
color = entity['color']
self.material = scene.append_material(
(color['red'], color['green'], color['blue']))
else:
self.material = None
def is_root(self):
if self.parent is None:
return True
return False
def select(self):
self.blender_object.select = True
def build(self):
# First places down the children (recursive)
for child in self.children:
child.build()
# Place self by selecting what primitive to add, depending on type and shape.
# Boxes and Spheres are the only primitive to have a separate type vs others
if self.type == 'Shape':
if self.shape == 'Icosahedron':
prims.add_icosahedron(self)
elif self.shape == 'Dodecahedron':
prims.add_docadehedron(self)
elif self.shape == 'Cylinder':
prims.add_cylinder(self)
elif self.shape == 'Octagon':
prims.add_octagon(self)
elif self.shape == 'Hexagon':
prims.add_hexagon(self)
elif self.shape == 'Tetrahedron':
prims.add_tetrahedron(self)
elif self.shape == 'Octahedron':
prims.add_octahedron(self)
elif self.shape == 'Cone':
prims.add_cone(self)
elif self.shape == 'Triangle':
prims.add_triangle(self)
# The following will be deprecated, as they are no longer quads or circles when exported
elif self.shape == 'Quad':
prims.add_quad(self)
elif self.shape == 'Circle':
prims.add_circle(self)
else:
print(' Warning: ', self.shape, ' Not Defined ')
return
else:
if self.type == "Text":
prims.add_box(self)
elif self.type == "Light":
prims.add_light(self)
elif self.type == "Model":
prims.add_box(self)
elif self.type == 'Sphere':
if self.scene.uv_sphere:
prims.add_uv_sphere(self)
else:
prims.add_sphere(self)
elif self.type == 'Box':
prims.add_box(self)
else:
print(' Warning: ', self.type, self.shape, ' Not Supported ')
return
# Now if above is a mesh type to do join / boolean operations on
if self.blender_object.type == "MESH":
for child in self.children:
# Material Combinator to pre-combine materials prior to applying boolean operator or joining objects together
# This allows the materials to be maintained even if they are joined.
# for each material the child's blender objects have
for material in child.blender_object.data.materials.values():
# and if the material is set, and is not yet set for the parent, add an instance of the material to the parent
if material is not None and material not in bpy.context.object.data.materials.values():
bpy.context.object.data.materials.append(material)
# If the scene wants to use boolean operators, this overrides join children (as it is a method to join children)
if self.scene.use_boolean_operation != "NONE":
bpy.ops.object.modifier_add(type='BOOLEAN')
# Set name for modifier to keep track of it.
name = child.name + '-Boolean'
bpy.context.object.modifiers["Boolean"].name = name
bpy.context.object.modifiers[name].operation = 'UNION'
bpy.context.object.modifiers[name].solver = self.scene.use_boolean_operation
bpy.context.object.modifiers[name].object = child.blender_object
bpy.ops.object.modifier_apply(
apply_as='DATA', modifier=name)
# Clean up the child object from the blender scene.
bpy.data.objects.remove(child.blender_object)
# TODO: Set Child.blender_object as the blender object of the parent to maintain links
# If not boolean operator, but join_children is still True
elif self.scene.join_children:
# Select the child
child.select()
# Select self
self.select()
# Join
bpy.ops.object.join()
# Safety select
self.select()
bpy.ops.object.modifier_add(type='EDGE_SPLIT')
bpy.ops.object.mode_set(mode='EDIT')
if len(self.children) > 0:
bpy.ops.mesh.remove_doubles(
threshold=self.scene.merge_distance)
# endif
# And at the end, make sure object is selected.
bpy.ops.object.mode_set(mode='OBJECT')
# Position and rotations of children are always relative to parent in Hifi Tree.
# Get the absolute position by getting the relative position of the parent, and adding my own to it.
# note that then position is relative to the parents rotation too, so make sure to eliminate that as well.
def relative_position(self):
if self.parent is not None:
return self.parent.relative_rotation() * self.position + self.parent.relative_position()
else:
return self.position
# Rotation is based on the rotaiton of the parent and self.
def relative_rotation(self):
if self.parent is not None:
rotation = self.parent.relative_rotation()
return rotation * self.rotation
else:
return self.rotation
def set_parent(self, parent):
if type(parent) is HifiObject:
self.parent = parent
else:
print('Warning: Type parent was not HifiObject')
def add_child(self, child):
self.children.append(child)