未验证 提交 2a0d0bb1 编写于 作者: M Mr.doob 提交者: GitHub

Merge pull request #14117 from donmccurdy/deprecate-blender-exporter

Remove Blender exporter, add 'Loading 3D models' guide.
......@@ -10,6 +10,7 @@ var list = {
"How to run things locally": "manual/introduction/How-to-run-things-locally",
"Drawing Lines": "manual/introduction/Drawing-lines",
"Creating Text": "manual/introduction/Creating-text",
"Loading 3D Models": "manual/introduction/Loading-3D-models",
"Migration Guide": "manual/introduction/Migration-guide",
"Code Style Guide": "manual/introduction/Code-style-guide",
"FAQ": "manual/introduction/FAQ",
......
......@@ -35,11 +35,11 @@
<p class="desc">
If you have successfully imported an animated 3D object (it doesn't matter if it has
bones or morph targets or both) - for example exporting it from Blender with the
[link:https://github.com/mrdoob/three.js/tree/master/utils/exporters/blender/addons/io_three Blender exporter] and
loading it into a three.js scene using [page:JSONLoader] -, one of the geometry's
properties of the loaded mesh should be an array named "animations", containing the
[page:AnimationClip AnimationClips] for this model (see a list of possible loaders below).<br /><br />
bones or morph targets or both) — for example exporting, it from Blender with the
[link:https://github.com/KhronosGroup/glTF-Blender-Exporter glTF Blender exporter] and
loading it into a three.js scene using [page:GLTFLoader] — one of the response fields
should be an array named "animations", containing the [page:AnimationClip AnimationClips]
for this model (see a list of possible loaders below).<br /><br />
Each *AnimationClip* usually holds the data for a certain activity of the object. If the
mesh is a character, for example, there may be one AnimationClip for a walkcycle, a second
......
......@@ -16,7 +16,7 @@
The recommended format for importing and exporting assets is glTF (GL Transmission Format). Because glTF is focused on runtime asset delivery, it is compact to transmit and fast to load.
</p>
<p>
three.js provides loaders for many other popular formats like FBX, Collada or OBJ as well. Nevertheless, you should always try to establish a glTF based workflow in your projects first.
three.js provides loaders for many other popular formats like FBX, Collada or OBJ as well. Nevertheless, you should always try to establish a glTF based workflow in your projects first. For more information, see [link:#manual/introduction/Loading-3D-models loading 3D models].
</p>
</div>
......
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<base href="../../" />
<script src="list.js"></script>
<script src="page.js"></script>
<link type="text/css" rel="stylesheet" href="page.css" />
</head>
<body>
<h1>[name]</h1>
<br />
<p>
3D models are available in hundreds of file formats, each with different
purposes, assorted features, and varying complexity. Although
<a href="https://github.com/mrdoob/three.js/tree/dev/examples/js/loaders">
three.js provides many loaders</a>, choosing the right format and
workflow will save time and frustration later on. Some formats are
difficult to work with, inefficient for realtime experiences, or simply not
fully supported at this time.
</p>
<p>
This guide provides a workflow recommended for most users, and suggestions
for what to try if things don't go as expected.
</p>
<h2>Before we start</h2>
<p>
If you're new to running a local server, begin with
[link:#manual/introduction/How-to-run-things-locally how to run things locally]
first. Many common errors viewing 3D models can be avoided by hosting files
correctly.
</p>
<h2>Recommended workflow</h2>
<p>
Where possible, we recommend using glTF (GL Transmission Format). Both
<small>.GLB</small> and <small>.GLTF</small> versions of the format are
well supported. Because glTF is focused on runtime asset delivery, it is
compact to transmit and fast to load. Features include meshes, materials,
textures, skins, skeletons, morph targets, animations, lights, and
cameras.
</p>
<p>
Public-domain glTF files are available on sites like
<a href="https://sketchfab.com/models?features=downloadable&sort_by=-likeCount&type=models">
Sketchfab</a>, or various tools include glTF export:
</p>
<ul>
<li><a href="https://github.com/KhronosGroup/glTF-Blender-Exporter">glTF-Blender-Exporter</a> by the Khronos Group</li>
<li><a href="https://github.com/KhronosGroup/COLLADA2GLTF">COLLADA2GLTF</a> by the Khronos Group</li>
<li><a href="https://github.com/facebookincubator/FBX2glTF">FBX2GLTF</a> by Facebook</li>
<li><a href="https://github.com/AnalyticalGraphicsInc/obj2gltf">OBJ2GLTF</a> by Analytical Graphics Inc</li>
<li><a href="https://www.allegorithmic.com/products/substance-painter">Substance Painter</a> by Allegorithmic</li>
<li><a href="https://www.foundry.com/products/modo">Modo</a> by Foundry</li>
<li><a href="https://www.marmoset.co/toolbag/">Toolbag</a> by Marmoset</li>
<li>&hellip;and <a href="https://github.com/khronosgroup/gltf#gltf-tools">many more</a></li>
</ul>
<p>
If your preferred tools do not support glTF, consider requesting glTF
export from the authors, or posting on
<a href="https://github.com/KhronosGroup/glTF/issues/1051">the glTF roadmap thread</a>.
</p>
<p>
When glTF is not an option, popular formats such as FBX, OBJ, or COLLADA
are also available and regularly maintained.
</p>
<h2>Troubleshooting</h2>
<p>
You've spent hours modeling an artisanal masterpiece, you load it into
the webpage, and — oh no! 😭 It's distorted, miscolored, or missing entirely.
Start with these troubleshooting steps:
</p>
<ol>
<li>
Check the JavaScript console for errors, and make sure you've used an
<em>onError</em> callback when calling <em>.load()</em> to log the result.
</li>
<li>
View the model in another application. For glTF, drag-and-drop viewers
are available for
<a href="https://gltf-viewer.donmccurdy.com/">three.js</a> and
<a href="http://sandbox.babylonjs.com/">babylon.js</a>. If the model
appears correctly in one or more applications,
<a href="https://github.com/mrdoob/three.js/issues/new">file a bug against three.js</a>.
If the model cannot be shown in any application, we strongly encourage
filing a bug with the application used to create the model.
</li>
<li>
Try scaling the model up or down by a factor of 1000. Many models are
scaled differently, and large models may not appear if the camera is
inside the model.
</li>
<li>
Look for failed texture requests in the network tab, like
<em>C:\\Path\To\Model\texture.jpg</em>. Use paths relative to your
model instead, such as <em>images/texture.jpg</em> — this may require
editing the model file in a text editor.
</li>
</ol>
<h2>Asking for help</h2>
<p>
If you've gone through the troubleshooting process above and your model
still isn't working, the right approach to asking for help will get you to
a solution faster. Whenever possible, include your model (or a simpler
model with the same problem) in any formats you have available. Include
enough information for someone else to reproduce the issue quickly —
ideally, a live demo.
</p>
<p>
TODO: Do we recommend model-related questions go to GitHub, Stack Overflow,
or the Discourse forum?
</p>
</body>
</html>
\ No newline at end of file
# Three.js Blender Export
Exports Three.js' ASCII JSON format.
## IMPORTANT
The exporter (r69 and earlier) has been completely replaced. Please ensure you have removed the io_three_mesh addon from your Blender addons directory before installing the current addon (io_three).
## Installation
Recommended Blender version **>= 2.73.0**
Copy the io_three folder to the scripts/addons folder. If it doesn't exist, create it. The full path is OS-dependent (see below).
Once that is done, you need to activate the plugin. Open Blender preferences, look for
Addons, search for `three`, enable the checkbox next to the `Import-Export: Three.js Format` entry.
Goto Usage.
### Windows
Should look like this:
C:\Program Files\Blender Foundation\Blender\2.7X\scripts\addons
OR (for 2.6)
C:\Users\USERNAME\AppData\Roaming\Blender Foundation\Blender\2.6X\scripts\addons
### OSX
In your user's library for user installed Blender addons:
/Users/(myuser)/Library/Application Support/Blender/2.7X/scripts/addons
OR (for 2.79)
/Applications/Blender/blender.app/Contents/Resources/2.79/scripts/addons
### Linux
By default, this should look like:
/home/USERNAME/.config/blender/2.6X/scripts/addons
For Ubuntu users who installed Blender 2.68 via apt-get, this is the location:
/usr/lib/blender/scripts/addons
For Ubuntu users who installed Blender 2.7x via apt-get, this is the location:
/usr/share/blender/scripts/addons
## Usage
Activate the Import-Export addon under "User Preferences" > "Addons" and then use the regular Export menu within Blender, select `Three.js (json)`.
## Enabling msgpack
To enable msgpack compression copy the msgpack to scripts/modules.
## Importer
Currently there is no import functionality available.
> **NOTICE:** The Blender exporter for the Three.js JSON format has been removed, to focus on better support for other workflows. For recommended alternatives, see [Loading 3D Models](https://threejs.org/docs/#manual/introduction/loading-3d-models). The Three.js JSON format is still fully supported for use with [Object3D.toJSON](https://threejs.org/docs/#api/core/Object3D.toJSON), the [Editor](https://threejs.org/editor/), [THREE.ObjectLoader](https://threejs.org/docs/#api/loaders/ObjectLoader), [THREE.JSONLoader](https://threejs.org/docs/#api/loaders/JSONLoader), and [converters](https://github.com/mrdoob/three.js/tree/dev/utils/converters).
'''
All constant data used in the package should be defined here.
'''
from collections import OrderedDict as BASE_DICT
BLENDING_TYPES = type('Blending', (), {
'NONE': 'NoBlending',
'NORMAL': 'NormalBlending',
'ADDITIVE': 'AdditiveBlending',
'SUBTRACTIVE': 'SubtractiveBlending',
'MULTIPLY': 'MultiplyBlending',
'CUSTOM': 'CustomBlending'
})
BLENDING_CONSTANTS = type('BlendingConstant', (), {
'NoBlending':0,
'NormalBlending':1,
'AdditiveBlending':2,
'SubtractiveBlending':3,
'MultiplyBlending':4,
'CustomBlending':5
})
NEAREST_FILTERS = type('NearestFilters', (), {
'NEAREST': 'NearestFilter',
'MIP_MAP_NEAREST': 'NearestMipMapNearestFilter',
'MIP_MAP_LINEAR': 'NearestMipMapLinearFilter'
})
LINEAR_FILTERS = type('LinearFilters', (), {
'LINEAR': 'LinearFilter',
'MIP_MAP_NEAREST': 'LinearMipMapNearestFilter',
'MIP_MAP_LINEAR': 'LinearMipMapLinearFilter'
})
MAPPING_TYPES = type('Mapping', (), {
'UV': 'UVMapping',
'CUBE_REFLECTION': 'CubeReflectionMapping',
'CUBE_REFRACTION': 'CubeRefractionMapping',
'SPHERICAL_REFLECTION': 'SphericalReflectionMapping'
})
NUMERIC = {
'UVMapping': 300,
'CubeReflectionMapping': 301,
'CubeRefractionMapping': 302,
'EquirectangularReflectionMapping': 303,
'EquirectangularRefractionMapping': 304,
'SphericalReflectionMapping': 305,
'RepeatWrapping': 1000,
'repeat': 1000,
'ClampToEdgeWrapping': 1001,
'MirroredRepeatWrapping': 1002,
'NearestFilter': 1003,
'NearestMipMapNearestFilter': 1004,
'NearestMipMapLinearFilter': 1005,
'LinearFilter': 1006,
'LinearMipMapNearestFilter': 1007,
'LinearMipMapLinearFilter': 1008
}
JSON = 'json'
EXTENSION = '.%s' % JSON
INDENT = 'indent'
MATERIALS = 'materials'
SCENE = 'scene'
VERTICES = 'vertices'
FACES = 'faces'
NORMALS = 'normals'
BONES = 'bones'
UVS = 'uvs'
APPLY_MODIFIERS = 'applyModifiers'
COLORS = 'colors'
MIX_COLORS = 'mixColors'
EXTRA_VGROUPS = 'extraVertexGroups'
INDEX = 'index'
DRAW_CALLS = 'drawcalls'
DC_START = 'start'
DC_COUNT = 'count'
DC_INDEX = 'index'
GROUPS = 'groups'
SCALE = 'scale'
COMPRESSION = 'compression'
MAPS = 'maps'
FRAME_STEP = 'frameStep'
FRAME_INDEX_AS_TIME = 'frameIndexAsTime'
ANIMATION = 'animations'
CLIPS="clips"
KEYFRAMES = 'tracks'
BAKE_KEYFRAMES = 'bake_tracks'
MORPH_TARGETS = 'morphTargets'
MORPH_TARGETS_ANIM = 'morphTargetsAnimation'
BLEND_SHAPES = 'blendShapes'
POSE = 'pose'
REST = 'rest'
SKIN_INDICES = 'skinIndices'
SKIN_WEIGHTS = 'skinWeights'
LOGGING = 'logging'
CAMERAS = 'cameras'
LIGHTS = 'lights'
HIERARCHY = 'hierarchy'
FACE_MATERIALS = 'faceMaterials'
SKINNING = 'skinning'
EXPORT_TEXTURES = 'exportTextures'
EMBED_TEXTURES = 'embedTextures'
TEXTURE_FOLDER = 'textureFolder'
ENABLE_PRECISION = 'enablePrecision'
PRECISION = 'precision'
DEFAULT_PRECISION = 6
CUSTOM_PROPERTIES = 'customProperties'
EMBED_GEOMETRY = 'embedGeometry'
EMBED_ANIMATION = 'embedAnimation'
OFF = 'off'
GLOBAL = 'global'
BUFFER_GEOMETRY = 'BufferGeometry'
GEOMETRY = 'geometry'
GEOMETRY_TYPE = 'geometryType'
INDEX_TYPE = 'indexType'
CRITICAL = 'critical'
ERROR = 'error'
WARNING = 'warning'
INFO = 'info'
DEBUG = 'debug'
DISABLED = 'disabled'
NONE = 'None'
MSGPACK = 'msgpack'
PACK = 'pack'
FLOAT_32 = 'Float32Array'
UINT_16 = 'Uint16Array'
UINT_32 = 'Uint32Array'
INFLUENCES_PER_VERTEX = 'influencesPerVertex'
EXPORT_OPTIONS = {
FACES: True,
VERTICES: True,
NORMALS: True,
UVS: True,
APPLY_MODIFIERS: True,
COLORS: False,
EXTRA_VGROUPS: '',
INDEX_TYPE: UINT_16,
MATERIALS: False,
FACE_MATERIALS: False,
SCALE: 1,
FRAME_STEP: 1,
FRAME_INDEX_AS_TIME: False,
SCENE: False,
MIX_COLORS: False,
COMPRESSION: None,
MAPS: False,
ANIMATION: OFF,
KEYFRAMES: False,
BAKE_KEYFRAMES: False,
BONES: False,
SKINNING: False,
MORPH_TARGETS: False,
BLEND_SHAPES: False,
CAMERAS: False,
LIGHTS: False,
HIERARCHY: False,
EXPORT_TEXTURES: True,
EMBED_TEXTURES: False,
TEXTURE_FOLDER: '',
LOGGING: DEBUG,
ENABLE_PRECISION: False,
PRECISION: DEFAULT_PRECISION,
CUSTOM_PROPERTIES: False,
EMBED_GEOMETRY: True,
EMBED_ANIMATION: True,
GEOMETRY_TYPE: BUFFER_GEOMETRY,
INFLUENCES_PER_VERTEX: 2,
INDENT: True
}
FORMAT_VERSION = 4.4
VERSION = 'version'
THREE = 'io_three'
GENERATOR = 'generator'
SOURCE_FILE = 'sourceFile'
VALID_DATA_TYPES = (str, int, float, bool, list, tuple, dict)
JSON = 'json'
GZIP = 'gzip'
EXTENSIONS = {
JSON: '.json',
MSGPACK: '.pack',
GZIP: '.gz'
}
METADATA = 'metadata'
GEOMETRIES = 'geometries'
IMAGES = 'images'
TEXTURE = 'texture'
TEXTURES = 'textures'
USER_DATA = 'userData'
DATA = 'data'
TYPE = 'type'
MATERIAL = 'material'
OBJECT = 'object'
PERSPECTIVE_CAMERA = 'PerspectiveCamera'
ORTHOGRAPHIC_CAMERA = 'OrthographicCamera'
AMBIENT_LIGHT = 'AmbientLight'
DIRECTIONAL_LIGHT = 'DirectionalLight'
POINT_LIGHT = 'PointLight'
SPOT_LIGHT = 'SpotLight'
# TODO (abelnation): confirm this is correct area light string for exporter
RECT_AREA_LIGHT = 'RectAreaLight'
HEMISPHERE_LIGHT = 'HemisphereLight'
# TODO: RectAreaLight support
MESH = 'Mesh'
EMPTY = 'Empty'
SPRITE = 'Sprite'
DEFAULT_METADATA = {
VERSION: FORMAT_VERSION,
TYPE: OBJECT.title(),
GENERATOR: THREE
}
UUID = 'uuid'
MATRIX = 'matrix'
POSITION = 'position'
QUATERNION = 'quaternion'
ROTATION = 'rotation'
SCALE = 'scale'
UV = 'uv'
UV2 = 'uv2'
ATTRIBUTES = 'attributes'
NORMAL = 'normal'
ITEM_SIZE = 'itemSize'
ARRAY = 'array'
VISIBLE = 'visible'
CAST_SHADOW = 'castShadow'
RECEIVE_SHADOW = 'receiveShadow'
QUAD = 'quad'
MASK = {
QUAD: 0,
MATERIALS: 1,
UVS: 3,
NORMALS: 5,
COLORS: 7
}
CHILDREN = 'children'
URL = 'url'
WRAP = 'wrap'
REPEAT = 'repeat'
WRAPPING = type('Wrapping', (), {
'REPEAT': 'repeat',
'CLAMP': 'clamp',
'MIRROR': 'mirror'
})
ANISOTROPY = 'anisotropy'
MAG_FILTER = 'magFilter'
MIN_FILTER = 'minFilter'
MAPPING = 'mapping'
IMAGE = 'image'
NAME = 'name'
PARENT = 'parent'
LENGTH = 'length'
FPS = 'fps'
HIERARCHY = 'hierarchy'
POS = 'pos'
ROTQ = 'rotq'
ROT = 'rot'
SCL = 'scl'
TIME = 'time'
KEYS = 'keys'
COLOR = 'color'
EMISSIVE = 'emissive'
SPECULAR = 'specular'
SPECULAR_COEF = 'specularCoef'
SHININESS = 'shininess'
SIDE = 'side'
OPACITY = 'opacity'
TRANSPARENT = 'transparent'
WIREFRAME = 'wireframe'
BLENDING = 'blending'
VERTEX_COLORS = 'vertexColors'
DEPTH_WRITE = 'depthWrite'
DEPTH_TEST = 'depthTest'
MAP = 'map'
SPECULAR_MAP = 'specularMap'
LIGHT_MAP = 'lightMap'
BUMP_MAP = 'bumpMap'
BUMP_SCALE = 'bumpScale'
NORMAL_MAP = 'normalMap'
NORMAL_SCALE = 'normalScale'
#@TODO ENV_MAP, REFLECTIVITY, REFRACTION_RATIO, COMBINE
MAP_DIFFUSE = 'mapDiffuse'
MAP_DIFFUSE_REPEAT = 'mapDiffuseRepeat'
MAP_DIFFUSE_WRAP = 'mapDiffuseWrap'
MAP_DIFFUSE_ANISOTROPY = 'mapDiffuseAnisotropy'
MAP_SPECULAR = 'mapSpecular'
MAP_SPECULAR_REPEAT = 'mapSpecularRepeat'
MAP_SPECULAR_WRAP = 'mapSpecularWrap'
MAP_SPECULAR_ANISOTROPY = 'mapSpecularAnisotropy'
MAP_LIGHT = 'mapLight'
MAP_LIGHT_REPEAT = 'mapLightRepeat'
MAP_LIGHT_WRAP = 'mapLightWrap'
MAP_LIGHT_ANISOTROPY = 'mapLightAnisotropy'
MAP_NORMAL = 'mapNormal'
MAP_NORMAL_FACTOR = 'mapNormalFactor'
MAP_NORMAL_REPEAT = 'mapNormalRepeat'
MAP_NORMAL_WRAP = 'mapNormalWrap'
MAP_NORMAL_ANISOTROPY = 'mapNormalAnisotropy'
MAP_BUMP = 'mapBump'
MAP_BUMP_REPEAT = 'mapBumpRepeat'
MAP_BUMP_WRAP = 'mapBumpWrap'
MAP_BUMP_ANISOTROPY = 'mapBumpAnisotropy'
MAP_BUMP_SCALE = 'mapBumpScale'
NORMAL_BLENDING = 0
VERTEX_COLORS_ON = 2
VERTEX_COLORS_OFF = 0
SIDE_DOUBLE = 2
THREE_BASIC = 'MeshBasicMaterial'
THREE_LAMBERT = 'MeshLambertMaterial'
THREE_PHONG = 'MeshPhongMaterial'
INTENSITY = 'intensity'
DISTANCE = 'distance'
ANGLE = 'angle'
DECAY = 'decayExponent'
FOV = 'fov'
ASPECT = 'aspect'
NEAR = 'near'
FAR = 'far'
LEFT = 'left'
RIGHT = 'right'
TOP = 'top'
BOTTOM = 'bottom'
SHADING = 'shading'
COLOR_DIFFUSE = 'colorDiffuse'
COLOR_EMISSIVE = 'colorEmissive'
COLOR_SPECULAR = 'colorSpecular'
DBG_NAME = 'DbgName'
DBG_COLOR = 'DbgColor'
DBG_INDEX = 'DbgIndex'
EMIT = 'emit'
PHONG = 'phong'
LAMBERT = 'lambert'
BASIC = 'basic'
NORMAL_BLENDING = 'NormalBlending'
DBG_COLORS = (0xeeeeee, 0xee0000, 0x00ee00, 0x0000ee,
0xeeee00, 0x00eeee, 0xee00ee)
DOUBLE_SIDED = 'doubleSided'
EXPORT_SETTINGS_KEY = 'threeExportSettings'
# flips vectors
XZ_Y = "XZ_Y"
X_ZY = "X_ZY"
XYZ = "XYZ"
_XY_Z = "_XY_Z"
from bpy import context
CONTEXT = {
0: {
'title': "Error Message",
'icon': 'CANCEL'
},
1: {
'title': "Warning Message",
'icon': 'ERROR' # I prefer this icon for warnings
},
2: {
'title': "Message",
'icon': 'NONE'
},
3: {
'title': "Question",
'icon': 'QUESTION'
}
}
def error(message, title="", wrap=40):
"""Creates an error dialog.
:param message: text of the message body
:param title: text to append to the title
(Default value = "")
:param wrap: line width (Default value = 40)
"""
_draw(message, title, wrap, 0)
def warning(message, title="", wrap=40):
"""Creates an error dialog.
:param message: text of the message body
:param title: text to append to the title
(Default value = "")
:param wrap: line width (Default value = 40)
"""
_draw(message, title, wrap, 1)
def info(message, title="", wrap=40):
"""Creates an error dialog.
:param message: text of the message body
:param title: text to append to the title
(Default value = "")
:param wrap: line width (Default value = 40)
"""
_draw(message, title, wrap, 2)
def question(message, title="", wrap=40):
"""Creates an error dialog.
:param message: text of the message body
:param title: text to append to the title
(Default value = "")
:param wrap: line width (Default value = 40)
"""
_draw(message, title, wrap, 3)
# Great idea borrowed from
# http://community.cgcookie.com/t/code-snippet-easy-error-messages/203
def _draw(message, title, wrap, key):
"""
:type message: str
:type title: str
:type wrap: int
:type key: int
"""
lines = []
if wrap > 0:
while len(message) > wrap:
i = message.rfind(' ', 0, wrap)
if i == -1:
lines += [message[:wrap]]
message = message[wrap:]
else:
lines += [message[:i]]
message = message[i+1:]
if message:
lines += [message]
def draw(self, *args):
"""
:param self:
:param *args:
"""
for line in lines:
self.layout.label(line)
title = "%s: %s" % (title, CONTEXT[key]['title'])
icon = CONTEXT[key]['icon']
context.window_manager.popup_menu(
draw, title=title.strip(), icon=icon)
class ThreeError(Exception): pass
class UnimplementedFeatureError(ThreeError): pass
class ThreeValueError(ThreeError): pass
class UnsupportedObjectType(ThreeError): pass
class GeometryError(ThreeError): pass
class MaterialError(ThreeError): pass
class SelectionError(ThreeError): pass
class NGonError(ThreeError): pass
class BufferGeometryError(ThreeError): pass
import os
import sys
import traceback
from .. import constants, logger, exceptions, dialogs
from . import scene, geometry, api, base_classes
def _error_handler(func):
def inner(filepath, options, *args, **kwargs):
level = options.get(constants.LOGGING, constants.DISABLED)
version = options.get('addon_version')
if level != constants.DISABLED:
logger.init('io_three.export.log', level=level)
if version is not None:
logger.debug("Addon Version %s", version)
api.init()
try:
func(filepath, options, *args, **kwargs)
except:
info = sys.exc_info()
trace = traceback.format_exception(
info[0], info[1], info[2].tb_next)
trace = ''.join(trace)
logger.error(trace)
print('Error recorded to %s' % logger.LOG_FILE)
raise
else:
print('Log: %s' % logger.LOG_FILE)
return inner
@_error_handler
def export_scene(filepath, options):
selected = []
# during scene exports unselect everything. this is needed for
# applying modifiers, if it is necessary
# record the selected nodes so that selection is restored later
for obj in api.selected_objects():
api.object.unselect(obj)
selected.append(obj)
active = api.active_object()
try:
scene_ = scene.Scene(filepath, options=options)
scene_.parse()
scene_.write()
except:
_restore_selection(selected, active)
raise
_restore_selection(selected, active)
@_error_handler
def export_geometry(filepath, options, node=None):
msg = ""
exception = None
if node is None:
node = api.active_object()
if node is None:
msg = "Nothing selected"
logger.error(msg)
exception = exceptions.SelectionError
if node.type != 'MESH':
msg = "%s is not a valid mesh object" % node.name
logger.error(msg)
exception = exceptions.GeometryError
if exception is not None:
if api.batch_mode():
raise exception(msg)
else:
dialogs.error(msg)
return
mesh = api.object.mesh(node, options)
parent = base_classes.BaseScene(filepath, options)
geo = geometry.Geometry(mesh, parent)
geo.parse()
geo.write()
if not options.get(constants.EMBED_ANIMATION, True):
geo.write_animation(os.path.dirname(filepath))
def _restore_selection(objects, active):
for obj in objects:
api.object.select(obj)
api.set_active_object(active)
import json
from .. import constants
ROUND = constants.DEFAULT_PRECISION
## THREE override function
def _json_floatstr(o):
if ROUND is not None:
o = round(o, ROUND)
return '%g' % o
def _make_iterencode(markers, _default, _encoder, _indent, _floatstr,
_key_separator, _item_separator, _sort_keys, _skipkeys, _one_shot,
## HACK: hand-optimized bytecode; turn globals into locals
ValueError=ValueError,
dict=dict,
float=float,
id=id,
int=int,
isinstance=isinstance,
list=list,
str=str,
tuple=tuple,
):
'''
Overwrite json.encoder for Python 2.7 and above to not
assign each index of a list or tuple to its own row as
this is completely asinine behaviour
'''
## @THREE
# Override the function
_floatstr = _json_floatstr
if _indent is not None and not isinstance(_indent, str):
_indent = ' ' * _indent
def _iterencode_list(lst, _current_indent_level):
if not lst:
yield '[]'
return
if markers is not None:
markerid = id(lst)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = lst
buf = '['
## @THREEJS
# - block the moronic functionality that puts each
# index on its own line causing insane row counts
#if _indent is not None:
# _current_indent_level += 1
# newline_indent = '\n' + _indent * _current_indent_level
# separator = _item_separator + newline_indent
# buf += newline_indent
#else:
newline_indent = None
separator = _item_separator
first = True
for value in lst:
if first:
first = False
else:
buf = separator
if isinstance(value, str):
yield buf + _encoder(value)
elif value is None:
yield buf + 'null'
elif value is True:
yield buf + 'true'
elif value is False:
yield buf + 'false'
elif isinstance(value, int):
yield buf + str(value)
elif isinstance(value, float):
yield buf + _floatstr(value)
else:
yield buf
if isinstance(value, (list, tuple)):
chunks = _iterencode_list(value, _current_indent_level)
elif isinstance(value, dict):
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
for chunk in chunks:
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + _indent * _current_indent_level
yield ']'
if markers is not None:
del markers[markerid]
def _iterencode_dict(dct, _current_indent_level):
if not dct:
yield '{}'
return
if markers is not None:
markerid = id(dct)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = dct
yield '{'
if _indent is not None:
_current_indent_level += 1
newline_indent = '\n' + _indent * _current_indent_level
item_separator = _item_separator + newline_indent
yield newline_indent
else:
newline_indent = None
item_separator = _item_separator
first = True
if _sort_keys:
items = sorted(dct.items(), key=lambda kv: kv[0])
else:
items = dct.items()
for key, value in items:
if isinstance(key, str):
pass
# JavaScript is weakly typed for these, so it makes sense to
# also allow them. Many encoders seem to do something like this.
elif isinstance(key, float):
key = _floatstr(key)
elif key is True:
key = 'true'
elif key is False:
key = 'false'
elif key is None:
key = 'null'
elif isinstance(key, int):
key = str(key)
elif _skipkeys:
continue
else:
raise TypeError("key " + repr(key) + " is not a string")
if first:
first = False
else:
yield item_separator
yield _encoder(key)
yield _key_separator
if isinstance(value, str):
yield _encoder(value)
elif value is None:
yield 'null'
elif value is True:
yield 'true'
elif value is False:
yield 'false'
elif isinstance(value, int):
yield str(value)
elif isinstance(value, float):
yield _floatstr(value)
else:
if isinstance(value, (list, tuple)):
chunks = _iterencode_list(value, _current_indent_level)
elif isinstance(value, dict):
chunks = _iterencode_dict(value, _current_indent_level)
else:
chunks = _iterencode(value, _current_indent_level)
for chunk in chunks:
yield chunk
if newline_indent is not None:
_current_indent_level -= 1
yield '\n' + _indent * _current_indent_level
yield '}'
if markers is not None:
del markers[markerid]
def _iterencode(o, _current_indent_level):
if isinstance(o, str):
yield _encoder(o)
elif o is None:
yield 'null'
elif o is True:
yield 'true'
elif o is False:
yield 'false'
elif isinstance(o, int):
yield str(o)
elif isinstance(o, float):
yield _floatstr(o)
elif isinstance(o, (list, tuple)):
for chunk in _iterencode_list(o, _current_indent_level):
yield chunk
elif isinstance(o, dict):
for chunk in _iterencode_dict(o, _current_indent_level):
yield chunk
else:
if markers is not None:
markerid = id(o)
if markerid in markers:
raise ValueError("Circular reference detected")
markers[markerid] = o
o = _default(o)
for chunk in _iterencode(o, _current_indent_level):
yield chunk
if markers is not None:
del markers[markerid]
return _iterencode
# override the encoder
json.encoder._make_iterencode = _make_iterencode
import os
import bpy
from . import object as object_, mesh, material, camera, light
from .. import logger
def active_object():
"""
:return: The actively selected object
"""
return bpy.context.scene.objects.active
def batch_mode():
"""
:return: Whether or not the session is interactive
:rtype: bool
"""
return bpy.context.area is None
def data(node):
"""
:param node: name of an object node
:returns: the data block of the node
"""
try:
return bpy.data.objects[node].data
except KeyError:
pass
def init():
"""Initializing the api module. Required first step before
initializing the actual export process.
"""
logger.debug("Initializing API")
object_.clear_mesh_map()
def selected_objects(valid_types=None):
"""Selected objects.
:param valid_types: Filter for valid types (Default value = None)
"""
logger.debug("api.selected_objects(%s)", valid_types)
for node in bpy.context.selected_objects:
if valid_types is None:
yield node.name
elif valid_types is not None and node.type in valid_types:
yield node.name
def set_active_object(obj):
"""Set the object as active in the scene
:param obj:
"""
logger.debug("api.set_active_object(%s)", obj)
bpy.context.scene.objects.active = obj
def scene_name():
"""
:return: name of the current scene
"""
return os.path.basename(bpy.data.filepath)
"""
Module for handling the parsing of skeletal animation data.
updated on 2/07/2016: bones scaling support (@uthor verteAzur verteAzur@multivers3d.fr)
"""
import math
import mathutils
from bpy import data, context, ops
from .. import constants, logger
def pose_animation(armature, options):
"""Query armature animation using pose bones
:param armature:
:param options:
:returns: list dictionaries containing animationdata
:rtype: [{}, {}, ...]
"""
logger.debug("animation.pose_animation(%s)", armature)
func = _parse_pose_action
return _parse_action(func, armature, options)
def rest_animation(armature, options):
"""Query armature animation (REST position)
:param armature:
:param options:
:returns: list dictionaries containing animationdata
:rtype: [{}, {}, ...]
"""
logger.debug("animation.rest_animation(%s)", armature)
func = _parse_rest_action
return _parse_action(func, armature, options)
def _parse_action(func, armature, options):
"""
:param func:
:param armature:
:param options:
"""
animations = []
logger.info("Parsing %d actions", len(data.actions))
for action in data.actions:
logger.info("Parsing action %s", action.name)
animation = func(action, armature, options)
animations.append(animation)
return animations
def _parse_rest_action(action, armature, options):
"""
:param action:
:param armature:
:param options:
"""
end_frame = action.frame_range[1]
start_frame = action.frame_range[0]
frame_length = end_frame - start_frame
rot = armature.matrix_world.decompose()[1]
rotation_matrix = rot.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)
rot = _normalize_quaternion(rot)
sca, schange = _scale(bone, computed_frame,
action, armature.matrix_world)
pos_x, pos_y, pos_z = pos.x, pos.z, -pos.y
rot_x, rot_y, rot_z, rot_w = rot.x, rot.z, -rot.y, rot.w
sca_x, sca_y, sca_z = sca.x, sca.z, sca.y
if frame == start_frame:
time = (frame * frame_step - start_frame) / fps
keyframe = {
constants.TIME: time,
constants.POS: [pos_x, pos_y, pos_z],
constants.ROT: [rot_x, rot_y, rot_z, rot_w],
constants.SCL: [sca_x, sca_y, sca_z]
}
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: [pos_x, pos_y, pos_z],
constants.ROT: [rot_x, rot_y, rot_z, rot_w],
constants.SCL: [sca_x, sca_y, sca_z]
}
keys.append(keyframe)
# MIDDLE-FRAME: needs only one of the attributes,
# can be an empty frame (optional frame)
elif pchange is True or rchange is True or schange is True:
time = (frame * frame_step - start_frame) / fps
if pchange is True and rchange is True:
keyframe = {
constants.TIME: time,
constants.POS: [pos_x, pos_y, pos_z],
constants.ROT: [rot_x, rot_y, rot_z, rot_w],
constants.SCL: [sca_x, sca_y, sca_z]
}
elif pchange is True:
keyframe = {
constants.TIME: time,
constants.POS: [pos_x, pos_y, pos_z]
}
elif rchange is True:
keyframe = {
constants.TIME: time,
constants.ROT: [rot_x, rot_y, rot_z, rot_w]
}
elif schange is True:
keyframe = {
constants.TIME: time,
constants.SCL: [sca_x, sca_y, sca_z]
}
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):
"""
:param action:
:param armature:
:param options:
"""
try:
current_context = context.area.type
except AttributeError:
for window in context.window_manager.windows:
screen = window.screen
for area in screen.areas:
if area.type != 'VIEW_3D':
continue
override = {
'window': window,
'screen': screen,
'area': area
}
ops.screen.screen_full_area(override)
break
current_context = context.area.type
context.scene.objects.active = armature
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):
"""
:param channels:
:param frame:
"""
def find_keyframe_at(channel, frame):
"""
:param channel:
:param 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()
rot = _normalize_quaternion(rot)
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)
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):
"""
:param action:
:param bone:
:param 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):
"""
:param bone:
:param frame:
:param action:
:param 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):
"""
:param bone:
:param frame:
:param action:
:param 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 _scale(bone, frame, action, armature_matrix):
"""
:param bone:
:param frame:
:param action:
:param armature_matrix:
"""
scale = mathutils.Vector((1.0, 1.0, 1.0))
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:
print(action.groups[i].name)
index = i
if index > -1:
for channel in action.groups[index].channels:
if "scale" in channel.data_path:
has_changed = _handle_scale_channel(
channel, frame, scale)
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 "scale" in data_path:
has_changed = _handle_scale_channel(
channel, frame, scale)
change = change or has_changed
#scale.xyz = armature_matrix * scale.xyz
return scale, change
def _handle_rotation_channel(channel, frame, rotation):
"""
:param channel:
:param frame:
:param 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):
"""
:param channel:
:param frame:
:param 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
def _handle_scale_channel(channel, frame, scale):
"""
:param channel:
:param frame:
:param 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:
scale.x = value
if channel.array_index == 1:
scale.y = value
if channel.array_index == 2:
scale.z = value
return change
def _quaternion_length(quat):
"""Calculate the length of a quaternion
:param quat: Blender quaternion object
:rtype: float
"""
return math.sqrt(quat.x * quat.x + quat.y * quat.y +
quat.z * quat.z + quat.w * quat.w)
def _normalize_quaternion(quat):
"""Normalize a quaternion
:param quat: Blender quaternion object
:returns: generic quaternion enum object with normalized values
:rtype: object
"""
enum = type('Enum', (), {'x': 0, 'y': 0, 'z': 0, 'w': 1})
length = _quaternion_length(quat)
if length is not 0:
length = 1 / length
enum.x = quat.x * length
enum.y = quat.y * length
enum.z = quat.z * length
enum.w = quat.w * length
return enum
import math
from bpy import data, types, context
from .. import logger
def _camera(func):
"""
:param func:
"""
def inner(name, *args, **kwargs):
"""
:param name:
:param *args:
:param **kwargs:
"""
if isinstance(name, types.Camera):
camera = name
else:
camera = data.cameras[name]
return func(camera, *args, **kwargs)
return inner
@_camera
def aspect(camera):
"""
:param camera:
:rtype: float
"""
logger.debug("camera.aspect(%s)", camera)
render = context.scene.render
return render.resolution_x/render.resolution_y
@_camera
def bottom(camera):
"""
:param camera:
:rtype: float
"""
logger.debug("camera.bottom(%s)", camera)
return -(camera.angle_y * camera.ortho_scale)
@_camera
def far(camera):
"""
:param camera:
:rtype: float
"""
logger.debug("camera.far(%s)", camera)
return camera.clip_end
@_camera
def fov(camera):
"""
:param camera:
:rtype: float
"""
logger.debug("camera.fov(%s)", camera)
fov_in_radians = camera.angle
aspect_ratio = aspect(camera)
if aspect_ratio > 1:
fov_in_radians = 2 * math.atan(math.tan(fov_in_radians / 2) / aspect_ratio)
return math.degrees(fov_in_radians)
@_camera
def left(camera):
"""
:param camera:
:rtype: float
"""
logger.debug("camera.left(%s)", camera)
return -(camera.angle_x * camera.ortho_scale)
@_camera
def near(camera):
"""
:param camera:
:rtype: float
"""
logger.debug("camera.near(%s)", camera)
return camera.clip_start
@_camera
def right(camera):
"""
:param camera:
:rtype: float
"""
logger.debug("camera.right(%s)", camera)
return camera.angle_x * camera.ortho_scale
@_camera
def top(camera):
"""
:param camera:
:rtype: float
"""
logger.debug("camera.top(%s)", camera)
return camera.angle_y * camera.ortho_scale
MESH = 'MESH'
LAMP = 'LAMP'
EMPTY = 'EMPTY'
ARMATURE = 'ARMATURE'
SPOT = 'SPOT'
POINT = 'POINT'
SUN = 'SUN'
HEMI = 'HEMI'
AREA = 'AREA'
NO_SHADOW = 'NOSHADOW'
CAMERA = 'CAMERA'
PERSP = 'PERSP'
ORTHO = 'ORTHO'
RENDER = 'RENDER'
ZYX = 'ZYX'
MULTIPLY = 'MULTIPLY'
WIRE = 'WIRE'
IMAGE = 'IMAGE'
MAG_FILTER = 'LinearFilter'
MIN_FILTER = 'LinearMipMapLinearFilter'
MAPPING = 'UVMapping'
import os
from bpy import data, types
from .. import logger
def _image(func):
"""
:param func:
"""
def inner(name, *args, **kwargs):
"""
:param name:
:param *args:
:param **kwargs:
"""
if isinstance(name, types.Image):
mesh = name
else:
mesh = data.images[name]
return func(mesh, *args, **kwargs)
return inner
def file_name(image):
"""
:param image:
:rtype: str
"""
logger.debug("image.file_name(%s)", image)
return os.path.basename(file_path(image))
@_image
def file_path(image):
"""
:param image:
:rtype: str
"""
logger.debug("image.file_path(%s)", image)
return os.path.normpath(image.filepath_from_user())
from bpy import data, types
from .. import utilities, logger
def _lamp(func):
"""
:param func:
"""
def inner(name, *args, **kwargs):
"""
:param name:
:param *args:
:param **kwargs:
"""
if isinstance(name, types.Lamp):
lamp = name
else:
lamp = data.lamps[name]
return func(lamp, *args, **kwargs)
return inner
@_lamp
def angle(lamp):
"""
:param lamp:
:rtype: float
"""
logger.debug("light.angle(%s)", lamp)
return lamp.spot_size
@_lamp
def color(lamp):
"""
:param lamp:
:rtype: int
"""
logger.debug("light.color(%s)", lamp)
colour = (lamp.color.r, lamp.color.g, lamp.color.b)
return utilities.rgb2int(colour)
@_lamp
def distance(lamp):
"""
:param lamp:
:rtype: float
"""
logger.debug("light.distance(%s)", lamp)
return lamp.distance
@_lamp
def intensity(lamp):
"""
:param lamp:
:rtype: float
"""
logger.debug("light.intensity(%s)", lamp)
return round(lamp.energy, 2)
# mapping enum values to decay exponent
__FALLOFF_TO_EXP = {
'CONSTANT': 0,
'INVERSE_LINEAR': 1,
'INVERSE_SQUARE': 2,
'CUSTOM_CURVE': 0,
'LINEAR_QUADRATIC_WEIGHTED': 2
}
@_lamp
def falloff(lamp):
"""
:param lamp:
:rtype: float
"""
logger.debug("light.falloff(%s)", lamp)
return __FALLOFF_TO_EXP[lamp.falloff_type]
from bpy import data, types
from .. import constants, logger
from .constants import MULTIPLY, WIRE, IMAGE
def _material(func):
"""
:param func:
"""
def inner(name, *args, **kwargs):
"""
:param name:
:param *args:
:param **kwargs:
"""
material = None
if isinstance(name, types.Material):
material = name
elif name:
material = data.materials[name]
return func(material, *args, **kwargs) if material else None
return inner
@_material
def blending(material):
"""
:param material:
:return: THREE_blending_type value
"""
logger.debug("material.blending(%s)", material)
try:
blend = material.THREE_blending_type
except AttributeError:
logger.debug("No THREE_blending_type attribute found")
blend = constants.NORMAL_BLENDING
blend = getattr( constants.BLENDING_CONSTANTS , blend) #manthrax: Translate the blending type name, to the three.js constant value.
return blend
@_material
def bump_map(material):
"""
:param material:
:return: texture node for bump
"""
logger.debug("material.bump_map(%s)", material)
for texture in _valid_textures(material):
if texture.use_map_normal and not \
texture.texture.use_normal_map:
return texture.texture
@_material
def bump_scale(material):
"""
:param material:
:rtype: float
"""
logger.debug("material.bump_scale(%s)", material)
for texture in _valid_textures(material):
if texture.use_map_normal:
return texture.normal_factor
@_material
def depth_test(material):
"""
:param material:
:return: THREE_depth_test value
:rtype: bool
"""
logger.debug("material.depth_test(%s)", material)
try:
test = material.THREE_depth_test
except AttributeError:
logger.debug("No THREE_depth_test attribute found")
test = True
return test
@_material
def depth_write(material):
"""
:param material:
:return: THREE_depth_write value
:rtype: bool
"""
logger.debug("material.depth_write(%s)", material)
try:
write = material.THREE_depth_write
except AttributeError:
logger.debug("No THREE_depth_write attribute found")
write = True
return write
@_material
def double_sided(material):
"""
:param material:
:return: THREE_double_sided value
:rtype: bool
"""
logger.debug("material.double_sided(%s)", material)
try:
write = material.THREE_double_sided
except AttributeError:
logger.debug("No THREE_double_sided attribute found")
write = False
return write
@_material
def diffuse_color(material):
"""
:param material:
:return: rgb value
:rtype: tuple
"""
logger.debug("material.diffuse_color(%s)", material)
return (material.diffuse_intensity * material.diffuse_color[0],
material.diffuse_intensity * material.diffuse_color[1],
material.diffuse_intensity * material.diffuse_color[2])
@_material
def diffuse_map(material):
"""
:param material:
:return: texture node for map
"""
logger.debug("material.diffuse_map(%s)", material)
for texture in _valid_textures(material):
if texture.use_map_color_diffuse and not \
texture.blend_type == MULTIPLY:
return texture.texture
@_material
def emissive_color(material):
"""
:param material:
:return: rgb value
:rtype: tuple
"""
logger.debug("material.emissive_color(%s)", material)
diffuse = diffuse_color(material)
return (material.emit * diffuse[0],
material.emit * diffuse[1],
material.emit * diffuse[2])
@_material
def light_map(material):
"""
:param material:
:return: texture node for light maps
"""
logger.debug("material.light_map(%s)", material)
for texture in _valid_textures(material, strict_use=False):
if texture.use_map_color_diffuse and \
texture.blend_type == MULTIPLY:
return texture.texture
@_material
def normal_scale(material):
"""
:param material:
:rtype: float
"""
logger.debug("material.normal_scale(%s)", material)
for texture in _valid_textures(material):
if texture.use_map_normal:
return (texture.normal_factor, texture.normal_factor)
@_material
def normal_map(material):
"""
:param material:
:return: texture node for normals
"""
logger.debug("material.normal_map(%s)", material)
for texture in _valid_textures(material):
if texture.use_map_normal and \
texture.texture.use_normal_map:
return texture.texture
@_material
def opacity(material):
"""
:param material:
:rtype: float
"""
logger.debug("material.opacity(%s)", material)
return round(material.alpha, 2)
@_material
def shading(material):
"""
:param material:
:return: shading type (phong or lambert)
"""
logger.debug("material.shading(%s)", material)
dispatch = {
True: constants.PHONG,
False: constants.LAMBERT
}
if material.use_shadeless:
return constants.BASIC
return dispatch[material.specular_intensity > 0.0]
@_material
def specular_coef(material):
"""
:param material:
:rtype: float
"""
logger.debug("material.specular_coef(%s)", material)
return material.specular_hardness
@_material
def specular_color(material):
"""
:param material:
:return: rgb value
:rtype: tuple
"""
logger.debug("material.specular_color(%s)", material)
return (material.specular_intensity * material.specular_color[0],
material.specular_intensity * material.specular_color[1],
material.specular_intensity * material.specular_color[2])
@_material
def specular_map(material):
"""
:param material:
:return: texture node for specular
"""
logger.debug("material.specular_map(%s)", material)
for texture in _valid_textures(material):
if texture.use_map_specular:
return texture.texture
@_material
def transparent(material):
"""
:param material:
:rtype: bool
"""
logger.debug("material.transparent(%s)", material)
return material.use_transparency
@_material
def type(material):
"""
:param material:
:return: THREE compatible shader type
"""
logger.debug("material.type(%s)", material)
if material.diffuse_shader != 'LAMBERT':
material_type = constants.BASIC
elif material.specular_intensity > 0:
material_type = constants.PHONG
else:
material_type = constants.LAMBERT
return material_type
@_material
def use_vertex_colors(material):
"""
:param material:
:rtype: bool
"""
logger.debug("material.use_vertex_colors(%s)", material)
return material.use_vertex_color_paint
def used_materials():
"""
:return: list of materials that are in use
:rtype: generator
"""
logger.debug("material.used_materials()")
for material in data.materials:
if material.users > 0:
yield material.name
@_material
def visible(material):
"""
:param material:
:return: THREE_visible value
:rtype: bool
"""
logger.debug("material.visible(%s)", material)
try:
vis = material.THREE_visible
except AttributeError:
logger.debug("No THREE_visible attribute found")
vis = True
return vis
@_material
def wireframe(material):
"""
:param material:
:rtype: bool
"""
logger.debug("material.wireframe(%s)", material)
return material.type == WIRE
def _valid_textures(material, strict_use=True):
"""
:param material:
:rtype: generator
"""
for texture in material.texture_slots:
if not texture:
continue
if strict_use:
in_use = texture.use
else:
in_use = True
if not in_use:
continue
if not texture.texture or texture.texture.type != IMAGE:
logger.warning("Unable to export non-image texture %s", texture)
continue
logger.debug("Valid texture found %s", texture)
yield texture
## mspgack
https://github.com/msgpack/msgpack-python
此差异已折叠。
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册