提交 51fdb4c7 编写于 作者: M Mr.doob

Merge pull request #6048 from repsac/io_three

Updating the Blender exporter
......@@ -34,6 +34,7 @@ MAPPING_TYPES = type('Mapping', (), {
JSON = 'json'
EXTENSION = '.%s' % JSON
INDENT = 'indent'
MATERIALS = 'materials'
......@@ -49,8 +50,11 @@ SCALE = 'scale'
COMPRESSION = 'compression'
MAPS = 'maps'
FRAME_STEP = 'frameStep'
ANIMATION = 'animation'
FRAME_INDEX_AS_TIME = 'frameIndexAsTime'
ANIMATION = 'animations'
MORPH_TARGETS = 'morphTargets'
POSE = 'pose'
REST = 'rest'
SKIN_INDICES = 'skinIndices'
SKIN_WEIGHTS = 'skinWeights'
LOGGING = 'logging'
......@@ -64,6 +68,7 @@ PRECISION = 'precision'
DEFAULT_PRECISION = 6
EMBED_GEOMETRY = 'embedGeometry'
EMBED_ANIMATION = 'embedAnimation'
OFF = 'off'
GLOBAL = 'global'
BUFFER_GEOMETRY = 'BufferGeometry'
......@@ -93,11 +98,12 @@ EXPORT_OPTIONS = {
FACE_MATERIALS: False,
SCALE: 1,
FRAME_STEP: 1,
SCENE: True,
FRAME_INDEX_AS_TIME: False,
SCENE: False,
MIX_COLORS: False,
COMPRESSION: None,
MAPS: False,
ANIMATION: False,
ANIMATION: OFF,
BONES: False,
SKINNING: False,
MORPH_TARGETS: False,
......@@ -105,12 +111,13 @@ EXPORT_OPTIONS = {
LIGHTS: False,
COPY_TEXTURES: True,
LOGGING: DEBUG,
ENABLE_PRECISION: False,
ENABLE_PRECISION: True,
PRECISION: DEFAULT_PRECISION,
EMBED_GEOMETRY: True,
EMBED_ANIMATION: True,
GEOMETRY_TYPE: GEOMETRY,
INFLUENCES_PER_VERTEX: 2
INFLUENCES_PER_VERTEX: 2,
INDENT: True
}
......@@ -210,10 +217,15 @@ IMAGE = 'image'
NAME = 'name'
PARENT = 'parent'
#@TODO move to api.constants?
LENGTH = 'length'
FPS = 'fps'
HIERARCHY = 'hierarchy'
POS = 'pos'
ROTQ = 'rotq'
ROT = 'rot'
SCL = 'scl'
TIME = 'time'
KEYS = 'keys'
AMBIENT = 'ambient'
COLOR = 'color'
......
import mathutils
from bpy import data, context
from .. import constants, logger, utilities
def pose_animation(armature, options):
logger.debug('animation.pose_animation %s', armature)
func = _parse_pose_action
return _parse_action(func, armature, options)
def rest_animation(armature, options):
logger.debug('animation.rest_animation %s', armature)
func = _parse_rest_action
return _parse_action(func, armature, options)
def _parse_action(func, armature, options):
animations = []
logger.info('Parsing %d actions', len(data.actions))
round_off, round_val = utilities.rounding(options)
for action in data.actions:
logger.info('Parsing action %s', action.name)
animation = func(action, armature, options, round_off, round_val)
animations.append(animation)
return animations
def _parse_rest_action(action, armature, options, round_off, round_val):
end_frame = action.frame_range[1]
start_frame = action.frame_range[0]
frame_length = end_frame - start_frame
l,r,s = armature.matrix_world.decompose()
rotation_matrix = r.to_matrix()
hierarchy = []
parent_index = -1
frame_step = options.get(constants.FRAME_STEP, 1)
fps = context.scene.render.fps
start = int(start_frame)
end = int(end_frame / frame_step) + 1
for bone in armature.data.bones:
# I believe this was meant to skip control bones, may
# not be useful. needs more testing
if bone.use_deform is False:
logger.info('Skipping animation data for bone %s', bone.name)
continue
logger.info('Parsing animation data for bone %s', bone.name)
keys = []
for frame in range(start, end):
computed_frame = frame * frame_step
pos, pchange = _position(bone, computed_frame,
action, armature.matrix_world)
rot, rchange = _rotation(bone, computed_frame,
action, rotation_matrix)
# flip y and z
px, py, pz = pos.x, pos.z, -pos.y
rx, ry, rz, rw = rot.x, rot.z, -rot.y, rot.w
if frame == start_frame:
time = (frame * frame_step - start_frame) / fps
#@TODO: missing scale values
keyframe = {
constants.TIME: time,
constants.POS: [px, py, pz],
constants.ROT: [rx, ry, rz, rw],
constants.SCL: [1, 1, 1]
}
keys.append(keyframe)
# END-FRAME: needs pos, rot and scl attributes
# with animation length (required frame)
elif frame == end_frame / frame_step:
time = frame_length / fps
keyframe = {
constants.TIME: time,
constants.POS: [px, py, pz],
constants.ROT: [rx, ry, rz, rw],
constants.SCL: [1, 1, 1]
}
keys.append(keyframe)
# MIDDLE-FRAME: needs only one of the attributes,
# can be an empty frame (optional frame)
elif pchange == True or rchange == True:
time = (frame * frame_step - start_frame) / fps
if pchange == True and rchange == True:
keyframe = {
constants.TIME: time,
constants.POS: [px, py, pz],
constants.ROT: [rx, ry, rz, rw]
}
elif pchange == True:
keyframe = {
constants.TIME: time,
constants.POS: [px, py, pz]
}
elif rchange == True:
keyframe = {
constants.TIME: time,
constants.ROT: [rx, ry, rz, rw]
}
keys.append(keyframe)
hierarchy.append({
constants.KEYS: keys,
constants.PARENT: parent_index
})
parent_index += 1
animation = {
constants.HIERARCHY: hierarchy,
constants.LENGTH:frame_length / fps,
constants.FPS: fps,
constants.NAME: action.name
}
return animation
def _parse_pose_action(action, armature, options, round_off, round_val):
#@TODO: this seems to fail in batch mode meaning the
# user has to have th GUI open. need to improve
# this logic to allow batch processing, if Blender
# chooses to behave....
current_context = context.area.type
context.area.type = 'DOPESHEET_EDITOR'
context.space_data.mode = 'ACTION'
context.area.spaces.active.action = action
armature_matrix = armature.matrix_world
fps = context.scene.render.fps
end_frame = action.frame_range[1]
start_frame = action.frame_range[0]
frame_length = end_frame - start_frame
frame_step = options.get(constants.FRAME_STEP, 1)
used_frames = int(frame_length / frame_step) + 1
keys = []
channels_location = []
channels_rotation = []
channels_scale = []
for pose_bone in armature.pose.bones:
logger.info('Processing channels for %s',
pose_bone.bone.name)
keys.append([])
channels_location.append(
_find_channels(action,
pose_bone.bone,
'location'))
channels_rotation.append(
_find_channels(action,
pose_bone.bone,
'rotation_quaternion'))
channels_rotation.append(
_find_channels(action,
pose_bone.bone,
'rotation_euler'))
channels_scale.append(
_find_channels(action,
pose_bone.bone,
'scale'))
frame_step = options[constants.FRAME_STEP]
frame_index_as_time = options[constants.FRAME_INDEX_AS_TIME]
for frame_index in range(0, used_frames):
if frame_index == used_frames - 1:
frame = end_frame
else:
frame = start_frame + frame_index * frame_step
logger.info('Processing frame %d', frame)
time = frame - start_frame
if frame_index_as_time is False:
time = time / fps
context.scene.frame_set(frame)
bone_index = 0
def has_keyframe_at(channels, frame):
def find_keyframe_at(channel, frame):
for keyframe in channel.keyframe_points:
if keyframe.co[0] == frame:
return keyframe
return None
for channel in channels:
if not find_keyframe_at(channel, frame) is None:
return True
return False
for pose_bone in armature.pose.bones:
logger.info('Processing bone %s', pose_bone.bone.name)
if pose_bone.parent is None:
bone_matrix = armature_matrix * pose_bone.matrix
else:
parent_matrix = armature_matrix * pose_bone.parent.matrix
bone_matrix = armature_matrix * pose_bone.matrix
bone_matrix = parent_matrix.inverted() * bone_matrix
pos, rot, scl = bone_matrix.decompose()
pchange = True or has_keyframe_at(
channels_location[bone_index], frame)
rchange = True or has_keyframe_at(
channels_rotation[bone_index], frame)
schange = True or has_keyframe_at(
channels_scale[bone_index], frame)
if round_off:
pos = (
utilities.round_off(pos.x, round_val),
utilities.round_off(pos.z, round_val),
-utilities.round_off(pos.y, round_val)
)
rot = (
utilities.round_off(rot.x, round_val),
utilities.round_off(rot.z, round_val),
-utilities.round_off(rot.y, round_val),
utilities.round_off(rot.w, round_val)
)
scl = (
utilities.round_off(scl.x, round_val),
utilities.round_off(scl.z, round_val),
utilities.round_off(scl.y, round_val)
)
else:
pos = (pos.x, pos.z, -pos.y)
rot = (rot.x, rot.z, -rot.y, rot.w)
scl = (scl.x, scl.z, scl.y)
keyframe = {constants.TIME: time}
if frame == start_frame or frame == end_frame:
keyframe.update({
constants.POS: pos,
constants.ROT: rot,
constants.SCL: scl
})
elif any([pchange, rchange, schange]):
if pchange is True:
keyframe[constants.POS] = pos
if rchange is True:
keyframe[constants.ROT] = rot
if schange is True:
keyframe[constants.SCL] = scl
if len(keyframe.keys()) > 1:
logger.info('Recording keyframe data for %s %s',
pose_bone.bone.name, str(keyframe))
keys[bone_index].append(keyframe)
else:
logger.info('No anim data to record for %s',
pose_bone.bone.name)
bone_index += 1
hierarchy = []
bone_index = 0
for pose_bone in armature.pose.bones:
hierarchy.append({
constants.PARENT: bone_index - 1,
constants.KEYS: keys[bone_index]
})
bone_index += 1
if frame_index_as_time is False:
frame_length = frame_length / fps
context.scene.frame_set(start_frame)
context.area.type = current_context
animation = {
constants.HIERARCHY: hierarchy,
constants.LENGTH:frame_length,
constants.FPS: fps,
constants.NAME: action.name
}
return animation
def _find_channels(action, bone, channel_type):
result = []
if len(action.groups):
group_index = -1
for index, group in enumerate(action.groups):
if group.name == bone.name:
group_index = index
#@TODO: break?
if group_index > -1:
for channel in action.groups[group_index].channels:
if channel_type in channel.data_path:
result.append(channel)
else:
bone_label = '"%s"' % bone.name
for channel in action.fcurves:
data_path = [bone_label in channel.data_path,
channel_type in channel.data_path]
if all(data_path):
result.append(channel)
return result
def _position(bone, frame, action, armature_matrix):
position = mathutils.Vector((0,0,0))
change = False
ngroups = len(action.groups)
if ngroups > 0:
index = 0
for i in range(ngroups):
if action.groups[i].name == bone.name:
index = i
for channel in action.groups[index].channels:
if "location" in channel.data_path:
has_changed = _handle_position_channel(
channel, frame, position)
change = change or has_changed
else:
bone_label = '"%s"' % bone.name
for channel in action.fcurves:
data_path = channel.data_path
if bone_label in data_path and \
"location" in data_path:
has_changed = _handle_position_channel(
channel, frame, position)
change = change or has_changed
position = position * bone.matrix_local.inverted()
if bone.parent is None:
position.x += bone.head.x
position.y += bone.head.y
position.z += bone.head.z
else:
parent = bone.parent
parent_matrix = parent.matrix_local.inverted()
diff = parent.tail_local - parent.head_local
position.x += (bone.head * parent_matrix).x + diff.x
position.y += (bone.head * parent_matrix).y + diff.y
position.z += (bone.head * parent_matrix).z + diff.z
return armature_matrix*position, change
def _rotation(bone, frame, action, armature_matrix):
# TODO: calculate rotation also from rotation_euler channels
rotation = mathutils.Vector((0,0,0,1))
change = False
ngroups = len(action.groups)
# animation grouped by bones
if ngroups > 0:
index = -1
for i in range(ngroups):
if action.groups[i].name == bone.name:
index = i
if index > -1:
for channel in action.groups[index].channels:
if "quaternion" in channel.data_path:
has_changed = _handle_rotation_channel(
channel, frame, rotation)
change = change or has_changed
# animation in raw fcurves
else:
bone_label = '"%s"' % bone.name
for channel in action.fcurves:
data_path = channel.data_path
if bone_label in data_path and \
"quaternion" in data_path:
has_changed = _handle_rotation_channel(
channel, frame, rotation)
change = change or has_changed
rot3 = rotation.to_3d()
rotation.xyz = rot3 * bone.matrix_local.inverted()
rotation.xyz = armature_matrix * rotation.xyz
return rotation, change
def _handle_rotation_channel(channel, frame, rotation):
change = False
if channel.array_index in [0, 1, 2, 3]:
for keyframe in channel.keyframe_points:
if keyframe.co[0] == frame:
change = True
value = channel.evaluate(frame)
if channel.array_index == 1:
rotation.x = value
elif channel.array_index == 2:
rotation.y = value
elif channel.array_index == 3:
rotation.z = value
elif channel.array_index == 0:
rotation.w = value
return change
def _handle_position_channel(channel, frame, position):
change = False
if channel.array_index in [0, 1, 2]:
for keyframe in channel.keyframe_points:
if keyframe.co[0] == frame:
change = True
value = channel.evaluate(frame)
if channel.array_index == 0:
position.x = value
if channel.array_index == 1:
position.y = value
if channel.array_index == 2:
position.z = value
return change
......@@ -17,7 +17,6 @@ from .constants import (
PERSP,
ORTHO,
RENDER,
ZYX,
NO_SHADOW
)
......@@ -46,14 +45,16 @@ def _object(func):
return inner
def assemblies(valid_types):
def assemblies(valid_types, options):
logger.debug('object.assemblies(%s)', valid_types)
for obj in data.objects:
if not obj.parent and obj.type in valid_types:
yield obj.name
elif obj.parent and not obj.parent.parent \
and obj.parent.type == ARMATURE:
# rigged assets are parented under armature nodes
if obj.parent and obj.parent.type != ARMATURE:
continue
if obj.parent and obj.parent.type == ARMATURE:
logger.info('Has armature parent %s', obj.name)
if _valid_node(obj, valid_types, options):
yield obj.name
......@@ -152,38 +153,15 @@ def node_type(obj):
def nodes(valid_types, options):
visible_layers = _visible_scene_layers()
for obj in data.objects:
# skip objects that are not on visible layers
if not _on_visible_layer(obj, visible_layers):
continue
try:
export = obj.THREE_export
except AttributeError:
export = True
mesh_node = mesh(obj, options)
is_mesh = obj.type == MESH
# skip objects that a mesh could not be resolved
if is_mesh and not mesh_node:
continue
# secondary test; if a mesh node was resolved but no
# faces are detected then bow out
if is_mesh:
mesh_node = data.meshes[mesh_node]
if len(mesh_node.tessfaces) is 0:
continue
if obj.type in valid_types and export:
if _valid_node(obj, valid_types, options):
yield obj.name
@_object
def position(obj, options):
logger.debug('object.position(%s)', obj)
vector = _matrix(obj)[0]
parent = obj.parent is None
vector = _decompose_matrix(obj, local=not parent)[0]
vector = (vector.x, vector.y, vector.z)
round_off, round_val = utilities.rounding(options)
......@@ -206,8 +184,8 @@ def receive_shadow(obj):
@_object
def rotation(obj, options):
logger.debug('object.rotation(%s)', obj)
vector = _matrix(obj)[1].to_euler(ZYX)
vector = (vector.x, vector.y, vector.z)
vector = _decompose_matrix(obj)[1]
vector = (vector.x, vector.y, vector.z, vector.w)
round_off, round_val = utilities.rounding(options)
if round_off:
......@@ -219,7 +197,7 @@ def rotation(obj, options):
@_object
def scale(obj, options):
logger.debug('object.scale(%s)', obj)
vector = _matrix(obj)[2]
vector = _decompose_matrix(obj)[2]
vector = (vector.x, vector.y, vector.z)
round_off, round_val = utilities.rounding(options)
......@@ -381,8 +359,11 @@ def extracted_meshes():
return [key for key in _MESH_MAP.keys()]
def _matrix(obj):
matrix = ROTATE_X_PI2 * obj.matrix_world
def _decompose_matrix(obj, local=False):
if local:
matrix = ROTATE_X_PI2 * obj.matrix_local
else:
matrix = ROTATE_X_PI2 * obj.matrix_world
return matrix.decompose()
......@@ -401,3 +382,40 @@ def _visible_scene_layers():
for index, layer in enumerate(context.scene.layers):
if layer: visible_layers.append(index)
return visible_layers
def _valid_node(obj, valid_types, options):
if obj.type not in valid_types:
return False
# skip objects that are not on visible layers
visible_layers = _visible_scene_layers()
if not _on_visible_layer(obj, visible_layers):
return False
try:
export = obj.THREE_export
except AttributeError:
export = True
if not export:
return False
mesh_node = mesh(obj, options)
is_mesh = obj.type == MESH
# skip objects that a mesh could not be resolved
if is_mesh and not mesh_node:
return False
# secondary test; if a mesh node was resolved but no
# faces are detected then bow out
if is_mesh:
mesh_node = data.meshes[mesh_node]
if len(mesh_node.tessfaces) is 0:
return False
# if we get this far assume that the mesh is valid
return True
import uuid
from .. import constants, exceptions
from . import utilities
from .. import constants, exceptions
class BaseClass(constants.BASE_DICT):
"""Base class which inherits from a base dictionary object."""
_defaults = {}
def __init__(self, parent=None, type=None):
constants.BASE_DICT.__init__(self)
self.__type = type
self._type = type
self.__parent = parent
self._parent = parent
constants.BASE_DICT.update(self, self._defaults.copy())
def __setitem__(self, key, value):
if not isinstance(value, constants.VALID_DATA_TYPES):
msg = 'Value is an invalid data type: %s' % type(value)
raise exceptions.ThreeValueError(msg)
msg = "Value is an invalid data type: %s" % type(value)
raise exceptions.ThreeValueError(msg)
constants.BASE_DICT.__setitem__(self, key, value)
@property
def count(self):
"""
:return: number of keys
:rtype: int
"""
return len(self.keys())
@property
def parent(self):
return self.__parent
"""
:return: parent object
"""
return self._parent
@property
def type(self):
return self.__type
"""
:return: the type (if applicable)
"""
return self._type
def copy(self):
"""Copies the items to a standard dictionary object.
:rtype: dict
"""
data = {}
def _dict_copy(old, new):
"""Recursive function for processing all values
:param old:
:param new:
"""
for key, value in old.items():
if isinstance(value, (str, list)):
new[key] = value[:]
......@@ -51,12 +79,16 @@ class BaseClass(constants.BASE_DICT):
return data
class BaseNode(BaseClass):
class BaseNode(BaseClass):
"""Base class for all nodes for the current platform."""
def __init__(self, node, parent, type):
BaseClass.__init__(self, parent=parent, type=type)
self.__node = node
if node is not None:
self._node = node
if node is None:
self[constants.UUID] = utilities.id()
else:
self[constants.NAME] = node
self[constants.UUID] = utilities.id_from_name(node)
if isinstance(parent, BaseScene):
scene = parent
......@@ -65,35 +97,51 @@ class BaseNode(BaseClass):
else:
scene = None
self.__scene = scene
self._scene = scene
self[constants.UUID] = str(uuid.uuid4()).upper()
@property
def node(self):
return self.__node
"""
:return: name of the node
"""
return self._node
@property
def scene(self):
return self.__scene
"""
:return: returns the scene point
"""
return self._scene
@property
def options(self):
"""
:return: export options
:retype: dict
"""
return self.scene.options
class BaseScene(BaseClass):
"""Base class that scenes inherit from."""
def __init__(self, filepath, options):
BaseClass.__init__(self, type=constants.SCENE)
self.__filepath = filepath
self._filepath = filepath
self.__options = options.copy()
self._options = options.copy()
@property
def filepath(self):
return self.__filepath
return self._filepath
@property
def options(self):
return self.__options
return self._options
......@@ -7,9 +7,10 @@ FORMAT_VERSION = 3
class Geometry(base_classes.BaseNode):
"""Class that wraps a single mesh/geometry node."""
def __init__(self, node, parent=None):
logger.debug('Geometry().__init__(%s)', node)
logger.debug("Geometry().__init__(%s)", node)
#@TODO: maybe better to have `three` constants for
# strings that are specific to `three` properties
geo_type = constants.GEOMETRY.title()
......@@ -18,23 +19,28 @@ class Geometry(base_classes.BaseNode):
if opt_type == constants.BUFFER_GEOMETRY:
geo_type = constants.BUFFER_GEOMETRY
elif opt_type != constants.GEOMETRY:
logger.error('Unknown geometry type %s', opt_type)
logger.error("Unknown geometry type %s", opt_type)
logger.info('Setting %s to "%s"', node, geo_type)
logger.info("Setting %s to '%s'", node, geo_type)
self._defaults[constants.TYPE] = geo_type
base_classes.BaseNode.__init__(self, node,
parent=parent,
type=geo_type)
base_classes.BaseNode.__init__(self, node,
parent=parent,
type=geo_type)
@property
def animation_filename(self):
"""Calculate the file name for the animation file
:return: base name for the file
"""
compression = self.options.get(constants.COMPRESSION)
if compression in (None, constants.NONE):
ext = constants.JSON
elif compression == constants.MSGPACK:
ext = constants.PACK
key = ''
for key in (constants.MORPH_TARGETS, constants.ANIMATION):
try:
self[key]
......@@ -42,27 +48,32 @@ class Geometry(base_classes.BaseNode):
except KeyError:
pass
else:
logger.info('%s has no animation data', self.node)
logger.info("%s has no animation data", self.node)
return
return '%s.%s.%s' % (self.node, key, ext)
@property
def face_count(self):
"""Parse the bit masks of the `faces` array.
:rtype: int
"""
try:
faces = self[constants.FACES]
except KeyError:
logger.debug('No parsed faces found')
logger.debug("No parsed faces found")
return 0
length = len(faces)
offset = 0
bitset = lambda x,y: x & ( 1 << y )
bitset = lambda x, y: x & (1 << y)
face_count = 0
masks = (constants.MASK[constants.UVS],
constants.MASK[constants.NORMALS],
constants.MASK[constants.COLORS])
constants.MASK[constants.NORMALS],
constants.MASK[constants.COLORS])
while offset < length:
bit = faces[offset]
......@@ -84,20 +95,32 @@ class Geometry(base_classes.BaseNode):
@property
def metadata(self):
"""Metadata for the current node.
:rtype: dict
"""
metadata = {
constants.GENERATOR: constants.THREE,
constants.VERSION: FORMAT_VERSION
}
if self[constants.TYPE] == constants.GEOMETRY.title():
self.__geometry_metadata(metadata)
self._geometry_metadata(metadata)
else:
self.__buffer_geometry_metadata(metadata)
self._buffer_geometry_metadata(metadata)
return metadata
def copy(self, scene=True):
logger.debug('Geometry().copy(scene=%s)', scene)
"""Copy the geometry definitions to a standard dictionary.
:param scene: toggle for scene formatting (Default = True)
:type scene: bool
:rtype: dict
"""
logger.debug("Geometry().copy(scene=%s)", scene)
dispatch = {
True: self._scene_format,
False: self._geometry_format
......@@ -107,47 +130,68 @@ class Geometry(base_classes.BaseNode):
try:
data[constants.MATERIALS] = self[constants.MATERIALS].copy()
except KeyError:
logger.debug('No materials to copy')
logger.debug("No materials to copy")
return data
def copy_textures(self):
logger.debug('Geometry().copy_textures()')
"""Copy the textures to the destination directory."""
logger.debug("Geometry().copy_textures()")
if self.options.get(constants.COPY_TEXTURES):
texture_registration = self.register_textures()
if texture_registration:
logger.info('%s has registered textures', self.node)
logger.info("%s has registered textures", self.node)
io.copy_registered_textures(
os.path.dirname(self.scene.filepath),
texture_registration)
def parse(self):
logger.debug('Geometry().parse()')
"""Parse the current node"""
logger.debug("Geometry().parse()")
if self[constants.TYPE] == constants.GEOMETRY.title():
logger.info('Parsing Geometry format')
self.__parse_geometry()
logger.info("Parsing Geometry format")
self._parse_geometry()
else:
logger.info('Parsing BufferGeometry format')
self.__parse_buffer_geometry()
logger.info("Parsing BufferGeometry format")
self._parse_buffer_geometry()
def register_textures(self):
logger.debug('Geometry().register_textures()')
return api.mesh.texture_registration(self.node)
"""Obtain a texture registration object.
:rtype: dict
"""
logger.debug("Geometry().register_textures()")
return api.mesh.texture_registration(self.node)
def write(self, filepath=None):
logger.debug('Geometry().write(filepath=%s)', filepath)
"""Write the geometry definitions to disk. Uses the
desitnation path of the scene.
:param filepath: optional output file path (Default=None)
:type filepath: str
"""
logger.debug("Geometry().write(filepath=%s)", filepath)
filepath = filepath or self.scene.filepath
io.dump(filepath, self.copy(scene=False),
options=self.scene.options)
io.dump(filepath, self.copy(scene=False),
options=self.scene.options)
if self.options.get(constants.MAPS):
logger.info('Copying textures for %s', self.node)
logger.info("Copying textures for %s", self.node)
self.copy_textures()
def write_animation(self, filepath):
logger.debug('Geometry().write_animation(%s)', filepath)
"""Write the animation definitions to a separate file
on disk. This helps optimize the geometry file size.
:param filepath: destination path
:type filepath: str
"""
logger.debug("Geometry().write_animation(%s)", filepath)
for key in (constants.MORPH_TARGETS, constants.ANIMATION):
try:
......@@ -156,29 +200,35 @@ class Geometry(base_classes.BaseNode):
except KeyError:
pass
else:
logger.info('%s has no animation data', self.node)
logger.info("%s has no animation data", self.node)
return
filepath = os.path.join(filepath, self.animation_filename)
if filepath:
logger.info('Dumping animation data to %s', filepath)
logger.info("Dumping animation data to %s", filepath)
io.dump(filepath, data, options=self.scene.options)
return filepath
else:
logger.warning('Could not determine a filepath for '\
'animation data. Nothing written to disk.')
logger.warning("Could not determine a filepath for "\
"animation data. Nothing written to disk.")
def _component_data(self):
logger.debug('Geometry()._component_data()')
"""Query the component data only
:rtype: dict
"""
logger.debug("Geometry()._component_data()")
if self[constants.TYPE] != constants.GEOMETRY.title():
return self[constants.ATTRIBUTES]
components = [constants.VERTICES, constants.FACES,
constants.UVS, constants.COLORS, constants.NORMALS,
constants.BONES, constants.SKIN_WEIGHTS,
constants.SKIN_INDICES, constants.NAME,
constants.INFLUENCES_PER_VERTEX]
components = [constants.VERTICES, constants.FACES,
constants.UVS, constants.COLORS,
constants.NORMALS, constants.BONES,
constants.SKIN_WEIGHTS,
constants.SKIN_INDICES, constants.NAME,
constants.INFLUENCES_PER_VERTEX]
data = {}
anim_components = [constants.MORPH_TARGETS, constants.ANIMATION]
......@@ -192,20 +242,25 @@ class Geometry(base_classes.BaseNode):
pass
else:
data[component] = os.path.basename(
self.animation_filename)
self.animation_filename)
break
else:
logger.info('No animation data found for %s', self.node)
logger.info("No animation data found for %s", self.node)
for component in components:
try:
data[component] = self[component]
except KeyError:
logger.debug('Component %s not found', component)
pass
logger.debug("Component %s not found", component)
return data
def _geometry_format(self):
"""Three.Geometry formatted definitions
:rtype: dict
"""
data = self._component_data()
if self[constants.TYPE] != constants.GEOMETRY.title():
......@@ -219,17 +274,27 @@ class Geometry(base_classes.BaseNode):
return data
def __buffer_geometry_metadata(self, metadata):
def _buffer_geometry_metadata(self, metadata):
"""Three.BufferGeometry metadata
:rtype: dict
"""
for key, value in self[constants.ATTRIBUTES].items():
size = value[constants.ITEM_SIZE]
array = value[constants.ARRAY]
metadata[key] = len(array)/size
def __geometry_metadata(self, metadata):
def _geometry_metadata(self, metadata):
"""Three.Geometry metadat
:rtype: dict
"""
skip = (constants.TYPE, constants.FACES, constants.UUID,
constants.ANIMATION, constants.SKIN_INDICES,
constants.SKIN_WEIGHTS, constants.NAME,
constants.INFLUENCES_PER_VERTEX)
constants.ANIMATION, constants.SKIN_INDICES,
constants.SKIN_WEIGHTS, constants.NAME,
constants.INFLUENCES_PER_VERTEX)
vectors = (constants.VERTICES, constants.NORMALS)
for key in self.keys():
......@@ -249,6 +314,11 @@ class Geometry(base_classes.BaseNode):
metadata[constants.FACES] = faces
def _scene_format(self):
"""Format the output for Scene compatability
:rtype: dict
"""
data = {
constants.UUID: self[constants.UUID],
constants.TYPE: self[constants.TYPE]
......@@ -267,34 +337,35 @@ class Geometry(base_classes.BaseNode):
}
else:
data[constants.ATTRIBUTES] = component_data
data[constants.METADATA] = self.metadata
data[constants.METADATA] = self.metadata
data[constants.NAME] = self[constants.NAME]
return data
return data
def __parse_buffer_geometry(self):
def _parse_buffer_geometry(self):
"""Parse the geometry to Three.BufferGeometry specs"""
self[constants.ATTRIBUTES] = {}
options_vertices = self.options.get(constants.VERTICES)
option_normals = self.options.get(constants.NORMALS)
option_uvs = self.options.get(constants.UVS)
dispatch = (
(constants.POSITION, options_vertices,
api.mesh.buffer_position, 3),
(constants.UV, option_uvs, api.mesh.buffer_uv, 2),
(constants.NORMAL, option_normals,
api.mesh.buffer_normal, 3)
)
pos_tuple = (constants.POSITION, options_vertices,
api.mesh.buffer_position, 3)
uvs_tuple = (constants.UV, option_uvs,
api.mesh.buffer_uv, 2)
normals_tuple = (constants.NORMAL, option_normals,
api.mesh.buffer_normal, 3)
dispatch = (pos_tuple, uvs_tuple, normals_tuple)
for key, option, func, size in dispatch:
for key, option, func, size in dispatch:
if not option:
continue
array = func(self.node, self.options)
if not array:
logger.warning('No array could be made for %s', key)
array = func(self.node, self.options) or []
if not array:
logger.warning("No array could be made for %s", key)
continue
self[constants.ATTRIBUTES][key] = {
......@@ -303,53 +374,55 @@ class Geometry(base_classes.BaseNode):
constants.ARRAY: array
}
def __parse_geometry(self):
def _parse_geometry(self):
"""Parse the geometry to Three.Geometry specs"""
if self.options.get(constants.VERTICES):
logger.info('Parsing %s', constants.VERTICES)
logger.info("Parsing %s", constants.VERTICES)
self[constants.VERTICES] = api.mesh.vertices(
self.node, self.options)
if self.options.get(constants.FACES):
logger.info('Parsing %s', constants.FACES)
self[constants.FACES] = api.mesh.faces(
self.node, self.options)
self.node, self.options) or []
if self.options.get(constants.NORMALS):
logger.info('Parsing %s', constants.NORMALS)
logger.info("Parsing %s", constants.NORMALS)
self[constants.NORMALS] = api.mesh.normals(
self.node, self.options)
self.node, self.options) or []
if self.options.get(constants.COLORS):
logger.info('Parsing %s', constants.COLORS)
logger.info("Parsing %s", constants.COLORS)
self[constants.COLORS] = api.mesh.vertex_colors(
self.node)
self.node) or []
if self.options.get(constants.FACE_MATERIALS):
logger.info('Parsing %s', constants.FACE_MATERIALS)
logger.info("Parsing %s", constants.FACE_MATERIALS)
self[constants.MATERIALS] = api.mesh.materials(
self.node, self.options)
self.node, self.options) or []
if self.options.get(constants.UVS):
logger.info('Parsing %s', constants.UVS)
logger.info("Parsing %s", constants.UVS)
self[constants.UVS] = api.mesh.uvs(
self.node, self.options)
self.node, self.options) or []
if self.options.get(constants.ANIMATION):
logger.info('Parsing %s', constants.ANIMATION)
self[constants.ANIMATION] = api.mesh.animation(
self.node, self.options)
if self.options.get(constants.FACES):
logger.info("Parsing %s", constants.FACES)
self[constants.FACES] = api.mesh.faces(
self.node, self.options) or []
no_anim = (None, False, constants.OFF)
if self.options.get(constants.ANIMATION) not in no_anim:
logger.info("Parsing %s", constants.ANIMATION)
self[constants.ANIMATION] = api.mesh.skeletal_animation(
self.node, self.options) or []
#@TODO: considering making bones data implied when
# querying skinning data
bone_map = {}
if self.options.get(constants.BONES):
logger.info('Parsing %s', constants.BONES)
bones, bone_map = api.mesh.bones(self.node)
logger.info("Parsing %s", constants.BONES)
bones, bone_map = api.mesh.bones(self.node, self.options)
self[constants.BONES] = bones
if self.options.get(constants.SKINNING):
logger.info('Parsing %s', constants.SKINNING)
logger.info("Parsing %s", constants.SKINNING)
influences = self.options.get(
constants.INFLUENCES_PER_VERTEX, 2)
......@@ -360,7 +433,7 @@ class Geometry(base_classes.BaseNode):
self.node, bone_map, influences)
if self.options.get(constants.MORPH_TARGETS):
logger.info('Parsing %s', constants.MORPH_TARGETS)
logger.info("Parsing %s", constants.MORPH_TARGETS)
self[constants.MORPH_TARGETS] = api.mesh.morph_targets(
self.node, self.options)
......@@ -4,22 +4,43 @@ from . import base_classes, io, api
class Image(base_classes.BaseNode):
"""Class the wraps an image node. This is the node that
represent that actual file on disk.
"""
def __init__(self, node, parent):
logger.debug('Image().__init__(%s)', node)
logger.debug("Image().__init__(%s)", node)
base_classes.BaseNode.__init__(self, node, parent, constants.IMAGE)
self[constants.URL] = api.image.file_name(self.node)
@property
def destination(self):
"""
:return: full destination path (when copied)
"""
dirname = os.path.dirname(self.scene.filepath)
return os.path.join(dirname, self[constants.URL])
@property
def filepath(self):
"""
:return: source file path
"""
return api.image.file_path(self.node)
def copy_texture(self, func=io.copy):
logger.debug('Image().copy_texture()')
"""Copy the texture.
self.filepath > self.destination
:param func: Optional function override (Default = io.copy)
arguments are (<source>, <destination>)
:return: path the texture was copied to
"""
logger.debug("Image().copy_texture()")
func(self.filepath, self.destination)
return self.destination
import os
import shutil
from .. import constants, logger
from . import _json
def copy_registered_textures(dest, registration):
logger.debug('io.copy_registered_textures(%s, %s)', dest, registration)
"""Copy the registered textures to the destination (root) path
:param dest: destination directory
:param registration: registered textures
:type dest: str
:type registration: dict
"""
logger.debug("io.copy_registered_textures(%s, %s)", dest, registration)
for value in registration.values():
copy(value['file_path'], dest)
def copy(src, dst):
logger.debug('io.copy(%s, %s)' % (src, dst))
shutil.copy(src, dst)
"""Copy a file to a destination
:param src: source file
:param dst: destination file/path
"""
logger.debug("io.copy(%s, %s)" % (src, dst))
if os.path.isdir(dst):
file_name = os.path.basename(src)
dst = os.path.join(dst, file_name)
if src != dst:
shutil.copy(src, dst)
def dump(filepath, data, options=None):
"""Dump the output to disk (JSON, msgpack, etc)
:param filepath: output file path
:param data: serializable data to write to disk
:param options: (Default = None)
:type options: dict
"""
options = options or {}
logger.debug('io.dump(%s, data, options=%s)', filepath, options)
logger.debug("io.dump(%s, data, options=%s)", filepath, options)
compress = options.get(constants.COMPRESSION, constants.NONE)
if compress == constants.MSGPACK:
try:
import msgpack
except ImportError:
logger.error('msgpack module not found')
logger.error("msgpack module not found")
raise
logger.info('Dumping to msgpack')
func = lambda x,y: msgpack.dump(x, y)
logger.info("Dumping to msgpack")
func = lambda x, y: msgpack.dump(x, y)
mode = 'wb'
else:
round_off = options.get(constants.ENABLE_PRECISION)
......@@ -36,28 +64,37 @@ def dump(filepath, data, options=None):
else:
_json.ROUND = None
logger.info('Dumping to JSON')
func = lambda x,y: _json.json.dump(x, y, indent=4)
indent = options.get(constants.INDENT, True)
indent = 4 if indent else None
logger.info("Dumping to JSON")
func = lambda x, y: _json.json.dump(x, y, indent=indent)
mode = 'w'
logger.info('Writing to %s', filepath)
logger.info("Writing to %s", filepath)
with open(filepath, mode=mode) as stream:
func(data, stream)
def load(filepath, options):
logger.debug('io.load(%s, %s)', filepath, options)
"""Load the contents of the file path with the correct parser
:param filepath: input file path
:param options:
:type options: dict
"""
logger.debug("io.load(%s, %s)", filepath, options)
compress = options.get(constants.COMPRESSION, constants.NONE)
if compress == constants.MSGPACK:
try:
import msgpack
except ImportError:
logger.error('msgpack module not found')
logger.error("msgpack module not found")
raise
module = msgpack
mode = 'rb'
else:
logger.info('Loading JSON')
logger.info("Loading JSON")
module = _json.json
mode = 'r'
......
......@@ -3,21 +3,23 @@ from . import base_classes, utilities, api
class Material(base_classes.BaseNode):
"""Class that wraps material nodes"""
def __init__(self, node, parent):
logger.debug('Material().__init__(%s)', node)
base_classes.BaseNode.__init__(self, node, parent,
constants.MATERIAL)
self.__common_attributes()
logger.debug("Material().__init__(%s)", node)
base_classes.BaseNode.__init__(self, node, parent,
constants.MATERIAL)
self._common_attributes()
if self[constants.TYPE] == constants.THREE_PHONG:
self.__phong_attributes()
self._phong_attributes()
textures = self.parent.options.get(constants.MAPS)
if textures:
self.__update_maps()
self._update_maps()
def __common_attributes(self):
logger.debug('Material().__common_attributes()')
def _common_attributes(self):
"""Parse the common material attributes"""
logger.debug('Material()._common_attributes()')
dispatch = {
constants.PHONG: constants.THREE_PHONG,
constants.LAMBERT: constants.THREE_LAMBERT,
......@@ -26,14 +28,15 @@ class Material(base_classes.BaseNode):
shader_type = api.material.type(self.node)
self[constants.TYPE] = dispatch[shader_type]
ambient = api.material.ambient_color(self.node)
self[constants.AMBIENT] = utilities.rgb2int(ambient)
diffuse = api.material.diffuse_color(self.node)
self[constants.COLOR] = utilities.rgb2int(diffuse)
emissive = api.material.emissive_color(self.node)
self[constants.EMISSIVE] = utilities.rgb2int(emissive)
if self[constants.TYPE] != constants.THREE_BASIC:
ambient = api.material.ambient_color(self.node)
self[constants.AMBIENT] = utilities.rgb2int(ambient)
emissive = api.material.emissive_color(self.node)
self[constants.EMISSIVE] = utilities.rgb2int(emissive)
vertex_color = api.material.use_vertex_colors(self.node)
self[constants.VERTEX_COLORS] = vertex_color
......@@ -44,14 +47,18 @@ class Material(base_classes.BaseNode):
self[constants.DEPTH_WRITE] = api.material.depth_write(self.node)
def __phong_attributes(self):
logger.debug('Material().__phong_attributes()')
def _phong_attributes(self):
"""Parse phong specific attributes"""
logger.debug("Material()._phong_attributes()")
specular = api.material.specular_color(self.node)
self[constants.SPECULAR] = utilities.rgb2int(specular)
self[constants.SHININESS] = api.material.specular_coef(self.node)
def __update_maps(self):
logger.debug('Material().__update_maps()')
def _update_maps(self):
"""Parses maps/textures and updates the textures array
with any new nodes found.
"""
logger.debug("Material()._update_maps()")
mapping = (
(api.material.diffuse_map, constants.MAP),
......@@ -59,14 +66,14 @@ class Material(base_classes.BaseNode):
(api.material.light_map, constants.LIGHT_MAP)
)
for func,key in mapping:
for func, key in mapping:
map_node = func(self.node)
if map_node:
logger.info('Found map node %s for %s', map_node, key)
tex_inst = self.scene.texture(map_node.name)
self[key] = tex_inst[constants.UUID]
self[key] = tex_inst[constants.UUID]
if self[constants.TYPE] == constants.THREE_PHONG:
if self[constants.TYPE] == constants.THREE_PHONG:
mapping = (
(api.material.bump_map, constants.BUMP_MAP,
constants.BUMP_SCALE, api.material.bump_scale),
......@@ -76,8 +83,9 @@ class Material(base_classes.BaseNode):
for func, map_key, scale_key, scale_func in mapping:
map_node = func(self.node)
if not map_node: continue
logger.info('Found map node %s for %s', map_node, map_key)
if not map_node:
continue
logger.info("Found map node %s for %s", map_node, map_key)
tex_inst = self.scene.texture(map_node.name)
self[map_key] = tex_inst[constants.UUID]
self[map_key] = tex_inst[constants.UUID]
self[scale_key] = scale_func(self.node)
......@@ -3,18 +3,19 @@ from . import base_classes, api
class Object(base_classes.BaseNode):
"""Class that wraps an object node"""
def __init__(self, node, parent=None, type=None):
logger.debug('Object().__init__(%s)', node)
logger.debug("Object().__init__(%s)", node)
base_classes.BaseNode.__init__(self, node, parent=parent, type=type)
if self.node:
self.__node_setup()
self._node_setup()
else:
self.__root_setup()
self._root_setup()
def __init_camera(self):
logger.debug('Object().__init_camera()')
def _init_camera(self):
"""Initialize camera attributes"""
logger.debug("Object()._init_camera()")
self[constants.FAR] = api.camera.far(self.node)
self[constants.NEAR] = api.camera.near(self.node)
......@@ -29,29 +30,32 @@ class Object(base_classes.BaseNode):
#@TODO: need more light attributes. Some may have to come from
# custom blender attributes.
def __init_light(self):
logger.debug('Object().__init_light()')
def _init_light(self):
"""Initialize light attributes"""
logger.debug("Object()._init_light()")
self[constants.COLOR] = api.light.color(self.node)
self[constants.INTENSITY] = api.light.intensity(self.node)
if self[constants.TYPE] != constants.DIRECTIONAL_LIGHT:
self[constants.DISTANCE] = api.light.distance(self.node)
if self[constants.TYPE] == constants.SPOT_LIGHT:
self[constants.ANGLE] = api.light.angle(self.node)
def __init_mesh(self):
logger.debug('Object().__init_mesh()')
def _init_mesh(self):
"""Initialize mesh attributes"""
logger.debug("Object()._init_mesh()")
mesh = api.object.mesh(self.node, self.options)
node = self.scene.geometry(mesh)
if node:
self[constants.GEOMETRY] = node[constants.UUID]
else:
msg = 'Could not find Geometry() node for %s'
msg = "Could not find Geometry() node for %s"
logger.error(msg, self.node)
def __node_setup(self):
logger.debug('Object().__node_setup()')
def _node_setup(self):
"""Parse common node attributes of all objects"""
logger.debug("Object()._node_setup()")
self[constants.NAME] = api.object.name(self.node)
self[constants.POSITION] = api.object.position(
......@@ -68,49 +72,52 @@ class Object(base_classes.BaseNode):
self[constants.TYPE] = api.object.node_type(self.node)
if self.options.get(constants.MATERIALS):
logger.info('Parsing materials for %s', self.node)
logger.info("Parsing materials for %s", self.node)
material_name = api.object.material(self.node)
if material_name:
logger.info('Material found %s', material_name)
logger.info("Material found %s", material_name)
material_inst = self.scene.material(material_name)
self[constants.MATERIAL] = material_inst[constants.UUID]
else:
logger.info('%s has no materials', self.node)
logger.info("%s has no materials", self.node)
casts_shadow = (constants.MESH,
constants.DIRECTIONAL_LIGHT,
constants.SPOT_LIGHT)
casts_shadow = (constants.MESH,
constants.DIRECTIONAL_LIGHT,
constants.SPOT_LIGHT)
if self[constants.TYPE] in casts_shadow:
logger.info('Querying shadow casting for %s', self.node)
logger.info("Querying shadow casting for %s", self.node)
self[constants.CAST_SHADOW] = \
api.object.cast_shadow(self.node)
if self[constants.TYPE] == constants.MESH:
logger.info('Querying shadow receive for %s', self.node)
logger.info("Querying shadow receive for %s", self.node)
self[constants.RECEIVE_SHADOW] = \
api.object.receive_shadow(self.node)
camera = (constants.PERSPECTIVE_CAMERA,
constants.ORTHOGRAPHIC_CAMERA)
constants.ORTHOGRAPHIC_CAMERA)
lights = (constants.AMBIENT_LIGHT, constants.DIRECTIONAL_LIGHT,
constants.AREA_LIGHT, constants.POINT_LIGHT,
constants.SPOT_LIGHT, constants.HEMISPHERE_LIGHT)
lights = (constants.AMBIENT_LIGHT,
constants.DIRECTIONAL_LIGHT,
constants.AREA_LIGHT, constants.POINT_LIGHT,
constants.SPOT_LIGHT, constants.HEMISPHERE_LIGHT)
if self[constants.TYPE] == constants.MESH:
self.__init_mesh()
self._init_mesh()
elif self[constants.TYPE] in camera:
self.__init_camera()
self._init_camera()
elif self[constants.TYPE] in lights:
self.__init_light()
self._init_light()
#for child in api.object.children(self.node, self.scene.valid_types):
# if not self.get(constants.CHILDREN):
# self[constants.CHILDREN] = [Object(child, parent=self)]
# else:
# self[constants.CHILDREN].append(Object(child, parent=self))
for child in api.object.children(self.node, self.scene.valid_types):
if not self.get(constants.CHILDREN):
self[constants.CHILDREN] = [Object(child, parent=self)]
else:
self[constants.CHILDREN].append(Object(child, parent=self))
def __root_setup(self):
logger.debug('Object().__root_setup()')
self[constants.MATRIX] = [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]
def _root_setup(self):
"""Applies to a root/scene object"""
logger.debug("Object()._root_setup()")
self[constants.MATRIX] = [1, 0, 0, 0, 0, 1, 0, 0, 0, 0,
1, 0, 0, 0, 0, 1]
......@@ -4,14 +4,16 @@ from . import (
base_classes,
texture,
material,
geometry,
object,
geometry,
object as object_,
utilities,
io,
api
)
class Scene(base_classes.BaseScene):
"""Class that handles the contruction of a Three scene"""
_defaults = {
constants.METADATA: constants.DEFAULT_METADATA.copy(),
constants.GEOMETRIES: [],
......@@ -21,7 +23,7 @@ class Scene(base_classes.BaseScene):
}
def __init__(self, filepath, options=None):
logger.debug('Scene().__init__(%s, %s)', filepath, options)
logger.debug("Scene().__init__(%s, %s)", filepath, options)
base_classes.BaseScene.__init__(self, filepath, options or {})
source_file = api.scene_name()
......@@ -30,73 +32,107 @@ class Scene(base_classes.BaseScene):
@property
def valid_types(self):
"""
:return: list of valid node types
"""
valid_types = [api.constants.MESH]
if self.options.get(constants.CAMERAS):
logger.info('Adding cameras to valid object types')
logger.info("Adding cameras to valid object types")
valid_types.append(api.constants.CAMERA)
if self.options.get(constants.LIGHTS):
logger.info('Adding lights to valid object types')
logger.info("Adding lights to valid object types")
valid_types.append(api.constants.LAMP)
return valid_types
def geometry(self, arg):
logger.debug('Scene().geometry(%s)', arg)
return self._find_node(arg, self[constants.GEOMETRIES])
def geometry(self, value):
"""Find a geometry node that matches either a name
or uuid value.
:param value: name or uuid
:type value: str
"""
logger.debug("Scene().geometry(%s)", value)
return _find_node(value, self[constants.GEOMETRIES])
def image(self, value):
"""Find a image node that matches either a name
or uuid value.
def image(self, arg):
logger.debug('Scene().image%s)', arg)
return self._find_node(arg, self[constants.IMAGES])
:param value: name or uuid
:type value: str
def material(self, arg):
logger.debug('Scene().material(%s)', arg)
return self._find_node(arg, self[constants.MATERIALS])
"""
logger.debug("Scene().image%s)", value)
return _find_node(value, self[constants.IMAGES])
def material(self, value):
"""Find a material node that matches either a name
or uuid value.
:param value: name or uuid
:type value: str
"""
logger.debug("Scene().material(%s)", value)
return _find_node(value, self[constants.MATERIALS])
def parse(self):
logger.debug('Scene().parse()')
"""Execute the parsing of the scene"""
logger.debug("Scene().parse()")
if self.options.get(constants.MAPS):
self.__parse_textures()
self._parse_textures()
if self.options.get(constants.MATERIALS):
self.__parse_materials()
self._parse_materials()
self._parse_geometries()
self._parse_objects()
def texture(self, value):
"""Find a texture node that matches either a name
or uuid value.
self.__parse_geometries()
self.__parse_objects()
:param value: name or uuid
:type value: str
def texture(self, arg):
logger.debug('Scene().texture(%s)', arg)
return self._find_node(arg, self[constants.TEXTURES])
"""
logger.debug("Scene().texture(%s)", value)
return _find_node(value, self[constants.TEXTURES])
def write(self):
logger.debug('Scene().write()')
"""Write the parsed scene to disk."""
logger.debug("Scene().write()")
data = {}
embed_anim = self.options.get(constants.EMBED_ANIMATION, True)
embed = self.options[constants.EMBED_GEOMETRY]
embed = self.options.get(constants.EMBED_GEOMETRY, True)
compression = self.options.get(constants.COMPRESSION)
extension = constants.EXTENSIONS.get(compression,
extension = constants.EXTENSIONS.get(
compression,
constants.EXTENSIONS[constants.JSON])
export_dir = os.path.dirname(self.filepath)
for key, value in self.items():
if key == constants.GEOMETRIES:
geometries = []
for geometry in value:
for geom in value:
if not embed_anim:
geometry.write_animation(export_dir)
geom.write_animation(export_dir)
geom_data = geom.copy()
if embed:
for each in value:
geometries.append(each.copy())
geometries.append(geom_data)
continue
geom_data = geometry.copy()
geo_type = geom_data[constants.TYPE].lower()
if geo_type == constants.GEOMETRY.lower():
geom_data.pop(constants.DATA)
......@@ -104,10 +140,10 @@ class Scene(base_classes.BaseScene):
geom_data.pop(constants.ATTRIBUTES)
geom_data.pop(constants.METADATA)
url = 'geometry.%s%s' % (geometry.node, extension)
url = 'geometry.%s%s' % (geom.node, extension)
geometry_file = os.path.join(export_dir, url)
geometry.write(filepath=geometry_file)
geom.write(filepath=geometry_file)
geom_data[constants.URL] = os.path.basename(url)
geometries.append(geom_data)
......@@ -124,20 +160,12 @@ class Scene(base_classes.BaseScene):
if self.options.get(constants.COPY_TEXTURES):
for geo in self[constants.GEOMETRIES]:
logger.info('Copying textures from %s', geo.node)
logger.info("Copying textures from %s", geo.node)
geo.copy_textures()
def _find_node(self, arg, manifest):
for index in manifest:
uuid = index.get(constants.UUID) == arg
name = index.node == arg
if uuid or name:
return index
else:
logger.debug('No matching node for %s', arg)
def __parse_geometries(self):
logger.debug('Scene().__parse_geometries()')
def _parse_geometries(self):
"""Locate all geometry nodes and parse them"""
logger.debug("Scene()._parse_geometries()")
# this is an important step. please refer to the doc string
# on the function for more information
......@@ -146,47 +174,75 @@ class Scene(base_classes.BaseScene):
# now iterate over all the extracted mesh nodes and parse each one
for mesh in api.object.extracted_meshes():
logger.info('Parsing geometry %s', mesh)
logger.info("Parsing geometry %s", mesh)
geo = geometry.Geometry(mesh, self)
geo.parse()
geometries.append(geo)
logger.info('Added %d geometry nodes', len(geometries))
logger.info("Added %d geometry nodes", len(geometries))
self[constants.GEOMETRIES] = geometries
def __parse_materials(self):
logger.debug('Scene().__parse_materials()')
def _parse_materials(self):
"""Locate all non-orphaned materials and parse them"""
logger.debug("Scene()._parse_materials()")
materials = []
for material_name in api.material.used_materials():
logger.info('Parsing material %s', material_name)
materials.append(material.Material(material_name, parent=self))
logger.info("Parsing material %s", material_name)
materials.append(material.Material(material_name, parent=self))
logger.info('Added %d material nodes', len(materials))
logger.info("Added %d material nodes", len(materials))
self[constants.MATERIALS] = materials
def __parse_objects(self):
logger.debug('Scene().__parse_objects()')
self[constants.OBJECT] = object.Object(None, parent=self)
def _parse_objects(self):
"""Locate all valid objects in the scene and parse them"""
logger.debug("Scene()._parse_objects()")
try:
scene_name = self[constants.METADATA][constants.SOURCE_FILE]
except KeyError:
scene_name = constants.SCENE
self[constants.OBJECT] = object_.Object(None, parent=self)
self[constants.OBJECT][constants.TYPE] = constants.SCENE.title()
self[constants.UUID] = utilities.id_from_name(scene_name)
objects = []
for node in api.object.nodes(self.valid_types, self.options):
logger.info('Parsing object %s', node)
obj = object.Object(node, parent=self[constants.OBJECT])
objects = []
for node in api.object.assemblies(self.valid_types, self.options):
logger.info("Parsing object %s", node)
obj = object_.Object(node, parent=self[constants.OBJECT])
objects.append(obj)
logger.info('Added %d object nodes', len(objects))
logger.info("Added %d object nodes", len(objects))
self[constants.OBJECT][constants.CHILDREN] = objects
def __parse_textures(self):
logger.debug('Scene().__parse_textures()')
def _parse_textures(self):
"""Locate all non-orphaned textures and parse them"""
logger.debug("Scene()._parse_textures()")
textures = []
for texture_name in api.texture.textures():
logger.info('Parsing texture %s', texture_name)
logger.info("Parsing texture %s", texture_name)
tex_inst = texture.Texture(texture_name, self)
textures.append(tex_inst)
logger.info('Added %d texture nodes', len(textures))
logger.info("Added %d texture nodes", len(textures))
self[constants.TEXTURES] = textures
def _find_node(value, manifest):
"""Find a node that matches either a name
or uuid value.
:param value: name or uuid
:param manifest: manifest of nodes to search
:type value: str
:type manifest: list
"""
for index in manifest:
uuid = index.get(constants.UUID) == value
name = index.node == value
if uuid or name:
return index
else:
logger.debug("No matching node for %s", value)
......@@ -3,8 +3,9 @@ from . import base_classes, image, api
class Texture(base_classes.BaseNode):
"""Class that wraps a texture node"""
def __init__(self, node, parent):
logger.debug('Texture().__init__(%s)', node)
logger.debug("Texture().__init__(%s)", node)
base_classes.BaseNode.__init__(self, node, parent, constants.TEXTURE)
img_inst = self.scene.image(api.texture.file_name(self.node))
......@@ -29,4 +30,10 @@ class Texture(base_classes.BaseNode):
@property
def image(self):
"""
:return: the image object of the current texture
:rtype: image.Image
"""
return self.scene.image(self[constants.IMAGE])
......@@ -8,9 +8,15 @@ ROUND = constants.DEFAULT_PRECISION
def bit_mask(flags):
"""Generate a bit mask.
:type flags: dict
:return: int
"""
bit = 0
true = lambda x,y: (x | (1 << y))
false = lambda x,y: (x & (~(1 << y)))
true = lambda x, y: (x | (1 << y))
false = lambda x, y: (x & (~(1 << y)))
for mask, position in constants.MASK.items():
func = true if flags.get(mask) else false
......@@ -20,16 +26,43 @@ def bit_mask(flags):
def hash(value):
"""Generate a hash from a given value
:param value:
:rtype: str
"""
hash_ = hashlib.md5()
hash_.update(repr(value).encode('utf8'))
return hash_.hexdigest()
def id():
"""Generate a random UUID
:rtype: str
"""
return str(uuid.uuid4()).upper()
def id_from_name(name):
"""Generate a UUID using a name as the namespace
:type name: str
:rtype: str
"""
return str(uuid.uuid3(uuid.NAMESPACE_DNS, name)).upper()
def rgb2int(rgb):
"""Convert a given rgb value to an integer
:type rgb: list|tuple
:rtype: int
"""
is_tuple = isinstance(rgb, tuple)
rgb = list(rgb) if is_tuple else rgb
......@@ -38,6 +71,15 @@ def rgb2int(rgb):
def round_off(value, ndigits=ROUND):
"""Round off values to specified limit
:param value: value(s) to round off
:param ndigits: limit (Default = ROUND)
:type value: float|list|tuple
:return: the same data type that was passed
:rtype: float|list|tuple
"""
is_tuple = isinstance(value, tuple)
is_list = isinstance(value, list)
......@@ -55,10 +97,17 @@ def round_off(value, ndigits=ROUND):
def rounding(options):
round_off = options.get(constants.ENABLE_PRECISION)
if round_off:
"""By evaluation the options determine if precision was
enabled and what the value is
:type options: dict
:rtype: bool, int
"""
round_off_ = options.get(constants.ENABLE_PRECISION)
if round_off_:
round_val = options[constants.PRECISION]
else:
round_val = None
return (round_off, round_val)
return (round_off_, round_val)
......@@ -15,7 +15,14 @@ LEVELS = {
constants.CRITICAL: logging.CRITICAL
}
def init(filename, level=constants.DEBUG):
"""Initialize the logger.
:param filename: base name of the log file
:param level: logging level (Default = DEBUG)
"""
global LOG_FILE
LOG_FILE = os.path.join(tempfile.gettempdir(), filename)
with open(LOG_FILE, 'w'):
......
......@@ -99,6 +99,10 @@ function loadObject( data ) {
camera = scene.children[ i ];
var container = document.getElementById( 'viewport' );
orbit = new THREE.OrbitControls( camera, container );
orbit.addEventListener( 'change', render );
var aspect = container.offsetWidth / container.offsetHeight;
camera.aspect = aspect;
camera.updateProjectionMatrix();
......@@ -131,14 +135,14 @@ function loadGeometry( data, url ) {
var material = new THREE.MeshFaceMaterial( data.materials );
var mesh;
if ( data.geometry.animation !== undefined ) {
if ( data.geometry.animations !== undefined && data.geometry.animations.length > 0 ) {
console.log( 'loading animation' );
data.materials[ 0 ].skinning = true;
mesh = new THREE.SkinnedMesh( data.geometry, material, false);
mesh = new THREE.SkinnedMesh( data.geometry, material, false );
var name = data.geometry.animation.name;
animation = new THREE.Animation( mesh, data.geometry.animation );
var name = data.geometry.animations[0].name;
animation = new THREE.Animation( mesh, data.geometry.animations[0] );
} else {
......
......@@ -4,5 +4,5 @@ DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$DIR/setup_test_env.bash"
blender --background $BLEND/torusA.blend --python $PYSCRIPT -- \
$JSON --vertices --faces --normals
$JSON --vertices --faces --normals --indent
makereview $@ --tag $(tagname)
#!/bin/bash
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source "$DIR/setup_test_env.bash"
blender --background $BLEND/scene_children.blend \
--python $PYSCRIPT -- $JSON --vertices --faces --scene \
--cameras --materials --embedGeometry --lights --cameras
makereview $@ --tag $(tagname)
......@@ -5,5 +5,5 @@ source "$DIR/setup_test_env.bash"
blender --background $BLEND/scene_instancing.blend --python $PYSCRIPT -- \
$JSON --vertices --faces --scene --materials --enablePrecision \
--precision 4 --embedGeometry
--precision 4 --embedGeometry --indent
makereview $@ --tag $(tagname)
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册