Sculpt Branch:

svn merge https://svn.blender.org/svnroot/bf-blender/trunk/blender -r24889:25180
This commit is contained in:
Brecht Van Lommel
2009-12-07 19:22:48 +00:00
383 changed files with 31716 additions and 24473 deletions

View File

@@ -757,6 +757,7 @@ class PovrayRender(bpy.types.RenderEngine):
pov_binary = winreg.QueryValueEx(regKey, 'Home')[0] + '\\bin\\pvengine'
if 1:
# TODO, when povray isnt found this gives a cryptic error, would be nice to be able to detect if it exists
self._process = subprocess.Popen([pov_binary, self._temp_file_ini]) # stdout=subprocess.PIPE, stderr=subprocess.PIPE
else:
# This works too but means we have to wait until its done

View File

@@ -486,7 +486,7 @@ def write(filename, batch_objects = None, \
self.blenBone = blenBone
self.blenMeshes = {} # fbxMeshObName : mesh
self.fbxArm = fbxArm
self.restMatrix = blenBone.armature_matrix
self.restMatrix = blenBone.matrix_local
# self.restMatrix = blenBone.matrix['ARMATURESPACE']
# not used yet
@@ -664,13 +664,13 @@ def write(filename, batch_objects = None, \
# we know we have a matrix
# matrix = mtx4_z90 * (ob.matrix['ARMATURESPACE'] * matrix_mod)
matrix = mtx4_z90 * ob.armature_matrix # dont apply armature matrix anymore
matrix = mtx4_z90 * ob.matrix_local # dont apply armature matrix anymore
# matrix = mtx4_z90 * ob.matrix['ARMATURESPACE'] # dont apply armature matrix anymore
parent = ob.parent
if parent:
#par_matrix = mtx4_z90 * (parent.matrix['ARMATURESPACE'] * matrix_mod)
par_matrix = mtx4_z90 * parent.armature_matrix # dont apply armature matrix anymore
par_matrix = mtx4_z90 * parent.matrix_local # dont apply armature matrix anymore
# par_matrix = mtx4_z90 * parent.matrix['ARMATURESPACE'] # dont apply armature matrix anymore
matrix = matrix * par_matrix.copy().invert()
@@ -841,7 +841,7 @@ def write(filename, batch_objects = None, \
"""
file.write('\n\t\t\tProperty: "LimbLength", "double", "",%.6f' %
(my_bone.blenBone.armature_head - my_bone.blenBone.armature_tail).length)
(my_bone.blenBone.head_local - my_bone.blenBone.tail_local).length)
# (my_bone.blenBone.head['ARMATURESPACE'] - my_bone.blenBone.tail['ARMATURESPACE']).length)
#file.write('\n\t\t\tProperty: "LimbLength", "double", "",1')

View File

@@ -85,19 +85,6 @@ def fixName(name):
else:
return name.replace(' ', '_')
# this used to be in BPySys module
# frankly, I don't understand how it works
def BPySys_cleanName(name):
v = [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,46,47,58,59,60,61,62,63,64,91,92,93,94,96,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254]
invalid = ''.join([chr(i) for i in v])
for ch in invalid:
name = name.replace(ch, '_')
return name
# A Dict of Materials
# (material.name, image.name):matname_imagename # matname_imagename has gaps removed.
MTL_DICT = {}
@@ -884,7 +871,7 @@ def do_export(filename, context,
orig_frame = scn.current_frame
if EXPORT_ALL_SCENES: # Add scene name into the context_name
context_name[1] = '_%s' % BPySys_cleanName(scn.name) # WARNING, its possible that this could cause a collision. we could fix if were feeling parranoied.
context_name[1] = '_%s' % bpy.utils.clean_name(scn.name) # WARNING, its possible that this could cause a collision. we could fix if were feeling parranoied.
# Export an animation?
if EXPORT_ANIMATION:
@@ -998,9 +985,9 @@ class ExportOBJ(bpy.types.Operator):
wm.add_fileselect(self)
return ('RUNNING_MODAL',)
def poll(self, context): # Poll isnt working yet
print("Poll")
return context.active_object != None
bpy.ops.add(ExportOBJ)
@@ -1021,4 +1008,4 @@ if __name__ == "__main__":
# - NURBS - needs API additions
# - all scenes export
# + normals calculation
# - get rid of cleanName somehow

View File

@@ -263,9 +263,9 @@ class ExportPLY(bpy.types.Operator):
path = StringProperty(name="File Path", description="File path used for exporting the PLY file", maxlen= 1024, default= "")
use_modifiers = BoolProperty(name="Apply Modifiers", description="Apply Modifiers to the exported mesh", default= True)
use_normals = BoolProperty(name="Export Normals", description="Export Normals for smooth and hard shaded faces", default= True)
use_uvs = BoolProperty(name="Export UVs", description="Exort the active UV layer", default= True)
use_colors = BoolProperty(name="Export Vertex Colors", description="Exort the active vertex color layer", default= True)
use_normals = BoolProperty(name="Normals", description="Export Normals for smooth and hard shaded faces", default= True)
use_uvs = BoolProperty(name="UVs", description="Exort the active UV layer", default= True)
use_colors = BoolProperty(name="Vertex Colors", description="Exort the active vertex color layer", default= True)
def poll(self, context):
@@ -291,14 +291,25 @@ class ExportPLY(bpy.types.Operator):
wm.add_fileselect(self)
return ('RUNNING_MODAL',)
def draw(self, context):
layout = self.layout
props = self.properties
row = layout.row()
row.prop(props, "use_modifiers")
row.prop(props, "use_normals")
row = layout.row()
row.prop(props, "use_uvs")
row.prop(props, "use_colors")
bpy.ops.add(ExportPLY)
import dynamic_menu
def menu_func(self, context):
default_path = bpy.data.filename.replace(".blend", ".ply")
self.layout.operator(ExportPLY.bl_idname, text="Stanford (.ply)...").path = default_path
default_path = bpy.data.filename.replace(".blend", ".ply")
self.layout.operator(ExportPLY.bl_idname, text="Stanford (.ply)...").path = default_path
menu_item = dynamic_menu.add(bpy.types.INFO_MT_file_export, menu_func)

View File

@@ -227,8 +227,6 @@ def read_bvh(context, file_path, GLOBAL_SCALE=1.0):
if ROT_STYLE != 'NATIVE':
rx, ry, rz = eulerRotate(rx, ry, rz, bvh_node.rot_order)
#x,y,z = x/10.0, y/10.0, z/10.0 # For IPO's 36 is 360d
# Make interpolation not cross between 180d, thjis fixes sub frame interpolation and time scaling.
# Will go from (355d to 365d) rather then to (355d to 5d) - inbetween these 2 there will now be a correct interpolation.
@@ -337,7 +335,7 @@ def bvh_node_dict2objects(context, bvh_nodes, IMPORT_START_FRAME= 1, IMPORT_LOOP
bvh_node.temp.rot= rx*DEG2RAD,ry*DEG2RAD,rz*DEG2RAD
bvh_node.temp.insertIpoKey(Blender.Object.IpoKeyTypes.LOCROT)
bvh_node.temp.insertIpoKey(Blender.Object.IpoKeyTypes.LOCROT) # XXX invalid
scn.update(1)
return objects
@@ -401,7 +399,7 @@ def bvh_node_dict2armature(context, bvh_nodes, IMPORT_START_FRAME= 1, IMPORT_LOO
#XXX - sloppy operator code
bpy.ops.armature.delete()
bpy.ops.armature.select_all_toggle()
bpy.ops.armature.select_all()
bpy.ops.armature.delete()
ZERO_AREA_BONES= []
@@ -486,8 +484,8 @@ def bvh_node_dict2armature(context, bvh_nodes, IMPORT_START_FRAME= 1, IMPORT_LOO
pass
bpy.ops.pose.select_all_toggle() # set
bpy.ops.anim.insert_keyframe_menu(type=-4) # XXX - -4 ???
bpy.ops.pose.select_all() # set
bpy.ops.anim.keyframe_insert_menu(type=-4) # XXX - -4 ???
@@ -500,7 +498,7 @@ def bvh_node_dict2armature(context, bvh_nodes, IMPORT_START_FRAME= 1, IMPORT_LOO
#XXX action = Blender.Armature.NLA.NewAction("Action")
#XXX action.setActive(arm_ob)
#bpy.ops.act.new()
#bpy.ops.action.new()
#action = bpy.data.actions[-1]
# arm_ob.animation_data.action = action
@@ -540,6 +538,7 @@ def bvh_node_dict2armature(context, bvh_nodes, IMPORT_START_FRAME= 1, IMPORT_LOO
'''
# KEYFRAME METHOD, SLOW, USE IPOS DIRECT
# TODO: use f-point samples instead (Aligorith)
# Animate the data, the last used bvh_node will do since they all have the same number of frames
for current_frame in range(len(bvh_node.anim_data)-1): # skip the first frame (rest frame)
@@ -618,7 +617,7 @@ def bvh_node_dict2armature(context, bvh_nodes, IMPORT_START_FRAME= 1, IMPORT_LOO
# bpy.ops.anim.insert_keyframe_menu(type=-4) # XXX - -4 ???
# bpy.ops.anim.keyframe_insert_menu(type=-4) # XXX - -4 ???
bpy.ops.screen.frame_offset(delta=1)
# First time, set the IPO's to linear

View File

@@ -807,7 +807,7 @@ def create_mesh(scn, new_objects, has_ngons, CREATE_FGONS, CREATE_EDGES, verts_l
blender_tface.uv2= verts_tex[face_vert_tex_indicies[1]]
blender_tface.uv3= verts_tex[face_vert_tex_indicies[2]]
if blender_face.verts[3] != 0:
if len(face_vert_loc_indicies)==4:
blender_tface.uv4= verts_tex[face_vert_tex_indicies[3]]
# for ii, uv in enumerate(blender_face.uv):
@@ -1277,7 +1277,7 @@ def load_obj(filepath,
# deselect all
# if context.selected_objects:
# bpy.ops.OBJECT_OT_select_all_toggle()
# bpy.ops.OBJECT_OT_select_all()
scene = context.scene
# scn = bpy.data.scenes.active
@@ -1640,5 +1640,5 @@ menu_item = dynamic_menu.add(bpy.types.INFO_MT_file_import, menu_func)
# search image in bpy.config.textureDir - load_image
# replaced BPyImage.comprehensiveImageLoad with a simplified version that only checks additional directory specified, but doesn't search dirs recursively (obj_image_load)
# bitmask won't work? - 132
# uses operator bpy.ops.OBJECT_OT_select_all_toggle() to deselect all (not necessary?)
# uses operator bpy.ops.OBJECT_OT_select_all() to deselect all (not necessary?)
# uses bpy.sys.time()

View File

@@ -127,7 +127,7 @@ def clientSendJob(conn, scene, anim = False):
# FLUID + POINT CACHE
###########################
root, ext = os.path.splitext(name)
default_path = path + "blendcache_" + root + os.sep # need an API call for that
default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that
for object in bpy.data.objects:
for modifier in object.modifiers:

View File

@@ -26,6 +26,64 @@ from netrender.utils import *
import netrender.client as client
import netrender.model
@rnaOperator
class RENDER_OT_netslave_bake(bpy.types.Operator):
'''NEED DESCRIPTION'''
bl_idname = "render.netslavebake"
bl_label = "Bake all in file"
def poll(self, context):
return True
def execute(self, context):
scene = context.scene
netsettings = scene.network_render
filename = bpy.data.filename
path, name = os.path.split(filename)
root, ext = os.path.splitext(name)
default_path = path + os.sep + "blendcache_" + root + os.sep # need an API call for that
relative_path = os.sep + os.sep + "blendcache_" + root + os.sep
# Force all point cache next to the blend file
for object in bpy.data.objects:
for modifier in object.modifiers:
if modifier.type == 'FLUID_SIMULATION' and modifier.settings.type == "DOMAIN":
modifier.settings.path = relative_path
bpy.ops.fluid.bake({"active_object": object, "scene": scene})
elif modifier.type == "CLOTH":
modifier.point_cache.step = 1
modifier.point_cache.disk_cache = True
modifier.point_cache.external = False
elif modifier.type == "SOFT_BODY":
modifier.point_cache.step = 1
modifier.point_cache.disk_cache = True
modifier.point_cache.external = False
elif modifier.type == "SMOKE" and modifier.smoke_type == "TYPE_DOMAIN":
modifier.domain_settings.point_cache_low.step = 1
modifier.domain_settings.point_cache_low.disk_cache = True
modifier.domain_settings.point_cache_low.external = False
modifier.domain_settings.point_cache_high.step = 1
modifier.domain_settings.point_cache_high.disk_cache = True
modifier.domain_settings.point_cache_high.external = False
# particles modifier are stupid and don't contain data
# we have to go through the object property
for psys in object.particle_systems:
psys.point_cache.step = 1
psys.point_cache.disk_cache = True
psys.point_cache.external = False
psys.point_cache.filepath = relative_path
bpy.ops.ptcache.bake_all()
#bpy.ops.wm.save_mainfile(path = path + os.sep + root + "_baked.blend")
return ('FINISHED',)
def invoke(self, context, event):
return self.execute(context)
@rnaOperator
class RENDER_OT_netclientanim(bpy.types.Operator):
'''Start rendering an animation on network'''

View File

@@ -16,6 +16,8 @@
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
# internal blender C module
import _bpy
from _bpy import types, props
@@ -31,35 +33,50 @@ from bpy import ops as _ops_module
# fake operator module
ops = _ops_module.ops_fake_module
import sys
DEBUG = ("-d" in sys.argv)
def load_scripts(reload_scripts=False):
import os
import sys
import traceback
import time
t_main = time.time()
def test_import(module_name):
try:
return __import__(module_name)
t = time.time()
ret= __import__(module_name)
if DEBUG:
print("time %s %.4f" % (module_name, time.time() - t))
return ret
except:
traceback.print_exc()
return None
for base_path in utils.script_paths():
for path_subdir in ("ui", "op", "io"):
path = os.path.join(base_path, path_subdir)
sys.path.insert(0, path)
for f in sorted(os.listdir(path)):
if f.endswith(".py"):
# python module
mod = test_import(f[0:-3])
elif "." not in f:
# python package
mod = test_import(f)
else:
mod = None
if os.path.isdir(path):
sys.path.insert(0, path)
for f in sorted(os.listdir(path)):
if f.endswith(".py"):
# python module
mod = test_import(f[0:-3])
elif "." not in f:
# python package
mod = test_import(f)
else:
mod = None
if reload_scripts and mod:
print("Reloading:", mod)
reload(mod)
if reload_scripts and mod:
print("Reloading:", mod)
reload(mod)
if DEBUG:
print("Time %.4f" % (time.time() - t_main))
def _main():
@@ -69,7 +86,8 @@ def _main():
import sys
sys.stdin = None
if "-d" in sys.argv and False: # Enable this to measure startup speed
# if "-d" in sys.argv: # Enable this to measure startup speed
if 0:
import cProfile
cProfile.run('import bpy; bpy.load_scripts()', 'blender.prof')

View File

@@ -23,6 +23,7 @@ from _bpy import ops as ops_module
op_add = ops_module.add
op_remove = ops_module.remove
op_add_macro = ops_module.add_macro
op_dir = ops_module.dir
op_call = ops_module.call
op_as_string = ops_module.as_string
@@ -59,6 +60,9 @@ class bpy_ops(object):
def add(self, pyop):
op_add(pyop)
def add_macro(self, pyop):
op_add_macro(pyop)
def remove(self, pyop):
op_remove(pyop)

View File

@@ -16,6 +16,8 @@
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import bpy
import os
@@ -25,27 +27,70 @@ def expandpath(path):
return path
_unclean_chars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, \
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, \
35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 46, 47, 58, 59, 60, 61, 62, 63, \
64, 91, 92, 93, 94, 96, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, \
133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, \
147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 160, \
161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, \
175, 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, \
189, 190, 191, 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, \
203, 204, 205, 206, 207, 208, 209, 210, 211, 212, 213, 214, 215, 216, \
217, 218, 219, 220, 221, 222, 223, 224, 225, 226, 227, 228, 229, 230, \
231, 232, 233, 234, 235, 236, 237, 238, 239, 240, 241, 242, 243, 244, \
245, 246, 247, 248, 249, 250, 251, 252, 253, 254]
_unclean_chars = ''.join([chr(i) for i in _unclean_chars])
def clean_name(name, replace="_"):
'''
All characters besides A-Z/a-z, 0-9 are replaced with "_"
or the replace argumet if defined.
'''
for ch in _unclean_chars:
name = name.replace(ch, replace)
return name
# base scripts
_scripts = os.path.join(os.path.dirname(__file__), os.path.pardir, os.path.pardir)
_scripts = (os.path.normpath(_scripts), )
def script_paths(*args):
scripts = list(_scripts)
# add user scripts dir
user_script_path = bpy.context.user_preferences.filepaths.python_scripts_directory
if not user_script_path:
# XXX - WIN32 needs checking, perhaps better call a blender internal function.
user_script_path = os.path.join(os.path.expanduser("~"), ".blender", "scripts")
user_script_path = os.path.normpath(user_script_path)
if user_script_path not in scripts and os.path.isdir(user_script_path):
scripts.append(user_script_path)
if not args:
return _scripts
return scripts
subdir = os.path.join(*args)
script_paths = []
for path in _scripts:
script_paths.append(os.path.join(path, subdir))
for path in scripts:
path_subdir = os.path.join(path, subdir)
if os.path.isdir(path_subdir):
script_paths.append(path_subdir)
return script_paths
_presets = os.path.join(_scripts[0], "presets") # FIXME - multiple paths
_presets = os.path.join(_scripts[0], "presets") # FIXME - multiple paths
def preset_paths(subdir):
'''
Returns a list of paths for a spesific preset.
'''
return (os.path.join(_presets, subdir), )
'''
Returns a list of paths for a spesific preset.
'''
return (os.path.join(_presets, subdir), )

View File

@@ -15,6 +15,9 @@
# Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
from _bpy import types as bpy_types
StructRNA = bpy_types.Struct.__bases__[0]
@@ -22,6 +25,7 @@ StructRNA = bpy_types.Struct.__bases__[0]
class Context(StructRNA):
__slots__ = ()
def copy(self):
new_context = {}
@@ -34,6 +38,7 @@ class Context(StructRNA):
class Object(bpy_types.ID):
__slots__ = ()
@property
def children(self):
@@ -46,13 +51,19 @@ class _GenericBone:
functions for bones, common between Armature/Pose/Edit bones.
internal subclassing use only.
'''
__slots__ = ()
def translate(self, vec):
self.head += vec
self.tail += vec
def parent_index(self, parent_test):
'''
The same as 'bone in other_bone.parent_recursive' but saved generating a list.
'''
# use the name so different types can be tested.
name = parent_test.name
parent = self.parent
i = 1
while parent:
@@ -60,26 +71,36 @@ class _GenericBone:
return i
parent = parent.parent
i += 1
return 0
@property
def basename(self):
#return self.name.rsplit(".", 1)[0]
return self.name.split(".")[0]
@property
def parent_recursive(self):
parent_list = []
parent = self.parent
while parent:
if parent:
parent_list.append(parent)
parent = parent.parent
return parent_list
@property
def length(self):
return (self.head - self.tail).length
@length.setter
def length(self, value):
"""The distance from head to tail"""
self.tail = self.head + ((self.tail - self.head).normalize() * value)
@property
def children(self):
return [child for child in self._other_bones if child.parent == self]
@@ -91,43 +112,101 @@ class _GenericBone:
index = bone.parent_index(self)
if index:
bones_children.append((index, bone))
# sort by distance to parent
bones_children.sort(key=lambda bone_pair: bone_pair[0])
return [bone for index, bone in bones_children]
@property
def children_recursive_basename(self):
'''
Returns a chain of children with the same base name as this bone
Only direct chains are supported, forks caused by multiple children with matching basenames will.
'''
basename = self.basename
chain = []
child = self
while True:
children = child.children
children_basename = []
for child in children:
if basename == child.basename:
children_basename.append(child)
if len(children_basename) == 1:
child = children_basename[0]
chain.append(child)
else:
if len(children_basename):
print("multiple basenames found, this is probably not what you want!", bone.name, children_basename)
break
return chain
@property
def _other_bones(self):
id_data = self.id_data
id_data_type = type(id_data)
if id_data_type == bpy_types.Object:
bones = id_data.pose.bones
elif id_data_type == bpy_types.Armature:
bones = id_data.edit_bones
if not bones: # not in editmode
bones = id_data.bones
return bones
class PoseBone(StructRNA, _GenericBone):
pass
__slots__ = ()
class Bone(StructRNA, _GenericBone):
pass
__slots__ = ()
class EditBone(StructRNA, _GenericBone):
pass
__slots__ = ()
def ord_ind(i1,i2):
if i1<i2: return i1,i2
return i2,i1
def ord_ind(i1, i2):
if i1 < i2:
return i1, i2
return i2, i1
class Mesh(bpy_types.ID):
__slots__ = ()
def from_pydata(self, verts, edges, faces):
'''
Make a mesh from a list of verts/edges/faces
Until we have a nicer way to make geometry, use this.
'''
self.add_geometry(len(verts), len(edges), len(faces))
verts_flat = [f for v in verts for f in v]
self.verts.foreach_set("co", verts_flat)
del verts_flat
edges_flat = [i for e in edges for i in e]
self.edges.foreach_set("verts", edges_flat)
del edges_flat
def treat_face(f):
if len(f) == 3:
return f[0], f[1], f[2], 0
elif f[3] == 0:
return f[3], f[0], f[1], f[2]
return f
faces_flat = [v for f in faces for v in treat_face(f)]
self.faces.foreach_set("verts_raw", faces_flat)
del faces_flat
@property
def edge_keys(self):
@@ -153,6 +232,7 @@ class Mesh(bpy_types.ID):
class MeshEdge(StructRNA):
__slots__ = ()
@property
def key(self):
@@ -160,48 +240,65 @@ class MeshEdge(StructRNA):
class MeshFace(StructRNA):
__slots__ = ()
@property
def edge_keys(self):
verts = tuple(self.verts)
if len(verts)==3:
return ord_ind(verts[0], verts[1]), ord_ind(verts[1], verts[2]), ord_ind(verts[2], verts[0])
if len(verts) == 3:
return ord_ind(verts[0], verts[1]), ord_ind(verts[1], verts[2]), ord_ind(verts[2], verts[0])
return ord_ind(verts[0], verts[1]), ord_ind(verts[1], verts[2]), ord_ind(verts[2], verts[3]), ord_ind(verts[3], verts[0])
return ord_ind(verts[0], verts[1]), ord_ind(verts[1], verts[2]), ord_ind(verts[2], verts[3]), ord_ind(verts[3], verts[0])
import collections
class OrderedMeta(type):
def __init__(cls, name, bases, attributes):
super(OrderedMeta, cls).__init__(name, bases, attributes)
cls.order = list(attributes.keys())
def __prepare__(name, bases, **kwargs):
return collections.OrderedDict()
# Only defined so operators members can be used by accessing self.order
class Operator(StructRNA, metaclass=OrderedMeta):
pass
__slots__ = ()
class Macro(StructRNA, metaclass=OrderedMeta):
# bpy_types is imported before ops is defined
# so we have to do a local import on each run
__slots__ = ()
@classmethod
def define(self, opname):
from _bpy import ops
return ops.macro_define(self, opname)
class Menu(StructRNA):
__slots__ = ()
def path_menu(self, searchpaths, operator):
layout = self.layout
# hard coded to set the operators 'path' to the filename.
import os
def path_to_name(f):
''' Only capitalize all lowercase names, mixed case use them as is.
'''
f_base = os.path.splitext(f)[0]
# string replacements
f_base = f_base.replace("_colon_", ":")
f_base = f_base.replace("_", " ")
if f_base.lower() == f_base:
return ' '.join([w[0].upper() + w[1:] for w in f_base.split()])
else:
@@ -222,7 +319,7 @@ class Menu(StructRNA):
continue
layout.operator(operator, text=path_to_name(f)).path = path
def draw_preset(self, context):
'''Define these on the subclass
- preset_operator

View File

@@ -69,10 +69,13 @@ def get_root_modules():
modules += sys.builtin_module_names
# needed for modules defined in C
modules += sys.modules.keys()
modules = list(set(modules))
if '__init__' in modules:
modules.remove('__init__')
modules = sorted(set(modules))
modules = sorted(modules)
if store:
ROOT_MODULES = modules
return modules

View File

@@ -16,8 +16,11 @@
#
# ##### END GPL LICENSE BLOCK #####
# <pep8 compliant>
import bpy
def collect_baseclasses(_class, bases):
if _class is type or _class is object:
@@ -29,6 +32,7 @@ def collect_baseclasses(_class, bases):
return bases
def collect_subclasses(_class, subs):
if _class is type or _class is object:
@@ -40,6 +44,7 @@ def collect_subclasses(_class, subs):
return subs
class DynMenu(bpy.types.Menu):
def draw(self, context):
@@ -61,6 +66,7 @@ class DynMenu(bpy.types.Menu):
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)
@@ -88,6 +94,7 @@ def setup(menu_class):
root_class.draw = DynMenu.draw
def add(menu_class, func):
'''
Add a single function directly without having to make a class

View File

@@ -0,0 +1,187 @@
# ##### 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>
import bpy
header = '''
digraph ancestors {
graph [fontsize=30 labelloc="t" label="" splines=false overlap=true, rankdir=BT];
ratio = "auto" ;
'''
footer = '''
}
'''
def compat_str(text, line_length=0):
if line_length:
text_ls = []
while len(text) > line_length:
text_ls.append(text[:line_length])
text = text[line_length:]
if text:
text_ls.append(text)
text = '\n '.join(text_ls)
#text = text.replace('.', '.\n')
#text = text.replace(']', ']\n')
text = text.replace("\n", "\\n")
text = text.replace('"', '\\"')
return "* " + text
def graph_armature(obj, path, FAKE_PARENT=True, CONSTRAINTS=True, DRIVERS=True, XTRA_INFO=False):
CONSTRAINTS = DRIVERS = True
fileobject = open(path, "w")
fw = fileobject.write
fw(header)
fw('label = "%s::%s" ;' % (bpy.data.filename.split("/")[-1].split("\\")[-1], obj.name))
arm = obj.data
bones = [bone.name for bone in arm.bones]
bones.sort()
print("")
for bone in bones:
b = arm.bones[bone]
print(">>", bone, ["*>", "->"][b.connected], getattr(getattr(b, "parent", ""), "name", ""))
label = [bone]
bone = arm.bones[bone]
for key, value in obj.pose.bones[bone.name].items():
if key.startswith("_"):
continue
if type(value) == float:
value = "%.3f" % value
elif type(value) == str:
value = compat_str(value)
label.append("%s = %s" % (key, value))
opts = ["shape=box", "regular=1", "style=filled", "fixedsize=false", 'label="%s"' % compat_str('\n'.join(label))]
if bone.name.startswith('ORG'):
opts.append("fillcolor=yellow")
else:
opts.append("fillcolor=white")
fw('"%s" [%s];\n' % (bone.name, ','.join(opts)))
fw('\n\n# Hierarchy:\n')
# Root node.
if FAKE_PARENT:
fw('"Object::%s" [];\n' % obj.name)
for bone in bones:
bone = arm.bones[bone]
parent = bone.parent
if parent:
parent_name = parent.name
connected = bone.connected
elif FAKE_PARENT:
parent_name = 'Object::%s' % obj.name
connected = False
else:
continue
opts = ["dir=forward", "weight=2", "arrowhead=normal"]
if not connected:
opts.append("style=dotted")
fw('"%s" -> "%s" [%s] ;\n' % (bone.name, parent_name, ','.join(opts)))
del bone
# constraints
if CONSTRAINTS:
fw('\n\n# Constraints:\n')
for bone in bones:
pbone = obj.pose.bones[bone]
# must be ordered
for constraint in pbone.constraints:
subtarget = getattr(constraint, "subtarget", "")
if subtarget:
# TODO, not internal links
opts = ['dir=forward', "weight=1", "arrowhead=normal", "arrowtail=none", "constraint=false", 'color="red"', 'labelfontsize=4']
if XTRA_INFO:
label = "%s\n%s" % (constraint.type, constraint.name)
opts.append('label="%s"' % compat_str(label))
fw('"%s" -> "%s" [%s] ;\n' % (subtarget, pbone.name, ','.join(opts)))
# Drivers
if DRIVERS:
fw('\n\n# Drivers:\n')
def rna_path_as_pbone(rna_path):
if not rna_path.startswith("pose.bones["):
return None
#rna_path_bone = rna_path[:rna_path.index("]") + 1]
#return obj.path_resolve(rna_path_bone)
bone_name = rna_path.split("[")[1].split("]")[0]
return obj.pose.bones[bone_name[1:-1]]
animation_data = obj.animation_data
if animation_data:
fcurve_drivers = [fcurve_driver for fcurve_driver in animation_data.drivers]
fcurve_drivers.sort(key=lambda fcurve_driver: fcurve_driver.rna_path)
for fcurve_driver in fcurve_drivers:
rna_path = fcurve_driver.rna_path
pbone = rna_path_as_pbone(rna_path)
if pbone:
for target in fcurve_driver.driver.targets:
pbone_target = rna_path_as_pbone(target.rna_path)
rna_path_target = target.rna_path
if pbone_target:
opts = ['dir=forward', "weight=1", "arrowhead=normal", "arrowtail=none", "constraint=false", 'color="blue"', "labelfontsize=4"] # ,
display_source = rna_path.replace("pose.bones", "")
display_target = rna_path_target.replace("pose.bones", "")
if XTRA_INFO:
label = "%s\\n%s" % (display_source, display_target)
opts.append('label="%s"' % compat_str(label))
fw('"%s" -> "%s" [%s] ;\n' % (pbone_target.name, pbone.name, ','.join(opts)))
fw(footer)
fileobject.close()
'''
print(".", end='')
import sys
sys.stdout.flush()
'''
print("\nSaved:", path)
return True
if __name__ == "__main__":
import os
tmppath = "/tmp/test.dot"
graph_armature(bpy.context.object, tmppath, CONSTRAINTS=True, DRIVERS=True)
os.system("dot -Tpng %s > %s; eog %s &" % (tmppath, tmppath + '.png', tmppath + '.png'))

View File

@@ -0,0 +1,301 @@
# ##### 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>
import bpy
EPS_SPLINE_DIV = 15.0 # remove doubles is ~15th the length of the spline
def get_hub(co, _hubs, EPS_SPLINE):
if 1:
for hub in _hubs.values():
if (hub.co - co).length < EPS_SPLINE:
return hub
key = co.toTuple(3)
hub = _hubs[key] = Hub(co, key, len(_hubs))
return hub
else:
pass
'''
key = co.toTuple(3)
try:
return _hubs[key]
except:
hub = _hubs[key] = Hub(co, key, len(_hubs))
return hub
'''
class Hub(object):
__slots__ = "co", "key", "index", "links"
def __init__(self, co, key, index):
self.co = co.copy()
self.key = key
self.index = index
self.links = []
def get_weight(self):
f = 0.0
for hub_other in self.links:
f += (self.co - hub_other.co).length
def replace(self, other):
for hub in self.links:
try:
hub.links.remove(self)
except:
pass
if other not in hub.links:
hub.links.append(other)
def dist(self, other):
return (self.co - other.co).length
def calc_faces(self, hub_ls):
faces = []
# first tris
for l_a in self.links:
for l_b in l_a.links:
if l_b is not self and l_b in self.links:
# will give duplicates
faces.append((self.index, l_a.index, l_b.index))
# now quads, check which links share 2 different verts directly
def validate_quad(face):
if len(set(face)) != len(face):
return False
if hub_ls[face[0]] in hub_ls[face[2]].links:
return False
if hub_ls[face[2]] in hub_ls[face[0]].links:
return False
if hub_ls[face[1]] in hub_ls[face[3]].links:
return False
if hub_ls[face[3]] in hub_ls[face[1]].links:
return False
return True
for i, l_a in enumerate(self.links):
links_a = set([l.index for l in l_a.links])
for j in range(i):
l_b = self.links[j]
links_b = set([l.index for l in l_b.links])
isect = links_a.intersection(links_b)
if len(isect) == 2:
isect = list(isect)
# check there are no diagonal lines
face = (isect[0], l_a.index, isect[1], l_b.index)
if validate_quad(face):
faces.append(face)
return faces
class Spline(object):
__slots__ = "points", "hubs", "length"
def __init__(self, points):
self.points = points
self.hubs = []
# calc length
f = 0.0
co_prev = self.points[0]
for co in self.points[1:]:
f += (co - co_prev).length
co_prev = co
self.length = f
def link(self):
if len(self.hubs) < 2:
return
edges = list(set([i for i, hub in self.hubs]))
edges.sort()
edges_order = {}
for i in edges:
edges_order[i] = []
# self.hubs.sort()
for i, hub in self.hubs:
edges_order[i].append(hub)
hubs_order = []
for i in edges:
ls = edges_order[i]
edge_start = self.points[i]
ls.sort(key=lambda hub: (hub.co - edge_start).length)
hubs_order.extend(ls)
# Now we have the order, connect the hubs
hub_prev = hubs_order[0]
for hub in hubs_order[1:]:
hub.links.append(hub_prev)
hub_prev.links.append(hub)
hub_prev = hub
def get_points(stroke):
return [point.co.copy() for point in stroke.points]
def get_splines(gp):
l = None
for l in gp.layers:
if l.active: # XXX - should be layers.active
break
if l:
frame = l.active_frame
return [Spline(get_points(stroke)) for stroke in frame.strokes]
else:
return []
def xsect_spline(sp_a, sp_b, _hubs):
from Mathutils import LineIntersect
from Mathutils import MidpointVecs
from Geometry import ClosestPointOnLine
pt_a_prev = pt_b_prev = None
EPS_SPLINE = (sp_a.length + sp_b.length) / (EPS_SPLINE_DIV * 2)
pt_a_prev = sp_a.points[0]
for a, pt_a in enumerate(sp_a.points[1:]):
pt_b_prev = sp_b.points[0]
for b, pt_b in enumerate(sp_b.points[1:]):
# Now we have 2 edges
# print(pt_a, pt_a_prev, pt_b, pt_b_prev)
xsect = LineIntersect(pt_a, pt_a_prev, pt_b, pt_b_prev)
if xsect is not None:
if (xsect[0] - xsect[1]).length <= EPS_SPLINE:
f = ClosestPointOnLine(xsect[1], pt_a, pt_a_prev)[1]
# if f >= 0.0-EPS_SPLINE and f <= 1.0+EPS_SPLINE: # for some reason doesnt work so well, same below
if f >= 0.0 and f <= 1.0:
f = ClosestPointOnLine(xsect[0], pt_b, pt_b_prev)[1]
# if f >= 0.0-EPS_SPLINE and f <= 1.0+EPS_SPLINE:
if f >= 0.0 and f <= 1.0:
# This wont happen often
co = MidpointVecs(xsect[0], xsect[1])
hub = get_hub(co, _hubs, EPS_SPLINE)
sp_a.hubs.append((a, hub))
sp_b.hubs.append((b, hub))
pt_b_prev = pt_b
pt_a_prev = pt_a
def calculate(gp):
splines = get_splines(gp)
_hubs = {}
for i, sp in enumerate(splines):
for j, sp_other in enumerate(splines):
if j <= i:
continue
xsect_spline(sp, sp_other, _hubs)
for sp in splines:
sp.link()
# remove these
hubs_ls = [hub for hub in _hubs.values() if hub.index != -1]
_hubs.clear()
_hubs = None
for i, hub in enumerate(hubs_ls):
hub.index = i
# Now we have connected hubs, write all edges!
def order(i1, i2):
if i1 > i2:
return i2, i1
return i1, i2
edges = {}
for hub in hubs_ls:
i1 = hub.index
for hub_other in hub.links:
i2 = hub_other.index
edges[order(i1, i2)] = None
verts = []
edges = edges.keys()
faces = []
for hub in hubs_ls:
verts.append(hub.co)
faces.extend(hub.calc_faces(hubs_ls))
# remove double faces
faces = dict([(tuple(sorted(f)), f) for f in faces]).values()
mesh = bpy.data.add_mesh("Retopo")
mesh.from_pydata(verts, [], faces)
scene = bpy.context.scene
mesh.update()
obj_new = bpy.data.add_object('MESH', "Torus")
obj_new.data = mesh
scene.objects.link(obj_new)
return obj_new
def main():
scene = bpy.context.scene
obj = bpy.context.object
gp = None
if obj:
gp = obj.grease_pencil
if not gp:
gp = scene.grease_pencil
if not gp:
raise Exception("no active grease pencil")
obj_new = calculate(gp)
scene.objects.active = obj_new
obj_new.selected = True
# nasty, recalc normals
bpy.ops.object.mode_set(mode='EDIT', toggle=False)
bpy.ops.mesh.normals_make_consistent(inside=False)
bpy.ops.object.mode_set(mode='OBJECT', toggle=False)

View File

@@ -1,385 +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
from functools import reduce
# TODO, have these in a more general module
from rna_prop_ui import rna_idprop_ui_get, rna_idprop_ui_prop_get
empty_layer = [False] * 16
def gen_none(obj, orig_bone_name):
pass
def get_bone_data(obj, bone_name):
arm = obj.data
pbone = obj.pose.bones[bone_name]
if obj.mode == 'EDIT':
bone = arm.edit_bones[bone_name]
else:
bone = arm.bones[bone_name]
return obj, arm, pbone, bone
def bone_basename(name):
return name.split(".")[0]
def add_stretch_to(obj, from_name, to_name, name):
'''
Adds a bone that stretches from one to another
'''
is_editmode = (obj.mode == 'EDIT')
if not is_editmode:
bpy.ops.object.mode_set(mode='EDIT')
arm = obj.data
stretch_ebone = arm.edit_bones.new(name)
stretch_name = stretch_ebone.name
head = stretch_ebone.head = arm.edit_bones[from_name].head.copy()
tail = stretch_ebone.tail = arm.edit_bones[to_name].head.copy()
# Now for the constraint
bpy.ops.object.mode_set(mode='OBJECT')
from_pbone = obj.pose.bones[from_name]
to_pbone = obj.pose.bones[to_name]
stretch_pbone = obj.pose.bones[stretch_name]
con = stretch_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = from_name
con = stretch_pbone.constraints.new('STRETCH_TO')
con.target = obj
con.subtarget = to_name
con.original_length = (head-tail).length
con.keep_axis = 'PLANE_X'
con.volume = 'NO_VOLUME'
if is_editmode:
bpy.ops.object.mode_set(mode='EDIT')
#else:
# bpy.ops.object.mode_set(mode='OBJECT')
def gen_finger(obj, orig_bone_name):
# *** EDITMODE
# get assosiated data
obj, arm, orig_pbone, orig_ebone = get_bone_data(obj, orig_bone_name)
obj.animation_data_create() # needed if its a new armature with no keys
arm.layer[0] = arm.layer[8] = True
children = orig_pbone.children_recursive
tot_len = reduce(lambda f, pbone: f + pbone.bone.length, children, orig_pbone.bone.length)
base_name = bone_basename(orig_pbone.name)
# first make a new bone at the location of the finger
control_ebone = arm.edit_bones.new(base_name)
control_bone_name = control_ebone.name # we dont know if we get the name requested
# Place the finger bone
head = orig_ebone.head.copy()
tail = orig_ebone.tail.copy()
control_ebone.head = head
control_ebone.tail = head + ((tail - head).normalize() * tot_len)
control_ebone.roll = orig_ebone.roll
# now add bones inbetween this and its children recursively
# switching modes so store names only!
children = [pbone.name for pbone in children]
# set an alternate layer for driver bones
other_layer = empty_layer[:]
other_layer[8] = True
driver_bone_pairs = []
for child_bone_name in children:
obj, arm, pbone_child, child_ebone = get_bone_data(obj, child_bone_name)
# finger.02 --> finger_driver.02
driver_bone_name = child_bone_name.split('.')
driver_bone_name = driver_bone_name[0] + "_driver." + ".".join(driver_bone_name[1:])
driver_ebone = arm.edit_bones.new(driver_bone_name)
driver_bone_name = driver_ebone.name # cant be too sure!
driver_ebone.layer = other_layer
new_len = pbone_child.bone.length / 2.0
head = child_ebone.head.copy()
tail = child_ebone.tail.copy()
driver_ebone.head = head
driver_ebone.tail = head + ((tail - head).normalize() * new_len)
driver_ebone.roll = child_ebone.roll
# Insert driver_ebone in the chain without connected parents
driver_ebone.connected = False
driver_ebone.parent = child_ebone.parent
child_ebone.connected = False
child_ebone.parent = driver_ebone
# Add the drivers to these when in posemode.
driver_bone_pairs.append((child_bone_name, driver_bone_name))
del control_ebone
# *** POSEMODE
bpy.ops.object.mode_set(mode='OBJECT')
obj, arm, orig_pbone, orig_bone = get_bone_data(obj, orig_bone_name)
obj, arm, control_pbone, control_bone= get_bone_data(obj, control_bone_name)
# only allow Y scale
control_pbone.lock_scale = (True, False, True)
control_pbone["bend_ratio"]= 0.4
prop = rna_idprop_ui_prop_get(control_pbone, "bend_ratio", create=True)
prop["min"] = 0.0
prop["max"] = 1.0
con = orig_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = control_bone_name
con = orig_pbone.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = control_bone_name
# setup child drivers on each new smaller bone added. assume 2 for now.
# drives the bones
controller_path = control_pbone.path_to_id() # 'pose.bones["%s"]' % control_bone_name
i = 0
for child_bone_name, driver_bone_name in driver_bone_pairs:
# XXX - todo, any number
if i==2:
break
obj, arm, driver_pbone, driver_bone = get_bone_data(obj, driver_bone_name)
driver_pbone.rotation_mode = 'YZX'
fcurve_driver = driver_pbone.driver_add("rotation_euler", 0)
#obj.driver_add('pose.bones["%s"].scale', 1)
#obj.animation_data.drivers[-1] # XXX, WATCH THIS
driver = fcurve_driver.driver
# scale target
tar = driver.targets.new()
tar.name = "scale"
tar.id_type = 'OBJECT'
tar.id = obj
tar.array_index = 1 # Y scale
tar.rna_path = controller_path + '.scale'
# bend target
tar = driver.targets.new()
tar.name = "br"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = controller_path + '["bend_ratio"]'
# XXX - todo, any number
if i==0:
driver.expression = '(-scale+1.0)*pi*2.0*(1.0-br)'
elif i==1:
driver.expression = '(-scale+1.0)*pi*2.0*br'
obj, arm, child_pbone, child_bone = get_bone_data(obj, child_bone_name)
# only allow X rotation
driver_pbone.lock_rotation = child_pbone.lock_rotation = (False, True, True)
i += 1
def gen_delta(obj, delta_name):
'''
Use this bone to define a delta thats applied to its child in pose mode.
'''
mode_orig = obj.mode
bpy.ops.object.mode_set(mode='OBJECT')
delta_pbone = obj.pose.bones[delta_name]
children = delta_pbone.children
if len(children) != 1:
print("only 1 child supported for delta")
child_name = children[0].name
delta_head = delta_pbone.head.copy()
delta_tail = delta_pbone.tail.copy()
delta_matrix = delta_pbone.matrix.copy()
children = delta_pbone.children
bpy.ops.object.mode_set(mode='EDIT')
arm = obj.data
# XXX -probably should allow via the UI
for ebone in arm.edit_bones:
ebone.selected = ebone.head_selected = ebone.tail_selected = False
# Select for deleting
delta_ebone = arm.edit_bones[delta_name]
delta_ebone.selected = delta_ebone.head_selected = delta_ebone.tail_selected = True
bpy.ops.armature.delete()
bpy.ops.object.mode_set(mode='OBJECT')
# Move the child bone to the deltas location
obj.animation_data_create()
child_pbone = obj.pose[child_name]
# ------------------- drivers
fcurve_driver = child_pbone.driver_add("rotation_euler", 0)
#fcurve_driver = obj.animation_data.drivers[-1] # XXX, WATCH THIS
driver = fcurve_driver.driver
driver.type = 'AVERAGE'
mod = driver.modifiers.new('GENERATOR')
obj, arm, parent_pbone, parent_bone = get_bone_data(obj, delta_name)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode=mode_orig)
def gen_arm(obj, orig_bone_name):
"""
the bone with the 'arm' property is the upper arm, this assumes a chain as follows.
[shoulder, upper_arm, forearm, hand]
...where this bone is 'upper_arm'
"""
def validate_chain():
'''
Sanity check and return the arm as a list of bone names.
'''
# do a sanity check
obj, arm, orig_pbone, orig_ebone = get_bone_data(obj, orig_bone_name)
shoulder_pbone = arm_pbone.parent
if not shoulder_pbone:
print("could not find 'arm' parent, skipping:", orig_bone_name)
return
# We could have some bones attached, find the bone that has this as its 2nd parent
hands = []
for pbone in obj.pose.bones:
index = pbone.parent_index(orig_pbone)
if index == 2:
hands.append(pbone)
if len(hands) > 1:
print("more then 1 hand found on:", orig_bone_name)
return
# first add the 2 new bones
hand_pbone = hands[0]
forearm_pbone = hand_pbone.parent
return shoulder_pbone.name, orig_pbone.name, forearm_pbone.name, hand_pbone.name
shoulder_name, arm_name, forearm_name, hand_name = validate_chain()
obj, arm, hand_pbone, hand_ebone = get_bone_data(obj, hand_name)
# Add the edit bones
hand_ik_ebone = arm.edit_bones.new(hand_name + "_ik")
hand_ik_ebone.head = hand_ebone.head
hand_ik_ebone.tail = hand_ebone.tail
hand_ik_ebone.roll = hand_ebone.roll
gen_table = {
"":gen_none, \
"finger":gen_finger, \
"delta":gen_delta, \
"arm":gen_arm, \
}
def generate_rig(context, ob):
# add_stretch_to(ob, "a", "b", "c")
bpy.ops.object.mode_set(mode='OBJECT')
# copy object and data
ob.selected = False
ob_new = ob.copy()
ob_new.data = ob.data.copy()
scene = context.scene
scene.objects.link(ob_new)
scene.objects.active = ob_new
ob_new.selected = True
# enter armature editmode
for pbone_name in ob_new.pose.bones.keys():
bone_type = ob_new.pose.bones[pbone_name].get("type", "")
try:
func = gen_table[bone_type]
except KeyError:
print("\tunknown type '%s', bone '%s'" % (bone_type, pbone_name))
# Toggle editmode so the pose data is always up to date
bpy.ops.object.mode_set(mode='EDIT')
func(ob_new, pbone_name)
bpy.ops.object.mode_set(mode='OBJECT')
# needed to update driver deps
# context.scene.update()
if __name__ == "__main__":
generate_rig(bpy.context, bpy.context.object)

View File

@@ -0,0 +1,625 @@
# ##### 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>
import bpy
from Mathutils import Vector
# TODO, have these in a more general module
from rna_prop_ui import rna_idprop_ui_prop_get
empty_layer = [False] * 32
def auto_class(slots, name="ContainerClass", class_dict=None):
if class_dict:
class_dict = class_dict.copy()
else:
class_dict = {}
class_dict["__slots__"] = tuple(slots)
return type(name, (object,), class_dict)
def auto_class_instance(slots, name="ContainerClass", class_dict=None):
return auto_class(slots, name, class_dict)()
def _bone_class_instance_update(self):
''' Re-Assigns bones from the blender data
'''
arm = self.obj.data
bbones = arm.bones
pbones = self.obj.pose.bones
ebones = arm.edit_bones
for member in self.attr_names:
name = getattr(self, member, None)
if name is not None:
setattr(self, member + "_b", bbones.get(name, None))
setattr(self, member + "_p", pbones.get(name, None))
setattr(self, member + "_e", ebones.get(name, None))
def _bone_class_instance_rename(self, attr, new_name):
''' Rename bones, editmode only
'''
if self.obj.mode != 'EDIT':
raise Exception("Only rename in editmode supported")
ebone = getattr(self, attr + "_e")
ebone.name = new_name
# we may not get what is asked for so get the name from the editbone
setattr(self, attr, ebone.name)
def _bone_class_instance_copy(self, from_prefix="", to_prefix=""):
from_name_ls = []
new_name_ls = []
new_slot_ls = []
for attr in self.attr_names:
bone_name_orig = getattr(self, attr)
ebone = getattr(self, attr + "_e")
# orig_names[attr] = bone_name_orig
# insert prefix
if from_prefix:
bone_name = from_prefix + bone_name_orig
ebone.name = bone_name
bone_name = ebone.name # cant be sure we get what we ask for
else:
bone_name = bone_name_orig
setattr(self, attr, bone_name)
new_slot_ls.append(attr)
from_name_ls.append(bone_name)
bone_name_orig = bone_name_orig.replace("ORG-", "") # XXX - we need a better way to do this
new_name_ls.append(to_prefix + bone_name_orig)
new_bones = copy_bone_simple_list(self.obj.data, from_name_ls, new_name_ls, True)
new_bc = bone_class_instance(self.obj, new_slot_ls)
for i, attr in enumerate(new_slot_ls):
ebone = new_bones[i]
setattr(new_bc, attr + "_e", ebone)
setattr(new_bc, attr, ebone.name)
return new_bc
def _bone_class_instance_names(self):
return [getattr(self, attr) for attr in self.attr_names]
def _bone_class_instance_blend(self, from_bc, to_bc, target_bone=None, target_prop="blend"):
'''
Use for blending bone chains.
blend_target = (bone_name, bone_property)
default to the last bone, blend prop
XXX - toggles editmode, need to re-validate all editbones :(
'''
if self.attr_names != from_bc.attr_names or self.attr_names != to_bc.attr_names:
raise Exception("can only blend between matching chains")
apply_bones = [getattr(self, attr) for attr in self.attr_names]
from_bones = [getattr(from_bc, attr) for attr in from_bc.attr_names]
to_bones = [getattr(to_bc, attr) for attr in to_bc.attr_names]
blend_bone_list(self.obj, apply_bones, from_bones, to_bones, target_bone, target_prop)
def bone_class_instance(obj, slots, name="BoneContainer"):
attr_names = tuple(slots) # dont modify the original
slots = list(slots) # dont modify the original
for i in range(len(slots)):
member = slots[i]
slots.append(member + "_b") # bone bone
slots.append(member + "_p") # pose bone
slots.append(member + "_e") # edit bone
class_dict = { \
"obj": obj, \
"attr_names": attr_names, \
"update": _bone_class_instance_update, \
"rename": _bone_class_instance_rename, \
"names": _bone_class_instance_names, \
"copy": _bone_class_instance_copy, \
"blend": _bone_class_instance_blend, \
}
instance = auto_class_instance(slots, name, class_dict)
return instance
def get_bone_data(obj, bone_name):
arm = obj.data
pbone = obj.pose.bones[bone_name]
if obj.mode == 'EDIT':
bone = arm.edit_bones[bone_name]
else:
bone = arm.bones[bone_name]
return arm, pbone, bone
def copy_bone_simple(arm, from_bone, name, parent=False):
ebone = arm.edit_bones[from_bone]
ebone_new = arm.edit_bones.new(name)
if parent:
ebone_new.connected = ebone.connected
ebone_new.parent = ebone.parent
ebone_new.head = ebone.head
ebone_new.tail = ebone.tail
ebone_new.roll = ebone.roll
return ebone_new
def copy_bone_simple_list(arm, from_bones, to_bones, parent=False):
if len(from_bones) != len(to_bones):
raise Exception("bone list sizes must match")
copy_bones = [copy_bone_simple(arm, bone_name, to_bones[i], True) for i, bone_name in enumerate(from_bones)]
# now we need to re-parent
for ebone in copy_bones:
parent = ebone.parent
if parent:
try:
i = from_bones.index(parent.name)
except:
i = -1
if i == -1:
ebone.parent = None
else:
ebone.parent = copy_bones[i]
return copy_bones
def blend_bone_list(obj, apply_bones, from_bones, to_bones, target_bone=None, target_prop="blend"):
if obj.mode == 'EDIT':
raise Exception("blending cant be called in editmode")
# setup the blend property
if target_bone is None:
target_bone = apply_bones[-1] # default to the last bone
prop_pbone = obj.pose.bones[target_bone]
if prop_pbone.get(target_bone, None) is None:
prop = rna_idprop_ui_prop_get(prop_pbone, target_prop, create=True)
prop_pbone[target_prop] = 0.5
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
driver_path = prop_pbone.path_to_id() + ('["%s"]' % target_prop)
def blend_target(driver):
tar = driver.targets.new()
tar.name = target_bone
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = driver_path
def blend_location(new_pbone, from_bone_name, to_bone_name):
con = new_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = from_bone_name
con = new_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = to_bone_name
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
driver.type = 'AVERAGE'
fcurve.modifiers.remove(0) # grr dont need a modifier
blend_target(driver)
def blend_rotation(new_pbone, from_bone_name, to_bone_name):
con = new_pbone.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = from_bone_name
con = new_pbone.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = to_bone_name
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
driver.type = 'AVERAGE'
fcurve.modifiers.remove(0) # grr dont need a modifier
blend_target(driver)
for i, new_bone_name in enumerate(apply_bones):
from_bone_name = from_bones[i]
to_bone_name = to_bones[i]
# allow skipping some bones by having None in the list
if None in (new_bone_name, from_bone_name, to_bone_name):
continue
new_pbone = obj.pose.bones[new_bone_name]
if not new_pbone.bone.connected:
blend_location(new_pbone, from_bone_name, to_bone_name)
blend_rotation(new_pbone, from_bone_name, to_bone_name)
def add_stretch_to(obj, from_name, to_name, name):
'''
Adds a bone that stretches from one to another
'''
mode_orig = obj.mode
bpy.ops.object.mode_set(mode='EDIT')
arm = obj.data
stretch_ebone = arm.edit_bones.new(name)
stretch_name = stretch_ebone.name
del name
head = stretch_ebone.head = arm.edit_bones[from_name].head.copy()
#tail = stretch_ebone.tail = arm.edit_bones[to_name].head.copy()
# annoying exception for zero length bones, since its using stretch_to the rest pose doesnt really matter
#if (head - tail).length < 0.1:
if 1:
tail = stretch_ebone.tail = arm.edit_bones[from_name].tail.copy()
# Now for the constraint
bpy.ops.object.mode_set(mode='OBJECT')
stretch_pbone = obj.pose.bones[stretch_name]
con = stretch_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = from_name
con = stretch_pbone.constraints.new('STRETCH_TO')
con.target = obj
con.subtarget = to_name
con.original_length = (head - tail).length
con.keep_axis = 'PLANE_X'
con.volume = 'NO_VOLUME'
bpy.ops.object.mode_set(mode=mode_orig)
def add_pole_target_bone(obj, base_name, name, mode='CROSS'):
'''
Does not actually create a poll target, just the bone to use as a poll target
'''
mode_orig = obj.mode
bpy.ops.object.mode_set(mode='EDIT')
arm = obj.data
poll_ebone = arm.edit_bones.new(base_name + "_poll")
base_ebone = arm.edit_bones[base_name]
poll_name = poll_ebone.name
parent_ebone = base_ebone.parent
base_head = base_ebone.head.copy()
base_tail = base_ebone.tail.copy()
base_dir = base_head - base_tail
parent_head = parent_ebone.head.copy()
parent_tail = parent_ebone.tail.copy()
parent_dir = parent_head - parent_tail
distance = (base_dir.length + parent_dir.length)
if mode == 'CROSS':
offset = base_dir.copy().normalize() - parent_dir.copy().normalize()
offset.length = distance
else:
offset = Vector(0, 0, 0)
if mode[0] == "+":
val = distance
else:
val = - distance
setattr(offset, mode[1].lower(), val)
poll_ebone.head = base_head + offset
poll_ebone.tail = base_head + (offset * (1.0 - (1.0 / 4.0)))
bpy.ops.object.mode_set(mode=mode_orig)
return poll_name
def generate_rig(context, obj_orig, prefix="ORG-"):
from collections import OrderedDict
global_undo = context.user_preferences.edit.global_undo
context.user_preferences.edit.global_undo = False
bpy.ops.object.mode_set(mode='OBJECT')
# copy object and data
obj_orig.selected = False
obj = obj_orig.copy()
obj.data = obj_orig.data.copy()
scene = context.scene
scene.objects.link(obj)
scene.objects.active = obj
obj.selected = True
arm = obj.data
# original name mapping
base_names = {}
bpy.ops.object.mode_set(mode='EDIT')
for bone in arm.edit_bones:
bone_name = bone.name
bone.name = prefix + bone_name
base_names[bone.name] = bone_name # new -> old mapping
bpy.ops.object.mode_set(mode='OBJECT')
# key: bone name
# value: {type:definition, ...}
# where type is the submodule name - leg, arm etc
# and definition is a list of bone names
bone_definitions = {}
# key: bone name
# value: [functions, ...]
# each function is from the module. eg leg.ik, arm.main
bone_typeinfos = {}
# inspect all bones and assign their definitions before modifying
for pbone in obj.pose.bones:
bone_name = pbone.name
bone_type = obj.pose.bones[bone_name].get("type", "")
bone_type_list = [bt for bt in bone_type.replace(",", " ").split()]
for bone_type in bone_type_list:
type_pair = bone_type.split(".")
# 'leg.ik' will look for an ik function in the leg module
# 'leg' will look up leg.main
if len(type_pair) == 1:
type_pair = type_pair[0], "main"
submod_name, func_name = type_pair
# from rigify import leg
submod = __import__(name="%s.%s" % (__package__, submod_name), fromlist=[submod_name])
reload(submod)
bone_def_dict = bone_definitions.setdefault(bone_name, {})
# Only calculate bone definitions once
if submod_name not in bone_def_dict:
metarig_definition_func = getattr(submod, "metarig_definition")
bone_def_dict[submod_name] = metarig_definition_func(obj, bone_name)
bone_typeinfo = bone_typeinfos.setdefault(bone_name, [])
type_func = getattr(submod, func_name)
bone_typeinfo.append((submod_name, type_func))
# sort bones, not needed but gives more pradictable execution which may be useful in rare cases
bones_sorted = obj.pose.bones.values()
bones_sorted.sort(key=lambda pbone: pbone.name) # first sort by names
bones_sorted.sort(key=lambda pbone: - len(pbone.parent_recursive)) # children before parents
# now we have all the info about bones we can start operating on them
# for pbone in obj.pose.bones:
for pbone in bones_sorted:
bone_name = pbone.name
if bone_name not in bone_typeinfos:
continue
bone_def_dict = bone_definitions[bone_name]
# Only blend results from the same submodule, eg.
# leg.ik and arm.fk could not be blended.
results = OrderedDict()
for submod_name, type_func in bone_typeinfos[bone_name]:
# this bones definition of the current typeinfo
definition = bone_def_dict[submod_name]
bpy.ops.object.mode_set(mode='EDIT')
ret = type_func(obj, definition, base_names)
bpy.ops.object.mode_set(mode='OBJECT')
if ret:
result_submod = results.setdefault(submod_name, [])
if result_submod and len(result_submod[-1]) != len(ret):
raise Exception("bone lists not compatible: %s, %s" % (result_submod[-1], ret))
result_submod.append(ret)
for result_submod in results.values():
# blend 2 chains
definition = bone_def_dict[submod_name]
if len(result_submod) == 2:
blend_bone_list(obj, definition, result_submod[0], result_submod[1])
# needed to update driver deps
# context.scene.update()
# Only for demo'ing
# obj.restrict_view = True
obj.data.draw_axes = False
context.user_preferences.edit.global_undo = global_undo
return obj
def write_meta_rig(obj, func_name="metarig_template"):
''' Must be in editmode
'''
code = []
code.append("def %s():" % func_name)
code.append(" # generated by rigify.write_meta_rig")
bpy.ops.object.mode_set(mode='EDIT')
code.append(" bpy.ops.object.mode_set(mode='EDIT')")
code.append(" obj = bpy.context.object")
code.append(" arm = obj.data")
arm = obj.data
# write parents first
bones = [(len(bone.parent_recursive), bone.name) for bone in arm.edit_bones]
bones.sort(key=lambda item: item[0])
bones = [item[1] for item in bones]
for bone_name in bones:
bone = arm.edit_bones[bone_name]
code.append(" bone = arm.edit_bones.new('%s')" % bone.name)
code.append(" bone.head[:] = %.4f, %.4f, %.4f" % bone.head.toTuple(4))
code.append(" bone.tail[:] = %.4f, %.4f, %.4f" % bone.tail.toTuple(4))
code.append(" bone.roll = %.4f" % bone.roll)
code.append(" bone.connected = %s" % str(bone.connected))
if bone.parent:
code.append(" bone.parent = arm.edit_bones['%s']" % bone.parent.name)
bpy.ops.object.mode_set(mode='OBJECT')
code.append("")
code.append(" bpy.ops.object.mode_set(mode='OBJECT')")
for bone_name in bones:
pbone = obj.pose.bones[bone_name]
pbone_written = False
# Only 1 level of props, simple types supported
for key, value in pbone.items():
if key.startswith("_"):
continue
if type(value) not in (float, str, int):
print("Unsupported ID Prop:", str((key, value)))
continue
if type(value) == str:
value = "'" + value + "'"
if not pbone_written: # only write bones we need
code.append(" pbone = obj.pose.bones['%s']" % bone_name)
code.append(" pbone['%s'] = %s" % (key, value))
return "\n".join(code)
def generate_test(context):
import os
new_objects = []
scene = context.scene
def create_empty_armature(name):
obj_new = bpy.data.add_object('ARMATURE', name)
armature = bpy.data.add_armature(name)
obj_new.data = armature
scene.objects.link(obj_new)
scene.objects.active = obj_new
files = os.listdir(os.path.dirname(__file__))
for f in files:
if f.startswith("_"):
continue
if not f.endswith(".py"):
continue
module_name = f[:-3]
submodule = __import__(name="%s.%s" % (__package__, module_name), fromlist=[module_name])
metarig_template = getattr(submodule, "metarig_template", None)
if metarig_template:
create_empty_armature("meta_" + module_name) # sets active
metarig_template()
obj = context.object
obj_new = generate_rig(context, obj)
new_objects.append((obj, obj_new))
else:
print("note: rig type '%s' has no metarig_template(), can't test this", module_name)
return new_objects
def generate_test_all(context):
import rigify
import graphviz_export
import os
reload(rigify)
reload(graphviz_export)
new_objects = rigify.generate_test(context)
base_name = os.path.splitext(bpy.data.filename)[0]
for obj, obj_new in new_objects:
for obj in (obj, obj_new):
fn = base_name + "-" + bpy.utils.clean_name(obj.name)
path_dot = fn + ".dot"
path_png = fn + ".png"
saved = graphviz_export.graph_armature(obj, path_dot, CONSTRAINTS=True, DRIVERS=True)
#if saved:
# os.system("dot -Tpng %s > %s; eog %s" % (path_dot, path_png, path_png))
i = 0
for obj, obj_new in new_objects:
obj.data.drawtype = 'STICK'
obj.location[1] += i
obj_new.location[1] += i
obj_new.selected = False
obj.selected = True
i += 4
if __name__ == "__main__":
generate_rig(bpy.context, bpy.context.object)

View File

@@ -0,0 +1,345 @@
# ##### 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>
import bpy
from rigify import bone_class_instance, copy_bone_simple, add_pole_target_bone, add_stretch_to
from rna_prop_ui import rna_idprop_ui_prop_get
METARIG_NAMES = "shoulder", "arm", "forearm", "hand"
def metarig_template():
bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.object
arm = obj.data
bone = arm.edit_bones.new('shoulder')
bone.head[:] = 0.0000, -0.4515, 0.0000
bone.tail[:] = 1.0000, -0.0794, 0.3540
bone.roll = -0.2227
bone.connected = False
bone = arm.edit_bones.new('upper_arm')
bone.head[:] = 1.1319, -0.0808, -0.0101
bone.tail[:] = 3.0319, 0.2191, -0.1101
bone.roll = 1.6152
bone.connected = False
bone.parent = arm.edit_bones['shoulder']
bone = arm.edit_bones.new('forearm')
bone.head[:] = 3.0319, 0.2191, -0.1101
bone.tail[:] = 4.8319, -0.0809, -0.0242
bone.roll = 1.5153
bone.connected = True
bone.parent = arm.edit_bones['upper_arm']
bone = arm.edit_bones.new('hand')
bone.head[:] = 4.8319, -0.0809, -0.0242
bone.tail[:] = 5.7590, -0.1553, -0.1392
bone.roll = -3.0083
bone.connected = True
bone.parent = arm.edit_bones['forearm']
bpy.ops.object.mode_set(mode='OBJECT')
pbone = obj.pose.bones['upper_arm']
pbone['type'] = 'arm'
def metarig_definition(obj, orig_bone_name):
mt = bone_class_instance(obj, METARIG_NAMES) # meta
mt.arm = orig_bone_name
mt.update()
mt.shoulder_p = mt.arm_p.parent
if not mt.shoulder_p:
raise Exception("could not find 'arm' parent, skipping:", orig_bone_name)
print(mt.shoulder_p)
mt.shoulder = mt.shoulder_p.name
# We could have some bones attached, find the bone that has this as its 2nd parent
hands = []
for pbone in obj.pose.bones:
index = pbone.parent_index(mt.arm_p)
if index == 2:
hands.append(pbone)
if len(hands) > 1:
raise Exception("more then 1 hand found on:", orig_bone_name)
# first add the 2 new bones
mt.hand_p = hands[0]
mt.hand = mt.hand_p.name
mt.forearm_p = mt.hand_p.parent
mt.forearm = mt.forearm_p.name
return mt.names()
def main(obj, definitions, base_names):
"""
the bone with the 'arm' property is the upper arm, this assumes a chain as follows.
[shoulder, upper_arm, forearm, hand]
...where this bone is 'upper_arm'
there are 3 chains
- Original
- IK, MCH-%s_ik
- IKSwitch, MCH-%s ()
"""
# Since there are 3 chains, this gets confusing so divide into 3 chains
# Initialize container classes for convenience
mt = bone_class_instance(obj, METARIG_NAMES) # meta
mt.shoulder, mt.arm, mt.forearm, mt.hand = definitions
ik = bone_class_instance(obj, ["arm", "forearm", "pole", "hand"]) # ik
sw = bone_class_instance(obj, ["socket", "shoulder", "arm", "forearm", "hand"]) # hinge
ex = bone_class_instance(obj, ["arm_hinge"]) # hinge & extras
arm = obj.data
def chain_ik(prefix="MCH-%s_ik"):
mt.update()
# Add the edit bones
ik.hand_e = copy_bone_simple(arm, mt.hand, prefix % base_names[mt.hand])
ik.hand = ik.hand_e.name
ik.arm_e = copy_bone_simple(arm, mt.arm, prefix % base_names[mt.arm])
ik.arm = ik.arm_e.name
ik.forearm_e = copy_bone_simple(arm, mt.forearm, prefix % base_names[mt.forearm])
ik.forearm = ik.forearm_e.name
ik.arm_e.parent = mt.arm_e.parent
ik.forearm_e.connected = mt.arm_e.connected
ik.forearm_e.parent = ik.arm_e
ik.forearm_e.connected = True
# Add the bone used for the arms poll target
ik.pole = add_pole_target_bone(obj, mt.forearm, "elbow_poll", mode='+Z')
bpy.ops.object.mode_set(mode='OBJECT')
ik.update()
con = ik.forearm_p.constraints.new('IK')
con.target = obj
con.subtarget = ik.hand
con.pole_target = obj
con.pole_subtarget = ik.pole
con.use_tail = True
con.use_stretch = True
con.use_target = True
con.use_rotation = False
con.chain_length = 2
con.pole_angle = -90.0 # XXX, RAD2DEG
# ID Propery on the hand for IK/FK switch
prop = rna_idprop_ui_prop_get(ik.hand_p, "ik", create=True)
ik.hand_p["ik"] = 0.5
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
bpy.ops.object.mode_set(mode='EDIT')
def chain_switch(prefix="MCH-%s"):
print(mt.obj.mode)
sw.update()
mt.update()
sw.shoulder_e = copy_bone_simple(arm, mt.shoulder, prefix % base_names[mt.shoulder])
sw.shoulder = sw.shoulder_e.name
sw.shoulder_e.parent = mt.shoulder_e.parent
sw.shoulder_e.connected = mt.shoulder_e.connected
sw.arm_e = copy_bone_simple(arm, mt.arm, prefix % base_names[mt.arm])
sw.arm = sw.arm_e.name
sw.arm_e.parent = sw.shoulder_e
sw.arm_e.connected = arm.edit_bones[mt.shoulder].connected
sw.forearm_e = copy_bone_simple(arm, mt.forearm, prefix % base_names[mt.forearm])
sw.forearm = sw.forearm_e.name
sw.forearm_e.parent = sw.arm_e
sw.forearm_e.connected = arm.edit_bones[mt.forearm].connected
sw.hand_e = copy_bone_simple(arm, mt.hand, prefix % base_names[mt.hand])
sw.hand = sw.hand_e.name
sw.hand_e.parent = sw.forearm_e
sw.hand_e.connected = arm.edit_bones[mt.hand].connected
# The sw.hand_e needs to own all the children on the metarig's hand
for child in mt.hand_e.children:
child.parent = sw.hand_e
# These are made the children of sw.shoulder_e
bpy.ops.object.mode_set(mode='OBJECT')
# Add constraints
sw.update()
#dummy, ik.arm, ik.forearm, ik.hand, ik.pole = ik_chain_tuple
ik_driver_path = obj.pose.bones[ik.hand].path_to_id() + '["ik"]'
def ik_fk_driver(con):
'''
3 bones use this for ik/fk switching
'''
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
tar = driver.targets.new()
driver.type = 'AVERAGE'
tar.name = "ik"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = ik_driver_path
# ***********
con = sw.arm_p.constraints.new('COPY_ROTATION')
con.name = "FK"
con.target = obj
con.subtarget = mt.arm
con = sw.arm_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = ik.arm
con.influence = 0.5
ik_fk_driver(con)
# ***********
con = sw.forearm_p.constraints.new('COPY_ROTATION')
con.name = "FK"
con.target = obj
con.subtarget = mt.forearm
con = sw.forearm_p.constraints.new('COPY_ROTATION')
con.name = "IK"
con.target = obj
con.subtarget = ik.forearm
con.influence = 0.5
ik_fk_driver(con)
# ***********
con = sw.hand_p.constraints.new('COPY_ROTATION')
con.name = "FK"
con.target = obj
con.subtarget = mt.hand
con = sw.hand_p.constraints.new('COPY_ROTATION')
con.name = "IK"
con.target = obj
con.subtarget = ik.hand
con.influence = 0.5
ik_fk_driver(con)
add_stretch_to(obj, sw.forearm, ik.pole, "VIS-elbow_ik_poll")
add_stretch_to(obj, sw.hand, ik.hand, "VIS-hand_ik")
bpy.ops.object.mode_set(mode='EDIT')
def chain_shoulder(prefix="MCH-%s"):
sw.socket_e = copy_bone_simple(arm, mt.arm, (prefix % base_names[mt.arm]) + "_socket")
sw.socket = sw.socket_e.name
sw.socket_e.tail = arm.edit_bones[mt.shoulder].tail
# Set the shoulder as parent
ik.update()
sw.update()
mt.update()
sw.socket_e.parent = sw.shoulder_e
ik.arm_e.parent = sw.shoulder_e
# ***** add the shoulder hinge
# yes this is correct, the shoulder copy gets the arm's name
ex.arm_hinge_e = copy_bone_simple(arm, mt.shoulder, (prefix % base_names[mt.arm]) + "_hinge")
ex.arm_hinge = ex.arm_hinge_e.name
offset = ex.arm_hinge_e.length / 2.0
ex.arm_hinge_e.head.y += offset
ex.arm_hinge_e.tail.y += offset
# Note: meta arm becomes child of hinge
mt.arm_e.parent = ex.arm_hinge_e
bpy.ops.object.mode_set(mode='OBJECT')
ex.update()
con = mt.arm_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = sw.socket
# Hinge constraint & driver
con = ex.arm_hinge_p.constraints.new('COPY_ROTATION')
con.name = "hinge"
con.target = obj
con.subtarget = sw.shoulder
driver_fcurve = con.driver_add("influence", 0)
driver = driver_fcurve.driver
controller_path = mt.arm_p.path_to_id()
# add custom prop
mt.arm_p["hinge"] = 0.0
prop = rna_idprop_ui_prop_get(mt.arm_p, "hinge", create=True)
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
# *****
driver = driver_fcurve.driver
driver.type = 'AVERAGE'
tar = driver.targets.new()
tar.name = "hinge"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = controller_path + '["hinge"]'
bpy.ops.object.mode_set(mode='EDIT')
# remove the shoulder and re-parent
chain_ik()
chain_switch()
chain_shoulder()
# Shoulder with its delta and hinge.
# TODO - return a list for fk and IK
return None

View File

@@ -0,0 +1,134 @@
# ##### 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>
import bpy
from rigify import get_bone_data
# not used, defined for completeness
METARIG_NAMES = tuple()
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the head, its parent is the body,
# its only child the first of a chain with matching basenames.
eg.
body -> head -> neck_01 -> neck_02 -> neck_03.... etc
'''
arm = obj.data
delta = arm.bones[orig_bone_name]
children = delta.children
if len(children) != 1:
print("only 1 child supported for delta")
bone_definition = [delta.name, children[0].name]
return bone_definition
def main(obj, bone_definition, base_names):
'''
Use this bone to define a delta thats applied to its child in pose mode.
'''
mode_orig = obj.mode
bpy.ops.object.mode_set(mode='OBJECT')
delta_name, child_name = bone_definition
delta_pbone = obj.pose.bones[delta_name]
arm, child_pbone, child_bone = get_bone_data(obj, child_name)
delta_phead = delta_pbone.head.copy()
delta_ptail = delta_pbone.tail.copy()
delta_pmatrix = delta_pbone.matrix.copy()
child_phead = child_pbone.head.copy()
child_ptail = child_pbone.tail.copy()
child_pmatrix = child_pbone.matrix.copy()
children = delta_pbone.children
bpy.ops.object.mode_set(mode='EDIT')
delta_ebone = arm.edit_bones[delta_name]
child_ebone = arm.edit_bones[child_name]
delta_head = delta_ebone.head.copy()
delta_tail = delta_ebone.tail.copy()
# arm, parent_pbone, parent_bone = get_bone_data(obj, delta_name)
child_head = child_ebone.head.copy()
child_tail = child_ebone.tail.copy()
#arm.edit_bones.remove(delta_ebone)
#del delta_ebone # cant use this
del child_pbone
bpy.ops.object.mode_set(mode='OBJECT')
# Move the child bone to the deltas location
obj.animation_data_create()
delta_pbone = obj.pose.bones[delta_name]
# child_pbone = obj.pose.bones[child_name]
# ------------------- drivers
delta_pbone.rotation_mode = 'XYZ'
rot = delta_pmatrix.invert().rotationPart() * child_pmatrix.rotationPart()
rot = rot.invert().toEuler()
fcurve_drivers = delta_pbone.driver_add("rotation_euler", -1)
for i, fcurve_driver in enumerate(fcurve_drivers):
driver = fcurve_driver.driver
driver.type = 'AVERAGE'
#mod = fcurve_driver.modifiers.new('GENERATOR')
mod = fcurve_driver.modifiers[0]
mod.poly_order = 1
mod.coefficients[0] = rot[i]
mod.coefficients[1] = 0.0
# tricky, find the transform to drive the bone to this location.
delta_head_offset = child_pmatrix.rotationPart() * (delta_phead - child_phead)
fcurve_drivers = delta_pbone.driver_add("location", -1)
for i, fcurve_driver in enumerate(fcurve_drivers):
driver = fcurve_driver.driver
driver.type = 'AVERAGE'
#mod = fcurve_driver.modifiers.new('GENERATOR')
mod = fcurve_driver.modifiers[0]
mod.poly_order = 1
mod.coefficients[0] = delta_head_offset[i]
mod.coefficients[1] = 0.0
# arm, parent_pbone, parent_bone = get_bone_data(obj, delta_name)
bpy.ops.object.mode_set(mode='EDIT')
bpy.ops.object.mode_set(mode=mode_orig)
# no blendeing
return None

View File

@@ -0,0 +1,223 @@
# ##### 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>
import bpy
from rigify import get_bone_data, empty_layer, copy_bone_simple
from rna_prop_ui import rna_idprop_ui_prop_get
from functools import reduce
METARIG_NAMES = "finger_01", "finger_02", "finger_03"
def metarig_template():
bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.object
arm = obj.data
bone = arm.edit_bones.new('finger.01')
bone.head[:] = 0.0000, 0.0000, 0.0000
bone.tail[:] = 0.8788, -0.4584, -0.1327
bone.roll = -2.8722
bone.connected = False
bone = arm.edit_bones.new('finger.02')
bone.head[:] = 0.8788, -0.4584, -0.1327
bone.tail[:] = 1.7483, -0.9059, -0.3643
bone.roll = -2.7099
bone.connected = True
bone.parent = arm.edit_bones['finger.01']
bone = arm.edit_bones.new('finger.03')
bone.head[:] = 1.7483, -0.9059, -0.3643
bone.tail[:] = 2.2478, -1.1483, -0.7408
bone.roll = -2.1709
bone.connected = True
bone.parent = arm.edit_bones['finger.02']
bpy.ops.object.mode_set(mode='OBJECT')
pbone = obj.pose.bones['finger.01']
pbone['type'] = 'finger'
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the first in a chain
Expects a chain of at least 2 children.
eg.
finger -> finger_01 -> finger_02
'''
bone_definition = []
orig_bone = obj.data.bones[orig_bone_name]
bone_definition.append(orig_bone.name)
bone = orig_bone
chain = 0
while chain < 2: # first 2 bones only have 1 child
children = bone.children
if len(children) != 1:
raise Exception("expected the chain to have 2 children without a fork")
bone = children[0]
bone_definition.append(bone.name) # finger_02, finger_03
chain += 1
if len(bone_definition) != len(METARIG_NAMES):
raise Exception("internal problem, expected %d bones" % len(METARIG_NAMES))
return bone_definition
def main(obj, bone_definition, base_names):
# *** EDITMODE
# get assosiated data
arm, orig_pbone, orig_ebone = get_bone_data(obj, bone_definition[0])
obj.animation_data_create() # needed if its a new armature with no keys
arm.layer[0] = arm.layer[8] = True
children = orig_pbone.children_recursive
tot_len = reduce(lambda f, pbone: f + pbone.bone.length, children, orig_pbone.bone.length)
base_name = base_names[bone_definition[0]].rsplit(".", 1)[0]
# first make a new bone at the location of the finger
#control_ebone = arm.edit_bones.new(base_name)
control_ebone = copy_bone_simple(arm, bone_definition[0], base_name)
control_bone_name = control_ebone.name # we dont know if we get the name requested
control_ebone.connected = orig_ebone.connected
control_ebone.parent = orig_ebone.parent
control_ebone.length = tot_len
# now add bones inbetween this and its children recursively
# switching modes so store names only!
children = [pbone.name for pbone in children]
# set an alternate layer for driver bones
other_layer = empty_layer[:]
other_layer[8] = True
driver_bone_pairs = []
for child_bone_name in children:
child_ebone = arm.edit_bones[child_bone_name]
# finger.02 --> finger_driver.02
driver_bone_name = child_bone_name.split('.')
driver_bone_name = driver_bone_name[0] + "_driver." + ".".join(driver_bone_name[1:])
driver_ebone = copy_bone_simple(arm, child_ebone.name, driver_bone_name)
driver_ebone.length *= 0.5
driver_ebone.layer = other_layer
# Insert driver_ebone in the chain without connected parents
driver_ebone.connected = False
driver_ebone.parent = child_ebone.parent
child_ebone.connected = False
child_ebone.parent = driver_ebone
# Add the drivers to these when in posemode.
driver_bone_pairs.append((child_bone_name, driver_bone_name))
del control_ebone
# *** POSEMODE
bpy.ops.object.mode_set(mode='OBJECT')
orig_pbone = obj.pose.bones[bone_definition[0]]
control_pbone = obj.pose.bones[control_bone_name]
# only allow Y scale
control_pbone.lock_scale = (True, False, True)
control_pbone["bend_ratio"] = 0.4
prop = rna_idprop_ui_prop_get(control_pbone, "bend_ratio", create=True)
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
con = orig_pbone.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = control_bone_name
con = orig_pbone.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = control_bone_name
# setup child drivers on each new smaller bone added. assume 2 for now.
# drives the bones
controller_path = control_pbone.path_to_id() # 'pose.bones["%s"]' % control_bone_name
i = 0
for child_bone_name, driver_bone_name in driver_bone_pairs:
# XXX - todo, any number
if i == 2:
break
driver_pbone = obj.pose.bones[driver_bone_name]
driver_pbone.rotation_mode = 'YZX'
fcurve_driver = driver_pbone.driver_add("rotation_euler", 0)
#obj.driver_add('pose.bones["%s"].scale', 1)
#obj.animation_data.drivers[-1] # XXX, WATCH THIS
driver = fcurve_driver.driver
# scale target
tar = driver.targets.new()
tar.name = "scale"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = controller_path + '.scale[1]'
# bend target
tar = driver.targets.new()
tar.name = "br"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = controller_path + '["bend_ratio"]'
# XXX - todo, any number
if i == 0:
driver.expression = '(-scale+1.0)*pi*2.0*(1.0-br)'
elif i == 1:
driver.expression = '(-scale+1.0)*pi*2.0*br'
child_pbone = obj.pose.bones[child_bone_name]
# only allow X rotation
driver_pbone.lock_rotation = child_pbone.lock_rotation = (False, True, True)
i += 1
# no blending the result of this
return None

View File

@@ -0,0 +1,341 @@
# ##### 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>
import bpy
from rigify import bone_class_instance, copy_bone_simple, blend_bone_list
from rna_prop_ui import rna_idprop_ui_prop_get
METARIG_NAMES = "hips", "thigh", "shin", "foot", "toe", "heel"
def metarig_template():
# generated by rigify.write_meta_rig
bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.object
arm = obj.data
bone = arm.edit_bones.new('hips')
bone.head[:] = 0.0000, 0.0000, 0.0000
bone.tail[:] = 0.0000, 0.0000, 1.0000
bone.roll = 0.0000
bone.connected = False
bone = arm.edit_bones.new('thigh')
bone.head[:] = 0.5000, 0.0000, 0.0000
bone.tail[:] = 0.3000, -0.1000, -1.7000
bone.roll = 0.1171
bone.connected = False
bone.parent = arm.edit_bones['hips']
bone = arm.edit_bones.new('shin')
bone.head[:] = 0.3000, -0.1000, -1.7000
bone.tail[:] = 0.3000, 0.0000, -3.5000
bone.roll = -0.0000
bone.connected = True
bone.parent = arm.edit_bones['thigh']
bone = arm.edit_bones.new('foot')
bone.head[:] = 0.3000, 0.0000, -3.5000
bone.tail[:] = 0.4042, -0.5909, -3.9000
bone.roll = -0.4662
bone.connected = True
bone.parent = arm.edit_bones['shin']
bone = arm.edit_bones.new('toe')
bone.head[:] = 0.4042, -0.5909, -3.9000
bone.tail[:] = 0.4391, -0.9894, -3.9000
bone.roll = -3.1416
bone.connected = True
bone.parent = arm.edit_bones['foot']
bone = arm.edit_bones.new('heel')
bone.head[:] = 0.2600, 0.2000, -4.0000
bone.tail[:] = 0.3700, -0.4000, -4.0000
bone.roll = 0.0000
bone.connected = False
bone.parent = arm.edit_bones['foot']
bpy.ops.object.mode_set(mode='OBJECT')
pbone = obj.pose.bones['thigh']
pbone['type'] = 'leg'
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the first in a chain
Expects a chain of at least 3 children.
eg.
thigh -> shin -> foot -> [toe, heel]
'''
bone_definition = []
orig_bone = obj.data.bones[orig_bone_name]
orig_bone_parent = orig_bone.parent
if orig_bone_parent is None:
raise Exception("expected the thigh bone to have a parent hip bone")
bone_definition.append(orig_bone_parent.name)
bone_definition.append(orig_bone.name)
bone = orig_bone
chain = 0
while chain < 2: # first 2 bones only have 1 child
children = bone.children
if len(children) != 1:
raise Exception("expected the thigh bone to have 3 children without a fork")
bone = children[0]
bone_definition.append(bone.name) # shin, foot
chain += 1
children = bone.children
# Now there must be 2 children, only one connected
if len(children) != 2:
raise Exception("expected the foot bone:'%s' to have 2 children" % bone.name )
if children[0].connected == children[1].connected:
raise Exception("expected one bone to be connected")
toe, heel = children
if heel.connected:
toe, heel = heel, toe
bone_definition.append(toe.name)
bone_definition.append(heel.name)
if len(bone_definition) != len(METARIG_NAMES):
raise Exception("internal problem, expected %d bones" % len(METARIG_NAMES))
return bone_definition
def ik(obj, bone_definition, base_names):
arm = obj.data
# setup the existing bones
mt_chain = bone_class_instance(obj, ["thigh", "shin", "foot", "toe"])
mt = bone_class_instance(obj, ["hips", "heel"])
#ex = bone_class_instance(obj, [""])
ex = bone_class_instance(obj, ["thigh_socket", "thigh_hinge", "foot_roll_1", "foot_roll_2", "foot_roll_3"])
# children of ik_foot
ik = bone_class_instance(obj, ["foot", "foot_roll", "foot_roll_01", "foot_roll_02", "knee_target"])
# XXX - duplicate below
for bone_class in (mt, mt_chain):
for attr in bone_class.attr_names:
i = METARIG_NAMES.index(attr)
ebone = arm.edit_bones[bone_definition[i]]
setattr(bone_class, attr, ebone.name)
bone_class.update()
# XXX - end dupe
# Make a new chain, ORG are the original bones renamed.
ik_chain = mt_chain.copy(to_prefix="MCH-")
# simple rename
ik_chain.rename("thigh", ik_chain.thigh + "_ik")
ik_chain.rename("shin", ik_chain.shin + "_ik")
# ik foot, no parents
base_foot_name = base_names[mt_chain.foot] # whatever the foot is called, use that!, XXX - ORG!
ik.foot_e = copy_bone_simple(arm, mt_chain.foot, "%s_ik" % base_foot_name)
ik.foot = ik.foot_e.name
ik.foot_e.tail.z = ik.foot_e.head.z
ik.foot_e.roll = 0.0
# heel pointing backwards, half length
ik.foot_roll_e = copy_bone_simple(arm, mt.heel, "%s_roll" % base_foot_name)
ik.foot_roll = ik.foot_roll_e.name
ik.foot_roll_e.tail = ik.foot_roll_e.head + (ik.foot_roll_e.head - ik.foot_roll_e.tail) / 2.0
ik.foot_roll_e.parent = ik.foot_e # heel is disconnected
# heel pointing forwards to the toe base, parent of the following 2 bones
ik.foot_roll_01_e = copy_bone_simple(arm, mt.heel, "MCH-%s_roll.01" % base_foot_name)
ik.foot_roll_01 = ik.foot_roll_01_e.name
ik.foot_roll_01_e.tail = mt_chain.foot_e.tail
ik.foot_roll_01_e.parent = ik.foot_e # heel is disconnected
# same as above but reverse direction
ik.foot_roll_02_e = copy_bone_simple(arm, mt.heel, "MCH-%s_roll.02" % base_foot_name)
ik.foot_roll_02 = ik.foot_roll_02_e.name
ik.foot_roll_02_e.parent = ik.foot_roll_01_e # heel is disconnected
ik.foot_roll_02_e.head = mt_chain.foot_e.tail
ik.foot_roll_02_e.tail = mt.heel_e.head
del base_foot_name
# rename 'MCH-toe' --> to 'toe_ik' and make the child of ik.foot_roll_01
# ------------------ FK or IK?
ik_chain.rename("toe", base_names[mt_chain.toe] + "_ik")
ik_chain.toe_e.connected = False
ik_chain.toe_e.parent = ik.foot_roll_01_e
# re-parent ik_chain.foot to the
ik_chain.foot_e.connected = False
ik_chain.foot_e.parent = ik.foot_roll_02_e
# knee target is the heel moved up and forward on its local axis
ik.knee_target_e = copy_bone_simple(arm, mt.heel, "knee_target")
ik.knee_target = ik.knee_target_e.name
offset = ik.knee_target_e.tail - ik.knee_target_e.head
offset.z = 0
offset.length = mt_chain.shin_e.head.z - mt.heel_e.head.z
offset.z += offset.length
ik.knee_target_e.translate(offset)
ik.knee_target_e.length *= 0.5
ik.knee_target_e.parent = ik.foot_e
# roll the bone to point up... could also point in the same direction as ik.foot_roll
# ik.foot_roll_02_e.matrix * Vector(0.0, 0.0, 1.0) # ACK!, no rest matrix in editmode
ik.foot_roll_01_e.align((0.0, 0.0, -1.0))
bpy.ops.object.mode_set(mode='OBJECT')
ik.update()
ex.update()
mt_chain.update()
ik_chain.update()
# IK
con = ik_chain.shin_p.constraints.new('IK')
con.chain_length = 2
con.iterations = 500
con.pole_angle = -90.0 # XXX - in deg!
con.use_tail = True
con.use_stretch = True
con.use_target = True
con.use_rotation = False
con.weight = 1.0
con.target = obj
con.subtarget = ik.foot
con.pole_target = obj
con.pole_subtarget = ik.knee_target
# foot roll
cons = [ \
(ik.foot_roll_01_p.constraints.new('COPY_ROTATION'), ik.foot_roll_01_p.constraints.new('LIMIT_ROTATION')), \
(ik.foot_roll_02_p.constraints.new('COPY_ROTATION'), ik.foot_roll_02_p.constraints.new('LIMIT_ROTATION'))]
for con, con_l in cons:
con.target = obj
con.subtarget = ik.foot_roll
con.use_x, con.use_y, con.use_z = True, False, False
con.target_space = con.owner_space = 'LOCAL'
con = con_l
con.use_limit_x, con.use_limit_y, con.use_limit_z = True, False, False
con.owner_space = 'LOCAL'
if con_l is cons[-1][-1]:
con.minimum_x = 0.0
con.maximum_x = 180.0 # XXX -deg
else:
con.minimum_x = -180.0 # XXX -deg
con.maximum_x = 0.0
bpy.ops.object.mode_set(mode='EDIT')
return None, ik_chain.thigh, ik_chain.shin, ik_chain.foot, ik_chain.toe, None
def fk(obj, bone_definition, base_names):
from Mathutils import Vector
arm = obj.data
# these account for all bones in METARIG_NAMES
mt_chain = bone_class_instance(obj, ["thigh", "shin", "foot", "toe"])
mt = bone_class_instance(obj, ["hips", "heel"])
# new bones
ex = bone_class_instance(obj, ["thigh_socket", "thigh_hinge"])
for bone_class in (mt, mt_chain):
for attr in bone_class.attr_names:
i = METARIG_NAMES.index(attr)
ebone = arm.edit_bones[bone_definition[i]]
setattr(bone_class, attr, ebone.name)
bone_class.update()
ex.thigh_socket_e = copy_bone_simple(arm, mt_chain.thigh, "MCH-%s_socket" % base_names[mt_chain.thigh], parent=True)
ex.thigh_socket = ex.thigh_socket_e.name
ex.thigh_socket_e.tail = ex.thigh_socket_e.head + Vector(0.0, 0.0, ex.thigh_socket_e.length / 4.0)
ex.thigh_hinge_e = copy_bone_simple(arm, mt_chain.thigh, "MCH-%s_hinge" % base_names[mt_chain.thigh], parent=True)
ex.thigh_hinge = ex.thigh_hinge_e.name
ex.thigh_hinge_e.tail = ex.thigh_hinge_e.head + Vector(0.0, 0.0, mt_chain.thigh_e.head.length)
ex.thigh_hinge_e.translate(Vector( - (mt.hips_e.head.x - mt_chain.thigh_e.head.x), 0.0, 0.0))
ex.thigh_hinge_e.length = mt.hips_e.length
fk_chain = mt_chain.copy() # fk has no prefix!
fk_chain.thigh_e.connected = False
fk_chain.thigh_e.parent = ex.thigh_hinge_e
bpy.ops.object.mode_set(mode='OBJECT')
ex.update()
mt_chain.update()
fk_chain.update()
con = fk_chain.thigh_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = ex.thigh_socket
# hinge
prop = rna_idprop_ui_prop_get(fk_chain.thigh_p, "hinge", create=True)
fk_chain.thigh_p["hinge"] = 0.5
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
con = ex.thigh_hinge_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = mt.hips
# add driver
hinge_driver_path = fk_chain.thigh_p.path_to_id() + '["hinge"]'
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
tar = driver.targets.new()
driver.type = 'AVERAGE'
tar.name = "var"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = hinge_driver_path
mod = fcurve.modifiers[0]
mod.poly_order = 1
mod.coefficients[0] = 1.0
mod.coefficients[1] = -1.0
bpy.ops.object.mode_set(mode='EDIT')
# dont blend the hips or heel
return None, fk_chain.thigh, fk_chain.shin, fk_chain.foot, fk_chain.toe, None
def main(obj, bone_definition, base_names):
bones_ik = ik(obj, bone_definition, base_names)
bones_fk = fk(obj, bone_definition, base_names)
bpy.ops.object.mode_set(mode='OBJECT')
blend_bone_list(obj, bone_definition, bones_ik, bones_fk)

View File

@@ -0,0 +1,267 @@
# ##### 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>
import bpy
from rigify import bone_class_instance, copy_bone_simple
from rna_prop_ui import rna_idprop_ui_prop_get
# not used, defined for completeness
METARIG_NAMES = ("body", "head")
def metarig_template():
bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.object
arm = obj.data
bone = arm.edit_bones.new('body')
bone.head[:] = -0.0000, -0.2771, -1.3345
bone.tail[:] = -0.0000, -0.1708, -0.3984
bone.roll = 0.0000
bone.connected = False
bone = arm.edit_bones.new('head')
bone.head[:] = -0.0000, -0.1708, -0.1984
bone.tail[:] = 0.0000, -0.1708, 1.6016
bone.roll = -0.0000
bone.connected = False
bone.parent = arm.edit_bones['body']
bone = arm.edit_bones.new('neck.01')
bone.head[:] = 0.0000, -0.1708, -0.1984
bone.tail[:] = -0.0000, -0.0994, 0.1470
bone.roll = -0.0000
bone.connected = False
bone.parent = arm.edit_bones['head']
bone = arm.edit_bones.new('neck.02')
bone.head[:] = -0.0000, -0.0994, 0.1470
bone.tail[:] = 0.0000, -0.2428, 0.5162
bone.roll = -0.0000
bone.connected = True
bone.parent = arm.edit_bones['neck.01']
bone = arm.edit_bones.new('neck.03')
bone.head[:] = 0.0000, -0.2428, 0.5162
bone.tail[:] = 0.0000, -0.4190, 0.8722
bone.roll = -0.0000
bone.connected = True
bone.parent = arm.edit_bones['neck.02']
bone = arm.edit_bones.new('neck.04')
bone.head[:] = 0.0000, -0.4190, 0.8722
bone.tail[:] = 0.0000, -0.5111, 1.1956
bone.roll = 0.0000
bone.connected = True
bone.parent = arm.edit_bones['neck.03']
bone = arm.edit_bones.new('neck.05')
bone.head[:] = 0.0000, -0.5111, 1.1956
bone.tail[:] = 0.0000, -0.5391, 1.6081
bone.roll = 0.0000
bone.connected = True
bone.parent = arm.edit_bones['neck.04']
bpy.ops.object.mode_set(mode='OBJECT')
pbone = obj.pose.bones['head']
pbone['type'] = 'neck'
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the head, its parent is the body,
# its only child the first of a chain with matching basenames.
eg.
body -> head -> neck_01 -> neck_02 -> neck_03.... etc
'''
arm = obj.data
head = arm.bones[orig_bone_name]
body = head.parent
children = head.children
if len(children) != 1:
print("expected the head to have only 1 child.")
child = children[0]
bone_definition = [body.name, head.name, child.name]
bone_definition.extend([child.name for child in child.children_recursive_basename])
return bone_definition
def main(obj, bone_definition, base_names):
from Mathutils import Vector
arm = obj.data
# Initialize container classes for convenience
mt = bone_class_instance(obj, ["body", "head"]) # meta
mt.body = bone_definition[0]
mt.head = bone_definition[1]
mt.update()
neck_chain = bone_definition[2:]
mt_chain = bone_class_instance(obj, [("neck_%.2d" % (i + 1)) for i in range(len(neck_chain))]) # 99 bones enough eh?
for i, attr in enumerate(mt_chain.attr_names):
setattr(mt_chain, attr, neck_chain[i])
mt_chain.update()
neck_chain_basename = mt_chain.neck_01_e.basename
neck_chain_segment_length = mt_chain.neck_01_e.length
ex = bone_class_instance(obj, ["body", "head", "head_hinge", "neck_socket"]) # hinge & extras
# Add the head hinge at the bodys location, becomes the parent of the original head
# Copy the head bone and offset
ex.head_e = copy_bone_simple(arm, mt.head, "MCH_%s" % mt.head, parent=True)
ex.head = ex.head_e.name
# offset
head_length = ex.head_e.length
ex.head_e.head.y += head_length / 2.0
ex.head_e.tail.y += head_length / 2.0
# Yes, use the body bone but call it a head hinge
ex.head_hinge_e = copy_bone_simple(arm, mt.body, "MCH_%s_hinge" % mt.head, parent=True)
ex.head_hinge = ex.head_hinge_e.name
ex.head_hinge_e.head.y += head_length / 4.0
ex.head_hinge_e.tail.y += head_length / 4.0
# reparent the head, assume its not connected
mt.head_e.parent = ex.head_hinge_e
# Insert the neck socket, the head copys this loation
ex.neck_socket_e = arm.edit_bones.new("MCH-%s_socked" % neck_chain_basename)
ex.neck_socket = ex.neck_socket_e.name
ex.neck_socket_e.parent = mt.body_e
ex.neck_socket_e.head = mt.head_e.head
ex.neck_socket_e.tail = mt.head_e.head - Vector(0.0, neck_chain_segment_length / 2.0, 0.0)
ex.neck_socket_e.roll = 0.0
# offset the head, not really needed since it has a copyloc constraint
mt.head_e.head.y += head_length / 4.0
mt.head_e.tail.y += head_length / 4.0
for i, attr in enumerate(mt_chain.attr_names):
neck_e = getattr(mt_chain, attr + "_e")
# dont store parent names, re-reference as each chain bones parent.
neck_e_parent = arm.edit_bones.new("MCH-rot_%s" % neck_e.name)
neck_e_parent.head = neck_e.head
neck_e_parent.tail = neck_e.head + ((mt.head_e.tail - mt.head_e.head).normalize() * neck_chain_segment_length / 2.0)
neck_e_parent.roll = neck_e.roll
orig_parent = neck_e.parent
neck_e.connected = False
neck_e.parent = neck_e_parent
neck_e_parent.connected = False
if i == 0:
neck_e_parent.parent = mt.body_e
else:
neck_e_parent.parent = orig_parent
bpy.ops.object.mode_set(mode='OBJECT')
mt.update()
mt_chain.update()
ex.update()
# Simple one off constraints, no drivers
con = mt.head_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = ex.neck_socket
con = ex.head_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = mt.head
# driven hinge
prop = rna_idprop_ui_prop_get(mt.head_p, "hinge", create=True)
mt.head_p["hinge"] = 0.0
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
con = ex.head_hinge_p.constraints.new('COPY_ROTATION')
con.name = "hinge"
con.target = obj
con.subtarget = mt.body
# add driver
hinge_driver_path = mt.head_p.path_to_id() + '["hinge"]'
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
tar = driver.targets.new()
driver.type = 'AVERAGE'
tar.name = "var"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = hinge_driver_path
#mod = fcurve_driver.modifiers.new('GENERATOR')
mod = fcurve.modifiers[0]
mod.poly_order = 1
mod.coefficients[0] = 1.0
mod.coefficients[1] = -1.0
head_driver_path = mt.head_p.path_to_id()
# b01/max(0.001,b01+b02+b03+b04+b05)
target_names = [("b%.2d" % (i + 1)) for i in range(len(neck_chain))]
expression_suffix = "/max(0.001,%s)" % "+".join(target_names)
for i, attr in enumerate(mt_chain.attr_names):
neck_p = getattr(mt_chain, attr + "_p")
neck_p.lock_location = True, True, True
neck_p.lock_location = True, True, True
neck_p.lock_rotations_4d = True
# Add bend prop
prop_name = "bend_%.2d" % (i + 1)
prop = rna_idprop_ui_prop_get(mt.head_p, prop_name, create=True)
mt.head_p[prop_name] = 1.0
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
# add parent constraint
neck_p_parent = neck_p.parent
# add constraint
con = neck_p_parent.constraints.new('COPY_ROTATION')
con.name = "Copy Rotation"
con.target = obj
con.subtarget = ex.head
con.owner_space = 'LOCAL'
con.target_space = 'LOCAL'
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
driver.type = 'SCRIPTED'
# b01/max(0.001,b01+b02+b03+b04+b05)
driver.expression = target_names[i] + expression_suffix
fcurve.modifiers.remove(0) # grr dont need a modifier
for j in range(len(neck_chain)):
tar = driver.targets.new()
tar.name = target_names[j]
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = head_driver_path + ('["bend_%.2d"]' % (j + 1))
# no blending the result of this
return None

View File

@@ -0,0 +1,208 @@
# ##### 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>
import bpy
from rigify import get_bone_data, copy_bone_simple
from rna_prop_ui import rna_idprop_ui_prop_get
# not used, defined for completeness
METARIG_NAMES = tuple()
def metarig_template():
# generated by rigify.write_meta_rig
bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.object
arm = obj.data
bone = arm.edit_bones.new('hand')
bone.head[:] = 0.0082, -1.2492, 0.0000
bone.tail[:] = 0.0423, -0.4150, 0.0000
bone.roll = 0.0000
bone.connected = False
bone = arm.edit_bones.new('palm.03')
bone.head[:] = 0.0000, 0.0000, -0.0000
bone.tail[:] = 0.0506, 1.2781, -0.1299
bone.roll = -3.1396
bone.connected = False
bone.parent = arm.edit_bones['hand']
bone = arm.edit_bones.new('palm.04')
bone.head[:] = 0.5000, -0.0000, 0.0000
bone.tail[:] = 0.6433, 1.2444, -0.1299
bone.roll = -3.1357
bone.connected = False
bone.parent = arm.edit_bones['hand']
bone = arm.edit_bones.new('palm.05')
bone.head[:] = 1.0000, 0.0000, 0.0000
bone.tail[:] = 1.3961, 1.0084, -0.1299
bone.roll = -3.1190
bone.connected = False
bone.parent = arm.edit_bones['hand']
bone = arm.edit_bones.new('palm.02')
bone.head[:] = -0.5000, 0.0000, -0.0000
bone.tail[:] = -0.5674, 1.2022, -0.1299
bone.roll = 3.1386
bone.connected = False
bone.parent = arm.edit_bones['hand']
bone = arm.edit_bones.new('palm.01')
bone.head[:] = -1.0000, 0.0000, -0.0000
bone.tail[:] = -1.3286, 1.0590, -0.1299
bone.roll = 3.1239
bone.connected = False
bone.parent = arm.edit_bones['hand']
bone = arm.edit_bones.new('palm.06')
bone.head[:] = 1.3536, -0.2941, 0.0000
bone.tail[:] = 2.1109, 0.4807, -0.1299
bone.roll = -3.0929
bone.connected = False
bone.parent = arm.edit_bones['hand']
bpy.ops.object.mode_set(mode='OBJECT')
pbone = obj.pose.bones['palm.05']
pbone['type'] = 'palm'
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the first in an array of siblings with a matching basename
sorted with the little finger lowest.
eg.
[pinky, ring... etc]
'''
arm = obj.data
palm_bone = arm.bones[orig_bone_name]
palm_parent = palm_bone.parent
palm_base = palm_bone.basename
bone_definition = [bone.name for bone in palm_parent.children if bone.basename == palm_base]
bone_definition.sort()
return [palm_parent.name] + bone_definition
def main(obj, bone_definition, base_names):
arm, palm_pbone, palm_ebone = get_bone_data(obj, bone_definition[0])
children = bone_definition[1:]
# Make a copy of the pinky
# simply assume the pinky has the lowest name
pinky_ebone = arm.edit_bones[children[0]]
ring_ebone = arm.edit_bones[children[1]]
control_ebone = copy_bone_simple(arm, pinky_ebone.name, "palm_control", parent=True)
control_name = control_ebone.name
offset = (pinky_ebone.head - ring_ebone.head)
control_ebone.translate(offset)
bpy.ops.object.mode_set(mode='OBJECT')
arm, control_pbone, control_ebone = get_bone_data(obj, control_name)
arm, pinky_pbone, pinky_ebone = get_bone_data(obj, children[0])
control_pbone.rotation_mode = 'YZX'
control_pbone.lock_rotation = False, True, True
driver_fcurves = pinky_pbone.driver_add("rotation_euler")
controller_path = control_pbone.path_to_id()
# add custom prop
control_pbone["spread"] = 0.0
prop = rna_idprop_ui_prop_get(control_pbone, "spread", create=True)
prop["soft_min"] = -1.0
prop["soft_max"] = 1.0
# *****
driver = driver_fcurves[0].driver
driver.type = 'AVERAGE'
tar = driver.targets.new()
tar.name = "x"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = controller_path + ".rotation_euler[0]"
# *****
driver = driver_fcurves[1].driver
driver.expression = "-x/4.0"
tar = driver.targets.new()
tar.name = "x"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = controller_path + ".rotation_euler[0]"
# *****
driver = driver_fcurves[2].driver
driver.expression = "(1.0-cos(x))-s"
tar = driver.targets.new()
tar.name = "x"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = controller_path + ".rotation_euler[0]"
tar = driver.targets.new()
tar.name = "s"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = controller_path + '["spread"]'
for i, child_name in enumerate(children):
child_pbone = obj.pose.bones[child_name]
child_pbone.rotation_mode = 'YZX'
if child_name != children[-1] and child_name != children[0]:
# this is somewhat arbitrary but seems to look good
inf = i / (len(children) + 1)
inf = 1.0 - inf
inf = ((inf * inf) + inf) / 2.0
# used for X/Y constraint
inf_minor = inf * inf
con = child_pbone.constraints.new('COPY_ROTATION')
con.name = "Copy Z Rot"
con.target = obj
con.subtarget = children[0] # also pinky_pbone
con.owner_space = con.target_space = 'LOCAL'
con.use_x, con.use_y, con.use_z = False, False, True
con.influence = inf
con = child_pbone.constraints.new('COPY_ROTATION')
con.name = "Copy XY Rot"
con.target = obj
con.subtarget = children[0] # also pinky_pbone
con.owner_space = con.target_space = 'LOCAL'
con.use_x, con.use_y, con.use_z = True, True, False
con.influence = inf_minor
child_pbone = obj.pose.bones[children[-1]]
child_pbone.rotation_mode = 'QUATERNION'
# no blending the result of this
return None

View File

@@ -0,0 +1,472 @@
# ##### 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>
import bpy
from rigify import bone_class_instance, copy_bone_simple
from rna_prop_ui import rna_idprop_ui_prop_get
# not used, defined for completeness
METARIG_NAMES = ("pelvis", "ribcage")
def metarig_template():
bpy.ops.object.mode_set(mode='EDIT')
obj = bpy.context.object
arm = obj.data
bone = arm.edit_bones.new('pelvis')
bone.head[:] = -0.0000, -0.2559, 0.8673
bone.tail[:] = -0.0000, -0.2559, -0.1327
bone.roll = 0.0000
bone.connected = False
bone = arm.edit_bones.new('rib_cage')
bone.head[:] = -0.0000, -0.2559, 0.8673
bone.tail[:] = -0.0000, -0.2559, 1.8673
bone.roll = -0.0000
bone.connected = False
bone.parent = arm.edit_bones['pelvis']
bone = arm.edit_bones.new('spine.01')
bone.head[:] = -0.0000, -0.0000, 0.0000
bone.tail[:] = -0.0000, -0.2559, 0.8673
bone.roll = -0.0000
bone.connected = False
bone.parent = arm.edit_bones['rib_cage']
bone = arm.edit_bones.new('spine.02')
bone.head[:] = -0.0000, -0.2559, 0.8673
bone.tail[:] = -0.0000, -0.3321, 1.7080
bone.roll = -0.0000
bone.connected = True
bone.parent = arm.edit_bones['spine.01']
bone = arm.edit_bones.new('spine.03')
bone.head[:] = -0.0000, -0.3321, 1.7080
bone.tail[:] = -0.0000, -0.0787, 2.4160
bone.roll = 0.0000
bone.connected = True
bone.parent = arm.edit_bones['spine.02']
bone = arm.edit_bones.new('spine.04')
bone.head[:] = -0.0000, -0.0787, 2.4160
bone.tail[:] = -0.0000, 0.2797, 3.0016
bone.roll = 0.0000
bone.connected = True
bone.parent = arm.edit_bones['spine.03']
bone = arm.edit_bones.new('spine.05')
bone.head[:] = -0.0000, 0.2797, 3.0016
bone.tail[:] = -0.0000, 0.4633, 3.6135
bone.roll = 0.0000
bone.connected = True
bone.parent = arm.edit_bones['spine.04']
bone = arm.edit_bones.new('spine.06')
bone.head[:] = -0.0000, 0.4633, 3.6135
bone.tail[:] = -0.0000, 0.3671, 4.3477
bone.roll = -0.0000
bone.connected = True
bone.parent = arm.edit_bones['spine.05']
bone = arm.edit_bones.new('spine.07')
bone.head[:] = -0.0000, 0.3671, 4.3477
bone.tail[:] = -0.0000, 0.0175, 5.0033
bone.roll = -0.0000
bone.connected = True
bone.parent = arm.edit_bones['spine.06']
bpy.ops.object.mode_set(mode='OBJECT')
pbone = obj.pose.bones['rib_cage']
pbone['type'] = 'spine'
def metarig_definition(obj, orig_bone_name):
'''
The bone given is the second in a chain.
Expects at least 1 parent and a chain of children withe the same basename
eg.
pelvis -> rib_cage -> spine.01 -> spine.02 -> spine.03
note: same as neck.
'''
arm = obj.data
ribcage = arm.bones[orig_bone_name]
pelvis = ribcage.parent
if pelvis is None:
raise Exception("expected the ribcage bone:'%s' to have a parent (ribcage)." % ribcage.name)
children = ribcage.children
if len(children) != 1:
raise Exception("expected the ribcage to have only 1 child.")
child = children[0]
bone_definition = [pelvis.name, ribcage.name, child.name]
bone_definition.extend([child.name for child in child.children_recursive_basename])
return bone_definition
def fk(*args):
main(*args)
def main(obj, bone_definition, base_names):
from Mathutils import Vector, RotationMatrix
from math import radians, pi
arm = obj.data
# Initialize container classes for convenience
mt = bone_class_instance(obj, ["pelvis", "ribcage"]) # meta
mt.pelvis = bone_definition[0]
mt.ribcage = bone_definition[1]
mt.update()
spine_chain_orig = tuple(bone_definition[2:])
spine_chain = [arm.edit_bones[child_name] for child_name in spine_chain_orig]
spine_chain_basename = base_names[spine_chain[0].name].rsplit(".", 1)[0] # probably 'ORG-spine.01' -> 'spine'
spine_chain_len = len(spine_chain_orig)
child = spine_chain[0]
spine_chain_segment_length = child.length
child.parent = mt.pelvis_e # was mt.ribcage
# The first bone in the chain happens to be the basis of others, create them now
ex = bone_class_instance(obj, ["pelvis", "ribcage", "ribcage_hinge", "spine_rotate"])
df = bone_class_instance(obj, ["pelvis", "ribcage"]) # DEF-wgt_pelvis, DEF-wgt_rib_cage
# copy the pelvis, offset to make MCH-spine_rotate and MCH-ribcage_hinge
ex.ribcage_hinge_e = copy_bone_simple(arm, mt.pelvis, "MCH-%s_hinge" % mt.ribcage)
ex.ribcage_hinge = ex.ribcage_hinge_e.name
ex.ribcage_hinge_e.translate(Vector(0.0, spine_chain_segment_length / 4.0, 0.0))
mt.ribcage_e.parent = ex.ribcage_hinge_e
ex.spine_rotate_e = copy_bone_simple(arm, mt.pelvis, "MCH-%s_rotate" % spine_chain_basename)
ex.spine_rotate = ex.spine_rotate_e.name
ex.spine_rotate_e.translate(Vector(0.0, spine_chain_segment_length / 2.0, 0.0))
# swap head/tail
ex.spine_rotate_e.head, ex.spine_rotate_e.tail = ex.spine_rotate_e.tail.copy(), ex.spine_rotate_e.head.copy()
ex.spine_rotate_e.parent = mt.pelvis_e
df.pelvis_e = copy_bone_simple(arm, child.name, "DEF-wgt_%s" % mt.pelvis)
df.pelvis = df.pelvis_e.name
df.pelvis_e.translate(Vector(spine_chain_segment_length * 2.0, - spine_chain_segment_length, 0.0))
ex.pelvis_e = copy_bone_simple(arm, child.name, "MCH-wgt_%s" % mt.pelvis)
ex.pelvis = ex.pelvis_e.name
ex.pelvis_e.translate(Vector(0.0, - spine_chain_segment_length, 0.0))
ex.pelvis_e.parent = mt.pelvis_e
# Copy the last bone now
child = spine_chain[-1]
df.ribcage_e = copy_bone_simple(arm, child.name, "DEF-wgt_%s" % mt.ribcage)
df.ribcage = df.ribcage_e.name
df.ribcage_e.translate(Vector(spine_chain_segment_length * 2.0, - df.ribcage_e.length / 2.0, 0.0))
ex.ribcage_e = copy_bone_simple(arm, child.name, "MCH-wgt_%s" % mt.ribcage)
ex.ribcage = ex.ribcage_e.name
ex.ribcage_e.translate(Vector(0.0, - ex.ribcage_e.length / 2.0, 0.0))
ex.ribcage_e.parent = mt.ribcage_e
spine_chain = [child.name for child in spine_chain]
# We have 3 spine chains
# - original (ORG_*)
# - copy (*use original name*)
# - reverse (MCH-rev_*)
spine_chain_attrs = [("spine_%.2d" % (i + 1)) for i in range(spine_chain_len)]
mt_chain = bone_class_instance(obj, spine_chain_attrs) # ORG_*
rv_chain = bone_class_instance(obj, spine_chain_attrs) # *
ex_chain = bone_class_instance(obj, spine_chain_attrs) # MCH-rev_*
del spine_chain_attrs
for i, child_name in enumerate(spine_chain):
child_name_orig = base_names[spine_chain_orig[i]]
attr = mt_chain.attr_names[i] # eg. spine_04
setattr(mt_chain, attr, spine_chain_orig[i]) # the original bone
ebone = copy_bone_simple(arm, child_name, child_name_orig) # use the original name
setattr(ex_chain, attr, ebone.name)
ebone = copy_bone_simple(arm, child_name, "MCH-rev_%s" % child_name_orig)
setattr(rv_chain, attr, ebone.name)
ebone.connected = False
mt_chain.update()
ex_chain.update()
rv_chain.update()
# Now we need to re-parent these chains
for i, child_name in enumerate(spine_chain_orig):
attr = ex_chain.attr_names[i] + "_e"
if i == 0:
getattr(ex_chain, attr).parent = mt.pelvis_e
else:
attr_parent = ex_chain.attr_names[i-1] + "_e"
getattr(ex_chain, attr).parent = getattr(ex_chain, attr_parent)
# intentional! get the parent from the other paralelle chain member
getattr(rv_chain, attr).parent = getattr(ex_chain, attr)
# ex_chain needs to interlace bones!
# Note, skip the first bone
for i in range(1, spine_chain_len): # similar to neck
child_name_orig = spine_chain_orig[i]
spine_e = getattr(mt_chain, mt_chain.attr_names[i] + "_e")
# dont store parent names, re-reference as each chain bones parent.
spine_e_parent = arm.edit_bones.new("MCH-rot_%s" % child_name_orig)
spine_e_parent.head = spine_e.head
spine_e_parent.tail = spine_e.head + ((mt.ribcage_e.tail - mt.ribcage_e.head).normalize() * spine_chain_segment_length / 2.0)
spine_e_parent.roll = mt.ribcage_e.roll
spine_e = getattr(ex_chain, ex_chain.attr_names[i] + "_e")
orig_parent = spine_e.parent
spine_e.connected = False
spine_e.parent = spine_e_parent
spine_e_parent.connected = False
spine_e_parent.parent = orig_parent
# Rotate the rev chain 180 about the by the first bones center point
pivot = (rv_chain.spine_01_e.head + rv_chain.spine_01_e.tail) * 0.5
matrix = RotationMatrix(radians(180), 3, 'X')
for i, attr in enumerate(rv_chain.attr_names): # similar to neck
spine_e = getattr(rv_chain, attr + "_e")
# use the first bone as the pivot
spine_e.head = ((spine_e.head - pivot) * matrix) + pivot
spine_e.tail = ((spine_e.tail - pivot) * matrix) + pivot
spine_e.roll += pi # 180d roll
del spine_e
bpy.ops.object.mode_set(mode='OBJECT')
# refresh pose bones
mt.update()
ex.update()
df.update()
mt_chain.update()
ex_chain.update()
rv_chain.update()
# df.pelvis_p / DEF-wgt_pelvis
con = df.pelvis_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = ex.pelvis
con.owner_space = 'LOCAL'
con.target_space = 'LOCAL'
con = df.pelvis_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = ex.pelvis
con.owner_space = 'LOCAL'
con.target_space = 'LOCAL'
# df.ribcage_p / DEF-wgt_rib_cage
con = df.ribcage_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = ex.ribcage
con.owner_space = 'LOCAL'
con.target_space = 'LOCAL'
con = df.ribcage_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = ex.ribcage
con.owner_space = 'LOCAL'
con.target_space = 'LOCAL'
con = ex.ribcage_hinge_p.constraints.new('COPY_ROTATION')
con.name = "hinge"
con.target = obj
con.subtarget = mt.pelvis
# add driver
hinge_driver_path = mt.ribcage_p.path_to_id() + '["hinge"]'
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
tar = driver.targets.new()
driver.type = 'AVERAGE'
tar.name = "var"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = hinge_driver_path
mod = fcurve.modifiers[0]
mod.poly_order = 1
mod.coefficients[0] = 1.0
mod.coefficients[1] = -1.0
con = ex.spine_rotate_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = mt.ribcage
# ex.pelvis_p / MCH-wgt_pelvis
con = ex.pelvis_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = mt_chain.spine_01
con.owner_space = 'WORLD'
con.target_space = 'WORLD'
con = ex.pelvis_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = mt_chain.spine_01
con.owner_space = 'WORLD'
con.target_space = 'WORLD'
# ex.ribcage_p / MCH-wgt_rib_cage
con = ex.ribcage_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = getattr(mt_chain, mt_chain.attr_names[-1])
con.head_tail = 0.0
con = ex.ribcage_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = getattr(mt_chain, mt_chain.attr_names[-1])
# mt.pelvis_p / rib_cage
con = mt.ribcage_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = mt.pelvis
con.head_tail = 0.0
# This stores all important ID props
prop = rna_idprop_ui_prop_get(mt.ribcage_p, "hinge", create=True)
mt.ribcage_p["hinge"] = 1.0
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
prop = rna_idprop_ui_prop_get(mt.ribcage_p, "pivot_slide", create=True)
mt.ribcage_p["pivot_slide"] = 0.5
prop["soft_min"] = 1.0 / spine_chain_len
prop["soft_max"] = 1.0
for i in range(spine_chain_len - 1):
prop_name = "bend_%.2d" % (i + 1)
prop = rna_idprop_ui_prop_get(mt.ribcage_p, prop_name, create=True)
mt.ribcage_p[prop_name] = 1.0
prop["soft_min"] = 0.0
prop["soft_max"] = 1.0
# Create a fake connected parent/child relationship with bone location constraints
# positioned at the tip.
# reverse bones / MCH-rev_spine.##
for i in range(1, spine_chain_len):
spine_p = getattr(rv_chain, rv_chain.attr_names[i] + "_p")
spine_fake_parent_name = getattr(rv_chain, rv_chain.attr_names[i - 1])
con = spine_p.constraints.new('COPY_LOCATION')
con.target = obj
con.subtarget = spine_fake_parent_name
con.head_tail = 1.0
del spine_p, spine_fake_parent_name, con
# Constrain 'inbetween' bones
# b01/max(0.001,b01+b02+b03+b04+b05)
target_names = [("b%.2d" % (i + 1)) for i in range(spine_chain_len - 1)]
expression_suffix = "/max(0.001,%s)" % "+".join(target_names)
rib_driver_path = mt.ribcage_p.path_to_id()
for i in range(1, spine_chain_len):
spine_p = getattr(ex_chain, ex_chain.attr_names[i] + "_p")
spine_p_parent = spine_p.parent # interlaced bone
con = spine_p_parent.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = ex.spine_rotate
con.owner_space = 'LOCAL'
con.target_space = 'LOCAL'
del spine_p
# add driver
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
driver.type = 'SCRIPTED'
# b01/max(0.001,b01+b02+b03+b04+b05)
driver.expression = target_names[i - 1] + expression_suffix
fcurve.modifiers.remove(0) # grr dont need a modifier
for j in range(spine_chain_len - 1):
tar = driver.targets.new()
tar.name = target_names[j]
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = rib_driver_path + ('["bend_%.2d"]' % (j + 1))
# original bone drivers
# note: the first bone has a lot more constraints, but also this simple one is first.
for i, attr in enumerate(mt_chain.attr_names):
spine_p = getattr(mt_chain, attr + "_p")
con = spine_p.constraints.new('COPY_ROTATION')
con.target = obj
con.subtarget = getattr(ex_chain, attr) # lock to the copy's rotation
del spine_p
# pivot slide: - lots of copy location constraints.
con = mt_chain.spine_01_p.constraints.new('COPY_LOCATION')
con.name = "base"
con.target = obj
con.subtarget = rv_chain.spine_01 # lock to the reverse location
for i in range(1, spine_chain_len + 1):
con = mt_chain.spine_01_p.constraints.new('COPY_LOCATION')
con.name = "slide_%d" % i
con.target = obj
if i == spine_chain_len:
attr = mt_chain.attr_names[i - 1]
else:
attr = mt_chain.attr_names[i]
con.subtarget = getattr(rv_chain, attr) # lock to the reverse location
if i == spine_chain_len:
con.head_tail = 1.0
fcurve = con.driver_add("influence", 0)
driver = fcurve.driver
tar = driver.targets.new()
driver.type = 'AVERAGE'
tar.name = "var"
tar.id_type = 'OBJECT'
tar.id = obj
tar.rna_path = rib_driver_path + '["pivot_slide"]'
mod = fcurve.modifiers[0]
mod.poly_order = 1
mod.coefficients[0] = - (i - 1)
mod.coefficients[1] = spine_chain_len
# no support for blending chains
return None

View File

@@ -20,6 +20,7 @@
import bpy
def rna_idprop_ui_get(item, create=True):
try:
return item['_RNA_UI']
@@ -57,7 +58,7 @@ def rna_idprop_ui_prop_clear(item, prop):
pass
def draw(layout, context, context_member, use_edit = True):
def draw(layout, context, context_member, use_edit=True):
def assign_props(prop, val, key):
prop.path = context_member
@@ -69,7 +70,7 @@ def draw(layout, context, context_member, use_edit = True):
pass
rna_item = eval("context." + context_member)
# poll should really get this...
if not rna_item:
return
@@ -138,6 +139,7 @@ rna_property = StringProperty(name="Property Name",
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 path)'''
bl_idname = "wm.properties_edit"
@@ -206,34 +208,16 @@ class WM_OT_properties_edit(bpy.types.Operator):
prop_ui = rna_idprop_ui_prop_get(item, self.properties.property, False) # dont create
if prop_ui:
self.properties.min = prop_ui.get("min", -1000000000)
self.properties.max = prop_ui.get("max", 1000000000)
self.properties.description = prop_ui.get("description", "")
if 0:
_message= "PyConsole, press Ctrl+D to unlock the BGE"
import sys
# evaluate commands in current namespace
frame= sys._getframe()
namespace = frame.f_globals.copy()
namespace.update(frame.f_locals)
import code
# Autocomp in python, not as comprehensive as IPython
import rlcompleter
try: # ick, some pythons dont have this
import readline
readline.parse_and_bind("tab: complete")
except:
pass
code.interact(banner=_message, local=namespace)
self.properties.max = prop_ui.get("max", 1000000000)
self.properties.description = prop_ui.get("description", "")
wm = context.manager
# This crashes, TODO - fix
#return wm.invoke_props_popup(self, event)
wm.invoke_props_popup(self, event)
return ('RUNNING_MODAL',)
class WM_OT_properties_add(bpy.types.Operator):
@@ -252,7 +236,7 @@ class WM_OT_properties_add(bpy.types.Operator):
i = 1
while prop_new in names:
prop_new = prop + str(i)
i+=1
i += 1
return prop_new
@@ -261,6 +245,7 @@ class WM_OT_properties_add(bpy.types.Operator):
item[property] = 1.0
return ('FINISHED',)
class WM_OT_properties_remove(bpy.types.Operator):
'''Internal use (edit a property path)'''
bl_idname = "wm.properties_remove"
@@ -273,4 +258,3 @@ class WM_OT_properties_remove(bpy.types.Operator):
item = eval("context.%s" % self.properties.path)
del item[self.properties.property]
return ('FINISHED',)

View File

@@ -22,11 +22,13 @@ import bpy
language_id = 'python'
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
@@ -70,6 +72,7 @@ def get_console(console_id):
PROMPT = '>>> '
PROMPT_MULTI = '... '
def execute(context):
sc = context.space_data

View File

@@ -25,19 +25,21 @@ import bpy
language_id = 'shell'
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 shell_run(text):
import subprocess
val, output= subprocess.getstatusoutput(text)
val, output = subprocess.getstatusoutput(text)
if not val:
style= 'OUTPUT'
style = 'OUTPUT'
else:
style= 'ERROR'
style = 'ERROR'
add_scrollback(output, style)
@@ -60,7 +62,7 @@ def execute(context):
bpy.ops.console.history_append(text="", current_character=0,
remove_duplicates=True)
sc.prompt = os.getcwd()+PROMPT
sc.prompt = os.getcwd() + PROMPT
return ('FINISHED',)
@@ -74,7 +76,6 @@ def banner(context):
sc = context.space_data
shell_run("bash --version")
sc.prompt = os.getcwd()+PROMPT
sc.prompt = os.getcwd() + PROMPT
return ('FINISHED',)

View File

@@ -4,12 +4,12 @@
# 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.
@@ -29,620 +29,620 @@ from Mathutils import AngleBetweenVecs as _AngleBetweenVecs_
BIG_NUM = 1<<30
global CULL_METHOD
CULL_METHOD = 0
CULL_METHOD = 0
def AngleBetweenVecs(a1,a2):
import math
try:
return math.degrees(_AngleBetweenVecs_(a1,a2))
except:
return 180.0
import math
try:
return math.degrees(_AngleBetweenVecs_(a1,a2))
except:
return 180.0
class edge(object):
__slots__ = 'v1', 'v2', 'co1', 'co2', 'length', 'removed', 'match', 'cent', 'angle', 'next', 'prev', 'normal', 'fake'
def __init__(self, v1,v2):
self.v1 = v1
self.v2 = v2
co1, co2= v1.co, v2.co
self.co1= co1
self.co2= co2
# uv1 uv2 vcol1 vcol2 # Add later
self.length = (co1 - co2).length
self.removed = 0 # Have we been culled from the eloop
self.match = None # The other edge were making a face with
self.cent= MidpointVecs(co1, co2)
self.angle= 0.0
self.fake= False
__slots__ = 'v1', 'v2', 'co1', 'co2', 'length', 'removed', 'match', 'cent', 'angle', 'next', 'prev', 'normal', 'fake'
def __init__(self, v1,v2):
self.v1 = v1
self.v2 = v2
co1, co2= v1.co, v2.co
self.co1= co1
self.co2= co2
# uv1 uv2 vcol1 vcol2 # Add later
self.length = (co1 - co2).length
self.removed = 0 # Have we been culled from the eloop
self.match = None # The other edge were making a face with
self.cent= MidpointVecs(co1, co2)
self.angle= 0.0
self.fake= False
class edgeLoop(object):
__slots__ = 'centre', 'edges', 'normal', 'closed', 'backup_edges'
def __init__(self, loop, me, closed): # Vert loop
# Use next and prev, nextDist, prevDist
# Get Loops centre.
fac= len(loop)
verts = me.verts
self.centre= functools.reduce(lambda a,b: a+verts[b].co/fac, loop, Vector())
# Convert Vert loop to Edges.
self.edges = [edge(verts[loop[vIdx-1]], verts[loop[vIdx]]) for vIdx in range(len(loop))]
if not closed:
self.edges[0].fake = True # fake edge option
self.closed = closed
# Assign linked list
for eIdx in range(len(self.edges)-1):
self.edges[eIdx].next = self.edges[eIdx+1]
self.edges[eIdx].prev = self.edges[eIdx-1]
# Now last
self.edges[-1].next = self.edges[0]
self.edges[-1].prev = self.edges[-2]
# GENERATE AN AVERAGE NORMAL FOR THE WHOLE LOOP.
self.normal = Vector()
for e in self.edges:
n = (self.centre-e.co1).cross(self.centre-e.co2)
# Do we realy need tot normalize?
n.normalize()
self.normal += n
# Generate the angle
va= e.cent - e.prev.cent
vb= e.next.cent - e.cent
e.angle= AngleBetweenVecs(va, vb)
# Blur the angles
#for e in self.edges:
# e.angle= (e.angle+e.next.angle)/2
# Blur the angles
#for e in self.edges:
# e.angle= (e.angle+e.prev.angle)/2
self.normal.normalize()
# Generate a normal for each edge.
for e in self.edges:
n1 = e.co1
n2 = e.co2
n3 = e.prev.co1
a = n1-n2
b = n1-n3
normal1 = a.cross(b)
normal1.normalize()
n1 = e.co2
n3 = e.next.co2
n2 = e.co1
a = n1-n2
b = n1-n3
normal2 = a.cross(b)
normal2.normalize()
# Reuse normal1 var
normal1 += normal1 + normal2
normal1.normalize()
e.normal = normal1
#print e.normal
__slots__ = 'centre', 'edges', 'normal', 'closed', 'backup_edges'
def __init__(self, loop, me, closed): # Vert loop
# Use next and prev, nextDist, prevDist
# Get Loops centre.
fac= len(loop)
verts = me.verts
self.centre= functools.reduce(lambda a,b: a+verts[b].co/fac, loop, Vector())
# Convert Vert loop to Edges.
self.edges = [edge(verts[loop[vIdx-1]], verts[loop[vIdx]]) for vIdx in range(len(loop))]
if not closed:
self.edges[0].fake = True # fake edge option
self.closed = closed
def backup(self):
# Keep a backup of the edges
self.backup_edges = self.edges[:]
def restore(self):
self.edges = self.backup_edges[:]
for e in self.edges:
e.removed = 0
def reverse(self):
self.edges.reverse()
self.normal.negate()
for e in self.edges:
e.normal.negate()
e.v1, e.v2 = e.v2, e.v1
e.co1, e.co2 = e.co2, e.co1
e.next, e.prev = e.prev, e.next
def removeSmallest(self, cullNum, otherLoopLen):
'''
Removes N Smallest edges and backs up the loop,
this is so we can loop between 2 loops as if they are the same length,
backing up and restoring incase the loop needs to be skinned with another loop of a different length.
'''
global CULL_METHOD
if CULL_METHOD == 1: # Shortest edge
eloopCopy = self.edges[:]
# Length sort, smallest first
try: eloopCopy.sort(key = lambda e1: e1.length)
except: eloopCopy.sort(lambda e1, e2: cmp(e1.length, e2.length ))
# Dont use atm
#eloopCopy.sort(lambda e1, e2: cmp(e1.angle*e1.length, e2.angle*e2.length)) # Length sort, smallest first
#eloopCopy.sort(lambda e1, e2: cmp(e1.angle, e2.angle)) # Length sort, smallest first
remNum = 0
for i, e in enumerate(eloopCopy):
if not e.fake:
e.removed = 1
self.edges.remove( e ) # Remove from own list, still in linked list.
remNum += 1
if not remNum < cullNum:
break
else: # CULL METHOD is even
culled = 0
step = int(otherLoopLen / float(cullNum)) * 2
currentEdge = self.edges[0]
while culled < cullNum:
# Get the shortest face in the next STEP
step_count= 0
bestAng= 360.0
smallestEdge= None
while step_count<=step or smallestEdge==None:
step_count+=1
if not currentEdge.removed: # 0 or -1 will not be accepted
if currentEdge.angle<bestAng and not currentEdge.fake:
smallestEdge= currentEdge
bestAng= currentEdge.angle
currentEdge = currentEdge.next
# In that stepping length we have the smallest edge.remove it
smallestEdge.removed = 1
self.edges.remove(smallestEdge)
# Start scanning from the edge we found? - result is over fanning- no good.
#currentEdge= smallestEdge.next
culled+=1
# Assign linked list
for eIdx in range(len(self.edges)-1):
self.edges[eIdx].next = self.edges[eIdx+1]
self.edges[eIdx].prev = self.edges[eIdx-1]
# Now last
self.edges[-1].next = self.edges[0]
self.edges[-1].prev = self.edges[-2]
# GENERATE AN AVERAGE NORMAL FOR THE WHOLE LOOP.
self.normal = Vector()
for e in self.edges:
n = (self.centre-e.co1).cross(self.centre-e.co2)
# Do we realy need tot normalize?
n.normalize()
self.normal += n
# Generate the angle
va= e.cent - e.prev.cent
vb= e.next.cent - e.cent
e.angle= AngleBetweenVecs(va, vb)
# Blur the angles
#for e in self.edges:
# e.angle= (e.angle+e.next.angle)/2
# Blur the angles
#for e in self.edges:
# e.angle= (e.angle+e.prev.angle)/2
self.normal.normalize()
# Generate a normal for each edge.
for e in self.edges:
n1 = e.co1
n2 = e.co2
n3 = e.prev.co1
a = n1-n2
b = n1-n3
normal1 = a.cross(b)
normal1.normalize()
n1 = e.co2
n3 = e.next.co2
n2 = e.co1
a = n1-n2
b = n1-n3
normal2 = a.cross(b)
normal2.normalize()
# Reuse normal1 var
normal1 += normal1 + normal2
normal1.normalize()
e.normal = normal1
#print e.normal
def backup(self):
# Keep a backup of the edges
self.backup_edges = self.edges[:]
def restore(self):
self.edges = self.backup_edges[:]
for e in self.edges:
e.removed = 0
def reverse(self):
self.edges.reverse()
self.normal.negate()
for e in self.edges:
e.normal.negate()
e.v1, e.v2 = e.v2, e.v1
e.co1, e.co2 = e.co2, e.co1
e.next, e.prev = e.prev, e.next
def removeSmallest(self, cullNum, otherLoopLen):
'''
Removes N Smallest edges and backs up the loop,
this is so we can loop between 2 loops as if they are the same length,
backing up and restoring incase the loop needs to be skinned with another loop of a different length.
'''
global CULL_METHOD
if CULL_METHOD == 1: # Shortest edge
eloopCopy = self.edges[:]
# Length sort, smallest first
try: eloopCopy.sort(key = lambda e1: e1.length)
except: eloopCopy.sort(lambda e1, e2: cmp(e1.length, e2.length ))
# Dont use atm
#eloopCopy.sort(lambda e1, e2: cmp(e1.angle*e1.length, e2.angle*e2.length)) # Length sort, smallest first
#eloopCopy.sort(lambda e1, e2: cmp(e1.angle, e2.angle)) # Length sort, smallest first
remNum = 0
for i, e in enumerate(eloopCopy):
if not e.fake:
e.removed = 1
self.edges.remove( e ) # Remove from own list, still in linked list.
remNum += 1
if not remNum < cullNum:
break
else: # CULL METHOD is even
culled = 0
step = int(otherLoopLen / float(cullNum)) * 2
currentEdge = self.edges[0]
while culled < cullNum:
# Get the shortest face in the next STEP
step_count= 0
bestAng= 360.0
smallestEdge= None
while step_count<=step or smallestEdge==None:
step_count+=1
if not currentEdge.removed: # 0 or -1 will not be accepted
if currentEdge.angle<bestAng and not currentEdge.fake:
smallestEdge= currentEdge
bestAng= currentEdge.angle
currentEdge = currentEdge.next
# In that stepping length we have the smallest edge.remove it
smallestEdge.removed = 1
self.edges.remove(smallestEdge)
# Start scanning from the edge we found? - result is over fanning- no good.
#currentEdge= smallestEdge.next
culled+=1
# Returns face edges.
# face must have edge data.
def mesh_faces_extend(me, faces, mat_idx = 0):
orig_facetot = len(me.faces)
new_facetot = len(faces)
me.add_geometry(0, 0, new_facetot)
tot = orig_facetot+new_facetot
me_faces = me.faces
i= 0
while i < new_facetot:
f = [v.index for v in faces[i]]
if len(f)==4:
if f[3]==0:
f = f[1], f[2], f[3], f[0]
else:
f = f[0], f[1], f[2], 0
mf = me_faces[orig_facetot+i]
mf.verts_raw = f
mf.material_index = mat_idx
i+=1
def mesh_faces_extend(me, faces, mat_idx = 0):
orig_facetot = len(me.faces)
new_facetot = len(faces)
me.add_geometry(0, 0, new_facetot)
tot = orig_facetot+new_facetot
me_faces = me.faces
i= 0
while i < new_facetot:
f = [v.index for v in faces[i]]
if len(f)==4:
if f[3]==0:
f = f[1], f[2], f[3], f[0]
else:
f = f[0], f[1], f[2], 0
mf = me_faces[orig_facetot+i]
mf.verts_raw = f
mf.material_index = mat_idx
i+=1
# end utils
def getSelectedEdges(context, me, ob):
MESH_MODE= context.scene.tool_settings.mesh_selection_mode
if MESH_MODE in ('EDGE', 'VERTEX'):
context.scene.tool_settings.mesh_selection_mode = 'EDGE'
edges= [ ed for ed in me.edges if ed.selected ]
# print len(edges), len(me.edges)
context.scene.tool_settings.mesh_selection_mode = MESH_MODE
return edges
if MESH_MODE == 'FACE':
context.scene.tool_settings.mesh_selection_mode = 'EDGE'
# value is [edge, face_sel_user_in]
edge_dict= dict((ed.key, [ed, 0]) for ed in me.edges)
for f in me.faces:
if f.selected:
for edkey in f.edge_keys:
edge_dict[edkey][1] += 1
context.scene.tool_settings.mesh_selection_mode = MESH_MODE
return [ ed_data[0] for ed_data in edge_dict.values() if ed_data[1] == 1 ]
MESH_MODE= context.scene.tool_settings.mesh_selection_mode
if MESH_MODE in ('EDGE', 'VERTEX'):
context.scene.tool_settings.mesh_selection_mode = 'EDGE'
edges= [ ed for ed in me.edges if ed.selected ]
# print len(edges), len(me.edges)
context.scene.tool_settings.mesh_selection_mode = MESH_MODE
return edges
if MESH_MODE == 'FACE':
context.scene.tool_settings.mesh_selection_mode = 'EDGE'
# value is [edge, face_sel_user_in]
edge_dict= dict((ed.key, [ed, 0]) for ed in me.edges)
for f in me.faces:
if f.selected:
for edkey in f.edge_keys:
edge_dict[edkey][1] += 1
context.scene.tool_settings.mesh_selection_mode = MESH_MODE
return [ ed_data[0] for ed_data in edge_dict.values() if ed_data[1] == 1 ]
def getVertLoops(selEdges, me):
'''
return a list of vert loops, closed and open [(loop, closed)...]
'''
mainVertLoops = []
# second method
tot = len(me.verts)
vert_siblings = [[] for i in range(tot)]
vert_used = [False] * tot
for ed in selEdges:
i1, i2 = ed.key
vert_siblings[i1].append(i2)
vert_siblings[i2].append(i1)
# find the first used vert and keep looping.
for i in range(tot):
if vert_siblings[i] and not vert_used[i]:
sbl = vert_siblings[i] # siblings
if len(sbl) > 2:
return None
vert_used[i] = True
# do an edgeloop seek
if len(sbl) == 2:
contextVertLoop= [sbl[0], i, sbl[1]] # start the vert loop
vert_used[contextVertLoop[ 0]] = True
vert_used[contextVertLoop[-1]] = True
else:
contextVertLoop= [i, sbl[0]]
vert_used[contextVertLoop[ 1]] = True
# Always seek up
ok = True
while ok:
ok = False
closed = False
sbl = vert_siblings[contextVertLoop[-1]]
if len(sbl) == 2:
next = sbl[not sbl.index( contextVertLoop[-2] )]
if vert_used[next]:
closed = True
# break
else:
contextVertLoop.append( next ) # get the vert that isnt the second last
vert_used[next] = True
ok = True
# Seek down as long as the starting vert was not at the edge.
if not closed and len(vert_siblings[i]) == 2:
ok = True
while ok:
ok = False
sbl = vert_siblings[contextVertLoop[0]]
if len(sbl) == 2:
next = sbl[not sbl.index( contextVertLoop[1] )]
if vert_used[next]:
closed = True
else:
contextVertLoop.insert(0, next) # get the vert that isnt the second last
vert_used[next] = True
ok = True
mainVertLoops.append((contextVertLoop, closed))
verts = me.verts
# convert from indicies to verts
# mainVertLoops = [([verts[i] for i in contextVertLoop], closed) for contextVertLoop, closed in mainVertLoops]
# print len(mainVertLoops)
return mainVertLoops
'''
return a list of vert loops, closed and open [(loop, closed)...]
'''
mainVertLoops = []
# second method
tot = len(me.verts)
vert_siblings = [[] for i in range(tot)]
vert_used = [False] * tot
for ed in selEdges:
i1, i2 = ed.key
vert_siblings[i1].append(i2)
vert_siblings[i2].append(i1)
# find the first used vert and keep looping.
for i in range(tot):
if vert_siblings[i] and not vert_used[i]:
sbl = vert_siblings[i] # siblings
if len(sbl) > 2:
return None
vert_used[i] = True
# do an edgeloop seek
if len(sbl) == 2:
contextVertLoop= [sbl[0], i, sbl[1]] # start the vert loop
vert_used[contextVertLoop[ 0]] = True
vert_used[contextVertLoop[-1]] = True
else:
contextVertLoop= [i, sbl[0]]
vert_used[contextVertLoop[ 1]] = True
# Always seek up
ok = True
while ok:
ok = False
closed = False
sbl = vert_siblings[contextVertLoop[-1]]
if len(sbl) == 2:
next = sbl[not sbl.index( contextVertLoop[-2] )]
if vert_used[next]:
closed = True
# break
else:
contextVertLoop.append( next ) # get the vert that isnt the second last
vert_used[next] = True
ok = True
# Seek down as long as the starting vert was not at the edge.
if not closed and len(vert_siblings[i]) == 2:
ok = True
while ok:
ok = False
sbl = vert_siblings[contextVertLoop[0]]
if len(sbl) == 2:
next = sbl[not sbl.index( contextVertLoop[1] )]
if vert_used[next]:
closed = True
else:
contextVertLoop.insert(0, next) # get the vert that isnt the second last
vert_used[next] = True
ok = True
mainVertLoops.append((contextVertLoop, closed))
verts = me.verts
# convert from indicies to verts
# mainVertLoops = [([verts[i] for i in contextVertLoop], closed) for contextVertLoop, closed in mainVertLoops]
# print len(mainVertLoops)
return mainVertLoops
def skin2EdgeLoops(eloop1, eloop2, me, ob, MODE):
new_faces= [] #
# Make sure e1 loops is bigger then e2
if len(eloop1.edges) != len(eloop2.edges):
if len(eloop1.edges) < len(eloop2.edges):
eloop1, eloop2 = eloop2, eloop1
eloop1.backup() # were about to cull faces
CULL_FACES = len(eloop1.edges) - len(eloop2.edges)
eloop1.removeSmallest(CULL_FACES, len(eloop1.edges))
else:
CULL_FACES = 0
# First make sure poly vert loops are in sync with eachother.
# The vector allong which we are skinning.
skinVector = eloop1.centre - eloop2.centre
loopDist = skinVector.length
# IS THE LOOP FLIPPED, IF SO FLIP BACK. we keep it flipped, its ok,
if eloop1.closed or eloop2.closed:
angleBetweenLoopNormals = AngleBetweenVecs(eloop1.normal, eloop2.normal)
if angleBetweenLoopNormals > 90:
eloop2.reverse()
DIR= eloop1.centre - eloop2.centre
# if eloop2.closed:
bestEloopDist = BIG_NUM
bestOffset = 0
# Loop rotation offset to test.1
eLoopIdxs = list(range(len(eloop1.edges)))
for offset in range(len(eloop1.edges)):
totEloopDist = 0 # Measure this total distance for thsi loop.
offsetIndexLs = eLoopIdxs[offset:] + eLoopIdxs[:offset] # Make offset index list
# e1Idx is always from 0uu to N, e2Idx is offset.
for e1Idx, e2Idx in enumerate(offsetIndexLs):
e1= eloop1.edges[e1Idx]
e2= eloop2.edges[e2Idx]
# Include fan connections in the measurement.
OK= True
while OK or e1.removed:
OK= False
# Measure the vloop distance ===============
diff= ((e1.cent - e2.cent).length) #/ nangle1
ed_dir= e1.cent-e2.cent
a_diff= AngleBetweenVecs(DIR, ed_dir)/18 # 0 t0 18
totEloopDist += (diff * (1+a_diff)) / (1+loopDist)
# Premeture break if where no better off
if totEloopDist > bestEloopDist:
break
e1=e1.next
if totEloopDist < bestEloopDist:
bestOffset = offset
bestEloopDist = totEloopDist
# Modify V2 LS for Best offset
eloop2.edges = eloop2.edges[bestOffset:] + eloop2.edges[:bestOffset]
else:
# Both are open loops, easier to calculate.
# Make sure the fake edges are at the start.
for i, edloop in enumerate((eloop1, eloop2)):
# print "LOOPO"
if edloop.edges[0].fake:
# alredy at the start
#print "A"
pass
elif edloop.edges[-1].fake:
# put the end at the start
edloop.edges.insert(0, edloop.edges.pop())
#print "B"
else:
for j, ed in enumerate(edloop.edges):
if ed.fake:
#print "C"
edloop.edges = edloop.edges = edloop.edges[j:] + edloop.edges[:j]
break
# print "DONE"
ed1, ed2 = eloop1.edges[0], eloop2.edges[0]
if not ed1.fake or not ed2.fake:
raise "Error"
# Find the join that isnt flipped (juts like detecting a bow-tie face)
a1 = (ed1.co1 - ed2.co1).length + (ed1.co2 - ed2.co2).length
a2 = (ed1.co1 - ed2.co2).length + (ed1.co2 - ed2.co1).length
if a1 > a2:
eloop2.reverse()
# make the first edge the start edge still
eloop2.edges.insert(0, eloop2.edges.pop())
for loopIdx in range(len(eloop2.edges)):
e1 = eloop1.edges[loopIdx]
e2 = eloop2.edges[loopIdx]
# Remember the pairs for fan filling culled edges.
e1.match = e2; e2.match = e1
if not (e1.fake or e2.fake):
new_faces.append([e1.v1, e1.v2, e2.v2, e2.v1])
# FAN FILL MISSING FACES.
if CULL_FACES:
# Culled edges will be in eloop1.
FAN_FILLED_FACES = 0
contextEdge = eloop1.edges[0] # The larger of teh 2
while FAN_FILLED_FACES < CULL_FACES:
while contextEdge.next.removed == 0:
contextEdge = contextEdge.next
vertFanPivot = contextEdge.match.v2
while contextEdge.next.removed == 1:
#if not contextEdge.next.fake:
new_faces.append([contextEdge.next.v1, contextEdge.next.v2, vertFanPivot])
# Should we use another var?, this will work for now.
contextEdge.next.removed = 1
contextEdge = contextEdge.next
FAN_FILLED_FACES += 1
# may need to fan fill backwards 1 for non closed loops.
eloop1.restore() # Add culled back into the list.
return new_faces
new_faces= [] #
# Make sure e1 loops is bigger then e2
if len(eloop1.edges) != len(eloop2.edges):
if len(eloop1.edges) < len(eloop2.edges):
eloop1, eloop2 = eloop2, eloop1
eloop1.backup() # were about to cull faces
CULL_FACES = len(eloop1.edges) - len(eloop2.edges)
eloop1.removeSmallest(CULL_FACES, len(eloop1.edges))
else:
CULL_FACES = 0
# First make sure poly vert loops are in sync with eachother.
# The vector allong which we are skinning.
skinVector = eloop1.centre - eloop2.centre
loopDist = skinVector.length
# IS THE LOOP FLIPPED, IF SO FLIP BACK. we keep it flipped, its ok,
if eloop1.closed or eloop2.closed:
angleBetweenLoopNormals = AngleBetweenVecs(eloop1.normal, eloop2.normal)
if angleBetweenLoopNormals > 90:
eloop2.reverse()
DIR= eloop1.centre - eloop2.centre
# if eloop2.closed:
bestEloopDist = BIG_NUM
bestOffset = 0
# Loop rotation offset to test.1
eLoopIdxs = list(range(len(eloop1.edges)))
for offset in range(len(eloop1.edges)):
totEloopDist = 0 # Measure this total distance for thsi loop.
offsetIndexLs = eLoopIdxs[offset:] + eLoopIdxs[:offset] # Make offset index list
# e1Idx is always from 0uu to N, e2Idx is offset.
for e1Idx, e2Idx in enumerate(offsetIndexLs):
e1= eloop1.edges[e1Idx]
e2= eloop2.edges[e2Idx]
# Include fan connections in the measurement.
OK= True
while OK or e1.removed:
OK= False
# Measure the vloop distance ===============
diff= ((e1.cent - e2.cent).length) #/ nangle1
ed_dir= e1.cent-e2.cent
a_diff= AngleBetweenVecs(DIR, ed_dir)/18 # 0 t0 18
totEloopDist += (diff * (1+a_diff)) / (1+loopDist)
# Premeture break if where no better off
if totEloopDist > bestEloopDist:
break
e1=e1.next
if totEloopDist < bestEloopDist:
bestOffset = offset
bestEloopDist = totEloopDist
# Modify V2 LS for Best offset
eloop2.edges = eloop2.edges[bestOffset:] + eloop2.edges[:bestOffset]
else:
# Both are open loops, easier to calculate.
# Make sure the fake edges are at the start.
for i, edloop in enumerate((eloop1, eloop2)):
# print "LOOPO"
if edloop.edges[0].fake:
# alredy at the start
#print "A"
pass
elif edloop.edges[-1].fake:
# put the end at the start
edloop.edges.insert(0, edloop.edges.pop())
#print "B"
else:
for j, ed in enumerate(edloop.edges):
if ed.fake:
#print "C"
edloop.edges = edloop.edges = edloop.edges[j:] + edloop.edges[:j]
break
# print "DONE"
ed1, ed2 = eloop1.edges[0], eloop2.edges[0]
if not ed1.fake or not ed2.fake:
raise "Error"
# Find the join that isnt flipped (juts like detecting a bow-tie face)
a1 = (ed1.co1 - ed2.co1).length + (ed1.co2 - ed2.co2).length
a2 = (ed1.co1 - ed2.co2).length + (ed1.co2 - ed2.co1).length
if a1 > a2:
eloop2.reverse()
# make the first edge the start edge still
eloop2.edges.insert(0, eloop2.edges.pop())
for loopIdx in range(len(eloop2.edges)):
e1 = eloop1.edges[loopIdx]
e2 = eloop2.edges[loopIdx]
# Remember the pairs for fan filling culled edges.
e1.match = e2; e2.match = e1
if not (e1.fake or e2.fake):
new_faces.append([e1.v1, e1.v2, e2.v2, e2.v1])
# FAN FILL MISSING FACES.
if CULL_FACES:
# Culled edges will be in eloop1.
FAN_FILLED_FACES = 0
contextEdge = eloop1.edges[0] # The larger of teh 2
while FAN_FILLED_FACES < CULL_FACES:
while contextEdge.next.removed == 0:
contextEdge = contextEdge.next
vertFanPivot = contextEdge.match.v2
while contextEdge.next.removed == 1:
#if not contextEdge.next.fake:
new_faces.append([contextEdge.next.v1, contextEdge.next.v2, vertFanPivot])
# Should we use another var?, this will work for now.
contextEdge.next.removed = 1
contextEdge = contextEdge.next
FAN_FILLED_FACES += 1
# may need to fan fill backwards 1 for non closed loops.
eloop1.restore() # Add culled back into the list.
return new_faces
def main(context):
global CULL_METHOD
ob = context.object
is_editmode = (ob.mode=='EDIT')
if is_editmode: bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
if ob == None or ob.type != 'MESH':
raise Exception("BPyMessages.Error_NoMeshActive()")
return
me = ob.data
time1 = time.time()
selEdges = getSelectedEdges(context, me, ob)
vertLoops = getVertLoops(selEdges, me) # list of lists of edges.
if vertLoops == None:
raise Exception('Error%t|Selection includes verts that are a part of more then 1 loop')
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
return
# print len(vertLoops)
if len(vertLoops) > 2:
choice = PupMenu('Loft '+str(len(vertLoops))+' edge loops%t|loop|segment')
if choice == -1:
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
return
elif len(vertLoops) < 2:
raise Exception('Error%t|No Vertloops found!')
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
return
else:
choice = 2
# The line below checks if any of the vert loops are differenyt in length.
if False in [len(v[0]) == len(vertLoops[0][0]) for v in vertLoops]:
global CULL_METHOD
ob = context.object
is_editmode = (ob.mode=='EDIT')
if is_editmode: bpy.ops.object.mode_set(mode='OBJECT', toggle=False)
if ob == None or ob.type != 'MESH':
raise Exception("BPyMessages.Error_NoMeshActive()")
return
me = ob.data
time1 = time.time()
selEdges = getSelectedEdges(context, me, ob)
vertLoops = getVertLoops(selEdges, me) # list of lists of edges.
if vertLoops == None:
raise Exception('Error%t|Selection includes verts that are a part of more then 1 loop')
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
return
# print len(vertLoops)
if len(vertLoops) > 2:
choice = PupMenu('Loft '+str(len(vertLoops))+' edge loops%t|loop|segment')
if choice == -1:
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
return
elif len(vertLoops) < 2:
raise Exception('Error%t|No Vertloops found!')
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
return
else:
choice = 2
# The line below checks if any of the vert loops are differenyt in length.
if False in [len(v[0]) == len(vertLoops[0][0]) for v in vertLoops]:
#XXX CULL_METHOD = PupMenu('Small to large edge loop distrobution method%t|remove edges evenly|remove smallest edges')
#XXX if CULL_METHOD == -1:
#XXX if is_editmode: Window.EditMode(1)
#XXX return
CULL_METHOD = 1 # XXX FIXME
if CULL_METHOD ==1: # RESET CULL_METHOD
CULL_METHOD = 0 # shortest
else:
CULL_METHOD = 1 # even
time1 = time.time()
# Convert to special edge data.
edgeLoops = []
for vloop, closed in vertLoops:
edgeLoops.append(edgeLoop(vloop, me, closed))
# VERT LOOP ORDERING CODE
# "Build a worm" list - grow from Both ends
edgeOrderedList = [edgeLoops.pop()]
# Find the closest.
bestSoFar = BIG_NUM
bestIdxSoFar = None
for edLoopIdx, edLoop in enumerate(edgeLoops):
l =(edgeOrderedList[-1].centre - edLoop.centre).length
if l < bestSoFar:
bestIdxSoFar = edLoopIdx
bestSoFar = l
edgeOrderedList.append( edgeLoops.pop(bestIdxSoFar) )
# Now we have the 2 closest, append to either end-
# Find the closest.
while edgeLoops:
bestSoFar = BIG_NUM
bestIdxSoFar = None
first_or_last = 0 # Zero is first
for edLoopIdx, edLoop in enumerate(edgeLoops):
l1 =(edgeOrderedList[-1].centre - edLoop.centre).length
if l1 < bestSoFar:
bestIdxSoFar = edLoopIdx
bestSoFar = l1
first_or_last = 1 # last
l2 =(edgeOrderedList[0].centre - edLoop.centre).length
if l2 < bestSoFar:
bestIdxSoFar = edLoopIdx
bestSoFar = l2
first_or_last = 0 # last
if first_or_last: # add closest Last
edgeOrderedList.append( edgeLoops.pop(bestIdxSoFar) )
else: # Add closest First
edgeOrderedList.insert(0, edgeLoops.pop(bestIdxSoFar) ) # First
faces = []
for i in range(len(edgeOrderedList)-1):
faces.extend( skin2EdgeLoops(edgeOrderedList[i], edgeOrderedList[i+1], me, ob, 0) )
if choice == 1 and len(edgeOrderedList) > 2: # Loop
faces.extend( skin2EdgeLoops(edgeOrderedList[0], edgeOrderedList[-1], me, ob, 0) )
# REMOVE SELECTED FACES.
MESH_MODE= ob.mode
if MESH_MODE == 'EDGE' or MESH_MODE == 'VERTEX': pass
elif MESH_MODE == 'FACE':
try: me.faces.delete(1, [ f for f in me.faces if f.sel ])
except: pass
if 1: # 2.5
mesh_faces_extend(me, faces, ob.active_material_index)
me.update(calc_edges=True)
else:
me.faces.extend(faces, smooth = True)
print('\nSkin done in %.4f sec.' % (time.time()-time1))
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
CULL_METHOD = 1 # XXX FIXME
if CULL_METHOD ==1: # RESET CULL_METHOD
CULL_METHOD = 0 # shortest
else:
CULL_METHOD = 1 # even
time1 = time.time()
# Convert to special edge data.
edgeLoops = []
for vloop, closed in vertLoops:
edgeLoops.append(edgeLoop(vloop, me, closed))
# VERT LOOP ORDERING CODE
# "Build a worm" list - grow from Both ends
edgeOrderedList = [edgeLoops.pop()]
# Find the closest.
bestSoFar = BIG_NUM
bestIdxSoFar = None
for edLoopIdx, edLoop in enumerate(edgeLoops):
l =(edgeOrderedList[-1].centre - edLoop.centre).length
if l < bestSoFar:
bestIdxSoFar = edLoopIdx
bestSoFar = l
edgeOrderedList.append( edgeLoops.pop(bestIdxSoFar) )
# Now we have the 2 closest, append to either end-
# Find the closest.
while edgeLoops:
bestSoFar = BIG_NUM
bestIdxSoFar = None
first_or_last = 0 # Zero is first
for edLoopIdx, edLoop in enumerate(edgeLoops):
l1 =(edgeOrderedList[-1].centre - edLoop.centre).length
if l1 < bestSoFar:
bestIdxSoFar = edLoopIdx
bestSoFar = l1
first_or_last = 1 # last
l2 =(edgeOrderedList[0].centre - edLoop.centre).length
if l2 < bestSoFar:
bestIdxSoFar = edLoopIdx
bestSoFar = l2
first_or_last = 0 # last
if first_or_last: # add closest Last
edgeOrderedList.append( edgeLoops.pop(bestIdxSoFar) )
else: # Add closest First
edgeOrderedList.insert(0, edgeLoops.pop(bestIdxSoFar) ) # First
faces = []
for i in range(len(edgeOrderedList)-1):
faces.extend( skin2EdgeLoops(edgeOrderedList[i], edgeOrderedList[i+1], me, ob, 0) )
if choice == 1 and len(edgeOrderedList) > 2: # Loop
faces.extend( skin2EdgeLoops(edgeOrderedList[0], edgeOrderedList[-1], me, ob, 0) )
# REMOVE SELECTED FACES.
MESH_MODE= ob.mode
if MESH_MODE == 'EDGE' or MESH_MODE == 'VERTEX': pass
elif MESH_MODE == 'FACE':
try: me.faces.delete(1, [ f for f in me.faces if f.sel ])
except: pass
if 1: # 2.5
mesh_faces_extend(me, faces, ob.active_material_index)
me.update(calc_edges=True)
else:
me.faces.extend(faces, smooth = True)
print('\nSkin done in %.4f sec.' % (time.time()-time1))
if is_editmode: bpy.ops.object.mode_set(mode='EDIT', toggle=False)
class MESH_OT_skin(bpy.types.Operator):
'''Bridge face loops.'''
bl_idname = "mesh.skin"
bl_label = "Add Torus"
bl_register = True
bl_undo = True
'''
loft_method = EnumProperty(attr="loft_method", items=[(), ()], description="", default= True)
'''
def execute(self, context):
main(context)
return ('FINISHED',)
'''Bridge face loops.'''
bl_idname = "mesh.skin"
bl_label = "Add Torus"
bl_register = True
bl_undo = True
'''
loft_method = EnumProperty(attr="loft_method", items=[(), ()], description="", default= True)
'''
def execute(self, context):
main(context)
return ('FINISHED',)
# Register the operator
@@ -653,4 +653,4 @@ import dynamic_menu
menu_item = dynamic_menu.add(bpy.types.VIEW3D_MT_edit_mesh_faces, (lambda self, context: self.layout.operator("mesh.skin", text="Bridge Faces")) )
if __name__ == "__main__":
bpy.ops.mesh.skin()
bpy.ops.mesh.skin()

View File

@@ -0,0 +1,124 @@
# ##### 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>
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_register = True
bl_undo = True
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.properties.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.properties.pattern):
item.selected = True
elif not self.properties.extend:
item.selected = False
return ('FINISHED',)
def invoke(self, context, event):
wm = context.manager
# return wm.invoke_props_popup(self, event)
wm.invoke_props_popup(self, event)
return ('RUNNING_MODAL',)
def draw(self, context):
layout = self.layout
props = self.properties
layout.prop(props, "pattern")
row = layout.row()
row.prop(props, "case_sensitive")
row.prop(props, "extend")
class SubsurfSet(bpy.types.Operator):
'''Sets a Subdivision Surface Level (1-5)'''
bl_idname = "object.subsurf_set"
bl_label = "Subsurf Set"
bl_register = True
bl_undo = True
level = IntProperty(name="Level",
default=1, min=0, max=6)
def poll(self, context):
ob = context.active_object
return (ob and ob.type == 'MESH')
def execute(self, context):
level = self.properties.level
ob = context.active_object
for mod in ob.modifiers:
if mod.type == 'SUBSURF':
if mod.levels != level:
mod.levels = level
return ('FINISHED',)
# adda new modifier
mod = ob.modifiers.new("Subsurf", 'SUBSURF')
mod.levels = level
return ('FINISHED',)
class Retopo(bpy.types.Operator):
'''TODO - doc'''
bl_idname = "object.retopology"
bl_label = "Retopology from Grease Pencil"
bl_register = True
bl_undo = True
def execute(self, context):
import retopo
retopo.main()
return ('FINISHED',)
bpy.ops.add(SelectPattern)
bpy.ops.add(SubsurfSet)
bpy.ops.add(Retopo)

View File

@@ -4,12 +4,12 @@
# 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.
@@ -21,28 +21,14 @@ import os
class AddPresetBase(bpy.types.Operator):
'''Base preset class, only for subclassing
subclasses must define
subclasses must define
- preset_values
- preset_path '''
- preset_subdir '''
bl_idname = "render.preset_add"
bl_label = "Add Render Preset"
name = bpy.props.StringProperty(name="Name", description="Name of the preset, used to make the path name", maxlen= 64, default= "")
'''
preset_values = [
"bpy.context.scene.render_data.resolution_x",
"bpy.context.scene.render_data.resolution_y",
"bpy.context.scene.render_data.pixel_aspect_x",
"bpy.context.scene.render_data.pixel_aspect_y",
"bpy.context.scene.render_data.fps",
"bpy.context.scene.render_data.fps_base",
"bpy.context.scene.render_data.resolution_percentage",
]
preset_subdir = "render"
'''
def _as_filename(self, name): # could reuse for other presets
for char in " !@#$%^&*(){}:\";'[]<>,./?":
name = name.replace('.', '_')
@@ -58,7 +44,7 @@ class AddPresetBase(bpy.types.Operator):
target_path = bpy.utils.preset_paths(self.preset_subdir)[0] # we need some way to tell the user and system preset path
file_preset = open(os.path.join(target_path, filename), 'w')
for rna_path in self.preset_values:
file_preset.write("%s = %s\n" % (rna_path, eval(rna_path)))
@@ -68,6 +54,9 @@ class AddPresetBase(bpy.types.Operator):
def invoke(self, context, event):
wm = context.manager
#crashes, TODO - fix
#return wm.invoke_props_popup(self, event)
wm.invoke_props_popup(self, event)
return ('RUNNING_MODAL',)
@@ -114,13 +103,13 @@ class AddPresetSSS(AddPresetBase):
]
preset_subdir = "sss"
class AddPresetCloth(AddPresetBase):
'''Add a Cloth Preset.'''
bl_idname = "cloth.preset_add"
bl_label = "Add Cloth Preset"
name = AddPresetBase.name
preset_values = [
"bpy.context.cloth.settings.quality",
"bpy.context.cloth.settings.mass",
@@ -129,11 +118,10 @@ class AddPresetCloth(AddPresetBase):
"bpy.context.cloth.settings.spring_damping",
"bpy.context.cloth.settings.air_damping",
]
preset_subdir = "cloth"
bpy.ops.add(AddPresetRender)
bpy.ops.add(AddPresetSSS)
bpy.ops.add(AddPresetCloth)

View File

@@ -1,24 +1,24 @@
# --------------------------------------------------------------------------
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------
# --------------------------------------------------------------------------
# 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
#
# ***** END GPL LICENCE BLOCK *****
# --------------------------------------------------------------------------
#from Blender import Object, Draw, Window, sys, Mesh, Geometry
@@ -40,96 +40,96 @@ 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)
l1 = [side1[0], side1[1], side1[2]]
l2 = [side2[0], side2[1], side2[2]]
l3 = [nor[0], nor[1], nor[2]]
mtx = Matrix(l1, l2, l3)
# 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
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)
l1 = [side1[0], side1[1], side1[2]]
l2 = [side2[0], side2[1], side2[2]]
l3 = [nor[0], nor[1], nor[2]]
mtx = Matrix(l1, l2, l3)
# 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
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
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)
@@ -137,44 +137,44 @@ def boundsEdgeLoop(edges):
# 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.__copy__().resize3D() for v in unique_points.values()]
# 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.__copy__().resize3D() for v in unique_points.values()]
# ========================= NOT WORKING????
# Find if a points inside an edge loop, un-orderd.
# pt is and x/y
@@ -182,97 +182,97 @@ def island2Edge(island):
# #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
#
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 = Vector(); vec2 = Vector(); vec3 = 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]
vec1 = Vector(); vec2 = Vector(); vec3 = 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
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.LineIntersect2D(\
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.resize3D()
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.
# Is 1 point in the box, inside the vertLoops
edgeLoopsSource = source[6] # Pretend this is offset
edgeLoopsTarget = target[6]
return 0 # NO INTERSECTION
# Edge intersect test
for ed in edgeLoopsSource:
for seg in edgeLoopsTarget:
i = Geometry.LineIntersect2D(\
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.resize3D()
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
# 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 = RotationMatrix( radians(90.0), 2)
@@ -281,855 +281,855 @@ ROTMAT_2D_POS_45D = RotationMatrix( radians(45.0), 2)
RotMatStepRotation = []
rot_angle = 22.5 #45.0/2
while rot_angle > 0.1:
RotMatStepRotation.append([\
RotationMatrix( radians(rot_angle), 2),\
RotationMatrix( radians(-rot_angle), 2)])
rot_angle = rot_angle/2.0
RotMatStepRotation.append([\
RotationMatrix( radians(rot_angle), 2),\
RotationMatrix( 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)
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]))
global USER_FILL_HOLES
global USER_FILL_HOLES_QUALITY
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)))
# Pack islands to bottom LHS
# Sync with island
# 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 alredy 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
#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 alredy 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)
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.
# 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)
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.seam:
edge_seams[ed.key] = None # dummy var- use sets!
# Done finding seams
islandList = []
# Get seams so we dont cross over seams
edge_seams = {} # shoudl be a set
for ed in me.edges:
if ed.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
#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
for island in islandList:
optiRotateUvIsland(island)
return islandList
def packIslands(islandList):
if USER_FILL_HOLES:
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...'
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.BoxPack2D(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'
time1 = time.time()
packWidth, packHeight = Geometry.BoxPack2D(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
# 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 VectoMat(vec):
a3 = vec.__copy__().normalize()
up = Vector(0,0,1)
if abs(a3.dot(up)) == 1.0:
up = Vector(0,1,0)
a1 = a3.cross(up).normalize()
a2 = a3.cross(a1)
return Matrix([a1[0], a1[1], a1[2]], [a2[0], a2[1], a2[2]], [a3[0], a3[1], a3[2]])
a3 = vec.__copy__().normalize()
up = Vector(0,0,1)
if abs(a3.dot(up)) == 1.0:
up = Vector(0,1,0)
a1 = a3.cross(up).normalize()
a2 = a3.cross(a1)
return Matrix([a1[0], a1[1], a1[2]], [a2[0], a2[1], a2[2]], [a3[0], a3[1], a3[2]])
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.verts]
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
__slost__= 'v', 'uv', 'no', 'area', 'edge_keys'
def __init__(self, face, uvface, mesh_verts):
self.v = [mesh_verts[i] for i in face.verts]
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]
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 ob.selected == 0 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)
'''
if ob and ob.selected == 0 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 len(me.uv_textures)==0: # Mesh has no UV Coords, dont bother.
me.add_uv_texture()
uv_layer = me.active_uv_texture.data
me_verts = list(me.verts)
if USER_ONLY_SELECTED_FACES:
meshFaces = [thickface(f, uv_layer[i], me_verts) for i, f in enumerate(me.faces) if f.selected]
#else:
# meshFaces = map(thickface, me.faces)
if not meshFaces:
continue
# 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 len(me.uv_textures)==0: # Mesh has no UV Coords, dont bother.
me.add_uv_texture()
uv_layer = me.active_uv_texture.data
me_verts = list(me.verts)
if USER_ONLY_SELECTED_FACES:
meshFaces = [thickface(f, uv_layer[i], me_verts) for i, f in enumerate(me.faces) if f.selected]
#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.matrixWorld.copy().invert().rotationPart()] # 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)
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.normalize())
# 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 alredy 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.
MatProj = VectoMat(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 * MatProj)[: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:
# =======
# 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.matrixWorld.copy().invert().rotationPart()] # 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)
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.normalize())
# 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 alredy 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.
MatProj = VectoMat(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 * MatProj)[: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')
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',\
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.'),\
('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)'),\
]
'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"
'''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_register = True
bl_undo = True
bl_register = True
bl_undo = True
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)
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)
island_margin = FloatProperty(name="Island Margin",
description="Margin to reduce bleed from adjacent islands.",
default=0.0, min=0.0, max=1.0)
def poll(self, context):
return context.active_object != None
def poll(self, context):
return context.active_object != None
def execute(self, context):
main(context, self.properties.island_margin, self.properties.angle_limit)
return ('FINISHED',)
def execute(self, context):
main(context, self.properties.island_margin, self.properties.angle_limit)
return ('FINISHED',)
bpy.ops.add(SmartProject)
@@ -1137,10 +1137,10 @@ bpy.ops.add(SmartProject)
import dynamic_menu
menu_func = (lambda self, context: self.layout.operator(SmartProject.bl_idname,
text="Smart Project"))
text="Smart Project"))
menu_item = dynamic_menu.add(bpy.types.VIEW3D_MT_uv_map, menu_func)
if __name__ == '__main__':
bpy.ops.uv.smart_project()
bpy.ops.uv.smart_project()

View File

@@ -33,6 +33,7 @@ class MESH_OT_delete_edgeloop(bpy.types.Operator):
bpy.ops.tfm.edge_slide(value=1.0)
bpy.ops.mesh.select_more()
bpy.ops.mesh.remove_doubles()
return ('FINISHED',)
rna_path_prop = StringProperty(name="Context Attributes",
@@ -358,8 +359,7 @@ class WM_OT_doc_edit(bpy.types.Operator):
def invoke(self, context, event):
wm = context.manager
wm.invoke_props_popup(self, event)
return ('RUNNING_MODAL',)
return wm.invoke_props_popup(self, event)
class WM_OT_reload_scripts(bpy.types.Operator):

View File

@@ -41,8 +41,7 @@ class ExportSomeData(bpy.types.Operator):
return ('RUNNING_MODAL',)
elif 0:
# Redo popup
wm.invoke_props_popup(self, event) #
return ('RUNNING_MODAL',)
return wm.invoke_props_popup(self, event) #
elif 0:
return self.execute(context)

View File

@@ -189,6 +189,9 @@ class BONE_PT_relations(BoneButtonsPanel):
sub.prop(bone, "connected")
sub.prop(bone, "hinge", text="Inherit Rotation")
sub.prop(bone, "inherit_scale", text="Inherit Scale")
sub = col.column()
sub.active = (not bone.parent or not bone.connected)
sub.prop(bone, "local_location", text="Local Location")
class BONE_PT_display(BoneButtonsPanel):

View File

@@ -128,6 +128,13 @@ class DATA_PT_shape_curve(DataButtonsPanel):
class DATA_PT_geometry_curve(DataButtonsPanel):
bl_label = "Geometry"
def poll(self, context):
obj = context.object
if obj and obj.type == 'SURFACE':
return False
return context.curve
def draw(self, context):
layout = self.layout

View File

@@ -365,11 +365,11 @@ class DATA_PT_modifiers(DataButtonsPanel):
def MESH_DEFORM(self, layout, ob, md, wide_ui):
split = layout.split()
col = split.column()
col.label(text="Object:")
col.prop(md, "object", text="")
if md.object and md.object.type == 'ARMATURE':
col.label(text="Bone:")
col.prop_object(md, "subtarget", md.object.data, "bones", text="")
sub = col.column()
sub.label(text="Object:")
sub.prop(md, "object", text="")
sub.prop(md, "mode", text="")
sub.active = not md.is_bound
if wide_ui:
col = split.column()
col.label(text="Vertex Group:")
@@ -385,14 +385,16 @@ class DATA_PT_modifiers(DataButtonsPanel):
layout.operator("object.meshdeform_bind", text="Unbind")
else:
layout.operator("object.meshdeform_bind", text="Bind")
split = layout.split()
col = split.column()
col.prop(md, "precision")
if md.mode == 'VOLUME':
split = layout.split()
if wide_ui:
col = split.column()
col.prop(md, "dynamic")
col.prop(md, "precision")
if wide_ui:
col = split.column()
col.prop(md, "dynamic")
def MIRROR(self, layout, ob, md, wide_ui):
layout.prop(md, "merge_limit")
@@ -554,7 +556,7 @@ class DATA_PT_modifiers(DataButtonsPanel):
col.label(text="Origin:")
col.prop(md, "origin", text="")
sub = col.column()
sub.active = md.origin
sub.active = (md.origin != "")
sub.prop(md, "relative")
if wide_ui:

View File

@@ -481,7 +481,7 @@ class MATERIAL_PT_sss(MaterialButtonsPanel):
mat = active_node_mat(context.material)
sss = mat.subsurface_scattering
wide_ui = context.region.width > narrowui
layout.active = (sss.enabled) and (not mat.shadeless)
row = layout.row().split()
@@ -495,7 +495,7 @@ class MATERIAL_PT_sss(MaterialButtonsPanel):
col.prop(sss, "ior")
col.prop(sss, "scale")
col.prop(sss, "color", text="")
col.prop(sss, "radius", text="RGB Radius")
col.prop(sss, "radius", text="RGB Radius", expand=True)
if wide_ui:
col = split.column()

View File

@@ -162,7 +162,7 @@ class OBJECT_PT_groups(ObjectButtonsPanel):
row = col.box().row()
row.prop(group, "name", text="")
row.operator("object.group_remove", text="", icon='VICON_X')
row.operator("object.group_remove", text="", icon='ICON_X')
split = col.box().split()

View File

@@ -21,6 +21,7 @@ import bpy
narrowui = 180
class ConstraintButtonsPanel(bpy.types.Panel):
bl_space_type = 'PROPERTIES'
bl_region_type = 'WINDOW'
@@ -77,7 +78,7 @@ class ConstraintButtonsPanel(bpy.types.Panel):
else:
layout.prop_object(con, "subtarget", con.target.data, "bones", text="")
if con.type == 'COPY_LOCATION':
if con.type in ('COPY_LOCATION', 'STRETCH_TO', 'TRACK_TO'):
row = layout.row()
row.label(text="Head/Tail:")
row.prop(con, "head_tail", text="")
@@ -112,21 +113,21 @@ class ConstraintButtonsPanel(bpy.types.Panel):
col = split.column()
col.label(text="Location:")
col.prop(con, "locationx", text="X")
col.prop(con, "locationy", text="Y")
col.prop(con, "locationz", text="Z")
col.prop(con, "use_location_x", text="X")
col.prop(con, "use_location_y", text="Y")
col.prop(con, "use_location_z", text="Z")
col = split.column()
col.label(text="Rotation:")
col.prop(con, "rotationx", text="X")
col.prop(con, "rotationy", text="Y")
col.prop(con, "rotationz", text="Z")
col.prop(con, "use_rotation_x", text="X")
col.prop(con, "use_rotation_y", text="Y")
col.prop(con, "use_rotation_z", text="Z")
col = split.column()
col.label(text="Scale:")
col.prop(con, "sizex", text="X")
col.prop(con, "sizey", text="Y")
col.prop(con, "sizez", text="Z")
col.prop(con, "use_scale_x", text="X")
col.prop(con, "use_scale_y", text="Y")
col.prop(con, "use_scale_z", text="Z")
split = layout.split()
@@ -187,16 +188,16 @@ class ConstraintButtonsPanel(bpy.types.Panel):
col.label(text="Weight:")
col.prop(con, "weight", text="Position", slider=True)
sub = col.column()
sub.active = con.rotation
sub.active = con.use_rotation
sub.prop(con, "orient_weight", text="Rotation", slider=True)
if wide_ui:
col = split.column()
col.prop(con, "tail")
col.prop(con, "stretch")
col.prop(con, "use_tail")
col.prop(con, "use_stretch")
col.separator()
col.prop(con, "targetless")
col.prop(con, "rotation")
col.prop(con, "use_target")
col.prop(con, "use_rotation")
def IK_COPY_POSE(self, context, layout, con, wide_ui):
self.target_template(layout, con, wide_ui)
@@ -217,13 +218,13 @@ class ConstraintButtonsPanel(bpy.types.Panel):
row.prop(con, "pos_lock_x", text="X")
row.prop(con, "pos_lock_y", text="Y")
row.prop(con, "pos_lock_z", text="Z")
split.active = con.position
split.active = con.use_position
split = layout.split(percentage=0.33)
split.row().prop(con, "rotation")
row = split.row()
row.prop(con, "orient_weight", text="Weight", slider=True)
row.active = con.rotation
row.active = con.use_rotation
split = layout.split(percentage=0.33)
row = split.row()
row.label(text="Lock:")
@@ -231,7 +232,7 @@ class ConstraintButtonsPanel(bpy.types.Panel):
row.prop(con, "rot_lock_x", text="X")
row.prop(con, "rot_lock_y", text="Y")
row.prop(con, "rot_lock_z", text="Z")
split.active = con.rotation
split.active = con.use_rotation
def IK_DISTANCE(self, context, layout, con, wide_ui):
self.target_template(layout, con, wide_ui)
@@ -402,24 +403,24 @@ class ConstraintButtonsPanel(bpy.types.Panel):
split = layout.split()
col = split.column()
col.prop(con, "rotate_like_x", text="X")
col.prop(con, "use_x", text="X")
sub = col.column()
sub.active = con.rotate_like_x
sub.active = con.use_x
sub.prop(con, "invert_x", text="Invert")
col = split.column()
col.prop(con, "rotate_like_y", text="Y")
col.prop(con, "use_y", text="Y")
sub = col.column()
sub.active = con.rotate_like_y
sub.active = con.use_y
sub.prop(con, "invert_y", text="Invert")
col = split.column()
col.prop(con, "rotate_like_z", text="Z")
col.prop(con, "use_z", text="Z")
sub = col.column()
sub.active = con.rotate_like_z
sub.active = con.use_z
sub.prop(con, "invert_z", text="Invert")
layout.prop(con, "offset")
layout.prop(con, "use_offset")
self.space_template(layout, con, wide_ui)
@@ -429,24 +430,24 @@ class ConstraintButtonsPanel(bpy.types.Panel):
split = layout.split()
col = split.column()
col.prop(con, "locate_like_x", text="X")
col.prop(con, "use_x", text="X")
sub = col.column()
sub.active = con.locate_like_x
sub.active = con.use_x
sub.prop(con, "invert_x", text="Invert")
col = split.column()
col.prop(con, "locate_like_y", text="Y")
col.prop(con, "use_y", text="Y")
sub = col.column()
sub.active = con.locate_like_y
sub.active = con.use_y
sub.prop(con, "invert_y", text="Invert")
col = split.column()
col.prop(con, "locate_like_z", text="Z")
col.prop(con, "use_z", text="Z")
sub = col.column()
sub.active = con.locate_like_z
sub.active = con.use_z
sub.prop(con, "invert_z", text="Invert")
layout.prop(con, "offset")
layout.prop(con, "use_offset")
self.space_template(layout, con, wide_ui)
@@ -454,11 +455,11 @@ class ConstraintButtonsPanel(bpy.types.Panel):
self.target_template(layout, con, wide_ui)
row = layout.row(align=True)
row.prop(con, "size_like_x", text="X")
row.prop(con, "size_like_y", text="Y")
row.prop(con, "size_like_z", text="Z")
row.prop(con, "use_x", text="X")
row.prop(con, "use_y", text="Y")
row.prop(con, "use_z", text="Z")
layout.prop(con, "offset")
layout.prop(con, "use_offset")
self.space_template(layout, con, wide_ui)
@@ -683,9 +684,9 @@ class ConstraintButtonsPanel(bpy.types.Panel):
if con.shrinkwrap_type == 'PROJECT':
row = layout.row(align=True)
row.prop(con, "axis_x")
row.prop(con, "axis_y")
row.prop(con, "axis_z")
row.prop(con, "use_x")
row.prop(con, "use_y")
row.prop(con, "use_z")
def DAMPED_TRACK(self, context, layout, con, wide_ui):
self.target_template(layout, con, wide_ui)

View File

@@ -208,6 +208,10 @@ class PARTICLE_PT_hair_dynamics(ParticleButtonsPanel):
layout = self.layout
psys = context.particle_system
if not psys.cloth:
return
#part = psys.settings
cloth = psys.cloth.settings
@@ -298,8 +302,8 @@ class PARTICLE_PT_velocity(ParticleButtonsPanel):
sub.prop(part, "random_factor")
#if part.type=='REACTOR':
# sub.prop(part, "reactor_factor")
# sub.prop(part, "reaction_shape", slider=True)
# sub.prop(part, "reactor_factor")
# sub.prop(part, "reaction_shape", slider=True)
class PARTICLE_PT_rotation(ParticleButtonsPanel):
@@ -454,7 +458,7 @@ class PARTICLE_PT_physics(ParticleButtonsPanel):
sub = col.row()
subsub = sub.column(align=True)
subsub.operator("particle.new_target", icon='ICON_ZOOMIN', text="")
subsub.operator("particle.remove_target", icon='ICON_ZOOMOUT', text="")
subsub.operator("particle.target_remove", icon='ICON_ZOOMOUT', text="")
sub = col.row()
subsub = sub.column(align=True)
subsub.operator("particle.target_move_up", icon='VICON_MOVE_UP', text="")

View File

@@ -28,7 +28,7 @@ from properties_physics_common import effector_weights_ui
def cloth_panel_enabled(md):
return md.point_cache.baked is False
class CLOTH_MT_presets(bpy.types.Menu):
'''
@@ -83,10 +83,10 @@ class PHYSICS_PT_cloth(PhysicButtonsPanel):
if md:
cloth = md.settings
layout.active = cloth_panel_enabled(md)
split = layout.split()
split.active = cloth_panel_enabled(md)
col = split.column()
col.label(text="Presets:")
@@ -227,7 +227,7 @@ class PHYSICS_PT_cloth_field_weights(PhysicButtonsPanel):
def draw(self, context):
cloth = context.cloth.settings
effector_weights_ui(self, context, cloth.effector_weights)
bpy.types.register(CLOTH_MT_presets)
bpy.types.register(PHYSICS_PT_cloth)

View File

@@ -30,7 +30,7 @@ def point_cache_ui(self, context, cache, enabled, particles, smoke):
row = layout.row()
row.template_list(cache, "point_cache_list", cache, "active_point_cache_index", rows=2)
col = row.column(align=True)
col.operator("ptcache.add_new", icon='ICON_ZOOMIN', text="")
col.operator("ptcache.add", icon='ICON_ZOOMIN', text="")
col.operator("ptcache.remove", icon='ICON_ZOOMOUT', text="")
row = layout.row()

View File

@@ -344,6 +344,10 @@ class RENDER_PT_output(RenderButtonsPanel):
split = layout.split()
split.prop(rd, "tiff_bit")
elif rd.file_format == 'QUICKTIME_CARBON':
split = layout.split()
split.operator("scene.render_data_set_quicktime_codec")
elif rd.file_format == 'QUICKTIME_QTKIT':
split = layout.split()
col = split.column()
@@ -401,8 +405,8 @@ class RENDER_PT_encoding(RenderButtonsPanel):
col.label(text="Mux:")
col.prop(rd, "ffmpeg_muxrate", text="Rate")
col.prop(rd, "ffmpeg_packetsize", text="Packet Size")
# Audio:
# Audio:
layout.prop(rd, "ffmpeg_multiplex_audio", text="Audio")
sub = layout.column()

View File

@@ -219,10 +219,12 @@ class WORLD_PT_ambient_occlusion(WorldButtonsPanel):
col = split.column()
col.prop(ao, "energy")
col.prop(ao, "indirect_energy")
if wide_ui:
col = split.column()
col.prop(ao, "color")
col.prop(ao, "indirect_bounces")
bpy.types.register(WORLD_PT_context_world)
bpy.types.register(WORLD_PT_preview)

View File

@@ -71,6 +71,11 @@ class CONSOLE_MT_console(bpy.types.Menu):
layout.menu("CONSOLE_MT_language")
layout.itemM("CONSOLE_MT_language")
layout.separator()
layout.operator("screen.area_dupli")
layout.operator("screen.screen_full_area")
class CONSOLE_MT_report(bpy.types.Menu):
bl_label = "Report"

View File

@@ -0,0 +1,192 @@
# ##### 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>
import bpy
class GRAPH_HT_header(bpy.types.Header):
bl_space_type = 'GRAPH_EDITOR'
def draw(self, context):
layout = self.layout
st = context.space_data
row = layout.row(align=True)
row.template_header()
if context.area.show_menus:
sub = row.row(align=True)
sub.menu("GRAPH_MT_view")
sub.menu("GRAPH_MT_select")
sub.menu("GRAPH_MT_channel")
sub.menu("GRAPH_MT_key")
layout.prop(st, "mode", text="")
layout.template_dopesheet_filter(st.dopesheet)
layout.prop(st, "autosnap", text="")
layout.prop(st, "pivot_point", text="", icon_only=True)
row = layout.row(align=True)
row.operator("graph.copy", text="", icon='ICON_COPYDOWN')
row.operator("graph.paste", text="", icon='ICON_PASTEDOWN')
row = layout.row(align=True)
# these likely need new icons
row.operator("graph.ghost_curves_create", text="", icon='ICON_GHOST_ENABLED')
row.operator("graph.ghost_curves_clear", text="", icon='ICON_GHOST_DISABLED')
class GRAPH_MT_view(bpy.types.Menu):
bl_label = "View"
def draw(self, context):
layout = self.layout
st = context.space_data
layout.column()
layout.separator()
layout.operator("graph.properties")
layout.prop(st, "show_cframe_indicator")
layout.prop(st, "show_cursor")
layout.prop(st, "show_sliders")
layout.prop(st, "automerge_keyframes")
layout.separator()
layout.operator("graph.handles_view_toggle")
layout.prop(st, "only_selected_curves_handles")
layout.prop(st, "only_selected_keyframe_handles")
layout.operator("anim.time_toggle")
layout.separator()
layout.operator("anim.previewrange_set")
layout.operator("anim.previewrange_clear")
layout.operator("graph.previewrange_set")
layout.separator()
layout.operator("graph.frame_jump")
layout.operator("graph.view_all")
layout.separator()
layout.operator("screen.area_dupli")
layout.operator("screen.screen_full_area")
class GRAPH_MT_select(bpy.types.Menu):
bl_label = "Select"
def draw(self, context):
layout = self.layout
layout.column()
# This is a bit misleading as the operator's default text is "Select All" while it actually *toggles* All/None
layout.operator("graph.select_all_toggle")
layout.operator("graph.select_all_toggle", text="Invert Selection").invert = True
layout.separator()
layout.operator("graph.select_border")
layout.operator("graph.select_border", text="Border Axis Range").axis_range = True
layout.separator()
layout.operator("graph.select_column", text="Columns on Selected Keys").mode = 'KEYS'
layout.operator("graph.select_column", text="Column on Current Frame").mode = 'CFRA'
layout.operator("graph.select_column", text="Columns on Selected Markers").mode = 'MARKERS_COLUMN'
layout.operator("graph.select_column", text="Between Selected Markers").mode = 'MARKERS_BETWEEN'
class GRAPH_MT_channel(bpy.types.Menu):
bl_label = "Channel"
def draw(self, context):
layout = self.layout
layout.column()
layout.operator("anim.channels_setting_toggle")
layout.operator("anim.channels_setting_enable")
layout.operator("anim.channels_setting_disable")
layout.separator()
layout.operator("anim.channels_editable_toggle")
layout.separator()
layout.operator("anim.channels_expand")
layout.operator("anim.channels_collapse")
class GRAPH_MT_key(bpy.types.Menu):
bl_label = "Key"
def draw(self, context):
layout = self.layout
layout.column()
layout.menu("GRAPH_MT_key_transform", text="Transform")
layout.operator_menu_enum("graph.snap", property="type", text="Snap")
layout.operator_menu_enum("graph.mirror", property="type", text="Mirror")
layout.separator()
layout.operator("graph.keyframe_insert")
layout.operator("graph.fmodifier_add")
layout.separator()
layout.operator("graph.duplicate")
layout.operator("graph.delete")
layout.separator()
layout.operator_menu_enum("graph.handle_type", property="type", text="Handle Type")
layout.operator_menu_enum("graph.interpolation_type", property="type", text="Interpolation Mode")
layout.operator_menu_enum("graph.extrapolation_type", property="type", text="Extrapolation Mode")
layout.separator()
layout.operator("graph.clean")
layout.operator("graph.sample")
layout.operator("graph.bake")
layout.separator()
layout.operator("graph.copy")
layout.operator("graph.paste")
class GRAPH_MT_key_transform(bpy.types.Menu):
bl_label = "Transform"
def draw(self, context):
layout = self.layout
layout.column()
layout.operator("tfm.translate", text="Grab/Move")
layout.operator("tfm.transform", text="Extend").mode = 'TIME_EXTEND'
layout.operator("tfm.rotate", text="Rotate")
layout.operator("tfm.resize", text="Scale")
bpy.types.register(GRAPH_HT_header) # header/menu classes
bpy.types.register(GRAPH_MT_view)
bpy.types.register(GRAPH_MT_select)
bpy.types.register(GRAPH_MT_channel)
bpy.types.register(GRAPH_MT_key)
bpy.types.register(GRAPH_MT_key_transform)

View File

@@ -61,6 +61,10 @@ class IMAGE_MT_view(bpy.types.Menu):
layout.operator("image.view_selected")
layout.operator("image.view_all")
layout.separator()
layout.operator("screen.area_dupli")
layout.operator("screen.screen_full_area")
@@ -75,7 +79,7 @@ class IMAGE_MT_select(bpy.types.Menu):
layout.separator()
layout.operator("uv.select_all_toggle")
layout.operator("uv.select_all")
layout.operator("uv.select_inverse")
layout.operator("uv.unlink_selection")
@@ -151,6 +155,23 @@ class IMAGE_MT_uvs_transform(bpy.types.Menu):
layout.operator("tfm.resize")
class IMAGE_MT_uvs_snap(bpy.types.Menu):
bl_label = "Snap"
def draw(self, context):
layout = self.layout
layout.operator_context = 'EXEC_REGION_WIN'
layout.operator("uv.snap_selection", text="Selected to Pixels").target = 'PIXELS'
layout.operator("uv.snap_selection", text="Selected to Cursor").target = 'CURSOR'
layout.operator("uv.snap_selection", text="Selected to Adjacent Unselected").target = 'ADJACENT_UNSELECTED'
layout.separator()
layout.operator("uv.snap_cursor", text="Cursor to Pixels").target = 'PIXELS'
layout.operator("uv.snap_cursor", text="Cursor to Selection").target = 'SELECTION'
class IMAGE_MT_uvs_mirror(bpy.types.Menu):
bl_label = "Mirror"
@@ -203,6 +224,7 @@ class IMAGE_MT_uvs(bpy.types.Menu):
layout.menu("IMAGE_MT_uvs_transform")
layout.menu("IMAGE_MT_uvs_mirror")
layout.menu("IMAGE_MT_uvs_snap")
layout.menu("IMAGE_MT_uvs_weldalign")
layout.separator()
@@ -520,6 +542,7 @@ bpy.types.register(IMAGE_MT_select)
bpy.types.register(IMAGE_MT_image)
bpy.types.register(IMAGE_MT_uvs_showhide)
bpy.types.register(IMAGE_MT_uvs_transform)
bpy.types.register(IMAGE_MT_uvs_snap)
bpy.types.register(IMAGE_MT_uvs_mirror)
bpy.types.register(IMAGE_MT_uvs_weldalign)
bpy.types.register(IMAGE_MT_uvs)

View File

@@ -83,12 +83,18 @@ class INFO_MT_file(bpy.types.Menu):
layout.operator("wm.save_mainfile", text="Save", icon='ICON_FILE_TICK')
layout.operator_context = 'INVOKE_AREA'
layout.operator("wm.save_as_mainfile", text="Save As...")
layout.operator("screen.userpref_show", text="User Preferences...", icon='ICON_PREFERENCES')
layout.separator()
layout.operator("screen.userpref_show", text="User Preferences...", icon='ICON_PREFERENCES')
layout.operator("wm.read_homefile", text="Load Factory Settings").factory = True
layout.separator()
layout.operator_context = 'INVOKE_AREA'
layout.operator("wm.link_append", text="Link")
layout.operator("wm.link_append", text="Append").link = False
layout.separator()
layout.menu("INFO_MT_file_import")
@@ -179,7 +185,7 @@ class INFO_MT_add(bpy.types.Menu):
layout.operator_context = 'EXEC_SCREEN'
# layout.operator_menu_enum("object.mesh_add", "type", text="Mesh", icon='ICON_OUTLINER_OB_MESH')
#layout.operator_menu_enum("object.mesh_add", "type", text="Mesh", icon='ICON_OUTLINER_OB_MESH')
layout.menu("INFO_MT_mesh_add", icon='ICON_OUTLINER_OB_MESH')
layout.operator_menu_enum("object.curve_add", "type", text="Curve", icon='ICON_OUTLINER_OB_CURVE')
@@ -189,7 +195,7 @@ class INFO_MT_add(bpy.types.Menu):
layout.separator()
layout.operator_context = 'INVOKE_SCREEN'
layout.operator_context = 'INVOKE_REGION_WIN'
layout.operator("object.armature_add", text="Armature", icon='ICON_OUTLINER_OB_ARMATURE')
layout.operator("object.add", text="Lattice", icon='ICON_OUTLINER_OB_LATTICE').type = 'LATTICE'

View File

@@ -82,6 +82,10 @@ class NODE_MT_view(bpy.types.Menu):
layout.separator()
layout.operator("node.view_all")
layout.separator()
layout.operator("screen.area_dupli")
layout.operator("screen.screen_full_area")

View File

@@ -53,8 +53,8 @@ class OUTLINER_HT_header(bpy.types.Header):
row.prop_object(scene, "active_keying_set", scene, "keying_sets", text="")
row = layout.row(align=True)
row.operator("anim.insert_keyframe", text="", icon='ICON_KEY_HLT')
row.operator("anim.delete_keyframe", text="", icon='ICON_KEY_DEHLT')
row.operator("anim.keyframe_insert", text="", icon='ICON_KEY_HLT')
row.operator("anim.keyframe_delete", text="", icon='ICON_KEY_DEHLT')
else:
row = layout.row(align=False)
row.label(text="No Keying Set active")
@@ -77,6 +77,11 @@ class OUTLINER_MT_view(bpy.types.Menu):
col.operator("outliner.show_one_level")
col.operator("outliner.show_hierarchy")
layout.separator()
layout.operator("screen.area_dupli")
layout.operator("screen.screen_full_area")
class OUTLINER_MT_edit_datablocks(bpy.types.Menu):
bl_label = "Edit"

View File

@@ -100,19 +100,6 @@ class SEQUENCER_MT_view(bpy.types.Menu):
layout.separator()
layout.operator("sequencer.view_all")
layout.operator("sequencer.view_selected")
layout.separator()
layout.operator("screen.screen_full_area", text="Toggle Full Screen")
"""
/* Lock Time */
uiDefIconTextBut(block, BUTM, 1, (v2d->flag & V2D_VIEWSYNC_SCREEN_TIME)?ICON_CHECKBOX_HLT:ICON_CHECKBOX_DEHLT,
"Lock Time to Other Windows|", 0, yco-=20,
menuwidth, 19, NULL, 0.0, 0.0, 1, 5, "");
/* Draw time or frames.*/
uiDefMenuSep(block);
"""
layout.prop(st, "draw_frames")
layout.prop(st, "show_cframe_indicator")
@@ -121,11 +108,10 @@ class SEQUENCER_MT_view(bpy.types.Menu):
if st.display_mode == 'WAVEFORM':
layout.prop(st, "separate_color_preview")
"""
if(!sa->full) uiDefIconTextBut(block, BUTM, B_FULL, ICON_BLANK1, "Maximize Window|Ctrl UpArrow", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0,0, "");
else uiDefIconTextBut(block, BUTM, B_FULL, ICON_BLANK1, "Tile Window|Ctrl DownArrow", 0, yco-=20, menuwidth, 19, NULL, 0.0, 0.0, 0, 0, "");
layout.separator()
"""
layout.operator("screen.area_dupli")
layout.operator("screen.screen_full_area")
class SEQUENCER_MT_select(bpy.types.Menu):
@@ -351,7 +337,10 @@ class SEQUENCER_PT_effect(SequencerButtonsPanel):
if not strip:
return False
return strip.type in ('COLOR', 'WIPE', 'GLOW', 'SPEED', 'TRANSFORM')
return strip.type in ('ADD', 'SUBTRACT', 'ALPHA_OVER', 'ALPHA_UNDER',
'GAMMA_CROSS', 'MULTIPLY', 'OVER_DROP',
'PLUGIN',
'WIPE', 'GLOW', 'TRANSFORM', 'COLOR', 'SPEED')
def draw(self, context):
layout = self.layout
@@ -431,7 +420,9 @@ class SEQUENCER_PT_effect(SequencerButtonsPanel):
if strip.type == 'SPEED':
col.prop(strip, "speed_fader", text="Speed fader")
else:
col.prop(strip, "effect_fader", text="Effect fader")
col.prop(strip, "use_effect_default_fade", "Default fade")
if not strip.use_effect_default_fade:
col.prop(strip, "effect_fader", text="Effect fader")
class SEQUENCER_PT_input(SequencerButtonsPanel):
@@ -522,7 +513,7 @@ class SEQUENCER_PT_sound(SequencerButtonsPanel):
row.prop(strip.sound, "caching")
layout.prop(strip, "volume")
class SEQUENCER_PT_scene(SequencerButtonsPanel):
bl_label = "Scene"
@@ -541,7 +532,7 @@ class SEQUENCER_PT_scene(SequencerButtonsPanel):
layout = self.layout
strip = act_strip(context)
layout.template_ID(strip, "scene")

View File

@@ -160,6 +160,11 @@ class TEXT_MT_text(bpy.types.Menu):
layout.menu("TEXT_MT_templates")
layout.separator()
layout.operator("screen.area_dupli")
layout.operator("screen.screen_full_area")
class TEXT_MT_templates(bpy.types.Menu):
'''
@@ -168,7 +173,6 @@ class TEXT_MT_templates(bpy.types.Menu):
bl_label = "Script Templates"
def draw(self, context):
import os
self.path_menu(bpy.utils.script_paths("templates"), "text.open")

View File

@@ -72,14 +72,14 @@ class TIME_HT_header(bpy.types.Header):
subsub = row.row()
subsub.prop(tools, "record_with_nla", toggle=True)
layout.prop(scene, "sync_audio", text="", toggle=True, icon='ICON_SPEAKER')
layout.prop(scene, "sync_audio", text="Realtime", toggle=True, icon='ICON_SPEAKER')
layout.separator()
row = layout.row(align=True)
row.prop_object(scene, "active_keying_set", scene, "keying_sets", text="")
row.operator("anim.insert_keyframe", text="", icon='ICON_KEY_HLT')
row.operator("anim.delete_keyframe", text="", icon='ICON_KEY_DEHLT')
row.operator("anim.keyframe_insert", text="", icon='ICON_KEY_HLT')
row.operator("anim.keyframe_delete", text="", icon='ICON_KEY_DEHLT')
class TIME_MT_view(bpy.types.Menu):
@@ -91,6 +91,7 @@ class TIME_MT_view(bpy.types.Menu):
st = context.space_data
layout.operator("anim.time_toggle")
layout.operator("time.view_all")
layout.separator()
@@ -145,7 +146,7 @@ class TIME_MT_playback(bpy.types.Menu):
layout.separator()
layout.prop(scene, "sync_audio", icon='ICON_SPEAKER')
layout.prop(scene, "sync_audio", text="Realtime Playback", icon='ICON_SPEAKER')
layout.prop(scene, "mute_audio")
layout.prop(scene, "scrub_audio")

View File

@@ -88,7 +88,7 @@ class USERPREF_PT_interface(bpy.types.Panel):
sub1.prop(view, "show_playback_fps", text="Playback FPS")
sub1.prop(view, "global_scene")
sub1.prop(view, "pin_floating_panels")
sub1.prop(view, "object_center_size")
sub1.prop(view, "object_origin_size")
sub1.separator()
sub1.separator()
sub1.separator()
@@ -119,11 +119,11 @@ class USERPREF_PT_interface(bpy.types.Panel):
sub1 = sub.column()
#Toolbox doesn't exist yet
# sub1.label(text="Toolbox:")
# sub1.prop(view, "use_column_layout")
# sub1.label(text="Open Toolbox Delay:")
# sub1.prop(view, "open_left_mouse_delay", text="Hold LMB")
# sub1.prop(view, "open_right_mouse_delay", text="Hold RMB")
# sub1.label(text="Toolbox:")
# sub1.prop(view, "use_column_layout")
# sub1.label(text="Open Toolbox Delay:")
# sub1.prop(view, "open_left_mouse_delay", text="Hold LMB")
# sub1.prop(view, "open_right_mouse_delay", text="Hold RMB")
#manipulator
sub1.prop(view, "use_manipulator")
@@ -244,7 +244,7 @@ class USERPREF_PT_edit(bpy.types.Panel):
sub1.prop(edit, "duplicate_lamp", text="Lamp")
sub1.prop(edit, "duplicate_material", text="Material")
sub1.prop(edit, "duplicate_texture", text="Texture")
sub1.prop(edit, "duplicate_ipo", text="F-Curve")
sub1.prop(edit, "duplicate_fcurve", text="F-Curve")
sub1.prop(edit, "duplicate_action", text="Action")
sub1.prop(edit, "duplicate_particle", text="Particle")
@@ -264,6 +264,9 @@ class USERPREF_PT_system(bpy.types.Panel):
userpref = context.user_preferences
system = userpref.system
lamp0 = system.solid_lights[0]
lamp1 = system.solid_lights[1]
lamp2 = system.solid_lights[2]
split = layout.split()
@@ -321,6 +324,34 @@ class USERPREF_PT_system(bpy.types.Panel):
sub1 = sub.column()
sub1.label(text="Solid OpenGL lights:")
sub2 = sub1.split()
col = sub2.column()
col.prop(lamp0, "enabled")
sub = col.column()
sub.active = lamp0.enabled
sub.prop(lamp0, "diffuse_color")
sub.prop(lamp0, "specular_color")
sub.prop(lamp0, "direction")
col = sub2.column()
col.prop(lamp1, "enabled")
sub = col.column()
sub.active = lamp1.enabled
sub.prop(lamp1, "diffuse_color")
sub.prop(lamp1, "specular_color")
sub.prop(lamp1, "direction")
col = sub2.column()
col.prop(lamp2, "enabled")
sub = col.column()
sub.active = lamp2.enabled
sub.prop(lamp2, "diffuse_color")
sub.prop(lamp2, "specular_color")
sub.prop(lamp2, "direction")
sub1.label(text="OpenGL:")
sub1.prop(system, "clip_alpha", slider=True)
sub1.prop(system, "use_mipmaps")
@@ -1136,6 +1167,9 @@ class USERPREF_PT_input(bpy.types.Panel):
sub.label(text="Zoom Style:")
sub.row().prop(inputs, "viewport_zoom_style", expand=True)
if inputs.viewport_zoom_style == 'DOLLY':
sub.row().prop(inputs, "zoom_axis", expand=True)
sub.prop(inputs, "invert_zoom_direction")
#sub.prop(inputs, "use_middle_mouse_paste")
@@ -1143,7 +1177,6 @@ class USERPREF_PT_input(bpy.types.Panel):
#sub = col.column()
#sub.label(text="Mouse Wheel:")
#sub.prop(view, "wheel_invert_zoom", text="Invert Zoom")
#sub.prop(view, "wheel_scroll_lines", text="Scroll Lines")
col.separator()
@@ -1382,9 +1415,9 @@ class WM_OT_keymap_edit(bpy.types.Operator):
class WM_OT_keymap_restore(bpy.types.Operator):
"Restore key map"
"Restore key map(s)."
bl_idname = "wm.keymap_restore"
bl_label = "Restore Key Map"
bl_label = "Restore Key Map(s)"
all = BoolProperty(attr="all", name="All Keymaps", description="Restore all keymaps to default.")

View File

@@ -27,17 +27,19 @@ class VIEW3D_HT_header(bpy.types.Header):
def draw(self, context):
layout = self.layout
# view = context.space_data
view = context.space_data
mode_string = context.mode
edit_object = context.edit_object
object = context.active_object
obj = context.active_object
toolsettings = context.scene.tool_settings
row = layout.row(align=True)
row = layout.row()
row.template_header()
sub = row.row(align=True)
# Menus
if context.area.show_menus:
sub = row.row(align=True)
sub.menu("VIEW3D_MT_view")
@@ -47,13 +49,56 @@ class VIEW3D_HT_header(bpy.types.Header):
if edit_object:
sub.menu("VIEW3D_MT_edit_%s" % edit_object.type.lower())
elif object:
if mode_string not in ['PAINT_WEIGHT', 'PAINT_TEXTURE']:
elif obj:
if mode_string not in ('PAINT_WEIGHT'):
sub.menu("VIEW3D_MT_%s" % mode_string.lower())
else:
sub.menu("VIEW3D_MT_object")
layout.template_header_3D()
row.template_header_3D()
# Particle edit
if obj and obj.mode == 'PARTICLE_EDIT':
row.prop(toolsettings.particle_edit, "selection_mode", text="", expand=True, toggle=True)
# Occlude geometry
if obj and view.viewport_shading in ('SOLID', 'SHADED', 'TEXTURED') and (obj.mode == 'PARTICLE_EDIT' or (obj.mode == 'EDIT' and obj.type == 'MESH')):
row.prop(view, "occlude_geometry", text="")
# Proportional editing
if obj and obj.mode in ('OBJECT', 'EDIT'):
row = layout.row(align=True)
row.prop(toolsettings, "proportional_editing", text="", icon_only=True)
if toolsettings.proportional_editing != 'DISABLED':
row.prop(toolsettings, "proportional_editing_falloff", text="", icon_only=True)
# Snap
row = layout.row(align=True)
row.prop(toolsettings, "snap", text="")
row.prop(toolsettings, "snap_element", text="", icon_only=True)
if toolsettings.snap_element != 'INCREMENT':
row.prop(toolsettings, "snap_target", text="")
if obj and obj.mode == 'OBJECT':
row.prop(toolsettings, "snap_align_rotation", text="")
if toolsettings.snap_element == 'VOLUME':
row.prop(toolsettings, "snap_peel_object", text="")
elif toolsettings.snap_element == 'FACE':
row.prop(toolsettings, "snap_project", text="")
# OpenGL render
row = layout.row(align=True)
row.operator("screen.opengl_render", text="", icon='ICON_RENDER_STILL')
props = row.operator("screen.opengl_render", text="", icon='ICON_RENDER_ANIMATION')
props.animation = True
# Pose
if obj and obj.mode == 'POSE':
row = layout.row(align=True)
row.operator("pose.copy", text="", icon='ICON_COPYDOWN')
row.operator("pose.paste", text="", icon='ICON_PASTEDOWN')
props = row.operator("pose.paste", text="", icon='ICON_PASTEFLIPDOWN')
props.flipped = 1
# ********** Menu **********
@@ -78,16 +123,16 @@ class VIEW3D_MT_transform(bpy.types.Menu):
# TODO: get rid of the custom text strings?
def draw(self, context):
layout = self.layout
layout.operator("tfm.translate", text="Grab/Move")
# TODO: sub-menu for grab per axis
layout.operator("tfm.rotate", text="Rotate")
# TODO: sub-menu for rot per axis
layout.operator("tfm.resize", text="Scale")
# TODO: sub-menu for scale per axis
layout.separator()
layout.operator("tfm.tosphere", text="To Sphere")
layout.operator("tfm.shear", text="Shear")
layout.operator("tfm.warp", text="Warp")
@@ -95,17 +140,18 @@ class VIEW3D_MT_transform(bpy.types.Menu):
if context.edit_object and context.edit_object.type == 'ARMATURE':
layout.operator("armature.align")
else:
layout.operator_context = 'EXEC_AREA'
layout.operator_context = 'EXEC_REGION_WIN'
layout.operator("tfm.transform", text="Align to Transform Orientation").mode = 'ALIGN' # XXX see alignmenu() in edit.c of b2.4x to get this working
layout.separator()
layout.operator_context = 'EXEC_AREA'
layout.operator("object.center_set").type = 'CENTER'
layout.operator("object.center_set").type = 'CENTER_NEW'
layout.operator("object.center_set").type = 'CENTER_CURSOR'
layout.operator("object.origin_set", text="Geometry to Origin").type = 'GEOMETRY_ORIGIN'
layout.operator("object.origin_set", text="Origin to Geometry").type = 'ORIGIN_GEOMETRY'
layout.operator("object.origin_set", text="Origin to 3D Cursor").type = 'ORIGIN_CURSOR'
class VIEW3D_MT_mirror(bpy.types.Menu):
bl_label = "Mirror"
@@ -113,11 +159,11 @@ class VIEW3D_MT_mirror(bpy.types.Menu):
layout = self.layout
layout.operator("tfm.mirror", text="Interactive Mirror")
layout.separator()
layout.operator_context = 'EXEC_AREA'
layout.operator_context = 'INVOKE_REGION_WIN'
props = layout.operator("tfm.mirror", text="X Global")
props.constraint_axis = (True, False, False)
props.constraint_orientation = 'GLOBAL'
@@ -127,10 +173,10 @@ class VIEW3D_MT_mirror(bpy.types.Menu):
props = layout.operator("tfm.mirror", text="Z Global")
props.constraint_axis = (False, False, True)
props.constraint_orientation = 'GLOBAL'
if context.edit_object:
layout.separator()
props = layout.operator("tfm.mirror", text="X Local")
props.constraint_axis = (True, False, False)
props.constraint_orientation = 'LOCAL'
@@ -140,7 +186,8 @@ class VIEW3D_MT_mirror(bpy.types.Menu):
props = layout.operator("tfm.mirror", text="Z Local")
props.constraint_axis = (False, False, True)
props.constraint_orientation = 'LOCAL'
class VIEW3D_MT_snap(bpy.types.Menu):
bl_label = "Snap"
@@ -149,7 +196,7 @@ class VIEW3D_MT_snap(bpy.types.Menu):
layout.operator("view3d.snap_selected_to_grid", text="Selection to Grid")
layout.operator("view3d.snap_selected_to_cursor", text="Selection to Cursor")
layout.operator("view3d.snap_selected_to_center", text="Selection to Center")
layout.operator("view3d.snap_selected_to_center", text="Selection to Origin")
layout.separator()
@@ -188,10 +235,10 @@ class VIEW3D_MT_view(bpy.types.Menu):
layout.separator()
layout.operator("view3d.viewnumpad").type = 'CAMERA'
layout.operator("view3d.viewnumpad").type = 'TOP'
layout.operator("view3d.viewnumpad").type = 'FRONT'
layout.operator("view3d.viewnumpad").type = 'RIGHT'
layout.operator("view3d.viewnumpad", text="Camera").type = 'CAMERA'
layout.operator("view3d.viewnumpad", text="Top").type = 'TOP'
layout.operator("view3d.viewnumpad", text="Front").type = 'FRONT'
layout.operator("view3d.viewnumpad", text="Right").type = 'RIGHT'
layout.menu("VIEW3D_MT_view_cameras", text="Cameras")
@@ -223,12 +270,13 @@ class VIEW3D_MT_view(bpy.types.Menu):
layout.separator()
layout.operator("screen.region_foursplit", text="Toggle Quad View")
layout.operator("screen.screen_full_area", text="Toggle Full Screen")
layout.operator("screen.animation_play", text="Playback Animation")
layout.separator()
layout.operator("screen.animation_play", text="Playback Animation", icon='ICON_PLAY')
layout.operator("screen.area_dupli")
layout.operator("screen.region_quadview")
layout.operator("screen.screen_full_area")
class VIEW3D_MT_view_navigation(bpy.types.Menu):
@@ -318,7 +366,7 @@ class VIEW3D_MT_select_object(bpy.types.Menu):
layout.separator()
layout.operator("object.select_all_toggle", text="Select/Deselect All")
layout.operator("object.select_all", text="Select/Deselect All")
layout.operator("object.select_inverse", text="Inverse")
layout.operator("object.select_random", text="Random")
layout.operator("object.select_mirror", text="Mirror")
@@ -342,15 +390,15 @@ class VIEW3D_MT_select_pose(bpy.types.Menu):
layout.separator()
layout.operator("pose.select_all_toggle", text="Select/Deselect All")
layout.operator("pose.select_all", text="Select/Deselect All")
layout.operator("pose.select_inverse", text="Inverse")
layout.operator("pose.select_constraint_target", text="Constraint Target")
layout.operator("pose.select_linked", text="Linked")
layout.separator()
layout.operator("pose.select_hierarchy").direction = 'PARENT'
layout.operator("pose.select_hierarchy").direction = 'CHILD'
layout.operator("pose.select_hierarchy", text="Parent").direction = 'PARENT'
layout.operator("pose.select_hierarchy", text="Child").direction = 'CHILD'
layout.separator()
@@ -362,6 +410,8 @@ class VIEW3D_MT_select_pose(bpy.types.Menu):
props.extend = True
props.direction = 'CHILD'
layout.operator("object.select_pattern", text="Select Pattern...")
class VIEW3D_MT_select_particle(bpy.types.Menu):
bl_label = "Select"
@@ -373,7 +423,7 @@ class VIEW3D_MT_select_particle(bpy.types.Menu):
layout.separator()
layout.operator("particle.select_all_toggle", text="Select/Deselect All")
layout.operator("particle.select_all", text="Select/Deselect All")
layout.operator("particle.select_linked")
layout.operator("particle.select_inverse")
@@ -399,7 +449,7 @@ class VIEW3D_MT_select_edit_mesh(bpy.types.Menu):
layout.separator()
layout.operator("mesh.select_all_toggle", text="Select/Deselect All")
layout.operator("mesh.select_all", text="Select/Deselect All")
layout.operator("mesh.select_inverse", text="Inverse")
layout.separator()
@@ -448,7 +498,7 @@ class VIEW3D_MT_select_edit_curve(bpy.types.Menu):
layout.separator()
layout.operator("curve.select_all_toggle", text="Select/Deselect All")
layout.operator("curve.select_all", text="Select/Deselect All")
layout.operator("curve.select_inverse")
layout.operator("curve.select_random")
layout.operator("curve.select_every_nth")
@@ -477,7 +527,7 @@ class VIEW3D_MT_select_edit_surface(bpy.types.Menu):
layout.separator()
layout.operator("curve.select_all_toggle", text="Select/Deselect All")
layout.operator("curve.select_all", text="Select/Deselect All")
layout.operator("curve.select_inverse")
layout.operator("curve.select_random")
layout.operator("curve.select_every_nth")
@@ -520,7 +570,7 @@ class VIEW3D_MT_select_edit_lattice(bpy.types.Menu):
layout.separator()
layout.operator("lattice.select_all_toggle", text="Select/Deselect All")
layout.operator("lattice.select_all", text="Select/Deselect All")
class VIEW3D_MT_select_edit_armature(bpy.types.Menu):
@@ -534,7 +584,7 @@ class VIEW3D_MT_select_edit_armature(bpy.types.Menu):
layout.separator()
layout.operator("armature.select_all_toggle", text="Select/Deselect All")
layout.operator("armature.select_all", text="Select/Deselect All")
layout.operator("armature.select_inverse", text="Inverse")
layout.separator()
@@ -552,13 +602,15 @@ class VIEW3D_MT_select_edit_armature(bpy.types.Menu):
props.extend = True
props.direction = 'CHILD'
layout.operator("object.select_pattern", text="Select Pattern...")
class VIEW3D_MT_select_face(bpy.types.Menu):# XXX no matching enum
bl_label = "Select"
def draw(self, context):
layout = self.layout
# TODO
# see view3d_select_faceselmenu
@@ -580,8 +632,8 @@ class VIEW3D_MT_object(bpy.types.Menu):
layout.separator()
layout.operator("anim.insert_keyframe_menu", text="Insert Keyframe...")
layout.operator("anim.delete_keyframe_v3d", text="Delete Keyframe...")
layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe...")
layout.operator("anim.keyframe_delete_v3d", text="Delete Keyframe...")
layout.separator()
@@ -592,7 +644,6 @@ class VIEW3D_MT_object(bpy.types.Menu):
layout.menu("VIEW3D_MT_make_links", text="Make Links...")
layout.operator_menu_enum("object.make_local", "type", text="Make Local...")
layout.menu("VIEW3D_MT_make_single_user")
layout.menu("VIEW3D_MT_make_links")
layout.separator()
@@ -603,6 +654,7 @@ class VIEW3D_MT_object(bpy.types.Menu):
layout.separator()
layout.operator("object.join_shapes")
layout.operator("object.join")
layout.separator()
@@ -749,7 +801,7 @@ class VIEW3D_MT_hook(bpy.types.Menu):
layout.operator_context = 'EXEC_AREA'
layout.operator("object.hook_add_newob")
layout.operator("object.hook_add_selob")
if [mod.type == 'HOOK' for mod in context.active_object.modifiers]:
layout.separator()
layout.operator_menu_enum("object.hook_assign", "modifier")
@@ -767,7 +819,7 @@ class VIEW3D_MT_vertex_group(bpy.types.Menu):
layout = self.layout
layout.operator_context = 'EXEC_AREA'
layout.operator("object.vertex_group_assign", text="Assign to New Group").new = True
ob = context.active_object
if ob.mode == 'EDIT':
if ob.vertex_groups and ob.active_vertex_group:
@@ -776,7 +828,7 @@ class VIEW3D_MT_vertex_group(bpy.types.Menu):
layout.operator("object.vertex_group_remove_from", text="Remove from Active Group")
layout.operator("object.vertex_group_remove_from", text="Remove from All").all = True
layout.separator()
if ob.vertex_groups and ob.active_vertex_group:
layout.operator_menu_enum("object.vertex_group_set_active", "group", text="Set Active Group")
layout.operator("object.vertex_group_remove", text="Remove Active Group")
@@ -862,7 +914,7 @@ class VIEW3D_MT_pose(bpy.types.Menu):
layout = self.layout
arm = context.active_object.data
layout.menu("VIEW3D_MT_transform")
layout.menu("VIEW3D_MT_snap")
if arm.drawtype in ('BBONE', 'ENVELOPE'):
@@ -872,8 +924,8 @@ class VIEW3D_MT_pose(bpy.types.Menu):
layout.separator()
layout.operator("anim.insert_keyframe_menu", text="Insert Keyframe...")
layout.operator("anim.delete_keyframe_v3d", text="Delete Keyframe...")
layout.operator("anim.keyframe_insert_menu", text="Insert Keyframe...")
layout.operator("anim.keyframe_delete_v3d", text="Delete Keyframe...")
layout.separator()
@@ -1202,7 +1254,7 @@ def draw_curve(self, context):
layout = self.layout
settings = context.tool_settings
layout.menu("VIEW3D_MT_transform")
layout.menu("VIEW3D_MT_mirror")
layout.menu("VIEW3D_MT_snap")
@@ -1338,7 +1390,7 @@ class VIEW3D_MT_edit_meta(bpy.types.Menu):
layout.operator("ed.redo")
layout.separator()
layout.menu("VIEW3D_MT_transform")
layout.menu("VIEW3D_MT_mirror")
layout.menu("VIEW3D_MT_snap")
@@ -1376,7 +1428,7 @@ class VIEW3D_MT_edit_lattice(bpy.types.Menu):
layout = self.layout
settings = context.tool_settings
layout.menu("VIEW3D_MT_transform")
layout.menu("VIEW3D_MT_mirror")
layout.menu("VIEW3D_MT_snap")
@@ -1399,7 +1451,7 @@ class VIEW3D_MT_edit_armature(bpy.types.Menu):
edit_object = context.edit_object
arm = edit_object.data
layout.menu("VIEW3D_MT_transform")
layout.menu("VIEW3D_MT_mirror")
layout.menu("VIEW3D_MT_snap")
@@ -1516,18 +1568,16 @@ class VIEW3D_PT_3dview_properties(bpy.types.Panel):
col.label(text="Camera:")
col.prop(view, "camera", text="")
col.prop(view, "lens")
col.label(text="Lock to Object:")
col.prop(view, "lock_object", text="")
if view.lock_object and view.lock_object.type == 'ARMATURE':
col.prop_object(view, "lock_bone", view.lock_object.data, "bones", text="")
col = layout.column(align=True)
col.label(text="Clip:")
col.prop(view, "clip_start", text="Start")
col.prop(view, "clip_end", text="End")
col = layout.column(align=True)
col.label(text="Grid:")
col.prop(view, "grid_lines", text="Lines")
col.prop(view, "grid_spacing", text="Spacing")
col.prop(view, "grid_subdivisions", text="Subdivisions")
layout.column().prop(scene, "cursor_location", text="3D Cursor:")
class VIEW3D_PT_3dview_item(bpy.types.Panel):
@@ -1577,17 +1627,24 @@ class VIEW3D_PT_3dview_display(bpy.types.Panel):
ob = context.object
col = layout.column()
col.prop(view, "display_floor", text="Grid Floor")
col.prop(view, "display_x_axis", text="X Axis")
col.prop(view, "display_y_axis", text="Y Axis")
col.prop(view, "display_z_axis", text="Z Axis")
col.prop(view, "outline_selected")
col.prop(view, "all_object_centers")
col.prop(view, "all_object_origins")
col.prop(view, "relationship_lines")
if ob and ob.type == 'MESH':
mesh = ob.data
col.prop(mesh, "all_edges")
col = layout.column()
col.prop(view, "display_floor", text="Grid Floor")
sub = col.column(align=True)
sub.active = view.display_floor
sub.prop(view, "grid_lines", text="Lines")
sub.prop(view, "grid_spacing", text="Spacing")
sub.prop(view, "grid_subdivisions", text="Subdivisions")
col = layout.column()
col.label(text="Shading:")
col.prop(gs, "material_mode", text="")
@@ -1687,8 +1744,7 @@ class VIEW3D_PT_background_image(bpy.types.Panel):
layout.active = view.display_background_image
col = layout.column()
col.prop(bg, "image", text="")
#col.prop(bg, "image_user")
col.template_ID(bg, "image", open="image.open")
col.prop(bg, "size")
col.prop(bg, "transparency", slider=True)
@@ -1786,7 +1842,7 @@ class VIEW3D_PT_context_properties(bpy.types.Panel):
return "object"
return ""
def poll(self, context):
member = self._active_context_member(context)
if member:
@@ -1805,46 +1861,6 @@ class VIEW3D_PT_context_properties(bpy.types.Panel):
rna_prop_ui.draw(self.layout, context, member, False)
# Operators
from bpy.props import *
class OBJECT_OT_select_pattern(bpy.types.Operator):
'''Select object matching a naming pattern.'''
bl_idname = "object.select_pattern"
bl_label = "Select Pattern"
bl_register = True
bl_undo = True
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.properties.case_sensitive:
pattern_match = fnmatch.fnmatchcase
else:
pattern_match = lambda a, b: fnmatch.fnmatchcase(a.upper(), b.upper())
for ob in context.visible_objects:
if pattern_match(ob.name, self.properties.pattern):
ob.selected = True
elif not self.properties.extend:
ob.selected = False
return ('FINISHED',)
# TODO - python cant do popups yet
'''
def invoke(self, context, event):
wm = context.manager
wm.add_fileselect(self)
return ('RUNNING_MODAL',)
'''
bpy.types.register(VIEW3D_HT_header) # Header
bpy.types.register(VIEW3D_MT_view) #View Menus
@@ -1939,5 +1955,3 @@ bpy.types.register(VIEW3D_PT_transform_orientations)
bpy.types.register(VIEW3D_PT_etch_a_ton)
bpy.types.register(VIEW3D_PT_context_properties)
bpy.ops.add(OBJECT_OT_select_pattern)

View File

@@ -57,8 +57,8 @@ class VIEW3D_PT_tools_objectmode(View3DPanel):
col = layout.column(align=True)
col.label(text="Keyframes:")
col.operator("anim.insert_keyframe_menu", text="Insert")
col.operator("anim.delete_keyframe_v3d", text="Remove")
col.operator("anim.keyframe_insert_menu", text="Insert")
col.operator("anim.keyframe_delete_v3d", text="Remove")
col = layout.column(align=True)
col.label(text="Repeat:")
@@ -101,8 +101,8 @@ class VIEW3D_PT_tools_meshedit(View3DPanel):
col.label(text="Add:")
col.operator("mesh.extrude_move")
col.operator("mesh.subdivide")
col.operator("mesh.loopcut")
col.operator("mesh.duplicate_move")
col.operator("mesh.loopcut_slide")
col.operator("mesh.duplicate_move", text="Duplicate")
col.operator("mesh.spin")
col.operator("mesh.screw")
@@ -171,10 +171,10 @@ class VIEW3D_PT_tools_curveedit(View3DPanel):
col.operator("tfm.translate")
col.operator("tfm.rotate")
col.operator("tfm.resize", text="Scale")
col = layout.column(align=True)
col.operator("tfm.transform").mode = 'TILT'
col.operator("tfm.transform").mode = 'CURVE_SHRINKFATTEN'
col.operator("tfm.transform", text="Tilt").mode = 'TILT'
col.operator("tfm.transform", text="Shrink/Fatten").mode = 'CURVE_SHRINKFATTEN'
col = layout.column(align=True)
col.label(text="Curve:")
@@ -188,9 +188,9 @@ class VIEW3D_PT_tools_curveedit(View3DPanel):
col.label(text="Handles:")
row = col.row()
row.operator("curve.handle_type_set", text="Auto").type = 'AUTOMATIC'
row.operator("curve.handle_type_set").type = 'VECTOR'
row.operator("curve.handle_type_set", text="Vector").type = 'VECTOR'
row = col.row()
row.operator("curve.handle_type_set").type = 'ALIGN'
row.operator("curve.handle_type_set", text="Align").type = 'ALIGN'
row.operator("curve.handle_type_set", text="Free").type = 'FREE_ALIGN'
col = layout.column(align=True)
@@ -273,9 +273,9 @@ class VIEW3D_PT_tools_textedit(View3DPanel):
col = layout.column(align=True)
col.label(text="Style:")
col.operator("font.style_toggle").style = 'BOLD'
col.operator("font.style_toggle").style = 'ITALIC'
col.operator("font.style_toggle").style = 'UNDERLINE'
col.operator("font.style_toggle", text="Bold").style = 'BOLD'
col.operator("font.style_toggle", text="Italic").style = 'ITALIC'
col.operator("font.style_toggle", text="Underline").style = 'UNDERLINE'
col = layout.column(align=True)
col.label(text="Repeat:")
@@ -430,8 +430,8 @@ class VIEW3D_PT_tools_posemode(View3DPanel):
col = layout.column(align=True)
col.label(text="Keyframes:")
col.operator("anim.insert_keyframe_menu", text="Insert")
col.operator("anim.delete_keyframe_v3d", text="Remove")
col.operator("anim.keyframe_insert_menu", text="Insert")
col.operator("anim.keyframe_delete_v3d", text="Remove")
col = layout.column(align=True)
col.label(text="Repeat:")