copied across changes which were missed by merging.
This commit is contained in:
@@ -1,1112 +0,0 @@
|
||||
#!BPY
|
||||
"""
|
||||
Name: 'Cal3D (.cfg .xaf .xsf .xmf .xrf)...'
|
||||
Blender: 243
|
||||
Group: 'Export'
|
||||
Tip: 'Export armature/bone/mesh/action data to the Cal3D format.'
|
||||
"""
|
||||
|
||||
# export_cal3d.py
|
||||
# Copyright (C) 2003-2004 Jean-Baptiste LAMY -- jibalamy@free.fr
|
||||
# Copyright (C) 2004 Matthias Braun -- matze@braunis.de
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
|
||||
__version__ = '0.9f'
|
||||
__author__ = 'Jean-Baptiste, Jiba, Lamy, Campbell Barton (Ideasman42)'
|
||||
__email__ = ['Authors email, jibalamy:free*fr']
|
||||
__url__ = ['Soya3ds homepage, http://home.gna.org/oomadness/en/soya/', 'Cal3d, http://cal3d.sourceforge.net']
|
||||
__bpydoc__ =\
|
||||
'''This script is a Blender => Cal3D converter.
|
||||
(See http://blender.org and http://cal3d.sourceforge.net)
|
||||
|
||||
USAGE:
|
||||
|
||||
To install it, place the script in your $HOME/.blender/scripts directory.
|
||||
|
||||
Then open the File->Export->Cal3d v0.9 menu. And select the filename of the .cfg file.
|
||||
The exporter will create a set of other files with same prefix (ie. bla.cfg, bla.xsf,
|
||||
bla_Action1.xaf, bla_Action2.xaf, ...).
|
||||
|
||||
You should be able to open the .cfg file in cal3d_miniviewer.
|
||||
|
||||
|
||||
NOT (YET) SUPPORTED:
|
||||
|
||||
- Rotation, translation, or stretching Blender objects is still quite
|
||||
buggy, so AVOID MOVING / ROTATING / RESIZE OBJECTS (either mesh or armature) !
|
||||
Instead, edit the object (with tab), select all points / bones (with "a"),
|
||||
and move / rotate / resize them.<br>
|
||||
- no support for exporting springs yet<br>
|
||||
- no support for exporting material colors (most games should only use images
|
||||
I think...)
|
||||
|
||||
|
||||
KNOWN ISSUES:
|
||||
|
||||
- Cal3D versions <=0.9.1 have a bug where animations aren't played when the root bone
|
||||
is not animated;<br>
|
||||
- Cal3D versions <=0.9.1 have a bug where objects that aren't influenced by any bones
|
||||
are not drawn (fixed in Cal3D CVS).
|
||||
|
||||
|
||||
NOTES:
|
||||
|
||||
It requires a very recent version of Blender (>= 2.44).
|
||||
|
||||
Build a model following a few rules:<br>
|
||||
- Use only a single armature;<br>
|
||||
- Use only a single rootbone (Cal3D doesn't support floating bones);<br>
|
||||
- Use only locrot keys (Cal3D doesn't support bone's size change);<br>
|
||||
- Don't try to create child/parent constructs in blender object, that gets exported
|
||||
incorrectly at the moment;<br>
|
||||
- Objects or animations whose names start by "_" are not exported (hidden object).
|
||||
|
||||
You can pass as many parameters as you want at the end, "EXPORT_FOR_SOYA=1" is just an
|
||||
example. The parameters are the same as below.
|
||||
'''
|
||||
|
||||
# True (=1) to export for the Soya 3D engine
|
||||
# (http://oomadness.tuxfamily.org/en/soya).
|
||||
# (=> rotate meshes and skeletons so as X is right, Y is top and -Z is front)
|
||||
# EXPORT_FOR_SOYA = 0
|
||||
|
||||
# Enables LODs computation. LODs computation is quite slow, and the algo is
|
||||
# surely not optimal :-(
|
||||
LODS = 0
|
||||
|
||||
# Scale the model (not supported by Soya).
|
||||
|
||||
# See also BASE_MATRIX below, if you want to rotate/scale/translate the model at
|
||||
# the exportation.
|
||||
|
||||
#########################################################################################
|
||||
# Code starts here.
|
||||
# The script should be quite re-useable for writing another Blender animation exporter.
|
||||
# Most of the hell of it is to deal with Blender's head-tail-roll bone's definition.
|
||||
|
||||
import math
|
||||
import Blender
|
||||
import BPyMesh
|
||||
import BPySys
|
||||
import BPyArmature
|
||||
import BPyObject
|
||||
import bpy
|
||||
|
||||
def best_armature_root(armature):
|
||||
'''
|
||||
Find the armature root bone with the most children, return that bone
|
||||
'''
|
||||
|
||||
bones = [bone for bone in armature.bones.values() if bone.hasChildren() == True]
|
||||
if len(bones) == 1:
|
||||
return bones[0]
|
||||
|
||||
# Get the best root since we have more then 1
|
||||
bones = [(len(bone.getAllChildren()), bone) for bone in bones]
|
||||
bones.sort()
|
||||
return bones[-1][1] # bone with most children
|
||||
|
||||
|
||||
Vector = Blender.Mathutils.Vector
|
||||
Quaternion = Blender.Mathutils.Quaternion
|
||||
Matrix = Blender.Mathutils.Matrix
|
||||
|
||||
# HACK -- it seems that some Blender versions don't define sys.argv,
|
||||
# which may crash Python if a warning occurs.
|
||||
# if not hasattr(sys, 'argv'): sys.argv = ['???']
|
||||
|
||||
def matrix_multiply(b, a):
|
||||
return [ [
|
||||
a[0][0] * b[0][0] + a[0][1] * b[1][0] + a[0][2] * b[2][0],
|
||||
a[0][0] * b[0][1] + a[0][1] * b[1][1] + a[0][2] * b[2][1],
|
||||
a[0][0] * b[0][2] + a[0][1] * b[1][2] + a[0][2] * b[2][2],
|
||||
0.0,
|
||||
], [
|
||||
a[1][0] * b[0][0] + a[1][1] * b[1][0] + a[1][2] * b[2][0],
|
||||
a[1][0] * b[0][1] + a[1][1] * b[1][1] + a[1][2] * b[2][1],
|
||||
a[1][0] * b[0][2] + a[1][1] * b[1][2] + a[1][2] * b[2][2],
|
||||
0.0,
|
||||
], [
|
||||
a[2][0] * b[0][0] + a[2][1] * b[1][0] + a[2][2] * b[2][0],
|
||||
a[2][0] * b[0][1] + a[2][1] * b[1][1] + a[2][2] * b[2][1],
|
||||
a[2][0] * b[0][2] + a[2][1] * b[1][2] + a[2][2] * b[2][2],
|
||||
0.0,
|
||||
], [
|
||||
a[3][0] * b[0][0] + a[3][1] * b[1][0] + a[3][2] * b[2][0] + b[3][0],
|
||||
a[3][0] * b[0][1] + a[3][1] * b[1][1] + a[3][2] * b[2][1] + b[3][1],
|
||||
a[3][0] * b[0][2] + a[3][1] * b[1][2] + a[3][2] * b[2][2] + b[3][2],
|
||||
1.0,
|
||||
] ]
|
||||
|
||||
# multiplies 2 quaternions in x,y,z,w notation
|
||||
def quaternion_multiply(q1, q2):
|
||||
return Quaternion(\
|
||||
q2[3] * q1[0] + q2[0] * q1[3] + q2[1] * q1[2] - q2[2] * q1[1],
|
||||
q2[3] * q1[1] + q2[1] * q1[3] + q2[2] * q1[0] - q2[0] * q1[2],
|
||||
q2[3] * q1[2] + q2[2] * q1[3] + q2[0] * q1[1] - q2[1] * q1[0],
|
||||
q2[3] * q1[3] - q2[0] * q1[0] - q2[1] * q1[1] - q2[2] * q1[2],\
|
||||
)
|
||||
|
||||
def matrix_translate(m, v):
|
||||
m[3][0] += v[0]
|
||||
m[3][1] += v[1]
|
||||
m[3][2] += v[2]
|
||||
return m
|
||||
|
||||
def matrix2quaternion(m):
|
||||
s = math.sqrt(abs(m[0][0] + m[1][1] + m[2][2] + m[3][3]))
|
||||
if s == 0.0:
|
||||
x = abs(m[2][1] - m[1][2])
|
||||
y = abs(m[0][2] - m[2][0])
|
||||
z = abs(m[1][0] - m[0][1])
|
||||
if (x >= y) and (x >= z): return Quaternion(1.0, 0.0, 0.0, 0.0)
|
||||
elif (y >= x) and (y >= z): return Quaternion(0.0, 1.0, 0.0, 0.0)
|
||||
else: return Quaternion(0.0, 0.0, 1.0, 0.0)
|
||||
|
||||
q = Quaternion([
|
||||
-(m[2][1] - m[1][2]) / (2.0 * s),
|
||||
-(m[0][2] - m[2][0]) / (2.0 * s),
|
||||
-(m[1][0] - m[0][1]) / (2.0 * s),
|
||||
0.5 * s,
|
||||
])
|
||||
q.normalize()
|
||||
#print q
|
||||
return q
|
||||
|
||||
def vector_by_matrix_3x3(p, m):
|
||||
return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0],
|
||||
p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1],
|
||||
p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2]]
|
||||
|
||||
def vector_add(v1, v2):
|
||||
return [v1[0]+v2[0], v1[1]+v2[1], v1[2]+v2[2]]
|
||||
|
||||
def vector_sub(v1, v2):
|
||||
return [v1[0]-v2[0], v1[1]-v2[1], v1[2]-v2[2]]
|
||||
|
||||
def quaternion2matrix(q):
|
||||
xx = q[0] * q[0]
|
||||
yy = q[1] * q[1]
|
||||
zz = q[2] * q[2]
|
||||
xy = q[0] * q[1]
|
||||
xz = q[0] * q[2]
|
||||
yz = q[1] * q[2]
|
||||
wx = q[3] * q[0]
|
||||
wy = q[3] * q[1]
|
||||
wz = q[3] * q[2]
|
||||
return Matrix([1.0 - 2.0 * (yy + zz), 2.0 * (xy + wz), 2.0 * (xz - wy), 0.0],
|
||||
[ 2.0 * (xy - wz), 1.0 - 2.0 * (xx + zz), 2.0 * (yz + wx), 0.0],
|
||||
[ 2.0 * (xz + wy), 2.0 * (yz - wx), 1.0 - 2.0 * (xx + yy), 0.0],
|
||||
[0.0 , 0.0 , 0.0 , 1.0])
|
||||
|
||||
def matrix_invert(m):
|
||||
det = (m[0][0] * (m[1][1] * m[2][2] - m[2][1] * m[1][2])
|
||||
- m[1][0] * (m[0][1] * m[2][2] - m[2][1] * m[0][2])
|
||||
+ m[2][0] * (m[0][1] * m[1][2] - m[1][1] * m[0][2]))
|
||||
if det == 0.0: return None
|
||||
det = 1.0 / det
|
||||
r = [ [
|
||||
det * (m[1][1] * m[2][2] - m[2][1] * m[1][2]),
|
||||
- det * (m[0][1] * m[2][2] - m[2][1] * m[0][2]),
|
||||
det * (m[0][1] * m[1][2] - m[1][1] * m[0][2]),
|
||||
0.0,
|
||||
], [
|
||||
- det * (m[1][0] * m[2][2] - m[2][0] * m[1][2]),
|
||||
det * (m[0][0] * m[2][2] - m[2][0] * m[0][2]),
|
||||
- det * (m[0][0] * m[1][2] - m[1][0] * m[0][2]),
|
||||
0.0
|
||||
], [
|
||||
det * (m[1][0] * m[2][1] - m[2][0] * m[1][1]),
|
||||
- det * (m[0][0] * m[2][1] - m[2][0] * m[0][1]),
|
||||
det * (m[0][0] * m[1][1] - m[1][0] * m[0][1]),
|
||||
0.0,
|
||||
] ]
|
||||
r.append([
|
||||
-(m[3][0] * r[0][0] + m[3][1] * r[1][0] + m[3][2] * r[2][0]),
|
||||
-(m[3][0] * r[0][1] + m[3][1] * r[1][1] + m[3][2] * r[2][1]),
|
||||
-(m[3][0] * r[0][2] + m[3][1] * r[1][2] + m[3][2] * r[2][2]),
|
||||
1.0,
|
||||
])
|
||||
return r
|
||||
|
||||
|
||||
def point_by_matrix(p, m):
|
||||
return [p[0] * m[0][0] + p[1] * m[1][0] + p[2] * m[2][0] + m[3][0],
|
||||
p[0] * m[0][1] + p[1] * m[1][1] + p[2] * m[2][1] + m[3][1],
|
||||
p[0] * m[0][2] + p[1] * m[1][2] + p[2] * m[2][2] + m[3][2]]
|
||||
|
||||
# Hack for having the model rotated right.
|
||||
# Put in BASE_MATRIX your own rotation if you need some.
|
||||
|
||||
BASE_MATRIX = None
|
||||
|
||||
|
||||
# Cal3D data structures
|
||||
|
||||
CAL3D_VERSION = 910
|
||||
MATERIALS = {} # keys are (mat.name, img.name)
|
||||
|
||||
class Cal3DMaterial(object):
|
||||
__slots__ = 'amb', 'diff', 'spec', 'shininess', 'maps_filenames', 'id'
|
||||
def __init__(self, blend_world, blend_material, blend_images):
|
||||
|
||||
# Material Settings
|
||||
if blend_world: amb = [ int(c*255) for c in blend_world.amb ]
|
||||
else: amb = [0,0,0] # Default value
|
||||
|
||||
if blend_material:
|
||||
self.amb = tuple([int(c*blend_material.amb) for c in amb] + [255])
|
||||
self.diff = tuple([int(c*255) for c in blend_material.rgbCol] + [int(blend_material.alpha*255)])
|
||||
self.spec = tuple([int(c*255) for c in blend_material.rgbCol] + [int(blend_material.alpha*255)])
|
||||
self.shininess = (float(blend_material.hard)-1)/5.10
|
||||
else:
|
||||
self.amb = tuple(amb + [255])
|
||||
self.diff = (255,255,255,255)
|
||||
self.spec = (255,255,255,255)
|
||||
self.shininess = 1.0
|
||||
|
||||
self.maps_filenames = []
|
||||
for image in blend_images:
|
||||
if image:
|
||||
self.maps_filenames.append( image.filename.split('\\')[-1].split('/')[-1] )
|
||||
|
||||
self.id = len(MATERIALS)
|
||||
MATERIALS[blend_material, blend_images] = self
|
||||
|
||||
# new xml format
|
||||
def writeCal3D(self, file):
|
||||
file.write('<?xml version="1.0"?>\n')
|
||||
file.write('<HEADER MAGIC="XRF" VERSION="%i"/>\n' % CAL3D_VERSION)
|
||||
file.write('<MATERIAL NUMMAPS="%s">\n' % len(self.maps_filenames))
|
||||
file.write('\t<AMBIENT>%i %i %i %i</AMBIENT>\n' % self.amb)
|
||||
file.write('\t<DIFFUSE>%i %i %i %i</DIFFUSE>\n' % self.diff)
|
||||
file.write('\t<SPECULAR>%i %i %i %i</SPECULAR>\n' % self.spec)
|
||||
file.write('\t<SHININESS>%.6f</SHININESS>\n' % self.shininess)
|
||||
|
||||
for map_filename in self.maps_filenames:
|
||||
file.write('\t<MAP>%s</MAP>\n' % map_filename)
|
||||
|
||||
file.write('</MATERIAL>\n')
|
||||
|
||||
|
||||
class Cal3DMesh(object):
|
||||
__slots__ = 'name', 'submeshes', 'matrix', 'matrix_normal'
|
||||
def __init__(self, ob, blend_mesh, blend_world):
|
||||
self.name = ob.name
|
||||
self.submeshes = []
|
||||
|
||||
BPyMesh.meshCalcNormals(blend_mesh)
|
||||
|
||||
self.matrix = ob.matrixWorld
|
||||
self.matrix_normal = self.matrix.copy().rotationPart()
|
||||
|
||||
#if BASE_MATRIX:
|
||||
# matrix = matrix_multiply(BASE_MATRIX, matrix)
|
||||
|
||||
face_groups = {}
|
||||
blend_materials = blend_mesh.materials
|
||||
uvlayers = ()
|
||||
mat = None # incase we have no materials
|
||||
if blend_mesh.faceUV:
|
||||
uvlayers = blend_mesh.getUVLayerNames()
|
||||
if len(uvlayers) == 1:
|
||||
for f in blend_mesh.faces:
|
||||
image = (f.image,) # bit in a tuple so we can match multi UV code
|
||||
if blend_materials: mat = blend_materials[f.mat] # if no materials, mat will always be None
|
||||
face_groups.setdefault( (mat,image), (mat,image,[]) )[2].append( f )
|
||||
else:
|
||||
# Multi UV's
|
||||
face_multi_images = [[] for i in xrange(len(blend_mesh.faces))]
|
||||
face_multi_uvs = [[[] for i in xrange(len(f)) ] for f in blend_mesh.faces]
|
||||
for uvlayer in uvlayers:
|
||||
blend_mesh.activeUVLayer = uvlayer
|
||||
for i, f in enumerate(blend_mesh.faces):
|
||||
face_multi_images[i].append(f.image)
|
||||
if f.image:
|
||||
for j, uv in enumerate(f.uv):
|
||||
face_multi_uvs[i][j].append( tuple(uv) )
|
||||
|
||||
# Convert UV's to tuples so they can be compared with eachother
|
||||
# when creating new verts
|
||||
for fuv in face_multi_uvs:
|
||||
for i, uv in enumerate(fuv):
|
||||
fuv[i] = tuple(uv)
|
||||
|
||||
for i, f in enumerate(blend_mesh.faces):
|
||||
image = tuple(face_multi_images[i])
|
||||
if blend_materials: mat = blend_materials[f.mat]
|
||||
face_groups.setdefault( (mat,image), (mat,image,[]) )[2].append( f )
|
||||
else:
|
||||
# No UV's
|
||||
for f in blend_mesh.faces:
|
||||
if blend_materials: mat = blend_materials[f.mat]
|
||||
face_groups.setdefault( (mat,()), (mat,(),[]) )[2].append( f )
|
||||
|
||||
for blend_material, blend_images, faces in face_groups.itervalues():
|
||||
|
||||
try: material = MATERIALS[blend_material, blend_images]
|
||||
except: material = MATERIALS[blend_material, blend_images] = Cal3DMaterial(blend_world, blend_material, blend_images)
|
||||
|
||||
submesh = Cal3DSubMesh(self, material, len(self.submeshes))
|
||||
self.submeshes.append(submesh)
|
||||
|
||||
# Check weather we need to write UVs, dont do it if theres no image
|
||||
# Multilayer UV's have alredy checked that they have images when
|
||||
# building face_multi_uvs
|
||||
if len(uvlayers) == 1:
|
||||
if blend_images == (None,):
|
||||
write_single_layer_uvs = False
|
||||
else:
|
||||
write_single_layer_uvs = True
|
||||
|
||||
|
||||
for face in faces:
|
||||
|
||||
if not face.smooth:
|
||||
normal = face.no
|
||||
|
||||
face_vertices = []
|
||||
face_v = face.v
|
||||
|
||||
|
||||
if len(uvlayers)>1:
|
||||
for i, blend_vert in enumerate(face_v):
|
||||
if face.smooth: normal = blend_vert.no
|
||||
vertex = submesh.getVertex(blend_mesh, blend_vert, normal, face_multi_uvs[face.index][i])
|
||||
face_vertices.append(vertex)
|
||||
|
||||
elif len(uvlayers)==1:
|
||||
if write_single_layer_uvs:
|
||||
face_uv = face.uv
|
||||
|
||||
for i, blend_vert in enumerate(face_v):
|
||||
if face.smooth: normal = blend_vert.no
|
||||
if write_single_layer_uvs: uvs = (tuple(face_uv[i]),)
|
||||
else: uvs = ()
|
||||
|
||||
vertex = submesh.getVertex(blend_mesh, blend_vert, normal, uvs )
|
||||
face_vertices.append(vertex)
|
||||
else:
|
||||
# No UVs
|
||||
for i, blend_vert in enumerate(face_v):
|
||||
if face.smooth: normal = blend_vert.no
|
||||
vertex = submesh.getVertex(blend_mesh, blend_vert, normal, () )
|
||||
face_vertices.append(vertex)
|
||||
|
||||
|
||||
# Split faces with more than 3 vertices
|
||||
for i in xrange(1, len(face) - 1):
|
||||
submesh.faces.append(Cal3DFace(face_vertices[0], face_vertices[i], face_vertices[i + 1]))
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('<?xml version="1.0"?>\n')
|
||||
file.write('<HEADER MAGIC="XMF" VERSION="%i"/>\n' % CAL3D_VERSION)
|
||||
file.write('<MESH NUMSUBMESH="%i">\n' % len(self.submeshes))
|
||||
for submesh in self.submeshes:
|
||||
submesh.writeCal3D(file, self.matrix, self.matrix_normal)
|
||||
file.write('</MESH>\n')
|
||||
|
||||
|
||||
class Cal3DSubMesh(object):
|
||||
__slots__ = 'material', 'vertices', 'vert_mapping', 'vert_count', 'faces', 'nb_lodsteps', 'springs', 'id'
|
||||
def __init__(self, mesh, material, id):
|
||||
self.material = material
|
||||
self.vertices = []
|
||||
self.vert_mapping = {} # map original indicies to local
|
||||
self.vert_count = 0
|
||||
self.faces = []
|
||||
self.nb_lodsteps = 0
|
||||
self.springs = []
|
||||
self.id = id
|
||||
|
||||
def getVertex(self, blend_mesh, blend_vert, normal, maps):
|
||||
'''
|
||||
Request a vertex, and create a new one or return a matching vertex
|
||||
'''
|
||||
blend_index = blend_vert.index
|
||||
index_map = self.vert_mapping.get(blend_index)
|
||||
|
||||
if index_map == None:
|
||||
vertex = Cal3DVertex(blend_vert.co, normal, maps, blend_mesh.getVertexInfluences(blend_index))
|
||||
self.vertices.append([vertex])
|
||||
self.vert_mapping[blend_index] = len(self.vert_mapping)
|
||||
self.vert_count +=1
|
||||
return vertex
|
||||
else:
|
||||
vertex_list = self.vertices[index_map]
|
||||
|
||||
for v in vertex_list:
|
||||
if v.normal == normal and\
|
||||
v.maps == maps:
|
||||
return v # reusing
|
||||
|
||||
# No match, add a new vert
|
||||
# Use the first verts influences
|
||||
vertex = Cal3DVertex(blend_vert.co, normal, maps, vertex_list[0].influences)
|
||||
vertex_list.append(vertex)
|
||||
# self.vert_mapping[blend_index] = len(self.vert_mapping)
|
||||
self.vert_count +=1
|
||||
return vertex
|
||||
|
||||
|
||||
def compute_lods(self):
|
||||
'''Computes LODs info for Cal3D (there's no Blender related stuff here).'''
|
||||
|
||||
print 'Start LODs computation...'
|
||||
vertex2faces = {}
|
||||
for face in self.faces:
|
||||
for vertex in (face.vertex1, face.vertex2, face.vertex3):
|
||||
l = vertex2faces.get(vertex)
|
||||
if not l: vertex2faces[vertex] = [face]
|
||||
else: l.append(face)
|
||||
|
||||
couple_treated = {}
|
||||
couple_collapse_factor = []
|
||||
for face in self.faces:
|
||||
for a, b in ((face.vertex1, face.vertex2), (face.vertex1, face.vertex3), (face.vertex2, face.vertex3)):
|
||||
a = a.cloned_from or a
|
||||
b = b.cloned_from or b
|
||||
if a.id > b.id: a, b = b, a
|
||||
if not couple_treated.has_key((a, b)):
|
||||
# The collapse factor is simply the distance between the 2 points :-(
|
||||
# This should be improved !!
|
||||
if vector_dotproduct(a.normal, b.normal) < 0.9: continue
|
||||
couple_collapse_factor.append((point_distance(a.loc, b.loc), a, b))
|
||||
couple_treated[a, b] = 1
|
||||
|
||||
couple_collapse_factor.sort()
|
||||
|
||||
collapsed = {}
|
||||
new_vertices = []
|
||||
new_faces = []
|
||||
for factor, v1, v2 in couple_collapse_factor:
|
||||
# Determines if v1 collapses to v2 or v2 to v1.
|
||||
# We choose to keep the vertex which is on the smaller number of faces, since
|
||||
# this one has more chance of being in an extrimity of the body.
|
||||
# Though heuristic, this rule yields very good results in practice.
|
||||
if len(vertex2faces[v1]) < len(vertex2faces[v2]): v2, v1 = v1, v2
|
||||
elif len(vertex2faces[v1]) == len(vertex2faces[v2]):
|
||||
if collapsed.get(v1, 0): v2, v1 = v1, v2 # v1 already collapsed, try v2
|
||||
|
||||
if (not collapsed.get(v1, 0)) and (not collapsed.get(v2, 0)):
|
||||
collapsed[v1] = 1
|
||||
collapsed[v2] = 1
|
||||
|
||||
# Check if v2 is already colapsed
|
||||
while v2.collapse_to: v2 = v2.collapse_to
|
||||
|
||||
common_faces = filter(vertex2faces[v1].__contains__, vertex2faces[v2])
|
||||
|
||||
v1.collapse_to = v2
|
||||
v1.face_collapse_count = len(common_faces)
|
||||
|
||||
for clone in v1.clones:
|
||||
# Find the clone of v2 that correspond to this clone of v1
|
||||
possibles = []
|
||||
for face in vertex2faces[clone]:
|
||||
possibles.append(face.vertex1)
|
||||
possibles.append(face.vertex2)
|
||||
possibles.append(face.vertex3)
|
||||
clone.collapse_to = v2
|
||||
for vertex in v2.clones:
|
||||
if vertex in possibles:
|
||||
clone.collapse_to = vertex
|
||||
break
|
||||
|
||||
clone.face_collapse_count = 0
|
||||
new_vertices.append(clone)
|
||||
|
||||
# HACK -- all faces get collapsed with v1 (and no faces are collapsed with v1's
|
||||
# clones). This is why we add v1 in new_vertices after v1's clones.
|
||||
# This hack has no other incidence that consuming a little few memory for the
|
||||
# extra faces if some v1's clone are collapsed but v1 is not.
|
||||
new_vertices.append(v1)
|
||||
|
||||
self.nb_lodsteps += 1 + len(v1.clones)
|
||||
|
||||
new_faces.extend(common_faces)
|
||||
for face in common_faces:
|
||||
face.can_collapse = 1
|
||||
|
||||
# Updates vertex2faces
|
||||
vertex2faces[face.vertex1].remove(face)
|
||||
vertex2faces[face.vertex2].remove(face)
|
||||
vertex2faces[face.vertex3].remove(face)
|
||||
vertex2faces[v2].extend(vertex2faces[v1])
|
||||
|
||||
new_vertices.extend(filter(lambda vertex: not vertex.collapse_to, self.vertices))
|
||||
new_vertices.reverse() # Cal3D want LODed vertices at the end
|
||||
for i in xrange(len(new_vertices)): new_vertices[i].id = i
|
||||
self.vertices = new_vertices
|
||||
|
||||
new_faces.extend(filter(lambda face: not face.can_collapse, self.faces))
|
||||
new_faces.reverse() # Cal3D want LODed faces at the end
|
||||
self.faces = new_faces
|
||||
|
||||
print 'LODs computed : %s vertices can be removed (from a total of %s).' % (self.nb_lodsteps, len(self.vertices))
|
||||
|
||||
|
||||
def writeCal3D(self, file, matrix, matrix_normal):
|
||||
|
||||
file.write('\t<SUBMESH NUMVERTICES="%i" NUMFACES="%i" MATERIAL="%i" ' % \
|
||||
(self.vert_count, len(self.faces), self.material.id))
|
||||
file.write('NUMLODSTEPS="%i" NUMSPRINGS="%i" NUMTEXCOORDS="%i">\n' % \
|
||||
(self.nb_lodsteps, len(self.springs),
|
||||
len(self.material.maps_filenames)))
|
||||
|
||||
i = 0
|
||||
for v in self.vertices:
|
||||
for item in v:
|
||||
item.id = i
|
||||
item.writeCal3D(file, matrix, matrix_normal)
|
||||
i += 1
|
||||
|
||||
for item in self.springs:
|
||||
item.writeCal3D(file)
|
||||
for item in self.faces:
|
||||
item.writeCal3D(file)
|
||||
|
||||
file.write('\t</SUBMESH>\n')
|
||||
|
||||
class Cal3DVertex(object):
|
||||
__slots__ = 'loc','normal','collapse_to','face_collapse_count','maps','influences','weight','cloned_from','clones','id'
|
||||
def __init__(self, loc, normal, maps, blend_influences):
|
||||
self.loc = loc
|
||||
self.normal = normal
|
||||
self.collapse_to = None
|
||||
self.face_collapse_count = 0
|
||||
self.maps = maps
|
||||
self.weight = None
|
||||
|
||||
self.cloned_from = None
|
||||
self.clones = []
|
||||
|
||||
self.id = -1
|
||||
|
||||
if len(blend_influences) == 0 or isinstance(blend_influences[0], Cal3DInfluence):
|
||||
# This is a copy from another vert
|
||||
self.influences = blend_influences
|
||||
else:
|
||||
# Pass the blender influences
|
||||
|
||||
self.influences = []
|
||||
# should this really be a warning? (well currently enabled,
|
||||
# because blender has some bugs where it doesn't return
|
||||
# influences in python api though they are set, and because
|
||||
# cal3d<=0.9.1 had bugs where objects without influences
|
||||
# aren't drawn.
|
||||
#if not blend_influences:
|
||||
# print 'A vertex of object "%s" has no influences.\n(This occurs on objects placed in an invisible layer, you can fix it by using a single layer)' % ob.name
|
||||
|
||||
# sum of influences is not always 1.0 in Blender ?!?!
|
||||
sum = 0.0
|
||||
|
||||
for bone_name, weight in blend_influences:
|
||||
sum += weight
|
||||
|
||||
for bone_name, weight in blend_influences:
|
||||
bone = BONES.get(bone_name)
|
||||
if not bone: # keys
|
||||
# print 'Couldnt find bone "%s" which influences object "%s"' % (bone_name, ob.name)
|
||||
continue
|
||||
|
||||
if weight:
|
||||
self.influences.append(Cal3DInfluence(bone, weight / sum))
|
||||
|
||||
|
||||
def writeCal3D(self, file, matrix, matrix_normal):
|
||||
if self.collapse_to:
|
||||
collapse_id = self.collapse_to.id
|
||||
else:
|
||||
collapse_id = -1
|
||||
file.write('\t\t<VERTEX ID="%i" NUMINFLUENCES="%i">\n' % \
|
||||
(self.id, len(self.influences)))
|
||||
file.write('\t\t\t<POS>%.6f %.6f %.6f</POS>\n' % tuple(self.loc*matrix))
|
||||
file.write('\t\t\t<NORM>%.6f %.6f %.6f</NORM>\n' % tuple( (self.normal*matrix_normal).normalize() ))
|
||||
if collapse_id != -1:
|
||||
file.write('\t\t\t<COLLAPSEID>%i</COLLAPSEID>\n' % collapse_id)
|
||||
file.write('\t\t\t<COLLAPSECOUNT>%i</COLLAPSECOUNT>\n' % \
|
||||
self.face_collapse_count)
|
||||
|
||||
for uv in self.maps:
|
||||
# we cant have more UV's then our materials image maps
|
||||
# check for this
|
||||
file.write('\t\t\t<TEXCOORD>%.6f %.6f</TEXCOORD>\n' % uv)
|
||||
|
||||
for item in self.influences:
|
||||
item.writeCal3D(file)
|
||||
|
||||
if self.weight != None:
|
||||
file.write('\t\t\t<PHYSIQUE>%.6f</PHYSIQUE>\n' % len(self.weight))
|
||||
file.write('\t\t</VERTEX>\n')
|
||||
|
||||
class Cal3DInfluence(object):
|
||||
__slots__ = 'bone', 'weight'
|
||||
def __init__(self, bone, weight):
|
||||
self.bone = bone
|
||||
self.weight = weight
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('\t\t\t<INFLUENCE ID="%i">%.6f</INFLUENCE>\n' % \
|
||||
(self.bone.id, self.weight))
|
||||
|
||||
class Cal3DSpring(object):
|
||||
__slots__ = 'vertex1', 'vertex2', 'spring_coefficient', 'idlelength'
|
||||
def __init__(self, vertex1, vertex2):
|
||||
self.vertex1 = vertex1
|
||||
self.vertex2 = vertex2
|
||||
self.spring_coefficient = 0.0
|
||||
self.idlelength = 0.0
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('\t\t<SPRING VERTEXID="%i %i" COEF="%.6f" LENGTH="%.6f"/>\n' % \
|
||||
(self.vertex1.id, self.vertex2.id, self.spring_coefficient, self.idlelength))
|
||||
|
||||
class Cal3DFace(object):
|
||||
__slots__ = 'vertex1', 'vertex2', 'vertex3', 'can_collapse',
|
||||
def __init__(self, vertex1, vertex2, vertex3):
|
||||
self.vertex1 = vertex1
|
||||
self.vertex2 = vertex2
|
||||
self.vertex3 = vertex3
|
||||
self.can_collapse = 0
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('\t\t<FACE VERTEXID="%i %i %i"/>\n' % \
|
||||
(self.vertex1.id, self.vertex2.id, self.vertex3.id))
|
||||
|
||||
class Cal3DSkeleton(object):
|
||||
__slots__ = 'bones'
|
||||
def __init__(self):
|
||||
self.bones = []
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('<?xml version="1.0"?>\n')
|
||||
file.write('<HEADER MAGIC="XSF" VERSION="%i"/>\n' % CAL3D_VERSION)
|
||||
file.write('<SKELETON NUMBONES="%i">\n' % len(self.bones))
|
||||
for item in self.bones:
|
||||
item.writeCal3D(file)
|
||||
|
||||
file.write('</SKELETON>\n')
|
||||
|
||||
BONES = {}
|
||||
POSEBONES= {}
|
||||
class Cal3DBone(object):
|
||||
__slots__ = 'head', 'tail', 'name', 'cal3d_parent', 'loc', 'quat', 'children', 'matrix', 'lloc', 'lquat', 'id'
|
||||
def __init__(self, skeleton, blend_bone, arm_matrix, cal3d_parent=None):
|
||||
|
||||
# def treat_bone(b, parent = None):
|
||||
head = blend_bone.head['BONESPACE']
|
||||
tail = blend_bone.tail['BONESPACE']
|
||||
#print parent.quat
|
||||
# Turns the Blender's head-tail-roll notation into a quaternion
|
||||
#quat = matrix2quaternion(blender_bone2matrix(head, tail, blend_bone.roll['BONESPACE']))
|
||||
quat = matrix2quaternion(blend_bone.matrix['BONESPACE'].copy().resize4x4())
|
||||
|
||||
# Pose location
|
||||
ploc = POSEBONES[blend_bone.name].loc
|
||||
|
||||
if cal3d_parent:
|
||||
# Compute the translation from the parent bone's head to the child
|
||||
# bone's head, in the parent bone coordinate system.
|
||||
# The translation is parent_tail - parent_head + child_head,
|
||||
# but parent_tail and parent_head must be converted from the parent's parent
|
||||
# system coordinate into the parent system coordinate.
|
||||
|
||||
parent_invert_transform = matrix_invert(quaternion2matrix(cal3d_parent.quat))
|
||||
parent_head = vector_by_matrix_3x3(cal3d_parent.head, parent_invert_transform)
|
||||
parent_tail = vector_by_matrix_3x3(cal3d_parent.tail, parent_invert_transform)
|
||||
ploc = vector_add(ploc, blend_bone.head['BONESPACE'])
|
||||
|
||||
# EDIT!!! FIX BONE OFFSET BE CAREFULL OF THIS PART!!! ??
|
||||
#diff = vector_by_matrix_3x3(head, parent_invert_transform)
|
||||
parent_tail= vector_add(parent_tail, head)
|
||||
# DONE!!!
|
||||
|
||||
parentheadtotail = vector_sub(parent_tail, parent_head)
|
||||
# hmm this should be handled by the IPos, but isn't for non-animated
|
||||
# bones which are transformed in the pose mode...
|
||||
loc = parentheadtotail
|
||||
|
||||
else:
|
||||
# Apply the armature's matrix to the root bones
|
||||
head = point_by_matrix(head, arm_matrix)
|
||||
tail = point_by_matrix(tail, arm_matrix)
|
||||
|
||||
loc = head
|
||||
quat = matrix2quaternion(matrix_multiply(arm_matrix, quaternion2matrix(quat))) # Probably not optimal
|
||||
|
||||
self.head = head
|
||||
self.tail = tail
|
||||
|
||||
self.cal3d_parent = cal3d_parent
|
||||
self.name = blend_bone.name
|
||||
self.loc = loc
|
||||
self.quat = quat
|
||||
self.children = []
|
||||
|
||||
self.matrix = matrix_translate(quaternion2matrix(quat), loc)
|
||||
if cal3d_parent:
|
||||
self.matrix = matrix_multiply(cal3d_parent.matrix, self.matrix)
|
||||
|
||||
# lloc and lquat are the bone => model space transformation (translation and rotation).
|
||||
# They are probably specific to Cal3D.
|
||||
m = matrix_invert(self.matrix)
|
||||
self.lloc = m[3][0], m[3][1], m[3][2]
|
||||
self.lquat = matrix2quaternion(m)
|
||||
|
||||
self.id = len(skeleton.bones)
|
||||
skeleton.bones.append(self)
|
||||
BONES[self.name] = self
|
||||
|
||||
if not blend_bone.hasChildren(): return
|
||||
for blend_child in blend_bone.children:
|
||||
self.children.append(Cal3DBone(skeleton, blend_child, arm_matrix, self))
|
||||
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('\t<BONE ID="%i" NAME="%s" NUMCHILD="%i">\n' % \
|
||||
(self.id, self.name, len(self.children)))
|
||||
# We need to negate quaternion W value, but why ?
|
||||
file.write('\t\t<TRANSLATION>%.6f %.6f %.6f</TRANSLATION>\n' % \
|
||||
(self.loc[0], self.loc[1], self.loc[2]))
|
||||
file.write('\t\t<ROTATION>%.6f %.6f %.6f %.6f</ROTATION>\n' % \
|
||||
(self.quat[0], self.quat[1], self.quat[2], -self.quat[3]))
|
||||
file.write('\t\t<LOCALTRANSLATION>%.6f %.6f %.6f</LOCALTRANSLATION>\n' % \
|
||||
(self.lloc[0], self.lloc[1], self.lloc[2]))
|
||||
file.write('\t\t<LOCALROTATION>%.6f %.6f %.6f %.6f</LOCALROTATION>\n' % \
|
||||
(self.lquat[0], self.lquat[1], self.lquat[2], -self.lquat[3]))
|
||||
if self.cal3d_parent:
|
||||
file.write('\t\t<PARENTID>%i</PARENTID>\n' % self.cal3d_parent.id)
|
||||
else:
|
||||
file.write('\t\t<PARENTID>%i</PARENTID>\n' % -1)
|
||||
|
||||
for item in self.children:
|
||||
file.write('\t\t<CHILDID>%i</CHILDID>\n' % item.id)
|
||||
|
||||
file.write('\t</BONE>\n')
|
||||
|
||||
class Cal3DAnimation:
|
||||
def __init__(self, name, duration = 0.0):
|
||||
self.name = name
|
||||
self.duration = duration
|
||||
self.tracks = {} # Map bone names to tracks
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('<?xml version="1.0"?>\n')
|
||||
file.write('<HEADER MAGIC="XAF" VERSION="%i"/>\n' % CAL3D_VERSION)
|
||||
file.write('<ANIMATION DURATION="%.6f" NUMTRACKS="%i">\n' % \
|
||||
(self.duration, len(self.tracks)))
|
||||
|
||||
for item in self.tracks.itervalues():
|
||||
item.writeCal3D(file)
|
||||
|
||||
file.write('</ANIMATION>\n')
|
||||
|
||||
class Cal3DTrack(object):
|
||||
__slots__ = 'bone', 'keyframes'
|
||||
def __init__(self, bone):
|
||||
self.bone = bone
|
||||
self.keyframes = []
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('\t<TRACK BONEID="%i" NUMKEYFRAMES="%i">\n' %
|
||||
(self.bone.id, len(self.keyframes)))
|
||||
for item in self.keyframes:
|
||||
item.writeCal3D(file)
|
||||
file.write('\t</TRACK>\n')
|
||||
|
||||
class Cal3DKeyFrame(object):
|
||||
__slots__ = 'time', 'loc', 'quat'
|
||||
def __init__(self, time, loc, quat):
|
||||
self.time = time
|
||||
self.loc = loc
|
||||
self.quat = quat
|
||||
|
||||
def writeCal3D(self, file):
|
||||
file.write('\t\t<KEYFRAME TIME="%.6f">\n' % self.time)
|
||||
file.write('\t\t\t<TRANSLATION>%.6f %.6f %.6f</TRANSLATION>\n' % \
|
||||
(self.loc[0], self.loc[1], self.loc[2]))
|
||||
# We need to negate quaternion W value, but why ?
|
||||
file.write('\t\t\t<ROTATION>%.6f %.6f %.6f %.6f</ROTATION>\n' % \
|
||||
(self.quat[0], self.quat[1], self.quat[2], -self.quat[3]))
|
||||
file.write('\t\t</KEYFRAME>\n')
|
||||
|
||||
def export_cal3d(filename, PREF_SCALE=0.1, PREF_BAKE_MOTION = True, PREF_ACT_ACTION_ONLY=True, PREF_SCENE_FRAMES=False):
|
||||
if not filename.endswith('.cfg'):
|
||||
filename += '.cfg'
|
||||
|
||||
file_only = filename.split('/')[-1].split('\\')[-1]
|
||||
file_only_noext = file_only.split('.')[0]
|
||||
base_only = filename[:-len(file_only)]
|
||||
|
||||
def new_name(dataname, ext):
|
||||
return file_only_noext + '_' + BPySys.cleanName(dataname) + ext
|
||||
|
||||
#if EXPORT_FOR_SOYA:
|
||||
# global BASE_MATRIX
|
||||
# BASE_MATRIX = matrix_rotate_x(-math.pi / 2.0)
|
||||
# Get the sce
|
||||
|
||||
sce = bpy.data.scenes.active
|
||||
blend_world = sce.world
|
||||
# ---- Export skeleton (armature) ----------------------------------------
|
||||
|
||||
skeleton = Cal3DSkeleton()
|
||||
blender_armature = [ob for ob in sce.objects.context if ob.type == 'Armature']
|
||||
if len(blender_armature) > 1: print "Found multiple armatures! using ",armatures[0].name
|
||||
if blender_armature: blender_armature = blender_armature[0]
|
||||
else:
|
||||
# Try find a meshes armature
|
||||
for ob in sce.objects.context:
|
||||
blender_armature = BPyObject.getObjectArmature(ob)
|
||||
if blender_armature:
|
||||
break
|
||||
|
||||
if not blender_armature:
|
||||
Blender.Draw.PupMenu('Aborting%t|No Armature in selection')
|
||||
return
|
||||
|
||||
# we need pose bone locations
|
||||
for pbone in blender_armature.getPose().bones.values():
|
||||
POSEBONES[pbone.name] = pbone
|
||||
|
||||
Cal3DBone(skeleton, best_armature_root(blender_armature.getData()), blender_armature.matrixWorld)
|
||||
|
||||
# ---- Export Mesh data ---------------------------------------------------
|
||||
meshes = []
|
||||
for ob in sce.objects.context:
|
||||
if ob.type != 'Mesh': continue
|
||||
blend_mesh = ob.getData(mesh=1)
|
||||
|
||||
if not blend_mesh.faces: continue
|
||||
meshes.append( Cal3DMesh(ob, blend_mesh, blend_world) )
|
||||
|
||||
# ---- Export animations --------------------------------------------------
|
||||
backup_action = blender_armature.action
|
||||
|
||||
ANIMATIONS = []
|
||||
SUPPORTED_IPOS = 'QuatW', 'QuatX', 'QuatY', 'QuatZ', 'LocX', 'LocY', 'LocZ'
|
||||
|
||||
if PREF_ACT_ACTION_ONLY: action_items = [(blender_armature.action.name, blender_armature.action)]
|
||||
else: action_items = Blender.Armature.NLA.GetActions().items()
|
||||
|
||||
print len(action_items), 'action_items'
|
||||
|
||||
for animation_name, blend_action in action_items:
|
||||
|
||||
# get frame range
|
||||
if PREF_SCENE_FRAMES:
|
||||
action_start= Blender.Get('staframe')
|
||||
action_end= Blender.Get('endframe')
|
||||
else:
|
||||
_frames = blend_action.getFrameNumbers()
|
||||
action_start= min(_frames);
|
||||
action_end= max(_frames);
|
||||
del _frames
|
||||
|
||||
blender_armature.action = blend_action
|
||||
|
||||
if PREF_BAKE_MOTION:
|
||||
# We need to set the action active if we are getting baked data
|
||||
pose_data = BPyArmature.getBakedPoseData(blender_armature, action_start, action_end)
|
||||
|
||||
# Fake, all we need is bone names
|
||||
blend_action_ipos_items = [(pbone, True) for pbone in POSEBONES.iterkeys()]
|
||||
else:
|
||||
# real (bone_name, ipo) pairs
|
||||
blend_action_ipos_items = blend_action.getAllChannelIpos().items()
|
||||
|
||||
# Now we mau have some bones with no channels, easiest to add their names and an empty list here
|
||||
# this way they are exported with dummy keyfraames at teh first used frame
|
||||
action_bone_names = [name for name, ipo in blend_action_ipos_items]
|
||||
for bone_name in BONES: # iterkeys
|
||||
if bone_name not in action_bone_names:
|
||||
blend_action_ipos_items.append( (bone_name, []) )
|
||||
|
||||
animation = Cal3DAnimation(animation_name)
|
||||
# ----------------------------
|
||||
ANIMATIONS.append(animation)
|
||||
animation.duration = 0.0
|
||||
|
||||
for bone_name, ipo in blend_action_ipos_items:
|
||||
# Baked bones may have no IPO's width motion still
|
||||
if bone_name not in BONES:
|
||||
print '\tNo Bone "' + bone_name + '" in (from Animation "' + animation_name + '") ?!?'
|
||||
continue
|
||||
|
||||
# So we can loop without errors
|
||||
if ipo==None: ipo = []
|
||||
|
||||
bone = BONES[bone_name]
|
||||
track = animation.tracks[bone_name] = Cal3DTrack(bone)
|
||||
|
||||
if PREF_BAKE_MOTION:
|
||||
for i in xrange(action_end - action_start):
|
||||
cal3dtime = i / 25.0 # assume 25FPS by default
|
||||
|
||||
if cal3dtime > animation.duration:
|
||||
animation.duration = cal3dtime
|
||||
|
||||
#print pose_data[i][bone_name], i
|
||||
loc, quat = pose_data[i][bone_name]
|
||||
|
||||
loc = vector_by_matrix_3x3(loc, bone.matrix)
|
||||
loc = vector_add(bone.loc, loc)
|
||||
quat = quaternion_multiply(quat, bone.quat)
|
||||
quat = Quaternion(quat)
|
||||
|
||||
quat.normalize()
|
||||
quat = tuple(quat)
|
||||
|
||||
track.keyframes.append( Cal3DKeyFrame(cal3dtime, loc, quat) )
|
||||
|
||||
else:
|
||||
#run 1: we need to find all time values where we need to produce keyframes
|
||||
times = set()
|
||||
for curve in ipo:
|
||||
curve_name = curve.name
|
||||
if curve_name in SUPPORTED_IPOS:
|
||||
for p in curve.bezierPoints:
|
||||
times.add( p.pt[0] )
|
||||
|
||||
times = list(times)
|
||||
times.sort()
|
||||
|
||||
# Incase we have no keys here or ipo==None
|
||||
if not times: times.append(action_start)
|
||||
|
||||
# run2: now create keyframes
|
||||
for time in times:
|
||||
cal3dtime = (time-1) / 25.0 # assume 25FPS by default
|
||||
if cal3dtime > animation.duration:
|
||||
animation.duration = cal3dtime
|
||||
|
||||
trans = Vector()
|
||||
quat = Quaternion()
|
||||
|
||||
for curve in ipo:
|
||||
val = curve.evaluate(time)
|
||||
# val = 0.0
|
||||
curve_name= curve.name
|
||||
if curve_name == 'LocX': trans[0] = val
|
||||
elif curve_name == 'LocY': trans[1] = val
|
||||
elif curve_name == 'LocZ': trans[2] = val
|
||||
elif curve_name == 'QuatW': quat[3] = val
|
||||
elif curve_name == 'QuatX': quat[0] = val
|
||||
elif curve_name == 'QuatY': quat[1] = val
|
||||
elif curve_name == 'QuatZ': quat[2] = val
|
||||
|
||||
transt = vector_by_matrix_3x3(trans, bone.matrix)
|
||||
loc = vector_add(bone.loc, transt)
|
||||
quat = quaternion_multiply(quat, bone.quat)
|
||||
quat = Quaternion(quat)
|
||||
|
||||
quat.normalize()
|
||||
quat = tuple(quat)
|
||||
|
||||
track.keyframes.append( Cal3DKeyFrame(cal3dtime, loc, quat) )
|
||||
|
||||
|
||||
if animation.duration <= 0:
|
||||
print 'Ignoring Animation "' + animation_name + '": duration is 0.\n'
|
||||
continue
|
||||
|
||||
# Restore the original armature
|
||||
blender_armature.action = backup_action
|
||||
# ------------------------------------- End Animation
|
||||
|
||||
|
||||
|
||||
cfg = open((filename), 'wb')
|
||||
cfg.write('# Cal3D model exported from Blender with export_cal3d.py\n# from %s\n' % Blender.Get('filename'))
|
||||
|
||||
if PREF_SCALE != 1.0: cfg.write('scale=%.6f\n' % PREF_SCALE)
|
||||
|
||||
fname = file_only_noext + '.xsf'
|
||||
file = open( base_only + fname, 'wb')
|
||||
skeleton.writeCal3D(file)
|
||||
file.close()
|
||||
|
||||
cfg.write('skeleton=%s\n' % fname)
|
||||
|
||||
for animation in ANIMATIONS:
|
||||
if not animation.name.startswith('_'):
|
||||
if animation.duration > 0.1: # Cal3D does not support animation with only one state
|
||||
fname = new_name(animation.name, '.xaf')
|
||||
file = open(base_only + fname, 'wb')
|
||||
animation.writeCal3D(file)
|
||||
file.close()
|
||||
cfg.write('animation=%s\n' % fname)
|
||||
|
||||
for mesh in meshes:
|
||||
if not mesh.name.startswith('_'):
|
||||
fname = new_name(mesh.name, '.xmf')
|
||||
file = open(base_only + fname, 'wb')
|
||||
mesh.writeCal3D(file)
|
||||
file.close()
|
||||
|
||||
cfg.write('mesh=%s\n' % fname)
|
||||
|
||||
materials = MATERIALS.values()
|
||||
materials.sort(key = lambda a: a.id)
|
||||
for material in materials:
|
||||
# Just number materials, its less trouble
|
||||
fname = new_name(str(material.id), '.xrf')
|
||||
|
||||
file = open(base_only + fname, 'wb')
|
||||
material.writeCal3D(file)
|
||||
file.close()
|
||||
|
||||
cfg.write('material=%s\n' % fname)
|
||||
|
||||
print 'Cal3D Saved to "%s.cfg"' % file_only_noext
|
||||
|
||||
# Warnings
|
||||
if len(animation.tracks) < 2:
|
||||
Blender.Draw.PupMenu('Warning, the armature has less then 2 tracks, file may not load in Cal3d')
|
||||
|
||||
|
||||
def export_cal3d_ui(filename):
|
||||
|
||||
PREF_SCALE= Blender.Draw.Create(1.0)
|
||||
PREF_BAKE_MOTION = Blender.Draw.Create(1)
|
||||
PREF_ACT_ACTION_ONLY= Blender.Draw.Create(1)
|
||||
PREF_SCENE_FRAMES= Blender.Draw.Create(0)
|
||||
|
||||
block = [\
|
||||
('Scale: ', PREF_SCALE, 0.01, 100, 'The scale to set in the Cal3d .cfg file (unsupported by soya)'),\
|
||||
('Baked Motion', PREF_BAKE_MOTION, 'use final pose position instead of ipo keyframes (IK and constraint support)'),\
|
||||
('Active Action', PREF_ACT_ACTION_ONLY, 'Only export action applied to this armature, else export all actions.'),\
|
||||
('Scene Frames', PREF_SCENE_FRAMES, 'Use scene frame range, else the actions start/end'),\
|
||||
]
|
||||
|
||||
if not Blender.Draw.PupBlock('Cal3D Options', block):
|
||||
return
|
||||
|
||||
Blender.Window.WaitCursor(1)
|
||||
export_cal3d(filename, 1.0/PREF_SCALE.val, PREF_BAKE_MOTION.val, PREF_ACT_ACTION_ONLY.val, PREF_SCENE_FRAMES.val)
|
||||
Blender.Window.WaitCursor(0)
|
||||
|
||||
|
||||
#import os
|
||||
if __name__ == '__main__':
|
||||
Blender.Window.FileSelector(export_cal3d_ui, 'Cal3D Export', Blender.Get('filename').replace('.blend', '.cfg'))
|
||||
#export_cal3d('/cally/data/skeleton/skeleton' + '.cfg', 1.0, True, False, False)
|
||||
#export_cal3d('/test' + '.cfg')
|
||||
#export_cal3d_ui('/test' + '.cfg')
|
||||
#os.system('cd /; wine /cal3d_miniviewer.exe /skeleton.cfg')
|
||||
#os.system('cd /cally/;wine cally')
|
||||
@@ -1,26 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
# constants
|
||||
import _bpy
|
||||
version = _bpy._VERSION
|
||||
version_string = _bpy._VERSION_STR
|
||||
home = _bpy._HOME
|
||||
binary_path = _bpy._BINPATH
|
||||
@@ -1,200 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8-80 compliant>
|
||||
|
||||
# for slightly faster access
|
||||
from bpy.__ops__ import add as op_add
|
||||
from bpy.__ops__ import remove as op_remove
|
||||
from bpy.__ops__ import dir as op_dir
|
||||
from bpy.__ops__ import call as op_call
|
||||
from bpy.__ops__ import as_string as op_as_string
|
||||
from bpy.__ops__ import get_rna as op_get_rna
|
||||
|
||||
# Keep in sync with WM_types.h
|
||||
context_dict = {
|
||||
'INVOKE_DEFAULT': 0,
|
||||
'INVOKE_REGION_WIN': 1,
|
||||
'INVOKE_AREA': 2,
|
||||
'INVOKE_SCREEN': 3,
|
||||
'EXEC_DEFAULT': 4,
|
||||
'EXEC_REGION_WIN': 5,
|
||||
'EXEC_AREA': 6,
|
||||
'EXEC_SCREEN': 7,
|
||||
}
|
||||
|
||||
|
||||
class bpy_ops(object):
|
||||
'''
|
||||
Fake module like class.
|
||||
|
||||
bpy.ops
|
||||
'''
|
||||
|
||||
def __getattr__(self, module):
|
||||
'''
|
||||
gets a bpy.ops submodule
|
||||
'''
|
||||
if module.startswith('__'):
|
||||
raise AttributeError(module)
|
||||
return bpy_ops_submodule(module)
|
||||
|
||||
def add(self, pyop):
|
||||
op_add(pyop)
|
||||
|
||||
def remove(self, pyop):
|
||||
op_remove(pyop)
|
||||
|
||||
def __dir__(self):
|
||||
|
||||
submodules = set()
|
||||
|
||||
# add this classes functions
|
||||
for id_name in dir(self.__class__):
|
||||
if not id_name.startswith('__'):
|
||||
submodules.add(id_name)
|
||||
|
||||
for id_name in op_dir():
|
||||
id_split = id_name.split('_OT_', 1)
|
||||
|
||||
if len(id_split) == 2:
|
||||
submodules.add(id_split[0].lower())
|
||||
else:
|
||||
submodules.add(id_split[0])
|
||||
|
||||
return list(submodules)
|
||||
|
||||
def __repr__(self):
|
||||
return "<module like class 'bpy.ops'>"
|
||||
|
||||
|
||||
class bpy_ops_submodule(object):
|
||||
'''
|
||||
Utility class to fake submodules.
|
||||
|
||||
eg. bpy.ops.object
|
||||
'''
|
||||
__keys__ = ('module',)
|
||||
|
||||
def __init__(self, module):
|
||||
self.module = module
|
||||
|
||||
def __getattr__(self, func):
|
||||
'''
|
||||
gets a bpy.ops.submodule function
|
||||
'''
|
||||
if func.startswith('__'):
|
||||
raise AttributeError(func)
|
||||
return bpy_ops_submodule_op(self.module, func)
|
||||
|
||||
def __dir__(self):
|
||||
|
||||
functions = set()
|
||||
|
||||
module_upper = self.module.upper()
|
||||
|
||||
for id_name in op_dir():
|
||||
id_split = id_name.split('_OT_', 1)
|
||||
if len(id_split) == 2 and module_upper == id_split[0]:
|
||||
functions.add(id_split[1])
|
||||
|
||||
return list(functions)
|
||||
|
||||
def __repr__(self):
|
||||
return "<module like class 'bpy.ops.%s'>" % self.module
|
||||
|
||||
|
||||
class bpy_ops_submodule_op(object):
|
||||
'''
|
||||
Utility class to fake submodule operators.
|
||||
|
||||
eg. bpy.ops.object.somefunc
|
||||
'''
|
||||
|
||||
__keys__ = ('module', 'func')
|
||||
|
||||
|
||||
def _get_doc(self):
|
||||
return op_as_string(self.idname())
|
||||
|
||||
__doc__ = property(_get_doc)
|
||||
|
||||
|
||||
def _get_doc(self):
|
||||
return op_as_string(self.idname())
|
||||
|
||||
__doc__ = property(_get_doc)
|
||||
|
||||
def __init__(self, module, func):
|
||||
self.module = module
|
||||
self.func = func
|
||||
|
||||
def idname(self):
|
||||
# submod.foo -> SUBMOD_OT_foo
|
||||
return self.module.upper() + '_OT_' + self.func
|
||||
|
||||
def __call__(self, *args, **kw):
|
||||
|
||||
# Get the operator from blender
|
||||
if len(args) > 2:
|
||||
raise ValueError("1 or 2 args execution context is supported")
|
||||
|
||||
C_dict = None
|
||||
|
||||
if args:
|
||||
|
||||
C_exec = 'EXEC_DEFAULT'
|
||||
|
||||
if len(args) == 2:
|
||||
C_exec = args[0]
|
||||
C_dict = args[1]
|
||||
else:
|
||||
if type(args[0]) != str:
|
||||
C_dict = args[0]
|
||||
else:
|
||||
C_exec = args[0]
|
||||
|
||||
try:
|
||||
context = context_dict[C_exec]
|
||||
except:
|
||||
raise ValueError("Expected a single context argument in: " + \
|
||||
str(list(context_dict.keys())))
|
||||
|
||||
if len(args) == 2:
|
||||
C_dict = args[1]
|
||||
|
||||
return op_call(self.idname(), C_dict, kw, context)
|
||||
|
||||
else:
|
||||
return op_call(self.idname(), C_dict, kw)
|
||||
|
||||
def get_rna(self):
|
||||
'''
|
||||
currently only used for 'bl_rna'
|
||||
'''
|
||||
return op_get_rna(self.idname())
|
||||
|
||||
def __repr__(self): # useful display, repr(op)
|
||||
return op_as_string(self.idname())
|
||||
|
||||
def __str__(self): # used for print(...)
|
||||
return "<function bpy.ops.%s.%s at 0x%x'>" % \
|
||||
(self.module, self.func, id(self))
|
||||
|
||||
import bpy
|
||||
bpy.ops = bpy_ops()
|
||||
@@ -1,111 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
import bpy
|
||||
|
||||
def collect_baseclasses(_class, bases):
|
||||
|
||||
if _class is type or _class is object:
|
||||
return bases
|
||||
|
||||
bases.append(_class)
|
||||
for _superclass in _class.__bases__:
|
||||
collect_baseclasses(_superclass, bases)
|
||||
|
||||
return bases
|
||||
|
||||
def collect_subclasses(_class, subs):
|
||||
|
||||
if _class is type or _class is object:
|
||||
return subs
|
||||
|
||||
subs.append(_class)
|
||||
for _subclass in _class.__subclasses__():
|
||||
collect_subclasses(_subclass, subs)
|
||||
|
||||
return subs
|
||||
|
||||
class DynMenu(bpy.types.Menu):
|
||||
|
||||
def draw(self, context):
|
||||
'''
|
||||
This is a draw function that is used to call all subclasses draw functions
|
||||
starting from the registered classes draw function and working down.
|
||||
|
||||
DynMenu.setup() must be called first.
|
||||
|
||||
Sort/group classes could be nice
|
||||
'''
|
||||
|
||||
subclass_ls = []
|
||||
collect_subclasses(self.__class__, subclass_ls)
|
||||
# print(subclass_ls)
|
||||
|
||||
for subclass in subclass_ls:
|
||||
# print("drawwing", subclass) # , dir(subclass))
|
||||
subclass.internal_draw(self, context)
|
||||
# print("subclass.internal_draw", subclass.internal_draw)
|
||||
|
||||
def setup(menu_class):
|
||||
'''
|
||||
Setup subclasses (not needed when self.add() is used)
|
||||
'''
|
||||
bases = collect_baseclasses(menu_class, [])
|
||||
|
||||
# Incase 'DynMenu' isnt last
|
||||
while bases[-1] is not DynMenu:
|
||||
bases.pop()
|
||||
bases.pop() # remove 'DynMenu'
|
||||
|
||||
root_class = bases[-1] # this is the registered class
|
||||
|
||||
for subclass in collect_subclasses(root_class, []):
|
||||
#print(subclass)
|
||||
|
||||
draw = getattr(subclass, 'draw', None)
|
||||
if draw and not hasattr(subclass, 'internal_draw'):
|
||||
# print("replace", subclass, draw)
|
||||
try:
|
||||
del subclass.draw
|
||||
except:
|
||||
pass
|
||||
subclass.internal_draw = draw
|
||||
|
||||
root_class.draw = DynMenu.draw
|
||||
|
||||
def add(menu_class, func):
|
||||
'''
|
||||
Add a single function directly without having to make a class
|
||||
|
||||
important that the returned value should be stored in the module that called it.
|
||||
'''
|
||||
|
||||
newclass = type('<menuclass>', (menu_class,), {})
|
||||
newclass.internal_draw = func
|
||||
setup(menu_class)
|
||||
return newclass
|
||||
|
||||
'''
|
||||
# so we dont need to import this module
|
||||
DynMenu.setup = setup
|
||||
DynMenu.add = add
|
||||
|
||||
# Only so we can access as bpy.types.
|
||||
# dont ever use this directly!
|
||||
bpy.types.register(DynMenu)
|
||||
'''
|
||||
@@ -1,154 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
import bpy
|
||||
import mathutils
|
||||
from math import cos, sin, pi
|
||||
|
||||
|
||||
def add_torus(major_rad, minor_rad, major_seg, minor_seg):
|
||||
Vector = mathutils.Vector
|
||||
Quaternion = mathutils.Quaternion
|
||||
|
||||
PI_2 = pi * 2.0
|
||||
z_axis = 0.0, 0.0, 1.0
|
||||
|
||||
verts = []
|
||||
faces = []
|
||||
i1 = 0
|
||||
tot_verts = major_seg * minor_seg
|
||||
for major_index in range(major_seg):
|
||||
quat = Quaternion(z_axis, (major_index / major_seg) * PI_2)
|
||||
|
||||
for minor_index in range(minor_seg):
|
||||
angle = 2 * pi * minor_index / minor_seg
|
||||
|
||||
vec = Vector((major_rad + (cos(angle) * minor_rad), 0.0,
|
||||
(sin(angle) * minor_rad))) * quat
|
||||
|
||||
verts.extend(vec[:])
|
||||
|
||||
if minor_index + 1 == minor_seg:
|
||||
i2 = (major_index) * minor_seg
|
||||
i3 = i1 + minor_seg
|
||||
i4 = i2 + minor_seg
|
||||
|
||||
else:
|
||||
i2 = i1 + 1
|
||||
i3 = i1 + minor_seg
|
||||
i4 = i3 + 1
|
||||
|
||||
if i2 >= tot_verts:
|
||||
i2 = i2 - tot_verts
|
||||
if i3 >= tot_verts:
|
||||
i3 = i3 - tot_verts
|
||||
if i4 >= tot_verts:
|
||||
i4 = i4 - tot_verts
|
||||
|
||||
# stupid eekadoodle
|
||||
if i2:
|
||||
faces.extend([i1, i3, i4, i2])
|
||||
else:
|
||||
faces.extend([i2, i1, i3, i4])
|
||||
|
||||
i1 += 1
|
||||
|
||||
return verts, faces
|
||||
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
class AddTorus(bpy.types.Operator):
|
||||
'''Add a torus mesh'''
|
||||
bl_idname = "mesh.primitive_torus_add"
|
||||
bl_label = "Add Torus"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
major_radius = FloatProperty(name="Major Radius",
|
||||
description="Radius from the origin to the center of the cross sections",
|
||||
default=1.0, min=0.01, max=100.0)
|
||||
minor_radius = FloatProperty(name="Minor Radius",
|
||||
description="Radius of the torus' cross section",
|
||||
default=0.25, min=0.01, max=100.0)
|
||||
major_segments = IntProperty(name="Major Segments",
|
||||
description="Number of segments for the main ring of the torus",
|
||||
default=48, min=3, max=256)
|
||||
minor_segments = IntProperty(name="Minor Segments",
|
||||
description="Number of segments for the minor ring of the torus",
|
||||
default=12, min=3, max=256)
|
||||
use_abso = BoolProperty(name="Use Int+Ext Controls",
|
||||
description="Use the Int / Ext controls for torus dimensions",
|
||||
default=False)
|
||||
abso_major_rad = FloatProperty(name="Exterior Radius",
|
||||
description="Total Exterior Radius of the torus",
|
||||
default=1.0, min=0.01, max=100.0)
|
||||
abso_minor_rad = FloatProperty(name="Inside Radius",
|
||||
description="Total Interior Radius of the torus",
|
||||
default=0.5, min=0.01, max=100.0)
|
||||
|
||||
# generic transform props
|
||||
view_align = BoolProperty(name="Align to View",
|
||||
default=False)
|
||||
location = FloatVectorProperty(name="Location",
|
||||
subtype='TRANSLATION')
|
||||
rotation = FloatVectorProperty(name="Rotation",
|
||||
subtype='EULER')
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
if self.use_abso == True:
|
||||
extra_helper = (self.abso_major_rad - self.abso_minor_rad) * 0.5
|
||||
self.major_radius = self.abso_minor_rad + extra_helper
|
||||
self.minor_radius = extra_helper
|
||||
|
||||
verts_loc, faces = add_torus(self.major_radius,
|
||||
self.minor_radius,
|
||||
self.major_segments,
|
||||
self.minor_segments)
|
||||
|
||||
mesh = bpy.data.meshes.new("Torus")
|
||||
|
||||
mesh.vertices.add(len(verts_loc) // 3)
|
||||
mesh.faces.add(len(faces) // 4)
|
||||
|
||||
mesh.vertices.foreach_set("co", verts_loc)
|
||||
mesh.faces.foreach_set("vertices_raw", faces)
|
||||
mesh.update()
|
||||
|
||||
import add_object_utils
|
||||
add_object_utils.object_data_add(context, mesh, operator=self)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(AddTorus.bl_idname, text="Torus", icon='MESH_TORUS')
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_class(AddTorus)
|
||||
bpy.types.INFO_MT_mesh_add.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_class(AddTorus)
|
||||
bpy.types.INFO_MT_mesh_add.remove(menu_func)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,708 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
data_path_update = [
|
||||
("ClothCollisionSettings", "min_distance", "distance_min"),
|
||||
("ClothCollisionSettings", "self_min_distance", "self_distance_min"),
|
||||
("ClothCollisionSettings", "enable_collision", "use_collision"),
|
||||
("ClothCollisionSettings", "enable_self_collision", "use_self_collision"),
|
||||
("ClothSettings", "pin_cloth", "use_pin_cloth"),
|
||||
("ClothSettings", "stiffness_scaling", "use_stiffness_scale"),
|
||||
("CollisionSettings", "random_damping", "damping_random"),
|
||||
("CollisionSettings", "random_friction", "friction_random"),
|
||||
("CollisionSettings", "inner_thickness", "thickness_inner"),
|
||||
("CollisionSettings", "outer_thickness", "thickness_outer"),
|
||||
("CollisionSettings", "kill_particles", "use_particle_kill"),
|
||||
("Constraint", "proxy_local", "is_proxy_local"),
|
||||
("ActionConstraint", "maximum", "max"),
|
||||
("ActionConstraint", "minimum", "min"),
|
||||
("FollowPathConstraint", "use_fixed_position", "use_fixed_location"),
|
||||
("KinematicConstraint", "chain_length", "chain_count"),
|
||||
("KinematicConstraint", "pos_lock_x", "lock_location_x"),
|
||||
("KinematicConstraint", "pos_lock_y", "lock_location_y"),
|
||||
("KinematicConstraint", "pos_lock_z", "lock_location_z"),
|
||||
("KinematicConstraint", "rot_lock_x", "lock_rotation_x"),
|
||||
("KinematicConstraint", "rot_lock_y", "lock_rotation_y"),
|
||||
("KinematicConstraint", "rot_lock_z", "lock_rotation_z"),
|
||||
("KinematicConstraint", "axis_reference", "reference_axis"),
|
||||
("KinematicConstraint", "use_position", "use_location"),
|
||||
("LimitLocationConstraint", "maximum_x", "max_x"),
|
||||
("LimitLocationConstraint", "maximum_y", "max_y"),
|
||||
("LimitLocationConstraint", "maximum_z", "max_z"),
|
||||
("LimitLocationConstraint", "minimum_x", "min_x"),
|
||||
("LimitLocationConstraint", "minimum_y", "min_y"),
|
||||
("LimitLocationConstraint", "minimum_z", "min_z"),
|
||||
("LimitLocationConstraint", "use_maximum_x", "use_max_x"),
|
||||
("LimitLocationConstraint", "use_maximum_y", "use_max_y"),
|
||||
("LimitLocationConstraint", "use_maximum_z", "use_max_z"),
|
||||
("LimitLocationConstraint", "use_minimum_x", "use_min_x"),
|
||||
("LimitLocationConstraint", "use_minimum_y", "use_min_y"),
|
||||
("LimitLocationConstraint", "use_minimum_z", "use_min_z"),
|
||||
("LimitLocationConstraint", "limit_transform", "use_transform_limit"),
|
||||
("LimitRotationConstraint", "maximum_x", "max_x"),
|
||||
("LimitRotationConstraint", "maximum_y", "max_y"),
|
||||
("LimitRotationConstraint", "maximum_z", "max_z"),
|
||||
("LimitRotationConstraint", "minimum_x", "min_x"),
|
||||
("LimitRotationConstraint", "minimum_y", "min_y"),
|
||||
("LimitRotationConstraint", "minimum_z", "min_z"),
|
||||
("LimitRotationConstraint", "limit_transform", "use_transform_limit"),
|
||||
("LimitScaleConstraint", "maximum_x", "max_x"),
|
||||
("LimitScaleConstraint", "maximum_y", "max_y"),
|
||||
("LimitScaleConstraint", "maximum_z", "max_z"),
|
||||
("LimitScaleConstraint", "minimum_x", "min_x"),
|
||||
("LimitScaleConstraint", "minimum_y", "min_y"),
|
||||
("LimitScaleConstraint", "minimum_z", "min_z"),
|
||||
("LimitScaleConstraint", "use_maximum_x", "use_max_x"),
|
||||
("LimitScaleConstraint", "use_maximum_y", "use_max_y"),
|
||||
("LimitScaleConstraint", "use_maximum_z", "use_max_z"),
|
||||
("LimitScaleConstraint", "use_minimum_x", "use_min_x"),
|
||||
("LimitScaleConstraint", "use_minimum_y", "use_min_y"),
|
||||
("LimitScaleConstraint", "use_minimum_z", "use_min_z"),
|
||||
("LimitScaleConstraint", "limit_transform", "use_transform_limit"),
|
||||
("PivotConstraint", "enabled_rotation_range", "rotation_range"),
|
||||
("PivotConstraint", "use_relative_position", "use_relative_location"),
|
||||
("PythonConstraint", "number_of_targets", "target_count"),
|
||||
("SplineIKConstraint", "chain_length", "chain_count"),
|
||||
("SplineIKConstraint", "chain_offset", "use_chain_offset"),
|
||||
("SplineIKConstraint", "even_divisions", "use_even_divisions"),
|
||||
("SplineIKConstraint", "y_stretch", "use_y_stretch"),
|
||||
("SplineIKConstraint", "xz_scaling_mode", "xz_scale_mode"),
|
||||
("StretchToConstraint", "original_length", "rest_length"),
|
||||
("TrackToConstraint", "target_z", "use_target_z"),
|
||||
("TransformConstraint", "extrapolate_motion", "use_motion_extrapolate"),
|
||||
("FieldSettings", "do_location", "apply_to_location"),
|
||||
("FieldSettings", "do_rotation", "apply_to_rotation"),
|
||||
("FieldSettings", "maximum_distance", "distance_max"),
|
||||
("FieldSettings", "minimum_distance", "distance_min"),
|
||||
("FieldSettings", "radial_maximum", "radial_max"),
|
||||
("FieldSettings", "radial_minimum", "radial_min"),
|
||||
("FieldSettings", "force_2d", "use_2d_force"),
|
||||
("FieldSettings", "do_absorption", "use_absorption"),
|
||||
("FieldSettings", "global_coordinates", "use_global_coords"),
|
||||
("FieldSettings", "guide_path_add", "use_guide_path_add"),
|
||||
("FieldSettings", "multiple_springs", "use_multiple_springs"),
|
||||
("FieldSettings", "use_coordinates", "use_object_coords"),
|
||||
("FieldSettings", "root_coordinates", "use_root_coords"),
|
||||
("ControlFluidSettings", "reverse_frames", "use_reverse_frames"),
|
||||
("DomainFluidSettings", "real_world_size", "simulation_scale"),
|
||||
("DomainFluidSettings", "surface_smoothing", "surface_smooth"),
|
||||
("DomainFluidSettings", "reverse_frames", "use_reverse_frames"),
|
||||
("DomainFluidSettings", "generate_speed_vectors", "use_speed_vectors"),
|
||||
("DomainFluidSettings", "override_time", "use_time_override"),
|
||||
("FluidFluidSettings", "export_animated_mesh", "use_animated_mesh"),
|
||||
("InflowFluidSettings", "export_animated_mesh", "use_animated_mesh"),
|
||||
("InflowFluidSettings", "local_coordinates", "use_local_coords"),
|
||||
("ObstacleFluidSettings", "export_animated_mesh", "use_animated_mesh"),
|
||||
("OutflowFluidSettings", "export_animated_mesh", "use_animated_mesh"),
|
||||
("ParticleFluidSettings", "drops", "use_drops"),
|
||||
("ParticleFluidSettings", "floats", "use_floats"),
|
||||
("Armature", "drawtype", "draw_type"),
|
||||
("Armature", "layer_protection", "layers_protected"),
|
||||
("Armature", "auto_ik", "use_auto_ik"),
|
||||
("Armature", "delay_deform", "use_deform_delay"),
|
||||
("Armature", "deform_envelope", "use_deform_envelopes"),
|
||||
("Armature", "deform_quaternion", "use_deform_preserve_volume"),
|
||||
("Armature", "deform_vertexgroups", "use_deform_vertex_groups"),
|
||||
("Armature", "x_axis_mirror", "use_mirror_x"),
|
||||
("Curve", "width", "offset"),
|
||||
("Image", "animation_speed", "fps"),
|
||||
("Image", "animation_end", "frame_end"),
|
||||
("Image", "animation_start", "frame_start"),
|
||||
("Image", "animated", "use_animation"),
|
||||
("Image", "clamp_x", "use_clamp_x"),
|
||||
("Image", "clamp_y", "use_clamp_y"),
|
||||
("Image", "premultiply", "use_premultiply"),
|
||||
("AreaLamp", "shadow_ray_sampling_method", "shadow_ray_sample_method"),
|
||||
("AreaLamp", "only_shadow", "use_only_shadow"),
|
||||
("AreaLamp", "shadow_layer", "use_shadow_layer"),
|
||||
("AreaLamp", "umbra", "use_umbra"),
|
||||
("PointLamp", "shadow_ray_sampling_method", "shadow_ray_sample_method"),
|
||||
("PointLamp", "only_shadow", "use_only_shadow"),
|
||||
("PointLamp", "shadow_layer", "use_shadow_layer"),
|
||||
("PointLamp", "sphere", "use_sphere"),
|
||||
("SpotLamp", "shadow_ray_sampling_method", "shadow_ray_sample_method"),
|
||||
("SpotLamp", "auto_clip_end", "use_auto_clip_end"),
|
||||
("SpotLamp", "auto_clip_start", "use_auto_clip_start"),
|
||||
("SpotLamp", "only_shadow", "use_only_shadow"),
|
||||
("SpotLamp", "shadow_layer", "use_shadow_layer"),
|
||||
("SpotLamp", "sphere", "use_sphere"),
|
||||
("SunLamp", "only_shadow", "use_only_shadow"),
|
||||
("SunLamp", "shadow_layer", "use_shadow_layer"),
|
||||
("Material", "z_offset", "offset_z"),
|
||||
("Material", "shadow_casting_alpha", "shadow_cast_alpha"),
|
||||
("Material", "cast_approximate", "use_cast_approximate"),
|
||||
("Material", "cast_buffer_shadows", "use_cast_buffer_shadows"),
|
||||
("Material", "cast_shadows_only", "use_cast_shadows_only"),
|
||||
("Material", "face_texture", "use_face_texture"),
|
||||
("Material", "face_texture_alpha", "use_face_texture_alpha"),
|
||||
("Material", "full_oversampling", "use_full_oversampling"),
|
||||
("Material", "light_group_exclusive", "use_light_group_exclusive"),
|
||||
("Material", "object_color", "use_object_color"),
|
||||
("Material", "only_shadow", "use_only_shadow"),
|
||||
("Material", "ray_shadow_bias", "use_ray_shadow_bias"),
|
||||
("Material", "traceable", "use_raytrace"),
|
||||
("Material", "shadeless", "use_shadeless"),
|
||||
("Material", "tangent_shading", "use_tangent_shading"),
|
||||
("Material", "transparency", "use_transparency"),
|
||||
("Material", "receive_transparent_shadows", "use_transparent_shadows"),
|
||||
("Material", "vertex_color_light", "use_vertex_color_light"),
|
||||
("Material", "vertex_color_paint", "use_vertex_color_paint"),
|
||||
("Mesh", "autosmooth_angle", "auto_smooth_angle"),
|
||||
("Mesh", "autosmooth", "use_auto_smooth"),
|
||||
("Object", "max_draw_type", "draw_type"),
|
||||
("Object", "use_dupli_verts_rotation", "use_dupli_vertices_rotation"),
|
||||
("Object", "shape_key_edit_mode", "use_shape_key_edit_mode"),
|
||||
("Object", "slow_parent", "use_slow_parent"),
|
||||
("Object", "time_offset_add_parent", "use_time_offset_add_parent"),
|
||||
("Object", "time_offset_edit", "use_time_offset_edit"),
|
||||
("Object", "time_offset_parent", "use_time_offset_parent"),
|
||||
("Object", "time_offset_particle", "use_time_offset_particle"),
|
||||
("ParticleSettings", "adaptive_pix", "adaptive_pixel"),
|
||||
("ParticleSettings", "child_effector", "apply_effector_to_children"),
|
||||
("ParticleSettings", "child_guide", "apply_guide_to_children"),
|
||||
("ParticleSettings", "billboard_split_offset", "billboard_offset_split"),
|
||||
("ParticleSettings", "billboard_random_tilt", "billboard_tilt_random"),
|
||||
("ParticleSettings", "child_length_thres", "child_length_threshold"),
|
||||
("ParticleSettings", "child_random_size", "child_size_random"),
|
||||
("ParticleSettings", "clumppow", "clump_shape"),
|
||||
("ParticleSettings", "damp_factor", "damping"),
|
||||
("ParticleSettings", "draw_as", "draw_method"),
|
||||
("ParticleSettings", "random_factor", "factor_random"),
|
||||
("ParticleSettings", "grid_invert", "invert_grid"),
|
||||
("ParticleSettings", "random_length", "length_random"),
|
||||
("ParticleSettings", "random_lifetime", "lifetime_random"),
|
||||
("ParticleSettings", "billboard_lock", "lock_billboard"),
|
||||
("ParticleSettings", "boids_2d", "lock_boids_to_surface"),
|
||||
("ParticleSettings", "object_aligned_factor", "object_align_factor"),
|
||||
("ParticleSettings", "random_phase_factor", "phase_factor_random"),
|
||||
("ParticleSettings", "ren_as", "render_type"),
|
||||
("ParticleSettings", "rendered_child_nbr", "rendered_child_count"),
|
||||
("ParticleSettings", "random_rotation_factor", "rotation_factor_random"),
|
||||
("ParticleSettings", "rough1", "roughness_1"),
|
||||
("ParticleSettings", "rough1_size", "roughness_1_size"),
|
||||
("ParticleSettings", "rough2", "roughness_2"),
|
||||
("ParticleSettings", "rough2_size", "roughness_2_size"),
|
||||
("ParticleSettings", "rough2_thres", "roughness_2_threshold"),
|
||||
("ParticleSettings", "rough_end_shape", "roughness_end_shape"),
|
||||
("ParticleSettings", "rough_endpoint", "roughness_endpoint"),
|
||||
("ParticleSettings", "random_size", "size_random"),
|
||||
("ParticleSettings", "abs_path_time", "use_absolute_path_time"),
|
||||
("ParticleSettings", "animate_branching", "use_animate_branching"),
|
||||
("ParticleSettings", "branching", "use_branching"),
|
||||
("ParticleSettings", "died", "use_dead"),
|
||||
("ParticleSettings", "die_on_collision", "use_die_on_collision"),
|
||||
("ParticleSettings", "rotation_dynamic", "use_dynamic_rotation"),
|
||||
("ParticleSettings", "even_distribution", "use_even_distribution"),
|
||||
("ParticleSettings", "rand_group", "use_group_pick_random"),
|
||||
("ParticleSettings", "hair_bspline", "use_hair_bspline"),
|
||||
("ParticleSettings", "sizemass", "use_multiply_size_mass"),
|
||||
("ParticleSettings", "react_multiple", "use_react_multiple"),
|
||||
("ParticleSettings", "react_start_end", "use_react_start_end"),
|
||||
("ParticleSettings", "render_adaptive", "use_render_adaptive"),
|
||||
("ParticleSettings", "self_effect", "use_self_effect"),
|
||||
("ParticleSettings", "enable_simplify", "use_simplify"),
|
||||
("ParticleSettings", "size_deflect", "use_size_deflect"),
|
||||
("ParticleSettings", "render_strand", "use_strand_primitive"),
|
||||
("ParticleSettings", "symmetric_branching", "use_symmetric_branching"),
|
||||
("ParticleSettings", "velocity_length", "use_velocity_length"),
|
||||
("ParticleSettings", "whole_group", "use_whole_group"),
|
||||
("CloudsTexture", "noise_size", "noise_scale"),
|
||||
("DistortedNoiseTexture", "noise_size", "noise_scale"),
|
||||
("EnvironmentMapTexture", "filter_size_minimum", "use_filter_size_min"),
|
||||
("EnvironmentMapTexture", "mipmap_gauss", "use_mipmap_gauss"),
|
||||
("ImageTexture", "calculate_alpha", "use_calculate_alpha"),
|
||||
("ImageTexture", "checker_even", "use_checker_even"),
|
||||
("ImageTexture", "checker_odd", "use_checker_odd"),
|
||||
("ImageTexture", "filter_size_minimum", "use_filter_size_min"),
|
||||
("ImageTexture", "flip_axis", "use_flip_axis"),
|
||||
("ImageTexture", "mipmap_gauss", "use_mipmap_gauss"),
|
||||
("ImageTexture", "mirror_x", "use_mirror_x"),
|
||||
("ImageTexture", "mirror_y", "use_mirror_y"),
|
||||
("ImageTexture", "normal_map", "use_normal_map"),
|
||||
("MarbleTexture", "noise_size", "noise_scale"),
|
||||
("MarbleTexture", "noisebasis2", "noisebasis_2"),
|
||||
("MusgraveTexture", "highest_dimension", "dimension_max"),
|
||||
("MusgraveTexture", "noise_size", "noise_scale"),
|
||||
("StucciTexture", "noise_size", "noise_scale"),
|
||||
("VoronoiTexture", "coloring", "color_mode"),
|
||||
("VoronoiTexture", "noise_size", "noise_scale"),
|
||||
("WoodTexture", "noise_size", "noise_scale"),
|
||||
("WoodTexture", "noisebasis2", "noisebasis_2"),
|
||||
("World", "blend_sky", "use_sky_blend"),
|
||||
("World", "paper_sky", "use_sky_paper"),
|
||||
("World", "real_sky", "use_sky_real"),
|
||||
("ImageUser", "auto_refresh", "use_auto_refresh"),
|
||||
("MaterialHalo", "flares_sub", "flare_subflare_count"),
|
||||
("MaterialHalo", "flare_subsize", "flare_subflare_size"),
|
||||
("MaterialHalo", "line_number", "line_count"),
|
||||
("MaterialHalo", "rings", "ring_count"),
|
||||
("MaterialHalo", "star_tips", "star_tip_count"),
|
||||
("MaterialHalo", "xalpha", "use_extreme_alpha"),
|
||||
("MaterialHalo", "flare_mode", "use_flare_mode"),
|
||||
("MaterialHalo", "vertex_normal", "use_vertex_normal"),
|
||||
("MaterialPhysics", "align_to_normal", "use_normal_align"),
|
||||
("MaterialStrand", "min_size", "size_min"),
|
||||
("MaterialStrand", "blender_units", "use_blender_units"),
|
||||
("MaterialStrand", "surface_diffuse", "use_surface_diffuse"),
|
||||
("MaterialStrand", "tangent_shading", "use_tangent_shading"),
|
||||
("MaterialSubsurfaceScattering", "error_tolerance", "error_threshold"),
|
||||
("MaterialVolume", "depth_cutoff", "depth_threshold"),
|
||||
("MaterialVolume", "lighting_mode", "light_method"),
|
||||
("MaterialVolume", "step_calculation", "step_method"),
|
||||
("MaterialVolume", "external_shadows", "use_external_shadows"),
|
||||
("MaterialVolume", "light_cache", "use_light_cache"),
|
||||
("ArmatureModifier", "multi_modifier", "use_multi_modifier"),
|
||||
("ArrayModifier", "constant_offset_displacement", "constant_offset_displace"),
|
||||
("ArrayModifier", "merge_distance", "merge_threshold"),
|
||||
("ArrayModifier", "relative_offset_displacement", "relative_offset_displace"),
|
||||
("ArrayModifier", "constant_offset", "use_constant_offset"),
|
||||
("ArrayModifier", "merge_adjacent_vertices", "use_merge_vertices"),
|
||||
("ArrayModifier", "merge_end_vertices", "use_merge_vertices_cap"),
|
||||
("ArrayModifier", "add_offset_object", "use_object_offset"),
|
||||
("ArrayModifier", "relative_offset", "use_relative_offset"),
|
||||
("BevelModifier", "only_vertices", "use_only_vertices"),
|
||||
("CastModifier", "from_radius", "use_radius_as_size"),
|
||||
("DisplaceModifier", "midlevel", "mid_level"),
|
||||
("DisplaceModifier", "texture_coordinates", "texture_coords"),
|
||||
("EdgeSplitModifier", "use_sharp", "use_edge_sharp"),
|
||||
("ExplodeModifier", "split_edges", "use_edge_split"),
|
||||
("MirrorModifier", "merge_limit", "merge_threshold"),
|
||||
("MirrorModifier", "mirror_u", "use_mirror_u"),
|
||||
("MirrorModifier", "mirror_v", "use_mirror_v"),
|
||||
("MirrorModifier", "mirror_vertex_groups", "use_mirror_vertex_groups"),
|
||||
("ParticleInstanceModifier", "particle_system_number", "particle_system_index"),
|
||||
("ParticleInstanceModifier", "keep_shape", "use_preserve_shape"),
|
||||
("ShrinkwrapModifier", "cull_back_faces", "use_cull_back_faces"),
|
||||
("ShrinkwrapModifier", "cull_front_faces", "use_cull_front_faces"),
|
||||
("ShrinkwrapModifier", "keep_above_surface", "use_keep_above_surface"),
|
||||
("SimpleDeformModifier", "lock_x_axis", "lock_x"),
|
||||
("SimpleDeformModifier", "lock_y_axis", "lock_y"),
|
||||
("SmokeModifier", "smoke_type", "type"),
|
||||
("SubsurfModifier", "subsurf_uv", "use_subsurf_uv"),
|
||||
("UVProjectModifier", "num_projectors", "projector_count"),
|
||||
("UVProjectModifier", "override_image", "use_image_override"),
|
||||
("WaveModifier", "texture_coordinates", "texture_coords"),
|
||||
("WaveModifier", "x_normal", "use_normal_x"),
|
||||
("WaveModifier", "y_normal", "use_normal_y"),
|
||||
("WaveModifier", "z_normal", "use_normal_z"),
|
||||
("NlaStrip", "blending", "blend_type"),
|
||||
("NlaStrip", "animated_influence", "use_animated_influence"),
|
||||
("NlaStrip", "animated_time", "use_animated_time"),
|
||||
("NlaStrip", "animated_time_cyclic", "use_animated_time_cyclic"),
|
||||
("NlaStrip", "auto_blending", "use_auto_blend"),
|
||||
("CompositorNodeAlphaOver", "convert_premul", "use_premultiply"),
|
||||
("CompositorNodeBlur", "sizex", "size_x"),
|
||||
("CompositorNodeBlur", "sizey", "size_y"),
|
||||
("CompositorNodeChannelMatte", "algorithm", "limit_method"),
|
||||
("CompositorNodeChromaMatte", "acceptance", "tolerance"),
|
||||
("CompositorNodeColorBalance", "correction_formula", "correction_method"),
|
||||
("CompositorNodeColorSpill", "algorithm", "limit_method"),
|
||||
("CompositorNodeColorSpill", "unspill", "use_unspill"),
|
||||
("CompositorNodeCrop", "x2", "max_x"),
|
||||
("CompositorNodeCrop", "y2", "max_y"),
|
||||
("CompositorNodeCrop", "x1", "min_x"),
|
||||
("CompositorNodeCrop", "y1", "min_y"),
|
||||
("CompositorNodeCrop", "crop_size", "use_crop_size"),
|
||||
("CompositorNodeDefocus", "max_blur", "blur_max"),
|
||||
("CompositorNodeDefocus", "gamma_correction", "use_gamma_correction"),
|
||||
("CompositorNodeGlare", "rotate_45", "use_rotate_45"),
|
||||
("CompositorNodeImage", "auto_refresh", "use_auto_refresh"),
|
||||
("CompositorNodeLensdist", "projector", "use_projector"),
|
||||
("CompositorNodeVecBlur", "max_speed", "speed_max"),
|
||||
("CompositorNodeVecBlur", "min_speed", "speed_min"),
|
||||
("ShaderNodeMapping", "maximum", "max"),
|
||||
("ShaderNodeMapping", "minimum", "min"),
|
||||
("ShaderNodeMapping", "clamp_maximum", "use_max"),
|
||||
("ShaderNodeMapping", "clamp_minimum", "use_min"),
|
||||
("VertexPaint", "all_faces", "use_all_faces"),
|
||||
("VertexPaint", "spray", "use_spray"),
|
||||
("ParticleEdit", "add_keys", "default_key_count"),
|
||||
("ParticleEdit", "selection_mode", "select_mode"),
|
||||
("ParticleEdit", "auto_velocity", "use_auto_velocity"),
|
||||
("ParticleEdit", "add_interpolate", "use_default_interpolate"),
|
||||
("ParticleEdit", "emitter_deflect", "use_emitter_deflect"),
|
||||
("ParticleEdit", "fade_time", "use_fade_time"),
|
||||
("ParticleEdit", "keep_lengths", "use_preserve_length"),
|
||||
("ParticleEdit", "keep_root", "use_preserve_root"),
|
||||
("ParticleSystem", "vertex_group_clump_negate", "invert_vertex_group_clump"),
|
||||
("ParticleSystem", "vertex_group_density_negate", "invert_vertex_group_density"),
|
||||
("ParticleSystem", "vertex_group_field_negate", "invert_vertex_group_field"),
|
||||
("ParticleSystem", "vertex_group_kink_negate", "invert_vertex_group_kink"),
|
||||
("ParticleSystem", "vertex_group_length_negate", "invert_vertex_group_length"),
|
||||
("ParticleSystem", "vertex_group_rotation_negate", "invert_vertex_group_rotation"),
|
||||
("ParticleSystem", "vertex_group_roughness1_negate", "invert_vertex_group_roughness_1"),
|
||||
("ParticleSystem", "vertex_group_roughness2_negate", "invert_vertex_group_roughness_2"),
|
||||
("ParticleSystem", "vertex_group_roughness_end_negate", "invert_vertex_group_roughness_end"),
|
||||
("ParticleSystem", "vertex_group_size_negate", "invert_vertex_group_size"),
|
||||
("ParticleSystem", "vertex_group_tangent_negate", "invert_vertex_group_tangent"),
|
||||
("ParticleSystem", "vertex_group_velocity_negate", "invert_vertex_group_velocity"),
|
||||
("ParticleSystem", "hair_dynamics", "use_hair_dynamics"),
|
||||
("ParticleSystem", "keyed_timing", "use_keyed_timing"),
|
||||
("PointDensity", "falloff_softness", "falloff_soft"),
|
||||
("PointDensity", "particle_cache", "particle_cache_space"),
|
||||
("PointDensity", "turbulence_size", "turbulence_scale"),
|
||||
("PointDensity", "turbulence", "use_turbulence"),
|
||||
("PointDensity", "vertices_cache", "vertex_cache_space"),
|
||||
("PoseBone", "ik_lin_weight", "ik_linear_weight"),
|
||||
("PoseBone", "ik_rot_weight", "ik_rotation_weight"),
|
||||
("PoseBone", "ik_limit_x", "use_ik_limit_x"),
|
||||
("PoseBone", "ik_limit_y", "use_ik_limit_y"),
|
||||
("PoseBone", "ik_limit_z", "use_ik_limit_z"),
|
||||
("PoseBone", "ik_lin_control", "use_ik_linear_control"),
|
||||
("PoseBone", "ik_rot_control", "use_ik_rotation_control"),
|
||||
("Bone", "use_hinge", "use_inherit_rotation"),
|
||||
("SPHFluidSettings", "spring_k", "spring_force"),
|
||||
("SPHFluidSettings", "stiffness_k", "stiffness"),
|
||||
("SPHFluidSettings", "stiffness_knear", "stiffness_near"),
|
||||
("SceneGameData", "framing_color", "frame_color"),
|
||||
("SceneGameData", "framing_type", "frame_type"),
|
||||
("SceneGameData", "eye_separation", "stereo_eye_separation"),
|
||||
("SceneGameData", "activity_culling", "use_activity_culling"),
|
||||
("SceneGameData", "auto_start", "use_auto_start"),
|
||||
("SceneGameData", "glsl_extra_textures", "use_glsl_extra_textures"),
|
||||
("SceneGameData", "glsl_lights", "use_glsl_lights"),
|
||||
("SceneGameData", "glsl_nodes", "use_glsl_nodes"),
|
||||
("SceneGameData", "glsl_ramps", "use_glsl_ramps"),
|
||||
("SceneGameData", "glsl_shaders", "use_glsl_shaders"),
|
||||
("SceneGameData", "glsl_shadows", "use_glsl_shadows"),
|
||||
("Sequence", "blend_opacity", "blend_alpha"),
|
||||
("Sequence", "blend_mode", "blend_type"),
|
||||
("Sequence", "frame_final_length", "frame_final_duration"),
|
||||
("Sequence", "use_effect_default_fade", "use_default_fade"),
|
||||
("SequenceColorBalance", "inverse_gain", "invert_gain"),
|
||||
("SequenceColorBalance", "inverse_gamma", "invert_gamma"),
|
||||
("SequenceColorBalance", "inverse_lift", "invert_lift"),
|
||||
("EffectSequence", "multiply_colors", "color_multiply"),
|
||||
("EffectSequence", "de_interlace", "use_deinterlace"),
|
||||
("EffectSequence", "flip_x", "use_flip_x"),
|
||||
("EffectSequence", "flip_y", "use_flip_y"),
|
||||
("EffectSequence", "convert_float", "use_float"),
|
||||
("EffectSequence", "premultiply", "use_premultiply"),
|
||||
("EffectSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
|
||||
("EffectSequence", "proxy_custom_file", "use_proxy_custom_file"),
|
||||
("EffectSequence", "reverse_frames", "use_reverse_frames"),
|
||||
("GlowSequence", "blur_distance", "blur_radius"),
|
||||
("GlowSequence", "only_boost", "use_only_boost"),
|
||||
("SpeedControlSequence", "curve_compress_y", "use_curve_compress_y"),
|
||||
("SpeedControlSequence", "curve_velocity", "use_curve_velocity"),
|
||||
("SpeedControlSequence", "frame_blending", "use_frame_blend"),
|
||||
("TransformSequence", "uniform_scale", "use_uniform_scale"),
|
||||
("ImageSequence", "animation_end_offset", "animation_offset_end"),
|
||||
("ImageSequence", "animation_start_offset", "animation_offset_start"),
|
||||
("ImageSequence", "multiply_colors", "color_multiply"),
|
||||
("ImageSequence", "de_interlace", "use_deinterlace"),
|
||||
("ImageSequence", "flip_x", "use_flip_x"),
|
||||
("ImageSequence", "flip_y", "use_flip_y"),
|
||||
("ImageSequence", "convert_float", "use_float"),
|
||||
("ImageSequence", "premultiply", "use_premultiply"),
|
||||
("ImageSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
|
||||
("ImageSequence", "proxy_custom_file", "use_proxy_custom_file"),
|
||||
("ImageSequence", "reverse_frames", "use_reverse_frames"),
|
||||
("MetaSequence", "animation_end_offset", "animation_offset_end"),
|
||||
("MetaSequence", "animation_start_offset", "animation_offset_start"),
|
||||
("MetaSequence", "multiply_colors", "color_multiply"),
|
||||
("MetaSequence", "de_interlace", "use_deinterlace"),
|
||||
("MetaSequence", "flip_x", "use_flip_x"),
|
||||
("MetaSequence", "flip_y", "use_flip_y"),
|
||||
("MetaSequence", "convert_float", "use_float"),
|
||||
("MetaSequence", "premultiply", "use_premultiply"),
|
||||
("MetaSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
|
||||
("MetaSequence", "proxy_custom_file", "use_proxy_custom_file"),
|
||||
("MetaSequence", "reverse_frames", "use_reverse_frames"),
|
||||
("MovieSequence", "animation_end_offset", "animation_offset_end"),
|
||||
("MovieSequence", "animation_start_offset", "animation_offset_start"),
|
||||
("MovieSequence", "multiply_colors", "color_multiply"),
|
||||
("MovieSequence", "de_interlace", "use_deinterlace"),
|
||||
("MovieSequence", "flip_x", "use_flip_x"),
|
||||
("MovieSequence", "flip_y", "use_flip_y"),
|
||||
("MovieSequence", "convert_float", "use_float"),
|
||||
("MovieSequence", "premultiply", "use_premultiply"),
|
||||
("MovieSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
|
||||
("MovieSequence", "proxy_custom_file", "use_proxy_custom_file"),
|
||||
("MovieSequence", "reverse_frames", "use_reverse_frames"),
|
||||
("MulticamSequence", "animation_end_offset", "animation_offset_end"),
|
||||
("MulticamSequence", "animation_start_offset", "animation_offset_start"),
|
||||
("MulticamSequence", "multiply_colors", "color_multiply"),
|
||||
("MulticamSequence", "de_interlace", "use_deinterlace"),
|
||||
("MulticamSequence", "flip_x", "use_flip_x"),
|
||||
("MulticamSequence", "flip_y", "use_flip_y"),
|
||||
("MulticamSequence", "convert_float", "use_float"),
|
||||
("MulticamSequence", "premultiply", "use_premultiply"),
|
||||
("MulticamSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
|
||||
("MulticamSequence", "proxy_custom_file", "use_proxy_custom_file"),
|
||||
("MulticamSequence", "reverse_frames", "use_reverse_frames"),
|
||||
("SceneSequence", "animation_end_offset", "animation_offset_end"),
|
||||
("SceneSequence", "animation_start_offset", "animation_offset_start"),
|
||||
("SceneSequence", "multiply_colors", "color_multiply"),
|
||||
("SceneSequence", "de_interlace", "use_deinterlace"),
|
||||
("SceneSequence", "flip_x", "use_flip_x"),
|
||||
("SceneSequence", "flip_y", "use_flip_y"),
|
||||
("SceneSequence", "convert_float", "use_float"),
|
||||
("SceneSequence", "premultiply", "use_premultiply"),
|
||||
("SceneSequence", "proxy_custom_directory", "use_proxy_custom_directory"),
|
||||
("SceneSequence", "proxy_custom_file", "use_proxy_custom_file"),
|
||||
("SceneSequence", "reverse_frames", "use_reverse_frames"),
|
||||
("SoundSequence", "animation_end_offset", "animation_offset_end"),
|
||||
("SoundSequence", "animation_start_offset", "animation_offset_start"),
|
||||
("SmokeDomainSettings", "smoke_domain_colli", "collision_extents"),
|
||||
("SmokeDomainSettings", "smoke_cache_high_comp", "point_cache_compress_high_type"),
|
||||
("SmokeDomainSettings", "smoke_cache_comp", "point_cache_compress_type"),
|
||||
("SmokeDomainSettings", "maxres", "resolution_max"),
|
||||
("SmokeDomainSettings", "smoothemitter", "smooth_emitter"),
|
||||
("SmokeDomainSettings", "dissolve_smoke", "use_dissolve_smoke"),
|
||||
("SmokeDomainSettings", "dissolve_smoke_log", "use_dissolve_smoke_log"),
|
||||
("SmokeDomainSettings", "highres", "use_high_resolution"),
|
||||
("SoftBodySettings", "bending", "bend"),
|
||||
("SoftBodySettings", "error_limit", "error_threshold"),
|
||||
("SoftBodySettings", "lcom", "location_mass_center"),
|
||||
("SoftBodySettings", "lrot", "rotation_estimate"),
|
||||
("SoftBodySettings", "lscale", "scale_estimate"),
|
||||
("SoftBodySettings", "maxstep", "step_max"),
|
||||
("SoftBodySettings", "minstep", "step_min"),
|
||||
("SoftBodySettings", "diagnose", "use_diagnose"),
|
||||
("SoftBodySettings", "edge_collision", "use_edge_collision"),
|
||||
("SoftBodySettings", "estimate_matrix", "use_estimate_matrix"),
|
||||
("SoftBodySettings", "face_collision", "use_face_collision"),
|
||||
("SoftBodySettings", "self_collision", "use_self_collision"),
|
||||
("SoftBodySettings", "stiff_quads", "use_stiff_quads"),
|
||||
("TexMapping", "maximum", "max"),
|
||||
("TexMapping", "minimum", "min"),
|
||||
("TexMapping", "has_maximum", "use_max"),
|
||||
("TexMapping", "has_minimum", "use_min"),
|
||||
("TextCharacterFormat", "bold", "use_bold"),
|
||||
("TextCharacterFormat", "italic", "use_italic"),
|
||||
("TextCharacterFormat", "underline", "use_underline"),
|
||||
("TextureSlot", "rgb_to_intensity", "use_rgb_to_intensity"),
|
||||
("TextureSlot", "stencil", "use_stencil"),
|
||||
("LampTextureSlot", "texture_coordinates", "texture_coords"),
|
||||
("LampTextureSlot", "map_color", "use_map_color"),
|
||||
("LampTextureSlot", "map_shadow", "use_map_shadow"),
|
||||
("MaterialTextureSlot", "coloremission_factor", "color_emission_factor"),
|
||||
("MaterialTextureSlot", "colordiff_factor", "diffuse_color_factor"),
|
||||
("MaterialTextureSlot", "x_mapping", "mapping_x"),
|
||||
("MaterialTextureSlot", "y_mapping", "mapping_y"),
|
||||
("MaterialTextureSlot", "z_mapping", "mapping_z"),
|
||||
("MaterialTextureSlot", "colorreflection_factor", "reflection_color_factor"),
|
||||
("MaterialTextureSlot", "colorspec_factor", "specular_color_factor"),
|
||||
("MaterialTextureSlot", "texture_coordinates", "texture_coords"),
|
||||
("MaterialTextureSlot", "colortransmission_factor", "transmission_color_factor"),
|
||||
("MaterialTextureSlot", "from_dupli", "use_from_dupli"),
|
||||
("MaterialTextureSlot", "from_original", "use_from_original"),
|
||||
("MaterialTextureSlot", "map_alpha", "use_map_alpha"),
|
||||
("MaterialTextureSlot", "map_ambient", "use_map_ambient"),
|
||||
("MaterialTextureSlot", "map_colordiff", "use_map_color_diff"),
|
||||
("MaterialTextureSlot", "map_coloremission", "use_map_color_emission"),
|
||||
("MaterialTextureSlot", "map_colorreflection", "use_map_color_reflection"),
|
||||
("MaterialTextureSlot", "map_colorspec", "use_map_color_spec"),
|
||||
("MaterialTextureSlot", "map_colortransmission", "use_map_color_transmission"),
|
||||
("MaterialTextureSlot", "map_density", "use_map_density"),
|
||||
("MaterialTextureSlot", "map_diffuse", "use_map_diffuse"),
|
||||
("MaterialTextureSlot", "map_displacement", "use_map_displacement"),
|
||||
("MaterialTextureSlot", "map_emission", "use_map_emission"),
|
||||
("MaterialTextureSlot", "map_emit", "use_map_emit"),
|
||||
("MaterialTextureSlot", "map_hardness", "use_map_hardness"),
|
||||
("MaterialTextureSlot", "map_mirror", "use_map_mirror"),
|
||||
("MaterialTextureSlot", "map_normal", "use_map_normal"),
|
||||
("MaterialTextureSlot", "map_raymir", "use_map_raymir"),
|
||||
("MaterialTextureSlot", "map_reflection", "use_map_reflect"),
|
||||
("MaterialTextureSlot", "map_scattering", "use_map_scatter"),
|
||||
("MaterialTextureSlot", "map_specular", "use_map_specular"),
|
||||
("MaterialTextureSlot", "map_translucency", "use_map_translucency"),
|
||||
("MaterialTextureSlot", "map_warp", "use_map_warp"),
|
||||
("WorldTextureSlot", "texture_coordinates", "texture_coords"),
|
||||
("WorldTextureSlot", "map_blend", "use_map_blend"),
|
||||
("WorldTextureSlot", "map_horizon", "use_map_horizon"),
|
||||
("WorldTextureSlot", "map_zenith_down", "use_map_zenith_down"),
|
||||
("WorldTextureSlot", "map_zenith_up", "use_map_zenith_up"),
|
||||
("VoxelData", "still_frame_number", "still_frame"),
|
||||
("WorldLighting", "ao_blend_mode", "ao_blend_type"),
|
||||
("WorldLighting", "error_tolerance", "error_threshold"),
|
||||
("WorldLighting", "use_ambient_occlusion", "use_ambient_occlusian"),
|
||||
("WorldLighting", "pixel_cache", "use_cache"),
|
||||
("WorldLighting", "use_environment_lighting", "use_environment_light"),
|
||||
("WorldLighting", "use_indirect_lighting", "use_indirect_light"),
|
||||
("WorldStarsSettings", "color_randomization", "color_random"),
|
||||
("WorldStarsSettings", "min_distance", "distance_min"),
|
||||
("WorldLighting", "falloff", "use_falloff"),
|
||||
("Constraint", "disabled", "is_valid"),
|
||||
("ClampToConstraint", "cyclic", "use_cyclic"),
|
||||
("ImageTexture", "filter", "filter_type"),
|
||||
("ImageTexture", "interpolation", "use_interpolation"),
|
||||
("ImageTexture", "mipmap", "use_mipmap"),
|
||||
("ImageUser", "frames", "frame_duration"),
|
||||
("ImageUser", "offset", "frame_offset"),
|
||||
("ImageUser", "cyclic", "use_cyclic"),
|
||||
("ArmatureModifier", "invert", "invert_vertex_group"),
|
||||
("ArmatureModifier", "quaternion", "use_deform_preserve_volume"),
|
||||
("ArrayModifier", "length", "fit_length"),
|
||||
("BevelModifier", "angle", "angle_limit"),
|
||||
("BuildModifier", "length", "frame_duration"),
|
||||
("BuildModifier", "randomize", "use_random_order"),
|
||||
("CastModifier", "x", "use_x"),
|
||||
("CastModifier", "y", "use_y"),
|
||||
("CastModifier", "z", "use_z"),
|
||||
("ExplodeModifier", "size", "use_size"),
|
||||
("MaskModifier", "invert", "invert_vertex_group"),
|
||||
("MeshDeformModifier", "invert", "invert_vertex_group"),
|
||||
("MeshDeformModifier", "dynamic", "use_dynamic_bind"),
|
||||
("MirrorModifier", "clip", "use_clip"),
|
||||
("MirrorModifier", "x", "use_x"),
|
||||
("MirrorModifier", "y", "use_y"),
|
||||
("MirrorModifier", "z", "use_z"),
|
||||
("ParticleInstanceModifier", "children", "use_children"),
|
||||
("ParticleInstanceModifier", "normal", "use_normal"),
|
||||
("ParticleInstanceModifier", "size", "use_size"),
|
||||
("ShrinkwrapModifier", "negative", "use_negative_direction"),
|
||||
("ShrinkwrapModifier", "positive", "use_positive_direction"),
|
||||
("ShrinkwrapModifier", "x", "use_project_x"),
|
||||
("ShrinkwrapModifier", "y", "use_project_y"),
|
||||
("ShrinkwrapModifier", "z", "use_project_z"),
|
||||
("ShrinkwrapModifier", "mode", "wrap_method"),
|
||||
("SimpleDeformModifier", "mode", "deform_method"),
|
||||
("SimpleDeformModifier", "relative", "use_relative"),
|
||||
("SmoothModifier", "repeat", "iterations"),
|
||||
("SmoothModifier", "x", "use_x"),
|
||||
("SmoothModifier", "y", "use_y"),
|
||||
("SmoothModifier", "z", "use_z"),
|
||||
("SolidifyModifier", "invert", "invert_vertex_group"),
|
||||
("WaveModifier", "cyclic", "use_cyclic"),
|
||||
("WaveModifier", "normals", "use_normal"),
|
||||
("WaveModifier", "x", "use_x"),
|
||||
("WaveModifier", "y", "use_y"),
|
||||
("DampedTrackConstraint", "track", "track_axis"),
|
||||
("FloorConstraint", "sticky", "use_sticky"),
|
||||
("FollowPathConstraint", "forward", "forward_axis"),
|
||||
("FollowPathConstraint", "up", "up_axis"),
|
||||
("LockedTrackConstraint", "lock", "lock_axis"),
|
||||
("LockedTrackConstraint", "track", "track_axis"),
|
||||
("MaintainVolumeConstraint", "axis", "free_axis"),
|
||||
("TrackToConstraint", "track", "track_axis"),
|
||||
("TrackToConstraint", "up", "up_axis"),
|
||||
("GameProperty", "debug", "show_debug"),
|
||||
("Image", "tiles", "use_tiles"),
|
||||
("Lamp", "diffuse", "use_diffuse"),
|
||||
("Lamp", "negative", "use_negative"),
|
||||
("Lamp", "layer", "use_own_layer"),
|
||||
("Lamp", "specular", "use_specular"),
|
||||
("AreaLamp", "dither", "use_dither"),
|
||||
("AreaLamp", "jitter", "use_jitter"),
|
||||
("SpotLamp", "square", "use_square"),
|
||||
("Material", "cubic", "use_cubic"),
|
||||
("Material", "shadows", "use_shadows"),
|
||||
("ParticleSettings", "amount", "count"),
|
||||
("ParticleSettings", "display", "draw_percentage"),
|
||||
("ParticleSettings", "velocity", "show_velocity"),
|
||||
("ParticleSettings", "trand", "use_emit_random"),
|
||||
("ParticleSettings", "parent", "use_parent_particles"),
|
||||
("ParticleSettings", "emitter", "use_render_emitter"),
|
||||
("ParticleSettings", "viewport", "use_simplify_viewport"),
|
||||
("Texture", "brightness", "intensity"),
|
||||
("CloudsTexture", "stype", "cloud_type"),
|
||||
("EnvironmentMapTexture", "filter", "filter_type"),
|
||||
("EnvironmentMapTexture", "mipmap", "use_mipmap"),
|
||||
("MarbleTexture", "stype", "marble_type"),
|
||||
("StucciTexture", "stype", "stucci_type"),
|
||||
("WoodTexture", "stype", "wood_type"),
|
||||
("World", "range", "color_range"),
|
||||
("World", "lighting", "light_settings"),
|
||||
("World", "mist", "mist_settings"),
|
||||
("World", "stars", "star_settings"),
|
||||
("MaterialHalo", "lines", "use_lines"),
|
||||
("MaterialHalo", "ring", "use_ring"),
|
||||
("MaterialHalo", "soft", "use_soft"),
|
||||
("MaterialHalo", "star", "use_star"),
|
||||
("MaterialHalo", "texture", "use_texture"),
|
||||
("MaterialPhysics", "damp", "damping"),
|
||||
("MaterialRaytraceTransparency", "limit", "depth_max"),
|
||||
("NlaStrip", "reversed", "use_reverse"),
|
||||
("CompositorNodeBlur", "bokeh", "use_bokeh"),
|
||||
("CompositorNodeBlur", "gamma", "use_gamma_correction"),
|
||||
("CompositorNodeBlur", "relative", "use_relative"),
|
||||
("CompositorNodeChannelMatte", "high", "limit_max"),
|
||||
("CompositorNodeChannelMatte", "low", "limit_min"),
|
||||
("CompositorNodeChannelMatte", "channel", "matte_channel"),
|
||||
("CompositorNodeChromaMatte", "cutoff", "threshold"),
|
||||
("CompositorNodeColorMatte", "h", "color_hue"),
|
||||
("CompositorNodeColorMatte", "s", "color_saturation"),
|
||||
("CompositorNodeColorMatte", "v", "color_value"),
|
||||
("CompositorNodeDBlur", "wrap", "use_wrap"),
|
||||
("CompositorNodeDefocus", "preview", "use_preview"),
|
||||
("CompositorNodeHueSat", "hue", "color_hue"),
|
||||
("CompositorNodeHueSat", "sat", "color_saturation"),
|
||||
("CompositorNodeHueSat", "val", "color_value"),
|
||||
("CompositorNodeImage", "frames", "frame_duration"),
|
||||
("CompositorNodeImage", "offset", "frame_offset"),
|
||||
("CompositorNodeImage", "start", "frame_start"),
|
||||
("CompositorNodeImage", "cyclic", "use_cyclic"),
|
||||
("CompositorNodeInvert", "alpha", "invert_alpha"),
|
||||
("CompositorNodeInvert", "rgb", "invert_rgb"),
|
||||
("CompositorNodeLensdist", "fit", "use_fit"),
|
||||
("CompositorNodeLensdist", "jitter", "use_jitter"),
|
||||
("CompositorNodeMixRGB", "alpha", "use_alpha"),
|
||||
("CompositorNodeRotate", "filter", "filter_type"),
|
||||
("CompositorNodeTime", "end", "frame_end"),
|
||||
("CompositorNodeTime", "start", "frame_start"),
|
||||
("CompositorNodeVecBlur", "curved", "use_curved"),
|
||||
("ShaderNodeExtendedMaterial", "diffuse", "use_diffuse"),
|
||||
("ShaderNodeExtendedMaterial", "specular", "use_specular"),
|
||||
("ShaderNodeMaterial", "diffuse", "use_diffuse"),
|
||||
("ShaderNodeMaterial", "specular", "use_specular"),
|
||||
("ShaderNodeMixRGB", "alpha", "use_alpha"),
|
||||
("TextureNodeCurveTime", "end", "frame_end"),
|
||||
("TextureNodeCurveTime", "start", "frame_start"),
|
||||
("TextureNodeMixRGB", "alpha", "use_alpha"),
|
||||
("TextureSlot", "negate", "invert"),
|
||||
("TextureSlot", "size", "scale"),
|
||||
("SoftBodySettings", "damp", "damping"),
|
||||
("SequenceCrop", "right", "max_x"),
|
||||
("SequenceCrop", "top", "max_y"),
|
||||
("SequenceCrop", "bottom", "min_x"),
|
||||
("SequenceCrop", "left", "min_y"),
|
||||
("Sequence", "speed_fader", "speed_factor"),
|
||||
("SpeedControlSequence", "global_speed", "multiply_speed"),
|
||||
("SpeedControlSequence", "use_curve_velocity", "use_as_speed"),
|
||||
("SpeedControlSequence", "use_curve_compress_y", "scale_to_length"),
|
||||
]
|
||||
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class UpdateAnimData(bpy.types.Operator):
|
||||
'''Update data paths from 2.53 to edited data paths of drivers and fcurves'''
|
||||
bl_idname = "anim.update_data_paths"
|
||||
bl_label = "Update Animation Data"
|
||||
|
||||
def execute(self, context):
|
||||
import animsys_refactor
|
||||
animsys_refactor.update_data_paths(data_path_update)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
bpy.ops.anim.update_data_paths()
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
@@ -1,294 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
import sys
|
||||
import bpy
|
||||
|
||||
language_id = 'python'
|
||||
|
||||
# store our own __main__ module, not 100% needed
|
||||
# but python expects this in some places
|
||||
_BPY_MAIN_OWN = True
|
||||
|
||||
|
||||
def add_scrollback(text, text_type):
|
||||
for l in text.split('\n'):
|
||||
bpy.ops.console.scrollback_append(text=l.replace('\t', ' '),
|
||||
type=text_type)
|
||||
|
||||
|
||||
def get_console(console_id):
|
||||
'''
|
||||
helper function for console operators
|
||||
currently each text datablock gets its own
|
||||
console - code.InteractiveConsole()
|
||||
...which is stored in this function.
|
||||
|
||||
console_id can be any hashable type
|
||||
'''
|
||||
from code import InteractiveConsole
|
||||
|
||||
consoles = getattr(get_console, "consoles", None)
|
||||
hash_next = hash(bpy.context.window_manager)
|
||||
|
||||
if consoles is None:
|
||||
consoles = get_console.consoles = {}
|
||||
get_console.consoles_namespace_hash = hash_next
|
||||
else:
|
||||
# check if clearning the namespace is needed to avoid a memory leak.
|
||||
# the window manager is normally loaded with new blend files
|
||||
# so this is a reasonable way to deal with namespace clearing.
|
||||
# bpy.data hashing is reset by undo so cant be used.
|
||||
hash_prev = getattr(get_console, "consoles_namespace_hash", 0)
|
||||
|
||||
if hash_prev != hash_next:
|
||||
get_console.consoles_namespace_hash = hash_next
|
||||
consoles.clear()
|
||||
|
||||
console_data = consoles.get(console_id)
|
||||
|
||||
if console_data:
|
||||
console, stdout, stderr = console_data
|
||||
|
||||
# XXX, bug in python 3.1.2 ? (worked in 3.1.1)
|
||||
# seems there is no way to clear StringIO objects for writing, have to make new ones each time.
|
||||
import io
|
||||
stdout = io.StringIO()
|
||||
stderr = io.StringIO()
|
||||
else:
|
||||
if _BPY_MAIN_OWN:
|
||||
import types
|
||||
bpy_main_mod = types.ModuleType("__main__")
|
||||
namespace = bpy_main_mod.__dict__
|
||||
else:
|
||||
namespace = {}
|
||||
|
||||
namespace["__builtins__"] = sys.modules["builtins"]
|
||||
namespace["bpy"] = bpy
|
||||
namespace["C"] = bpy.context
|
||||
|
||||
console = InteractiveConsole(locals=namespace, filename="<blender_console>")
|
||||
|
||||
console.push("from mathutils import *")
|
||||
console.push("from math import *")
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
console._bpy_main_mod = bpy_main_mod
|
||||
|
||||
import io
|
||||
stdout = io.StringIO()
|
||||
stderr = io.StringIO()
|
||||
|
||||
consoles[console_id] = console, stdout, stderr
|
||||
|
||||
return console, stdout, stderr
|
||||
|
||||
|
||||
# Both prompts must be the same length
|
||||
PROMPT = '>>> '
|
||||
PROMPT_MULTI = '... '
|
||||
|
||||
|
||||
def execute(context):
|
||||
sc = context.space_data
|
||||
|
||||
try:
|
||||
line_object = sc.history[-1]
|
||||
except:
|
||||
return {'CANCELLED'}
|
||||
|
||||
console, stdout, stderr = get_console(hash(context.region))
|
||||
|
||||
# redirect output
|
||||
sys.stdout = stdout
|
||||
sys.stderr = stderr
|
||||
|
||||
# dont allow the stdin to be used, can lock blender.
|
||||
stdin_backup = sys.stdin
|
||||
sys.stdin = None
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
main_mod_back = sys.modules["__main__"]
|
||||
sys.modules["__main__"] = console._bpy_main_mod
|
||||
|
||||
# incase exception happens
|
||||
line = "" # incase of encodingf error
|
||||
is_multiline = False
|
||||
|
||||
try:
|
||||
line = line_object.body
|
||||
|
||||
# run the console, "\n" executes a multiline statement
|
||||
line_exec = line if line.strip() else "\n"
|
||||
|
||||
is_multiline = console.push(line_exec)
|
||||
except:
|
||||
# unlikely, but this can happen with unicode errors for example.
|
||||
import traceback
|
||||
stderr.write(traceback.format_exc())
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
sys.modules["__main__"] = main_mod_back
|
||||
|
||||
stdout.seek(0)
|
||||
stderr.seek(0)
|
||||
|
||||
output = stdout.read()
|
||||
output_err = stderr.read()
|
||||
|
||||
# cleanup
|
||||
sys.stdout = sys.__stdout__
|
||||
sys.stderr = sys.__stderr__
|
||||
sys.last_traceback = None
|
||||
|
||||
# So we can reuse, clear all data
|
||||
stdout.truncate(0)
|
||||
stderr.truncate(0)
|
||||
|
||||
# special exception. its possible the command loaded a new user interface
|
||||
if hash(sc) != hash(context.space_data):
|
||||
return
|
||||
|
||||
bpy.ops.console.scrollback_append(text=sc.prompt + line, type='INPUT')
|
||||
|
||||
if is_multiline:
|
||||
sc.prompt = PROMPT_MULTI
|
||||
else:
|
||||
sc.prompt = PROMPT
|
||||
|
||||
# insert a new blank line
|
||||
bpy.ops.console.history_append(text="", current_character=0,
|
||||
remove_duplicates=True)
|
||||
|
||||
# Insert the output into the editor
|
||||
# not quite correct because the order might have changed,
|
||||
# but ok 99% of the time.
|
||||
if output:
|
||||
add_scrollback(output, 'OUTPUT')
|
||||
if output_err:
|
||||
add_scrollback(output_err, 'ERROR')
|
||||
|
||||
# restore the stdin
|
||||
sys.stdin = stdin_backup
|
||||
|
||||
# execute any hooks
|
||||
for func, args in execute.hooks:
|
||||
func(*args)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
execute.hooks = []
|
||||
|
||||
|
||||
def autocomplete(context):
|
||||
from console import intellisense
|
||||
|
||||
sc = context.space_data
|
||||
|
||||
console = get_console(hash(context.region))[0]
|
||||
|
||||
if not console:
|
||||
return {'CANCELLED'}
|
||||
|
||||
# dont allow the stdin to be used, can lock blender.
|
||||
# note: unlikely stdin would be used for autocomp. but its possible.
|
||||
stdin_backup = sys.stdin
|
||||
sys.stdin = None
|
||||
|
||||
scrollback = ""
|
||||
scrollback_error = ""
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
main_mod_back = sys.modules["__main__"]
|
||||
sys.modules["__main__"] = console._bpy_main_mod
|
||||
|
||||
try:
|
||||
current_line = sc.history[-1]
|
||||
line = current_line.body
|
||||
|
||||
# This function isnt aware of the text editor or being an operator
|
||||
# just does the autocomp then copy its results back
|
||||
current_line.body, current_line.current_character, scrollback = \
|
||||
intellisense.expand(
|
||||
line=current_line.body,
|
||||
cursor=current_line.current_character,
|
||||
namespace=console.locals,
|
||||
private=bpy.app.debug)
|
||||
except:
|
||||
# unlikely, but this can happen with unicode errors for example.
|
||||
# or if the api attribute access its self causes an error.
|
||||
import traceback
|
||||
scrollback_error = traceback.format_exc()
|
||||
|
||||
if _BPY_MAIN_OWN:
|
||||
sys.modules["__main__"] = main_mod_back
|
||||
|
||||
# Separate automplete output by command prompts
|
||||
if scrollback != '':
|
||||
bpy.ops.console.scrollback_append(text=sc.prompt + current_line.body, type='INPUT')
|
||||
|
||||
# Now we need to copy back the line from blender back into the
|
||||
# text editor. This will change when we dont use the text editor
|
||||
# anymore
|
||||
if scrollback:
|
||||
add_scrollback(scrollback, 'INFO')
|
||||
|
||||
if scrollback_error:
|
||||
add_scrollback(scrollback_error, 'ERROR')
|
||||
|
||||
# restore the stdin
|
||||
sys.stdin = stdin_backup
|
||||
|
||||
context.area.tag_redraw()
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def banner(context):
|
||||
sc = context.space_data
|
||||
version_string = sys.version.strip().replace('\n', ' ')
|
||||
|
||||
add_scrollback("PYTHON INTERACTIVE CONSOLE %s" % version_string, 'OUTPUT')
|
||||
add_scrollback("", 'OUTPUT')
|
||||
add_scrollback("Command History: Up/Down Arrow", 'OUTPUT')
|
||||
add_scrollback("Cursor: Left/Right Home/End", 'OUTPUT')
|
||||
add_scrollback("Remove: Backspace/Delete", 'OUTPUT')
|
||||
add_scrollback("Execute: Enter", 'OUTPUT')
|
||||
add_scrollback("Autocomplete: Ctrl+Space", 'OUTPUT')
|
||||
add_scrollback("Ctrl +/- Wheel: Zoom", 'OUTPUT')
|
||||
add_scrollback("Builtin Modules: bpy, bpy.data, bpy.ops, bpy.props, bpy.types, bpy.context, bgl, blf, mathutils", 'OUTPUT')
|
||||
add_scrollback("Convenience Imports: from mathutils import *; from math import *", 'OUTPUT')
|
||||
add_scrollback("", 'OUTPUT')
|
||||
add_scrollback(" WARNING!!! Blender 2.5 API is subject to change, see API reference for more info.", 'ERROR')
|
||||
add_scrollback("", 'OUTPUT')
|
||||
sc.prompt = PROMPT
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
pass
|
||||
|
||||
|
||||
def unregister():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,90 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
from math import *
|
||||
import bpy
|
||||
from mathutils import *
|
||||
|
||||
|
||||
def main(context):
|
||||
def cleanupEulCurve(fcv):
|
||||
keys = []
|
||||
|
||||
for k in fcv.keyframe_points:
|
||||
keys.append([k.handle_left.copy(), k.co.copy(), k.handle_right.copy()])
|
||||
print(keys)
|
||||
|
||||
for i in range(len(keys)):
|
||||
cur = keys[i]
|
||||
prev = keys[i - 1] if i > 0 else None
|
||||
next = keys[i + 1] if i < len(keys) - 1 else None
|
||||
|
||||
if prev is None:
|
||||
continue
|
||||
|
||||
th = pi
|
||||
if abs(prev[1][1] - cur[1][1]) >= th: # more than 180 degree jump
|
||||
fac = pi * 2.0
|
||||
if prev[1][1] > cur[1][1]:
|
||||
while abs(cur[1][1] - prev[1][1]) >= th: # < prev[1][1]:
|
||||
cur[0][1] += fac
|
||||
cur[1][1] += fac
|
||||
cur[2][1] += fac
|
||||
elif prev[1][1] < cur[1][1]:
|
||||
while abs(cur[1][1] - prev[1][1]) >= th:
|
||||
cur[0][1] -= fac
|
||||
cur[1][1] -= fac
|
||||
cur[2][1] -= fac
|
||||
|
||||
for i in range(len(keys)):
|
||||
for x in range(2):
|
||||
fcv.keyframe_points[i].handle_left[x] = keys[i][0][x]
|
||||
fcv.keyframe_points[i].co[x] = keys[i][1][x]
|
||||
fcv.keyframe_points[i].handle_right[x] = keys[i][2][x]
|
||||
|
||||
flist = bpy.context.active_object.animation_data.action.fcurves
|
||||
for f in flist:
|
||||
if f.select and f.data_path.endswith("rotation_euler"):
|
||||
cleanupEulCurve(f)
|
||||
|
||||
|
||||
class DiscontFilterOp(bpy.types.Operator):
|
||||
"""Fixes the most common causes of gimbal lock in the fcurves of the active bone"""
|
||||
bl_idname = "graph.euler_filter"
|
||||
bl_label = "Filter out discontinuities in the active fcurves"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object != None
|
||||
|
||||
def execute(self, context):
|
||||
main(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,205 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
from bpy.props import StringProperty
|
||||
|
||||
|
||||
class EditExternally(bpy.types.Operator):
|
||||
'''Edit image in an external application'''
|
||||
bl_idname = "image.external_edit"
|
||||
bl_label = "Image Edit Externally"
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
filepath = StringProperty(name="File Path", description="Path to an image file", maxlen=1024, default="")
|
||||
|
||||
def _editor_guess(self, context):
|
||||
import platform
|
||||
try:
|
||||
system = platform.system()
|
||||
except UnicodeDecodeError:
|
||||
import sys
|
||||
system = sys.platform
|
||||
|
||||
image_editor = context.user_preferences.filepaths.image_editor
|
||||
|
||||
# use image editor in the preferences when available.
|
||||
if not image_editor:
|
||||
if system in ('Windows', 'win32'):
|
||||
image_editor = ["start"] # not tested!
|
||||
elif system == 'Darwin':
|
||||
image_editor = ["open"]
|
||||
else:
|
||||
image_editor = ["gimp"]
|
||||
else:
|
||||
if system == 'Darwin':
|
||||
# blender file selector treats .app as a folder
|
||||
# and will include a trailing backslash, so we strip it.
|
||||
image_editor.rstrip('\\')
|
||||
image_editor = ["open", "-a", image_editor]
|
||||
else:
|
||||
image_editor = [image_editor]
|
||||
|
||||
return image_editor
|
||||
|
||||
def execute(self, context):
|
||||
import os
|
||||
import subprocess
|
||||
filepath = bpy.path.abspath(self.filepath)
|
||||
|
||||
if not os.path.exists(filepath):
|
||||
self.report('ERROR', "Image path '%s' not found." % filepath)
|
||||
return {'CANCELLED'}
|
||||
|
||||
cmd = self._editor_guess(context) + [filepath]
|
||||
|
||||
subprocess.Popen(cmd)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
try:
|
||||
filepath = context.space_data.image.filepath
|
||||
except:
|
||||
self.report({'ERROR'}, "Image not found on disk")
|
||||
return {'CANCELLED'}
|
||||
|
||||
self.filepath = filepath
|
||||
self.execute(context)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SaveDirty(bpy.types.Operator):
|
||||
"""Save all modified textures"""
|
||||
bl_idname = "image.save_dirty"
|
||||
bl_label = "Save Dirty"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
unique_paths = set()
|
||||
for image in bpy.data.images:
|
||||
if image.is_dirty:
|
||||
filepath = bpy.path.abspath(image.filepath)
|
||||
if "\\" not in filepath and "/" not in filepath:
|
||||
self.report({'WARNING'}, "Invalid path: " + filepath)
|
||||
elif filepath in unique_paths:
|
||||
self.report({'WARNING'}, "Path used by more then one image: " + filepath)
|
||||
else:
|
||||
unique_paths.add(filepath)
|
||||
image.save()
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class ProjectEdit(bpy.types.Operator):
|
||||
"""Edit a snapshot if the viewport in an external image editor"""
|
||||
bl_idname = "image.project_edit"
|
||||
bl_label = "Project Edit"
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
_proj_hack = [""]
|
||||
|
||||
def execute(self, context):
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
EXT = "png" # could be made an option but for now ok
|
||||
|
||||
for image in bpy.data.images:
|
||||
image.tag = True
|
||||
|
||||
if 'FINISHED' not in bpy.ops.paint.image_from_view():
|
||||
return {'CANCELLED'}
|
||||
|
||||
image_new = None
|
||||
for image in bpy.data.images:
|
||||
if not image.tag:
|
||||
image_new = image
|
||||
break
|
||||
|
||||
if not image_new:
|
||||
self.report({'ERROR'}, "Could not make new image")
|
||||
return {'CANCELLED'}
|
||||
|
||||
filepath = os.path.basename(bpy.data.filepath)
|
||||
filepath = os.path.splitext(filepath)[0]
|
||||
# filepath = bpy.path.clean_name(filepath) # fixes <memory> rubbish, needs checking
|
||||
|
||||
if filepath.startswith(".") or filepath == "":
|
||||
# TODO, have a way to check if the file is saved, assume startup.blend
|
||||
tmpdir = context.user_preferences.filepaths.temporary_directory
|
||||
filepath = os.path.join(tmpdir, "project_edit")
|
||||
else:
|
||||
filepath = "//" + filepath
|
||||
|
||||
obj = context.object
|
||||
|
||||
if obj:
|
||||
filepath += "_" + bpy.path.clean_name(obj.name)
|
||||
|
||||
filepath_final = filepath + "." + EXT
|
||||
i = 0
|
||||
|
||||
while os.path.exists(bpy.path.abspath(filepath_final)):
|
||||
filepath_final = filepath + ("%.3d.%s" % (i, EXT))
|
||||
i += 1
|
||||
|
||||
image_new.name = os.path.basename(filepath_final)
|
||||
ProjectEdit._proj_hack[0] = image_new.name
|
||||
|
||||
image_new.filepath_raw = filepath_final # TODO, filepath raw is crummy
|
||||
image_new.file_format = 'PNG'
|
||||
image_new.save()
|
||||
|
||||
bpy.ops.image.external_edit(filepath=filepath_final)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class ProjectApply(bpy.types.Operator):
|
||||
"""Project edited image back onto the object"""
|
||||
bl_idname = "image.project_apply"
|
||||
bl_label = "Project Apply"
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
image_name = ProjectEdit._proj_hack[0] # TODO, deal with this nicer
|
||||
|
||||
try:
|
||||
image = bpy.data.images[image_name]
|
||||
except KeyError:
|
||||
self.report({'ERROR'}, "Could not find image '%s'" % image_name)
|
||||
return {'CANCELLED'}
|
||||
|
||||
image.reload()
|
||||
bpy.ops.paint.project_image(image=image_name)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,183 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
class MeshSelectInteriorFaces(bpy.types.Operator):
|
||||
'''Select faces where all edges have more then 2 face users.'''
|
||||
|
||||
bl_idname = "mesh.faces_select_interior"
|
||||
bl_label = "Select Interior Faces"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ob = context.active_object
|
||||
return (ob and ob.type == 'MESH')
|
||||
|
||||
def execute(self, context):
|
||||
ob = context.active_object
|
||||
context.tool_settings.mesh_select_mode = False, False, True
|
||||
is_editmode = (ob.mode == 'EDIT')
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
|
||||
mesh = ob.data
|
||||
|
||||
face_list = mesh.faces[:]
|
||||
face_edge_keys = [face.edge_keys for face in face_list]
|
||||
|
||||
edge_face_count = mesh.edge_face_count_dict
|
||||
|
||||
def test_interior(index):
|
||||
for key in face_edge_keys[index]:
|
||||
if edge_face_count[key] < 3:
|
||||
return False
|
||||
return True
|
||||
|
||||
for index, face in enumerate(face_list):
|
||||
if(test_interior(index)):
|
||||
face.select = True
|
||||
else:
|
||||
face.select = False
|
||||
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MeshMirrorUV(bpy.types.Operator):
|
||||
'''Copy mirror UV coordinates on the X axis based on a mirrored mesh'''
|
||||
bl_idname = "mesh.faces_miror_uv"
|
||||
bl_label = "Copy Mirrored UV coords"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
ob = context.active_object
|
||||
return (ob and ob.type == 'MESH')
|
||||
|
||||
def execute(self, context):
|
||||
DIR = 1 # TODO, make an option
|
||||
|
||||
from mathutils import Vector
|
||||
|
||||
ob = context.active_object
|
||||
is_editmode = (ob.mode == 'EDIT')
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
|
||||
mesh = ob.data
|
||||
|
||||
# mirror lookups
|
||||
mirror_gt = {}
|
||||
mirror_lt = {}
|
||||
|
||||
vcos = [v.co.to_tuple(5) for v in mesh.vertices]
|
||||
|
||||
for i, co in enumerate(vcos):
|
||||
if co[0] > 0.0:
|
||||
mirror_gt[co] = i
|
||||
elif co[0] < 0.0:
|
||||
mirror_lt[co] = i
|
||||
else:
|
||||
mirror_gt[co] = i
|
||||
mirror_lt[co] = i
|
||||
|
||||
#for i, v in enumerate(mesh.vertices):
|
||||
vmap = {}
|
||||
for mirror_a, mirror_b in (mirror_gt, mirror_lt), (mirror_lt, mirror_gt):
|
||||
for co, i in mirror_a.items():
|
||||
nco = (-co[0], co[1], co[2])
|
||||
j = mirror_b.get(nco)
|
||||
if j is not None:
|
||||
vmap[i] = j
|
||||
|
||||
active_uv_layer = None
|
||||
for lay in mesh.uv_textures:
|
||||
if lay.active:
|
||||
active_uv_layer = lay.data
|
||||
break
|
||||
|
||||
fuvs = [(uv.uv1, uv.uv2, uv.uv3, uv.uv4) for uv in active_uv_layer]
|
||||
fuvs_cpy = [(uv[0].copy(), uv[1].copy(), uv[2].copy(), uv[3].copy()) for uv in fuvs]
|
||||
|
||||
# as a list
|
||||
faces = mesh.faces[:]
|
||||
|
||||
fuvsel = [(False not in uv.select_uv) for uv in active_uv_layer]
|
||||
fcents = [f.center for f in faces]
|
||||
|
||||
# find mirror faces
|
||||
mirror_fm = {}
|
||||
for i, f in enumerate(faces):
|
||||
verts = f.vertices[:]
|
||||
verts.sort()
|
||||
verts = tuple(verts)
|
||||
mirror_fm[verts] = i
|
||||
|
||||
fmap = {}
|
||||
for i, f in enumerate(faces):
|
||||
verts = [vmap.get(j) for j in f.vertices]
|
||||
if None not in verts:
|
||||
verts.sort()
|
||||
j = mirror_fm.get(tuple(verts))
|
||||
if j is not None:
|
||||
fmap[i] = j
|
||||
|
||||
done = [False] * len(faces)
|
||||
for i, j in fmap.items():
|
||||
|
||||
if not fuvsel[i] or not fuvsel[j]:
|
||||
continue
|
||||
elif DIR == 0 and fcents[i][0] < 0.0:
|
||||
continue
|
||||
elif DIR == 1 and fcents[i][0] > 0.0:
|
||||
continue
|
||||
|
||||
# copy UVs
|
||||
uv1 = fuvs[i]
|
||||
uv2 = fuvs_cpy[j]
|
||||
|
||||
# get the correct rotation
|
||||
v1 = faces[j].vertices[:]
|
||||
v2 = [vmap[k] for k in faces[i].vertices[:]]
|
||||
|
||||
for k in range(len(uv1)):
|
||||
k_map = v1.index(v2[k])
|
||||
uv1[k].x = - (uv2[k_map].x - 0.5) + 0.5
|
||||
uv1[k].y = uv2[k_map].y
|
||||
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,185 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def pose_info():
|
||||
from mathutils import Matrix
|
||||
|
||||
info = {}
|
||||
|
||||
obj = bpy.context.object
|
||||
pose = obj.pose
|
||||
|
||||
pose_items = pose.bones.items()
|
||||
|
||||
for name, pbone in pose_items:
|
||||
binfo = {}
|
||||
bone = pbone.bone
|
||||
|
||||
binfo["parent"] = getattr(bone.parent, "name", None)
|
||||
binfo["bone"] = bone
|
||||
binfo["pbone"] = pbone
|
||||
binfo["matrix_local"] = bone.matrix_local.copy()
|
||||
try:
|
||||
binfo["matrix_local_inv"] = binfo["matrix_local"].inverted()
|
||||
except:
|
||||
binfo["matrix_local_inv"] = Matrix()
|
||||
|
||||
binfo["matrix"] = bone.matrix.copy()
|
||||
binfo["matrix_pose"] = pbone.matrix.copy()
|
||||
try:
|
||||
binfo["matrix_pose_inv"] = binfo["matrix_pose"].inverted()
|
||||
except:
|
||||
binfo["matrix_pose_inv"] = Matrix()
|
||||
|
||||
print(binfo["matrix_pose"])
|
||||
info[name] = binfo
|
||||
|
||||
for name, pbone in pose_items:
|
||||
binfo = info[name]
|
||||
binfo_parent = binfo.get("parent", None)
|
||||
if binfo_parent:
|
||||
binfo_parent = info[binfo_parent]
|
||||
|
||||
matrix = binfo["matrix_pose"]
|
||||
rest_matrix = binfo["matrix_local"]
|
||||
|
||||
if binfo_parent:
|
||||
matrix = binfo_parent["matrix_pose_inv"] * matrix
|
||||
rest_matrix = binfo_parent["matrix_local_inv"] * rest_matrix
|
||||
|
||||
matrix = rest_matrix.inverted() * matrix
|
||||
|
||||
binfo["matrix_key"] = matrix.copy()
|
||||
|
||||
return info
|
||||
|
||||
|
||||
def bake(frame_start, frame_end, step=1, only_selected=False):
|
||||
scene = bpy.context.scene
|
||||
obj = bpy.context.object
|
||||
pose = obj.pose
|
||||
|
||||
info_ls = []
|
||||
|
||||
frame_range = range(frame_start, frame_end + 1, step)
|
||||
|
||||
# could spped this up by applying steps here too...
|
||||
for f in frame_range:
|
||||
scene.frame_set(f)
|
||||
|
||||
info = pose_info()
|
||||
info_ls.append(info)
|
||||
f += 1
|
||||
|
||||
action = bpy.data.actions.new("Action")
|
||||
|
||||
bpy.context.object.animation_data.action = action
|
||||
|
||||
pose_items = pose.bones.items()
|
||||
|
||||
for name, pbone in pose_items:
|
||||
if only_selected and not pbone.select:
|
||||
continue
|
||||
|
||||
for f in frame_range:
|
||||
matrix = info_ls[int((f - frame_start) / step)][name]["matrix_key"]
|
||||
|
||||
#pbone.location = matrix.to_translation()
|
||||
#pbone.rotation_quaternion = matrix.to_quaternion()
|
||||
pbone.matrix_basis = matrix
|
||||
|
||||
pbone.keyframe_insert("location", -1, f, name)
|
||||
|
||||
rotation_mode = pbone.rotation_mode
|
||||
|
||||
if rotation_mode == 'QUATERNION':
|
||||
pbone.keyframe_insert("rotation_quaternion", -1, f, name)
|
||||
elif rotation_mode == 'AXIS_ANGLE':
|
||||
pbone.keyframe_insert("rotation_axis_angle", -1, f, name)
|
||||
else: # euler, XYZ, ZXY etc
|
||||
pbone.keyframe_insert("rotation_euler", -1, f, name)
|
||||
|
||||
pbone.keyframe_insert("scale", -1, f, name)
|
||||
|
||||
return action
|
||||
|
||||
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
class BakeAction(bpy.types.Operator):
|
||||
'''Bake animation to an Action'''
|
||||
bl_idname = "nla.bake"
|
||||
bl_label = "Bake Action"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
frame_start = IntProperty(name="Start Frame",
|
||||
description="Start frame for baking",
|
||||
default=1, min=1, max=300000)
|
||||
frame_end = IntProperty(name="End Frame",
|
||||
description="End frame for baking",
|
||||
default=250, min=1, max=300000)
|
||||
step = IntProperty(name="Frame Step",
|
||||
description="Frame Step",
|
||||
default=1, min=1, max=120)
|
||||
only_selected = BoolProperty(name="Only Selected",
|
||||
default=True)
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
action = bake(self.frame_start, self.frame_end, self.step, self.only_selected)
|
||||
|
||||
# basic cleanup, could move elsewhere
|
||||
for fcu in action.fcurves:
|
||||
keyframe_points = fcu.keyframe_points
|
||||
i = 1
|
||||
while i < len(fcu.keyframe_points) - 1:
|
||||
val_prev = keyframe_points[i - 1].co[1]
|
||||
val_next = keyframe_points[i + 1].co[1]
|
||||
val = keyframe_points[i].co[1]
|
||||
|
||||
if abs(val - val_prev) + abs(val - val_next) < 0.0001:
|
||||
keyframe_points.remove(keyframe_points[i])
|
||||
else:
|
||||
i += 1
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
|
||||
#def menu_func(self, context):
|
||||
# self.layout.operator(BakeAction.bl_idname, text="Bake Armature Action")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,574 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
class SelectPattern(bpy.types.Operator):
|
||||
'''Select object matching a naming pattern'''
|
||||
bl_idname = "object.select_pattern"
|
||||
bl_label = "Select Pattern"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
pattern = StringProperty(name="Pattern", description="Name filter using '*' and '?' wildcard chars", maxlen=32, default="*")
|
||||
case_sensitive = BoolProperty(name="Case Sensitive", description="Do a case sensitive compare", default=False)
|
||||
extend = BoolProperty(name="Extend", description="Extend the existing selection", default=True)
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
import fnmatch
|
||||
|
||||
if self.case_sensitive:
|
||||
pattern_match = fnmatch.fnmatchcase
|
||||
else:
|
||||
pattern_match = lambda a, b: fnmatch.fnmatchcase(a.upper(), b.upper())
|
||||
|
||||
obj = context.object
|
||||
if obj and obj.mode == 'POSE':
|
||||
items = obj.data.bones
|
||||
elif obj and obj.type == 'ARMATURE' and obj.mode == 'EDIT':
|
||||
items = obj.data.edit_bones
|
||||
else:
|
||||
items = context.visible_objects
|
||||
|
||||
# Can be pose bones or objects
|
||||
for item in items:
|
||||
if pattern_match(item.name, self.pattern):
|
||||
item.select = True
|
||||
elif not self.extend:
|
||||
item.select = False
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_popup(self, event)
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
|
||||
layout.prop(self, "pattern")
|
||||
row = layout.row()
|
||||
row.prop(self, "case_sensitive")
|
||||
row.prop(self, "extend")
|
||||
|
||||
|
||||
class SelectCamera(bpy.types.Operator):
|
||||
'''Select object matching a naming pattern'''
|
||||
bl_idname = "object.select_camera"
|
||||
bl_label = "Select Camera"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.scene.camera is not None
|
||||
|
||||
def execute(self, context):
|
||||
scene = context.scene
|
||||
camera = scene.camera
|
||||
if camera.name not in scene.objects:
|
||||
self.report({'WARNING'}, "Active camera is not in this scene")
|
||||
|
||||
context.scene.objects.active = camera
|
||||
camera.select = True
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SelectHierarchy(bpy.types.Operator):
|
||||
'''Select object relative to the active objects position in the hierarchy'''
|
||||
bl_idname = "object.select_hierarchy"
|
||||
bl_label = "Select Hierarchy"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
direction = EnumProperty(items=(
|
||||
('PARENT', "Parent", ""),
|
||||
('CHILD', "Child", "")),
|
||||
name="Direction",
|
||||
description="Direction to select in the hierarchy",
|
||||
default='PARENT')
|
||||
|
||||
extend = BoolProperty(name="Extend", description="Extend the existing selection", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.object
|
||||
|
||||
def execute(self, context):
|
||||
select_new = []
|
||||
act_new = None
|
||||
|
||||
selected_objects = context.selected_objects
|
||||
obj_act = context.object
|
||||
|
||||
if context.object not in selected_objects:
|
||||
selected_objects.append(context.object)
|
||||
|
||||
if self.direction == 'PARENT':
|
||||
for obj in selected_objects:
|
||||
parent = obj.parent
|
||||
|
||||
if parent:
|
||||
if obj_act == obj:
|
||||
act_new = parent
|
||||
|
||||
select_new.append(parent)
|
||||
|
||||
else:
|
||||
for obj in selected_objects:
|
||||
select_new.extend(obj.children)
|
||||
|
||||
if select_new:
|
||||
select_new.sort(key=lambda obj_iter: obj_iter.name)
|
||||
act_new = select_new[0]
|
||||
|
||||
# dont edit any object settings above this
|
||||
if select_new:
|
||||
if not self.extend:
|
||||
bpy.ops.object.select_all(action='DESELECT')
|
||||
|
||||
for obj in select_new:
|
||||
obj.select = True
|
||||
|
||||
context.scene.objects.active = act_new
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
class SubdivisionSet(bpy.types.Operator):
|
||||
'''Sets a Subdivision Surface Level (1-5)'''
|
||||
|
||||
bl_idname = "object.subdivision_set"
|
||||
bl_label = "Subdivision Set"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
level = IntProperty(name="Level",
|
||||
default=1, min=-100, max=100, soft_min=-6, soft_max=6)
|
||||
|
||||
relative = BoolProperty(name="Relative", description="Apply the subsurf level as an offset relative to the current level", default=False)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obs = context.selected_editable_objects
|
||||
return (obs is not None)
|
||||
|
||||
def execute(self, context):
|
||||
level = self.level
|
||||
relative = self.relative
|
||||
|
||||
if relative and level == 0:
|
||||
return {'CANCELLED'} # nothing to do
|
||||
|
||||
def set_object_subd(obj):
|
||||
for mod in obj.modifiers:
|
||||
if mod.type == 'MULTIRES':
|
||||
if not relative:
|
||||
if level <= mod.total_levels:
|
||||
if obj.mode == 'SCULPT':
|
||||
if mod.sculpt_levels != level:
|
||||
mod.sculpt_levels = level
|
||||
elif obj.mode == 'OBJECT':
|
||||
if mod.levels != level:
|
||||
mod.levels = level
|
||||
return
|
||||
else:
|
||||
if obj.mode == 'SCULPT':
|
||||
if mod.sculpt_levels + level <= mod.total_levels:
|
||||
mod.sculpt_levels += level
|
||||
elif obj.mode == 'OBJECT':
|
||||
if mod.levels + level <= mod.total_levels:
|
||||
mod.levels += level
|
||||
return
|
||||
|
||||
elif mod.type == 'SUBSURF':
|
||||
if relative:
|
||||
mod.levels += level
|
||||
else:
|
||||
if mod.levels != level:
|
||||
mod.levels = level
|
||||
|
||||
return
|
||||
|
||||
# add a new modifier
|
||||
try:
|
||||
mod = obj.modifiers.new("Subsurf", 'SUBSURF')
|
||||
mod.levels = level
|
||||
except:
|
||||
self.report({'WARNING'}, "Modifiers cannot be added to object: " + obj.name)
|
||||
|
||||
for obj in context.selected_editable_objects:
|
||||
set_object_subd(obj)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class ShapeTransfer(bpy.types.Operator):
|
||||
'''Copy another selected objects active shape to this one by applying the relative offsets'''
|
||||
|
||||
bl_idname = "object.shape_key_transfer"
|
||||
bl_label = "Transfer Shape Key"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
mode = EnumProperty(items=(
|
||||
('OFFSET', "Offset", "Apply the relative positional offset"),
|
||||
('RELATIVE_FACE', "Relative Face", "Calculate the geometricly relative position (using faces)."),
|
||||
('RELATIVE_EDGE', "Relative Edge", "Calculate the geometricly relative position (using edges).")),
|
||||
name="Transformation Mode",
|
||||
description="Method to apply relative shape positions to the new shape",
|
||||
default='OFFSET')
|
||||
|
||||
use_clamp = BoolProperty(name="Clamp Offset",
|
||||
description="Clamp the transformation to the distance each vertex moves in the original shape.",
|
||||
default=False)
|
||||
|
||||
def _main(self, ob_act, objects, mode='OFFSET', use_clamp=False):
|
||||
|
||||
def me_nos(verts):
|
||||
return [v.normal.copy() for v in verts]
|
||||
|
||||
def me_cos(verts):
|
||||
return [v.co.copy() for v in verts]
|
||||
|
||||
def ob_add_shape(ob, name):
|
||||
me = ob.data
|
||||
key = ob.shape_key_add(from_mix=False)
|
||||
if len(me.shape_keys.keys) == 1:
|
||||
key.name = "Basis"
|
||||
key = ob.shape_key_add(from_mix=False) # we need a rest
|
||||
key.name = name
|
||||
ob.active_shape_key_index = len(me.shape_keys.keys) - 1
|
||||
ob.show_only_shape_key = True
|
||||
|
||||
from mathutils.geometry import barycentric_transform
|
||||
from mathutils import Vector
|
||||
|
||||
if use_clamp and mode == 'OFFSET':
|
||||
use_clamp = False
|
||||
|
||||
me = ob_act.data
|
||||
orig_key_name = ob_act.active_shape_key.name
|
||||
|
||||
orig_shape_coords = me_cos(ob_act.active_shape_key.data)
|
||||
|
||||
orig_normals = me_nos(me.vertices)
|
||||
# orig_coords = me_cos(me.vertices) # the actual mverts location isnt as relyable as the base shape :S
|
||||
orig_coords = me_cos(me.shape_keys.keys[0].data)
|
||||
|
||||
for ob_other in objects:
|
||||
me_other = ob_other.data
|
||||
if len(me_other.vertices) != len(me.vertices):
|
||||
self.report({'WARNING'}, "Skipping '%s', vertex count differs" % ob_other.name)
|
||||
continue
|
||||
|
||||
target_normals = me_nos(me_other.vertices)
|
||||
if me_other.shape_keys:
|
||||
target_coords = me_cos(me_other.shape_keys.keys[0].data)
|
||||
else:
|
||||
target_coords = me_cos(me_other.vertices)
|
||||
|
||||
ob_add_shape(ob_other, orig_key_name)
|
||||
|
||||
# editing the final coords, only list that stores wrapped coords
|
||||
target_shape_coords = [v.co for v in ob_other.active_shape_key.data]
|
||||
|
||||
median_coords = [[] for i in range(len(me.vertices))]
|
||||
|
||||
# Method 1, edge
|
||||
if mode == 'OFFSET':
|
||||
for i, vert_cos in enumerate(median_coords):
|
||||
vert_cos.append(target_coords[i] + (orig_shape_coords[i] - orig_coords[i]))
|
||||
|
||||
elif mode == 'RELATIVE_FACE':
|
||||
for face in me.faces:
|
||||
i1, i2, i3, i4 = face.vertices_raw
|
||||
if i4 != 0:
|
||||
pt = barycentric_transform(orig_shape_coords[i1],
|
||||
orig_coords[i4], orig_coords[i1], orig_coords[i2],
|
||||
target_coords[i4], target_coords[i1], target_coords[i2])
|
||||
median_coords[i1].append(pt)
|
||||
|
||||
pt = barycentric_transform(orig_shape_coords[i2],
|
||||
orig_coords[i1], orig_coords[i2], orig_coords[i3],
|
||||
target_coords[i1], target_coords[i2], target_coords[i3])
|
||||
median_coords[i2].append(pt)
|
||||
|
||||
pt = barycentric_transform(orig_shape_coords[i3],
|
||||
orig_coords[i2], orig_coords[i3], orig_coords[i4],
|
||||
target_coords[i2], target_coords[i3], target_coords[i4])
|
||||
median_coords[i3].append(pt)
|
||||
|
||||
pt = barycentric_transform(orig_shape_coords[i4],
|
||||
orig_coords[i3], orig_coords[i4], orig_coords[i1],
|
||||
target_coords[i3], target_coords[i4], target_coords[i1])
|
||||
median_coords[i4].append(pt)
|
||||
|
||||
else:
|
||||
pt = barycentric_transform(orig_shape_coords[i1],
|
||||
orig_coords[i3], orig_coords[i1], orig_coords[i2],
|
||||
target_coords[i3], target_coords[i1], target_coords[i2])
|
||||
median_coords[i1].append(pt)
|
||||
|
||||
pt = barycentric_transform(orig_shape_coords[i2],
|
||||
orig_coords[i1], orig_coords[i2], orig_coords[i3],
|
||||
target_coords[i1], target_coords[i2], target_coords[i3])
|
||||
median_coords[i2].append(pt)
|
||||
|
||||
pt = barycentric_transform(orig_shape_coords[i3],
|
||||
orig_coords[i2], orig_coords[i3], orig_coords[i1],
|
||||
target_coords[i2], target_coords[i3], target_coords[i1])
|
||||
median_coords[i3].append(pt)
|
||||
|
||||
elif mode == 'RELATIVE_EDGE':
|
||||
for ed in me.edges:
|
||||
i1, i2 = ed.vertices
|
||||
v1, v2 = orig_coords[i1], orig_coords[i2]
|
||||
edge_length = (v1 - v2).length
|
||||
n1loc = v1 + orig_normals[i1] * edge_length
|
||||
n2loc = v2 + orig_normals[i2] * edge_length
|
||||
|
||||
# now get the target nloc's
|
||||
v1_to, v2_to = target_coords[i1], target_coords[i2]
|
||||
edlen_to = (v1_to - v2_to).length
|
||||
n1loc_to = v1_to + target_normals[i1] * edlen_to
|
||||
n2loc_to = v2_to + target_normals[i2] * edlen_to
|
||||
|
||||
pt = barycentric_transform(orig_shape_coords[i1],
|
||||
v2, v1, n1loc,
|
||||
v2_to, v1_to, n1loc_to)
|
||||
median_coords[i1].append(pt)
|
||||
|
||||
pt = barycentric_transform(orig_shape_coords[i2],
|
||||
v1, v2, n2loc,
|
||||
v1_to, v2_to, n2loc_to)
|
||||
median_coords[i2].append(pt)
|
||||
|
||||
# apply the offsets to the new shape
|
||||
from functools import reduce
|
||||
VectorAdd = Vector.__add__
|
||||
|
||||
for i, vert_cos in enumerate(median_coords):
|
||||
if vert_cos:
|
||||
co = reduce(VectorAdd, vert_cos) / len(vert_cos)
|
||||
|
||||
if use_clamp:
|
||||
# clamp to the same movement as the original
|
||||
# breaks copy between different scaled meshes.
|
||||
len_from = (orig_shape_coords[i] - orig_coords[i]).length
|
||||
ofs = co - target_coords[i]
|
||||
ofs.length = len_from
|
||||
co = target_coords[i] + ofs
|
||||
|
||||
target_shape_coords[i][:] = co
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj and obj.mode != 'EDIT')
|
||||
|
||||
def execute(self, context):
|
||||
C = bpy.context
|
||||
ob_act = C.active_object
|
||||
objects = [ob for ob in C.selected_editable_objects if ob != ob_act]
|
||||
|
||||
if 1: # swap from/to, means we cant copy to many at once.
|
||||
if len(objects) != 1:
|
||||
self.report({'ERROR'}, "Expected one other selected mesh object to copy from")
|
||||
return {'CANCELLED'}
|
||||
ob_act, objects = objects[0], [ob_act]
|
||||
|
||||
if ob_act.type != 'MESH':
|
||||
self.report({'ERROR'}, "Other object is not a mesh.")
|
||||
return {'CANCELLED'}
|
||||
|
||||
if ob_act.active_shape_key is None:
|
||||
self.report({'ERROR'}, "Other object has no shape key")
|
||||
return {'CANCELLED'}
|
||||
return self._main(ob_act, objects, self.mode, self.use_clamp)
|
||||
|
||||
|
||||
class JoinUVs(bpy.types.Operator):
|
||||
'''Copy UV Layout to objects with matching geometry'''
|
||||
bl_idname = "object.join_uvs"
|
||||
bl_label = "Join as UVs"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj and obj.type == 'MESH')
|
||||
|
||||
def _main(self, context):
|
||||
import array
|
||||
obj = context.active_object
|
||||
mesh = obj.data
|
||||
|
||||
is_editmode = (obj.mode == 'EDIT')
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
|
||||
if not mesh.uv_textures:
|
||||
self.report({'WARNING'}, "Object: %s, Mesh: '%s' has no UVs\n" % (obj.name, mesh.name))
|
||||
else:
|
||||
len_faces = len(mesh.faces)
|
||||
|
||||
uv_array = array.array('f', [0.0] * 8) * len_faces # seems to be the fastest way to create an array
|
||||
mesh.uv_textures.active.data.foreach_get("uv_raw", uv_array)
|
||||
|
||||
objects = context.selected_editable_objects[:]
|
||||
|
||||
for obj_other in objects:
|
||||
if obj_other.type == 'MESH':
|
||||
obj_other.data.tag = False
|
||||
|
||||
for obj_other in objects:
|
||||
if obj_other != obj and obj_other.type == 'MESH':
|
||||
mesh_other = obj_other.data
|
||||
if mesh_other != mesh:
|
||||
if mesh_other.tag == False:
|
||||
mesh_other.tag = True
|
||||
|
||||
if len(mesh_other.faces) != len_faces:
|
||||
self.report({'WARNING'}, "Object: %s, Mesh: '%s' has %d faces, expected %d\n" % (obj_other.name, mesh_other.name, len(mesh_other.faces), len_faces))
|
||||
else:
|
||||
uv_other = mesh_other.uv_textures.active
|
||||
if not uv_other:
|
||||
uv_other = mesh_other.uv_textures.new() # should return the texture it adds
|
||||
|
||||
# finally do the copy
|
||||
uv_other.data.foreach_set("uv_raw", uv_array)
|
||||
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
|
||||
def execute(self, context):
|
||||
self._main(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class MakeDupliFace(bpy.types.Operator):
|
||||
'''Make linked objects into dupli-faces'''
|
||||
bl_idname = "object.make_dupli_face"
|
||||
bl_label = "Make Dupli-Face"
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj and obj.type == 'MESH')
|
||||
|
||||
def _main(self, context):
|
||||
from mathutils import Vector
|
||||
|
||||
SCALE_FAC = 0.01
|
||||
offset = 0.5 * SCALE_FAC
|
||||
base_tri = Vector((-offset, -offset, 0.0)), Vector((offset, -offset, 0.0)), Vector((offset, offset, 0.0)), Vector((-offset, offset, 0.0))
|
||||
|
||||
def matrix_to_quat(matrix):
|
||||
# scale = matrix.median_scale
|
||||
trans = matrix.to_translation()
|
||||
rot = matrix.to_3x3() # also contains scale
|
||||
|
||||
return [(b * rot) + trans for b in base_tri]
|
||||
scene = bpy.context.scene
|
||||
linked = {}
|
||||
for obj in bpy.context.selected_objects:
|
||||
data = obj.data
|
||||
if data:
|
||||
linked.setdefault(data, []).append(obj)
|
||||
|
||||
for data, objects in linked.items():
|
||||
face_verts = [axis for obj in objects for v in matrix_to_quat(obj.matrix_world) for axis in v]
|
||||
faces = list(range(len(face_verts) // 3))
|
||||
|
||||
mesh = bpy.data.meshes.new(data.name + "_dupli")
|
||||
|
||||
mesh.vertices.add(len(face_verts) // 3)
|
||||
mesh.faces.add(len(face_verts) // 12)
|
||||
|
||||
mesh.vertices.foreach_set("co", face_verts)
|
||||
mesh.faces.foreach_set("vertices_raw", faces)
|
||||
mesh.update() # generates edge data
|
||||
|
||||
# pick an object to use
|
||||
obj = objects[0]
|
||||
|
||||
ob_new = bpy.data.objects.new(mesh.name, mesh)
|
||||
base = scene.objects.link(ob_new)
|
||||
base.layers[:] = obj.layers
|
||||
|
||||
ob_inst = bpy.data.objects.new(data.name, data)
|
||||
base = scene.objects.link(ob_inst)
|
||||
base.layers[:] = obj.layers
|
||||
|
||||
for obj in objects:
|
||||
scene.objects.unlink(obj)
|
||||
|
||||
ob_new.dupli_type = 'FACES'
|
||||
ob_inst.parent = ob_new
|
||||
ob_new.use_dupli_faces_scale = True
|
||||
ob_new.dupli_faces_scale = 1.0 / SCALE_FAC
|
||||
|
||||
def execute(self, context):
|
||||
self._main(context)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class IsolateTypeRender(bpy.types.Operator):
|
||||
'''Hide unselected render objects of same type as active by setting the hide render flag'''
|
||||
bl_idname = "object.isolate_type_render"
|
||||
bl_label = "Restrict Render Unselected"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
act_type = context.object.type
|
||||
|
||||
for obj in context.visible_objects:
|
||||
|
||||
if obj.select:
|
||||
obj.hide_render = False
|
||||
else:
|
||||
if obj.type == act_type:
|
||||
obj.hide_render = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class ClearAllRestrictRender(bpy.types.Operator):
|
||||
'''Reveal all render objects by setting the hide render flag'''
|
||||
bl_idname = "object.hide_render_clear_all"
|
||||
bl_label = "Clear All Restrict Render"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
def execute(self, context):
|
||||
for obj in context.scene.objects:
|
||||
obj.hide_render = False
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,300 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
from mathutils import Vector
|
||||
|
||||
|
||||
def align_objects(align_x, align_y, align_z, align_mode, relative_to):
|
||||
|
||||
cursor = bpy.context.scene.cursor_location
|
||||
|
||||
Left_Up_Front_SEL = [0.0, 0.0, 0.0]
|
||||
Right_Down_Back_SEL = [0.0, 0.0, 0.0]
|
||||
|
||||
flag_first = True
|
||||
|
||||
objs = []
|
||||
|
||||
for obj in bpy.context.selected_objects:
|
||||
matrix_world = obj.matrix_world
|
||||
bb_world = [Vector(v[:]) * matrix_world for v in obj.bound_box]
|
||||
objs.append((obj, bb_world))
|
||||
|
||||
if not objs:
|
||||
return False
|
||||
|
||||
for obj, bb_world in objs:
|
||||
Left_Up_Front = bb_world[1]
|
||||
Right_Down_Back = bb_world[7]
|
||||
|
||||
# Active Center
|
||||
|
||||
if obj == bpy.context.active_object:
|
||||
|
||||
center_active_x = (Left_Up_Front[0] + Right_Down_Back[0]) / 2.0
|
||||
center_active_y = (Left_Up_Front[1] + Right_Down_Back[1]) / 2.0
|
||||
center_active_z = (Left_Up_Front[2] + Right_Down_Back[2]) / 2.0
|
||||
|
||||
size_active_x = (Right_Down_Back[0] - Left_Up_Front[0]) / 2.0
|
||||
size_active_y = (Right_Down_Back[1] - Left_Up_Front[1]) / 2.0
|
||||
size_active_z = (Left_Up_Front[2] - Right_Down_Back[2]) / 2.0
|
||||
|
||||
# Selection Center
|
||||
|
||||
if flag_first:
|
||||
flag_first = False
|
||||
|
||||
Left_Up_Front_SEL[0] = Left_Up_Front[0]
|
||||
Left_Up_Front_SEL[1] = Left_Up_Front[1]
|
||||
Left_Up_Front_SEL[2] = Left_Up_Front[2]
|
||||
|
||||
Right_Down_Back_SEL[0] = Right_Down_Back[0]
|
||||
Right_Down_Back_SEL[1] = Right_Down_Back[1]
|
||||
Right_Down_Back_SEL[2] = Right_Down_Back[2]
|
||||
|
||||
else:
|
||||
# X axis
|
||||
if Left_Up_Front[0] < Left_Up_Front_SEL[0]:
|
||||
Left_Up_Front_SEL[0] = Left_Up_Front[0]
|
||||
# Y axis
|
||||
if Left_Up_Front[1] < Left_Up_Front_SEL[1]:
|
||||
Left_Up_Front_SEL[1] = Left_Up_Front[1]
|
||||
# Z axis
|
||||
if Left_Up_Front[2] > Left_Up_Front_SEL[2]:
|
||||
Left_Up_Front_SEL[2] = Left_Up_Front[2]
|
||||
|
||||
# X axis
|
||||
if Right_Down_Back[0] > Right_Down_Back_SEL[0]:
|
||||
Right_Down_Back_SEL[0] = Right_Down_Back[0]
|
||||
# Y axis
|
||||
if Right_Down_Back[1] > Right_Down_Back_SEL[1]:
|
||||
Right_Down_Back_SEL[1] = Right_Down_Back[1]
|
||||
# Z axis
|
||||
if Right_Down_Back[2] < Right_Down_Back_SEL[2]:
|
||||
Right_Down_Back_SEL[2] = Right_Down_Back[2]
|
||||
|
||||
center_sel_x = (Left_Up_Front_SEL[0] + Right_Down_Back_SEL[0]) / 2.0
|
||||
center_sel_y = (Left_Up_Front_SEL[1] + Right_Down_Back_SEL[1]) / 2.0
|
||||
center_sel_z = (Left_Up_Front_SEL[2] + Right_Down_Back_SEL[2]) / 2.0
|
||||
|
||||
# Main Loop
|
||||
|
||||
for obj, bb_world in objs:
|
||||
|
||||
loc_world = obj.location
|
||||
bb_world = [Vector(v[:]) * obj.matrix_world for v in obj.bound_box]
|
||||
|
||||
Left_Up_Front = bb_world[1]
|
||||
Right_Down_Back = bb_world[7]
|
||||
|
||||
center_x = (Left_Up_Front[0] + Right_Down_Back[0]) / 2.0
|
||||
center_y = (Left_Up_Front[1] + Right_Down_Back[1]) / 2.0
|
||||
center_z = (Left_Up_Front[2] + Right_Down_Back[2]) / 2.0
|
||||
|
||||
positive_x = Right_Down_Back[0]
|
||||
positive_y = Right_Down_Back[1]
|
||||
positive_z = Left_Up_Front[2]
|
||||
|
||||
negative_x = Left_Up_Front[0]
|
||||
negative_y = Left_Up_Front[1]
|
||||
negative_z = Right_Down_Back[2]
|
||||
|
||||
obj_loc = obj.location
|
||||
|
||||
if align_x:
|
||||
|
||||
# Align Mode
|
||||
|
||||
if relative_to == 'OPT_4': # Active relative
|
||||
if align_mode == 'OPT_1':
|
||||
obj_x = obj_loc[0] - negative_x - size_active_x
|
||||
|
||||
elif align_mode == 'OPT_3':
|
||||
obj_x = obj_loc[0] - positive_x + size_active_x
|
||||
|
||||
else: # Everything else relative
|
||||
if align_mode == 'OPT_1':
|
||||
obj_x = obj_loc[0] - negative_x
|
||||
|
||||
elif align_mode == 'OPT_3':
|
||||
obj_x = obj_loc[0] - positive_x
|
||||
|
||||
if align_mode == 'OPT_2': # All relative
|
||||
obj_x = obj_loc[0] - center_x
|
||||
|
||||
# Relative To
|
||||
|
||||
if relative_to == 'OPT_1':
|
||||
loc_x = obj_x
|
||||
|
||||
elif relative_to == 'OPT_2':
|
||||
loc_x = obj_x + cursor[0]
|
||||
|
||||
elif relative_to == 'OPT_3':
|
||||
loc_x = obj_x + center_sel_x
|
||||
|
||||
elif relative_to == 'OPT_4':
|
||||
loc_x = obj_x + center_active_x
|
||||
|
||||
obj.location[0] = loc_x
|
||||
|
||||
if align_y:
|
||||
# Align Mode
|
||||
|
||||
if relative_to == 'OPT_4': # Active relative
|
||||
if align_mode == 'OPT_1':
|
||||
obj_y = obj_loc[1] - negative_y - size_active_y
|
||||
|
||||
elif align_mode == 'OPT_3':
|
||||
obj_y = obj_loc[1] - positive_y + size_active_y
|
||||
|
||||
else: # Everything else relative
|
||||
if align_mode == 'OPT_1':
|
||||
obj_y = obj_loc[1] - negative_y
|
||||
|
||||
elif align_mode == 'OPT_3':
|
||||
obj_y = obj_loc[1] - positive_y
|
||||
|
||||
if align_mode == 'OPT_2': # All relative
|
||||
obj_y = obj_loc[1] - center_y
|
||||
|
||||
# Relative To
|
||||
|
||||
if relative_to == 'OPT_1':
|
||||
loc_y = obj_y
|
||||
|
||||
elif relative_to == 'OPT_2':
|
||||
loc_y = obj_y + cursor[1]
|
||||
|
||||
elif relative_to == 'OPT_3':
|
||||
loc_y = obj_y + center_sel_y
|
||||
|
||||
elif relative_to == 'OPT_4':
|
||||
loc_y = obj_y + center_active_y
|
||||
|
||||
obj.location[1] = loc_y
|
||||
|
||||
if align_z:
|
||||
# Align Mode
|
||||
if relative_to == 'OPT_4': # Active relative
|
||||
if align_mode == 'OPT_1':
|
||||
obj_z = obj_loc[2] - negative_z - size_active_z
|
||||
|
||||
elif align_mode == 'OPT_3':
|
||||
obj_z = obj_loc[2] - positive_z + size_active_z
|
||||
|
||||
else: # Everything else relative
|
||||
if align_mode == 'OPT_1':
|
||||
obj_z = obj_loc[2] - negative_z
|
||||
|
||||
elif align_mode == 'OPT_3':
|
||||
obj_z = obj_loc[2] - positive_z
|
||||
|
||||
if align_mode == 'OPT_2': # All relative
|
||||
obj_z = obj_loc[2] - center_z
|
||||
|
||||
# Relative To
|
||||
|
||||
if relative_to == 'OPT_1':
|
||||
loc_z = obj_z
|
||||
|
||||
elif relative_to == 'OPT_2':
|
||||
loc_z = obj_z + cursor[2]
|
||||
|
||||
elif relative_to == 'OPT_3':
|
||||
loc_z = obj_z + center_sel_z
|
||||
|
||||
elif relative_to == 'OPT_4':
|
||||
loc_z = obj_z + center_active_z
|
||||
|
||||
obj.location[2] = loc_z
|
||||
|
||||
return True
|
||||
|
||||
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
class AlignObjects(bpy.types.Operator):
|
||||
'''Align Objects'''
|
||||
bl_idname = "object.align"
|
||||
bl_label = "Align Objects"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
align_mode = bpy.props.EnumProperty(items=(
|
||||
('OPT_1', "Negative Sides", ""),
|
||||
('OPT_2', "Centers", ""),
|
||||
('OPT_3', "Positive Sides", "")),
|
||||
name="Align Mode:",
|
||||
description="",
|
||||
default='OPT_2')
|
||||
|
||||
relative_to = bpy.props.EnumProperty(items=(
|
||||
('OPT_1', "Scene Origin", ""),
|
||||
('OPT_2', "3D Cursor", ""),
|
||||
('OPT_3', "Selection", ""),
|
||||
('OPT_4', "Active", "")),
|
||||
name="Relative To:",
|
||||
description="",
|
||||
default='OPT_4')
|
||||
|
||||
align_axis = EnumProperty(items=(
|
||||
('X', "X", ""),
|
||||
('Y', "Y", ""),
|
||||
('Z', "Z", ""),
|
||||
),
|
||||
name="Align",
|
||||
description="Align to axis",
|
||||
options={'ENUM_FLAG'})
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.mode == 'OBJECT'
|
||||
|
||||
def execute(self, context):
|
||||
align_axis = self.align_axis
|
||||
ret = align_objects('X' in align_axis, 'Y' in align_axis, 'Z' in align_axis, self.align_mode, self.relative_to)
|
||||
|
||||
if not ret:
|
||||
self.report({'WARNING'}, "No objects with bound-box selected")
|
||||
return {'CANCELLED'}
|
||||
else:
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
if context.mode == 'OBJECT':
|
||||
self.layout.operator(AlignObjects.bl_idname,
|
||||
text="Align Objects")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.types.VIEW3D_MT_transform.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
bpy.types.VIEW3D_MT_transform.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,167 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def randomize_selected(seed, delta, loc, rot, scale, scale_even):
|
||||
|
||||
import random
|
||||
from random import uniform
|
||||
from mathutils import Vector
|
||||
|
||||
random.seed(seed)
|
||||
|
||||
def rand_vec(vec_range):
|
||||
return Vector(uniform(-val, val) for val in vec_range)
|
||||
|
||||
for obj in bpy.context.selected_objects:
|
||||
|
||||
if loc:
|
||||
if delta:
|
||||
obj.delta_location += rand_vec(loc)
|
||||
else:
|
||||
obj.location += rand_vec(loc)
|
||||
else: # otherwise the values change under us
|
||||
uniform(0.0, 0.0), uniform(0.0, 0.0), uniform(0.0, 0.0)
|
||||
|
||||
if rot: # TODO, non euler's
|
||||
vec = rand_vec(rot)
|
||||
if delta:
|
||||
obj.delta_rotation_euler[0] += vec[0]
|
||||
obj.delta_rotation_euler[1] += vec[1]
|
||||
obj.delta_rotation_euler[2] += vec[2]
|
||||
else:
|
||||
obj.rotation_euler[0] += vec[0]
|
||||
obj.rotation_euler[1] += vec[1]
|
||||
obj.rotation_euler[2] += vec[2]
|
||||
else:
|
||||
uniform(0.0, 0.0), uniform(0.0, 0.0), uniform(0.0, 0.0)
|
||||
|
||||
if scale:
|
||||
if delta:
|
||||
org_sca_x, org_sca_y, org_sca_z = obj.delta_scale
|
||||
else:
|
||||
org_sca_x, org_sca_y, org_sca_z = obj.scale
|
||||
|
||||
if scale_even:
|
||||
sca_x = sca_y = sca_z = uniform(scale[0], - scale[0])
|
||||
uniform(0.0, 0.0), uniform(0.0, 0.0)
|
||||
else:
|
||||
sca_x, sca_y, sca_z = rand_vec(scale)
|
||||
|
||||
if scale_even:
|
||||
aX = -(sca_x * org_sca_x) + org_sca_x
|
||||
aY = -(sca_x * org_sca_y) + org_sca_y
|
||||
aZ = -(sca_x * org_sca_z) + org_sca_z
|
||||
else:
|
||||
aX = sca_x + org_sca_x
|
||||
aY = sca_y + org_sca_y
|
||||
aZ = sca_z + org_sca_z
|
||||
|
||||
if delta:
|
||||
obj.delta_scale = aX, aY, aZ
|
||||
else:
|
||||
obj.scale = aX, aY, aZ
|
||||
else:
|
||||
uniform(0.0, 0.0), uniform(0.0, 0.0), uniform(0.0, 0.0)
|
||||
|
||||
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
class RandomizeLocRotSize(bpy.types.Operator):
|
||||
'''Randomize objects loc/rot/scale'''
|
||||
bl_idname = "object.randomize_transform"
|
||||
bl_label = "Randomize Transform"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
random_seed = IntProperty(name="Random Seed",
|
||||
description="Seed value for the random generator",
|
||||
default=0, min=0, max=1000)
|
||||
|
||||
use_delta = BoolProperty(name="Transform Delta",
|
||||
description="Randomize delta transform values instead of regular transform", default=False)
|
||||
|
||||
use_loc = BoolProperty(name="Randomize Location",
|
||||
description="Randomize the location values", default=True)
|
||||
|
||||
loc = FloatVectorProperty(name="Location",
|
||||
description="Maximun distance the objects can spread over each axis",
|
||||
default=(0.0, 0.0, 0.0), min=-100.0, max=100.0, subtype='TRANSLATION')
|
||||
|
||||
use_rot = BoolProperty(name="Randomize Rotation",
|
||||
description="Randomize the rotation values", default=True)
|
||||
|
||||
rot = FloatVectorProperty(name="Rotation",
|
||||
description="Maximun rotation over each axis",
|
||||
default=(0.0, 0.0, 0.0), min=-180.0, max=180.0, subtype='TRANSLATION')
|
||||
|
||||
use_scale = BoolProperty(name="Randomize Scale",
|
||||
description="Randomize the scale values", default=True)
|
||||
|
||||
scale_even = BoolProperty(name="Scale Even",
|
||||
description="Use the same scale value for all axis", default=False)
|
||||
|
||||
'''scale_min = FloatProperty(name="Minimun Scale Factor",
|
||||
description="Lowest scale percentage possible",
|
||||
default=0.15, min=-1.0, max=1.0, precision=3)'''
|
||||
|
||||
scale = FloatVectorProperty(name="Scale",
|
||||
description="Maximum scale randomization over each axis",
|
||||
default=(0.0, 0.0, 0.0), min=-100.0, max=100.0, subtype='TRANSLATION')
|
||||
|
||||
def execute(self, context):
|
||||
from math import radians
|
||||
|
||||
seed = self.random_seed
|
||||
|
||||
delta = self.use_delta
|
||||
|
||||
loc = None if not self.use_loc else self.loc
|
||||
rot = None if not self.use_rot else self.rot * radians(1.0)
|
||||
scale = None if not self.use_scale else self.scale
|
||||
|
||||
scale_even = self.scale_even
|
||||
#scale_min = self.scale_min
|
||||
|
||||
randomize_selected(seed, delta, loc, rot, scale, scale_even)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
if context.mode == 'OBJECT':
|
||||
self.layout.operator(RandomizeLocRotSize.bl_idname,
|
||||
text="Randomize Transform")
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.types.VIEW3D_MT_transform.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
bpy.types.VIEW3D_MT_transform.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,364 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
import os
|
||||
|
||||
|
||||
class AddPresetBase():
|
||||
'''Base preset class, only for subclassing
|
||||
subclasses must define
|
||||
- preset_values
|
||||
- preset_subdir '''
|
||||
# bl_idname = "script.preset_base_add"
|
||||
# bl_label = "Add a Python Preset"
|
||||
bl_options = {'REGISTER'} # only because invoke_props_popup requires.
|
||||
|
||||
name = bpy.props.StringProperty(name="Name", description="Name of the preset, used to make the path name", maxlen=64, default="")
|
||||
remove_active = bpy.props.BoolProperty(default=False, options={'HIDDEN'})
|
||||
|
||||
@staticmethod
|
||||
def as_filename(name): # could reuse for other presets
|
||||
for char in " !@#$%^&*(){}:\";'[]<>,.\\/?":
|
||||
name = name.replace(char, '_')
|
||||
return name.lower().strip()
|
||||
|
||||
def execute(self, context):
|
||||
import os
|
||||
|
||||
if hasattr(self, "pre_cb"):
|
||||
self.pre_cb(context)
|
||||
|
||||
preset_menu_class = getattr(bpy.types, self.preset_menu)
|
||||
|
||||
if not self.remove_active:
|
||||
|
||||
if not self.name:
|
||||
return {'FINISHED'}
|
||||
|
||||
filename = self.as_filename(self.name)
|
||||
|
||||
target_path = bpy.utils.user_resource('SCRIPTS', os.path.join("presets", self.preset_subdir), create=True)
|
||||
|
||||
if not target_path:
|
||||
self.report({'WARNING'}, "Failed to create presets path")
|
||||
return {'CANCELLED'}
|
||||
|
||||
filepath = os.path.join(target_path, filename) + ".py"
|
||||
|
||||
if hasattr(self, "add"):
|
||||
self.add(context, filepath)
|
||||
else:
|
||||
file_preset = open(filepath, 'w')
|
||||
file_preset.write("import bpy\n")
|
||||
|
||||
if hasattr(self, "preset_defines"):
|
||||
for rna_path in self.preset_defines:
|
||||
exec(rna_path)
|
||||
file_preset.write("%s\n" % rna_path)
|
||||
file_preset.write("\n")
|
||||
|
||||
for rna_path in self.preset_values:
|
||||
value = eval(rna_path)
|
||||
# convert thin wrapped sequences to simple lists to repr()
|
||||
try:
|
||||
value = value[:]
|
||||
except:
|
||||
pass
|
||||
|
||||
file_preset.write("%s = %r\n" % (rna_path, value))
|
||||
|
||||
file_preset.close()
|
||||
|
||||
preset_menu_class.bl_label = bpy.path.display_name(filename)
|
||||
|
||||
else:
|
||||
preset_active = preset_menu_class.bl_label
|
||||
|
||||
# fairly sloppy but convenient.
|
||||
filepath = bpy.utils.preset_find(preset_active, self.preset_subdir)
|
||||
|
||||
if not filepath:
|
||||
filepath = bpy.utils.preset_find(preset_active, self.preset_subdir, display_name=True)
|
||||
|
||||
if not filepath:
|
||||
return {'CANCELLED'}
|
||||
|
||||
if hasattr(self, "remove"):
|
||||
self.remove(context, filepath)
|
||||
else:
|
||||
try:
|
||||
os.remove(filepath)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
# XXX, stupid!
|
||||
preset_menu_class.bl_label = "Presets"
|
||||
|
||||
if hasattr(self, "post_cb"):
|
||||
self.post_cb(context)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def check(self, context):
|
||||
self.name = self.as_filename(self.name)
|
||||
|
||||
def invoke(self, context, event):
|
||||
if not self.remove_active:
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
else:
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
class ExecutePreset(bpy.types.Operator):
|
||||
''' Executes a preset '''
|
||||
bl_idname = "script.execute_preset"
|
||||
bl_label = "Execute a Python Preset"
|
||||
|
||||
filepath = bpy.props.StringProperty(name="Path", description="Path of the Python file to execute", maxlen=512, default="")
|
||||
menu_idname = bpy.props.StringProperty(name="Menu ID Name", description="ID name of the menu this was called from", default="")
|
||||
|
||||
def execute(self, context):
|
||||
from os.path import basename
|
||||
filepath = self.filepath
|
||||
|
||||
# change the menu title to the most recently chosen option
|
||||
preset_class = getattr(bpy.types, self.menu_idname)
|
||||
preset_class.bl_label = bpy.path.display_name(basename(filepath))
|
||||
|
||||
# execute the preset using script.python_file_run
|
||||
bpy.ops.script.python_file_run(filepath=filepath)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class AddPresetRender(AddPresetBase, bpy.types.Operator):
|
||||
'''Add a Render Preset'''
|
||||
bl_idname = "render.preset_add"
|
||||
bl_label = "Add Render Preset"
|
||||
preset_menu = "RENDER_MT_presets"
|
||||
|
||||
preset_defines = [
|
||||
"scene = bpy.context.scene"
|
||||
]
|
||||
|
||||
preset_values = [
|
||||
"scene.render.field_order",
|
||||
"scene.render.fps",
|
||||
"scene.render.fps_base",
|
||||
"scene.render.pixel_aspect_x",
|
||||
"scene.render.pixel_aspect_y",
|
||||
"scene.render.resolution_percentage",
|
||||
"scene.render.resolution_x",
|
||||
"scene.render.resolution_y",
|
||||
"scene.render.use_fields",
|
||||
"scene.render.use_fields_still",
|
||||
]
|
||||
|
||||
preset_subdir = "render"
|
||||
|
||||
|
||||
class AddPresetSSS(AddPresetBase, bpy.types.Operator):
|
||||
'''Add a Subsurface Scattering Preset'''
|
||||
bl_idname = "material.sss_preset_add"
|
||||
bl_label = "Add SSS Preset"
|
||||
preset_menu = "MATERIAL_MT_sss_presets"
|
||||
|
||||
preset_defines = [
|
||||
"material = (bpy.context.material.active_node_material if bpy.context.material.active_node_material else bpy.context.material)"
|
||||
]
|
||||
|
||||
preset_values = [
|
||||
"material.subsurface_scattering.back",
|
||||
"material.subsurface_scattering.color",
|
||||
"material.subsurface_scattering.color_factor",
|
||||
"material.subsurface_scattering.error_threshold",
|
||||
"material.subsurface_scattering.front",
|
||||
"material.subsurface_scattering.ior",
|
||||
"material.subsurface_scattering.radius",
|
||||
"material.subsurface_scattering.scale",
|
||||
"material.subsurface_scattering.texture_factor",
|
||||
]
|
||||
|
||||
preset_subdir = "sss"
|
||||
|
||||
|
||||
class AddPresetCloth(AddPresetBase, bpy.types.Operator):
|
||||
'''Add a Cloth Preset'''
|
||||
bl_idname = "cloth.preset_add"
|
||||
bl_label = "Add Cloth Preset"
|
||||
preset_menu = "CLOTH_MT_presets"
|
||||
|
||||
preset_defines = [
|
||||
"cloth = bpy.context.cloth"
|
||||
]
|
||||
|
||||
preset_values = [
|
||||
"cloth.settings.air_damping",
|
||||
"cloth.settings.bending_stiffness",
|
||||
"cloth.settings.mass",
|
||||
"cloth.settings.quality",
|
||||
"cloth.settings.spring_damping",
|
||||
"cloth.settings.structural_stiffness",
|
||||
]
|
||||
|
||||
preset_subdir = "cloth"
|
||||
|
||||
|
||||
class AddPresetSunSky(AddPresetBase, bpy.types.Operator):
|
||||
'''Add a Sky & Atmosphere Preset'''
|
||||
bl_idname = "lamp.sunsky_preset_add"
|
||||
bl_label = "Add Sunsky Preset"
|
||||
preset_menu = "LAMP_MT_sunsky_presets"
|
||||
|
||||
preset_defines = [
|
||||
"sky = bpy.context.object.data.sky"
|
||||
]
|
||||
|
||||
preset_values = [
|
||||
"sky.atmosphere_extinction",
|
||||
"sky.atmosphere_inscattering",
|
||||
"sky.atmosphere_turbidity",
|
||||
"sky.backscattered_light",
|
||||
"sky.horizon_brightness",
|
||||
"sky.spread",
|
||||
"sky.sun_brightness",
|
||||
"sky.sun_intensity",
|
||||
"sky.sun_size",
|
||||
"sky.use_sky_blend",
|
||||
"sky.use_sky_blend_type",
|
||||
"sky.use_sky_color_space",
|
||||
"sky.use_sky_exposure",
|
||||
]
|
||||
|
||||
preset_subdir = "sunsky"
|
||||
|
||||
|
||||
class AddPresetInteraction(AddPresetBase, bpy.types.Operator):
|
||||
'''Add an Application Interaction Preset'''
|
||||
bl_idname = "wm.interaction_preset_add"
|
||||
bl_label = "Add Interaction Preset"
|
||||
preset_menu = "USERPREF_MT_interaction_presets"
|
||||
|
||||
preset_defines = [
|
||||
"user_preferences = bpy.context.user_preferences"
|
||||
]
|
||||
|
||||
preset_values = [
|
||||
"user_preferences.edit.use_drag_immediately",
|
||||
"user_preferences.edit.use_insertkey_xyz_to_rgb",
|
||||
"user_preferences.inputs.invert_mouse_wheel_zoom",
|
||||
"user_preferences.inputs.select_mouse",
|
||||
"user_preferences.inputs.use_emulate_numpad",
|
||||
"user_preferences.inputs.use_mouse_continuous",
|
||||
"user_preferences.inputs.use_mouse_emulate_3_button",
|
||||
"user_preferences.inputs.view_rotate_method",
|
||||
"user_preferences.inputs.view_zoom_axis",
|
||||
"user_preferences.inputs.view_zoom_method",
|
||||
]
|
||||
|
||||
preset_subdir = "interaction"
|
||||
|
||||
|
||||
class AddPresetKeyconfig(AddPresetBase, bpy.types.Operator):
|
||||
'''Add a Keyconfig Preset'''
|
||||
bl_idname = "wm.keyconfig_preset_add"
|
||||
bl_label = "Add Keyconfig Preset"
|
||||
preset_menu = "USERPREF_MT_keyconfigs"
|
||||
preset_subdir = "keyconfig"
|
||||
|
||||
def add(self, context, filepath):
|
||||
bpy.ops.wm.keyconfig_export(filepath=filepath)
|
||||
bpy.utils.keyconfig_set(filepath)
|
||||
|
||||
def pre_cb(self, context):
|
||||
keyconfigs = bpy.context.window_manager.keyconfigs
|
||||
if self.remove_active:
|
||||
preset_menu_class = getattr(bpy.types, self.preset_menu)
|
||||
preset_menu_class.bl_label = keyconfigs.active.name
|
||||
|
||||
def post_cb(self, context):
|
||||
keyconfigs = bpy.context.window_manager.keyconfigs
|
||||
if self.remove_active:
|
||||
keyconfigs.remove(keyconfigs.active)
|
||||
|
||||
|
||||
class AddPresetOperator(AddPresetBase, bpy.types.Operator):
|
||||
'''Add an Application Interaction Preset'''
|
||||
bl_idname = "wm.operator_preset_add"
|
||||
bl_label = "Operator Preset"
|
||||
preset_menu = "WM_MT_operator_presets"
|
||||
|
||||
operator = bpy.props.StringProperty(name="Operator", maxlen=64, options={'HIDDEN'})
|
||||
|
||||
# XXX, not ideal
|
||||
preset_defines = [
|
||||
"op = bpy.context.space_data.operator",
|
||||
]
|
||||
|
||||
@property
|
||||
def preset_subdir(self):
|
||||
return __class__.operator_path(self.operator)
|
||||
|
||||
@property
|
||||
def preset_values(self):
|
||||
properties_blacklist = bpy.types.Operator.bl_rna.properties.keys()
|
||||
|
||||
prefix, suffix = self.operator.split("_OT_", 1)
|
||||
operator_rna = getattr(getattr(bpy.ops, prefix.lower()), suffix).get_rna().bl_rna
|
||||
|
||||
ret = []
|
||||
for prop_id, prop in operator_rna.properties.items():
|
||||
if (not prop.is_hidden) and prop_id not in properties_blacklist:
|
||||
ret.append("op.%s" % prop_id)
|
||||
|
||||
return ret
|
||||
|
||||
@staticmethod
|
||||
def operator_path(operator):
|
||||
import os
|
||||
prefix, suffix = operator.split("_OT_", 1)
|
||||
return os.path.join("operator", "%s.%s" % (prefix.lower(), suffix))
|
||||
|
||||
|
||||
class WM_MT_operator_presets(bpy.types.Menu):
|
||||
bl_label = "Operator Presets"
|
||||
|
||||
def draw(self, context):
|
||||
self.operator = context.space_data.operator.bl_idname
|
||||
bpy.types.Menu.draw_preset(self, context)
|
||||
|
||||
@property
|
||||
def preset_subdir(self):
|
||||
return AddPresetOperator.operator_path(self.operator)
|
||||
|
||||
preset_operator = "script.execute_preset"
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,157 +0,0 @@
|
||||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Script copyright (C) Campbell J Barton
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ***** END GPL LICENCE BLOCK *****
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
# History
|
||||
#
|
||||
# Originally written by Matt Ebb
|
||||
|
||||
import bpy
|
||||
import os
|
||||
|
||||
|
||||
def guess_player_path(preset):
|
||||
import platform
|
||||
try:
|
||||
system = platform.system()
|
||||
except UnicodeDecodeError:
|
||||
import sys
|
||||
system = sys.platform
|
||||
|
||||
if preset == 'BLENDER24':
|
||||
player_path = "blender"
|
||||
|
||||
if system == 'Darwin':
|
||||
test_path = "/Applications/blender 2.49.app/Contents/MacOS/blender"
|
||||
elif system in ('Windows', 'win32'):
|
||||
test_path = "/Program Files/Blender Foundation/Blender/blender.exe"
|
||||
|
||||
if os.path.exists(test_path):
|
||||
player_path = test_path
|
||||
|
||||
elif preset == 'DJV':
|
||||
player_path = "djv_view"
|
||||
|
||||
if system == 'Darwin':
|
||||
test_path = '/Applications/djv-0.8.2.app/Contents/Resources/bin/djv_view'
|
||||
if os.path.exists(test_path):
|
||||
player_path = test_path
|
||||
|
||||
elif preset == 'FRAMECYCLER':
|
||||
player_path = "framecycler"
|
||||
|
||||
elif preset == 'RV':
|
||||
player_path = "rv"
|
||||
|
||||
elif preset == 'MPLAYER':
|
||||
player_path = "mplayer"
|
||||
|
||||
return player_path
|
||||
|
||||
|
||||
class PlayRenderedAnim(bpy.types.Operator):
|
||||
'''Plays back rendered frames/movies using an external player.'''
|
||||
bl_idname = "render.play_rendered_anim"
|
||||
bl_label = "Play Rendered Animation"
|
||||
bl_options = {'REGISTER'}
|
||||
|
||||
def execute(self, context):
|
||||
import subprocess
|
||||
|
||||
scene = context.scene
|
||||
rd = scene.render
|
||||
prefs = context.user_preferences
|
||||
|
||||
preset = prefs.filepaths.animation_player_preset
|
||||
player_path = prefs.filepaths.animation_player
|
||||
file_path = bpy.path.abspath(rd.filepath)
|
||||
is_movie = rd.is_movie_format
|
||||
|
||||
# try and guess a command line if it doesn't exist
|
||||
if player_path == '':
|
||||
player_path = guess_player_path(preset)
|
||||
|
||||
if is_movie == False and preset in ('FRAMECYCLER', 'RV', 'MPLAYER'):
|
||||
# replace the number with '#'
|
||||
file_a = rd.frame_path(frame=0)
|
||||
|
||||
# TODO, make an api call for this
|
||||
frame_tmp = 9
|
||||
file_b = rd.frame_path(frame=frame_tmp)
|
||||
|
||||
while len(file_a) == len(file_b):
|
||||
frame_tmp = (frame_tmp * 10) + 9
|
||||
print(frame_tmp)
|
||||
file_b = rd.frame_path(frame=frame_tmp)
|
||||
file_b = rd.frame_path(frame=int(frame_tmp / 10))
|
||||
|
||||
file = "".join((c if file_b[i] == c else "#") for i, c in enumerate(file_a))
|
||||
else:
|
||||
# works for movies and images
|
||||
file = rd.frame_path(frame=scene.frame_start)
|
||||
|
||||
file = bpy.path.abspath(file) # expand '//'
|
||||
|
||||
cmd = [player_path]
|
||||
# extra options, fps controls etc.
|
||||
if preset == 'BLENDER24':
|
||||
opts = ["-a", "-f", str(rd.fps), str(rd.fps_base), file]
|
||||
cmd.extend(opts)
|
||||
elif preset == 'DJV':
|
||||
opts = [file, "-playback_speed", str(rd.fps)]
|
||||
cmd.extend(opts)
|
||||
elif preset == 'FRAMECYCLER':
|
||||
opts = [file, "%d-%d" % (scene.frame_start, scene.frame_end)]
|
||||
cmd.extend(opts)
|
||||
elif preset == 'RV':
|
||||
opts = ["-fps", str(rd.fps), "-play", "[ %s ]" % file]
|
||||
cmd.extend(opts)
|
||||
elif preset == 'MPLAYER':
|
||||
opts = []
|
||||
if is_movie:
|
||||
opts.append(file)
|
||||
else:
|
||||
opts.append("mf://%s" % file.replace("#", "?"))
|
||||
opts += ["-mf", "fps=%.4f" % (rd.fps / rd.fps_base)]
|
||||
opts += ["-loop", "0", "-really-quiet", "-fs"]
|
||||
cmd.extend(opts)
|
||||
else: # 'CUSTOM'
|
||||
cmd.append(file)
|
||||
|
||||
# launch it
|
||||
try:
|
||||
process = subprocess.Popen(cmd)
|
||||
except:
|
||||
pass
|
||||
#raise OSError("Couldn't find an external animation player.")
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,146 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
class SequencerCrossfadeSounds(bpy.types.Operator):
|
||||
'''Do crossfading volume animation of two selected sound strips.'''
|
||||
|
||||
bl_idname = "sequencer.crossfade_sounds"
|
||||
bl_label = "Crossfade sounds"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
|
||||
return context.scene.sequence_editor.active_strip.type == 'SOUND'
|
||||
else:
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
seq1 = None
|
||||
seq2 = None
|
||||
for s in context.scene.sequence_editor.sequences:
|
||||
if s.select and s.type == 'SOUND':
|
||||
if seq1 is None:
|
||||
seq1 = s
|
||||
elif seq2 is None:
|
||||
seq2 = s
|
||||
else:
|
||||
seq2 = None
|
||||
break
|
||||
if seq2 is None:
|
||||
self.report({'ERROR'}, "Select 2 sound strips.")
|
||||
return {'CANCELLED'}
|
||||
if seq1.frame_final_start > seq2.frame_final_start:
|
||||
s = seq1
|
||||
seq1 = seq2
|
||||
seq2 = s
|
||||
if seq1.frame_final_end > seq2.frame_final_start:
|
||||
tempcfra = context.scene.frame_current
|
||||
context.scene.frame_current = seq2.frame_final_start
|
||||
seq1.keyframe_insert('volume')
|
||||
context.scene.frame_current = seq1.frame_final_end
|
||||
seq1.volume = 0
|
||||
seq1.keyframe_insert('volume')
|
||||
seq2.keyframe_insert('volume')
|
||||
context.scene.frame_current = seq2.frame_final_start
|
||||
seq2.volume = 0
|
||||
seq2.keyframe_insert('volume')
|
||||
context.scene.frame_current = tempcfra
|
||||
return {'FINISHED'}
|
||||
else:
|
||||
self.report({'ERROR'}, "The selected strips don't overlap.")
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
class SequencerCutMulticam(bpy.types.Operator):
|
||||
'''Cut multicam strip and select camera.'''
|
||||
|
||||
bl_idname = "sequencer.cut_multicam"
|
||||
bl_label = "Cut multicam"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
camera = IntProperty(name="Camera",
|
||||
default=1, min=1, max=32, soft_min=1, soft_max=32)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.scene and context.scene.sequence_editor and context.scene.sequence_editor.active_strip:
|
||||
return context.scene.sequence_editor.active_strip.type == 'MULTICAM'
|
||||
else:
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
camera = self.camera
|
||||
|
||||
s = context.scene.sequence_editor.active_strip
|
||||
|
||||
if s.multicam_source == camera or camera >= s.channel:
|
||||
return {'FINISHED'}
|
||||
|
||||
if not s.select:
|
||||
s.select = True
|
||||
|
||||
cfra = context.scene.frame_current
|
||||
bpy.ops.sequencer.cut(frame=cfra, type='SOFT', side='RIGHT')
|
||||
for s in context.scene.sequence_editor.sequences_all:
|
||||
if s.select and s.type == 'MULTICAM' and s.frame_final_start <= cfra and cfra < s.frame_final_end:
|
||||
context.scene.sequence_editor.active_strip = s
|
||||
|
||||
context.scene.sequence_editor.active_strip.multicam_source = camera
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class SequencerDeinterlaceSelectedMovies(bpy.types.Operator):
|
||||
'''Deinterlace all selected movie sources.'''
|
||||
|
||||
bl_idname = "sequencer.deinterlace_selected_movies"
|
||||
bl_label = "Deinterlace Movies"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
if context.scene and context.scene.sequence_editor:
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def execute(self, context):
|
||||
for s in context.scene.sequence_editor.sequences_all:
|
||||
if s.select and s.type == 'MOVIE':
|
||||
s.use_deinterlace = True
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,381 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
def write_svg(fw, mesh, image_width, image_height, face_iter):
|
||||
# for making an XML compatible string
|
||||
from xml.sax.saxutils import escape
|
||||
from os.path import basename
|
||||
|
||||
fw('<?xml version="1.0" standalone="no"?>\n')
|
||||
fw('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" \n')
|
||||
fw(' "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">\n')
|
||||
fw('<svg width="%dpx" height="%dpx" viewBox="0px 0px %dpx %dpx"\n' % (image_width, image_height, image_width, image_height))
|
||||
fw(' xmlns="http://www.w3.org/2000/svg" version="1.1">\n')
|
||||
desc = "%r, %s, (Blender %s)" % (basename(bpy.data.filepath), mesh.name, bpy.app.version_string)
|
||||
fw('<desc>%s</desc>\n' % escape(desc))
|
||||
|
||||
# svg colors
|
||||
fill_settings = []
|
||||
fill_default = 'fill="grey"'
|
||||
for mat in mesh.materials if mesh.materials else [None]:
|
||||
if mat:
|
||||
fill_settings.append('fill="rgb(%d, %d, %d)"' % tuple(int(c * 255) for c in mat.diffuse_color))
|
||||
else:
|
||||
fill_settings.append(fill_default)
|
||||
|
||||
faces = mesh.faces
|
||||
for i, uvs in face_iter:
|
||||
try: # rare cases material index is invalid.
|
||||
fill = fill_settings[faces[i].material_index]
|
||||
except IndexError:
|
||||
fill = fill_default
|
||||
|
||||
fw('<polygon %s fill-opacity="0.5" stroke="black" stroke-width="1px" \n' % fill)
|
||||
fw(' points="')
|
||||
|
||||
for j, uv in enumerate(uvs):
|
||||
x, y = uv[0], 1.0 - uv[1]
|
||||
fw('%.3f,%.3f ' % (x * image_width, y * image_height))
|
||||
fw('" />\n')
|
||||
fw('\n')
|
||||
fw('</svg>\n')
|
||||
|
||||
|
||||
def write_eps(fw, mesh, image_width, image_height, face_iter):
|
||||
fw('%!PS-Adobe-3.0 EPSF-3.0\n')
|
||||
fw("%%%%Creator: Blender %s\n" % bpy.app.version_string)
|
||||
fw('%%Pages: 1\n')
|
||||
fw('%%Orientation: Portrait\n')
|
||||
fw("%%%%BoundingBox: 0 0 %d %d\n" % (image_width, image_height))
|
||||
fw("%%%%HiResBoundingBox: 0.0 0.0 %.4f %.4f\n" % (image_width, image_height))
|
||||
fw('%%EndComments\n')
|
||||
fw('%%Page: 1 1\n')
|
||||
fw('0 0 translate\n')
|
||||
fw('1.0 1.0 scale\n')
|
||||
fw('0 0 0 setrgbcolor\n')
|
||||
fw('[] 0 setdash\n')
|
||||
fw('1 setlinewidth\n')
|
||||
fw('1 setlinejoin\n')
|
||||
fw('1 setlinecap\n')
|
||||
fw('/DRAW {')
|
||||
# can remove from here to next comment to disable filling, aparently alpha is not supported
|
||||
fw('gsave\n')
|
||||
fw('0.7 setgray\n')
|
||||
fw('fill\n')
|
||||
fw('grestore\n')
|
||||
fw('0 setgray\n')
|
||||
# remove to here
|
||||
fw('stroke\n')
|
||||
fw('} def\n')
|
||||
fw('newpath\n')
|
||||
|
||||
firstline = True
|
||||
for i, uvs in face_iter:
|
||||
for j, uv in enumerate(uvs):
|
||||
x, y = uv[0], uv[1]
|
||||
if j == 0:
|
||||
if not firstline:
|
||||
fw('closepath\n')
|
||||
fw('DRAW\n')
|
||||
fw('newpath\n')
|
||||
firstline = False
|
||||
fw('%.5f %.5f moveto\n' % (x * image_width, y * image_height))
|
||||
else:
|
||||
fw('%.5f %.5f lineto\n' % (x * image_width, y * image_height))
|
||||
|
||||
fw('closepath\n')
|
||||
fw('DRAW\n')
|
||||
fw('showpage\n')
|
||||
fw('%%EOF\n')
|
||||
|
||||
|
||||
def write_png(fw, mesh_source, image_width, image_height, face_iter):
|
||||
filepath = fw.__self__.name
|
||||
fw.__self__.close()
|
||||
|
||||
material_solids = [bpy.data.materials.new("uv_temp_solid") for i in range(max(1, len(mesh_source.materials)))]
|
||||
material_wire = bpy.data.materials.new("uv_temp_wire")
|
||||
|
||||
scene = bpy.data.scenes.new("uv_temp")
|
||||
mesh = bpy.data.meshes.new("uv_temp")
|
||||
for mat_solid in material_solids:
|
||||
mesh.materials.append(mat_solid)
|
||||
|
||||
tot_verts = 0
|
||||
face_lens = []
|
||||
for f in mesh_source.faces:
|
||||
tot_verts += len(f.vertices)
|
||||
|
||||
faces_source = mesh_source.faces
|
||||
|
||||
# get unique UV's incase there are many overlapping which slow down filling.
|
||||
face_hash_3 = set()
|
||||
face_hash_4 = set()
|
||||
for i, uv in face_iter:
|
||||
material_index = faces_source[i].material_index
|
||||
if len(uv) == 3:
|
||||
face_hash_3.add((uv[0][0], uv[0][1], uv[1][0], uv[1][1], uv[2][0], uv[2][1], material_index))
|
||||
else:
|
||||
face_hash_4.add((uv[0][0], uv[0][1], uv[1][0], uv[1][1], uv[2][0], uv[2][1], uv[3][0], uv[3][1], material_index))
|
||||
|
||||
# now set the faces coords and locations
|
||||
# build mesh data
|
||||
mesh_new_vertices = []
|
||||
mesh_new_materials = []
|
||||
mesh_new_face_vertices = []
|
||||
|
||||
current_vert = 0
|
||||
|
||||
for face_data in face_hash_3:
|
||||
mesh_new_vertices.extend([face_data[0], face_data[1], 0.0, face_data[2], face_data[3], 0.0, face_data[4], face_data[5], 0.0])
|
||||
mesh_new_face_vertices.extend([current_vert, current_vert + 1, current_vert + 2, 0])
|
||||
mesh_new_materials.append(face_data[6])
|
||||
current_vert += 3
|
||||
for face_data in face_hash_4:
|
||||
mesh_new_vertices.extend([face_data[0], face_data[1], 0.0, face_data[2], face_data[3], 0.0, face_data[4], face_data[5], 0.0, face_data[6], face_data[7], 0.0])
|
||||
mesh_new_face_vertices.extend([current_vert, current_vert + 1, current_vert + 2, current_vert + 3])
|
||||
mesh_new_materials.append(face_data[8])
|
||||
current_vert += 4
|
||||
|
||||
mesh.vertices.add(len(mesh_new_vertices) // 3)
|
||||
mesh.faces.add(len(mesh_new_face_vertices) // 4)
|
||||
|
||||
mesh.vertices.foreach_set("co", mesh_new_vertices)
|
||||
mesh.faces.foreach_set("vertices_raw", mesh_new_face_vertices)
|
||||
mesh.faces.foreach_set("material_index", mesh_new_materials)
|
||||
|
||||
mesh.update(calc_edges=True)
|
||||
|
||||
obj_solid = bpy.data.objects.new("uv_temp_solid", mesh)
|
||||
obj_wire = bpy.data.objects.new("uv_temp_wire", mesh)
|
||||
base_solid = scene.objects.link(obj_solid)
|
||||
base_wire = scene.objects.link(obj_wire)
|
||||
base_solid.layers[0] = True
|
||||
base_wire.layers[0] = True
|
||||
|
||||
# place behind the wire
|
||||
obj_solid.location = 0, 0, -1
|
||||
|
||||
obj_wire.material_slots[0].link = 'OBJECT'
|
||||
obj_wire.material_slots[0].material = material_wire
|
||||
|
||||
# setup the camera
|
||||
cam = bpy.data.cameras.new("uv_temp")
|
||||
cam.type = 'ORTHO'
|
||||
cam.ortho_scale = 1.0
|
||||
obj_cam = bpy.data.objects.new("uv_temp_cam", cam)
|
||||
obj_cam.location = 0.5, 0.5, 1.0
|
||||
scene.objects.link(obj_cam)
|
||||
scene.camera = obj_cam
|
||||
|
||||
# setup materials
|
||||
for i, mat_solid in enumerate(material_solids):
|
||||
if mesh_source.materials and mesh_source.materials[i]:
|
||||
mat_solid.diffuse_color = mesh_source.materials[i].diffuse_color
|
||||
|
||||
mat_solid.use_shadeless = True
|
||||
mat_solid.use_transparency = True
|
||||
mat_solid.alpha = 0.25
|
||||
|
||||
material_wire.type = 'WIRE'
|
||||
material_wire.use_shadeless = True
|
||||
material_wire.diffuse_color = 0, 0, 0
|
||||
|
||||
# scene render settings
|
||||
scene.render.use_raytrace = False
|
||||
scene.render.alpha_mode = 'STRAIGHT'
|
||||
scene.render.color_mode = 'RGBA'
|
||||
|
||||
scene.render.resolution_x = image_width
|
||||
scene.render.resolution_y = image_height
|
||||
scene.render.resolution_percentage = 100
|
||||
|
||||
if image_width > image_height:
|
||||
scene.render.pixel_aspect_y = image_width / image_height
|
||||
elif image_width < image_height:
|
||||
scene.render.pixel_aspect_x = image_height / image_width
|
||||
|
||||
scene.frame_start = 1
|
||||
scene.frame_end = 1
|
||||
|
||||
scene.render.file_format = 'PNG'
|
||||
scene.render.filepath = filepath
|
||||
|
||||
data_context = {"blend_data": bpy.context.blend_data, "scene": scene}
|
||||
bpy.ops.render.render(data_context, write_still=True)
|
||||
|
||||
# cleanup
|
||||
bpy.data.scenes.remove(scene)
|
||||
bpy.data.objects.remove(obj_cam)
|
||||
bpy.data.objects.remove(obj_solid)
|
||||
bpy.data.objects.remove(obj_wire)
|
||||
|
||||
bpy.data.cameras.remove(cam)
|
||||
bpy.data.meshes.remove(mesh)
|
||||
|
||||
bpy.data.materials.remove(material_wire)
|
||||
for mat_solid in material_solids:
|
||||
bpy.data.materials.remove(mat_solid)
|
||||
|
||||
|
||||
class ExportUVLayout(bpy.types.Operator):
|
||||
"""Export UV layout to file"""
|
||||
|
||||
bl_idname = "uv.export_layout"
|
||||
bl_label = "Export UV Layout"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
filepath = StringProperty(name="File Path", description="File path used for exporting the SVG file", maxlen=1024, default="", subtype='FILE_PATH')
|
||||
check_existing = BoolProperty(name="Check Existing", description="Check and warn on overwriting existing files", default=True, options={'HIDDEN'})
|
||||
export_all = BoolProperty(name="All UV's", description="Export all UVs in this mesh (not just the visible ones)", default=False)
|
||||
mode = EnumProperty(items=(
|
||||
('SVG', "Scalable Vector Graphic (.svg)", "Export the UV layout to a vector SVG file"),
|
||||
('EPS', "Encapsulate PostScript (.eps)", "Export the UV layout to a vector EPS file"),
|
||||
('PNG', "PNG Image (.png)", "Export the UV layout a bitmap image")),
|
||||
name="Format",
|
||||
description="File format to export the UV layout to",
|
||||
default='PNG')
|
||||
size = IntVectorProperty(size=2, default=(1024, 1024), min=8, max=32768, description="Dimensions of the exported file")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj and obj.type == 'MESH' and obj.data.uv_textures)
|
||||
|
||||
def _space_image(self, context):
|
||||
space_data = context.space_data
|
||||
if isinstance(space_data, bpy.types.SpaceImageEditor):
|
||||
return space_data
|
||||
else:
|
||||
return None
|
||||
|
||||
def _image_size(self, context, default_width=1024, default_height=1024):
|
||||
# fallback if not in image context.
|
||||
image_width, image_height = default_width, default_height
|
||||
|
||||
space_data = self._space_image(context)
|
||||
if space_data:
|
||||
image = space_data.image
|
||||
if image:
|
||||
width, height = tuple(context.space_data.image.size)
|
||||
# incase no data is found.
|
||||
if width and height:
|
||||
image_width, image_height = width, height
|
||||
|
||||
return image_width, image_height
|
||||
|
||||
def _face_uv_iter(self, context):
|
||||
obj = context.active_object
|
||||
mesh = obj.data
|
||||
uv_layer = mesh.uv_textures.active.data
|
||||
uv_layer_len = len(uv_layer)
|
||||
|
||||
if not self.export_all:
|
||||
|
||||
local_image = Ellipsis
|
||||
|
||||
if context.tool_settings.show_uv_local_view:
|
||||
space_data = self._space_image(context)
|
||||
if space_data:
|
||||
local_image = space_data.image
|
||||
|
||||
faces = mesh.faces
|
||||
|
||||
for i in range(uv_layer_len):
|
||||
uv_elem = uv_layer[i]
|
||||
# context checks
|
||||
if faces[i].select and (local_image is Ellipsis or local_image == uv_elem.image):
|
||||
#~ uv = uv_elem.uv
|
||||
#~ if False not in uv_elem.select_uv[:len(uv)]:
|
||||
#~ yield (i, uv)
|
||||
|
||||
# just write what we see.
|
||||
yield (i, uv_layer[i].uv)
|
||||
else:
|
||||
# all, simple
|
||||
for i in range(uv_layer_len):
|
||||
yield (i, uv_layer[i].uv)
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
obj = context.active_object
|
||||
is_editmode = (obj.mode == 'EDIT')
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
|
||||
|
||||
mesh = obj.data
|
||||
|
||||
mode = self.mode
|
||||
|
||||
filepath = self.filepath
|
||||
filepath = bpy.path.ensure_ext(filepath, "." + mode.lower())
|
||||
file = open(filepath, "w")
|
||||
fw = file.write
|
||||
|
||||
if mode == 'SVG':
|
||||
func = write_svg
|
||||
elif mode == 'EPS':
|
||||
func = write_eps
|
||||
elif mode == 'PNG':
|
||||
func = write_png
|
||||
|
||||
func(fw, mesh, self.size[0], self.size[1], self._face_uv_iter(context))
|
||||
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def check(self, context):
|
||||
filepath = bpy.path.ensure_ext(self.filepath, "." + self.mode.lower())
|
||||
if filepath != self.filepath:
|
||||
self.filepath = filepath
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def invoke(self, context, event):
|
||||
import os
|
||||
self.size = self._image_size(context)
|
||||
self.filepath = os.path.splitext(bpy.data.filepath)[0]
|
||||
wm = context.window_manager
|
||||
wm.fileselect_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
def menu_func(self, context):
|
||||
self.layout.operator(ExportUVLayout.bl_idname)
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.types.IMAGE_MT_uvs.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
bpy.types.IMAGE_MT_uvs.remove(menu_func)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,264 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
#for full docs see...
|
||||
# http://mediawiki.blender.org/index.php/Scripts/Manual/UV_Calculate/Follow_active_quads
|
||||
|
||||
import bpy
|
||||
|
||||
|
||||
def extend(obj, operator, EXTEND_MODE):
|
||||
me = obj.data
|
||||
me_verts = me.vertices
|
||||
# script will fail without UVs
|
||||
if not me.uv_textures:
|
||||
me.uv_textures.new()
|
||||
|
||||
# Toggle Edit mode
|
||||
is_editmode = (obj.mode == 'EDIT')
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
|
||||
#t = sys.time()
|
||||
edge_average_lengths = {}
|
||||
|
||||
OTHER_INDEX = 2, 3, 0, 1
|
||||
FAST_INDICIES = 0, 2, 1, 3 # order is faster
|
||||
|
||||
def extend_uvs(face_source, face_target, edge_key):
|
||||
'''
|
||||
Takes 2 faces,
|
||||
Projects its extends its UV coords onto the face next to it.
|
||||
Both faces must share an edge
|
||||
'''
|
||||
|
||||
def face_edge_vs(vi):
|
||||
# assume a quad
|
||||
return [(vi[0], vi[1]), (vi[1], vi[2]), (vi[2], vi[3]), (vi[3], vi[0])]
|
||||
|
||||
vidx_source = face_source.vertices
|
||||
vidx_target = face_target.vertices
|
||||
|
||||
faceUVsource = me.uv_textures.active.data[face_source.index]
|
||||
uvs_source = [faceUVsource.uv1, faceUVsource.uv2, faceUVsource.uv3, faceUVsource.uv4]
|
||||
|
||||
faceUVtarget = me.uv_textures.active.data[face_target.index]
|
||||
uvs_target = [faceUVtarget.uv1, faceUVtarget.uv2, faceUVtarget.uv3, faceUVtarget.uv4]
|
||||
|
||||
# vertex index is the key, uv is the value
|
||||
|
||||
uvs_vhash_source = {vindex: uvs_source[i] for i, vindex in enumerate(vidx_source)}
|
||||
|
||||
uvs_vhash_target = {vindex: uvs_target[i] for i, vindex in enumerate(vidx_target)}
|
||||
|
||||
edge_idxs_source = face_edge_vs(vidx_source)
|
||||
edge_idxs_target = face_edge_vs(vidx_target)
|
||||
|
||||
source_matching_edge = -1
|
||||
target_matching_edge = -1
|
||||
|
||||
edge_key_swap = edge_key[1], edge_key[0]
|
||||
|
||||
try:
|
||||
source_matching_edge = edge_idxs_source.index(edge_key)
|
||||
except:
|
||||
source_matching_edge = edge_idxs_source.index(edge_key_swap)
|
||||
try:
|
||||
target_matching_edge = edge_idxs_target.index(edge_key)
|
||||
except:
|
||||
target_matching_edge = edge_idxs_target.index(edge_key_swap)
|
||||
|
||||
edgepair_inner_source = edge_idxs_source[source_matching_edge]
|
||||
edgepair_inner_target = edge_idxs_target[target_matching_edge]
|
||||
edgepair_outer_source = edge_idxs_source[OTHER_INDEX[source_matching_edge]]
|
||||
edgepair_outer_target = edge_idxs_target[OTHER_INDEX[target_matching_edge]]
|
||||
|
||||
if edge_idxs_source[source_matching_edge] == edge_idxs_target[target_matching_edge]:
|
||||
iA = 0 # Flipped, most common
|
||||
iB = 1
|
||||
else: # The normals of these faces must be different
|
||||
iA = 1
|
||||
iB = 0
|
||||
|
||||
# Set the target UV's touching source face, no tricky calc needed,
|
||||
uvs_vhash_target[edgepair_inner_target[0]][:] = uvs_vhash_source[edgepair_inner_source[iA]]
|
||||
uvs_vhash_target[edgepair_inner_target[1]][:] = uvs_vhash_source[edgepair_inner_source[iB]]
|
||||
|
||||
# Set the 2 UV's on the target face that are not touching
|
||||
# for this we need to do basic expaning on the source faces UV's
|
||||
if EXTEND_MODE == 'LENGTH':
|
||||
|
||||
try: # divide by zero is possible
|
||||
'''
|
||||
measure the length of each face from the middle of each edge to the opposite
|
||||
allong the axis we are copying, use this
|
||||
'''
|
||||
i1a = edgepair_outer_target[iB]
|
||||
i2a = edgepair_inner_target[iA]
|
||||
if i1a > i2a:
|
||||
i1a, i2a = i2a, i1a
|
||||
|
||||
i1b = edgepair_outer_source[iB]
|
||||
i2b = edgepair_inner_source[iA]
|
||||
if i1b > i2b:
|
||||
i1b, i2b = i2b, i1b
|
||||
# print edge_average_lengths
|
||||
factor = edge_average_lengths[i1a, i2a][0] / edge_average_lengths[i1b, i2b][0]
|
||||
except:
|
||||
# Div By Zero?
|
||||
factor = 1.0
|
||||
|
||||
uvs_vhash_target[edgepair_outer_target[iB]][:] = uvs_vhash_source[edgepair_inner_source[0]] + factor * (uvs_vhash_source[edgepair_inner_source[0]] - uvs_vhash_source[edgepair_outer_source[1]])
|
||||
uvs_vhash_target[edgepair_outer_target[iA]][:] = uvs_vhash_source[edgepair_inner_source[1]] + factor * (uvs_vhash_source[edgepair_inner_source[1]] - uvs_vhash_source[edgepair_outer_source[0]])
|
||||
|
||||
else:
|
||||
# same as above but with no factors
|
||||
uvs_vhash_target[edgepair_outer_target[iB]][:] = uvs_vhash_source[edgepair_inner_source[0]] + (uvs_vhash_source[edgepair_inner_source[0]] - uvs_vhash_source[edgepair_outer_source[1]])
|
||||
uvs_vhash_target[edgepair_outer_target[iA]][:] = uvs_vhash_source[edgepair_inner_source[1]] + (uvs_vhash_source[edgepair_inner_source[1]] - uvs_vhash_source[edgepair_outer_source[0]])
|
||||
|
||||
if not me.uv_textures:
|
||||
me.uv_textures.new()
|
||||
|
||||
face_act = me.faces.active
|
||||
if face_act == -1:
|
||||
operator.report({'ERROR'}, "No active face.")
|
||||
return
|
||||
|
||||
face_sel = [f for f in me.faces if len(f.vertices) == 4 and f.select]
|
||||
|
||||
face_act_local_index = -1
|
||||
for i, f in enumerate(face_sel):
|
||||
if f.index == face_act:
|
||||
face_act_local_index = i
|
||||
break
|
||||
|
||||
if face_act_local_index == -1:
|
||||
operator.report({'ERROR'}, "Active face not selected.")
|
||||
return
|
||||
|
||||
# Modes
|
||||
# 0 unsearched
|
||||
# 1:mapped, use search from this face. - removed!!
|
||||
# 2:all siblings have been searched. dont search again.
|
||||
face_modes = [0] * len(face_sel)
|
||||
face_modes[face_act_local_index] = 1 # extend UV's from this face.
|
||||
|
||||
# Edge connectivty
|
||||
edge_faces = {}
|
||||
for i, f in enumerate(face_sel):
|
||||
for edkey in f.edge_keys:
|
||||
try:
|
||||
edge_faces[edkey].append(i)
|
||||
except:
|
||||
edge_faces[edkey] = [i]
|
||||
|
||||
if EXTEND_MODE == 'LENGTH':
|
||||
edge_loops = me.edge_loops_from_faces(face_sel, [ed.key for ed in me.edges if ed.use_seam])
|
||||
me_verts = me.vertices
|
||||
for loop in edge_loops:
|
||||
looplen = [0.0]
|
||||
for ed in loop:
|
||||
edge_average_lengths[ed] = looplen
|
||||
looplen[0] += (me_verts[ed[0]].co - me_verts[ed[1]].co).length
|
||||
looplen[0] = looplen[0] / len(loop)
|
||||
|
||||
# remove seams, so we dont map accross seams.
|
||||
for ed in me.edges:
|
||||
if ed.use_seam:
|
||||
# remove the edge pair if we can
|
||||
try:
|
||||
del edge_faces[ed.key]
|
||||
except:
|
||||
pass
|
||||
# Done finding seams
|
||||
|
||||
# face connectivity - faces around each face
|
||||
# only store a list of indices for each face.
|
||||
face_faces = [[] for i in range(len(face_sel))]
|
||||
|
||||
for edge_key, faces in edge_faces.items():
|
||||
if len(faces) == 2: # Only do edges with 2 face users for now
|
||||
face_faces[faces[0]].append((faces[1], edge_key))
|
||||
face_faces[faces[1]].append((faces[0], edge_key))
|
||||
|
||||
# Now we know what face is connected to what other face, map them by connectivity
|
||||
ok = True
|
||||
while ok:
|
||||
ok = False
|
||||
for i in range(len(face_sel)):
|
||||
if face_modes[i] == 1: # searchable
|
||||
for f_sibling, edge_key in face_faces[i]:
|
||||
if face_modes[f_sibling] == 0:
|
||||
face_modes[f_sibling] = 1 # mapped and search from.
|
||||
extend_uvs(face_sel[i], face_sel[f_sibling], edge_key)
|
||||
face_modes[i] = 1 # we can map from this one now.
|
||||
ok = True # keep searching
|
||||
|
||||
face_modes[i] = 2 # dont search again
|
||||
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
else:
|
||||
me.update_tag()
|
||||
|
||||
|
||||
def main(context, operator):
|
||||
obj = context.active_object
|
||||
|
||||
extend(obj, operator, operator.properties.mode)
|
||||
|
||||
|
||||
class FollowActiveQuads(bpy.types.Operator):
|
||||
'''Follow UVs from active quads along continuous face loops'''
|
||||
bl_idname = "uv.follow_active_quads"
|
||||
bl_label = "Follow Active Quads"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
mode = bpy.props.EnumProperty(items=(("EVEN", "Even", "Space all UVs evently"), ("LENGTH", "Length", "Average space UVs edge length of each loop")),
|
||||
name="Edge Length Mode",
|
||||
description="Method to space UV edge loops",
|
||||
default="LENGTH")
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
obj = context.active_object
|
||||
return (obj is not None and obj.type == 'MESH')
|
||||
|
||||
def execute(self, context):
|
||||
main(context, self)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Add to a menu
|
||||
menu_func = (lambda self, context: self.layout.operator(FollowActiveQuads.bl_idname))
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.types.VIEW3D_MT_uv_map.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
bpy.types.VIEW3D_MT_uv_map.remove(menu_func)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,1142 +0,0 @@
|
||||
# --------------------------------------------------------------------------
|
||||
# Smart Projection UV Projection Unwrapper v1.2 by Campbell Barton (AKA Ideasman)
|
||||
# --------------------------------------------------------------------------
|
||||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ***** END GPL LICENCE BLOCK *****
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
from mathutils import Matrix, Vector, geometry
|
||||
import time
|
||||
import bpy
|
||||
from math import cos, radians
|
||||
|
||||
DEG_TO_RAD = 0.017453292519943295 # pi/180.0
|
||||
SMALL_NUM = 0.000000001
|
||||
BIG_NUM = 1e15
|
||||
|
||||
global USER_FILL_HOLES
|
||||
global USER_FILL_HOLES_QUALITY
|
||||
USER_FILL_HOLES = None
|
||||
USER_FILL_HOLES_QUALITY = None
|
||||
|
||||
dict_matrix = {}
|
||||
|
||||
def pointInTri2D(v, v1, v2, v3):
|
||||
global dict_matrix
|
||||
|
||||
key = v1.x, v1.y, v2.x, v2.y, v3.x, v3.y
|
||||
|
||||
# Commented because its slower to do teh bounds check, we should realy cache the bounds info for each face.
|
||||
'''
|
||||
# BOUNDS CHECK
|
||||
xmin= 1000000
|
||||
ymin= 1000000
|
||||
|
||||
xmax= -1000000
|
||||
ymax= -1000000
|
||||
|
||||
for i in (0,2,4):
|
||||
x= key[i]
|
||||
y= key[i+1]
|
||||
|
||||
if xmax<x: xmax= x
|
||||
if ymax<y: ymax= y
|
||||
if xmin>x: xmin= x
|
||||
if ymin>y: ymin= y
|
||||
|
||||
x= v.x
|
||||
y= v.y
|
||||
|
||||
if x<xmin or x>xmax or y < ymin or y > ymax:
|
||||
return False
|
||||
# Done with bounds check
|
||||
'''
|
||||
try:
|
||||
mtx = dict_matrix[key]
|
||||
if not mtx:
|
||||
return False
|
||||
except:
|
||||
side1 = v2 - v1
|
||||
side2 = v3 - v1
|
||||
|
||||
nor = side1.cross(side2)
|
||||
|
||||
mtx = Matrix((side1, side2, nor))
|
||||
|
||||
# Zero area 2d tri, even tho we throw away zerop area faces
|
||||
# the projection UV can result in a zero area UV.
|
||||
if not mtx.determinant():
|
||||
dict_matrix[key] = None
|
||||
return False
|
||||
|
||||
mtx.invert()
|
||||
|
||||
dict_matrix[key] = mtx
|
||||
|
||||
uvw = (v - v1) * mtx
|
||||
return 0 <= uvw[0] and 0 <= uvw[1] and uvw[0] + uvw[1] <= 1
|
||||
|
||||
|
||||
def boundsIsland(faces):
|
||||
minx = maxx = faces[0].uv[0][0] # Set initial bounds.
|
||||
miny = maxy = faces[0].uv[0][1]
|
||||
# print len(faces), minx, maxx, miny , maxy
|
||||
for f in faces:
|
||||
for uv in f.uv:
|
||||
x= uv.x
|
||||
y= uv.y
|
||||
if x<minx: minx= x
|
||||
if y<miny: miny= y
|
||||
if x>maxx: maxx= x
|
||||
if y>maxy: maxy= y
|
||||
|
||||
return minx, miny, maxx, maxy
|
||||
|
||||
"""
|
||||
def boundsEdgeLoop(edges):
|
||||
minx = maxx = edges[0][0] # Set initial bounds.
|
||||
miny = maxy = edges[0][1]
|
||||
# print len(faces), minx, maxx, miny , maxy
|
||||
for ed in edges:
|
||||
for pt in ed:
|
||||
print 'ass'
|
||||
x= pt[0]
|
||||
y= pt[1]
|
||||
if x<minx: x= minx
|
||||
if y<miny: y= miny
|
||||
if x>maxx: x= maxx
|
||||
if y>maxy: y= maxy
|
||||
|
||||
return minx, miny, maxx, maxy
|
||||
"""
|
||||
|
||||
# Turns the islands into a list of unpordered edges (Non internal)
|
||||
# Onlt for UV's
|
||||
# only returns outline edges for intersection tests. and unique points.
|
||||
|
||||
def island2Edge(island):
|
||||
|
||||
# Vert index edges
|
||||
edges = {}
|
||||
|
||||
unique_points= {}
|
||||
|
||||
for f in island:
|
||||
f_uvkey= map(tuple, f.uv)
|
||||
|
||||
|
||||
for vIdx, edkey in enumerate(f.edge_keys):
|
||||
unique_points[f_uvkey[vIdx]] = f.uv[vIdx]
|
||||
|
||||
if f.v[vIdx].index > f.v[vIdx-1].index:
|
||||
i1= vIdx-1; i2= vIdx
|
||||
else:
|
||||
i1= vIdx; i2= vIdx-1
|
||||
|
||||
try: edges[ f_uvkey[i1], f_uvkey[i2] ] *= 0 # sets eny edge with more then 1 user to 0 are not returned.
|
||||
except: edges[ f_uvkey[i1], f_uvkey[i2] ] = (f.uv[i1] - f.uv[i2]).length,
|
||||
|
||||
# If 2 are the same then they will be together, but full [a,b] order is not correct.
|
||||
|
||||
# Sort by length
|
||||
|
||||
|
||||
length_sorted_edges = [(Vector(key[0]), Vector(key[1]), value) for key, value in edges.items() if value != 0]
|
||||
|
||||
try: length_sorted_edges.sort(key = lambda A: -A[2]) # largest first
|
||||
except: length_sorted_edges.sort(lambda A, B: cmp(B[2], A[2]))
|
||||
|
||||
# Its okay to leave the length in there.
|
||||
#for e in length_sorted_edges:
|
||||
# e.pop(2)
|
||||
|
||||
# return edges and unique points
|
||||
return length_sorted_edges, [v.to_3d() for v in unique_points.values()]
|
||||
|
||||
# ========================= NOT WORKING????
|
||||
# Find if a points inside an edge loop, un-orderd.
|
||||
# pt is and x/y
|
||||
# edges are a non ordered loop of edges.
|
||||
# #offsets are the edge x and y offset.
|
||||
"""
|
||||
def pointInEdges(pt, edges):
|
||||
#
|
||||
x1 = pt[0]
|
||||
y1 = pt[1]
|
||||
|
||||
# Point to the left of this line.
|
||||
x2 = -100000
|
||||
y2 = -10000
|
||||
intersectCount = 0
|
||||
for ed in edges:
|
||||
xi, yi = lineIntersection2D(x1,y1, x2,y2, ed[0][0], ed[0][1], ed[1][0], ed[1][1])
|
||||
if xi != None: # Is there an intersection.
|
||||
intersectCount+=1
|
||||
|
||||
return intersectCount % 2
|
||||
"""
|
||||
|
||||
def pointInIsland(pt, island):
|
||||
vec1, vec2, vec3 = Vector(), Vector(), Vector()
|
||||
for f in island:
|
||||
vec1.x, vec1.y = f.uv[0]
|
||||
vec2.x, vec2.y = f.uv[1]
|
||||
vec3.x, vec3.y = f.uv[2]
|
||||
|
||||
if pointInTri2D(pt, vec1, vec2, vec3):
|
||||
return True
|
||||
|
||||
if len(f.v) == 4:
|
||||
vec1.x, vec1.y = f.uv[0]
|
||||
vec2.x, vec2.y = f.uv[2]
|
||||
vec3.x, vec3.y = f.uv[3]
|
||||
if pointInTri2D(pt, vec1, vec2, vec3):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
# box is (left,bottom, right, top)
|
||||
def islandIntersectUvIsland(source, target, SourceOffset):
|
||||
# Is 1 point in the box, inside the vertLoops
|
||||
edgeLoopsSource = source[6] # Pretend this is offset
|
||||
edgeLoopsTarget = target[6]
|
||||
|
||||
# Edge intersect test
|
||||
for ed in edgeLoopsSource:
|
||||
for seg in edgeLoopsTarget:
|
||||
i = geometry.intersect_line_line_2d(\
|
||||
seg[0], seg[1], SourceOffset+ed[0], SourceOffset+ed[1])
|
||||
if i:
|
||||
return 1 # LINE INTERSECTION
|
||||
|
||||
# 1 test for source being totally inside target
|
||||
SourceOffset.resize_3d()
|
||||
for pv in source[7]:
|
||||
if pointInIsland(pv+SourceOffset, target[0]):
|
||||
return 2 # SOURCE INSIDE TARGET
|
||||
|
||||
# 2 test for a part of the target being totaly inside the source.
|
||||
for pv in target[7]:
|
||||
if pointInIsland(pv-SourceOffset, source[0]):
|
||||
return 3 # PART OF TARGET INSIDE SOURCE.
|
||||
|
||||
return 0 # NO INTERSECTION
|
||||
|
||||
|
||||
|
||||
|
||||
# Returns the X/y Bounds of a list of vectors.
|
||||
def testNewVecLs2DRotIsBetter(vecs, mat=-1, bestAreaSoFar = -1):
|
||||
|
||||
# UV's will never extend this far.
|
||||
minx = miny = BIG_NUM
|
||||
maxx = maxy = -BIG_NUM
|
||||
|
||||
for i, v in enumerate(vecs):
|
||||
|
||||
# Do this allong the way
|
||||
if mat != -1:
|
||||
v = vecs[i] = v*mat
|
||||
x= v.x
|
||||
y= v.y
|
||||
if x<minx: minx= x
|
||||
if y<miny: miny= y
|
||||
if x>maxx: maxx= x
|
||||
if y>maxy: maxy= y
|
||||
|
||||
# Spesific to this algo, bail out if we get bigger then the current area
|
||||
if bestAreaSoFar != -1 and (maxx-minx) * (maxy-miny) > bestAreaSoFar:
|
||||
return (BIG_NUM, None), None
|
||||
w = maxx-minx
|
||||
h = maxy-miny
|
||||
return (w*h, w,h), vecs # Area, vecs
|
||||
|
||||
# Takes a list of faces that make up a UV island and rotate
|
||||
# until they optimally fit inside a square.
|
||||
ROTMAT_2D_POS_90D = Matrix.Rotation( radians(90.0), 2)
|
||||
ROTMAT_2D_POS_45D = Matrix.Rotation( radians(45.0), 2)
|
||||
|
||||
RotMatStepRotation = []
|
||||
rot_angle = 22.5 #45.0/2
|
||||
while rot_angle > 0.1:
|
||||
RotMatStepRotation.append([\
|
||||
Matrix.Rotation( radians(rot_angle), 2),\
|
||||
Matrix.Rotation( radians(-rot_angle), 2)])
|
||||
|
||||
rot_angle = rot_angle/2.0
|
||||
|
||||
|
||||
def optiRotateUvIsland(faces):
|
||||
global currentArea
|
||||
|
||||
# Bestfit Rotation
|
||||
def best2dRotation(uvVecs, MAT1, MAT2):
|
||||
global currentArea
|
||||
|
||||
newAreaPos, newfaceProjectionGroupListPos =\
|
||||
testNewVecLs2DRotIsBetter(uvVecs[:], MAT1, currentArea[0])
|
||||
|
||||
|
||||
# Why do I use newpos here? May as well give the best area to date for an early bailout
|
||||
# some slight speed increase in this.
|
||||
# If the new rotation is smaller then the existing, we can
|
||||
# avoid copying a list and overwrite the old, crappy one.
|
||||
|
||||
if newAreaPos[0] < currentArea[0]:
|
||||
newAreaNeg, newfaceProjectionGroupListNeg =\
|
||||
testNewVecLs2DRotIsBetter(uvVecs, MAT2, newAreaPos[0]) # Reuse the old bigger list.
|
||||
else:
|
||||
newAreaNeg, newfaceProjectionGroupListNeg =\
|
||||
testNewVecLs2DRotIsBetter(uvVecs[:], MAT2, currentArea[0]) # Cant reuse, make a copy.
|
||||
|
||||
|
||||
# Now from the 3 options we need to discover which to use
|
||||
# we have cerrentArea/newAreaPos/newAreaNeg
|
||||
bestArea = min(currentArea[0], newAreaPos[0], newAreaNeg[0])
|
||||
|
||||
if currentArea[0] == bestArea:
|
||||
return uvVecs
|
||||
elif newAreaPos[0] == bestArea:
|
||||
uvVecs = newfaceProjectionGroupListPos
|
||||
currentArea = newAreaPos
|
||||
elif newAreaNeg[0] == bestArea:
|
||||
uvVecs = newfaceProjectionGroupListNeg
|
||||
currentArea = newAreaNeg
|
||||
|
||||
return uvVecs
|
||||
|
||||
|
||||
# Serialized UV coords to Vectors
|
||||
uvVecs = [uv for f in faces for uv in f.uv]
|
||||
|
||||
# Theres a small enough number of these to hard code it
|
||||
# rather then a loop.
|
||||
|
||||
# Will not modify anything
|
||||
currentArea, dummy =\
|
||||
testNewVecLs2DRotIsBetter(uvVecs)
|
||||
|
||||
|
||||
# Try a 45d rotation
|
||||
newAreaPos, newfaceProjectionGroupListPos = testNewVecLs2DRotIsBetter(uvVecs[:], ROTMAT_2D_POS_45D, currentArea[0])
|
||||
|
||||
if newAreaPos[0] < currentArea[0]:
|
||||
uvVecs = newfaceProjectionGroupListPos
|
||||
currentArea = newAreaPos
|
||||
# 45d done
|
||||
|
||||
# Testcase different rotations and find the onfe that best fits in a square
|
||||
for ROTMAT in RotMatStepRotation:
|
||||
uvVecs = best2dRotation(uvVecs, ROTMAT[0], ROTMAT[1])
|
||||
|
||||
# Only if you want it, make faces verticle!
|
||||
if currentArea[1] > currentArea[2]:
|
||||
# Rotate 90d
|
||||
# Work directly on the list, no need to return a value.
|
||||
testNewVecLs2DRotIsBetter(uvVecs, ROTMAT_2D_POS_90D)
|
||||
|
||||
|
||||
# Now write the vectors back to the face UV's
|
||||
i = 0 # count the serialized uv/vectors
|
||||
for f in faces:
|
||||
#f.uv = [uv for uv in uvVecs[i:len(f)+i] ]
|
||||
for j, k in enumerate(range(i, len(f.v)+i)):
|
||||
f.uv[j][:] = uvVecs[k]
|
||||
i += len(f.v)
|
||||
|
||||
|
||||
# Takes an island list and tries to find concave, hollow areas to pack smaller islands into.
|
||||
def mergeUvIslands(islandList):
|
||||
global USER_FILL_HOLES
|
||||
global USER_FILL_HOLES_QUALITY
|
||||
|
||||
|
||||
# Pack islands to bottom LHS
|
||||
# Sync with island
|
||||
|
||||
#islandTotFaceArea = [] # A list of floats, each island area
|
||||
#islandArea = [] # a list of tuples ( area, w,h)
|
||||
|
||||
|
||||
decoratedIslandList = []
|
||||
|
||||
islandIdx = len(islandList)
|
||||
while islandIdx:
|
||||
islandIdx-=1
|
||||
minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
|
||||
w, h = maxx-minx, maxy-miny
|
||||
|
||||
totFaceArea = 0
|
||||
offset= Vector((minx, miny))
|
||||
for f in islandList[islandIdx]:
|
||||
for uv in f.uv:
|
||||
uv -= offset
|
||||
|
||||
totFaceArea += f.area
|
||||
|
||||
islandBoundsArea = w*h
|
||||
efficiency = abs(islandBoundsArea - totFaceArea)
|
||||
|
||||
# UV Edge list used for intersections as well as unique points.
|
||||
edges, uniqueEdgePoints = island2Edge(islandList[islandIdx])
|
||||
|
||||
decoratedIslandList.append([islandList[islandIdx], totFaceArea, efficiency, islandBoundsArea, w,h, edges, uniqueEdgePoints])
|
||||
|
||||
|
||||
# Sort by island bounding box area, smallest face area first.
|
||||
# no.. chance that to most simple edge loop first.
|
||||
decoratedIslandListAreaSort =decoratedIslandList[:]
|
||||
|
||||
decoratedIslandListAreaSort.sort(key = lambda A: A[3])
|
||||
|
||||
# sort by efficiency, Least Efficient first.
|
||||
decoratedIslandListEfficSort = decoratedIslandList[:]
|
||||
# decoratedIslandListEfficSort.sort(lambda A, B: cmp(B[2], A[2]))
|
||||
|
||||
decoratedIslandListEfficSort.sort(key = lambda A: -A[2])
|
||||
|
||||
# ================================================== THESE CAN BE TWEAKED.
|
||||
# This is a quality value for the number of tests.
|
||||
# from 1 to 4, generic quality value is from 1 to 100
|
||||
USER_STEP_QUALITY = ((USER_FILL_HOLES_QUALITY - 1) / 25.0) + 1
|
||||
|
||||
# If 100 will test as long as there is enough free space.
|
||||
# this is rarely enough, and testing takes a while, so lower quality speeds this up.
|
||||
|
||||
# 1 means they have the same quality
|
||||
USER_FREE_SPACE_TO_TEST_QUALITY = 1 + (((100 - USER_FILL_HOLES_QUALITY)/100.0) *5)
|
||||
|
||||
#print 'USER_STEP_QUALITY', USER_STEP_QUALITY
|
||||
#print 'USER_FREE_SPACE_TO_TEST_QUALITY', USER_FREE_SPACE_TO_TEST_QUALITY
|
||||
|
||||
removedCount = 0
|
||||
|
||||
areaIslandIdx = 0
|
||||
ctrl = Window.Qual.CTRL
|
||||
BREAK= False
|
||||
while areaIslandIdx < len(decoratedIslandListAreaSort) and not BREAK:
|
||||
sourceIsland = decoratedIslandListAreaSort[areaIslandIdx]
|
||||
# Alredy packed?
|
||||
if not sourceIsland[0]:
|
||||
areaIslandIdx+=1
|
||||
else:
|
||||
efficIslandIdx = 0
|
||||
while efficIslandIdx < len(decoratedIslandListEfficSort) and not BREAK:
|
||||
|
||||
if Window.GetKeyQualifiers() & ctrl:
|
||||
BREAK= True
|
||||
break
|
||||
|
||||
# Now we have 2 islands, is the efficience of the islands lowers theres an
|
||||
# increasing likely hood that we can fit merge into the bigger UV island.
|
||||
# this ensures a tight fit.
|
||||
|
||||
# Just use figures we have about user/unused area to see if they might fit.
|
||||
|
||||
targetIsland = decoratedIslandListEfficSort[efficIslandIdx]
|
||||
|
||||
|
||||
if sourceIsland[0] == targetIsland[0] or\
|
||||
not targetIsland[0] or\
|
||||
not sourceIsland[0]:
|
||||
pass
|
||||
else:
|
||||
|
||||
# ([island, totFaceArea, efficiency, islandArea, w,h])
|
||||
# Waisted space on target is greater then UV bounding island area.
|
||||
|
||||
|
||||
# if targetIsland[3] > (sourceIsland[2]) and\ #
|
||||
# print USER_FREE_SPACE_TO_TEST_QUALITY, 'ass'
|
||||
if targetIsland[2] > (sourceIsland[1] * USER_FREE_SPACE_TO_TEST_QUALITY) and\
|
||||
targetIsland[4] > sourceIsland[4] and\
|
||||
targetIsland[5] > sourceIsland[5]:
|
||||
|
||||
# DEBUG # print '%.10f %.10f' % (targetIsland[3], sourceIsland[1])
|
||||
|
||||
# These enough spare space lets move the box until it fits
|
||||
|
||||
# How many times does the source fit into the target x/y
|
||||
blockTestXUnit = targetIsland[4]/sourceIsland[4]
|
||||
blockTestYUnit = targetIsland[5]/sourceIsland[5]
|
||||
|
||||
boxLeft = 0
|
||||
|
||||
|
||||
# Distllllance we can move between whilst staying inside the targets bounds.
|
||||
testWidth = targetIsland[4] - sourceIsland[4]
|
||||
testHeight = targetIsland[5] - sourceIsland[5]
|
||||
|
||||
# Increment we move each test. x/y
|
||||
xIncrement = (testWidth / (blockTestXUnit * ((USER_STEP_QUALITY/50)+0.1)))
|
||||
yIncrement = (testHeight / (blockTestYUnit * ((USER_STEP_QUALITY/50)+0.1)))
|
||||
|
||||
# Make sure were not moving less then a 3rg of our width/height
|
||||
if xIncrement<sourceIsland[4]/3:
|
||||
xIncrement= sourceIsland[4]
|
||||
if yIncrement<sourceIsland[5]/3:
|
||||
yIncrement= sourceIsland[5]
|
||||
|
||||
|
||||
boxLeft = 0 # Start 1 back so we can jump into the loop.
|
||||
boxBottom= 0 #-yIncrement
|
||||
|
||||
##testcount= 0
|
||||
|
||||
while boxBottom <= testHeight:
|
||||
# Should we use this? - not needed for now.
|
||||
#if Window.GetKeyQualifiers() & ctrl:
|
||||
# BREAK= True
|
||||
# break
|
||||
|
||||
##testcount+=1
|
||||
#print 'Testing intersect'
|
||||
Intersect = islandIntersectUvIsland(sourceIsland, targetIsland, Vector((boxLeft, boxBottom)))
|
||||
#print 'Done', Intersect
|
||||
if Intersect == 1: # Line intersect, dont bother with this any more
|
||||
pass
|
||||
|
||||
if Intersect == 2: # Source inside target
|
||||
'''
|
||||
We have an intersection, if we are inside the target
|
||||
then move us 1 whole width accross,
|
||||
Its possible this is a bad idea since 2 skinny Angular faces
|
||||
could join without 1 whole move, but its a lot more optimal to speed this up
|
||||
since we have already tested for it.
|
||||
|
||||
It gives about 10% speedup with minimal errors.
|
||||
'''
|
||||
#print 'ass'
|
||||
# Move the test allong its width + SMALL_NUM
|
||||
#boxLeft += sourceIsland[4] + SMALL_NUM
|
||||
boxLeft += sourceIsland[4]
|
||||
elif Intersect == 0: # No intersection?? Place it.
|
||||
# Progress
|
||||
removedCount +=1
|
||||
#XXX Window.DrawProgressBar(0.0, 'Merged: %i islands, Ctrl to finish early.' % removedCount)
|
||||
|
||||
# Move faces into new island and offset
|
||||
targetIsland[0].extend(sourceIsland[0])
|
||||
offset= Vector((boxLeft, boxBottom))
|
||||
|
||||
for f in sourceIsland[0]:
|
||||
for uv in f.uv:
|
||||
uv+= offset
|
||||
|
||||
sourceIsland[0][:] = [] # Empty
|
||||
|
||||
|
||||
# Move edge loop into new and offset.
|
||||
# targetIsland[6].extend(sourceIsland[6])
|
||||
#while sourceIsland[6]:
|
||||
targetIsland[6].extend( [ (\
|
||||
(e[0]+offset, e[1]+offset, e[2])\
|
||||
) for e in sourceIsland[6] ] )
|
||||
|
||||
sourceIsland[6][:] = [] # Empty
|
||||
|
||||
# Sort by edge length, reverse so biggest are first.
|
||||
|
||||
try: targetIsland[6].sort(key = lambda A: A[2])
|
||||
except: targetIsland[6].sort(lambda B,A: cmp(A[2], B[2] ))
|
||||
|
||||
|
||||
targetIsland[7].extend(sourceIsland[7])
|
||||
offset= Vector((boxLeft, boxBottom, 0.0))
|
||||
for p in sourceIsland[7]:
|
||||
p+= offset
|
||||
|
||||
sourceIsland[7][:] = []
|
||||
|
||||
|
||||
# Decrement the efficiency
|
||||
targetIsland[1]+=sourceIsland[1] # Increment totFaceArea
|
||||
targetIsland[2]-=sourceIsland[1] # Decrement efficiency
|
||||
# IF we ever used these again, should set to 0, eg
|
||||
sourceIsland[2] = 0 # No area if anyone wants to know
|
||||
|
||||
break
|
||||
|
||||
|
||||
# INCREMENR NEXT LOCATION
|
||||
if boxLeft > testWidth:
|
||||
boxBottom += yIncrement
|
||||
boxLeft = 0.0
|
||||
else:
|
||||
boxLeft += xIncrement
|
||||
##print testcount
|
||||
|
||||
efficIslandIdx+=1
|
||||
areaIslandIdx+=1
|
||||
|
||||
# Remove empty islands
|
||||
i = len(islandList)
|
||||
while i:
|
||||
i-=1
|
||||
if not islandList[i]:
|
||||
del islandList[i] # Can increment islands removed here.
|
||||
|
||||
# Takes groups of faces. assumes face groups are UV groups.
|
||||
def getUvIslands(faceGroups, me):
|
||||
|
||||
# Get seams so we dont cross over seams
|
||||
edge_seams = {} # shoudl be a set
|
||||
for ed in me.edges:
|
||||
if ed.use_seam:
|
||||
edge_seams[ed.key] = None # dummy var- use sets!
|
||||
# Done finding seams
|
||||
|
||||
|
||||
islandList = []
|
||||
|
||||
#XXX Window.DrawProgressBar(0.0, 'Splitting %d projection groups into UV islands:' % len(faceGroups))
|
||||
#print '\tSplitting %d projection groups into UV islands:' % len(faceGroups),
|
||||
# Find grouped faces
|
||||
|
||||
faceGroupIdx = len(faceGroups)
|
||||
|
||||
while faceGroupIdx:
|
||||
faceGroupIdx-=1
|
||||
faces = faceGroups[faceGroupIdx]
|
||||
|
||||
if not faces:
|
||||
continue
|
||||
|
||||
# Build edge dict
|
||||
edge_users = {}
|
||||
|
||||
for i, f in enumerate(faces):
|
||||
for ed_key in f.edge_keys:
|
||||
if ed_key in edge_seams: # DELIMIT SEAMS! ;)
|
||||
edge_users[ed_key] = [] # so as not to raise an error
|
||||
else:
|
||||
try: edge_users[ed_key].append(i)
|
||||
except: edge_users[ed_key] = [i]
|
||||
|
||||
# Modes
|
||||
# 0 - face not yet touched.
|
||||
# 1 - added to island list, and need to search
|
||||
# 2 - touched and searched - dont touch again.
|
||||
face_modes = [0] * len(faces) # initialize zero - untested.
|
||||
|
||||
face_modes[0] = 1 # start the search with face 1
|
||||
|
||||
newIsland = []
|
||||
|
||||
newIsland.append(faces[0])
|
||||
|
||||
|
||||
ok = True
|
||||
while ok:
|
||||
|
||||
ok = True
|
||||
while ok:
|
||||
ok= False
|
||||
for i in range(len(faces)):
|
||||
if face_modes[i] == 1: # search
|
||||
for ed_key in faces[i].edge_keys:
|
||||
for ii in edge_users[ed_key]:
|
||||
if i != ii and face_modes[ii] == 0:
|
||||
face_modes[ii] = ok = 1 # mark as searched
|
||||
newIsland.append(faces[ii])
|
||||
|
||||
# mark as searched, dont look again.
|
||||
face_modes[i] = 2
|
||||
|
||||
islandList.append(newIsland)
|
||||
|
||||
ok = False
|
||||
for i in range(len(faces)):
|
||||
if face_modes[i] == 0:
|
||||
newIsland = []
|
||||
newIsland.append(faces[i])
|
||||
|
||||
face_modes[i] = ok = 1
|
||||
break
|
||||
# if not ok will stop looping
|
||||
|
||||
#XXX Window.DrawProgressBar(0.1, 'Optimizing Rotation for %i UV Islands' % len(islandList))
|
||||
|
||||
for island in islandList:
|
||||
optiRotateUvIsland(island)
|
||||
|
||||
return islandList
|
||||
|
||||
|
||||
def packIslands(islandList):
|
||||
if USER_FILL_HOLES:
|
||||
#XXX Window.DrawProgressBar(0.1, 'Merging Islands (Ctrl: skip merge)...')
|
||||
mergeUvIslands(islandList) # Modify in place
|
||||
|
||||
|
||||
# Now we have UV islands, we need to pack them.
|
||||
|
||||
# Make a synchronised list with the islands
|
||||
# so we can box pak the islands.
|
||||
packBoxes = []
|
||||
|
||||
# Keep a list of X/Y offset so we can save time by writing the
|
||||
# uv's and packed data in one pass.
|
||||
islandOffsetList = []
|
||||
|
||||
islandIdx = 0
|
||||
|
||||
while islandIdx < len(islandList):
|
||||
minx, miny, maxx, maxy = boundsIsland(islandList[islandIdx])
|
||||
|
||||
w, h = maxx-minx, maxy-miny
|
||||
|
||||
if USER_ISLAND_MARGIN:
|
||||
minx -= USER_ISLAND_MARGIN# *w
|
||||
miny -= USER_ISLAND_MARGIN# *h
|
||||
maxx += USER_ISLAND_MARGIN# *w
|
||||
maxy += USER_ISLAND_MARGIN# *h
|
||||
|
||||
# recalc width and height
|
||||
w, h = maxx-minx, maxy-miny
|
||||
|
||||
if w < 0.00001 or h < 0.00001:
|
||||
del islandList[islandIdx]
|
||||
islandIdx -=1
|
||||
continue
|
||||
|
||||
'''Save the offset to be applied later,
|
||||
we could apply to the UVs now and allign them to the bottom left hand area
|
||||
of the UV coords like the box packer imagines they are
|
||||
but, its quicker just to remember their offset and
|
||||
apply the packing and offset in 1 pass '''
|
||||
islandOffsetList.append((minx, miny))
|
||||
|
||||
# Add to boxList. use the island idx for the BOX id.
|
||||
packBoxes.append([0, 0, w, h])
|
||||
islandIdx+=1
|
||||
|
||||
# Now we have a list of boxes to pack that syncs
|
||||
# with the islands.
|
||||
|
||||
#print '\tPacking UV Islands...'
|
||||
#XXX Window.DrawProgressBar(0.7, 'Packing %i UV Islands...' % len(packBoxes) )
|
||||
|
||||
time1 = time.time()
|
||||
packWidth, packHeight = geometry.box_pack_2d(packBoxes)
|
||||
|
||||
# print 'Box Packing Time:', time.time() - time1
|
||||
|
||||
#if len(pa ckedLs) != len(islandList):
|
||||
# raise "Error packed boxes differes from original length"
|
||||
|
||||
#print '\tWriting Packed Data to faces'
|
||||
#XXX Window.DrawProgressBar(0.8, 'Writing Packed Data to faces')
|
||||
|
||||
# Sort by ID, so there in sync again
|
||||
islandIdx = len(islandList)
|
||||
# Having these here avoids devide by 0
|
||||
if islandIdx:
|
||||
|
||||
if USER_STRETCH_ASPECT:
|
||||
# Maximize to uv area?? Will write a normalize function.
|
||||
xfactor = 1.0 / packWidth
|
||||
yfactor = 1.0 / packHeight
|
||||
else:
|
||||
# Keep proportions.
|
||||
xfactor = yfactor = 1.0 / max(packWidth, packHeight)
|
||||
|
||||
while islandIdx:
|
||||
islandIdx -=1
|
||||
# Write the packed values to the UV's
|
||||
|
||||
xoffset = packBoxes[islandIdx][0] - islandOffsetList[islandIdx][0]
|
||||
yoffset = packBoxes[islandIdx][1] - islandOffsetList[islandIdx][1]
|
||||
|
||||
for f in islandList[islandIdx]: # Offsetting the UV's so they fit in there packed box
|
||||
for uv in f.uv:
|
||||
uv.x= (uv.x+xoffset) * xfactor
|
||||
uv.y= (uv.y+yoffset) * yfactor
|
||||
|
||||
|
||||
|
||||
def VectoQuat(vec):
|
||||
vec = vec.normalized()
|
||||
if abs(vec.x) > 0.5:
|
||||
return vec.to_track_quat('Z', 'X')
|
||||
else:
|
||||
return vec.to_track_quat('Z', 'Y')
|
||||
|
||||
|
||||
class thickface(object):
|
||||
__slost__= 'v', 'uv', 'no', 'area', 'edge_keys'
|
||||
def __init__(self, face, uvface, mesh_verts):
|
||||
self.v = [mesh_verts[i] for i in face.vertices]
|
||||
if len(self.v)==4:
|
||||
self.uv = uvface.uv1, uvface.uv2, uvface.uv3, uvface.uv4
|
||||
else:
|
||||
self.uv = uvface.uv1, uvface.uv2, uvface.uv3
|
||||
|
||||
self.no = face.normal
|
||||
self.area = face.area
|
||||
self.edge_keys = face.edge_keys
|
||||
|
||||
global ob
|
||||
ob = None
|
||||
def main(context, island_margin, projection_limit):
|
||||
global USER_FILL_HOLES
|
||||
global USER_FILL_HOLES_QUALITY
|
||||
global USER_STRETCH_ASPECT
|
||||
global USER_ISLAND_MARGIN
|
||||
|
||||
#XXX objects= bpy.data.scenes.active.objects
|
||||
objects = context.selected_editable_objects
|
||||
|
||||
|
||||
# we can will tag them later.
|
||||
obList = [ob for ob in objects if ob.type == 'MESH']
|
||||
|
||||
# Face select object may not be selected.
|
||||
#XXX ob = objects.active
|
||||
ob= objects[0]
|
||||
|
||||
if ob and (not ob.select) and ob.type == 'MESH':
|
||||
# Add to the list
|
||||
obList =[ob]
|
||||
del objects
|
||||
|
||||
if not obList:
|
||||
raise('error, no selected mesh objects')
|
||||
|
||||
# Create the variables.
|
||||
USER_PROJECTION_LIMIT = projection_limit
|
||||
USER_ONLY_SELECTED_FACES = (1)
|
||||
USER_SHARE_SPACE = (1) # Only for hole filling.
|
||||
USER_STRETCH_ASPECT = (1) # Only for hole filling.
|
||||
USER_ISLAND_MARGIN = island_margin # Only for hole filling.
|
||||
USER_FILL_HOLES = (0)
|
||||
USER_FILL_HOLES_QUALITY = (50) # Only for hole filling.
|
||||
USER_VIEW_INIT = (0) # Only for hole filling.
|
||||
USER_AREA_WEIGHT = (1) # Only for hole filling.
|
||||
|
||||
# Reuse variable
|
||||
if len(obList) == 1:
|
||||
ob = "Unwrap %i Selected Mesh"
|
||||
else:
|
||||
ob = "Unwrap %i Selected Meshes"
|
||||
|
||||
# HACK, loop until mouse is lifted.
|
||||
'''
|
||||
while Window.GetMouseButtons() != 0:
|
||||
time.sleep(10)
|
||||
'''
|
||||
|
||||
#XXX if not Draw.PupBlock(ob % len(obList), pup_block):
|
||||
#XXX return
|
||||
#XXX del ob
|
||||
|
||||
# Convert from being button types
|
||||
|
||||
USER_PROJECTION_LIMIT_CONVERTED = cos(USER_PROJECTION_LIMIT * DEG_TO_RAD)
|
||||
USER_PROJECTION_LIMIT_HALF_CONVERTED = cos((USER_PROJECTION_LIMIT/2) * DEG_TO_RAD)
|
||||
|
||||
|
||||
# Toggle Edit mode
|
||||
is_editmode = (context.active_object.mode == 'EDIT')
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='OBJECT')
|
||||
# Assume face select mode! an annoying hack to toggle face select mode because Mesh dosent like faceSelectMode.
|
||||
|
||||
if USER_SHARE_SPACE:
|
||||
# Sort by data name so we get consistant results
|
||||
obList.sort(key = lambda ob: ob.data.name)
|
||||
collected_islandList= []
|
||||
|
||||
#XXX Window.WaitCursor(1)
|
||||
|
||||
time1 = time.time()
|
||||
|
||||
# Tag as False se we dont operate on teh same mesh twice.
|
||||
#XXX bpy.data.meshes.tag = False
|
||||
for me in bpy.data.meshes:
|
||||
me.tag = False
|
||||
|
||||
|
||||
for ob in obList:
|
||||
me = ob.data
|
||||
|
||||
if me.tag or me.library:
|
||||
continue
|
||||
|
||||
# Tag as used
|
||||
me.tag = True
|
||||
|
||||
if not me.uv_textures: # Mesh has no UV Coords, dont bother.
|
||||
me.uv_textures.new()
|
||||
|
||||
uv_layer = me.uv_textures.active.data
|
||||
me_verts = list(me.vertices)
|
||||
|
||||
if USER_ONLY_SELECTED_FACES:
|
||||
meshFaces = [thickface(f, uv_layer[i], me_verts) for i, f in enumerate(me.faces) if f.select]
|
||||
#else:
|
||||
# meshFaces = map(thickface, me.faces)
|
||||
|
||||
if not meshFaces:
|
||||
continue
|
||||
|
||||
#XXX Window.DrawProgressBar(0.1, 'SmartProj UV Unwrapper, mapping "%s", %i faces.' % (me.name, len(meshFaces)))
|
||||
|
||||
# =======
|
||||
# Generate a projection list from face normals, this is ment to be smart :)
|
||||
|
||||
# make a list of face props that are in sync with meshFaces
|
||||
# Make a Face List that is sorted by area.
|
||||
# meshFaces = []
|
||||
|
||||
# meshFaces.sort( lambda a, b: cmp(b.area , a.area) ) # Biggest first.
|
||||
meshFaces.sort( key = lambda a: -a.area )
|
||||
|
||||
# remove all zero area faces
|
||||
while meshFaces and meshFaces[-1].area <= SMALL_NUM:
|
||||
# Set their UV's to 0,0
|
||||
for uv in meshFaces[-1].uv:
|
||||
uv.zero()
|
||||
meshFaces.pop()
|
||||
|
||||
# Smallest first is slightly more efficient, but if the user cancels early then its better we work on the larger data.
|
||||
|
||||
# Generate Projection Vecs
|
||||
# 0d is 1.0
|
||||
# 180 IS -0.59846
|
||||
|
||||
|
||||
# Initialize projectVecs
|
||||
if USER_VIEW_INIT:
|
||||
# Generate Projection
|
||||
projectVecs = [Vector(Window.GetViewVector()) * ob.matrix_world.inverted().to_3x3()] # We add to this allong the way
|
||||
else:
|
||||
projectVecs = []
|
||||
|
||||
newProjectVec = meshFaces[0].no
|
||||
newProjectMeshFaces = [] # Popping stuffs it up.
|
||||
|
||||
|
||||
# Predent that the most unique angke is ages away to start the loop off
|
||||
mostUniqueAngle = -1.0
|
||||
|
||||
# This is popped
|
||||
tempMeshFaces = meshFaces[:]
|
||||
|
||||
|
||||
|
||||
# This while only gathers projection vecs, faces are assigned later on.
|
||||
while 1:
|
||||
# If theres none there then start with the largest face
|
||||
|
||||
# add all the faces that are close.
|
||||
for fIdx in range(len(tempMeshFaces)-1, -1, -1):
|
||||
# Use half the angle limit so we dont overweight faces towards this
|
||||
# normal and hog all the faces.
|
||||
if newProjectVec.dot(tempMeshFaces[fIdx].no) > USER_PROJECTION_LIMIT_HALF_CONVERTED:
|
||||
newProjectMeshFaces.append(tempMeshFaces.pop(fIdx))
|
||||
|
||||
# Add the average of all these faces normals as a projectionVec
|
||||
averageVec = Vector((0.0, 0.0, 0.0))
|
||||
if USER_AREA_WEIGHT:
|
||||
for fprop in newProjectMeshFaces:
|
||||
averageVec += (fprop.no * fprop.area)
|
||||
else:
|
||||
for fprop in newProjectMeshFaces:
|
||||
averageVec += fprop.no
|
||||
|
||||
if averageVec.x != 0 or averageVec.y != 0 or averageVec.z != 0: # Avoid NAN
|
||||
projectVecs.append(averageVec.normalized())
|
||||
|
||||
|
||||
# Get the next vec!
|
||||
# Pick the face thats most different to all existing angles :)
|
||||
mostUniqueAngle = 1.0 # 1.0 is 0d. no difference.
|
||||
mostUniqueIndex = 0 # dummy
|
||||
|
||||
for fIdx in range(len(tempMeshFaces)-1, -1, -1):
|
||||
angleDifference = -1.0 # 180d difference.
|
||||
|
||||
# Get the closest vec angle we are to.
|
||||
for p in projectVecs:
|
||||
temp_angle_diff= p.dot(tempMeshFaces[fIdx].no)
|
||||
|
||||
if angleDifference < temp_angle_diff:
|
||||
angleDifference= temp_angle_diff
|
||||
|
||||
if angleDifference < mostUniqueAngle:
|
||||
# We have a new most different angle
|
||||
mostUniqueIndex = fIdx
|
||||
mostUniqueAngle = angleDifference
|
||||
|
||||
if mostUniqueAngle < USER_PROJECTION_LIMIT_CONVERTED:
|
||||
#print 'adding', mostUniqueAngle, USER_PROJECTION_LIMIT, len(newProjectMeshFaces)
|
||||
# Now weight the vector to all its faces, will give a more direct projection
|
||||
# if the face its self was not representive of the normal from surrounding faces.
|
||||
|
||||
newProjectVec = tempMeshFaces[mostUniqueIndex].no
|
||||
newProjectMeshFaces = [tempMeshFaces.pop(mostUniqueIndex)]
|
||||
|
||||
|
||||
else:
|
||||
if len(projectVecs) >= 1: # Must have at least 2 projections
|
||||
break
|
||||
|
||||
|
||||
# If there are only zero area faces then its possible
|
||||
# there are no projectionVecs
|
||||
if not len(projectVecs):
|
||||
Draw.PupMenu('error, no projection vecs where generated, 0 area faces can cause this.')
|
||||
return
|
||||
|
||||
faceProjectionGroupList =[[] for i in range(len(projectVecs)) ]
|
||||
|
||||
# MAP and Arrange # We know there are 3 or 4 faces here
|
||||
|
||||
for fIdx in range(len(meshFaces)-1, -1, -1):
|
||||
fvec = meshFaces[fIdx].no
|
||||
i = len(projectVecs)
|
||||
|
||||
# Initialize first
|
||||
bestAng = fvec.dot(projectVecs[0])
|
||||
bestAngIdx = 0
|
||||
|
||||
# Cycle through the remaining, first already done
|
||||
while i-1:
|
||||
i-=1
|
||||
|
||||
newAng = fvec.dot(projectVecs[i])
|
||||
if newAng > bestAng: # Reverse logic for dotvecs
|
||||
bestAng = newAng
|
||||
bestAngIdx = i
|
||||
|
||||
# Store the area for later use.
|
||||
faceProjectionGroupList[bestAngIdx].append(meshFaces[fIdx])
|
||||
|
||||
# Cull faceProjectionGroupList,
|
||||
|
||||
|
||||
# Now faceProjectionGroupList is full of faces that face match the project Vecs list
|
||||
for i in range(len(projectVecs)):
|
||||
# Account for projectVecs having no faces.
|
||||
if not faceProjectionGroupList[i]:
|
||||
continue
|
||||
|
||||
# Make a projection matrix from a unit length vector.
|
||||
MatQuat = VectoQuat(projectVecs[i])
|
||||
|
||||
# Get the faces UV's from the projected vertex.
|
||||
for f in faceProjectionGroupList[i]:
|
||||
f_uv = f.uv
|
||||
for j, v in enumerate(f.v):
|
||||
# XXX - note, between mathutils in 2.4 and 2.5 the order changed.
|
||||
f_uv[j][:] = (v.co * MatQuat)[:2]
|
||||
|
||||
|
||||
if USER_SHARE_SPACE:
|
||||
# Should we collect and pack later?
|
||||
islandList = getUvIslands(faceProjectionGroupList, me)
|
||||
collected_islandList.extend(islandList)
|
||||
|
||||
else:
|
||||
# Should we pack the islands for this 1 object?
|
||||
islandList = getUvIslands(faceProjectionGroupList, me)
|
||||
packIslands(islandList)
|
||||
|
||||
|
||||
# update the mesh here if we need to.
|
||||
|
||||
# We want to pack all in 1 go, so pack now
|
||||
if USER_SHARE_SPACE:
|
||||
#XXX Window.DrawProgressBar(0.9, "Box Packing for all objects...")
|
||||
packIslands(collected_islandList)
|
||||
|
||||
print("Smart Projection time: %.2f" % (time.time() - time1))
|
||||
# Window.DrawProgressBar(0.9, "Smart Projections done, time: %.2f sec." % (time.time() - time1))
|
||||
|
||||
if is_editmode:
|
||||
bpy.ops.object.mode_set(mode='EDIT')
|
||||
|
||||
#XXX Window.DrawProgressBar(1.0, "")
|
||||
#XXX Window.WaitCursor(0)
|
||||
#XXX Window.RedrawAll()
|
||||
|
||||
"""
|
||||
pup_block = [\
|
||||
'Projection',\
|
||||
* ('Angle Limit:', USER_PROJECTION_LIMIT, 1, 89, ''),\
|
||||
('Selected Faces Only', USER_ONLY_SELECTED_FACES, 'Use only selected faces from all selected meshes.'),\
|
||||
('Init from view', USER_VIEW_INIT, 'The first projection will be from the view vector.'),\
|
||||
('Area Weight', USER_AREA_WEIGHT, 'Weight projections vector by face area.'),\
|
||||
'',\
|
||||
'',\
|
||||
'',\
|
||||
'UV Layout',\
|
||||
('Share Tex Space', USER_SHARE_SPACE, 'Objects Share texture space, map all objects into 1 uvmap.'),\
|
||||
('Stretch to bounds', USER_STRETCH_ASPECT, 'Stretch the final output to texture bounds.'),\
|
||||
* ('Island Margin:', USER_ISLAND_MARGIN, 0.0, 0.5, ''),\
|
||||
'Fill in empty areas',\
|
||||
('Fill Holes', USER_FILL_HOLES, 'Fill in empty areas reduced texture waistage (slow).'),\
|
||||
('Fill Quality:', USER_FILL_HOLES_QUALITY, 1, 100, 'Depends on fill holes, how tightly to fill UV holes, (higher is slower)'),\
|
||||
]
|
||||
"""
|
||||
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
class SmartProject(bpy.types.Operator):
|
||||
'''This script projection unwraps the selected faces of a mesh. it operates on all selected mesh objects, and can be used unwrap selected faces, or all faces.'''
|
||||
bl_idname = "uv.smart_project"
|
||||
bl_label = "Smart UV Project"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
angle_limit = FloatProperty(name="Angle Limit",
|
||||
description="lower for more projection groups, higher for less distortion.",
|
||||
default=66.0, min=1.0, max=89.0)
|
||||
|
||||
island_margin = FloatProperty(name="Island Margin",
|
||||
description="Margin to reduce bleed from adjacent islands.",
|
||||
default=0.0, min=0.0, max=1.0)
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object != None
|
||||
|
||||
def execute(self, context):
|
||||
main(context, self.island_margin, self.angle_limit)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
# Add to a menu
|
||||
menu_func = (lambda self, context: self.layout.operator(SmartProject.bl_idname,
|
||||
text="Smart Project"))
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
bpy.types.VIEW3D_MT_uv_map.append(menu_func)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
bpy.types.VIEW3D_MT_uv_map.remove(menu_func)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,190 +0,0 @@
|
||||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Script copyright (C) Campbell J Barton
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ***** END GPL LICENCE BLOCK *****
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
# History
|
||||
#
|
||||
# Originally written by Campbell Barton aka ideasman42
|
||||
#
|
||||
# 2009-11-01: * 2.5 port by Keith "Wahooney" Boshoff
|
||||
# * Replaced old method with my own, speed is similar (about 0.001 sec on Suzanne)
|
||||
# but results are far more accurate
|
||||
#
|
||||
|
||||
import bpy
|
||||
import math
|
||||
import time
|
||||
|
||||
from mathutils import Vector
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
def applyVertexDirt(me, blur_iterations, blur_strength, clamp_dirt, clamp_clean, dirt_only):
|
||||
## Window.WaitCursor(1)
|
||||
|
||||
#BPyMesh.meshCalcNormals(me)
|
||||
|
||||
vert_tone = [0.0] * len(me.vertices)
|
||||
|
||||
min_tone = 180.0
|
||||
max_tone = 0.0
|
||||
|
||||
# create lookup table for each vertex's connected vertices (via edges)
|
||||
con = []
|
||||
|
||||
con = [[] for i in range(len(me.vertices))]
|
||||
|
||||
# add connected verts
|
||||
for e in me.edges:
|
||||
con[e.vertices[0]].append(e.vertices[1])
|
||||
con[e.vertices[1]].append(e.vertices[0])
|
||||
|
||||
for i, v in enumerate(me.vertices):
|
||||
vec = Vector()
|
||||
no = v.normal
|
||||
co = v.co
|
||||
|
||||
# get the direction of the vectors between the vertex and it's connected vertices
|
||||
for c in con[i]:
|
||||
vec += (me.vertices[c].co - co).normalized()
|
||||
|
||||
# normalize the vector by dividing by the number of connected verts
|
||||
tot_con = len(con[i])
|
||||
|
||||
if tot_con == 0:
|
||||
continue
|
||||
|
||||
vec /= tot_con
|
||||
|
||||
# angle is the acos of the dot product between vert and connected verts normals
|
||||
ang = math.acos(no.dot(vec))
|
||||
|
||||
# enforce min/max
|
||||
ang = max(clamp_dirt, ang)
|
||||
|
||||
if not dirt_only:
|
||||
ang = min(clamp_clean, ang)
|
||||
|
||||
vert_tone[i] = ang
|
||||
|
||||
# blur tones
|
||||
for i in range(blur_iterations):
|
||||
# backup the original tones
|
||||
orig_vert_tone = list(vert_tone)
|
||||
|
||||
# use connected verts look up for blurring
|
||||
for j, c in enumerate(con):
|
||||
for v in c:
|
||||
vert_tone[j] += blur_strength * orig_vert_tone[v]
|
||||
|
||||
vert_tone[j] /= len(c) * blur_strength + 1
|
||||
|
||||
min_tone = min(vert_tone)
|
||||
max_tone = max(vert_tone)
|
||||
|
||||
# debug information
|
||||
# print(min_tone * 2 * math.pi)
|
||||
# print(max_tone * 2 * math.pi)
|
||||
# print(clamp_clean)
|
||||
# print(clamp_dirt)
|
||||
|
||||
tone_range = max_tone - min_tone
|
||||
|
||||
if not tone_range:
|
||||
return
|
||||
|
||||
active_col_layer = None
|
||||
|
||||
if len(me.vertex_colors):
|
||||
for lay in me.vertex_colors:
|
||||
if lay.active:
|
||||
active_col_layer = lay.data
|
||||
else:
|
||||
bpy.ops.mesh.vertex_color_add()
|
||||
me.vertex_colors[0].active = True
|
||||
active_col_layer = me.vertex_colors[0].data
|
||||
|
||||
if not active_col_layer:
|
||||
return('CANCELLED', )
|
||||
|
||||
for i, f in enumerate(me.faces):
|
||||
if not me.use_paint_mask or f.select:
|
||||
|
||||
f_col = active_col_layer[i]
|
||||
|
||||
f_col = [f_col.color1, f_col.color2, f_col.color3, f_col.color4]
|
||||
|
||||
for j, v in enumerate(f.vertices):
|
||||
col = f_col[j]
|
||||
tone = vert_tone[me.vertices[v].index]
|
||||
tone = (tone - min_tone) / tone_range
|
||||
|
||||
if dirt_only:
|
||||
tone = min(tone, 0.5)
|
||||
tone *= 2
|
||||
|
||||
col[0] = tone * col[0]
|
||||
col[1] = tone * col[1]
|
||||
col[2] = tone * col[2]
|
||||
|
||||
## Window.WaitCursor(0)
|
||||
|
||||
|
||||
class VertexPaintDirt(bpy.types.Operator):
|
||||
|
||||
bl_idname = "paint.vertex_color_dirt"
|
||||
bl_label = "Dirty Vertex Colors"
|
||||
bl_options = {'REGISTER', 'UNDO'}
|
||||
|
||||
blur_strength = FloatProperty(name="Blur Strength", description="Blur strength per iteration", default=1.0, min=0.01, max=1.0)
|
||||
blur_iterations = IntProperty(name="Blur Iterations", description="Number times to blur the colors. (higher blurs more)", default=1, min=0, max=40)
|
||||
clean_angle = FloatProperty(name="Highlight Angle", description="Less then 90 limits the angle used in the tonal range", default=180.0, min=0.0, max=180.0)
|
||||
dirt_angle = FloatProperty(name="Dirt Angle", description="Less then 90 limits the angle used in the tonal range", default=0.0, min=0.0, max=180.0)
|
||||
dirt_only = BoolProperty(name="Dirt Only", description="Dont calculate cleans for convex areas", default=False)
|
||||
|
||||
def execute(self, context):
|
||||
obj = context.object
|
||||
|
||||
if not obj or obj.type != 'MESH':
|
||||
print('Error, no active mesh object, aborting')
|
||||
return('CANCELLED',)
|
||||
|
||||
mesh = obj.data
|
||||
|
||||
t = time.time()
|
||||
|
||||
applyVertexDirt(mesh, self.blur_iterations, self.blur_strength, math.radians(self.dirt_angle), math.radians(self.clean_angle), self.dirt_only)
|
||||
|
||||
print('Dirt calculated in %.6f' % (time.time() - t))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,859 +0,0 @@
|
||||
# ##### BEGIN GPL LICENSE BLOCK #####
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
#
|
||||
# ##### END GPL LICENSE BLOCK #####
|
||||
|
||||
# <pep8 compliant>
|
||||
|
||||
import bpy
|
||||
|
||||
from bpy.props import *
|
||||
from rna_prop_ui import rna_idprop_ui_prop_get, rna_idprop_ui_prop_clear
|
||||
|
||||
|
||||
class MESH_OT_delete_edgeloop(bpy.types.Operator):
|
||||
'''Delete an edge loop by merging the faces on each side to a single face loop'''
|
||||
bl_idname = "mesh.delete_edgeloop"
|
||||
bl_label = "Delete Edge Loop"
|
||||
|
||||
def execute(self, context):
|
||||
if 'FINISHED' in bpy.ops.transform.edge_slide(value=1.0):
|
||||
bpy.ops.mesh.select_more()
|
||||
bpy.ops.mesh.remove_doubles()
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
rna_path_prop = StringProperty(name="Context Attributes",
|
||||
description="rna context string", maxlen=1024, default="")
|
||||
|
||||
rna_reverse_prop = BoolProperty(name="Reverse",
|
||||
description="Cycle backwards", default=False)
|
||||
|
||||
rna_relative_prop = BoolProperty(name="Relative",
|
||||
description="Apply relative to the current value (delta)",
|
||||
default=False)
|
||||
|
||||
|
||||
def context_path_validate(context, data_path):
|
||||
import sys
|
||||
try:
|
||||
value = eval("context.%s" % data_path) if data_path else Ellipsis
|
||||
except AttributeError:
|
||||
if "'NoneType'" in str(sys.exc_info()[1]):
|
||||
# One of the items in the rna path is None, just ignore this
|
||||
value = Ellipsis
|
||||
else:
|
||||
# We have a real error in the rna path, dont ignore that
|
||||
raise
|
||||
|
||||
return value
|
||||
|
||||
|
||||
def execute_context_assign(self, context):
|
||||
if context_path_validate(context, self.data_path) is Ellipsis:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
if getattr(self, "relative", False):
|
||||
exec("context.%s+=self.value" % self.data_path)
|
||||
else:
|
||||
exec("context.%s=self.value" % self.data_path)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class BRUSH_OT_set_active_number(bpy.types.Operator):
|
||||
'''Set active sculpt/paint brush from it's number'''
|
||||
bl_idname = "brush.set_active_number"
|
||||
bl_label = "Set Brush Number"
|
||||
|
||||
mode = StringProperty(name="mode",
|
||||
description="Paint mode to set brush for", maxlen=1024)
|
||||
number = IntProperty(name="number",
|
||||
description="Brush number")
|
||||
|
||||
_attr_dict = {"sculpt": "use_paint_sculpt",
|
||||
"vertex_paint": "use_paint_vertex",
|
||||
"weight_paint": "use_paint_weight",
|
||||
"image_paint": "use_paint_texture"}
|
||||
|
||||
def execute(self, context):
|
||||
attr = self._attr_dict.get(self.mode)
|
||||
if attr is None:
|
||||
return {'CANCELLED'}
|
||||
|
||||
for i, brush in enumerate((cur for cur in bpy.data.brushes if getattr(cur, attr))):
|
||||
if i == self.number:
|
||||
getattr(context.tool_settings, self.mode).brush = brush
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'CANCELLED'}
|
||||
|
||||
|
||||
class WM_OT_context_set_boolean(bpy.types.Operator):
|
||||
'''Set a context value.'''
|
||||
bl_idname = "wm.context_set_boolean"
|
||||
bl_label = "Context Set Boolean"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
value = BoolProperty(name="Value",
|
||||
description="Assignment value", default=True)
|
||||
|
||||
execute = execute_context_assign
|
||||
|
||||
|
||||
class WM_OT_context_set_int(bpy.types.Operator): # same as enum
|
||||
'''Set a context value.'''
|
||||
bl_idname = "wm.context_set_int"
|
||||
bl_label = "Context Set"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
value = IntProperty(name="Value", description="Assign value", default=0)
|
||||
relative = rna_relative_prop
|
||||
|
||||
execute = execute_context_assign
|
||||
|
||||
|
||||
class WM_OT_context_scale_int(bpy.types.Operator):
|
||||
'''Scale an int context value.'''
|
||||
bl_idname = "wm.context_scale_int"
|
||||
bl_label = "Context Set"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
value = FloatProperty(name="Value", description="Assign value", default=1.0)
|
||||
always_step = BoolProperty(name="Always Step",
|
||||
description="Always adjust the value by a minimum of 1 when 'value' is not 1.0.",
|
||||
default=True)
|
||||
|
||||
def execute(self, context):
|
||||
if context_path_validate(context, self.data_path) is Ellipsis:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
value = self.value
|
||||
data_path = self.data_path
|
||||
|
||||
if value == 1.0: # nothing to do
|
||||
return {'CANCELLED'}
|
||||
|
||||
if getattr(self, "always_step", False):
|
||||
if value > 1.0:
|
||||
add = "1"
|
||||
func = "max"
|
||||
else:
|
||||
add = "-1"
|
||||
func = "min"
|
||||
exec("context.%s = %s(round(context.%s * value), context.%s + %s)" % (data_path, func, data_path, data_path, add))
|
||||
else:
|
||||
exec("context.%s *= value" % self.data_path)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_context_set_float(bpy.types.Operator): # same as enum
|
||||
'''Set a context value.'''
|
||||
bl_idname = "wm.context_set_float"
|
||||
bl_label = "Context Set Float"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
value = FloatProperty(name="Value",
|
||||
description="Assignment value", default=0.0)
|
||||
relative = rna_relative_prop
|
||||
|
||||
execute = execute_context_assign
|
||||
|
||||
|
||||
class WM_OT_context_set_string(bpy.types.Operator): # same as enum
|
||||
'''Set a context value.'''
|
||||
bl_idname = "wm.context_set_string"
|
||||
bl_label = "Context Set String"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
value = StringProperty(name="Value",
|
||||
description="Assign value", maxlen=1024, default="")
|
||||
|
||||
execute = execute_context_assign
|
||||
|
||||
|
||||
class WM_OT_context_set_enum(bpy.types.Operator):
|
||||
'''Set a context value.'''
|
||||
bl_idname = "wm.context_set_enum"
|
||||
bl_label = "Context Set Enum"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
value = StringProperty(name="Value",
|
||||
description="Assignment value (as a string)",
|
||||
maxlen=1024, default="")
|
||||
|
||||
execute = execute_context_assign
|
||||
|
||||
|
||||
class WM_OT_context_set_value(bpy.types.Operator):
|
||||
'''Set a context value.'''
|
||||
bl_idname = "wm.context_set_value"
|
||||
bl_label = "Context Set Value"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
value = StringProperty(name="Value",
|
||||
description="Assignment value (as a string)",
|
||||
maxlen=1024, default="")
|
||||
|
||||
def execute(self, context):
|
||||
if context_path_validate(context, self.data_path) is Ellipsis:
|
||||
return {'PASS_THROUGH'}
|
||||
exec("context.%s=%s" % (self.data_path, self.value))
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_context_toggle(bpy.types.Operator):
|
||||
'''Toggle a context value.'''
|
||||
bl_idname = "wm.context_toggle"
|
||||
bl_label = "Context Toggle"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
if context_path_validate(context, self.data_path) is Ellipsis:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
exec("context.%s=not (context.%s)" %
|
||||
(self.data_path, self.data_path))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_context_toggle_enum(bpy.types.Operator):
|
||||
'''Toggle a context value.'''
|
||||
bl_idname = "wm.context_toggle_enum"
|
||||
bl_label = "Context Toggle Values"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
value_1 = StringProperty(name="Value", \
|
||||
description="Toggle enum", maxlen=1024, default="")
|
||||
|
||||
value_2 = StringProperty(name="Value", \
|
||||
description="Toggle enum", maxlen=1024, default="")
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
if context_path_validate(context, self.data_path) is Ellipsis:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
exec("context.%s = ['%s', '%s'][context.%s!='%s']" % \
|
||||
(self.data_path, self.value_1,\
|
||||
self.value_2, self.data_path,
|
||||
self.value_2))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_context_cycle_int(bpy.types.Operator):
|
||||
'''Set a context value. Useful for cycling active material, '''
|
||||
'''vertex keys, groups' etc.'''
|
||||
bl_idname = "wm.context_cycle_int"
|
||||
bl_label = "Context Int Cycle"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
reverse = rna_reverse_prop
|
||||
|
||||
def execute(self, context):
|
||||
data_path = self.data_path
|
||||
value = context_path_validate(context, data_path)
|
||||
if value is Ellipsis:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
if self.reverse:
|
||||
value -= 1
|
||||
else:
|
||||
value += 1
|
||||
|
||||
exec("context.%s=value" % data_path)
|
||||
|
||||
if value != eval("context.%s" % data_path):
|
||||
# relies on rna clamping int's out of the range
|
||||
if self.reverse:
|
||||
value = (1 << 31) - 1
|
||||
else:
|
||||
value = -1 << 31
|
||||
|
||||
exec("context.%s=value" % data_path)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_context_cycle_enum(bpy.types.Operator):
|
||||
'''Toggle a context value.'''
|
||||
bl_idname = "wm.context_cycle_enum"
|
||||
bl_label = "Context Enum Cycle"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
reverse = rna_reverse_prop
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
value = context_path_validate(context, self.data_path)
|
||||
if value is Ellipsis:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
orig_value = value
|
||||
|
||||
# Have to get rna enum values
|
||||
rna_struct_str, rna_prop_str = self.data_path.rsplit('.', 1)
|
||||
i = rna_prop_str.find('[')
|
||||
|
||||
# just incse we get "context.foo.bar[0]"
|
||||
if i != -1:
|
||||
rna_prop_str = rna_prop_str[0:i]
|
||||
|
||||
rna_struct = eval("context.%s.rna_type" % rna_struct_str)
|
||||
|
||||
rna_prop = rna_struct.properties[rna_prop_str]
|
||||
|
||||
if type(rna_prop) != bpy.types.EnumProperty:
|
||||
raise Exception("expected an enum property")
|
||||
|
||||
enums = rna_struct.properties[rna_prop_str].items.keys()
|
||||
orig_index = enums.index(orig_value)
|
||||
|
||||
# Have the info we need, advance to the next item
|
||||
if self.reverse:
|
||||
if orig_index == 0:
|
||||
advance_enum = enums[-1]
|
||||
else:
|
||||
advance_enum = enums[orig_index - 1]
|
||||
else:
|
||||
if orig_index == len(enums) - 1:
|
||||
advance_enum = enums[0]
|
||||
else:
|
||||
advance_enum = enums[orig_index + 1]
|
||||
|
||||
# set the new value
|
||||
exec("context.%s=advance_enum" % self.data_path)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_context_cycle_array(bpy.types.Operator):
|
||||
'''Set a context array value.
|
||||
Useful for cycling the active mesh edit mode.'''
|
||||
bl_idname = "wm.context_cycle_array"
|
||||
bl_label = "Context Array Cycle"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
reverse = rna_reverse_prop
|
||||
|
||||
def execute(self, context):
|
||||
data_path = self.data_path
|
||||
value = context_path_validate(context, data_path)
|
||||
if value is Ellipsis:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
def cycle(array):
|
||||
if self.reverse:
|
||||
array.insert(0, array.pop())
|
||||
else:
|
||||
array.append(array.pop(0))
|
||||
return array
|
||||
|
||||
exec("context.%s=cycle(context.%s[:])" % (data_path, data_path))
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_MT_context_menu_enum(bpy.types.Menu):
|
||||
bl_label = ""
|
||||
data_path = "" # BAD DESIGN, set from operator below.
|
||||
|
||||
def draw(self, context):
|
||||
data_path = self.data_path
|
||||
value = context_path_validate(bpy.context, data_path)
|
||||
if value is Ellipsis:
|
||||
return {'PASS_THROUGH'}
|
||||
base_path, prop_string = data_path.rsplit(".", 1)
|
||||
value_base = context_path_validate(context, base_path)
|
||||
|
||||
values = [(i.name, i.identifier) for i in value_base.bl_rna.properties[prop_string].items]
|
||||
|
||||
for name, identifier in values:
|
||||
prop = self.layout.operator("wm.context_set_enum", text=name)
|
||||
prop.data_path = data_path
|
||||
prop.value = identifier
|
||||
|
||||
|
||||
class WM_OT_context_menu_enum(bpy.types.Operator):
|
||||
bl_idname = "wm.context_menu_enum"
|
||||
bl_label = "Context Enum Menu"
|
||||
bl_options = {'UNDO'}
|
||||
data_path = rna_path_prop
|
||||
|
||||
def execute(self, context):
|
||||
data_path = self.data_path
|
||||
WM_MT_context_menu_enum.data_path = data_path
|
||||
bpy.ops.wm.call_menu(name="WM_MT_context_menu_enum")
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
|
||||
class WM_OT_context_set_id(bpy.types.Operator):
|
||||
'''Toggle a context value.'''
|
||||
bl_idname = "wm.context_set_id"
|
||||
bl_label = "Set Library ID"
|
||||
bl_options = {'UNDO'}
|
||||
|
||||
data_path = rna_path_prop
|
||||
value = StringProperty(name="Value",
|
||||
description="Assign value", maxlen=1024, default="")
|
||||
|
||||
def execute(self, context):
|
||||
value = self.value
|
||||
data_path = self.data_path
|
||||
|
||||
# match the pointer type from the target property to bpy.data.*
|
||||
# so we lookup the correct list.
|
||||
data_path_base, data_path_prop = data_path.rsplit(".", 1)
|
||||
data_prop_rna = eval("context.%s" % data_path_base).rna_type.properties[data_path_prop]
|
||||
data_prop_rna_type = data_prop_rna.fixed_type
|
||||
|
||||
id_iter = None
|
||||
|
||||
for prop in bpy.data.rna_type.properties:
|
||||
if prop.rna_type.identifier == "CollectionProperty":
|
||||
if prop.fixed_type == data_prop_rna_type:
|
||||
id_iter = prop.identifier
|
||||
break
|
||||
|
||||
if id_iter:
|
||||
value_id = getattr(bpy.data, id_iter).get(value)
|
||||
exec("context.%s=value_id" % data_path)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
doc_id = StringProperty(name="Doc ID",
|
||||
description="", maxlen=1024, default="", options={'HIDDEN'})
|
||||
|
||||
doc_new = StringProperty(name="Edit Description",
|
||||
description="", maxlen=1024, default="")
|
||||
|
||||
|
||||
class WM_OT_context_modal_mouse(bpy.types.Operator):
|
||||
'''Adjust arbitrary values with mouse input'''
|
||||
bl_idname = "wm.context_modal_mouse"
|
||||
bl_label = "Context Modal Mouse"
|
||||
|
||||
data_path_iter = StringProperty(description="The data path relative to the context, must point to an iterable.")
|
||||
data_path_item = StringProperty(description="The data path from each iterable to the value (int or float)")
|
||||
input_scale = FloatProperty(default=0.01, description="Scale the mouse movement by this value before applying the delta")
|
||||
invert = BoolProperty(default=False, description="Invert the mouse input")
|
||||
initial_x = IntProperty(options={'HIDDEN'})
|
||||
|
||||
def _values_store(self, context):
|
||||
data_path_iter = self.data_path_iter
|
||||
data_path_item = self.data_path_item
|
||||
|
||||
self._values = values = {}
|
||||
|
||||
for item in getattr(context, data_path_iter):
|
||||
try:
|
||||
value_orig = eval("item." + data_path_item)
|
||||
except:
|
||||
continue
|
||||
|
||||
# check this can be set, maybe this is library data.
|
||||
try:
|
||||
exec("item.%s = %s" % (data_path_item, value_orig))
|
||||
except:
|
||||
continue
|
||||
|
||||
values[item] = value_orig
|
||||
|
||||
def _values_delta(self, delta):
|
||||
delta *= self.input_scale
|
||||
if self.invert:
|
||||
delta = - delta
|
||||
|
||||
data_path_item = self.data_path_item
|
||||
for item, value_orig in self._values.items():
|
||||
if type(value_orig) == int:
|
||||
exec("item.%s = int(%d)" % (data_path_item, round(value_orig + delta)))
|
||||
else:
|
||||
exec("item.%s = %f" % (data_path_item, value_orig + delta))
|
||||
|
||||
def _values_restore(self):
|
||||
data_path_item = self.data_path_item
|
||||
for item, value_orig in self._values.items():
|
||||
exec("item.%s = %s" % (data_path_item, value_orig))
|
||||
|
||||
self._values.clear()
|
||||
|
||||
def _values_clear(self):
|
||||
self._values.clear()
|
||||
|
||||
def modal(self, context, event):
|
||||
event_type = event.type
|
||||
|
||||
if event_type == 'MOUSEMOVE':
|
||||
delta = event.mouse_x - self.initial_x
|
||||
self._values_delta(delta)
|
||||
|
||||
elif 'LEFTMOUSE' == event_type:
|
||||
self._values_clear()
|
||||
return {'FINISHED'}
|
||||
|
||||
elif event_type in ('RIGHTMOUSE', 'ESC'):
|
||||
self._values_restore()
|
||||
return {'FINISHED'}
|
||||
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
self._values_store(context)
|
||||
|
||||
if not self._values:
|
||||
self.report({'WARNING'}, "Nothing to operate on: %s[ ].%s" %
|
||||
(self.data_path_iter, self.data_path_item))
|
||||
|
||||
return {'CANCELLED'}
|
||||
else:
|
||||
self.initial_x = event.mouse_x
|
||||
|
||||
context.window_manager.modal_handler_add(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
|
||||
class WM_OT_url_open(bpy.types.Operator):
|
||||
"Open a website in the Webbrowser"
|
||||
bl_idname = "wm.url_open"
|
||||
bl_label = ""
|
||||
|
||||
url = StringProperty(name="URL", description="URL to open")
|
||||
|
||||
def execute(self, context):
|
||||
import webbrowser
|
||||
webbrowser.open(self.url)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_path_open(bpy.types.Operator):
|
||||
"Open a path in a file browser"
|
||||
bl_idname = "wm.path_open"
|
||||
bl_label = ""
|
||||
|
||||
filepath = StringProperty(name="File Path", maxlen=1024, subtype='FILE_PATH')
|
||||
|
||||
def execute(self, context):
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
filepath = bpy.path.abspath(self.filepath)
|
||||
filepath = os.path.normpath(filepath)
|
||||
|
||||
if not os.path.exists(filepath):
|
||||
self.report({'ERROR'}, "File '%s' not found" % filepath)
|
||||
return {'CANCELLED'}
|
||||
|
||||
if sys.platform == 'win32':
|
||||
subprocess.Popen(['start', filepath], shell=True)
|
||||
elif sys.platform == 'darwin':
|
||||
subprocess.Popen(['open', filepath])
|
||||
else:
|
||||
try:
|
||||
subprocess.Popen(['xdg-open', filepath])
|
||||
except OSError:
|
||||
# xdg-open *should* be supported by recent Gnome, KDE, Xfce
|
||||
pass
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_doc_view(bpy.types.Operator):
|
||||
'''Load online reference docs'''
|
||||
bl_idname = "wm.doc_view"
|
||||
bl_label = "View Documentation"
|
||||
|
||||
doc_id = doc_id
|
||||
_prefix = "http://www.blender.org/documentation/blender_python_api_%s" % "_".join(str(v) for v in bpy.app.version)
|
||||
|
||||
def _nested_class_string(self, class_string):
|
||||
ls = []
|
||||
class_obj = getattr(bpy.types, class_string, None).bl_rna
|
||||
while class_obj:
|
||||
ls.insert(0, class_obj)
|
||||
class_obj = class_obj.nested
|
||||
return '.'.join(class_obj.identifier for class_obj in ls)
|
||||
|
||||
def execute(self, context):
|
||||
id_split = self.doc_id.split('.')
|
||||
if len(id_split) == 1: # rna, class
|
||||
url = '%s/bpy.types.%s.html' % (self._prefix, id_split[0])
|
||||
elif len(id_split) == 2: # rna, class.prop
|
||||
class_name, class_prop = id_split
|
||||
|
||||
if hasattr(bpy.types, class_name.upper() + '_OT_' + class_prop):
|
||||
url = '%s/bpy.ops.%s.html#bpy.ops.%s.%s' % \
|
||||
(self._prefix, class_name, class_name, class_prop)
|
||||
else:
|
||||
# It so happens that epydoc nests these, not sphinx
|
||||
# class_name_full = self._nested_class_string(class_name)
|
||||
url = '%s/bpy.types.%s.html#bpy.types.%s.%s' % \
|
||||
(self._prefix, class_name, class_name, class_prop)
|
||||
|
||||
else:
|
||||
return {'PASS_THROUGH'}
|
||||
|
||||
import webbrowser
|
||||
webbrowser.open(url)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_doc_edit(bpy.types.Operator):
|
||||
'''Load online reference docs'''
|
||||
bl_idname = "wm.doc_edit"
|
||||
bl_label = "Edit Documentation"
|
||||
|
||||
doc_id = doc_id
|
||||
doc_new = doc_new
|
||||
|
||||
_url = "http://www.mindrones.com/blender/svn/xmlrpc.php"
|
||||
|
||||
def _send_xmlrpc(self, data_dict):
|
||||
print("sending data:", data_dict)
|
||||
|
||||
import xmlrpc.client
|
||||
user = 'blenderuser'
|
||||
pwd = 'blender>user'
|
||||
|
||||
docblog = xmlrpc.client.ServerProxy(self._url)
|
||||
docblog.metaWeblog.newPost(1, user, pwd, data_dict, 1)
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
doc_id = self.doc_id
|
||||
doc_new = self.doc_new
|
||||
|
||||
class_name, class_prop = doc_id.split('.')
|
||||
|
||||
if not doc_new:
|
||||
self.report({'ERROR'}, "No input given for '%s'" % doc_id)
|
||||
return {'CANCELLED'}
|
||||
|
||||
# check if this is an operator
|
||||
op_name = class_name.upper() + '_OT_' + class_prop
|
||||
op_class = getattr(bpy.types, op_name, None)
|
||||
|
||||
# Upload this to the web server
|
||||
upload = {}
|
||||
|
||||
if op_class:
|
||||
rna = op_class.bl_rna
|
||||
doc_orig = rna.description
|
||||
if doc_orig == doc_new:
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
print("op - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
|
||||
upload["title"] = 'OPERATOR %s:%s' % (doc_id, doc_orig)
|
||||
else:
|
||||
rna = getattr(bpy.types, class_name).bl_rna
|
||||
doc_orig = rna.properties[class_prop].description
|
||||
if doc_orig == doc_new:
|
||||
return {'RUNNING_MODAL'}
|
||||
|
||||
print("rna - old:'%s' -> new:'%s'" % (doc_orig, doc_new))
|
||||
upload["title"] = 'RNA %s:%s' % (doc_id, doc_orig)
|
||||
|
||||
upload["description"] = doc_new
|
||||
|
||||
self._send_xmlrpc(upload)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def draw(self, context):
|
||||
layout = self.layout
|
||||
layout.label(text="Descriptor ID: '%s'" % self.doc_id)
|
||||
layout.prop(self, "doc_new", text="")
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self, width=600)
|
||||
|
||||
|
||||
from bpy.props import *
|
||||
|
||||
|
||||
rna_path = StringProperty(name="Property Edit",
|
||||
description="Property data_path edit", maxlen=1024, default="", options={'HIDDEN'})
|
||||
|
||||
rna_value = StringProperty(name="Property Value",
|
||||
description="Property value edit", maxlen=1024, default="")
|
||||
|
||||
rna_property = StringProperty(name="Property Name",
|
||||
description="Property name edit", maxlen=1024, default="")
|
||||
|
||||
rna_min = FloatProperty(name="Min", default=0.0, precision=3)
|
||||
rna_max = FloatProperty(name="Max", default=1.0, precision=3)
|
||||
|
||||
|
||||
class WM_OT_properties_edit(bpy.types.Operator):
|
||||
'''Internal use (edit a property data_path)'''
|
||||
bl_idname = "wm.properties_edit"
|
||||
bl_label = "Edit Property"
|
||||
bl_options = {'REGISTER'} # only because invoke_props_popup requires.
|
||||
|
||||
data_path = rna_path
|
||||
property = rna_property
|
||||
value = rna_value
|
||||
min = rna_min
|
||||
max = rna_max
|
||||
description = StringProperty(name="Tip", default="")
|
||||
|
||||
def execute(self, context):
|
||||
data_path = self.data_path
|
||||
value = self.value
|
||||
prop = self.property
|
||||
prop_old = self._last_prop[0]
|
||||
|
||||
try:
|
||||
value_eval = eval(value)
|
||||
except:
|
||||
value_eval = value
|
||||
|
||||
# First remove
|
||||
item = eval("context.%s" % data_path)
|
||||
|
||||
rna_idprop_ui_prop_clear(item, prop_old)
|
||||
exec_str = "del item['%s']" % prop_old
|
||||
# print(exec_str)
|
||||
exec(exec_str)
|
||||
|
||||
# Reassign
|
||||
exec_str = "item['%s'] = %s" % (prop, repr(value_eval))
|
||||
# print(exec_str)
|
||||
exec(exec_str)
|
||||
self._last_prop[:] = [prop]
|
||||
|
||||
prop_type = type(item[prop])
|
||||
|
||||
prop_ui = rna_idprop_ui_prop_get(item, prop)
|
||||
|
||||
if prop_type in (float, int):
|
||||
|
||||
prop_ui['soft_min'] = prop_ui['min'] = prop_type(self.min)
|
||||
prop_ui['soft_max'] = prop_ui['max'] = prop_type(self.max)
|
||||
|
||||
prop_ui['description'] = self.description
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
|
||||
self._last_prop = [self.property]
|
||||
|
||||
item = eval("context.%s" % self.data_path)
|
||||
|
||||
# setup defaults
|
||||
prop_ui = rna_idprop_ui_prop_get(item, self.property, False) # dont create
|
||||
if prop_ui:
|
||||
self.min = prop_ui.get("min", -1000000000)
|
||||
self.max = prop_ui.get("max", 1000000000)
|
||||
self.description = prop_ui.get("description", "")
|
||||
|
||||
wm = context.window_manager
|
||||
return wm.invoke_props_dialog(self)
|
||||
|
||||
|
||||
class WM_OT_properties_add(bpy.types.Operator):
|
||||
'''Internal use (edit a property data_path)'''
|
||||
bl_idname = "wm.properties_add"
|
||||
bl_label = "Add Property"
|
||||
|
||||
data_path = rna_path
|
||||
|
||||
def execute(self, context):
|
||||
item = eval("context.%s" % self.data_path)
|
||||
|
||||
def unique_name(names):
|
||||
prop = 'prop'
|
||||
prop_new = prop
|
||||
i = 1
|
||||
while prop_new in names:
|
||||
prop_new = prop + str(i)
|
||||
i += 1
|
||||
|
||||
return prop_new
|
||||
|
||||
property = unique_name(item.keys())
|
||||
|
||||
item[property] = 1.0
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_properties_remove(bpy.types.Operator):
|
||||
'''Internal use (edit a property data_path)'''
|
||||
bl_idname = "wm.properties_remove"
|
||||
bl_label = "Remove Property"
|
||||
|
||||
data_path = rna_path
|
||||
property = rna_property
|
||||
|
||||
def execute(self, context):
|
||||
item = eval("context.%s" % self.data_path)
|
||||
del item[self.property]
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_keyconfig_activate(bpy.types.Operator):
|
||||
bl_idname = "wm.keyconfig_activate"
|
||||
bl_label = "Activate Keyconfig"
|
||||
|
||||
filepath = StringProperty(name="File Path", maxlen=1024)
|
||||
|
||||
def execute(self, context):
|
||||
bpy.utils.keyconfig_set(self.filepath)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
class WM_OT_sysinfo(bpy.types.Operator):
|
||||
'''Generate System Info'''
|
||||
bl_idname = "wm.sysinfo"
|
||||
bl_label = "System Info"
|
||||
|
||||
def execute(self, context):
|
||||
import sys_info
|
||||
sys_info.write_sysinfo(self)
|
||||
return {'FINISHED'}
|
||||
|
||||
|
||||
def register():
|
||||
bpy.utils.register_module(__name__)
|
||||
|
||||
|
||||
def unregister():
|
||||
bpy.utils.unregister_module(__name__)
|
||||
|
||||
if __name__ == "__main__":
|
||||
register()
|
||||
@@ -1,382 +1,6 @@
|
||||
# Configuration Maya
|
||||
# Configuration Blender
|
||||
import bpy
|
||||
|
||||
wm = bpy.context.window_manager
|
||||
kc = wm.keyconfigs.new('Maya')
|
||||
|
||||
# Map 3D View
|
||||
km = kc.keymaps.new('3D View', space_type='VIEW_3D', region_type='WINDOW', modal=False)
|
||||
|
||||
kmi = km.items.new('view3d.manipulator', 'LEFTMOUSE', 'PRESS', any=True)
|
||||
kmi.properties.release_confirm = True
|
||||
kmi = km.items.new('view3d.cursor3d', 'ACTIONMOUSE', 'PRESS')
|
||||
kmi = km.items.new('view3d.rotate', 'LEFTMOUSE', 'PRESS', alt=True)
|
||||
kmi = km.items.new('view3d.move', 'MIDDLEMOUSE', 'PRESS', alt=True)
|
||||
kmi = km.items.new('view3d.zoom', 'RIGHTMOUSE', 'PRESS', alt=True)
|
||||
kmi = km.items.new('view3d.view_selected', 'NUMPAD_PERIOD', 'PRESS')
|
||||
kmi = km.items.new('view3d.view_center_cursor', 'NUMPAD_PERIOD', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('view3d.fly', 'F', 'PRESS', shift=True)
|
||||
kmi = km.items.new('view3d.smoothview', 'TIMER1', 'ANY', any=True)
|
||||
kmi = km.items.new('view3d.rotate', 'TRACKPADPAN', 'ANY', alt=True)
|
||||
kmi = km.items.new('view3d.rotate', 'MOUSEROTATE', 'ANY')
|
||||
kmi = km.items.new('view3d.move', 'TRACKPADPAN', 'ANY')
|
||||
kmi = km.items.new('view3d.zoom', 'TRACKPADZOOM', 'ANY')
|
||||
kmi = km.items.new('view3d.zoom', 'NUMPAD_PLUS', 'PRESS')
|
||||
kmi.properties.delta = 1
|
||||
kmi = km.items.new('view3d.zoom', 'NUMPAD_MINUS', 'PRESS')
|
||||
kmi.properties.delta = -1
|
||||
kmi = km.items.new('view3d.zoom', 'EQUAL', 'PRESS', ctrl=True)
|
||||
kmi.properties.delta = 1
|
||||
kmi = km.items.new('view3d.zoom', 'MINUS', 'PRESS', ctrl=True)
|
||||
kmi.properties.delta = -1
|
||||
kmi = km.items.new('view3d.zoom', 'WHEELINMOUSE', 'PRESS')
|
||||
kmi.properties.delta = 1
|
||||
kmi = km.items.new('view3d.zoom', 'WHEELOUTMOUSE', 'PRESS')
|
||||
kmi.properties.delta = -1
|
||||
kmi = km.items.new('view3d.view_all', 'HOME', 'PRESS')
|
||||
kmi.properties.center = False
|
||||
kmi = km.items.new('view3d.view_all', 'C', 'PRESS', shift=True)
|
||||
kmi.properties.center = True
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_0', 'PRESS')
|
||||
kmi.properties.type = 'CAMERA'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS')
|
||||
kmi.properties.type = 'FRONT'
|
||||
kmi = km.items.new('view3d.view_orbit', 'NUMPAD_2', 'PRESS')
|
||||
kmi.properties.type = 'ORBITDOWN'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS')
|
||||
kmi.properties.type = 'RIGHT'
|
||||
kmi = km.items.new('view3d.view_orbit', 'NUMPAD_4', 'PRESS')
|
||||
kmi.properties.type = 'ORBITLEFT'
|
||||
kmi = km.items.new('view3d.view_persportho', 'NUMPAD_5', 'PRESS')
|
||||
kmi = km.items.new('view3d.view_orbit', 'NUMPAD_6', 'PRESS')
|
||||
kmi.properties.type = 'ORBITRIGHT'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS')
|
||||
kmi.properties.type = 'TOP'
|
||||
kmi = km.items.new('view3d.view_orbit', 'NUMPAD_8', 'PRESS')
|
||||
kmi.properties.type = 'ORBITUP'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS', ctrl=True)
|
||||
kmi.properties.type = 'BACK'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS', ctrl=True)
|
||||
kmi.properties.type = 'LEFT'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS', ctrl=True)
|
||||
kmi.properties.type = 'BOTTOM'
|
||||
kmi = km.items.new('view3d.view_pan', 'NUMPAD_2', 'PRESS', ctrl=True)
|
||||
kmi.properties.type = 'PANDOWN'
|
||||
kmi = km.items.new('view3d.view_pan', 'NUMPAD_4', 'PRESS', ctrl=True)
|
||||
kmi.properties.type = 'PANLEFT'
|
||||
kmi = km.items.new('view3d.view_pan', 'NUMPAD_6', 'PRESS', ctrl=True)
|
||||
kmi.properties.type = 'PANRIGHT'
|
||||
kmi = km.items.new('view3d.view_pan', 'NUMPAD_8', 'PRESS', ctrl=True)
|
||||
kmi.properties.type = 'PANUP'
|
||||
kmi = km.items.new('view3d.view_pan', 'WHEELUPMOUSE', 'PRESS', ctrl=True)
|
||||
kmi.properties.type = 'PANRIGHT'
|
||||
kmi = km.items.new('view3d.view_pan', 'WHEELDOWNMOUSE', 'PRESS', ctrl=True)
|
||||
kmi.properties.type = 'PANLEFT'
|
||||
kmi = km.items.new('view3d.view_pan', 'WHEELUPMOUSE', 'PRESS', shift=True)
|
||||
kmi.properties.type = 'PANUP'
|
||||
kmi = km.items.new('view3d.view_pan', 'WHEELDOWNMOUSE', 'PRESS', shift=True)
|
||||
kmi.properties.type = 'PANDOWN'
|
||||
kmi = km.items.new('view3d.view_orbit', 'WHEELUPMOUSE', 'PRESS', ctrl=True, alt=True)
|
||||
kmi.properties.type = 'ORBITLEFT'
|
||||
kmi = km.items.new('view3d.view_orbit', 'WHEELDOWNMOUSE', 'PRESS', ctrl=True, alt=True)
|
||||
kmi.properties.type = 'ORBITRIGHT'
|
||||
kmi = km.items.new('view3d.view_orbit', 'WHEELUPMOUSE', 'PRESS', shift=True, alt=True)
|
||||
kmi.properties.type = 'ORBITUP'
|
||||
kmi = km.items.new('view3d.view_orbit', 'WHEELDOWNMOUSE', 'PRESS', shift=True, alt=True)
|
||||
kmi.properties.type = 'ORBITDOWN'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS', shift=True)
|
||||
kmi.properties.align_active = True
|
||||
kmi.properties.type = 'FRONT'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS', shift=True)
|
||||
kmi.properties.align_active = True
|
||||
kmi.properties.type = 'RIGHT'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS', shift=True)
|
||||
kmi.properties.align_active = True
|
||||
kmi.properties.type = 'TOP'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_1', 'PRESS', shift=True, ctrl=True)
|
||||
kmi.properties.align_active = True
|
||||
kmi.properties.type = 'BACK'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_3', 'PRESS', shift=True, ctrl=True)
|
||||
kmi.properties.align_active = True
|
||||
kmi.properties.type = 'LEFT'
|
||||
kmi = km.items.new('view3d.viewnumpad', 'NUMPAD_7', 'PRESS', shift=True, ctrl=True)
|
||||
kmi.properties.align_active = True
|
||||
kmi.properties.type = 'BOTTOM'
|
||||
kmi = km.items.new('view3d.localview', 'NUMPAD_SLASH', 'PRESS')
|
||||
kmi = km.items.new('view3d.layers', 'ACCENT_GRAVE', 'PRESS')
|
||||
kmi.properties.nr = 0
|
||||
kmi = km.items.new('view3d.layers', 'ONE', 'PRESS', any=True)
|
||||
kmi.properties.nr = 1
|
||||
kmi = km.items.new('view3d.layers', 'TWO', 'PRESS', any=True)
|
||||
kmi.properties.nr = 2
|
||||
kmi = km.items.new('view3d.layers', 'THREE', 'PRESS', any=True)
|
||||
kmi.properties.nr = 3
|
||||
kmi = km.items.new('view3d.layers', 'FOUR', 'PRESS', any=True)
|
||||
kmi.properties.nr = 4
|
||||
kmi = km.items.new('view3d.layers', 'FIVE', 'PRESS', any=True)
|
||||
kmi.properties.nr = 5
|
||||
kmi = km.items.new('view3d.layers', 'SIX', 'PRESS', any=True)
|
||||
kmi.properties.nr = 6
|
||||
kmi = km.items.new('view3d.layers', 'SEVEN', 'PRESS', any=True)
|
||||
kmi.properties.nr = 7
|
||||
kmi = km.items.new('view3d.layers', 'EIGHT', 'PRESS', any=True)
|
||||
kmi.properties.nr = 8
|
||||
kmi = km.items.new('view3d.layers', 'NINE', 'PRESS', any=True)
|
||||
kmi.properties.nr = 9
|
||||
kmi = km.items.new('view3d.layers', 'ZERO', 'PRESS', any=True)
|
||||
kmi.properties.nr = 10
|
||||
kmi = km.items.new('wm.context_toggle_enum', 'Z', 'PRESS')
|
||||
kmi.properties.data_path = 'space_data.viewport_shade'
|
||||
kmi.properties.value_1 = 'SOLID'
|
||||
kmi.properties.value_2 = 'WIREFRAME'
|
||||
kmi = km.items.new('wm.context_toggle_enum', 'Z', 'PRESS', alt=True)
|
||||
kmi.properties.data_path = 'space_data.viewport_shade'
|
||||
kmi.properties.value_1 = 'TEXTURED'
|
||||
kmi.properties.value_2 = 'SOLID'
|
||||
kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS')
|
||||
kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True)
|
||||
kmi.properties.extend = True
|
||||
kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', ctrl=True)
|
||||
kmi.properties.center = True
|
||||
kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', alt=True)
|
||||
kmi.properties.enumerate = True
|
||||
kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True, ctrl=True)
|
||||
kmi.properties.center = True
|
||||
kmi.properties.extend = True
|
||||
kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', ctrl=True, alt=True)
|
||||
kmi.properties.center = True
|
||||
kmi.properties.enumerate = True
|
||||
kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True, alt=True)
|
||||
kmi.properties.enumerate = True
|
||||
kmi.properties.extend = True
|
||||
kmi = km.items.new('view3d.select', 'SELECTMOUSE', 'PRESS', shift=True, ctrl=True, alt=True)
|
||||
kmi.properties.center = True
|
||||
kmi.properties.enumerate = True
|
||||
kmi.properties.extend = True
|
||||
kmi = km.items.new('view3d.select_border', 'EVT_TWEAK_S', 'ANY')
|
||||
kmi.properties.extend = False
|
||||
kmi = km.items.new('view3d.select_lasso', 'EVT_TWEAK_A', 'ANY', ctrl=True)
|
||||
kmi = km.items.new('view3d.select_lasso', 'EVT_TWEAK_A', 'ANY', shift=True, ctrl=True)
|
||||
kmi.properties.deselect = True
|
||||
kmi = km.items.new('view3d.select_circle', 'C', 'PRESS')
|
||||
kmi = km.items.new('view3d.clip_border', 'B', 'PRESS', alt=True)
|
||||
kmi = km.items.new('view3d.zoom_border', 'B', 'PRESS', shift=True)
|
||||
kmi = km.items.new('view3d.render_border', 'B', 'PRESS', shift=True)
|
||||
kmi = km.items.new('view3d.camera_to_view', 'NUMPAD_0', 'PRESS', ctrl=True, alt=True)
|
||||
kmi = km.items.new('view3d.object_as_camera', 'NUMPAD_0', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('wm.call_menu', 'S', 'PRESS', shift=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_snap'
|
||||
kmi = km.items.new('wm.context_set_enum', 'COMMA', 'PRESS')
|
||||
kmi.properties.data_path = 'space_data.pivot_point'
|
||||
kmi.properties.value = 'BOUNDING_BOX_CENTER'
|
||||
kmi = km.items.new('wm.context_set_enum', 'COMMA', 'PRESS', ctrl=True)
|
||||
kmi.properties.data_path = 'space_data.pivot_point'
|
||||
kmi.properties.value = 'MEDIAN_POINT'
|
||||
kmi = km.items.new('wm.context_toggle', 'COMMA', 'PRESS', alt=True)
|
||||
kmi.properties.data_path = 'space_data.use_pivot_point_align'
|
||||
kmi = km.items.new('wm.context_toggle', 'Q', 'PRESS')
|
||||
kmi.properties.data_path = 'space_data.show_manipulator'
|
||||
kmi = km.items.new('wm.context_set_enum', 'PERIOD', 'PRESS')
|
||||
kmi.properties.data_path = 'space_data.pivot_point'
|
||||
kmi.properties.value = 'CURSOR'
|
||||
kmi = km.items.new('wm.context_set_enum', 'PERIOD', 'PRESS', ctrl=True)
|
||||
kmi.properties.data_path = 'space_data.pivot_point'
|
||||
kmi.properties.value = 'INDIVIDUAL_ORIGINS'
|
||||
kmi = km.items.new('wm.context_set_enum', 'PERIOD', 'PRESS', alt=True)
|
||||
kmi.properties.data_path = 'space_data.pivot_point'
|
||||
kmi.properties.value = 'ACTIVE_ELEMENT'
|
||||
kmi = km.items.new('transform.translate', 'G', 'PRESS', shift=True)
|
||||
kmi = km.items.new('transform.translate', 'EVT_TWEAK_S', 'ANY')
|
||||
kmi = km.items.new('transform.rotate', 'R', 'PRESS', shift=True)
|
||||
kmi = km.items.new('transform.resize', 'S', 'PRESS', shift=True)
|
||||
kmi = km.items.new('transform.warp', 'W', 'PRESS', shift=True)
|
||||
kmi = km.items.new('transform.tosphere', 'S', 'PRESS', shift=True, alt=True)
|
||||
kmi = km.items.new('transform.shear', 'S', 'PRESS', shift=True, ctrl=True, alt=True)
|
||||
kmi = km.items.new('transform.select_orientation', 'SPACE', 'PRESS', alt=True)
|
||||
kmi = km.items.new('transform.create_orientation', 'SPACE', 'PRESS', ctrl=True, alt=True)
|
||||
kmi.properties.use = True
|
||||
kmi = km.items.new('transform.mirror', 'M', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('wm.context_toggle', 'TAB', 'PRESS', shift=True)
|
||||
kmi.properties.data_path = 'tool_settings.use_snap'
|
||||
kmi = km.items.new('transform.snap_type', 'TAB', 'PRESS', shift=True, ctrl=True)
|
||||
kmi = km.items.new('view3d.enable_manipulator', 'W', 'PRESS')
|
||||
kmi.properties.translate = True
|
||||
kmi = km.items.new('view3d.enable_manipulator', 'E', 'PRESS')
|
||||
kmi.properties.rotate = True
|
||||
kmi = km.items.new('view3d.enable_manipulator', 'R', 'PRESS')
|
||||
kmi.properties.scale = True
|
||||
kmi = km.items.new('view3d.select_border', 'EVT_TWEAK_S', 'ANY', shift=True)
|
||||
kmi.properties.extend = True
|
||||
|
||||
# Map Object Mode
|
||||
km = kc.keymaps.new('Object Mode', space_type='EMPTY', region_type='WINDOW', modal=False)
|
||||
|
||||
kmi = km.items.new('wm.context_cycle_enum', 'O', 'PRESS', shift=True)
|
||||
kmi.properties.data_path = 'tool_settings.proportional_edit_falloff'
|
||||
kmi = km.items.new('wm.context_toggle_enum', 'O', 'PRESS')
|
||||
kmi.properties.data_path = 'tool_settings.proportional_edit'
|
||||
kmi.properties.value_1 = 'DISABLED'
|
||||
kmi.properties.value_2 = 'ENABLED'
|
||||
kmi = km.items.new('view3d.game_start', 'P', 'PRESS')
|
||||
kmi = km.items.new('object.select_all', 'A', 'PRESS')
|
||||
kmi = km.items.new('object.select_inverse', 'I', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('object.select_linked', 'L', 'PRESS', shift=True)
|
||||
kmi = km.items.new('object.select_grouped', 'G', 'PRESS', shift=True)
|
||||
kmi = km.items.new('object.select_mirror', 'M', 'PRESS', shift=True, ctrl=True)
|
||||
kmi = km.items.new('object.select_hierarchy', 'LEFT_BRACKET', 'PRESS')
|
||||
kmi.properties.direction = 'PARENT'
|
||||
kmi = km.items.new('object.select_hierarchy', 'LEFT_BRACKET', 'PRESS', shift=True)
|
||||
kmi.properties.direction = 'PARENT'
|
||||
kmi.properties.extend = True
|
||||
kmi = km.items.new('object.select_hierarchy', 'RIGHT_BRACKET', 'PRESS')
|
||||
kmi.properties.direction = 'CHILD'
|
||||
kmi = km.items.new('object.select_hierarchy', 'RIGHT_BRACKET', 'PRESS', shift=True)
|
||||
kmi.properties.direction = 'CHILD'
|
||||
kmi.properties.extend = True
|
||||
kmi = km.items.new('object.parent_set', 'P', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('object.parent_no_inverse_set', 'P', 'PRESS', shift=True, ctrl=True)
|
||||
kmi = km.items.new('object.parent_clear', 'P', 'PRESS', alt=True)
|
||||
kmi = km.items.new('object.track_set', 'T', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('object.track_clear', 'T', 'PRESS', alt=True)
|
||||
kmi = km.items.new('object.constraint_add_with_targets', 'C', 'PRESS', shift=True, ctrl=True)
|
||||
kmi = km.items.new('object.constraints_clear', 'C', 'PRESS', ctrl=True, alt=True)
|
||||
kmi = km.items.new('object.location_clear', 'G', 'PRESS', alt=True)
|
||||
kmi = km.items.new('object.rotation_clear', 'R', 'PRESS', alt=True)
|
||||
kmi = km.items.new('object.scale_clear', 'S', 'PRESS', alt=True)
|
||||
kmi = km.items.new('object.origin_clear', 'O', 'PRESS', alt=True)
|
||||
kmi = km.items.new('object.hide_view_clear', 'H', 'PRESS', alt=True)
|
||||
kmi = km.items.new('object.hide_view_set', 'H', 'PRESS')
|
||||
kmi = km.items.new('object.hide_view_set', 'H', 'PRESS', shift=True)
|
||||
kmi.properties.unselected = True
|
||||
kmi = km.items.new('object.move_to_layer', 'M', 'PRESS')
|
||||
kmi = km.items.new('object.delete', 'X', 'PRESS')
|
||||
kmi = km.items.new('object.delete', 'DEL', 'PRESS')
|
||||
kmi = km.items.new('wm.call_menu', 'A', 'PRESS', shift=True)
|
||||
kmi.properties.name = 'INFO_MT_add'
|
||||
kmi = km.items.new('object.duplicates_make_real', 'A', 'PRESS', shift=True, ctrl=True)
|
||||
kmi = km.items.new('wm.call_menu', 'A', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_object_apply'
|
||||
kmi = km.items.new('wm.call_menu', 'U', 'PRESS')
|
||||
kmi.properties.name = 'VIEW3D_MT_make_single_user'
|
||||
kmi = km.items.new('wm.call_menu', 'L', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_make_links'
|
||||
kmi = km.items.new('object.duplicate_move', 'D', 'PRESS', shift=True)
|
||||
kmi = km.items.new('object.duplicate_move_linked', 'D', 'PRESS', alt=True)
|
||||
kmi = km.items.new('object.join', 'J', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('object.convert', 'C', 'PRESS', alt=True)
|
||||
kmi = km.items.new('object.proxy_make', 'P', 'PRESS', ctrl=True, alt=True)
|
||||
kmi = km.items.new('object.make_local', 'L', 'PRESS')
|
||||
kmi = km.items.new('anim.keyframe_insert_menu', 'I', 'PRESS')
|
||||
kmi = km.items.new('anim.keyframe_delete_v3d', 'I', 'PRESS', alt=True)
|
||||
kmi = km.items.new('anim.keying_set_active_set', 'I', 'PRESS', shift=True, ctrl=True, alt=True)
|
||||
kmi = km.items.new('group.create', 'G', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('group.objects_remove', 'G', 'PRESS', ctrl=True, alt=True)
|
||||
kmi = km.items.new('group.objects_add_active', 'G', 'PRESS', shift=True, ctrl=True)
|
||||
kmi = km.items.new('group.objects_remove_active', 'G', 'PRESS', shift=True, alt=True)
|
||||
kmi = km.items.new('wm.call_menu', 'W', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_object_specials'
|
||||
kmi = km.items.new('object.subdivision_set', 'ZERO', 'PRESS', ctrl=True)
|
||||
kmi.properties.level = 0
|
||||
kmi = km.items.new('object.subdivision_set', 'ONE', 'PRESS', ctrl=True)
|
||||
kmi.properties.level = 1
|
||||
kmi = km.items.new('object.subdivision_set', 'TWO', 'PRESS', ctrl=True)
|
||||
kmi.properties.level = 2
|
||||
kmi = km.items.new('object.subdivision_set', 'THREE', 'PRESS', ctrl=True)
|
||||
kmi.properties.level = 3
|
||||
kmi = km.items.new('object.subdivision_set', 'FOUR', 'PRESS', ctrl=True)
|
||||
kmi.properties.level = 4
|
||||
kmi = km.items.new('object.subdivision_set', 'FIVE', 'PRESS', ctrl=True)
|
||||
kmi.properties.level = 5
|
||||
kmi = km.items.new('object.select_all', 'SELECTMOUSE', 'CLICK')
|
||||
kmi.properties.action = 'DESELECT'
|
||||
|
||||
# Map Mesh
|
||||
km = kc.keymaps.new('Mesh', space_type='EMPTY', region_type='WINDOW', modal=False)
|
||||
|
||||
kmi = km.items.new('mesh.loopcut_slide', 'R', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('mesh.loop_select', 'SELECTMOUSE', 'PRESS', ctrl=True, alt=True)
|
||||
kmi = km.items.new('mesh.loop_select', 'SELECTMOUSE', 'PRESS', shift=True, alt=True)
|
||||
kmi.properties.extend = True
|
||||
kmi = km.items.new('mesh.edgering_select', 'SELECTMOUSE', 'PRESS', ctrl=True, alt=True)
|
||||
kmi = km.items.new('mesh.edgering_select', 'SELECTMOUSE', 'PRESS', shift=True, ctrl=True, alt=True)
|
||||
kmi.properties.extend = True
|
||||
kmi = km.items.new('mesh.select_shortest_path', 'SELECTMOUSE', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('mesh.select_all', 'A', 'PRESS')
|
||||
kmi = km.items.new('mesh.select_more', 'NUMPAD_PLUS', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('mesh.select_less', 'NUMPAD_MINUS', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('mesh.select_inverse', 'I', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('mesh.select_non_manifold', 'M', 'PRESS', shift=True, ctrl=True, alt=True)
|
||||
kmi = km.items.new('mesh.select_linked', 'L', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('mesh.select_linked_pick', 'L', 'PRESS')
|
||||
kmi = km.items.new('mesh.select_linked_pick', 'L', 'PRESS', shift=True)
|
||||
kmi.properties.deselect = True
|
||||
kmi = km.items.new('mesh.faces_select_linked_flat', 'F', 'PRESS', shift=True, ctrl=True, alt=True)
|
||||
kmi.properties.sharpness = 135.0
|
||||
kmi = km.items.new('mesh.select_similar', 'G', 'PRESS', shift=True)
|
||||
kmi = km.items.new('wm.call_menu', 'TAB', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_edit_mesh_selection_mode'
|
||||
kmi = km.items.new('mesh.hide', 'H', 'PRESS')
|
||||
kmi = km.items.new('mesh.hide', 'H', 'PRESS', shift=True)
|
||||
kmi.properties.unselected = True
|
||||
kmi = km.items.new('mesh.reveal', 'H', 'PRESS', alt=True)
|
||||
kmi = km.items.new('mesh.normals_make_consistent', 'N', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('mesh.normals_make_consistent', 'N', 'PRESS', shift=True, ctrl=True)
|
||||
kmi.properties.inside = True
|
||||
kmi = km.items.new('view3d.edit_mesh_extrude_move_normal', 'E', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('view3d.edit_mesh_extrude_individual_move', 'E', 'PRESS', shift=True)
|
||||
kmi = km.items.new('wm.call_menu', 'E', 'PRESS', alt=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_edit_mesh_extrude'
|
||||
kmi = km.items.new('mesh.spin', 'R', 'PRESS', alt=True)
|
||||
kmi = km.items.new('mesh.fill', 'F', 'PRESS', alt=True)
|
||||
kmi = km.items.new('mesh.beautify_fill', 'F', 'PRESS', shift=True, alt=True)
|
||||
kmi = km.items.new('mesh.quads_convert_to_tris', 'T', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('mesh.tris_convert_to_quads', 'J', 'PRESS', alt=True)
|
||||
kmi = km.items.new('mesh.edge_flip', 'F', 'PRESS', shift=True, ctrl=True)
|
||||
kmi = km.items.new('mesh.rip_move', 'V', 'PRESS')
|
||||
kmi = km.items.new('mesh.merge', 'M', 'PRESS', alt=True)
|
||||
kmi = km.items.new('transform.shrink_fatten', 'S', 'PRESS', ctrl=True, alt=True)
|
||||
kmi = km.items.new('mesh.edge_face_add', 'F', 'PRESS')
|
||||
kmi = km.items.new('mesh.duplicate_move', 'D', 'PRESS', shift=True)
|
||||
kmi = km.items.new('wm.call_menu', 'A', 'PRESS', shift=True)
|
||||
kmi.properties.name = 'INFO_MT_mesh_add'
|
||||
kmi = km.items.new('mesh.separate', 'P', 'PRESS')
|
||||
kmi = km.items.new('mesh.split', 'Y', 'PRESS')
|
||||
kmi = km.items.new('mesh.dupli_extrude_cursor', 'ACTIONMOUSE', 'CLICK', ctrl=True)
|
||||
kmi = km.items.new('mesh.delete', 'X', 'PRESS')
|
||||
kmi = km.items.new('mesh.delete', 'DEL', 'PRESS')
|
||||
kmi = km.items.new('mesh.knife_cut', 'LEFTMOUSE', 'PRESS', key_modifier='K')
|
||||
kmi = km.items.new('mesh.knife_cut', 'LEFTMOUSE', 'PRESS', shift=True, key_modifier='K')
|
||||
kmi.properties.type = 'MIDPOINTS'
|
||||
kmi = km.items.new('object.vertex_parent_set', 'P', 'PRESS', ctrl=True)
|
||||
kmi = km.items.new('wm.call_menu', 'W', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_edit_mesh_specials'
|
||||
kmi = km.items.new('wm.call_menu', 'F', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_edit_mesh_faces'
|
||||
kmi = km.items.new('wm.call_menu', 'E', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_edit_mesh_edges'
|
||||
kmi = km.items.new('wm.call_menu', 'V', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_edit_mesh_vertices'
|
||||
kmi = km.items.new('wm.call_menu', 'H', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_hook'
|
||||
kmi = km.items.new('wm.call_menu', 'U', 'PRESS')
|
||||
kmi.properties.name = 'VIEW3D_MT_uv_map'
|
||||
kmi = km.items.new('wm.call_menu', 'G', 'PRESS', ctrl=True)
|
||||
kmi.properties.name = 'VIEW3D_MT_vertex_group'
|
||||
kmi = km.items.new('wm.context_cycle_enum', 'O', 'PRESS', shift=True)
|
||||
kmi.properties.data_path = 'tool_settings.proportional_edit_falloff'
|
||||
kmi = km.items.new('wm.context_toggle_enum', 'O', 'PRESS')
|
||||
kmi.properties.data_path = 'tool_settings.proportional_edit'
|
||||
kmi.properties.value_1 = 'DISABLED'
|
||||
kmi.properties.value_2 = 'ENABLED'
|
||||
kmi = km.items.new('wm.context_toggle_enum', 'O', 'PRESS', alt=True)
|
||||
kmi.properties.data_path = 'tool_settings.proportional_edit'
|
||||
kmi.properties.value_1 = 'DISABLED'
|
||||
kmi.properties.value_2 = 'CONNECTED'
|
||||
kmi = km.items.new('mesh.select_all', 'SELECTMOUSE', 'CLICK')
|
||||
kmi.properties.action = 'DESELECT'
|
||||
|
||||
wm.keyconfigs.active = kc
|
||||
|
||||
bpy.context.user_preferences.edit.use_drag_immediately = True
|
||||
bpy.context.user_preferences.edit.use_insertkey_xyz_to_rgb = False
|
||||
bpy.context.user_preferences.inputs.select_mouse = 'LEFT'
|
||||
|
||||
@@ -164,8 +164,6 @@ class INFO_MT_file_export(bpy.types.Menu):
|
||||
bl_label = "Export"
|
||||
|
||||
def draw(self, context):
|
||||
self.layout.operator("export_mesh.wavefront", text="Wavefront (.obj)")
|
||||
|
||||
if hasattr(bpy.types, "WM_OT_collada_export"):
|
||||
self.layout.operator("wm.collada_export", text="COLLADA (.dae)")
|
||||
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
import bpy
|
||||
|
||||
def write_some_data(context, filepath, use_some_setting):
|
||||
print("running write_some_data...")
|
||||
pass
|
||||
|
||||
from bpy.props import *
|
||||
|
||||
class ExportSomeData(bpy.types.Operator):
|
||||
'''This appiers in the tooltip of the operator and in the generated docs.'''
|
||||
bl_idname = "export.some_data" # this is important since its how bpy.ops.export.some_data is constructed
|
||||
bl_label = "Export Some Data"
|
||||
|
||||
# List of operator properties, the attributes will be assigned
|
||||
# to the class instance from the operator settings before calling.
|
||||
|
||||
# TODO, add better example props
|
||||
filepath = StringProperty(name="File Path", description="File path used for exporting the PLY file", maxlen= 1024, default= "")
|
||||
use_setting = BoolProperty(name="Example Boolean", description="Example Tooltip", default= True)
|
||||
|
||||
type = bpy.props.EnumProperty(items=(('OPT_A', "First Option", "Description one"), ('OPT_B', "Second Option", "Description two.")),
|
||||
name="Example Enum",
|
||||
description="Choose between two items",
|
||||
default='OPT_A')
|
||||
|
||||
@classmethod
|
||||
def poll(cls, context):
|
||||
return context.active_object != None
|
||||
|
||||
def execute(self, context):
|
||||
|
||||
# # Bug, currently isnt working
|
||||
#if not self.is_property_set("filepath"):
|
||||
# raise Exception("filename not set")
|
||||
|
||||
write_some_data(self.properties.filepath, context, self.properties.use_setting)
|
||||
|
||||
return {'FINISHED'}
|
||||
|
||||
def invoke(self, context, event):
|
||||
wm = context.window_manager
|
||||
|
||||
if True:
|
||||
# File selector
|
||||
wm.add_fileselect(self) # will run self.execute()
|
||||
return {'RUNNING_MODAL'}
|
||||
elif True:
|
||||
# search the enum
|
||||
wm.invoke_search_popup(self)
|
||||
return {'RUNNING_MODAL'}
|
||||
elif False:
|
||||
# Redo popup
|
||||
return wm.invoke_props_popup(self, event) #
|
||||
elif False:
|
||||
return self.execute(context)
|
||||
|
||||
|
||||
# Only needed if you want to add into a dynamic menu
|
||||
menu_func = lambda self, context: self.layout.operator("export.some_data", text="Example Exporter...")
|
||||
bpy.types.INFO_MT_file_export.append(menu_func)
|
||||
|
||||
if __name__ == "__main__":
|
||||
bpy.ops.export.some_data('INVOKE_DEFAULT', filepath="/tmp/test.ply")
|
||||
@@ -1,129 +0,0 @@
|
||||
#!BPY
|
||||
"""
|
||||
Name: 'UVs from unselected adjacent'
|
||||
Blender: 242
|
||||
Group: 'UVCalculation'
|
||||
Tooltip: 'Assign UVs to selected faces from surrounding unselected faces.'
|
||||
"""
|
||||
__author__ = "Campbell Barton"
|
||||
__url__ = ("blender", "elysiun")
|
||||
__version__ = "1.0 2006/02/07"
|
||||
|
||||
__bpydoc__ = """\
|
||||
This script sets the UV mapping and image of selected faces from adjacent unselected faces.
|
||||
|
||||
Use this script in face select mode for texturing between textured faces.
|
||||
"""
|
||||
|
||||
# ***** BEGIN GPL LICENSE BLOCK *****
|
||||
#
|
||||
# Script copyright (C) Campbell J Barton
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or
|
||||
# modify it under the terms of the GNU General Public License
|
||||
# as published by the Free Software Foundation; either version 2
|
||||
# of the License, or (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program; if not, write to the Free Software Foundation,
|
||||
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
||||
#
|
||||
# ***** END GPL LICENCE BLOCK *****
|
||||
# --------------------------------------------------------------------------
|
||||
|
||||
|
||||
from Blender import *
|
||||
import bpy
|
||||
|
||||
def mostUsedImage(imageList): # Returns the image most used in the list.
|
||||
if not imageList:
|
||||
return None
|
||||
elif len(imageList) < 3:
|
||||
return imageList[0]
|
||||
|
||||
# 3+ Images, Get the most used image for surrounding faces.
|
||||
imageCount = {}
|
||||
for image in imageList:
|
||||
if image:
|
||||
image_key= image.name
|
||||
else:
|
||||
image_key = None
|
||||
|
||||
try:
|
||||
imageCount[image_key]['imageCount'] +=1 # an extra user of this image
|
||||
except:
|
||||
imageCount[image_key] = {'imageCount':1, 'blenderImage':image} # start with 1 user.
|
||||
|
||||
# Now a list of tuples, (imageName, {imageCount, image})
|
||||
imageCount = imageCount.items()
|
||||
|
||||
try: imageCount.sort(key=lambda a: a[1])
|
||||
except: imageCount.sort(lambda a,b: cmp(a[1], b[1]))
|
||||
|
||||
|
||||
return imageCount[-1][1]['blenderImage']
|
||||
|
||||
|
||||
def main():
|
||||
sce = bpy.data.scenes.active
|
||||
ob = sce.objects.active
|
||||
|
||||
if ob == None or ob.type != 'Mesh':
|
||||
Draw.PupMenu('ERROR: No mesh object in face select mode.')
|
||||
return
|
||||
me = ob.getData(mesh=1)
|
||||
|
||||
if not me.faceUV:
|
||||
Draw.PupMenu('ERROR: No mesh object in face select mode.')
|
||||
return
|
||||
|
||||
selfaces = [f for f in me.faces if f.sel]
|
||||
unselfaces = [f for f in me.faces if not f.sel]
|
||||
|
||||
|
||||
# Gather per Vert UV and Image, store in vertUvAverage
|
||||
vertUvAverage = [[[],[]] for i in xrange(len(me.verts))]
|
||||
|
||||
for f in unselfaces: # Unselected faces only.
|
||||
fuv = f.uv
|
||||
for i,v in enumerate(f):
|
||||
vertUvAverage[v.index][0].append(fuv[i])
|
||||
vertUvAverage[v.index][1].append(f.image)
|
||||
|
||||
# Average per vectex UV coords
|
||||
for vertUvData in vertUvAverage:
|
||||
uvList = vertUvData[0]
|
||||
if uvList:
|
||||
# Convert from a list of vectors into 1 vector.
|
||||
vertUvData[0] = reduce(lambda a,b: a+b, uvList, Mathutils.Vector(0,0)) * (1.0/len(uvList))
|
||||
else:
|
||||
vertUvData[0] = None
|
||||
|
||||
# Assign to selected faces
|
||||
TEX_FLAG = Mesh.FaceModes['TEX']
|
||||
for f in selfaces:
|
||||
uvlist = []
|
||||
imageList = []
|
||||
for i,v in enumerate(f):
|
||||
uv, vImages = vertUvAverage[v.index]
|
||||
uvlist.append( uv )
|
||||
imageList.extend(vImages)
|
||||
|
||||
if None not in uvlist:
|
||||
# all the faces images used by this faces vert. some faces will be added twice but thats ok.
|
||||
# Get the most used image and assign to the face.
|
||||
image = mostUsedImage(imageList)
|
||||
f.uv = uvlist
|
||||
|
||||
if image:
|
||||
f.image = image
|
||||
f.mode |= TEX_FLAG
|
||||
Window.RedrawAll()
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user